/* theme — ten colour tokens in two sets. The default set is light; a browser
   that reports a dark OS swaps in the dark set through the media query. The
   two data-theme rules below are what the header's toggle writes, and an
   attribute selector on :root outweighs the bare :root both sets above use, so
   the reader's pick wins over the OS whichever way the OS is set. The values
   are the mockup's, unchanged.

   color-scheme rides along with each set. It is what tells the browser which
   way to paint the things we do not style — form controls, scrollbars, the
   canvas behind the page — and without it a light page keeps dark inputs. */
:root { color-scheme: light; --bg: #eceef2; --surface: #fafbfc; --line-subtle: #8c929e; --line: #767d8b; --line-quote: #6c7382; --accent: #2f5bd0; --text: #1a1d24; --text-muted: #4d5462; --text-problem: #a3242a; --focus: #a13a00; --chart-1: #005ea8; --chart-2: #70429b; --chart-3: #007a5e; --chart-4: #a94700; --chart-5: #9c1f6d; }
@media (prefers-color-scheme: dark) {
:root { color-scheme: dark; --bg: #191c22; --surface: #22262e; --line-subtle: #69717f; --line: #7d8698; --line-quote: #8d97aa; --accent: #9ec0ff; --text: #e8ebf0; --text-muted: #c5cbd6; --text-problem: #ffa8a3; --focus: #ffd47a; --chart-1: #6cb6ff; --chart-2: #d2a8ff; --chart-3: #56d4a4; --chart-4: #ffb86b; --chart-5: #ff93dd; }
}
:root[data-theme="light"] { color-scheme: light; --bg: #eceef2; --surface: #fafbfc; --line-subtle: #8c929e; --line: #767d8b; --line-quote: #6c7382; --accent: #2f5bd0; --text: #1a1d24; --text-muted: #4d5462; --text-problem: #a3242a; --focus: #a13a00; --chart-1: #005ea8; --chart-2: #70429b; --chart-3: #007a5e; --chart-4: #a94700; --chart-5: #9c1f6d; }
:root[data-theme="dark"] { color-scheme: dark; --bg: #191c22; --surface: #22262e; --line-subtle: #69717f; --line: #7d8698; --line-quote: #8d97aa; --accent: #9ec0ff; --text: #e8ebf0; --text-muted: #c5cbd6; --text-problem: #ffa8a3; --focus: #ffd47a; --chart-1: #6cb6ff; --chart-2: #d2a8ff; --chart-3: #56d4a4; --chart-4: #ffb86b; --chart-5: #ff93dd; }

/* scale — the tokens that do not change with the theme, and the page's own
   font. --text-base sits on :root as font-size, so 1rem is 18px here and
   every rem below — spacing, max-width, heading defaults — rises with it. */
:root { --space-1: .25rem; --space-2: .5rem; --space-3: .75rem; --space-4: 1rem; --space-5: 1.25rem; --space-6: 2rem; --space-7: 3rem; --font-sans: ui-sans-serif, system-ui, sans-serif; --text-sm: 16.5px; --text-base: 18px; --weight-normal: normal; --radius-control: .5rem; --radius-surface: .75rem; --border: 1px; --border-accent: 2px; --border-quote: 3px; --busy-opacity: .6; font-family: var(--font-sans); font-size: var(--text-base); background: var(--bg); color: var(--text); }

/* base — element defaults, and the page's own width and rhythm. Every <p> on
   these screens is secondary text, which is where .caveat, .empty and .read-at
   get their colour from; their own rules carry no colour. */
body { margin: 0; }
main { max-width: 70rem; margin: 0 auto; padding: var(--space-7) var(--space-6); }
p { color: var(--text-muted); }
/* app.js moves focus to runs-heading and failure-heading after a swap, so where
   focus landed has to be visible: without a mark the reader is told the answer
   arrived and not where. The browser default is whatever the browser picked for
   this background and is not measured. :focus-visible, not :focus, so a mouse
   click on a button does not draw the ring. */
:focus-visible { outline: var(--border-accent) solid var(--focus); outline-offset: var(--border-accent); }

/* app-header */
header { display: flex; gap: var(--space-4); align-items: baseline; padding: var(--space-5) var(--space-6); border-bottom: var(--border) solid var(--line-subtle); }
header span { color: var(--text-muted); }
.environments { margin-left: auto; font-size: var(--text-sm); }
.session { margin-left: auto; }
a.session { color: var(--text); }
/* Placement only, the way .panel-head .btn is: the theme button is last in
   every header and sits at the far end. On the two screens that already push
   something there it follows that thing across the header's own gap instead of
   splitting the free space with it, which is what two auto margins would do. */
.theme-toggle { margin-left: auto; }
.environments + .theme-toggle, .session + .theme-toggle { margin-left: 0; }

/* button — every <button> on these screens carries .btn, so one rule answers
   all of them. Placement stays with the component that places it: see
   .panel-head .btn below. */
.btn { font: inherit; color: inherit; background: none; border: var(--border) solid var(--line); border-radius: var(--radius-control); padding: var(--space-1) var(--space-3); cursor: pointer; }

/* login — a public shell with no operational content. */
.login-page { min-height: calc(100vh - 5rem); display: grid; place-items: center; box-sizing: border-box; }
.login-card { width: min(100%, 28rem); padding: var(--space-7); border: var(--border) solid var(--line); border-radius: var(--radius-surface); background: var(--surface); }
.login-card h1 { margin: var(--space-2) 0 var(--space-4); }
.login-eyebrow { margin: 0; font-size: var(--text-sm); letter-spacing: .08em; }
.login-action { display: inline-block; margin-top: var(--space-4); color: var(--text); text-decoration: none; }

/* card-grid and card */
/* The domain list was a grid of cards, each an <article class="domain"> whose
   own <h2> named it. It is now a section with one heading and a list of rows.
   The card rules are gone rather than left to apply to the new markup: as a
   grid, .domains put the section's heading in one column and the list in the
   next, and .domain drew every row as a card. */
/* The build-stage label is an implementation stage read from the registry, not
   an operating state (app-shell.md §9). It has to look unlike the status
   vocabulary, and it is deliberately not styled as one.

   ⚠️ It was ".domains .build-stage" until Home stopped carrying the domain
   list, which left /system — the page the list moved to — using the class with
   no rule that matched it. A descendant selector named the one page that had
   it; this names the label. */
.build-stage { color: var(--text-muted); font-size: var(--text-sm); }

/* panel */
/* A table too wide for its column scrolls inside its own panel. Columns are not
   dropped and the page itself never scrolls sideways (§8).

   This is not scoped to the narrow band. It was, and then the middle band was
   added and 800px scrolled the whole page sideways again: the sidebar takes its
   share, the table does not fit what is left, and there was no scroll container
   at that width. A panel wider than its column is the same problem at every
   width, so the rule lives with the component. */
.panel { overflow-x: auto; margin-top: var(--space-6); padding: var(--space-5); border: var(--border) solid var(--line); border-radius: var(--radius-surface); background: var(--surface); }
.panel-head { display: flex; align-items: baseline; gap: var(--space-4); }
.panel-head h2 { margin: 0; }
/* Placement only: the head's button sits at the far end. Every button does not
   get this, which is why it is scoped to the head and not folded into .btn. */
.panel-head .btn { margin-left: auto; }

/* tabs — trigger switch */
.triggers { display: flex; gap: var(--space-3); margin: var(--space-4) 0; }
.triggers a { color: var(--text-muted); text-decoration: none; border-bottom: var(--border-accent) solid transparent; }
.triggers a[aria-current] { color: var(--text); border-bottom-color: var(--accent); }

/* filter-form — window and page size */
.window { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: baseline; margin-top: var(--space-6); }
.window input { font: inherit; color: inherit; background: var(--bg); border: var(--border) solid var(--line); border-radius: var(--radius-control); padding: var(--space-1) var(--space-2); }
.window span { color: var(--text-muted); font-size: var(--text-sm); }

/* data-table — one table per screen, so the element selectors stay */
table { width: 100%; border-collapse: collapse; margin-top: var(--space-4); }
th, td { text-align: left; padding: var(--space-2); border-bottom: var(--border) solid var(--line-subtle); font-size: var(--text-sm); }
th { color: var(--text-muted); font-weight: var(--weight-normal); }

/* key-value — one dl per screen, same reason */
dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-4); margin: var(--space-4) 0; }
dt { color: var(--text-muted); }

/* plain-list */
.pipelines ul { list-style: none; padding: 0; }
.pipelines li { padding: var(--space-2) 0; border-bottom: var(--border) solid var(--line-subtle); }
.failures { list-style: none; padding: 0; }
.failures li { padding: var(--space-3) 0; border-bottom: var(--border) solid var(--line-subtle); }
.failures span { display: inline-block; margin-right: var(--space-4); color: var(--text-muted); font-size: var(--text-sm); }

/* code — the pipeline row is the only place a <code> is styled. The old
   `.run code` matched nothing: .run holds an h3, a dl and a button. */
.pipelines code { color: var(--text-muted); font-size: var(--text-sm); }

/* note — secondary sentences. Colour comes from p above, except .problem. */
.caveat { border-left: var(--border-quote) solid var(--line-quote); padding-left: var(--space-4); }
.empty, .read-at { color: var(--text-muted); }
.read-at { font-size: var(--text-sm); }
.problem { color: var(--text-problem); }

/* chart — JavaScript enhances the host, while the caption, summary and table
   remain complete server-rendered content. ECharts reads every colour from the
   tokens above; its default palette must never leak through. */
.chart-figure { margin: var(--space-6) 0 0; padding: var(--space-5); border: var(--border) solid var(--line); border-radius: var(--radius-surface); background: var(--surface); }
.chart-figure figcaption { font-weight: var(--weight-strong, 600); }
.chart-summary { margin: var(--space-2) 0 var(--space-4); }
.chart-host { width: 100%; height: 22rem; }
.chart-host.is-trace { height: 18rem; }
/* A model the adapter rejected leaves a sentence, not a chart-shaped hole. */
.chart-host[data-bo-chart-state="failed"] { height: auto; }
.chart-failed { margin: 0; padding: var(--space-4); border: var(--border) dashed var(--line); border-radius: var(--radius-control); color: var(--text-problem); }
.chart-companion { margin-top: var(--space-5); }
.chart-companion summary { cursor: pointer; color: var(--text-muted); }
@media (max-width: 48rem) {
  .chart-host { height: 18rem; }
  .chart-host.is-trace { height: 16rem; }
}

/* busy — htmx puts this on the element that started a request, for as long as
   it is in flight. Without a rule for it a refresh looks like nothing happened
   until the answer lands, and a DE read is budgeted at 3 seconds. No new
   colour — the element is dimmed, not repainted. */
.htmx-request { opacity: var(--busy-opacity); cursor: progress; }

/* --- app shell ------------------------------------------------------------
   docs/designs/app-shell.md. The shell is one grid: header across the top,
   navigation down the left, workspace filling the rest. Three bands: 16rem
   sidebar above 64rem, 12rem between 48rem and 64rem, and below 48rem the
   navigation becomes a modal drawer over the same grid (§8).

   Current place is never carried by colour alone (§5.2): the marker is a left
   border and a weight as well. */

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: var(--space-2);
  z-index: 3;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: var(--border) solid var(--line);
  border-radius: var(--radius-control);
  color: var(--text);
  /* Off screen until focused, and still in the focus order — display:none or
     visibility:hidden would take it out of that order and the first Tab would
     land somewhere else. */
  transform: translateY(-300%);
}
.skip-link:focus { transform: none; }

body.app {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 16rem 1fr;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "header header"
    "nav    main";
}
body.app > .global-header { grid-area: header; }
body.app > .primary-navigation { grid-area: nav; }
/* min-width: 0 is load-bearing. A grid item defaults to min-width: auto, which
   is its min-content width, so one wide table made this column — and the page —
   wider than the screen and scrolled the whole document sideways. At 0 the
   column may be narrower than its content, which is what lets the scroll
   container on .panel below actually take effect. Measured at 360px: without
   it, scrollWidth 647 against a 360 viewport. */
body.app > #main { grid-area: main; min-width: 0; }

.global-header { align-items: center; }
.header-identity { display: flex; gap: var(--space-3); align-items: baseline; }
.product-identity { color: var(--text); text-decoration: none; font-weight: var(--weight-strong, 600); }
.bo-environment { color: var(--text-muted); font-size: var(--text-sm); }
.global-actions { margin-left: auto; display: flex; gap: var(--space-3); align-items: center; }
/* Both used to push themselves to the right on their own. Inside the actions
   group the group does it once. */
.global-actions .theme-toggle, .global-actions .session { margin-left: 0; }

.nav-trigger {
  display: none;
  font: inherit;
  color: inherit;
  background: none;
  border: var(--border) solid var(--line);
  border-radius: var(--radius-control);
  padding: var(--space-1) var(--space-3);
  cursor: pointer;
}

.primary-navigation {
  padding: var(--space-6) var(--space-4);
  border-right: var(--border) solid var(--line-subtle);
  overflow-y: auto;
}
.nav-root, .nav-group ul { list-style: none; margin: 0; padding: 0; }
.nav-group { margin-top: var(--space-6); }
.nav-group-label {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-normal);
  color: var(--text-muted);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.nav-item {
  display: block;
  padding: var(--space-2) var(--space-3);
  color: var(--text-muted);
  text-decoration: none;
  border-left: var(--border-accent) solid transparent;
}
a.nav-item:hover { color: var(--text); }
.nav-item[aria-current="page"] {
  color: var(--text);
  border-left-color: var(--accent);
  font-weight: var(--weight-strong, 600);
}
.nav-item[data-current="location"] { color: var(--text); border-left-color: var(--line); }
/* A resource type with no list route yet. Not a disabled link — there is
   nothing to enable — so it is plainly not a link. */
.nav-item.is-textual { cursor: default; }

.workspace { width: 100%; box-sizing: border-box; margin: 0 auto; padding: var(--space-7) var(--space-6); }
.width-reading { max-width: 44rem; }
.width-workspace { max-width: 70rem; }
.width-wide { max-width: 100rem; }

.breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: 0;
  font-size: var(--text-sm);
}
.breadcrumb li + li::before { content: "/"; margin-right: var(--space-2); color: var(--text-muted); }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb [aria-current="page"] { color: var(--text); }

.page-header { margin-bottom: var(--space-6); }
.page-header h1 { margin: 0 0 var(--space-2); }
.data-environment { margin: var(--space-3) 0 0; font-size: var(--text-sm); }
.target-environment { font-size: var(--text-sm); }

/* .muted is used by steward's tables and by Home's areas and had no rule at
   all, so "muted" text rendered at full contrast — the emphasis the markup
   asked for was not on the screen. It is the muted text token and nothing
   else: it does not carry a size, because it is used inside cells that set
   their own. */
.muted { color: var(--text-muted); }

/* The operating areas (home-overview.md §표현). A definition list, because
   each area is a name and what it answers — and it stays readable when the
   grid collapses, which a card row does not. */
.areas { display: grid; grid-template-columns: minmax(10rem, auto) 1fr; gap: var(--space-2) var(--space-4); margin: 0; }
.areas dt { font-weight: 600; }
.areas dd { margin: 0; color: var(--text-muted); }
@media (max-width: 40rem) {
  .areas { grid-template-columns: 1fr; gap: 0; }
  .areas dt { margin-top: var(--space-3); }
}

.public-content { min-height: calc(100vh - 5rem); display: grid; place-items: center; box-sizing: border-box; }

.nav-scrim { position: fixed; inset: 0; z-index: 1; background: var(--scrim, rgba(0, 0, 0, .5)); }

/* The middle band (§8): wide enough for a sidebar, not wide enough to give it
   16rem. The column narrows and the labels stay words — a rail of icons would
   make the operator guess, and nothing here has an icon with an agreed meaning.

   48.01rem rather than 48rem so this cannot overlap the drawer band below,
   which is max-width: 48rem and would otherwise both match at exactly 768px —
   and this rule, coming first, would lose to it by source order. The sliver
   between 768px and 768.16px matches neither and falls back to the wide
   layout, which is the harmless direction. */
@media (min-width: 48.01rem) and (max-width: 64rem) {
  body.app { grid-template-columns: 12rem 1fr; }
  .primary-navigation { padding: var(--space-6) var(--space-3); }
  .nav-item { padding: var(--space-2); }
  /* "Data Engineering" is wider than 12rem allows on one line at this size. */
  .nav-group-label { letter-spacing: .04em; }
  .workspace { padding: var(--space-6) var(--space-5); }
}

@media (max-width: 48rem) {
  body.app {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main";
  }
  .nav-trigger { display: inline-flex; }
  .primary-navigation {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 2;
    width: min(20rem, 85vw);
    box-sizing: border-box;
    background: var(--surface);
    border-right: var(--border) solid var(--line);
    transform: translateX(-100%);
    transition: transform 160ms ease;
  }
  html.nav-open .primary-navigation { transform: none; }
  /* The page behind holds still; the drawer scrolls on its own (overflow-y
     above). */
  html.nav-open, html.nav-open body { overflow: hidden; }
  .workspace { padding: var(--space-6) var(--space-4); }
}

@media (prefers-reduced-motion: reduce) {
  .primary-navigation { transition: none; }
}

/* Warden → Projects → each project: the sidebar's one third level, loaded
   after the page (warden_nav.go). Indented under its parent and a step
   smaller, so it reads as the parent's contents and not as siblings. */
.nav-group ul.nav-children { margin: 0 0 var(--space-2) var(--space-4); }
.nav-children .nav-item { padding-top: var(--space-1); padding-bottom: var(--space-1); font-size: var(--text-sm); }
