/* App-surface CSS that utilities provably cannot express.
 *
 * Everything here was measured against Crosswind's generated sheet before it
 * was moved: pseudo-elements, attribute-state selectors, bare element
 * selectors, `@keyframes`, and page-scoped custom properties all produce NO
 * rule when written as a utility or variant. The failure is silent — no error,
 * no warning, just a class that does nothing — so the test is always "does the
 * served sheet contain it", never "does it look plausible".
 *
 * `config/crosswind.ts` `preflights` is where ch.11.2/11.7 says this belongs.
 * It does not work on the stx serve path: the entry is spread into the
 * generator config and then never emitted, verified with a probe token that
 * appears nowhere in the served CSS on any route. A linked stylesheet is the
 * substitute, and it is also the router-safe home — the SPA router destroys
 * every <head> style that is not the Crosswind tag, but leaves <link>s alone.
 *
 * Linked once from config/stx.ts `app.head.link`, after /tokens.css so it can
 * read the palette, before the Crosswind sheet so utilities still win a
 * specificity tie against anything here.
 *
 * Rules that WERE expressible are deliberately absent — they live as utilities
 * in the markup. Do not move a rule back here because it is more convenient.
 */

/* ---------------------------------------------------------------------------
 * Page-scoped tokens
 *
 * dashboard is the one surface with its own accent: indigo, so navigation
 * never reads as a severity colour (see 20f40ed). It used to be a `:root`
 * block inside dashboard.stx's <style>, scoped only by the accident of which
 * page carried the tag. Declared globally that would repaint the whole app, so
 * it hangs off a wrapper class on the dashboard's root element and cascades to
 * its descendants — including the fixed-position chrome, which is inside the
 * wrapper.
 * ------------------------------------------------------------------------ */
.page-dashboard {
  --accent: #4f46e5;
  --rail: rgba(79, 70, 229, 0.14);
}

@media (prefers-color-scheme: dark) {
  .page-dashboard {
    --accent: #818cf8;
    --rail: rgba(129, 140, 248, 0.16);
  }
}

:root[data-theme='dark'] .page-dashboard {
  --accent: #818cf8;
  --rail: rgba(129, 140, 248, 0.16);
}

:root[data-theme='light'] .page-dashboard {
  --accent: #4f46e5;
  --rail: rgba(79, 70, 229, 0.14);
}

/* Channel brand colours. Only settings uses them, but they are declared here
 * with everything else rather than in a page tag. */
:root {
  --slack: #611f69;
  --discord: #5865f2;
}

@media (prefers-color-scheme: dark) {
  :root {
    --discord: #818cf8;
  }
}

:root[data-theme='dark'] {
  --discord: #818cf8;
}

:root[data-theme='light'] {
  --discord: #5865f2;
}

/* Tabular numerals for the log stream's counts and timestamps, so digits do
 * not jitter as values change.
 *
 * This is a real rule rather than an arbitrary property because the value has
 * to be quoted — `font-feature-settings: 'tnum' 1` — and a quoted value cannot
 * survive Crosswind's class-name syntax. Written as
 * [font-feature-settings:'tnum'_1] it generates NOTHING; the three
 * font-feature-settings declarations in the served sheet are all preflight
 * resets, and the computed value on the element stayed `normal`.
 *
 * `tabular-nums` is not a substitute: it sets font-variant-numeric, a
 * different property, and would not reproduce the measured baseline.
 */
.tnum {
  font-feature-settings: 'tnum' 1;
}

/* ---------------------------------------------------------------------------
 * Bare element selectors — no utility targets an element type
 * ------------------------------------------------------------------------ */
html {
  scroll-behavior: smooth;
}

/* ---------------------------------------------------------------------------
 * settings: the alert-channel toggle
 *
 * The knob is a ::after pseudo-element and both states are attribute-driven.
 * Crosswind generates nothing for `after:` or for a `[data-on]` variant.
 * ------------------------------------------------------------------------ */
.tgl::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  background: #fff;
  transition: transform 0.15s ease;
}

.tgl[data-on='1'] {
  background: var(--ok);
}

.tgl[data-on='1']::after {
  transform: translateX(16px);
}

/* settings: the Slack/Discord channel picker. Selected state is attribute
 * driven, and each brand paints its own fill. */
.seg[data-sel='1'] {
  color: var(--accent-ink);
  border-color: transparent;
}

.seg-slack[data-sel='1'] {
  background: var(--slack);
}

.seg-discord[data-sel='1'] {
  background: var(--discord);
}

/* settings: delete-zone modal. `[hidden]` has to beat the overlay's own
 * `display: flex`, which a utility cannot guarantee — Crosswind's preflight
 * `[hidden]{display:none}` ties with `.flex` on specificity and loses on
 * order. */
.dz-overlay[hidden] {
  display: none;
}

@keyframes dz-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes dz-pop {
  from {
    opacity: 0;
    transform: translateY(6px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
 * dashboard
 *
 * Moved verbatim from dashboard.stx's <style> block. Every family below is
 * entangled with a selector no utility reaches, so splitting them would have
 * meant half a component in each place:
 *
 *   .pill*        hover/active are tag-scoped (`a.pill:hover, button.pill:hover`)
 *                 on purpose — the disabled pager is a <span class="pill"> and
 *                 must NOT light up on hover.
 *   .log-row      `details[open] > .log-row` has to outrank the hover state.
 *   .search-input ::placeholder and :focus-visible.
 *   .chrome-*     `.chrome-pro, .chrome-pro:hover` cancels .chrome-upgrade's
 *                 hover: Pro is a status, not an action.
 *   summary       bare element selector plus ::-webkit-details-marker.
 *   :focus-visible a bare-element accessibility rule, not optional.
 *   .grain,       data-URI backgrounds with prefers-color-scheme variants.
 *   select.app-panel
 *   .reveal       @keyframes plus a reduced-motion variant.
 *
 * What IS expressible went the other way and is now utilities in the markup:
 * .mono, .wordmark, .wordmark-link, .panel, .lvl-chip, .ctx-pre and .icon-btn.
 * ------------------------------------------------------------------------ */
.pill { border-radius: 999px; transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, transform 0.12s ease; }
a.pill:hover, button.pill:hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); background: color-mix(in srgb, var(--accent) 6%, transparent); }
a.pill:active, button.pill:active { transform: translateY(1px); }
/* Filter-tab state. These were inline style ternaries on every tab, which
  outrank the hover rules above and could not be themed. The two levels differ
  because the severity tabs are the primary control and the range tabs are
  secondary. */
.pill-on { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
a.pill-on:hover { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.pill-off { color: var(--text-2); border-color: var(--border); }
.pill-on-soft { color: var(--accent); border-color: var(--accent); }
.pill-off-soft { color: var(--text-3); border-color: var(--border); }
.log-row { transition: background-color 0.12s ease; }
.log-row:hover { background: color-mix(in srgb, var(--accent) 4%, transparent); }
details[open] > .log-row { background: color-mix(in srgb, var(--accent) 5%, transparent); }
summary { list-style: none; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
.search-input { background: var(--panel); border: 1px solid var(--border); border-radius: 999px; color: var(--text); outline: none; transition: border-color 0.15s ease; }
.search-input:focus { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.search-input::placeholder { color: var(--text-3); }
/* Position was an inline style string assembled in JS. It lives here now, so
  the chrome is styleable and themeable like everything else. */
.chrome-logout, .chrome-upgrade { position: fixed; top: 16px; z-index: 60; padding: 8px 13px; border-radius: 9px; font-size: 13px; line-height: 1; font-family: var(--sans, ui-sans-serif); }
.chrome-logout { right: 18px; font-weight: 500; cursor: pointer; border: 1px solid var(--border); background: var(--panel); color: var(--text-2); transition: color 0.15s ease, border-color 0.15s ease; }
.chrome-logout:hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.chrome-upgrade { right: 112px; font-weight: 600; text-decoration: none; background: var(--accent); color: var(--accent-ink); transition: filter 0.15s ease, transform 0.12s ease; }
.chrome-upgrade:hover { filter: brightness(1.12); }
.chrome-upgrade:active { transform: translateY(1px); }
/* Pro is a status, not an action: no link, no hover lift. */
.chrome-pro, .chrome-pro:hover { background: transparent; color: var(--accent); border: 1px solid var(--accent); cursor: default; filter: none; }
/* The ONLY .reveal in the app. marketing.css carried a second copy with
  different values (0.6s/12px against this one's 0.5s/10px) and a second
  @keyframes of the same name; both sheets load on every marketing page, so the
  marketing copy won there and this one only ever reached the dashboard. These
  are the marketing values, kept because 23 pages used them and one did not. */
.reveal { opacity: 0; transform: translateY(12px); animation: reveal 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
@keyframes reveal { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .reveal { animation: none; opacity: 1; transform: none; } }
/* Keyboard focus — a visible ring on every interactive control (accessibility,
  not optional). Search gets an inset ring so it doesn't clash with its pill.

  This rule used to also declare `border-radius: 8px`, which was not a property
  of the ring — it RESHAPED THE ELEMENT for as long as it held focus. Tabbing
  onto a filter tab squared off its 999px pill; the same happened to the pager
  and to every rounded control here, because `a:focus-visible` (0,1,1) outranks
  `.pill` (0,1,0). Nothing about a keyboard user's focus should change an
  element's geometry.

  Nothing replaces it: `outline` has followed the element's own border-radius
  since Chrome 94 / Firefox 88 / Safari 16.4, so the ring is correctly rounded
  on a pill and correctly square on a square control, which is what the 8px was
  reaching for in the first place. */
a:focus-visible, button:focus-visible, summary:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
summary:focus-visible { outline-offset: -2px; }
.search-input:focus-visible { outline: none; border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
/* Physical press feedback on a log row. */
.log-row:active { background: color-mix(in srgb, var(--accent) 9%, transparent); }
/* Log stream aligned as fixed columns: rail · time · level · channel · message. */
.log-row { display: grid; grid-template-columns: 3px 4.75rem 3.75rem 6rem minmax(0, 1fr); align-items: center; column-gap: 0.75rem; }
/* Subtle grain so the flat surfaces don't read as sterile. Fixed, non-blocking,
  barely-there; sits under the floating chrome (z-60). */
.grain { position: fixed; inset: 0; z-index: 5; pointer-events: none; opacity: 0.03;
  background-size: 170px 170px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='170'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
@media (prefers-reduced-motion: no-preference) { .grain { opacity: 0.032; } }
/* Project dropdown: the native select chevron hugged the rounded border. Reset
  the native appearance and draw our own with a comfortable inset + room for the
  value text. Chevron is themed to --text-3 in each mode. */
select.app-panel {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  padding-right: 2rem; background-repeat: no-repeat;
  background-position: right 0.7rem center; background-size: 13px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2397a1b2' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) { select.app-panel { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23667085' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); } }

/* Log severity, driven by one data attribute rather than an inline style.
 *
 * The rail and the level badge on a log row both used
 * `style="background: {{ levelColor(entry.level) }}"`, and the stat dot and
 * value used `style="color: {{ s.color }}"` — four inline declarations carrying
 * hex literals computed in the view. `data-level` on the row sets `--sev` once
 * and both children read it, so the colour is named in exactly one place
 * (public/tokens.css) and follows the theme.
 *
 * The default covers a level the server does not recognise: it falls back to
 * the debug tone rather than to no colour, so the rail never disappears. */
[data-level] { --sev: var(--sev-debug); }
[data-level='emergency'], [data-level='alert'], [data-level='critical'] { --sev: var(--sev-critical); }
[data-level='error'] { --sev: var(--sev-error); }
[data-level='warning'] { --sev: var(--sev-warning); }
[data-level='notice'] { --sev: var(--sev-notice); }
[data-level='info'] { --sev: var(--sev-info); }
.log-rail { background: var(--sev); min-height: 20px; }
.log-level { color: var(--sev); background: color-mix(in srgb, var(--sev) 14%, transparent); }
/* One segment of the level-mix bars on /analytics. Reads the same --sev the
 * rail and the chip do, so a colour still means the same severity across the
 * whole app and the analytics page names no palette of its own. */
.level-seg { background: var(--sev); }

/* Stat cards. `data-tone` is set only when the count is non-zero, so an empty
 * warnings/errors card stays neutral — which is what `s.on` expressed inline. */
.stat-dot { background: var(--text-3); opacity: 0.35; }
.stat-value { color: var(--text); }
[data-tone='warning'] .stat-dot { background: var(--sev-warning); opacity: 1; }
[data-tone='warning'] .stat-value { color: var(--sev-warning); }
[data-tone='error'] .stat-dot { background: var(--sev-critical); opacity: 1; }
[data-tone='error'] .stat-value { color: var(--sev-critical); }

/* ---------------------------------------------------------------------------
 * Narrow viewports
 *
 * This sheet had NO width query at all, while marketing.css has four. The app
 * surface is the half that people actually work in, and it was the half with no
 * mobile behaviour — the two rules below are both fixed geometry that a phone
 * cannot satisfy.
 *
 * Everything here adjusts rules THIS FILE owns. It deliberately does not try to
 * override a utility (the page's own `px-5 py-10`, for instance): this sheet is
 * linked before the Crosswind sheet precisely so utilities win a specificity
 * tie, so a `.page-dashboard { padding-top }` here would be silently dead. Any
 * responsive change to a utility-styled element belongs in the markup as an
 * `sm:`/`md:` variant.
 *
 * 40rem rather than a round 375px: the breakpoint is where the content stops
 * fitting, and it is in rem so it also fires for someone at a large default
 * font size on a wider screen, who has the same problem for a different reason.
 * ------------------------------------------------------------------------ */
@media (max-width: 40rem) {
  /* The log stream was a single row of five fixed columns:
   *   3px rail + 4.75rem time + 3.75rem level + 6rem channel
   * plus four 0.75rem gaps and the row's own 20px side padding — 323px spoken
   * for before the message column starts. On a 375px phone that leaves the
   * message 52px, about four characters, on the one line that carries what
   * actually happened. The row was not narrow, it was empty.
   *
   * It becomes two rows: metadata above, message across the full width below.
   * Nothing is dropped — the channel is not repeated in the expanded panel, so
   * hiding it would have lost the only place it appears.
   *
   * Addressed by position because these are bare spans in the markup with no
   * hooks of their own, and the order is fixed by the template: rail, time,
   * level, channel, message. */
  .log-row {
    grid-template-columns: 3px auto auto minmax(0, 1fr);
    column-gap: 0.5rem;
    row-gap: 0.2rem;
  }

  /* The rail spans both rows so it stays one continuous severity stripe beside
   * the whole entry rather than a stub next to the timestamp. */
  .log-row > :nth-child(1) { grid-row: 1 / span 2; }
  .log-row > :nth-child(5) { grid-column: 2 / -1; }

  /* The floating chrome is positioned for a desktop margin: `top: 16px` with
   * 8px padding puts its lower edge at about 46px, and the page's own content
   * starts at 40px. On a wide screen the centred column leaves that gutter
   * empty so nothing shows; on a phone the column is the full width and the
   * header runs underneath the buttons.
   *
   * Smaller and tighter, so the chrome's lower edge lands near 32px and closes
   * the overlap window rather than merely making it less likely. */
  .chrome-logout, .chrome-upgrade {
    top: 10px;
    padding: 5px 10px;
    font-size: 12px;
    border-radius: 8px;
  }

  .chrome-logout { right: 12px; }
  .chrome-upgrade { right: 88px; }
}
