/* OEM dashboard shell, matches the YMH Yacht Assistant Figma
   (Chat Landing v2 · Desktop 1440). Light theme, Open Sans, action cards
   with left color stripe, chat pill with orange-circle send. Applied to
   /try, /focus, and future /oem/<brand> surfaces.

   Design tokens are inlined here (not in site.css) so the dashboard can
   ship a light theme without competing with the marketing helm's dark
   theme on /. */

/* ── Design tokens (from Figma Chat Landing v2) ────────────────────── */
:root {
  --ymh-bg: #f5f7f9;
  --ymh-panel: #ffffff;
  --ymh-sidebar: #0b1420;
  --ymh-sidebar-item: #162538;
  --ymh-ink: #0b1e33;
  --ymh-ink-2: #171c2f;
  --ymh-muted: #6b8099;
  --ymh-muted-light: #8c9eb8;
  --ymh-muted-lighter: #bfccde;
  --ymh-link: #0d426a;
  --ymh-orange: #f5a623;
  --ymh-orange-2: #f9bf20;
  --ymh-peach: #fff1dd;
  --ymh-danger: #ec4c3d;
  --ymh-info: #9ccdef;
  --ymh-line: #ebedf0;
  --ymh-row-hover: #f2f5f9;
  --ymh-chip-bg: #f8fafc;
  --ymh-r-card: 12px;
  --ymh-r-row: 8px;
  --ymh-r-pill: 999px;
  --ymh-r-icon: 10px;
  --ymh-shadow-card: 0 2px 8px 0 rgba(11,20,32,.06);
  --ymh-shadow-hover: 0 6px 16px 0 rgba(11,20,32,.10);
}

/* Load Open Sans (already preconnected in the HTML head). Fallback to
   system sans stack so the page renders even if Google Fonts is slow. */
body {
  font-family: "Open Sans", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--ymh-bg);
  color: var(--ymh-ink);
  margin: 0;
  min-height: 100vh;
}

/* Kill the site.css dark hero styles for pages that use the dashboard. */
.oemd { background: var(--ymh-bg); color: var(--ymh-ink); }

/* [hidden] fix: our .oemd-live sets display:grid and .oemd-identify would
   inherit its box display from its parent, both of which override the
   browser's default [hidden] { display: none }. Explicit rule so the
   identify -> live swap actually hides the other section.
   Broader [hidden] rule too so the engineer.js DOM shim (a hidden div
   containing .eng-hero / .eng-chat with dark 100vh backgrounds) does
   not leak into the page. Some element selectors in engineer.css set
   properties that ignore [hidden] on their ancestors. */
.oemd [hidden] { display: none !important; }
body.oemd-page [hidden] { display: none !important; }

/* Site nav is position:fixed cream-on-transparent, it's invisible on the
   light dashboard bg. Turn it into a proper opaque bar with dark text
   while the dashboard is being viewed. Applied via body class oemd-page
   so this doesn't affect the marketing helm at /. Also force the light
   background at every relevant scope because engineer.css keeps a full-
   viewport dark hero gradient that would otherwise leak through when
   the hidden shim is present. */
html, body.oemd-page {
  background: var(--ymh-bg) !important;
  min-height: 100vh;
}
body.oemd-page { padding-top: var(--nav-h, 80px); }
body.oemd-page .nav {
  background: var(--ymh-panel);
  border-bottom: 1px solid var(--ymh-line);
}
body.oemd-page .nav-links a,
body.oemd-page .nav-cta { color: var(--ymh-muted); }
body.oemd-page .nav-links a:hover,
body.oemd-page .nav-cta:hover { color: var(--ymh-ink); }
body.oemd-page .nav-links a.active { color: var(--ymh-orange); }
body.oemd-page .nav-toggle span { background: var(--ymh-ink); }
/* Neutralize engineer.css hero + chat backgrounds that were meant for the
   old standalone chat pages. In the dashboard those elements only exist
   inside the DOM shim (already hidden), but this belt-and-suspenders
   rule guarantees no dark gradient can leak into the page. */
body.oemd-page .eng-hero,
body.oemd-page .eng-chat {
  background: transparent !important;
  min-height: 0 !important;
  color: inherit !important;
}
/* engineer.js adds `eng-chatting` to body once the first message fires,
   and engineer.css sets `body.eng-chatting { background: #0f1428 }`
   (dark navy chat page). Force the light bg here so the dashboard keeps
   its theme even after the chat gets going. */
body.oemd-page.eng-chatting { background: var(--ymh-bg) !important; }

/* ── Demo banner (top strip) ───────────────────────────────────────── */
.oem-demo-banner {
  max-width: 1080px; margin: 16px auto 0; padding: 8px 16px;
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-pill); text-align: center; color: var(--ymh-muted);
  font-size: 12px; letter-spacing: .02em;
}
.oem-demo-banner b {
  color: var(--ymh-orange); letter-spacing: .12em; text-transform: uppercase;
  font-size: 10px; margin-right: 8px; font-weight: 700;
}

/* ── Page shell ────────────────────────────────────────────────────── */
.oemd { max-width: 1200px; margin: 0 auto; padding: 16px 24px 24px; }

/* ── Identify state (before vessel is known) ───────────────────────── */
.oemd-identify {
  max-width: 640px; margin: 24px auto 0; text-align: center;
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-card); padding: 28px 28px;
  box-shadow: var(--ymh-shadow-card);
}
.oemd-h1 {
  font-size: 26px; font-weight: 700; line-height: 1.15; color: var(--ymh-ink);
  margin: 0 0 10px; letter-spacing: -.01em;
}
.oemd-sub {
  font-size: 14px; color: var(--ymh-muted); margin: 0 0 22px; line-height: 1.55;
}
.oemd-identify-input {
  display: flex; gap: 8px; margin-bottom: 20px;
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-pill); padding: 4px 4px 4px 16px;
  transition: border-color .18s ease;
}
.oemd-identify-input:focus-within { border-color: var(--ymh-orange); }
.oemd-identify-input input {
  flex: 1; background: transparent; border: 0; padding: 10px 0;
  color: var(--ymh-ink); font-size: 14px; font-family: inherit; outline: none;
}
.oemd-identify-input input::placeholder { color: var(--ymh-muted); }
.oemd-identify-input button {
  padding: 10px 20px; border-radius: var(--ymh-r-pill); border: 0;
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  font-weight: 600; font-size: 12px; cursor: pointer; font-family: inherit;
}
.oemd-identify-input button:hover { filter: brightness(.98); }
.oemd-identify-chips {
  display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
}
.oemd-chip {
  padding: 6px 14px; border-radius: var(--ymh-r-pill);
  background: var(--ymh-chip-bg); border: 1px solid var(--ymh-line);
  color: var(--ymh-ink); font-size: 12px; font-weight: 600;
  cursor: pointer; font-family: inherit; transition: all .18s ease;
}
.oemd-chip:hover {
  background: var(--ymh-peach); border-color: var(--ymh-orange); color: var(--ymh-ink-2);
}

/* ── Live state (after vessel is identified) ───────────────────────── */
.oemd-live {
  display: grid; grid-template-columns: 1fr 320px; gap: 16px; margin-top: 8px;
  align-items: start;
}
@media (max-width: 900px) { .oemd-live { grid-template-columns: 1fr; } }

/* Main column (left, wide) */
.oemd-main {
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-card); padding: 20px 24px;
  display: flex; flex-direction: column; gap: 14px;
  min-width: 0;
  box-shadow: var(--ymh-shadow-card);
}

/* Right rail */
.oemd-side {
  display: flex; flex-direction: column; gap: 18px; min-width: 0;
}
.oemd-card {
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-card); padding: 20px;
  box-shadow: var(--ymh-shadow-card);
}

/* Vessel profile card (right rail top). Kept compact so the entire
   right rail fits above the fold at 900px viewport height. */
.oemd-vessel { padding: 14px; }
.oemd-vessel-badge {
  display: inline-block; font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ymh-orange); font-weight: 700;
  background: var(--ymh-peach); padding: 3px 9px; border-radius: var(--ymh-r-pill);
  margin-bottom: 10px;
}
.oemd-vessel-hero {
  height: 90px; border-radius: var(--ymh-r-card);
  background: linear-gradient(135deg, #0e1c32 0%, #1b2b41 100%);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 10px; overflow: hidden;
}
.oemd-vessel-hero svg { max-width: 82%; max-height: 82%; }
.oemd-vessel-builder {
  font-size: 12px; font-weight: 700; color: var(--ymh-ink);
  margin-bottom: 2px;
}
.oemd-vessel-model {
  font-size: 18px; font-weight: 700; color: var(--ymh-ink);
  margin-bottom: 4px; line-height: 1.2;
}
.oemd-vessel-specs {
  font-size: 11px; color: var(--ymh-muted); line-height: 1.5;
}
.oemd-vessel-change {
  display: block; margin-top: 12px; background: none; border: none;
  color: var(--ymh-orange); font-size: 11px; font-weight: 600;
  cursor: pointer; text-decoration: none; padding: 0; font-family: inherit;
}
.oemd-vessel-change:hover { text-decoration: underline; }

/* Warranty Tracker (right rail, Focus surface), replaces Passport on
   OEM surfaces where the owner already has active factory + component
   coverage that the surface needs to visualize. */
.oemd-warranty { padding: 10px 14px; }
.oemd-warranty-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 6px;
}
.oemd-warranty-title {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ymh-muted); font-weight: 700;
}
.oemd-warranty-tag {
  font-size: 9.5px; letter-spacing: .06em; font-weight: 700;
  color: #d97706; background: rgba(217,119,6,.14);
  padding: 3px 8px; border-radius: var(--ymh-r-pill);
}
.oemd-warranty-list {
  display: flex; flex-direction: column; gap: 5px;
}
.oemd-warranty-row {
  display: grid; grid-template-columns: 20px 1fr auto; gap: 8px;
  align-items: start; padding: 6px 10px;
  background: var(--ymh-row-hover); border-radius: var(--ymh-r-row);
}
.oemd-w-icon {
  width: 20px; height: 20px; border-radius: 999px;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700; color: #ffffff;
  background: #16a34a;
  margin-top: 2px;
}
.oemd-warranty-row[data-status="warn"] .oemd-w-icon { background: #d97706; }
.oemd-w-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.oemd-w-org {
  font-size: 9px; font-weight: 700; letter-spacing: .04em;
  color: var(--ymh-muted); text-transform: uppercase;
}
.oemd-w-item { font-size: 11.5px; font-weight: 600; color: var(--ymh-ink); }
.oemd-w-bar {
  height: 3px; background: #e5e9ef; border-radius: 999px;
  overflow: hidden; margin-top: 3px;
}
.oemd-w-fill {
  height: 100%; background: #16a34a; border-radius: 999px;
}
.oemd-warranty-row[data-status="warn"] .oemd-w-fill { background: #d97706; }
.oemd-w-right {
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  min-width: 88px;
}
.oemd-w-until {
  font-size: 9.5px; color: var(--ymh-muted); white-space: nowrap;
}
.oemd-w-pct {
  font-size: 9.5px; font-weight: 700; color: #16a34a;
}
.oemd-warranty-row[data-status="warn"] .oemd-w-pct { color: #d97706; }
.oemd-warranty-foot {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--ymh-line);
  font-size: 10.5px; color: var(--ymh-muted); line-height: 1.5;
  text-align: center;
}

/* Yacht Passport progress (right rail) */
.oemd-passport-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 8px;
}
.oemd-passport-title {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ymh-muted); font-weight: 700;
}
.oemd-passport-tag {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ymh-orange); font-weight: 700;
}
.oemd-passport-bar {
  height: 6px; background: var(--ymh-row-hover);
  border-radius: var(--ymh-r-pill); overflow: hidden; margin-bottom: 6px;
}
.oemd-passport-fill {
  height: 100%; background: var(--ymh-orange); transition: width .3s ease;
}
.oemd-passport-pct {
  font-size: 10px; font-weight: 600; color: var(--ymh-orange);
  margin-bottom: 12px;
}
.oemd-passport-list {
  list-style: none; padding: 0; margin: 0 0 12px;
}
.oemd-passport-list li {
  padding-left: 20px; position: relative; font-size: 12px;
  color: var(--ymh-muted); margin-bottom: 6px; line-height: 1.4;
}
.oemd-passport-list li::before {
  content: "○"; position: absolute; left: 4px; color: var(--ymh-muted-light);
}
.oemd-passport-list li.done { color: var(--ymh-ink); font-weight: 600; }
.oemd-passport-list li.done::before { content: "●"; color: var(--ymh-orange); }
.oemd-passport-foot {
  font-size: 10.5px; color: var(--ymh-muted); line-height: 1.5;
  padding-top: 10px; border-top: 1px solid var(--ymh-line);
}

/* ── Main column contents ──────────────────────────────────────────── */

/* Welcome header + identity chip strip */
.oemd-welcome-h {
  font-size: 22px; font-weight: 700; color: var(--ymh-ink);
  margin: 0 0 6px; line-height: 1.25;
}
.oemd-welcome-id {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ymh-muted); margin-bottom: 4px;
}
.oemd-welcome-id b { color: var(--ymh-ink); font-weight: 600; }
.oemd-welcome-id .dot { color: var(--ymh-muted-light); }
.oemd-welcome-id .val { color: var(--ymh-link); }
.oemd-sep {
  height: 1px; background: var(--ymh-line); margin: 6px 0;
}
.oemd-things-h {
  font-size: 13px; font-weight: 600; color: var(--ymh-ink); margin: 0;
}

/* Action tile: reshaped to Figma "action card" pattern with a left
   color stripe, icon pill on the left, title/subtitle stack, orange
   Start pill on the right. 2-column grid on wider screens so the
   dashboard fits without heavy scrolling. */
.oemd-tiles {
  /* 2026-08-17 fix: auto-fit min-260px so wider screens flow to 3
     columns and the 6-tile intro grid on /try fits without scrolling.
     Narrow viewports still collapse cleanly (2 cols mid, 1 col mobile).
     Owner reported the tiles felt oversized and asymmetric to the page. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 8px;

  /* ── TILE SIZING SYSTEM ──────────────────────────────────────────
     Tile height used to be whatever the copy happened to need, so the
     six tiles came out 119/119/119/99/99/99 on desktop and 90/90/90/
     72/72/72 on a phone: the first row's hint text wraps to two lines
     and the second row's does not. Every breakpoint then got its own
     patch, which is why they drifted apart in the first place.

     Two rules replace all of that, and they compose:

       --tile-sub-lines  caps the hint. This BOUNDS the tallest a tile
                         can ever get, whatever anyone writes in it.
       grid-auto-rows:1fr  makes every row equal to that tallest tile.

     Consequence: all tiles are the same height, that height is set by
     content rather than a magic number, and it can never run away
     because the clamp holds the ceiling. Changing the line budget is a
     one-line change here, not six overrides across three files. */
  --tile-sub-lines: 2;
  grid-auto-rows: 1fr;
  /* Offset for the sticky appshell topbar so #help anchor navigation
     (from the topbar "What can I ask?" chip) doesn't land underneath it. */
  scroll-margin-top: 72px;
}
@media (max-width: 720px) { .oemd-tiles { grid-template-columns: 1fr; } }

/* Tiles, match the equipment-row + rail-list-row visual language:
   white panel, single peach icon well, semantic color lives only in the
   4px left stripe, whole tile is the CTA (chevron on right, no button
   pill). Height drops ~35% vs the previous rainbow-pill version. */
.oemd-tile {
  /* height:100% so a short tile fills the row height grid-auto-rows:1fr
     assigned it, instead of sitting short inside its own cell. */
  height: 100%;
  display: flex; align-items: stretch; padding: 0;
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-card); cursor: pointer; font-family: inherit;
  overflow: hidden; transition: all .18s ease; text-align: left;
  box-shadow: var(--ymh-shadow-card);
}
.oemd-tile:hover {
  transform: translateY(-1px); box-shadow: var(--ymh-shadow-hover);
  border-color: rgba(245,166,35,.35);
}
.oemd-tile:hover .oemd-tile-arrow { color: var(--ymh-orange); transform: translateX(2px); }
.oemd-tile.disabled { opacity: .55; cursor: not-allowed; }
.oemd-tile.disabled:hover { transform: none; box-shadow: var(--ymh-shadow-card); border-color: var(--ymh-line); }
.oemd-tile.disabled:hover .oemd-tile-arrow { color: var(--ymh-muted-light); transform: none; }
.oemd-tile:focus-visible { outline: 2px solid var(--ymh-orange); outline-offset: 2px; }
.oemd-tile.active { background: #fff9ee; border-color: var(--ymh-orange); }

.oemd-tile-stripe {
  width: 3px; flex: 0 0 3px;
  background: var(--ymh-orange);
}
.oemd-tile-inner {
  /* 2026-08-17: tightened top/bottom padding from 10px to 8px and gap
     from 10 to 8; combined with the auto-fit grid this shaves ~10px
     off each tile so the 6-tile intro grid on /try fits above the fold. */
  flex: 1; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 8px 12px; min-width: 0;
}
.oemd-tile-left { display: flex; gap: 10px; align-items: center; min-width: 0; }
.oemd-tile-icon-pill {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 7px;
  background: var(--ymh-peach); display: grid; place-items: center;
  color: var(--ymh-orange);
}
.oemd-tile-icon-pill svg { display: block; }
.oemd-tile-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.oemd-tile-title {
  font-size: 13px; font-weight: 600; color: var(--ymh-ink); line-height: 1.3;
}
.oemd-tile-sub {
  font-size: 11px; color: var(--ymh-muted); line-height: 1.4;
  /* The ceiling. Everything else about tile height follows from this. */
  display: -webkit-box;
  -webkit-line-clamp: var(--tile-sub-lines, 2);
  line-clamp: var(--tile-sub-lines, 2);
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.oemd-tile-arrow {
  flex: 0 0 auto; color: var(--ymh-muted-light); font-size: 16px;
  transition: color .15s ease, transform .15s ease;
}
.oemd-tile-arrow::after { content: "→"; }

/* Legacy .oemd-tile-cta pill kept as an alias: the inline HTML still writes
   'Start', 'Decode', 'Upload', 'See my plan', etc. into a span. We render
   those as the same subtle chevron the row-family uses, the button-label
   text is hidden (font-size:0) and only the ::after arrow shows. Preserves
   accessibility (the label reads to a screen reader) while matching the
   app-brand row-family visual. */
.oemd-tile-cta {
  flex: 0 0 auto;
  color: var(--ymh-muted-light);
  font-size: 0;
  background: transparent !important;
  padding: 0;
  border-radius: 0;
  font-weight: 400;
  transition: color .15s ease, transform .15s ease;
  min-width: 16px; text-align: right;
}
.oemd-tile-cta::after {
  content: "→";
  font-size: 16px;
  font-weight: 400;
  color: inherit;
}
.oemd-tile:hover .oemd-tile-cta { color: var(--ymh-orange); transform: translateX(2px); }

/* Stripe colors per tile action, the ONLY semantic color the tile uses. */
.oemd-tile[data-action="troubleshoot"] .oemd-tile-stripe { background: var(--ymh-orange); }
.oemd-tile[data-action="ask"]           .oemd-tile-stripe { background: var(--ymh-link); }
.oemd-tile[data-action="safety"]        .oemd-tile-stripe { background: #22c55e; }
.oemd-tile[data-action="photo"]         .oemd-tile-stripe { background: var(--ymh-danger); }
.oemd-tile[data-action="weekly"]        .oemd-tile-stripe { background: var(--ymh-link); }
.oemd-tile[data-action="preventive"]    .oemd-tile-stripe { background: #16a34a; }
.oemd-tile[data-action="fault"]         .oemd-tile-stripe { background: var(--ymh-danger); }
.oemd-tile[data-action="passport"]      .oemd-tile-stripe { background: var(--ymh-muted-lighter); }
/* Icon pill inherits the tile's peach + orange treatment. Inline styles
   in the current HTML markup override this with per-action tints, those
   still work, but the default is aligned with the rest of the app. */

/* Panels (fault code, photo) */
.oemd-panel {
  background: var(--ymh-panel); border: 1px solid var(--ymh-orange);
  border-radius: var(--ymh-r-card); overflow: hidden;
  animation: oemdFade .25s ease both;
  box-shadow: var(--ymh-shadow-card);
}
/* Floating panel variant, proper viewport-anchored modal + scrim so the
   fault/photo panels can't overflow into the sidebar, right rail, or
   topbar regardless of what column they were rendered inside. Uses a
   ::before pseudo for the backdrop so no extra DOM node is needed. */
.oemd-panel-float {
  position: fixed;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 96px);
  display: flex; flex-direction: column;
  z-index: 201;
  box-shadow: 0 24px 60px rgba(11,20,32,.32), var(--ymh-shadow-card);
}
.oemd-panel-float .oemd-panel-head { flex: 0 0 auto; }
.oemd-panel-float .oemd-panel-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
/* Modal backdrop + body scroll lock. The .oemd-modal-scrim node is
   injected by oem-dashboard.js when a panel opens and removed on close,
   so the scrim always attaches to the viewport, not to any element's
   stacking context. */
body.oemd-modal-lock { overflow: hidden; }
.oemd-modal-scrim {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(11,20,32,.55);
  animation: oemdFade .16s ease both;
}
@keyframes oemdFade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.oemd-panel-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 20px; border-bottom: 1px solid var(--ymh-line);
}
.oemd-panel-title { font-size: 14px; font-weight: 600; color: var(--ymh-ink); }
.oemd-panel-close {
  background: none; border: none; color: var(--ymh-muted);
  font-size: 20px; cursor: pointer; padding: 0; width: 28px; height: 28px;
  border-radius: var(--ymh-r-pill); display: grid; place-items: center;
  font-family: inherit;
}
.oemd-panel-close:hover { background: var(--ymh-row-hover); color: var(--ymh-ink); }
.oemd-panel-body { padding: 20px; }

/* Fault-code widget */
.oemd-fault-input, .oemd-photo-input {
  display: flex; gap: 8px; background: var(--ymh-chip-bg);
  border: 1px solid var(--ymh-line); border-radius: var(--ymh-r-pill);
  padding: 4px 4px 4px 16px; transition: border-color .18s ease;
}
.oemd-fault-input:focus-within, .oemd-photo-input:focus-within { border-color: var(--ymh-orange); }
.oemd-fault-input input, .oemd-photo-input input {
  flex: 1; background: transparent; border: 0; padding: 10px 0;
  color: var(--ymh-ink); font-size: 14px; font-family: inherit; outline: none;
}
.oemd-fault-input input {
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px;
}
.oemd-fault-input input::placeholder, .oemd-photo-input input::placeholder { color: var(--ymh-muted); }
.oemd-fault-input button, .oemd-photo-input button {
  padding: 10px 20px; border-radius: var(--ymh-r-pill); border: 0;
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  font-weight: 600; font-size: 12px; cursor: pointer; font-family: inherit;
}
.oemd-fault-input button:disabled, .oemd-photo-input button:disabled { opacity: .45; cursor: not-allowed; }

.oemd-fault-result, .oemd-photo-result {
  margin-top: 16px; display: grid; gap: 10px; font-size: 13px;
}
.oemd-fault-result .row, .oemd-photo-result .row {
  padding: 10px 12px; background: var(--ymh-row-hover);
  border-radius: var(--ymh-r-row);
}
.oemd-fault-result .k, .oemd-photo-result .k {
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ymh-muted); margin-bottom: 4px; font-weight: 700;
}
.oemd-fault-result .v, .oemd-photo-result .v {
  color: var(--ymh-ink); line-height: 1.55;
}
.oemd-fault-result .sev-info, .oemd-photo-result .sev-info { color: #16a34a; }
.oemd-fault-result .sev-advisory, .oemd-photo-result .sev-advisory { color: #d97706; }
.oemd-fault-result .sev-safety_critical, .oemd-photo-result .sev-safety_critical {
  color: var(--ymh-danger); font-weight: 600;
}
.oemd-fault-result .sev-unknown, .oemd-photo-result .sev-unknown { color: var(--ymh-muted); }
.oemd-fault-result ol, .oemd-photo-result ol { margin: 0; padding-left: 22px; }
.oemd-fault-result li, .oemd-photo-result li { line-height: 1.55; margin-bottom: 4px; }
.oemd-fault-result .disclaimer, .oemd-photo-result .disclaimer {
  font-size: 11.5px; color: var(--ymh-muted); font-style: italic;
  padding-top: 4px;
}

/* Photo drop zone */
.oemd-photo-drop {
  display: block; padding: 30px 20px; text-align: center;
  border: 2px dashed rgba(245,166,35,.35); border-radius: var(--ymh-r-card);
  cursor: pointer; transition: all .18s ease; background: #fffdf9;
  margin-bottom: 12px;
}
.oemd-photo-drop:hover { border-color: var(--ymh-orange); background: #fff9ee; }
.oemd-photo-drop.dragover { border-color: var(--ymh-orange); background: #fff5e0; }
.oemd-photo-icon { font-size: 30px; margin-bottom: 6px; }
.oemd-photo-text { font-size: 12.5px; color: var(--ymh-muted); }
/* Multi-photo preview: horizontal thumb row + "add another" button.
   Replaces the single-img layout. Up to 3 thumbs, 96px square each. */
.oemd-photo-preview {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 12px;
}
.oemd-photo-thumb {
  position: relative; width: 96px; height: 96px;
  border-radius: var(--ymh-r-card); overflow: hidden;
  background: var(--ymh-row-hover); border: 1px solid var(--ymh-line);
}
.oemd-photo-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.oemd-photo-thumb-remove {
  position: absolute; top: 4px; right: 4px;
  width: 20px; height: 20px; border-radius: 50%;
  background: rgba(11,30,51,.85); border: 0; color: #fff;
  font: 700 14px/1 inherit; cursor: pointer;
  display: grid; place-items: center;
}
.oemd-photo-thumb-remove:hover { background: var(--ymh-ink); }
.oemd-photo-thumb-add {
  width: 96px; height: 96px;
  border-radius: var(--ymh-r-card);
  border: 1px dashed var(--ymh-line);
  background: transparent; color: var(--ymh-muted);
  font: 300 34px/1 inherit; cursor: pointer;
  display: grid; place-items: center;
  transition: border-color .12s ease, color .12s ease;
}
.oemd-photo-thumb-add:hover { border-color: var(--ymh-orange); color: var(--ymh-orange); }

/* Badge row above the identified line: severity, confidence, failure mode,
   quality flag. Keeps the reading order (badges first, then explanation). */
.oemd-photo-badges {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 0 0 12px;
}
.oemd-photo-badge {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  font-weight: 700; padding: 4px 10px; border-radius: var(--ymh-r-pill);
  background: rgba(11,30,51,.06); color: var(--ymh-ink);
}
.oemd-photo-badge.sev-info { background: rgba(22,163,74,.14); color: #14532d; }
.oemd-photo-badge.sev-advisory { background: rgba(217,119,6,.16); color: #7c2d12; }
.oemd-photo-badge.sev-safety_critical { background: rgba(220,38,38,.16); color: #7f1d1d; }
.oemd-photo-badge.sev-unknown { background: rgba(11,30,51,.06); color: var(--ymh-muted); }
.oemd-photo-badge.conf-high { background: rgba(22,163,74,.10); color: #16a34a; }
.oemd-photo-badge.conf-medium { background: rgba(59,130,246,.14); color: #1d4ed8; }
.oemd-photo-badge.conf-low { background: rgba(107,128,153,.18); color: var(--ymh-muted); }
.oemd-photo-badge.mode { background: rgba(11,30,51,.06); color: var(--ymh-ink); }
.oemd-photo-badge.quality-poor { background: rgba(220,38,38,.14); color: #7f1d1d; }

/* Nudge line surfaced when the model was uncertain AND the captain sent
   too few / low-quality photos to be decisive. Coaches them to add
   angles rather than trusting a low-confidence verdict. */
.oemd-photo-nudge {
  margin: 0 0 12px;
  padding: 10px 12px;
  background: rgba(254,162,0,.10);
  border: 1px dashed rgba(254,162,0,.45);
  border-radius: 10px;
  font-size: 12.5px; color: var(--ymh-ink); line-height: 1.4;
}

/* Follow-up input pinned to the bottom of the result card so the captain
   can ask "what should a healthy one look like" without re-uploading. */
.oemd-photo-followup {
  display: flex; gap: 8px; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid var(--ymh-line);
}
.oemd-photo-followup input {
  flex: 1; min-width: 0;
  padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--ymh-line); background: #fff;
  font: 400 13px/1.3 inherit; color: var(--ymh-ink);
  outline: none;
}
.oemd-photo-followup input:focus { border-color: var(--ymh-orange); }
.oemd-photo-followup button {
  padding: 0 14px; min-height: 36px; border-radius: 10px;
  border: 0; background: var(--ymh-orange); color: var(--ymh-ink-2);
  font: 600 12.5px/1 inherit; cursor: pointer;
}

/* ── Chat pane (main column bottom) ────────────────────────────────── */
.oemd-chat-intro {
  text-align: center; padding: 8px 0 0; font-size: 12px; color: var(--ymh-muted);
}
.oemd-chat {
  display: flex; flex-direction: column; gap: 10px;
}
.oemd-chat .eng-thread {
  min-height: 120px; max-height: 340px; overflow-y: auto;
  padding: 6px 0; display: flex; flex-direction: column; gap: 12px;
}
.oemd-chat .eng-thread:empty { min-height: 0; padding: 0; }
.oemd-chat .eng-thread > *:first-child { margin-top: auto; }

/* Intent-chip row above the chat input */
.oemd-mode-row {
  display: flex; gap: 6px; flex-wrap: wrap;
}
.oemd-mode {
  padding: 6px 13px; border-radius: var(--ymh-r-pill);
  border: 1px solid transparent; background: var(--ymh-panel);
  color: var(--ymh-link); font-size: 12px; font-weight: 600;
  cursor: pointer; font-family: inherit;
  transition: background .16s ease, color .16s ease, border-color .16s ease, transform .16s ease;
  display: inline-flex; align-items: center; gap: 6px;
}
.oemd-mode:hover { background: #f2f6fa; transform: translateY(-1px); }
.oemd-mode.active {
  background: var(--ymh-ink); border-color: var(--ymh-ink);
  color: #ffffff;
}
.oemd-mode.vessel {
  background: var(--ymh-peach); border-color: transparent; color: var(--ymh-ink-2);
}

/* Chat input pill (Figma pattern) */
.oemd-chat .eng-input-wrap {
  display: flex; align-items: center; gap: 10px;
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  border-radius: var(--ymh-r-pill); padding: 6px 6px 6px 16px;
  transition: border-color .18s ease;
}
.oemd-chat .eng-input-wrap:focus-within { border-color: var(--ymh-orange); }
.oemd-chat .eng-input {
  flex: 1; background: transparent; border: 0; padding: 10px 0;
  color: var(--ymh-ink); font-size: 14px; font-family: inherit; outline: none;
}
.oemd-chat .eng-input::placeholder { color: var(--ymh-muted); }
.oemd-chat .eng-mic,
.oemd-chat .eng-attach {
  width: 32px; height: 32px; flex: 0 0 auto; background: transparent;
  border: 0; color: var(--ymh-muted); cursor: pointer;
  display: grid; place-items: center; border-radius: var(--ymh-r-pill);
  transition: all .12s ease;
}
.oemd-chat .eng-mic:hover:not(:disabled),
.oemd-chat .eng-attach:hover:not(:disabled) { color: var(--ymh-orange); background: var(--ymh-peach); }
.oemd-chat .eng-mic:disabled,
.oemd-chat .eng-attach:disabled { opacity: .5; cursor: not-allowed; }
.oemd-chat .eng-send {
  width: 36px; height: 36px; flex: 0 0 auto; border: 0;
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  border-radius: var(--ymh-r-pill); font-size: 16px; cursor: pointer;
  display: grid; place-items: center; font-weight: 700;
  transition: filter .12s ease;
}
.oemd-chat .eng-send:hover { filter: brightness(1.03); }
.oemd-chat .eng-send:disabled { opacity: .5; cursor: not-allowed; }

/* Chat message bubbles, light theme override for engineer.css defaults */
.oemd .eng-msg-wrap { align-self: flex-start; max-width: 88%; }
.oemd .eng-msg-wrap.user { align-self: flex-end; }
.oemd .eng-msg {
  padding: 12px 16px; border-radius: 14px; line-height: 1.55;
  font-size: 14px; animation: engFadeIn .35s cubic-bezier(.2,.9,.3,1.05) both;
  font-family: inherit; white-space: pre-wrap;
}
.oemd .eng-msg.user {
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  font-weight: 500; border-radius: 14px 14px 4px 14px;
}
.oemd .eng-msg.bot {
  background: var(--ymh-row-hover); color: var(--ymh-ink);
  border: 1px solid var(--ymh-line);
  border-radius: 14px 14px 14px 4px;
}
.oemd .eng-msg.typing {
  background: var(--ymh-row-hover); border: 1px solid var(--ymh-line);
  padding: 12px 20px;
}
.oemd .eng-msg.typing i {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ymh-orange); margin: 0 2px; opacity: .4;
  animation: engBlink 1.2s infinite;
}
.oemd .eng-msg.typing i:nth-child(2) { animation-delay: .15s; }
.oemd .eng-msg.typing i:nth-child(3) { animation-delay: .3s; }
.oemd .eng-fb {
  display: flex; gap: 6px; align-items: center; padding: 6px 4px 0 4px;
  opacity: .55; transition: opacity .18s ease;
}
.oemd .eng-msg-wrap:hover .eng-fb { opacity: 1; }
.oemd .eng-fb-btn {
  background: var(--ymh-chip-bg); border: 1px solid var(--ymh-line);
  color: var(--ymh-ink); font-size: 14px; padding: 4px 10px;
  border-radius: var(--ymh-r-pill); cursor: pointer;
  transition: all .18s ease; display: inline-flex; align-items: center;
}
.oemd .eng-fb-btn:hover { border-color: var(--ymh-orange); color: var(--ymh-orange); }
.oemd .eng-fb-thanks { font-size: 12px; color: var(--ymh-muted); font-style: italic; }

/* Loading spinner (shared) */
.oemd-spinner {
  display: inline-block; width: 14px; height: 14px;
  border: 2px solid rgba(11,20,32,.15); border-top-color: var(--ymh-orange);
  border-radius: 50%; animation: oemdSpin .8s linear infinite;
  vertical-align: middle; margin-right: 6px;
}
@keyframes oemdSpin { to { transform: rotate(360deg); } }
@keyframes engBlink { 0%,60%,100% { opacity: .3; } 30% { opacity: 1; } }
@keyframes engFadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ═══════════════════════════════════════════════════════════════════════
   APP SHELL (v3, matches Figma Chat Landing v2 pattern)
   ═══════════════════════════════════════════════════════════════════════
   Opt-in via body.oemd-shell so we don't disturb pages still on the older
   two-column dashboard. Grid layout:
     [ 256px sidebar ][ 64px topbar spanning main + rail ]
     [ 256px sidebar ][ 1fr main content ][ 352px right rail ]
   Sidebar and rail are position:fixed so only the main column scrolls.
   Kills the marketing site nav on shell pages via a fresh override
   (body.oemd-shell hides the .nav that was fixed at top). */

body.oemd-shell {
  padding: 0 !important;
  overflow-x: hidden;
}
/* Kill the fixed marketing nav on shell pages, the shell has its own
   topbar and there's no room at the top for the site nav strip. */
body.oemd-shell > header.nav { display: none !important; }

.appshell {
  display: grid;
  /* Round 12 · the same zero-floor the mobile block applies. `1fr` is
     minmax(auto, 1fr), so the growing column cannot shrink below its
     content's min-content width. Below 768 that produced M-01; in the
     768-1023 tablet band it was quietly producing a 10px overhang on
     /try (M-20). Fixed at every width rather than one band at a time. */
  grid-template-columns: minmax(0, 1fr) 352px;
  grid-template-rows: 64px 1fr;
  min-height: 100vh;
  background: var(--ymh-bg);
  position: relative;
}
/* App pages (Projects, Equipment, Warranty, Preventive, Fault, etc.)
   don't render the right rail, reclaim its 352px so the main column
   uses the full width. Cap content at 1200px for readability. */
.appshell:has(.appshell-app) { grid-template-columns: minmax(0, 1fr); }
.appshell-main.appshell-app { max-width: 1200px; }

/* ── SIDEBAR, overlay drawer, hidden by default on every viewport
   (ChatGPT-style burger nav). Slides in from the left when
   body.appshell-drawer-open is set. Scrim below dims the page. ── */
.appshell-sidebar {
  position: fixed; top: 0; left: 0;
  width: 300px; height: 100vh; z-index: 100;
  background: var(--ymh-sidebar); color: #ffffff;
  padding: 14px 14px 12px;
  display: flex; flex-direction: column; gap: 14px;
  /* 2026-08-17 fix: was overflow:hidden — but at typical laptop
     viewport heights (~800px), the sidebar content (New chat +
     Your vessel + YOUR CHATS + APPS + UNLOCK box + Log in) is TALLER
     than 100vh. Log in button was pushed off-screen with no way to
     reach it. Now scrolls within the sidebar so every item stays
     reachable regardless of window height. */
  overflow-y: auto;
  transform: translateX(-100%);
  transition: transform .22s cubic-bezier(.2,.7,.2,1);
  will-change: transform;
}
body.appshell-drawer-open .appshell-sidebar {
  transform: translateX(0);
  box-shadow: 8px 0 32px rgba(0,0,0,.28);
}
.appshell-scrim {
  position: fixed; inset: 0; z-index: 99;
  background: rgba(11,20,32,.55);
  opacity: 0; pointer-events: none;
  transition: opacity .22s ease;
}
body.appshell-drawer-open .appshell-scrim {
  opacity: 1; pointer-events: auto;
}
body.appshell-drawer-open { overflow: hidden; }
.appshell-logo {
  display: flex; gap: 10px; align-items: center; padding: 4px 8px;
}
.appshell-logo-mark {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 8px;
  background: var(--ymh-orange); display: grid; place-items: center;
  color: var(--ymh-ink-2); font-weight: 700; font-size: 15px;
}
.appshell-logo-mark svg { display: block; }
.appshell-logo-title {
  font-size: 13px; font-weight: 700; line-height: 1.2; color: #ffffff;
}
.appshell-logo-sub {
  font-size: 11px; color: var(--ymh-muted-light); line-height: 1.2;
}
.appshell-newchat {
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  border: 0; border-radius: var(--ymh-r-pill);
  padding: 10px 14px; font-weight: 600; font-size: 13px;
  cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  transition: filter .12s ease;
}
.appshell-newchat:hover { filter: brightness(.98); }
.appshell-vessel-switch {
  border: 1px solid rgba(255,255,255,.24); border-radius: var(--ymh-r-pill);
  background: transparent; color: #ffffff;
  padding: 9px 14px; font-weight: 600; font-size: 12px;
  cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.appshell-vessel-switch:hover { background: rgba(255,255,255,.06); }
.appshell-vessel-switch .name { display: flex; align-items: center; gap: 8px; }
.appshell-sect-label {
  font-size: 9.5px; font-weight: 700; color: var(--ymh-muted-light);
  letter-spacing: .1em; padding: 0 8px 6px;
}
.appshell-history { display: flex; flex-direction: column; gap: 2px; }
.appshell-hist-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--ymh-r-row);
  color: #ffffff; font-size: 12px; font-family: inherit;
  background: transparent; border: 0; cursor: pointer; text-align: left;
  width: 100%;
}
.appshell-hist-item:hover { background: rgba(255,255,255,.04); }
.appshell-hist-item.active { background: var(--ymh-sidebar-item); font-weight: 600; }
.appshell-hist-item svg { flex: 0 0 auto; }
.appshell-spacer { flex: 1 1 auto; min-height: 8px; }
.appshell-apps { display: flex; flex-direction: column; gap: 2px; }
.appshell-nav-item {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 12px; border-radius: var(--ymh-r-row);
  color: var(--ymh-muted-lighter); font-size: 12px; font-family: inherit;
  background: transparent; border: 0; cursor: pointer; text-align: left;
  width: 100%; text-decoration: none;
}
.appshell-nav-item:hover { background: rgba(255,255,255,.05); color: #ffffff; }
.appshell-nav-item.active { background: rgba(245,166,35,.14); color: var(--ymh-orange); }
.appshell-nav-item svg { flex: 0 0 auto; }
.appshell-nav-item .lock { margin-left: auto; opacity: .6; }
.appshell-upgrade {
  background: #0f1c30; border: 1px solid rgba(191,204,222,.28);
  border-radius: var(--ymh-r-card); padding: 12px 14px;
  display: flex; flex-direction: column; gap: 4px;
}
.appshell-upgrade-tag {
  font-size: 9px; font-weight: 700; color: var(--ymh-muted-light);
  letter-spacing: .06em;
}
.appshell-upgrade-title {
  font-size: 13px; font-weight: 700; color: var(--ymh-orange);
  margin-top: 2px;
}
.appshell-upgrade-body {
  font-size: 11px; color: var(--ymh-muted-lighter); line-height: 1.45;
  margin: 4px 0 6px;
}
.appshell-upgrade-link {
  font-size: 11px; font-weight: 700; color: var(--ymh-orange);
  text-decoration: none;
}
.appshell-upgrade-link:hover { text-decoration: underline; }
.appshell-logout {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 12px; border-radius: var(--ymh-r-row);
  color: var(--ymh-muted-lighter); font-size: 12px; font-family: inherit;
  background: transparent; border: 0; cursor: pointer;
}
/* 2026-08-20 · "the log out button is gone".
 *
 * It was not gone, it was 31px below the fold. The 2026-08-17 fix made the
 * sidebar scroll so nothing would be unreachable, and that is true but weak:
 * it only helps if the captain realises the sidebar scrolls, and Log out is
 * the LAST child, so it is always the one that falls off. On a 706px
 * viewport it measured top:702 in a container that scrolls all of 43px.
 *
 * Sticky to the bottom of the scroll container instead. It stays put at
 * either end of the scroll, it keeps its own opaque ground so the list
 * passes behind it rather than through it, and margin-top:auto holds it at
 * the bottom when the content is short enough not to scroll at all.
 *
 * Verified on a signed-in /app at 706px: 658-694 at scrollTop 0 and at
 * scrollTop max, hit-testing to .appshell-logout in both. */
.appshell-sidebar > .appshell-logout {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  flex-shrink: 0;
  background: var(--ymh-sidebar);
  border-top: 1px solid rgba(255,255,255,.08);
  box-shadow: 0 -10px 18px -6px var(--ymh-sidebar);
}
.appshell-logout:hover { background: rgba(255,255,255,.05); color: #ffffff; }

/* ── TOPBAR (spans full width; sidebar is now an overlay drawer) ── */
.appshell-topbar {
  grid-column: 1 / -1; grid-row: 1;
  background: var(--ymh-panel); border-bottom: 1px solid var(--ymh-line);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 16px 0 8px;
  position: sticky; top: 0; z-index: 10;
}

/* Round 12 · the topbar is a fixed-height single-line bar, so nothing in it may
 * wrap. Without this every text run in it breaks onto 2-4 lines from 768px all
 * the way up through 1280: the title splits ("Yachtmasters" / "Hub"), the crumb
 * stacks the vessel name vertically, "Sign out" becomes "Sign" / "out".
 *
 * It was previously only prevented below 767.98px in app-shell-mobile.css, so
 * the whole desktop range was affected and nobody noticed, because the text
 * WRAPS rather than overflows — measured horizontal overflow is zero at every
 * width. Overflow checks cannot see this; only a line-box count can.
 *
 * Elastic runs get an ellipsis so nowrap degrades instead of pushing the row
 * wide. Applied to every descendant: white-space only governs text flow, so it
 * cannot affect icon sizing, and excluding icon-bearing controls just left
 * .appshell-tb-back breaking its label away from its glyph. */
.appshell-topbar,
.appshell-tb-left,
.appshell-tb-right { flex-wrap: nowrap; }

.appshell-topbar, .appshell-topbar * { white-space: nowrap; }

.appshell-tb-title,
.appshell-tb-crumb {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Scoped to 768px and up ON PURPOSE. Below that, app-shell-mobile.css already
 * has its own tuned clamps for this bar and production is clean there; layering
 * a second set of shrink weights on top squeezed the identity chip below its
 * non-shrinkable content and pushed "Sign out" 19px off a 320px screen. One
 * shrink system per range. */
@media (min-width: 768px) {
  /* Round 12 · make the bar adaptive rather than rigid.
   *
   * The right cluster measured a flat 635px at EVERY width from 960 to 2560: it
   * never yielded, so the left side absorbed the entire squeeze and the boat
   * identity was the only thing that paid. At 768 the title and crumb rendered
   * at literally 0px. The vessel name is the one thing in this bar a captain
   * actually needs, and it was the first thing to go.
   *
   * Everything now shrinks, with weights so the least load-bearing text yields
   * first: the session chip, then the "Get the app" pill, then the account
   * email, and the boat identity last of all with a floor so it can never
   * vanish.
   *
   * Deliberately NOT breakpoint-driven. The same rules apply at 768 and at 2560,
   * so the bar degrades continuously instead of rearranging itself at a
   * threshold, and desktop above ~1440 is untouched because nothing needs to
   * shrink there. */
  .appshell-tb-left  { flex: 1 1 auto; min-width: 0; }
  .appshell-tb-right { flex: 1 1 auto; min-width: 0; justify-content: flex-end; }

  .appshell-tb-title { flex: 0 1 auto; flex-shrink: 1; min-width: 3.5rem; }
  .appshell-tb-crumb { flex: 0 1 auto; flex-shrink: 2; min-width: 3.5rem; }

  .appshell-tb-identity { flex-shrink: 4; min-width: 0; overflow: hidden; }
  .appshell-tb-identity > span:nth-child(2) {
    display: inline-block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
  }
  .appshell-tb-chip,
  .appshell-tb-cta {
    min-width: 0;
    overflow: hidden;
  }
  /* A flex container cannot ellipsize its own text — the rules above
     silently hard-clipped mid-word (live 2026-08-21). The inner label
     spans are plain boxes, so truncation actually works there. */
  .appshell-tb-chip-label,
  .appshell-tb-cta-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .appshell-tb-chip { flex-shrink: 6; }
  .appshell-tb-cta  { flex-shrink: 3; }

  /* Icon controls keep their size at every width: they are tap targets with no
   * text to give up. .appshell-identity-reset is deliberately NOT here despite
   * being a control, because it is a text label ("Sign out") that can ellipsis. */
  .appshell-burger,
  .appshell-notif-wrap { flex-shrink: 0; }
}
/* Burger, the only entry point to the nav drawer on any viewport. */
.appshell-burger {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  border-radius: 10px; color: var(--ymh-ink);
  font-family: inherit;
}
.appshell-burger:hover { background: var(--ymh-row-hover); }
.appshell-burger:focus-visible { outline: 2px solid var(--ymh-orange); outline-offset: 2px; }
.appshell-burger svg { display: block; }
.appshell-tb-left {
  display: flex; align-items: center; gap: 10px;
  font-size: 14px; color: var(--ymh-muted);
  min-width: 0;
}
.appshell-tb-title { color: var(--ymh-ink); font-weight: 600; }
.appshell-tb-crumb { color: var(--ymh-link); font-weight: 500; font-size: 12.5px; }
.appshell-tb-right { display: flex; align-items: center; gap: 10px; }
.appshell-tb-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--ymh-r-pill);
  background: var(--ymh-chip-bg); border: 1px solid var(--ymh-line);
  color: var(--ymh-link); font-size: 12px; font-weight: 600;
  font-family: inherit; cursor: pointer; text-decoration: none;
}
.appshell-tb-chip:hover { background: #f0f4fa; }
.appshell-tb-cta {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: var(--ymh-r-pill);
  background: var(--ymh-ink); color: #ffffff;
  font-size: 12px; font-weight: 600; font-family: inherit;
  border: 0; cursor: pointer; text-decoration: none;
}
.appshell-tb-cta:hover { filter: brightness(1.08); }
.appshell-tb-cta.orange { background: var(--ymh-orange); color: var(--ymh-ink-2); }

/* ── MAIN CONTENT AREA (left column, only scrollable region) ── */
.appshell-main {
  grid-column: 1; grid-row: 2;
  min-width: 0; padding: 22px 24px;
  overflow-y: auto;
}

/* ── RIGHT RAIL (fixed 352px on wide desktop, scrolls internally) ── */
.appshell-rail {
  grid-column: 2; grid-row: 2;
  background: var(--ymh-panel); border-left: 1px solid var(--ymh-line);
  padding: 14px 16px; min-width: 0;
  display: flex; flex-direction: column; gap: 10px;
  /* 2026-08-17 fix: was overflow:hidden — but at typical laptop
     viewport heights, rail content (vessel card + profile completeness
     + NEXT 3 DUE + QUICK ACTIONS + UNLOCK + Save with Google) is TALLER
     than calc(100vh - 64px). Bottom CTA was clipped. Now scrolls within
     the rail so every item stays reachable. */
  overflow-y: auto;
  position: sticky; top: 64px; height: calc(100vh - 64px);
}
/* Rail children get min-height:0 so shrinking works. The photo card and the
   bottom Focus-care/Unlock CTA stay pinned. Middle sections (Next 3 Due,
   Quick Actions, etc.) are flex columns whose section header stays put
   and whose list inside can scroll if there are more items than fit. */
.appshell-rail > * { min-height: 0; }
/* .rail-inspection joins the pinned set. It is a small fixed card, not
   a scrollable section, so shrinking it has nowhere to give: the rail
   simply cut the "Run weekly walk" button in half. The rail scrolls
   (overflow-y above), so refusing to shrink costs nothing. Same defect
   the hero photo card had, one tile later. */
.appshell-rail > .rail-photo,
.appshell-rail > .rail-inspection,
.appshell-rail > .rail-focus-care { flex-shrink: 0; }
.appshell-rail > div:not(.rail-photo):not(.rail-inspection):not(.rail-focus-care) {
  flex-shrink: 1;
  display: flex; flex-direction: column;
  min-height: 0;
}
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .rail-section-head,
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .oemd-warranty-head {
  flex: 0 0 auto;
}
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .oemd-warranty-foot {
  flex: 0 0 auto;
}
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .rail-section-list,
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .oemd-warranty-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Slim scrollbar so 'a little scroll' reads as scroll, not chrome. */
  scrollbar-width: thin;
  scrollbar-color: rgba(191,204,222,.55) transparent;
}
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .rail-section-list::-webkit-scrollbar,
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .oemd-warranty-list::-webkit-scrollbar {
  width: 6px;
}
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .rail-section-list::-webkit-scrollbar-thumb,
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) > .oemd-warranty-list::-webkit-scrollbar-thumb {
  background: rgba(191,204,222,.55); border-radius: 999px;
}
/* Rail children were letting their content paint outside the flex box
 * when vertical space got tight (warranty rows leaking into NEXT 3 DUE,
 * QUICK ACTIONS title landing on top of the warranty foot). Clip each
 * section to its allocated flex box so overflow scrolls the list inside
 * instead of colliding with the next section. */
.appshell-rail > div:not(.rail-photo):not(.rail-focus-care) {
  overflow: hidden;
}

/* Boat photo hero card at the top of the right rail */
.rail-photo {
  height: 128px; flex: 0 0 auto; border-radius: var(--ymh-r-card);
  background: linear-gradient(135deg, #0e1c32 0%, #1b2b41 100%);
  position: relative; overflow: hidden;
  display: flex; align-items: flex-end; padding: 12px 14px;
}
.rail-photo-badge {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: var(--ymh-r-pill);
  background: rgba(74,222,128,.18); color: #4ade80;
  font-size: 9px; font-weight: 700; letter-spacing: .06em;
}
.rail-photo-avatar {
  position: absolute; top: 10px; left: 10px;
  width: 28px; height: 28px; border-radius: 8px;
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  display: grid; place-items: center;
  font-weight: 700; font-size: 13px;
}
.rail-photo-avatar svg { display: block; }
.rail-photo-hrs {
  position: absolute; top: 40px; right: 10px;
  padding: 3px 10px; border-radius: var(--ymh-r-pill);
  background: rgba(255,255,255,.14); color: #ffffff;
  font-size: 10px; font-weight: 600;
}
.rail-vessel-name {
  color: #ffffff; font-weight: 700; font-size: 16px; line-height: 1.2;
}
.rail-vessel-meta {
  color: var(--ymh-muted-lighter); font-size: 11px; margin-top: 2px;
}
.rail-photo-inner {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
}

/* Reused inside rail: NEXT 3 DUE, QUICK ACTIONS sections */
.rail-section-head {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 8px;
}
.rail-section-title {
  font-size: 10.5px; font-weight: 700; color: var(--ymh-muted);
  letter-spacing: .08em; text-transform: uppercase;
}
.rail-section-list { display: flex; flex-direction: column; gap: 4px; }
.rail-list-row {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 10px; border-radius: var(--ymh-r-row);
  background: var(--ymh-row-hover);
}
.rail-list-row .dot {
  width: 8px; height: 8px; border-radius: 999px; background: var(--ymh-orange);
  flex: 0 0 auto;
}
.rail-list-row .dot.blue { background: var(--ymh-info); }
.rail-list-row .body { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rail-list-row .t { font-size: 12px; font-weight: 600; color: var(--ymh-ink); line-height: 1.3; }
.rail-list-row .m { font-size: 10px; color: var(--ymh-muted); }
.rail-qa-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 12px; border-radius: var(--ymh-r-pill);
  background: var(--ymh-panel); border: 1px solid var(--ymh-line);
  color: var(--ymh-ink); font-size: 12px; font-weight: 600;
  cursor: pointer; font-family: inherit; text-align: left;
  width: 100%;
}
.rail-qa-row:hover { border-color: var(--ymh-orange); }
.rail-qa-row svg { flex: 0 0 auto; }
.rail-qa-row .arrow { margin-left: auto; color: var(--ymh-muted); }
.rail-focus-care {
  margin-top: auto;
  background: linear-gradient(135deg, #0e1c32 0%, #1b2b41 100%);
  border-radius: var(--ymh-r-card); padding: 10px 14px;
  color: #ffffff;
}
.rail-focus-care .tag {
  font-size: 9px; font-weight: 700; color: var(--ymh-orange);
  letter-spacing: .08em; margin-bottom: 4px;
}
.rail-focus-care .title { font-size: 14px; font-weight: 700; margin-bottom: 3px; }
.rail-focus-care .body {
  font-size: 10.5px; color: var(--ymh-muted-lighter); line-height: 1.4;
  margin-bottom: 8px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.rail-focus-care .cta {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px; border-radius: var(--ymh-r-pill);
  background: var(--ymh-orange); color: var(--ymh-ink-2);
  font-size: 11.5px; font-weight: 700; text-decoration: none;
  border: 0; cursor: pointer; font-family: inherit;
}

/* ═══════════════════════════════════════════════════════════════
 * Spec 0180 (2026-08-17): full responsive pass across 4 breakpoints.
 *
 * Prior state: sidebar+rail turned on at 1024, giving main only 384px
 * at 1024 viewport (256 + 384 + 352 + gaps = 992). Captains on 13"
 * laptops saw tiles overflow, cards clipped, "elements hide" complaint.
 *
 * New band map:
 *   ≥1440  : sidebar 256 + main 1fr + rail 352    (comfortable)
 *   1280-1439: sidebar 256 + main 1fr + rail 300  (rail narrower)
 *   1024-1279: sidebar 256 + main 1fr + rail 280  (rail even narrower)
 *   768-1023 : sidebar drawer, main 1fr, rail STACKS BELOW main (was hidden)
 *   ≤767     : full mobile pass (spec 0132, unchanged)
 *
 * Rationale: at 1024 laptop viewport, main now gets 1024-256-280-16
 * = 472px minimum. Rail content still accessible on the same viewport
 * (no toggle needed) — just narrower. Tablet band gets full-width main
 * with rail as a below-fold section instead of a hidden feature.
 * ═══════════════════════════════════════════════════════════════ */

/* Desktop ≥ 1024px: sidebar is persistent (256px column), no drawer,
   burger hidden. Reclaims 300px of always-visible nav real estate so
   Projects / Equipment / Documents / Warranty / Focus Care / Settings
   are one click away. Under 1024px the drawer pattern still applies. */
@media (min-width: 1024px) {
  .appshell {
    /* Spec 0180: rail width scales down at narrower desktops (see
       ≥1280 override below); at 1024-1279 it's 280 to keep main >= 470px. */
    grid-template-columns: 256px 1fr 280px;
  }
  .appshell:has(.appshell-app) {
    grid-template-columns: 256px 1fr;
  }
  .appshell-sidebar {
    position: sticky;
    top: 0; left: auto;
    height: 100vh;
    width: auto;
    grid-column: 1; grid-row: 1 / span 2;
    transform: none;
    box-shadow: none;
    z-index: 5;
    transition: none;
  }
  .appshell-topbar { grid-column: 2 / -1; }
  .appshell-main   { grid-column: 2; }
  .appshell-rail   { grid-column: 3; }
  /* Drawer-open state on desktop is a no-op: sidebar is already visible,
     and there's no scrim. */
  body.appshell-drawer-open .appshell-sidebar { box-shadow: none; }
  body.appshell-drawer-open .appshell-scrim   { display: none; }
  body.appshell-drawer-open                   { overflow: auto; }
  .appshell-burger { display: none; }
}

/* Spec 0180: 1280-1439 band gives rail a bit more room (300px). */
@media (min-width: 1280px) {
  .appshell {
    grid-template-columns: 256px 1fr 300px;
  }
}

/* Spec 0180: 1440+ (typical external monitor) rail returns to full 352. */
@media (min-width: 1440px) {
  .appshell {
    grid-template-columns: 256px 1fr 352px;
  }
}

/* Spec 0180: tablet band (768-1023). Sidebar is drawer. Rail STACKS
   below main content instead of hiding (was display:none pre-0180).
   Captain still sees vessel details on the same viewport; scrolls to
   reach them. */
@media (max-width: 1023px) and (min-width: 768px) {
  .appshell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 64px auto auto;
  }
  .appshell-topbar { grid-column: 1; grid-row: 1; }
  .appshell-main   { grid-column: 1; grid-row: 2; }
  .appshell-rail {
    grid-column: 1; grid-row: 3;
    display: block;
    padding: 8px 16px 24px;
    background: var(--ymh-bg);
    border-top: 1px solid var(--ymh-line);
    max-width: 100%;
  }
}

/* Mobile: drop the right rail and tighten padding. The sidebar is an
   overlay drawer below 1024px so nothing else needs to shift.
   (Spec 0132 mobile pass at ≤767.98px handles the deeper mobile work
   including stacking the rail — keeping this fallback for the
   double-breakpoint band 640-767 as belt-and-suspenders.) */
@media (max-width: 767.98px) {
  .appshell { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .appshell-main { padding: 16px 14px; }
  .appshell-topbar { padding: 0 8px 0 4px; }
  .appshell-tb-title { font-size: 13px; }
  /* On tight screens the crumb + secondary CTA crowd the topbar; hide them
     so the burger + title + primary right chip stay legible. Nav content
     stays reachable through the drawer. */
  .appshell-tb-crumb, .appshell-tb-chip { display: none; }
  .appshell-sidebar { width: 86vw; max-width: 320px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   AI CHAT SURFACE, "ambient glass + gradient" (only the conversation
   scrolls; everything else is fixed). Applies to /focus and /try where
   .appshell-main contains .oemd-chat.oemd-chat-hero.
   ═══════════════════════════════════════════════════════════════════════ */

/* When the main column holds the chat hero, lock its scroll and stack the
   hero-block (welcome + tiles) on top of the chat pane. The chat pane's
   internal .eng-thread is the only scrollable region. */
.appshell-main:has(> .oemd-chat-hero) {
  overflow: hidden;
  padding: 0;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 64px);
  position: relative;
}
/* Hero block above the chat, welcome heading + tiles + panels. Collapses
   to zero height once the first message fires (body.eng-chatting) so the
   chat takes over the whole surface. Also collapses when a tile is picked
   (body.oemd-hero-collapsed, wired below). */
.oemd-hero-block {
  flex: 0 0 auto;
  overflow-y: auto;
  padding: 22px 24px 8px;
  transition: max-height .28s ease, opacity .18s ease, padding .28s ease;
  max-height: 60vh;
  opacity: 1;
}
body.eng-chatting .oemd-hero-block,
body.oemd-hero-collapsed .oemd-hero-block {
  max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0;
  pointer-events: none; overflow: hidden;
}

/* Chat pane, ambient gradient bg so the surface feels lifted. Grid:
   messages on top (flex-grows), input dock pinned at bottom. */
.oemd-chat-hero {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* Round 12 (2026-08-19). Was `1fr auto auto` — three declared rows for
   * FOUR children (.eng-thread, .oemd-mode-row, .eng-input-wrap and
   * p.eng-disclaimer). The disclaimer landed in an implicit row, and with
   * align-content resolving to stretch the leftover height was shared out
   * across the tracks instead of going to the 1fr: measured on a 430px
   * phone in the chatting state the tracks came back as
   * 149.5 / 181.5 / 197.5 / 169.4, so the composer rendered 189.5px tall
   * around 42px of content, the mode pills 169.5px tall, and the thread
   * was squeezed to 149.5px.
   *
   * Declaring only the growing row and letting every later row size to
   * its content fixes it without hard-coding the child count, which JS
   * changes. Measured after: thread 538, mode row 44, pill 40,
   * composer 60, disclaimer 32. */
  grid-template-rows: minmax(0, 1fr);
  grid-auto-rows: auto;
  /* Round 12 · M-19. Columns were never declared, so the single implicit
   * column was `auto` — which carries an automatic MINIMUM, exactly like
   * the `1fr` that caused M-01 on .appshell. The track could not shrink
   * below the min-content of its widest item, so at 320px .eng-thread and
   * .eng-input-wrap rendered 349px inside a 292px parent and the composer
   * ran 47px off the screen. Declaring the column with a zero floor lets
   * it follow the container. */
  grid-template-columns: minmax(0, 1fr);
  padding: 20px 24px 24px;
  gap: 10px;
  position: relative;
  /* No decorative glows. Low-alpha radials over a large cream field only
     have a few RGB levels to step through, so they render as concentric
     rings, an airbrush artifact, not an accent. Flat ground reads calmer
     and more current. */
  background: var(--ymh-bg);
}
/* Messages column, grows; only region that scrolls. */
.oemd-chat-hero .eng-thread {
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  padding: 6px 4px 12px;
  display: flex; flex-direction: column; gap: 24px;
  scroll-behavior: smooth;
  /* The rail sheet already hides its own bar (passport.css). A permanent
     grey track down the side of the answers is the loudest thing on the
     surface and it is furniture, not content. */
  scrollbar-width: none;
  /* Fade the top edge so scrolled-off messages slip away instead of hard
     cut. Only visible once content overflows. */
  mask-image: linear-gradient(to bottom, transparent 0, #000 24px, #000 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px, #000 100%);
}
.oemd-chat-hero .eng-thread::-webkit-scrollbar { width: 0; height: 0; }
.oemd-chat-hero .eng-thread > *:first-child { margin-top: auto; }

/* Empty-thread state: no centerpiece, the hero tiles above already
   answer "what can I ask." Keeping empty::before/after removed so the
   layout collapses cleanly into the input row (matches app chat). */

/* ── Messages: no avatars, either side ── */
/* Claude, ChatGPT and Grok all dropped in-thread avatars: the reply is
   plain text at full column width, the user's words are a right-aligned
   bubble, and alignment alone carries the role. The diamond and disc
   that used to sit here were the last avatars in the product. */
.oemd-chat-hero .eng-msg-wrap {
  display: grid; grid-template-columns: 1fr;
  align-self: stretch; max-width: 780px; width: 100%;
  margin: 0 auto;
  align-items: flex-start;
}
.oemd-chat-hero .eng-msg-wrap::before { content: none; }
/* The assistant does not speak in a bubble. A card around every answer
   makes a two-paragraph troubleshooting reply read like a chat sticker;
   stripping the chrome lets the text set its own measure and gives the
   reader a normal reading column. Role is carried by the avatar and the
   left offset, which is all it ever needed to carry. */
.oemd-chat-hero .eng-msg {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 1px 0 0;
  line-height: 1.7; font-size: 15.5px;
  color: var(--ymh-ink);
  box-shadow: none;
  white-space: pre-wrap;
  animation: engFadeIn .28s cubic-bezier(.2,.9,.3,1.05) both;
  /* 68ch is the comfortable measure; the 780px wrap caps it on wide screens. */
  max-width: 68ch;
}
/* The user's own words stay in a bubble, that is the one place the shape
   earns its keep. But a full-saturation gradient plus a coloured glow on
   every line the owner types is the loudest thing on the page, and it is
   the least important. Tint it instead: same brand hue, a tenth of the
   volume. */
.oemd-chat-hero .eng-msg-wrap.user .eng-msg {
  /* Neutral, like every current chat product: a quiet surface bubble.
     Brand orange on every typed line made the least important text on
     the page the loudest. */
  background: var(--ymh-panel);
  color: var(--ymh-ink);
  border: 0;
  border-radius: 18px;
  padding: 10px 16px;
  box-shadow: 0 1px 3px rgba(11,20,32,.06);
  justify-self: end;
  max-width: 82%;
}
/* Streaming cursor, added by engineer.js as content lands, removed on
   completion. Also renders visually on any .streaming bot msg. */
.oemd-chat-hero .eng-msg.streaming::after {
  content: "▍";
  display: inline-block; margin-left: 3px;
  color: var(--ymh-orange);
  animation: aiCursor 1s steps(2) infinite;
  font-weight: 700;
}
@keyframes aiCursor { 50% { opacity: 0; } }

/* Thinking dots, refined: three orange dots pulsing in a pill. */
.oemd-chat-hero .eng-msg.typing {
  background: transparent;
  border: 0;
  padding: 6px 0;
  display: inline-flex; gap: 5px; align-items: center;
}
.oemd-chat-hero .eng-msg.typing i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ymh-orange);
  opacity: .35; margin: 0;
  animation: aiPulse 1.1s ease-in-out infinite;
}
.oemd-chat-hero .eng-msg.typing i:nth-child(2) { animation-delay: .12s; }
.oemd-chat-hero .eng-msg.typing i:nth-child(3) { animation-delay: .24s; }
@keyframes aiPulse {
  0%, 60%, 100% { opacity: .35; transform: scale(.9); }
  30%           { opacity: 1;   transform: scale(1.15); }
}

/* ── Input dock, pinned bottom, backdrop-blur glass ── */
/* Chip row is hidden on the initial /focus + /try landing so tiles are the
   only intent picker. Once chat activates (body.eng-chatting) tiles
   collapse and the chip row appears as the compact switcher inside the
   input dock. Dedupes 'do I tap tile or chip?' during the first turn. */
.oemd-chat-hero .oemd-mode-row {
  display: none;
  max-width: 780px; width: 100%; margin: 0 auto;
  padding: 0 4px;
}
body.eng-chatting .oemd-chat-hero .oemd-mode-row { display: flex; }
.oemd-chat-hero .eng-input-wrap {
  max-width: 780px; width: 100%; margin: 0 auto;
  background: rgba(255,255,255,.72);
  /* Tonal pass: elevation carries the surface, not an outline. The 1px
     border stays, transparent, so the focus ring below swaps in with no
     geometry shift. Capsule radius + scale make the composer the hero,
     the way every current chat product treats it. */
  border: 1px solid transparent;
  border-radius: 28px;
  padding: 8px 8px 8px 20px;
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  box-shadow:
    0 2px 6px rgba(11,20,32,.05),
    0 18px 60px rgba(11,20,32,.10);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.oemd-chat-hero .eng-input-wrap:focus-within {
  border-color: var(--ymh-orange);
  box-shadow:
    0 0 0 4px rgba(245,166,35,.12),
    0 18px 60px rgba(11,20,32,.12);
}
.oemd-chat-hero .eng-input { font-size: 16px; padding: 8px 0; line-height: 1.4; }
.oemd-chat-hero .eng-send {
  width: 40px; height: 40px;
  background: linear-gradient(135deg, var(--ymh-orange) 0%, #ff8f2a 100%);
  color: var(--ymh-ink-2);
  box-shadow: 0 2px 8px rgba(245,166,35,.28);
}
.oemd-chat-hero .eng-send:hover { filter: brightness(1.04); transform: translateY(-1px); }

/* Right rail on chat pages also gets a fixed feel, its own scroll only.
   Already sticky with overflow-y auto; add a subtle top fade so items
   scroll cleanly under the topbar shadow. */
.appshell-main:has(> .oemd-chat-hero) ~ .appshell-rail {
  border-left: 1px solid var(--ymh-line);
}

@media (max-width: 640px) {
  .oemd-chat-hero { padding: 14px 12px 12px; }
  .oemd-chat-hero .eng-msg { font-size: 15px; line-height: 1.62; }
  .oemd-chat-hero .eng-thread { gap: 20px; }
  .oemd-chat-hero .eng-msg-wrap { gap: 8px; grid-template-columns: 24px 1fr; }
  .oemd-chat-hero .eng-msg-wrap.user { grid-template-columns: 1fr 24px; }
  .oemd-chat-hero .eng-msg-wrap::before { width: 22px; height: 22px; }
  .oemd-chat-hero .eng-msg-wrap.user .eng-msg { padding: 8px 13px; max-width: 88%; }
  /* 16px, not 14px: below 16px iOS Safari force-zooms the whole page on focus. */
  .oemd-chat-hero .eng-input { font-size: 16px; padding: 7px 0; }
  .oemd-hero-block { padding: 16px 14px 4px; }
}

/* MOBILE (<= 900px): the 'only chat scrolls' desktop pattern gets in the
   way on a phone (users can't scroll to see the 5th + 6th tile, and the
   hero-block max-height clips content). Fall back to a normal page scroll
   on mobile: main is auto-height, hero-block has no cap, and the chat
   input dock sticks to the bottom of the viewport instead of the grid.  */
@media (max-width: 900px) {
  .appshell-main:has(> .oemd-chat-hero) {
    height: auto;
    min-height: calc(100vh - 64px);
    overflow: visible;
  }
  .oemd-hero-block {
    max-height: none;
    overflow: visible;
    padding: 16px 14px 8px;
  }
  .oemd-chat-hero {
    /* Round 12 (2026-08-19). Was `auto auto auto` — the same three-rows-
     * for-four-children mismatch as the base rule at :1282, and this one
     * wins below 900px, which is why the phone was the surface that showed
     * it. All-auto rows plus align-content resolving to stretch meant the
     * leftover height was shared across the tracks: composer 190px around
     * 42px of content, mode pills 170px tall, thread squeezed to 150px.
     * Same shape as the base rule now: one growing row, everything after
     * it sized to its own content, no dependence on the child count. */
    grid-template-rows: minmax(0, 1fr);
    grid-auto-rows: min-content;
    padding: 8px 12px 12px;
  }
  .oemd-chat-hero .eng-thread {
    /* Round 12 · was min-height:200px / max-height:none, on the reasoning that
       "the chat thread grows with content, the whole page scrolls, not the
       internal grid". That is what lost the composer: with the PAGE scrolling,
       the composer scrolled away with it. Measured at 768, the page scrolled
       1391px and the composer ended at top:-69px, off the screen entirely,
       while position:sticky could not save it because it was only ever
       position:relative in that band.

       Now the thread scrolls internally and the chat column is viewport
       height, which is what desktop has always done. The composer is a grid
       row that cannot leave the viewport. */
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    mask-image: none;
    -webkit-mask-image: none;
  }
  .oemd-chat-hero .eng-input-wrap {
    /* No position games. It is the last row of a viewport-height grid, so it
       is always on screen without depending on sticky, on an ancestor's
       overflow, or on how far the page has scrolled. */
    position: relative;
    bottom: auto;
    box-shadow: 0 8px 24px rgba(11,20,32,.12);
  }
  /* Show the Infinity/Focus 3X photo on mobile as a small hero card
     at the top of the hero-block. Scoped to data-shell-brand="focus"
     (fix 2026-08-16): previously matched any .oemd-hero-block, so the
     Focus 3X photo rendered on /try guest mobile, brand confusion,
     since /try is the generic YMH surface, not Focus co-branded.
     /focus.html sets data-shell-brand="focus"; /engineer.html + /try
     do not. */
  body[data-shell-brand="focus"] .oemd-hero-block::before {
    content: "";
    display: block;
    height: 120px;
    background: linear-gradient(180deg,rgba(11,20,32,.15) 0%,rgba(11,20,32,.65) 100%),url('/assets/vessels/infinity-focus-3x.jpg') center/cover no-repeat;
    border-radius: 12px;
    margin-bottom: 12px;
  }
}

/* Save-to-Projects-Hub button on photo diagnose verdicts. Only rendered
   when the model identified a specific failure mode. Flips to a done
   state after save. */
.oemd-photo-savetask {
  display: block;
  width: 100%;
  margin: 12px 0 0;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid rgba(11,30,51,.14);
  background: #fff;
  color: var(--ymh-ink);
  font: 700 12.5px/1 inherit;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.oemd-photo-savetask:hover:not(:disabled) {
  border-color: var(--ymh-orange);
  background: rgba(245,166,35,.06);
  color: var(--ymh-orange);
}
.oemd-photo-savetask.saved {
  background: rgba(22,163,74,.10);
  border-color: rgba(22,163,74,.35);
  color: #16a34a;
  cursor: default;
}

/* ═══════════════════════════════════════════════════════════════════
 * Round 12 · the composer is FIXED while chatting. Non-negotiable.
 * ═══════════════════════════════════════════════════════════════════
 * Owner report: "I was chatting with it and I lost the damn text bar."
 * Reproduced at 768: the page scrolled 1391px and the composer ended at
 * top:-61px, off the screen, with no way to type.
 *
 * It was never pinned. Measured: position:relative at 393 and 768,
 * position:static at 1280. It only looked pinned on desktop because the
 * desktop chat does not scroll the page at all.
 *
 * Sizing the chat column to the viewport does NOT fix this: below 1024 the
 * right rail stacks underneath, so the page is tall regardless and scrolling
 * carries the whole main column, composer included, off the top.
 *
 * So: position fixed. It does not depend on an ancestor's overflow, on sticky
 * containing blocks, or on how far the page has scrolled. Scoped to below
 * 1024 because at desktop the page does not scroll and the composer is
 * already always on screen.
 * ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 1023.98px) {
  body.eng-chatting .eng-input-wrap,
  body.eng-chatting .oemd-chat-hero .eng-input-wrap {
    position: fixed !important;
    left: 0;
    right: 0;
    /* Not `0`. On iOS the layout viewport's bottom is BEHIND the open
       keyboard; --kb is the gap, published by keyboard-inset.js. Falls
       back to 0, which is the desktop and keyboard-closed case. */
    bottom: var(--kb, 0px);
    /* 70, not 60. passport.css turns .appshell-rail into a fixed 130px
       peek sheet at z-index 60 on the same edge of the screen. Equal
       z-index means DOM order decides, the rail comes after main, and the
       composer loses. The rail's own mitigation is
       `.appshell-main { padding-bottom: 140px }`, which was correct when
       the composer scrolled with the page and does nothing now that it is
       fixed. See --peek-h below for the other half of this. */
    z-index: 70;
    margin: 0;
    border-radius: 0;
    /* clear of the iOS home indicator */
    padding-top: 5px !important;
    padding-bottom: calc(5px + env(safe-area-inset-bottom)) !important;
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
    background: var(--ymh-panel);
    border-top: 1px solid var(--ymh-line);
    box-shadow: 0 -8px 24px rgba(11, 20, 32, .12);
  }

  /* Reserve the space it now occupies so the last message is never hidden
     behind it, and the disclaimer under it stays readable. */
  body.eng-chatting .eng-thread {
    padding-bottom: calc(96px + env(safe-area-inset-bottom)) !important;
  }
  body.eng-chatting .eng-disclaimer {
    margin-bottom: calc(84px + env(safe-area-inset-bottom));
  }
}

/* Round 12 · the topbar is FIXED while chatting, for the same reason.
 *
 * position:sticky on a grid item only sticks within its own grid AREA, and the
 * topbar's row is 56px tall, so on a phone it has nowhere to travel and scrolls
 * away with the page. Measured at 393: the burger left the screen entirely
 * while the tablet band happened to hold. The burger is the only way into the
 * nav drawer on any viewport, so losing it mid-chat strands the captain.
 *
 * Scoped to the chat surface and to below 1024, where the page scrolls and the
 * sidebar is a drawer rather than a permanent column. */
@media (max-width: 1023.98px) {
  body.eng-chatting .appshell-topbar {
    position: fixed !important;
    /* Safari scrolls the layout viewport to reveal a focused input, which
       carries a top:0 fixed bar off the top of what the user can see.
       --vvtop is how far it moved. */
    top: var(--vvtop, 0px);
    left: 0;
    right: 0;
    z-index: 50;
  }
  /* Give back the height the fixed bar no longer occupies in flow. */
  body.eng-chatting .appshell-main {
    padding-top: 64px !important;
  }
}


/* ── Yacht Passport drawer row · mobile only ─────────────────────────
   On desktop the passport IS the permanent right rail, so a drawer row
   would be a second door into the same room. It only earns its place on
   a phone, where the rail is hidden until asked for. */
.appshell-nav-passport { display: none; }
@media (max-width: 767.98px) {
  .appshell-nav-passport { display: flex; }
}

/* ── Sidebar chat history · the scrollbar was the loudest thing in the
   drawer. A chunky default track down a dark navy panel reads as a
   defect. Thin, and coloured to the panel it lives on. ─────────────── */
.appshell-history {
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.16) transparent;
}
.appshell-history::-webkit-scrollbar { width: 6px; }
.appshell-history::-webkit-scrollbar-track { background: transparent; }
.appshell-history::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.14);
  border-radius: 3px;
}
.appshell-history:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,.24); }


/* ═══════════════════════════════════════════════════════════════════
 * MOBILE CHAT CHROME · round 13
 * ═══════════════════════════════════════════════════════════════════ */

/* ── Topbar identity ────────────────────────────────────────────────
 * The phone topbar was rendering a bare "·" between the burger and the
 * bell. Two rules combined to produce it: the 640px rule hides
 * .appshell-tb-crumb (the vessel name), and .appshell-tb-title is
 * flex-shrinkable down to a few pixels, so "Yacht Assistant" collapsed
 * to nothing. What survived was the separator between two invisible
 * things.
 *
 * The deeper problem is that it was showing the wrong one. On a phone
 * the scarcest thing is horizontal space, and "Yacht Assistant" is the
 * app's name — the captain knows which app they opened. "Rani" is the
 * vessel every answer is grounded on, and it is the one piece of
 * identity worth the pixels. So on mobile: drop the app name, drop the
 * separator, show the boat. */
@media (max-width: 767.98px) {
  .appshell-topbar .appshell-tb-title,
  .appshell-topbar .appshell-tb-sep,
  /* The topbar is rendered from two places: appshell.js builds it on some
     surfaces, app.html hard-codes it on /app, and only one of them carried
     a class on the separator. Catch the bare span too, so it does not
     matter which path drew it. CSS has no previous-sibling selector, hence
     matching on "unclassed span in the left cluster" rather than on
     "the span before the crumb". */
  .appshell-tb-left > span:not([class]) { display: none !important; }
  .appshell-topbar .appshell-tb-crumb {
    display: block !important;
    font-size: 15px !important;
    font-weight: 700 !important;
    color: var(--ymh-ink) !important;
    letter-spacing: -.01em;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* ── The chrome is fixed from the first paint, not from the first
 * message ─────────────────────────────────────────────────────────
 * The pinning shipped in round 12 was gated on body.eng-chatting, which
 * engineer.js only sets once you send. So on the /app landing — the
 * screen everyone actually opens — the composer and the burger were
 * still in flow and still scrolled away. From the captain's side there
 * is no such thing as "before chatting": the text bar is either always
 * there or it is unreliable. Drop the gate on mobile. */
@media (max-width: 1023.98px) {
  .oemd-chat-hero .eng-input-wrap,
  .appshell .eng-input-wrap {
    position: fixed !important;
    left: 0; right: 0;
    bottom: var(--kb, 0px);
    z-index: 70;
    margin: 0;
    border-radius: 0;
    padding-top: 5px !important;
    /* When the keyboard is up it has already covered the home indicator,
       so adding the safe-area inset on top of --kb floats the bar a
       thumb's width above the keys. Subtract it back out. This is the
       "align perfectly with it" gap. */
    padding-bottom: calc(5px + max(0px, env(safe-area-inset-bottom) - var(--kb, 0px))) !important;
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
    background: var(--ymh-panel);
    border-top: 1px solid var(--ymh-line);
    box-shadow: 0 -8px 24px rgba(11, 20, 32, .12);
  }
  .appshell .appshell-topbar {
    position: fixed !important;
    top: var(--vvtop, 0px);
    left: 0; right: 0;
    z-index: 50;
  }
  .appshell .appshell-main {
    padding-top: 64px !important;
    padding-bottom: calc(var(--composer-h, 56px) + 16px + env(safe-area-inset-bottom)) !important;
  }
  /* The thread is the only thing that should move. Give it a real
     scrollport between the two fixed bars rather than letting the whole
     document scroll underneath them. */
  .oemd-chat-hero .eng-thread {
    overflow-y: auto !important;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
}


/* ── Topbar space budget on phones ───────────────────────────────────
 * The vessel name was rendering as a single letter, "R". Measured at
 * 393: the right cluster took 242px and .appshell-tb-back ("Home") took
 * another 73px, leaving 22px for the crumb, which has min-width:0 and so
 * collapsed rather than pushing back.
 *
 * Every item in that bar was allowed to shrink except the two that
 * should have. So state the priority explicitly, worst to first:
 *
 *   burger        never shrinks, it is the only way into the nav
 *   bell          never shrinks
 *   Sign out      never shrinks
 *   vessel name   shrinks last, and never below 4rem
 *   "Home"        gone, the drawer already has it
 *   email         gone, it is in the drawer too
 *
 * The floor matters as much as the order: min-width:0 is what let this
 * collapse silently instead of overflowing visibly, and a silent collapse
 * is the harder bug to notice. */
@media (max-width: 767.98px) {
  .appshell-topbar .appshell-tb-left {
    flex: 1 1 auto;
    min-width: 0;
    gap: 8px;
  }
  .appshell-topbar .appshell-tb-right {
    flex: 0 0 auto;
    min-width: 0;
  }
  .appshell-topbar .appshell-tb-crumb {
    flex: 1 1 auto;
    min-width: 4rem;
  }
  .appshell-topbar .appshell-tb-back { display: none !important; }
  .appshell-topbar .appshell-burger,
  .appshell-topbar .appshell-notif-wrap { flex: 0 0 auto; }
  /* The identity cluster is plan chip + email + Sign out. The email is a
     wide string the captain already knows, and it is in the drawer. */
  .appshell-topbar .appshell-tb-identity > span:nth-child(2) { display: none; }
  .appshell-topbar .appshell-tb-identity { flex: 0 1 auto; min-width: 0; }
}


/* ── Topbar right cluster: an explicit order ─────────────────────────
 * The bell was landing mid-bar, ahead of the account, the session chip
 * and the "Get the app" CTA. Nothing had decided where it goes: both
 * ymh-notifications.js and appshell.js mounted with
 * insertBefore(firstChild), so they raced and whichever ran last took
 * the front. Order was an accident of script timing.
 *
 * Decide it here instead. Reading left to right, the trailing cluster
 * goes contextual, then primary action, then utility, then account:
 *
 *   1  tb-chip     "What can I ask?" / session state
 *   2  tb-cta      "Get the app"
 *   3  notif-wrap  the bell
 *   4  tb-identity plan + Sign out
 *
 * The account sits last because that is where every application on a
 * captain's phone puts it, and the bell sits immediately beside it
 * because a notification bell is an account-scoped utility, not a piece
 * of page content. */
.appshell-tb-right { display: flex; align-items: center; }
.appshell-tb-right > .appshell-tb-chip     { order: 1; }
.appshell-tb-right > .appshell-tb-cta      { order: 2; }
.appshell-tb-right > .appshell-tb-identity { order: 3; margin-right: 0 !important; }
/* The bell sits between the account information and the account ACTION,
   so it ends up immediately left of Sign out rather than a full email
   address away from it. */
.appshell-tb-right > .appshell-notif-wrap  { order: 4; }
.appshell-tb-right > .appshell-tb-signout  { order: 5; }

/* The streaming skeleton (.shimmer, engineer.css) is dark-theme styled:
   white-alpha lines in a bordered card. On the light chat ground that
   reads as an empty white box while the model thinks. Unboxed here, with
   ink-alpha lines that actually show against cream. */
.oemd-chat-hero .eng-msg.shimmer {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 4px 0 0;
  min-height: 0;
  width: 100%;
  max-width: 68ch;
}
.oemd-chat-hero .eng-shimmer-line {
  background: linear-gradient(90deg, rgba(11,20,32,.05) 0%, rgba(11,20,32,.12) 50%, rgba(11,20,32,.05) 100%);
  background-size: 200% 100%;
}

/* Tonal pass: the thumbs sit as bare glyphs until the pointer arrives;
   hover fills with the existing row-hover tone. No outlines. */
.oemd-chat-hero .eng-fb-btn {
  background: transparent;
  border-color: transparent;
}
.oemd-chat-hero .eng-fb-btn:hover {
  background: var(--ymh-row-hover);
  border-color: transparent;
}
