/* ============================================================
   desktop.css -- NULL//OS desktop shell (VAIHE 1 + VAIHE 2)
   ------------------------------------------------------------
   SCOPE RULE: every selector is gated behind body.null-os. The
   legacy terminal UI never sets that flag, so nothing here can
   reach it. This is why the file ships beside the 10 legacy CSS
   files with zero edits to them.

   VAIHE 1: tokens, chrome-suppression, desktop surface, boot card.
   VAIHE 2: window chrome (titlebar, controls, body, focus ring,
            drag/min/max states) + BOTH orientations.
            Landscape = free-floating overlapping windows.
            Portrait  = one fullscreen app with a titlebar.

   Z-INDEX: windows are position:absolute inside #desktop-host
   (z-index:1 -> own stacking context). Their z lives 100+, set by
   JS. They can never reach the legacy modal band (9000-9999) or
   the HC pact (2147483000) because they aren't fixed and aren't in
   the root stacking context.
   ============================================================ */

/* ---- Tokens: locked kali-cyan neon (variant B) ------------- */
body.null-os {
  --nos-bg:        #05080a;
  --nos-bg-2:      #070d11;
  --nos-surface:   #0a1319;
  --nos-surface-2: #0c1922;
  --nos-line:      #123039;
  --nos-line-2:    #1b4653;

  --nos-cyan:      #31e6ff;
  --nos-cyan-rgb:  49, 230, 255;
  --nos-cyan-dim:  #0bb6d6;
  --nos-cyan-ink:  #9ff3ff;

  --nos-text:      #c7e9f0;
  --nos-text-dim:  #6b8b94;
  --nos-warn:      #ff5d5d;
  --nos-coral:     #ff7a5c;

  --nos-grid:      22px;
  --nos-radius:    3px;
  --nos-font:      'JetBrains Mono', ui-monospace, monospace;
  --nos-bar-h:     30px;      /* titlebar height, shared by JS clamp */
  /* dock height + the device's bottom safe area (gesture bar / home
     indicator). The surface reserves exactly this so windows never
     sit under the dock, and the dock pads itself above the gesture
     zone so its buttons aren't swallowed by the OS nav area. */
  --nos-dock-h:    44px;
  --nos-safe-b:    env(safe-area-inset-bottom, 0px);
  --nos-shadow:    0 0 0 1px var(--nos-line),
                   0 8px 30px rgba(0,0,0,0.55);
  --nos-shadow-focus: 0 0 0 1px var(--nos-line-2),
                   0 0 22px rgba(var(--nos-cyan-rgb),0.22),
                   0 12px 40px rgba(0,0,0,0.6);
}

/* ---- Legacy-chrome suppression ---------------------------- */
/* Unconditional: whenever the desktop skin is on, the legacy topbar and
   bottomnav must be gone so GHOST OS is truly fullscreen. The older rules
   depended on DOM sibling order (~) or a .nos-active class; if the legacy
   chrome sat BEFORE #screen-desktop in the DOM the ~ selector missed it and
   the header/footer kept taking up space (worse on tablet). This flat rule
   catches every case. */
body.null-os #topbar,
body.null-os #bottomnav {
  display: none !important;
}
body.null-os #screen-desktop.active ~ #topbar,
body.null-os #screen-desktop.active ~ #bottomnav {
  display: none !important;
}
body.null-os.nos-active #topbar,
body.null-os.nos-active #bottomnav {
  display: none !important;
}

/* ---- Desktop container ------------------------------------ */
/* Blurred fill behind a 'contain' wallpaper (fills leftover space so it's
   not a flat bar -- the polished Windows/Linux look). Sits at the very back
   of the surface; windows + icons stack above it. */
body.null-os .nos-wall-fill {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  filter: blur(28px) brightness(0.55) saturate(1.1);
  transform: scale(1.12);   /* hide the blur's soft edges */
  pointer-events: none;
}
/* Sharp CONTAIN image above the blurred fill: whole image, real aspect
   ratio, scaled to fit, never cropped, never stretched. */
body.null-os .nos-wall-sharp {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-position: center;
  background-size: contain;
  background-repeat: no-repeat;
  pointer-events: none;
}

body.null-os #screen-desktop {
  position: fixed;
  left: 0; right: 0; top: 0;
  /* Kill the legacy .screen padding (styles.css: 48px top + 80px bottom,
     reserved for the legacy topbar/bottomnav). GHOST OS hides that chrome,
     so the reserved space became dead margin at the screen edges -- not a
     true fullscreen OS. Zero it so the desktop fills edge to edge; the dock
     handles its own safe-area at the bottom. */
  padding: 0 !important;
  /* Full visible viewport height (dvh, not vh/inset:0 which include
     the address-bar area on mobile). The background fills all the way
     to the physical bottom; the dock -- like the legacy .bottomnav --
     sits at bottom:0 and lifts its own contents above the gesture bar
     with padding-bottom: env(safe-area-inset-bottom). This is the
     exact pattern the legacy UI already uses and is confirmed working
     on WpE's devices. */
  height: 100vh;
  height: 100dvh;
  background:
    radial-gradient(120% 80% at 50% -10%, #08151b 0%, var(--nos-bg) 60%),
    var(--nos-bg);
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 13px;
  line-height: 1.4;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}
body.null-os #screen-desktop.active { display: block; }

body.null-os #screen-desktop::before {
  content: "";
  position: absolute;
  /* oversize by one grid cell in each direction so the transform can
     travel a full cell without exposing an edge */
  top: calc(-1 * var(--nos-grid));
  left: calc(-1 * var(--nos-grid));
  right: calc(-1 * var(--nos-grid));
  bottom: calc(-1 * var(--nos-grid));
  background-image:
    linear-gradient(rgba(var(--nos-cyan-rgb),0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--nos-cyan-rgb),0.035) 1px, transparent 1px);
  background-size: var(--nos-grid) var(--nos-grid);
  pointer-events: none;
  /* GPU-composited: transform + will-change means the browser promotes
     this to its own layer and the drift never triggers paint (unlike the
     old background-position animation, which repainted the whole screen
     every frame -- the PC fan-runaway cause). */
  will-change: transform;
  transform: translateZ(0);
  animation: nos-grid-drift 14s linear infinite;
}
@keyframes nos-grid-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(var(--nos-grid), var(--nos-grid), 0); }
}

/* ---- Host + surface --------------------------------------- */
body.null-os #desktop-host {
  position: relative;
  z-index: 1;               /* own stacking context; windows live above */
  height: 100%;
  overflow: hidden;
}
/* The surface is the coordinate space windows are absolutely
   positioned within. It fills the host minus the test dock. */
body.null-os .nos-surface {
  position: absolute;
  /* reserve the dock's full footprint: content row + its safe-area
     bottom padding */
  inset: 0 0 calc(var(--nos-dock-h) + var(--nos-safe-b)) 0;
  overflow: hidden;
}

/* ============================================================
   WINDOW CHROME
   ============================================================ */
body.null-os .nos-win {
  position: absolute;
  min-width: 240px;
  min-height: 120px;
  display: flex;
  flex-direction: column;
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  box-shadow: var(--nos-shadow);
  overflow: hidden;
  /* no transition on left/top -- drag must be 1:1 with the pointer */
}
body.null-os .nos-win.is-focus {
  border-color: var(--nos-line-2);
  box-shadow: var(--nos-shadow-focus);
}
body.null-os .nos-win.is-dragging { user-select: none; cursor: grabbing; }
body.null-os .nos-win.is-min { display: none; }

/* maximized (landscape): fill the surface */
body.null-os .nos-win.is-max {
  left: 0 !important; top: 0 !important;
  width: 100% !important; height: 100% !important;
  border-radius: 0;
}

/* ---- titlebar ---- */
body.null-os .nos-win-bar {
  height: var(--nos-bar-h);
  flex: 0 0 var(--nos-bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 4px 0 10px;
  background: linear-gradient(180deg, var(--nos-surface-2), var(--nos-bg-2));
  border-bottom: 1px solid var(--nos-line);
  cursor: grab;
  touch-action: none;       /* let Pointer Events own the gesture */
  user-select: none;
}
body.null-os .nos-win.is-focus .nos-win-bar { border-bottom-color: var(--nos-line-2); }
body.null-os .nos-win-title {
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--nos-text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.null-os .nos-win.is-focus .nos-win-title { color: var(--nos-cyan-ink); }

/* ---- window controls ---- */
body.null-os .nos-win-ctrls { display: flex; gap: 2px; flex: 0 0 auto; }
body.null-os .nos-win-btn {
  width: 26px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--nos-font);
  font-size: 13px; line-height: 1;
  color: var(--nos-text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
  cursor: pointer;
}
body.null-os .nos-win-btn:hover { color: var(--nos-cyan-ink); border-color: var(--nos-line-2); }
body.null-os .nos-win-btn.nos-cls:hover { color: #fff; border-color: var(--nos-warn); background: rgba(255,93,93,0.14); }
body.null-os .nos-win-btn:focus-visible { outline: 2px solid var(--nos-cyan); outline-offset: 1px; }

/* ---- body ---- */
body.null-os .nos-win-body {
  flex: 1 1 auto;
  overflow: auto;
  padding: 14px;
  background: var(--nos-surface);
  color: var(--nos-text);
  font-size: 13px;
}
body.null-os .nos-win-placeholder {
  color: var(--nos-text-dim);
  letter-spacing: 1px;
  font-size: 12px;
}

/* ---- resize handle (bottom-right corner, landscape only) ---- *
   A small grip in the SE corner. The window body is flex:1 with
   overflow:auto, so shrinking the window just scrolls its content
   instead of breaking layout; the JS min-size (240x120) stops it
   going unusable. Hidden in portrait (windows are fullscreen there)
   and on maximized windows. */
body.null-os .nos-win-resize {
  position: absolute;
  right: 0; bottom: 0;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  touch-action: none;            /* Pointer Events own the gesture */
  z-index: 2;
  /* two cyan hatch lines as the classic grip tell */
  background:
    linear-gradient(135deg, transparent 0 6px, var(--nos-cyan-dim) 6px 7px,
                    transparent 7px 10px, var(--nos-cyan-dim) 10px 11px,
                    transparent 11px);
  opacity: 0.5;
}
body.null-os .nos-win.is-focus .nos-win-resize { opacity: 0.85; }
body.null-os .nos-win.is-max .nos-win-resize { display: none; }

/* ============================================================
   TASKBAR (VAIHE 4) -- legacy .bottomnav safe-area pattern
   ============================================================ */
body.null-os .nos-taskbar {
  /* EXACTLY the legacy .bottomnav pattern that is confirmed working on
     WpE's devices: position:fixed to the viewport (NOT absolute inside
     #desktop-host, whose box can spill into the gesture strip and drag
     bottom:0 down with it), bottom:0, and padding-bottom:env(safe-area)
     to lift contents above the gesture bar while the background reaches
     the physical bottom. fixed leaves the host stacking context, but in
     desktop mode the legacy chrome is hidden (body.nos-active) so there
     is no collision; z stays well under the HC pact. */
  position: fixed;
  left: 0; right: 0; bottom: 0;
  min-height: var(--nos-dock-h);
  padding: 0 8px env(safe-area-inset-bottom, 0px) 8px;
  display: flex;
  align-items: stretch;
  gap: 6px;
  background: linear-gradient(180deg, var(--nos-surface-2), var(--nos-bg));
  border-top: 1px solid var(--nos-line-2);
  z-index: 8000;            /* above desktop windows, under legacy modals/HC pact */
}

/* START button */
body.null-os .nos-start-btn {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 6px;
  padding: 0 12px;
  min-height: var(--nos-dock-h);
  font-family: var(--nos-font);
  font-size: 11px; font-weight: 700; letter-spacing: 2px;
  color: var(--nos-cyan-ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--nos-radius);
  cursor: pointer;
}
body.null-os .nos-start-btn svg { width: 16px; height: 16px; }
body.null-os .nos-start-btn:hover {
  border-color: var(--nos-cyan); background: rgba(var(--nos-cyan-rgb),0.06);
}

/* open-window buttons */
body.null-os .nos-tasklist {
  flex: 1 1 auto;
  min-width: 0;              /* allow shrink below content so it scrolls
                               internally instead of pushing the clock off */
  display: flex; align-items: center; gap: 6px;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--nos-cyan-dim) transparent;
  cursor: grab;
}
body.null-os .nos-tasklist:active { cursor: grabbing; }
body.null-os .nos-tasklist::-webkit-scrollbar { height: 6px; }
body.null-os .nos-tasklist::-webkit-scrollbar-thumb {
  background: var(--nos-cyan-dim); border-radius: 3px;
}
body.null-os .nos-tasklist::-webkit-scrollbar-track { background: transparent; }
body.null-os .nos-task {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 6px;
  max-width: 160px;
  padding: 6px 10px;
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-text-dim);
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  cursor: pointer;
}
body.null-os .nos-task svg { width: 14px; height: 14px; flex: 0 0 auto; }
body.null-os .nos-task-label {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.null-os .nos-task.is-focus {
  color: var(--nos-cyan-ink);
  border-color: var(--nos-line-2);
  background: rgba(var(--nos-cyan-rgb),0.07);
}
body.null-os .nos-task.is-min { opacity: 0.5; }
body.null-os .nos-task:hover { border-color: var(--nos-cyan); }

/* clock + status line (handle@ghost | ip | date | time) */
body.null-os .nos-clock {
  flex: 0 0 auto;           /* fixed: never shrink, always pinned right --
                               the tasklist scrolls, the clock stays put */
  display: flex; align-items: center;
  padding: 0 10px;
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-cyan-dim);
  white-space: nowrap;
}

/* ============================================================
   START MENU (VAIHE 4)
   ============================================================ */
body.null-os .nos-startmenu {
  /* fixed to match the now-fixed taskbar, so it anchors just above the
     bar regardless of the host box */
  position: fixed;
  left: 8px;
  bottom: calc(var(--nos-dock-h) + var(--nos-safe-b) + 6px);
  width: min(420px, calc(100% - 16px));
  max-height: 60%;
  display: none;
  flex-direction: column;
  background: var(--nos-surface);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  box-shadow: var(--nos-shadow-focus);
  z-index: 8001;            /* just above the fixed taskbar */
  overflow: hidden;
}
body.null-os .nos-startmenu.is-open { display: flex; }
body.null-os .nos-start-head {
  padding: 10px 14px;
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-cyan-dim);
  border-bottom: 1px solid var(--nos-line);
}
body.null-os .nos-start-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 4px;
  padding: 10px;
  overflow-y: auto;
}
body.null-os .nos-start-item {
  position: relative;
  border: 1px solid transparent;
  border-radius: var(--nos-radius);
}
body.null-os .nos-start-item:hover { border-color: var(--nos-line-2); background: rgba(var(--nos-cyan-rgb),0.05); }
body.null-os .nos-start-launch {
  width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 6px;
  font-family: var(--nos-font);
  font-size: 10px; letter-spacing: 1px;
  color: var(--nos-text-dim);
  background: transparent;
  border: none;
  cursor: pointer;
}
body.null-os .nos-start-launch svg { width: 26px; height: 26px; color: var(--nos-cyan); }
body.null-os .nos-start-item:hover .nos-start-launch { color: var(--nos-cyan-ink); }
body.null-os .nos-start-item.is-native .nos-start-launch svg { color: var(--nos-coral); }
/* pin (+ / check) button in the top-right corner of a START tile */
body.null-os .nos-start-pin {
  position: absolute;
  top: 2px; right: 2px;
  width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--nos-font); font-size: 13px; line-height: 1;
  color: var(--nos-text-dim);
  background: var(--nos-bg-2);
  border: 1px solid var(--nos-line);
  border-radius: 2px;
  cursor: pointer;
  opacity: 0.7;
}
body.null-os .nos-start-pin:hover { color: var(--nos-cyan-ink); border-color: var(--nos-cyan); opacity: 1; }
body.null-os .nos-start-pin.is-on { color: var(--nos-cyan); border-color: var(--nos-line-2); opacity: 1; }

/* ============================================================
   DESKTOP ICONS (VAIHE 4b)
   ============================================================ */
body.null-os .nos-iconlayer {
  position: absolute;
  inset: 0;
  z-index: 2;               /* above wallpaper layers (0/1), below windows (100+) */
  pointer-events: none;     /* only the icons themselves are interactive */
}
body.null-os .nos-dicon {
  position: absolute;
  width: 72px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 6px 4px;
  border: 1px solid transparent;
  border-radius: var(--nos-radius);
  cursor: pointer;
  pointer-events: auto;
  touch-action: none;       /* Pointer Events own the drag */
  user-select: none;
  text-align: center;
}
body.null-os .nos-dicon-ico {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  color: var(--nos-cyan);
  background: rgba(10,19,25,0.6);
  border: 1px solid var(--nos-line);
  border-radius: 8px;
}
body.null-os .nos-dicon-ico svg { width: 26px; height: 26px; }
body.null-os .nos-dicon-label {
  font-family: var(--nos-font);
  font-size: 10px; letter-spacing: 0.5px;
  color: var(--nos-text);
  text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  max-width: 72px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.null-os .nos-dicon.is-sel {
  border-color: var(--nos-line-2);
  background: rgba(var(--nos-cyan-rgb),0.10);
}
body.null-os .nos-dicon.is-sel .nos-dicon-ico { border-color: var(--nos-cyan); }
body.null-os .nos-dicon.is-drag { opacity: 0.85; }

/* ============================================================
   WALLPAPER APP (VAIHE 4b)
   ============================================================ */
body.null-os .nos-wallapp {
  display: flex; flex-direction: column; gap: 12px;
}
body.null-os .nos-wall-row { display: flex; gap: 8px; align-items: center; }
body.null-os .nos-wall-file,
body.null-os .nos-wall-reset {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-text);
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  padding: 8px 14px;
  cursor: pointer;
}
body.null-os .nos-wall-file:hover,
body.null-os .nos-wall-reset:hover { border-color: var(--nos-cyan); color: var(--nos-cyan-ink); }
body.null-os .nos-wall-presets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 8px;
}
body.null-os .nos-wall-preset {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-text-dim);
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  padding: 14px 8px;
  cursor: pointer;
}
body.null-os .nos-wall-preset:hover {
  border-color: var(--nos-cyan); color: var(--nos-cyan-ink);
  background: rgba(var(--nos-cyan-rgb),0.05);
}
body.null-os .nos-wall-note {
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-cyan-dim); min-height: 14px;
}

/* ============================================================
   APP MOUNT states (VAIHE 5)
   ============================================================ */
body.null-os .nos-app-loading {
  padding: 20px;
  font-size: 12px; letter-spacing: 1px;
  color: var(--nos-cyan-dim);
}
body.null-os .nos-app-fallback {
  height: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  padding: 24px; text-align: center;
}
body.null-os .nos-app-fallback p {
  margin: 0;
  font-size: 12px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}
body.null-os .nos-app-fs {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  padding: 10px 18px;
  cursor: pointer;
}
body.null-os .nos-app-fs:hover {
  border-color: var(--nos-cyan);
  box-shadow: 0 0 12px rgba(var(--nos-cyan-rgb),0.25);
}

/* ============================================================
   SCRATCH APP (VAIHE 6)
   ============================================================ */
body.null-os .nos-scratchapp {
  padding: 0;
  display: flex; flex-direction: column;
  height: 100%;
}
body.null-os .nos-scratch-ta {
  flex: 1 1 auto;
  width: 100%;
  resize: none;
  border: none;
  outline: none;
  padding: 14px;
  background: var(--nos-surface);
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 13px; line-height: 1.5;
}
body.null-os .nos-scratch-ta::placeholder { color: var(--nos-text-dim); }
body.null-os .nos-scratch-bar {
  flex: 0 0 auto;
  padding: 6px 12px;
  border-top: 1px solid var(--nos-line);
  background: var(--nos-bg-2);
}
body.null-os .nos-scratch-stat {
  font-size: 10px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}

/* ============================================================
   NULL_AMP APP (VAIHE 6)
   ============================================================ */
body.null-os .nos-ampapp {
  padding: 0;
  display: flex; flex-direction: column;
  height: 100%;
}
body.null-os .nos-amp-vizwrap {
  position: relative;
  width: 100%;
  flex: 1 1 auto;
  min-height: 120px;
  display: flex;
}
body.null-os .nos-amp-vizmode {
  position: absolute;
  top: 4px;
  right: 4px;
  font-family: inherit;
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--nos-cyan, #33d6ff);
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(51,214,255,0.5);
  border-radius: 3px;
  padding: 3px 7px;
  cursor: pointer;
  min-height: 24px;
  z-index: 2;
}
body.null-os .nos-amp-vizmode:active {
  background: rgba(51,214,255,0.25);
}
body.null-os .nos-amp-viz {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 120px;
  background: #05080a;
  border-radius: 4px;
}
body.null-os .nos-amp-viz span {
  flex: 1 1 auto;
  min-width: 2px;
  height: 30%;
  /* '80s boombox EQ: green base -> yellow mid -> red peak. The bar is a tall
     fixed gradient; as height grows it reveals more of the hot top. */
  background: linear-gradient(180deg,
    #ff2f2f 0%, #ff6a2f 12%, #ffd21f 32%, #7dff3a 60%, #12e36a 100%);
  opacity: 0.92;
  border-radius: 1px;
  box-shadow: 0 0 4px rgba(18,227,106,0.35);
  transition: height 0.06s linear;
}
/* staggered bar heights via nth-child so it looks alive without JS */
body.null-os .nos-amp-viz span:nth-child(3n)   { animation-delay: 0.15s; }
body.null-os .nos-amp-viz span:nth-child(3n+1) { animation-delay: 0.35s; }
body.null-os .nos-amp-viz span:nth-child(4n)   { animation-delay: 0.55s; }
@keyframes nos-amp-bar {
  0%,100% { height: 20%; } 50% { height: 85%; }
}
body.null-os .nos-amp-now {
  flex: 0 0 auto;
  padding: 8px 12px;
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-cyan-dim);
  border-bottom: 1px solid var(--nos-line);
}
body.null-os .nos-amp-transport {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 6px;
  padding: 10px 12px;
}
body.null-os .nos-amp-transport button {
  width: 38px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px;
  color: var(--nos-cyan-ink);
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  cursor: pointer;
}
body.null-os .nos-amp-transport button:hover {
  border-color: var(--nos-cyan);
  box-shadow: 0 0 10px rgba(var(--nos-cyan-rgb),0.2);
}
body.null-os .nos-amp-vol {
  flex: 1 1 auto;
  margin-left: 8px;
  accent-color: var(--nos-cyan);
}
body.null-os .nos-amp-list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px;
}
body.null-os .nos-amp-track {
  display: block; width: 100%;
  text-align: left;
  padding: 8px 10px;
  font-family: var(--nos-font);
  font-size: 12px; letter-spacing: 0.5px;
  color: var(--nos-text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--nos-radius);
  cursor: pointer;
}
body.null-os .nos-amp-track:hover { color: var(--nos-cyan-ink); border-color: var(--nos-line); }
body.null-os .nos-amp-track.is-on {
  color: var(--nos-cyan-ink);
  border-color: var(--nos-line-2);
  background: rgba(var(--nos-cyan-rgb),0.07);
}
body.null-os .nos-amp-empty {
  padding: 16px; text-align: center;
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}
/* Many phones/tablets ship with "reduce motion" on by default, which was
   killing the visualizer entirely (why it only animated on PC). A music
   visualizer benefits from motion, so under reduce-motion we keep it alive
   but gentler (slower) rather than freezing it. Only the duration is changed
   here -- redefining @keyframes inside a media query is unreliable across
   browsers, so the single nos-amp-bar keyframes above stays authoritative. */
@media (prefers-reduced-motion: reduce) {
  body.null-os .nos-amp-viz span {
    animation-duration: 2.4s;
    opacity: 0.45;
  }
}

/* ============================================================
   OS-STYLE NOTIFICATIONS (VAIHE 7 polish)
   ------------------------------------------------------------
   Windows-style cards bottom-right, above the taskbar. The layer is
   a direct child of <body>, gated by body.null-os so it only shows
   while the desktop skin is active (matches the JS wrapper).
   ============================================================ */
body.null-os .nos-notify-layer {
  position: fixed;
  right: 12px;
  bottom: calc(var(--nos-dock-h) + var(--nos-safe-b) + 12px);
  display: flex; flex-direction: column; gap: 8px;
  align-items: flex-end;
  z-index: 8002;            /* above taskbar (8000) + start menu (8001) */
  pointer-events: none;     /* only cards catch clicks */
  max-width: min(360px, calc(100vw - 24px));
}
body.null-os .nos-notify {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 10px;
  width: 320px; max-width: 100%;
  padding: 12px 14px;
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  border-left: 3px solid var(--nos-cyan);
  border-radius: var(--nos-radius);
  box-shadow: var(--nos-shadow-focus);
  cursor: pointer;
  /* start off-screen right, slide in */
  transform: translateX(120%);
  opacity: 0;
  transition: transform 0.26s cubic-bezier(0.2,0.8,0.2,1), opacity 0.26s;
}
body.null-os .nos-notify.is-in { transform: translateX(0); opacity: 1; }
body.null-os .nos-notify.is-out { transform: translateX(120%); opacity: 0; }
body.null-os .nos-notify.is-err  { border-left-color: var(--nos-warn); }
body.null-os .nos-notify.is-warn { border-left-color: var(--nos-coral); }
body.null-os .nos-notify.is-info { border-left-color: var(--nos-cyan); }
body.null-os .nos-notify-ico {
  flex: 0 0 auto;
  font-size: 14px; line-height: 1.3;
  color: var(--nos-cyan);
}
body.null-os .nos-notify.is-err .nos-notify-ico  { color: var(--nos-warn); }
body.null-os .nos-notify.is-warn .nos-notify-ico { color: var(--nos-coral); }
body.null-os .nos-notify-txt { flex: 1 1 auto; min-width: 0; }
body.null-os .nos-notify-title {
  font-family: var(--nos-font);
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
  margin-bottom: 3px;
}
body.null-os .nos-notify-body {
  font-family: var(--nos-font);
  font-size: 12px; line-height: 1.4;
  color: var(--nos-text);
  word-break: break-word;
}
@media (prefers-reduced-motion: reduce) {
  body.null-os .nos-notify { transition: opacity 0.2s; transform: none; }
  body.null-os .nos-notify.is-in { transform: none; }
  body.null-os .nos-notify.is-out { transform: none; }
}

body.null-os .nos-start-legacy {
  margin: 0;
  padding: 12px 14px;
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  text-align: left;
  color: var(--nos-text-dim);
  background: var(--nos-bg-2);
  border: none;
  border-top: 1px solid var(--nos-line);
  cursor: pointer;
}
body.null-os .nos-start-legacy:hover { color: var(--nos-cyan-ink); }

/* ---- blinking cursor (real-terminal tell) ---- */
body.null-os .nos-cursor::after {
  content: "_";
  color: var(--nos-cyan);
  animation: nos-blink 1.1s steps(1) infinite;
}
@keyframes nos-blink { 0%,50% { opacity: 1; } 51%,100% { opacity: 0; } }

/* ---- boot card (VAIHE 1 proof, kept for pre-WM state) ---- */
body.null-os .nos-boot-mark {
  font-size: 26px; letter-spacing: 6px; font-weight: 700;
  color: var(--nos-cyan); text-shadow: 0 0 18px rgba(var(--nos-cyan-rgb),0.45);
}
body.null-os .nos-boot-sub {
  font-size: 11px; letter-spacing: 2px;
  color: var(--nos-text-dim); text-transform: uppercase;
}
body.null-os .nos-boot-line { font-size: 12px; color: var(--nos-cyan-dim); opacity: 0.85; }

/* ============================================================
   PORTRAIT: one fullscreen app at a time, titlebar kept, no drag
   ------------------------------------------------------------
   ORIENTATION ONLY. A max-width fallback here was wrong -- a phone
   in landscape is still narrow, so max-width:949px forced fullscreen
   sideways too (matching the old JS bug). Keyed purely on
   orientation so landscape phones get real draggable windows.
   ============================================================ */
@media (orientation: portrait) {
  body.null-os .nos-surface { inset: 0 0 calc(var(--nos-dock-h) + var(--nos-safe-b)) 0; }

  /* Every window becomes a fullscreen sheet FILLING THE SURFACE.
     Use inset:0 (position:absolute already set on .nos-win) instead
     of height:100% -- a percentage height resolved against an
     absolutely-positioned, inset-sized parent is unreliable on mobile
     Safari/Chrome and let the sheet overshoot past the surface, so its
     bottom (and content) slid under the 44px dock. inset:0 pins all
     four edges to the surface box directly, no percentage chain. */
  body.null-os .nos-win,
  body.null-os .nos-win.is-portrait {
    inset: 0 !important;
    left: 0 !important; top: 0 !important;
    right: 0 !important; bottom: 0 !important;
    width: auto !important; height: auto !important;
    border-radius: 0;
    border-left: none; border-right: none;
  }
  /* stacking still applies: the focused (topmost) app is the one
     the player sees; others sit behind it full-size. Minimized
     apps hide as usual and are re-reachable from the taskbar (V4). */
  body.null-os .nos-win-bar { cursor: default; }
  /* maximize button is meaningless when always-fullscreen */
  body.null-os .nos-win-btn.nos-max { display: none; }
  /* no resizing a fullscreen sheet */
  body.null-os .nos-win-resize { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  body.null-os .nos-cursor::after { animation: none; opacity: 1; }
  body.null-os #screen-desktop::before { animation: none; }
}

/* ============================================================
   LEGACY MODAL RESKIN (VAIHE 7 polish)
   ------------------------------------------------------------
   Critical alerts (NB.notifications.alert -> NB.modal for "server
   breached", offline-earnings reveal, etc.) render as legacy
   .modal-bg/.modal into #modal-host. Those need to be acknowledged
   (a button, unlike the auto-dismissing toast), so we KEEP the modal
   -- we just reskin it to the kali-cyan NULL//OS look while the
   desktop is on, so it reads as an OS dialog instead of a legacy
   green box punching through. CSS-only, scoped to body.null-os, so
   the legacy look is untouched when the desktop is off. We only
   override colors/border/geometry, never layout logic.
   ============================================================ */
body.null-os .modal-bg {
  /* darker scrim + sit above the taskbar/start (8000/8001) */
  background: rgba(2, 8, 10, 0.78) !important;
  z-index: 8050 !important;
  backdrop-filter: blur(2px);
}
body.null-os .modal {
  background: var(--nos-surface-2) !important;
  border: 1px solid var(--nos-line-2) !important;
  border-left: 3px solid var(--nos-cyan) !important;
  border-radius: var(--nos-radius) !important;
  box-shadow: var(--nos-shadow-focus) !important;
  color: var(--nos-text) !important;
  font-family: var(--nos-font) !important;
}
body.null-os .modal-title {
  color: var(--nos-cyan-ink) !important;
  font-family: var(--nos-font) !important;
  letter-spacing: 1px;
  text-transform: uppercase;
}
body.null-os .modal-body {
  color: var(--nos-text) !important;
  font-family: var(--nos-font) !important;
}
/* the acknowledge / primary button in NULL//OS dressing */
body.null-os .modal .btn {
  font-family: var(--nos-font) !important;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--nos-text) !important;
  background: var(--nos-surface) !important;
  border: 1px solid var(--nos-line-2) !important;
  border-radius: var(--nos-radius) !important;
}
body.null-os .modal .btn-primary {
  color: var(--nos-cyan-ink) !important;
  border-color: var(--nos-cyan) !important;
  background: rgba(var(--nos-cyan-rgb), 0.08) !important;
}
body.null-os .modal .btn-primary:hover {
  box-shadow: 0 0 12px rgba(var(--nos-cyan-rgb), 0.25) !important;
}

/* ============================================================
   BOOT OVERLAY (Linux-POST mask, VAIHE 7 polish)
   ============================================================ */
/* CSS-ONLY COLD-START MASK -- immune to JS timing/wiping.
   index.html loads desktop.js BEFORE app.js, so earlyBootMask sets
   html.null-os very early; this ::before then fills the screen with the
   boot background INSTANTLY (no DOM element app.js could wipe, no JS race).
   It hides the legacy offline-modal flash that fires on DOMContentLoaded
   (app.js init). It stays until GHOST OS finishes booting and adds
   html.nos-booted (set when bootSequence's overlay fades). The real
   .nos-boot-overlay (with the POST log) paints on top of this base. */
html.null-os::before {
  content: '';
  position: fixed; inset: 0;
  z-index: 9490;            /* just under the real boot overlay (9500) */
  background:
    radial-gradient(120% 90% at 50% 0%, #061a20 0%, #02070a 70%, #01050a 100%);
  pointer-events: none;
}
html.nos-booted::before { display: none; }   /* lifted once booted */

/* The legacy offline-earnings card (idle.js .ofp-overlay / #ofp-*) is NEVER
   shown in GHOST OS -- it broke immersion (popped mid-boot, needed a claim
   tap). GHOST captures the earnings itself and shows them as its own OS
   notification after boot instead. display:none, always. The BTC is still
   credited by legacy; only this visual is suppressed. */
body.null-os .ofp-overlay,
body.null-os #ofp-card,
body.null-os [id^="ofp-"],
html.null-os .ofp-overlay,
html.null-os #ofp-card,
html.null-os [id^="ofp-"] {
  display: none !important;
}

/* Other legacy modals (breach alert, daily summary, etc.) are legit and must
   show -- but not DURING the boot POST. Hide them only while booting; they
   appear normally once html.nos-booted is set. */
html.null-os:not(.nos-booted) #modal-host,
html.null-os:not(.nos-booted) .modal-bg,
html.null-os:not(.nos-booted) .modal {
  visibility: hidden !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

body.null-os .nos-boot-overlay,
html.null-os .nos-boot-overlay {
  position: fixed; inset: 0;
  z-index: 9500;            /* above everything incl. reskinned modals,
                               below the HC pact band */
  background:
    radial-gradient(120% 90% at 50% 0%, #061a20 0%, #02070a 70%, #01050a 100%);
  display: flex; align-items: center; justify-content: center;
  opacity: 1;
  transition: opacity 0.5s ease;
}
body.null-os .nos-boot-overlay.is-out,
html.null-os .nos-boot-overlay.is-out { opacity: 0; }
body.null-os .nos-boot-inner {
  width: min(520px, calc(100vw - 48px));
  padding: 0 24px;
}
body.null-os .nos-boot-logo {
  font-family: var(--nos-font);
  font-size: 34px; font-weight: 700; letter-spacing: 6px;
  color: var(--nos-cyan-ink);
  text-align: center;
  margin-bottom: 22px;
  text-shadow: 0 0 18px rgba(var(--nos-cyan-rgb),0.4);
}
body.null-os .nos-boot-logo span { color: var(--nos-cyan-dim); }
body.null-os .nos-boot-lines {
  font-family: var(--nos-font);
  font-size: 12px; line-height: 1.7;
  color: var(--nos-cyan-dim);
  min-height: 140px;
}
body.null-os .nos-boot-line {
  white-space: nowrap;
  overflow: hidden;
  animation: nos-boot-type 0.18s steps(24) both;
}
@keyframes nos-boot-type { from { width: 0; opacity: 0.4; } to { width: 100%; opacity: 1; } }
body.null-os .nos-boot-bar {
  margin-top: 18px;
  height: 3px;
  background: var(--nos-line);
  border-radius: 2px;
  overflow: hidden;
}
body.null-os .nos-boot-bar i {
  display: block; height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--nos-cyan-dim), var(--nos-cyan));
  animation: nos-boot-fill 5.2s ease-out forwards;
}
@keyframes nos-boot-fill { from { width: 0; } to { width: 100%; } }
@media (prefers-reduced-motion: reduce) {
  body.null-os .nos-boot-line { animation: none; }
  body.null-os .nos-boot-bar i { animation: none; width: 100%; }
}

/* ============================================================
   CALENDAR APP (VAIHE 7, daily login)
   ============================================================ */
body.null-os .nos-calapp {
  display: flex; flex-direction: column;
  height: 100%;
  padding: 12px;
  gap: 10px;
}
body.null-os .nos-cal-head {
  flex: 0 0 auto;
  display: flex; align-items: baseline; justify-content: space-between;
}
body.null-os .nos-cal-month {
  font-family: var(--nos-font);
  font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
}
body.null-os .nos-cal-streak {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}
body.null-os .nos-cal-streak b { color: var(--nos-cyan); }

body.null-os .nos-cal-prize {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 6px 0 8px;
  padding: 6px 8px;
  border: 1px solid var(--nos-border, rgba(255,255,255,0.12));
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px;
}
body.null-os .nos-cal-prize-label {
  color: var(--nos-text-dim);
  text-transform: uppercase;
}
body.null-os .nos-cal-prize-name {
  color: var(--nos-cyan);
  font-weight: bold;
}
body.null-os .nos-cal-prize-val {
  margin-left: auto;
  color: var(--nos-text-dim);
}
body.null-os .nos-cal-prize.is-ready {
  border-color: var(--nos-amber, #ffd21f);
}
body.null-os .nos-cal-prize.is-ready .nos-cal-prize-val {
  color: var(--nos-amber, #ffd21f);
}
body.null-os .nos-cal-prize.is-done {
  border-color: var(--nos-green, #12e36a);
}
body.null-os .nos-cal-prize.is-done .nos-cal-prize-val {
  color: var(--nos-green, #12e36a);
}
body.null-os .nos-cal-prize.is-locked {
  opacity: 0.7;
}

body.null-os .nos-cal-grid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  align-content: start;
}
body.null-os .nos-cal-dow {
  text-align: center;
  font-family: var(--nos-font);
  font-size: 9px; letter-spacing: 1px;
  color: var(--nos-text-dim);
  padding-bottom: 2px;
}
body.null-os .nos-cal-cell {
  aspect-ratio: 1 / 1;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--nos-font);
  font-size: 12px;
  color: var(--nos-text-dim);
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: 4px;
}
body.null-os .nos-cal-cell.is-empty {
  background: transparent; border-color: transparent;
}
body.null-os .nos-cal-cell.is-claimed {
  color: var(--nos-cyan-ink);
  background: rgba(var(--nos-cyan-rgb),0.12);
  border-color: var(--nos-cyan-dim);
}
body.null-os .nos-cal-cell.is-shield {
  color: var(--nos-coral);
  border-color: var(--nos-coral);
  background: rgba(255,120,90,0.08);
}
body.null-os .nos-cal-cell.is-today {
  box-shadow: 0 0 0 1px var(--nos-cyan) inset;
  color: var(--nos-cyan-ink);
}
body.null-os .nos-cal-foot {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  min-height: 36px;
}
body.null-os .nos-cal-claim {
  font-family: var(--nos-font);
  font-size: 12px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
  background: rgba(var(--nos-cyan-rgb),0.08);
  border: 1px solid var(--nos-cyan);
  border-radius: var(--nos-radius);
  padding: 10px 20px;
  cursor: pointer;
}
body.null-os .nos-cal-claim:hover {
  box-shadow: 0 0 12px rgba(var(--nos-cyan-rgb),0.3);
}
body.null-os .nos-cal-done {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}

/* ============================================================
   NULL_TERM APP (cosmetic terminal)
   ============================================================ */
body.null-os .nos-termapp {
  display: flex; flex-direction: column;
  height: 100%;
  padding: 10px;
  background: var(--nos-bg);
  font-family: var(--nos-font);
}
body.null-os .nos-term-out {
  flex: 1 1 auto;
  overflow-y: auto;
  font-size: 12px; line-height: 1.55;
  color: var(--nos-text);
  white-space: pre-wrap;
  word-break: break-word;
}
body.null-os .nos-term-line { white-space: pre-wrap; }
body.null-os .nos-term-echo { color: var(--nos-cyan-dim); }
body.null-os .nos-term-ok  { color: var(--nos-cyan-ink); }
body.null-os .nos-term-err { color: var(--nos-coral); }
body.null-os .nos-term-inrow {
  flex: 0 0 auto;
  display: flex; align-items: center;
  gap: 6px;
  border-top: 1px solid var(--nos-line);
  padding-top: 8px; margin-top: 6px;
}
body.null-os .nos-term-prompt {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--nos-cyan-ink);
  white-space: nowrap;
}
body.null-os .nos-term-in {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none; outline: none;
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 12px;
  caret-color: var(--nos-cyan);
}

/* ============================================================
   LEGACY VARIABLE REMAP (VAIHE 5 -- in-window game views)
   ------------------------------------------------------------
   WINDOW_SAFE game views (crew, wall, daily, missions, messages,
   skilltree, leaderboard, stats, store, intrusion...) render their
   OWN markup using LEGACY css vars (--fg, --cyan, --bg, --amber...).
   Without a remap they resolve to the legacy palette and look like
   legacy inside a GHOST OS window. Here we RE-BIND those legacy vars
   to --nos-* tokens, scoped to the window body, so any legacy view
   mounted into a window inherits the kali-cyan look -- with NO change
   to legacy code and NO effect when the desktop is off (this only
   applies under body.null-os, inside the app-window body).

   Semantics preserved: danger stays warm (red->coral, amber->warn),
   chrome/text/surfaces map to the nos equivalents. Accent hues that
   have no nos token (magenta) fall back to cyan so nothing renders in
   the old teal/green legacy accent.

   Scope note: we bind on the window body class so ONLY in-window game
   views are affected, not GHOST OS's own chrome (which already uses
   --nos-* directly). If the window body class differs, this still
   safely no-ops.
   ============================================================ */
body.null-os .nos-win-body {
  /* backgrounds / surfaces */
  --bg:            var(--nos-bg);
  --bg-elev:       var(--nos-surface);
  --bg-panel:      var(--nos-surface);
  --bg-soft:       var(--nos-surface-2);
  /* text */
  --fg:            var(--nos-text);
  --fg-bright:     var(--nos-cyan-ink);
  --fg-dim:        var(--nos-text-dim);
  --fg-faint:      var(--nos-text-dim);
  /* borders */
  --border:        var(--nos-line);
  --border-bright: var(--nos-line-2);
  /* accents (keep danger/warn semantics) */
  --cyan:          var(--nos-cyan);
  --amber:         var(--nos-warn);
  --red:           var(--nos-coral);
  --magenta:       var(--nos-cyan);   /* no nos magenta -> cyan, avoids legacy hue */
  /* fonts */
  --font-display:  var(--nos-font);
  --font-mono:     var(--nos-font);
  /* spacing / radius (harmless to align with nos radius) */
  --r-sm:          var(--nos-radius);
  --r-md:          var(--nos-radius);
}

/* ============================================================
   THEME APP (GHOST OS theme maker)
   ============================================================ */
body.null-os .nos-themeapp { height: 100%; overflow: hidden; }
body.null-os .nos-theme-scroll {
  height: 100%; overflow-y: auto;
  padding: 12px; display: flex; flex-direction: column; gap: 6px;
  font-family: var(--nos-font);
}
body.null-os .nos-theme-sec {
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
  margin-top: 8px; margin-bottom: 2px;
}
body.null-os .nos-theme-presets,
body.null-os .nos-theme-wps { display: flex; flex-wrap: wrap; gap: 6px; }
body.null-os .nos-theme-preset,
body.null-os .nos-theme-wp {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px;
  color: var(--nos-text);
  background: var(--nos-surface);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  padding: 6px 10px; cursor: pointer;
}
body.null-os .nos-theme-wp.is-on {
  border-color: var(--nos-cyan); color: var(--nos-cyan-ink);
  box-shadow: 0 0 8px rgba(var(--nos-cyan-rgb),0.25);
}
body.null-os .nos-theme-colrow {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; color: var(--nos-text);
  padding: 4px 0;
}
body.null-os .nos-theme-col {
  width: 48px; height: 28px; padding: 0;
  background: transparent; border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius); cursor: pointer;
}
body.null-os .nos-theme-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
body.null-os .nos-theme-apply,
body.null-os .nos-theme-save,
body.null-os .nos-theme-reset,
body.null-os .nos-theme-load,
body.null-os .nos-theme-copy {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-text);
  background: var(--nos-surface);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  padding: 8px 12px; cursor: pointer;
}
body.null-os .nos-theme-apply {
  color: var(--nos-cyan-ink); border-color: var(--nos-cyan);
  background: rgba(var(--nos-cyan-rgb),0.08);
}
body.null-os .nos-theme-reset { color: var(--nos-coral); border-color: var(--nos-coral); }
body.null-os .nos-theme-saved { display: flex; flex-direction: column; gap: 6px; }
body.null-os .nos-theme-slot {
  display: flex; align-items: center; gap: 6px;
  background: var(--nos-surface); border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius); padding: 6px 8px; cursor: pointer;
  font-family: var(--nos-font); font-size: 11px; color: var(--nos-text);
}
body.null-os .nos-theme-swatch {
  width: 16px; height: 16px; border-radius: 3px;
  border: 1px solid var(--nos-line-2); flex: 0 0 auto;
}
body.null-os .nos-theme-slotlbl { margin-left: 4px; color: var(--nos-text-dim); }
body.null-os .nos-theme-empty { font-size: 11px; color: var(--nos-text-dim); padding: 4px 0; }
body.null-os .nos-theme-seedrow { display: flex; gap: 6px; }
body.null-os .nos-theme-seedin {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--nos-font); font-size: 12px;
  color: var(--nos-text); background: var(--nos-bg);
  border: 1px solid var(--nos-line-2); border-radius: var(--nos-radius);
  padding: 8px 10px; outline: none;
}
body.null-os .nos-theme-seedin:focus { border-color: var(--nos-cyan); }
body.null-os .nos-theme-seedout {
  display: flex; align-items: center; gap: 8px; margin-top: 6px; min-height: 24px;
}
body.null-os .nos-theme-seedcode {
  font-family: var(--nos-font); font-size: 13px; letter-spacing: 1px;
  color: var(--nos-cyan-ink);
  background: var(--nos-surface); border: 1px solid var(--nos-cyan-dim);
  border-radius: var(--nos-radius); padding: 6px 10px;
  user-select: all;
}

/* ============================================================
   NULL_PLAYER -- Kick live player fills the window body.
   Appended for desktop.css .6.23
   ============================================================ */
.nos-playerapp {
  padding: 0 !important;
  height: 100%;
  background: #000;
}
.nos-player-frame {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  background: #000;
  overflow: hidden;
}
.nos-player-iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.nos-player-load {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--nos-font);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--nos-cyan-ink);
  background: var(--nos-bg);
  z-index: 1;
  pointer-events: none;
}

/* START menu scroll affordance -- fade + chevron when the app list scrolls */
body.null-os .nos-start-grid { scroll-behavior: smooth; }
body.null-os .nos-start-scrollcue {
  position: absolute; left: 1px; right: 1px; bottom: 40px; height: 46px;
  pointer-events: none; display: flex; align-items: flex-end;
  justify-content: center; padding-bottom: 5px;
  background: linear-gradient(to bottom, rgba(10,19,25,0) 0%, var(--nos-surface) 88%);
  opacity: 0; transition: opacity .18s ease; z-index: 2;
}
body.null-os .nos-startmenu.is-scrollable:not(.at-bottom) .nos-start-scrollcue { opacity: 1; }
body.null-os .nos-start-scrollcue .nos-start-chev {
  font-size: 13px; line-height: 1; color: var(--nos-cyan);
  animation: nos-start-bob 1.4s ease-in-out infinite;
}
@keyframes nos-start-bob {
  0%,100% { transform: translateY(0); opacity: .7; }
  50% { transform: translateY(3px); opacity: 1; }
}

/* ---- CHAT in a GHOST window: fit the window, not the viewport ---- *
   Legacy chat sizes #chat-content to calc(100vh - 200px) + min-height:400px
   (inline, chat.js). In a GHOST window the host is far shorter than the
   viewport, so that inline height overflows .nos-win-body (overflow:auto),
   pushing the tab row up out of view. Scoped to body.null-os only, so legacy
   fullscreen chat is untouched. !important is required to beat chat.js's
   inline height. min-height:0 lets the flex child shrink to the window. */
body.null-os .nos-win-body .chat-mode {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
body.null-os .nos-win-body #chat-content {
  height: auto !important;
  min-height: 0 !important;
  flex: 1 1 auto !important;
}
/* Keep the tab row from shrinking; only the message list scrolls. */
body.null-os .nos-win-body .chat-mode > *:first-child {
  flex: 0 0 auto;
}
body.null-os .nos-win-body:has(.chat-mode) {
  overflow: hidden;
}

/* ---- HELP app (native, reuses NB.help.getContent) --------- */
body.null-os .nos-helpapp { display:flex; flex-direction:column; height:100%; }
body.null-os .nos-help-nav {
  display:flex; flex-wrap:wrap; gap:4px; padding:8px;
  border-bottom:1px solid var(--nos-line);
  flex:0 0 auto;
}
body.null-os .nos-help-tab {
  flex:1 0 auto; padding:5px 9px;
  background:var(--nos-bg-2); color:var(--nos-text-dim);
  border:1px solid var(--nos-line);
  border-radius:var(--nos-radius);
  font-family:var(--nos-font); font-size:11px;
  cursor:pointer; white-space:nowrap;
}
body.null-os .nos-help-tab:hover { border-color:var(--nos-line-2); color:var(--nos-text); }
body.null-os .nos-help-tab.is-active {
  background:rgba(var(--nos-cyan-rgb),0.12);
  border-color:var(--nos-cyan); color:var(--nos-cyan);
}
body.null-os .nos-help-body {
  flex:1 1 auto; overflow-y:auto; padding:14px 16px;
  line-height:1.7; color:var(--nos-text);
}
body.null-os .nos-help-empty { padding:18px; color:var(--nos-text-dim); }

/* ============================================================
   KILO app -- orange accent (amber = grey-market operator, matches
   the Kilo panel's amber left-border in the game). APPEND to the
   GHOST OS desktop stylesheet (css/desktop.css). Cosmetic only.
   Selectors verified against desktop.js markup.
   ============================================================ */

/* START-menu launcher */
.nos-start-launch[data-launch="kilo"] .nos-start-ico { color: #ff9d2e; }

/* Desktop icon (pinned) */
.nos-dicon[data-app="kilo"] .nos-dicon-ico { color: #ff9d2e; }

/* Taskbar button */
.nos-task[data-task="kilo"] .nos-task-ico { color: #ff9d2e; }

/* ============================================================
   NFT GALLERY (NCO Artgen DLC) -- orange accent.
   Same orange as KILO above: both are the grey-market side of the
   game, and the OS palette has no second orange that reads as one.
   If the two ever need telling apart at a glance, change THIS value,
   not Kilo's -- Kilo's has been that colour since the app shipped.
   App styles live in css/artgen.css, not here. Cosmetic only.
   ============================================================ */

/* START-menu launcher */
.nos-start-launch[data-launch="artgen"] .nos-start-ico { color: #ff9d2e; }

/* Desktop icon (pinned) */
.nos-dicon[data-app="artgen"] .nos-dicon-ico { color: #ff9d2e; }

/* Taskbar button */
.nos-task[data-task="artgen"] .nos-task-ico { color: #ff9d2e; }
/* ============================================================
   DATA CENTER (DLC) -- GHOST OS native app
   ============================================================ */
.dc-panel { padding: 10px; }

.dc-summary {
  display: flex; flex-wrap: wrap; gap: 14px;
  padding: 9px 11px; margin-bottom: 8px;
  border: 1px solid var(--border); background: var(--bg-dim);
  font-size: 12px; color: var(--fg-dim);
}
.dc-sum-item b { color: var(--cyan); font-weight: 600; }

.dc-msg { min-height: 16px; margin-bottom: 8px; font-size: 12px; }
.dc-msg.is-ok  { color: var(--cyan); }
.dc-msg.is-bad { color: var(--red); }

.dc-locked {
  padding: 14px; font-size: 12px; line-height: 1.6; color: var(--fg-dim);
}

/* Rows are 5 wide on desktop. The machine grid wraps to 2 rows of 5;
   the upgrade grid is a single row of 5. */
.dc-grid {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 6px; margin-bottom: 8px;
}

.dc-box {
  border: 1px solid var(--border); background: var(--bg-dim);
  padding: 8px; font-size: 12px; min-height: 82px;
}
.dc-machine { cursor: pointer; }
.dc-machine:hover, .dc-empty:hover { border-color: var(--fg-dim); }
.dc-box.is-sel { border-color: var(--cyan); }

.dc-box-top { display: flex; justify-content: space-between; align-items: baseline; }
.dc-slot { color: var(--fg-dim); }
.dc-hp { color: var(--cyan); font-weight: 600; }
.dc-nick {
  margin: 4px 0; color: var(--fg); font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dc-levels { color: var(--fg-dim); font-size: 11px; }
.dc-rep { margin-top: 3px; color: var(--fg-dim); font-size: 11px; }
.dc-warn { color: var(--red); }

.dc-empty { display: flex; flex-direction: column; justify-content: space-between; }
.dc-empty-label { color: var(--fg-dim); opacity: 0.6; margin: 4px 0; }

.dc-target { margin: 5px 0 7px; font-size: 12px; color: var(--fg-dim); }
.dc-target b { color: var(--cyan); }

.dc-up { display: flex; flex-direction: column; }
.dc-up-name { color: var(--fg-dim); letter-spacing: 0.5px; }
.dc-up-lvl { font-size: 22px; color: var(--fg); margin: 3px 0; }
.dc-up-note { color: var(--fg-dim); opacity: 0.75; font-size: 11px; margin-bottom: 6px; }

.dc-btn {
  width: 100%; padding: 4px 5px; cursor: pointer;
  border: 1px solid var(--border); background: transparent;
  color: var(--cyan); font: inherit; font-size: 12px;
}
.dc-btn:hover:not(:disabled) { border-color: var(--cyan); background: rgba(77,226,255,0.07); }
.dc-btn:disabled { color: var(--fg-dim); opacity: 0.45; cursor: default; }

.dc-actions { margin-top: 4px; }
.dc-btn-wide { width: auto; padding: 5px 14px; }

/* Portrait / phone: the 5-wide grid is unreadable under ~560px, so both
   grids drop to 2 columns. The machine grid stops being "rows 1-2" there,
   which is fine -- slot numbers carry the identity, not the position. */
@media (max-width: 560px) {
  .dc-grid { grid-template-columns: repeat(2, 1fr); }
  .dc-summary { gap: 8px; font-size: 10px; }
}

/* DC rent bar -- own row above the grid, per WpE's layout call */
.dc-rent {
  padding: 9px 11px; margin-bottom: 8px;
  border: 1px solid var(--amber); background: var(--bg-dim);
  font-size: 12px; color: var(--fg);
}
.dc-rent b { color: var(--amber); }
.dc-rent-line { margin-bottom: 7px; line-height: 1.5; }
.dc-rent-note { color: var(--fg-dim); font-size: 11px; }
.dc-rent-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.dc-rent-btns .dc-btn { color: var(--amber); }
.dc-rent-btns .dc-btn:hover:not(:disabled) {
  border-color: var(--amber); background: rgba(255,165,0,0.08);
}
.dc-rent-ok {
  border-color: var(--border); color: var(--fg-dim); line-height: 1.5;
}
.dc-rent-ok b { color: var(--fg-dim); }

/* DC bar -- one box: stats + upkeep centred, pay buttons on the flanks.
   flex-wrap means a narrow window stacks the three parts instead of
   squeezing them; the buttons keep their own line and the text stays
   centred either way. */
.dc-bar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 8px;
  padding: 9px 11px; margin-bottom: 8px;
  border: 1px solid var(--border); background: var(--bg-dim);
  font-size: 12px; color: var(--fg-dim);
}
.dc-bar.is-owing { border-color: var(--amber); }

.dc-bar-mid {
  flex: 1 1 240px; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  text-align: center;
}
.dc-bar-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; }
.dc-bar-row b { color: var(--cyan); font-weight: 600; }
.dc-bar-sub { font-size: 11px; line-height: 1.5; }
.dc-bar.is-owing .dc-bar-sub b { color: var(--amber); }
.dc-bar-sub i { font-style: normal; opacity: 0.7; }

.dc-bar-act {
  flex: 0 0 auto; width: auto; padding: 6px 12px;
  color: var(--amber);
}
.dc-bar-act:hover:not(:disabled) {
  border-color: var(--amber); background: rgba(255,165,0,0.08);
}

/* Under ~440px the flanking buttons would each get a sliver, so let them
   go full width under the text rather than clipping. */
@media (max-width: 440px) {
  .dc-bar-act { flex: 1 1 100%; }
}

/* Repair button: amber, same language as the upkeep bar -- both are about
   damage done by unpaid rent. */
.dc-btn-fix { color: var(--amber); margin-left: 6px; }
.dc-btn-fix:hover:not(:disabled) {
  border-color: var(--amber); background: rgba(255,165,0,0.08);
}

/* Scrap button: red and last in the row. Destroys hardware, refunds
   nothing, and burns the name -- it should not look like the others. */
.dc-btn-scrap { color: var(--red); margin-left: 6px; }
.dc-btn-scrap:hover:not(:disabled) {
  border-color: var(--red); background: rgba(255,70,70,0.08);
}

/* ------------------------------------------------------------
   NULL//BANK -- GHOST OS native app (js/modes/bank.js)
   Spec: ~/nullbyte-api/docs/NULLBANK_SPEC.md kohta 8.

   Readability is a hard requirement here, not a preference: these
   are money figures that must be legible at a glance. Balances are
   deliberately large, and nothing important is rendered in
   --nos-text-dim on a dark surface.
   ------------------------------------------------------------ */
.bank-panel { padding: 10px; }

.bank-load,
.bank-empty {
  color: var(--nos-text-dim);
  padding: 14px 4px;
  font-size: 13px;
}

/* ---- balances: three pools, never summed (spec 11.3) ---- */
.bank-bal {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.bank-bal-box {
  flex: 1 1 150px;
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  padding: 10px 12px;
}
.bank-bal-k {
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--nos-cyan-dim);
}
.bank-bal-v {
  font-size: 26px;
  line-height: 1.15;
  font-weight: 600;
  color: var(--nos-cyan-ink);
  margin: 2px 0 3px;
  word-break: break-all;
}
/* NLC is never rounded, so its value can be long. Smaller only when it
   has to be -- still well above body size. */
.bank-bal-v.bank-exact { font-size: 20px; }
.bank-bal-n {
  font-size: 11px;
  color: var(--nos-text-dim);
}

/* ---- message line ---- */
.bank-msg { min-height: 18px; font-size: 13px; margin: 6px 2px; }
.bank-msg.is-ok  { color: var(--nos-cyan); }
.bank-msg.is-bad { color: var(--nos-warn); }

/* ---- send ---- */
.bank-send {
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  padding: 10px 12px;
  margin-bottom: 12px;
}
.bank-send-title {
  font-size: 12px;
  letter-spacing: 0.12em;
  color: var(--nos-cyan-dim);
  margin-bottom: 8px;
}
.bank-send-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.bank-in {
  flex: 1 1 140px;
  min-width: 0;
  background: var(--nos-bg-2);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 15px;
  padding: 9px 10px;
}
.bank-in:focus {
  outline: none;
  border-color: var(--nos-cyan-dim);
}
.bank-in::placeholder { color: var(--nos-text-dim); }
.bank-fee {
  margin-top: 8px;
  font-size: 13px;
  color: var(--nos-cyan-ink);
}

/* ---- buttons ---- */
.bank-btn {
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  border-radius: var(--nos-radius);
  color: var(--nos-cyan);
  font-family: var(--nos-font);
  font-size: 14px;
  padding: 9px 16px;
  cursor: pointer;
}
.bank-btn:hover:not(:disabled) {
  border-color: var(--nos-cyan-dim);
  background: rgba(var(--nos-cyan-rgb), 0.08);
}
.bank-btn:disabled { opacity: 0.45; cursor: default; }
.bank-btn-sm { font-size: 12px; padding: 5px 10px; }

/* ---- sections ---- */
.bank-sec { margin-bottom: 14px; }
.bank-sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--nos-cyan-dim);
  border-bottom: 1px solid var(--nos-line);
  padding-bottom: 6px;
  margin-bottom: 4px;
}

/* ---- rows ---- */
.bank-rows { display: flex; flex-direction: column; }
.bank-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--nos-line);
}
.bank-row:last-child { border-bottom: none; }
.bank-row-l { min-width: 0; }
.bank-src {
  font-size: 14px;
  color: var(--nos-text);
  word-break: break-word;
}
.bank-when {
  font-size: 12px;
  color: var(--nos-text-dim);
  margin-top: 2px;
}
.bank-who { color: var(--nos-cyan-dim); }
.bank-tag {
  display: inline-block;
  font-size: 10px;
  letter-spacing: 0.08em;
  border: 1px solid var(--nos-line-2);
  border-radius: 2px;
  padding: 0 4px;
  margin-left: 4px;
  color: var(--nos-text-dim);
  vertical-align: middle;
}
.bank-amt {
  font-size: 17px;
  font-weight: 600;
  white-space: nowrap;
  text-align: right;
}
/* Money in / money out. Colour is a second signal only -- the sign is
   always printed, so this still reads without colour vision. */
.bank-amt.is-in  { color: var(--nos-cyan); }
.bank-amt.is-out { color: var(--nos-coral); }
.bank-amt.bank-exact { font-size: 15px; }
.bank-feetag {
  display: block;
  font-size: 11px;
  font-weight: 400;
  color: var(--nos-text-dim);
}

/* ------------------------------------------------------------
   NULL//BANK -- scrollable row lists.

   Both lists can hold up to 200 rows (MAX_ROWS_PER_SOURCE). Without a
   cap, the balances and the send box scroll off the top and the player
   has to thumb back up to see what they actually have -- and with two
   lists stacked, reaching NULLcoin meant scrolling past everything BTC.

   Each list gets its own box instead. Balances and send stay put.
   ------------------------------------------------------------ */
.bank-rows {
  max-height: min(46vh, 420px);
  overflow-y: auto;
  /* Stop a flick that hits the end of the list from scrolling the
     window behind it. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Room for the scrollbar so it never sits on top of an amount. */
  padding-right: 10px;
}

/* Firefox. */
.bank-rows {
  scrollbar-width: auto;
  scrollbar-color: var(--nos-line-2) transparent;
}

/* WebKit / Blink. Deliberately wide: the default 6-8px bar is a poor
   touch target, and on iOS it is invisible until you already scroll. */
.bank-rows::-webkit-scrollbar {
  width: 14px;
}
.bank-rows::-webkit-scrollbar-track {
  background: var(--nos-bg-2);
  border-radius: var(--nos-radius);
}
.bank-rows::-webkit-scrollbar-thumb {
  background: var(--nos-line-2);
  border-radius: 7px;
  /* Inset the thumb without shrinking the grabbable area. */
  border: 3px solid var(--nos-bg-2);
  min-height: 40px;
}
.bank-rows::-webkit-scrollbar-thumb:hover {
  background: var(--nos-cyan-dim);
}

/* A fade at the bottom edge, so a full list reads as "there is more"
   rather than ending mid-row. */
.bank-sec { position: relative; }
.bank-sec .bank-rows { position: relative; }
.bank-sec::after {
  content: '';
  position: absolute;
  left: 0;
  right: 14px;          /* clear of the scrollbar */
  bottom: 0;
  height: 22px;
  pointer-events: none;
  background: linear-gradient(to bottom,
              rgba(5, 8, 10, 0), var(--nos-bg));
}

/* ------------------------------------------------------------
   NULL//BANK -- BTC / NULLCOIN tabs.

   Replaces the two stacked lists. On a phone those filled the screen
   twice over: balances and the send box scrolled away, and reaching
   NULLCOIN meant thumbing past up to 200 BTC rows.

   One list at a time, and a shorter box -- so balances, send and the
   list all fit without scrolling the window itself.
   ------------------------------------------------------------ */
.bank-tabs {
  display: flex;
  align-items: stretch;
  gap: 6px;
  border-bottom: 1px solid var(--nos-line);
  padding-bottom: 6px;
  margin-bottom: 4px;
}
.bank-tab {
  flex: 1 1 auto;
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  color: var(--nos-text-dim);
  font-family: var(--nos-font);
  font-size: 13px;
  letter-spacing: 0.08em;
  /* Tall enough to hit with a thumb without aiming. */
  padding: 10px 8px;
  cursor: pointer;
}
.bank-tab:hover { color: var(--nos-text); }
.bank-tab.is-on {
  background: var(--nos-surface-2);
  border-color: var(--nos-cyan-dim);
  color: var(--nos-cyan);
}
/* Row count on the tab, so an empty list is visible before tapping. */
.bank-tab-n {
  font-size: 11px;
  color: var(--nos-text-dim);
  margin-left: 4px;
}
.bank-tab.is-on .bank-tab-n { color: var(--nos-cyan-dim); }

.bank-sec-note {
  font-size: 11px;
  color: var(--nos-text-dim);
  margin: 0 2px 4px;
}

/* Shorter than the earlier 46vh: only one list shows now, and the
   scrollbar handles the rest. */
.bank-rows { max-height: clamp(220px, 38vh, 260px); }

/* ------------------------------------------------------------
   NULL//BANK -- four tabs (BTC / REP / KICK / NULLCOIN).

   With four of them the refresh button no longer fits on the tab row,
   so it moved to its own line next to the window note. The tabs wrap
   instead of shrinking below a readable size.
   ------------------------------------------------------------ */
.bank-tabs {
  flex-wrap: wrap;
}
.bank-tab {
  /* Two per row on a narrow window, four on a wide one -- never
     squeezed to unreadable. */
  flex: 1 1 calc(50% - 3px);
  min-width: 92px;
}
.bank-sec-head2 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0 2px 4px;
}
.bank-sec-head2 .bank-sec-note { margin: 0; }

/* Currency suffix on Kick rows: the tab mixes BTC and REP payouts, so
   the unit has to be on the row itself. */
.bank-cur {
  font-size: 11px;
  font-weight: 400;
  color: var(--nos-text-dim);
  margin-left: 2px;
}

/* NULL//BANK -- idle mining strip.
   Deliberately NOT a .bank-bal-box: this is production since the player's
   own midnight, not a balance, and it must never read as a fifth pool to
   be added to the others. Hence one flat row, no card, no border box.
   Append to css/desktop.css next to the other .bank-* rules (~r.1644-1943)
   until the bank gets its own stylesheet. */
.bank-idle {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin: 6px 0 2px;
  padding: 7px 10px;
  background: var(--bg-soft);
  border-left: 2px solid var(--cyan);
  border-radius: 3px;
  font-size: 12px;
  line-height: 1.4;
}
.bank-idle-k {
  color: var(--cyan);
  letter-spacing: 1px;
  font-weight: bold;
}
.bank-idle-since {
  color: var(--fg-dim);
  font-size: 11px;
}
.bank-idle-v {
  margin-left: auto;
  color: var(--amber);
  font-weight: bold;
  font-size: 14px;
}

/* ============================================================
   ECHO -- orange accent. Same orange as KILO and the NFT GALLERY:
   all three are the grey-market side of the game. App styles live in
   css/echo.css, not here. Cosmetic only.
   Selectors verified against desktop.js markup.
   ============================================================ */

/* START-menu launcher */
.nos-start-launch[data-launch="echo"] .nos-start-ico { color: #ff9d2e; }

/* Desktop icon (pinned) */
.nos-dicon[data-app="echo"] .nos-dicon-ico { color: #ff9d2e; }

/* Taskbar button */
.nos-task[data-task="echo"] .nos-task-ico { color: #ff9d2e; }


/* ============================================================
   START MENU FOLDERS + ETH0 // ORGANIZE
   ============================================================
   The START menu is a tile grid (.nos-start-grid, auto-fill minmax 96px),
   so a folder is a TILE shaped like an app, and its contents open in a
   full-width drawer underneath. Everything here is scoped under
   body.null-os to match the rules it sits beside -- unscoped, the
   existing `body.null-os .nos-start-item` would out-specify it.
   ============================================================ */
body.null-os .nos-start-item.is-folder .nos-start-launch svg { color: var(--nos-amber, #ffd21f); }
/* An OPEN folder spans the grid and becomes the header of its own
   drawer. As a one-cell tile it left two empty cells beside it, which
   reads as a layout accident rather than as "these belong together". */
body.null-os .nos-start-item.is-folder.is-open {
  grid-column: 1 / -1;
  border-color: var(--nos-amber, #ffd21f);
  border-bottom-color: transparent;
  background: rgba(255, 210, 31, 0.07);
  border-radius: var(--nos-radius) var(--nos-radius) 0 0;
}
body.null-os .nos-start-item.is-folder.is-open .nos-start-launch {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 8px 10px;
}
body.null-os .nos-start-item.is-folder.is-open .nos-start-launch svg {
  width: 18px; height: 18px;
}
/* The count sits where an app tile puts its pin, so the two never collide. */
body.null-os .nos-start-count {
  position: absolute;
  top: 3px;
  right: 5px;
  font-size: 9px;
  color: var(--nos-amber, #ffd21f);
  opacity: 0.85;
  pointer-events: none;
}

/* The drawer spans the whole grid whatever the column count is. */
body.null-os .nos-start-drawer {
  grid-column: 1 / -1;
  border: 1px solid var(--nos-amber, #ffd21f);
  border-top: 0;
  background: rgba(255, 210, 31, 0.05);
  border-radius: 0 0 var(--nos-radius) var(--nos-radius);
  padding: 4px;
  margin-bottom: 4px;
}
body.null-os .nos-start-subgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 4px;
}

/* ---- the ORGANIZE app ---- */
body.null-os .nos-orgapp { overflow-y: auto; font-size: 12px; }
body.null-os .nos-org-wrap {
  display: flex; flex-direction: column; gap: 8px; padding: 8px;
}
body.null-os .nos-org-intro { margin: 0; font-size: 11px; opacity: 0.7; line-height: 1.5; }
body.null-os .nos-org-head {
  margin-top: 6px;
  font-size: 10px; letter-spacing: 1px;
  color: var(--nos-cyan-dim);
  border-bottom: 1px solid var(--nos-line);
  padding-bottom: 3px;
}
body.null-os .nos-org-new { display: flex; gap: 6px; }
body.null-os .nos-org-newname { flex: 1 1 auto; min-width: 0; }
body.null-os .nos-org-newname,
body.null-os .nos-org-name,
body.null-os .nos-org-pick {
  background: var(--nos-bg-2);
  border: 1px solid var(--nos-line-2);
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 12px;
  padding: 5px 7px;
  outline: none;
  box-sizing: border-box;
}
body.null-os .nos-org-newname:focus,
body.null-os .nos-org-name:focus,
body.null-os .nos-org-pick:focus { border-color: var(--nos-cyan); }

body.null-os .nos-org-add,
body.null-os .nos-org-del,
body.null-os .nos-org-chip {
  background: var(--nos-surface-2);
  border: 1px solid var(--nos-line-2);
  color: var(--nos-text);
  font-family: var(--nos-font);
  font-size: 11px;
  padding: 5px 9px;
  border-radius: var(--nos-radius);
  cursor: pointer;
}
body.null-os .nos-org-add:hover,
body.null-os .nos-org-chip:hover { border-color: var(--nos-cyan); color: var(--nos-cyan-ink); }
body.null-os .nos-org-del:hover { border-color: var(--nos-coral); color: var(--nos-coral); }

body.null-os .nos-org-folder {
  border: 1px solid var(--nos-line-2);
  border-left: 2px solid var(--nos-amber, #ffd21f);
  border-radius: var(--nos-radius);
  padding: 7px;
  display: flex; flex-direction: column; gap: 6px;
}
body.null-os .nos-org-frow { display: flex; align-items: center; gap: 6px; }
body.null-os .nos-org-fico { width: 20px; height: 20px; flex: 0 0 20px; }
body.null-os .nos-org-fico svg { width: 100%; height: 100%; color: var(--nos-amber, #ffd21f); }
body.null-os .nos-org-name { flex: 1 1 auto; min-width: 0; }
body.null-os .nos-org-iconpick {
  display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
}
body.null-os .nos-org-hint { font-size: 10px; color: var(--nos-cyan-dim); }
body.null-os .nos-org-chip { display: inline-flex; align-items: center; gap: 4px; }
body.null-os .nos-org-chip.is-on { border-color: var(--nos-amber, #ffd21f); color: var(--nos-amber, #ffd21f); }
body.null-os .nos-org-chip-ico { width: 13px; height: 13px; display: inline-block; }
body.null-os .nos-org-chip-ico svg { width: 100%; height: 100%; }
body.null-os .nos-org-empty { font-size: 10px; opacity: 0.55; margin: 0; }

body.null-os .nos-org-row {
  display: flex; align-items: center; gap: 7px;
  padding: 3px 0;
  border-bottom: 1px solid var(--nos-line);
}
body.null-os .nos-org-ico { width: 17px; height: 17px; flex: 0 0 17px; }
body.null-os .nos-org-ico svg { width: 100%; height: 100%; color: var(--nos-cyan); }
/* A label can be long ('TERMINAL MASTERS'); it must not squeeze the
   picker off the row. */
body.null-os .nos-org-label {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.null-os .nos-org-pick { flex: 0 0 38%; max-width: 150px; }
body.null-os .nos-org-note { font-size: 10px; color: var(--nos-cyan-dim); min-height: 1.2em; }

/* ============================================================
   CALENDAR :: admin-made events (app/routes/events.py)
   ============================================================ */
/* Event day: its own marker, independent of the daily-login fill/border.
   A coral corner flag + an EVENT tag under the number. */
body.null-os .nos-cal-cell { position: relative; overflow: hidden; }
body.null-os .nos-cal-cell { flex-direction: column; gap: 2px; }
body.null-os .nos-cal-cell.is-event::after {
  content: '';
  position: absolute; top: 0; right: 0;
  border-style: solid;
  border-width: 0 14px 14px 0;
  border-color: transparent var(--nos-coral) transparent transparent;
}
body.null-os .nos-cal-evtag {
  font-size: 8px; letter-spacing: 1px; text-transform: uppercase;
  line-height: 1;
  color: #05080a;
  background: var(--nos-coral);
  padding: 2px 4px;
  border-radius: 2px;
}
body.null-os .nos-cal-legend {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-family: var(--nos-font); font-size: 10px; letter-spacing: 1px;
  color: var(--nos-text-dim);
}
body.null-os .nos-cal-lg-login,
body.null-os .nos-cal-lg-ev {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
}
body.null-os .nos-cal-lg-login {
  background: rgba(var(--nos-cyan-rgb),0.12);
  border: 1px solid var(--nos-cyan-dim);
}
body.null-os .nos-cal-lg-ev { background: var(--nos-coral); margin-left: 8px; }
/* day cells are buttons (doors): strip the button chrome, keep the cell look */
body.null-os button.nos-cal-cell {
  box-sizing: border-box;
  width: 100%; min-width: 0; padding: 0; margin: 0;
  font-family: var(--nos-font);
  cursor: pointer;
}
/* the open door: cyan, so it never reads as the coral event marker */
body.null-os .nos-cal-cell.is-sel {
  outline: 2px solid var(--nos-cyan);
  outline-offset: 1px;
  color: var(--nos-cyan-ink);
}
body.null-os .nos-calapp { overflow-y: auto; }
body.null-os .nos-cal-grid { flex: 0 0 auto; }

body.null-os .nos-cal-nav { display: flex; align-items: center; gap: 6px; }
body.null-os .nos-cal-navbtn {
  font-family: var(--nos-font); font-size: 15px; line-height: 1;
  color: var(--nos-cyan-ink);
  background: transparent;
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  padding: 2px 8px;
  cursor: pointer;
}
body.null-os .nos-cal-navbtn:hover { border-color: var(--nos-cyan); }
body.null-os .nos-cal-navbtn[disabled] { opacity: 0.25; cursor: default; border-color: var(--nos-line); }

/* the opened door */
body.null-os .nos-cal-door {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--nos-line);
  border-radius: var(--nos-radius);
  background: var(--nos-surface);
  font-family: var(--nos-font);
}
body.null-os .nos-cal-door-date {
  font-size: 11px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--nos-cyan-ink);
}
body.null-os .nos-cal-door-line { font-size: 11px; color: var(--nos-text-dim); }
body.null-os .nos-cal-door-empty { font-size: 11px; color: var(--nos-text-dim); font-style: italic; }
body.null-os .nos-cal-door-ev {
  display: flex; flex-direction: column; gap: 3px;
  padding: 6px 8px;
  border-left: 3px solid var(--nos-coral);
  background: rgba(255,120,90,0.06);
}
body.null-os .nos-cal-door-ev.is-ended { border-left-color: var(--nos-line); opacity: 0.75; }
body.null-os .nos-cal-door-ev-head { display: flex; align-items: baseline; gap: 8px; }
body.null-os .nos-cal-door-chip {
  font-size: 9px; letter-spacing: 1px; text-transform: uppercase;
  padding: 1px 6px; border: 1px solid currentColor; border-radius: 2px;
  color: var(--nos-text-dim);
}
body.null-os .nos-cal-door-ev.is-live .nos-cal-door-chip { color: var(--nos-coral); }
body.null-os .nos-cal-door-ev.is-soon .nos-cal-door-chip { color: var(--nos-amber, #ffd21f); }
body.null-os .nos-cal-door-title { font-size: 12px; color: var(--nos-text); overflow-wrap: anywhere; }
body.null-os .nos-cal-door-meta { font-size: 10px; letter-spacing: 1px; color: var(--nos-coral); }
body.null-os .nos-cal-door-desc { font-size: 11px; color: var(--nos-text-dim); line-height: 1.4; }
body.null-os .nos-cal-door-open {
  align-self: flex-start;
  font-family: var(--nos-font); font-size: 10px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-coral);
  background: transparent;
  border: 1px solid var(--nos-coral);
  border-radius: var(--nos-radius);
  padding: 4px 10px;
  cursor: pointer;
}

/* ============================================================
   EVENT RANKINGS -- GHOST OS native app (js/modes/events.js)
   ============================================================ */
body.null-os .nos-evapp { height: 100%; }
body.null-os .nos-ev-panel {
  display: flex; flex-direction: column;
  height: 100%;
  padding: 12px; gap: 8px;
  font-family: var(--nos-font);
}
body.null-os .nos-ev-top {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
}
body.null-os .nos-ev-head {
  font-size: 13px; letter-spacing: 1px;
  color: var(--nos-coral);
}
body.null-os .nos-ev-refresh,
body.null-os .nos-ev-back,
body.null-os .nos-ev-btn {
  font-family: var(--nos-font);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--nos-coral);
  background: rgba(255,120,90,0.06);
  border: 1px solid var(--nos-coral);
  border-radius: var(--nos-radius);
  padding: 6px 12px;
  cursor: pointer;
}
body.null-os .nos-ev-refresh[disabled],
body.null-os .nos-ev-btn[disabled] { opacity: 0.5; cursor: default; }
body.null-os .nos-ev-back { align-self: flex-start; margin-bottom: 8px; }
body.null-os .nos-ev-note {
  flex: 0 0 auto;
  font-size: 11px; color: var(--nos-text-dim);
}
body.null-os .nos-ev-note.is-bad { color: var(--nos-warn); }
body.null-os .nos-ev-scroll {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px;
}
body.null-os .nos-ev-sec {
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--nos-text-dim);
  margin-top: 4px;
}
body.null-os .nos-ev-empty {
  font-size: 12px; color: var(--nos-text-dim);
  padding: 24px 0; text-align: center;
}
body.null-os .nos-ev-card {
  display: flex; flex-direction: column; gap: 4px;
  padding: 10px;
  background: var(--nos-surface);
  border: 1px solid var(--nos-line);
  border-left: 3px solid var(--nos-line);
  border-radius: var(--nos-radius);
}
body.null-os .nos-ev-card.is-active { border-left-color: var(--nos-coral); }
body.null-os .nos-ev-card.is-tally { border-left-color: var(--nos-amber, #ffd21f); }
body.null-os .nos-ev-card.is-final { opacity: 0.8; }
body.null-os .nos-ev-card .nos-ev-btn { align-self: flex-start; margin-top: 4px; }
body.null-os .nos-ev-card-head,
body.null-os .nos-ev-board-head {
  display: flex; align-items: baseline; gap: 8px;
}
body.null-os .nos-ev-chip {
  flex: 0 0 auto;
  font-size: 9px; letter-spacing: 1px;
  padding: 1px 6px;
  border: 1px solid currentColor;
  border-radius: 2px;
  color: var(--nos-text-dim);
}
body.null-os .is-active .nos-ev-chip { color: var(--nos-coral); }
body.null-os .is-tally .nos-ev-chip { color: var(--nos-amber, #ffd21f); }
body.null-os .nos-ev-title {
  font-size: 13px; color: var(--nos-text);
  overflow-wrap: anywhere;
}
body.null-os .nos-ev-metric { font-size: 11px; color: var(--nos-coral); letter-spacing: 1px; }
body.null-os .nos-ev-desc { font-size: 11px; color: var(--nos-text-dim); line-height: 1.4; }
body.null-os .nos-ev-time { font-size: 11px; color: var(--nos-text-dim); }
body.null-os .nos-ev-stand {
  display: flex; gap: 16px; flex-wrap: wrap;
  font-size: 11px; color: var(--nos-text-dim);
}
body.null-os .nos-ev-stand b { color: var(--nos-text); font-weight: normal; }
body.null-os .nos-ev-goal {
  height: 4px; background: var(--nos-line); border-radius: 2px; overflow: hidden;
}
body.null-os .nos-ev-goal i { display: block; height: 100%; background: var(--nos-coral); }
body.null-os .nos-ev-goal-txt { font-size: 10px; color: var(--nos-text-dim); }
body.null-os .nos-ev-prizes {
  display: flex; flex-direction: column; gap: 2px;
  margin-top: 2px;
  font-size: 11px; color: var(--nos-text);
}
body.null-os .nos-ev-prize-rank { color: var(--nos-amber, #ffd21f); }

body.null-os .nos-ev-rewards {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px;
  border: 1px solid var(--nos-amber, #ffd21f);
  border-radius: var(--nos-radius);
}
body.null-os .nos-ev-reward {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11px;
}
body.null-os .nos-ev-reward-what { color: var(--nos-text); }
body.null-os .nos-ev-reward-from { color: var(--nos-text-dim); flex: 1 1 auto; }
body.null-os .nos-ev-reward-state { color: var(--nos-green, #12e36a); }
body.null-os .nos-ev-reward.is-pending .nos-ev-reward-what { color: var(--nos-amber, #ffd21f); }

body.null-os .nos-ev-rows { display: flex; flex-direction: column; }
body.null-os .nos-ev-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 5px 4px;
  font-size: 12px;
  border-bottom: 1px solid var(--nos-line);
}
body.null-os .nos-ev-rank { flex: 0 0 3.5em; color: var(--nos-text-dim); }
body.null-os .nos-ev-handle {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--nos-text);
}
body.null-os .nos-ev-handle i { color: var(--nos-text-dim); font-style: normal; }
body.null-os .nos-ev-score { flex: 0 0 auto; color: var(--nos-text); }
body.null-os .nos-ev-row.is-top .nos-ev-rank { color: var(--nos-amber, #ffd21f); }
body.null-os .nos-ev-row.is-me {
  background: rgba(255,120,90,0.08);
  box-shadow: 2px 0 0 var(--nos-coral) inset;
}
body.null-os .nos-ev-mine { margin-top: 6px; border-top: 1px dashed var(--nos-line); }

/* EVENT RANKINGS :: community goal (shared total, everyone who helps wins) */
body.null-os .nos-ev-community {
  display: flex; flex-direction: column; gap: 3px;
  padding: 6px 8px;
  border: 1px dashed var(--nos-coral);
  border-radius: var(--nos-radius);
}
body.null-os .nos-ev-community.is-reached { border-style: solid; border-color: var(--nos-green, #12e36a); }
body.null-os .nos-ev-community-head {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--nos-text-dim);
}
body.null-os .nos-ev-community-head b { color: var(--nos-text); font-weight: normal; letter-spacing: 1px; }
body.null-os .nos-ev-community.is-reached .nos-ev-goal i { background: var(--nos-green, #12e36a); }
body.null-os .nos-ev-community.is-reached .nos-ev-goal-txt { color: var(--nos-green, #12e36a); }
body.null-os .nos-ev-prize-min { color: var(--nos-text-dim); }
