/* Dreamwar modern: shell, header, navigation.
   Colors, sizes and spacing come from tokens.css. No literal colors here.

   One markup tree for every width. The phone gets a single column and the
   bottom tab bar; from 1000px the same markup becomes a desktop client: a
   sticky header, a sticky left rail with the full menu, and a wide content
   area that each screen composes on its own (see "screen layout" in
   components.css). Nothing is duplicated per device: the rail and the tab
   bar are both in the markup, and CSS shows the one that fits.

   Base element rules live inside :where() on purpose. :where() carries no
   specificity, so a component class always wins over a base rule. Without
   it `body.dw a { color }` (0,1,2) out-ranked `.dw-btn--main { color }`
   (0,1,0) and every button lost its text color. A base rule is a fallback,
   never a decision. */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body.dw {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-body-size);
  line-height: var(--text-body-line);
  font-variant-numeric: tabular-nums;
}

/* hidden means hidden: a component that sets its own display (a chip, a
   pager, a row) would otherwise stay on screen with the attribute on -
   the inventory hides its Belt button until the script is there and
   pages the elixir list by it (28.09). */
body.dw [hidden] { display: none !important; }

/* Phone: no entry field under 16px. Safari on the iPhone zooms the page
   into a field set smaller the moment the cursor lands in it, and does not
   zoom back - the player then has to drag the page or pinch it (player
   FURY, 13.10). One rule for every screen, the legacy layer and fields
   with no class included. !important because this is a floor, not a look:
   no component rule may undercut it. A field that wants MORE says so with
   --dw-field-size next to its own font-size (the gold of a parcel and the
   code boxes: 20px). Ticks, radios, files and input-buttons are not typed
   into and stay as they are. From 1000px nothing changes. */
@media (max-width: 999.98px) {
  body.dw input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="range"]):not([type="color"]),
  body.dw textarea,
  body.dw select { font-size: max(16px, var(--dw-field-size, 16px)) !important; }
}

:where(body.dw a) { color: var(--text); text-decoration: none; }
:where(body.dw a:hover) { color: var(--accent-text); }

/* The engine's output pass (libs/core.php:5030) wraps what is inside
   every form into <div class="block"> to slip in the hidden sid field,
   whenever the request carries sid. Grid and flex forms then see one
   child and lose their gaps (the new letter, 27.09). The wrapper is made
   transparent to layout, so the form lays out its own fields. */
body.dw .dw-main:not(.dw-legacy) form > .block:only-child { display: contents; }

/* Keyboard focus is visible on the desktop, where Tab is a real way to
   move. The ring is the accent outside the element, never a glow. */
:where(body.dw a:focus-visible),
:where(body.dw button:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Hover feedback settles in 120ms. Nothing moves on its own, and a player
   who asked the system for less motion gets none. */
@media (prefers-reduced-motion: no-preference) {
  :where(body.dw a) { transition: color 0.12s, background-color 0.12s, border-color 0.12s; }
}

/* ---- header ----
   Fixed structure, one row, never ragged: identity on the left, a strip of
   alerts on the right. Alerts are an icon plus a number, not a text pill:
   at 390px three text pills wrapped onto two lines and left a hole in the
   middle of the header. On the phone the header scrolls away with the page
   (the tab bar is the persistent navigation there); on the desktop it is
   sticky, because there the rail is sticky too and the two form the frame
   of the client. */

.dw-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--head-height);
  padding: 0 var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* A guest's bar: the game's name where a player's nick is, then Log in
   and Register (owner, 27.09: the same frame with or without a login). */
.dw-head__brand {
  margin-right: auto;
  min-width: 0;
  overflow: hidden;
  color: var(--text);
  font-family: var(--font-display);
  font-size: 18px;
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Two selectors deep: .dw-btn in components.css loads later and would
   otherwise win back its 44px touch height. */
.dw-head .dw-head__btn { min-height: 36px; padding: 0 var(--space-3); white-space: nowrap; }

@media (max-width: 639.98px) {
  .dw-head__brand { font-size: 16px; }
  .dw-head .dw-head__btn { min-height: 32px; padding: 0 10px; font-size: 14px; }
}

.dw-head__me {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  flex: 1 1 auto;
}

/* The avatar circle carries a person glyph, the same stroke icon as the
   fighter tab, so the circle says where the link goes. It used to carry
   the first letter of the nick, and the owner read his letter as a
   stray checkmark leading nowhere (2026-09-23). */
.dw-head__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  border-radius: var(--radius-round);
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  color: var(--accent-text);
}

.dw-head__avatar svg { width: 17px; height: 17px; }

/* The circle is the player's photo, like Telegram (owner, 30.09, [DW-AVATAR]); a tap opens a menu:
   my fighter, set or change the photo, remove it. Without a photo the person glyph stays. */
.dw-av { position: relative; flex: 0 0 auto; }
.dw-av > summary { overflow: hidden; cursor: pointer; list-style: none; }
.dw-av > summary::-webkit-details-marker { display: none; }
.dw-av[open] > summary { border-color: var(--accent); }
/* A photo fills the circle edge to edge, no grey ring (owner, 30.09: the ringed 32px thumbnail looked bad). */
.dw-av--pic > summary { border-color: transparent; }
.dw-av--pic[open] > summary { box-shadow: 0 0 0 2px var(--accent); }
.dw-av__img { display: block; width: 100%; height: 100%; max-width: none; object-fit: cover; }
.dw-av:not(.dw-av--pic) .dw-av__img,
.dw-av--pic .dw-av__glyph { display: none; }
.dw-av--busy > summary { opacity: .5; }
.dw-av__menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 30;
  width: min(250px, calc(100vw - 24px));
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .35);
}
.dw-av__form { margin: 0; }
/* The engine slips a hidden sid field into every form inside <div class="block">. */
.dw-av__form > .block { display: contents; }
.dw-av__item {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
:where(body.dw) .dw-av__item:hover { background: var(--surface-2); color: var(--text); }
.dw-av__item--danger,
:where(body.dw) .dw-av__item--danger:hover { color: var(--danger-text); }
.dw-av__file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.dw-av--js .dw-av__send { display: none; }
.dw-av:not(.dw-av--pic) .dw-av__del { display: none; }
.dw-av__msg { margin: 6px 12px 4px; color: var(--danger-text); font-size: var(--text-caption-size); line-height: 1.35; }

/* The level is a status line under the nick, like a name and its status in a messenger (owner 03.10: "the nick is
   handsome and the level is nailed on beside it like a stick"). Until 03.10 it sat on the nick's baseline. */
.dw-head__who { display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; }

.dw-head__nick {
  font-family: var(--font-display);
  font-size: var(--text-h2-size);
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dw-head__sub {
  flex: 0 0 auto;
  font-size: var(--text-caption-size);
  line-height: 1;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}

.dw-head__alerts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Mail and events live only here now: no tab, no rail item, no hub row
   (owner, 2026-09-23). So the icons are always on, larger than before
   (22px in a 40px target) and change state rather than appear: muted
   when there is nothing new, brass with a filled count when there is.
   A number on a fill reads at a glance; a number as bare text next to
   an 18px icon did not. */
.dw-alert {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 6px;
  border-radius: var(--radius-md);
  color: var(--muted);
}

.dw-alert:hover { background: var(--surface-2); color: var(--text); }

/* The label next to the icon exists only on the desktop: the phone
   header has no room, and there the thumb target is the 40px icon. */
.dw-alert__label { display: none; }
.dw-alert__icon { width: 22px; height: 22px; }

/* Something new: the icon turns brass and the count sits on a brass
   disc at its corner, ground-coloured digits. */
.dw-alert--new { color: var(--accent-text); }
.dw-alert--new:hover { color: var(--accent-text); }

/* Theme toggle: a button dressed as an alert icon, in the header and on
   the login screen. The icon shows where it switches to: the sun in the
   dark theme, the moon in the light one. Dark is the default (owner,
   2026-09-23); the choice lives in the dw_theme cookie. */
.dw-theme { border: 0; background: none; font: inherit; cursor: pointer; }
.dw-theme__moon { display: none; }
:root[data-theme="light"] .dw-theme__sun { display: none; }
:root[data-theme="light"] .dw-theme__moon { display: block; }

/* One digit gives a round disc: 4px sides keep it at the 18px minimum
   (5px made it a 19.6 x 18 oval). Proportional figures: Golos draws
   figures tabular by default, and the wide cell of "1" pushed "12" to
   the right (owner, 2026-10-08: the digit is off centre). */
.dw-alert__n {
  position: absolute;
  top: 3px;
  right: 1px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  border: 1.5px solid var(--surface);
  background: var(--accent);
  color: var(--bg);
  font-size: 11px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: proportional-nums;
}

/* The one alert that is not a counter but a state: the player is in a
   battle right now and every other screen is a detour. */
.dw-alert--danger { color: var(--danger-text); }
.dw-alert--danger:hover { background: var(--danger-soft); color: var(--danger-text); }
.dw-alert--danger .dw-alert__n { color: var(--danger-text); }

/* ---- shell ---- */

.dw-shell { display: block; }

/* ---- rail, desktop only ----
   Only `.dw-side` itself is switched by width; the look of its items is
   plain CSS outside the media query. Below 1000px the rail is display:
   none, so the item rules cost nothing, and the buttons stand can show a
   real rail at any width without copying them. */

/* Everything but the switch itself: the rail looks the same wherever it
   is shown, only `display` and the sticky frame belong to the width. */
.dw-side {
  display: none;
  flex-direction: column;
  background: var(--sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

/* The name of the game heads the rail. The phone has no room for it and
   the tab title carries it there; the desktop rail is where a client
   says what it is. */
.dw-side__brand {
  display: block;
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--line-2);
  font-family: var(--font-display);
  font-size: var(--text-h2-size);
  line-height: 1.1;
  color: var(--text);
}

.dw-side__brand:hover { color: var(--accent-text); }

/* Groups: play (city, battle, fighter, inventory), world (talk, clans,
   world, shop), and the way out. A hairline between groups, the exit
   pushed to the bottom of the rail. */
.dw-side__group {
  display: flex;
  flex-direction: column;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line-2);
}

.dw-side__group:last-child { border-bottom: 0; margin-top: auto; }

.dw-side__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch);
  padding: 0 var(--space-4);
  color: var(--muted);
  font-size: var(--text-body-size);
}

.dw-side__icon { width: 18px; height: 18px; flex: 0 0 18px; }

.dw-side__item:hover { background: var(--surface-2); color: var(--text); }

/* The screen the player is on. Brass text and icon, no stripe: the
   stripe on the left belongs to the one notice card of a screen. */
.dw-side__item--on { color: var(--accent-text); font-weight: 600; }
.dw-side__item--on .dw-side__icon { color: var(--accent); }

/* Same state as the phone tab: in a battle, or out in the lands, the
   menu item turns brass and carries the mark. */
.dw-side__item--live { background: var(--accent); color: var(--bg); font-weight: 600; }
.dw-side__item--live:hover { background: var(--accent-text); color: var(--bg); }
.dw-side__item--live .dw-side__icon { color: var(--bg); }

/* Design switch (13.10, owner: "highlight it"): the way back to the old
   design is a brass-outlined entry - in the side menu on a desktop, a row
   above the exit on a phone (main.html). Margin + padding keep the icon on
   the same line as the other entries. */
.dw-side__item--switch { margin: var(--space-1) var(--space-2); padding: 0 var(--space-2); border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent-soft); color: var(--accent-text); font-weight: 600; white-space: nowrap; }
.dw-side__item--switch .dw-side__icon { color: var(--accent); }
.dw-side__item--switch:hover { background: var(--accent); color: var(--bg); }
.dw-side__item--switch:hover .dw-side__icon { color: var(--bg); }
.dw-row.dw-row--switch { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; box-shadow: inset 0 0 0 1px var(--accent); }
.dw-row.dw-row--switch .dw-row__go { color: var(--accent); }

.dw-side__mark { top: 7px; left: 24px; }

.dw-side__item > span { flex: 1 1 auto; min-width: 0; }

/* One column, but not an endlessly wide one: past ~760px a list row
   becomes a rule with a word at each end. Applies to a tablet held
   upright and to a phone that claims 980px in "desktop site" mode. */
.dw-main {
  max-width: 760px;
  margin: 0 auto;
  padding: var(--space-3) var(--space-4);
  padding-bottom: calc(var(--tab-height) + var(--space-4) + env(safe-area-inset-bottom, 0px));
}

/* Back to where the player came from, above the screen: "< Inventory".
   The bottom "City"/"Back" buttons are gone - the city is in the rail
   and the tabs (owner, 27.09). */
/* Up to the section, the topic, the mail: a pill with a brass label, easy to see and
   to hit on the phone (owner, 30.09: "back to the section or topic must be more
   noticeable, on the phone and on the desktop"); it was a muted caption. */
.dw-backto {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 36px;
  margin: 0 0 var(--space-3);
  padding: 0 16px 0 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--accent-text);
  font-size: var(--text-body-size);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
}

.dw-backto:hover { border-color: var(--accent); background: var(--surface-2); color: var(--accent-text); }

.dw-devtime {
  margin-top: var(--space-3);
  color: var(--dim);
  font-size: var(--text-caption-size);
  text-align: center;
}

/* ---- bottom tabs, phone only ---- */

/* The bar is fixed to the very bottom and pads itself by the phone's
   safe-area inset instead of sitting above it: the browser's own strip
   (Safari's toolbar, the iPhone home indicator, Chrome's Android bar)
   lives inside that inset. Without the padding the last 34px of the bar
   are under the home indicator and a tap there goes to the system, not
   to the game. The inset is only non-zero when the page's viewport meta
   says viewport-fit=cover, which the wrapper sets.

   The height is content plus inset, not a fixed number, so --tab-height
   stays the height of the row a finger actually aims at. */
.dw-tabs {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  min-height: var(--tab-height);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--line);
}

.dw-tabs__item {
  flex: 1 1 0;
  min-height: var(--tab-height);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  color: var(--muted);
}

.dw-tabs__item:hover { color: var(--accent-text); }

.dw-tabs__icon { width: 20px; height: 20px; }

.dw-tabs__label { font-size: var(--text-tab-size); line-height: var(--text-tab-line); }

/* The tab of the screen the player is on. */
.dw-tabs__item--on { color: var(--accent-text); }
.dw-tabs__item--on .dw-tabs__label { font-weight: 600; }

/* The Battle tab has a state. Not in a battle: it opens the battle hub.
   In a battle: the same cell turns brass and returns the player to the
   fight they are in. Every other screen is a detour then, and the one
   main action of the whole game sits under the thumb, not as an icon in
   the header. Static, no pulse: nothing moves on its own. */
.dw-tabs__item--live {
  background: var(--accent);
  color: var(--bg);
}

.dw-tabs__item--live:hover { color: var(--bg); background: var(--accent-text); }
.dw-tabs__item--live .dw-tabs__label { font-weight: 600; }

/* The exclamation mark at the corner of the icon is the second signal
   next to the colour: a fight, or the lands, are waiting for the
   player's move. Drawn only with the live state, never on the screen it
   points at. Red on brass, because red is the one colour the brass
   state does not already use - the palette is not two colours wide, and
   the first two versions (ground on brass, then text on brass) both
   read as decoration. Red here and the enemy's health bar are the only
   red in the game, so it keeps meaning danger. */
.dw-tabs__item { position: relative; }

.dw-tabs__mark,
.dw-side__mark {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-round);
  border: 1.5px solid var(--accent);
  background: var(--alert);
  color: var(--alert-on);
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 800;
  line-height: 15px;
  text-align: center;
}

.dw-tabs__mark { top: 4px; left: calc(50% + 4px); }

/* ---- desktop from 1000px ----
   Why 1000 and not 960: "request desktop site" in Safari and in Chrome
   for Android makes a phone report a 980px viewport. At a 960px
   breakpoint an iPhone answered with the full desktop client, rail and
   all, shrunk to thumbnail size - which is what the owner saw. The
   check could be written on pointer or hover instead, but a browser
   that does not know those features would fail the whole query and put
   every desktop on the phone layout; a number cannot fail that way.
   Nothing real is lost: a desktop window 960-999px wide now gets the
   one-column layout, and no phone reports 1000px.

   Header 60px, sticky. Under it a centered frame up to --page-max wide:
   the rail on the left, the content on the right. The rail is sticky
   under the header and scrolls inside itself only when the window is
   shorter than the menu. The content area has no padding of its own: a
   screen composes its columns itself and uses the full width. */

@media (min-width: 1000px) {
  .dw-tabs { display: none; }

  .dw-head {
    position: sticky;
    top: 0;
    z-index: 3;
    height: 60px;
    padding-left: var(--space-6);
    padding-right: var(--space-6);
  }

  .dw-head__nick { font-size: var(--text-h1-size); }
  .dw-head__avatar { width: 40px; height: 40px; flex-basis: 40px; }

  /* Bare icons are a poor mouse target (owner, 2026-09-24): on the
     desktop the header actions are labelled buttons with a border,
     not icons to hunt for. */
  .dw-alert {
    min-width: 44px;
    height: 44px;
    gap: var(--space-2);
    padding: 0 var(--space-3);
    border: 1px solid var(--line);
    background: var(--surface-2);
  }

  .dw-alert:hover { border-color: var(--accent); }
  .dw-alert__icon { width: 22px; height: 22px; }
  .dw-alert__label { display: inline; font-size: 14px; line-height: 1; }
  .dw-head__alerts { gap: var(--space-2); }

  /* On the labeled desktop button the count sits inline after the word,
     not floating over the corner: half-outside the border it read as a
     misprint (owner, 2026-09-24). The corner badge stays on the phone,
     where the button is an icon. The disc is 18px, an even number: the
     figures are 8px tall, and in 17px the 9px left over cannot split
     evenly, so on a 1x monitor the digit sat a pixel low (and high at
     125%). Measured 2026-10-08 at 1 / 1.25 / 1.5 / 2 / 3. */
  .dw-alert__n {
    position: static;
    margin-left: 2px;
    border: 0;
    height: 18px;
    min-width: 18px;
    line-height: 18px;
    font-size: 11px;
  }

  .dw-shell {
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--space-5) var(--space-6) var(--space-6);
  }

  /* A guest has no rail: one column, or the page lands in the rail's
     224px slot on the left (the login screen did, 2026-09-23). */
  .dw-shell--solo { grid-template-columns: minmax(0, 1fr); }

  .dw-side {
    display: flex;
    position: sticky;
    top: calc(60px + var(--space-5));
    max-height: calc(100vh - 60px - var(--space-5) * 2);
    overflow-y: auto;
  }

  .dw-main { max-width: none; margin: 0; padding: 0; min-width: 0; }
}
