/* =====================================================================
 * Jackson Enterprises maintenance portal: components this app adds on top
 * of the shared design system (style.css) and its theme (theme.css).
 *
 * Sections, in cascade order:
 *   banner · checkbox filter (.fdd) · thumbnails · provenance · panels and
 *   key/value lists · record head · equipment cards · property cards ·
 *   schedule rows (.agenda) · timeline · photos and uploader · AI box ·
 *   phone tab bar · Ask AI · sticky action bar · small utilities · print
 *
 * Everything reads the tokens. No colors are written here except through
 * var(--…), so both themes follow.
 * Page-only styling belongs in assets/pages/<page>.css, not here.
 * =================================================================== */

/* ── Banner ──────────────────────────────────────────────────────────
   A full-width note at the top of the page that stays until its reason
   goes away: a starting password, an offline phone, an unconfirmed record. */
.banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin: 0 0 16px;
  padding: 10px 14px;
  border: 1px solid var(--info-line);
  border-radius: var(--radius);
  background: var(--info-bg);
  color: var(--info);
  font-size: var(--fs-sm);
}
.banner > svg { flex: 0 0 auto; }
.banner > span { flex: 1 1 240px; min-width: 0; }
.banner-warn { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn); }
.banner-neutral { border-color: var(--line); background: var(--panel-soft); color: var(--ink-2); }
.banner .btn { flex: 0 0 auto; }

/* ── Checkbox filter (.fdd) ─────────────────────────────────────────
   Markup: Ui::fdd(). Behavior: app.js. A two-line tile (the filter's name
   over what is ticked) that opens a panel of checkboxes with search, All,
   Clear, a per-option "Only", and a live count beside each option. */
.fdd { position: relative; display: inline-block; min-width: 0; }
.fdd-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: 8px;
  align-items: center;
  min-width: 128px;
  max-width: 220px;
  height: 40px;
  padding: 4px 8px 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
.fdd-tile:hover { border-color: var(--line-strong); background: var(--panel); }
.fdd-tile:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.fdd-lbl {
  grid-column: 1;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--ink-muted);
  white-space: nowrap;
}
.fdd-val {
  grid-column: 1;
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 1.25;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fdd-chev { grid-column: 2; grid-row: 1 / span 2; color: var(--ink-faint); transition: transform var(--t-fast); }
.fdd-tile[aria-expanded="true"] { border-color: var(--accent); }
.fdd-tile[aria-expanded="true"] .fdd-chev { transform: rotate(180deg); }
/* On: an ink border, a tinted fill, and the selection in full-strength ink. */
.fdd-tile.is-set { border-color: var(--accent); background: var(--accent-soft); }
.fdd-tile.is-set .fdd-val { color: var(--ink); }
.fdd-tile.is-set .fdd-lbl { color: var(--ink-2); }

.fdd-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  width: max(100%, 272px);
  max-width: calc(100vw - 2 * var(--gutter));
  max-height: min(26rem, calc(100vh - 140px));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
/* app.js adds .fdd-right when a panel would hang off the window. A bottom sheet (below) is never aligned. */
@media (min-width: 721px) and (min-height: 501px) { .fdd-right .fdd-panel { left: auto; right: 0; } }
.fdd-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink-faint);
}
.fdd-search input,
.fdd-search input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  height: 38px;
  padding: 0;
  border: 0;
  background: transparent none;
  box-shadow: none;
  font-size: var(--fs-sm);
}
.fdd-search input:focus { box-shadow: none; }
.fdd-status { margin: 0; padding: 6px 12px 0; font-size: var(--fs-2xs); color: var(--ink-muted); }
/* The scroll lives on a div: Chrome will not scroll a fieldset. */
.fdd-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 6px; overscroll-behavior: contain; }
.fdd-none { margin: 0; padding: 14px 8px; font-size: var(--fs-sm); color: var(--ink-muted); text-align: center; }
.fdd-opt {
  display: flex;
  /* style.css lays every <label> out as a column (label over its control).
     An option is a row: tick box, name, count, "Only". */
  flex-direction: row;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 4px 6px 4px 8px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 450;
  color: var(--ink);
  cursor: pointer;
}
.fdd-opt:hover, .fdd-opt:focus-within { background: var(--bg-soft); }
.fdd-opt input { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; }
.fdd-opt-lbl { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fdd-cnt { flex: 0 0 auto; font-size: var(--fs-xs); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
/* An option that would show nothing stays tickable (it may already be
   ticked and need unticking), just quieter. */
.fdd-opt.is-zero .fdd-opt-lbl, .fdd-opt.is-zero .fdd-cnt { color: var(--ink-faint); }
.fdd-opt[hidden] { display: none; }
.fdd-only {
  flex: 0 0 auto;
  height: 22px;
  padding: 0 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  box-shadow: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-2xs);
  font-weight: 600;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.fdd-opt:hover .fdd-only, .fdd-opt:focus-within .fdd-only, .fdd-only:focus-visible { opacity: 1; }
.fdd-only:hover { border-color: var(--accent); color: var(--ink); background: var(--panel); }
/* No hover on a touch screen, so "Only" is simply there. */
@media (hover: none) { .fdd-only { opacity: 1; } }
.fdd-foot {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px;
  border-top: 1px solid var(--line-soft);
  background: var(--panel-soft);
}
.fdd-act {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
}
.fdd-act:hover:not(:disabled) { background: var(--panel-inset); color: var(--ink); }
.fdd-act-clear:hover:not(:disabled) { color: var(--danger); background: var(--danger-bg); }
.fdd-act:disabled { opacity: .4; cursor: default; }
.fdd-count { flex: 1 1 auto; min-width: 0; padding: 0 6px; font-size: var(--fs-xs); color: var(--ink-muted); text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The row of filters, its count and its reset link. */
.filterbar .fdd { flex: 0 0 auto; }
.filterbar .search { height: 40px; }
.filter-reset { font-size: var(--fs-sm); font-weight: 550; color: var(--ink-muted); text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; }
.filter-reset:hover { color: var(--link-hover); }
.filter-count { margin-left: auto; font-size: var(--fs-sm); color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* While new results are on their way. */
[data-live].is-loading { opacity: .55; pointer-events: none; transition: opacity var(--t-base); }

@media (max-width: 720px) {
  /* Phones: two filters to a row, and the panel becomes a sheet pinned to
     the bottom of the screen, where a thumb can reach every option. */
  .filterbar .search { flex: 1 1 100%; max-width: none; }
  .filterbar .fdd { flex: 1 1 calc(50% - 6px); }
  .fdd-tile { width: 100%; max-width: none; min-width: 0; height: 44px; }
  .fdd-panel {
    position: fixed;
    inset: auto 0 0 0;
    width: auto;
    max-width: none;
    max-height: min(78vh, 34rem);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    z-index: 90;
  }
  .fdd-opt { min-height: 46px; font-size: 15px; }
  .fdd-opt input { width: 20px; height: 20px; }
  .fdd-act, .fdd-foot .btn { height: 40px; }
  .fdd-only { height: 30px; padding: 0 10px; }
  .filter-count { flex-basis: 100%; margin-left: 0; }
}
@media (max-height: 500px) {
  /* A phone on its side is wide but very short. A filter bar that stays stuck
     would leave almost no list, and a panel hanging from it would end under the
     tab bar with Done out of reach. The bar scrolls away and the panel is the
     same bottom sheet as on a phone held upright. */
  .filterbar[data-sticky],
  .filterbar[data-sticky].is-stuck { position: static; margin: 0 0 12px; padding: 0; background: none; border: 0; box-shadow: none; }
  .fdd-panel {
    position: fixed;
    inset: auto 0 0 0;
    width: auto;
    max-width: none;
    max-height: 88vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    border-bottom: 0;
    padding: 0 env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
    z-index: 90;
  }
  .fdd-opt { min-height: 44px; }
}
/* The dimmed page behind an open filter sheet (app.js adds it). */
.fdd-scrim { position: fixed; inset: 0; z-index: 85; background: var(--scrim); }
@media (min-width: 721px) and (min-height: 501px) { .fdd-scrim { display: none; } }

/* ── Thumbnails ──────────────────────────────────────────────────────
   Ui::thumb(): the photo, or a quiet tile with the category's icon. */
.thumb {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  overflow: hidden;
  border-radius: 7px;
  background: var(--panel-inset);
  color: var(--ink-faint);
}
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb-sm { width: 40px; height: 40px; }
.thumb-md { width: 56px; height: 56px; border-radius: 9px; }
.thumb-lg { width: 96px; height: 96px; border-radius: 12px; }
.thumb-fill { width: 100%; height: 100%; border-radius: 0; }
/* A name beside its thumbnail, in a table cell or a list row. */
.with-thumb { display: flex; align-items: center; gap: 12px; min-width: 0; }
.with-thumb > .cell-two, .with-thumb > .with-thumb-text { min-width: 0; }

/* ── Provenance ──────────────────────────────────────────────────────
   Ui::origin(): the note on a record that was loaded for the client rather
   than typed in by them. Loaded data is never dressed up as entered data. */
.origin {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px 4px;
  font-size: var(--fs-2xs);
  font-weight: 500;
  color: var(--ink-muted);
}
.origin > svg { flex: 0 0 auto; }
.origin a, .tbl td .origin a { color: inherit; font-weight: inherit; text-decoration: underline; text-underline-offset: 2px; }
.origin a:hover { color: var(--link-hover); }
/* "Needs review" / "Needs details": something a person still has to confirm. */
.flag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border: 1px dashed var(--warn-line);
  border-radius: 999px;
  background: var(--warn-bg);
  color: var(--warn);
  font-size: var(--fs-2xs);
  font-weight: 600;
  white-space: nowrap;
}

/* ── Panels and key/value lists ──────────────────────────────────────
   .panel is the design system's card. These give it a head and a body so a
   record page reads as a stack of titled sections. */
.panel-flush { padding: 0; overflow: hidden; }
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.panel-head > h2, .panel-head > h3 { margin: 0; font-size: var(--fs-h3); }
.panel-head .muted { font-size: var(--fs-sm); font-weight: 400; }
.panel-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.panel-body { padding: 16px 18px; }
.panel-body > :first-child { margin-top: 0; }
.panel-body > :last-child { margin-bottom: 0; }
.panel + .panel, .panel + .table-panel, .table-panel + .panel { margin-top: 16px; }
/* Two columns of panels on a wide record page: the story on the left, the
   facts on the right. One column below 1000px. */
.record-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.record-grid > * { min-width: 0; }
.record-grid > * > .panel + .panel, .record-grid > * > .panel + .table-panel, .record-grid > * > .table-panel + .panel { margin-top: 16px; }
@media (min-width: 1000px) { .record-grid { grid-template-columns: minmax(0, 1.65fr) minmax(280px, 1fr); } }

/* The rows share one grid: a long label takes up to 40% and wraps, so it cannot crush every value. */
.kv { display: grid; grid-template-columns: fit-content(40%) minmax(0, 1fr); gap: 9px 16px; margin: 0; font-size: var(--fs-sm); }
.kv dt { min-width: 96px; color: var(--ink-muted); font-weight: 400; overflow-wrap: anywhere; }
.kv dd { margin: 0; color: var(--ink); font-weight: 500; overflow-wrap: anywhere; }
.kv dd .muted { font-weight: 400; }
.kv-src { display: block; margin-top: 1px; font-size: var(--fs-2xs); font-weight: 400; color: var(--ink-faint); }
.kv-src a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* A plain list of rows inside a panel: routines, parts, documents. */
.rows { list-style: none; margin: 0; padding: 0; }
.rows > li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--line-soft);
}
.rows > li:first-child { border-top: 0; }
.row-main { flex: 1 1 auto; min-width: 0; }
.row-title { display: block; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.row-sub { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-muted); overflow-wrap: anywhere; }
.row-side { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; text-align: right; }
@media (max-width: 560px) {
  .rows > li { flex-wrap: wrap; }
  .row-side { flex: 1 1 100%; justify-content: space-between; text-align: left; }
}

/* ── Record head ─────────────────────────────────────────────────────
   The top of an equipment or property page: photo, name, the few facts
   that matter, the main action. */
.hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  margin: 0 0 20px;
}
.hero-photo {
  width: 132px;
  height: 132px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--panel-inset);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  color: var(--ink-faint);
}
.hero-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-text { min-width: 0; }
.hero-title { margin: 0; font-size: var(--fs-display); line-height: 1.15; letter-spacing: -.02em; overflow-wrap: anywhere; }
.hero-sub { margin: 6px 0 0; font-size: var(--fs-body); color: var(--ink-muted); overflow-wrap: anywhere; }
.hero-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 10px 0 0; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; }
@media (max-width: 720px) {
  .hero { grid-template-columns: 88px minmax(0, 1fr); gap: 14px; align-items: start; }
  .hero-photo { width: 88px; height: 88px; }
  .hero-title { font-size: 22px; }
  .hero-actions { grid-column: 1 / -1; margin-top: 4px; }
  .hero-actions > .btn { flex: 1 1 auto; }
}

/* ── Equipment cards ─────────────────────────────────────────────────
   The grid view of a list: photo on top, name, where it is, when it is due. */
.acards { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 14px; }
.acard {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  overflow: hidden;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
a.acard:hover { border-color: var(--line-strong); box-shadow: var(--shadow-pop); color: var(--ink); transform: translateY(-1px); }
a.acard:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.acard-photo { position: relative; aspect-ratio: 4 / 3; background: var(--panel-inset); display: grid; place-items: center; color: var(--ink-faint); }
.acard-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.acard-badge { position: absolute; left: 10px; top: 10px; }
.acard-body { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px 14px; min-width: 0; }
.acard-name { font-weight: 650; font-size: var(--fs-h3); line-height: 1.3; overflow-wrap: anywhere; }
.acard-sub { font-size: var(--fs-sm); color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acard-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; font-size: var(--fs-xs); color: var(--ink-muted); }
/* Pills over a photo need their own backing to stay readable. */
.acard-badge .pill { background: var(--panel); box-shadow: var(--shadow-1); }
@media (max-width: 560px) {
  .acards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .acard-body { padding: 10px 11px 12px; }
  .acard-name { font-size: 14px; }
}

/* ── Property cards ──────────────────────────────────────────────── */
.pcards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.pcard {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-1);
  color: var(--ink);
  overflow: hidden;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
a.pcard:hover { border-color: var(--line-strong); box-shadow: var(--shadow-pop); color: var(--ink); transform: translateY(-1px); }
a.pcard:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.pcard-photo { position: relative; aspect-ratio: 16 / 9; background: linear-gradient(135deg, var(--rail-bg), var(--rail-bg-2)); display: grid; place-items: center; color: rgba(255, 255, 255, .38); }
.pcard-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pcard-type { position: absolute; left: 12px; bottom: 10px; display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px; background: rgba(18, 18, 23, .72); color: #fff; font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; backdrop-filter: blur(6px); }
.pcard-body { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px 16px; }
.pcard-name { font-size: var(--fs-h2); font-weight: 650; letter-spacing: -.01em; line-height: 1.25; }
.pcard-sub { font-size: var(--fs-sm); color: var(--ink-muted); }
.pcard-stats { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line-soft); font-size: var(--fs-sm); color: var(--ink-muted); }
.pcard-stats strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.pcard-stats .is-bad strong, .pcard-stats .is-bad { color: var(--danger); }
.pcard-stats .is-warn strong, .pcard-stats .is-warn { color: var(--warn); }

/* ── Schedule rows (.agenda) ─────────────────────────────────────────
   One routine per row, grouped under a period heading. The date sits in
   its own column so a week reads down the left edge. */
.agenda-group + .agenda-group { margin-top: 20px; }
.agenda-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 8px;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--ink);
}
.agenda-head .cnt { font-size: var(--fs-xs); font-weight: 500; color: var(--ink-muted); }
.agenda-head.is-overdue { color: var(--danger); }
.agenda { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--panel); box-shadow: var(--shadow-1); overflow: hidden; }
.agenda > li { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: 14px; align-items: center; padding: 11px 16px; border-top: 1px solid var(--line-soft); }
.agenda > li:first-child { border-top: 0; }
.agenda > li:hover { background: var(--bg-soft); }
.agenda-date { text-align: center; line-height: 1.1; }
.agenda-date b { display: block; font-size: 19px; font-weight: 650; font-variant-numeric: tabular-nums; color: var(--ink); }
.agenda-date span { display: block; margin-top: 2px; font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.agenda > li.is-overdue .agenda-date b { color: var(--danger); }
.agenda-main { min-width: 0; }
.agenda-title { display: block; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.agenda-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-muted); }
.agenda-sub > * { min-width: 0; overflow-wrap: anywhere; }
.agenda-sub a { color: inherit; }
.agenda-sub a:hover { color: var(--link-hover); }
.agenda-side { display: flex; align-items: center; gap: 8px; }
@media (max-width: 720px) {
  .agenda > li { grid-template-columns: 48px minmax(0, 1fr); gap: 12px; padding: 12px 12px; }
  .agenda-side { grid-column: 1 / -1; justify-content: space-between; }
  .agenda-side .btn { flex: 1 1 auto; }
}

/* ── Timeline ────────────────────────────────────────────────────────
   Service history: a rail of dots, newest at the top. */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline > li { position: relative; display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 12px; padding: 0 0 20px; }
.timeline > li:last-child { padding-bottom: 0; }
.timeline > li::before { content: ""; position: absolute; left: 13.5px; top: 30px; bottom: 2px; width: 1px; background: var(--line); }
.timeline > li:last-child::before { display: none; }
.tl-dot { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: var(--panel-inset); color: var(--ink-2); border: 1px solid var(--line); }
.tl-dot.is-inspection { background: var(--info-bg); color: var(--info); border-color: var(--info-line); }
.tl-dot.is-repair, .tl-dot.is-replacement { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.tl-dot.is-maintenance { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.tl-body { min-width: 0; padding-top: 3px; }
.tl-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; }
.tl-title { font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.tl-date { font-size: var(--fs-sm); color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tl-meta { margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-muted); overflow-wrap: anywhere; }
.tl-notes { margin: 6px 0 0; font-size: var(--fs-sm); color: var(--ink-2); white-space: pre-line; overflow-wrap: anywhere; }
.tl-files { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tl-tools { margin-left: auto; }

/* ── Photos and the uploader ─────────────────────────────────────── */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
.photo-tile { position: relative; aspect-ratio: 1; border-radius: var(--radius); overflow: hidden; background: var(--panel-inset); border: 1px solid var(--line); display: block; }
.photo-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-tile.is-main { outline: 2px solid var(--brand); outline-offset: -2px; }
.photo-tile-tools { position: absolute; right: 4px; top: 4px; display: flex; gap: 4px; }
.photo-tile-tools button, .photo-tile-tools .btn-icon { width: 26px; height: 26px; min-width: 0; padding: 0; border: 0; border-radius: 6px; background: rgba(18, 18, 23, .72); color: #fff; box-shadow: none; }
.photo-tile-tools button:hover { background: rgba(18, 18, 23, .92); color: #fff; }
.photo-tile-tools button:focus-visible { box-shadow: var(--focus-ring); }
/* A document that is not a picture: icon, name, size. */
.file-chip { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); color: var(--ink); font-size: var(--fs-sm); font-weight: 500; }
.file-chip:hover { border-color: var(--line-strong); color: var(--ink); }
.file-chip span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-chip small { color: var(--ink-muted); font-weight: 400; white-space: nowrap; }

/* The uploader: two big buttons on a phone (camera, library), a drop area
   on a desk. app.js shrinks photos before they leave the device. */
.uploader { display: flex; flex-direction: column; gap: 10px; }
.uploader-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
.uploader-buttons .btn { flex: 1 1 150px; }
.uploader input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.uploader-drop { padding: 18px; border: 1.5px dashed var(--line-strong); border-radius: var(--radius-lg); background: var(--panel-soft); color: var(--ink-muted); font-size: var(--fs-sm); text-align: center; }
.uploader.is-over .uploader-drop { border-color: var(--accent); background: var(--accent-soft); color: var(--ink); }
.uploader-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.uploader-item { position: relative; aspect-ratio: 1; border-radius: var(--radius); overflow: hidden; background: var(--panel-inset); border: 1px solid var(--line); }
.uploader-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A file that is not a picture shows its name, clear of the remove button. */
.uploader-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; margin: 30px 8px 0; font-size: var(--fs-xs); line-height: 1.3; color: var(--ink-2); overflow-wrap: anywhere; }
.uploader-item.is-busy::after { content: ""; position: absolute; inset: 0; background: rgba(255, 255, 255, .55); }
.uploader-item.is-error { outline: 2px solid var(--danger-solid); outline-offset: -2px; }
.uploader-note { margin: 0; font-size: var(--fs-xs); color: var(--ink-muted); }
@media (hover: none) { .uploader-drop { display: none; } }

/* ── AI box ──────────────────────────────────────────────────────────
   Anything the assistant proposes lands in one of these: clearly a
   suggestion, with what it could not tell at the top and its sources at the
   bottom. Nothing in it is saved until a person presses "Use this". */
.ai-box { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--panel-soft); overflow: hidden; }
.ai-box-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--line-soft); font-size: var(--fs-sm); font-weight: 650; color: var(--ink); }
.ai-box-head svg { color: var(--brand); }
.ai-box-head .spacer { flex: 1 1 auto; }
.ai-box-body { padding: 12px 14px; font-size: var(--fs-sm); }
.ai-box-body > :first-child { margin-top: 0; }
.ai-box-body > :last-child { margin-bottom: 0; }
.ai-note { margin: 0 0 10px; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--warn-bg); color: var(--warn); font-size: var(--fs-sm); }
.ai-sources { margin: 10px 0 0; padding: 0; list-style: none; font-size: var(--fs-xs); color: var(--ink-muted); }
.ai-sources li { overflow-wrap: anywhere; }
.ai-sources a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.ai-box-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--line-soft); }
.ai-busy { display: inline-flex; align-items: center; gap: 8px; color: var(--ink-muted); font-size: var(--fs-sm); }
.ai-dots { display: inline-flex; gap: 3px; }
.ai-dots i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; animation: ai-bounce 1s infinite ease-in-out; }
.ai-dots i:nth-child(2) { animation-delay: .15s; }
.ai-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes ai-bounce { 0%, 80%, 100% { opacity: .3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }
/* The button that asks for a suggestion: recognisably the assistant's. */
.btn-ai { border-color: var(--line-strong); background: var(--panel); color: var(--ink); }
.btn-ai svg { color: var(--brand); }
.btn-ai:hover { border-color: var(--accent); background: var(--panel); color: var(--ink); }
/* A field the assistant filled in and nobody has looked at yet. */
.is-ai-filled { box-shadow: 0 0 0 2px var(--brand-soft); border-color: var(--brand) !important; }

/* ── Phone tab bar ───────────────────────────────────────────────────
   Phones and tablets only (900px and below, the same point at which the
   rail becomes a drawer). Four places and Scan in the middle. */
.tabbar { display: none; }
@media (max-width: 900px) {
  .tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 45;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    align-items: end;
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    padding: 0 4px env(safe-area-inset-bottom, 0px);
    background: var(--rail-bg);
    border-top: 1px solid var(--rail-line);
  }
  .tabbar > a, .tabbar > label {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: var(--tabbar-h);
    color: var(--rail-ink-2);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .01em;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .tabbar > a.is-active, .tabbar > a[aria-current="page"] { color: #fff; }
  .tabbar > a:focus-visible, .tabbar > label:focus-visible { outline: 2px solid var(--accent-2); outline-offset: -4px; border-radius: 10px; }
  .tabbar-scan { position: relative; }
  .tabbar-scan-btn {
    display: grid;
    place-items: center;
    width: 54px;
    height: 54px;
    margin-top: -22px;
    border-radius: 50%;
    background: var(--brand);
    color: #fff;
    border: 4px solid var(--rail-bg);
    box-shadow: 0 6px 16px rgba(0, 0, 0, .35);
  }
  .tabbar-scan { color: #fff !important; }
  /* A page that takes over the screen (the scanner) hides the bar. */
  body.no-tabbar .tabbar { display: none; }
  body.no-tabbar { --tabbar-h: 0px; }
}

/* ── Ask AI ──────────────────────────────────────────────────────────
   One labeled button on every page, never a bare icon. The pill on a
   desk; a chip in the top bar on a phone, where the tab bar owns the
   bottom edge. */
.ask-pill {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 44;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 42px;
  padding: 0 16px 0 14px;
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 999px;
  background: var(--rail-bg);
  color: #fff;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 650;
  box-shadow: var(--shadow-pop);
  cursor: pointer;
  transition: transform var(--t-fast), background var(--t-fast);
}
.ask-pill svg { color: #ff5a60; }
.ask-pill:hover { background: #000; color: #fff; transform: translateY(-1px); }
.ask-pill:focus-visible { outline: none; box-shadow: var(--shadow-pop), var(--focus-ring); }
.ask-chip { display: none; }
@media (max-width: 900px) {
  .ask-pill { display: none; }
  .ask-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 32px;
    padding: 0 11px 0 9px;
    border: 1px solid var(--rail-line);
    border-radius: 999px;
    background: rgba(255, 255, 255, .06);
    color: #fff;
    font: inherit;
    font-size: 12.5px;
    font-weight: 650;
    box-shadow: none;
    cursor: pointer;
  }
  .ask-chip svg { color: #ff5a60; }
  .ask-chip:hover { background: rgba(255, 255, 255, .12); color: #fff; }
}
@media print { .ask-pill, .ask-chip { display: none !important; } }

dialog.ask-drawer .modal-title { display: flex; align-items: center; gap: 8px; }
dialog.ask-drawer .modal-title svg { color: var(--brand); }
.ask-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }
.ask-intro p { margin: 0 0 12px; color: var(--ink-muted); }
.ask-starters { display: flex; flex-direction: column; gap: 6px; }
.ask-starter {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}
.ask-starter:hover { border-color: var(--accent); background: var(--panel-soft); color: var(--ink); }
.ask-thread { display: flex; flex-direction: column; gap: 14px; }
.ask-turn { max-width: 100%; font-size: var(--fs-body); line-height: 1.55; overflow-wrap: anywhere; }
.ask-turn.is-user { align-self: flex-end; max-width: 88%; padding: 9px 13px; border-radius: 14px 14px 4px 14px; background: var(--rail-bg); color: #fff; }
.ask-turn.is-ai { align-self: stretch; }
.ask-turn.is-ai p { margin: 0 0 8px; }
.ask-turn.is-ai p:last-child { margin-bottom: 0; }
.ask-turn.is-ai ul, .ask-turn.is-ai ol { margin: 0 0 8px; padding-left: 20px; }
.ask-turn.is-ai li + li { margin-top: 3px; }
.ask-turn.is-error { padding: 9px 12px; border-radius: var(--radius); background: var(--danger-bg); color: var(--danger); font-size: var(--fs-sm); }
.ask-foot { flex: 0 0 auto; padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--line); background: var(--panel); }
.ask-form { display: flex; flex-direction: row; align-items: flex-end; gap: 8px; }
.ask-form textarea { flex: 1 1 auto; min-height: 40px; max-height: 140px; resize: none; padding: 9px 12px; }
.ask-form .btn { flex: 0 0 auto; height: 40px; }
.ask-note { margin: 8px 0 0; font-size: var(--fs-2xs); color: var(--ink-faint); }

/* ── Sticky action bar ───────────────────────────────────────────────
   A record page's main action, pinned above the tab bar on a phone so it
   is under the thumb however long the page is. Hidden on a desk, where the
   same button sits in the record head. */
.actionbar { display: none; }
@media (max-width: 720px) {
  .actionbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    z-index: 42;
    display: flex;
    gap: 8px;
    /* 22px at the bottom: the tab bar's round Scan button stands 17px proud of it. */
    padding: 10px max(12px, env(safe-area-inset-right, 0px)) 22px max(12px, env(safe-area-inset-left, 0px));
    background: var(--panel);
    border-top: 1px solid var(--line);
    box-shadow: 0 -6px 18px rgba(16, 16, 23, .08);
  }
  .actionbar > .btn { flex: 1 1 auto; height: 48px; font-size: 15px; }
  body:has(.actionbar) .main { padding-bottom: calc(140px + var(--tabbar-h) + env(safe-area-inset-bottom, 0px)); }
  .hide-phone { display: none !important; }
}
@media not all and (max-width: 720px) { .only-phone { display: none !important; } }

/* ── Small utilities ─────────────────────────────────────────────── */
.stack { display: flex; flex-direction: column; gap: 16px; }
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.grow { flex: 1 1 auto; min-width: 0; }
.text-right { text-align: right; }
.mt-0 { margin-top: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }
/* Thumb-sized controls wherever the pointer is a finger. :where() keeps the
   specificity at zero, so any component or page rule that sets its own size
   (a 26px tool on a photo, a chip in the top bar) still wins. */
@media (hover: none) and (pointer: coarse) {
  :where(.btn) { min-height: 42px; }
  :where(.btn-sm) { min-height: 38px; }
  /* Below 16px iOS zooms the page on focus. style.css has this rule at 720px and
     under; a phone on its side is wider than that, so it holds for every touch screen. */
  input:not([type]),
  input:is([type="text"], [type="email"], [type="password"], [type="tel"], [type="url"],
           [type="number"], [type="date"], [type="month"], [type="week"], [type="time"],
           [type="search"], [type="datetime-local"]),
  textarea,
  select { font-size: 16px !important; }
}

/* A form's sticky Save bar would slide under the phone tab bar and its raised
   Scan button: lift it clear of both. */
@media (max-width: 900px) {
  .form-actions.is-sticky, .form-actions[data-sticky] {
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    z-index: 41;
  }
  body:has(.form-actions.is-sticky, .form-actions[data-sticky]) .main {
    padding-bottom: calc(120px + var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  }
}

/* ── Long words ──────────────────────────────────────────────────────
   One long unbroken name must not set the width of the page. */
.page-title, .page-head .eyebrow, .empty-title { overflow-wrap: anywhere; }

/* ── Toasts on phones and tablets ────────────────────────────────────
   Above the tab bar with its raised Scan button, and above an action bar or
   Save bar, so a message never sits on the controls. --sticky-bottom already
   includes the tab bar when a Save bar is showing, hence max() and not a sum. */
@media (max-width: 900px) {
  .toasts { bottom: calc(22px + max(var(--tabbar-h) + env(safe-area-inset-bottom, 0px), var(--sticky-bottom, 0px))); }
}
@media (max-width: 720px) {
  /* The action bar is 81px tall: 48px button, 10px and 22px padding, 1px rule. */
  body:has(.actionbar) .toasts { bottom: calc(8px + 81px + max(var(--tabbar-h) + env(safe-area-inset-bottom, 0px), var(--sticky-bottom, 0px))); }
}

/* ── The phone's edges ───────────────────────────────────────────────
   The page covers the whole screen (viewport-fit=cover), and in the installed
   iPhone app it runs under the status bar (black-translucent), so the shell
   keeps its controls inside the safe area itself. Every inset is 0 where there
   is nothing to avoid. --top-h already counts the top inset (theme.css). */
.main {
  padding-left: max(var(--gutter), env(safe-area-inset-left, 0px));
  padding-right: max(var(--gutter), env(safe-area-inset-right, 0px));
}
.sidebar {
  padding-top: calc(14px + env(safe-area-inset-top, 0px));
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  padding-left: max(12px, env(safe-area-inset-left, 0px));
}
@media (max-width: 900px) {
  .mobile-topbar {
    height: var(--top-h);
    padding: env(safe-area-inset-top, 0px) max(8px, env(safe-area-inset-right, 0px)) 0 max(4px, env(safe-area-inset-left, 0px));
  }
  .tabbar {
    padding-left: max(4px, env(safe-area-inset-left, 0px));
    padding-right: max(4px, env(safe-area-inset-right, 0px));
  }
}
/* What fills the screen from its top edge starts under the status bar too. The
   band is a border in the rail's color, so the status bar's white text stays
   readable on a light sheet. */
dialog.drawer .modal-head { border-top: env(safe-area-inset-top, 0px) solid var(--rail-bg); }
body.no-nav::before { content: ""; position: fixed; inset: 0 0 auto 0; z-index: 1; height: env(safe-area-inset-top, 0px); background: var(--rail-bg); }
@media (max-width: 640px) {
  dialog.modal .modal-head { border-top: env(safe-area-inset-top, 0px) solid var(--rail-bg); }
  .palette-search { height: calc(54px + env(safe-area-inset-top, 0px)); border-top: env(safe-area-inset-top, 0px) solid var(--rail-bg); }
}

/* ── Print ───────────────────────────────────────────────────────── */
@media print {
  .tabbar, .actionbar, .banner, .filterbar, .page-actions, .hero-actions, .panel-tools { display: none !important; }
  .main { padding: 0 !important; max-width: none !important; }
}
