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

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

/* 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;
}
/* Drop-merge candidate during hover dwell — subtle hint before the
   row escalates to a full merge target. */
.task-row.drop-merge-pending,
.tree-task.drop-merge-pending {
  background: rgba(131, 165, 152, .08);
  outline: 1px dashed var(--gv-blue);
  outline-offset: -2px;
  border-radius: .25rem;
}
/* Hovering a task during a drag → drop-on-task = create a project. */
.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 to group";
  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); }
/* 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 — indented so the nesting reads at a glance; no left
   accent rail (removed as visual clutter). */
.task-row.subtask {
  margin-left: 2px;
}
.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;
}
.cal-event-title { flex: 1; 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);
}
.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; }

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

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

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

/* +assignee 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;
}
.qa-ac-item:hover,
.qa-ac-item.active { background: var(--gv-bg2); }
.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);
}

/* ============= 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;
}
.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); }
.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;
}
html.cairn-ps-collapsed .app-project-sidebar {
  flex-basis: var(--project-sidebar-collapsed-width);
  width: var(--project-sidebar-collapsed-width);
}
html.cairn-ps-collapsed .app-project-sidebar .ps-title,
html.cairn-ps-collapsed .app-project-sidebar .ps-action,
html.cairn-ps-collapsed .app-project-sidebar .ps-body,
html.cairn-ps-collapsed .app-project-sidebar .ps-resizer {
  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: 768px) {
  .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; }
.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);
}

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

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

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

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


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

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