/*
 * Rhizome webclient theme — the browser client's skin.
 *
 * The webclient page extends the website's _base.html for its chrome (the
 * site nav with the ambience mute toggle), so public.css and rhizome.css are
 * on the page too; everything below the nav is the client's own world. The
 * palette below is a deliberate restatement of the design target in
 * planning/webclient-mockup.html: a single committed dark theme (a game
 * terminal is its own visual world), tuned to sit beside the public site's
 * green-on-humus look rather than to match it token for token. The two
 * sheets share one token name, --line, in near-identical shades; this one
 * loads later and wins, which is invisible.
 *
 * Loaded last in <head> by templates/webclient/webclient.html — after the
 * site sheets, Bootstrap, webclient.css and the GoldenLayout CDN themes — so
 * plain (non-!important) rules win without specificity games.
 */

:root {
  /* Surfaces, darkest to lightest. */
  --ground: #0b0f0c;
  --frame: #080b09;
  --pane: #0f1611;
  --pane-raised: #131c15;
  --line: #1e2a20;
  --line-soft: #16211a;

  /* Type. */
  --text: #c9d2c0;
  --text-dim: #75816f;
  --text-faint: #4d5849;

  /* Accents. Semantic roles, not decoration:
   *   moss  — the game's own voice, affordances, "you"
   *   amber — vigor/stamina and warnings (yellow by genre convention)
   *   teal  — RESERVED for a future mana/arcana role; never stamina
   *   wound — injury and errors
   * Phase 2's status sidebar consumes these same custom properties. */
  --moss: #82c98f;
  --moss-deep: #3e7a4c;
  --amber: #d9a650;
  --teal: #6fb3ab;
  --wound: #c4705f;

  --mono: 'DejaVu Sans Mono', ui-monospace, 'Cascadia Code', 'JetBrains Mono',
          Menlo, Consolas, monospace;
}

/* ── Page shell ──────────────────────────────────────────────────── */

html, body {
  background: var(--ground);
}

body {
  color: var(--text);
  font-family: var(--mono);
  font-size: 0.88rem;
  line-height: 1.6;
}

::selection {
  background: rgba(130, 201, 143, 0.25);
}

/* Keyboard focus must stay visible everywhere — the client is driven almost
 * entirely from the keyboard. */
:focus-visible {
  outline: 1px solid var(--moss);
  outline-offset: 1px;
}

/* _base.html's #page is a min-height:100vh column (nav, content, footer);
 * the client is the content, wants exactly the viewport and never scrolls
 * the page, so the column is pinned to the viewport and the client takes
 * what the nav leaves. */
#page {
  height: 100vh;
  min-height: 0;
}

/* #clientwrapper stacks the toolbar above the GoldenLayout area. The d-none
 * class Bootstrap puts on it wins until the ready handler strips it (its
 * display:none carries !important), so this only takes effect once the
 * client is actually ready to show. */
#clientwrapper {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  /* stock webclient.css pins this to 100%, which would ignore the nav */
  height: auto;
}

#main {
  flex: 1;
  min-height: 0;
  /* stock webclient.css pins this to 100%, which would ignore the toolbar */
  height: auto;
}

/* The HTML-defined input row is emptied by goldenlayout.js (it removes
 * #prompt and #inputcontrol) but the wrapper is left behind, fixed to the
 * bottom of the viewport where it can swallow clicks. */
#input {
  display: none;
}

/* ── Site nav ────────────────────────────────────────────────────── */

/* The nav is the site's (public.css); this sheet only reconciles it with the
 * page it now sits on. Bootstrap's reboot loads after public.css here and
 * underlines every link on hover and would otherwise have the nav inherit
 * the terminal's monospace body font; and the site's padding is cut down
 * vertically, since every pixel of nav is a line of game text lost. */
.site-nav {
  flex: none;
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
  font-family: var(--sans);
}

.site-nav a:hover {
  text-decoration: none;
}

/* The connection pip, slotted into the nav after the brand and driven by
 * the rhizome_topbar plugin. */
.site-nav .conn {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}

.site-nav .conn::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-faint);
}

.site-nav .conn.online::before {
  background: var(--moss);
  box-shadow: 0 0 6px var(--moss);
}

.site-nav .conn.offline {
  color: var(--wound);
}

.site-nav .conn.offline::before {
  background: var(--wound);
}

/* The game date, written by the rhizome_topbar plugin from what the server
 * pushes. The brand's auto margin pushes everything after it to the right,
 * so the clock sits with the links; the span is empty until the first push
 * lands and takes up no room at all until then. */
.site-nav .clock {
  font-size: 0.72rem;
  color: var(--text-dim);
  white-space: nowrap;
}

/* The part of the date that actually moves during a session. */
.site-nav .clock .period {
  color: var(--amber);
}

/* ── GoldenLayout chrome ─────────────────────────────────────────── */

.lm_goldenlayout {
  background: var(--frame);
}

.lm_content {
  background: var(--pane);
}

/* Height must agree with dimensions.headerHeight in
 * js/plugins/goldenlayout_default_config.js — GoldenLayout offsets pane
 * content by the configured number, not by what CSS renders. */
.lm_header {
  height: 24px;
  background: var(--frame);
  border-bottom: 1px solid var(--line);
}

.lm_header .lm_tab {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  background: var(--pane);
  border: 1px solid var(--line-soft);
  border-bottom: none;
  box-shadow: none;
}

.lm_header .lm_tab:hover,
.lm_header .lm_tab.lm_active {
  background: var(--pane-raised);
  color: var(--moss);
  box-shadow: none;
}

/* goldenlayout.js hangs its per-tab controls (rename ▸, type filter ◆,
 * update-method ▸, split +) off the tab as extra .lm_title spans around the
 * real title. They stay available — power users drag and split panes — but
 * read as chrome rather than as part of the pane's name. The attribute
 * selector is what separates them from the pane's own title: the injected
 * spans each carry an inline style, the real title never does. */
.lm_header .lm_tab > .lm_title[style] {
  color: var(--text-faint);
  opacity: 0.55;
}

.lm_header .lm_tab > .lm_title[style]:hover {
  color: var(--moss);
  opacity: 1;
}

.lm_close_tab {
  color: var(--text-faint);
}

.lm_close_tab:hover {
  color: var(--wound);
}

.lm_splitter {
  background: var(--line);
}

.lm_splitter:hover,
.lm_splitter.lm_dragging {
  background: var(--moss-deep);
  opacity: 1;
}

.lm_dropTargetIndicator {
  outline: 1px dashed var(--moss-deep);
  box-shadow: inset 0 0 30px rgba(130, 201, 143, 0.15);
}

/* ── Output panes ────────────────────────────────────────────────── */

/* .content is the scrolling text div inside every non-input pane. */
.content {
  background: var(--pane);
  border: none;
  box-shadow: none;
  padding: 0.9rem 1.1rem;
  scrollbar-width: thin;
  scrollbar-color: var(--line) var(--pane);
}

.content::-webkit-scrollbar {
  width: 9px;
}

.content::-webkit-scrollbar-track {
  background: var(--pane);
}

.content::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 5px;
}

.content::-webkit-scrollbar-thumb:hover {
  background: var(--moss-deep);
}

/* Message classes emitted by the client itself (game text carries its own
 * ANSI colour classes and is left alone). */
.out {
  color: var(--text);
  background-color: transparent;
}

.sys {
  color: var(--moss);
}

.inp {
  color: var(--text-faint);
}

.err {
  color: var(--wound);
}

/* Clickable game text: exits, room and inventory sdescs, disambiguation rows,
 * prompt numbers. Evennia stamps every one of these anchors with the same
 * id="mxplink" (utils/text2html.py sub_mxp_links), duplicate ids and all, and
 * there is no per-link class — so this rule is necessarily the styling for
 * every link the game emits. Per-surface styling would have to come from
 * colour codes wrapped around the label, which nest inside the anchor, rather
 * than from a CSS selector.
 *
 * The colour is inherited on purpose: a linked label often carries its own
 * ANSI colour (an item's quality marker), and the underline alone is enough
 * to read as clickable. */
a#mxplink {
  color: inherit;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--moss-deep);
  text-underline-offset: 2px;
  cursor: pointer;
}

a#mxplink:hover {
  color: var(--moss);
  text-decoration-style: solid;
  text-decoration-color: var(--moss);
}

/* Panes holding a browser-in-browser (the help pane) fill their container. */
.lm_content > iframe {
  border: 0;
  width: 100%;
  height: 100%;
}

/* ── Side panes (status, map) ─────────────────────────────────────── */

/* Both panes the side column carries are built by plugins out of out-of-band
 * payloads, and both read as a stack of small titled sections. The shell and
 * the section label are therefore shared; everything below this block is one
 * pane's own furniture. */

.statuspane,
.mappane {
  height: 100%;
  overflow-y: auto;
  background: var(--pane);
  padding: 0.75rem 0.9rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  scrollbar-width: thin;
  scrollbar-color: var(--line) var(--pane);
}

.statuspane .side-label,
.mappane .side-label {
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 0.5rem;
}

/* ── Status pane ─────────────────────────────────────────────────── */

/* The character's condition, built by js/plugins/rhizome_status.js from the
 * out-of-band payload world/status_feed.py pushes. Nothing here ever prints a
 * number: the orb fills are drawn to a fraction, and the word below each orb
 * is the readout. Colour roles are fixed by design decision -- vitality is
 * moss/green, vigor is amber/yellow, and teal stays reserved for a future
 * mana orb. */

.statuspane .char-name {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
  margin: 0;
}

.statuspane .char-sdesc {
  font-size: 0.72rem;
  color: var(--text-dim);
  margin: 0.15rem 0 0;
}

/* Orbs. */

.statuspane .orbs {
  display: flex;
  justify-content: space-around;
  gap: 0.5rem;
}

.statuspane .orb-block {
  text-align: center;
}

.statuspane .orb {
  position: relative;
  width: 70px;
  height: 70px;
  border-radius: 50%;
  background: var(--frame);
  border: 1px solid var(--line);
  overflow: hidden;
  margin: 0 auto;
  box-shadow: inset 0 6px 14px rgba(0, 0, 0, 0.7);
}

/* Wider than the well and hung off its left edge, so the rounded top reads as
 * a liquid surface rather than a bar. Height is set from the payload. */
.statuspane .orb .fill {
  position: absolute;
  left: -10%;
  width: 120%;
  bottom: 0;
  height: 0;
  border-radius: 45% 45% 0 0;
  transition: height 0.6s ease-out;
  animation: orb-bob 5s ease-in-out infinite;
}

/* The gradients are the theme's own accent tokens at reduced strength -- the
 * rgba() forms restate --moss-deep and --amber, as the glows elsewhere in this
 * file restate --moss. */
.statuspane .orb.vitality .fill {
  background: linear-gradient(to top, rgba(62, 122, 76, 0.95), var(--moss));
  box-shadow: 0 0 18px rgba(130, 201, 143, 0.4);
}

.statuspane .orb.vigor .fill {
  background: linear-gradient(to top, rgba(217, 166, 80, 0.5), var(--amber));
  box-shadow: 0 0 18px rgba(217, 166, 80, 0.4);
  animation-delay: -2.2s;
}

.statuspane .orb .glass {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(ellipse 55% 32% at 32% 20%,
                              rgba(255, 255, 255, 0.16), transparent 70%);
  pointer-events: none;
}

@keyframes orb-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(3px); }
}

.statuspane .orb-word {
  font-size: 0.72rem;
  color: var(--text);
  margin: 0.45rem 0 0;
}

/* Tones come from the server (world/status_feed.py), so a word only shouts
 * when the state behind it warrants it. */
.statuspane .orb-word.warn {
  color: var(--amber);
}

.statuspane .orb-word.alert {
  color: var(--wound);
}

.statuspane .orb-name {
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0.1rem 0 0;
}

/* Chips -- the condition row, and the tongue being spoken. */

.statuspane .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* The condition chips hang straight off the orbs above them with no label of
 * their own; every other chip row follows a side-label that spaces it already. */
.statuspane .status-condition .chips {
  margin-top: 0.7rem;
}

.statuspane .chip {
  font-size: 0.68rem;
  padding: 0.14rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--pane-raised);
  color: var(--text-dim);
}

.statuspane .chip.warn {
  color: var(--amber);
  border-color: rgba(217, 166, 80, 0.35);
}

/* Hands. A line per hand: the hand named quietly, then what it holds, in the
 * item's own words (its sdesc) so the panel reads as the game does. */

.statuspane .hands {
  font-size: 0.74rem;
  line-height: 1.6;
}

.statuspane .hands .slot {
  color: var(--text-faint);
}

.statuspane .hands .item {
  color: var(--text);
}

/* An empty hand is a readout, not a blank -- stated, but in the faint italic
 * that marks it as the absence of an item rather than the name of one. */
.statuspane .hands .empty {
  color: var(--text-faint);
  font-style: italic;
}

/* The orbs settle and breathe; neither is information, so both stop for
 * anyone who has asked the platform for less motion. */
@media (prefers-reduced-motion: reduce) {
  .statuspane .orb .fill {
    transition: none;
    animation: none;
  }
}

/* ── Map pane ────────────────────────────────────────────────────── */

/* The compass and the minimap, built by js/plugins/rhizome_map.js from the
 * out-of-band payload world/map_feed.py pushes. The compass is the pane's
 * only interactive surface: a direction drawn open is a direction the player
 * can walk, and clicking it sends that direction as a command. The minimap
 * is display-only by design decision -- a clickable room would be autowalk. */

/* Compass. */

.mappane .compass {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.3rem;
  max-width: 150px;
  margin: 0 auto;
}

/* A direction that is closed stays on the rose, because the rose is only
 * readable at a glance while it keeps its shape. It is drawn in --text-dim
 * rather than the mockup's --text-faint for that reason: dim enough to read
 * as unavailable, still legible enough to tell N from NE. */
.mappane .dir {
  font-size: 0.7rem;
  text-align: center;
  padding: 0.32rem 0;
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--text-dim);
  background: var(--pane-raised);
}

.mappane .dir.open {
  color: var(--moss);
  border-color: var(--moss-deep);
  cursor: pointer;
}

/* The same brightening a#mxplink does when a clickable label is hovered --
 * every clickable thing in this client answers to the pointer the same way.
 * The rgba() restates --moss at the strength the glows elsewhere use. */
.mappane .dir.open:hover {
  color: var(--moss);
  border-color: var(--moss);
  background: rgba(130, 201, 143, 0.12);
}

/* The centre of the rose: the square the player is standing in. */
.mappane .dir.blank {
  border: none;
  background: none;
}

/* Up, down, in and out have no place on a rose, so they sit under it as
 * pills -- the status pane's chip shape, carrying the compass's own colours
 * because they are directions and behave like one when clicked. */
.mappane .dir-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

.mappane .dir-chips .dir {
  font-size: 0.68rem;
  padding: 0.14rem 0.55rem;
  border-radius: 999px;
}

/* Minimap. */

/* The column count must agree with MINIMAP_SIZE in
 * js/plugins/rhizome_map.js -- the grid is sized here, not by the payload. */
.mappane .minimap {
  display: grid;
  grid-template-columns: repeat(5, 20px);
  gap: 3px;
  justify-content: center;
}

/* An empty cell is ground the map does not reach, not a room: it holds its
 * place in the grid and recedes. */
.mappane .cell {
  width: 20px;
  height: 20px;
  /* Stated rather than inherited from Bootstrap's reset: the grid's tracks are
   * a fixed 20px, so a cell whose borders sat outside its box would overflow
   * them. */
  box-sizing: border-box;
  border-radius: 3px;
  background: var(--frame);
  border: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 0.7rem;
  /* The cell's height less its two borders, so the glyph sits centred. */
  line-height: 18px;
  text-align: center;
  overflow: hidden;
}

/* A room lifts off that ground. The mockup's one-off #1c2b20 sits between the
 * theme's two darkest lines, so the tokens stand in for it. */
.mappane .cell.room {
  background: var(--line-soft);
  border-color: var(--line);
  color: var(--text);
}

/* Where the player is. Specific enough to beat the glyph colours below --
 * whatever colour the room's own glyph carries, "you" is drawn as you. */
.mappane .minimap .cell.here {
  background: var(--moss-deep);
  border-color: var(--moss);
  box-shadow: 0 0 8px rgba(130, 201, 143, 0.4);
  color: var(--text);
  font-weight: 700;
}

/* Glyph colours. The server sends each room's glyph carrying the raw Evennia
 * colour code its builder authored (typeclasses/rooms.py get_glyph_data), and
 * rhizome_map.js turns the eight letter hues into the classes below; the
 * xterm digit codes are an unbounded set and arrive as an inline colour
 * instead. These are cartography, not state: teal is reserved against a
 * future mana readout in the *status* pane's gauges, and a builder who paints
 * a river |c means the colour cyan. */
.mappane .cell.glyph-red {
  color: var(--wound);
}

.mappane .cell.glyph-green {
  color: var(--moss);
}

.mappane .cell.glyph-yellow {
  color: var(--amber);
}

.mappane .cell.glyph-cyan {
  color: var(--teal);
}

/* Blue and magenta carry no role in the theme and so have no token of their
 * own; these are the palette's own muted, slightly desaturated character
 * taken to those two hues, so a coloured glyph never arrives as a colour the
 * rest of the client cannot live beside. */
.mappane .cell.glyph-blue {
  color: #6f8fb3;
}

.mappane .cell.glyph-magenta {
  color: #ab86bd;
}

.mappane .cell.glyph-white {
  color: var(--text);
}

.mappane .cell.glyph-grey {
  color: var(--text-dim);
}

/* Evennia's uppercase codes are the unhilit form of the same eight hues, so
 * rhizome_map.js marks them rather than colouring them twice. */
.mappane .cell.glyph-dim {
  opacity: 0.6;
}

/* ── Input pane ──────────────────────────────────────────────────── */

.inputwrap {
  display: flex;
  flex-direction: column;
  background: var(--pane);
  border-top: 1px solid var(--line);
}

/* Server prompts sit above the field; stock CSS lets a non-empty prompt push
 * the (height:100%) field out the bottom of the pane. */
.inputwrap > .prompt {
  flex: none;
  color: var(--text-dim);
  font-size: 0.78rem;
  padding: 0.25rem 0.6rem 0;
}

.inputwrap > .inputfieldwrapper {
  flex: 1;
  min-height: 0;
  height: auto;
}

.inputfield,
#inputfield,
#inputsizer {
  background: var(--pane);
  color: var(--text);
  caret-color: var(--moss);
  font-family: var(--mono);
  font-size: 0.9rem;
  line-height: 1.5;
  border: none;
  padding: 0.5rem 0.7rem;
}

.inputfield:focus,
#inputfield:focus {
  background: var(--pane-raised);
  color: var(--text);
  outline: none;
  box-shadow: inset 2px 0 0 var(--moss-deep);
}

.inputsend {
  background: var(--pane);
  color: var(--moss-deep);
  border: none;
  font-family: var(--mono);
  font-size: 1rem;
}

.inputsend:hover {
  color: var(--moss);
  background: var(--pane-raised);
}

/* ── Dialogs (options, client help) ──────────────────────────────── */

.dialog {
  background-color: var(--pane);
  border: 1px solid var(--line);
  color: var(--text);
}

.dialogtitle {
  background-color: var(--pane-raised);
  border-bottom: 1px solid var(--line);
  color: var(--moss);
  font-family: var(--mono);
  font-size: 0.85rem;
  letter-spacing: 0.1em;
}

.dialogclose {
  color: var(--text-dim);
}

.dialogclose:hover,
.dialogclose:focus {
  color: var(--wound);
}

#helpdialog .dialogcontent,
.dialogcontent {
  background-color: var(--pane);
  color: var(--text);
}

/* ── Controls (options pane, dialogs, message-routing rows) ──────── */

/* options2.js builds the options pane out of Bootstrap accordion cards; stock
 * webclient.css leaves them light grey. */
#clientwrapper .card {
  background-color: var(--pane-raised);
  border: none;
  border-bottom: 1px solid var(--line-soft);
  padding: 0.3rem 0.6rem;
}

#clientwrapper label {
  color: var(--text);
}

#clientwrapper input[type="checkbox"],
#clientwrapper input[type="radio"] {
  accent-color: var(--moss-deep);
}

#clientwrapper input[type="button"],
#clientwrapper button:not(.inputsend) {
  background: var(--pane);
  color: var(--text);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8rem;
  padding: 0.25rem 0.7rem;
}

#clientwrapper input[type="button"]:hover,
#clientwrapper button:not(.inputsend):hover {
  border-color: var(--moss-deep);
  color: var(--moss);
}

#clientwrapper input[type="button"]:disabled {
  color: var(--text-faint);
  border-color: var(--line-soft);
}

#clientwrapper input[type="text"],
#clientwrapper select {
  background: var(--pane);
  color: var(--text);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8rem;
}

#connecting {
  color: var(--text-dim);
}

/* The gear cloned into the Main tab header. It is a <button>, so it needs the
 * control chrome above taken back off. */
#clientwrapper #optionsbutton {
  color: var(--text-faint);
  background: none;
  border: none;
  padding: 0;
}

#clientwrapper #optionsbutton:hover {
  color: var(--moss);
}

/* Dropdowns goldenlayout.js builds for the tab controls. */
.typelistsub,
.updatelistsub,
#renameboxin {
  background-color: var(--pane-raised);
  color: var(--text);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.75rem;
}

/* ── Narrow viewports ────────────────────────────────────────────── */

/* Stock webclient.css drops to font-size .5rem/line-height .7rem under 480px,
 * which is unreadable on a phone. Restate legible sizing and trim the padding
 * instead; the default layout stacks the help pane as a tab (rather than a
 * side column) below 900px — see js/plugins/goldenlayout_default_config.js. */
@media screen and (max-width: 600px) {
  body {
    font-size: 0.8rem;
    line-height: 1.5;
  }

  .content {
    padding: 0.55rem 0.6rem;
  }

  .site-nav {
    padding: 0.35rem 0.6rem;
    gap: 0.6rem;
  }

  /* The date is a whole sentence and will not share a phone's nav with the
   * brand, the pip and the links. The site nav already wraps; ordered last
   * and given the full width, the clock takes the second row on its own. */
  .site-nav .clock {
    order: 1;
    flex-basis: 100%;
    font-size: 0.66rem;
    white-space: normal;
  }

  /* Comfortable thumb target for the send button. */
  .inputsend {
    width: 42px;
  }

  .inputfield {
    width: calc(100% - 42px);
    font-size: 1rem;
  }
}
