/* ============ DARK MODE ROOT & VARIABLES ============ */

:root {
  --bg: #1a1a1c;
  --panel: #232326;
  --panel2: #2a2a2e;
  --border: #3a3a3f;
  --text: #e8e8ea;
  --text-dim: #9a9aa2;
  --accent: #4fa3d1;
  --accent-hover: #63b3dd;
  --accent-dim: #2f6f92;
  --done-row: #595959;
  --milestone: #e0b23c;
  --danger: #c1554d;
  --success: #4caf7d;
  --row-alt: #242427;
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  /* Rounder than before (was 4/8/12) -- reads more like a modern touch
     app, less like a dense desktop admin panel. */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  /* Subtle elevation for cards/modals/dropdowns -- this app had zero
     shadows anywhere before, which read flat/dated rather than modern. */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.45);
  /* Baseline touch target -- Apple/Material both recommend ~44px minimum
     for anything tappable. Referenced directly (custom properties can't
     be used in media queries), but every interactive rule below targets
     this. */
}

/* ============ RESET & BASE ============ */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
  font-family: 'Segoe UI', Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background-color: var(--bg);
}

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-width: 100vw;
}

/* ============ LAYOUT ============ */

header {
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  padding: 8px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
  position: relative;
  min-height: 0;
}

/* "View As" mode -- deliberately loud (matches the danger/alert color, not
   the panel palette) so it's unmistakable at a glance which identity is
   currently active, since every tab/permission check is genuinely
   operating as that vendor until Exit is clicked. */
.viewing-as-banner {
  background: var(--danger);
  color: #fff;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  flex-shrink: 0;
}

.viewing-as-banner button {
  background: #fff;
  color: var(--danger);
  border: none;
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

/* Export/Import/Delete Build always collapse behind the "⋮" menu button now
   (used to render inline on desktop and only collapse on mobile) -- same
   popup panel at every screen size. */
.header-actions {
  display: none;
  position: absolute;
  top: 100%;
  right: 16px;
  margin-top: 4px;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  z-index: 55;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  min-width: 180px;
}

.header-actions.open {
  display: flex;
}

.header-actions .current-project-label {
  max-width: none;
  text-align: center;
  padding: 2px 0;
  border-top: 1px solid var(--border);
}

.header-menu-btn {
  display: inline-flex;
  flex-shrink: 0;
  font-size: 16px;
  line-height: 1;
  padding: 6px 10px;
}

/* Left-aligned next to "BuildHub" rather than centered -- centering it in
   a slim persistent top bar that also has to fit the address label and
   menu button led to either the logo colliding with that content or
   everything else needing to be pushed off-balance to make room. Left of
   the wordmark is the more standard placement for a top-bar brand mark
   anyway (the big centered logo already has its moment on the weather
   ribbon below). */
header .logo {
  height: 28px;
  width: auto;
  display: block;
  flex-shrink: 0;
}

.buildhub-mark {
  height: 22px;
  width: 22px;
  flex-shrink: 0;
  margin-left: -2px;
}

header h1 {
  font-size: 17px;
  margin: 0;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.current-project-label {
  color: var(--text-dim);
  font-size: 13px;
  white-space: nowrap;
}

/* The build/project address shown in the header -- about 3x the old
   13px. Its own class now (used to share .current-project-label with the
   email shown in the "⋮" dropdown menu, which stays small on purpose). */
.header-page-title {
  color: var(--text);
  font-size: 39px;
  font-weight: 700;
  line-height: 1.1;
  white-space: normal;
}

/* Running weather-delay tally next to the build address -- see
   totalRainDelayDays (src/index.js) / renderLoggedInHeader. */
.header-rain-delay {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 700;
  color: #f0b93a;
  background: rgba(240, 185, 58, 0.12);
  border: 1px solid rgba(240, 185, 58, 0.35);
  border-radius: 999px;
  padding: 5px 12px;
  white-space: nowrap;
}

.spacer {
  flex: 1;
}

@media (max-width: 640px) {
  header {
    padding: 5px 10px;
    gap: 8px;
    flex-wrap: wrap;
  }

  header .logo {
    height: 20px;
  }

  .buildhub-mark {
    height: 17px;
    width: 17px;
  }

  header h1 {
    font-size: 13px;
  }

  .header-page-title {
    /* Still much bigger than before, but not literally 3x here -- a
       phone-width header has no room to spare, and header now wraps
       (see above) so a long address gets its own full-width second line
       instead of being squeezed or clipped. */
    font-size: 24px;
    width: 100%;
    order: 3;
  }

  .header-rain-delay {
    font-size: 11px;
    padding: 4px 9px;
    order: 4;
  }

  .current-project-label {
    font-size: 11px;
    max-width: 110px;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .header-actions {
    right: 10px;
  }

  /* Home page only: center the logo/title, since there's nothing else on
     that row now (actions are behind the menu button). */
  header.header-home {
    justify-content: center;
    text-align: center;
  }

  header.header-home .header-menu-btn {
    position: absolute;
    right: 10px;
  }

  /* Taken out of flow above, so the spacer shouldn't still claim the
     width it would otherwise leave for it — let it collapse to zero so
     justify-content:center actually centers the logo/title/label group. */
  header.header-home .spacer {
    flex: 0 0 0;
    width: 0;
  }
}

nav.tabs {
  display: flex;
  gap: 2px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  padding: 0 16px;
  position: sticky;
  top: 0;
  z-index: 40;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  flex-shrink: 0;
}

nav.tabs button {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--text-dim);
  padding: 13px 16px;
  min-height: 44px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  white-space: nowrap;
  box-shadow: none;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}

/* Kiosk map display (MAP_VIEW role) -- full-bleed, no page chrome, meant
   to sit untouched on a TV. */
.map-kiosk-page {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  background: var(--bg);
}

.map-kiosk-page .rt-map-card {
  width: 100%;
  max-width: none;
}

.map-kiosk-exit {
  position: fixed;
  bottom: 10px;
  right: 10px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 11px;
  opacity: 0.15;
  padding: 10px 14px;
  cursor: pointer;
  transition: opacity 0.15s;
}

.map-kiosk-exit:hover {
  opacity: 0.8;
}

nav.tabs button.active {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* While a tab is still loading (see switchTab's tabSwitchBusy guard) --
   blocks a second click from piling another full render on top of the one
   already in flight, and shows a wait cursor so it doesn't just look
   broken. */
nav.tabs.tabs-busy button {
  pointer-events: none;
  opacity: 0.65;
  cursor: wait;
}

nav.tabs button:hover {
  background: var(--panel2);
}

/* Buttons everywhere else get a press-down scale (see button:active) --
   deliberately skipped here since a tab bar shifting size on tap reads as
   glitchy rather than responsive. */
nav.tabs button:active {
  transform: none;
  background: var(--panel2);
}

main {
  flex: 1;
  /* Classic flexbox gotcha: in a column flex layout (body), a flex item's
     default min-width is its content's intrinsic min-content size --
     which for the Lot Matrix's very wide table is enormous. Without
     min-width:0 here, that width demand propagates up through main to
     body, stretching the WHOLE page (header and nav included, since
     they're siblings in the same flex column) wider than the viewport --
     exactly the "table looks fine but the header/nav are shifted/cut off
     too" overscroll this fixes. overflow-x:hidden alone doesn't prevent
     it; that only clips content that overflows main's own box, it
     doesn't stop main's box itself from being sized too wide to begin
     with. */
  min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px;
  background: var(--bg);
}

#mainContent, #tabContent {
  min-width: 0;
  max-width: 100%;
}

/* ============ ADMIN PAGE ============ */

.admin-page {
  max-width: 900px;
}

.admin-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
  margin-bottom: 20px;
  box-shadow: var(--shadow-sm);
  /* The Vendor Logins table has 5+ columns of real content (email, name,
     role, projects, status, actions) that don't fit a phone width -- this
     lets IT scroll horizontally in its own box instead of the whole page
     overflowing sideways (the same class of bug the Lot Matrix had). */
  overflow-x: auto;
}

.admin-section h3 {
  margin-bottom: 8px;
}

.admin-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.admin-section-header h3 {
  margin-bottom: 0;
}

.admin-hint {
  color: var(--text-dim);
  font-size: 12px;
  margin-bottom: 14px;
}

.admin-users-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.admin-users-table th {
  text-align: left;
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
}

.admin-users-table td {
  padding: 8px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.admin-users-table tr.admin-row-inactive {
  opacity: 0.5;
}

.admin-row-actions {
  display: flex;
  gap: 4px;
  white-space: nowrap;
}

.admin-email-form {
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.admin-email-form .row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.admin-email-row-split {
  display: flex;
  gap: 10px;
}

.admin-email-row-split .row {
  flex: 1;
}

.admin-email-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 4px;
}

.admin-email-actions #emailTestTo {
  flex: 1;
  min-width: 180px;
}

.admin-option-toolbar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.admin-option-toolbar #adminOptionNew {
  flex: 1;
  min-width: 160px;
}

.admin-option-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

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

.admin-neighborhood-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 8px 0;
}

/* Dims the individual project checkboxes once "All Projects" is checked --
   they're moot at that point, but leaving them checkable would look like
   they still matter. */
.admin-checks-disabled {
  opacity: 0.4;
  pointer-events: none;
}

.password-field-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
}

.password-field-wrap input {
  flex: 1;
}

.password-toggle-btn {
  flex-shrink: 0;
}

.readonly-banner {
  background: var(--panel2);
  border: 1px solid var(--border);
  border-left: 4px solid var(--milestone);
  border-radius: 6px;
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--text-dim);
}

/* ============ FORMS ============ */

/* 16px is deliberate, not just "bigger" -- iOS Safari auto-zooms the whole
   page when you tap into any input/select/textarea under 16px, which was
   happening on every single field in this app before. Dense grid cells
   (Lot Matrix, Schedule's Gantt table) override this back down further
   below, since those genuinely can't afford 16px in a 70px-wide column --
   but every normal form (Daily Logs, To-Do's, Inspections, modals, admin)
   gets the fix. */
select, input[type=text], input[type=date], input[type=number], input[type=email], input[type=password], input[type=search], textarea {
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-size: 16px;
  min-height: 44px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

textarea {
  min-height: 80px;
}

select:focus, input:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 163, 209, 0.25);
}

input[type=checkbox], input[type=radio] {
  accent-color: var(--accent);
  width: 20px;
  height: 20px;
  cursor: pointer;
  flex-shrink: 0;
}

/* ============ BUTTONS ============ */

button {
  background: var(--accent-dim);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.08s ease;
}

button:hover {
  background: var(--accent);
  box-shadow: var(--shadow-md);
}

/* Real tap feedback -- nothing in this app visibly responded to a touch
   before; everything just waited for the next render. A quick scale-down
   on press reads as "the app registered your tap" even before a network
   round-trip finishes. */
button:active {
  transform: scale(0.96);
  box-shadow: var(--shadow-sm);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

button.secondary {
  background: var(--panel2);
  border-color: var(--border);
  color: var(--text);
}

button.secondary:hover {
  background: var(--border);
}

button.secondary.active-toggle {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

button.danger {
  background: #5a2a26;
  border-color: var(--danger);
}

button.danger:hover {
  background: var(--danger);
}

/* Icon-only buttons (✕, 📷, ▼, ⋮...) used to be 3px/7px padding at 12px
   font -- comfortably under half the minimum comfortable tap size on a
   touchscreen. Square-ish 44px minimum now, centered regardless of
   whether the button holds one glyph or an icon+short label. */
button.icon {
  padding: 8px;
  font-size: 15px;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

/* ============ LOGIN PAGE ============ */

.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: var(--bg);
}

.login-box {
  background: var(--panel);
  padding: var(--spacing-xl);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  width: 90%;
  max-width: 400px;
}

.login-box h1 {
  font-size: 28px;
  margin-bottom: var(--spacing-sm);
  color: var(--accent);
}

.login-subtitle {
  color: var(--text-dim);
  margin-bottom: var(--spacing-lg);
  font-size: 14px;
}

/* Never had its own rule -- label and input just sat side by side at the
   browser's default cramped input width instead of stacking full-width
   like every other form in the app (see .modal .row). The flex-column
   here is what actually stretches the input to fill the box; nothing
   sets a width directly. */
.form-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--spacing-md);
}

.form-group label {
  font-size: 13px;
  color: var(--text-dim);
}

.login-footer {
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-lg);
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
}

/* ============ HOME PAGE ============ */

.home-wrap {
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
  text-align: center;
  padding: 24px 0;
}

.home-logo img {
  max-height: 90px;
  max-width: 360px;
  margin-bottom: 16px;
}

.home-project-name {
  font-size: 22px;
  margin: 0 0 18px;
  font-weight: 700;
  color: var(--text);
}

.progress-bar-bg {
  position: relative;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 6px;
  height: 28px;
  overflow: hidden;
  margin-bottom: 22px;
}

.progress-bar-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: var(--success);
  transition: width 0.3s;
}

.progress-bar-label {
  position: relative;
  line-height: 28px;
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
}

.home-job-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: left;
}

.home-job-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 18px 20px;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s, transform 0.1s;
  /* Staggered fade/slide-in on the Home page (see renderHomePage, which
     sets animation-delay per card) -- "both" holds the pre-animation
     state until it starts and the post-animation state after, so a card
     isn't briefly visible at full opacity before its delay kicks in. */
  animation: home-card-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* A soft ambient glow in the corner, like the accent lighting in a dark
   dashboard UI -- faint at rest, brighter on hover, never sharp-edged. */
.home-job-card::before {
  content: '';
  position: absolute;
  top: -40px;
  right: -40px;
  width: 140px;
  height: 140px;
  background: radial-gradient(circle, rgba(79, 163, 209, 0.18), transparent 70%);
  opacity: 0.6;
  transition: opacity 0.25s;
  pointer-events: none;
}

.home-job-card:hover {
  border-color: var(--accent);
  background: var(--panel2);
  box-shadow: var(--shadow-md);
}

.home-job-card:hover::before {
  opacity: 1;
}

.home-job-card:active {
  transform: scale(0.99);
}

@keyframes home-card-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .home-job-card { animation: none; }
}

/* ============ REUSABLE CARD ENTRANCE / GLOW ============ */
/* Same two effects as the Home page's project cards (see home-card-in /
   .home-job-card::before above), generalized so any card-like panel
   elsewhere in the app can opt into the same "dark dashboard" feel --
   apply .anim-card-in for the fade/slide-in (stagger it by setting
   animation-delay from JS across a small group of cards), and .glow-card
   for the soft corner accent light. Kept separate from .home-job-card's
   own copies rather than merged, so nothing about the already-shipped
   Home page changes by editing this. */
.anim-card-in {
  animation: home-card-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .anim-card-in { animation: none; }
}

.glow-card {
  position: relative;
  overflow: hidden;
}

.glow-card::before {
  content: '';
  position: absolute;
  top: -40px;
  right: -40px;
  width: 140px;
  height: 140px;
  background: radial-gradient(circle, rgba(79, 163, 209, 0.18), transparent 70%);
  opacity: 0.6;
  transition: opacity 0.25s;
  pointer-events: none;
}

.glow-card:hover::before {
  opacity: 1;
}

/* A plain, non-staggered fade-up for a page's single main panel (a table
   wrap, a form, a list) -- same easing/motion as the card entrance, just
   without per-item delay since these aren't small repeating card grids. */
.anim-panel-in {
  animation: home-card-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .anim-panel-in { animation: none; }
}

.home-job-address {
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}

.home-job-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.home-job-date-badge {
  flex-shrink: 0;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 3px 8px;
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Thin overall-completion bar under a project card's name -- fills in on
   load (see the requestAnimationFrame pair in renderHomePage) rather than
   just appearing at width, and the soft accent-colored glow around the
   fill is the "futuristic" touch instead of a flat, hard-edged bar. */
.home-job-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}

.home-job-progress-track {
  flex: 1;
  height: 5px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

.home-job-progress-fill {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-dim), var(--accent-hover));
  box-shadow: 0 0 8px 0 rgba(79, 163, 209, 0.6);
  transition: width 1s cubic-bezier(0.16, 1, 0.3, 1);
}

.home-job-progress-pct {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-dim);
  min-width: 28px;
  text-align: right;
}

.home-job-card .progress-bar-bg {
  height: 22px;
  margin-bottom: 0;
}

.home-job-card .progress-bar-label {
  line-height: 22px;
  font-size: 12px;
}

.home-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 28px 0 12px;
}

.home-section-header h3 {
  margin: 0;
  font-size: 13px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.home-todo-widget {
  display: flex;
  gap: 16px;
  margin: 12px 0;
  flex-wrap: wrap;
}

.home-todo-group {
  flex: 1;
  min-width: 260px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  box-shadow: var(--shadow-sm);
}

/* A quick color cue for which box is which without having to read the
   title first -- red for today (more urgent), amber for tomorrow. */
.home-todo-group-overdue {
  border-left-color: var(--danger);
}

.home-todo-group-overdue .home-todo-group-title {
  color: var(--danger);
}

.home-todo-group-today {
  border-left-color: var(--danger);
}

.home-todo-group-tomorrow {
  border-left-color: #c99a3a;
}

.home-todo-group-title {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
  cursor: pointer;
}

/* Rolled up: only the title bar shows. */
.home-todo-group.collapsed .home-todo-group-items {
  display: none;
}

/* Deliberately near-invisible -- same dark tone as the group's own panel
   background (which matches the weather ribbon above it), so the count is
   there if you look for it but doesn't compete with the title. */
.home-todo-count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 400;
  color: var(--panel2);
  text-transform: none;
  letter-spacing: 0;
}

.home-todo-item {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 0;
  border-top: 1px solid var(--border);
  cursor: pointer;
  font-size: 13px;
}

.home-todo-item:first-of-type {
  border-top: none;
}

.home-todo-item:hover .home-todo-text {
  color: var(--accent);
}

.home-todo-project {
  color: var(--text-dim);
  font-size: 11px;
  white-space: nowrap;
  flex-shrink: 0;
}

.home-todo-text {
  flex: 1;
  text-align: right;
  color: var(--text);
}

.home-template-card {
  background: var(--panel);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 12px 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  transition: border-color 0.15s, background 0.15s;
}

.home-template-card:hover {
  border-color: var(--accent);
  background: var(--panel2);
}

.home-template-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}

/* ============ PROJECT HOME ============ */

.project-home h2 {
  font-size: 20px;
  margin: 0;
  color: var(--text);
}

/* Reusable title-row-with-actions header, used anywhere a page needs a
   heading plus a cluster of buttons (Project Home today) without the
   title just sitting stacked above its own button row -- title and
   actions share one line on desktop, and stack cleanly (buttons evenly
   filling the width instead of an odd 2-then-1 wrap) on mobile. */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
}

.page-header-actions {
  display: flex;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

/* Thin pill buttons here specifically -- not a global button change, since
   44px min-height is a real accessibility floor everywhere else in the
   app. These three (Back to Home / Create New Build / Lot Matrix) are
   navigation, not the kind of one-shot destructive/critical action that
   needs the biggest possible target, so a slimmer pill matches the
   reference look without meaningfully hurting tappability. */
.page-header-actions button {
  min-height: 36px;
  padding: 7px 16px;
  border-radius: 999px;
  font-size: 13px;
}

@media (max-width: 640px) {
  .page-header {
    align-items: stretch;
  }

  .page-header-actions {
    width: 100%;
  }

  .page-header-actions button {
    flex: 1 1 auto;
  }
}

.builds-container {
  margin: var(--spacing-lg) 0;
}

.section-header {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: var(--spacing-md);
  padding-bottom: var(--spacing-sm);
  border-bottom: 2px solid var(--accent);
  color: var(--accent);
}

/* Builds list under the map on Project Home: rolled up by street, one
   compact row per build -- same idea as .rt-street-index beside the map,
   just with room for status/%/buyer since this section isn't squeezed
   next to the map SVG. */
.builds-street-block {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: 14px;
  padding: 4px 14px;
}

.builds-street-header {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 10px 0 8px;
  border-bottom: 1px solid var(--border);
}

.build-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  cursor: pointer;
}

.build-row:last-child {
  border-bottom: none;
}

.build-row:hover {
  color: var(--accent);
}

.build-row-addr {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.build-row-status {
  background-color: var(--panel2);
  border: 1px solid var(--border);
  padding: 3px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
}

.build-row-pct {
  font-weight: 700;
  color: var(--success);
  white-space: nowrap;
  min-width: 34px;
  text-align: right;
}

.build-row-buyer {
  color: var(--text-dim);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 140px;
}

@media (max-width: 640px) {
  .build-row {
    flex-wrap: wrap;
    gap: 6px 10px;
  }

  .build-row-addr {
    flex-basis: 100%;
  }
}

/* ============ INTERACTIVE MAP ============ */

.rt-map-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 14px;
  margin-bottom: 20px;
  box-shadow: var(--shadow-md);
}

.rt-map-svg-wrap {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
}

.rt-map-svg-wrap svg {
  display: block;
  width: 100%;
  height: auto;
}

.rt-map-lot rect {
  transition: filter 0.1s;
  cursor: pointer;
}

.rt-map-lot:hover .rt-lot-shape {
  filter: brightness(1.25) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
  stroke: var(--accent);
}

.rt-map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.rt-map-legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--text-dim);
}

.rt-map-swatch {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  display: inline-block;
  border: 1px solid rgba(255, 255, 255, 0.15);
}

/* Street index sits below the map now, not beside it -- freeing the map to
   use the card's full width instead of splitting it with a flex sibling
   (Nick's request, 2026-08-27: "put the street names below the map that
   way the map can be larger"). */
.rt-map-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.rt-map-primary {
  min-width: 0;
}

.rt-street-index {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.rt-street-col {
  min-width: 190px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.rt-street-col h4 {
  margin: 0 0 14px;
}

.rt-street-pill {
  display: inline-block;
  background: #5c7a92;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 5px 10px;
  border-radius: 5px;
  white-space: nowrap;
}

/* Thin, clickable boxes now instead of plain lines separated by a bottom
   border -- same pill treatment as .lm-stat-box, so it reads as
   "tappable" at a glance instead of looking like static text. */
.rt-street-addr,
.rt-street-addr-empty {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 999px;
  background: var(--panel2);
  border: 1px solid var(--border);
  min-height: 15px;
}

.rt-street-addr {
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.rt-street-addr:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--border);
}

.rt-street-addr-num {
  font-weight: 700;
  flex-shrink: 0;
}

.rt-street-addr-model {
  flex: 1;
  min-width: 0;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rt-street-addr-pct {
  flex-shrink: 0;
  margin-left: auto;
  color: var(--text-dim);
  font-weight: 700;
  font-size: 12px;
}

/* ============ LOT MATRIX ============ */

.lm-top-header {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  margin-bottom: 4px;
}

.lm-top.collapsed .lm-top-content {
  display: none;
}

.lm-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.lm-toolbar input[type=text] {
  width: 240px;
}

.lm-save-flash {
  font-size: 12px;
  color: var(--success);
  opacity: 0;
  transition: opacity 0.3s;
  white-space: nowrap;
}

.lm-save-flash.show {
  opacity: 1;
}

.lm-stat-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

/* Thin single-line pills (number and label side by side) instead of the
   number stacked on its own line above the label -- that's what was
   actually making these boxes tall on a phone, more than the font size
   itself. min-width dropped too, so more of them fit per row instead of
   forcing an awkward 2-3-across grid. */
.lm-stat-box {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}

.lm-stat-box strong {
  color: var(--text);
  font-size: 13px;
}

/* ============ OVERVIEW TAB ============
   Every field from the single-lot printable Lot Matrix sheet, grouped the
   same way (Construction Progress first) -- see renderOverviewSections
   in app.js. */
.overview-page {
  max-width: 820px;
}

.overview-gauge-box {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 20px;
  /* Override .lm-stat-box's new thin-pill look (white-space/border-radius/
     padding) -- this box holds a gauge plus wrapping address text, not a
     one-line stat, so it needs its own normal box shape back. */
  white-space: normal;
  border-radius: var(--radius-md);
  padding: 16px;
}

.overview-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: 16px;
  background: var(--panel);
}

.overview-section-header {
  padding: 8px 14px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
}

.overview-field-grid {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 0;
}

.overview-field-label {
  padding: 8px 14px;
  font-size: 12px;
  color: var(--text-dim);
  background: var(--panel2);
  border-top: 1px solid var(--border);
  border-right: 1px solid var(--border);
}

.overview-field-value {
  padding: 8px 14px;
  font-size: 13px;
  color: var(--text);
  border-top: 1px solid var(--border);
  word-break: break-word;
}

@media (max-width: 640px) {
  .overview-field-grid {
    grid-template-columns: 140px 1fr;
  }
}

.lm-table-wrap {
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: auto;
  max-width: 100%;
  background: var(--panel);
  max-height: calc(100vh - 300px);
  /* Mobile keyboards/native pickers (e.g. tapping a status dropdown while
     a row is highlighted) shrink the real visible viewport without firing
     a resize in some embedded browsers -- plain vh stays pinned to the
     tallest viewport seen, so the table's max-height doesn't shrink with
     it and the top rows end up scrolled out of reach behind the picker.
     dvh tracks the actual visible viewport and updates live; browsers
     that don't support it just keep the vh value above. */
  max-height: calc(100dvh - 300px);
  min-height: 300px;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-x pan-y;
}

table.lm-table {
  border-collapse: separate;
  border-spacing: 0;
  width: max-content;
  min-width: 100%;
  table-layout: fixed;
}

table.lm-table th {
  position: sticky;
  height: 34px;
  background: var(--panel2);
  color: var(--text-dim);
  text-align: left;
  padding: 0 6px;
  font-weight: 600;
  font-size: 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  z-index: 7;
}

/* Two-row sticky thead ("ribbon" group row + column-label row): each row
   must stick to its OWN top offset, not both to top:0 -- otherwise both
   rows try to occupy the same spot while scrolling and the column-label
   row visually breaks/overlaps instead of sitting cleanly below the group
   ribbon. Group row (with the 3 rowspan=2 sticky corner cells) sticks at
   the very top; the column-label row sticks right below it, offset by the
   group row's own height. */
table.lm-table thead tr#lmGroupRow th {
  top: 0;
}

table.lm-table thead tr#lmColRow th {
  top: 34px;
}

table.lm-table td {
  /* Slightly taller than the original 29px -- a real 44px touch target is
     impossible in a 33-column sheet without losing the point of a grid,
     but a few extra px meaningfully helps thumb accuracy without
     sacrificing many fewer rows per screen. */
  height: 34px;
  padding: 0 6px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  font-size: 13px;
  vertical-align: middle;
  overflow: hidden;
  background: var(--panel);
}

/* The global 16px/44px input sizing (see FORMS above) is right for normal
   forms but would blow this dense grid apart -- 33 columns, many under
   100px wide. Pulled back down to fit the row height set above. */
table.lm-table td input, table.lm-table td select {
  min-height: unset;
  height: 100%;
  padding: 2px 6px;
  font-size: 13px;
  border-radius: 3px;
}

table.lm-table tr:nth-child(even) td {
  background: var(--row-alt);
}

table.lm-table th.lm-print-col, table.lm-table td.lm-print-col {
  position: sticky;
  left: 0;
  z-index: 5;
  background: var(--panel2);
  text-align: center;
  min-width: 34px;
  max-width: 34px;
  padding: 0 !important;
}

table.lm-table th.lm-lot-col, table.lm-table td.lm-lot-col {
  position: sticky;
  left: 34px;
  z-index: 4;
  background: var(--panel2);
  text-align: center;
  font-weight: 700;
  min-width: 44px;
  max-width: 44px;
}

table.lm-table th.lm-addr-col, table.lm-table td.lm-addr-col {
  position: sticky;
  left: 78px;
  z-index: 4;
  background: var(--panel2);
  min-width: 170px;
  max-width: 170px;
}

/* Needs to be strictly higher than the regular (non-sticky-left) header
   cells' z-index:7 above, not equal to it -- at equal z-index, later
   DOM/paint order wins ties, and the scrolling column headers sit later
   in the row than these 3 corner cells. That tie was the bug: on a
   horizontal scroll the scrolling column labels painted OVER top of the
   frozen #/Lot/Address header cells instead of staying behind them, the
   one place that didn't match how the body rows already behave (their
   sticky td z-index of 4/5 is unambiguously higher than a plain td's
   default of 0, so there's no tie there). */
table.lm-table thead th.lm-lot-col,
table.lm-table thead th.lm-print-col,
table.lm-table thead th.lm-addr-col {
  z-index: 9;
}

table.lm-table td.lm-lot-col {
  color: var(--accent);
}

table.lm-table tr:nth-child(even) td {
  background: var(--row-alt);
}

/* Status row coloring -- covers every cell in the row, including the
   inputs/selects sitting on top of them (which otherwise carry their own
   opaque background and would hide the row color everywhere except the
   3 sticky columns). Color comes from --lm-row-tint (set inline per row,
   same hex as the interactive map's legend) blended into the panel
   background, so every status gets a matching tint here and on the map --
   not just the 4 that used to have hardcoded colors. */
table.lm-table tr.lm-row-tinted td,
table.lm-table tr.lm-row-tinted td input,
table.lm-table tr.lm-row-tinted td select {
  background: color-mix(in srgb, var(--lm-row-tint) 32%, var(--panel2)) !important;
}

/* Tap-to-highlight — the whole row, not just the sticky columns, and it
   stays visible while you scroll the rest of the row horizontally so you
   don't lose your place. */
table.lm-table tr.lm-row-selected td {
  box-shadow: inset 0 0 0 2px var(--accent);
}

table.lm-table tr.lm-row-selected td,
table.lm-table tr.lm-row-selected td input,
table.lm-table tr.lm-row-selected td select {
  background: var(--accent-dim) !important;
}

table.lm-table input[type=text] {
  width: 100%;
  background: var(--panel2);
  color: var(--text);
  border: none;
  padding: 4px 2px;
}

.lm-row-print-btn, .lm-row-print-btn:hover {
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0;
  font-size: 15px;
  min-width: unset;
  min-height: unset;
  width: 100%;
  height: 100%;
  box-shadow: none;
}

table.lm-table td.lm-lined-out input,
table.lm-table td.lm-lined-out select {
  opacity: 0.4;
  text-decoration: line-through;
}

table.lm-table td.lm-va-highlight {
  background: #3a3110 !important;
}

table.lm-table td.lm-computed {
  color: var(--text-dim);
  font-style: italic;
  padding: 6px 8px;
  white-space: nowrap;
}

/* Mobile: the sticky Address column (170px, same as desktop) was eating
   most of a phone's actual screen width before you even reached the
   first scrollable data column -- 34 (print) + 44 (lot) + 170 (address)
   is 248px of a ~390px-wide phone gone before anything else. Shrunk to
   fit a real address at a smaller size instead, with the sticky columns'
   own left offsets adjusted to match (see the lm-lot-col/lm-addr-col
   `left` values below, which have to stay in sync with lm-print-col/
   lm-lot-col's actual widths for the sticky stack to line up). Overflow
   text still reads fully via the input's own value/title on tap-and-hold
   or by widening -- ellipsis just keeps the column itself narrow. */
@media (max-width: 640px) {
  table.lm-table th.lm-lot-col, table.lm-table td.lm-lot-col {
    left: 30px;
    min-width: 34px;
    max-width: 34px;
  }

  table.lm-table th.lm-print-col, table.lm-table td.lm-print-col {
    min-width: 30px;
    max-width: 30px;
  }

  table.lm-table th.lm-addr-col, table.lm-table td.lm-addr-col {
    left: 64px;
    min-width: 100px;
    max-width: 100px;
  }

  table.lm-table td.lm-addr-col input {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  table.lm-table td, table.lm-table th {
    font-size: 12px;
  }
}

/* Print overlays (single-lot sheet + full matrix sheet): hidden on screen
   until their body class is set, shown alone when printing. Both carry a
   "← Back to Lot Matrix" button (.lm-print-back) that's the reliable way
   out -- afterprint doesn't fire consistently on mobile browsers, so
   cleanup can't depend on it alone. */
.lm-print-overlay {
  display: none;
}

body.printing-single > *:not(.lm-print-overlay),
body.printing-full-sheet > *:not(.lm-print-overlay) {
  display: none !important;
}

body.printing-single .lm-print-overlay,
body.printing-full-sheet .lm-print-overlay {
  display: block;
  padding: 20px;
  color: #111;
  background: #fff;
}

.lm-print-logo {
  text-align: center;
  margin-bottom: 14px;
}

.lm-print-logo img {
  height: 60px;
  width: auto;
}

.lm-print-back {
  display: inline-block;
  margin-bottom: 14px;
  padding: 8px 14px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}

.lm-print-overlay h1 {
  font-size: 20px;
  margin-bottom: 4px;
}

.lm-single-status {
  font-size: 13px;
  color: #555;
  margin-bottom: 16px;
}

.lm-single-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 14px;
}

.lm-single-table th {
  text-align: left;
  background: #eee;
  padding: 4px 8px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.lm-single-table td {
  border-bottom: 1px solid #ddd;
  padding: 4px 8px;
  font-size: 13px;
}

.lm-single-label {
  color: #555;
  width: 40%;
}

/* Full-sheet print: a plain auto-layout table (not the live matrix's wide
   sticky-column table, which only ever printed whatever was scrolled into
   view) with a repeating header, so it paginates normally across as many
   landscape pages as it needs. */
.lm-full-print-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
}

.lm-full-print-table th, .lm-full-print-table td {
  border: 1px solid #999;
  padding: 4px 6px;
  text-align: left;
}

.lm-full-print-table thead {
  display: table-header-group;
}

.lm-full-print-table tr {
  page-break-inside: avoid;
}

/* Page orientation per print job: everything defaults to landscape (the
   full sheet needs the width), but the single-lot sheet is a stack of
   narrow label/value tables that reads naturally in portrait. */
@page {
  size: landscape;
}

@page single-lot {
  size: portrait;
}

#lmSingleLotPrint {
  page: single-lot;
}

/* ============ BUILD PAGE ============ */

.build-page {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.sched-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.sched-toolbar label {
  color: var(--text-dim);
  font-size: 13px;
}

.sched-lock-banner {
  display: none;
  align-items: center;
  gap: 10px;
  background: #5a2622;
  border: 1px solid #d6483c;
  border-radius: 6px;
  padding: 9px 14px;
  margin-bottom: 12px;
  font-size: 13px;
}

#view-schedule.sched-locked .sched-lock-banner {
  display: flex;
}

#view-schedule.sched-locked input:not(.lock-exempt),
#view-schedule.sched-locked select:not(.lock-exempt),
#view-schedule.sched-locked button:not(.lock-exempt),
#view-schedule.sched-locked .drag-handle,
#view-schedule.sched-locked [draggable="true"] {
  pointer-events: none;
  opacity: 0.55;
}

#view-schedule.sched-locked .gantt-bar,
#view-schedule.sched-locked .gantt-milestone {
  pointer-events: none;
}

.col-picker-menu {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px;
  z-index: 50;
  min-width: 170px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}

.col-picker-menu.open {
  display: block;
}

.col-picker-menu label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text);
  font-size: 13px;
  padding: 4px 2px;
  cursor: pointer;
}

/* Was desktop-hidden (display:none here, only shown inside the <=640px
   media query below) while its collapse effect (.sched-intro.collapsed)
   was ALSO only defined inside that same media query -- so on a PC-width
   screen where it happened to still be visible, clicking it toggled the
   class but nothing actually collapsed. Now both the button and the
   collapse effect apply at every width, so it genuinely works instead of
   needing to be mobile-only. */
.sched-intro-toggle {
  display: inline-flex;
  margin-bottom: 8px;
}

.sched-intro.collapsed {
  display: none;
}

.sched-empty {
  padding: 24px 16px !important;
  text-align: left;
  color: var(--text-dim);
  white-space: normal !important;
  position: sticky;
  left: 0;
  width: max-content;
  max-width: 90vw;
}

.sched-empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
}

.sched-wrap {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: auto;
  background: var(--panel);
  max-height: calc(100vh - 300px);
  min-height: 300px;
  isolation: isolate;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-x pan-y;
}

@media (max-width: 640px) {

  /* The Gantt table needs 2D touch scrolling (horizontal timeline + vertical
     rows) nested inside the page's own vertical scroll -- a combination
     iOS Safari won't reliably hand touch gestures to, no matter how the
     overflow/touch-action/sticky CSS is tuned (tried and confirmed still
     broken on a real device). Simplest reliable fix: don't ask mobile to
     do that at all. Swap the table out for a plain vertical card list
     (.sched-mobile-list, populated in schedule.js) that only ever needs
     the same single-direction page scroll every other tab already uses
     successfully. */
  .sched-wrap {
    display: none;
  }

  .sched-desktop-only {
    display: none;
  }
}

.sched-mobile-list {
  display: none;
}

@media (max-width: 640px) {
  .sched-mobile-list {
    display: block;
  }
}

.sched-empty-mobile {
  padding: 20px 4px;
  color: var(--text-dim);
}

.sched-m-phase {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: 10px;
  overflow: hidden;
}

.sched-m-phase.upcoming { border-color: var(--milestone); }
.sched-m-phase.overdue { border-color: var(--danger); }

.sched-m-phase-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--panel2);
  font-weight: 700;
  cursor: pointer;
}

.sched-m-phase.done .sched-m-phase-header {
  background: var(--done-row);
}

.sched-m-collapse, .sched-m-collapse:hover {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 15px;
  cursor: pointer;
  flex-shrink: 0;
  padding: 4px;
  min-width: 36px;
  box-shadow: none;
  transition: transform 0.2s ease;
}

/* Always renders the same ▼ glyph -- collapsed state is a rotation, not a
   character swap, so it animates smoothly instead of just flipping. */
.sched-m-collapse.collapsed {
  transform: rotate(-90deg);
}

.sched-m-phase-name {
  flex: 1;
  min-width: 0;
}

.sched-m-phase-meta {
  display: flex;
  justify-content: space-between;
  padding: 6px 12px;
  font-size: 12px;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border);
}

.sched-m-phase-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.sched-m-phase-actual {
  padding: 6px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
}

.sched-m-phase-actual label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sched-m-actual-input {
  width: 64px;
}

.sched-m-completed {
  display: inline-block;
  margin-left: 10px;
  color: var(--success);
  font-size: 11px;
}

.sched-m-task-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--text-dim);
  flex: 1;
  min-width: 0;
}

.sched-m-task-field input {
  font-size: 12px;
  padding: 4px 6px;
}

.sched-m-task-actual {
  width: 100%;
}

.sched-m-tasks.collapsed {
  display: none;
}

.sched-m-task {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.sched-m-task:last-child {
  border-bottom: none;
}

.sched-m-task.done {
  opacity: 0.6;
}

.sched-m-task.done .sched-m-task-name {
  text-decoration: line-through;
}

.sched-m-task-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.sched-m-task-delay {
  padding-left: 24px;
  margin-top: 4px;
}

.sched-m-task-delay-toggle {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  flex-shrink: 0;
}

.sched-m-task-name {
  flex: 1;
  min-width: 0;
}

.sched-m-task-detail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding-left: 24px;
}

.sched-m-task-dates {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  flex-shrink: 0;
}

.sched-m-task-assigned {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  padding: 4px 6px;
}

table.gantt-table {
  border-collapse: separate;
  border-spacing: 0;
  width: max-content;
  min-width: 100%;
  table-layout: fixed;
}

table.gantt-table th {
  position: sticky;
  top: 0;
  height: 34px;
  background: var(--panel2);
  color: var(--text-dim);
  text-align: left;
  padding: 0 6px;
  font-weight: 600;
  font-size: 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

table.gantt-table td {
  height: 34px;
  padding: 0 6px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  font-size: 13px;
  vertical-align: middle;
  overflow: hidden;
}

/* Same reasoning as the Lot Matrix override above -- the global 16px/44px
   input sizing doesn't fit a Start/Duration/Actual Days cell that's often
   under 90px wide. */
table.gantt-table td input, table.gantt-table td select {
  min-height: unset;
  height: 100%;
  padding: 2px 6px;
  font-size: 13px;
  border-radius: 3px;
}

/* Delete-row icon buttons (and anything else button.icon) inside either
   dense grid -- the global 44px minimum (right for the rest of the app)
   would force these ~34px rows to grow taller than intended to fit a
   44px button, undoing the row-height tuning above. */
table.gantt-table button.icon, table.lm-table button.icon {
  min-width: 26px;
  min-height: 26px;
  padding: 2px;
  font-size: 13px;
  box-shadow: none;
}

table.gantt-table th.sticky-col, table.gantt-table td.sticky-col {
  position: sticky;
  z-index: 5;
  background: var(--panel2);
}

table.gantt-table td.sticky-col {
  background: var(--panel);
}

tr.phase-row td.sticky-col {
  background: var(--panel2);
}

tr.task-row:nth-child(even) td.sticky-col {
  background: var(--row-alt);
}

tr.done td.sticky-col {
  background: var(--done-row) !important;
}

tr.task-row.upcoming td.sticky-col, tr.phase-row.upcoming td.sticky-col {
  background: #3d3726;
}

tr.task-row.overdue td.sticky-col, tr.phase-row.overdue td.sticky-col {
  background: #3b2725;
}

/* Single click anywhere on a row (that isn't an input/button) highlights it
   across the whole width -- frozen columns and the Gantt timeline cell --
   so you can trace one row's bar out to the right without losing it. */
tr.gantt-row-selected td {
  box-shadow: inset 0 0 0 1px var(--accent);
}

tr.gantt-row-selected td.sticky-col {
  background: var(--accent-dim) !important;
}

tr.gantt-row-selected td.timeline-td {
  background: rgba(79, 163, 209, 0.15);
}

/* Phase/Task name column's width -- was a flat 240px, which read as too
   wide by default (see .sched-col1-resize-handle below for the drag-to-
   widen-it-back-out escape hatch). Every column after it derives its
   `left` from this same variable via calc(), so dragging col-1 wider or
   narrower shifts every column (and the scrolling timeline right after
   them) in sync instead of needing each one hand-recomputed. */
/* Each optional column's width lives in its own custom property (rather
   than a plain number) so hiding one via the Columns menu can zero it out
   and have every later column's `left` -- built by adding up these same
   variables -- shrink to follow it automatically. Before this, hiding a
   column just blanked its content in place (visibility:hidden) because
   the `left` values were hardcoded px math that assumed every prior
   column was still full width; now the column actually collapses and the
   rest of the frozen columns + the scrolling timeline slide left to
   close the gap, same as unchecking a column in a normal spreadsheet. */
.sched-wrap {
  --sched-col1-w: 180px;
  --col2-w: 85px;
  --col3-w: 85px;
  --col4-w: 90px;
  --col5-w: 90px;
  --col6-w: 110px;
  --col7-w: 60px;
  --col8-w: 90px;
  --col9-w: 130px;
  --col10-w: 90px;
}

th.col-num, td.col-num { left: 0; width: 36px; text-align: center; color: var(--text-dim); font-size: 11px; }
th.col-0, td.col-0 { left: 36px; width: 46px; }
th.col-1, td.col-1 { left: 82px; width: var(--sched-col1-w); }
th.col-2, td.col-2 { left: calc(82px + var(--sched-col1-w)); width: var(--col2-w); }
th.col-3, td.col-3 { left: calc(82px + var(--sched-col1-w) + var(--col2-w)); width: var(--col3-w); }
th.col-4, td.col-4 { left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w)); width: var(--col4-w); }
th.col-5, td.col-5 { left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w)); width: var(--col5-w); }
th.col-6, td.col-6 { left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w) + var(--col5-w)); width: var(--col6-w); }
/* Actions now only holds the delete button (the done checkbox moved into
   col-1, next to the name, matching the legacy app) so it can be much
   narrower than before. */
th.col-7, td.col-7 {
  left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w) + var(--col5-w) + var(--col6-w));
  width: var(--col7-w);
}

/* Per-phase Alert threshold dropdown -- empty on task rows. */
th.col-8, td.col-8 {
  left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w) + var(--col5-w) + var(--col6-w) + var(--col7-w));
  width: var(--col8-w);
}

/* Completion timestamp. */
th.col-9, td.col-9 {
  left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w) + var(--col5-w) + var(--col6-w) + var(--col7-w) + var(--col8-w));
  width: var(--col9-w);
  font-size: 11px;
  color: var(--text-dim);
}

/* Delayed checkbox -- now the last sticky column, so it carries the edge
   shadow separating the frozen columns from the scrolling timeline. */
th.col-10, td.col-10 {
  left: calc(82px + var(--sched-col1-w) + var(--col2-w) + var(--col3-w) + var(--col4-w) + var(--col5-w) + var(--col6-w) + var(--col7-w) + var(--col8-w) + var(--col9-w));
  width: var(--col10-w);
  text-align: center;
  border-right: 1px solid var(--border);
  box-shadow: 3px 0 6px -3px rgba(0, 0, 0, 0.5);
}

/* Drag-to-resize handle on the Phase/Task column's right edge. */
th.col-1 {
  position: relative;
}

.sched-col1-resize-handle {
  position: absolute;
  top: 0;
  right: -3px;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  z-index: 5;
  touch-action: none;
}

.sched-col1-resize-handle:hover,
.sched-col1-resize-handle.dragging {
  background: var(--accent);
  opacity: 0.6;
}

.sched-delay-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 100%;
}

.sched-delay-cell label {
  display: flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
}

.sched-delay-days {
  font-size: 11px;
  font-weight: 700;
  color: var(--danger);
  cursor: pointer;
}

.sched-delay-cell.rain .sched-delay-days {
  color: #4fb3d9;
}

/* "+" to report another delay on top of one already there -- deliberately
   tiny (this column is only ~90px wide), same pattern as the other
   icon-button size overrides in this file for dense grids. */
.sched-add-delay-btn {
  width: 24px;
  height: 24px;
  min-width: unset;
  min-height: unset;
  padding: 0;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  border-radius: 50%;
  box-shadow: none;
  flex-shrink: 0;
}

/* Columns ▾ show/hide menu (legacy-style checklist) -- each toggled-off
   column zeroes out its own width variable (see the --colN-w custom
   properties on .sched-wrap above), which shifts every later sticky
   column's `left` to close the gap, since those are all calc()'d from the
   same variables (needed even though the cell itself is display:none
   below -- a later column's `left` still has to know this one contributes
   0, not its original width).

   The cell is also set to display:none outright, not just width:0 --
   table-layout:fixed does NOT reliably collapse a cell to 0 rendered width
   just because its `width` computes to 0 (confirmed live: a "hidden"
   column kept rendering at 70-120px, some kind of leftover-space
   redistribution across every fixed-width column once the freed space
   didn't add up to the wrapper's min-width:100%, not just the one
   auto-width timeline column that's supposed to absorb it). display:none
   removes the cell from table layout entirely instead, which is the only
   way that's actually reliable across the layout algorithm's edge cases. */
.sched-wrap.col-hidden-2 { --col2-w: 0px; }
.sched-wrap.col-hidden-3 { --col3-w: 0px; }
.sched-wrap.col-hidden-4 { --col4-w: 0px; }
.sched-wrap.col-hidden-5 { --col5-w: 0px; }
.sched-wrap.col-hidden-6 { --col6-w: 0px; }
.sched-wrap.col-hidden-7 { --col7-w: 0px; }
.sched-wrap.col-hidden-8 { --col8-w: 0px; }
.sched-wrap.col-hidden-9 { --col9-w: 0px; }
.sched-wrap.col-hidden-10 { --col10-w: 0px; }

.sched-wrap.col-hidden-2 th.col-2, .sched-wrap.col-hidden-2 td.col-2,
.sched-wrap.col-hidden-3 th.col-3, .sched-wrap.col-hidden-3 td.col-3,
.sched-wrap.col-hidden-4 th.col-4, .sched-wrap.col-hidden-4 td.col-4,
.sched-wrap.col-hidden-5 th.col-5, .sched-wrap.col-hidden-5 td.col-5,
.sched-wrap.col-hidden-6 th.col-6, .sched-wrap.col-hidden-6 td.col-6,
.sched-wrap.col-hidden-7 th.col-7, .sched-wrap.col-hidden-7 td.col-7,
.sched-wrap.col-hidden-8 th.col-8, .sched-wrap.col-hidden-8 td.col-8,
.sched-wrap.col-hidden-9 th.col-9, .sched-wrap.col-hidden-9 td.col-9,
.sched-wrap.col-hidden-10 th.col-10, .sched-wrap.col-hidden-10 td.col-10 {
  display: none;
}

.sched-column-menu {
  padding: 4px;
}

.sched-column-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  font-size: 13px;
  white-space: nowrap;
  cursor: pointer;
}

.sched-column-item:hover {
  background: var(--panel);
}

/* A task's Start date input once manually overridden (as opposed to
   auto-computed from its predecessors) -- a small accent underline is
   enough to flag "this one was set by hand" without being loud about it. */
input.start-input.manual {
  border-color: var(--accent);
  box-shadow: inset 0 -2px 0 0 var(--accent);
}

.task-name-cell {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}

.task-name-cell .name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}

.alert-select {
  width: 100%;
  font-size: 12px;
  padding: 5px 4px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
}

tr.phase-row.alert-red td, tr.phase-row.alert-red td.sticky-col {
  background: #5a2622 !important;
}

tr.phase-row.alert-red td:first-child {
  box-shadow: inset 4px 0 0 0 #d6483c;
}

tr.phase-row.alert-red .gantt-bar {
  border-color: #d6483c;
  box-shadow: 0 0 0 1px #d6483c inset;
  background: #7a3530;
}

.sched-stat-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 14px;
  min-width: 150px;
}

.sched-stat-card.alert-red {
  background: #5a2622;
  border-color: #d6483c;
}

.sched-stat-card .alert-select-row {
  margin-top: 6px;
}

.sched-stat-card select.alert-select {
  width: auto;
}

.sched-stat-label {
  color: var(--text-dim);
  font-size: 11px;
  margin-bottom: 3px;
}

.sched-stat-value {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}

.sched-stat-value.late {
  color: var(--danger);
}

.sched-stat-value.ontrack {
  color: var(--success);
}

.sched-stat-value.weather-delay {
  color: #4fa3d1;
}

th.timeline-th {
  padding: 0;
}

td.timeline-td {
  padding: 0;
  position: relative;
}

.gantt-row-inner {
  position: relative;
  height: 100%;
  min-height: 100%;
}

.drag-handle {
  cursor: grab;
  color: var(--text-dim);
  font-size: 13px;
  padding: 0 3px;
  user-select: none;
}

.drag-handle:active {
  cursor: grabbing;
}

tr.phase-row.drag-over td {
  box-shadow: inset 0 2px 0 0 var(--accent);
}

tr.phase-row.dragging {
  opacity: 0.4;
}

tr.phase-row {
  background: var(--panel2);
  font-weight: 700;
}

tr.task-row:nth-child(even) {
  background: var(--row-alt);
}

tr.done {
  background: var(--done-row) !important;
  color: #d8d8d8;
}

tr.done td {
  color: #d8d8d8;
}

.indent {
  display: inline-block;
  width: 18px;
}

.milestone-name {
  font-weight: 700;
  color: var(--text);
}

.collapse-btn, .collapse-btn:hover {
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0;
  font-size: 12px;
  width: 24px;
  min-width: 24px;
  min-height: 24px;
  box-shadow: none;
}

.dur-input, .actual-input {
  width: 64px;
  font-size: 13px;
  padding: 6px 6px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
}

.assigned-input {
  width: 100px;
  font-size: 12px;
  padding: 6px 6px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
}

.pred-btn {
  width: 100%;
  font-size: 11px;
  padding: 5px 6px;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
}

.pred-modal-list {
  max-height: 50vh;
  overflow-y: auto;
  min-width: 280px;
  background: var(--panel);
}

.pred-group-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  margin: 12px 0 4px;
}

.pred-group:first-child .pred-group-label {
  margin-top: 0;
}

.pred-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 2px;
  border-bottom: 1px solid var(--border);
}

.pred-row-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  cursor: pointer;
  color: var(--text);
}

.pred-lag-input {
  width: 52px;
  font-size: 12px;
  padding: 4px 5px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
}

input[type=checkbox].done-check {
  width: 19px;
  height: 19px;
  cursor: pointer;
  accent-color: var(--accent);
}

tr.task-row.upcoming td, tr.phase-row.upcoming td {
  background: rgba(230, 192, 60, 0.06);
}

tr.task-row.overdue td, tr.phase-row.overdue td {
  background: rgba(214, 72, 60, 0.07);
}

tr.task-row.upcoming td:first-child, tr.phase-row.upcoming td:first-child {
  box-shadow: inset 4px 0 0 0 #e0c03c;
}

tr.task-row.overdue td:first-child, tr.phase-row.overdue td:first-child {
  box-shadow: inset 4px 0 0 0 #d6483c;
}

.row-status-badge {
  display: inline-block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 1px 5px;
  border-radius: 3px;
  margin-left: 6px;
  vertical-align: middle;
}

.row-status-badge.upcoming {
  background: #e0c03c;
  color: #332b06;
}

.row-status-badge.overdue {
  background: #d6483c;
  color: #fff;
}

.row-status-badge.delayed {
  background: var(--danger);
  color: #fff;
  text-transform: none;
}

/* Rain delays (applied via the Project Home "Weather Delay" button) get
   their own blue tone instead of the regular delay's red/amber, so a
   storm day reads as clearly different from a manually-reported delay. */
.row-status-badge.rain-delayed {
  background: #2f7d9e;
  color: #fff;
  text-transform: none;
}

tr.upcoming .gantt-bar {
  border-color: #e6c03c;
  box-shadow: 0 0 0 1px #e6c03c inset;
}

tr.overdue .gantt-bar {
  border-color: #d6483c;
  box-shadow: 0 0 0 1px #d6483c inset;
}

.row-actions {
  display: flex;
  gap: 3px;
}

.gantt-bar {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 19px;
  border-radius: 3px;
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  cursor: grab;
  z-index: 0;
}

.gantt-bar.done {
  background: #4a4a4a;
  border-color: #6a6a6a;
}

/* Sits flush against the right edge of a task's normal bar, showing only
   the extra time a reported delay added -- not the whole bar in red. */
.gantt-bar-delay {
  background: var(--danger);
  border: 1px solid #a83f38;
  border-left: none;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  cursor: default;
}

/* Marks the boundary between one delay report and the next within the
   same task's red delay span -- a crisp light line reads clearly against
   the red fill, unlike the segment's own dark-red border. */
.gantt-bar-delay-split {
  border-right: 2px solid #ffe3e0;
}

/* Weather-caused delay segment (the Weather Delay button, source: 'rain')
   -- same blue as the rain badge text elsewhere (.sched-delay-cell.rain),
   so a delay's cause reads consistently at a glance whether you're looking
   at the Gantt bar or the compact badge next to it. */
.gantt-bar-delay-rain {
  background: #4fb3d9;
  border-color: #2f7f9e;
}

.gantt-bar-delay-rain.gantt-bar-delay-split {
  border-right: 2px solid #d5f0fa;
}

.gantt-bar.manual {
  border-style: dashed;
}

.gantt-bar.dragging-move {
  cursor: grabbing;
}

.gantt-bar.dragging-resize {
  cursor: ew-resize;
}

.gantt-drag-tooltip {
  position: fixed;
  background: #111;
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 200;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.gantt-milestone {
  position: absolute;
  top: 50%;
  width: 18px;
  height: 18px;
  background: var(--milestone);
  border: 1px solid #b8871f;
  transform: translateY(-50%) rotate(45deg);
  cursor: pointer;
  z-index: 0;
}

.gantt-milestone.done {
  background: #8a7332;
  border-color: #6a6a6a;
}

.col-resize-handle {
  width: 10px;
  height: 100%;
  cursor: col-resize;
  display: flex;
  align-items: center;
  justify-content: center;
}

.col-resize-handle::before {
  content: '';
  display: block;
  width: 2px;
  height: 20px;
  background: var(--text-dim);
  border-radius: 1px;
  opacity: 0.5;
}

.col-resize-handle:hover::before, .col-resize-handle.dragging::before {
  background: var(--accent);
  opacity: 1;
}

.today-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #e0623c;
  pointer-events: none;
}

.gridline {
  position: absolute;
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--border);
}

.gridline.weekend {
  background: rgba(255, 255, 255, 0.02);
}

.month-label {
  position: absolute;
  top: 2px;
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}

.day-label {
  position: absolute;
  bottom: 2px;
  font-size: 9px;
  color: var(--text-dim);
}

#timelineHeaderInner {
  position: relative;
  height: 34px;
}

.notes-form {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 14px;
  margin-bottom: 16px;
}

.notes-form textarea {
  width: 100%;
  min-height: 70px;
  resize: vertical;
  margin-bottom: 8px;
}

.notes-form .row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.note-entry {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 10px;
}

.note-entry .meta {
  color: var(--text-dim);
  font-size: 12px;
  margin-bottom: 6px;
  display: flex;
  justify-content: space-between;
}

.note-entry .text {
  white-space: pre-wrap;
  margin-bottom: 8px;
  color: var(--text);
}

.photo-grid {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.photo-thumb {
  width: 110px;
  height: 90px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  cursor: pointer;
}

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 50;
  cursor: zoom-out;
}

.lightbox.open {
  display: flex;
}

.lightbox img {
  max-width: 90vw;
  max-height: 90vh;
  border-radius: 4px;
}

.empty-state {
  color: var(--text-dim);
  padding: 30px;
  text-align: center;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 60;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.modal-overlay.open {
  display: flex;
  opacity: 1;
}

.modal {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  min-width: 320px;
  max-width: 90vw;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
  transform: translateY(8px) scale(0.98);
  transition: transform 0.15s ease;
}

.modal-overlay.open .modal {
  transform: translateY(0) scale(1);
}

.modal h2 {
  margin-top: 0;
  margin-bottom: 4px;
  font-size: 18px;
  color: var(--text);
}

.modal .row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}

.cb-progress-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 12px;
}

@media (max-width: 480px) {
  .cb-progress-grid {
    grid-template-columns: 1fr;
  }
}

.delay-history-list {
  max-height: 320px;
  overflow-y: auto;
}

.delay-history-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.delay-history-item:first-child {
  border-top: none;
}

.delay-history-num {
  font-weight: 700;
  color: var(--text-dim);
  min-width: 44px;
}

.delay-history-days {
  font-weight: 700;
  color: var(--danger);
  min-width: 36px;
}

.delay-history-when {
  color: var(--text-dim);
  margin-left: auto;
}

.modal-item-preview {
  color: var(--text-dim);
  font-size: 13px;
  margin: 0 0 16px;
  padding: 8px 10px;
  background: var(--panel2);
  border-radius: var(--radius-sm);
}

.modal .actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}

.modal label {
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.legend {
  display: flex;
  gap: 16px;
  color: var(--text-dim);
  font-size: 12px;
  margin-top: 8px;
}

.legend span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.swatch {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  display: inline-block;
}

.sched-stats {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  position: relative;
}

.schedule-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.sched-hint {
  color: var(--text-dim);
  font-size: 12px;
  font-style: italic;
}

/* Right-click menu on a phase/task row (Rename / Add). */
.sched-context-menu {
  position: fixed;
  z-index: 200;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  min-width: 200px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.sched-context-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 4px;
  color: var(--text);
  padding: 8px 10px;
  font-size: 13px;
  cursor: pointer;
}

.sched-context-item:hover {
  background: var(--panel);
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
}

/* Fit to Screen no longer forces the table to width:100% -- that let
   table-layout:fixed's automatic slack-distribution stretch columns
   unpredictably (the original cause of the Phase/Task column misalignment
   bug). Instead schedule.js computes an exact cellWidthPx so the sticky
   columns (932px, fixed) plus the timeline's own explicit width already
   sum to the wrapper's available width -- no extra width to distribute. */

tr.task-row-hidden {
  display: none;
}

.sched-gauge-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 100px;
  gap: 2px;
}

.sched-gauge {
  display: block;
}

/* ============ CALENDAR VIEW (alternative to the Gantt) ============ */

.sched-calendar {
  display: none;
}

.schedule-page.calendar-mode .sched-wrap,
.schedule-page.calendar-mode .sched-mobile-list {
  display: none !important;
}

/* The Gantt table (position:sticky-heavy, thousands of cells on a big
   schedule) never actually leaves the DOM when switching to Calendar
   view -- it's just hidden. display:none forces the browser to tear down
   and later fully rebuild its layout/sticky tracking, which is the
   "runs a little slow" switching back and forth. content-visibility:
   hidden skips that work while hidden and keeps it cached instead, so
   switching back is near-instant. Falls back to the plain display:none
   above on browsers that don't support it yet. */
@supports (content-visibility: hidden) {
  .schedule-page.calendar-mode .sched-wrap,
  .schedule-page.calendar-mode .sched-mobile-list {
    display: block !important;
    content-visibility: hidden;
  }
}

.schedule-page.calendar-mode .sched-calendar {
  display: block;
}

.cal-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.cal-month-label {
  font-weight: 700;
  font-size: 16px;
  flex: 1;
}

.cal-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  font-size: 11px;
  color: var(--text-dim);
  text-align: center;
  margin-bottom: 4px;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

.cal-cell {
  background: var(--panel);
  min-height: 88px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cal-cell.cal-empty {
  background: var(--bg);
}

.cal-cell.cal-today {
  background: var(--accent-dim);
}

.cal-daynum {
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 600;
}

.cal-events {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.cal-event {
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--panel2);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cal-event.cal-milestone {
  background: var(--milestone-bg, #5c4a1f);
  color: #f2d33c;
}

.cal-event.cal-done {
  opacity: 0.5;
  text-decoration: line-through;
}

.cal-event.cal-finish {
  border-left: 2px solid var(--success);
}

.cal-more {
  font-size: 10px;
  color: var(--text-dim);
}

@media (max-width: 640px) {
  .cal-cell {
    min-height: 56px;
  }
  .cal-event {
    font-size: 9px;
  }
}

#schedFireworksCanvas {
  position: absolute;
  inset: -10px;
  width: calc(100% + 20px);
  height: calc(100% + 20px);
  pointer-events: none;
  z-index: 0;
}

.weather-logo-bar {
  position: relative;
  display: flex;
  justify-content: center;
  margin-bottom: 10px;
}

/* Flashlight sweep: this pseudo-element is clipped to the logo's own
   opaque pixels (via mask-image using the same PNG), so the bright band
   it paints only ever travels across the gold artwork itself -- like a
   flashlight passing over it -- rather than the whole mark brightening
   at once. Starts almost as soon as the page opens, then repeats on a
   slow, mostly-idle cycle. */
.weather-logo-bar::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 100%;
  max-width: 640px;
  height: 100%;
  max-height: 160px;
  -webkit-mask-image: url('/logo.png');
  mask-image: url('/logo.png');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  background: linear-gradient(
    100deg,
    transparent 40%,
    rgba(255, 250, 227, 0.85) 48%,
    rgba(255, 255, 255, 1) 50%,
    rgba(255, 250, 227, 0.85) 52%,
    transparent 60%
  );
  background-size: 300% 100%;
  mix-blend-mode: screen;
  pointer-events: none;
  animation: logo-shine-sweep 9s ease-in-out 0.2s infinite;
}

.weather-ribbon {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 18px;
  margin-bottom: 8px;
  display: flex;
  gap: 20px;
  /* flex-start, not stretch -- the box's height should come from the
     day-by-day forecast (.weather-main) alone. Weather Watch
     (.weather-side) gets capped to match that same height via JS (see
     syncWeatherSideHeight in app.js, called right after this renders)
     and scrolls its own alert list internally past that point instead of
     pushing the whole ribbon taller. */
  align-items: flex-start;
  max-width: 100%;
  overflow: hidden;
  box-sizing: border-box;
}

/* Desktop: capped narrower than the 1200px page container and centered,
   instead of stretching edge to edge -- the day strip and alerts column
   don't need anywhere near that much room, so it was mostly just wasted
   space either side of it on a wide screen. A fixed cap rather than
   width:fit-content -- the day cells now flex to fill whatever width
   they're given (see .weather-day above), so "fit content" has no real
   content size to hug anymore and would just collapse the box down too
   far. Mobile keeps the full-width stacked layout (see the 700px
   breakpoint below) since there's no spare width to reclaim there anyway. */
@media (min-width: 701px) {
  .weather-ribbon {
    width: 100%;
    max-width: 1080px;
    margin-left: auto;
    margin-right: auto;
  }
}

.weather-main {
  flex: 1;
  min-width: 0;
  max-width: 100%;
}

.weather-loc {
  color: var(--text-dim);
  font-size: 13px;
  margin-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.weather-days {
  display: flex;
  gap: 4px;
  max-width: 100%;
}

.weather-side {
  flex: 0 0 220px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding-left: 20px;
  border-left: 1px solid var(--border);
}

/* The Project Home "Weather Delay" button's own column, off to the right
   of Weather Watch (used to be squeezed inside .weather-side below the
   alerts list, cramped against the bottom edge -- Nick's request,
   2026-08-27). Empty (no button rendered, see renderWeatherDelayButton) on
   the plain Home page or for non-Owner logins, in which case it just takes
   up no space. */
.weather-delay-col {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  align-self: stretch;
  padding-left: 20px;
  border-left: 1px solid var(--border);
}

.weather-delay-col:empty {
  padding-left: 0;
  border-left: none;
}

.weather-delay-col button {
  white-space: nowrap;
}

/* Doubled from the original 320x80 -- both the plain Home page and Project
   Home asked for a bigger company logo, so this is just the base size now
   rather than a .project-home-only override. */
.weather-logo {
  max-width: 640px;
  max-height: 160px;
  object-fit: contain;
  animation: logo-entrance 0.6s ease-out both;
}

/* Entrance for both logo sizes -- a soft rise-and-settle rather than just
   appearing. */
@keyframes logo-entrance {
  from { opacity: 0; transform: translateY(-10px) scale(0.94); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* Slow flashlight pass: fades in almost immediately (short delay above),
   then takes its time crossing the logo (0% to 40% of this 9s cycle is
   ~3.6s of travel) before fading out and sitting idle for the rest of
   the cycle until it repeats. */
@keyframes logo-shine-sweep {
  0% {
    background-position: 150% 0;
    opacity: 0;
  }
  4% {
    opacity: 1;
  }
  40% {
    background-position: -50% 0;
    opacity: 1;
  }
  46%, 100% {
    background-position: -50% 0;
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .weather-logo, .weather-logo-bar::after {
    animation: none;
  }
}

.weather-alerts {
  width: 100%;
  font-size: 12.5px;
  /* max-height is set inline by syncWeatherSideHeight() in app.js, sized
     to match .weather-main's actual rendered height exactly -- a long
     alert list scrolls within that budget instead of pushing the whole
     ribbon taller than the day-forecast strip. This 220px is only a
     fallback for the instant before that JS runs (and for the stacked
     mobile layout, where the two columns aren't side-by-side and this
     kind of matching doesn't apply). */
  max-height: 220px;
  overflow-y: auto;
  /* Firefox: dark thumb on a dark track instead of the pale default
     scrollbar, which stood out against this panel. */
  scrollbar-color: #55524a var(--panel2);
  scrollbar-width: thin;
}

/* Chrome/Edge/Safari scrollbar -- same darker thumb as the
   scrollbar-color rule above (Firefox has no ::-webkit-scrollbar
   support, hence both). */
.weather-alerts::-webkit-scrollbar {
  width: 8px;
}

.weather-alerts::-webkit-scrollbar-track {
  background: var(--panel2);
  border-radius: 4px;
}

.weather-alerts::-webkit-scrollbar-thumb {
  background: #55524a;
  border-radius: 4px;
}

.weather-alerts::-webkit-scrollbar-thumb:hover {
  background: #6b6759;
}

.weather-alerts-title {
  font-weight: 700;
  color: var(--milestone, #e0b23c);
  margin-bottom: 6px;
  text-align: center;
}

.weather-alert-item {
  color: var(--text-dim);
  padding: 4px 6px;
  border-top: 1px solid var(--border);
  border-left: 3px solid transparent;
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}

/* Colorized by intensity -- a light drizzle day and a hail-producing
   thunderstorm used to read identically in this list. Moderate/severe/
   extreme mirror how real weather-alert services grade a hazard. */
.weather-alert-moderate {
  border-left-color: #e0b23c;
}

.weather-alert-severe {
  border-left-color: #d98a3d;
}

.weather-alert-extreme {
  border-left-color: var(--danger);
  background: rgba(193, 85, 77, 0.08);
}

.weather-alert-severity {
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: 999px;
  flex-shrink: 0;
}

.weather-alert-moderate .weather-alert-severity {
  background: rgba(224, 178, 60, 0.18);
  color: #e0b23c;
}

.weather-alert-severe .weather-alert-severity {
  background: rgba(217, 138, 61, 0.18);
  color: #d98a3d;
}

.weather-alert-extreme .weather-alert-severity {
  background: rgba(193, 85, 77, 0.2);
  color: var(--danger);
}

.weather-alerts-empty {
  color: var(--text-dim);
  text-align: center;
  font-style: italic;
}

@media (max-width: 700px) {
  .weather-ribbon {
    flex-direction: column;
  }

  .weather-side {
    flex: none;
    width: 100%;
    padding-left: 0;
    padding-top: 12px;
    border-left: none;
    border-top: 1px solid var(--border);
  }

  .weather-delay-col {
    flex: none;
    width: 100%;
    padding-left: 0;
    padding-top: 12px;
    border-left: none;
    border-top: 1px solid var(--border);
  }

  .weather-delay-col:empty {
    padding-top: 0;
    border-top: none;
  }

  .weather-delay-col button {
    width: 100%;
  }

  /* Desktop-only note: none of this touches anything above 700px -- the
     PC ribbon (.weather-day flex:1 1 0, no min-width, see below) is
     staying exactly as it is. On a phone, though, shrinking 14 days down
     to fit the screen with no minimum width left every column truncated
     to 1-2 letters and cut-off numbers (confirmed from a real screenshot)
     -- illegible instead of just cozy. Below this width, cells stop
     shrinking below a readable size and the row scrolls horizontally
     instead, with a visible scrollbar so it doesn't look broken. */
  .weather-days {
    overflow-x: auto;
    padding-bottom: 6px;
    scrollbar-color: #55524a var(--panel2);
    scrollbar-width: thin;
  }

  .weather-days::-webkit-scrollbar {
    height: 8px;
  }

  .weather-days::-webkit-scrollbar-track {
    background: var(--panel2);
    border-radius: 4px;
  }

  .weather-days::-webkit-scrollbar-thumb {
    background: #55524a;
    border-radius: 4px;
  }

  .weather-day {
    flex: 0 0 auto;
    min-width: 58px;
  }

  .weather-dow {
    white-space: nowrap;
    overflow: visible;
    text-overflow: unset;
  }
}

/* flex:1 1 0 with no min-width -- all 14 days share the row and shrink
   together to whatever width is actually available, instead of holding a
   fixed min-width that forced a horizontal scrollbar once they didn't
   all fit. Padding/font sizes below are trimmed to match -- shorter
   overall, and still legible once every cell is this much narrower. */
.weather-day {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  padding: 4px 2px;
  border-radius: 6px;
  background: var(--panel2);
  line-height: 1.2;
}

.weather-dow {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.weather-icon {
  font-size: 23px;
  margin-bottom: 1px;
  line-height: 1.1;
}

.weather-temp {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  /* No forced nowrap -- on the narrowest phones this can wrap to two
     lines rather than the row needing to scroll to keep it on one. */
}

.weather-pop {
  font-size: 11.5px;
  color: var(--accent);
  margin-top: 1px;
}

.weather-loading {
  color: var(--text-dim);
  font-size: 13px;
}

.notes-todo-columns {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.todo-panel {
  flex: 1;
  min-width: 280px;
}

.notes-panel {
  flex: 2;
  min-width: 340px;
}

.todo-panel h3, .notes-panel h3 {
  font-size: 13px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0 0 10px;
}

.dropzone-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}

.dropzone {
  border: 2px dashed var(--border);
  border-radius: 8px;
  padding: 22px 14px;
  text-align: center;
  cursor: pointer;
  background: var(--panel);
  transition: border-color 0.15s, background 0.15s;
}

.dropzone:hover {
  border-color: var(--accent-dim);
}

.dropzone.dragover {
  border-color: var(--accent);
  background: var(--panel2);
}

.dz-icon {
  font-size: 26px;
  margin-bottom: 6px;
  opacity: 0.7;
}

.dz-title {
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 4px;
  color: var(--text);
}

.dz-hint {
  font-size: 11px;
  color: var(--text-dim);
}

/* Search-as-you-type "Assigned to" field, replacing the old plain select. */
.contact-picker {
  position: relative;
  flex: 1;
  min-width: 180px;
}

.contact-search-input {
  width: 100%;
}

.contact-suggestions {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  max-height: 240px;
  overflow-y: auto;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}

.contact-suggestion {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  cursor: pointer;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
}

.contact-suggestion:last-child {
  border-bottom: none;
}

.contact-suggestion:hover {
  background: var(--panel);
}

.contact-suggestion-email {
  color: var(--text-dim);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.contact-suggestion-new {
  color: var(--accent);
  font-weight: 600;
}

.contact-suggestion-empty {
  padding: 10px;
  color: var(--text-dim);
  font-size: 13px;
}

.inspections-punch-columns {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.insp-panel {
  flex: 1;
  min-width: 320px;
}

.punch-panel {
  flex: 1;
  min-width: 320px;
}

.type-tag {
  flex-shrink: 0;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
}

.type-tag.insp {
  background: var(--accent-dim);
  color: #eaf4fa;
}

.type-tag.punch {
  background: #5a4a2a;
  color: #f5e2b8;
}

.review-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

.review-item input[type=text] {
  flex: 1;
}

.data-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 4px solid var(--text-dim);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin-bottom: 8px;
  flex-wrap: wrap;
  box-shadow: var(--shadow-sm);
}

.data-row .grow {
  flex: 1;
  min-width: 120px;
}

.data-row.done {
  opacity: 0.55;
}

.data-row.done .grow {
  text-decoration: line-through;
}

.data-row .meta,
.todo-item-main .meta {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}

/* A bare, unset <input type=date> shows as a totally empty box on mobile
   -- there's no placeholder text native date inputs can show, so with
   nothing next to it there's no way to tell what it's even for. This
   small label sits right before it wherever that's the case. */
.field-label {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}

.data-row .meta.due-today {
  color: var(--danger);
  font-weight: 700;
}

.data-row .meta.insp-completed-badge {
  color: var(--success);
}

.data-row.status-passed, .data-row.status-selected, .data-row.status-ordered, .data-row.status-installed {
  border-left-color: var(--success);
}

.data-row.status-failed {
  border-left-color: var(--danger);
}

.data-row.overdue {
  border-left-color: var(--danger);
}

.data-row select, .data-row input[type="date"] {
  flex-shrink: 0;
}

.data-row-photo-link {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}

.data-row-photo-thumb {
  width: 28px;
  height: 28px;
  object-fit: cover;
  border-radius: 3px;
  cursor: pointer;
}

.insp-punch-combined {
  max-width: 1100px;
}

.doc-group {
  margin-bottom: 16px;
}

.doc-group-title {
  font-size: 12px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}

/* ============ CELEBRATION ANIMATIONS ============ */
/* See showFireworksAnimation/showBigTextCelebration/showMoneyRainAnimation/
   showFistBumpAnimation in utils.js -- each one creates one of these
   overlays, plays, then removes itself. pointer-events: none throughout
   so the app underneath stays clickable while it plays. */
.celebration-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  overflow: hidden;
}

.firework-burst {
  position: absolute;
  width: 4px;
  height: 4px;
}

.firework-spark {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  top: 0;
  left: 0;
  opacity: 0;
  animation: firework-spark-fly 1s ease-out forwards;
  transform: rotate(var(--angle));
  box-shadow: 0 0 6px 1px currentColor;
}

@keyframes firework-spark-fly {
  0% { transform: rotate(var(--angle)) translateY(0); opacity: 1; }
  15% { opacity: 1; }
  100% { transform: rotate(var(--angle)) translateY(-90px); opacity: 0; }
}

.celebration-bigtext {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.4);
}

.celebration-bigtext-inner {
  font-size: 64px;
  font-weight: 900;
  color: #fff;
  text-shadow: 0 0 24px var(--accent), 0 4px 14px rgba(0, 0, 0, 0.6);
  animation: bigtext-pop 1.8s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  text-align: center;
  padding: 0 20px;
}

.celebration-emoji {
  display: block;
  font-size: 80px;
}

@keyframes bigtext-pop {
  0% { transform: scale(0.3); opacity: 0; }
  15% { transform: scale(1.15); opacity: 1; }
  25% { transform: scale(1); }
  80% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.08); opacity: 0; }
}

.money-bill {
  position: absolute;
  top: -40px;
  opacity: 0;
  animation-name: money-fall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
}

@keyframes money-fall {
  0% { transform: translateY(-40px) rotate(0deg); opacity: 1; }
  100% { transform: translateY(110vh) rotate(360deg); opacity: 0.9; }
}

.celebration-fistbump {
  display: flex;
  align-items: center;
  justify-content: center;
}

.fistbump-hand {
  position: absolute;
  font-size: 72px;
  animation-duration: 0.7s;
  animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
  animation-fill-mode: both;
}

.fistbump-left {
  animation-name: fistbump-in-left;
}

.fistbump-right {
  animation-name: fistbump-in-right;
}

@keyframes fistbump-in-left {
  0% { transform: translateX(-60vw); opacity: 0; }
  70% { transform: translateX(-14px); opacity: 1; }
  100% { transform: translateX(0); opacity: 1; }
}

@keyframes fistbump-in-right {
  0% { transform: translateX(60vw) scaleX(-1); opacity: 0; }
  70% { transform: translateX(14px) scaleX(-1); opacity: 1; }
  100% { transform: translateX(0) scaleX(-1); opacity: 1; }
}

.fistbump-burst {
  position: absolute;
  font-size: 0;
  opacity: 0;
  animation: fistbump-burst-pop 0.5s ease-out 0.6s both;
}

@keyframes fistbump-burst-pop {
  0% { font-size: 0; opacity: 0; }
  50% { font-size: 60px; opacity: 1; }
  100% { font-size: 84px; opacity: 0; }
}

.dropzone-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
  max-width: 1000px;
  margin-bottom: 16px;
}

.dropzone {
  border: 2px dashed var(--border);
  border-radius: var(--radius-md);
  padding: 24px 12px;
  text-align: center;
  cursor: pointer;
  transition: all 0.15s;
  background: var(--panel);
}

.dropzone:hover, .dropzone.dragover {
  border-color: var(--accent);
  background: var(--panel2);
}

.dz-title {
  font-weight: 700;
  margin-bottom: 4px;
}

.dz-hint {
  font-size: 11px;
  color: var(--text-dim);
}

.doc-groups {
  max-width: 1000px;
}

.doc-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.doc-filename {
  color: var(--accent);
  text-decoration: none;
  flex-shrink: 0;
}

.doc-filename:hover {
  text-decoration: underline;
}

.doc-item .doc-meta {
  font-size: 11px;
  color: var(--text-dim);
}

.doc-item .doc-delete {
  margin-left: auto;
}

/* Print media */
@media print {
  header, nav, .lm-toolbar, .sched-toolbar, button, input {
    display: none;
  }

  /* The blanket "input { display:none }" above also hid the Done
     checkboxes on the Inspections & Punch List roster -- those are
     meaningful state (was this actually finished?), not a form control,
     so they stay visible on a printed copy same as any other checked box
     on a paper form would. */
  .data-row input[type="checkbox"] {
    display: inline-block !important;
    opacity: 1 !important;
  }

  #view-lotmatrix th.lm-lot-col, #view-lotmatrix td.lm-lot-col,
  #view-lotmatrix th.lm-print-col, #view-lotmatrix td.lm-print-col,
  #view-lotmatrix th.lm-addr-col, #view-lotmatrix td.lm-addr-col {
    position: static;
  }

  #view-lotmatrix .lm-print-col {
    display: none;
  }

  .print-hide-temp {
    display: none !important;
  }

  /* button.icon (Print/Email/Export/Send Out, plus each row's delete ✕ and
     photo 📷 buttons) sets its own display:inline-flex with higher
     specificity than the plain "button { display:none }" above, so it was
     winning right through the blanket hide and showing up on the printed
     page. Called out explicitly here so it actually goes away. */
  .dl-toolbar button, .data-row button.icon {
    display: none !important;
  }

  /* A printed Inspections & Punch List is a working punch sheet -- items
     already marked complete don't need to take up paper alongside what's
     still outstanding. */
  .data-row.done {
    display: none !important;
  }

  .print-logo-banner {
    display: flex !important;
    justify-content: center;
    margin-bottom: 12px;
  }

  .print-logo-banner img {
    height: 60px;
    width: auto;
  }
}

.print-logo-banner {
  display: none;
}

/* ============ DAILY LOGS & TO-DO'S ============ */

.notes-todo-columns {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  max-width: 1100px;
}

.todo-panel {
  flex: 1;
  min-width: 260px;
}

.notes-panel {
  flex: 2;
  min-width: 0;
}

.notes-todo-columns h3 {
  font-size: 15px;
  margin-bottom: 10px;
  color: var(--text);
}

/* Daily Logs and To-Do's are now separate tabs, each full-width (the old
   side-by-side .notes-todo-columns layout didn't work on phones). 700px
   was applied unconditionally though, so on a real desktop screen the
   whole tab looked like a narrow phone-width column floating in a sea of
   empty space -- widen it substantially once there's room to. */
.notes-panel-full, .todo-panel-full {
  max-width: 700px;
}

@media (min-width: 900px) {
  .notes-panel-full, .todo-panel-full {
    max-width: 1100px;
  }
}

.day-block {
  margin-bottom: 20px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  box-shadow: var(--shadow-sm);
}

.day-block-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}

.dl-collapse-btn, .dl-collapse-btn:hover {
  background: none;
  border: none;
  color: inherit;
  font-size: 13px;
  padding: 0;
  min-width: 32px;
  min-height: 32px;
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}

.day-block.collapsed .day-block-header,
.week-block.collapsed .week-block-header {
  margin-bottom: 0;
  border-bottom: none;
  padding-bottom: 10px;
}

.day-block.collapsed .day-block-entries,
.week-block.collapsed .week-block-days {
  display: none;
}

/* Entries within a day-block are rows in one card, not separate boxes --
   each one is only divided from the next by a hairline. */
.day-block .log-entry {
  background: none;
  border: none;
  border-radius: 0;
  padding: 10px 0;
  margin-bottom: 0;
  border-bottom: 1px solid var(--border);
}

.day-block .log-entry:last-child {
  border-bottom: none;
  padding-bottom: 2px;
}

.dl-add-form {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.dl-add-form input[type="text"] {
  flex: 1;
  min-width: 160px;
}

.dl-add-form-stacked {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}

.dl-add-form-stacked textarea {
  width: 100%;
  resize: vertical;
  font-family: inherit;
}

.dl-note-form-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.dl-delay-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  width: fit-content;
}

.dl-delay-fields {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* The `hidden` attribute and this class have equal CSS specificity, so
   without this the display:flex above would win and the fields would stay
   visible even while hidden is set. */
.dl-delay-fields[hidden] {
  display: none;
}

.dl-delay-fields select {
  flex: 1;
  min-width: 200px;
}

.dl-delay-fields input[type="number"] {
  width: 130px;
}

.dl-file-label {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.dl-photo-count {
  font-size: 12px;
  color: var(--text-dim);
}

.dl-spacer {
  flex: 1;
}

.dl-search-row {
  margin-bottom: 10px;
}

.dl-search-input {
  width: 100%;
  max-width: 420px;
}

.dl-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

/* Every direct child gets its own row-wrap slot rather than being
   squeezed by flexbox's default shrink -- that squeeze is what used to
   clip the Week/Month segments off the end of .dl-view-toggle (its own
   overflow:hidden hid the excess instead of the row wrapping). */
.dl-toolbar > * {
  flex-shrink: 0;
}

.dl-loading, .dl-empty {
  color: var(--text-dim);
  font-size: 13px;
  padding: 12px 0;
}

/* Plat auto-detect "thinking" state -- a real, visible sign AI work is in
   progress, not just text easy to miss (Nick's request, 2026-08-28: he
   wasn't sure whether it was safe to click Next while it was still
   reading). Spinner + accent-colored text while a read is in flight;
   plain dimmed text once it resolves either way. */
.plat-parse-status.thinking {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--accent);
  font-size: 13px;
  padding: 12px 0;
}

.plat-parse-spinner {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: plat-parse-spin 0.8s linear infinite;
}

@keyframes plat-parse-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .plat-parse-spinner {
    animation: none;
  }
}

.dl-view-toggle {
  display: flex;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

@media (max-width: 640px) {
  /* The spacer only makes sense pinning Print/Export to the far right of
     a single wide row -- once the toolbar wraps onto multiple narrow
     rows it just leaves a confusing gap, so drop it and let everything
     flow left-aligned instead. */
  .dl-toolbar .dl-spacer {
    display: none;
  }
}

.dl-view-toggle button {
  border: none;
  border-radius: 0;
  background: var(--panel2);
  color: var(--text-dim);
  padding: 6px 12px;
  font-size: 12px;
  cursor: pointer;
}

.dl-view-toggle button + button {
  border-left: 1px solid var(--border);
}

.dl-view-toggle button.active {
  background: var(--accent);
  color: #fff;
}

/* Week/Month rollup -- a bigger card wrapping one or more day-blocks, so
   entries stay grouped by day underneath (drillable) instead of losing
   which specific day something happened on. */
.week-block {
  margin-bottom: 20px;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px;
}

.week-block-header {
  display: flex;
  align-items: baseline;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 2px solid var(--accent);
}

.week-block-count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 400;
  color: var(--text-dim);
  text-transform: none;
  letter-spacing: 0;
}

.day-block.day-block-nested {
  margin-bottom: 10px;
}

.day-block.day-block-nested:last-child {
  margin-bottom: 0;
}

.todo-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Split into two sub-rows (checkbox+text, then the priority/due/photo/
   delete controls) instead of one long flex row -- on a narrow phone
   screen, cramming all of that plus the task text into a single
   non-wrapping row left almost no width for the text itself, so it
   wrapped one or two words per line into a very tall, hard-to-read
   column. Both sub-rows stay side by side on desktop where there's
   room; only the mobile breakpoint below stacks them. */
.todo-item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  padding: 8px 6px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.todo-item-main {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 140px;
}

.todo-item-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.todo-item.done .todo-text {
  text-decoration: line-through;
  color: var(--text-dim);
}

.todo-text {
  flex: 1;
  min-width: 0;
}

.todo-due {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}

.todo-due.due-today {
  color: var(--danger);
  font-weight: 700;
}

.todo-due-input {
  font-size: 11px;
  padding: 3px 4px;
  width: 128px;
  flex-shrink: 0;
}

.todo-due-input.due-today {
  border-color: var(--danger);
  color: var(--danger);
}

/* Priority color scheme: 1 = red (most urgent), 2 = yellow, 3 = no color.
   Sort order (P3 first) is unrelated and unchanged -- see ORDER BY CAST
   (priority AS INTEGER) DESC in src/index.js; auto-created overdue alerts
   are still created as P3, which just means they land at the top of the
   list without a color flag by default. */
.todo-item.priority-1 {
  background: rgba(193, 85, 77, 0.12);
  border-left: 3px solid var(--danger);
  padding-left: 3px;
}

.todo-item.priority-2 {
  border-left: 3px solid var(--milestone);
  padding-left: 3px;
}

.todo-priority-select {
  font-size: 11px;
  font-weight: 700;
  padding: 2px 4px;
  width: 48px;
  flex-shrink: 0;
}

.todo-priority-select.priority-1 {
  background: var(--danger);
  color: #fff;
  border-color: var(--danger);
}

.todo-priority-select.priority-2 {
  background: var(--milestone);
  color: #332b06;
  border-color: var(--milestone);
}

@media (max-width: 640px) {
  /* Stack the two sub-rows so the task text gets the full screen width
     to wrap normally, with the priority/due/photo/delete controls on
     their own row underneath instead of squeezed alongside it. */
  .todo-item {
    flex-direction: column;
    align-items: stretch;
  }
  .todo-item-controls {
    flex-wrap: wrap;
    padding-left: 26px; /* lines up under the text, past the checkbox */
  }
  .todo-due-input {
    width: auto;
    flex: 1 1 110px;
  }
}

.notes-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 70vh;
  overflow-y: auto;
}

.log-entry {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}

.entry-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}

.entry-time {
  font-size: 12px;
  color: var(--text-dim);
}

.entry-actions {
  display: flex;
  gap: 6px;
}

.entry-text {
  font-size: 13px;
  white-space: pre-wrap;
  margin-bottom: 6px;
}

.photo-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.photo-item {
  position: relative;
  width: 84px;
  height: 84px;
}

.photo-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
  cursor: pointer;
  display: block;
}

.photo-remove {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 26px;
  height: 26px;
  min-width: unset;
  min-height: unset;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
}

@media (max-width: 800px) {
  .notes-todo-columns {
    flex-direction: column;
  }

  .todo-panel, .notes-panel {
    width: 100%;
  }
}

/* Responsive */
@media (max-width: 900px) {
  th.col-7, td.col-7 { width: 140px; }
  th.col-6, td.col-6 { width: 90px; }
}

@media (max-width: 600px) {
  main {
    padding: var(--spacing-sm);
  }
}

.rt-plat-link-row {
  margin: -6px 0 10px;
}

.rt-plat-link {
  font-size: 12px;
  color: var(--accent);
  text-decoration: none;
}

.rt-plat-link:hover {
  text-decoration: underline;
}

/* ============ CREATE NEW PROJECT ============ */

.modal-wide {
  min-width: 560px;
}

@media (max-width: 640px) {
  .modal-wide {
    min-width: 0;
    width: 100%;
  }
}

.project-type-choices {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}

.project-type-choice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  text-align: left;
  padding: 14px 16px;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.project-type-choice:hover {
  border-color: var(--accent);
  background: var(--panel);
}

.project-type-choice strong {
  font-size: 15px;
  color: var(--text);
}

.project-type-choice span {
  font-size: 12px;
  color: var(--text-dim);
}

.street-config-header {
  display: grid;
  grid-template-columns: 2fr 70px 110px 32px;
  gap: 8px;
  font-size: 11px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 2px 4px;
}

.street-config-row {
  display: grid;
  grid-template-columns: 2fr 70px 110px 32px;
  gap: 8px;
  margin-bottom: 8px;
  align-items: center;
}

.street-config-row input[type="text"],
.street-config-row input[type="numeric"] {
  min-height: 38px;
  padding: 6px 8px;
  font-size: 13px;
}

.warranty-boxes {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.warranty-box {
  flex: 1;
  min-width: 220px;
  border-radius: 8px;
  padding: 12px 16px;
  border: 1px solid;
}

.warranty-box.active {
  background: rgba(74, 156, 74, 0.12);
  border-color: var(--success);
}

.warranty-box.expired {
  background: rgba(197, 60, 60, 0.12);
  border-color: var(--danger);
}

.warranty-box-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin-bottom: 4px;
}

.warranty-box.active .warranty-box-date {
  color: var(--success);
  font-weight: 700;
}

.warranty-box.expired .warranty-box-date {
  color: var(--danger);
  font-weight: 700;
}

.page-subtitle {
  color: var(--text-dim);
  font-size: 13px;
  margin: -8px 0 14px;
}

.rt-plat-cross-hint {
  background: rgba(217, 164, 65, 0.12);
  border: 1px solid rgba(217, 164, 65, 0.4);
  border-radius: 6px;
  color: #d9a441;
  font-size: 13px;
  font-weight: 600;
  padding: 10px 12px;
  margin-bottom: 10px;
}

.street-cross-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  cursor: pointer;
}
.street-cross-label input[type="checkbox"] {
  min-height: 0;
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
}

@media (max-width: 640px) {
  .street-config-header {
    display: none;
  }
  .street-config-row {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "name name" "lots cross" "remove remove";
    gap: 6px;
    padding: 8px;
    background: var(--panel2);
    border-radius: 6px;
  }
  .street-config-row .street-name { grid-area: name; }
  .street-config-row .street-lots { grid-area: lots; }
  .street-config-row .street-cross-label { grid-area: cross; }
  .street-config-row .street-remove { grid-area: remove; justify-self: end; }
}

/* ---- Project Daily Notes (Project Home) -- see daily-logs.js ---- */
.project-daily-log {
  margin-top: 18px;
}
.project-daily-log .dropzone {
  margin-top: 10px;
}
.pdl-transcribing {
  color: var(--text-dim);
  font-style: italic;
}
.pdl-error {
  color: var(--danger);
}
.pdl-attachments {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.pdl-audio-link {
  color: var(--accent);
  text-decoration: none;
  font-size: 13px;
}
.pdl-audio-link:hover {
  text-decoration: underline;
}
.link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}
.link-btn:hover {
  opacity: 0.85;
}
.pdl-transcript-box {
  margin-top: 8px;
  padding: 10px 12px;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
  white-space: pre-wrap;
  max-height: 260px;
  overflow-y: auto;
}
.pdl-edit-row {
  margin-top: 4px;
}
.pdl-edit-textarea {
  width: 100%;
  resize: vertical;
}
.pdl-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}
