/* ============================================================================
   Dualog Insight — application chrome and components
   ============================================================================

   Insight's own surfaces, styled against the design-system tokens in theme.css.
   The split mirrors Workspace's own (base.scss = shared system, global.scss =
   app chrome): anything here may consume --p-* / --app-* tokens, but must never
   hardcode a brand hex. If you reach for a literal colour, it probably belongs
   in theme.css instead.

   Layout note: Insight is a header + centred-column app, not Workspace's
   nav-rail shell, so this doesn't clone that structure. What it does take is
   the visual language — the gradient ground, the floating 16px content card,
   transparent chrome, Inter, and the borders-not-shadows dark mode.
   ============================================================================ */

/* ─── App ground ────────────────────────────────────────────────────────────
   The aqua→periwinkle wash lives on a fixed pseudo-element rather than on
   <body> so it stays put while the page scrolls, instead of stretching to the
   full document height and washing out. */

/* A fixed-height shell, same as Workspace's .theme: the page itself never
   scrolls, each screen scrolls inside <main>. min-height would not do — it's a
   floor, so children stay free to grow and nothing can claim "the leftover
   viewport height". dvh so mobile browser chrome doesn't cost a strip. */
body {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: var(--app-text-sm);
  background: transparent;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--app-ground);
}

/* ─── Header ────────────────────────────────────────────────────────────────
   Workspace's header is transparent over the ground with brand-blue glyphs —
   no solid bar. Text colour comes from --p-text-color, which flips to a near
   white in dark mode, so the same rules read correctly in both schemes. */

header {
  min-height: 64px;
  color: var(--p-text-color);
  padding: var(--app-space-8) var(--app-space-20);
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 100;
  background: transparent;
  gap: var(--app-space-12);
}

.header-left {
  display: flex;
  align-items: center;
  gap: var(--app-space-12);
}

/* The wordmark is two-tone: the aqua half is fixed, the other half is
   `currentColor` so it reads as brand blue on the light ground and near-white
   on the dark one. That's why it's inlined here rather than an <img> —
   currentColor can't cross into an <img>'s isolated SVG document. (The shared-
   conversation page in main.py still uses the plain white logo.svg, which is
   correct: its header stays a solid dark bar.) */
.header-logo {
  height: 30px;
  width: auto;
  color: var(--p-text-color);
  flex-shrink: 0;
}

header h1 {
  font-size: var(--app-text-xl);
  font-weight: var(--app-font-semibold);
  letter-spacing: -0.01em;
}

header .tagline {
  color: var(--p-primary-color);
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-medium);
}

.header-right {
  display: flex;
  align-items: center;
  gap: var(--app-space-8);
  position: relative;
  min-width: 0;
}

/* The tab strip is the only part of the header allowed to run out of room.
   A superadmin sees ten tabs plus the theme toggle, username, role badge,
   Password and Logout; at 1366x768 that overflowed by 9px and clipped Logout,
   and the hamburger fallback only starts at 768px. Letting the tabs scroll
   inside their own track keeps the account controls pinned and reachable at
   any width. Scrollbar hidden — it would sit under the tabs in the header. */
/* Groups everything the mobile hamburger owns. `contents` means the wrapper
   contributes no box of its own on desktop — the tab strip, theme toggle and
   account menu lay out exactly as if it weren't here. It only becomes a real
   element inside the mobile media query. */
.header-menu {
  display: contents;
}

/* Wraps rather than scrolls. The hidden scrollbar meant a superadmin's tabs
   simply ran off the edge with nothing to show it: at 1280px "Admin" was
   gone entirely and "Vessels" was cut mid-word, which reads as a missing
   feature rather than as content out of view. Wrapping costs a second row
   on wide accounts and loses nothing. */
.nav-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-space-4);
  min-width: 0;
  scrollbar-width: none;
}

.nav-tabs::-webkit-scrollbar {
  display: none;
}

/* Tabs read as quiet text buttons until selected — the Workspace "text
   secondary" treatment — so the header stays calm at eight-plus tabs. */
.nav-tab {
  padding: var(--app-space-8) var(--app-space-12);
  background: transparent;
  color: var(--p-text-color);
  border: 1px solid transparent;
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  transition: background var(--app-duration-fast) var(--app-ease-standard),
    color var(--app-duration-fast) var(--app-ease-standard);
  white-space: nowrap;
}

/* Hover is a neutral wash, not the primary tint — the selected state owns the
   brand colour, so hover has to read as "reachable" without competing. 5% is
   tuned, not guessed: measured against the gradient behind the header it moves
   the background 15/255, where selected moves it 26. Weaker than selected but
   still legible. At 8% the two land 9 apart and are hard to tell apart; at 12%
   hover is louder than selected, which inverts the hierarchy. */
.nav-tab:hover {
  background: color-mix(in srgb, var(--p-text-color) 5%, transparent);
}

/* Selected: a tint rather than a solid aqua pill, at the stronger nav-specific
   opacity — enough on its own here, so no border. Weight does the rest.
   Text stays --p-text-color deliberately: aqua-on-tint measures ~2.5:1, which
   is unreadable, while brand blue holds ~7:1. */
.nav-tab.active {
  background: var(--app-nav-selected-background);
  color: var(--p-text-color);
  font-weight: var(--app-font-semibold);
}

/* ─── Account menu ──────────────────────────────────────────────────────────
   The username is the trigger; Password and Logout live behind it. With eleven
   tabs for a superadmin those two buttons overflowed the header at 1366px, and
   the hamburger fallback only starts at 768px. */

.user-menu-wrap {
  position: relative;
  flex-shrink: 0;
}

.user-info {
  display: flex;
  align-items: center;
  gap: var(--app-space-8);
  color: var(--p-text-color);
  font-size: var(--app-text-sm);
  /* It is a button now, so it needs the affordance the plain div never had. */
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--p-border-radius-md);
  padding: var(--app-space-4) var(--app-space-8);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--app-duration-fast) var(--app-ease-standard);
}

.user-info:hover,
.user-info[aria-expanded='true'] {
  background: color-mix(in srgb, var(--p-text-color) 5%, transparent);
}

.user-menu-caret {
  font-size: var(--app-text-2xs);
  color: var(--p-text-muted-color);
  line-height: var(--app-leading-none);
}

.user-menu {
  position: absolute;
  top: calc(100% + var(--app-space-4));
  right: 0;
  min-width: 180px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: var(--app-space-2);
  padding: var(--app-space-4);
  background: var(--p-overlay-background);
  border: 1px solid var(--p-overlay-border-color);
  border-radius: var(--p-border-radius-lg);
  box-shadow: var(--p-shadow-lg);
}

.user-menu button {
  appearance: none;
  background: transparent;
  border: none;
  border-radius: var(--p-border-radius-md);
  padding: var(--app-space-8) var(--app-space-12);
  text-align: left;
  color: var(--p-text-color);
  font-size: var(--app-text-sm);
  cursor: pointer;
  white-space: nowrap;
}

.user-menu button:hover,
.user-menu button:focus-visible {
  background: var(--p-selection-background);
  outline: none;
}

.user-role {
  padding: var(--app-space-2) var(--app-space-8);
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Icon buttons in the header carry no outline — they sit on the ground, not on
   a surface, and a box around each one made the bar read as a toolbar. Hover
   supplies the affordance. The border stays as `transparent` rather than
   `none` so their box matches the account trigger beside them.
   (.btn-logout dropped from this group: the account menu replaced it and the
   class no longer appears in the markup.) */
.btn-theme,
.btn-sidebar-toggle,
.btn-nav-toggle {
  padding: var(--app-space-8) var(--app-space-12);
  background: transparent;
  color: var(--p-text-color);
  border: 1px solid transparent;
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-sm);
  line-height: 1;
  white-space: nowrap;
  transition: background var(--app-duration-fast) var(--app-ease-standard);
}

.btn-theme:hover,
.btn-sidebar-toggle:hover,
.btn-nav-toggle:hover {
  background: color-mix(in srgb, var(--p-text-color) 5%, transparent);
}

.btn-sidebar-toggle,
.btn-nav-toggle {
  display: none;
}

/* Full-bleed, like Workspace's shell — the ground reads as a frame around the
   content rather than a field beside it. A flex column so the active screen can
   claim the leftover viewport height; min-height:0 lets it shrink below its
   content so inner panes scroll instead of pushing the page taller. */
main {
  flex: 1;
  width: 100%;
  padding: 0 var(--app-space-16) var(--app-space-16);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Since <body> no longer scrolls, this is the scroll container for the
     content-sized screens (admin, dashboard). Chat sizes itself to fit, so it
     never triggers this. */
  overflow-y: auto;
}

/* ─── Surfaces ──────────────────────────────────────────────────────────────
   The floating card: 16px radius, shadow-md in light, and in dark a visible
   border instead (shadow tokens resolve to `none` there). */

/* The card is .chat-layout, not its children: the sidebar and the chat column
   are siblings inside it, and they have to read as one surface split by a
   divider rather than two floating panels. overflow:hidden is what lets the
   sidebar's square corners sit inside the 16px radius. */
.chat-layout,
.admin-container,
.login-container {
  background: var(--p-content-background);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-md);
}

/* The chat card is deliberately borderless — it fills the viewport, so its own
   size is the edge and a rule around it just adds noise. The other two are
   content-sized boxes floating on the ground and still need one, which is also
   what carries their elevation in dark mode where shadows resolve to none. */
.admin-container,
.login-container {
  border: 1px solid var(--p-content-border-color);
}

/* width:100% is load-bearing now that <main> is a flex column: the `auto`
   side margins that centre this also switch off the default cross-axis
   stretch, so without an explicit width the card shrink-wraps its content
   (311px) instead of filling to its 400px cap. */
.login-container {
  width: 100%;
  max-width: 400px;
  margin: var(--app-space-48) auto;
  padding: var(--app-space-32);
  flex-shrink: 0;
}

.login-container h2 {
  margin-bottom: var(--app-space-24);
  color: var(--p-text-color);
  font-size: var(--app-text-2xl);
  font-weight: var(--app-font-semibold);
}

/* Transparent — it's a column inside the .chat-layout card, not a card itself.
   The card went full-bleed, which is right for the card and wrong for prose:
   at 1293px the transcript hit 102 characters per line and it scales with the
   monitor. This gutter centres a fixed-width reading column inside the card —
   the scrollbar stays on the card edge, only the content is constrained. */
.chat-container {
  --chat-gutter: max(var(--app-space-24), calc((100% - 880px) / 2));
  background: transparent;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  min-width: 0;
}

.admin-container {
  padding: var(--app-space-24);
}

/* ─── Forms ─────────────────────────────────────────────────────────────────
   1px borders (Aura) rather than the old 2px, with the focus ring carrying the
   emphasis instead of a thicker edge. */

.form-group {
  margin-bottom: var(--app-space-16);
}

.form-group label {
  display: block;
  margin-bottom: var(--app-space-8);
  font-weight: var(--app-font-medium);
  font-size: var(--app-text-sm);
  color: var(--p-text-color);
}

.form-input,
.input-area textarea,
.filter-row select,
.vessel-typeahead input {
  width: 100%;
  padding: var(--app-space-8) var(--app-space-12);
  background: var(--p-form-field-background);
  color: var(--p-text-color);
  border: 1px solid var(--p-form-field-border-color);
  border-radius: var(--p-border-radius-md);
  font-size: var(--app-text-sm);
  outline: none;
  transition: border-color var(--app-duration-fast) var(--app-ease-standard),
    box-shadow var(--app-duration-fast) var(--app-ease-standard);
}

.form-input:hover,
.input-area textarea:hover {
  border-color: var(--p-form-field-hover-border-color);
}

.form-input:focus,
.input-area textarea:focus,
.filter-row select:focus,
.vessel-typeahead input:focus {
  border-color: var(--p-primary-color);
  box-shadow: 0 0 0 3px var(--p-selection-background);
}

.form-input::placeholder,
.input-area textarea::placeholder {
  color: var(--p-text-muted-color);
}

/* ─── Buttons ───────────────────────────────────────────────────────────────
   Workspace's convention: solid primary = the CTA in a context; solid secondary
   = the dismissive partner in a dialog footer; danger = destructive. Aura's
   radius throughout — the old 3.75rem pills are what read least like Workspace.
   Primary is aqua, not brand blue: blue is for text and secondary actions. */

.btn-primary,
.btn-add,
.btn-new-chat,
.btn-respond,
.btn-reopen {
  padding: var(--app-space-8) var(--app-space-16);
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
  border: 1px solid var(--p-primary-color);
  border-radius: var(--p-border-radius-md);
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  cursor: pointer;
  transition: background var(--app-duration-fast) var(--app-ease-standard),
    border-color var(--app-duration-fast) var(--app-ease-standard);
}

.btn-primary:hover,
.btn-add:hover,
.btn-new-chat:hover,
.btn-respond:hover,
.btn-reopen:hover {
  background: var(--p-primary-hover-color);
  border-color: var(--p-primary-hover-color);
}

/* The auth card's submit is the one deliberately full-width button: it's the
   only action on the card, so stretching reads as "end of the form". Scoped to
   .login-container (login + invite screens) rather than living on .btn-primary
   itself — the same class is the CTA in fifteen modal footers, where stretching
   is wrong and where a different font-size made it a different height from the
   Cancel button beside it. */
.login-container .btn-primary {
  width: 100%;
  padding: var(--app-space-8) var(--app-space-16);
  /* Same size as a dialog footer action — this is the same kind of control: the
     terminal decision on a card, not a toolbar button. */
  font-size: var(--app-text-base);
}

.btn-primary:disabled,
.btn-add:disabled,
.btn-new-chat:disabled {
  background: var(--p-surface-400);
  border-color: var(--p-surface-400);
  cursor: not-allowed;
}

/* Solid secondary — Cancel in a dialog footer. Brand blue, per the button
   conventions table. */
.btn-secondary,
.btn-edit,
.btn-close-fb {
  padding: var(--app-space-8) var(--app-space-16);
  background: var(--p-dualog-blue-500);
  color: var(--p-content-background);
  border: 1px solid var(--p-dualog-blue-500);
  border-radius: var(--p-border-radius-md);
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  cursor: pointer;
  transition: opacity var(--app-duration-fast) var(--app-ease-standard);
}

/* No flex:1 — footer buttons hug their content. See .modal-actions. */

.btn-secondary:hover,
.btn-edit:hover,
.btn-close-fb:hover {
  opacity: 0.85;
}

.btn-delete {
  background: var(--p-danger-color);
  color: var(--p-danger-contrast-color);
  border: 1px solid var(--p-danger-color);
}

.btn-delete:hover {
  background: var(--p-danger-hover-color);
  border-color: var(--p-danger-hover-color);
}

.btn-action {
  padding: var(--app-space-4) var(--app-space-8);
  border: none;
  border-radius: var(--p-border-radius-sm);
  cursor: pointer;
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-medium);
  margin-right: var(--app-space-4);
}

/* Outlined secondary — available but dismissive. */
.btn-feedback {
  padding: var(--app-space-8) var(--app-space-12);
  background: transparent;
  color: var(--p-primary-color);
  border: 1px solid var(--p-primary-color);
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  transition: background var(--app-duration-fast) var(--app-ease-standard);
  white-space: nowrap;
}

.btn-feedback:hover {
  background: var(--p-selection-background);
}

.btn-sm {
  padding: var(--app-space-4) var(--app-space-12);
  font-size: var(--app-text-xs);
}

/* Carries the same 1px border as the real buttons even though it's an anchor —
   without it this sits 2px shorter than the Close button beside it in the file
   viewer's footer. */
.download-btn {
  display: inline-block;
  padding: var(--app-space-8) var(--app-space-16);
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
  text-decoration: none;
  border: 1px solid var(--p-primary-color);
  border-radius: var(--p-border-radius-md);
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  transition: background var(--app-duration-fast) var(--app-ease-standard),
    border-color var(--app-duration-fast) var(--app-ease-standard);
}

.download-btn:hover {
  background: var(--p-primary-hover-color);
  border-color: var(--p-primary-hover-color);
}

.error-msg {
  color: var(--p-danger-color);
  font-size: var(--app-text-sm);
  margin-top: var(--app-space-16);
}

/* ─── Chat ──────────────────────────────────────────────────────────────────
   Sent bubbles take Workspace's periwinkle (dualogPurple.subtle) rather than a
   solid brand-blue fill, so the transcript stays legible as dark-on-light in
   both schemes instead of flipping to white-on-blue. */

/* min-height:0 is what actually makes this scroll. It has had overflow-y:auto
   since before the redesign, but a flex item won't shrink below its content
   without this, so the transcript used to push the whole page taller instead
   of scrolling in place. */
.messages {
  flex: 1;
  min-height: 0;
  padding: var(--app-space-24) var(--chat-gutter);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--app-space-16);
}

/* Everything below the transcript keeps its natural height — only the message
   list absorbs the slack. */
.saved-queries,
.shared-queries,
.examples,
.input-area {
  flex-shrink: 0;
}

.message {
  /* Bounded by measure, not just by percentage: 80% of the reading column is
     still ~94 characters, past the ~75-90 where lines get hard to track back
     from. `ch` keeps that true if the type scale ever moves. */
  max-width: min(80%, 72ch);
  padding: var(--app-space-12) var(--app-space-16);
  border-radius: var(--p-border-radius-xl);
  line-height: var(--app-leading-relaxed);
  font-size: var(--app-text-sm);
}

.message.user {
  background: var(--p-dualog-purple-subtle);
  color: var(--p-text-color);
  align-self: flex-end;
  border-bottom-right-radius: var(--p-border-radius-sm);
  position: relative;
}

.message.assistant {
  background: var(--p-surface-200);
  color: var(--p-text-color);
  align-self: flex-start;
  border-bottom-left-radius: var(--p-border-radius-sm);
  position: relative;
}

body.dark .message.assistant {
  background: var(--p-surface-800);
}

.message pre {
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  padding: var(--app-space-12);
  border-radius: var(--p-border-radius-md);
  overflow-x: auto;
  margin: var(--app-space-8) 0;
  font-size: var(--app-text-xs);
}

.message.loading {
  color: var(--p-text-muted-color);
  font-style: italic;
}

.generated-file {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--app-space-8);
}

.generated-file .file-icon {
  font-size: var(--app-text-3xl);
}

.generated-file .file-name {
  font-weight: var(--app-font-medium);
  color: var(--p-text-color);
}

/* Rating buttons */
.rating-bar {
  display: flex;
  gap: var(--app-space-4);
  margin-top: var(--app-space-8);
  padding-top: var(--app-space-8);
  border-top: 1px solid var(--p-content-border-color);
}

.rating-btn {
  background: none;
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-sm);
  cursor: pointer;
  padding: var(--app-space-2) var(--app-space-8);
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  transition: all var(--app-duration-fast) var(--app-ease-standard);
}

.rating-btn:hover {
  color: var(--p-text-color);
  border-color: var(--p-text-muted-color);
}

.rating-btn.selected-up {
  color: var(--p-success-color);
  border-color: var(--p-success-color);
}

.rating-btn.selected-down {
  color: var(--p-danger-color);
  border-color: var(--p-danger-color);
}

/* ─── Source / document references ──────────────────────────────────────── */

.document-list {
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-lg);
  padding: var(--app-space-12);
  margin-top: var(--app-space-8);
}

.document-list-header {
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  margin-bottom: var(--app-space-8);
  font-size: var(--app-text-sm);
  cursor: pointer;
  list-style: none;
}

.document-list-header::-webkit-details-marker {
  display: none;
}

.document-list-header::before {
  content: '▶ ';
  font-size: var(--app-text-2xs);
}

details[open] > .document-list-header::before {
  content: '▼ ';
}

.document-item {
  display: flex;
  align-items: flex-start;
  gap: var(--app-space-8);
  padding: var(--app-space-4) 0;
  border-bottom: 1px solid var(--p-content-border-color);
}

.document-item:last-child {
  border-bottom: none;
}

.doc-icon {
  font-size: var(--app-text-base);
  flex-shrink: 0;
  line-height: var(--app-leading-normal);
}

/* subject + meta stacked; min-width:0 lets the subject wrap inside the flex row */
.doc-body {
  display: flex;
  flex-direction: column;
  gap: var(--app-space-2);
  min-width: 0;
  flex: 1;
}

.doc-link {
  color: var(--app-link-color);
  text-decoration: none;
  font-weight: var(--app-font-medium);
  white-space: normal;
  overflow-wrap: anywhere;
}

.doc-link:hover {
  text-decoration: underline;
  color: var(--app-link-hover-color);
}

/* date · sender line under each source; wraps instead of truncating */
.doc-meta {
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
  white-space: normal;
  overflow-wrap: anywhere;
}

.doc-path {
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Markdown tables in chat answers */
.md-table-wrap {
  overflow-x: auto;
  margin: var(--app-space-8) 0;
}

.md-table {
  border-collapse: collapse;
  font-size: var(--app-text-xs);
}

.md-table th,
.md-table td {
  border: 1px solid var(--p-content-border-color);
  padding: var(--app-space-4) var(--app-space-8);
  text-align: left;
  white-space: nowrap;
}

.md-table th {
  background: var(--p-surface-200);
  color: var(--p-text-color);
  font-weight: var(--app-font-semibold);
}

.md-table tr:nth-child(even) td {
  background: var(--p-surface-100);
}

/* surface.700, not surface.800 — the assistant bubble these tables sit inside
   is already surface.800, so a matching header row would vanish into it and the
   table would lose its header entirely in dark mode. */
body.dark .md-table th {
  background: var(--p-surface-700);
}

body.dark .md-table tr:nth-child(even) td {
  background: var(--p-surface-950);
}

/* ─── Composer ──────────────────────────────────────────────────────────── */

.input-area {
  padding: var(--app-space-16) var(--chat-gutter);
  background: var(--p-content-background);
  border-top: 1px solid var(--p-content-border-color);
  display: flex;
  gap: var(--app-space-8);
  align-items: flex-end;
}

.input-area textarea {
  flex: 1;
  font-family: inherit;
  resize: none;
  overflow: hidden;
  min-height: 40px;
  max-height: 10rem;
  line-height: var(--app-leading-normal);
}

.input-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-base);
  flex-shrink: 0;
  transition: all var(--app-duration-fast) var(--app-ease-standard);
  border: 1px solid transparent;
}

.input-btn.primary {
  background: var(--p-primary-color);
  border-color: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
}

.input-btn.primary:hover {
  background: var(--p-primary-hover-color);
  border-color: var(--p-primary-hover-color);
}

.input-btn.primary:disabled {
  background: var(--p-surface-400);
  border-color: var(--p-surface-400);
  cursor: not-allowed;
}

.input-btn.ghost {
  background: transparent;
  color: var(--p-text-muted-color);
  border-color: var(--p-content-border-color);
}

.input-btn.ghost:hover {
  color: var(--p-primary-color);
  border-color: var(--p-primary-color);
  background: var(--p-selection-background);
}

/* ─── Example prompts ───────────────────────────────────────────────────── */

.examples {
  padding: var(--app-space-8) var(--chat-gutter) var(--app-space-12);
}

.examples h3 {
  font-size: var(--app-text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--app-font-semibold);
  color: var(--p-text-muted-color);
  margin-bottom: var(--app-space-8);
}

.examples ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-space-8);
}

.examples li {
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  padding: var(--app-space-4) var(--app-space-12);
  border-radius: var(--app-radius-pill);
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  cursor: pointer;
  transition: all var(--app-duration-fast) var(--app-ease-standard);
}

.examples li:hover {
  border-color: var(--p-primary-color);
  color: var(--p-primary-color);
  background: var(--p-selection-background);
}

.examples-more {
  margin-top: var(--app-space-8);
  font-size: var(--app-text-xs);
}

.examples-more a {
  color: var(--app-link-color);
  text-decoration: none;
}

.examples-more a:hover {
  text-decoration: underline;
}

/* ─── Admin ─────────────────────────────────────────────────────────────── */

.admin-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--app-space-24);
  padding-bottom: var(--app-space-16);
  border-bottom: 1px solid var(--p-content-border-color);
}

.admin-header h2 {
  color: var(--p-text-color);
  font-size: var(--app-text-xl);
  font-weight: var(--app-font-semibold);
}

.users-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--app-text-sm);
}

.users-table th,
.users-table td {
  padding: var(--app-space-12) var(--app-space-16);
  text-align: left;
  border-bottom: 1px solid var(--p-content-border-color);
}

.users-table th {
  background: var(--p-surface-200);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

body.dark .users-table th {
  background: var(--p-surface-800);
}

.users-table tr:hover {
  background: var(--p-selection-background);
}

.role-badge {
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.role-admin {
  background: var(--p-dualog-blue-500);
  color: var(--p-content-background);
}

.role-manager {
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
}

.role-user {
  background: var(--p-surface-400);
  color: var(--p-surface-950);
}

.permissions-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-space-4);
}

.permission-tag {
  background: var(--p-surface-200);
  color: var(--p-text-color);
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  display: flex;
  align-items: center;
  gap: var(--app-space-4);
}

body.dark .permission-tag {
  background: var(--p-surface-800);
}

.permission-tag .remove,
.vessel-tag .remove {
  cursor: pointer;
  color: var(--p-danger-color);
  font-weight: var(--app-font-bold);
}

.vessel-tag {
  background: var(--p-dualog-purple-subtle);
  color: var(--p-text-color);
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  display: flex;
  align-items: center;
  gap: var(--app-space-4);
}

/* fixed, not absolute: it is positioned from getBoundingClientRect (viewport
   coords) and <body> no longer scrolls, so absolute + window.scrollY would be
   right only by coincidence. */
.vessel-typeahead {
  position: fixed;
  z-index: 1000;
  background: var(--p-overlay-background);
  border: 1px solid var(--p-overlay-border-color);
  border-radius: var(--p-border-radius-lg);
  box-shadow: var(--p-shadow-lg);
  width: 260px;
  max-height: 280px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.vessel-typeahead input {
  border: none;
  border-bottom: 1px solid var(--p-content-border-color);
  border-radius: 0;
  padding: var(--app-space-8);
}

.vessel-typeahead input:focus {
  box-shadow: none;
  border-color: var(--p-content-border-color);
  border-bottom-color: var(--p-primary-color);
}

.vessel-typeahead .vt-results {
  overflow-y: auto;
  max-height: 220px;
}

.vessel-typeahead .vt-item {
  padding: var(--app-space-8) var(--app-space-12);
  cursor: pointer;
  font-size: var(--app-text-sm);
  border-radius: var(--p-border-radius-md);
  margin: var(--app-space-2) var(--app-space-4);
}

.vessel-typeahead .vt-item:hover {
  background: var(--p-selection-background);
  color: var(--p-text-color);
}

.vessel-typeahead .vt-empty {
  padding: var(--app-space-8);
  color: var(--p-text-muted-color);
  font-size: var(--app-text-sm);
  text-align: center;
}

/* ─── Modals ────────────────────────────────────────────────────────────── */

.modal {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal.show {
  display: flex;
}

.modal-content {
  background: var(--p-overlay-background);
  color: var(--p-text-color);
  border: 1px solid var(--p-overlay-border-color);
  padding: var(--app-space-24);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-xl);
  max-width: 450px;
  width: 90%;
}

.modal-content h3 {
  margin-bottom: var(--app-space-24);
  color: var(--p-text-color);
  font-size: var(--app-text-xl);
  font-weight: var(--app-font-semibold);
}

/* Dialog footer. Buttons hug their content and sit bottom-right, dismissive
   first and affirmative last, so the action the dialog is for is the closest
   thing to the user's cursor. They are not a segmented control — stretching
   them to fill the dialog gives a 200px "Close" that reads as important as the
   CTA beside it. Every control here inherits the same font-size, padding and
   1px border, so they come out the same height without anyone setting one. */
.modal-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--app-space-8);
  margin-top: var(--app-space-24);
}

/* `a` as well as `button` — the file viewer's download action is an anchor. */
.modal-actions > button,
.modal-actions > a {
  flex: 0 0 auto;
  width: auto;
  /* A step up from the app's default `sm` — dialog actions are the terminal
     decision on a modal surface and carry more weight than a toolbar button. */
  font-size: var(--app-text-base);
  /* Without this a two-word label ("Send & Close") wraps inside its own box and
     comes out taller than its neighbours, which is the height inconsistency
     wearing a different hat. */
  white-space: nowrap;
}

/* ─── Email viewer ──────────────────────────────────────────────────────── */

.email-link {
  color: var(--app-link-color);
  text-decoration: underline;
  cursor: pointer;
  font-weight: var(--app-font-medium);
}

.email-link:hover,
.message.assistant .email-link:hover {
  color: var(--app-link-hover-color);
}

.message.assistant .email-link {
  color: var(--app-link-color);
}

.email-modal .modal-content {
  max-width: 800px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}

/* The source popup is where a user decides whether to trust an answer, so the
   header has to be scannable: subject first, then who and when, with the labels
   quiet enough to disappear. */
.email-header {
  border-bottom: 1px solid var(--p-content-border-color);
  padding: 0 0 var(--app-space-16) var(--app-space-12);
  margin-bottom: var(--app-space-16);
  border-left: 3px solid var(--p-primary-color);
}

.email-header h3 {
  margin-bottom: var(--app-space-8);
  color: var(--p-text-color);
  font-size: var(--app-text-lg);
  font-weight: var(--app-font-semibold);
  line-height: var(--app-leading-tight);
}

.email-meta {
  display: flex;
  flex-direction: column;
  gap: var(--app-space-2);
  font-size: var(--app-text-sm);
}

.email-meta-row {
  display: flex;
  align-items: baseline;
  gap: var(--app-space-12);
}

.email-meta-row.hidden {
  display: none;
}

.email-meta-label {
  flex: 0 0 3.2rem;
  color: var(--p-text-muted-color);
  font-size: var(--app-text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--app-font-semibold);
}

.email-meta-value {
  color: var(--p-text-color);
  min-width: 0;
  overflow-wrap: anywhere;
}

.email-addr {
  color: var(--p-text-color);
}

.email-folder-chip {
  display: inline-block;
  background: var(--p-selection-background);
  color: var(--p-primary-color);
  border-radius: var(--app-radius-pill);
  padding: var(--app-space-2) var(--app-space-8);
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-medium);
}

.email-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--app-space-16);
  background: var(--p-surface-200);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-lg);
  white-space: pre-wrap;
  font-size: var(--app-text-sm);
  line-height: var(--app-leading-relaxed);
  max-height: 50vh;
}

body.dark .email-body {
  background: var(--p-surface-950);
}

/* Replied-to text is context, not the message. Dimming it makes the new
   content in a long chain obvious at a glance. */
.email-quote {
  display: block;
  color: var(--p-text-muted-color);
  border-left: 2px solid var(--p-content-border-color);
  padding-left: var(--app-space-12);
  margin: var(--app-space-2) 0 var(--app-space-8) var(--app-space-2);
}

.email-sep {
  display: block;
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
  margin-top: var(--app-space-8);
}

.email-empty {
  color: var(--p-text-muted-color);
  font-style: italic;
}

.email-attach-block {
  margin-top: var(--app-space-16);
  padding-top: var(--app-space-12);
  border-top: 1px dashed var(--p-content-border-color);
}

.email-attach-title {
  font-size: var(--app-text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--app-font-semibold);
  color: var(--p-primary-color);
  margin-bottom: var(--app-space-8);
}

.email-loading {
  text-align: center;
  padding: var(--app-space-32);
  color: var(--p-text-muted-color);
}

/* ─── Thread view ───────────────────────────────────────────────────────── */

.modal-large .modal-content {
  max-width: 900px;
}

.thread-view {
  display: flex;
  flex-direction: column;
  gap: var(--app-space-16);
  max-height: 60vh;
  overflow-y: auto;
  padding: var(--app-space-16);
}

.thread-email {
  border-left: 3px solid var(--p-dualog-blue-300);
  padding: var(--app-space-16);
  margin-left: var(--app-space-16);
  background: var(--p-surface-200);
  border-radius: 0 var(--p-border-radius-lg) var(--p-border-radius-lg) 0;
}

body.dark .thread-email {
  background: var(--p-surface-950);
}

.thread-email.root {
  margin-left: 0;
  border-left-color: var(--p-primary-color);
}

.thread-email-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: var(--app-space-8);
}

.thread-email-from {
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
}

.thread-email-date,
.thread-email-subject,
.thread-count {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.thread-email-subject {
  font-size: var(--app-text-sm);
  margin-bottom: var(--app-space-8);
}

.thread-count {
  font-size: var(--app-text-sm);
  margin-bottom: var(--app-space-16);
}

.thread-email-body {
  font-size: var(--app-text-sm);
  line-height: var(--app-leading-relaxed);
  white-space: pre-wrap;
  color: var(--p-text-color);
}

/* ─── File viewer ───────────────────────────────────────────────────────── */

.file-type-badge {
  display: inline-block;
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  /* allow-hex: vendor file-format colours (Adobe / Microsoft), not brand */
  color: #ffffff;
}

/* allow-hex: canonical file-format brand colours */
.file-type-pdf {
  background: #e74c3c;
}
.file-type-docx {
  background: #2b5797;
}
.file-type-xlsx {
  background: #217346;
}
.file-type-pptx {
  background: #d04423;
}
.file-type-txt,
.file-type-md {
  background: var(--p-surface-600);
}

.file-link {
  color: var(--app-link-color);
  text-decoration: underline;
  cursor: pointer;
  font-weight: var(--app-font-medium);
}

.file-link:hover {
  color: var(--app-link-hover-color);
}

/* ─── Badges ────────────────────────────────────────────────────────────── */

.nav-tab-wrapper {
  position: relative;
  display: inline-flex;
}

.notification-badge,
.alert-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  color: var(--p-danger-contrast-color);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-bold);
  min-width: 16px;
  height: 16px;
  border-radius: var(--app-radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--app-space-4);
}

.notification-badge {
  background: var(--p-danger-color);
}

.alert-badge {
  background: var(--p-warning-color);
}

/* ─── Dashboard ─────────────────────────────────────────────────────────── */

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--app-space-16);
  margin-bottom: var(--app-space-24);
}

.stat-card,
.usage-stat,
.chart-container,
.feedback-item {
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-sm);
}

.stat-card {
  padding: var(--app-space-20);
  text-align: center;
}

.stat-card .stat-value {
  font-size: var(--app-text-3xl);
  font-weight: var(--app-font-bold);
  color: var(--p-primary-color);
  line-height: var(--app-leading-tight);
}

.stat-card .stat-label,
.usage-stat .stat-label {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: var(--app-space-4);
}

.charts-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--app-space-24);
  margin-bottom: var(--app-space-24);
}

@media (max-width: 768px) {
  .charts-row {
    grid-template-columns: 1fr;
  }
}

.chart-container {
  padding: var(--app-space-20);
}

/* Chart.js sizes a responsive canvas from its parent, so the parent has to own
   the height. Without this the charts keep `maintainAspectRatio`, which ties
   height to container width — fine while <main> was capped at 1100px, but the
   card is full-bleed now, so on a 2560px display each column is ~1250px wide
   and the doughnut would render ~1250px tall. Fixed height instead; Workspace's
   own <app-chart> wrapper does the same thing for the same reason. */
.chart-canvas {
  position: relative;
  height: 280px;
}

.chart-container h3 {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  margin-bottom: var(--app-space-16);
}

.dashboard-section {
  margin-bottom: var(--app-space-24);
}

.dashboard-section h3 {
  color: var(--p-text-color);
  margin-bottom: var(--app-space-16);
  font-size: var(--app-text-base);
  font-weight: var(--app-font-semibold);
}

.filter-row {
  display: flex;
  gap: var(--app-space-12);
  align-items: center;
  margin-bottom: var(--app-space-16);
}

.filter-row select {
  width: auto;
}

.usage-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--app-space-16);
  margin-bottom: var(--app-space-24);
}

.usage-stat {
  padding: var(--app-space-16);
  text-align: center;
}

.usage-stat .stat-value {
  font-size: var(--app-text-2xl);
  font-weight: var(--app-font-bold);
  color: var(--p-primary-color);
}

/* ─── Feedback ──────────────────────────────────────────────────────────── */

.feedback-tabs {
  display: flex;
  gap: var(--app-space-8);
  margin-bottom: var(--app-space-16);
}

.feedback-tab {
  padding: var(--app-space-8) var(--app-space-16);
  background: var(--p-content-background);
  color: var(--p-text-color);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-sm);
  transition: all var(--app-duration-fast) var(--app-ease-standard);
}

.feedback-tab.active {
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
  border-color: var(--p-primary-color);
}

.feedback-item {
  padding: var(--app-space-16);
  margin-bottom: var(--app-space-12);
}

.feedback-meta {
  display: flex;
  align-items: center;
  gap: var(--app-space-8);
  margin-bottom: var(--app-space-8);
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.feedback-category {
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--p-primary-contrast-color);
}

.category-bug {
  background: var(--p-danger-color);
}
.category-feature {
  background: var(--p-info-color);
}
.category-comment {
  background: var(--p-success-color);
}
.category-general {
  background: var(--p-surface-500);
}

.feedback-message {
  font-size: var(--app-text-sm);
  line-height: var(--app-leading-relaxed);
  margin-bottom: var(--app-space-8);
}

.feedback-response {
  background: var(--p-surface-200);
  border-left: 3px solid var(--p-primary-color);
  padding: var(--app-space-12);
  margin-top: var(--app-space-8);
  border-radius: 0 var(--p-border-radius-md) var(--p-border-radius-md) 0;
  font-size: var(--app-text-sm);
}

body.dark .feedback-response {
  background: var(--p-surface-950);
}

.feedback-actions {
  display: flex;
  gap: var(--app-space-8);
  margin-top: var(--app-space-12);
}

.feedback-actions button {
  padding: var(--app-space-4) var(--app-space-12);
  border: none;
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-medium);
}

/* ─── Sidebar ───────────────────────────────────────────────────────────── */

/* Claims the leftover viewport height rather than sizing to its content.
   min-height:0 is load-bearing: a flex item defaults to min-height:auto, which
   refuses to shrink below its content, so without it the card would grow past
   the viewport and overflow:hidden would clip the transcript out of reach. */
.chat-layout {
  display: flex;
  gap: 0;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* Width is a variable so the drag handle can set it; 380px is the default and
   what you get with no stored preference. Clamps live in the JS alongside the
   drag, so there is one source of truth for the bounds. */
.sidebar {
  width: var(--sidebar-width, 380px);
  background: transparent;
  border-right: 1px solid var(--p-content-border-color);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  flex-shrink: 0;
}

/* Straddles the divider: 5px of grab area, pulled back 2px each side so it
   occupies 1px of layout and the sidebar's border still reads as the line. */
.sidebar-resizer {
  flex: 0 0 5px;
  margin: 0 -2px;
  position: relative;
  z-index: 2;
  cursor: col-resize;
  background: transparent;
  touch-action: none;
}

.sidebar-resizer::after {
  content: '';
  position: absolute;
  inset: 0 2px;
  background: var(--p-primary-color);
  opacity: 0;
  transition: opacity var(--app-duration-fast) var(--app-ease-standard);
}

.sidebar-resizer:hover::after,
.sidebar-resizer:focus-visible::after,
.sidebar-resizer.dragging::after {
  opacity: 1;
}

.sidebar-resizer:focus-visible {
  outline: none;
}

/* Kills text selection and the I-beam cursor while dragging across the app. */
body.resizing-sidebar {
  user-select: none;
  cursor: col-resize;
}

.sidebar-header {
  padding: var(--app-space-16);
  border-bottom: 1px solid var(--p-content-border-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--app-space-8);
}

.sidebar-header h3 {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--app-font-semibold);
}

.btn-new-chat {
  font-size: var(--app-text-xs);
  padding: var(--app-space-4) var(--app-space-12);
}

.sidebar-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--app-space-8);
}

.sidebar-group-label {
  font-size: var(--app-text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--p-text-muted-color);
  padding: var(--app-space-8) var(--app-space-8) var(--app-space-4);
  font-weight: var(--app-font-semibold);
  display: flex;
  align-items: center;
  gap: var(--app-space-4);
  cursor: pointer;
  user-select: none;
  border-radius: var(--p-border-radius-md);
}

.sidebar-group-label:hover {
  color: var(--p-text-color);
}

.sidebar-caret {
  font-size: var(--app-text-2xs);
  line-height: var(--app-leading-none);
  width: 0.6rem;
}

/* The count is why collapsing is safe: a folded group still says how much is
   in it. */
.sidebar-group-count {
  margin-left: auto;
  background: var(--p-surface-200);
  color: var(--p-text-muted-color);
  border-radius: var(--app-radius-pill);
  padding: 0 var(--app-space-8);
  font-size: var(--app-text-2xs);
}

body.dark .sidebar-group-count {
  background: var(--p-surface-800);
}

.sidebar-group-items.hidden {
  display: none;
}

.sidebar-item {
  padding: var(--app-space-8) var(--app-space-12);
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-sm);
  color: var(--p-text-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--app-space-8);
  white-space: nowrap;
  overflow: hidden;
}

.sidebar-item:hover {
  background: var(--p-selection-background);
}

/* Selected rows use the shared selection tint rather than a solid fill — the
   Workspace pattern, so the label keeps its normal colour. */
.sidebar-item.active {
  background: var(--p-selection-background);
  color: var(--p-text-color);
  font-weight: var(--app-font-medium);
}

.sidebar-item-title {
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

.sidebar-item .sidebar-delete {
  display: none;
  background: none;
  border: none;
  color: var(--p-danger-color);
  cursor: pointer;
  font-size: var(--app-text-sm);
  padding: 0;
  flex-shrink: 0;
}

.sidebar-item:hover .sidebar-delete,
.sidebar-item.active .sidebar-delete {
  display: inline;
}

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 998;
}

.sidebar-backdrop.show {
  display: block;
}

.btn-load-more {
  width: calc(100% - var(--app-space-16));
  margin: var(--app-space-8);
  padding: var(--app-space-8);
  background: transparent;
  color: var(--p-primary-color);
  border: 1px dashed var(--p-content-border-color);
  border-radius: var(--p-border-radius-md);
  cursor: pointer;
  font-size: var(--app-text-xs);
}

.btn-load-more:hover {
  background: var(--p-selection-background);
}

/* ─── Saved / shared prompt chips ───────────────────────────────────────── */

.saved-queries,
.shared-queries {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-space-8);
  padding: var(--app-space-8) var(--chat-gutter) 0;
}

.saved-chip {
  background: var(--p-dualog-blue-500);
  color: var(--p-content-background);
  padding: var(--app-space-4) var(--app-space-12);
  border-radius: var(--app-radius-pill);
  font-size: var(--app-text-xs);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--app-space-4);
  transition: opacity var(--app-duration-fast) var(--app-ease-standard);
  max-width: 220px;
  position: relative;
}

.saved-chip:hover {
  opacity: 0.85;
}

.saved-chip.shared {
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
}

.saved-chip .chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.saved-chip:hover .chip-tooltip {
  display: block;
}

.chip-tooltip {
  display: none;
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--p-surface-900);
  /* allow-hex: white on a permanently dark tooltip, theme-agnostic */
  color: #ffffff;
  padding: var(--app-space-8) var(--app-space-12);
  border-radius: var(--p-border-radius-md);
  font-size: var(--app-text-xs);
  white-space: normal;
  /* From main: an absolutely positioned child sizes against its containing
     block, so without this it collapsed to the chip's own width and rendered as
     a ~90px column of single words. */
  width: max-content;
  max-width: 340px;
  z-index: 100;
  pointer-events: none;
  box-shadow: var(--p-shadow-md);
}

body.dark .chip-tooltip {
  background: var(--p-surface-700);
}

.saved-chip .chip-remove,
.saved-chip .chip-share {
  font-size: var(--app-text-2xs);
  opacity: 0.7;
  cursor: pointer;
  line-height: var(--app-leading-none);
}

.saved-chip .chip-remove:hover,
.saved-chip .chip-share:hover,
.saved-chip .chip-share.on {
  opacity: 1;
}

/* Prompts a colleague shared, kept below a line so your own row stays yours.
   Hidden entirely when nobody has shared anything. */
.shared-queries {
  margin-top: var(--app-space-8);
  border-top: 1px solid var(--p-content-border-color);
}

.shared-queries.hidden {
  display: none;
}

.shared-queries .shared-label {
  width: 100%;
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  margin-bottom: var(--app-space-2);
}

/* ─── Inline message actions ────────────────────────────────────────────── */

.msg-star,
.msg-delete {
  position: absolute;
  top: var(--app-space-4);
  right: var(--app-space-4);
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  opacity: 0;
  transition: opacity var(--app-duration-fast) var(--app-ease-standard),
    color var(--app-duration-fast) var(--app-ease-standard);
  padding: var(--app-space-2);
  line-height: var(--app-leading-none);
}

.message.user .msg-delete {
  right: var(--app-space-32);
}

.message:hover .msg-star,
.message:hover .msg-delete {
  opacity: 1;
}

/* allow-hex: gold star is a fixed affordance colour, not a brand token */
.msg-star:hover,
.msg-star.saved {
  color: #ffc233;
}

.msg-star.saved {
  opacity: 1;
}

.msg-delete:hover {
  color: var(--p-danger-color);
}

/* Mobile: always show action buttons (no hover) */
@media (max-width: 768px) {
  .msg-star,
  .msg-delete {
    opacity: 1;
    font-size: var(--app-text-base);
    padding: var(--app-space-4);
  }
}

/* ─── Digests ───────────────────────────────────────────────────────────── */

.digest-item,
.alert-item {
  padding: var(--app-space-12);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-lg);
  margin-bottom: var(--app-space-8);
}

.digest-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.digest-item-info {
  flex: 1;
}

.digest-item-info .digest-name {
  font-weight: var(--app-font-medium);
}

.digest-item-info .digest-schedule {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.digest-actions {
  display: flex;
  gap: var(--app-space-8);
  align-items: center;
}

.toggle-switch {
  position: relative;
  width: 40px;
  height: 22px;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--p-surface-400);
  border-radius: var(--app-radius-pill);
  transition: background var(--app-duration-base) var(--app-ease-standard);
}

.toggle-slider:before {
  content: '';
  position: absolute;
  height: 16px;
  width: 16px;
  left: 3px;
  bottom: 3px;
  /* allow-hex: the knob stays white on both the off and on track */
  background: #ffffff;
  border-radius: 50%;
  transition: transform var(--app-duration-base) var(--app-ease-standard);
}

.toggle-switch input:checked + .toggle-slider {
  background: var(--p-primary-color);
}

.toggle-switch input:checked + .toggle-slider:before {
  transform: translateX(18px);
}

/* ─── Alerts ────────────────────────────────────────────────────────────── */

.alert-item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--app-space-4);
}

.alert-item-name {
  font-weight: var(--app-font-medium);
}

.alert-item-query {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  margin-bottom: var(--app-space-8);
}

.alert-item-footer {
  display: flex;
  gap: var(--app-space-8);
  align-items: center;
}

.alert-pending {
  background: color-mix(in srgb, var(--p-warning-color) 18%, transparent);
  color: var(--p-warning-color);
  padding: var(--app-space-2) var(--app-space-8);
  border-radius: var(--app-radius-pill);
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-semibold);
}

/* ─── Prompt library ────────────────────────────────────────────────────── */

.library-modal-content {
  max-width: 780px;
  width: 92%;
}

.library-intro,
.hub-summary,
.lib-admin-note,
.lib-admin-body {
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
}

.library-intro {
  margin-bottom: var(--app-space-12);
}

.hub-summary {
  margin: 0 0 var(--app-space-16);
}

.hub-summary .hub-down {
  color: var(--p-warning-color);
}

.library-body {
  max-height: 60vh;
  overflow-y: auto;
  padding-right: var(--app-space-4);
}

.lib-cat {
  margin-bottom: var(--app-space-20);
}

.lib-cat h4 {
  font-size: var(--app-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--app-font-semibold);
  color: var(--p-primary-color);
  margin: 0 0 var(--app-space-8);
}

.lib-item {
  display: flex;
  gap: var(--app-space-12);
  align-items: flex-start;
  padding: var(--app-space-8) 0;
  border-bottom: 1px solid var(--p-content-border-color);
}

.lib-item-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--app-space-2);
}

.lib-item-main strong {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
}

.lib-body {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.lib-desc {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  font-style: italic;
}

.lib-item-actions {
  display: flex;
  gap: var(--app-space-4);
  flex-shrink: 0;
}

.lib-admin-note {
  margin: 0 0 var(--app-space-16);
  max-width: 60ch;
}

.lib-admin-note code {
  background: var(--p-surface-200);
  padding: 0 var(--app-space-4);
  border-radius: var(--p-border-radius-sm);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
}

body.dark .lib-admin-note code {
  background: var(--p-surface-800);
}

.lib-admin-body {
  margin-top: var(--app-space-2);
  max-width: 55ch;
}

.lib-retired {
  opacity: 0.55;
}

.lib-runs {
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
}

.lib-actions {
  white-space: nowrap;
}

.lib-actions .btn-sm {
  margin-right: var(--app-space-4);
}

/* ─── Responsive ────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  header {
    padding: var(--app-space-12) var(--app-space-16);
    flex-wrap: nowrap;
    gap: var(--app-space-8);
  }

  header h1 {
    font-size: var(--app-text-lg);
  }

  header .tagline {
    display: none;
  }

  .btn-nav-toggle,
  .btn-sidebar-toggle {
    display: inline-block;
  }

  /* ── One panel behind one hamburger ──────────────────────────────────────
     Tabs, theme and account actions all live in the hamburger here. Before
     this there were three controls in the bar (history, menu, account) and two
     dropdowns of their own — more chrome than the overflowing header it was
     meant to fix. The history drawer stays separate: it opens a surface, not a
     menu, and it is a per-message action rather than a setting. */
  .header-menu {
    display: none;
  }

  .header-right.nav-open .header-menu {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--app-space-2);
    position: absolute;
    top: 100%;
    right: 0;
    min-width: 210px;
    max-width: min(280px, calc(100vw - var(--app-space-16)));
    padding: var(--app-space-8);
    background: var(--p-overlay-background);
    border: 1px solid var(--p-overlay-border-color);
    border-radius: var(--p-border-radius-lg);
    box-shadow: var(--p-shadow-lg);
    z-index: 200;
  }

  /* The tab strip is a plain stack in here — no horizontal scroll track. */
  .header-right.nav-open .nav-tabs {
    display: flex;
    flex-direction: column;
    gap: var(--app-space-2);
    overflow: visible;
    min-width: 0;
  }

  .header-right.nav-open .nav-tab,
  .header-right.nav-open .btn-theme,
  .header-right.nav-open .user-menu button {
    width: 100%;
    text-align: left;
    justify-content: flex-start;
    padding: var(--app-space-8) var(--app-space-12);
  }

  /* Badges are positioned against the tab wrapper, which is now full width. */
  .header-right.nav-open .nav-tab-wrapper {
    display: block;
    position: relative;
  }

  .header-right.nav-open .btn-theme {
    display: block;
    border: none;
    background: transparent;
    font-size: var(--app-text-sm);
  }

  /* Account section: a labelled group at the foot of the panel rather than a
     nested dropdown, which on a touch screen is a trap. */
  .header-right.nav-open .user-menu-wrap {
    display: flex;
    flex-direction: column;
    width: 100%;
    border-top: 1px solid var(--p-content-border-color);
    margin-top: var(--app-space-4);
    padding-top: var(--app-space-4);
  }

  .header-right.nav-open .user-info {
    pointer-events: none;
    background: transparent;
    padding: var(--app-space-4) var(--app-space-12);
    color: var(--p-text-muted-color);
    font-size: var(--app-text-xs);
    min-width: 0;
  }

  /* A long username has to ellipsis rather than widen the panel. */
  .header-right.nav-open #username-display {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .header-right.nav-open .user-menu-caret {
    display: none;
  }

  /* Beats .hidden — both carry !important, so specificity decides, and this is
     three classes to its one. The panel owns visibility here, not the trigger. */
  .header-right.nav-open .user-menu {
    display: flex !important;
    position: static;
    min-width: 0;
    padding: 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }

  /* Edge to edge on a phone — a gutter here only narrows the reading column
     without the ground being visible enough to earn it. */
  main {
    padding: 0;
  }

  /* The card meets the bottom and side edges, so only the top corners are a
     real corner. Squaring the bottom stops the ground showing through in two
     small wedges under the composer. */
  .chat-layout {
    border-radius: var(--app-radius-card) var(--app-radius-card) 0 0;
  }

  .chat-container {
    min-height: 60vh;
  }

  .message {
    max-width: 95%;
  }

  .input-area {
    padding: var(--app-space-8);
    gap: var(--app-space-8);
  }

  .admin-container {
    overflow-x: auto;
  }

  .users-table {
    font-size: var(--app-text-xs);
  }

  .modal-content {
    margin: var(--app-space-8);
    max-height: 90vh;
    overflow-y: auto;
  }

  .sidebar {
    display: none;
  }

  /* The drawer is a fixed overlay at a percentage width here — nothing to drag
     against, and a 5px grab strip beside the transcript would only be a
     mis-tap. */
  .sidebar-resizer {
    display: none;
  }

  .sidebar.show {
    display: flex;
    position: fixed;
    top: 64px;
    left: 0;
    bottom: 0;
    z-index: 999;
    width: 85%;
    max-width: 340px;
    border-radius: 0;
    background: var(--p-content-background);
    box-shadow: var(--p-shadow-lg);
  }
}

.hidden {
  display: none !important;
}

/* ─── Ported from main ──────────────────────────────────────────────────────
   Components that landed on main while this branch was open. They were written
   against the old --dualog-* variables inside the inline <style> this branch
   removed, so they are re-expressed here against tokens rather than merged.

   Several referenced variables that were never defined anywhere (gray-500,
   gray-700, radius-full, font-medium) and so silently fell back to inherit or
   nothing; those get real values here. */

/* Chat toolbar — repeats the PDF/share actions above the transcript, because on
   a long answer the pair beside the composer is a full scroll away. */
.chat-toolbar {
  display: flex;
  align-items: center;
  gap: var(--app-space-8);
  padding: var(--app-space-8) var(--chat-gutter);
  border-bottom: 1px solid var(--p-content-border-color);
  flex-shrink: 0;
}

.chat-toolbar-title {
  flex: 1;
  min-width: 0;
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Form help text under an input. */
.field-hint {
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xs);
  margin: var(--app-space-4) 0 0;
  line-height: var(--app-leading-normal);
}

.field-hint code {
  background: var(--p-surface-200);
  padding: 0 var(--app-space-4);
  border-radius: var(--p-border-radius-sm);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
}

body.dark .field-hint code {
  background: var(--p-surface-800);
}

/* Indexing-filter preview (tenant admin: what a folder rule would exclude). */
.filter-preview {
  margin: var(--app-space-12) 0 var(--app-space-4);
  padding: var(--app-space-12);
  background: var(--p-surface-200);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-lg);
  font-size: var(--app-text-sm);
}

body.dark .filter-preview {
  background: var(--p-surface-950);
}

.filter-preview.hidden {
  display: none;
}

.filter-count {
  color: var(--p-text-color);
  margin-bottom: var(--app-space-4);
}

.filter-warn {
  color: var(--p-warning-color);
  font-weight: var(--app-font-medium);
  margin-bottom: var(--app-space-4);
}

.filter-samples {
  list-style: none;
  margin: var(--app-space-4) 0 0;
  padding: 0;
  max-height: 11rem;
  overflow-y: auto;
}

.filter-samples li {
  padding: var(--app-space-4) 0;
  border-top: 1px solid var(--p-content-border-color);
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.filter-sample-from {
  color: var(--p-text-color);
  margin-right: var(--app-space-4);
}

.filter-sample-date {
  color: var(--p-text-muted-color);
  margin-left: var(--app-space-4);
}

/* Deactivated users in the admin table. */
.inactive-tag {
  display: inline-block;
  margin-left: var(--app-space-8);
  padding: 0 var(--app-space-8);
  border-radius: var(--app-radius-pill);
  background: var(--p-surface-300);
  color: var(--p-surface-800);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

body.dark .inactive-tag {
  background: var(--p-surface-700);
  color: var(--p-surface-100);
}

.user-inactive td {
  opacity: 0.55;
}

/* ---------------------------------------------------------------------------
   Ship page

   Two columns: what changes on the left (calendar, findings, chat), what she
   simply is on the right (particulars). Reference data does not deserve the
   reading position. Collapses to one column below 900px, particulars first,
   because on a phone the identity check is what you came for.
   --------------------------------------------------------------------------- */

.ship-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--app-space-24);
  align-items: start;
}

@media (max-width: 900px) {
  .ship-layout { grid-template-columns: minmax(0, 1fr); }
  .ship-aside { order: -1; }
}

.ship-picker {
  display: flex;
  align-items: center;
  gap: var(--app-space-12);
  flex-wrap: wrap;
  margin-bottom: var(--app-space-20);
}

.ship-picker input {
  flex: 1 1 260px;
  min-width: 0;
}

.ship-card {
  background: var(--p-surface-0);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--app-radius-card);
  padding: var(--app-space-16);
  margin-bottom: var(--app-space-16);
}

.ship-card h3 {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-muted-color);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 0 0 var(--app-space-12);
}

.ship-facts { width: 100%; border-collapse: collapse; }

.ship-facts th,
.ship-facts td {
  text-align: left;
  padding: var(--app-space-4) 0;
  font-size: var(--app-text-sm);
  vertical-align: top;
}

.ship-facts th {
  color: var(--p-text-muted-color);
  font-weight: var(--app-font-regular);
  padding-right: var(--app-space-12);
  white-space: nowrap;
}

.ship-facts td { color: var(--p-text-color); font-weight: var(--app-font-medium); }

/* One row per entry, grouped under its month. A grid rather than a month
   grid on purpose: a ship's year is sparse, and empty squares for the 340
   days when nothing happens tell the user nothing. */
.cal-month {
  font-size: var(--app-text-xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--p-text-muted-color);
  margin: var(--app-space-16) 0 var(--app-space-8);
  padding-bottom: var(--app-space-4);
  border-bottom: 1px solid var(--p-content-border-color);
}

.cal-month:first-child { margin-top: 0; }

.cal-entry {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: var(--app-space-12);
  padding: var(--app-space-8) 0;
  border-bottom: 1px solid var(--p-surface-100);
}

.cal-entry:last-child { border-bottom: none; }

.cal-date {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  white-space: nowrap;
}

.cal-title { font-size: var(--app-text-sm); color: var(--p-text-color); }
.cal-detail { font-size: var(--app-text-xs); color: var(--p-text-muted-color); margin-top: 2px; }

/* An overdue obligation is the one thing on this page that is already a
   problem, so it is the only thing allowed to use the danger colour. */
.cal-entry.is-past .cal-date { color: var(--p-danger-color); }

/* The list runs from the recent past into next year, so it needs to say where
   "now" falls as plainly as the grid does. */
.cal-entry.is-today { background: var(--p-brand-aqua-50); }
body.dark .cal-entry.is-today { background: var(--p-brand-aqua-950); }

.cal-entry.is-today .cal-date {
  color: var(--p-primary-color);
  font-weight: var(--app-font-bold);
}

.cal-today-rule {
  display: flex;
  align-items: center;
  gap: var(--app-space-8);
  margin: var(--app-space-12) 0;
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--p-primary-color);
}

.cal-today-rule::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--p-primary-color);
}

.cal-tag {
  display: inline-block;
  font-size: var(--app-text-2xs);
  padding: 1px 6px;
  border-radius: var(--app-radius-pill);
  margin-left: var(--app-space-8);
  vertical-align: 1px;
  border: 1px solid transparent;
}

/* A register date is a fact; an extracted one is a reading of someone's
   prose. They must never look alike, or the guess borrows the fact's
   authority. */
.cal-tag.from-register {
  background: var(--p-surface-100);
  color: var(--p-text-muted-color);
}

.cal-tag.from-mail {
  background: transparent;
  border-color: var(--p-warning-color);
  color: var(--p-warning-color);
}

.ship-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--app-space-12);
  margin-bottom: var(--app-space-8);
  flex-wrap: wrap;
}

.ship-section-head h3 { margin: 0; font-size: var(--app-text-lg); }

.ship-hint {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  margin: 0 0 var(--app-space-12);
}

.ship-empty {
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  padding: var(--app-space-16) 0;
}

.ship-chat-log {
  max-height: 420px;
  overflow-y: auto;
  margin-bottom: var(--app-space-12);
}

/* A vessel name in the admin list is a way into her ship page. Styled as a
   link rather than left as bold text, or nobody discovers it is clickable. */
.vessel-open-link {
  color: var(--p-primary-color);
  font-weight: var(--app-font-semibold);
  text-decoration: none;
}

.vessel-open-link:hover { text-decoration: underline; }

/* Ship selector. Hand-built rather than a <datalist>: Firefox filters those
   on the option label, which held the IMO, so typing a ship's name matched
   nothing. Chrome filters on the value and looked correct. */
.ship-combo {
  position: relative;
  flex: 1 1 260px;
  min-width: 0;
}

.ship-combo .form-input { width: 100%; }

.ship-options {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  margin: 0;
  padding: var(--app-space-4);
  list-style: none;
  background: var(--p-surface-0);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-lg);
}

.ship-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--app-space-12);
  padding: var(--app-space-8) var(--app-space-12);
  border-radius: var(--p-border-radius-sm);
  font-size: var(--app-text-sm);
  cursor: pointer;
}

.ship-option:hover,
.ship-option.is-active {
  background: var(--p-surface-100);
}

.ship-option-imo {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  white-space: nowrap;
}

.ship-option.is-empty {
  color: var(--p-text-muted-color);
  cursor: default;
}

.ship-option.is-empty:hover { background: transparent; }

/* Leaving a ship page returns to the list it was opened from. */
.btn-back {
  background: none;
  border: none;
  padding: 0;
  margin-bottom: var(--app-space-16);
  color: var(--p-primary-color);
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-medium);
  cursor: pointer;
}

.btn-back:hover { text-decoration: underline; }

/* Opt a note out of the 60ch measure. The class was already on three of them
   in the markup but had never been defined, so they kept wrapping at 60ch and
   looked narrower than the form beneath them. */
.full-width {
  max-width: none;
}

/* ---- Calendar: list / month switch ---------------------------------- */

.cal-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--app-space-12);
  flex-wrap: wrap;
  margin-bottom: var(--app-space-12);
}

.cal-views { display: flex; gap: var(--app-space-2); }

.cal-view-btn {
  background: none;
  border: 1px solid var(--p-content-border-color);
  color: var(--p-text-muted-color);
  padding: var(--app-space-4) var(--app-space-12);
  font-size: var(--app-text-sm);
  cursor: pointer;
}

.cal-view-btn:first-child {
  border-radius: var(--p-border-radius-sm) 0 0 var(--p-border-radius-sm);
}

.cal-view-btn:last-child {
  border-radius: 0 var(--p-border-radius-sm) var(--p-border-radius-sm) 0;
  border-left: none;
}

.cal-view-btn.is-on {
  background: var(--p-primary-color);
  border-color: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
}

.cal-stepper { display: flex; align-items: center; gap: var(--app-space-8); }

.cal-stepper-label {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  min-width: 11ch;
  text-align: center;
}

.cal-step {
  background: none;
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-sm);
  color: var(--p-text-color);
  cursor: pointer;
  font-size: var(--app-text-lg);
  line-height: 1;
  padding: 0 var(--app-space-8);
}

.cal-step:hover { background: var(--p-surface-100); }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--p-content-border-color);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--app-radius-card);
  overflow: hidden;
}

.cal-dow {
  background: var(--p-surface-100);
  color: var(--p-text-muted-color);
  font-size: var(--app-text-2xs);
  font-weight: var(--app-font-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: var(--app-space-4) var(--app-space-8);
}

.cal-cell {
  background: var(--p-surface-0);
  min-height: 92px;
  padding: var(--app-space-4);
}

.cal-cell.is-blank { background: var(--p-surface-50); }
.cal-cell.is-weekend { background: var(--p-surface-50); }

/* The low end of the surface ramp is near-white in BOTH schemes, so every
   light surface above needs a dark counterpart or the whole grid renders as
   a white slab on the dark page. The is-today override below was already
   dark, which is what gave the miss away. Cells sit at content background,
   the ones that recede go under it, and the header rises above. */
body.dark .cal-dow { background: var(--p-surface-800); }
body.dark .cal-cell { background: var(--p-surface-900); }
body.dark .cal-cell.is-blank,
body.dark .cal-cell.is-weekend { background: var(--p-surface-950); }
body.dark .cal-step:hover { background: var(--p-surface-700); }

/* Today needs to be findable at a glance, which a 2px rule was not. The cell
   gets a tint and the date itself becomes a filled marker. Both are drawn in
   the brand aqua, well away from the red an overdue item uses, so a day that
   is both still reads correctly. */
.cal-cell.is-today {
  background: var(--p-brand-aqua-50);
  box-shadow: inset 0 0 0 2px var(--p-primary-color);
}

body.dark .cal-cell.is-today { background: var(--p-brand-aqua-950); }

.cal-cell-day {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  margin-bottom: 2px;
}

.cal-cell.is-today .cal-cell-day {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--app-radius-pill);
  background: var(--p-primary-color);
  color: var(--p-primary-contrast-color);
  font-weight: var(--app-font-semibold);
}

.cal-chip {
  font-size: var(--app-text-2xs);
  line-height: var(--app-leading-tight);
  padding: 2px 4px;
  margin-bottom: 2px;
  border-radius: var(--p-border-radius-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

/* Same distinction the list makes: a register date is a fact, a date read out
   of prose is a reading of one. */
.cal-chip.from-register {
  background: var(--p-surface-200);
  color: var(--p-text-color);
}

/* surface.200 against text-color is 1.3:1 in dark: a chip you cannot read. */
body.dark .cal-chip.from-register { background: var(--p-surface-700); }

.cal-chip.from-mail {
  background: transparent;
  border: 1px solid var(--p-warning-color);
  color: var(--p-warning-color);
}

.cal-chip:hover { text-decoration: underline; }

@media (max-width: 700px) {
  .cal-cell { min-height: 64px; }
  .cal-chip { font-size: 9px; }
}

/* ---- Guided tour ---------------------------------------------------- */

.tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
}

/* The dim is painted by the ring's own outer shadow, so the highlighted
   element stays fully lit and clickable-looking without cloning it. */
.tour-ring {
  position: fixed;
  border-radius: var(--p-border-radius-md);
  box-shadow: 0 0 0 9999px rgba(2, 64, 90, 0.55),  /* allow-hex: scrim over an arbitrary page */
              0 0 0 2px var(--p-primary-color);
  transition: top var(--app-duration-base) var(--app-ease-standard),
              left var(--app-duration-base) var(--app-ease-standard),
              width var(--app-duration-base) var(--app-ease-standard),
              height var(--app-duration-base) var(--app-ease-standard);
  pointer-events: none;
}

/* With no element to ring, the scrim has to come from the overlay itself. */
.tour-overlay:has(.tour-ring[style*="display: none"]) {
  background: rgba(2, 64, 90, 0.55);  /* allow-hex: same scrim, no cut-out */
}

.tour-card {
  position: fixed;
  width: 340px;
  max-width: calc(100vw - 24px);
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-xl);
  padding: var(--app-space-20);
}

.tour-card h3 {
  margin: 0 0 var(--app-space-8);
  font-size: var(--app-text-lg);
  color: var(--p-text-color);
}

.tour-card p {
  margin: 0 0 var(--app-space-16);
  font-size: var(--app-text-sm);
  line-height: var(--app-leading-relaxed);
  color: var(--p-text-muted-color);
}

.tour-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--app-space-12);
}

.tour-count {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
}

.tour-buttons { display: flex; gap: var(--app-space-8); }

.btn-tour-skip,
.btn-tour-back {
  background: none;
  border: none;
  color: var(--p-text-muted-color);
  font-size: var(--app-text-sm);
  cursor: pointer;
  padding: var(--app-space-4) var(--app-space-8);
}

.btn-tour-skip:hover,
.btn-tour-back:hover { color: var(--p-text-color); }

.btn-tour-next {
  padding: var(--app-space-4) var(--app-space-16);
  font-size: var(--app-text-sm);
}

/* ---- User guide ----------------------------------------------------- */

.guide-modal-content {
  max-width: 760px;
  width: calc(100vw - 32px);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
}

.guide-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--app-space-12);
}

.guide-head h3 { margin: 0; }

#guide-body {
  overflow-y: auto;
  margin: var(--app-space-16) 0;
  padding-right: var(--app-space-8);
}

.guide-section { margin-bottom: var(--app-space-32); }

.guide-section h3 {
  margin: 0 0 var(--app-space-4);
  font-size: var(--app-text-lg);
  color: var(--p-primary-color);
}

.guide-intro {
  margin: 0 0 var(--app-space-12);
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  line-height: var(--app-leading-relaxed);
}

.guide-list { margin: 0; }

.guide-list dt {
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  margin-top: var(--app-space-12);
}

/* Where to find it matters as much as what it is: the commonest failure is
   knowing a feature exists and not knowing which screen holds it. */
.guide-where {
  font-weight: var(--app-font-regular);
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  background: var(--p-surface-100);
  border-radius: var(--app-radius-pill);
  padding: 1px 8px;
  margin-left: var(--app-space-4);
  white-space: nowrap;
}

.guide-list dd {
  margin: 2px 0 0;
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  line-height: var(--app-leading-relaxed);
}

.guide-cats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-space-4);
  margin-top: var(--app-space-16);
}

.guide-cat {
  font-size: var(--app-text-xs);
  background: var(--p-surface-100);
  border-radius: var(--app-radius-pill);
  padding: 2px 10px;
  color: var(--p-text-muted-color);
}

.guide-cat b { color: var(--p-text-color); margin-left: 4px; }

.guide-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--app-space-8);
  border-top: 1px solid var(--p-content-border-color);
  padding-top: var(--app-space-16);
}

/* ---- Data sources --------------------------------------------------- */

.source-props {
  font-size: var(--app-text-xs);
  color: var(--p-text-muted-color);
  max-width: 340px;
  overflow-wrap: anywhere;
}

.source-props em { color: var(--p-text-color); font-style: normal; }

/* The test result is the gate on Save, so it has to read as a verdict
   rather than as another line of help text. */
.source-test {
  font-size: var(--app-text-sm);
  min-height: 1.4em;
  margin: var(--app-space-8) 0;
}

.source-test.is-ok { color: var(--p-success-color); }
.source-test.is-bad { color: var(--p-danger-color); }

.muted { color: var(--p-text-muted-color); }
.opt { color: var(--p-text-muted-color); font-weight: var(--app-font-regular); }

/* A failing feed has to be visible at a glance in the list; the message
   itself sits in the tooltip so the column stays narrow. */
.src-bad { color: var(--p-danger-color); font-weight: var(--app-font-semibold); }
.src-ok { color: var(--p-text-muted-color); font-size: var(--app-text-xs); }

/* ---- Feed health ----------------------------------------------------- */

/* A dot rather than a count: the number of broken feeds is not the point,
   the fact that one is broken is. Amber for overdue, red for stopped. */
.health-dot {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 9px;
  height: 9px;
  border-radius: var(--app-radius-pill);
  background: var(--p-warning-color);
  border: 2px solid var(--p-content-background);
  box-sizing: content-box;
}

.health-dot.is-error { background: var(--p-danger-color); }

.health-notice {
  position: fixed;
  right: var(--app-space-16);
  bottom: var(--app-space-16);
  z-index: 2500;
  width: 360px;
  max-width: calc(100vw - 32px);
  background: var(--p-content-background);
  border: 1px solid var(--p-content-border-color);
  border-left: 3px solid var(--p-danger-color);
  border-radius: var(--app-radius-card);
  box-shadow: var(--p-shadow-xl);
  padding: var(--app-space-16);
  /* Slides in from the right, and stays out of the way. It is a status
     message, not a decision to make, so it must never take the focus or
     block what the admin came to do. */
  transform: translateX(calc(100% + var(--app-space-16)));
  transition: transform var(--app-duration-slow) var(--app-ease-emphasized);
}

.health-notice.is-in { transform: translateX(0); }

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

.health-notice-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--app-space-8);
  margin-bottom: var(--app-space-8);
}

.health-notice-head strong {
  font-size: var(--app-text-base);
  color: var(--p-text-color);
}

.health-notice-close {
  background: none;
  border: none;
  color: var(--p-text-muted-color);
  font-size: var(--app-text-xl);
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--app-space-4);
}

.health-notice ul {
  margin: 0 0 var(--app-space-12);
  padding-left: var(--app-space-16);
}

.health-notice li {
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  line-height: var(--app-leading-relaxed);
  margin-bottom: var(--app-space-4);
}

.health-notice li strong { color: var(--p-text-color); display: block; }

.health-notice-actions { display: flex; justify-content: flex-end; }

/* ---- Source setup guidance ------------------------------------------ */

/* Collapsed by default: an admin adding their fifth source should not have
   to scroll past it, and one adding their first has to be able to find it. */
.source-setup {
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-md);
  background: var(--p-surface-50);
  padding: var(--app-space-8) var(--app-space-12);
  margin-bottom: var(--app-space-16);
}

body.dark .source-setup { background: var(--p-surface-800); }

.source-setup > summary {
  cursor: pointer;
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  list-style-position: inside;
}

.source-setup[open] > summary { margin-bottom: var(--app-space-8); }

.source-setup ol {
  margin: 0;
  /* The reset clears list styling globally, and these are a sequence: the
     numbers are how an admin keeps their place while switching to Entra and
     back. */
  list-style: decimal outside;
  padding-left: var(--app-space-20);
  font-size: var(--app-text-sm);
  color: var(--p-text-muted-color);
  line-height: var(--app-leading-relaxed);
}

.source-setup li { margin-bottom: var(--app-space-4); }

.source-setup a {
  display: inline-block;
  margin-top: var(--app-space-8);
  font-size: var(--app-text-xs);
  color: var(--p-primary-color);
}

/* The explanation behind the dot, on the page rather than in a tooltip. */
.health-panel {
  border: 1px solid var(--p-content-border-color);
  border-left: 3px solid var(--p-warning-color);
  border-radius: var(--app-radius-card);
  background: var(--p-surface-50);
  padding: var(--app-space-16) var(--app-space-20);
  margin-bottom: var(--app-space-24);
}

body.dark .health-panel { background: var(--p-surface-800); }

.health-panel.is-error { border-left-color: var(--p-danger-color); }

.health-panel h3 {
  margin: 0 0 var(--app-space-8);
  font-size: var(--app-text-base);
  color: var(--p-text-color);
}

.health-issue { margin-bottom: var(--app-space-8); font-size: var(--app-text-sm); }
.health-issue:last-child { margin-bottom: 0; }
.health-issue b { display: block; color: var(--p-text-color); }
.health-issue span { color: var(--p-text-muted-color); line-height: var(--app-leading-relaxed); }

/* The brief itself, on the screen where you edit how it reads. Collapsed by
   default: an admin who came to change one line should not have to scroll
   past yesterday's whole brief to reach the box. */
.brief-latest {
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-md);
  background: var(--p-surface-50);
  padding: var(--app-space-8) var(--app-space-12);
  margin-bottom: var(--app-space-16);
  max-width: 56rem;
}

.brief-latest > summary {
  cursor: pointer;
  font-size: var(--app-text-sm);
  font-weight: var(--app-font-semibold);
  color: var(--p-text-color);
  list-style-position: inside;
}

/* surface.50 is near-white in BOTH schemes, so a panel that uses it needs a
   dark step here or it lands white-on-white: summary text is text-color
   (#ebf0f2 dark), which against #f7f8f8 is a 1.1:1 contrast. Raised panels
   step to surface.800, same as .message.assistant. */
body.dark .brief-latest { background: var(--p-surface-800); }

.brief-latest[open] > summary { margin-bottom: var(--app-space-12); }

/* Shared by the stored brief and the preview, so what you are shown before
   saving is laid out the same as what went out. */
.brief-body {
  max-width: 56rem;
  margin-bottom: var(--app-space-16);
  font-size: var(--app-text-sm);
  /* text, not text-muted: the brief is the content here, not a caption.
     Muted lands at 3.35:1 on this panel in light mode, under the 4.5:1 the
     project requires for body text. */
  color: var(--p-text-color);
  line-height: var(--app-leading-relaxed);
}

.brief-body h2,
.brief-body h3 {
  font-size: var(--app-text-base);
  color: var(--p-text-color);
  margin: var(--app-space-16) 0 var(--app-space-4);
}

.brief-body ul { list-style: disc outside; padding-left: var(--app-space-20); }
.brief-body li { margin-bottom: var(--app-space-4); }
.brief-body a { color: var(--p-primary-color); }

/* The composed instruction, shown verbatim. Pre-wrap rather than a scrolling
   line: an editor reading what the brief is told to do should not have to
   drag a horizontal bar to reach the end of a rule. Capped in height so it
   cannot push the box it is meant to inform off the screen. */
.brief-prompt-text {
  margin: 0;
  max-height: 22rem;
  overflow-y: auto;
  padding: var(--app-space-12);
  border: 1px solid var(--p-content-border-color);
  border-radius: var(--p-border-radius-md);
  background: var(--p-form-field-background);
  color: var(--p-text-color);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  font-size: var(--app-text-xs);
  line-height: var(--app-leading-relaxed);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

#brief-prompt-note { margin-bottom: var(--app-space-8); }

/* The prompt is edited as text, so it gets a text editor's affordances:
   monospace so the bullet structure lines up, and tall enough to see a whole
   rule without scrolling mid-sentence. */
.brief-prompt-input {
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  font-size: var(--app-text-xs);
  line-height: var(--app-leading-relaxed);
  min-height: 18rem;
  resize: vertical;
}
