/* ============================================================================
   style.css — Bootstrap overrides and the few app objects Bootstrap lacks
   ============================================================================

   WHAT THIS FILE IS FOR
   Stock Bootstrap 5.3 is the design system for this tool. This file holds only what
   Bootstrap has no class or utility for. Before adding a rule here, search
   assets/vendor/bootstrap.css for the component and then for the utility. If one
   exists, use it in the markup instead.

   WHAT GOES ELSEWHERE
   The Gantt chart          assets/css/gantt.css
   Choices.js select skin   assets/css/choices.css
   A visual used on one page only, once   inline in that page

   NAMING
   SMACSS layering, OOCSS object/variant naming. Not BEM.
     l-      layout: the shell that positions regions      .l-app  .l-sidebar
     object  a reusable thing, named for what it is        .avatar  .color-dot
     variant a modifier on an object, hyphenated           .avatar-sm
     is-     a state a script toggles                      .is-active
   Bootstrap's own names are never renamed or cloned. Override .btn, do not invent
   .btn-ghost.

   HOW RULES ARE WRITTEN
   Selector on its own line, one property per line, closing brace on its own line,
   nested rules and media query contents indented one level.

   COLOURS
   Bootstrap's, via --bs-* variables and the text- and bg- utilities. The Gantt status
   colours are the only colours of our own and they live in gantt.css.
   ========================================================================== */

@import url("../vendor/montserrat.css");

:root {
  --field-h: 45px;
  /* The project's typeface, for every page. Bootstrap's own stack is the fallback. */
  --bs-body-font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
}

/* ---------------------------------------------------------------------------
   Layout — the app shell
   Bootstrap has a grid but no app shell: a fixed-width sidebar beside a scrolling
   content column, with the sidebar becoming an offcanvas below lg.
   --------------------------------------------------------------------------- */

.l-app {
  display: flex;
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
}

.l-sidebar {
  flex: 0 0 4.5rem;
  width: 4.5rem;
}

.l-content {
  flex: 1 1 auto;
  min-width: 0;
}

/* A view that scrolls inside itself — the Gantt chart — fills the content column
   and keeps its scrollbars to its own panes, so the top bar and the sidebar never
   move. Bootstrap has h-100 but no way to stop the column scrolling with it. */
.l-shell-fixed {
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

.l-view-fill {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

/* The view is mounted inside a wrapper element of its own, so the stretch has to
   be passed down one more level for the chart's 100% height to mean the column. */
.l-view-fill > * {
  flex: 1 1 auto;
  min-height: 0;
}

/* A flash message keeps its own height above the view. */
.l-view-fill > .alert {
  flex: 0 0 auto;
}

/* A list page (Projects, Tasks, Users) fills the column like the chart: the list
   scrolls inside itself, so a list wider than the window has its horizontal bar at
   the bottom of the page rather than under the last row. The padding is the
   list's own, so the bar runs the full width. Same thin bar as the chart. */
.l-list {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: auto;
}

.l-list > * {
  flex: 0 0 auto;
}

.l-list > .table-responsive {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 1rem 1rem 0.5rem;
  scrollbar-width: thin;
}

/* My Tasks hides its table when nothing matches; the empty message then sits at the top. */
.l-list > .table-responsive:has(> table.d-none) {
  flex: 0 0 auto;
  padding-bottom: 0;
}

.l-list > .table-responsive::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.l-list > .table-responsive::-webkit-scrollbar-thumb {
  border-radius: 4px;
  background-color: var(--bs-gray-400);
}

@media (min-width: 992px) {
  .l-list > .table-responsive {
    padding: 1.5rem 1.5rem 0.5rem;
  }
}

@media (max-width: 991.98px) {
  .l-sidebar {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   Objects
   --------------------------------------------------------------------------- */

/* A user's initials in a circle. Bootstrap has no avatar. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
  overflow: hidden;
}

.avatar-sm {
  width: 24px;
  height: 24px;
  font-size: 0.625rem;
}

.avatar-xs {
  width: 18px;
  height: 18px;
  font-size: 0.5rem;
}

.avatar-lg {
  width: 40px;
  height: 40px;
  font-size: 0.875rem;
}

/* Overlapping avatars for the people assigned to a task. */
.avatar-stack {
  display: inline-flex;
  align-items: center;
}

.avatar-stack > .avatar {
  position: relative;
  margin-left: -2px;
  box-shadow: 0 0 0 2px var(--bs-body-bg);
}

.avatar-overlap-sm > .avatar {
  margin-left: 0;
}

.avatar-overlap-md > .avatar {
  margin-left: -2px;
}

.avatar-overlap-lg > .avatar {
  margin-left: -8px;
}

.avatar-stack > .avatar:first-child {
  margin-left: 0;
}

/* A hovered avatar comes to the front of the stack, so its tooltip and its full
   circle are not cut by the next one. */
.avatar-stack > .avatar:hover {
  z-index: 1;
}

/* The question mark that opens a help tip. It sits quiet beside a label and comes up
   on hover; the text itself is in assets/js/help-tips.js. */
.help-tip {
  cursor: help;
  font-size: 0.65rem;
  margin-top: -0.25rem;
  opacity: 0.15;
}

.help-tip:hover {
  opacity: 0.75;
}

/* An explanatory tooltip holding a few paragraphs, not a one-line label. Bootstrap's
   tooltip is capped at 200px, which turns this into a narrow ribbon of text. */
.tooltip-help {
  --bs-tooltip-max-width: 24rem;
  --bs-tooltip-padding-x: 1rem;
  --bs-tooltip-padding-y: 0.75rem;
}

.tooltip-help .tooltip-inner {
  text-align: left;
}

/* A filled circle standing for a status or label colour. */
.color-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  vertical-align: -1px;
}

.color-dot-lg {
  width: 20px;
  height: 20px;
}

/* A milestone card in the project flyout: the project colour as a bar down the side,
   the title with its status pill, the description, the date row, and the task list
   the date row opens. The colour itself stays in the markup, per project; the four
   statuses are a fixed set, so each is a modifier on the pill. */
.milestone-card {
  padding: 1rem;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-tertiary-bg);
}

.milestone-row {
  display: flex;
  align-items: stretch;
  gap: 1rem;
}

.milestone-bar {
  flex: 0 0 auto;
  width: 4px;
  border-radius: 2px;
}

.milestone-body {
  min-width: 0;
  flex-grow: 1;
}

.milestone-head {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.milestone-main {
  min-width: 0;
  flex-grow: 1;
}

.milestone-titlerow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.milestone-title {
  font-weight: 500;
  color: rgba(var(--bs-dark-rgb), 1);
}

.milestone-status {
  border-radius: var(--bs-border-radius-pill);
  /* Bootstrap's badge is sized in em, so the same pill inside a dialog heading would
     come out half again as large. It is one size everywhere. */
  font-size: 0.75rem;
  font-weight: 400;
}

.milestone-status .bi {
  margin-right: 0.25rem;
}

/* The chart's top bar (app/templates/layout/app.php). The project name gives way
   before the buttons do. Below md the chart's actions drop to a line of their own
   under the menu, the name and the right-hand buttons; from md up they sit between
   them on the one line. They are centred at every size. */
.gt-topbar-name {
  flex-shrink: 1;
  min-width: 0;
}

.gt-topbar-actions {
  flex-shrink: 0;
  justify-content: center;
}

@media (max-width: 767.98px) {
  /* The name takes what the first line has left, so it truncates instead of
     pushing the right-hand buttons onto a line of their own. */
  .gt-topbar-name {
    flex: 1 1 0;
  }

  .gt-topbar-actions {
    order: 10;
    flex-basis: 100%;
  }
}

/* The count beside a tab's name (the task dialog's Comments and Files). */
.tab-count-pill {
  font-size: 0.5rem;
  background-color: #a6b8c6;
  color: #fff;
}

.milestone-status.milestone-done {
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

.milestone-status.milestone-near {
  background-color: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
}

.milestone-status.milestone-active {
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
}

.milestone-status.milestone-missed {
  background-color: var(--bs-danger-bg-subtle);
  color: var(--bs-danger-text-emphasis);
}

.milestone-desc {
  font-size: 0.875em;
  color: var(--bs-body-color);
}

.milestone-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

.milestone-date,
.milestone-note {
  font-size: 0.875em;
  color: var(--bs-secondary-color);
}

.milestone-date .bi {
  margin-right: 0.25rem;
}

.milestone-tasklink {
  font-size: 0.875em;
  color: rgba(var(--bs-secondary-rgb), 1);
  text-decoration: none;
}

.milestone-tasklink:hover {
  color: rgba(var(--bs-secondary-rgb), 0.8);
}

.milestone-tasklink .bi {
  margin-left: 0.25rem;
}

.milestone-tasks {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0.5rem 0 0;
  padding-left: 0;
  list-style: none;
}

.milestone-task {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.milestone-taskname {
  font-size: 0.875em;
  color: rgba(var(--bs-dark-rgb), 1);
}

.milestone-taskmeta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.milestone-hours {
  flex: 0 0 auto;
  width: 2.75rem;
  font-size: 0.875em;
  text-align: right;
  color: var(--bs-secondary-color);
}

/* The grid of hour checkboxes on a user and on a project. Bootstrap's form-check
   is the control; this only lays them out and keeps the hour labels narrow. */
.hours-grid {
  display: grid;
  grid-template-columns: 5.5rem repeat(24, minmax(1.5rem, 1fr));
  gap: 2px;
  align-items: center;
  min-width: 44rem;
}

.hours-grid .form-check-input {
  margin: 0;
}

/* The weekly hours table on the user flyout. No rules, gaps instead: the day columns
   are spaced apart by border-spacing, which a collapsed table cannot have. Cell
   padding is zero here, so a p-0 in the markup is redundant. */
/* Bootstrap paints every cell in --bs-table-bg, which would put white squares back on
   the panel's grey field. The cells carry no colour of their own; the tick boxes do. */
.hours-table {
  --bs-table-bg: transparent;
  border-collapse: separate;
  border-spacing: 0.65rem 0.5rem;
}

.hours-table th,
.hours-table td {
  padding: 0;
}

.hours-table td {
  width: 2.5rem;
  height: 2.5rem;
}

/* The heads carry no utility classes: Bootstrap's fw-* and text-* are !important, so
   the hover state below could never override them. Weight and colour live here. */
.hours-table th {
  font-weight: 400;
  color: var(--bs-secondary-color);
}

.hours-table thead th {
  text-align: center;
}

/* The hour of the row being pointed at goes bold, so it can be read off without
   counting rows. The day heads do not need it — the column is under the cursor. */
.hours-table tbody tr:hover th {
  font-weight: 600;
  color: var(--bs-body-color);
}

/* Every checkbox in this table fills its whole cell, so the whole square is the hit
   area, an unticked hour is a grey square and a ticked one a solid green block.
   Bootstrap's form-check-input is otherwise a fixed 1em box on the body background. */
.hours-table .form-check-input {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background-color: #dde3ec;
  background-size: 0.875rem;
  cursor: pointer;
}

.hours-table .form-check-input:hover:not(:checked) {
  background-color: #c6d0de;
}

.hours-table .form-check-input:checked {
  background-color: var(--bs-success);
}

/* An activity log entry: an icon column, the event, and the timestamp under it.
   Bootstrap has no list variant for this, and no font size below .small. */
.activity-icon {
  width: 1.25rem;
  text-align: center;
  flex: 0 0 auto;
}

.activity-time {
  font-size: 0.75rem;
}

/* The activity log used across the tool: a tinted icon circle on a rail, the event
   title with its variables, the details string, and the timestamp with the user's
   name beside it. The circle is an avatar's size, which Bootstrap has no badge for,
   and the three type sizes are steps Bootstrap's scale does not carry. The tint is
   Bootstrap's own .bg-*-subtle and .text-*-emphasis on the circle:
   success created, danger deleted, warning comment, secondary sign in,
   primary milestone and progress, info every other update. */
.activity-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  font-size: 0.625rem;
}

.activity-badge.activity-grey {
  background-color: var(--bs-secondary-bg-subtle);
  color: var(--bs-secondary-text-emphasis);
}

.activity-badge.activity-blue {
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis);
}

.activity-badge.activity-cyan {
  background-color: var(--bs-info-bg-subtle);
  color: var(--bs-info-text-emphasis);
}

.activity-badge.activity-green {
  background-color: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
}

.activity-badge.activity-amber {
  background-color: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
}

.activity-badge.activity-red {
  background-color: var(--bs-danger-bg-subtle);
  color: var(--bs-danger-text-emphasis);
}

/* One entry: the marker column on the left, the body on the right. The body carries
   the larger bottom padding so the rail runs past the text before the next circle;
   the last entry closes flush. */
.activity-item {
  display: flex;
  gap: 1rem;
  padding-bottom: 0.5rem;
}

.activity-item:last-child {
  padding-bottom: 0;
}

.activity-marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 0 auto;
}

.activity-body {
  min-width: 0;
  flex-grow: 1;
  padding-bottom: 1rem;
}

.activity-item:last-child .activity-body {
  padding-bottom: 0;
}

.activity-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
}

/* The line joining one entry's circle to the next. */
.activity-rail {
  flex-grow: 1;
  margin-top: 0.5rem;
  border-left: var(--bs-border-width) solid var(--bs-border-color);
}

.activity-title {
  min-width: 0;
  font-size: 0.875rem;
}

/* The task, project, person or value named inside an entry's sentence. */
.activity-var {
  font-weight: 500;
}

.activity-detail {
  margin: 0.25rem 0;
  padding: 0.5rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  font-size: 0.75rem;
}

/* The changes a merged entry is made of, one per line. */
.activity-items {
  margin: 0;
  padding-left: 1rem;
}

.activity-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.65rem;
  color: var(--bs-secondary-color);
}

/* The chevron that opens an entry's details. It carries the faint-secondary link
   colour Bootstrap's .link-secondary + .link-opacity-* pair gives, and the chevron's
   own size, which is below Bootstrap's smallest step. */
.activity-toggle {
  flex: 0 0 auto;
  font-size: 0.65rem;
  color: rgba(var(--bs-secondary-rgb), 0.25);
  text-decoration: none;
}

.activity-toggle:hover {
  color: rgba(var(--bs-secondary-rgb), 0.75);
}

/* A mini stat card: a rounded icon tile beside a label, a number and its unit.
   Bootstrap's .card carries the frame; this is the arrangement inside it, which
   Bootstrap has nothing for. Used six times in the project flyout's summary. */
.stat-card {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 1rem;
  height: 100%;
  padding: 0.5rem;
  border: 0;
  background-color: var(--bs-tertiary-bg);
}

.stat-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  height: 100%;
  aspect-ratio: 1;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-secondary-color);
  font-size: 1.25rem;
}

.stat-card-body {
  min-width: 0;
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

.stat-card-label {
  font-size: 0.875rem;
  line-height: 1;
  color: var(--bs-secondary-color);
}

.stat-card-value {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1;
  color: var(--bs-dark);
}

.stat-card-unit {
  font-size: 1rem;
  font-weight: 300;
}

/* ---------------------------------------------------------------------------
   Bootstrap overrides
   --------------------------------------------------------------------------- */

/* One step below .small, for a label that only has to be readable at a glance:
   Bootstrap's scale stops at .small (0.875em) and has nothing under it. */
.fs-xs {
  font-size: 0.6rem;
}

/* The sidebar's project links are a plain .nav, not pills, so they have no resting
   background of their own. */
.l-sidebar .nav-link:not(.active):hover {
  background-color: var(--bs-tertiary-bg);
  border-radius: var(--bs-border-radius);
}

/* A square icon button for the top bar's action rows. Bootstrap's buttons size to
   their content and all carry a border or a fill; these are uniform squares on a
   flat grey field. */
.btn-menu {
  aspect-ratio: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background-color: #eef1f3;
}

/* The same button carrying a word instead of an icon: its width is set, so the
   square ratio would make it that tall as well. */
.btn-menu-label {
  aspect-ratio: auto;
}

.btn-menu:hover,
.btn-menu:focus-visible {
  background-color: #e1e4e6;
}

.btn-menu:disabled,
.btn-menu.disabled {
  background-color: #eef1f3;
  opacity: 0.4;
}

/* The projects table: rows read as light cards, not ruled table lines. Removes
   Bootstrap's per-cell border and hover box-shadow so a background can stand in for
   both, and spaces rows apart instead of stacking them flush. */
.project-table {
  border-collapse: separate;
  border-spacing: 0 12px;
}

.project-table > :not(caption) > * > * {
  box-shadow: none;
  border-bottom-width: 0;
}

.project-table tbody > tr > * {
  background-color: var(--bs-tertiary-bg);
}

.project-table tbody > tr {
  cursor: pointer;
}

.project-table tbody > tr:hover > * {
  background-color: var(--bs-secondary-bg);
}

.project-table tbody tr td:first-child {
  border-radius: 0.5rem 0 0 0.5rem;
}

.project-table tbody tr td:last-child {
  border-radius: 0 0.5rem 0.5rem 0;
}

/* On the .nav-tabs of a tab set sitting at the bottom of an offcanvas header.
   Bootstrap draws the underline across the row's own box, which stops at the panel's
   padding. This pulls the row out to both edges so the line runs the full width, and
   puts the same padding back inside so the tabs stay on the panel's text column. */
.offcanvas-tabs {
  margin-right: calc(var(--bs-offcanvas-padding-x) * -1);
  margin-left: calc(var(--bs-offcanvas-padding-x) * -1);
  padding-right: var(--bs-offcanvas-padding-x);
  padding-left: var(--bs-offcanvas-padding-x);
}

/* An unselected tab reads as body text, which gives every tab the same weight as the
   one being looked at. Grey until it is hovered or selected; Bootstrap's own hover
   colour then takes over from the :hover rule below it. */
.offcanvas-tabs .nav-link:not(:hover):not(:focus-visible) {
  color: var(--bs-secondary-color);
}

/* Montserrat runs larger than the fallback stack, so the flyout's tab names are
   set down a step and the selected one carries the weight instead. */
.offcanvas-tabs .nav-link {
  font-size: 0.875rem;
  font-weight: 400;
}

.offcanvas-tabs .nav-link.active {
  font-weight: 500;
}

.offcanvas-tabs .nav-link.active:not(:hover):not(:focus-visible) {
  color: var(--bs-body-color);
}

/* A flyout is never the white of a page: header, body and footer all sit on the
   tertiary tint. */
.offcanvas {
  --bs-offcanvas-bg: var(--bs-tertiary-bg);
}

/* With a tab set it becomes a panel behind tabs: the body and the footer drop to the
   same light grey field as a dialog, so the selected tab reads as the front of that
   panel, while the top header stays on the tint the tabs are cut out of. */
.offcanvas:has(.offcanvas-tabs) {
  --bs-offcanvas-bg: #f1f4f9;
}

.offcanvas:has(.offcanvas-tabs) > .offcanvas-header:first-child {
  background-color: var(--bs-tertiary-bg);
}

/* Bootstrap paints the selected tab in the body background and hides the underline
   behind it with the same colour, so one variable covers both. */
.offcanvas-tabs {
  --bs-nav-tabs-link-active-bg: #f1f4f9;
  --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) #f1f4f9;
}

/* ---------------------------------------------------------------------------
   Modal
   The standard dialog look for the whole tool: a blue title bar with white type and
   a white close button, and a light grey field behind the body and the footer.
   Bootstrap's dialog is white throughout, so the header colour, the header padding
   and the panel background are set here once rather than on each dialog.
   --------------------------------------------------------------------------- */

.modal-content {
  --bs-modal-bg: #f1f4f9;
  --bs-modal-header-padding: 1rem 1.75rem 1rem 1.5rem;
  --bs-modal-header-border-width: 0;
}

.modal-header {
  background-color: #0171d8;
  color: #fff;
  border-top-left-radius: calc(var(--bs-modal-border-radius) - var(--bs-modal-border-width));
  border-top-right-radius: calc(var(--bs-modal-border-radius) - var(--bs-modal-border-width));
}

.modal-header .btn-close {
  filter: var(--bs-btn-close-white-filter);
}

/* ---------------------------------------------------------------------------
   Modal — moveable
   A dialog carrying modal-moveable is dragged by its title bar. Bootstrap has no
   moveable dialog, so the cursors are here and the drag is in assets/js/script.js,
   which writes a translate onto the .modal-dialog.
   --------------------------------------------------------------------------- */

.modal-moveable .modal-header {
  cursor: grab;
  user-select: none;
}

.modal-moveable .modal-header:active {
  cursor: grabbing;
}

/* The drag offset is a pair of custom properties rather than an inline transform, so
   Bootstrap's own transforms still compose with it: the slide-in as the dialog opens,
   and the nudge it gives a static dialog when the backdrop is clicked. A dialog that
   has not been dragged declares no transform at all, so Bootstrap's opening animation
   is untouched. */
.modal-moveable.is-modal-moved .modal-dialog {
  transform: translate(var(--modal-x, 0px), var(--modal-y, 0px));
}

.modal-moveable.is-modal-moved.modal-static .modal-dialog {
  transform: translate(var(--modal-x, 0px), var(--modal-y, 0px)) scale(1.02);
}

/* Bootstrap transitions the dialog's own transform as it opens, which would make a
   drag lag behind the pointer. */
.modal-moveable.is-modal-moving .modal-dialog {
  transition: none;
}

/* ---------------------------------------------------------------------------
   Prompt
   The app's own alert and confirmation dialog: #appPrompt in
   Page___AppShell.dc.html, shown by appAlert(), appConfirm() and appPrompt() in
   assets/js/script.js. The kind on .modal-content colours the title bar, and the
   body takes a tenth of the same colour. Bootstrap has no such dialog.
   --------------------------------------------------------------------------- */

.app-prompt .modal-content {
  --app-prompt: #0171d8;
  /* The body is the last thing in the dialog and carries its own colour, so it is
     clipped to the dialog's rounded corners. */
  overflow: hidden;
}

.app-prompt .modal-content[data-prompt-kind="warning"] {
  --app-prompt: var(--bs-warning);
}

.app-prompt .modal-content[data-prompt-kind="danger"] {
  --app-prompt: var(--bs-danger);
}

.app-prompt .modal-content[data-prompt-kind="success"] {
  --app-prompt: var(--bs-success);
}

.app-prompt .modal-header {
  background-color: var(--app-prompt);
}

/* Amber under white type is unreadable, so the warning bar keeps dark type. */
.app-prompt .modal-content[data-prompt-kind="warning"] .modal-header {
  color: var(--bs-body-color);
}

.app-prompt .modal-body {
  background-color: color-mix(in srgb, var(--app-prompt) 10%, #fff);
}

/* ---------------------------------------------------------------------------
   Form fields
   One height for every field, so a text input, a select, a Choices field and a
   colour field line up in a row. Buttons keep Bootstrap's own height. The -sm and
   -lg variants keep theirs too, which is why the selectors exclude them.
   --------------------------------------------------------------------------- */

.form-control:not(.form-control-sm):not(.form-control-lg),
.form-select:not(.form-select-sm):not(.form-select-lg),
.input-group-text {
  min-height: var(--field-h);
}

/* A file input's browse button is inset into the control by negative margins sized
   to Bootstrap's own 38px, so at 45px it stands short and its border cuts across the
   field. The control drops its vertical padding and the button takes the full inner
   height. */
.form-control[type="file"]:not(.form-control-sm):not(.form-control-lg) {
  padding-top: 0;
  padding-bottom: 0;
}

.form-control[type="file"]:not(.form-control-sm):not(.form-control-lg)::file-selector-button {
  height: calc(var(--field-h) - var(--bs-border-width) * 2);
  margin: 0 0.75rem 0 -0.75rem;
  padding: 0 0.75rem;
}

.form-label {
  margin-left: 0.15rem;
  margin-bottom: 0.15rem;
  font-size: 0.875rem;
  color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity, 1));
}

.form-text {
  color: var(--bs-tertiary-color);
  font-style: italic;
}

/* Bootstrap's placeholder is --bs-secondary-color, close enough to the value text to
   read as one. */
.form-control::placeholder {
  color: var(--bs-tertiary-color);
  opacity: 1;
}

/* ---------------------------------------------------------------------------
   Coloris
   The colour field. Coloris ships assets/vendor/coloris.css, which is read-only, so
   its overrides live here. One field type in this tool: the polaroid picker, with a
   rounded-square thumbnail at the left of the closed field.
   --------------------------------------------------------------------------- */

/* The picker is appended to the body at z-index 1000, so a colour field inside a
   flyout opens it underneath the panel and its backdrop. */
.clr-picker {
  z-index: 1060;
}

/* Coloris wraps the field in a .clr-field div of its own, inline-block and unsized, so
   a colour field sits at the input's default width instead of filling its grid column
   the way every other control does. */
.clr-field {
  display: block;
}

.clr-field input {
  width: 100%;
}

.clr-field button {
  left: 11px;
  right: auto;
  width: 22px;
  height: 22px;
  border-radius: 0.25rem;
}

.clr-field input {
  padding-left: 42px;
}

.clr-field input::placeholder {
  color: var(--bs-tertiary-color);
  opacity: 1;
}

/* The swatch strip centres its rows, which shifts a part-filled last row and breaks
   the column of neutrals down the left edge. */
.clr-swatches div {
  justify-content: flex-start;
}

/* Alpha is on so the first swatch can be transparent; the opacity slider itself is
   not offered. */
.clr-picker .clr-alpha {
  display: none;
}

/* ---------------------------------------------------------------------------
   Progress bar
   Bootstrap's .progress is unchanged, at its own height. .progress-lg is the taller
   bar. .progress-labeled puts the value in the track: the body-coloured copy reads on
   the empty track, and a white copy clipped to the fill reads on the bar. --p is the
   value, inline on the bar beside its width.
   --------------------------------------------------------------------------- */

.progress-lg {
  height: 1.25rem;
}

.progress-labeled {
  position: relative;
}

.progress-labeled .progress-label {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  inset: 0;
  color: var(--bs-body-color);
  font-size: 0.875rem;
  font-weight: 500;
}

.progress-labeled .progress-label-fill {
  color: #fff;
  clip-path: inset(0 calc(100% - var(--p, 0) * 1%) 0 0);
}

/* ---------------------------------------------------------------------------
   Range — bar modifier
   .form-range is unchanged. .form-range-bar draws the same input as a filled bar
   with the value in the thumb. A native thumb cannot hold text, so the number is a
   span over it. .range-field is the wrapper and carries --p; .range-control adds the
   step buttons, which script.js repeats while held.
   --------------------------------------------------------------------------- */

.range-field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.range-field .range-track {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

/* Block, so the track box collapses to the input's height instead of carrying an
   inline element's baseline gap — the number is centred against it. */
.form-range-bar {
  display: block;
  height: 22px;
  padding: 0;
}

/* The fill ends at the thumb's centre, on the same path as .range-value: the thumb
   only travels the track less its own 22px, so a plain p% fill would stop short of
   it near 0 and run past it near 100. */
.form-range-bar::-webkit-slider-runnable-track {
  height: 20px;
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-secondary-bg);
  background-image: linear-gradient(to right, var(--bs-primary) 0 calc(11px + (100% - 22px) * var(--p, 0) / 100), transparent calc(11px + (100% - 22px) * var(--p, 0) / 100));
}

.form-range-bar::-moz-range-track {
  height: 20px;
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--bs-secondary-bg);
  background-image: linear-gradient(to right, var(--bs-primary) 0 calc(11px + (100% - 22px) * var(--p, 0) / 100), transparent calc(11px + (100% - 22px) * var(--p, 0) / 100));
}

.form-range-bar::-webkit-slider-thumb {
  width: 22px;
  height: 22px;
  margin-top: -1px;
  border: var(--bs-border-width) solid var(--bs-primary);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
  background-image: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.form-range-bar::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: var(--bs-border-width) solid var(--bs-primary);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
  background-image: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

/* The number travels with the thumb, between its own half-widths so it never hangs
   off either end. */
.range-value {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  left: calc(11px + (100% - 22px) * var(--p, 0) / 100);
  width: 22px;
  height: 22px;
  transform: translate(-50%, -50%);
  color: var(--bs-body-color);
  font-size: 0.625rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}

.range-step {
  display: none;
}

.range-control .range-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: 50%;
  background-color: transparent;
  color: var(--bs-secondary-color);
  font-size: 0.75rem;
  line-height: 1;
}

.range-control .range-step:hover,
.range-control .range-step:focus-visible {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

/* ---------------------------------------------------------------------------
   Field icons and the field reset
   A native date or time field draws its own picker glyph at the end of the box.
   .input-icon-start moves it in front of the value, which needs a rule because it is
   a pseudo-element. .input-allow-reset is the wrapper of a field that carries a clear,
   and it alone keeps the room for it. .btn-input-reset is the round clear itself,
   which sits inside any field. .input-allow-reveal is the wrapper of a password field
   and .btn-input-reveal the eye inside it, which swaps the field between hidden and
   shown; where a field carries both the copy and the eye, the eye sits at the end.
   --------------------------------------------------------------------------- */

.input-icon-start .form-control {
  padding-left: 2.25rem;
}

/* An hours-and-minutes field: two boxes reading as one, the colon between them sitting
   on the same field with no sides of its own. The two halves face the colon, so the
   value reads as one number. */
[data-duration-group] .input-group-text {
  border-right: 0;
  border-left: 0;
  background-color: transparent;
}

/* The sides the colon replaces go with it, so the three boxes read as one field. */
[data-duration-group] > .form-control:first-child {
  border-right: 0;
}

[data-duration-group] > .form-control:last-child {
  border-left: 0;
}

/* Numbers only, but with no spinner: the arrows crowd a box this narrow, and the
   keyboard's own up and down still step the value. */
[data-duration-group] .form-control {
  -moz-appearance: textfield;
  appearance: textfield;
}

[data-duration-group] .form-control::-webkit-outer-spin-button,
[data-duration-group] .form-control::-webkit-inner-spin-button {
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
}

.input-allow-reset .form-control {
  padding-right: 2.25rem;
}

.input-allow-copy .form-control {
  padding-right: 2.25rem;
}

/* The copy inside a field: the same place as the clear, in the secondary ink rather
   than the danger red, on any .form-control or textarea inside the wrapper. */
.btn-input-copy {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: var(--bs-border-radius-sm);
  background: none;
  color: var(--bs-secondary-color);
}

.input-allow-copy .form-control:not(textarea) ~ .btn-input-copy {
  top: 50%;
  transform: translateY(-50%);
}

.btn-input-copy:hover {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.input-allow-reveal .form-control {
  padding-right: 2.25rem;
}

.input-allow-copy.input-allow-reveal .form-control {
  padding-right: 4rem;
}

/* The eye inside a password field: the same size and ink as the copy, at the end of the
   box, and the copy steps in front of it where a field carries both. */
.btn-input-reveal {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  right: 0.5rem;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  transform: translateY(-50%);
  border: 0;
  border-radius: var(--bs-border-radius-sm);
  background: none;
  color: var(--bs-secondary-color);
}

.btn-input-reveal:hover {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.input-allow-reveal .btn-input-copy {
  right: 2.25rem;
}

/* The generate inside a password field: the same box as the copy, in front of the eye,
   and behind the copy where the field carries one. */
.btn-input-generate {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  right: 2.25rem;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  transform: translateY(-50%);
  border: 0;
  border-radius: var(--bs-border-radius-sm);
  background: none;
  color: var(--bs-secondary-color);
}

.btn-input-generate:hover {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.input-allow-copy.input-allow-generate .btn-input-generate {
  right: 4rem;
}

.input-allow-generate .form-control {
  padding-right: 4rem;
}

.input-allow-copy.input-allow-generate .form-control {
  padding-right: 5.75rem;
}

/* A field with a message under it inside the same wrapper: both in-field buttons sit on
   the field's own middle, not the wrapper's, or they slide down over the message. And
   Bootstrap's own invalid exclamation is drawn where the buttons are, so the field with
   a button of its own carries the red border alone. */
.input-allow-reveal .form-control ~ .btn-input-reveal,
.input-allow-reveal .form-control ~ .btn-input-generate,
.input-allow-copy.input-allow-reveal .form-control ~ .btn-input-copy {
  top: calc(var(--field-h) / 2);
  transform: translateY(-50%);
}

.input-allow-reveal .form-control.is-invalid {
  padding-right: 2.25rem;
  background-image: none;
}

.input-allow-copy.input-allow-reveal .form-control.is-invalid {
  padding-right: 4rem;
}

.input-allow-generate .form-control.is-invalid {
  padding-right: 4rem;
}

.input-allow-copy.input-allow-generate .form-control.is-invalid {
  padding-right: 5.75rem;
}

.input-icon-start .form-control::-webkit-calendar-picker-indicator {
  position: absolute;
  top: 50%;
  left: 0.6rem;
  margin: 0;
  padding: 0;
  transform: translateY(-50%);
  opacity: 0.6;
  cursor: pointer;
}

.input-icon-start .form-control::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}

.btn-input-reset {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  right: 0.75rem;
  width: 1rem;
  height: 1rem;
  padding: 0;
  transform: translateY(-50%);
  border: 0;
  border-radius: 50%;
  background-color: var(--bs-danger);
  color: #fff;
  font-size: 0.75rem;
  line-height: 1;
  opacity: 0.15;
}

.btn-input-reset:hover {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
   Share link list item
   A link in the share dialog's list: who it is for on the left, then its expiry,
   password, permission and the members it is restricted to, all on one line. One row
   is the height of the create button under the list. The picked one turns white with
   a focus-blue border, so the fill alone never has to carry the selection.
   --------------------------------------------------------------------------- */

.share-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 24px;
  padding: 0.5rem 1rem;
  border: 1px solid transparent;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  text-align: left;
}

.share-item:hover {
  background-color: var(--bs-secondary-bg);
}

.share-item.active {
  border-color: #86b7fe;
  background-color: #fff;
}

/* The dashed add under the share list. Bootstrap has no dashed button; the hover is
   what says it can be clicked. */
.btn-add-share-item {
  padding: 0.5rem 0.75rem;
  border: 1px dashed var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
}

.btn-add-share-item:hover {
  border-color: var(--bs-secondary-color);
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

/* ---------------------------------------------------------------------------
   Save button
   The save that sits beside a dialog's close: the same white and the same 0.5 opacity
   as the X, full strength once something has changed.
   --------------------------------------------------------------------------- */

.btn-save {
  display: flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.125rem;
}

.modal-header .btn-save {
  color: #fff;
  opacity: 0.5;
}

.modal-header .btn-save[data-save-state="unsaved"],
.modal-header .btn-save:hover {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
   Panel tabs
   The tab row inside a dialog's side panel: no tab box, the active one bold with a
   rule under its own label. Bootstrap's .nav-underline does the underline; this sets
   the colours and closes the gap so the row can be spread across the panel.
   --------------------------------------------------------------------------- */

.panel-tabs {
  --bs-nav-underline-gap: 0;
}

.panel-tabs .nav-link {
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
  font-weight: 400;
}

.panel-tabs .nav-link:hover {
  color: var(--bs-body-color);
}

.panel-tabs .nav-link.active {
  color: var(--bs-body-color);
  font-weight: 500;
}

/* ---------------------------------------------------------------------------
   Comments
   A comment is an avatar, a name and time line, and the comment itself in a tinted
   box. The composer is a plain field on the panel's own white.
   --------------------------------------------------------------------------- */

.comment-list {
  min-height: 0;
  overflow-y: auto;
}

.comment-author {
  color: var(--bs-dark);
  font-size: 0.8125rem;
  font-weight: 500;
}

.comment-time {
  color: var(--bs-secondary-color);
  font-size: 0.65rem;
  white-space: nowrap;
}

.comment-body {
  margin-top: 0.25rem;
  padding: 0.5rem;
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-tertiary-bg);
  font-size: 0.8125rem;
}

.comment-composer {
  border: var(--bs-border-width) solid #e4e4e4;
  background-color: var(--bs-tertiary-bg);
  box-shadow: none;
}

/* The focus ring goes round the field and its send button together, so the two
   read as one control; the field itself only takes the focus border colour. */
.comment-composer-group {
  border-radius: var(--bs-border-radius);
  transition: box-shadow 0.15s ease-in-out;
}

.comment-composer-group:focus-within {
  box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);
}

.comment-composer:focus {
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
   Attachments
   The shortcut to a task's starred files, under the description. Each one is a card
   holding the file's type icon and its name (which opens it), and a menu on its right
   to view or download it.
   --------------------------------------------------------------------------- */

.attachment-list {
  padding-left: 0;
  list-style: none;
}

.attachment-item {
  display: inline-flex;
  align-items: center;
  max-width: 16rem;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  background-color: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-size: 0.8125rem;
  font-weight: 500;
}

.attachment-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.25rem 0.5rem 0.25rem 0.5rem;
  border-radius: var(--bs-border-radius-lg) 0 0 var(--bs-border-radius-lg);
  background-color: var(--bs-tertiary-bg);
  color: inherit;
  text-decoration: none;
}

.attachment-item > .dropdown {
  display: flex;
  align-self: stretch;
}

.attachment-menu {
  display: inline-flex;
  align-items: center;
  align-self: stretch;
  padding: 0 0.4rem;
  border: 0;
  border-left: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: 0 var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0;
  background: transparent;
  color: var(--bs-secondary-color);
  font-size: 0.625rem;
}

/* The link already sits on the tertiary background, so it steps up to secondary. */
.attachment-link:hover {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

.attachment-menu:hover,
.attachment-menu[aria-expanded="true"] {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

/* ---------------------------------------------------------------------------
   Dropdown link
   A dropdown whose toggle is a value in a table cell rather than a button.
   --------------------------------------------------------------------------- */

.dropdown-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
  text-decoration: none;
}

.dropdown-link:hover {
  color: var(--bs-body-color);
}

/* ---------------------------------------------------------------------------
   Light rule
   The divider between groups of fields, lighter than Bootstrap's own.
   --------------------------------------------------------------------------- */

.hr-light {
  border-top-color: #bcbcbc;
}

/* ---------------------------------------------------------------------------
   Priority pill
   A task's priority, beside its name in the dialog's header. Normal has no pill;
   planning and low are held back so the two that matter read first.
   --------------------------------------------------------------------------- */

.priority-pill {
  padding: 0.35em 0.65em;
  border-radius: var(--bs-border-radius-pill);
  font-size: 0.75em;
  font-weight: 500;
  line-height: 1;
}

.priority-pill.priority-planning {
  background-color: var(--bs-light);
  color: var(--bs-dark);
  opacity: 0.4;
}

.priority-pill.priority-low {
  background-color: var(--bs-info);
  color: var(--bs-dark);
  opacity: 0.4;
}

.priority-pill.priority-high {
  background-color: var(--bs-warning);
  color: var(--bs-dark);
}

.priority-pill.priority-urgent {
  background-color: var(--bs-danger);
  color: #fff;
}

/* ---------------------------------------------------------------------------
   The help dialog
   One dialog holds every help document, one per [data-help-id], all hidden. The
   trigger's data-help-modal names which one shows; the script sets an inline
   display on it and takes the dialog title from its data-help-title. The classes
   exist so a help document is markup and nothing else — no utility strings to
   keep in step across dozens of sections.
   --------------------------------------------------------------------------- */

.help-modal > [data-help-id] {
  display: none;
}

.help-modal-content {
  margin-bottom: 1.5rem;
}

.help-modal-content:last-child {
  margin-bottom: 0;
}

.help-modal-content .title {
  margin-bottom: 0.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
}

.help-modal-content .text {
  margin-bottom: 0.5rem;
  font-size: 0.875rem;
  color: var(--bs-secondary-color);
}

.help-modal-content .text:last-child {
  margin-bottom: 0;
}

/* A shortcut row: the keys on the left, the description beside it. The two tracks are
   owned once by the group wrapper and borrowed down the tree with subgrid, so the keys
   column is exactly as wide as the widest key set in the dialog and every row lines up,
   across the groups as well as inside one. A group's own title spans both tracks. */
.shortcut-groups {
  display: grid;
  grid-template-columns: max-content 1fr;
}

.shortcut-groups > .help-modal-content {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

.shortcut-groups > .help-modal-content > .title {
  grid-column: 1 / -1;
}

.shortcut-list {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.shortcut {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.3rem 0;
  font-size: 0.875rem;
}

.shortcut .keys {
  white-space: nowrap;
}

.help-modal kbd {
  background-color: #7F7F7F;
}

.shortcut .desc {
  color: var(--bs-secondary-color);
}

/* --------------------------------------------------------------------------
   The background behind the pages outside the shell: login, the password pages,
   the share-link prompt and the error page. Was a <style> block in each page.
   -------------------------------------------------------------------------- */
.login-page,
.error-page {
  background-color: #f1f4f9;
}
