/* ==========================================================================
   FleetLytics — "Yard Tag" design system
   ---------------------------------------------------------------------
   An internal ops tool, not a marketing page: dense tables, forms, and
   status at a glance win over whitespace. The identity is drawn from the
   fleet yard itself — asphalt + safety amber, condensed industrial type,
   and status rendered like a tag clipped to a truck's key rather than a
   generic UI pill. Built as an override layer on top of Bootstrap 5.3
   (loaded from the CDN in base.html) — no build step, so overrides use
   Bootstrap's own CSS custom properties where possible instead of forking
   its source.
   ========================================================================== */

:root {
  /* ---- Palette --------------------------------------------------------- */
  --ink:        #1B1F23;   /* asphalt — nav, headings, primary text */
  --ink-soft:   #4B525A;   /* secondary text */
  --paper:      #EEF0F0;   /* page background — steel-tinted, not stark white */
  --panel:      #FFFFFF;   /* card / table surface */
  --line:       #DBDFE1;   /* hairline borders */
  --line-soft:  #E8EBEC;

  --amber:      #D98A0F;   /* primary accent — caution-flag amber */
  --amber-deep: #B4720A;
  --amber-tint: #FBEEDA;

  --route:      #24587F;   /* secondary accent / links — highway-sign blue */
  --route-deep: #1B4360;
  --route-tint: #E4ECF1;

  --go:         #2E7D4E;   /* active / paid / success */
  --go-tint:    #E3F1E8;
  --stop:       #B23A2E;   /* down / unpaid / danger */
  --stop-tint:  #F8E7E4;
  --idle:       #6B7280;   /* retired / neutral */
  --idle-tint:  #EAEBEC;

  /* Component identity (modular build). Used for identity only — slot
     borders, tags, meter fills — never for buttons or alerts, which stay
     amber/route/go/stop so the action language doesn't fragment. */
  --chassis:    #6B4E8C;
  --body:       #1F6F72;
  --reefer:     #24587F;   /* aliases --route deliberately */

  /* ---- Type -------------------------------------------------------------*/
  --font-display: "Big Shoulders Display", "Arial Narrow", sans-serif;
  --font-body: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --radius: 5px;
  --shadow-sm: 0 1px 2px rgba(27, 31, 35, .07);
  --shadow-md: 0 6px 20px rgba(27, 31, 35, .10);

  /* Re-point Bootstrap's own contextual variables at our palette so every
     utility class built on them (bg-success, text-danger, border-warning,
     .badge bg-*, etc.) re-themes without touching template markup. */
  --bs-success-rgb: 46, 125, 78;
  --bs-danger-rgb: 178, 58, 46;
  --bs-warning-rgb: 217, 138, 15;
  --bs-secondary-rgb: 107, 114, 128;
  --bs-dark-rgb: 27, 31, 35;
  --bs-primary-rgb: 36, 88, 127;
  --bs-body-font-family: var(--font-body);
  --bs-link-color: var(--route);
  --bs-link-hover-color: var(--route-deep);
  --bs-border-color: var(--line);
}

/* ---- Base ---------------------------------------------------------------*/

body {
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: .95rem;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--ink);
}

.h3 { font-size: 1.7rem; text-transform: uppercase; letter-spacing: .03em; }

a { color: var(--route); text-decoration-color: rgba(36, 88, 127, .35); }
a:hover, a:focus { color: var(--route-deep); }

code {
  font-family: var(--font-mono);
  color: var(--route-deep);
  background: var(--route-tint);
  padding: .1em .35em;
  border-radius: 3px;
  font-size: .85em;
}

::selection { background: var(--amber-tint); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* Modular build wizard --------------------------------------------------- */
.build-steps ol {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: .5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.build-steps a,
.build-steps li > span,
.build-steps button {
  align-items: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink-soft);
  display: flex;
  font-size: .78rem;
  font-weight: 700;
  gap: .45rem;
  min-height: 42px;
  padding: .45rem .55rem;
  text-decoration: none;
  width: 100%;
}
.build-steps button { cursor: not-allowed; opacity: .55; }
.build-steps .n {
  align-items: center;
  background: var(--idle-tint);
  border-radius: 50%;
  display: inline-flex;
  flex: 0 0 24px;
  height: 24px;
  justify-content: center;
}
.build-steps .is-complete .n { background: var(--go); color: #fff; }
.build-steps .is-current > span { background: var(--ink); color: #fff; }
.build-steps .is-current .n { background: var(--amber); color: var(--ink); }

.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: .75rem;
}
.pick-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  min-height: 138px;
  padding: .85rem .9rem;
  position: relative;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.pick-card:hover { border-color: var(--amber); box-shadow: 0 2px 8px rgba(27,31,35,.08); }
.pick-card:has(input:focus-visible) { outline: 2px solid var(--amber); outline-offset: 2px; }
.pick-card:has(input:checked) {
  border: 2px solid var(--amber);
  box-shadow: 0 0 0 3px rgba(217,138,15,.15);
  padding: calc(.85rem - 1px) calc(.9rem - 1px);
}
.pick-card.pick-chassis { border-top: 4px solid var(--chassis); }
.pick-card.pick-body { border-top: 4px solid var(--body); }
.pick-card.pick-reefer { border-top: 4px solid var(--reefer); }
.pick-card .truck-no { font-size: 1.45rem; }
.pick-card .c-serial { font-size: 1rem; }
.pick-meta { color: var(--ink-soft); font-size: .78rem; }
.pick-radio {
  border: 2px solid var(--idle);
  border-radius: 50%;
  height: 17px;
  position: absolute;
  right: .8rem;
  top: .8rem;
  width: 17px;
}
.pick-card:has(input:checked) .pick-radio { border: 5px solid var(--amber); }
.registration-panel { display: none; }
.registration-panel.is-open { display: block; }
.c-serial-input { border: 2px solid var(--amber); font-family: var(--font-mono); }
.truck-no-input { font-family: var(--font-display); font-size: 1.15rem; font-weight: 700; }
.build-summary dt { color: var(--ink-soft); font-size: .75rem; text-transform: uppercase; }
.component-disposition {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .9rem;
}
.component-disposition legend { float: none; margin-bottom: .75rem; width: auto; }

@media (max-width: 991px) {
  .build-steps ol { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 575.98px) {
  .build-steps ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pick-grid { grid-template-columns: 1fr; }
}

/* ---- Navbar ---------------------------------------------------------- */

.navbar {
  background: var(--ink) !important;
  border-bottom: 3px solid var(--amber);
  padding-top: .55rem;
  padding-bottom: .55rem;
  box-shadow: var(--shadow-sm);
  position: relative;
  z-index: 1030;
}

.navbar-brand {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.55rem;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: #fff !important;
  display: flex;
  align-items: center;
  gap: .4rem;
}

.navbar .nav-link {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: rgba(255, 255, 255, .68) !important;
  padding: .55rem .85rem !important;
  border-bottom: 2px solid transparent;
  transition: color .15s ease, border-color .15s ease;
}

.navbar .nav-link:hover,
.navbar .nav-link:focus-visible {
  color: #fff !important;
  border-bottom-color: rgba(217, 138, 15, .55);
}

.navbar .nav-link.is-active {
  color: #fff !important;
  border-bottom-color: var(--amber);
}

.navbar-toggler {
  border-color: rgba(255, 255, 255, .25);
}
.navbar-toggler:focus { box-shadow: 0 0 0 .2rem rgba(217, 138, 15, .4); }

.navbar-brand-word { line-height: 1; }

/* ---- Footer ------------------------------------------------------------ */

.app-footer {
  margin-top: 2.5rem;
  padding: .9rem 0 1.6rem;
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
}

.app-footer-sep {
  color: var(--line);
  margin: 0 .3em;
}

/* ---- Buttons ----------------------------------------------------------- */

.btn {
  font-weight: 600;
  font-size: .85rem;
  border-radius: var(--radius);
  letter-spacing: .01em;
}

.btn-primary {
  --bs-btn-color: var(--ink);
  --bs-btn-bg: var(--amber);
  --bs-btn-border-color: var(--amber);
  --bs-btn-hover-color: var(--ink);
  --bs-btn-hover-bg: #C67F0C;
  --bs-btn-hover-border-color: #C67F0C;
  --bs-btn-active-color: var(--ink);
  --bs-btn-active-bg: var(--amber-deep);
  --bs-btn-active-border-color: var(--amber-deep);
  --bs-btn-focus-shadow-rgb: 217, 138, 15;
  --bs-btn-disabled-bg: var(--amber);
  --bs-btn-disabled-border-color: var(--amber);
}

.btn-success {
  --bs-btn-bg: var(--go);
  --bs-btn-border-color: var(--go);
  --bs-btn-hover-bg: #256841;
  --bs-btn-hover-border-color: #256841;
  --bs-btn-active-bg: #235c3a;
  --bs-btn-active-border-color: #235c3a;
  --bs-btn-focus-shadow-rgb: 46, 125, 78;
}

.btn-outline-primary {
  --bs-btn-color: var(--route);
  --bs-btn-border-color: var(--route);
  --bs-btn-hover-bg: var(--route);
  --bs-btn-hover-border-color: var(--route);
  --bs-btn-active-bg: var(--route-deep);
  --bs-btn-active-border-color: var(--route-deep);
  --bs-btn-focus-shadow-rgb: 36, 88, 127;
}

.btn-outline-secondary {
  --bs-btn-color: var(--ink-soft);
  --bs-btn-border-color: var(--line);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--ink-soft);
  --bs-btn-hover-border-color: var(--ink-soft);
  --bs-btn-active-bg: var(--ink);
  --bs-btn-active-border-color: var(--ink);
  --bs-btn-focus-shadow-rgb: 75, 82, 90;
}

.btn-outline-danger {
  --bs-btn-color: var(--stop);
  --bs-btn-border-color: var(--stop);
  --bs-btn-hover-bg: var(--stop);
  --bs-btn-hover-border-color: var(--stop);
  --bs-btn-active-bg: #8f2f25;
  --bs-btn-active-border-color: #8f2f25;
  --bs-btn-focus-shadow-rgb: 178, 58, 46;
}

.btn-link {
  --bs-btn-color: var(--route);
  --bs-btn-hover-color: var(--route-deep);
  font-weight: 600;
  text-decoration: none;
}
.btn-link:hover { text-decoration: underline; }
.btn-link.text-danger { --bs-btn-color: var(--stop); --bs-btn-hover-color: #8f2f25; }
.btn-link.text-secondary { --bs-btn-color: var(--ink-soft); --bs-btn-hover-color: var(--ink); }

/* ---- Badges & status tags ------------------------------------------------
   The signature element: status reads like a tag clipped to a key, not a
   generic pill — rectangular, uppercase, tight tracking, printed-edge feel.
   Color comes from the root --bs-*-rgb overrides above; this block only
   changes shape/type. */

.badge {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  border-radius: 3px;
  padding: .32em .55em;
  vertical-align: middle;
}

.status-badge-active,
.status-badge-down,
.status-badge-retired {
  border-radius: 3px;
  padding: .32em .6em;
  font-weight: 700;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  border: 1px solid rgba(0, 0, 0, .12);
}

.status-badge-active  { background-color: var(--go);   color: #fff; }
.status-badge-down    { background-color: var(--stop); color: #fff; }
.status-badge-retired { background-color: var(--idle); color: #fff; }

/* Preventative maintenance, in the same tag language as vehicle status.
   "unknown" gets the amber caution treatment rather than a neutral grey: a
   schedule nobody has configured is a gap to close, not a quiet default, and
   greying it out is how it stays unnoticed. */
.pm-badge-ok,
.pm-badge-due_soon,
.pm-badge-overdue,
.pm-badge-unknown,
.pm-badge-not_applicable {
  border-radius: 3px;
  padding: .32em .6em;
  font-weight: 700;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  border: 1px solid rgba(0, 0, 0, .12);
}

.pm-badge-ok             { background-color: var(--go);        color: #fff; }
.pm-badge-due_soon       { background-color: var(--amber);     color: var(--ink); }
.pm-badge-overdue        { background-color: var(--stop);      color: #fff; }
.pm-badge-unknown        { background-color: var(--amber-tint); color: var(--amber-deep); }
.pm-badge-not_applicable { background-color: var(--idle-tint); color: var(--ink-soft); }

/* Warranty, in the same tag language again, but deliberately quieter: it is
   an outline rather than a fill, and a size down. Most of these read "active"
   most of the time, and a wall of solid green competing with the PM badges
   would train people to stop seeing all of them. The colour still carries the
   state, and the text beside it always says the same thing in words -- nobody
   should have to distinguish this green from this amber to know where they
   stand. "unknown" stays amber for the reason given above. */
.warranty-badge-active,
.warranty-badge-expiring_soon,
.warranty-badge-expired,
.warranty-badge-unknown,
.warranty-badge-not_applicable {
  border-radius: 3px;
  padding: .2em .45em;
  font-weight: 600;
  font-size: .62rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  background-color: transparent;
  border: 1px solid currentColor;
}

.warranty-badge-active         { color: var(--go); }
.warranty-badge-expiring_soon  { color: var(--amber-deep); }
.warranty-badge-expired        { color: var(--stop); }
.warranty-badge-unknown        { color: var(--amber-deep); background-color: var(--amber-tint); }
.warranty-badge-not_applicable { color: var(--ink-soft); border-color: var(--line); }

/* ---- Cards --------------------------------------------------------------*/

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

.card-header {
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-soft);
  padding: .6rem .9rem;
}

.card-stat .display-6 {
  font-family: var(--font-display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.alert {
  border-radius: var(--radius);
  border-width: 1px;
  font-size: .9rem;
}
.alert-info {
  background: var(--route-tint);
  border-color: rgba(36, 88, 127, .25);
  color: var(--route-deep);
}
.alert-danger { background: var(--stop-tint); border-color: rgba(178, 58, 46, .3); color: #7a281f; }
.alert-success { background: var(--go-tint); border-color: rgba(46, 125, 78, .3); color: #1f5735; }
.alert-warning { background: var(--amber-tint); border-color: rgba(217, 138, 15, .3); color: #7a5308; }

/* ---- Tables ---------------------------------------------------------- */

.table-responsive {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  isolation: isolate;
}

/* The vehicle overview is wider than a phone viewport. Make horizontal
   touch panning explicit and keep iOS momentum scrolling enabled. */
.vehicle-table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y;
}

.vehicle-table-scroll:focus-visible {
  outline: 3px solid rgba(217, 138, 15, .4);
  outline-offset: 2px;
}

.table {
  margin-bottom: 0;
  font-variant-numeric: tabular-nums;
  --bs-table-hover-bg: var(--amber-tint);
}

.table > thead {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-soft);
}

.table > thead > tr > th {
  border-bottom: 2px solid var(--line);
  background: var(--paper);
}

.table > :not(caption) > * > * {
  border-bottom-color: var(--line-soft);
}

/* .table-responsive's overflow-x:auto only ever kicks in if a cell is
   willing to overflow instead of wrapping -- by default a free-text column
   (invoice/rental "description"/"reason", a document's "notes", etc.) wraps
   onto as many lines as it takes to fit the available width instead,
   producing rows that grow to match a single long entry (confirmed: a
   364-character description produced a 906px-tall row) rather than the
   table just scrolling sideways like the rest of its columns already do.
   Every table in the app sits inside a .table-responsive wrapper, so this
   is safe to apply generally rather than patching each affected column. */
.table-responsive .table > :not(caption) > * > * {
  white-space: nowrap;
}

/* nowrap above stops a long free-text cell from inflating its own row, but
   left uncapped that column's width is still shared across every row in the
   table (plain HTML table layout), so one long description drags an
   otherwise-short-entry table thousands of pixels wide -- confirmed: a
   single 364-character description forced a 2635px-wide column onto every
   row. Applied to whichever cell holds a free-text field (description,
   reason, notes); the full value remains one tap away since these rows
   already link through to the edit/detail page. */
.cell-truncate {
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Full-row-click table rows. The real link in the identifying cell remains
   keyboard and screen-reader accessible; base.html delegates pointer clicks
   from the rest of the row to it. Bootstrap's stretched-link pseudo-element
   is deliberately disabled here because absolutely positioned descendants
   of <tr> are not contained reliably by mobile browsers after scrolling and
   can otherwise sit invisibly over the navbar. */
.table tbody tr.row-link {
  cursor: pointer;
}

.table tbody tr.row-link .stretched-link::after {
  content: none;
}

.table tbody tr.row-link:hover {
  box-shadow: inset 3px 0 0 var(--amber);
}

/* Any other link/button/form in a row-link row (e.g. "+ Invoice", "View",
   "Excluded" toggle) needs to sit above the stretched-link's full-row
   overlay to stay independently clickable -- the standard caveat from
   Bootstrap's own stretched-link docs, applied to table rows instead of
   cards. */
.table tbody tr.row-link a:not(.stretched-link),
.table tbody tr.row-link button,
.table tbody tr.row-link form {
  position: relative;
  z-index: 2;
}

.table tfoot td,
.table .table-light {
  background: var(--paper) !important;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: .02em;
}

.expiring-soon {
  color: var(--stop);
  font-weight: 700;
}

/* ---- Invoice attachment preview ----------------------------------------
   PDF via the browser's own built-in viewer in an iframe; images directly.
   Both load through download_upload(view=True) -- see that route and
   auth.py's download_url() for why an inline (not forced-download)
   response is a separate opt-in rather than the default. */

/* The preview grows to whatever height the details card beside it sets, so
   the two columns end level instead of the viewer picking its own size. The
   min-height keeps a page of the PDF legible when the details card is short. */
.invoice-attachment-body {
  display: flex;
  flex-direction: column;
}

.invoice-attachment-preview {
  width: 100%;
  flex: 1 1 auto;
  min-height: 420px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.invoice-attachment-frame {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  justify-content: center;
}

.invoice-attachment-preview-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Create/edit uses the available browser window as a workbench: fields stay
   at a comfortable reading width while the paper remains visible beside
   them. The split editor then gets the full card width below instead of
   inheriting the PDF column's scale. */
.invoice-form {
  width: 100%;
  max-width: 1600px;
}

.invoice-form-workspace {
  display: grid;
  grid-template-columns: minmax(560px, 1fr) minmax(420px, .82fr);
  gap: 1.25rem;
  align-items: start;
}

/* A grid item defaults to min-width:auto, so it refuses to shrink below its
   widest content. The split editor puts #lineTable (min-width 1080px) in this
   column, which without this blows the column past its track and scrolls the
   whole page sideways -- taking the sticky viewer off-screen -- instead of
   scrolling inside the table's own .table-responsive wrapper. */
.invoice-form-fields {
  min-width: 0;
}

.invoice-form-preview-column {
  min-width: 0;
  position: sticky;
  top: 1rem;
}

.invoice-form-preview-column .invoice-attachment-preview {
  height: clamp(520px, calc(100vh - 230px), 800px);
  min-height: 0;
  flex: 0 0 auto;
}

/* The seven sized columns below come to 1052px. min-width has to clear that
   by enough to leave the one auto column -- Description, the widest thing
   anybody types -- something readable, because table-layout:fixed takes its
   widths from the colgroup and ignores the min-width on the <th>. At 1080px
   Description got the 28px that happened to be left over and rendered as a
   sliver with its header spilling across Vehicle's, which is what it looked
   like on a phone. 1340 = 1052 + 18rem for Description. */
#lineTable {
  min-width: 1340px;
  table-layout: fixed;
}

#lineTable .line-col-reference { width: 10rem; }
#lineTable .line-col-description { width: auto; }
#lineTable .line-col-vehicle { width: 12rem; }
#lineTable .line-col-shared { width: 5.5rem; }
#lineTable .line-col-owner { width: 12rem; }
#lineTable .line-col-work { width: 14rem; }
#lineTable .line-col-amount { width: 9rem; }
#lineTable .line-col-actions { width: 3.25rem; }

@media (max-width: 1199.98px) {
  .invoice-form-workspace {
    grid-template-columns: minmax(0, 1fr);
  }

  .invoice-form-preview-column {
    position: static;
  }

  .invoice-form-preview-column .invoice-attachment-preview {
    height: min(65vh, 680px);
  }
}

/* Stacked on phones there is no neighbour to match, so cap the height instead
   of letting a tall PDF push the rest of the page off-screen. */
@media (max-width: 767.98px) {
  .invoice-attachment-preview {
    height: 60vh;
    min-height: 0;
    flex: 0 0 auto;
  }

  .invoice-attachment-preview-img {
    max-height: 60vh;
  }
}

/* ---- Forms ------------------------------------------------------------- */

.form-label {
  font-weight: 600;
  font-size: .82rem;
  color: var(--ink-soft);
}

.form-control, .form-select {
  border-color: var(--line);
  border-radius: var(--radius);
  font-size: .9rem;
}

.form-control:focus, .form-select:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 .2rem rgba(217, 138, 15, .22);
}

.form-check-input:checked {
  background-color: var(--route);
  border-color: var(--route);
}
.form-check-input:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 .2rem rgba(217, 138, 15, .22);
}

.form-text { color: var(--ink-soft); font-size: .8rem; }

form.card {
  box-shadow: var(--shadow-md);
}

/* ---- Modals -------------------------------------------------------------*/

.modal-content {
  border-radius: var(--radius);
  border: 1px solid var(--line);
}

.modal-header {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.modal-title {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .02em;
  font-size: 1.15rem;
}

/* ---- Dashboard ----------------------------------------------------------*/

.dashboard-page .navbar {
  margin-bottom: .75rem !important;
}

.dashboard-summary {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: .75rem;
}

.dashboard-summary .card {
  border-left: 4px solid var(--line);
  overflow: hidden;
}

.dashboard-summary-item:nth-child(1) .card { border-left-color: var(--go); }
.dashboard-summary-item:nth-child(2) .card { border-left-color: var(--stop); }
.dashboard-summary-item:nth-child(3) .card { border-left-color: var(--amber); }
.dashboard-summary-item:nth-child(4) .card,
.dashboard-summary-item:nth-child(5) .card,
.dashboard-summary-item:nth-child(6) .card { border-left-color: var(--route); }

.dashboard-summary .card-body {
  padding: .65rem .5rem;
}

.dashboard-summary .display-6 {
  font-size: 1.5rem;
  line-height: 1.1;
  font-family: var(--font-display);
}

/* Tile label ("Active", "Spend This Month") — always paired with .small in
   the markup. The longer explanatory captions some tiles carry underneath
   (e.g. "Includes uninvoiced rental base minimums") reuse .text-muted
   without .small, so they're deliberately excluded from the tracked-caps
   treatment below or they'd wrap into dense, hard-to-scan blocks. */
.dashboard-summary .text-muted.small {
  line-height: 1.15;
  font-weight: 600;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .05em;
}

.dashboard-summary .card-body > .text-muted:not(.small) {
  font-weight: 400;
  line-height: 1.3;
  text-transform: none;
  letter-spacing: normal;
  margin-top: .15rem;
}

.dashboard-summary-link {
  color: inherit;
  display: block;
  height: 100%;
  text-decoration: none;
}

.dashboard-summary-link .card {
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.dashboard-summary-link:hover .card,
.dashboard-summary-link:focus-visible .card {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.dashboard-panel .card-header {
  padding: .5rem .8rem;
}

.dashboard-panel .table > :not(caption) > * > * {
  padding: .3rem .55rem;
}

.dashboard-panel .table {
  font-size: .85rem;
}

/* Fill the viewport, no page scroll: desktop only (min-width matches the
   col-lg-6 breakpoint the two panel rows already use). Below that, the six
   stat tiles alone can run tall enough that cramming both panel rows into
   whatever's left would leave them unusably short, so mobile/tablet keeps
   the normal stacked, page-scrolls layout instead. The two table panels
   split the remaining height evenly and scroll internally
   (.dashboard-panel .table-responsive) if their own content overflows,
   rather than growing the page. */
@media (min-width: 992px) {
  body.dashboard-page {
    height: 100vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  body.dashboard-page .navbar {
    flex: 0 0 auto;
  }

  body.dashboard-page > .container-fluid.px-4 {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: .5rem;
    overflow: hidden;
  }

  body.dashboard-page .dashboard-summary {
    flex: 0 0 auto;
  }

  /* Bootstrap's .row (flex + flex-wrap:wrap) doesn't reliably stretch a
     column to the row's own height once the column's content wants to be
     taller than that — confirmed directly (col height tracked its content,
     not the row, until content was pinned to an explicit pixel height).
     Grid's stretch doesn't have that failure mode, so the two panels use
     grid here instead of Bootstrap's column classes; gutter margin/padding
     that .row/.col-lg-6 still apply are zeroed in favor of grid's own gap
     so they don't stack on top of it. */
  body.dashboard-page .dashboard-panels {
    display: grid;
    /* auto-fit rather than a fixed two: the number of panels is not fixed and
       neither is the window. A hard `repeat(2, ...)` means an odd panel takes
       one cell and leaves the other visibly empty -- which is exactly what a
       third panel did to this row, reading as a gap under its neighbour rather
       than as a panel that had wrapped. Fitting as many ~26rem tracks as the
       width allows lets a row hold three on a wide screen, two on a laptop and
       one on a phone, and a lone panel stretch the full width, with no
       breakpoint to keep in step with the panel count.
       min(100%, 26rem) keeps the track from overflowing a viewport narrower
       than the minimum itself. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
    gap: 1rem;
    margin-left: 0;
    margin-right: 0;
    flex: 1 1 0;
    min-height: 0;
  }

  /* The column is also made a flex container (rather than relying on the
     card's .h-100 percentage height) for the same reason: a percentage
     height on the card doesn't reliably resolve against a grid-stretched
     column once the card's own content is tall, so the card fills it via
     flex:1 below instead. */
  body.dashboard-page .dashboard-panels > [class*="col-"] {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding-left: 0;
    padding-right: 0;
    /* col-lg-6's own width:50%/max-width:50% (left over from Bootstrap's
       percentage-width flex column system) still apply here regardless of
       the parent's display — left in place, they re-halve the already-
       50%-wide grid track (50% of a 688px track = 344px, with a dead gap
       where the other 344px should be). */
    width: auto;
    max-width: none;
  }

  body.dashboard-page .dashboard-panel {
    flex: 1 1 auto;
    min-height: 0;
  }

  body.dashboard-page .dashboard-panel .table-responsive {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
  }

  body.dashboard-page .dashboard-panel thead th {
    position: sticky;
    top: 0;
    background: var(--panel);
    box-shadow: inset 0 -1px 0 var(--line);
    z-index: 1;
  }

  body.dashboard-page .app-footer {
    flex: 0 0 auto;
    margin-top: 0;
    padding: .35rem 0 .5rem;
  }
}

@media (max-width: 991.98px) {
  .dashboard-summary {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 575.98px) {
  .dashboard-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .navbar-brand { font-size: 1.3rem; }

  /* Stat figures (cost/mile, total cost, etc.) default to Bootstrap's 2.5rem
     display-6 — fine in a full-width column, but it breaks mid-number
     ("$11,690.02") once two stats share a col-6 row on a phone. Shrink and
     keep each figure on one line instead. */
  .display-6 {
    font-size: 1.65rem;
    white-space: nowrap;
  }

  /* Vehicle detail's header (unit number + status tag on the left, Transfer
     Branch/Edit/Delete on the right) is the only place in the app pairing a
     heading with a same-row action-button cluster, so it's safe to target
     structurally here without a template change. Let the buttons drop to
     their own full-width block instead of squeezing the title.

     First pass tried 3 equal-width flex columns, which was wrong on two
     counts: "Transfer Branch" still didn't fit in a ~114px column and wrapped
     to 2 lines, and align-items:stretch then inflated the row height to match
     that wrapped button -- but only Delete's <form> wrapper (a flex sibling)
     stretched with it, not the <button> inside the form, leaving Delete
     visibly shorter than its neighbors. Stacking full-width instead removes
     the wrap outright (each label gets the full row width) and every button
     ends up the same natural single-line height with nothing to stretch. */
  .d-flex.justify-content-between.align-items-start {
    flex-wrap: wrap;
    row-gap: .6rem;
  }
  .d-flex.justify-content-between.align-items-start > .d-flex.gap-2 {
    width: 100%;
    flex-direction: column;
  }
  .d-flex.justify-content-between.align-items-start > .d-flex.gap-2 > .btn,
  .d-flex.justify-content-between.align-items-start > .d-flex.gap-2 > form > .btn {
    width: 100%;
  }
}

/* ---- Login page -----------------------------------------------------
   The one page in the app with no nav (base.html hides the nav links and
   toggler entirely when there's no current_user) -- so this owns its own
   centered layout rather than sitting inside the usual page container. */

.login-page .container-fluid {
  padding-top: 0;
  padding-bottom: 0;
}

.login-wrap {
  display: flex;
  justify-content: center;
  padding: 3.5rem 1rem;
}

.login-card {
  width: 100%;
  max-width: 380px;
  box-shadow: var(--shadow-md);
}

.login-card .card-body {
  padding: 2rem 1.75rem;
}

.login-mark {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin-bottom: 1.5rem;
}

.login-wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.4rem;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--ink);
}

/* ---- Account / self-service page -------------------------------------*/

.account-role-tag {
  display: inline-block;
  margin-bottom: 1rem;
}

/* ---- Frontline field UI (iPad-first PWA phase 2) ------------------------
   Touch-target and layout rules specific to templates/frontline/*.html.
   Not applied globally -- the rest of the app stays desktop/admin-oriented
   by design; this is where the phase-2 roadmap's "optimize touch targets,
   numeric keyboards, portrait/landscape" item actually lives. 2.75rem
   (~44px) matches Apple's minimum recommended touch target. */

.frontline-search {
  font-size: 1.05rem;
  padding: .65rem .9rem;
  min-height: 2.75rem;
}

.frontline-unit-list {
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.frontline-unit-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .85rem 1rem;
  min-height: 3.25rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}
.frontline-unit-row:hover,
.frontline-unit-row:focus-visible {
  border-color: var(--amber);
  background: var(--amber-tint);
}
.frontline-unit-row .unit-number {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--ink);
}

.frontline-action-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}

.frontline-action-card .card-body {
  padding: 1.1rem;
}

.frontline-action-card .btn,
.frontline-action-card .form-control,
.frontline-action-card .form-select {
  min-height: 2.75rem;
}

.frontline-action-card textarea.form-control {
  min-height: 4.5rem;
}

@media (max-width: 575.98px) {
  .frontline-action-grid { grid-template-columns: 1fr; }
}

/* ---- Line-note: what a proposed line covers ------------------------------
   A charge can cover two trucks, and the reader has to decide how it divides.
   The note is a strip under its own line, held in the margin by a rule, so it
   reads as an annotation on the line above rather than a billed line of its
   own.

   Route blue, not amber. Amber is this system's caution colour and is spent
   on PM due-soon and failed states; an unassigned multi-truck line is neither
   wrong nor urgent, it is a signpost saying which way to go next -- which is
   what highway-sign blue is for here. The one genuine caution, a unit this
   fleet does not have, is the only thing that takes the stop colour. */

/* Selector deliberately mirrors the nowrap rule's own shape above. That rule
   is `.table-responsive .table > :not(caption) > * > *`, which outranks a
   plain `.line-note-row > td` -- matching its wrapper classes is what lets
   this one win. */
.table-responsive .table > tbody > tr.line-note-row > td {
  border-top: 0;
  padding-top: 0;
  padding-bottom: .55rem;
  background: transparent;
  /* Data cells in this table are nowrap on purpose -- a figure or a unit
     should not fold in half, the table scrolls instead. This cell is a
     sentence, and the rule has to be lifted or it runs off the side of the
     scroll container and is never read. */
  white-space: normal;
}

/* inline-flex, not flex: the cell spans all eight columns so the note sits
   under its whole line, but the tinted strip should hug the few words it
   holds. Stretched to the full table width it becomes a wide empty field of
   colour that outweighs the line it is annotating. */
.line-note {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .3rem .5rem;
  margin: 0;
  padding: .4rem .6rem;
  border-left: 3px solid var(--route);
  background: var(--route-tint);
  border-radius: 0 var(--radius) var(--radius) 0;
  /* The cell spans a table that is wider than its scroll container, so the
     note has to be capped against the viewport rather than its parent -- a
     percentage here resolves against the table and overflows exactly as if
     there were no cap. A long basis is otherwise cut off mid-sentence at the
     container edge, which is the half that says to check the figure. */
  max-width: calc(100vw - 5rem);
}

.line-note-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--route-deep);
}

.line-note-text {
  font-size: .8rem;
  color: var(--ink-soft);
  /* A flex item will not shrink below its own min-content width unless told
     to, so without this the sentence keeps its full single-line width and
     runs out past the capped strip on a phone instead of wrapping inside it. */
  min-width: 0;
}

/* A unit number is how everyone here names a truck, so it is set as a tag
   clipped to a key -- the same idiom as the status badges -- and in the mono
   face, because it is an identifier and not a word. Two tags side by side say
   "one charge, two trucks" faster than the sentence beside them. */
.unit-tag {
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 600;
  line-height: 1.2;
  padding: .16em .4em;
  border-radius: 3px;
  background: var(--panel);
  color: var(--route-deep);
  border: 1px solid rgba(36, 88, 127, .35);
}

.unit-tag-missing {
  color: var(--stop);
  border-color: rgba(178, 58, 46, .45);
  border-style: dashed;
  background: var(--stop-tint);
}

/* On a phone the split table scrolls sideways inside its own container, and
   an unwrapped note is wider than the screen -- putting the sentence that
   explains the row off to the right, where somebody scanning the invoice will
   never see it. Cap it so it wraps and stays where reading starts. */
@media (max-width: 575.98px) {
  .line-note { max-width: 16rem; }
}

/* An estimated split is a figure this app worked out, not one printed on the
   invoice, and it must not read like the rest of the row. Amber, because it
   is the system's "check this" colour -- the same one PM due-soon uses -- and
   because a proposed number that looks settled is the one nobody verifies. */
.line-note-estimated {
  border-left-color: var(--amber);
  background: var(--amber-tint);
}

.line-note-estimated .line-note-label {
  color: var(--amber-deep);
}

/* ---- Component register (modular build) ---------------------------------
   Component statuses get their own tag vocabulary, deliberately not reusing
   the truck words (active/down/retired). A part is "in yard" or "in shop",
   never "down" — sharing the words makes the pool read as a second fleet
   list. Same rectangular printed-edge shape as .status-badge-* so they still
   look like one system. */

.t-inservice,
.t-yard,
.t-shop,
.t-scrapped,
.t-unrecorded {
  display: inline-block;
  border-radius: 3px;
  padding: .32em .6em;
  font-weight: 700;
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  border: 1px solid rgba(0, 0, 0, .12);
}

.t-inservice { background-color: var(--go-tint);    color: #1f5735; }
.t-yard      { background-color: var(--amber-tint); color: #7a5308; }
.t-shop      { background-color: var(--stop-tint);  color: #7a281f; }
.t-scrapped  { background-color: var(--idle-tint);  color: var(--ink-soft); }

/* A backfilled placeholder serial. Amber rather than grey on purpose: an
   unrecorded serial is a gap somebody should close on their next walk of the
   yard, and greying it out is how it stays unnoticed for a year. */
.t-unrecorded {
  background-color: var(--amber-tint);
  color: var(--amber-deep);
  margin-left: .4rem;
}

/* Serials are always mono, truck numbers always the display face. A reader
   should be able to tell which kind of identifier they're looking at without
   reading the column header. */
.c-serial {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 1rem;
  display: inline-block;
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.truck-no {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: .01em;
}

/* Service-life meter. Rendered only when there is something to measure —
   see the life_meter macro, which emits nothing rather than an empty track,
   because a 0%-wide bar reads as "nothing used yet" instead of "unknown". */
.life-meter { min-width: 150px; }

.life-track {
  height: 6px;
  border-radius: 3px;
  background: var(--line-soft);
  overflow: hidden;
}

.life-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--idle);
}

.life-fill.life-body   { background: var(--body); }
.life-fill.life-reefer { background: var(--reefer); }

/* At 70% of expected life the fill turns red — but the caption underneath
   always states the figures too, so the warning never rides on colour alone. */
.life-fill.is-spent { background: var(--stop); }

.life-caption {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  margin-top: .25rem;
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-soft);
}

.life-caption-alone { margin-top: 0; }

/* Serial entry is the one field on these forms that must be typed exactly,
   so it gets the amber emphasis and the mono face it will be displayed in. */
.serial-input {
  font-family: var(--font-mono);
  font-size: 1rem;
  border: 2px solid var(--amber);
}

.serial-input:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(217, 138, 15, .15);
}

/* ---- Assembly card (truck detail) ---------------------------------------
   Three slots, fixed order, always all three even when one is empty — an
   empty slot is information, and a card that quietly renders two reads as a
   complete build. */

/* "Make default" sits with Remove under each frame: both are decisions about
   this one photo, made where the photo is. The current default says so as
   plain text rather than a button, because it is a state, not an action. */
.photo-default { display: inline; }
.photo-default.is-default {
  color: var(--amber-deep);
  font-family: var(--font-display);
  font-size: .72rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* The build: one photo of the assembled truck beside the parts it is made of.
   Paired rather than stacked because they are the same statement -- this is the
   truck, and this is what is under it. The photo is a link to the rest of the
   photography, which used to occupy roughly a third of this page as three
   galleries with an upload form each.

   auto-fit keeps the pairing honest at any width: below about 46rem there is no
   room for a photo beside three slots, so it drops above them rather than
   squeezing both. */
.build {
  display: grid;
  grid-template-columns: minmax(min(100%, 15rem), 18rem) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

.build-photo {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--idle-tint);
  text-decoration: none;
}

.build-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.build-photo-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--ink-soft);
  font-size: .8rem;
}

/* Sits on the image rather than under it: the count is a way in to the rest of
   the photos, not a caption, and a line of text below would push the slots out
   of alignment with the frame. */
.build-photo-count {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: .3rem .55rem;
  background: linear-gradient(to top, rgba(27, 31, 35, .82), rgba(27, 31, 35, 0));
  color: #fff;
  font-family: var(--font-display);
  font-size: .78rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.build-photo:hover { border-color: var(--amber); }
.build-photo:hover .build-photo-count { background: linear-gradient(to top, rgba(217, 138, 15, .9), rgba(217, 138, 15, 0)); color: var(--ink); }

@media (max-width: 735px) {
  .build { grid-template-columns: 1fr; }
  .build-photo { aspect-ratio: 16 / 9; }
}

.assembly {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.assembly .slot {
  padding: .85rem .9rem;
  border-top: 4px solid var(--line);
  border-right: 1px solid var(--line-soft);
  background: var(--panel);
}

.assembly .slot:last-child { border-right: none; }

.slot-chassis { border-top-color: var(--chassis); }
.slot-body    { border-top-color: var(--body); }
.slot-reefer  { border-top-color: var(--reefer); }

/* An empty slot is hatched rather than blank: blank space reads as "still
   loading" or "nothing to say here", the hatch reads as "deliberately
   nothing fitted". */
.assembly .slot.is-empty {
  background: repeating-linear-gradient(
    45deg,
    var(--paper),
    var(--paper) 6px,
    transparent 6px,
    transparent 12px
  );
  border-top-color: var(--line);
}

.slot-kind {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--font-body);
  font-size: .66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--ink-soft);
  margin: 0 0 .35rem;
}

.slot-dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--line);
  flex: 0 0 auto;
}

.slot-chassis .slot-dot { background: var(--chassis); }
.slot-body    .slot-dot { background: var(--body); }
.slot-reefer  .slot-dot { background: var(--reefer); }

.slot-id { line-height: 1.2; }

/* The chassis slot carries a truck number, not a serial, so it renders in
   the display face even though it sits in a component card — the typography
   is what tells the reader which kind of identifier they're looking at. */
.slot-chassis .slot-id .truck-no { font-size: 1.5rem; }

.slot-meta {
  font-size: .75rem;
  color: var(--ink-soft);
  margin-top: .2rem;
}

.assembly.is-retired .slot { opacity: .6; }

@media (max-width: 899.98px) {
  /* Each slot keeps its coloured top border as the separator when stacked. */
  .assembly { grid-template-columns: 1fr; }
  .assembly .slot { border-right: none; }
}

/* ---- Inherited invoices -------------------------------------------------
   Spend billed to a part now fitted to this truck but logged under another.
   Tinted so it is never mistaken for this build's own cost, and shown by
   default — hiding it would recreate the blind spot the modular build exists
   to close. Never summed into cost/mile. */

tr.inherited > td,
tr.inherited > th {
  background: var(--route-tint);
}

.t-prior {
  display: inline-block;
  margin-left: .4rem;
  border-radius: 3px;
  padding: .22em .45em;
  font-size: .62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  background: var(--panel);
  color: var(--route-deep);
  border: 1px solid rgba(36, 88, 127, .3);
}

/* ---- "Billed to" consequence panel (invoice form) -----------------------
   States the outcome of the current choice in words. Route-tinted rather
   than amber: this is information about what will happen, not a warning
   that something is wrong. */

.billed-to-panel {
  background: var(--route-tint);
  border-left: 3px solid var(--route);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: .55rem .75rem;
  font-size: .82rem;
  color: var(--ink);
  min-height: 2.6rem;
}

/* --- Condition galleries -------------------------------------------------
   A scroll-snap strip, not a carousel. Native swipe on a phone, no JS, and
   no state to get out of sync with the DOM. Component colours are identity
   only here, same rule as the assembly slots: they tint the card edge so a
   body photo cannot be mistaken for a refrigeration photo at a glance. */
.photo-strip {
  display: flex;
  gap: .75rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  list-style: none;
  margin: 0;
  padding: 0 0 .5rem;
  scrollbar-width: thin;
}

.photo-strip:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
}

.photo-card {
  flex: 0 0 auto;
  width: 220px;
  scroll-snap-align: start;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.photo-card img {
  display: block;
  width: 100%;
  height: 165px;
  object-fit: cover;
  background: var(--paper);
}

.photo-meta {
  padding: .5rem .6rem;
  font-size: .72rem;
  color: var(--ink-soft);
  display: flex;
  flex-direction: column;
  gap: .15rem;
}

.photo-date {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.photo-caption {
  color: var(--ink);
  overflow-wrap: anywhere;
}

.photo-delete .btn-link {
  color: var(--stop);
  text-decoration: none;
  font-size: .72rem;
}

.photo-delete .btn-link:hover { text-decoration: underline; }

/* The strip is already horizontally scrollable, so it needs no breakpoint of
   its own -- only the cards shrink, to fit more than one on a narrow phone. */
@media (max-width: 575.98px) {
  .photo-card { width: 165px; }
  .photo-card img { height: 124px; }
}
