: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);
}

/* A read-only address doubles as a "get directions" link everywhere it appears. In
   tables and list rows it keeps the surrounding text colour, so a column of addresses
   doesn't turn into a column of blue; the underline on hover is what says it's live.
   The contact chips above style their own anchors and win on specificity, so the chip
   variant keeps the look it already had. */
.address-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.address-link:hover,
.address-link:focus-visible {
  color: var(--bs-link-color);
  text-decoration: underline;
}

/* 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);
}

/* The status line under a step: the booked date, "Done", "Skipped". Only the quote
   visit carries one today -- the real stages are self-explanatory from their position,
   whereas an optional side-step has to say what became of it. Sized well below the
   label so it reads as annotation and doesn't widen the tracker's columns. */
.stage-sublabel {
  font-size: 0.6rem;
  line-height: 1.1;
  color: var(--bs-secondary-color);
  opacity: 0.8;
  white-space: nowrap;
}

/* ---- Optional step (the quote visit) ---- */
/* Drawn on the same rail as the stages but marked as a detour rather than a gate: a
   dashed ring says "this one is allowed not to happen", which is what stops a job
   quoted over the phone from looking like it skipped a required step. */
.stage-step-optional .stage-dot {
  border-style: dashed;
}

.stage-step-optional.done .stage-dot,
.stage-step-optional.current .stage-dot {
  border-style: solid;
}

/* Passed over: the job moved on without it. Muted and dashed, never blue -- the flow
   has left this behind, so it must not compete with the live step for attention. */
.stage-step.skipped .stage-dot {
  background: var(--bs-body-bg);
  border-color: var(--bs-border-color);
  color: var(--bs-secondary-color);
  opacity: 0.75;
}

/* Both of these sit behind the job's current position, so their connectors continue the
   completed rail. Without this the green line from Lead to Quote breaks at the visit
   marker and reads as a rendering fault rather than as meaning. */
.stage-step.skipped::before,
.stage-step.skipped::after,
.stage-step.upcoming::before,
.stage-step.upcoming::after {
  background: #198754;
}

/* Still booked, for a day that hasn't arrived, while the quote went out early. Neither
   done nor missed, so it gets the flow's own colour as an outline. */
.stage-step.upcoming .stage-dot {
  border-color: #0d6efd;
  border-style: dashed;
  color: #0d6efd;
}

/* ---- Next step bar ---- */
/* The job page's instruction line: what to do now, and the button that does it. Given
   the page's strongest visual weight on purpose -- it is the one thing a person needs to
   read to keep a job moving, and it has to win against a page full of cards. */
.next-step-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
  border: 1px solid rgba(13, 110, 253, 0.35);
  border-left: 4px solid #0d6efd;
  border-radius: var(--bs-border-radius-lg, 0.5rem);
  background: rgba(13, 110, 253, 0.07);
}

/* Nothing outstanding reads as settled rather than as another instruction, so a finished
   job doesn't keep shouting for attention it doesn't need. */
.next-step-bar.is-settled {
  border-color: rgba(25, 135, 84, 0.35);
  border-left-color: #198754;
  background: rgba(25, 135, 84, 0.07);
}

.next-step-icon {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #0d6efd;
  color: #fff;
  font-size: 1rem;
}

.next-step-bar.is-settled .next-step-icon {
  background: #198754;
}

.next-step-text {
  flex: 1 1 auto;
  min-width: 0;
}

.next-step-eyebrow {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.next-step-headline {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--bs-body-color);
}

.next-step-action {
  flex: 0 0 auto;
}

/* On a phone the button drops under the sentence and goes full width -- it's the thing
   being tapped, usually one-handed, often outside. */
@media (max-width: 575.98px) {
  .next-step-bar {
    flex-wrap: wrap;
  }

  .next-step-action,
  .next-step-action .btn,
  .next-step-action form {
    width: 100%;
  }
}

/* ---- 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;
  position: relative;
  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;
}

/* The quote a photo was attached to, sat over the thumbnail's corner. The caption
   row below is already tight with a file name and two actions, so a badge there
   truncated the name away entirely. */
.photo-tile-quote {
  position: absolute;
  top: 0.35rem;
  left: 0.35rem;
  z-index: 1;
  font-size: 0.65rem;
}

.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;
}

/* ---- ZIP-first address entry ---- */
/* The ZIP is the one field that fills two others, so it gets the visual weight: a wider
   box, a larger type size, and its own line at the top of the address block. The .form-
   control-lg sizing is not decoration -- it lands the font above 16px, which is what stops
   iOS from zooming the viewport when the field takes focus, and gives a gloved thumb at a
   customer's curb something to actually hit. */
.zip-input {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  max-width: 9rem;
}

/* Confirmation line under the ZIP. The min-height is reserved whether or not there's
   anything to say, so the fields below never jump as the answer arrives. */
.zip-lookup-status {
  min-height: 1.5rem;
  margin-top: 0;
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.zip-lookup-status--ok {
  color: var(--bs-success);
}

/* The ZIP resolved but disagrees with a city/state the user already typed. Amber, not
   red: nothing is wrong yet, there's just a choice to make. */
.zip-lookup-status--offer {
  color: var(--bs-warning-text-emphasis);
}

.zip-lookup-status--miss {
  color: var(--bs-secondary-color);
}

/* Brief tint on a field the ZIP just filled. Someone glancing at the form needs to see
   that two boxes changed without being made to hunt for them, and the color fades out so
   it never reads as a permanent state on the field. */
.zip-filled-flash {
  animation: zip-filled-flash 1.1s ease-out;
}

@keyframes zip-filled-flash {
  from {
    background-color: rgba(var(--bs-success-rgb), 0.22);
  }
  to {
    background-color: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .zip-filled-flash {
    animation: none;
  }
}

/* Multi-day booking editor (_ScheduleDaysEditor). Extra days hang off the form's own
   date row with a left rule, so they read as more of the same booking rather than a
   second, unrelated set of fields. */
.schedule-days-list:has([data-schedule-day-row]) {
    border-left: 3px solid var(--bs-primary-border-subtle, #b6d4fe);
    padding-left: 0.75rem;
}

.schedule-day-row + .schedule-day-row {
    margin-top: 0.25rem;
}

.schedule-day-remove {
    min-width: 2.5rem;
}

.schedule-days-summary {
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
}

.schedule-days-summary.text-danger {
    color: var(--bs-danger) !important;
}

/* "Day 2 of 3" chip on calendar rows for a multi-day booking. */
.multi-day-chip {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.2rem 0.4rem;
    border-radius: 999px;
    background: var(--bs-primary-bg-subtle, #cfe2ff);
    color: var(--bs-primary-text-emphasis, #052c65);
    white-space: nowrap;
}

/* Live availability panel under the multi-day editor, and the tint it puts on the form
   row for a day that conflicts (or only needs a second look). */
.schedule-availability-day + .schedule-availability-day {
    margin-top: 0.35rem;
}

.schedule-availability-days {
    font-size: 0.875rem;
}

.schedule-availability-issues {
    list-style: none;
    padding-left: 1.35rem;
    margin: 0.1rem 0 0;
    font-size: 0.8125rem;
}

.availability-conflict input {
    border-color: var(--bs-danger);
}

.availability-warning input {
    border-color: var(--bs-warning);
}

/* ======================================================================================
   Job workflows
   Settings > Workflows (the list and the step editor), the workflow picker on a new job,
   and the custom-step touches on the job page. Colours come from Bootstrap's theme
   variables throughout so light and dark mode both read correctly.
   ====================================================================================== */

.min-w-0 {
  min-width: 0;
}

/* ---- Job page ---- */

.custom-step-badge {
  background: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
  border: 1px solid var(--bs-primary-border-subtle);
  font-weight: 600;
}

/* The shop's own words for what to do at a custom step, under the banner's headline. */
.next-step-instructions {
  margin-top: 0.2rem;
  font-size: 0.875rem;
  color: var(--bs-secondary-color);
  white-space: pre-line;
}

/* ---- Workflow picker (new job, change workflow) ---- */

.workflow-choices {
  display: grid;
  gap: 0.5rem;
}

.workflow-choice {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  background: var(--bs-body-bg);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}

.workflow-choice:hover {
  border-color: var(--bs-primary-border-subtle);
}

/* The native radio is kept for keyboard and screen-reader use, just visually hidden; the
   card itself shows which one is picked. */
.workflow-choice-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.workflow-choice-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.workflow-choice-name {
  font-weight: 600;
}

.workflow-choice-description {
  font-size: 0.85rem;
  color: var(--bs-secondary-color);
}

.workflow-choice-check {
  font-size: 1.25rem;
  color: var(--bs-border-color);
  flex: none;
}

.workflow-choice:has(.workflow-choice-input:checked) {
  border-color: var(--bs-primary);
  background: var(--bs-primary-bg-subtle);
  box-shadow: 0 0 0 1px var(--bs-primary);
}

.workflow-choice:has(.workflow-choice-input:checked) .workflow-choice-check {
  color: var(--bs-primary);
}

.workflow-choice:has(.workflow-choice-input:focus-visible) {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* ---- Settings > Workflows list ---- */

.workflow-list {
  display: grid;
  gap: 0.75rem;
}

.workflow-card.is-default {
  border-color: var(--bs-success-border-subtle);
}

/* The steps of a workflow at a glance, as a row of chips joined by arrows. Wraps rather
   than scrolls, so a long workflow is still readable on a phone. */
.workflow-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1.1rem;
  list-style: none;
  padding: 0;
  margin: 0.75rem 0;
}

.workflow-strip li {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color);
}

/* The arrow sits in the gap before each chip. */
.workflow-strip li + li::before {
  content: "\203A";
  position: absolute;
  left: -0.8rem;
  color: var(--bs-secondary-color);
  font-weight: 400;
}

.workflow-strip li.is-custom {
  background: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
  border-color: var(--bs-primary-border-subtle);
}

.workflow-empty-hint {
  display: flex;
  gap: 0.75rem;
  margin-top: 1rem;
  padding: 0.85rem 1rem;
  border-radius: var(--bs-border-radius-lg);
  background: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
  font-size: 0.875rem;
}

.workflow-empty-hint > i {
  margin-top: 0.15rem;
}

/* ---- Workflow editor ---- */

.workflow-editor-header {
  margin-bottom: 1rem;
}

.workflow-readonly-banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  padding: 0.85rem 1rem;
  border-radius: var(--bs-border-radius-lg);
  background: var(--bs-info-bg-subtle);
  color: var(--bs-info-text-emphasis);
  border: 1px solid var(--bs-info-border-subtle);
}

.workflow-problems ul {
  margin: 0;
  padding-left: 1.1rem;
}

/* A validation summary with nothing in it renders an empty alert box otherwise. */
.workflow-problems.validation-summary-valid {
  display: none;
}

/* Steps as a vertical timeline: a rail down the left with a marker per step, and a card
   per step beside it. Reads top to bottom the way a job actually moves. */
.wf-timeline {
  list-style: none;
  padding: 0;
  margin: 0 0 1.25rem;
  position: relative;
}

.wf-timeline::before {
  content: "";
  position: absolute;
  top: 1rem;
  bottom: 1rem;
  left: 1.05rem;
  width: 2px;
  background: var(--bs-border-color);
}

.wf-step {
  position: relative;
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 0.3rem 0;
}

.wf-step-marker {
  position: relative;
  z-index: 1;
  flex: none;
  width: 2.15rem;
  height: 2.15rem;
  margin-top: 0.45rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  background: var(--bs-body-bg);
  border: 2px solid var(--bs-success);
  color: var(--bs-success);
}

.wf-step-card {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  background: var(--bs-body-bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.wf-step-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.wf-step-title {
  font-weight: 600;
}

.wf-step-help {
  font-size: 0.85rem;
  color: var(--bs-secondary-color);
}

.wf-step-control {
  flex: none;
  font-size: 0.85rem;
}

.wf-lock {
  color: var(--bs-secondary-color);
  white-space: nowrap;
}

.wf-step-note {
  margin-top: 0.35rem;
  font-size: 0.78rem;
  color: var(--bs-secondary-color);
}

.wf-step-note-rule {
  color: var(--bs-warning-text-emphasis);
}

/* A built-in step switched off: still listed, so it can be switched back on in place,
   but clearly not part of the flow. */
.wf-step.is-off .wf-step-card {
  background: var(--bs-tertiary-bg);
  border-style: dashed;
}

.wf-step.is-off .wf-step-title,
.wf-step.is-off .wf-step-help {
  opacity: 0.6;
}

.wf-step.is-off .wf-step-title {
  text-decoration: line-through;
}

.wf-step.is-off .wf-step-marker {
  border-color: var(--bs-border-color);
  border-style: dashed;
  color: var(--bs-secondary-color);
}

.wf-step-visit .wf-step-marker {
  border-style: dashed;
}

/* The shop's own steps, in the app's accent so they stand out from the built-ins. */
.wf-step-custom .wf-step-marker {
  border-color: var(--bs-primary);
  color: var(--bs-primary);
}

.wf-step-custom .wf-step-card {
  border-color: var(--bs-primary-border-subtle);
  border-left: 3px solid var(--bs-primary);
}

.wf-step-custom.is-new .wf-step-card {
  animation: wf-step-arrive 0.6s ease;
}

@keyframes wf-step-arrive {
  from {
    background: var(--bs-primary-bg-subtle);
  }
}

.wf-step-name {
  font-weight: 600;
}

.wf-step-tools {
  display: flex;
  gap: 0.25rem;
  flex: none;
}

.wf-step-options {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  margin-top: 0.6rem;
  font-size: 0.875rem;
}

.wf-duration {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.wf-duration[hidden] {
  display: none;
}

.wf-duration .form-select {
  width: auto;
}

/* "Add a step here": quiet until wanted, so a long list isn't a wall of buttons, but
   always visible enough to find. */
.wf-add {
  position: relative;
  padding: 0.1rem 0 0.1rem 2.9rem;
}

.wf-add-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0.6rem;
  border: 1px dashed transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--bs-secondary-color);
  font-size: 0.78rem;
  opacity: 0.7;
  transition: opacity 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.wf-add-btn:hover,
.wf-add-btn:focus-visible {
  opacity: 1;
  color: var(--bs-primary);
  border-color: var(--bs-primary-border-subtle);
  background: var(--bs-primary-bg-subtle);
}

.wf-form-actions {
  display: flex;
  gap: 0.5rem;
}

.workflow-aside {
  position: sticky;
  top: 1rem;
}

.workflow-howto ul {
  padding-left: 1.1rem;
  font-size: 0.875rem;
}

.workflow-howto li + li {
  margin-top: 0.35rem;
}

@media (max-width: 575.98px) {
  .wf-step-head {
    flex-wrap: wrap;
  }

  .wf-step-custom .wf-step-head .wf-step-name {
    flex: 1 1 100%;
  }

  .wf-step-custom .wf-step-tools {
    margin-left: auto;
  }

  /* The save button is the one thing that matters at the end of a long list. */
  .wf-form-actions {
    position: sticky;
    bottom: 0;
    padding: 0.75rem 0;
    background: var(--bs-body-bg);
    border-top: 1px solid var(--bs-border-color);
  }

  .wf-form-actions .btn {
    flex: 1 1 0;
  }
}

/* The editor's own "this needs a name" line under a custom step. */
.wf-field-error {
  margin-top: 0.3rem;
  font-size: 0.8rem;
  color: var(--bs-danger-text-emphasis);
}

/* Jobs list: one button per step with a count of the jobs on it. Wraps on a wide screen,
   scrolls sideways on a phone so the row stays one thumb-swipe tall. Steps with nothing on
   them are faded so the ones that need attention stand out. */
.job-step-filters {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  padding-bottom: 0.25rem;
  scrollbar-width: thin;
}

@media (min-width: 768px) {
  .job-step-filters {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

.job-step-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: 0 0 auto;
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 999px;
  color: var(--bs-body-color);
  background: var(--bs-body-bg);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.job-step-filter:hover {
  border-color: var(--bs-primary);
  color: var(--bs-body-color);
}

.job-step-filter.is-empty {
  color: var(--bs-secondary-color);
}

.job-step-filter-count {
  min-width: 1.6rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--bs-tertiary-bg);
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
}

.job-step-filter.active {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

.job-step-filter.active .job-step-filter-count {
  background: rgba(255, 255, 255, 0.25);
  color: #fff;
}

/* A shop-added workflow step, told apart from the built-in stage colors. */
.badge.job-step-custom {
  background-color: var(--bs-purple, #6f42c1);
  color: #fff;
}

@media (min-width: 768px) {
  .job-list-search {
    max-width: 32rem;
  }
}
