/*
 * ds/shell.css — the app frame: rail, page, header, tabs.
 * The frame every screen sits in. Knows nothing about what a screen contains.
 */

/* =========================== 1. Frame ============================== */
.ds-app {
    display: grid;
    grid-template-columns: var(--ds-rail-w) minmax(0, 1fr);
    min-height: 100vh;
    background: var(--ground);
}
.ds-app--rail-min { grid-template-columns: var(--ds-rail-w-min) minmax(0, 1fr); }

/* =========================== 2. Rail =============================== */
.ds-rail {
    grid-column: 1;
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    background: var(--rail-bg);
    border-right: 1px solid var(--line);
    padding: var(--sp-3) var(--sp-2);
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    z-index: var(--z-rail);
}

.ds-rail-brand {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-2) var(--sp-3);
    min-width: 0;
}

/* The org / project switcher. A button, because it opens a menu. */
.ds-switch {
    display: flex; align-items: center; gap: var(--sp-2);
    width: 100%;
    padding: var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-dense);
    font-weight: var(--fw-medium);
    color: var(--ink);
    min-width: 0;
    transition: background var(--dur-fast) var(--ease-out);
}
.ds-switch:hover { background: var(--sunken); }
.ds-switch[aria-expanded="true"] { background: var(--sunken); }
.ds-switch-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0; text-align: left; }
.ds-switch-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The workspace, under the project rather than instead of it. Which project you are
   in changes every row below; which organisation owns it changes nothing you can
   see, so it is the caption and not the headline. */
.ds-switch-org { font-size: var(--fs-micro); color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-switch-caret { color: var(--mute); font-size: var(--fs-micro); flex: none; }

/* --------------------------- Menu ---------------------------------- *
 * A popover hung off a button. The product had none, so every choice between
 * named things was a page: `All projects` was a rail row that left the project
 * you were in to show you a grid of cards, and coming back meant going in again.
 * Switching is not a destination.
 */
.ds-menu {
    position: absolute;
    z-index: var(--z-popover);
    min-width: 232px; max-width: 320px;
    padding: var(--sp-1);
    background: var(--raised);
    border: 1px solid var(--line-firm);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xl);
    max-height: min(70vh, 480px); overflow-y: auto;
    animation: ds-pop var(--dur-fast) var(--ease-out);
}
.ds-menu[hidden] { display: none !important; }
.ds-menu-group {
    padding: var(--sp-2) var(--sp-2) var(--sp-1);
    font-size: var(--fs-micro); font-weight: var(--fw-semibold);
    letter-spacing: 0.06em; text-transform: uppercase; color: var(--mute);
}
.ds-menu-item {
    display: flex; align-items: center; gap: var(--sp-2);
    width: 100%;
    padding: var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-dense);
    color: var(--soft);
    min-width: 0;
}
.ds-menu-item:hover { background: var(--sunken); color: var(--ink); text-decoration: none; }
.ds-menu-item[aria-current="true"] { color: var(--ink); font-weight: var(--fw-medium); }
.ds-menu-item-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-menu-item-mark { flex: none; color: var(--accent-text); font-size: var(--fs-micro); }
.ds-menu-sep { height: 1px; margin: var(--sp-1) 0; background: var(--line); }

/* The rail is the menu's containing block, so a menu pinned under the switcher
   does not move when the page behind it scrolls. */
.ds-rail { position: sticky; }
.ds-rail-switch { position: relative; }
.ds-rail-switch > .ds-menu { top: calc(100% + 4px); left: 0; right: 0; }

/* Group heading inside the rail. The current product has four UNLABELLED
   groups, which is how eleven rows read as one undifferentiated list. */
.ds-rail-group {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-4) var(--sp-2) var(--sp-1);
    font-size: var(--fs-micro);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mute);
}

.ds-navlink {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-dense);
    color: var(--soft);
    min-width: 0;
    transition: background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}
.ds-navlink:hover { background: var(--sunken); color: var(--ink); text-decoration: none; }
.ds-navlink.is-active { background: var(--accent-soft); color: var(--accent-text); font-weight: var(--fw-medium); }
.ds-navlink-icon { flex: none; width: 16px; text-align: center; font-size: var(--fs-meta); }
.ds-navlink-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A count on a rail row. Never zero: a zero badge is noise that teaches people
   to stop reading badges. Hide it at the source rather than rendering "0". */
.ds-navlink-count {
    flex: none;
    font-size: var(--fs-micro);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--mute);
    background: var(--sunken);
    border-radius: var(--radius-full);
    padding: 0 var(--sp-1-5);
    min-width: 18px;
    text-align: center;
}
.ds-navlink-count--attention { color: var(--st-waiting-ink); background: var(--st-waiting-bg); }
.ds-navlink-count--urgent    { color: var(--st-blocked-ink); background: var(--st-blocked-bg); }

.ds-rail-foot { margin-top: auto; padding-top: var(--sp-3); }

/* Collapsed rail: icons only, labels gone, counts become a dot. */
.ds-app--rail-min .ds-navlink-text,
.ds-app--rail-min .ds-rail-group,
.ds-app--rail-min .ds-switch-id,
.ds-app--rail-min .ds-switch-caret { display: none; }
/* A menu anchored to a 56px rail would be 56px wide. Let it out. */
.ds-app--rail-min .ds-rail-switch > .ds-menu { right: auto; left: 0; min-width: 232px; }
.ds-app--rail-min .ds-navlink { justify-content: center; }
.ds-app--rail-min .ds-navlink-count {
    position: absolute; transform: translate(10px, -10px);
    min-width: 8px; height: 8px; padding: 0; overflow: hidden; color: transparent;
}
.ds-app--rail-min .ds-navlink { position: relative; }

/* =========================== 3. Page =============================== */
.ds-main { grid-column: 2; min-width: 0; display: flex; flex-direction: column; }

.ds-page {
    width: 100%;
    max-width: var(--ds-page-max);
    margin: 0 auto;
    padding: var(--sp-6) var(--sp-6) var(--sp-12);
    min-width: 0;
}
.ds-page--wide  { max-width: none; }
.ds-page--flush { padding: 0; max-width: none; }

/* =========================== 4. Header ============================= */
.ds-head {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding-bottom: var(--sp-4);
    margin-bottom: var(--sp-5);
    border-bottom: 1px solid var(--line);
}

.ds-crumbs {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: var(--sp-1);
    font-size: var(--fs-meta);
    color: var(--mute);
    min-width: 0;
}
.ds-crumbs a { color: var(--mute); }
.ds-crumbs a:hover { color: var(--ink); }
.ds-crumb-sep { color: var(--mute); }
.ds-crumbs [aria-current="page"] { color: var(--soft); }

.ds-head-main { display: flex; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.ds-head-main > .ds-head-actions { margin-left: auto; }
.ds-title { font-size: var(--fs-h2); min-width: 0; }
/*
 * ONE LINE. A Jira summary is a sentence, and this one ran to 108 characters: two
 * lines of 28px type across the full width, above a strip that says the key again
 * and a tab bar that says what the screen is. The whole first screenful was the
 * title of the thing rather than anything about it.
 *
 * Clamped rather than shortened in Python, so the full sentence is still selectable,
 * still searchable in the page, and still read out in full by a screen reader.
 */
.ds-title--line {
    font-size: var(--fs-h3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    display: block; width: 100%;
}
.ds-head-id { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.ds-head-key {
    font-family: var(--font-mono);
    font-size: var(--fs-dense);
    font-weight: var(--fw-medium);
    color: var(--soft);
}
.ds-head-actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
.ds-head-sub { font-size: var(--fs-body); color: var(--soft); max-width: var(--ds-prose-max); }

/* The facts strip: a few labelled numbers under the title. */
.ds-head-facts { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.ds-fact { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ds-fact-k { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.05em; color: var(--mute); }
.ds-fact-v { font-size: var(--fs-dense); font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }

/* =========================== 5. Tabs ============================== */
.ds-tabs {
    display: flex; align-items: center; gap: var(--sp-1);
    border-bottom: 1px solid var(--line);
    margin-bottom: var(--sp-5);
    overflow-x: auto;
    scrollbar-width: none;
}
.ds-tabs::-webkit-scrollbar { display: none; }

.ds-tab {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-dense);
    font-weight: var(--fw-medium);
    color: var(--mute);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}
.ds-tab:hover { color: var(--ink); text-decoration: none; }
.ds-tab[aria-selected="true"] { color: var(--accent-text); border-bottom-color: var(--accent-text); }
.ds-tab-count {
    font-size: var(--fs-micro);
    font-variant-numeric: tabular-nums;
    color: var(--mute);
    background: var(--sunken);
    border-radius: var(--radius-full);
    padding: 0 6px;
}

/* A tab panel must lose to [hidden]. The last design system shipped
   `.panel { display: flex }`, which beat the user-agent rule, so every tab
   selected correctly and no panel ever switched. */
.ds-tabpanel[hidden] { display: none !important; }

/* Stage dots — the lifecycle rail, as a tablist rather than five URLs. */
.ds-stages { display: flex; align-items: center; gap: 0; min-width: 0; }
.ds-stage {
    display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
    flex: 1 1 0; min-width: 0; position: relative;
    font-size: var(--fs-micro);
    color: var(--mute);
    padding-top: var(--sp-1);
}
.ds-stage-dot {
    width: 11px; height: 11px; border-radius: var(--radius-full);
    border: 2px solid var(--line-firm);
    background: var(--surface);
    z-index: 1;
}
/* The connector sits behind the dots, drawn from each stage except the first. */
.ds-stage:not(:first-child)::before {
    content: ""; position: absolute; top: calc(var(--sp-1) + 5px); right: 50%; left: -50%;
    height: 2px; background: var(--line); z-index: 0;
}
.ds-stage--done .ds-stage-dot { background: var(--pass); border-color: var(--pass); }
.ds-stage--done::before { background: var(--pass); }
.ds-stage--current .ds-stage-dot { border-color: var(--accent-text); background: var(--accent-fill); box-shadow: 0 0 0 3px var(--accent-soft); }
.ds-stage--current { color: var(--ink); font-weight: var(--fw-semibold); }
.ds-stage--blocked .ds-stage-dot { background: var(--st-blocked-ink); border-color: var(--st-blocked-ink); }
.ds-stage--waiting .ds-stage-dot { background: var(--st-waiting-ink); border-color: var(--st-waiting-ink); }
/* A STEP THAT DIED. The set had done, current, waiting and blocked -- four states, none
   of which is "this one failed", so a pipeline that stopped could only be drawn as one
   that had not started. Knowledge's ingest has five steps and any of them can throw. */
.ds-stage--failed .ds-stage-dot { background: var(--fail); border-color: var(--fail); }
.ds-stage--failed { color: var(--fail); font-weight: var(--fw-semibold); }
.ds-stage--failed::before { background: var(--line); }

/* =========================== 6. Narrow =========================== */
@media (max-width: 1024px) {
    .ds-app { grid-template-columns: var(--ds-rail-w-min) minmax(0, 1fr); }
    .ds-app .ds-navlink-text,
    .ds-app .ds-rail-group,
    .ds-app .ds-switch-id,
    .ds-app .ds-switch-caret { display: none; }
    .ds-app .ds-navlink { justify-content: center; }
    .ds-app .ds-rail-switch > .ds-menu { right: auto; left: 0; min-width: 232px; }
    .ds-page { padding: var(--sp-5) var(--sp-4) var(--sp-10); }
}
@media (max-width: 680px) {
    .ds-app { grid-template-columns: minmax(0, 1fr); }
    .ds-rail {
        position: fixed; inset: 0 auto 0 0; width: var(--ds-rail-w);
        transform: translateX(-100%);
        transition: transform var(--dur-base) var(--ease-out);
        box-shadow: var(--shadow-lg);
    }
    .ds-rail.is-open { transform: none; }
    .ds-main { grid-column: 1; }
    .ds-page { padding: var(--sp-4) var(--sp-4) var(--sp-10); }
    .ds-head-main { flex-wrap: wrap; }
    .ds-head-main > .ds-head-actions { margin-left: 0; width: 100%; }
}

/* =========================== 7. Rail extras ======================= */
/* The primary action sits above the nav rows, the way Linear puts New issue
   above Inbox: creating work is not a destination, so it is not a nav row. */
.ds-rail-new { margin: var(--sp-1) 0 var(--sp-3); }
.ds-rail-new:hover { text-decoration: none; }

/* Three-way appearance control. Icon-only, because the rail cannot afford three
   words -- each button keeps a visually-hidden label for screen readers. */
.ds-rail-theme { padding: var(--sp-2) var(--sp-1) var(--sp-1); }
.ds-rail-theme .ds-seg { width: 100%; }
.ds-rail-theme .ds-seg > button { flex: 1 1 0; text-align: center; padding: var(--sp-1); }

.ds-app--rail-min .ds-rail-theme,
.ds-app--rail-min .ds-rail-new .ds-navlink-text { display: none; }

@media (max-width: 1024px) {
    .ds-rail-theme { display: none; }
}

/* =========================== 8. Split ============================ */
/*
 * Content and its context, side by side. The aside is a fixed share rather than a
 * fraction so a long monospace branch name in it cannot steal width from the
 * reading column -- which is the failure the three-column review pane had.
 */
.ds-split-main { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--sp-5); align-items: start; min-width: 0; }
@media (max-width: 1100px) {
    .ds-split-main { grid-template-columns: minmax(0, 1fr); }
}

/* ======================================================================
   9. The issue frame — added when the five stages became five URLs
      sharing one header. See templates/includes/ds_issue_frame.html.
   ====================================================================== */

/* The title and the sentence under it stack. `.ds-head-id` was a row holding a
   title and an optional key; it now holds a title and WHAT IS BEING WAITED ON,
   which is a sentence and belongs on its own line. */
.ds-head-id { flex-direction: column; align-items: flex-start; gap: var(--sp-1); flex: 1 1 auto; }

/* `lifecycle` writes this line for every screen. On the analysis page it was
   rendering inside the information dialog, so the one sentence that says what
   happens next was behind an icon. Coloured by health, because "the agent is
   working" and "stopped for an approval" are not the same news. */
.ds-head-say {
    font-size: var(--fs-dense);
    color: var(--soft);
    display: flex; align-items: baseline; gap: var(--sp-2);
    max-width: var(--ds-prose-max);
}
/* `ds_issue_frame` always writes a modifier -- `ds-head-say--{{ stage.health }}` -- so
   the HEALTHY case needs a rule as much as the other two. It worked by being the
   absence of one, which is fine until somebody reads the stylesheet to find out what
   the three states look like and finds two. */
.ds-head-say--healthy { color: var(--soft); }
.ds-head-say--blocked { color: var(--st-waiting-ink); }
.ds-head-say--failed { color: var(--st-failed-ink); }

/* A tab that is a LINK marks itself with aria-current, not aria-selected:
   aria-selected means something only inside a tablist, and five URLs are not
   one. Same paint, correct semantics. */
.ds-tab[aria-current="page"] { color: var(--accent-text); border-bottom-color: var(--accent-text); }
.ds-tab[aria-disabled="true"] { color: var(--mute); opacity: 0.55; cursor: default; }
.ds-tab[aria-disabled="true"]:hover { color: var(--mute); }

/* One gap, named, instead of a style attribute per call site. */
.ds-gap-below { margin-bottom: var(--sp-5); }

/* A heading for a view that is NOT a stage -- the audit chain, a whole-object page --
   where the frame above already carries the issue's own `h1`. Smaller than `.ds-title`
   on purpose: two headings at the same size on one screen is two things claiming to be
   what the page is about. */
.ds-title--sub { font-size: var(--fs-h3); }

/* NINE TABS, NOT FIVE. The issue frame's five stages fit any window; Project setup has
   nine and they are words rather than short stage names, so on a narrow window the
   strip scrolls -- which `.ds-tabs` already allows -- and the icons go, because an icon
   beside a word is decoration and the word is the navigation. */
.ds-tabs--setup { gap: var(--sp-0-5); }
@media (max-width: 1024px) {
    .ds-tabs--setup .ds-tab > i { display: none; }
    .ds-tabs--setup .ds-tab { padding-left: var(--sp-2); padding-right: var(--sp-2); }
}

/* ===========================================================================
 * THE CONVERSATIONS PANE
 *
 * Chat is a two-column screen inside the shell: the conversations in this
 * project, and the one you are in. The list used to come from
 * `sidebar.html with show_conversations=True` -- the same include that drew the
 * app's navigation -- so a screen could not have the list without the whole
 * sidebar, and the chat page was the only one in the product with its own
 * `<body>` partly for that reason.
 *
 * `ds-page--flush` removes the page padding, so this fills the main column and
 * the message list scrolls inside it rather than the window scrolling.
 * ======================================================================== */
/* ONE COLUMN. The grid's first track was a 260px conversation pane, which put a
   second vertical list immediately beside the app's rail -- two left-hand navigations
   on the one screen, neither explaining the other. The list lives under the rail's
   Conversations row now (`ds_rail.html`), so this is the conversation and nothing
   else. */
.ds-page--flush.app-container {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    height: 100vh;
    min-height: 0;
}
.ds-convlist { flex: 1; min-height: 0; overflow-y: auto; padding: var(--sp-2); }
/* The rows `chat.js` writes. It owns the markup, so the selectors follow what it
   emits rather than the other way round -- renaming them here would be renaming
   something a script that is not in this file agrees about. */
.ds-convlist .conversation-item {
    display: block; padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-dense); color: var(--soft);
    cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-convlist .conversation-item:hover { background: var(--sunken); color: var(--ink); }
.ds-convlist .conversation-item.active {
    background: var(--accent-soft); color: var(--accent-text);
    font-weight: var(--fw-medium);
}
.ds-convlist .empty-message { padding: var(--sp-3); font-size: var(--fs-dense); color: var(--mute); }

/* The chat column itself: header, banner, message list, composer. Only the list
   scrolls. */
.app-container > .chat-container {
    display: flex; flex-direction: column; min-height: 0; min-width: 0;
}

/* THE CONVERSATIONS, IN THE RAIL.
   Under the row that starts a new one, which is the row they are about. Capped and
   scrollable: this sits beneath seven navigation rows and a switcher, and a rail that
   scrolls as a whole is a rail people stop reading the bottom of. */
/* FENCED ON BOTH SIDES. With a rule only above it, the row below -- Knowledge -- read
   as the last item of the conversation group rather than the next navigation row. */
.ds-railconv {
    display: flex; flex-direction: column; min-height: 0;
    margin: var(--sp-2) 0;
    padding: var(--sp-2) 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.ds-railconv-head {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: 0 var(--sp-3) var(--sp-1);
    font-size: var(--fs-micro); font-weight: var(--fw-semibold);
    letter-spacing: 0.05em; text-transform: uppercase; color: var(--mute);
}
.ds-railconv-head > span { margin-right: auto; }
.ds-railconv-new {
    border: 0; background: none; cursor: pointer;
    color: var(--mute); font-size: var(--fs-dense); line-height: 1;
    padding: 2px 4px; border-radius: var(--radius-xs);
}
.ds-railconv-new:hover { background: var(--sunken); color: var(--ink); }
.ds-railconv-list { max-height: 22vh; padding: 0; }
/* TWO MARKUPS, ONE ROW. The server renders an `<a>`; `chat.js` renders a `<div>`
   holding a title and a delete button, and it owns that markup -- the selectors here
   follow what it emits rather than the other way round. A `display: block` row made
   the delete button wrap onto a second line, which in a 180px rail reads as a broken
   list item. */
.ds-railconv-item, .ds-convlist .conversation-item {
    display: flex; align-items: center; gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-meta); color: var(--soft);
    cursor: pointer; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-decoration: none;
}
.ds-convlist .conversation-title {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Shown on hover and on keyboard focus: a delete button beside every row is a
   mis-click waiting to happen, and one that never appears is unreachable. */
.ds-convlist .delete-conversation {
    flex: none; border: 0; background: none; cursor: pointer;
    color: var(--mute); font-size: var(--fs-micro); line-height: 1;
    padding: 2px; border-radius: var(--radius-xs);
    opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
}
.ds-convlist .conversation-item:hover .delete-conversation,
.ds-convlist .delete-conversation:focus-visible { opacity: 1; }
.ds-convlist .delete-conversation:hover { color: var(--st-failed-ink); }
.ds-railconv-item:hover { background: var(--sunken); color: var(--ink); }
.ds-railconv-item.is-active {
    background: var(--accent-soft); color: var(--accent-text);
    font-weight: var(--fw-medium);
}
.ds-railconv-none {
    margin: 0; padding: var(--sp-1) var(--sp-3);
    font-size: var(--fs-meta); color: var(--mute);
}
