/*
 * ds/base.css — reset, typography, focus. No components.
 * Everything here is a bare element or a utility. Nothing knows about a screen.
 */

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

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: var(--fs-body);
    line-height: var(--lh-normal);
    font-weight: var(--fw-regular);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Headings carry no margin of their own. Spacing is the layout's job, not the
   glyph's -- the single most common cause of the uneven gaps across these screens
   was an h3 bringing its own 1em and a wrapper adding more. */
h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-weight: var(--fw-semibold);
    line-height: var(--lh-tight);
    letter-spacing: -0.011em;
    color: var(--ink);
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-lead); }
h5, h6 { font-size: var(--fs-body); }

p, ul, ol, dl, pre, figure, blockquote, table { margin: 0; }

a {
    color: var(--accent-text);
    text-decoration: none;
}
a:hover { text-decoration: underline; }

code, kbd, samp, pre {
    font-family: var(--font-mono);
    font-size: 0.925em;
}

/* An inline code span is an identifier, not a decoration. It must not wrap
   mid-token: a repository path broken across lines is unsearchable and unreadable,
   which is what the three-column monospace pane was fixed for.
   
   SO IT SCROLLS INSTEAD. `white-space: nowrap` alone meant a long one -- the stack
   panel prints a whole build command in one -- pushed the PAGE sideways: 537px of
   document in a 390px viewport, so the whole screen slid under the thumb to reveal the
   tail of a command. `inline-block` with `max-width: 100%` keeps the token whole and
   keeps the overflow inside the span, where it belongs. */
code {
    display: inline-block;
    max-width: 100%;
    overflow-x: auto;
    /* So the box sits on the text baseline rather than hanging below it, which is what
       `inline-block` with its own scroll area otherwise does to a line of prose. */
    vertical-align: bottom;
    background: var(--sunken);
    border: 1px solid var(--line);
    border-radius: var(--radius-xs);
    padding: 0.08em 0.34em;
    white-space: nowrap;
    color: var(--ink);
}
pre {
    font-size: var(--fs-meta);
    line-height: var(--lh-snug);
    overflow-x: auto;
}
pre code {
    background: none;
    border: 0;
    padding: 0;
    white-space: pre;
}

img, svg, video, canvas { max-width: 100%; height: auto; vertical-align: middle; }
hr { border: 0; border-top: 1px solid var(--line); margin: var(--sp-5) 0; }

button, input, select, textarea {
    font: inherit;
    color: inherit;
    margin: 0;
}
button { background: none; border: 0; padding: 0; cursor: pointer; }

/* ONE focus treatment for the whole product. :focus-visible so a mouse click does
   not leave a ring behind, but a Tab key always does. */
:focus { outline: none; }
:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

::selection { background: var(--accent-soft); }

[hidden] { display: none !important; }

/* Screen-reader-only, for the labels a dense UI cannot afford to print.
 *
 * `fixed`, AT THE ORIGIN, not `absolute` at its static position -- and this is a layout
 * fix, not a preference. The classic recipe is `position: absolute` with no `left` or
 * `top`, which puts a 1px box at wherever the element would have been. Inside the
 * horizontally-scrolling setup tab strip that is up to 841px along, and because the box
 * is absolutely positioned its containing block is the nearest POSITIONED ancestor --
 * the page, not the strip -- so the strip's `overflow: auto` does not clip it and the
 * DOCUMENT grew to hold it.
 *
 * The result was every Project setup tab, and Conventions, scrolling 59px sideways on a
 * 390px screen into blank space. Nothing was visible out there: it was a one-pixel
 * clipped box with the word "Repositories" in it. Found by measuring the live pages in
 * a browser at 390 -- no test in the suite reads geometry, and nothing about the markup
 * or the stylesheet looks wrong.
 *
 * `left: 0` on the absolute version also fixes it, and is the fix most codebases use.
 * It is not used here because it only works while the nearest positioned ancestor
 * happens to start at the page origin: drop a `.ds-sr` inside a `position: relative`
 * card and the bug comes back. A fixed element cannot extend any scroll container, ever.
 */
.ds-sr {
    position: fixed;
    left: 0; top: 0;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Skip link — the rail is long, and a keyboard user should not have to walk it. */
.ds-skip {
    position: absolute;
    left: var(--sp-2);
    top: -40px;
    z-index: var(--z-tooltip);
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--line-firm);
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-3);
    transition: top var(--dur-fast) var(--ease-out);
}
.ds-skip:focus { top: var(--sp-2); text-decoration: none; }

/* ---- text utilities ------------------------------------------------- */
.ds-mono   { font-family: var(--font-mono); }
.ds-micro  { font-size: var(--fs-micro); }
.ds-meta   { font-size: var(--fs-meta);  color: var(--mute); }
.ds-dense  { font-size: var(--fs-dense); }
.ds-lead   { font-size: var(--fs-lead);  line-height: var(--lh-snug); }
.ds-soft   { color: var(--soft); }
.ds-mute   { color: var(--mute); }
.ds-medium { font-weight: var(--fw-medium); }
.ds-strong { font-weight: var(--fw-semibold); }
.ds-prose  { max-width: var(--ds-prose-max); line-height: var(--lh-loose); }
/* A DOCUMENT, NOT A WALL.
 *
 * `.ds-prose` set a measure and a line height and nothing else, so the change document
 * -- a markdown file with nine headings, lists and fenced code, rendered into this
 * container -- arrived with browser-default margins: `h2` at twice the body size with
 * the same space above it as below, paragraphs touching the heading that follows them,
 * and code at whatever the UA felt like. The content was right and unreadable.
 *
 * Space BEFORE a heading and little after it, which is the one rule that makes a long
 * document scannable: a heading belongs to what follows it, and equal margins make it
 * belong to neither. The first child loses its top margin so the dialog's own padding
 * is not doubled. */
.ds-prose > :first-child { margin-top: 0; }
.ds-prose > :last-child  { margin-bottom: 0; }
.ds-prose h1, .ds-prose h2, .ds-prose h3, .ds-prose h4 {
    margin: var(--sp-6) 0 var(--sp-2);
    line-height: var(--lh-tight);
    font-weight: var(--fw-semibold);
}
.ds-prose h1 { font-size: var(--fs-h2); }
.ds-prose h2 { font-size: var(--fs-h3); }
.ds-prose h3 { font-size: var(--fs-body); }
.ds-prose h4 { font-size: var(--fs-dense); color: var(--soft); }
.ds-prose p  { margin: 0 0 var(--sp-3); }
.ds-prose ul, .ds-prose ol { margin: 0 0 var(--sp-3); padding-left: var(--sp-5); }
.ds-prose li { margin-bottom: var(--sp-1); }
.ds-prose li > ul, .ds-prose li > ol { margin-top: var(--sp-1); }
.ds-prose hr {
    margin: var(--sp-6) 0; border: 0; border-top: 1px solid var(--line);
}
.ds-prose blockquote {
    margin: 0 0 var(--sp-3); padding-left: var(--sp-3);
    border-left: 2px solid var(--line-firm); color: var(--soft);
}
/* Code wraps rather than scrolling sideways: a horizontal scrollbar inside a dialog
   that already scrolls vertically is two gestures to read one line. */
.ds-prose pre {
    margin: 0 0 var(--sp-3); padding: var(--sp-3);
    background: var(--panel-bg); border: 1px solid var(--line);
    border-radius: var(--radius-md);
    font-size: var(--fs-meta); line-height: var(--lh-snug);
    white-space: pre-wrap; word-break: break-word;
}
.ds-prose :not(pre) > code {
    padding: 1px 4px; border-radius: var(--radius-xs);
    background: var(--panel-bg); font-size: 0.92em;
}
.ds-prose table {
    width: 100%; margin: 0 0 var(--sp-3);
    border-collapse: collapse; font-size: var(--fs-meta);
}
.ds-prose th, .ds-prose td {
    padding: var(--sp-2); border-bottom: 1px solid var(--line);
    text-align: left; vertical-align: top;
}
.ds-prose th { color: var(--mute); font-weight: var(--fw-semibold); }
.ds-nowrap { white-space: nowrap; }
.ds-trunc  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A column head: the smallest, quietest text in the system. */
.ds-label {
    font-size: var(--fs-micro);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mute);
}

/* ---- layout primitives ---------------------------------------------- */
.ds-stack  { display: flex; flex-direction: column; gap: var(--sp-4); }
/* A STACK DOES NOT SHRINK ITS ITEMS. Flex children default to `flex-shrink: 1`, so a
   stack inside a container with a bounded height -- a drawer body, a panel -- squeezes
   every card instead of scrolling: the container's scrollHeight equals its clientHeight
   and nothing overflows, so nothing scrolls. Found on the analysis report, where a card
   holding a 266px table rendered 20px tall with `overflow: hidden` over the rest of it.
   A child that genuinely should flex says so after this rule. */
.ds-stack > * { flex-shrink: 0; }
.ds-stack--tight { gap: var(--sp-2); }
.ds-stack--loose { gap: var(--sp-6); }

.ds-row    { display: flex; align-items: center; gap: var(--sp-2); }
.ds-row--between { justify-content: space-between; }
.ds-row--top { align-items: flex-start; }
.ds-row--wrap { flex-wrap: wrap; }
.ds-spacer { flex: 1 1 auto; min-width: 0; }

/* min-width:0 on the children is load-bearing: without it a long monospace path
   inside a flex child refuses to shrink and pushes the actions off the row. */
.ds-row > * { min-width: 0; }

.ds-grid { display: grid; gap: var(--sp-4); }
.ds-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ds-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ds-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ds-grid--auto { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

@media (max-width: 900px) {
    .ds-grid--3, .ds-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
    .ds-grid--2, .ds-grid--3, .ds-grid--4 { grid-template-columns: minmax(0, 1fr); }
}

/* Two text colours that were being written as inline styles or as one-off classes in
   four stylesheets. A failure sentence and a success sentence, nothing more. */
.ds-fail-text { color: var(--st-failed-ink); font-size: var(--fs-dense); }
.ds-pass-text { color: var(--st-passed-ink); font-size: var(--fs-meta); }
/* Wide enough for a sentence plus three titles; `--auto`'s 200px wrapped every one of
   them to four lines. */
.ds-grid--glimpse { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* THE PAGE DOES NOT MOVE WHILE A DIALOG IS OPEN. Set by `ds-ui.js` from an observer,
   because a dozen functions across six templates open these by setting `hidden` and a
   lock each of them had to remember to call is one three of them would not. */
body.ds-locked { overflow: hidden; }
