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

/* Page ground. The canvas is a deep navy carrying a faint engineering-paper
   grid: a 4px pinstripe, a 20px major line, and a brighter mark at each major
   intersection. Every value is deliberately near-invisible — the grid should
   read as texture, not as a drawing.

   The colours are not declared here: `nu.theme` owns the palette and injects
   it onto `:root` as `--color-*` at startup. The fallbacks below are what the
   page paints with in the moment before `main.js` runs, so they are only the
   handful that would be visible as a flash — keep them in step with
   `src/nu/theme.cljs`. */
:root {
  --grid-gap: 4px;
}

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  background-color: var(--color-bg, #161d27);
  color: var(--color-text, #ebe7e4);
  font-family: var(--font-body, system-ui, sans-serif);
  line-height: 1.7;
  /* the composer bleeds to exactly 100vw; this guards against a rounding
     hair of it ever turning into a horizontal scrollbar */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}

/* Both layers are fixed and overscan the viewport, so the texture stays put
   while the transcript scrolls and never shows a seam at the edges. */
body::before, body::after {
  content: "";
  position: fixed;
  inset: -8rem;
  z-index: 0;
  pointer-events: none;
}

body::before {
  background-color: var(--color-bg, #161d27);
  background-image:
    linear-gradient(var(--color-grid-minor) 0 1px, transparent 1px var(--grid-gap)),
    linear-gradient(to right, var(--color-grid-minor) 0 1px, transparent 1px var(--grid-gap)),
    linear-gradient(var(--color-grid-major) 0 1px, transparent 1px calc(var(--grid-gap) * 5)),
    linear-gradient(to right, var(--color-grid-major) 0 1px, transparent 1px calc(var(--grid-gap) * 5)),
    /* punches out everything but a short cross at each major intersection */
    radial-gradient(circle at center,
                    transparent calc(var(--grid-gap) / 2),
                    var(--color-bg, #161d27) calc(var(--grid-gap) / 2) 100%),
    linear-gradient(var(--color-grid-cross) 0 1px, transparent 1px calc(var(--grid-gap) * 5)),
    linear-gradient(to right, var(--color-grid-cross) 0 1px, transparent 1px calc(var(--grid-gap) * 5));
  background-size:
    var(--grid-gap) var(--grid-gap),
    var(--grid-gap) var(--grid-gap),
    calc(var(--grid-gap) * 5) calc(var(--grid-gap) * 5),
    calc(var(--grid-gap) * 5) calc(var(--grid-gap) * 5),
    calc(var(--grid-gap) * 5) calc(var(--grid-gap) * 5),
    calc(var(--grid-gap) * 5) calc(var(--grid-gap) * 5),
    calc(var(--grid-gap) * 5) calc(var(--grid-gap) * 5);
  /* the cross mask is offset by half a major cell so it lands on the crossings */
  background-position:
    0 0, 0 0, 0 0, 0 0,
    calc(var(--grid-gap) * -2.5) calc(var(--grid-gap) * -2.5),
    0 0, 0 0;
}

/* Vignette: the grid is strongest at the top and washes out into flat canvas
   before the bottom of the viewport. */
body::after {
  background: linear-gradient(to bottom, var(--color-vignette), var(--color-bg, #161d27) 90%);
}

#root { position: relative; z-index: 1; }

button { font: inherit; }

/* The focus ring, once, for everything.

   The app is styled with inline React style props and `:focus-visible` cannot
   be written inline, which is why there was no focus styling anywhere. One
   rule in the palette's own accent covers every focusable thing at once — and
   it is what makes a list of things navigable at all: with `j` and `k` moving
   real DOM focus, the ring is the only thing on the page that says where you
   are. The outline follows the element's own corner, so a row keeps its
   shape. */
:focus-visible {
  outline: 2px solid var(--color-accent, #6a9fcc);
  outline-offset: 2px;
}

/* The transcript scrolls but shows no bar at all — its position is already
   obvious from the turns themselves. The id comes from
   `nu.screens.chat/transcript-element-id`. Both properties are needed:
   Chrome honours either, Firefox only the standard one. */
#Transcript {
  scrollbar-width: none;
}

#Transcript::-webkit-scrollbar {
  display: none;
}

/* The typing indicator, while a reply is in flight. The braille spinner beside
   it says the app is still going; this says it about the whole line, slowly
   enough to read as breathing rather than as blinking. Never fully out — the
   words stay readable through the whole cycle. */
@keyframes pulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

.pulse { animation: pulse 1.8s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .pulse { animation: none; }
}

/* The voice-note dialog's halo: two rings leaving the mic and fading out as
   they go, half a cycle apart so one of them is always on its way. They say
   the microphone is open on their own — the ring that answers the voice is
   drawn inline, from the analyser, and would sit still in a silent room. */
@keyframes halo {
  0%   { transform: scale(1); opacity: 0.5; }
  100% { transform: scale(2.3); opacity: 0; }
}

.halo { animation: halo 2.6s ease-out infinite; }
.halo-late { animation-delay: 1.3s; }

@media (prefers-reduced-motion: reduce) {
  .halo { animation: none; opacity: 0.25; }
}

/* The send button, while something is staged that has not gone anywhere. Not
   a breathing opacity like `.pulse` — the glyph is already the brightest
   thing on the composer and dimming it would say the opposite. A nudge
   instead: still for most of the cycle, then one small push in the direction
   the arrow points, which is where the eye is caught by movement rather than
   by a change of colour. */
@keyframes nudge {
  0%, 62%, 100% { transform: translateX(0); }
  70% { transform: translateX(3px); }
  78% { transform: translateX(-1px); }
  86% { transform: translateX(2px); }
}

.nudge { animation: nudge 2.8s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .nudge { animation: none; }
}

/* Every other scroller in the app, where the platform default is a bright
   slab with arrow buttons against a dark surface. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--color-scrollbar);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--color-scrollbar-hover); }

/* Firefox only. Chrome prefers these over the rules above whenever they are
   set — arrow buttons and all — so they are kept away from it. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--color-scrollbar) transparent;
  }
}
