/* app/web/static/app.css
   The one stylesheet for every BrainDown page. Before this file the same
   palette was copy-pasted into five inline <style> blocks, so each visual
   decision had to be made five times and drifted between them. */

/* Six hues exist in this product. Five name a kind of entity and come from the
   graph, which is the only place in BrainDown where colour ever meant anything.
   The sixth marks failure. Everything else is the neutral ramp.

   Entity hues appear ONLY as a 7px dot on a chip or a 2px selection edge —
   never a fill, never body text, never a button. That reservation is what lets
   five hues coexist without the page reading as a parrot. */

:root {
  /* Native controls (checkboxes, scrollbars) pick a light-on-white theme by
     default regardless of the page around them -- this tells the browser
     which of its own built-in palettes actually matches, so an unchecked
     checkbox renders as a dark square instead of a bright white one. */
  color-scheme: dark;

  /* Ground: warm charcoal. The four cool entity hues separate further against a
     warm ground than against the blue-black this replaces. */
  --bg: #16130f;
  --bg-raised: #1d1917;
  --surface: #231e1a;
  --surface-alt: #2a2420;
  --border: #332c26;
  --border-soft: #292320;
  --text: #f2ece3;
  --muted: #a3968a;
  /* #6f645b measured 3.22:1 against --bg -- below WCAG AA's 4.5:1 for normal
     text. This is the same hue lightened until it clears 4.5:1. */
  --faint: #8a7d72;

  /* Entity hues. graph.js reads these at runtime — they are defined here and
     nowhere else, so the graph and the pages cannot drift apart. */
  --project: #8b7cff;
  --organization: #3fc9bd;
  --channel: #5b9dff;
  --meeting-series: #ffb057;
  --person: #a3968a;

  /* Approving and granting projects is this product's main verb, so --project
     doubles as the interaction accent. No sixth hue is invented for "primary". */
  --accent: var(--project);
  --accent-soft: #2b2547;

  /* State, the second axis. Never a dot; only text or border on a failed or
     destructive outcome. */
  --danger: #ff6e6e;
  --danger-bg: #2a1614;
  --danger-border: #6b2a2a;

  --edge: #6b5f56;
  --edge-soft: #4a423b;

  --font-display: "Bricolage Grotesque", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-5: 24px; --space-6: 32px;
  --radius: 8px;
  --radius-sm: 6px;
}

/* Self-hosted rather than loaded from Google. BrainDown's whole premise is a
   private store; fetching fonts from a third party would tell that third party
   who visits and when, and would put a render-blocking dependency on a host we
   do not control. `swap` means the system fallback paints first either way. */
@font-face {
  font-family: "Bricolage Grotesque"; font-style: normal; font-weight: 600;
  font-display: swap; src: url("/static/fonts/bricolage-grotesque-600.woff2") format("woff2");
}
@font-face {
  font-family: "Bricolage Grotesque"; font-style: normal; font-weight: 800;
  font-display: swap; src: url("/static/fonts/bricolage-grotesque-800.woff2") format("woff2");
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("/static/fonts/inter-400.woff2") format("woff2");
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 600;
  font-display: swap; src: url("/static/fonts/inter-600.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("/static/fonts/jetbrains-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 500;
  font-display: swap; src: url("/static/fonts/jetbrains-mono-500.woff2") format("woff2");
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  line-height: 1.55;
}

/* ---- header ------------------------------------------------------------ */

.bd-header {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-5);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}
/* flex-wrap, for the same reason .bd-header above has it: the nav is a row of
   links whose count grows with the app, and a non-wrapping flex row has no width
   it will not exceed. At six areas -- the sixth being "Inicio", added with the
   first-run page -- the row ran 35px past a 360px viewport, which scrolls EVERY
   signed-in page sideways rather than only the nav. Caught by the browser check
   at 360x740; invisible at 1280 and invisible to all 1135 Python tests. */
.bd-nav { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.bd-nav a {
  color: var(--faint); text-decoration: none; font-size: 13px;
  padding-bottom: 2px; border-bottom: 2px solid transparent;
}
.bd-nav a:hover { color: var(--muted); }
.bd-nav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--accent); }
.bd-who { margin-left: auto; color: var(--faint); font-size: 13px; }
.bd-who form { display: inline; }
.bd-who button {
  background: none; border: none; color: var(--faint); cursor: pointer;
  font-size: 13px; text-decoration: underline; padding: 0;
}

/* ---- layout ------------------------------------------------------------ */

.bd-page { max-width: 760px; margin: 0 auto; padding: var(--space-6) var(--space-4); }
h1 {
  font-family: var(--font-display); font-weight: 800;
  font-size: 30px; letter-spacing: -.03em; margin: 0 0 var(--space-1);
}
h2 {
  /* 10px read as illegible even after --faint cleared AA contrast -- a
     section label this small still has to be read, not just technically
     passable. */
  font-family: var(--font-mono); font-size: 11px; text-transform: uppercase;
  color: var(--faint); letter-spacing: .1em; margin: var(--space-5) 0 var(--space-2);
}
.bd-brand { font-family: var(--font-display); font-weight: 800; font-size: 16px; letter-spacing: -.02em; }
.bd-sub { color: var(--muted); font-size: 14px; margin: 0 0 var(--space-5); }

/* ---- components -------------------------------------------------------- */

.bd-card {
  background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius); padding: var(--space-4);
  margin-bottom: var(--space-3);
}

/* `.bd-button` also lands on an <a> (the login pages' "Entrar con Google"),
   which without these two lines keeps the browser's default underline and
   computes display: inline -- a pill-shaped button that's underlined and
   whose padding collapses against its line box is not what this rule draws
   on a real <button>. */
button, .bd-button {
  display: inline-block; text-decoration: none;
  padding: 9px 15px; border: none; border-radius: var(--radius-sm);
  background: var(--accent); color: #120f1e; font-size: 14px; font-weight: 600;
  cursor: pointer; font-family: inherit;
}
button:disabled { opacity: .55; cursor: default; }
button.bd-quiet { background: var(--surface-alt); color: var(--text); border: 1px solid var(--border); }
button.bd-danger { background: var(--danger-bg); color: var(--danger); border: 1px solid var(--danger-border); }

/* The browser's default checkbox ignores the page's colours entirely, which
   makes it the one control on a dark page that looks like it belongs to a
   different site. `accent-color` restyles the native control rather than
   replacing it, so keyboard focus, the indeterminate state and screen-reader
   semantics all keep working. */
input[type="checkbox"] {
  accent-color: var(--accent);
  width: 15px; height: 15px;
  margin-right: var(--space-1);
  vertical-align: -2px;
  cursor: pointer;
}

/* `password` is here for the Slack token field on /connections: it is masked
   because it is a live credential someone pastes on a screen others can see,
   and an unstyled native input would be the one control on the page that looks
   like it belongs to a different site. */
input[type="text"], input[type="search"], input[type="password"] {
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 8px 11px;
  color: var(--text); font-size: 14px; font-family: inherit; width: 100%;
}
input::placeholder { color: var(--faint); }

pre {
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-3);
  overflow-x: auto; font-size: 13px; margin: 0;
}

/* These carried amber for emphasis, which collided with meeting-series. They
   now carry it through weight and surface. Colour is reserved for what colour
   means here: kind of thing, or failure. */
.bd-warn {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-3);
  font-size: 13px; margin-top: var(--space-3); color: var(--text);
}
/* The state axis, not the entity axis: a box that reports a failed connection
   or an error message is not "emphasis", it is a failure, and the spec
   reserves --danger for exactly that. A genuine warning (nothing failed, just
   worth noting) stays plain .bd-warn. */
.bd-warn--error {
  background: var(--danger-bg); border-color: var(--danger-border); color: var(--danger);
}
.bd-tag {
  display: inline-block; background: var(--accent-soft); color: var(--text);
  padding: 2px 8px; border-radius: 4px; font-size: 12px; font-family: var(--font-mono);
}
.bd-tag.bd-neutral { background: var(--surface-alt); color: var(--muted); }
.bd-flag { color: var(--muted); font-size: 13px; font-weight: 600; }
.bd-hidden { display: none; }

/* The signature. Any named entity renders as this: a monospaced label preceded
   by a dot in its kind's colour. One component wherever an entity appears, so
   the same channel looks the same in the picker, the graph and a search result.
   Unlike a decorative device it carries information — the dot says what kind of
   thing you are looking at. */
.bd-chip {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 5px; padding: 4px 10px 4px 8px;
  font-family: var(--font-mono); font-size: 13px;
}
.bd-chip::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--bd-chip-kind, var(--muted)); flex: none;
}
.bd-chip--project { --bd-chip-kind: var(--project); }
.bd-chip--organization { --bd-chip-kind: var(--organization); }
.bd-chip--channel { --bd-chip-kind: var(--channel); }
.bd-chip--meeting-series { --bd-chip-kind: var(--meeting-series); }
.bd-chip--person { --bd-chip-kind: var(--person); }

/* A checkbox and the entity it selects are one thing, so they have to wrap as one
   thing. Left as the default inline label, the pair breaks at the line edge and
   the box ends one row with its chip starting the next -- and in the /connections
   picker, which is 189 channels wide on a real account, that means you cannot tell
   what you are about to tick. Ticking the wrong one indexes the wrong channel.
   Found in production against real data; the five seeded channels used while
   building this never reached a line edge, which is exactly why it survived. */
label:has(> .bd-chip) {
  display: inline-flex; align-items: center; gap: var(--space-1);
  margin-right: var(--space-2);
}

/* Same argument as the rule above, for a checkbox whose label is a sentence
   rather than a chip: /connect's upload switch. The sentence wraps at 360px, and
   left as a default inline label the box would end one line with the words it
   governs starting the next — the phase-10a defect, on the one control in the
   app that grants a write permission. The box may sit centred against a wrapped
   sentence; what it may not do is leave it. */
.bd-switch {
  display: flex; align-items: center; gap: var(--space-1);
  margin-bottom: var(--space-2);
}

/* The list-and-panel picker: a scrollable list on the left, the selected
   person's detail sticky beside it. /admin and /compartir are the same shape --
   pick somebody, act on them -- and until now each carried its own byte-for-byte
   copy of this, comments included. Centralised here for the same reason colour
   was in phase 10a: two copies of one rule drift the first time either page is
   edited without the other in mind.
   Two columns where there is room for two, one above the other where there is
   not. Without the wrap the 300px list and the panel beside it added up to more
   than a phone's 360px and the whole page scrolled sideways -- found by
   tests/js/graph_layout_check.mjs, which measures both pages at 360x740. The
   panel's 320px basis is what makes it move to its own line: with `flex: 1` its
   basis was 0, so it stayed on the first line and overflowed at whatever width
   was left over. */
.bd-split { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: flex-start; }
.bd-people { flex: 0 1 300px; min-width: 0; }
.bd-panel { flex: 1 1 320px; min-width: 0; position: sticky; top: var(--space-5); }
.bd-person { padding: var(--space-3); border-radius: var(--radius-sm); cursor: pointer; }
.bd-person:hover { background: var(--surface); }
.bd-person[aria-selected="true"] { background: var(--surface); border-left: 2px solid var(--accent); }
/* Not scoped to .bd-person: the same chip markup renders again, unscoped,
   inside #panel, and a selector that only matched inside the list row left the
   panel's copy at .bd-chip's plain 13px -- the same person rendered at two
   different chip sizes on one screen. */
.bd-name { font-weight: 600; font-size: 14px; }
.bd-person .bd-meta { color: var(--muted); font-size: 12px; }
.bd-actions { display: flex; gap: var(--space-2); margin-top: var(--space-4); }

/* Identifiers — tokens, keys, timestamps, counts, IDs. Not a texture choice:
   these are machine data, and monospacing them makes a mistyped token prefix
   visible at a glance. */
.bd-mono { font-family: var(--font-mono); }

/* A member count sits inside the row it describes, so it must not compete with
   the name for the eye: it is the answer to "which #general is this", read only
   after the name has already been read. */
label:has(> .bd-chip) > .bd-mono { color: var(--faint); font-size: 12px; }

/* Inside the picker a row is a row -- and the row is the container, not the
   label. `inline-flex` is right where a label is just a checkbox and a short
   name (/compartir's project list), but here each conversation also carries a
   member count, sometimes an indexed count and a delete button, all of variable
   width. Left inline they pack two-per-line and a delete button lands in the
   middle of the next conversation, which is what shipped and what a screen of 59
   channels made obvious immediately.

   The container is why the label alone is no longer enough. The indexed count
   and the delete button used to be SIBLINGS of the label, so they rendered on a
   line of their own between two conversations and read as belonging to the one
   below -- pointing the irreversible control at the wrong name. They are now
   inside the row, and outside the label: a <button> within a <label> activates
   it, so "Borrar lo indexado" would tick the conversation it is about to empty.

   Scoped to #conversation-groups rather than widened globally: the short-label
   case elsewhere is still better inline. */
#conversation-groups .bd-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2); padding: 3px 0;
}
#conversation-groups .bd-row > label:has(> .bd-chip) {
  display: flex; align-items: center; gap: var(--space-1);
  flex: 1 1 auto; min-width: 0; margin-right: 0; padding: 0;
}
/* The counts and the button line up at the right edge, in that order, on the
   row's own line -- so the eye reads name on the left, everything about it on
   the right, and never has to work out which conversation a control belongs to.
   `margin-left: auto` inside the label pushes the member count across the gap
   the name does not fill. */
#conversation-groups .bd-row > label > .bd-mono { margin-left: auto; }
/* The confirmation belongs to its row, so it opens under that row rather than
   in the group. Full width on a line of its own. */
#conversation-groups .bd-row > .bd-confirm { flex-basis: 100%; }

/* The picker's own controls. The search box is the first thing on a screen of
   387 rows, so it gets the full width and its own space; the show-hidden
   toggle reads as an aside rather than an action, because the rows behind it
   are ones this page has already decided are not worth the scroll. */
#conversation-groups > input[type="search"] { margin-bottom: var(--space-3); }
#conversation-groups > button[data-show-hidden] {
  background: var(--surface-alt); color: var(--muted);
  border: 1px solid var(--border); font-weight: 400;
  margin-bottom: var(--space-4);
}
.bd-group { margin-bottom: var(--space-4); }
.bd-group h3 { margin: 0 0 var(--space-2); font-size: 15px; }
.bd-bulk {
  background: var(--surface-alt); color: var(--muted);
  border: 1px solid var(--border); font-weight: 400;
  margin-bottom: var(--space-2);
}

/* ---- privacy notices ---------------------------------------------------- */

/* The privacy notices. Left rail in the accent so it reads as an aside rather
   than as an error, which --danger would imply. */
.bd-privacy {
  margin: var(--space-5) 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--accent);
  /* --surface-alt, not --surface: on `/` this sits inside a .bd-card, which is
     --surface, and a notice the same colour as its container reads as body copy
     with a stray rule down the side. One step up separates it in both places --
     inside a card on `/`, and directly on --bg on the five login pages. */
  background: var(--surface-alt);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: left;
  max-width: 46ch;
}

.bd-privacy p { margin: 0 0 var(--space-2); color: var(--muted); }
.bd-privacy p:last-child { margin-bottom: 0; }
.bd-privacy strong { color: var(--text); }
.bd-privacy code { font-family: var(--font-mono); font-size: 0.85em; color: var(--text); }
