/* Gruvbox dark theme for bktodo.
   Palette: https://github.com/morhetz/gruvbox */

:root,
[data-bs-theme="dark"] {
  --gv-bg0-hard:  #1d2021;
  --gv-bg0:       #282828;
  --gv-bg0-soft:  #32302f;
  --gv-bg1:       #3c3836;
  --gv-bg2:       #504945;
  --gv-bg3:       #665c54;
  --gv-bg4:       #7c6f64;
  --gv-fg0:       #fbf1c7;
  --gv-fg1:       #ebdbb2;
  --gv-fg2:       #d5c4a1;
  --gv-fg3:       #bdae93;
  --gv-fg4:       #a89984;
  --gv-gray:      #928374;
  --gv-red:       #cc241d;
  --gv-red-br:    #fb4934;
  --gv-green:     #98971a;
  --gv-green-br:  #b8bb26;
  --gv-yellow:    #d79921;
  --gv-yellow-br: #fabd2f;
  --gv-blue:      #458588;
  --gv-blue-br:   #83a598;
  --gv-purple:    #b16286;
  --gv-purple-br: #d3869b;
  --gv-aqua:      #689d6a;
  --gv-aqua-br:   #8ec07c;
  --gv-orange:    #d65d0e;
  --gv-orange-br: #fe8019;

  /* map Bootstrap's tokens onto Gruvbox */
  --bs-body-bg: var(--gv-bg0);
  --bs-body-bg-rgb: 40, 40, 40;
  --bs-body-color: var(--gv-fg1);
  --bs-body-color-rgb: 235, 219, 178;
  --bs-secondary-bg: var(--gv-bg1);
  --bs-secondary-bg-rgb: 60, 56, 54;
  --bs-tertiary-bg: var(--gv-bg0-soft);
  --bs-tertiary-bg-rgb: 50, 48, 47;
  --bs-secondary-color: var(--gv-fg3);
  --bs-tertiary-color: var(--gv-fg4);
  --bs-emphasis-color: var(--gv-fg0);
  --bs-emphasis-color-rgb: 251, 241, 199;
  --bs-border-color: var(--gv-bg2);
  --bs-border-color-translucent: rgba(213, 196, 161, .15);

  --bs-primary: var(--gv-yellow-br);
  --bs-primary-rgb: 250, 189, 47;
  --bs-primary-bg-subtle: #3a2f0f;
  --bs-primary-border-subtle: var(--gv-yellow);
  --bs-primary-text-emphasis: var(--gv-yellow-br);
  --bs-secondary: var(--gv-bg3);
  --bs-success: var(--gv-green-br);
  --bs-success-rgb: 184, 187, 38;
  --bs-success-bg-subtle: #2c3318;
  --bs-success-border-subtle: var(--gv-green);
  --bs-success-text-emphasis: var(--gv-green-br);
  --bs-info: var(--gv-aqua-br);
  --bs-info-rgb: 142, 192, 124;
  --bs-info-bg-subtle: #25342a;
  --bs-info-border-subtle: var(--gv-aqua);
  --bs-info-text-emphasis: var(--gv-aqua-br);
  --bs-warning: var(--gv-orange-br);
  --bs-warning-rgb: 254, 128, 25;
  --bs-warning-bg-subtle: #3c2410;
  --bs-warning-border-subtle: var(--gv-orange);
  --bs-warning-text-emphasis: var(--gv-orange-br);
  --bs-danger: var(--gv-red-br);
  --bs-danger-rgb: 251, 73, 52;
  --bs-danger-bg-subtle: #3d1f1d;
  --bs-danger-border-subtle: var(--gv-red);
  --bs-danger-text-emphasis: var(--gv-red-br);
  --bs-light: var(--gv-fg2);
  --bs-dark: var(--gv-bg1);

  --bs-link-color: var(--gv-aqua-br);
  --bs-link-color-rgb: 142, 192, 124;
  --bs-link-hover-color: var(--gv-yellow-br);
  --bs-link-hover-color-rgb: 250, 189, 47;

  --bs-heading-color: var(--gv-fg0);
  --bs-code-color: var(--gv-orange-br);
}

html, body {
  background: var(--gv-bg0);
  color: var(--gv-fg1);
  font-family: ui-sans-serif, "Inter", "Segoe UI", system-ui, sans-serif;
}

/* navbar + cards in slightly lighter shade so they read as elevated */
.navbar.bg-body-tertiary,
.bg-body-tertiary {
  background-color: var(--gv-bg0-soft) !important;
  border-color: var(--gv-bg2) !important;
}
/* Pin the top bar to the viewport. The two sidebars stick at
   ``top: var(--nav-height)`` so they sit cleanly below — without this
   the navbar scrolls away and a 56-px gap opens up at the bottom of
   each sidebar where ``calc(100vh - 56px)`` falls short. */
:root { --nav-height: 56px; }
body > .navbar {
  position: sticky;
  top: 0;
  z-index: 1030;
}
.navbar-brand,
.navbar .dropdown-toggle {
  color: var(--gv-yellow-br) !important;
}

/* Desktop-app-style icon toolbar in the navbar */
.navbar-icon-toolbar .toolbar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: .35rem;
  color: var(--gv-fg2);
  background: transparent;
  border: 0;
  text-decoration: none;
  font-size: 1.05rem;
  transition: background .12s, color .12s;
}
.navbar-icon-toolbar .toolbar-icon:hover {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
}
.navbar-icon-toolbar .toolbar-icon.spinning i,
.navbar-icon-toolbar .toolbar-icon.spinning svg {
  animation: spin 1s linear infinite;
}
@keyframes spin {
  from { transform: rotate(0); }
  to { transform: rotate(360deg); }
}
/* The inspector's "Refresh from Zammad" icon turns while the request is in
   flight. It is one call to a live external system per click, so the button
   has to look like it landed — silence reads as a broken button and invites a
   second click. */
/* The list views' refresh button turns until the swapped <main> replaces it. */
.btn-refresh-list.spinning i {
  display: inline-block;
  animation: spin 1s linear infinite;
}
[data-zammad-refresh-icon].spin {
  display: inline-block;
  animation: spin 1s linear infinite;
}

/* Brand-specific importer icons keep their own colour even on hover —
   we just dim the rest of the toolbar background underneath. */
.navbar-icon-toolbar .toolbar-icon.brand-todoist:hover { background: rgba(228, 67, 50, .12); }
.navbar-icon-toolbar .toolbar-icon.brand-zammad { color: #34495e; }
.navbar-icon-toolbar .toolbar-icon.brand-zammad:hover { background: rgba(52, 73, 94, .15); }
.navbar-icon-toolbar .toolbar-icon svg { display: block; }
.card {
  background: var(--gv-bg0-soft);
  border-color: var(--gv-bg2);
}
.dropdown-menu {
  background: var(--gv-bg0-soft);
  border-color: var(--gv-bg2);
}
.dropdown-item {
  color: var(--gv-fg1);
}
.dropdown-item:hover, .dropdown-item:focus {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
}

/* forms */
.form-control, .form-select {
  background: var(--gv-bg0-hard);
  color: var(--gv-fg1);
  border-color: var(--gv-bg2);
}
.form-control:focus, .form-select:focus {
  background: var(--gv-bg0-hard);
  color: var(--gv-fg0);
  border-color: var(--gv-yellow);
  box-shadow: 0 0 0 .2rem rgba(250, 189, 47, .25);
}
.form-control::placeholder { color: var(--gv-gray); }
.form-check-input {
  background: var(--gv-bg1);
  border-color: var(--gv-bg3);
}
.form-check-input:checked {
  background: var(--gv-yellow-br);
  border-color: var(--gv-yellow);
}

/* buttons */
.btn-primary {
  background: var(--gv-yellow-br);
  border-color: var(--gv-yellow);
  color: #1d2021;
}
.btn-primary:hover, .btn-primary:focus {
  background: var(--gv-yellow);
  border-color: var(--gv-yellow);
  color: #1d2021;
}
/* Active-pill state for toolbar selectors (group-by, range, today/prev/next).
   Subtler than .btn-primary so the toolbar doesn't punch as hard as the
   main CTAs. */
.btn-toolbar-on,
.btn-toolbar-on:hover,
.btn-toolbar-on:focus {
  background: var(--bs-primary-bg-subtle);
  border-color: var(--gv-yellow);
  color: var(--gv-yellow-br);
}
.btn-success {
  background: var(--gv-green-br);
  border-color: var(--gv-green);
  color: #1d2021;
}
.btn-success:hover { background: var(--gv-green); color: #1d2021; }
.btn-danger {
  background: var(--gv-red-br);
  border-color: var(--gv-red);
  color: #fbf1c7;
}
.btn-outline-secondary {
  color: var(--gv-fg2);
  border-color: var(--gv-bg3);
}
.btn-outline-secondary:hover {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
  border-color: var(--gv-bg3);
}
.btn-outline-primary {
  color: var(--gv-yellow-br);
  border-color: var(--gv-yellow);
}
.btn-outline-primary:hover {
  background: var(--gv-yellow-br);
  color: #1d2021;
}
.btn-outline-danger {
  color: var(--gv-red-br);
  border-color: var(--gv-red);
}
.btn-outline-danger:hover {
  background: var(--gv-red-br);
  color: #fbf1c7;
}
.btn-link {
  color: var(--gv-aqua-br);
}
.btn-link:hover { color: var(--gv-yellow-br); }

/* tables */
.table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--gv-fg1);
  --bs-table-border-color: var(--gv-bg2);
  --bs-table-striped-bg: var(--gv-bg0-soft);
  --bs-table-hover-bg: var(--gv-bg1);
}

/* badges */
.badge.text-bg-light {
  background-color: var(--gv-bg2) !important;
  color: var(--gv-fg1) !important;
}
.badge.text-bg-secondary {
  background-color: var(--gv-bg3) !important;
  color: var(--gv-fg0) !important;
}
.badge.text-bg-info {
  background-color: var(--gv-aqua) !important;
  color: #1d2021 !important;
}
.badge.text-bg-warning {
  background-color: var(--gv-orange) !important;
  color: #1d2021 !important;
}

/* alerts */
.alert-success {
  background: var(--bs-success-bg-subtle);
  border-color: var(--gv-green);
  color: var(--gv-green-br);
}
.alert-info {
  background: var(--bs-info-bg-subtle);
  border-color: var(--gv-aqua);
  color: var(--gv-aqua-br);
}
.alert-warning {
  background: var(--bs-warning-bg-subtle);
  border-color: var(--gv-orange);
  color: var(--gv-orange-br);
}
.alert-danger {
  background: var(--bs-danger-bg-subtle);
  border-color: var(--gv-red);
  color: var(--gv-red-br);
}

/* Nav sidebar — fixed width, sticky to the viewport, scrolls
   internally if its own content overflows. The footer (About / Help
   / version) lives in the flex column so it pins to the bottom of the
   visible sidebar regardless of scroll position. */
.app-nav-sidebar {
  position: sticky;
  top: var(--nav-height, 56px);
  height: calc(100vh - var(--nav-height, 56px));
  background: var(--gv-bg0-soft) !important;
  border-right: 1px solid var(--gv-bg2) !important;
  display: flex;
  flex-direction: column;
}
.app-nav-sidebar .nav-sidebar-main {
  flex: 1 1 auto;
  overflow-y: auto;
  min-height: 0;
}
.app-nav-sidebar .nav-link {
  color: var(--gv-fg2);
  border-radius: .25rem;
  padding: .3rem .6rem;
}
.app-nav-sidebar .nav-link:hover {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
}
.app-nav-sidebar h6.text-muted {
  color: var(--gv-fg4) !important;
  letter-spacing: .05em;
}

/* Sidebar groups (services/sidebar.py): a header button per group folds it,
   the state lives on the user. The current page's link is highlighted. */
.app-nav-sidebar .nav-group + .nav-group { margin-top: .5rem; }
.app-nav-sidebar .nav-group-toggle {
  display: flex;
  align-items: center;
  gap: .35rem;
  width: 100%;
  padding: .2rem .35rem;
  border: 0;
  border-radius: .25rem;
  background: none;
  color: var(--gv-fg4);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: left;
}
.app-nav-sidebar .nav-group-toggle:hover { color: var(--gv-fg1); background: var(--gv-bg1); }
.app-nav-sidebar .nav-group-toggle:focus-visible {
  outline: 2px solid var(--gv-yellow);
  outline-offset: -2px;
}
.app-nav-sidebar .nav-group-chevron {
  font-size: .65rem;
  transition: transform .15s ease;
}
.app-nav-sidebar .nav-group.is-collapsed .nav-group-chevron { transform: rotate(-90deg); }
/* The summed badge only stands in for the hidden ones while folded. */
.app-nav-sidebar .nav-group:not(.is-collapsed) .nav-group-count { display: none; }
.app-nav-sidebar .nav-group-body { margin-top: .1rem; }
.app-nav-sidebar .nav-link.active {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
  font-weight: 600;
}

/* task list */
.task-row {
  display: grid;
  grid-template-columns: 68px 1fr auto;
  align-items: center;
  gap: .3rem;
  padding: .55rem .35rem;
  position: relative;
}
/* Divider only spans the title/meta area — the icon column stays
   border-free so the line reads as "under the task content". */
.task-row::before {
  content: "";
  position: absolute;
  left: calc(68px + .5rem);
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--gv-bg3);
  pointer-events: none;
}
.task-handle {
  cursor: grab;
  opacity: 0;
  transition: opacity .15s;
}
.task-row:hover .task-handle,
.tree-task:hover .task-handle { opacity: .55; }
.task-handle:hover { opacity: 1 !important; }
.drag-ghost {
  background: rgba(250, 189, 47, .12);
  outline: 1px dashed var(--gv-yellow);
  opacity: .6;
}
/* Dragged to the right → the drop nests instead of reordering.
   The dragged row steps in to show where it is going; the list is frozen at
   this point, so nothing else moves under the cursor. */
body.is-nesting .task-row.drag-ghost,
body.is-nesting .tree-task.drag-ghost {
  /* Composes with a sub-task row's own indent instead of overwriting it. */
  margin-left: calc(var(--indent-level, 0) * var(--task-indent-step) + 28px);
}
/* The row the dragged task will go under. */
.task-row.drop-merge-target,
.tree-task.drop-merge-target {
  background: rgba(131, 165, 152, .22) !important;
  outline: 2px solid var(--gv-blue-br);
  outline-offset: -2px;
  border-radius: .25rem;
  position: relative;
}
.task-row.drop-merge-target::after,
.tree-task.drop-merge-target::after {
  content: "Drop as sub-task";
  position: absolute;
  right: .5rem; top: 50%;
  transform: translateY(-50%);
  font-size: .65rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--gv-blue-br);
  background: var(--gv-bg0);
  padding: 0 .35rem;
  border-radius: .2rem;
  pointer-events: none;
}
.merged-block .merged-name-input {
  font-size: .95rem;
  font-weight: 600;
}
/* Sub-project row's open-project icon — quiet until you hover the row. */
.sp-open { opacity: 0; transition: opacity .15s; }
.task-row.sp-row:hover .sp-open { opacity: .6; }
.sp-open:hover { opacity: 1 !important; }
/* Task row's "open detail page" icon — same quiet-until-hover treatment
   as .sp-open. Title click opens the inspector; this icon gets you to
   the full detail page. */
.task-open { opacity: .35; transition: opacity .15s; }
.task-row:hover .task-open { opacity: .85; }
.task-open:hover { opacity: 1 !important; color: var(--gv-aqua-br) !important; }

/* Tasks that belong to a sub-project (rendered directly under the
   .sp-row on the parent project's page). Indent only — no border —
   so they read as a branch of the sub-project above without adding
   visual weight. */
.sp-children {
  margin-left: 1.5rem;
}
.sp-children > .task-list { border: 0; }
/* The sub-project row and its kids form one block — drop the divider
   between them so the group reads as one. */
.task-row.sp-row.sp-row-with-kids::before {
  display: none;
}

/* Secondary grouping headers (Priority, Status, Tag, Due …) sit one
   level inside the section block; small-caps, muted, tabular count
   for a clean Todoist/OmniFocus-style mini-header. */
.task-subgroup-header {
  margin: .85rem 0 .15rem .5rem;
  padding: 0;
  font-size: .67rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--gv-fg3);
  display: flex;
  align-items: center;
  gap: .3rem;
}
.task-subgroup-header > .text-muted {
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--gv-fg4) !important;
}
.task-subgroup-header i { font-size: 1em; opacity: .7; }
/* First subgroup directly under a section header doesn't need the big
   top margin — keeps the rhythm tight. */
.section-header + .task-subgroup-header,
.sp-children > .task-subgroup-header:first-child {
  margin-top: .25rem;
}

/* Empty drop targets are invisible until a drag is in progress, then they
   show a dashed outline + "Drop tasks here" hint. */
.task-list.is-empty[data-target-project] {
  min-height: 0;
  border: 1px dashed transparent;
  border-radius: .25rem;
  padding: 0;
  transition: min-height .15s, padding .15s, border-color .15s, background .15s;
}
.task-list.is-empty[data-target-project] .task-empty-hint {
  display: none;
  text-align: center;
  font-style: italic;
  margin: 0;
}
body.is-dragging .task-list.is-empty[data-target-project] {
  min-height: 44px;
  border-color: var(--gv-bg3);
  padding: .65rem;
  background: rgba(250, 189, 47, .04);
}
body.is-dragging .task-list.is-empty[data-target-project] .task-empty-hint {
  display: block;
}
body.is-dragging .task-list.is-empty[data-target-project]:hover {
  border-color: var(--gv-yellow);
  background: rgba(250, 189, 47, .12);
}
.task-row {
  transition: background .12s ease;
}
.task-row:hover {
  background: var(--gv-bg0-soft);
}
/* Task title — quiet weight, comfortable line-height. The flag icon
   sits inline before the title; keep them visually attached. */
.task-title {
  font-size: .93rem;
  line-height: 1.35;
  color: var(--gv-fg1);
}
.task-title .bi-flag-fill {
  font-size: .82em;
  margin-right: .2rem;
  vertical-align: baseline;
}
.task-row.done .task-title {
  text-decoration: line-through;
  color: var(--gv-gray);
}
/* "Next" (next available task in a sequential project) is signalled by
   the NEXT chip in the title row — no left-edge accent bar. */
/* Priority is signalled by the completion-circle tint (below) and the
   priority chip in the title row — no left-edge accent bar, which read
   as visual clutter across a dense list. */
/* Tint the empty completion circle to match the priority — picks up
   the row colour the way Todoist does on its checkbox ring. */
.task-row.task-priority-1 .bi-circle { color: var(--gv-red-br); }
.task-row.task-priority-2 .bi-circle { color: var(--gv-orange-br); }
.task-row.task-priority-3 .bi-circle { color: var(--gv-yellow-br); }
/* ---- Colour by project / customer (services/colors.py) -------------------
   A swatch of the work the row belongs to, carried on ``--dot``. In a dense
   list it is a DOT, not a left-edge bar: the bar idiom was tried for priority
   and for NEXT and read as clutter down a long list (see above), and it would
   also collide with the sub-task indent rail, which owns the left edge.
   The dot sits at the start of the title column, so a column of them lines up
   and the eye can run down it.
   The colour never becomes text and never becomes the whole background: what
   has to hold in both a dark and a light surface is only swatch-against-
   surface, which a mid-tone hue does either way. */
.item-dot {
  display: inline-block;
  width: .52rem;
  height: .52rem;
  border-radius: 50%;
  margin-right: .38rem;
  background: var(--dot, var(--gv-gray));
  /* A colour on its own background needs an edge or it dissolves into a
     similarly-toned surface; this one is the surface, at 45% over the hue. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--dot) 45%, transparent);
  vertical-align: middle;
  flex: 0 0 auto;
}
/* Blocked rows = deferred or sitting behind an earlier sibling in a
   sequential project. Show them, but dim them so the user knows. */
.task-row.blocked {
  opacity: .55;
}
.task-row.blocked .task-title { color: var(--gv-fg4); }
.task-row.blocked:hover { opacity: .85; }
/* Sub-task rows. The indent is the tree view's idiom, not a second one: step
   in by the depth and hang a dashed rail off the left edge, exactly what
   ``.tree-tasks`` does per level — one level in is then unmistakable without
   having to compare the row to its neighbours.

   The step is a variable so it can shrink where the width is dear; the row
   carries only ``--indent-level``. Indent with MARGIN, because a border sits
   outside the padding and the rail has to land at the indent, not at the far
   left of the row. */
:root { --task-indent-step: 26px; }
@media (max-width: 767.98px) { :root { --task-indent-step: 14px; } }
.task-row.subtask {
  margin-left: calc(var(--indent-level, 0) * var(--task-indent-step));
  border-left: 1px dashed var(--gv-bg3);
  padding-left: .5rem;
}
.task-row.subtask .task-title { color: var(--gv-fg3); }
/* Collapse chevron — small, muted, only visible on rows that have
   children. Placeholder keeps task titles aligned across rows. */
.task-collapse-toggle {
  width: 1rem;
  color: var(--gv-fg4);
  text-decoration: none;
  font-size: .75rem;
  line-height: 1;
  background: none;
  border: 0;
  padding: 0;
}
.task-collapse-toggle:hover { color: var(--gv-fg1); }
.task-collapse-spacer { display: inline-block; width: 1rem; }
/* Inspector "i" icon — sits next to the title, hidden until hover so it
   doesn't crowd the row. */
.task-inspector-btn {
  margin-left: .35rem;
  color: var(--gv-fg4);
  text-decoration: none;
  opacity: 0;
  font-size: .85rem;
}
.task-row:hover .task-inspector-btn { opacity: .65; }
.task-inspector-btn:hover { opacity: 1 !important; color: var(--gv-aqua-br); }

/* Arrow-key navigation cursor (shortcuts.js) — highlights the row that
   i / m / d act on. */
.kbd-cursor {
  outline: 2px solid var(--gv-aqua-br);
  outline-offset: -2px;
  border-radius: 4px;
}
.kbd-cursor .task-inspector-btn { opacity: .65; }

.deferred-chip,
.plan-chip,
.followup-chip {
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  margin-right: .3rem;
  font-size: .72rem;
  color: var(--gv-fg4);
}
.deferred-chip i,
.plan-chip i,
.followup-chip i { font-size: .85em; }
.plan-chip { color: var(--gv-aqua); }
.plan-chip.plan-today { color: var(--gv-aqua-br); font-weight: 600; }
.followup-chip { color: var(--gv-blue); }
.followup-chip.overdue { color: var(--gv-red-br); font-weight: 600; }

/* ============= External calendar events (Today) ============= */
.calendar-events { display: flex; flex-direction: column; gap: .3rem; }
.cal-event {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  padding: .4rem .6rem;
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-left: 4px solid var(--gv-aqua);
  border-radius: .35rem;
  font-size: .85rem;
}
.cal-event-main { display: flex; align-items: center; gap: .65rem; }
/* "## Briefing" of a linked note, inline under the event row */
.cal-event-brief {
  font-size: .78rem;
  color: var(--gv-fg2);
  padding-left: calc(120px + .65rem);
  border-top: 1px dashed var(--gv-bg2);
  padding-top: .3rem;
}
@media (max-width: 640px) {
  .cal-event-brief { padding-left: 0; }
}
.cal-event-time {
  color: var(--gv-fg4);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-size: .78rem;
  min-width: 120px;
  flex-shrink: 0;
}
.cal-event-title { flex: 1; min-width: 8rem; color: var(--gv-fg1); }
.cal-event-title a { color: var(--gv-fg1); text-decoration: none; }
.cal-event-title a:hover { color: var(--gv-yellow-br); }
.cal-event-loc, .cal-event-cal {
  font-size: .72rem;
  color: var(--gv-fg4);
}
/* One line, cut with an ellipsis: a Teams join URL is 300 characters
   and would otherwise squeeze the title into a column of single words */
.cal-event-loc {
  min-width: 0;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-event-loc a { color: inherit; }
.cal-event-cal, .cal-event-main > .btn, .cal-event-main > button { flex-shrink: 0; }
.cal-swatch {
  width: 14px; height: 14px;
  border-radius: 50%;
  display: inline-block;
}
.cal-day-header {
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  border-bottom: 1px dashed var(--gv-bg2);
  padding-bottom: .15rem;
}
.cal-empty-hint {
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  color: var(--gv-fg2);
}

.tag-color-picker {
  width: 56px;
  height: 38px;
  padding: 2px;
}
.tag-color-hex {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
  max-width: 160px;
}

/* Perspective customisation list */
.perspective-list { min-height: 40px; }
.perspective-list .list-group-item {
  cursor: grab;
  background: var(--gv-bg0-soft);
}
.perspective-list .list-group-item:active { cursor: grabbing; }
.perspective-list .list-group-item:hover { background: var(--gv-bg1); }
.perspective-ghost {
  background: rgba(250, 189, 47, .12) !important;
  outline: 1px dashed var(--gv-yellow);
  opacity: .6;
}

/* Integration cards (Google Calendar etc.) */
.integration-card {
  border: 1px solid var(--gv-bg2);
}
.integration-card.integration-disabled {
  opacity: .55;
}

/* OAuth setup steps */
.oauth-steps {
  list-style: none;
  padding-left: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.oauth-steps > li,
.oauth-step {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
}
.oauth-step-num {
  flex: 0 0 28px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--gv-bg2);
  color: var(--gv-fg2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: .85rem;
}
.oauth-step-num.done {
  background: var(--gv-green-br);
  color: #1d2021;
}
/* ============= Comments inside inspector ============= */
.comments-block .comments-list {
  height: 240px;
  min-height: 80px;
  overflow-y: auto;
  resize: vertical;            /* browser-native drag-handle */
  margin-bottom: .5rem;
  padding: .5rem;
  background: var(--gv-bg0);
  border: 1px solid var(--gv-bg2);
  border-radius: .35rem;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}
.comments-block.is-expanded .comments-list {
  height: 60vh;
}
/* Comments render newest-first in the DOM (the template reverses them, each
   row keeps its real data-index), so the default normal column shows the
   newest at the top and the scroll box starts there — no scrolling to find
   the latest. The optional "oldest first" toggle flips the visual order via
   column-reverse. Applies to the inspector list and the detail page. */
.comments-block .comments-list.oldest-first,
.task-detail-comments.oldest-first {
  flex-direction: column-reverse;
}
.task-detail-comments {
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.comments-block .comments-empty {
  margin: auto;
  text-align: center;
  font-style: italic;
}

.comment-item {
  display: grid;
  grid-template-columns: 32px 1fr;
  gap: .55rem;
  padding: .55rem .7rem;
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-left: 3px solid var(--gv-bg3);
  border-radius: .35rem;
}
.comment-item.is-public {
  border-left-color: var(--gv-green-br);
}
.comment-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--gv-bg2);
  color: var(--gv-yellow-br);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: .85rem;
  text-transform: uppercase;
}
.comment-header {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}
.comment-author { font-size: .9rem; color: var(--gv-fg1); }
.comment-time { font-size: .75rem; color: var(--gv-fg4); }
.comment-text {
  margin-top: .2rem;
  font-size: .9rem;
  color: var(--gv-fg1);
  /* Body is rendered Markdown (HTML with <br>/<p>), NOT plain text — so it
     must NOT be pre-wrap. With pre-wrap the literal "\n" that nl2br leaves
     after each "<br />" rendered as a SECOND line break, doubling the spacing
     on every comment (API + Zammad/Jira imports alike). */
  line-height: 1.45;
  word-break: break-word;
}

.comment-visibility-toggle {
  background: transparent;
  border: 0;
  padding: 0;
  margin-left: auto;
  cursor: pointer;
}
.comment-visibility-toggle:hover .badge {
  filter: brightness(1.2);
}

.comments-expand-toggle {
  line-height: 1;
}
.comments-expand-toggle:hover {
  color: var(--gv-yellow-br) !important;
}

/* Compact count chip in task rows */
.task-comment-count {
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  margin-right: .3rem;
  padding: 0 .35rem;
  border-radius: .25rem;
  background: var(--gv-bg2);
  color: var(--gv-fg3);
  font-size: .72rem;
}
.task-comment-count i { font-size: .8em; }

/* The chip is also the handle for the hover panel (comment-peek.js). */
.comment-peek-trigger { cursor: pointer; }
.comment-peek-trigger:hover,
.comment-peek-trigger[aria-expanded="true"] { background: var(--gv-bg2); color: var(--gv-fg1); }
.comment-peek-trigger:focus-visible { outline: 2px solid var(--gv-yellow); outline-offset: 1px; }

/* Comment hover panel. Lives in <body> (never inside a scrolling list or a
   kanban column, which would clip it) and is placed in fixed coordinates by
   comment-peek.js; the max-height it sets is what makes this scroll. */
.comment-peek {
  position: fixed;
  z-index: 2040;  /* over the inspector (1048), under the md lightbox (2050) */
  width: min(30rem, calc(100vw - 1rem));
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-radius: .4rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  font-size: .82rem;
}
.comment-peek[hidden] { display: none; }
.comment-peek-inner { padding: .5rem .6rem .6rem; }
.comment-peek-status { color: var(--gv-fg4); padding: .25rem 0; }
.comment-peek-head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: baseline;
  gap: .35rem;
  margin: -.5rem -.6rem .4rem;
  padding: .4rem .6rem;
  background: var(--gv-bg1);
  border-bottom: 1px solid var(--gv-bg2);
  color: var(--gv-fg3);
  font-size: .74rem;
}
.comment-peek-count { font-weight: 600; color: var(--gv-fg2); }
.comment-peek-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--gv-fg4);
}
.comment-peek-item { padding: .35rem 0; border-top: 1px solid var(--gv-bg1); }
.comment-peek-item:first-child { border-top: 0; }
.comment-peek-item.is-public { border-left: 2px solid var(--gv-green); padding-left: .4rem; }
.comment-peek-meta {
  display: flex;
  align-items: baseline;
  gap: .3rem;
  color: var(--gv-fg3);
  font-size: .72rem;
}
.comment-peek-meta .badge { font-size: .6rem; padding: .1rem .25rem; }
.comment-peek-time { margin-left: auto; color: var(--gv-fg4); white-space: nowrap; }
.comment-peek-body { color: var(--gv-fg1); }
.comment-peek-body > :last-child { margin-bottom: 0; }
/* A screenshot in a comment must not become the whole panel. */
.comment-peek-body img { max-height: 140px; }

.next-chip {
  display: inline-block;
  margin-left: .4rem;
  padding: 0 .35rem;
  font-size: .6rem;
  font-weight: 600;
  letter-spacing: .08em;
  border-radius: .2rem;
  border: 1px solid var(--gv-aqua);
  color: var(--gv-aqua);
  background: transparent;
  vertical-align: middle;
}
/* Middle grid column of a task row. min-width:0 lets the 1fr column shrink
   below its content's intrinsic width so a long title / chip (e.g. a location)
   wraps instead of pushing the right-hand actions (flag / delete) out of the
   row. Without it, grid 1fr columns default to min-width:auto and overflow. */
.task-main { min-width: 0; }
.task-title { overflow-wrap: anywhere; }

.task-meta {
  font-size: .75rem;
  color: var(--gv-fg4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem .7rem;
  margin-top: .15rem;
}
/* Tone down Bootstrap badge colours that bleed in via project /
   assignment chips — keep them as outline-only markers. */
.task-meta .badge {
  margin: 0;
  background: transparent !important;
  color: var(--gv-fg3) !important;
  border: 1px solid var(--gv-bg2);
  font-weight: 400;
  padding: .05rem .4rem;
}
.task-meta .badge.text-bg-info { color: var(--gv-blue-br) !important; }
.task-meta .badge.text-bg-warning { color: var(--gv-yellow) !important; }
/* Related-items marker stands out (blue accent) so a linked task is obvious. */
.task-meta .task-related-count {
  color: var(--gv-blue-br) !important;
  border-color: var(--gv-blue-br);
}

/* Multi-day Today: give each day its own clearly separated card so the days
   don't visually run together when several are shown at once. */
.daily-buckets { display: flex; flex-direction: column; gap: 1.1rem; }
.daily-bucket {
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-radius: 10px;
  padding: .55rem .75rem .7rem;
}
.daily-bucket-header {
  border-bottom: 1px solid var(--gv-bg2);
  padding-bottom: .4rem;
  margin-bottom: .55rem;
}
.daily-bucket-header h3 { font-weight: 600; letter-spacing: .01em; color: var(--gv-fg1); }
.daily-bucket-header .bi { color: var(--gv-fg4); }
/* The actual current day stands out with the accent colour. */
.daily-bucket.is-today { border-color: var(--gv-blue-br); }
.daily-bucket.is-today .daily-bucket-header { border-bottom-color: var(--gv-blue-br); }
.daily-bucket.is-today .daily-bucket-header h3,
.daily-bucket.is-today .daily-bucket-header .bi { color: var(--gv-blue-br); }
.daily-bucket-today-chip {
  font-size: .65rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  font-weight: 600;
  color: var(--gv-bg0-hard);
  background: var(--gv-blue-br);
  border-radius: 999px;
  padding: .05rem .45rem;
}

/* Inspector header title → clickable link to the full detail page. */
.inspector-title-link { color: inherit; text-decoration: none; }
.inspector-title-link:hover,
.inspector-title-link:focus { color: var(--gv-blue-br); text-decoration: underline; }

.task-note {
  /* No pre-wrap: this is rendered Markdown, and the renderer already turns the
     note's own line breaks into <br>. Honouring the source newlines on top of
     that put a blank line between every pair of lines in the excerpt. */
  margin-top: .1rem;
  font-size: .72rem;
  color: var(--gv-fg4);
  font-style: italic;
  opacity: .8;
  /* Trim long notes to a couple of lines with an ellipsis — no scrollbar.
     The full text stays available in the task detail/inspector. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.task-row:hover .task-note { opacity: 1; }

/* Project color dot — small swatch next to project names. */
.proj-dot {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  margin-right: .35rem;
  vertical-align: middle;
}

/* Markdown body — tight defaults so it doesn't punch a hole through
   compact layouts (task rows, comments). */
.md-body > :first-child { margin-top: 0; }
.md-body > :last-child { margin-bottom: 0; }
.md-body p { margin: 0 0 .35em; }
.md-body ul, .md-body ol { margin: .1em 0 .35em 1.25em; padding: 0; }
.md-body li { margin: 0; }
/* Markdown task lists (``- [ ]`` / ``- [x]``, see filters._checkboxes): the
   checkbox stands in for the bullet, so drop the marker and pull the row back
   flush. Interactive only inside a note body — elsewhere they render disabled. */
.md-body li.md-task { list-style: none; margin-left: -1.15em; }
.md-body li.md-task input { margin-right: .4em; vertical-align: -1px; }
.md-body code {
  font-size: .88em;
  padding: 0 .25em;
  background: var(--gv-bg1);
  border-radius: .2em;
}
.md-body pre {
  background: var(--gv-bg1);
  border-radius: .25em;
  padding: .5em .7em;
  overflow-x: auto;
  margin: .35em 0;
}
.md-body pre code { background: transparent; padding: 0; }
.md-body blockquote {
  border-left: 2px solid var(--gv-bg3);
  padding-left: .65em;
  color: var(--gv-fg3);
  margin: .25em 0;
}
.md-body h1, .md-body h2, .md-body h3,
.md-body h4, .md-body h5, .md-body h6 {
  font-size: 1em;
  font-weight: 600;
  margin: .4em 0 .2em;
}
/* Inline images in rendered markdown stay inside their column; click to open
   the full-size version in the lightbox (see md-lightbox.js). */
.md-body img {
  max-width: 100%;
  max-height: 360px;
  height: auto;
  border-radius: 6px;
  cursor: zoom-in;
}
.md-lightbox {
  position: fixed;
  inset: 0;
  z-index: 2050;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background: rgba(0, 0, 0, .82);
  cursor: zoom-out;
}
.md-lightbox.is-open { display: flex; }
.md-lightbox img {
  max-width: 95vw;
  max-height: 95vh;
  border-radius: 6px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .5);
}

.task-note.md-body { display: -webkit-box; }
/* The 2-line row excerpt is text-only — images would blow up the row height;
   they stay available on the task detail page. */
.task-note.md-body img { display: none; }
/* The task-row excerpt clamps to 2 lines via -webkit-line-clamp;
   the box-orient was already set on .task-note. */

/* Tag chips — neutral chip background with the tag's colour as a
   leading dot. Decouples text contrast from the user-picked tag
   colour (the previous design used the tag colour as background +
   dark text, which failed on saturated/dark colours like
   Gruvbox red/blue). */
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: 0 .45rem;
  border-radius: .25rem;
  background: var(--gv-bg2) !important;  /* overrides inline style */
  color: var(--gv-fg1);
  font-size: .72rem;
  line-height: 1.55;
  margin: 0;
  font-weight: 500;
}
.tag-chip::before {
  content: "";
  display: inline-block;
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  background: var(--tag-color, var(--gv-fg4));
  flex: 0 0 .55rem;
}
.tag-chip:hover { background: var(--gv-bg3) !important; }

/* The × that takes a label off where it stands, in any task list. The space
   is always reserved and only the glyph fades in on the row — revealing it by
   growing the chip would reflow a row of labels under the pointer, which is
   how you mis-click the one next to it. Focus shows it too, so Tab reaches it
   without a mouse. */
.tag-chip-remove {
  display: inline-flex;
  align-items: center;
  border: 0;
  background: none;
  color: inherit;
  padding: 0 0 0 .1rem;
  margin-right: -.1rem;
  font-size: .95rem;
  line-height: 1;
  opacity: 0;
  cursor: pointer;
  transition: opacity .12s, color .12s;
}
.task-row:hover .tag-chip-remove,
.kanban-card:hover .tag-chip-remove,
.subtask-item:hover .tag-chip-remove,
.tree-task:hover .tag-chip-remove,
tr:hover .tag-chip-remove {
  opacity: .5;
}
.tag-chip-remove:hover,
.tag-chip-remove:focus-visible {
  opacity: 1;
  color: var(--gv-red-br);
}

/* A label offered beside a comment box: click to arm it and it comes off the
   item when the comment is posted. Armed has to read as "this one is going",
   so it takes the delete colour and a strike-through — nothing is removed
   until the comment is actually submitted. */
.comment-drop-label { cursor: pointer; opacity: .7; }
.comment-drop-label:hover { opacity: 1; }
.btn-check:checked + .comment-drop-label {
  opacity: 1;
  color: var(--gv-red-br);
  text-decoration: line-through;
  box-shadow: inset 0 0 0 1px var(--gv-red-br);
}
.btn-check:focus-visible + .comment-drop-label {
  outline: 2px solid var(--gv-blue-br, #83a598);
  outline-offset: 1px;
}

/* priorities with Gruvbox colors */
.priority-p1 { color: var(--gv-red-br); font-weight: 700; }
.priority-p2 { color: var(--gv-orange-br); font-weight: 600; }
.priority-p3 { color: var(--gv-yellow-br); }
.priority-p4 { color: var(--gv-gray); }
.overdue { color: var(--gv-red-br); font-weight: 600; }

a, a:visited {
  color: var(--gv-aqua-br);
  text-decoration: none;
}
a:hover { color: var(--gv-yellow-br); }

hr { border-color: var(--gv-bg2); }

code, pre {
  background: var(--gv-bg0-hard);
  color: var(--gv-orange-br);
  border-radius: .25rem;
  padding: 0 .25rem;
}

/* close-button on alerts should be light */
.btn-close { filter: invert(1) grayscale(100%) brightness(150%); }

/* ============= Inspector (docked right-side panel, OmniFocus-style) ============= */
:root {
  --inspector-width: 460px;
}
.offcanvas.inspector {
  --bs-offcanvas-width: var(--inspector-width);
  background: var(--gv-bg0-soft);
  color: var(--gv-fg1);
  border-left: 1px solid var(--gv-bg2);
  box-shadow: none;             /* docked, not floating */
  visibility: visible !important; /* don't hide via CSS; Bootstrap toggles .show */
  /* In-flow column inside the .app-layout flex row (like the project
     sidebar) — never a floating overlay, so it can't cover content.
     Open = reserves its width; closed = collapses to zero. */
  position: sticky;
  top: var(--nav-height, 56px);
  /* align-self keeps the flex container (align-items: stretch) from stretching
     this sticky child — a stretched sticky flex item triggers a WebKit/Safari
     bug where the panel renders horizontally displaced until the next scroll
     repaints it. flex-start lets sticky position correctly on first paint. */
  align-self: flex-start;
  height: calc(100vh - var(--nav-height, 56px));
  flex: 0 0 var(--inspector-width);
  width: var(--inspector-width);
  max-width: var(--inspector-width);
  /* translateZ(0) instead of `none`: still no horizontal translate (so the
     panel collapses by width, not by sliding), but it promotes the panel to
     its own compositing layer. Without that, WebKit/Safari mis-paints the
     sticky flex child on the FIRST open after it expands from width:0 — the
     content renders horizontally displaced until a repaint (a second open or a
     scroll) settles it. The layer forces a correct paint from the first frame. */
  transform: translateZ(0) !important;
  transition: flex-basis .25s ease, width .25s ease;
  z-index: auto;
}
.offcanvas.inspector:not(.show) {
  /* Collapse the column so the main content reclaims the full row.
     Bootstrap toggles .show; we override its transform/visibility. */
  flex-basis: 0;
  width: 0;
  max-width: 0;
  min-width: 0;
  overflow: hidden;
  border-left: 0;
  pointer-events: none;
}
body.inspector-docked #bulk-bar {
  left: calc(50% - var(--inspector-width) / 2);
}
@media (max-width: 992px) {
  /* Narrow screens: a real column would crush the content, so fall back
     to a floating overlay that slides in from the right. */
  .offcanvas.inspector {
    position: fixed;
    top: 0;
    right: 0;
    height: 100%;
    z-index: 1045;
    flex-basis: auto;
    box-shadow: -8px 0 30px rgba(0, 0, 0, .35);
    transition: transform .3s ease;
  }
  .offcanvas.inspector:not(.show) {
    width: var(--inspector-width);
    max-width: var(--inspector-width);
    border-left: 1px solid var(--gv-bg2);
    transform: translateX(100%) !important;
  }
  body.inspector-docked #bulk-bar { left: 50%; }
}
.offcanvas.inspector .offcanvas-header {
  background: var(--gv-bg1);
  border-bottom: 1px solid var(--gv-bg2);
  padding: .65rem 1rem;
  position: sticky;
  top: 0;
  z-index: 2;
}
.offcanvas.inspector .offcanvas-title {
  color: var(--gv-yellow-br);
  font-weight: 600;
  letter-spacing: .02em;
}
.inspector-pin {
  color: var(--gv-fg3);
  line-height: 1;
}
.inspector-pin:hover { color: var(--gv-yellow-br); }
.inspector-pin.is-pinned { color: var(--gv-yellow-br); }
.offcanvas.inspector .offcanvas-body {
  padding: 0;
  display: flex;
  flex-direction: column;
}

.inspector-form {
  padding: .8rem .85rem 0;
  flex: 1 1 auto;
  overflow-y: auto;
  /* Never let an over-wide field (long tag/project label, etc.) spill past the
     panel and drag a horizontal scrollbar onto the whole page. */
  overflow-x: hidden;
  /* Each section is its own card; the gap does the spacing between them so
     scanning for the right block is easy (no more one-long-mass column). */
  display: flex;
  flex-direction: column;
  gap: .55rem;
}
/* The comments block lives OUTSIDE .inspector-form (kept separate so
   posting a comment doesn't submit the rest of the form), but it
   should still line up visually — same horizontal padding as the
   form sections above it. */
.offcanvas.inspector .offcanvas-body > .comments-block {
  padding: 0 1.1rem 1rem;
  margin-bottom: 0;
}
/* A section is a self-contained card: slightly lighter than the panel gutter
   with a full border, so each block is a discrete, findable unit. Inputs sit
   on the darker --gv-bg0 inside, giving an inset look that separates
   controls from their grouping card. */
.inspector-section {
  margin-bottom: 0;
  padding: .7rem .8rem;
  background: var(--gv-bg1);
  border: 1px solid var(--gv-bg2);
  border-radius: .5rem;
}
/* Extra breathing room before the sticky footer. */
.inspector-section:last-of-type {
  margin-bottom: .85rem;
}
/* The drag grip (added by inspector-reorder.js). Out of the way until the
   pointer is on the block: a handle on every section, always visible, would
   read as clutter in a panel this dense. */
.inspector-section[data-section-key] {
  position: relative;
}
.inspector-grip {
  position: absolute;
  top: .35rem;
  right: .4rem;
  line-height: 1;
  color: var(--gv-fg3);
  opacity: 0;
  cursor: grab;
  transition: opacity .12s ease-in-out;
}
.inspector-section[data-section-key]:hover .inspector-grip,
.inspector-grip:focus-visible {
  opacity: .55;
}
.inspector-grip:active {
  cursor: grabbing;
  opacity: .9;
}
.inspector-section-ghost {
  opacity: .35;
  border-style: dashed;
}

/* Highlight the block you're currently editing so you never lose your place. */
.inspector-section:focus-within {
  border-color: var(--gv-bg3);
}
.inspector-section .form-label {
  margin-bottom: .3rem;
  font-size: .72rem;
  letter-spacing: .07em;
  font-weight: 600;
  color: var(--gv-fg3) !important;
}
/* Two-column field row. Applies both to section-level grids
   (.inspector-section.grid-2) and grids nested inside a section
   (e.g. the follow-up date/cadence pair). */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .75rem;
}
/* Grid items default to min-width:auto, so a datetime-local input's intrinsic
   width blows out its track and squeezes the sibling (Due lost room to Defer).
   Let both columns shrink to equal 1fr tracks. */
.grid-2 > * {
  min-width: 0;
}
.inspector-form .form-control,
.inspector-form .form-select {
  background: var(--gv-bg0);
  border-color: var(--gv-bg2);
}
.inspector-form .form-control:focus,
.inspector-form .form-select:focus {
  background: var(--gv-bg0-hard);
  border-color: var(--gv-yellow);
}
.inspector-form .form-control-lg {
  font-size: 1.05rem;
  font-weight: 500;
}

.inspector-footer {
  position: sticky;
  bottom: 0;
  background: var(--gv-bg1);
  border-top: 1px solid var(--gv-bg2);
  padding: .65rem 1rem;
  display: flex;
  align-items: center;
  gap: .5rem;
  z-index: 2;
}

/* When the inspector is opened by Bootstrap, dim the backdrop subtly
   instead of full black so it feels more like a floating inspector. */
.offcanvas-backdrop.show { opacity: .35; }

/* Full-page fallback uses the same look, just framed in a card */
.inspector-static {
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-radius: .5rem;
  overflow: hidden;
}
.inspector-static .inspector-form { padding: 1rem 1.25rem 0; }
.inspector-static .inspector-footer { border-radius: 0; }

.inspector-saved-banner {
  background: var(--bs-success-bg-subtle);
  color: var(--gv-green-br);
  border-bottom: 1px solid var(--gv-green);
  padding: .35rem 1.1rem;
  font-size: .85rem;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: .35rem;
  transition: opacity .8s ease;
}
.inspector-saved-banner.fade-out { opacity: 0; }

/* ============= Quick-add modal ============= */
.quick-add-modal .modal-content {
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
}
.quick-add-modal .modal-footer { border-top-color: var(--gv-bg2); }
.quick-add-input {
  background: var(--gv-bg0-hard);
  border: 1px solid var(--gv-bg2);
  color: var(--gv-fg0);
  font-size: 1.05rem;
}
.quick-add-input:focus {
  background: var(--gv-bg0-hard);
  color: var(--gv-fg0);
  border-color: var(--gv-yellow);
  box-shadow: 0 0 0 .15rem rgba(250, 189, 47, .2);
}
.braindump-page {
  /* Fill the main area: viewport minus the 56px navbar and the .app-main
     py-3 padding (1rem top + 1rem bottom). */
  display: flex;
  flex-direction: column;
  height: calc(100vh - 56px - 2rem);
  /* It wears the note sheet, but as a working surface rather than a page:
     less padding, and it stretches instead of sitting in the middle. */
  padding: 1.25rem 1.5rem;
}
/* The pad is an outline on the note sheet now, not a monospace textarea —
   what you write here becomes tasks or a note, so it should already look like
   the thing it is about to become. */
.braindump-outline {
  flex: 1 1 auto;
  min-height: 12rem;
  overflow-y: auto;
}
/* A marked block converts whole, so say so while it is marked. */
.braindump-outline .ol-block ::selection { background: rgba(250, 189, 47, .28); }
.quick-add-preview {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
}
.qa-chip {
  display: inline-block;
  padding: .15rem .5rem;
  border-radius: .8rem;
  font-size: .72rem;
  letter-spacing: .03em;
  background: var(--gv-bg2);
  color: var(--gv-fg1);
}
.qa-chip.qa-tag      { background: var(--gv-blue-br); color: #1d2021; }
.qa-chip.qa-project  { background: var(--gv-orange-br); color: #1d2021; }
.qa-chip.qa-date     { background: var(--gv-aqua-br); color: #1d2021; }
.qa-chip.qa-prio     { background: var(--gv-red-br); color: #fbf1c7; font-weight: 600; }
.qa-chip.qa-flag     { background: var(--gv-yellow-br); color: #1d2021; font-weight: 600; }
.qa-chip.qa-assignee { background: var(--gv-purple-br, #d3869b); color: #1d2021; }

/* The share marker item_icon() hangs off a shared folder / project icon. */
.item-icon-share {
  font-size: .68em;
  vertical-align: super;
  margin-left: .1rem;
  color: var(--gv-blue-br);
}

/* Favorite star toggle — low-key until hovered or active (gold when favorited). */
.fav-star {
  border: 0;
  background: transparent;
  color: var(--gv-fg4);
  line-height: 1;
  padding: 0 .25rem;
  cursor: pointer;
}
.fav-star:hover { color: var(--gv-yellow-br); }
.fav-star.is-fav { color: var(--gv-yellow-br); }
/* In list/sidebar rows, fade the empty star in only on hover to stay quiet. */
.fav-star-hover { opacity: 0; transition: opacity .12s; }
.task-row:hover .fav-star-hover,
.nav-fav-row:hover .fav-star-hover,
tr:hover .fav-star-hover,
.fav-star-hover.is-fav { opacity: 1; }
/* Sidebar: keep the hover star OUT of normal flow so an invisible star never
   reserves width / forces the nav label to wrap. It floats over the row's right
   edge on hover; the count badge yields to it. */
.nav-fav-row { position: relative; }
.nav-fav-row .fav-star-hover {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  z-index: 2;
}
.nav-fav-row:hover .nav-count { visibility: hidden; }
/* While the star floats over the row, the label gives way to it instead of
   running underneath. */
.nav-fav-row:has(> .fav-star-hover):hover > .nav-link { padding-right: 1.6rem; }
/* A starred entry inside a group shows its star all the time; floating, it sat
   on top of the row's count badge. Such a row puts the star in the flow, after
   the badge, and keeps the badge visible. */
.nav-fav-row:has(> .fav-star-hover.is-fav) > .fav-star-hover {
  position: static;
  transform: none;
  flex: 0 0 auto;
}
.nav-fav-row:has(> .fav-star-hover.is-fav):hover > .nav-link { padding-right: .6rem; }
/* A row never grows past the sidebar: Bootstrap's .nav wraps, and a wrapping
   column takes the widest row's min-content as everyone's width, which pushed
   every star of that group past the scroll box's edge. */
.app-nav-sidebar .nav-group-body { flex-wrap: nowrap; }
.nav-fav-row, .nav-fav-row > .nav-link { min-width: 0; }
.nav-fav-row:has(> .fav-star-hover.is-fav):hover .nav-count { visibility: visible; }

/* @person / +tag autocomplete dropdown in the quick-add modal */
.qa-assignee-ac {
  margin-top: .35rem;
  border: 1px solid var(--gv-bg3);
  border-radius: .35rem;
  background: var(--gv-bg0-soft, var(--gv-bg1));
  overflow: hidden;
  max-height: 13rem;
  overflow-y: auto;
}
.qa-ac-item {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  color: var(--gv-fg1);
  padding: .35rem .6rem;
  font-size: .9rem;
}
/* No `:hover` here on purpose. This list pops open under the caret while the
   user is typing, which puts it under a mouse pointer that never moved — the
   row below it lit up "already selected" the moment `@` was typed, in the same
   colour as the keyboard cursor, so two rows looked picked at once. The
   keyboard cursor (`.active`, set only by ↓/↑) is the one and only mark; a
   click still works through the mousedown handler. */
.qa-ac-item { cursor: pointer; }
.qa-ac-item.active {
  background: var(--gv-bg2);
  box-shadow: inset 2px 0 0 var(--gv-yellow-br);
}
.qa-ac-item .bi { color: var(--gv-fg4); margin-right: .35rem; }
.quick-add-hint code {
  background: var(--gv-bg1);
  color: var(--gv-fg2);
  border: 1px solid var(--gv-bg2);
  padding: 0 .3rem;
}

/* ============= Bulk action bar ============= */
.bulk-bar {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  z-index: 1050;
  background: var(--gv-bg1);
  color: var(--gv-fg1);
  border: 1px solid var(--gv-bg3);
  border-radius: .5rem;
  padding: .55rem .8rem;
  display: flex;
  align-items: center;
  gap: .5rem;
  max-width: calc(100vw - 2rem);
  flex-wrap: wrap;
}
.bulk-bar .bulk-count {
  font-weight: 700;
  color: var(--gv-yellow-br);
}
.bulk-bar .dropdown-menu {
  background: var(--gv-bg0-soft);
  border-color: var(--gv-bg2);
}
.bulk-bar .dropdown-item { color: var(--gv-fg1); }
.bulk-bar .dropdown-item:hover {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
}
/* Bulk-selection checkboxes are functional but invisible — selection is
   driven by Cmd/Ctrl-click, Shift-click, Esc, Cmd+A. */
.bulk-check { display: none !important; }
/* Hide the table column entirely on the projects list. */
table td:has(.bulk-check) { display: none; }
/* The projects list has an (invisible) leading checkbox column. Hiding only
   the cells that *contain* a checkbox collapsed just the project rows, so
   their names slid one column left while the header and the Inbox row (whose
   leading cell is an empty placeholder, not a checkbox) stayed put. Hide the
   whole leading column instead — header cell + every row's first cell — but
   never the colspan rows (group headers, empty-state), whose single cell must
   keep spanning the table. */
.project-table > thead > tr > th:first-child,
.project-table > tbody > tr > td:first-child:not([colspan]) { display: none; }

/* Selected row affordance — clear without being visually loud. */
.task-row.selected {
  background: rgba(250, 189, 47, .08);
  box-shadow: inset 3px 0 0 var(--gv-yellow-br);
}
tr.selected td {
  background: rgba(250, 189, 47, .08) !important;
  box-shadow: inset 3px 0 0 var(--gv-yellow-br);
}

/* ============= Hover actions on rows ============= */
.row-actions { display: flex; gap: .25rem; justify-content: flex-end; }
.hover-action {
  opacity: 0;
  transition: opacity .12s;
}
tr:hover .hover-action,
.task-row:hover .hover-action,
.tree-project-header:hover .hover-action {
  opacity: 1;
}
/* The pencil stays visible to act as a hint of "row has actions". */
tr .row-actions .hover-action:last-child { opacity: .35; }
tr:hover .row-actions .hover-action:last-child { opacity: 1; }

.tree-row-actions {
  display: inline-flex;
  align-items: center;
  gap: .15rem;
  margin-left: auto;
}
.tree-row-actions .tree-action-btn {
  background: transparent;
  border: 0;
  padding: .1rem .35rem;
  border-radius: .25rem;
  color: var(--gv-fg4);
  text-decoration: none;
  opacity: 0;
  transition: opacity .12s, background .12s, color .12s;
  font-size: .8rem;
  line-height: 1;
}
.tree-project-header:hover .tree-action-btn,
.tree-task:hover .tree-action-btn { opacity: 1; }

/* List-row delete reveals on hover (mirrors tree-task hover). */
.task-row-hover-action { opacity: 0; transition: opacity .12s; }
.task-row:hover .task-row-hover-action,
tr:hover .task-row-hover-action { opacity: 1; }
.tree-row-actions .tree-action-btn:hover {
  background: var(--gv-bg2);
  color: var(--gv-yellow-br);
}

/* One-click NEXT on a task row: the arrow toggles the mark, the caret opens the
   durations. The caret is deliberately tiny and sits tight against the arrow so
   the two read as one control rather than two more icons in the action strip. */
.task-next-group { display: inline-flex; align-items: center; }
.task-next-toggle { color: var(--gv-fg4); line-height: 1; }
.task-next-toggle:hover { color: var(--gv-aqua-br); }
.task-next-caret {
  color: var(--gv-fg4); line-height: 1; font-size: .6rem;
  margin-left: -.1rem;
}
.task-next-caret:hover { color: var(--gv-aqua-br); }
/* The menu is opened from a row that is itself a drag handle and a link — keep
   the picker's own buttons compact so seven of them fit on two lines. */
.task-next-group .dropdown-menu .task-next-dur { min-width: 3rem; font-size: .75rem; }

/* ============= Kanban view ============= */
.kanban-board {
  display: flex;
  gap: .75rem;
  overflow-x: auto;
  padding-bottom: .75rem;
  align-items: flex-start;
}
.kanban-column {
  flex: 0 0 280px;
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2);
  border-radius: .5rem;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 250px);
}
.kanban-col-header {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .6rem .75rem;
  border-bottom: 1px solid var(--gv-bg2);
  background: var(--gv-bg1);
  border-top-left-radius: .5rem;
  border-top-right-radius: .5rem;
}
.kanban-col-name { font-weight: 600; flex: 1; }
.kanban-col-count {
  background: var(--gv-bg2);
  color: var(--gv-fg3);
  border-radius: .8rem;
  padding: 0 .5rem;
  font-size: .72rem;
}
.kanban-col-add {
  color: var(--gv-fg4);
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s, color .15s;
}
.kanban-column:hover .kanban-col-add { opacity: 1; }
.kanban-col-add:hover { color: var(--gv-yellow-br); }

.kanban-cards {
  padding: .5rem;
  overflow-y: auto;
  flex: 1;
  min-height: 60px;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
.kanban-card {
  display: flex;
  flex-direction: column;
  padding: .55rem .6rem;
  background: var(--gv-bg0-hard);
  border: 1px solid var(--gv-bg2);
  border-left: 3px solid var(--gv-bg3);
  border-radius: .35rem;
  font-size: .9rem;
  position: relative;
  cursor: grab;
  user-select: none;
}
.kanban-card:active { cursor: grabbing; }
.kanban-card.is-next { border-left-color: var(--gv-green-br); }
.kanban-card.blocked { opacity: .55; }
/* The grip icon is hidden in kanban — the whole card is the handle. */
.kanban-card .task-handle { display: none; }
.kanban-card-title { font-weight: 500; }
.kanban-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  margin-top: .35rem;
  font-size: .72rem;
  color: var(--gv-fg4);
}

.kanban-add-column {
  flex: 0 0 200px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 1rem 0;
}
.kanban-add-link {
  color: var(--gv-fg4);
  text-decoration: none;
  border: 1px dashed var(--gv-bg3);
  border-radius: .5rem;
  padding: .5rem 1rem;
  font-size: .85rem;
}
.kanban-add-link:hover {
  color: var(--gv-yellow-br);
  border-color: var(--gv-yellow);
}

/* ============= Calendar view ============= */
.task-calendar { margin-bottom: 1.5rem; }
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--gv-bg2);
  border: 1px solid var(--gv-bg2);
  border-radius: .35rem;
  overflow: hidden;
}
.calendar-weekday {
  background: var(--gv-bg1);
  padding: .4rem .5rem;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--gv-fg4);
  text-align: center;
}
.calendar-day {
  background: var(--gv-bg0-soft);
  min-height: 90px;
  padding: .25rem .35rem;
  position: relative;
}
.calendar-day.out-of-month {
  background: var(--gv-bg0);
  opacity: .45;
}
.calendar-day.is-today {
  background: rgba(250, 189, 47, .07);
  outline: 1px solid var(--gv-yellow);
  outline-offset: -1px;
}
.calendar-day-num {
  font-size: .8rem;
  color: var(--gv-fg3);
  font-weight: 500;
  margin-bottom: .2rem;
}
.calendar-day.is-today .calendar-day-num { color: var(--gv-yellow-br); font-weight: 700; }
.calendar-day-tasks { display: flex; flex-direction: column; gap: 2px; }
.calendar-task-pill {
  display: block;
  background: var(--gv-bg2);
  color: var(--gv-fg1);
  border-radius: .2rem;
  padding: .1rem .35rem;
  font-size: .7rem;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.calendar-task-pill:hover {
  background: var(--gv-yellow-br);
  color: #1d2021;
}
.calendar-task-pill.done { text-decoration: line-through; opacity: .55; }
.calendar-task-pill.blocked { opacity: .55; font-style: italic; }
.calendar-task-pill[draggable="true"] { cursor: grab; }
.calendar-task-pill.dragging { opacity: .4; }
/* Day highlighted while dragging a task over it (drag-to-plan) — both for
   native pill drags (.cal-drop-hover) and SortableJS row drags (the ghost
   row lands in the cell; we hide it and light up the day instead). */
.calendar-day.cal-drop-hover,
.calendar-day:has(.cal-drop-ghost) {
  outline: 2px dashed var(--gv-yellow-br);
  outline-offset: -2px;
  background: var(--gv-bg1);
}
.calendar-day .cal-drop-ghost {
  display: none !important;
}
.calendar-more {
  font-size: .65rem;
  color: var(--gv-fg4);
  padding-left: .35rem;
}

/* ============= Tree view ============= */
.tree-root .tree-project-header {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .35rem .5rem;
  border-radius: .25rem;
}
.tree-root .tree-project-header:hover {
  background: var(--gv-bg0-soft);
}
.tree-tasks {
  /* margin-left is set inline per-depth so the dashed border lines up
     under the project icon at any nesting level. */
  padding-left: .5rem;
  border-left: 1px dashed var(--gv-bg2);
  min-height: 8px;          /* drop target for empty containers */
}
.tree-task {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .2rem .35rem;
  border-radius: .25rem;
}
.tree-task:hover { background: var(--gv-bg0-soft); }
.tree-task .handle {
  cursor: grab;
  opacity: .35;
}
.tree-task:hover .handle { opacity: 1; }
.tree-ghost {
  background: var(--bs-warning-bg-subtle);
  outline: 1px dashed var(--gv-yellow);
  opacity: .5;
}

/* Project drag handle on the tree view. Mirrors .task-handle — hidden
   until row hover, grab cursor, fades up. */
.tree-project-header .project-handle {
  cursor: grab;
  opacity: 0;
  transition: opacity .12s;
}
.tree-project-header:hover .project-handle { opacity: .55; }
body.is-dragging .tree-project-header .project-handle { opacity: .55; }

/* Empty .tree-projects still needs to accept a drop, so give it a tiny
   minimum height — same trick used for .tree-tasks. */
.tree-projects {
  padding-left: .25rem;
  min-height: 8px;
}

/* Tree toolbar — sticky filter + quick-add bar at the top of the
   tree view. */
.tree-toolbar {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--gv-bg0);
  padding: .5rem 0 .65rem;
  margin-bottom: .5rem;
  border-bottom: 1px solid var(--gv-bg2);
}
.tree-filter-group { max-width: 320px; }

/* Filter hide: keep DOM intact (Sortable needs it) but visually drop
   non-matching rows. */
.tree-filter-hidden { display: none !important; }

/* Source / external-id chips on task rows + inspector. */
.source-chip {
  display: inline-flex;
  align-items: center;
  gap: .15rem;
  padding: 0 .35rem;
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  border-radius: .15rem;
  background: var(--gv-bg2);
  color: var(--gv-fg2);
  border: 1px solid transparent;
}
/* The ticket's own workflow status, read as written ("In Arbeit"). */
.source-chip-status { text-transform: none; letter-spacing: 0; font-weight: 500; }
.source-chip.source-jira    { background: rgba( 38,132,255,.15); color: #4aa3ff; border-color: rgba(38,132,255,.4); }
.source-chip.source-zammad  { background: rgba( 52, 73, 94,.20); color: #aac7e0; border-color: rgba(52,73,94,.5); }
.source-chip.source-todoist { background: rgba(228, 67, 50,.15); color: #ff8a7a; border-color: rgba(228,67,50,.4); }
.source-chip.source-csv     { background: rgba(131,165,152,.20); color: #b8d4cb; border-color: rgba(131,165,152,.4); }
.source-chip.source-helix   { background: rgba(215,153, 33,.15); color: #fabd2f; border-color: rgba(215,153,33,.4); }
.external-id-chip {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .7rem;
  color: var(--gv-fg4);
  padding: 0 .3rem;
  border-radius: .15rem;
  background: var(--gv-bg1);
}

/* Tag drag & drop visuals. The handle fades up on row hover, like
   tasks. ``.tag-drop-hover`` highlights the row being targeted as
   a new parent during drag. */
.tag-row:hover .tag-handle { opacity: 1 !important; }
.tag-row.tag-row-dragging { opacity: .45; }
.tag-row.tag-drop-hover,
.tag-drop-toplevel.tag-drop-hover {
  outline: 2px dashed var(--gv-yellow);
  background: rgba(250, 189, 47, .08);
  border-radius: .25rem;
}
.tag-drop-toplevel {
  border: 1px dashed var(--gv-bg2);
  border-radius: .25rem;
}
.tag-row .hover-action { opacity: 0; transition: opacity .12s; }
.tag-row:hover .hover-action { opacity: 1; }

/* Project-sidebar drop targets — light up during a drag so every
   valid destination is visible at a glance; the hover gets a
   stronger highlight so the precise target is unambiguous.
   The ghost that SortableJS inserts inside the <li> would otherwise
   visibly push the rest of the sidebar down, making it look as if
   the dragged item lands BETWEEN projects. Hide that artefact. */
body.is-dragging .ps-drop-target {
  outline: 1px dashed var(--gv-bg3);
  outline-offset: -2px;
  background: rgba(250, 189, 47, .04);
  transition: outline-color .15s, background .15s;
}
body.is-dragging .ps-drop-target:hover,
.ps-drop-target.is-drag-over {
  background: rgba(250, 189, 47, .25);
  outline: 2px solid var(--gv-yellow-br);
  outline-offset: -2px;
  box-shadow: 0 0 0 2px rgba(250, 189, 47, .35);
}
.ps-drop-target > .task-row,
.ps-drop-target > .tree-task,
.ps-drop-target > .drag-ghost {
  display: none !important;
}
.ps-drop-target.ps-drop-flash {
  background: rgba(184, 187, 38, .25);
  transition: background .6s;
}
.ps-row { display: flex; align-items: center; gap: .35rem; }

/* ============= Today calendar (timeline) view ============= */
.day-calendar {
  border: 1px solid var(--gv-bg2);
  border-radius: .35rem;
  background: var(--gv-bg0-soft);
  overflow: hidden;
  --day-cols: 1;
}
.day-calendar[data-days="3"]  { --day-cols: 3; }
.day-calendar[data-days="7"]  { --day-cols: 7; }
.day-calendar[data-days="14"] { --day-cols: 14; }
.day-grid-row {
  display: grid;
  grid-template-columns: 80px repeat(var(--day-cols), 1fr);
  border-top: 1px solid var(--gv-bg2);
  min-height: 38px;
}
.day-grid-row:first-child { border-top: 0; }
.day-grid-head { background: var(--gv-bg1); }
.day-col-head {
  padding: .35rem .5rem;
  border-left: 1px solid var(--gv-bg2);
}
.day-allday-row { background: rgba(131, 165, 152, .06); }
.day-hour-label {
  color: var(--gv-fg4);
  font-size: .75rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  padding: .35rem .5rem;
  text-align: right;
  border-right: 1px solid var(--gv-bg2);
  background: var(--gv-bg0-soft);
}
.day-col-cell {
  border-left: 1px solid var(--gv-bg2);
  padding: .25rem .35rem;
  min-height: 38px;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  align-content: flex-start;
  position: relative;        /* anchor absolutely-positioned task pills */
}
.day-hour-slots { overflow: visible; }
.day-hour-slots { /* alias kept for backwards-compat */ }

/* Compact week / month grid for ranges > 7 days. Each cell holds one
   day's items as a vertical pile of pills, no hour axis. */
.month-grid {
  border: 1px solid var(--gv-bg2);
  border-radius: .35rem;
  overflow: hidden;
  background: var(--gv-bg0-soft);
}
.month-grid-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  border-top: 1px solid var(--gv-bg2);
}
.month-grid-row:first-child { border-top: 0; }
.month-grid-cell {
  border-left: 1px solid var(--gv-bg2);
  min-height: 110px;
  padding: .35rem;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
.month-grid-cell:first-child { border-left: 0; }
.month-grid-day {
  border-bottom: 1px solid var(--gv-bg1);
  padding-bottom: .2rem;
  margin-bottom: .15rem;
}
.month-grid-items {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  overflow-y: auto;
}
.month-grid-items .cal-pill {
  font-size: .75rem;
  padding: .15rem .35rem;
  max-width: 100%;
}
.cal-pill-task[draggable="true"] { cursor: grab; }
.cal-pill-task.cal-pill-dragging { opacity: .5; }
/* Visual height tracks ``estimated_minutes`` / event duration so a
   90-min item spans ~1.5 hour rows. Absolute positioning lets the
   pill overflow into the rows below without changing their height —
   same model as a real calendar. 38px-per-hour ≈ .633 px-per-minute.
   Tasks and events share the same column-slot system (set by JS) so
   they interleave instead of overlapping. */
.day-calendar .day-hour-slots .cal-pill-task,
.day-calendar .day-hour-slots .cal-pill-event {
  position: absolute;
  /* Offset within the hour cell — a 10:30 meeting sits halfway down
     the 10:00 row instead of stacking with a 10:00 task at the top. */
  top: calc(var(--cal-top-min, 0) * 0.633px);
  left: calc(.35rem + var(--cal-col, 0) * (100% / var(--cal-cols, 1)));
  width: calc((100% - .7rem) / var(--cal-cols, 1) - 4px);
  height: max(24px, calc(var(--cal-est-min, 30) * 0.633px));
  align-items: flex-start;
  z-index: 2;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
  overflow: hidden;
}
.day-calendar .day-hour-slots .cal-pill-task.cal-pill-dragging {
  z-index: 6;
}
/* current-time marker on the calendar grid */
.day-col-cell.is-today-col { background: rgba(131, 165, 152, .05); }
.day-col-head.is-today {
  background: color-mix(in srgb, var(--gv-aqua) 16%, transparent);
  box-shadow: inset 0 -2px 0 var(--gv-aqua);
}
.day-hour-label.is-now { color: var(--gv-red); font-weight: 700; }
.cal-now {
  position: absolute; left: 0; right: 0; z-index: 4;
  top: calc(var(--cal-top-min, 0) * 0.633px);
  border-top: 2px solid var(--gv-red); pointer-events: none;
}
.cal-now-dot {
  position: absolute; left: -1px; top: -5px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--gv-red);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--gv-red) 60%, transparent);
  animation: plan-now-pulse 2.4s ease-out infinite;
}
.cal-now-time {
  position: absolute; right: 2px; top: -.62rem;
  font-size: .6rem; font-weight: 700; color: #fff;
  background: var(--gv-red); padding: 0 .3rem; border-radius: 1rem;
}
.cal-pill-link {
  color: inherit;
  text-decoration: none;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-pill-link:hover { text-decoration: underline; }
.cal-pill-dur {
  color: var(--gv-fg4);
  font-size: .7rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  flex: 0 0 auto;
}
.cal-pill-resize {
  cursor: ns-resize;
  color: var(--gv-fg4);
  user-select: none;
  font-size: .8rem;
  line-height: 1;
  padding: 0 .15rem;
  flex: 0 0 auto;
}
.cal-pill-resize:hover { color: var(--gv-yellow-br); }
body.is-resizing-cal { cursor: ns-resize !important; }
body.is-resizing-cal * { cursor: ns-resize !important; user-select: none !important; }
.day-drop.is-drop-over {
  background: rgba(250, 189, 47, .15) !important;
  outline: 2px dashed var(--gv-yellow-br);
  outline-offset: -2px;
}
.cal-pill {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .25rem .5rem;
  border-left: 3px solid var(--gv-blue);
  background: var(--gv-bg0);
  border-radius: .25rem;
  font-size: .85rem;
  color: var(--gv-fg1);
  text-decoration: none;
  max-width: 100%;
}
.cal-pill:hover { background: var(--gv-bg1); }
.cal-pill-task {
  border-left-color: var(--gv-yellow-br);
}
/* completed task stays on the calendar grid, struck through + dimmed */
.cal-pill-task.is-done { opacity: .6; filter: grayscale(.4); cursor: default; }
.cal-pill-task.is-done .cal-pill-link { text-decoration: line-through; color: var(--gv-fg4); }
.cal-pill-time {
  color: var(--gv-fg4);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .7rem;
}
.cal-pill-loc { font-size: .75rem; }

/* Sidebar HTML5 drag for project re-parenting. */
.ps-drop-target[draggable="true"] { cursor: grab; }
.ps-drop-target.ps-row-dragging { opacity: .45; }
.ps-drop-target.ps-drop-hover {
  outline: 2px solid var(--gv-yellow-br);
  outline-offset: -2px;
  background: rgba(250, 189, 47, .18);
  border-radius: .25rem;
}

/* Same idea for sub-project rows in the project detail view. */
body.is-dragging .sp-row[data-sp-id] {
  outline: 1px dashed var(--gv-bg3);
  outline-offset: -2px;
  background: rgba(250, 189, 47, .04);
  transition: outline-color .15s, background .15s;
}
/* The kids list inside a sub-project also takes drops — usually for
   reordering between existing sub-tasks. Without a visual cue users
   aim at the sp-row instead and lose precision. Pre-glow it too. */
body.is-dragging .sp-children > .task-list {
  outline: 1px dashed var(--gv-bg3);
  outline-offset: -2px;
  background: rgba(131, 165, 152, .04);
  border-radius: .25rem;
}
body.is-dragging .sp-children > .task-list.is-drag-over,
body.is-dragging .sp-children > .task-list:hover {
  outline: 2px solid var(--gv-blue-br);
  background: rgba(131, 165, 152, .15);
}
body.is-dragging .sp-row[data-sp-id]:hover,
.sp-row.is-drag-over {
  outline: 2px solid var(--gv-yellow-br);
  outline-offset: -2px;
  background: rgba(250, 189, 47, .22);
  box-shadow: 0 0 0 2px rgba(250, 189, 47, .35);
}
.sp-row[data-sp-id] > .task-row[data-task-id],
.sp-row[data-sp-id] > .tree-task,
.sp-row[data-sp-id] > .drag-ghost {
  display: none !important;
}
.sp-row.sp-drop-flash {
  background: rgba(184, 187, 38, .25);
  transition: background .6s;
}

/* ============= Project list table — extra breathing room ============= */
.project-table > tbody > tr > td,
.project-table > thead > tr > th {
  padding-top: .55rem;
  padding-bottom: .55rem;
}
.project-table > tbody > tr:hover { background: var(--gv-bg0-soft); }
/* Sections within projects/detail — flat page layout. Each section is
   just a header with a hairline divider, no card / box. */
.section-block {
  margin-top: 1.25rem;
}
.section-block .section-header {
  border-bottom: 1px solid var(--gv-bg2);
  padding: .15rem 0 .3rem;
  margin: 0 0 .4rem;
}
.section-block .section-header h3 {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  font-weight: 600;
  color: var(--gv-fg2);
}
.section-block .section-note {
  margin-bottom: .5rem;
}

/* ============= Project header (breadcrumb + light mode chips) ============= */
.project-header { min-width: 0; }
.project-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
  font-size: .8rem;
  color: var(--gv-fg4);
  margin-bottom: .25rem;
}
.project-breadcrumb .crumb {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding: .1rem .4rem;
  border-radius: .25rem;
  color: var(--gv-fg3);
  text-decoration: none;
  transition: background .12s, color .12s;
}
.project-breadcrumb .crumb:hover {
  background: var(--gv-bg1);
  color: var(--gv-yellow-br);
}
.project-breadcrumb .crumb-root { padding: .1rem .35rem; }
.project-breadcrumb .crumb-sep {
  color: var(--gv-bg3);
  font-weight: 300;
  user-select: none;
}

.project-title { font-weight: 500; }
.project-mode {
  display: inline-block;
  margin-left: .5rem;
  padding: .05rem .45rem;
  font-size: .65rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .08em;
  border-radius: .2rem;
  color: var(--gv-fg4);
  border: 1px solid var(--gv-bg3);
  background: transparent;
  vertical-align: middle;
}
.project-mode-on_hold   { color: var(--gv-orange-br); border-color: var(--gv-orange); }
.project-mode-completed { color: var(--gv-green-br);  border-color: var(--gv-green); }
.project-mode-dropped   { color: var(--gv-red-br);    border-color: var(--gv-red); }

/* Full-height app layout — nav sidebar, optional project sidebar, main. */
:root {
  --nav-sidebar-width: 220px;
  --project-sidebar-width: 260px;
  --project-sidebar-collapsed-width: 38px;
}
.app-layout {
  display: flex;
  align-items: stretch;
  min-height: calc(100vh - 56px);
}
.app-layout > .app-nav-sidebar {
  flex: 0 0 var(--nav-sidebar-width);
  width: var(--nav-sidebar-width);
  padding-left: .75rem;
  padding-right: .5rem;
}
.app-layout > .app-main {
  flex: 1 1 auto;
  min-width: 0;
}

/* Project sidebar — full height, resizable, collapsible. */
.app-project-sidebar {
  flex: 0 0 var(--project-sidebar-width);
  width: var(--project-sidebar-width);
  min-width: var(--project-sidebar-collapsed-width);
  max-width: 600px;
  border-right: 1px solid var(--gv-bg2);
  background: var(--gv-bg0);
  position: sticky;
  top: var(--nav-height, 56px);
  align-self: flex-start;   /* see .offcanvas.inspector — avoids the WebKit sticky-in-flex displacement */
  height: calc(100vh - var(--nav-height, 56px));
  display: flex;
  flex-direction: column;
}
/* The left navigation hidden for room — desktop only; below 992px it is
   the drawer and this class means nothing (nav-sidebar.js). */
.nav-sidebar-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-left: -.35rem;
  margin-right: .35rem;
  padding: 0;
  border: 0;
  font-size: 1.1rem;
}
/* .navbar in front: the navbar's own button colour outranks a lone class. */
.navbar .nav-sidebar-toggle { color: var(--gv-fg4); }
.navbar .nav-sidebar-toggle:hover,
.navbar .nav-sidebar-toggle:focus-visible { color: var(--gv-yellow-br); }
@media (min-width: 992px) {
  .nav-sidebar-toggle { display: inline-flex; }
  html.cairn-nav-collapsed .navbar [data-nav-sidebar-toggle],
  html.cairn-ps-collapsed .navbar [data-ps-toggle] { color: var(--gv-yellow); }
  html.cairn-nav-collapsed .app-layout > .app-nav-sidebar { display: none; }
}
/* Hidden means gone: the switch to bring it back is in the navbar. */
html.cairn-ps-collapsed .app-project-sidebar { display: none; }
.app-project-sidebar .ps-header {
  display: flex;
  align-items: center;
  padding: .75rem .6rem .5rem;
  border-bottom: 1px solid var(--gv-bg1);
}
.app-project-sidebar .ps-toggle {
  width: 1.5rem;
  text-align: center;
  flex: 0 0 1.5rem;
}
.app-project-sidebar .ps-body {
  flex: 1 1 auto;
  overflow: auto;
  padding: .5rem .5rem .75rem;
}
.app-project-sidebar .ps-search {
  margin-bottom: .5rem;
}
.app-project-sidebar .ps-list .nav-link {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--gv-fg2);
  border-radius: .25rem;
  display: block;
}
.app-project-sidebar .ps-list .nav-link.active {
  background: var(--gv-bg2);
  color: var(--gv-yellow-br);
  font-weight: 500;
}
.app-project-sidebar .ps-list .nav-link:hover {
  background: var(--gv-bg1);
}
.app-project-sidebar .proj-dot {
  display: inline-block;
  width: .65rem;
  height: .65rem;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: .2rem;
}
/* Drag handle — overlays the right border, full-height, cursor col-resize. */
.app-project-sidebar .ps-resizer {
  position: absolute;
  top: 0;
  right: -3px;
  bottom: 0;
  width: 6px;
  cursor: col-resize;
  background: transparent;
  z-index: 5;
}
.app-project-sidebar .ps-resizer:hover,
body.ps-resizing .app-project-sidebar .ps-resizer {
  background: var(--gv-yellow);
  opacity: .5;
}
body.ps-resizing,
body.ps-resizing * {
  cursor: col-resize !important;
  user-select: none;
}

@media (max-width: 767.98px) {
  .app-layout { flex-direction: column; }
  .app-layout > .app-nav-sidebar,
  .app-layout > .app-project-sidebar {
    flex-basis: auto;
    width: 100%;
    border-right: none;
    height: auto;
    max-height: 18rem;
    position: static;
    border-bottom: 1px solid var(--gv-bg2);
  }
  .app-project-sidebar .ps-resizer { display: none; }
}

/* Quick-jump command bar (cmd-K style) */
.quickjump-modal .modal-content { background: var(--gv-bg0); }
.quickjump-input {
  border: none;
  background: transparent;
  color: var(--gv-fg0);
}
.quickjump-input:focus {
  box-shadow: none;
  background: transparent;
}
.quickjump-results .quickjump-item {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .6rem;
  border-radius: .25rem;
  cursor: pointer;
  color: var(--gv-fg2);
}
.quickjump-results .quickjump-item:hover {
  background: var(--gv-bg1);
}
.quickjump-results .quickjump-item.active {
  background: var(--gv-bg2);
  color: var(--gv-yellow-br);
}
.quickjump-results .quickjump-title { font-weight: 500; }
/* A closed hit reads closed, the way a done row does in a task list. */
.quickjump-results .quickjump-item.closed .quickjump-title {
  text-decoration: line-through;
  color: var(--gv-gray);
}
.quickjump-results .quickjump-sub {
  color: var(--gv-fg4);
  font-size: .8em;
  margin-left: auto;
}

/* Task detail page — tighter header, inline meta, side panel with
   minimal padding. Goal is to feel close to the inspector but with
   room to breathe. */
.task-detail-header { padding-bottom: .5rem; border-bottom: 1px solid var(--gv-bg2); }
.task-detail-title { line-height: 1.25; }
.task-detail-meta { line-height: 1.6; }
.task-detail-check { line-height: 1; }
.task-detail-check:focus { box-shadow: none; }
.task-detail-actions .btn { font-size: .8rem; }

/* min-width:0 lets the column shrink so wide children (code blocks, long
   URLs) stay scrollable/wrapped inside it instead of widening the column. */
.task-detail-body { min-width: 0; }
.task-detail-body .task-detail-section { margin-bottom: 1.5rem; }
/* Note flows inline with the rest of the detail page — a soft box
   was making it look disconnected from the heading. A subtle left
   accent groups heading + body without fighting the layout. */
.task-detail-note {
  border-left: 2px solid var(--gv-bg2);
  padding-left: .75rem;
}
/* When the note is collapsed only the heading is left, so the left accent would
   shrink to a stray stub next to the chevron — drop it while collapsed. */
.task-detail-note:has(> .section-collapsible.d-none) {
  border-left: none;
  padding-left: 0;
}
.task-detail-note .md-body {
  color: var(--gv-fg2);
  font-size: .95rem;
  line-height: 1.55;
  /* Long URLs / unbroken strings must wrap instead of spilling out of the
     column and overlapping the inspector next to it. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

.task-detail-comment {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  padding: .6rem 0;
  border-top: 1px solid var(--gv-bg2);
}
.task-detail-comment:first-of-type { border-top: none; }
.task-detail-comment .comment-avatar {
  flex: 0 0 1.75rem;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--gv-bg2);
  color: var(--gv-fg2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .8rem;
  font-weight: 500;
}
.task-detail-comment .comment-body-wrap { flex: 1 1 auto; min-width: 0; }
.task-detail-comment .comment-header {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .8rem;
  color: var(--gv-fg4);
}
.task-detail-comment.is-public .comment-avatar {
  background: var(--gv-green);
  color: var(--gv-bg0);
}

/* Commits registered on a task (tasks/_commits.html): a quiet list under the
   related blocks, and the same hash as a chip under the comment it belongs to. */
.task-commits .task-commit { border-top: 1px solid var(--gv-bg2); min-width: 0; }
.task-commits .task-commit:first-child { border-top: none; }
.task-commit-subject { min-width: 0; }
.task-commit-age { white-space: nowrap; }
.commit-ref { white-space: nowrap; }
.commit-sha { font-size: .8rem; }
.comment-commits { display: flex; flex-wrap: wrap; gap: .25rem .9rem; margin-top: .25rem; color: var(--gv-fg4); }

.task-detail-side { padding: .75rem 1rem; background: var(--gv-bg0-soft); border-radius: .35rem; }
.task-detail-dl { display: grid; grid-template-columns: max-content 1fr; gap: .25rem .75rem; }
.task-detail-dl dt { color: var(--gv-fg4); font-weight: 400; font-size: .8rem; align-self: center; }
.task-detail-dl dd { margin: 0; font-size: .9rem; }

/* App footer at the bottom of the nav sidebar — About / Help / version. */
.app-nav-sidebar .nav-sidebar-footer {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--gv-bg2);
  font-size: .8rem;
}
.app-nav-sidebar .nav-sidebar-footer .nav-link {
  padding: .2rem .6rem;
  color: var(--gv-fg4);
}
.app-nav-sidebar .nav-sidebar-footer .nav-link:hover { color: var(--gv-yellow-br); }
.app-nav-sidebar .nav-sidebar-version {
  padding: .25rem .6rem;
  color: var(--gv-fg4);
  opacity: .6;
}

/* Repeat chip — small inline badge on tasks that auto-clone on
   completion. Keeps low contrast so it doesn't compete with status. */
.task-meta .repeat-chip {
  color: var(--gv-blue-br);
  font-size: .8em;
}
.task-meta .repeat-chip i { margin-right: .15em; }

/* Task-row leaving animation — kicks in when a task is checked off
   and we drop it from the visible list in-place. */
.task-row {
  transition: opacity .18s ease, transform .18s ease, max-height .2s ease, padding .2s ease, margin .2s ease;
}
.task-row.task-row-leaving {
  opacity: 0;
  transform: translateX(8px);
  max-height: 0;
  margin: 0;
  padding-top: 0;
  padding-bottom: 0;
  pointer-events: none;
  overflow: hidden;
}

/* An empty date field in the inspector shows nothing. Safari draws an empty
   datetime-local with today's date and time, which reads as a plan date that
   is not there (.dt-empty is set by quick-date.js). */
input.dt-empty:not(:focus)::-webkit-datetime-edit {
  -webkit-text-fill-color: transparent;
}

/* Quick-date buttons under datetime-local inputs — small + low key. They sit
   under Defer/Due inside the two-column .grid-2, which is too narrow for six
   connected buttons in a row, so let them wrap. Wrapping breaks the btn-group's
   joined look, so give each button its own rounded border + a small gap. */
.quick-date-row {
  flex-wrap: wrap;
  gap: .2rem;
}
.quick-date-row .btn {
  padding: .12rem .45rem;
  font-size: .72rem;
  /* !important: btn-group's per-child radius/overlap rules outrank this
     selector. Override so each wrapped button is a standalone rounded pill. */
  border-radius: .2rem !important;
  margin-left: 0 !important;
}

/* Review — a task whose own (overridden) review interval has come due gets
   a soft amber emphasis so it stands out among its project's actions. */
.review-task-list .review-task-due {
  background: var(--gv-yellow-dim, rgba(215, 153, 33, .14));
  border-radius: .25rem;
  padding: .05rem .35rem;
  margin-left: -.35rem;
}

/* Review mode — a sticky accent banner so it's unmistakable you're inside the
   review flow (not a normal project page). */
.review-banner {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: .7rem;
  margin: -1rem -1.5rem 1.25rem;   /* bleed to the .app-main padding edges */
  padding: .6rem 1.5rem;
  background: var(--gv-blue-dim, rgba(69, 133, 136, .16));
  border-bottom: 2px solid var(--gv-blue-br, #83a598);
  backdrop-filter: blur(4px);
}
.review-banner .review-mode-label {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  font-size: .8rem;
  color: var(--gv-blue-br, #83a598);
}
.review-banner .review-progress {
  color: var(--gv-fg3);
  font-size: .85rem;
}
/* Tint the whole review main area subtly so the mode reads at a glance. */
.app-main:has(.review-banner) {
  box-shadow: inset 3px 0 0 var(--gv-blue-br, #83a598);
}

/* ---- Today "Plan" timeline (structured.app-style day planner) ---- */
.plan-wrap { max-width: 940px; }
.plan-head .btn-primary {
  border-radius: 2rem; padding: .35rem 1rem; font-weight: 600;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--gv-aqua) 35%, transparent);
}
.plan-allday { display: flex; flex-wrap: wrap; gap: .4rem; }
.plan-allday-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .74rem; padding: .2rem .6rem; border-radius: 2rem;
  border: 1px solid color-mix(in srgb, var(--c, var(--gv-gray)) 55%, transparent);
  color: var(--gv-fg2);
  background: color-mix(in srgb, var(--c, var(--gv-gray)) 16%, transparent);
}
.plan-grid { position: relative; display: flex; gap: .25rem; }
.plan-axis {
  position: relative; width: 3.4rem; flex: 0 0 3.4rem;
  height: calc(var(--span) * var(--plan-ppm));
}
.plan-hour {
  position: absolute; top: calc(var(--top) * var(--plan-ppm));
  right: .5rem; transform: translateY(-.5em);
  font-size: .68rem; font-weight: 600; color: var(--gv-fg4);
  letter-spacing: .02em; white-space: nowrap;
}
.plan-track {
  position: relative; flex: 1 1 auto;
  height: calc(var(--span) * var(--plan-ppm));
  border: 1px solid var(--gv-bg2); border-radius: .9rem;
  background: var(--gv-bg0); overflow: hidden;
}
.plan-gridline {
  position: absolute; left: 0; right: 0;
  top: calc(var(--top) * var(--plan-ppm));
  border-top: 1px solid color-mix(in srgb, var(--gv-bg3) 55%, transparent);
}
.plan-worktime {
  position: absolute; left: 0; right: 0;
  top: calc(var(--top) * var(--plan-ppm));
  height: calc(var(--dur) * var(--plan-ppm));
  background: color-mix(in srgb, var(--gv-aqua) 5%, transparent);
  pointer-events: none;
}
.plan-past {
  position: absolute; left: 0; right: 0; top: 0;
  height: calc(var(--dur) * var(--plan-ppm));
  background: color-mix(in srgb, var(--gv-bg0) 45%, transparent);
  pointer-events: none; z-index: 4;
}
.plan-now {
  position: absolute; left: 0; right: 0; z-index: 6;
  top: calc(var(--top) * var(--plan-ppm));
  border-top: 2px solid var(--gv-red); pointer-events: none;
}
.plan-now-dot {
  position: absolute; left: 0; top: -5px;
  width: 9px; height: 9px; border-radius: 50%; background: var(--gv-red);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--gv-red) 60%, transparent);
  animation: plan-now-pulse 2.4s ease-out infinite;
}
@keyframes plan-now-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gv-red) 55%, transparent); }
  70%  { box-shadow: 0 0 0 8px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.plan-now-label {
  position: absolute; left: .5rem; top: -.7rem;
  font-size: .64rem; font-weight: 700; color: #fff;
  background: var(--gv-red); padding: .05rem .4rem; border-radius: 1rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.plan-lane { position: absolute; inset: 0; }

.plan-card {
  position: absolute; box-sizing: border-box;
  left: calc(var(--col, 0) * (100% / var(--cols, 1)) + 4px);
  width: calc(100% / var(--cols, 1) - 8px);
  top: calc(var(--top) * var(--plan-ppm) + 1px);
  height: calc(var(--dur) * var(--plan-ppm) - 2px);
  min-height: 1.6rem; overflow: hidden;
  display: flex; gap: .45rem; align-items: flex-start;
  border-radius: .65rem; padding: .3rem .45rem;
  font-size: .76rem; line-height: 1.25; z-index: 5;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .16);
  transition: box-shadow .14s ease, transform .14s ease, filter .14s ease, top .16s ease;
}
.plan-ico {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 1.35rem; height: 1.35rem; border-radius: .45rem; font-size: .8rem;
  background: color-mix(in srgb, var(--c, var(--gv-aqua)) 24%, transparent);
  color: var(--c, var(--gv-aqua));
}
.plan-card-body { min-width: 0; flex: 1 1 auto; }
.plan-card-title {
  font-weight: 600; color: var(--gv-fg1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.plan-card-title a { color: inherit; text-decoration: none; }
.plan-card-title a:hover { text-decoration: underline; }
.plan-card-meta {
  font-size: .68rem; color: var(--gv-fg4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.plan-card-brief {
  font-size: .68rem; color: var(--gv-fg2);
  margin-top: .15rem; overflow: hidden;
}
.plan-card-event {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--c) 26%, var(--gv-bg2)),
    color-mix(in srgb, var(--c) 14%, var(--gv-bg1)));
  border: 1px solid color-mix(in srgb, var(--c) 40%, transparent);
}
.plan-card-task {
  --c: var(--gv-aqua);
  background: linear-gradient(180deg, var(--gv-bg3), var(--gv-bg2));
  border: 1px solid var(--gv-bg3);
  border-left: 3px solid var(--c); cursor: grab;
}
.plan-card-task:hover {
  transform: translateY(-1px); filter: brightness(1.07);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .28); z-index: 8;
}
.plan-card-task:active { cursor: grabbing; }
.plan-card-task.plan-card-dragging { opacity: .4; transform: scale(.98); }
.plan-ico-task { background: color-mix(in srgb, var(--c) 24%, transparent); color: var(--c); }
.plan-card-resize {
  position: absolute; left: 0; right: 0; bottom: 0; height: 8px;
  cursor: ns-resize; border-radius: 0 0 .65rem .65rem;
}
.plan-card-resize::after {
  content: ""; position: absolute; left: 50%; bottom: 2px;
  width: 22px; height: 3px; border-radius: 2px; transform: translateX(-50%);
  background: color-mix(in srgb, var(--gv-fg4) 45%, transparent);
}
.plan-card-task:hover .plan-card-resize::after { background: var(--gv-fg4); }
.plan-track.is-drop-over .plan-lane { pointer-events: none; }
/* A plan card / rail pill is the row, and it has the room for more than a dot:
   the surface takes a wash of the project's colour. The LEFT BORDER stays
   ``--c`` — the priority — so a card can say "urgent" on its edge and "whose
   work" across its face without either giving way. Kept low (under a quarter)
   because the title sits on top of it. */
.plan-card-task.has-row-color {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--dot) 36%, var(--gv-bg2)),
    color-mix(in srgb, var(--dot) 20%, var(--gv-bg1)));
  /* Three sides, never ``border-color``: the fourth is the priority. */
  border-top-color: color-mix(in srgb, var(--dot) 45%, transparent);
  border-right-color: color-mix(in srgb, var(--dot) 45%, transparent);
  border-bottom-color: color-mix(in srgb, var(--dot) 45%, transparent);
}
.plan-rail-item.has-row-color {
  background: color-mix(in srgb, var(--dot) 24%, var(--gv-bg2));
}
.plan-rail-item.plan-next-item.has-row-color {
  background: color-mix(in srgb, var(--dot) 20%, var(--gv-bg1));
}
.plan-prio-1 { --c: var(--gv-red); }
.plan-prio-2 { --c: var(--gv-orange); }
.plan-prio-3 { --c: var(--gv-yellow); }
.plan-track.is-drop-over {
  outline: 2px dashed var(--gv-aqua); outline-offset: -2px;
  background: color-mix(in srgb, var(--gv-aqua) 5%, var(--gv-bg0));
}

/* live drop preview */
.plan-ghost {
  position: absolute; left: 4px; right: 4px; z-index: 9;
  top: calc(var(--top) * var(--plan-ppm));
  height: calc(var(--dur) * var(--plan-ppm));
  min-height: 1.6rem; pointer-events: none;
  border: 2px dashed var(--gv-aqua); border-radius: .65rem;
  background: color-mix(in srgb, var(--gv-aqua) 16%, transparent);
}
.plan-ghost-label {
  position: absolute; top: -.6rem; left: .5rem;
  font-size: .66rem; font-weight: 700; color: #fff;
  background: var(--gv-aqua); padding: .05rem .4rem; border-radius: 1rem;
  white-space: nowrap;
}

/* Time report: the pencil column stays on screen when a project's own
   fields make the table wider than the page (see _report_body.html). */
.tt-report-table > :is(thead, tbody) > tr > :last-child {
  position: sticky; right: 0; background: var(--gv-bg0);
}

/* backlog / next rails */
.plan-rails {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem; align-items: start;
}
.plan-rail-head {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--gv-fg4);
  display: flex; align-items: center; gap: .35rem; margin-bottom: .5rem;
}
.plan-rail-count {
  background: var(--gv-bg3); color: var(--gv-fg3);
  border-radius: 1rem; padding: 0 .45rem; font-size: .7rem;
}
.plan-rail-item {
  --c: var(--gv-aqua);
  display: flex; align-items: center; gap: .45rem;
  padding: .4rem .55rem; margin-bottom: .4rem; cursor: grab;
  border-radius: .6rem; background: var(--gv-bg2);
  border: 1px solid var(--gv-bg3); border-left: 3px solid var(--c);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
  transition: transform .12s ease, box-shadow .12s ease;
}
.plan-rail-item:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, .22); }
.plan-rail-item.plan-card-dragging { opacity: .4; }
.plan-next-item { background: var(--gv-bg1); }
.plan-grip { color: var(--gv-fg4); flex: 0 0 auto; }
/* Complete-directly control on the Due / Next rails. */
.plan-rail-check { flex: 0 0 auto; display: flex; cursor: default; }
.plan-rail-check .btn { color: var(--gv-fg4); line-height: 1; }
.plan-rail-check .btn:hover { color: var(--gv-green-br); }
.plan-rail-link {
  flex: 1 1 auto; min-width: 0; color: var(--gv-fg1); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.plan-rail-link:hover { text-decoration: underline; }
.plan-rail-dur {
  flex: 0 0 auto; font-size: .68rem; color: var(--gv-fg4);
  background: var(--gv-bg3); border-radius: 1rem; padding: 0 .45rem;
}
/* Plan view: what a task still waits for (its open dependencies). On a rail
   pill it takes a line of its own under the title. A wrong order (the blocker
   is not on the day, or comes after this task) is drawn in the warning colour. */
.plan-dep { font-size: .7rem; color: var(--gv-fg4); display: flex; flex-direction: column; gap: .1rem; }
.plan-dep-compact { font-size: .66rem; }
.plan-dep-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem; min-width: 0; }
.plan-dep-row.is-late { color: var(--gv-yellow-br); }
.plan-dep-title { color: inherit; font-weight: 600; text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.plan-dep-title:hover { text-decoration: underline; }
.plan-dep-pull { font-size: inherit; line-height: 1; color: var(--gv-aqua-br); }
.plan-rail-item:has(.plan-dep) { flex-wrap: wrap; }
.plan-rail-item .plan-dep { flex: 1 0 100%; order: 10; padding-left: 1.6rem; }
.wl-card > .plan-dep { margin: .1rem 0 .4rem; }

/* Next rail: a task that is with somebody else, and the switch that shows them. */
.plan-rail-delegated {
  flex: 0 0 auto; max-width: 45%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .68rem; color: var(--gv-bg0); background: var(--gv-blue);
  border-radius: 1rem; padding: 0 .45rem;
}
.plan-rail-toggle {
  font: inherit; font-size: .65rem; text-transform: none; letter-spacing: 0;
  color: var(--gv-fg4); background: none; cursor: pointer;
  border: 1px solid var(--gv-bg3); border-radius: 1rem; padding: 0 .45rem;
}
.plan-rail-toggle:hover { color: var(--gv-fg2); }
.plan-rail-toggle.is-on { color: var(--gv-fg1); border-color: var(--gv-blue); }
/* A rail split by project / customer. The section head is quieter than the
   rail's own head — it is a subdivision of one list, not a second list. */
.plan-rail-group { margin-bottom: .6rem; }
.plan-rail-group-head {
  display: flex; align-items: center; gap: .35rem;
  font-size: .68rem; color: var(--gv-fg3); margin: .25rem 0 .3rem;
  padding-bottom: .15rem; border-bottom: 1px solid var(--gv-bg3);
}
.plan-rail-group-head .plan-rail-count { margin-left: auto; }

/* complete-from-timeline chip */
.plan-ico.plan-complete {
  border: 0; padding: 0; line-height: 1; cursor: pointer;
  transition: background .12s ease, color .12s ease, transform .12s ease;
}
.plan-ico.plan-complete:hover {
  background: var(--gv-green); color: var(--gv-bg0); transform: scale(1.1);
}
/* "Back to Next" — an unfinished task the day ran out on goes back into Next
   actions and loses its plan date. A muted action like the note button beside
   it: the check chip is the primary move on a card, this is the other answer. */
.plan-back-next {
  flex: 0 0 auto; border: 0; background: transparent; padding: 0 .2rem;
  cursor: pointer; line-height: 1; font-size: .85rem; opacity: .55;
  color: var(--bs-secondary-color, #6c757d);
  display: inline-flex; align-items: center;
  transition: opacity .12s ease, color .12s ease;
}
.plan-back-next:hover { opacity: 1; color: var(--gv-aqua); }
/* On a TIMELINE card the note button is pinned to the top-right corner
   (today.html), so this one is pinned beside it instead of landing under it.
   Scoped to the lane on purpose: a rail pill carries ``plan-card-task`` too,
   is not positioned, and an absolute button there flies off the pill. */
.plan-lane .plan-card-task .plan-back-next {
  position: absolute; top: 2px; right: 26px;
}
/* completed task stays on the timeline, struck through + dimmed */
.plan-card.is-done { opacity: .62; filter: grayscale(.4); cursor: default; }
.plan-card.is-done .plan-card-title { text-decoration: line-through; }
.plan-card.is-done:hover { transform: none; box-shadow: 0 1px 3px rgba(0, 0, 0, .16); }
.plan-card.is-done .plan-card-resize { display: none; }
/* current / in-progress card (overlaps "now") */
.plan-card-current {
  box-shadow: 0 0 0 2px var(--gv-aqua), 0 6px 16px rgba(0, 0, 0, .3) !important;
}
.plan-card-current .plan-ico { animation: plan-now-pulse 2.4s ease-out infinite; }

/* deadline markers */
.plan-due {
  position: absolute; left: 0; right: 0; z-index: 7;
  top: calc(var(--top) * var(--plan-ppm));
  border-top: 1px dashed var(--gv-red); pointer-events: none;
}
.plan-due-label {
  position: absolute; right: .4rem; top: -.7rem;
  display: inline-flex; align-items: center; gap: .25rem;
  font-size: .62rem; font-weight: 700; color: var(--gv-red);
  background: var(--gv-bg0); padding: .03rem .4rem; border-radius: 1rem;
  border: 1px solid color-mix(in srgb, var(--gv-red) 50%, transparent);
  max-width: 70%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* quick-add inline input on an empty slot */
.plan-quickadd {
  position: absolute; left: 4px; right: 4px; z-index: 10;
  top: calc(var(--top) * var(--plan-ppm));
  font-size: .78rem; padding: .35rem .55rem;
  border: 2px solid var(--gv-aqua); border-radius: .55rem;
  background: var(--gv-bg0); color: var(--gv-fg1);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .35);
}
.plan-track { cursor: text; }
.plan-track .plan-card { cursor: grab; }

/* backlog as an unschedule drop target */
.plan-rail-hint { font-size: .67rem; color: var(--gv-fg4); margin-bottom: .45rem; }
#plan-backlog.is-drop-over {
  outline: 2px dashed var(--gv-aqua); outline-offset: 4px; border-radius: .6rem;
  background: color-mix(in srgb, var(--gv-aqua) 7%, transparent);
}

.plan-toast {
  position: fixed; bottom: 1.2rem; left: 50%;
  transform: translateX(-50%) translateY(1rem);
  background: var(--gv-bg0); color: var(--gv-fg1);
  padding: .6rem 1rem; border-radius: .6rem;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
  font-size: .85rem; opacity: 0; z-index: 1090;
  transition: opacity .2s, transform .2s;
}
.plan-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Sub-tasks on the task detail page */
.subtask-item.is-done .subtask-title { text-decoration: line-through; }
.subtask-toggle { line-height: 1; }
.subtask-add-input { max-width: 28rem; }

/* Note button on tasks / calendar events (day view + every task row) and its
   pop-up panel (see event-notes.js, tasks/_event_note_macros.html). */
.event-notes-btn,
.event-log-btn {
  border: 0; background: transparent; padding: 0 .25rem; cursor: pointer;
  color: var(--bs-secondary-color, #6c757d); line-height: 1;
  display: inline-flex; align-items: center; gap: .1rem; font-size: .8rem;
  opacity: .55; transition: opacity .12s ease, color .12s ease;
}
.event-notes-btn:hover,
.event-log-btn:hover,
.event-notes-btn.has-notes { opacity: 1; color: var(--bs-primary, #0d6efd); }
.event-notes-count { font-size: .7rem; font-weight: 600; }
/* In list rows the button sits among the other muted row actions. */
.task-row .event-notes-btn { font-size: .95rem; padding: 0 .2rem; }
.event-notes-panel {
  position: fixed; z-index: 2200; width: 250px; max-width: calc(100vw - 1rem);
  background: var(--bs-body-bg, #fff); border: 1px solid var(--bs-border-color, #dee2e6);
  border-radius: .5rem; box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.18); overflow: hidden;
}
.event-notes-panel .enp-head {
  padding: .4rem .6rem; font-size: .72rem; text-transform: uppercase;
  letter-spacing: .03em; color: var(--bs-secondary-color, #6c757d);
  border-bottom: 1px solid var(--bs-border-color, #dee2e6);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.event-notes-panel .enp-item, .event-notes-panel .enp-new {
  display: flex; align-items: center; gap: .4rem; width: 100%;
  padding: .45rem .6rem; font-size: .85rem; text-decoration: none;
  color: var(--bs-body-color, #212529); border: 0; background: transparent;
  text-align: left; cursor: pointer;
}
.event-notes-panel .enp-item:hover, .event-notes-panel .enp-new:hover {
  background: var(--bs-tertiary-bg, #f1f3f5);
}
.event-notes-panel .enp-item span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.event-notes-panel .enp-new {
  border-top: 1px solid var(--bs-border-color, #dee2e6);
  color: var(--bs-primary, #0d6efd); font-weight: 500;
}
/* The customer chip on a calendar event and its panel (event-customer.js).
   Muted like the note button until it names a customer; a SUGGESTION from
   the title is dashed and italic, so it never reads as something said. */
.event-cust-btn {
  border: 1px solid transparent; background: transparent; cursor: pointer;
  padding: 0 .3rem; border-radius: 999px; line-height: 1.3;
  display: inline-flex; align-items: center; gap: .25rem; font-size: .72rem;
  color: var(--bs-secondary-color, #6c757d); opacity: .55; max-width: 12rem;
  vertical-align: baseline;
}
.event-cust-btn:hover { opacity: 1; }
.event-cust-btn.has-customer {
  opacity: 1; color: var(--bs-body-color, #212529);
  border-color: var(--bs-border-color, #dee2e6);
}
.event-cust-btn.is-suggested { border-style: dashed; font-style: italic; opacity: .8; }
.event-cust-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.event-cust-dot {
  width: .55rem; height: .55rem; border-radius: 50%; flex: none;
  background: var(--dot);
}
.event-cust-panel { width: 280px; }
.event-cust-panel .ecp-search { margin: .4rem .6rem; width: calc(100% - 1.2rem); }
.event-cust-panel .ecp-list { max-height: 12rem; overflow-y: auto; }
.event-cust-panel .ecp-tasks-head { border-top: 1px solid var(--bs-border-color, #dee2e6); }
.event-notes-panel .enp-empty {
  padding: .5rem .6rem; font-size: .82rem; color: var(--bs-secondary-color, #6c757d);
}


/* ---- Time capture rows -------------------------------------------------
   The same markup is used in the day sheet (page width) and in the task
   inspector (460px). A flex item's default min-width is `auto`, so a <select>
   sized by its longest option ("Kunde A › Support") refuses to shrink and
   pushes the row straight out of the panel. Everything here is allowed to
   shrink and capped at the container, so the row wraps instead of overflowing.
   The inline max-widths on individual controls stay: they only cap how *wide*
   a field may get, never how narrow it may become. */
.time-add,
.tt-edit,
.time-list li,
.tt-custom-fields { min-width: 0; }
.time-add > *,
.tt-edit > *,
.tt-custom-fields > * { max-width: 100%; }
.time-add input,
.time-add select,
.tt-edit input,
.tt-edit select,
.tt-custom-fields input,
.tt-custom-fields select { min-width: 0; }
/* The project picker is the widest control by far — let it take the row it
   needs rather than squeezing the others out. */
.time-block .time-add .time-add-project,
.time-block .time-add .tt-custom-fields { flex: 1 1 100%; }
/* Logged entries are two lines: the booking, then what it was about. The
   description had been the first thing to be truncated away on one line, which
   is the one part nobody can reconstruct later — so it gets a line of its own
   and wraps rather than disappearing. */
.time-list li > .text-truncate,
.time-list li > span.text-muted.text-truncate { min-width: 0; }
.time-list .time-row:last-child { border-bottom: 0 !important; }
.time-list .time-row .small { overflow-wrap: anywhere; }


/* ---- Billing project rows (time tracking → customers & projects) --------
   The whole row is the disclosure's summary, so the default triangle is
   replaced by a chevron that lines up with the text, and the row highlights on
   hover to say it opens. */
.tt-project > summary { list-style: none; }
.tt-project > summary::-webkit-details-marker { display: none; }
.tt-project > summary::before {
  content: "\F285";                     /* bi-chevron-right */
  font-family: "bootstrap-icons";
  font-size: .8em;
  color: var(--bs-secondary-color);
  transition: transform .15s ease;
  display: inline-block;
}
.tt-project[open] > summary::before { content: "\F282"; }  /* bi-chevron-down */
.tt-project > summary:hover { background: var(--bs-tertiary-bg); }

/* Type-to-find picker for the billing project on a capture bar
   (project-picker.js). The native <select> stays in the DOM as the form's
   source of truth and is only hidden once the button is in place, so a page
   without JavaScript keeps a working control. */
.tt-pick { position: relative; display: inline-block; }
/* The swap happens only once the script has taken over: no JavaScript, no
   button, and the native select is left doing its job. */
.tt-pick-btn { display: none; max-width: 20rem; overflow: hidden;
               text-overflow: ellipsis; white-space: nowrap; }
.tt-pick[data-ready] > select { display: none; }
.tt-pick[data-ready] .tt-pick-btn { display: inline-block; }
.tt-pick-menu { display: none; position: absolute; z-index: 1060; top: 100%;
                left: 0; min-width: 22rem; max-width: 90vw; margin-top: .25rem;
                padding: .4rem; border: 1px solid var(--gv-bg2);
                border-radius: .375rem; background: var(--gv-bg0-soft);
                box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .35); }
.tt-pick-open .tt-pick-menu { display: block; }
.tt-pick-list { max-height: 16rem; overflow-y: auto; margin-top: .35rem; }
.tt-pick-opt { display: block; width: 100%; text-align: left; border: 0;
               background: none; color: var(--gv-fg1); font-size: .85rem;
               padding: .3rem .45rem; border-radius: .25rem; }
.tt-pick-opt:hover, .tt-pick-opt.active { background: var(--gv-bg2); }
.tt-pick-none { padding: .35rem .5rem; }


/* ---- Day sheet: week strip and logged entries ---------------------------
   The week strip's selected day used to be a filled yellow button, so the very
   number it carries — the hours booked that day — sat dark-on-bright and was
   the hardest thing on the page to read. It marks itself like the toolbar
   pills now: tinted, outlined, text light, hours in yellow. */
.tt-week .tt-day-on,
.tt-week .tt-day-on:hover,
.tt-week .tt-day-on:focus {
  background: var(--bs-primary-bg-subtle);
  border-color: var(--gv-yellow);
  color: var(--gv-fg0);
}
.tt-week .tt-day-on .fw-semibold { color: var(--gv-yellow-br); }

/* An entry is a four-column grid, not a flex row that wraps wherever it runs
   out of space. The widths are fixed rather than content-sized so the same
   column lines up down the whole list: clock times under clock times, and
   durations right-aligned in a column you can add up by eye. What the entry
   was about gets the wide middle; where it bills sits under it, quiet. */
.tt-row-grid {
  display: grid;
  grid-template-columns: 5.75rem minmax(0, 1fr) 5rem auto;
  grid-template-areas: "when main dur act";
  align-items: baseline;
  column-gap: .75rem;
}
.tt-row-when   { grid-area: when; }
.tt-row-main   { grid-area: main; min-width: 0; }
.tt-row-dur    { grid-area: dur;  text-align: right; }
.tt-row-actions{ grid-area: act;  display: flex; gap: .35rem; align-items: baseline; }

.tt-row-when {
  font-size: .8125rem;
  color: var(--gv-fg4);
  white-space: nowrap;
}
.tt-row-when .tt-row-sep { opacity: .55; }
.tt-row-notime { color: var(--gv-bg4); }

/* The description is the one part of an entry nobody can reconstruct later, so
   it reads at body size and wraps instead of being truncated away. */
.tt-row-note { overflow-wrap: anywhere; }
.tt-row-empty { color: var(--gv-fg4); font-style: italic; }
.tt-row-ref {
  font-family: var(--bs-font-monospace);
  font-size: .8125rem;
  color: var(--gv-blue-br);
  text-decoration: none;
  margin-right: .25rem;
}
.tt-row-ref:hover { color: var(--gv-yellow-br); }

/* Context line: billing project, service, custom fields, source task. Plain
   muted text separated by dots — a row of filled badges shouted louder than
   the description it belongs to. */
.tt-row-meta {
  font-size: .8125rem;
  color: var(--gv-fg4);
  overflow-wrap: anywhere;
}
.tt-row-meta > *:not(:first-child):not(.badge)::before {
  content: "·";
  margin: 0 .4rem;
  opacity: .55;
}
.tt-row-meta .badge { margin-left: .4rem; }
.tt-row-meta a { color: var(--gv-fg3); }
.tt-row-meta a:hover { color: var(--gv-yellow-br); }

/* "12h 30m" is the longest this gets — it must never wrap into two lines. */
.tt-row-dur { font-weight: 600; color: var(--gv-fg0); white-space: nowrap; }

/* Row actions recede until the row is under the cursor — three icons per line
   were a column of noise down the page. Touch devices never hover, so there
   they stay visible. */
.tt-row-actions .btn { color: var(--gv-fg4); opacity: .5; transition: opacity .12s ease; }
.tt-row:hover .tt-row-actions .btn,
.tt-row:focus-within .tt-row-actions .btn { opacity: 1; }
.tt-row-actions .btn:hover { color: var(--gv-yellow-br); }
@media (hover: none) {
  .tt-row-actions .btn { opacity: 1; }
}
.tt-entries .tt-row:hover { background: var(--bs-tertiary-bg); }

@media (max-width: 575.98px) {
  /* Narrow: when + duration share the top line, description below, actions
     last — the fixed gutters would eat the description otherwise. */
  .tt-row-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "when dur"
      "main main"
      "act  act";
    row-gap: .2rem;
  }
  .tt-row-actions { justify-self: end; }
}

/* Quiet chips: an outline instead of a filled block. Used for the exceptions
   worth spotting (non-billable) and in the inspector's time list. */
.tt-chip.badge {
  background: transparent !important;
  border: 1px solid var(--bs-border-color);
  color: var(--gv-fg3) !important;
  font-weight: 400;
}
.tt-chip-warn.badge {
  background: transparent !important;
  border: 1px solid var(--gv-orange);
  color: var(--gv-orange-br) !important;
  font-weight: 400;
}

/* ===================================================================
   Notes outline — a note body as a tree of blocks (services/outline.py,
   static/js/outliner.js). Lives here rather than in the note template
   because TWO pages carry an outline: a note, and a notes-folder, which
   is a container for filing and for access and also a page with a body.
   =================================================================== */

  /* ---- The outline ---------------------------------------------------
     One bullet per block, children indented under a guide line. The type is
     the sheet's, not a control's: editing a block swaps in a textarea that
     inherits the very same face, size and measure, so the page never jumps
     between "reading" and "writing". */
  #note-outline { font-family: var(--doc-face); font-size: 1.05rem; line-height: 1.7;
                  /* Room under the last block to click into it. A page with
                     one short line would otherwise be mostly dead surface. */
                  min-height: 8rem; }
  .ol-block { position: relative; }
  .ol-row { display: flex; align-items: flex-start; }

  /* The fold arrow is only there when there is something to fold, but it keeps
     its space either way so the bullets stay in one straight column. */
  .ol-fold { flex: 0 0 .85rem; width: .85rem; line-height: 1.7; font-size: .62rem;
             text-align: center; cursor: pointer; opacity: 0; user-select: none;
             transition: opacity .12s; color: var(--paper-ink-quiet); }
  .ol-block:hover > .ol-row > .ol-fold { opacity: .5; }
  .ol-block > .ol-row > .ol-fold:hover { opacity: 1; }
  .ol-fold::before { content: "▾"; }
  .ol-block.ol-folded > .ol-row > .ol-fold::before { content: "▸"; }
  /* Nothing under it, nothing to fold — but an embed IS something under it,
     even though it hangs in the content rather than in a children box. */
  .ol-block:not(:has(> .ol-children)):not(:has(> .ol-row > .ol-content > .ol-embed))
    > .ol-row > .ol-fold { visibility: hidden; }

  .ol-bullet { flex: 0 0 1rem; width: 1rem; height: 1.7rem; cursor: pointer;
               display: flex; align-items: center; justify-content: center; }
  .ol-bullet::before { content: ""; width: .38rem; height: .38rem; border-radius: 50%;
                       background: var(--paper-ink-quiet); }
  /* A ring means "there is more underneath" — so the shape of the note is
     readable before anything is folded, and a folded block says so louder. */
  .ol-bullet.ol-parent::before { box-shadow: 0 0 0 .2rem rgba(128, 128, 128, .2); }
  .ol-folded > .ol-row > .ol-bullet.ol-parent::before {
    box-shadow: 0 0 0 .26rem rgba(128, 128, 128, .38); }

  .ol-content { flex: 1 1 auto; min-width: 0; padding: .05rem 0 .05rem .15rem;
                cursor: text; }

  /* The sheet's document spacing (notes/_nav.html) is meant for ONE long text:
     a blank line under every paragraph, a lot of air above every heading. In an
     outline each block IS a paragraph, so that spacing puts a gap between every
     bullet and drops a heading's text far below its own bullet. Inside a block
     the spacing comes from the row, not from the text.

     Addressed by id on purpose: the sheet's rules are ".note-doc .note-body x"
     and sit further down this file, so at equal specificity they would win. */
  #note-outline .ol-content p { margin: 0; }
  #note-outline .ol-content ul, #note-outline .ol-content ol { margin: 0 0 0 1.4em; }
  #note-outline .ol-content li { margin: 0; }
  #note-outline .ol-content h1, #note-outline .ol-content h2,
  #note-outline .ol-content h3, #note-outline .ol-content h4,
  #note-outline .ol-content h5, #note-outline .ol-content h6 { margin: 0 0 .1em; }
  #note-outline .ol-content pre, #note-outline .ol-content blockquote,
  #note-outline .ol-content table, #note-outline .ol-content img { margin: .2em 0; }
  #note-outline .ol-content > :last-child { margin-bottom: 0; }
  /* An empty block is still a line you must be able to put the caret in.
     Without a floor it collapses to about two pixels, and a note you have just
     created — whose only block is empty — has nothing to click at all. */
  #note-outline .ol-content { min-height: 1.4em; }

  /* The keyword chip and the checkbox lead the block's first line, so that
     line has to run on beside them — as a paragraph of its own it would drop
     the text onto the next line and leave the chip stranded above it. */
  #note-outline .ol-content > :is(.ol-kw, .ol-box) ~ p:first-of-type { display: inline; }
  #note-outline .ol-box { vertical-align: -.08em; cursor: pointer; margin-right: .1rem; }

  .ol-children { margin-left: .92rem; padding-left: .62rem;
                 border-left: 1px solid var(--paper-edge); }
  /* A block that opens with a heading gets its air from the ROW, so the bullet
     moves with it instead of being left hanging above the text. */
  #note-outline .ol-block:has(> .ol-row > .ol-content > :is(h1, h2, h3)) {
    margin-top: 1.1em; }
  #note-outline > .ol-list > .ol-block:first-child { margin-top: 0; }

  .ol-edit { width: 100%; border: 0; padding: 0; margin: 0; resize: none;
             overflow: hidden; background: transparent; color: var(--paper-ink);
             font: inherit; line-height: inherit; }
  .ol-edit:focus { outline: none; }

  /* TODO / DOING / DONE. DONE strikes through everything after the chip —
     which is the whole block, since the chip is always its first child. */
  .ol-kw { border: 0; border-radius: .25rem; padding: 0 .3rem; margin-right: .3rem;
           font-family: var(--bs-font-monospace); font-size: .68em; font-weight: 700;
           letter-spacing: .03em; vertical-align: .1em; }
  .ol-kw-todo { background: rgba(220, 53, 69, .13); color: #b02a37; }
  .ol-kw-doing { background: rgba(255, 193, 7, .2); color: #8a6d00; }
  .ol-kw-done { background: rgba(25, 135, 84, .13); color: #146c43; }
  .ol-kw-done ~ * { text-decoration: line-through; opacity: .55; }

  /* A ((block reference)) is a link, but a quieter one than a note link —
     it points inside a document rather than at one. */
  #note-outline a[href*="zoom="] { text-decoration: none;
    border-bottom: 1px dashed currentColor; }
  .ol-embed { border-left: 2px solid var(--paper-edge); border-radius: .25rem;
              padding: .1rem .5rem; margin: .1rem 0;
              background: rgba(128, 128, 128, .045); }
  /* The host block's bullet already marks this line, so the embedded block's
     own would be a second one beside it — two bullets, two apparent blocks
     sitting side by side. Its CHILDREN keep theirs: they are further in. */
  .ol-embed > .ol-list > .ol-block > .ol-row > .ol-bullet { visibility: hidden; }
  .ol-embed > .ol-list > .ol-block > .ol-row > .ol-fold { display: none; }
  /* Folded, an embed keeps the line saying where it comes from — collapsing it
     to a bare bullet would leave a row that says nothing at all. */
  .ol-folded > .ol-row > .ol-content > .ol-embed > .ol-list { display: none; }
  .ol-folded > .ol-row > .ol-content > .ol-embed { padding: .1rem .5rem; }
  .ol-embed-src { font-size: .7em; opacity: .55; text-decoration: none; }
  .ol-missing { color: var(--bs-secondary-color); font-style: italic; }

  /* ---- Linked references -----------------------------------------------
     Under the note, ruled off from it: this is what OTHER pages say about
     this one, so it must read as a different voice without shouting. */
  .note-refs { margin-top: 2.5rem; padding-top: 1.25rem;
               border-top: 1px solid var(--paper-edge); }
  .note-refs-head {
    font-family: var(--bs-font-sans-serif); font-size: .72rem; font-weight: 600;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--paper-ink-quiet); margin: 0 0 1rem;
  }
  .note-refs-count {
    background: var(--paper-edge); color: var(--paper-ink-quiet);
    border-radius: 1rem; padding: 0 .4rem; margin-left: .3rem;
    font-size: .95em; letter-spacing: 0;
  }
  .note-refs-group { margin-bottom: 1.25rem; }
  .note-refs-page {
    display: inline-block; margin-bottom: .25rem; text-decoration: none;
    font-family: var(--bs-font-sans-serif); font-size: .82rem; font-weight: 600;
    color: var(--paper-ink-strong);
  }
  .note-refs-page:hover { text-decoration: underline; }
  .note-ref { margin: .35rem 0 .35rem .25rem; padding-left: .6rem;
              border-left: 2px solid var(--paper-edge); }
  .note-ref-trail {
    font-family: var(--bs-font-sans-serif); font-size: .68rem;
    color: var(--paper-ink-quiet); margin-bottom: .1rem;
  }
  .note-ref-sep { opacity: .5; }
  .note-ref-stale { font-size: .78rem; color: var(--paper-ink-quiet);
                    font-style: italic; margin: 0 0 0 .25rem; }
  /* A referenced block is quoted, not owned: no zooming, no folding, and the
     bullet stops pretending to be a handle. */
  .note-ref .ol-bullet, .note-ref .ol-fold { cursor: default; }
  .note-ref .ol-content { cursor: default; }

  .ol-crumbs { color: var(--paper-ink-quiet); }
  .ol-crumbs a { color: inherit; text-decoration: none; }
  .ol-crumbs a:hover { text-decoration: underline; }

  /* [[wiki link]] to a note that doesn't exist yet — expand() marks it with a
     trailing ⁺, this just keeps it visually quieter than a real link. */
  #note-outline a[href*="/notes/new"] { color: var(--bs-secondary-color); }
  #note-save-status { min-height: 1.2rem; }

  /* The shortcut sheet behind the "?" — two columns, keys against what they
     do, so it can be scanned rather than read. */
  .note-help { min-width: 21rem; max-width: 25rem; }
  .note-help-keys {
    display: grid; grid-template-columns: auto 1fr; gap: .35rem .8rem;
    align-items: baseline; font-size: .82rem;
  }
  .note-help-keys dt { font-weight: 400; white-space: nowrap; }
  .note-help-keys dd { margin: 0; color: var(--bs-secondary-color); }
  /* Floating "Create tasks" button, parked above the marked text. */
  .note-sel-capture { position: absolute; z-index: 1080; white-space: nowrap; }
  /* [[ autocomplete, positioned at the caret. */
  #wiki-complete {
    position: absolute; z-index: 2000; min-width: 14rem; max-height: 14rem;
    overflow-y: auto; display: none;
  }
  #wiki-complete .active { background: var(--bs-primary-bg-subtle); }
  /* "/" command menu, same treatment as the [[ ]] completion. */
  .slash-menu {
    position: absolute; z-index: 2000; min-width: 19rem; max-height: 16rem;
    overflow-y: auto;
  }

/* ===================================================================
   The note sheet — its design tokens and its document typography.
   Three pages wear it now (a note, a notes-folder, the braindump pad),
   and only one of them includes the notes navigator, so this cannot
   live in that template's style block any more.
   =================================================================== */
/* ---- the sheet ----------------------------------------------------
   A note is a document, not another panel of the task app, so the middle
   column is a page: its own warm surface lifted off the app background, a
   hairline edge, real page margins and a measure that stops at a readable
   line length. The palette is the app's own (gruvbox) one step lighter —
   nothing imported, just paper instead of chrome.

   The reading face is a serif, and the *editor uses the same one at the same
   size* (see notes/detail.html): switching to edit must not turn the page
   into a code window. That continuity is the whole idea. */
.note-doc {
  --paper: var(--gv-bg0-soft, #32302f);
  --paper-edge: var(--gv-bg1, #3c3836);
  --paper-ink: var(--gv-fg1, #ebdbb2);
  --paper-ink-strong: var(--gv-fg0, #fbf1c7);
  --paper-ink-quiet: var(--gv-fg4, #a89984);
  --doc-face: "Iowan Old Style", Charter, "Bitstream Charter", Palatino,
              "Palatino Linotype", Georgia, serif;
  --doc-measure: 46rem;

  background: var(--paper);
  border: 1px solid var(--paper-edge);
  border-radius: .5rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 12px 28px rgba(0, 0, 0, .18);
  padding: 2.25rem 2.75rem 3rem;
}
.note-doc-inner { max-width: var(--doc-measure); margin: 0 auto; }
/* A folder's own body sits above the list of what it contains, so it is the
   same sheet — just not the whole page, and not shouting for the room. */
.note-doc.note-doc-inline { padding: 1.25rem 1.5rem; }

/* Title and body carry the document face; the chrome around them (buttons,
   hints, storage meter, inspector) stays in the app's UI sans. */
.note-doc #note-title,
.note-doc .note-doc-title {
  font-family: var(--doc-face);
  font-size: 1.9rem; line-height: 1.2; letter-spacing: -.01em;
  color: var(--paper-ink-strong);
}
.note-doc #note-title::placeholder { color: var(--paper-ink-quiet); }
/* The meta line reads as a colophon: quiet, spaced, ruled off from the text
   the way a document separates its header from its body. */
.note-doc .note-doc-meta {
  font-size: .72rem; letter-spacing: .04em; color: var(--paper-ink-quiet);
  border-bottom: 1px solid var(--paper-edge);
  padding-bottom: .6rem; margin-bottom: 1.6rem;
}

/* ---- The Markdown document editor (static/js/note-document.js) ----
   The other note editor: one textarea over the whole body, for a note that is
   a plain Markdown document rather than an outline. Same face and measure as
   the rendered text, so opening it does not move the page — and monospace
   would be wrong here: this is prose, and the source is what you read while
   you write it. */
#note-document { min-height: 8rem; }
#note-document .nd-view { cursor: text; }
#note-document .nd-src {
  font-family: var(--doc-face); font-size: 1.05rem; line-height: 1.75;
  color: var(--paper-ink); background: transparent;
  border: 0; border-radius: 0; box-shadow: none; padding: 0;
  width: 100%; resize: none; overflow: hidden; white-space: pre-wrap;
}
#note-document .nd-src:focus {
  background: transparent; box-shadow: none; color: var(--paper-ink);
}

.note-doc .note-body {
  font-family: var(--doc-face);
  font-size: 1.05rem; line-height: 1.75; color: var(--paper-ink);
}
/* Document spacing — the app's .md-body is tuned for tight UI previews,
   which is exactly wrong at reading length. */
.note-doc .note-body p { margin: 0 0 1em; }
.note-doc .note-body ul, .note-doc .note-body ol { margin: 0 0 1em 1.4em; }
.note-doc .note-body li { margin: 0 0 .35em; }
.note-doc .note-body h1, .note-doc .note-body h2, .note-doc .note-body h3,
.note-doc .note-body h4, .note-doc .note-body h5, .note-doc .note-body h6 {
  font-family: var(--doc-face); color: var(--paper-ink-strong);
  margin: 1.9em 0 .5em; line-height: 1.25; font-weight: 600;
}
.note-doc .note-body > :first-child { margin-top: 0; }
.note-doc .note-body h1 { font-size: 1.55rem; }
.note-doc .note-body h2 { font-size: 1.3rem; }
.note-doc .note-body h3 { font-size: 1.12rem; }
.note-doc .note-body h4, .note-doc .note-body h5, .note-doc .note-body h6 {
  font-size: 1rem; color: var(--paper-ink);
}
.note-doc .note-body blockquote {
  border-left: 2px solid var(--bs-primary, #d79921);
  margin: 0 0 1em; padding: .1rem 0 .1rem 1rem;
  color: var(--paper-ink-quiet); font-style: italic;
}
.note-doc .note-body hr {
  border: 0; border-top: 1px solid var(--paper-edge); margin: 2em 0;
}
.note-doc .note-body table { margin: 0 0 1em; font-size: .95rem; }
.note-doc .note-body table th, .note-doc .note-body table td {
  border: 1px solid var(--paper-edge); padding: .35rem .6rem;
}
.note-doc .note-body pre, .note-doc .note-body code {
  font-family: var(--bs-font-monospace); font-size: .85em;
}
.note-doc .note-body pre { margin: 0 0 1em; padding: .75rem 1rem; }
.note-doc .note-body img { border-radius: .3rem; margin: .4em 0; }
/* Checkboxes get a document-sized hit target — a checklist in a note is
   something you tick while reading, not a dense UI control. */
.note-doc .note-body li.md-task { margin-left: -1.35em; }
.note-doc .note-body li.md-task input {
  width: 1rem; height: 1rem; margin-right: .55em; vertical-align: -2px;
  accent-color: var(--bs-primary, #d79921);
}

/* ---------------------------------------------------------------------------
   The scratchpad window (layout/_scratchpad_panel.html)

   A floating frame that holds /scratchpad/frame in an iframe, so the pad is on
   screen wherever you are. Position and size are inline styles written by
   static/js/scratchpad-panel.js from localStorage; everything here is chrome.
   --------------------------------------------------------------------------- */
.scratchpad-panel {
  position: fixed; z-index: 1048;
  display: flex; flex-direction: column;
  min-width: 260px; min-height: 220px;
  /* ``resize`` needs a clipping box; the padding keeps the browser's grip
     clear of the iframe, which would otherwise swallow the pointer. */
  overflow: hidden; resize: both; padding-bottom: 12px;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: .5rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .45);
}
.scratchpad-panel[hidden] { display: none; }
.scratchpad-panel .sp-head {
  display: flex; align-items: center; gap: .4rem;
  padding: .35rem .5rem; font-size: .85rem;
  background: var(--bs-tertiary-bg);
  border-bottom: 1px solid var(--bs-border-color);
  cursor: move; user-select: none;
}
.scratchpad-panel .sp-title { font-weight: 600; margin-right: auto; }
.scratchpad-panel .sp-btn {
  border: 0; background: none; padding: .1rem .3rem; line-height: 1;
  color: var(--bs-secondary-color); text-decoration: none; cursor: pointer;
}
.scratchpad-panel .sp-btn:hover { color: var(--bs-body-color); }
.scratchpad-panel .sp-frame {
  flex: 1 1 auto; width: 100%; border: 0; background: transparent;
}
/* While dragging, the pointer must stay with the title bar. */
.scratchpad-panel.sp-dragging .sp-frame { pointer-events: none; }

/* Inside the frame: the pad without a page around it. The sheet's paper
   chrome (border, shadow, generous margins) is what a document wants and what
   a 380px window has no room for — the window's own frame is the border. */
body.scratchpad-frame { padding: .5rem .65rem; }
body.scratchpad-frame .note-doc.scratchpad-compact {
  background: transparent; border: 0; box-shadow: none;
  border-radius: 0; padding: 0;
}
body.scratchpad-frame .note-doc .note-body {
  font-size: .95rem; line-height: 1.55;
}
body.scratchpad-frame .sp-tools {
  position: sticky; top: 0; z-index: 3;
  padding: .1rem 0 .35rem; background: var(--bs-body-bg);
}

/* Narrow screens have nowhere to float: dock it to the bottom edge instead.
   The JS drops its inline geometry below this breakpoint. */
@media (max-width: 575.98px) {
  .scratchpad-panel {
    left: 0; right: 0; bottom: 0; top: auto;
    width: 100%; height: 62vh;
    border-radius: .5rem .5rem 0 0; resize: none; padding-bottom: 0;
  }
}

/* ---------------------------------------------------------------------------
   Vim keys in the outliner (static/js/outliner-vim.js)

   The bar is vim's own status line: the mode on the left, the pending command
   or the "/" prompt beside it. Built by the JS, so a page without the file has
   nothing of this.
   --------------------------------------------------------------------------- */
.vim-bar {
  position: fixed; left: 0; bottom: 0; z-index: 1049;
  display: flex; align-items: center; gap: .6rem;
  padding: .15rem .6rem;
  font-family: var(--bs-font-monospace); font-size: .78rem; line-height: 1.5;
  background: var(--bs-tertiary-bg); border-top: 1px solid var(--bs-border-color);
  border-right: 1px solid var(--bs-border-color); border-top-right-radius: .3rem;
  color: var(--bs-secondary-color);
}
.vim-bar .vim-mode { font-weight: 700; letter-spacing: .04em; }
.vim-bar[data-mode="insert"] .vim-mode { color: var(--bs-success, #98971a); }
.vim-bar[data-mode="visual"] .vim-mode { color: var(--bs-info, #458588); }
.vim-bar[data-mode="normal"] .vim-mode { color: var(--bs-primary, #d79921); }
/* The pending command reads as what it is: keys you have typed but not
   finished ("2d", "/rele"). */
.vim-bar .vim-cmd { color: var(--bs-body-color); min-height: 1em; }

/* Normal mode draws a block cursor, which a textarea can only do as a
   one-character selection — so the thin caret has to go, or there are two. */
textarea.ol-edit.vim-normal { caret-color: transparent; }
textarea.ol-edit.vim-normal::selection { background: var(--bs-primary, #d79921); color: var(--bs-body-bg); }

/* ---- Plan view: the work list (tasks/_plan_worklist.html) ----
   One column of cards, read top to bottom. Quiet on purpose: the left edge is
   the priority as on the timeline cards, the project colour is a thin dot
   before the title, and everything else is the task's own text. */
.wl-card {
  --c: var(--gv-bg3);
  background: var(--gv-bg0-soft);
  border: 1px solid var(--gv-bg2); border-left: 3px solid var(--c);
  border-radius: .6rem; padding: .7rem .85rem; margin-bottom: .75rem;
}
/* Ticked off: the card fades and folds away (plan-worklist.js removes it). */
.wl-card {
  transition: opacity .18s ease, transform .18s ease, max-height .2s ease,
              padding .2s ease, margin .2s ease;
}
.wl-card.wl-leaving {
  opacity: 0; transform: translateX(8px); max-height: 0; overflow: hidden;
  margin: 0; padding-top: 0; padding-bottom: 0; pointer-events: none;
}
.wl-group-head { margin-top: .5rem; }
.wl-group + .wl-group .wl-group-head { margin-top: 1.5rem; }
.wl-head { display: flex; align-items: flex-start; gap: .6rem; }
.wl-check {
  flex: 0 0 auto; border: 0; background: none; padding: 0; line-height: 1;
  font-size: 1.25rem; color: var(--gv-fg4); cursor: pointer;
}
.wl-check:hover { color: var(--gv-green-br); }
.wl-title-wrap { flex: 1 1 auto; min-width: 0; }
.wl-title { font-weight: 600; font-size: 1rem; overflow-wrap: anywhere; }
.wl-title a { color: var(--gv-fg0); text-decoration: none; }
.wl-title a:hover { text-decoration: underline; }
.wl-card.has-row-color .wl-title::before {
  content: ""; display: inline-block; width: .55rem; height: .55rem;
  border-radius: 50%; background: var(--dot); margin-right: .4rem;
  vertical-align: middle;
}
.wl-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .75rem;
  font-size: .78rem; color: var(--gv-fg4); margin-top: .15rem;
}
.wl-when { color: var(--gv-fg2); font-weight: 600; }
.wl-actions { flex: 0 0 auto; display: flex; align-items: center; gap: .35rem; }
.wl-flash { margin: .3rem 0 0 1.85rem; }
.wl-fields {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .9rem;
  margin: .55rem 0 0 1.85rem;
}
.wl-field {
  display: inline-flex; align-items: center; gap: .3rem; margin: 0;
  color: var(--gv-fg4); font-size: .8rem;
}
.wl-field .form-control { width: auto; padding: .1rem .35rem; font-size: .8rem; }
.wl-tags { flex-wrap: wrap; }
.wl-tags .wl-tag-add { width: 7.5rem; }
.wl-note-wrap, .wl-comments { margin: .6rem 0 0 1.85rem; }
.wl-note-head {
  display: flex; align-items: center; gap: .5rem;
  font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--gv-fg4); margin-bottom: .2rem;
}
.wl-note { font-size: .9rem; overflow-wrap: anywhere; }
.wl-note.is-clamped:not(.is-open) {
  max-height: 9.5em; overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent);
  mask-image: linear-gradient(180deg, #000 70%, transparent);
}
.wl-note img { max-width: 100%; height: auto; }
.wl-comments { border-top: 1px dashed var(--gv-bg2); padding-top: .45rem; }
.wl-comment { font-size: .85rem; margin-bottom: .4rem; overflow-wrap: anywhere; }
.wl-comment-head { font-size: .75rem; }
.wl-comment-head .text-muted { margin-left: .35rem; }
.wl-comment img { max-width: 100%; height: auto; }
.wl-comment-form { display: flex; gap: .35rem; align-items: flex-start; }
.wl-comment-form textarea { resize: vertical; min-height: calc(1.5em + .5rem + 2px); }
/* Time tracking on the card: total + stopwatch on one line; the inspector's
   own section (timetracking/_task_time.html) replaces it once opened. */
.wl-time { margin: .6rem 0 0 1.85rem; }
.wl-time.is-open { border-top: 1px dashed var(--gv-bg2); padding-top: .45rem; }
.wl-time-line { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .75rem; font-size: .85rem; }
.wl-section-label {
  font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--gv-fg4);
}
@media (max-width: 575.98px) {
  .wl-card { padding: .6rem .6rem; }
  .wl-fields, .wl-note-wrap, .wl-comments, .wl-flash, .wl-time { margin-left: 0; }
}

/* A file attached to a task or note, in a rendered note or comment
   (``_attachment_link`` in views/filters.py): a paperclip in front, so a
   download reads as a file and not as a link to another page. */
a.attachment-link::before {
  font-family: "bootstrap-icons";
  content: "\f4b3";
  margin-right: .2em;
  vertical-align: -.125em;
}

/* Dependency graph nodes (dep-graph.js): the full graph and the project
   header's panel. */
.dep-graph svg { display: block; }
.dep-node rect { transition: filter .1s ease; cursor: pointer; }
.dep-node:hover rect { filter: brightness(1.08); }
.dep-node text { pointer-events: none; }

/* ---- Item dialog (static/js/item-modal.js) ------------------------------
   A task or note opens over the page, its own detail page in a frame. */
.item-modal .modal-dialog { max-width: min(1200px, 94vw); }
.item-modal .modal-content {
  height: calc(100vh - 3.5rem);
  overflow: hidden;
  background: var(--gv-bg0);
  border: 1px solid var(--gv-bg2);
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, .55);
}
@media (max-width: 991.98px) {
  .item-modal .modal-dialog { max-width: none; }
  .item-modal .modal-content { height: 100%; border: 0; }
}
.item-modal-bar {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem .6rem .4rem .9rem;
  border-bottom: 1px solid var(--gv-bg2);
  background: var(--gv-bg0-soft);
}
.item-modal-bar .btn-link { color: var(--gv-fg3); text-decoration: none; padding: .1rem .35rem; }
.item-modal-bar .btn-link:hover { color: var(--gv-yellow-br); }
.item-modal-kind { color: var(--gv-fg4); }
.item-modal-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: .85rem;
  font-weight: 500;
  color: var(--gv-fg3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.item-modal-frame { flex: 1 1 auto; width: 100%; border: 0; background: var(--gv-bg0); }

/* The page inside that frame: no navbar, no sidebars (base.html). */
html.item-embed .app-layout { min-height: 100vh; }
html.item-embed .app-main { padding: 1rem 1.4rem !important; }
/* A task on a wide dialog reads like Todoist's: content left, properties in
   their own column on the right, each scrolling on its own. */
@media (min-width: 992px) {
  html.item-embed body:has(.task-detail-panes) { height: 100vh; overflow: hidden; }
  html.item-embed body:has(.task-detail-panes) .app-layout { height: 100vh; }
  html.item-embed .app-main:has(.task-detail-panes) {
    display: flex;
    flex-direction: column;
    height: 100vh;
    padding-bottom: 0 !important;
  }
  html.item-embed .task-detail-header { flex: 0 0 auto; }
  html.item-embed .task-detail-panes { flex: 1 1 auto; min-height: 0; margin-top: 0; }
  html.item-embed .task-detail-panes > .task-detail-body,
  html.item-embed .task-detail-panes > .task-detail-aside {
    height: 100%;
    overflow-y: auto;
    margin-top: 0;
    padding-bottom: 1.5rem;
  }
  html.item-embed .task-detail-panes > .task-detail-aside {
    background: var(--gv-bg0-soft);
    border-left: 1px solid var(--gv-bg2);
    padding-top: 0;
    padding-bottom: 0;
  }
  /* Padding on the scroll box would leave a strip above the sticky chip row
     that the sections scroll through; the panel's own top gap goes here. */
  html.item-embed .task-detail-aside > .task-detail-inspector { padding-top: .75rem; }
}

/* ---- The inspector beside a task's page (dialog and full page): a property
   inspector made of distinct blocks, like an issue sidebar or a settings
   pane, not a flat list. Two earlier attempts failed in opposite ways: heavy
   cards with caps on every label and a dark well under every field was a
   wall; dropping all of that left a grey soup where no block could be told
   from the next. The rules here:
   - every section is its own block: a faint panel with a hairline frame and
     a real header row (icon, title in sentence case, chevron; the whole row
     folds), the body below a divider;
   - more space between blocks than inside them, so the eye finds the block
     first and the field second;
   - inside, one consistent rhythm: small muted labels above controls that
     look like controls (a light frame, slightly darker fill), yellow focus;
   - quick-date buttons stay small text links, help text only while the
     block has focus, so the content stays calm.
   The section ORDER is the user's (inspector_section_order) and untouched. */
.task-detail-inspector {
  --insp-block: rgba(235, 219, 178, .035);
  --insp-head: rgba(235, 219, 178, .04);
  --insp-line: rgba(235, 219, 178, .10);
  --insp-field: rgba(29, 32, 33, .55);
  --insp-field-line: rgba(235, 219, 178, .13);
}
.task-detail-inspector .inspector-form {
  padding: 0;
  gap: .75rem;
  overflow: visible;
}
.task-detail-inspector .insp-jump { display: none; }

/* The block. */
.task-detail-inspector .inspector-section {
  background: var(--insp-block);
  border: 1px solid var(--insp-line);
  border-radius: .5rem;
  padding: 0;
  margin: 0;
}
.task-detail-inspector .inspector-section:focus-within {
  border-color: rgba(235, 219, 178, .2);
  box-shadow: none;
}
.task-detail-inspector .inspector-section:last-of-type { margin-bottom: .75rem; }
.task-detail-inspector .inspector-section > .section-collapsible { padding: .7rem .8rem .8rem; }
/* Blocks without a header carry their padding directly. The title is not a
   block: it is the thing the blocks describe, a field on its own on top. */
.task-detail-inspector .inspector-section[data-section="quick-plan"] { padding: .65rem .8rem .75rem; }
.task-detail-inspector .inspector-section[data-section="title"] {
  background: transparent;
  border: 0;
  padding: 0;
}

/* The header row: the block's name, readable at a glance. */
.task-detail-inspector .inspector-section > h2 {
  position: relative;
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: 0 !important;
  padding: .5rem 2rem .5rem .7rem;
  background: var(--insp-head);
  border-bottom: 1px solid var(--insp-line);
  border-radius: calc(.5rem - 1px) calc(.5rem - 1px) 0 0;
  font-size: .82rem !important;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none !important;
  color: var(--gv-fg1) !important;
  user-select: none;
}
.task-detail-inspector .inspector-section > h2:has(+ .section-collapsible.d-none) {
  border-bottom-color: transparent;
  border-radius: calc(.5rem - 1px);
}
.task-detail-inspector .inspector-section > h2 > .text-muted,
.task-detail-inspector .inspector-section > h2 .text-muted:not(.btn) { color: var(--gv-fg4) !important; font-weight: 400; }
.insp-ico { margin-right: .35rem; }
.task-detail-inspector .inspector-section > h2 .insp-ico { color: var(--gv-fg4); font-size: .9rem; line-height: 1; margin-right: 0; }
/* The chevron sits at the right edge and its hit area covers the whole row,
   so clicking the block's name folds it. */
.task-detail-inspector .inspector-section > h2 > [data-collapse-key] {
  position: static;
  order: 99;
  margin: 0 0 0 auto !important;
  color: var(--gv-fg4) !important;
  font-size: .78rem;
}
.task-detail-inspector .inspector-section > h2 > [data-collapse-key]::after {
  content: "";
  position: absolute;
  inset: 0;
  cursor: pointer;
}
.task-detail-inspector .inspector-section > h2 > [data-collapse-key]:focus-visible { outline: none; }
.task-detail-inspector .inspector-section > h2:has(> [data-collapse-key]:focus-visible) {
  box-shadow: inset 0 0 0 2px rgba(250, 189, 47, .5);
}
.task-detail-inspector .inspector-section > h2:hover { color: var(--gv-fg0) !important; }
/* Anything else in the header (time tracking's controls) stays clickable
   above the fold target. */
.task-detail-inspector .inspector-section > h2 > :not([data-collapse-key]):not(.insp-ico) { position: relative; z-index: 1; }
/* The drag grip lives in the header row, left of the chevron. */
/* The drag grip sits at the right edge, the chevron left of it inside the
   header's right padding (they used to share the same spot). */
.task-detail-inspector .inspector-grip { top: .6rem; right: .55rem; z-index: 2; }

/* A date field with its clear button on the same line: the x belongs to the
   field, not to the quick dates under it, where it wrapped onto a line of
   its own. */
.insp-date-field { display: flex; align-items: stretch; gap: .25rem; }
.insp-date-field > .form-control { flex: 1 1 auto; min-width: 0; }
.insp-date-clear {
  flex: 0 0 auto;
  padding: 0 .55rem;
  line-height: 1;
  color: var(--gv-fg4);
  background: transparent;
  border: 1px solid transparent;
  border-radius: .375rem;
}
.insp-date-clear:hover,
.insp-date-clear:focus-visible { color: var(--gv-red-br); border-color: var(--gv-bg2); }

/* Field labels: small, muted, sentence case. */
.task-detail-inspector .inspector-section .form-label {
  text-transform: none !important;
  letter-spacing: 0;
  font-size: .75rem;
  font-weight: 500;
  color: var(--gv-fg4) !important;
  margin-bottom: .25rem;
}
/* Quick plan has no header row; its label reads as the block's name. */
.task-detail-inspector [data-section="quick-plan"] > .form-label {
  font-size: .82rem;
  font-weight: 600;
  color: var(--gv-fg1) !important;
  margin-bottom: .45rem;
}
/* The dialog already shows the title as its heading; the field is the editor,
   its label is for screen readers only. */
.task-detail-inspector [data-section="title"] > .form-label {
  position: absolute !important;
  width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
}
.task-detail-inspector .sec-fields .mt-3,
.task-detail-inspector .sec-fields .grid-2.mt-3 { margin-top: .8rem !important; }
.task-detail-inspector .grid-2 { gap: .6rem .75rem; }

/* Controls look like controls: a light frame and a slightly darker fill. */
.task-detail-inspector .inspector-form .form-control,
.task-detail-inspector .inspector-form .form-select {
  background-color: var(--insp-field);
  border-color: var(--insp-field-line);
  border-radius: .375rem;
  color: var(--gv-fg1);
  transition: border-color .12s ease, background-color .12s ease;
}
.task-detail-inspector .inspector-form .form-control::placeholder { color: var(--gv-bg4, #7c6f64); }
.task-detail-inspector .inspector-form .form-control:hover,
.task-detail-inspector .inspector-form .form-select:hover { border-color: rgba(235, 219, 178, .24); }
.task-detail-inspector .inspector-form .form-control:focus,
.task-detail-inspector .inspector-form .form-select:focus {
  background-color: var(--gv-bg0-hard);
  border-color: var(--gv-yellow);
  box-shadow: 0 0 0 .15rem rgba(215, 153, 33, .2);
}
.task-detail-inspector .inspector-form select[size],
.task-detail-inspector .inspector-form select[multiple] { max-height: 7.5rem; }
/* The title is the one large value. */
.task-detail-inspector .inspector-form .form-control-lg {
  font-size: 1.02rem;
  font-weight: 600;
  padding: .35rem .6rem;
}

/* Quick-date buttons: small text links under the field. */
.task-detail-inspector .quick-date-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 0 .1rem;
  margin-top: .15rem !important;
}
.task-detail-inspector .quick-date-row .btn {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--gv-fg4);
  padding: .05rem .3rem;
  font-size: .7rem;
}
.task-detail-inspector .quick-date-row .btn:first-child { margin-left: -.3rem !important; }
/* Half width columns: tighter, so the five quick dates stay on one line. */
.task-detail-inspector .grid-2 .quick-date-row { gap: 0; }
.task-detail-inspector .grid-2 .quick-date-row .btn { padding-left: .18rem; padding-right: .18rem; }
.task-detail-inspector .grid-2 .quick-date-row .btn:first-child { margin-left: -.18rem !important; }
/* A narrower inspector (the full page beside a sidebar) has no room for two
   date fields side by side: one per row, each with its quick dates on one
   line. */
.task-detail-inspector { container: insp / inline-size; }
@container insp (max-width: 420px) {
  .grid-2:has(input[type="datetime-local"]) { grid-template-columns: minmax(0, 1fr); }
}
.task-detail-inspector .quick-date-row .btn:hover,
.task-detail-inspector .quick-date-row .btn:focus-visible {
  color: var(--gv-yellow-br);
  background: rgba(235, 219, 178, .06);
}

/* Help text: only while you are working in that block. Status lines that
   carry an icon (last follow-up) stay. */
.task-detail-inspector .section-collapsible div.form-text:not(:has(.bi)) { display: none; }
.task-detail-inspector .section-collapsible span.form-text.mt-1 { display: none !important; }
.task-detail-inspector .section-collapsible:focus-within div.form-text,
.task-detail-inspector .section-collapsible:focus-within span.form-text.mt-1 { display: block !important; }
/* Sub-headers inside a block (Related, Depends on) read as field labels. */
.task-detail-inspector .section-collapsible h2.h6 {
  text-transform: none !important;
  letter-spacing: 0;
  font-size: .75rem !important;
  font-weight: 500;
  color: var(--gv-fg4) !important;
}
.task-detail-inspector .repeat-weekdays { display: flex; width: 100%; flex-wrap: nowrap; }
.task-detail-inspector .repeat-weekdays .btn {
  flex: 1 1 0;
  min-width: 0;
  font-size: .74rem;
  padding: .15rem .2rem;
  color: var(--gv-fg3);
  background: var(--insp-field);
  border-color: var(--insp-field-line);
}
.task-detail-inspector .repeat-weekdays .btn-check:checked + .btn { color: var(--gv-bg0); background: var(--gv-yellow); border-color: var(--gv-yellow); }
.task-detail-inspector .form-text { font-size: .72rem; color: var(--gv-fg4); }
.task-detail-inspector .form-check-label { font-size: .875rem; color: var(--gv-fg2); }
.task-detail-inspector .sec-fields hr { border-color: var(--insp-line); opacity: 1; margin: .85rem 0 !important; }

html.item-embed .task-detail-inspector { margin-bottom: 0; }
html.item-embed .task-detail-inspector .inspector-footer {
  background: var(--gv-bg0-soft);
  border-top-color: var(--insp-line);
  margin: 0;
  padding: .6rem 0;
}

/* The jump row (inspector-jump.js) where it is still shown, in the list's
   side panel: a quiet line of words, not a block of pills. */
.insp-jump {
  display: flex;
  flex-wrap: wrap;
  gap: .1rem .75rem;
  padding: .1rem 0 .5rem;
}
.insp-jump-chip {
  padding: 0;
  font-size: .74rem;
  line-height: 1.6;
  color: var(--gv-fg4);
  background: none;
  border: 0;
}
.insp-jump-chip:hover,
.insp-jump-chip:focus-visible { color: var(--gv-yellow-br); }
.insp-jump-chip i { display: none; }
.insp-jump-hit { animation: insp-jump-hit 1.4s ease-out; }
@keyframes insp-jump-hit {
  from { background-color: rgba(250, 189, 47, .12); }
  to { background-color: transparent; }
}

/* ---- Phones and tablets ---------------------------------------------------
   Everything below sits inside a max-width query: the desktop layout (992px
   and up) is not touched by any of it.
   - below 992px the left sidebar is a drawer behind the hamburger
     (nav-drawer.js); its groups and their folded state are unchanged;
   - below 768px (phones) lists, the task page, the inspector and the forms
     get touch sizes: 44px targets, 16px inputs (iOS zooms into anything
     smaller on focus), no action that only exists on hover, and room for the
     notch and the home indicator (viewport-fit=cover in base.html). */
.nav-drawer-toggle,
.nav-drawer-head,
.nav-drawer-backdrop,
.quick-add-fab,
.task-detail-tabs { display: none; }

@media (max-width: 991.98px) {
  .navbar > .container-fluid {
    flex-wrap: nowrap;
    padding-left: max(.5rem, env(safe-area-inset-left));
    padding-right: max(.5rem, env(safe-area-inset-right));
  }
  .navbar .navbar-brand { margin-right: .25rem; }
  .navbar .navbar-icon-toolbar { margin-left: .25rem !important; }
  .nav-drawer-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: -.4rem;
    padding: 0;
    border: 0;
    font-size: 1.6rem;
    color: var(--gv-fg1);
  }
  .nav-drawer-toggle:hover,
  .nav-drawer-toggle:focus-visible { color: var(--gv-yellow-br); }

  .app-layout > .app-nav-sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1050;
    flex: none;
    width: min(300px, 86vw);
    height: 100vh;
    height: 100dvh;
    max-height: none;
    padding: calc(.75rem + env(safe-area-inset-top)) .75rem
             calc(.75rem + env(safe-area-inset-bottom))
             calc(.75rem + env(safe-area-inset-left));
    border-right: 1px solid var(--gv-bg2) !important;
    border-bottom: 0;
    overscroll-behavior: contain;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform .25s ease, visibility 0s linear .25s;
  }
  html.nav-drawer-open .app-layout > .app-nav-sidebar {
    transform: none;
    visibility: visible;
    transition: transform .25s ease;
    box-shadow: 10px 0 40px rgba(0, 0, 0, .5);
  }
  .nav-drawer-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1049;
    background: rgba(0, 0, 0, .5);
    opacity: 0;
    transition: opacity .25s ease;
  }
  .nav-drawer-backdrop[hidden] { display: none; }
  html.nav-drawer-open .nav-drawer-backdrop { opacity: 1; }
  html.nav-drawer-open body { overflow: hidden; }

  .nav-drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: -.25rem 0 .5rem .25rem;
    color: var(--gv-yellow-br);
    font-size: 1.1rem;
  }
  .nav-drawer-close {
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    color: var(--gv-fg3);
    font-size: 1.15rem;
  }
  .nav-drawer-close:hover,
  .nav-drawer-close:focus-visible { color: var(--gv-yellow-br); }
  /* The whole drawer scrolls as one column; the footer follows the groups
     instead of pinning and leaving three entries of room above it. */
  .app-layout > .app-nav-sidebar { overflow-y: auto; }
  .app-nav-sidebar .nav-sidebar-main { flex: 0 0 auto; overflow: visible; }
  .app-nav-sidebar .nav-sidebar-footer { margin-top: 1rem; }
  /* Inside the drawer: rows a thumb can hit. */
  .app-nav-sidebar .nav-link { min-height: 44px; padding-top: 0 !important; padding-bottom: 0 !important; }
  .app-nav-sidebar .nav-group-toggle { min-height: 40px; }
  .app-nav-sidebar .nav-sidebar-main > .mb-3 .btn { min-height: 44px; font-size: 1rem; }
  .app-nav-sidebar .nav-sidebar-main > .mb-3 .text-muted.small { display: none; }
  .app-nav-sidebar .fav-star { min-width: 36px; min-height: 44px; }
  /* A star that only appears under the cursor has no cursor here. */
  .app-nav-sidebar .fav-star-hover:not(.is-fav) { display: none; }
  .app-nav-sidebar .nav-sidebar-footer .nav-link { min-height: 36px; }
  .app-nav-sidebar:focus { outline: none; }
}

@media (max-width: 767.98px) {
  /* The project tree stacked above every page pushed the content a screen
     down; on a phone the drawer's Projects entry is the way in. */
  .app-layout > .app-project-sidebar { display: none; }
  :root { --nav-height: 61px; }
  .app-main {
    padding-left: max(1rem, env(safe-area-inset-left)) !important;
    padding-right: max(1rem, env(safe-area-inset-right)) !important;
    padding-bottom: calc(1rem + env(safe-area-inset-bottom)) !important;
  }
  .navbar .btn-sm,
  .navbar .toolbar-icon { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .navbar .me-3, .navbar .me-2 { margin-right: .25rem !important; }

  /* Projects overview: name, status and the actions; the rest is on the
     project itself. */
  .project-table th:nth-child(3), .project-table td:nth-child(3),
  .project-table th:nth-child(5), .project-table td:nth-child(5),
  .project-table th:nth-child(6), .project-table td:nth-child(6) { display: none; }

  /* Task rows: the circle is a real target, the title gets the width. The
     drag handle and the hover-only icons go; what a row still shows on the
     right is state (a due follow-up, a running timer). */
  .task-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: .1rem;
    padding: .25rem 0;
    min-height: 52px;
  }
  .task-row::before { left: 44px; }
  .task-row .task-handle,
  .task-row .task-collapse-spacer,
  .task-row .task-inspector-btn { display: none; }
  .task-row [data-task-complete-form] .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
  }
  .task-row .task-collapse-toggle { width: 24px; height: 44px; margin-right: -.4rem; }
  .task-row .task-title { font-size: 1rem; }
  .task-row-actions > :not(.task-row-keep):not(.tt-timer-stop) { display: none !important; }
  .task-row-actions .btn { min-width: 40px; min-height: 44px; }
  .task-row.subtask { padding-left: .25rem; }

  /* The task page: title over the full width, its actions a toolbar under
     it, the inspector stacked below the content. */
  .task-detail-header > .d-flex { flex-wrap: wrap; column-gap: .25rem !important; }
  .task-detail-header > .d-flex > .me-auto { flex: 1 1 0; min-width: 0; }
  .task-detail-header .task-detail-check {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; margin: -.5rem 0 0 -.5rem;
  }
  .task-detail-title { font-size: 1.3rem; overflow-wrap: anywhere; }
  .task-detail-actions { flex: 1 0 100%; margin-top: .6rem; }
  .task-detail-actions > .btn,
  .task-detail-actions > form > .btn { min-height: 44px; min-width: 48px; height: 100%; }
  .task-detail-actions > .btn,
  .task-detail-actions > form { flex: 1 1 0; }
  .task-detail-actions > form > .btn { width: 100%; }
  .task-detail-meta { font-size: .85rem; }

  /* Task and Details as a segmented switch, pinned under the navbar. */
  .task-detail-tabs {
    display: flex;
    gap: .25rem;
    position: sticky;
    top: var(--nav-height);
    z-index: 5;
    margin: 0 0 .9rem;
    padding: .25rem;
    background: var(--gv-bg0-hard);
    border: 1px solid var(--gv-bg2);
    border-radius: .6rem;
  }
  .task-detail-tabs > button {
    flex: 1 1 0;
    min-height: 40px;
    border: 0;
    border-radius: .45rem;
    background: transparent;
    color: var(--gv-fg3);
    font-size: .95rem;
    font-weight: 500;
  }
  .task-detail-tabs > button[aria-selected="true"] {
    background: var(--gv-bg1);
    color: var(--gv-fg0);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
  }
  .task-detail-tabs > button[aria-selected="true"] .bi { color: var(--gv-yellow-br); }
  .task-detail-panes[data-pane="body"] > .task-detail-aside,
  .task-detail-panes[data-pane="aside"] > .task-detail-body { display: none; }
  .task-detail-panes { --bs-gutter-y: 0; margin-top: 0; }
  .task-detail-panes > * { margin-top: 0; }
  /* The tab replaces the show/hide switch, and a panel hidden on the desktop
     must still show here. */
  #inspector-toggle { display: none; }
  .task-detail-panes[data-pane="aside"] #inline-inspector.d-none { display: block !important; }
  #inline-inspector > .d-flex:first-child { display: none !important; }

  /* A list page's title row: the buttons keep their words on one line and
     move under the title when they do not fit beside it. */
  .app-main > .d-flex.align-items-center.mb-3:has(> h1) { flex-wrap: wrap; row-gap: .6rem !important; }
  .app-main > .d-flex.align-items-center.mb-3 > h1 { flex: 1 0 100%; font-size: 1.4rem; }
  .app-main > .d-flex.align-items-center.mb-3:has(> h1) .btn {
    white-space: nowrap;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
  }

  /* Capture is the thing a phone is for: Quick add as a round button over
     the bottom corner, clear of the home indicator. Not on a task's own page,
     where the bottom edge belongs to the inspector's Save. */
  .quick-add-fab {
    display: inline-flex;
    position: fixed;
    right: calc(1rem + env(safe-area-inset-right));
    bottom: calc(1rem + env(safe-area-inset-bottom));
    z-index: 1030;
    width: 56px;
    height: 56px;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: var(--gv-yellow);
    color: var(--gv-bg0-hard);
    font-size: 1.6rem;
    box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
  }
  .quick-add-fab:active { transform: scale(.96); }
  /* A list's own plain "New task" is the same dialog the round button opens. */
  .app-main > .d-flex.align-items-center.mb-3 > a.btn-primary[href="/tasks/new"] { display: none !important; }
  body:has(.task-detail-panes) .quick-add-fab,
  html.nav-drawer-open .quick-add-fab { display: none; }
  /* Room under the last row, so the button never covers it. */
  .app-main:has(.task-list) { padding-bottom: calc(5rem + env(safe-area-inset-bottom)) !important; }

  .task-detail-inspector .inspector-section > h2 { min-height: 44px; }
  .task-detail-inspector .quick-date-row .btn,
  .task-detail-inspector [data-quick-date] { min-height: 32px; padding-left: .45rem; padding-right: .45rem; }
  .inspector-footer { padding-bottom: calc(.6rem + env(safe-area-inset-bottom)) !important; }
  .insp-date-clear { min-width: 44px; font-size: 1.1rem; }
  /* Reordering blocks by drag is a desktop gesture; on a phone it fights
     the scroll. */
  .task-detail-inspector .inspector-grip { display: none; }
  /* A date and time at 16px does not fit half a phone: one field per row. */
  .task-detail-inspector .grid-2:has(input[type="datetime-local"]) { grid-template-columns: minmax(0, 1fr); }
  .inspector-footer .btn { min-height: 44px; padding-left: 1rem; padding-right: 1rem; font-size: 1rem; }

  /* Today's grouping switch is seven buttons wide: scroll it, never the page. */
  .today-group-by { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
  .today-group-by::-webkit-scrollbar { display: none; }
  .today-group-by > .btn { flex: 0 0 auto; }
}

/* Quick add and Log time on a phone: a full screen sheet, the button pinned
   above the home indicator instead of scrolled out of reach. */
@media (max-width: 575.98px) {
  .quick-add-modal .modal-dialog {
    margin: 0;
    max-width: none;
    width: 100%;
    height: 100%;
    min-height: 100%;
  }
  .quick-add-modal .modal-content {
    height: 100%;
    max-height: 100%;
    border: 0;
    border-radius: 0;
  }
  /* The form wraps body and footer, so it has to be the scrolling column. */
  .quick-add-modal .modal-content > form {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
  }
  .quick-add-modal .modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-top: calc(1rem + env(safe-area-inset-top)) !important;
    padding-left: max(1rem, env(safe-area-inset-left)) !important;
    padding-right: max(1rem, env(safe-area-inset-right)) !important;
  }
  .quick-add-modal .modal-footer {
    flex-wrap: nowrap;
    padding-bottom: calc(.5rem + env(safe-area-inset-bottom)) !important;
  }
  .quick-add-modal .modal-footer .btn { min-height: 44px; font-size: 1rem; white-space: nowrap; }
  .quick-add-modal .modal-footer .form-check { min-width: 0; }
  .quick-add-modal .modal-footer .btn-primary { padding-left: 1.25rem; padding-right: 1.25rem; }
  .quick-add-modal .row.g-2 > .col { flex: 1 0 100%; }
  .quick-add-modal .quick-date-row .btn { min-height: 36px; min-width: 44px; }
  .quick-add-modal .pending-attachments .btn { min-height: 40px; }
  #quickAddModal .quick-add-hint:has(.bi-keyboard) { display: none; }
}

/* 16px or iOS zooms the page into the field on focus: every phone, and a
   tablet driven by touch. */
@media (max-width: 767.98px), (max-width: 1199.98px) and (pointer: coarse) {
  input:not([type=checkbox]):not([type=radio]):not([type=range]),
  textarea,
  select,
  .form-control,
  .form-select { font-size: 16px !important; }
}

/* No action may exist only under a cursor a touch screen does not have. */
@media (hover: none) {
  .hover-action,
  .task-row-hover-action,
  .tree-row-actions .tree-action-btn,
  .task-inspector-btn { opacity: .7; }
}
