/*
 * App shell layout: topbar + sidebar + content grid.
 *
 * `.app-shell` is PERMANENT architecture (ui_ux/migration-plan.md §2.2) —
 * applied once to the shell markup in base.html (Phase 2) and never
 * removed. It is not a coexistence-scoping mechanism; see components.css
 * for why one isn't needed (the `pl-` prefix already avoids every
 * Bootstrap name collision).
 */

.app-shell {
  display: grid;
  /* `minmax(0, 1fr)`, not `1fr`: a bare `1fr` track will not shrink below the content's
     min-content width, and the topbar's no-wrap breadcrumb has a long one. A page whose
     breadcrumb ended in a long document title grew the whole shell past the window, and the
     page scrolled sideways with the topbar's controls and the page's own buttons off the edge. */
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  grid-template-rows: var(--topbar-height) 1fr;
  grid-template-areas:
    "brand topbar"
    "sidebar content";
  min-height: 100vh;
}

/* The brand occupies the sidebar's column in the top row, so the two vertical
   rules line up and the topbar starts where the content does. */
.app-brand {
  grid-area: brand;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  overflow: hidden;
}

/* The lockup (`_components/brand.html`), one rule for both slots it appears in.
   A fixed size rather than one taken from the slot: the mark is drawn on a
   32-unit grid and reads best at a whole multiple of it. Colours come from the
   tokens, so the mark is the same blue as every primary action. */
.brand-mark {
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
}

.brand-mark__tile {
  fill: var(--color-primary);
}

.brand-mark__glyph {
  fill: none;
  stroke: var(--color-text-on-primary);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.brand-mark__node {
  fill: var(--color-text-on-primary);
}

/* The name as text in the UI's own face, one colour: the wordmark is the
   page's typography, not a picture of different typography. */
.brand-wordmark {
  color: var(--color-text);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  line-height: 1;
}

.app-brand:hover {
  text-decoration: none;
}

.topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  /* `--space-6`, matching `.app-shell-content` — the topbar's contents have to
     start on the same vertical as the page's, not 8px inside it. */
  padding: 0 var(--space-6);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

/* Mirrors `.content-width`: same available width, same left edge, so the breadcrumb
   lines up with the page heading and the action button stops at the content edge
   instead of running on to the window edge. */
.topbar-inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  max-width: var(--content-max);
  min-width: 0;
}

.topbar-inner .breadcrumb-trail {
  overflow: hidden;
}

.sidebar-nav {
  grid-area: sidebar;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: var(--space-4) var(--space-3);
  /* White, like the topbar and the brand cell, so the frame reads as one
     surface around the grey canvas. Its old near-white tint was within a
     shade of the canvas and blurred the edge between chrome and page. */
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
}

/* `margin-top: auto` pins the account to the foot however short the nav is.
   The rule above it separates the session from the navigation: the two are
   different kinds of thing, and the account is the one place in the sidebar
   that acts on the reader rather than on the page. The negative margins take
   the rule to the sidebar's own edges, past its horizontal padding, so it
   spans the column like the shell's other separators instead of floating
   inside it. */
.sidebar-profile {
  margin: auto calc(-1 * var(--space-3)) 0;
  padding: var(--space-3) var(--space-3) 0;
  border-top: 1px solid var(--color-border);
}

.sidebar-profile .pl-dropdown {
  display: block;
}

/* Full width of the sidebar, and the name truncates rather than wrapping.
   Styled here rather than as a `.pl-btn--outline`: an outlined control reads
   as a form field, and this is an identity row that happens to open a menu.
   It matches the nav rows above it — same padding, radius and hover — so the
   sidebar reads as one column of rows, and earns its edges only on hover. */
.sidebar-profile-button {
  display: flex;
  align-items: center;
  width: 100%;
  justify-content: flex-start;
  gap: var(--space-2);
  overflow: hidden;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  cursor: pointer;
}

.sidebar-profile-button:hover {
  background: var(--color-neutral-bg);
}

/* The chevron is the affordance, not the subject: muted, and pushed to the
   far edge by the name's `flex: 1`. */
.sidebar-profile-button > .icon:last-child {
  flex: 0 0 auto;
  width: 0.875rem;
  height: 0.875rem;
  color: var(--color-text-subtle);
}

.sidebar-profile-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

/* Opens upward: the trigger sits at the bottom of the viewport, so a menu
   dropped below it would fall off the screen. */
.sidebar-profile .pl-dropdown--enhanced .pl-dropdown-menu {
  top: auto;
  bottom: 100%;
  left: 0;
  right: 0;
  margin: 0 0 var(--space-1) 0;
}

/* A column, so the footer inside it can sit at the bottom of a short page
   (`margin-top: auto` below) rather than straight under the content.

   `position: relative` is load-bearing, not decoration: this is the page's scroll
   container (`overflow-y: auto` computes `overflow-x: auto`), and it must also be
   the containing block for absolutely positioned descendants. Without it, a
   `.pl-visually-hidden` span — `position: absolute` by the standard recipe — inside
   a table wider than the viewport takes the initial containing block instead, escapes
   this element's clipping, and stretches the document: the overview at 390px scrolled
   sideways by 488px while this element's own overflow stayed clipped. */
.app-shell-content {
  grid-area: content;
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: var(--space-6);
}

/* Left-aligned rather than centred: the sidebar already anchors the layout to
   the left, and centring would make the content drift away from it as the
   window widens. `min-width: 0` so a wide child (a table that must scroll)
   shrinks to the wrapper instead of forcing the page to scroll sideways. */
.content-width {
  max-width: var(--content-max);
  min-width: 0;
}

/* Sidebar nav structure: `_components/global_sidebar.html` (Phase 2). */
.sidebar-nav h2 {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-caps);
  color: var(--color-text-subtle);
  margin: var(--space-5) 0 var(--space-1) var(--space-2);
}

/* The gap between sections is carried by the heading, not by the wrapper: the
   starred section wraps its heading in a div so the whole section can be
   swapped out of band, and that div renders as an empty (but not `:empty` —
   it holds whitespace) element when the reader has starred nothing. Spacing
   the heading itself means the absent section takes up no space at all, and
   the descendant selector above reaches it through the wrapper.

   Child combinator here, not descendant: only the nav's own first heading
   loses that gap, and the wrapped starred heading is never that one. */
.sidebar-nav > h2:first-child {
  margin-top: 0;
}

.sidebar-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Flex rather than block so the leading icon and the label sit on one line and
   the label can truncate — starred project names are user-supplied and have no
   length limit worth relying on. */
.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Tighter than the horizontal padding on purpose: nav rows are read as a
     list, and the eye groups them by the gap between them rather than by the
     height of each one. */
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  overflow: hidden;
}

/* The icon keeps its full size while the label absorbs the truncation. */
.sidebar-nav a .icon {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
  color: var(--color-text-subtle);
}

/* Icon and label move together on every state, so a row brightens as one
   element rather than as two that happen to sit side by side. */
.sidebar-nav a:hover .icon,
.sidebar-nav a[aria-current="page"] .icon {
  color: inherit;
}

.sidebar-nav a:hover {
  background: var(--color-neutral-bg);
  color: var(--color-text);
}

/* Tinted rather than filled. A full-accent slab is the loudest thing on the
   page and puts the navigation — which the reader is not looking at — above
   the content they came for; the wash carries the same "you are here" at the
   weight a chrome element deserves, and keeps `--color-primary` as its text
   above 4.5:1. */
.sidebar-nav a[aria-current="page"] {
  background: var(--color-primary-bg);
  color: var(--color-primary);
  font-weight: var(--font-weight-semibold);
}

.sidebar-nav a[aria-current="page"]:hover {
  background: var(--color-primary-bg-hover);
  color: var(--color-primary);
}

/* The starred section's icons keep the star's own colour in every state —
   it is the affordance's colour, not the row's, and a star that greys out
   with the label stops reading as a star. */
.sidebar-nav #starred-projects a .icon {
  color: var(--color-star-outline);
}

/* Topbar content: breadcrumb slot, spacer, and the page's primary action.
   `.topbar` itself (above) is reused as-is for the anonymous/no-shell case
   (e.g. the login page) — its `grid-area` declaration is simply inert without
   an `.app-shell` grid ancestor, and `.topbar-brand` below is what that case
   uses in place of `.app-brand`. */

/* Holds the action swapped in by `GET /project/{id}/status`. Empty and
   zero-width on pages that have no action, so it never adds a gap. */
.topbar-action:empty {
  display: none;
}

/* The create label names a record kind, and the longest ("New input requirement") wrapped onto two
   lines whenever a long breadcrumb shared the bar. The breadcrumb truncates; the action does not. */
.topbar-action {
  flex-shrink: 0;
}

.topbar-action .pl-btn {
  white-space: nowrap;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.topbar-brand:hover {
  text-decoration: none;
}

.topbar-spacer {
  flex: 1 1 auto;
}


.user-avatar {
  display: inline-flex;
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 1.75rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  object-fit: cover;
}

.user-avatar--fallback {
  background: var(--color-neutral-bg);
  color: var(--color-text-muted);
}

.user-avatar--fallback .icon {
  width: 1rem;
  height: 1rem;
}

.anonymous-content {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: var(--space-6);
}

/* Without the shell's `min-height: 100vh` grid, the anonymous page needs the
   body to be the column that pushes its footer to the bottom of the window. */
body:has(> .anonymous-content) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.app-footer {
  margin-top: auto;
  text-align: center;
  font-size: var(--font-size-xs);
  color: var(--color-text-subtle);
  padding: var(--space-8) var(--space-4);
}

/* Breadcrumb trail: `_components/breadcrumb.html`, filled in per-page via
   `{% block breadcrumb %}` starting in Phase 3. Empty on every page today. */

.breadcrumb-trail ol {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  min-width: 0;
}

.breadcrumb-trail li {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
}

.breadcrumb-trail li:not(:last-child)::after {
  content: "/";
  margin-left: var(--space-1);
  color: var(--color-border-strong);
}

.breadcrumb-trail a {
  color: var(--color-text-muted);
  text-decoration: none;
}

.breadcrumb-trail a:hover {
  color: var(--color-text);
}

.breadcrumb-trail li[aria-current="page"] {
  color: var(--color-text);
  font-weight: var(--font-weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 48rem) {
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
    /* The wrapped topbar sizes to its controls instead of taking the content's height. */
    grid-template-rows: var(--topbar-height) auto 1fr;
    grid-template-areas:
      "brand"
      "topbar"
      "content";
  }

  .app-brand {
    border-right: 0;
  }

  .sidebar-nav {
    display: none;
  }

  .app-shell-content {
    min-width: 0;
    padding: var(--space-4);
  }

  .topbar {
    padding: 0 var(--space-3);
  }

  .topbar-inner {
    gap: var(--space-2);
    flex-wrap: wrap;
    padding-block: var(--space-2);
  }

  .app-shell .topbar-spacer {
    display: none;
  }

  .breadcrumb-trail {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
  }
}

/* Inserting this collapsed row between two flex items will make
 * the flex item that comes after it break to a new row */
.flex-break {
  flex-basis: 100%;
  height: 0;
}

.no-margin {
  margin: 0;
}
