:root {
  --topnav-bg: #1e2530;
  --topnav-text: #adb5bd;
  --topnav-active-bg: rgba(13, 110, 253, 0.85);
  --topnav-hover-bg: rgba(255, 255, 255, 0.08);
  --topnav-height: 52px;
}

html {
  font-size: 14px;
}

@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

/* App-shell layout: a column flexbox -- topnav, mobile-nav, main -- where only `main`
   (flex: 1 1 auto) grows/scrolls; the navs stay their natural size and sit flush against
   the top. `min-height: 0` on main is required: flex children default to
   min-height:auto (at least as tall as their content), which silently defeats
   `flex: 1 1 auto` and would let content scroll the whole page instead of just `main`. */
/* 100% (not 100dvh): iOS home-screen web apps (standalone PWAs) have a WebKit bug
   where dvh units compute incorrectly, sometimes to 0. With body overflow hidden,
   a 0-height body renders the whole app as a blank window after launch. Percentage
   heights resolve against the real viewport in every mode, including standalone. */
html, body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Inherited from the removed footer, which used to be what kept content clear of the
     iPhone home-indicator bar in standalone mode. It lives on body rather than main
     because main carries Bootstrap's .p-3/.p-md-4 utilities, and a class selector beats
     an element selector -- a padding-bottom set on `main` would simply be overridden.
     body has no utility classes, and with border-box sizing this carves the inset out of
     its height: 100%, so the whole shell sits above the indicator. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

main {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Reserve the scrollbar's width whether or not one is showing. Without this, `auto`
     adds the scrollbar only on pages tall enough to need it, which changes main's content
     width and shifts everything inside it sideways -- most visibly the fixed-width
     .page-sidebar on Reports and Settings, which appeared to move depending on what was
     in the content pane next to it (a short General tab vs. a long Audit Log). The
     sidebar was never the thing changing; the space it was being laid out in was.
     No effect on iOS/Android, where scrollbars overlay rather than take up space. */
  scrollbar-gutter: stable;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
  color: var(--bs-secondary-color);
  text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
  text-align: start;
}

/* Subtle elevation on every card, matching a flatter/less "Bootstrap-default" feel */
.card {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  border: 1px solid var(--bs-border-color);
}

/* Top navigation: a hand-rolled dark slate bar (not Bootstrap's .navbar component)
   so it stays a consistent dark shell regardless of light/dark content theme.
   No sticky/fixed positioning needed -- it's a fixed-height flex child outside
   main's scrolling region (see the app-shell rules above), so it can never be
   scrolled behind. */
#topnav {
  background: var(--topnav-bg);
  /* Safe-area padding: in an installed (standalone) iOS web app with a
     black-translucent status bar, content draws under the clock/notch. Padding the
     bar down by the inset keeps the nav tappable; env() resolves to 0 in normal
     browser tabs so nothing changes there. Needs viewport-fit=cover in the meta. */
  height: calc(var(--topnav-height) + env(safe-area-inset-top, 0px));
  padding-top: env(safe-area-inset-top, 0px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.topnav-inner {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0 1rem;
}

.topnav-brand {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  margin-right: 1rem;
  white-space: nowrap;
}

.topnav-brand:hover {
  color: #fff;
}

#topnav-links {
  display: flex;
  align-items: center;
  height: var(--topnav-height);
  gap: 0.15rem;
}

/* Fixed pixel height, not derived from font/line-height or rem -- so it can't shift
   with font metrics, load timing, or the html-root font-size breakpoint. */
#topnav-links a {
  display: flex;
  align-items: center;
  height: 36px;
  color: var(--topnav-text);
  text-decoration: none;
  padding: 0 0.75rem;
  border-radius: 6px;
  font-size: 0.92rem;
  white-space: nowrap;
}

#topnav-links a:hover {
  background: var(--topnav-hover-bg);
  color: #fff;
}

/* Current section, matched on controller so sub-pages stay highlighted. Same rule
   shape as #mobile-nav a.active below, so both menus signal position identically. */
#topnav-links a.active {
  background: var(--topnav-active-bg);
  color: #fff;
  font-weight: 500;
}

/* Fixed icon column keeps labels optically aligned in both menus. */
#topnav-links a i,
#mobile-nav a i {
  width: 1.15rem;
  text-align: center;
  margin-right: 0.45rem;
  flex: none;
}

.topnav-sep {
  flex-grow: 1;
}

.topnav-user {
  color: var(--topnav-text);
  font-size: 0.85rem;
  white-space: nowrap;
}

.topnav-divider {
  width: 1px;
  height: 22px;
  background: rgba(255, 255, 255, 0.15);
  margin: 0 0.5rem;
}

.topnav-icon-btn {
  background: none;
  border: none;
  color: var(--topnav-text);
  width: 34px;
  height: 34px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
}

.topnav-icon-btn:hover {
  background: var(--topnav-hover-bg);
  color: #fff;
}

#topnav-toggle {
  display: none;
}

/* Mobile slide-down nav panel (below 768px), replacing the inline link list */
#mobile-nav {
  display: none;
  background: var(--topnav-bg);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  padding: 0.5rem 1rem 0.75rem;
}

#mobile-nav.open {
  display: block;
}

#mobile-nav a {
  display: flex;
  align-items: center;
  color: var(--topnav-text);
  text-decoration: none;
  padding: 0.55rem 0.5rem;
  border-radius: 6px;
  font-size: 0.95rem;
}

#mobile-nav a:hover {
  background: var(--topnav-hover-bg);
  color: #fff;
}

#mobile-nav a.active {
  background: var(--topnav-active-bg);
  color: #fff;
  font-weight: 500;
}

.mobile-nav-user {
  color: var(--topnav-text);
  font-size: 0.82rem;
  padding: 0.5rem 0.5rem 0;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  margin-top: 0.4rem;
}

@media (max-width: 767px) {
  #topnav-links {
    display: none !important;
  }

  #topnav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--topnav-text);
    width: 36px;
    height: 36px;
    border-radius: 6px;
    font-size: 1.2rem;
  }

  #topnav-toggle:hover {
    background: var(--topnav-hover-bg);
    color: #fff;
  }
}

/* Settings page: left sidenav + content, collapsing to a select dropdown on mobile */
.page-layout {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
}

.page-sidebar {
  width: 190px;
  flex-shrink: 0;
  position: sticky;
  /* Must be 0, not a positive offset. main's scrollport padding box already starts 24px
     down (its .p-md-4), which is where this sidebar naturally sits -- so a `top: 1rem`
     put the sticky threshold 16px *below* the natural position, and sticky responded by
     pushing the sidebar down to meet it. How much of that 16px actually materialized
     depended on how much scroll room the content pane had: a tall tab (Settings >
     Business Info) got the full shift, a short one (Scheduling, only 9px of overflow)
     couldn't move at all. Hence a sidebar that appeared to jump ~16px purely based on
     what was next to it. At 0 the threshold coincides with the natural position, so the
     sidebar never moves at rest and still pins correctly once you scroll -- main's
     padding keeps the visual breathing room the offset was reaching for. */
  top: 0;
}

.page-sidenav {
  width: 100%;
}

.page-sidenav .nav-link {
  border-radius: 6px;
  padding: 0.42rem 0.7rem;
  font-size: 0.9rem;
  color: var(--bs-body-color);
}

.page-sidenav .nav-link.active {
  background: rgba(13, 110, 253, 0.12);
  color: #0d6efd;
  font-weight: 500;
}

.page-sidenav-section {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--bs-secondary-color);
  margin: 0.75rem 0.7rem 0.25rem;
}

.page-content {
  flex-grow: 1;
  min-width: 0;
}

.page-sidenav-select {
  display: none;
}

@media (max-width: 767px) {
  .page-layout {
    flex-direction: column;
  }

  .page-sidebar {
    position: static;
    width: 100%;
  }

  .page-sidenav {
    display: none;
  }

  .page-sidenav-select {
    display: block;
    margin-bottom: 1rem;
  }
}

/* Mobile list: the card-per-row alternative to a wide table on small screens.
   Pair with a <table class="d-none d-md-table ..."> for the desktop view, and
   wrap this list in <div class="d-md-none">. Never let both render at once. */
.mobile-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.mobile-list-item {
  border: 1px solid var(--bs-border-color);
  border-radius: 8px;
  padding: 0.75rem 0.9rem;
}

.mobile-list-item-title {
  font-weight: 600;
  margin-bottom: 0.25rem;
}

.mobile-list-item-meta {
  font-size: 0.85rem;
  color: var(--bs-secondary-color);
  margin-bottom: 0.5rem;
}

.mobile-list-item-badges {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}

.mobile-list-item-actions {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.mobile-list-item-actions form {
  flex: 1 1 auto;
}

.mobile-list-item-actions .btn,
.mobile-list-item-actions form .btn {
  width: 100%;
}

/* Theme-aware replacement for Bootstrap's `.table-light` thead, which hardcodes a
   light background/text regardless of the active theme and goes unreadable in
   dark mode. Uses the same Bootstrap CSS variables the rest of the app themes off. */
.table-header-themed {
  --bs-table-color: var(--bs-body-color);
  --bs-table-bg: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  background-color: var(--bs-tertiary-bg);
}

/* Month schedule view: a 7-column CSS grid, theme-aware via Bootstrap CSS variables
   so it matches light/dark mode without its own overrides. */
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--bs-border-color);
  border: 1px solid var(--bs-border-color);
  border-radius: 8px;
  overflow: hidden;
}

.calendar-weekday {
  background: var(--bs-tertiary-bg);
  padding: 0.4rem;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--bs-secondary-color);
}

.calendar-cell.drop-target-active,
.agenda-column-body.drop-target-active {
  outline: 2px dashed var(--bs-primary);
  outline-offset: -2px;
  background: var(--bs-primary-bg-subtle, rgba(13, 110, 253, 0.08));
}

.calendar-cell {
  background: var(--bs-body-bg);
  min-height: 100px;
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.calendar-cell.other-month {
  background: var(--bs-tertiary-bg);
}

.calendar-cell.other-month .calendar-day-number {
  color: var(--bs-secondary-color);
}

/* .calendar-cell itself is flex-direction: column, so its direct children each get
   their own row -- the day number and the map-route button used to be two separate
   direct children and so ended up stacked on their own lines instead of side by side.
   Grouping them in this one row wrapper keeps them a single flex item (one row) from
   the cell's point of view, while the job entries below still stack normally. */
.calendar-cell-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.calendar-day-number {
  font-size: 0.8rem;
  font-weight: 600;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.calendar-cell.today .calendar-day-number {
  background: #0d6efd;
  color: #fff;
  border-radius: 50%;
}

.calendar-job-entry {
  font-size: 0.7rem;
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  background: rgba(13, 110, 253, 0.12);
  color: var(--bs-body-color);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}

.calendar-job-entry:hover {
  background: rgba(13, 110, 253, 0.22);
}

@media (max-width: 767px) {
  .calendar-cell {
    min-height: 64px;
    padding: 0.2rem;
  }

  .calendar-job-entry {
    font-size: 0.62rem;
  }
}

/* 5-Day schedule view: Outlook-style side-by-side day columns. */
.agenda-columns {
  display: grid;
  grid-template-columns: repeat(var(--agenda-day-count, 5), minmax(0, 1fr));
  gap: 1px;
  background: var(--bs-border-color);
  border: 1px solid var(--bs-border-color);
  border-radius: 8px;
  overflow: hidden;
}

.agenda-column {
  background: var(--bs-body-bg);
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.agenda-column-header {
  background: var(--bs-tertiary-bg);
  padding: 0.5rem 0.4rem;
  text-align: center;
  border-bottom: 1px solid var(--bs-border-color);
}

.agenda-column-header.today {
  background: rgba(13, 110, 253, 0.12);
}

.agenda-column-weekday {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--bs-secondary-color);
}

.agenda-column-daynum {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-top: 0.15rem;
  font-size: 0.95rem;
  font-weight: 600;
}

.agenda-column-header.today .agenda-column-daynum {
  background: #0d6efd;
  color: #fff;
  border-radius: 50%;
}

.agenda-column-body {
  flex: 1 1 auto;
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-height: 240px;
}

.agenda-event {
  display: block;
  background: rgba(13, 110, 253, 0.1);
  border-left: 3px solid #0d6efd;
  border-radius: 4px;
  padding: 0.35rem 0.5rem;
  color: var(--bs-body-color);
}

.agenda-event:hover {
  background: rgba(13, 110, 253, 0.2);
}

.agenda-event-time {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
}

.agenda-event-title {
  font-size: 0.8rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.agenda-event-meta {
  font-size: 0.7rem;
  color: var(--bs-secondary-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Fancy loading spinner used by any AJAX-loaded panel (suggested slots, scheduling
   assistant modal, etc.) -- a dual-ring CSS spinner instead of the plain Font Awesome
   fa-spin icon, dropped in via the shared _LoadingSpinner partial. */
.loading-mask {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 1.5rem 0.5rem;
  text-align: center;
}

.loading-spinner {
  --loading-spinner-size: 40px;
  width: var(--loading-spinner-size);
  height: var(--loading-spinner-size);
  position: relative;
}

.loading-spinner::before,
.loading-spinner::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid transparent;
}

.loading-spinner::before {
  border-top-color: #0d6efd;
  border-right-color: #0d6efd;
  animation: loading-spinner-spin 0.9s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}

.loading-spinner::after {
  border-bottom-color: rgba(13, 110, 253, 0.3);
  border-left-color: rgba(13, 110, 253, 0.3);
  animation: loading-spinner-spin 1.4s cubic-bezier(0.5, 0, 0.5, 1) infinite reverse;
}

.loading-mask-text {
  font-size: 0.8rem;
  color: var(--bs-secondary-color);
}

@keyframes loading-spinner-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Color-key legend for the daily route map when more than one person's route is shown
   at once (dashboard "Today's route", calendar day maps). */
.daily-map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-top: 0.5rem;
  font-size: 0.8rem;
}

.daily-map-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--bs-secondary-color);
}

.daily-map-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
}

/* "N jobs that day" chip on the suggested-slots panel -- a <span> (not <button>, see
   comment in _SuggestedSlots.cshtml) styled/behaving like a small clickable pill. */
.job-count-chip {
  cursor: pointer;
}

.job-count-chip:hover,
.job-count-chip:focus-visible {
  background-color: var(--bs-secondary-color) !important;
}

/* Suggested-times panel (Schedule form): per-person group header with an initials
   avatar, and the "N only" active-filter chip. */
.suggested-slots-group-header {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}

.suggested-slots-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  font-size: 0.65rem;
  font-weight: 700;
  flex: none;
}

.clear-suggest-filter {
  cursor: pointer;
  font-weight: 700;
}

/* Settings > General: groups a toggle with its dependent field(s) into one visually
   contained block instead of two loose, unrelated-looking form rows. */
.settings-subsection {
  padding: 0.85rem 1rem;
  background: var(--bs-tertiary-bg, var(--bs-secondary-bg));
  border: 1px solid var(--bs-border-color);
  border-radius: 0.375rem;
}

/* ---- Client profile page ---- */

/* Identity header: initials avatar + name + inline contact chips. Contact facts live
   here as chips (phone/email/address) instead of a separate labeled card, so the page
   opens with who the person is and how to reach them in one glance. */
.client-hero {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

.client-avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(13, 110, 253, 0.12);
  color: #0d6efd;
  font-weight: 700;
  font-size: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.client-contact-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  margin-top: 0.35rem;
  font-size: 0.9rem;
}

.client-contact-chips a,
.client-contact-chips span {
  color: var(--bs-secondary-color);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.client-contact-chips a:hover {
  color: var(--bs-body-color);
}

/* One-card stat strip: cells divided by hairlines rather than four floating cards,
   so the numbers read as a single band of facts. */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.stat-strip-cell {
  padding: 0.85rem 1.25rem;
  border-left: 1px solid var(--bs-border-color);
}

.stat-strip-cell:first-child {
  border-left: none;
}

.stat-strip-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--bs-secondary-color);
}

.stat-strip-value {
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1.3;
}

@media (max-width: 767px) {
  .stat-strip {
    grid-template-columns: repeat(2, 1fr);
  }

  .stat-strip-cell {
    padding: 0.7rem 1rem;
  }

  /* 2x2 wrap: the third cell starts a new row, so its left hairline would sit on the
     card edge. Drop it and draw a top hairline across the second row instead. */
  .stat-strip-cell:nth-child(3) {
    border-left: none;
  }

  .stat-strip-cell:nth-child(n + 3) {
    border-top: 1px solid var(--bs-border-color);
  }
}

/* Job rows: one responsive flex list used at every breakpoint, replacing the old
   desktop-table + mobile-card double markup. */
.job-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.8rem 1rem;
  color: var(--bs-body-color);
  text-decoration: none;
}

.job-row:hover {
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.job-row-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.job-row-main {
  flex: 1 1 auto;
  min-width: 0;
}

.job-row-title {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.job-row-meta {
  font-size: 0.82rem;
  color: var(--bs-secondary-color);
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.9rem;
}

.job-row-side {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

.job-row-chevron {
  color: var(--bs-secondary-color);
  font-size: 0.8rem;
}

@media (max-width: 575px) {
  .job-row-icon {
    display: none;
  }
}

/* Activity feed: a vertical timeline. Each entry gets an icon dot; a connector line
   runs through the dot column so the sequence reads as one thread. */
.activity-feed {
  padding: 0.25rem 0;
}

.activity-item {
  position: relative;
  display: flex;
  gap: 0.9rem;
  padding: 0.65rem 1.25rem;
}

.activity-item::before {
  content: "";
  position: absolute;
  left: calc(1.25rem + 15px);
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--bs-border-color);
}

.activity-item:first-child::before {
  top: 1rem;
}

.activity-item:last-child::before {
  bottom: calc(100% - 1rem);
}

.activity-dot {
  position: relative;
  z-index: 1;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
  font-size: 0.78rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  margin-top: 0.1rem;
}

.activity-dot.activity-dot-email {
  background: rgba(13, 202, 240, 0.15);
  color: #0aa2c0;
  border-color: rgba(13, 202, 240, 0.35);
}

.activity-body {
  flex: 1 1 auto;
  min-width: 0;
}

.activity-text {
  font-size: 0.92rem;
}

.activity-meta {
  font-size: 0.78rem;
  color: var(--bs-secondary-color);
}

/* Job stage tracker: a horizontal pipeline showing where the job sits in the
   Lead > Quote > Signed > Scheduled > In Progress > Complete > Invoiced > Paid flow.
   Steps before the current stage get a check, the current one is highlighted, the
   rest stay muted. Scrolls horizontally on narrow screens instead of wrapping. */
.stage-tracker {
  display: flex;
  align-items: flex-start;
  overflow-x: auto;
  padding: 1rem 0.75rem 0.75rem;
}

.stage-step {
  flex: 1 1 0;
  min-width: 74px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  position: relative;
}

.stage-step::before,
.stage-step::after {
  content: "";
  position: absolute;
  top: 11px;
  height: 2px;
  width: 50%;
  background: var(--bs-border-color);
}

.stage-step::before {
  left: 0;
}

.stage-step::after {
  right: 0;
}

.stage-step:first-child::before,
.stage-step:last-child::after {
  display: none;
}

.stage-step.done::before,
.stage-step.done::after,
.stage-step.current::before {
  background: #198754;
}

.stage-dot {
  position: relative;
  z-index: 1;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bs-body-bg);
  border: 2px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
  font-size: 0.62rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.stage-step.done .stage-dot {
  background: #198754;
  border-color: #198754;
  color: #fff;
}

.stage-step.current .stage-dot {
  border-color: #0d6efd;
  background: #0d6efd;
  color: #fff;
  box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.2);
}

.stage-label {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}

.stage-step.current .stage-label {
  color: var(--bs-body-color);
}

/* ---- County data info icon ---- */
/* Sits inline next to a client/job address. Styled as a bare icon button rather than a
   .btn so it doesn't compete visually with the address link it annotates. */

.parcel-info-btn {
  background: none;
  border: 0;
  padding: 0 0 0 0.35rem;
  line-height: 1;
  color: var(--bs-primary);
  cursor: pointer;
  font-size: 0.85em;
  vertical-align: baseline;
}

.parcel-info-btn:hover,
.parcel-info-btn:focus {
  color: var(--bs-link-hover-color);
}

/* Muted variant: the address is out of area or had no match. Deliberately low-contrast --
   it's an explanation available on demand, not something to draw the eye. */
.parcel-info-btn-muted {
  color: var(--bs-secondary-color);
}

.parcel-info-btn-muted:hover,
.parcel-info-btn-muted:focus {
  color: var(--bs-body-color);
}

.parcel-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.75rem;
  margin: 0;
  font-size: 0.85rem;
}

.parcel-facts dt {
  font-weight: 600;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}

.parcel-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.parcel-source {
  margin-top: 0.5rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--bs-border-color);
  font-size: 0.75rem;
  color: var(--bs-secondary-color);
}

/* Workflow section labels on the job page: the left column is ordered to match the
   stage tracker (sell it, do it, bill it), and these headings make that sequence
   visible instead of leaving it as an unlabelled stack of cards. */
.workflow-section {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.75rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.workflow-section-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  font-size: 0.72rem;
  flex: none;
}

/* Job photo grid. auto-fill keeps the tiles a consistent size and simply reflows to
   fewer columns on narrow screens, so one shared rule covers phone through desktop. */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.75rem;
}

.photo-tile {
  margin: 0;
  border: 1px solid var(--bs-border-color);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bs-body-bg);
}

.photo-tile-btn {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--bs-tertiary-bg);
  cursor: zoom-in;
  line-height: 0;
}

.photo-tile-btn img {
  width: 100%;
  /* Square crop so a mix of portrait and landscape phone shots still forms an even
     grid; the viewer shows the whole uncropped image. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.photo-tile-zoom {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(0, 0, 0, 0.35);
  opacity: 0;
  transition: opacity 0.15s ease-in-out;
}

.photo-tile-btn:hover .photo-tile-zoom,
.photo-tile-btn:focus-visible .photo-tile-zoom {
  opacity: 1;
}

.photo-tile-caption {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.5rem;
  font-size: 0.75rem;
  border-top: 1px solid var(--bs-border-color);
}

.photo-tile-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--bs-secondary-color);
}

.photo-tile-actions {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex: none;
}

.photo-tile-action {
  border: 0;
  background: none;
  padding: 0.15rem 0.25rem;
  color: var(--bs-secondary-color);
  line-height: 1;
}

.photo-tile-action:hover {
  color: var(--bs-body-color);
}

/* Photo viewer. The body is a dark stage so a photo of any aspect ratio sits on a
   neutral background instead of against the card surface. */
.photo-viewer-body {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 40vh;
  padding: 0;
  background: #101114;
}

.photo-viewer-body img {
  max-width: 100%;
  /* Bounded by the viewport, not the image, so a tall photo scrolls the page as
     little as possible and the nav arrows stay reachable. */
  max-height: 78vh;
  object-fit: contain;
}

.photo-viewer-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.photo-viewer-nav:hover {
  background: rgba(0, 0, 0, 0.75);
}

.photo-viewer-nav.prev {
  left: 0.75rem;
}

.photo-viewer-nav.next {
  right: 0.75rem;
}

/* Shared confirm/alert dialog (_ConfirmDialog.cshtml). The icon tints to the severity
   of the action so a delete reads differently from a routine confirmation. */
.crm-dialog-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  flex: none;
  font-size: 1.1rem;
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
}

.crm-dialog-icon-primary {
  background: rgba(var(--bs-primary-rgb), 0.12);
  color: var(--bs-primary);
}

.crm-dialog-icon-danger {
  background: rgba(var(--bs-danger-rgb), 0.12);
  color: var(--bs-danger);
}

.crm-dialog-icon-warning {
  background: rgba(var(--bs-warning-rgb), 0.15);
  color: var(--bs-warning-text-emphasis, var(--bs-warning));
}

/* ---- Time clock ---- */

/* The punch panel is the one control a technician opens this app for, usually one-handed
   in a truck, so it gets the whole top of the page: current state, a live elapsed timer,
   and buttons sized for a thumb rather than a mouse. */
.punch-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 2rem;
}

.punch-state {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: none;
}

/* Filled dot carrying the state color, so status is readable before any text is. */
.punch-dot {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--bs-secondary-color);
  flex: none;
}

.punch-dot-on {
  background: var(--bs-success);
  /* Slow pulse: the only moving thing on the page, and only while the clock is actually
     running, so "I am on the clock right now" is obvious at a glance across a room. */
  animation: punch-pulse 2s ease-in-out infinite;
}

.punch-dot-break {
  background: var(--bs-warning);
}

@keyframes punch-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--bs-success-rgb), 0.5); }
  50% { box-shadow: 0 0 0 0.45rem rgba(var(--bs-success-rgb), 0); }
}

@media (prefers-reduced-motion: reduce) {
  .punch-dot-on {
    animation: none;
  }
}

.punch-state-label {
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.2;
}

.punch-state-sub {
  font-size: 0.8rem;
  color: var(--bs-secondary-color);
}

/* Tabular figures so the seconds ticking over doesn't shift the layout every second. */
.punch-elapsed {
  font-size: 2rem;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.punch-elapsed-label {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--bs-secondary-color);
}

.punch-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
}

.punch-actions .btn {
  padding: 0.6rem 1.1rem;
  font-weight: 600;
}

@media (max-width: 575px) {
  /* Phone: the buttons become a full-width stack of large targets rather than a wrapped
     row of half-width ones, which is what a tech is actually tapping. */
  .punch-actions {
    margin-left: 0;
    width: 100%;
  }

  .punch-actions form,
  .punch-actions .btn {
    width: 100%;
  }

  .punch-actions .btn {
    padding: 0.8rem 1rem;
    font-size: 1.05rem;
  }
}

/* Day grouping in the timesheet. A time card is read a day at a time -- someone can have
   two shifts in one day, and the day's total is the number that matters -- so days get a
   header band with their own total rather than the rows running together. */
.timesheet-day {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background: var(--bs-tertiary-bg);
  border-top: 1px solid var(--bs-border-color);
  font-size: 0.8rem;
}

.timesheet-day:first-child {
  border-top: none;
}

.timesheet-day-label {
  font-weight: 600;
  letter-spacing: 0.02em;
}

.timesheet-day-total {
  color: var(--bs-secondary-color);
  font-variant-numeric: tabular-nums;
}

/* Reused by the timesheet, the time-off lists, and any other "nothing here yet" state on
   these pages: muted icon, one line of plain language, and the action that fixes it. */
.empty-state {
  text-align: center;
  color: var(--bs-secondary-color);
  padding: 2.5rem 1rem;
}

.empty-state-icon {
  font-size: 1.75rem;
  opacity: 0.25;
  display: block;
  margin-bottom: 0.6rem;
}

/* Warning band shown when a manager is editing somebody else's card. Payroll edits on
   the wrong person are silent and hard to notice later, so the page says whose card it
   is every time it isn't yours. */
.viewing-other {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--bs-border-color);
  border-left: 3px solid var(--bs-warning);
  border-radius: 0.375rem;
  background: var(--bs-tertiary-bg);
  font-size: 0.9rem;
}
