html {
  font-size: 14px;
}

@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

html {
  position: relative;
  min-height: 100%;
}

body {
  margin-bottom: 60px;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
  color: var(--bs-secondary-color);
  text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
  text-align: start;
}
.discussion-container {
    max-height: 450px;
    overflow-y: auto;
}

.discussion-item {
    display: flex;
    margin-bottom: 15px;
}

.avatar {
    margin-right: 10px;
}

    .avatar img {
        border-radius: 50%;
    }

.discussion-content {
    background: #f6f8fa;
    border-radius: 6px;
    padding: 10px;
    width: 100%;
}

.discussion-header {
    margin-bottom: 4px;
}

.discussion-text {
    white-space: pre-wrap;
}

.drop-zone {
    border: 2px dashed #ccc;
    border-radius: 8px;
    padding: 30px;
    text-align: center;
    cursor: pointer;
    background-color: #fafafa;
    transition: 0.2s;
    position: relative; /* ensure it's on top */
    z-index: 10;
}

.drop-zone:hover {
    background-color: #f0f0f0;
}

.drop-zone.dragover {
    border-color: #007bff;
    background-color: #e9f5ff;
}

.drop-zone-text {
    color: #666;
    font-size: 14px;
}

/* Modal size */
.devops-modal {
    max-width: 1100px;
    width: 1100px;
}

/* Full height modal */
.devops-modal .modal-content {
    height: 85vh;
    display: flex;
    flex-direction: column;

    }

/* Body layout */
.devops-body {
    flex: 1; 
    display: flex;
    flex-direction: column;
    padding: 0;
    min-height: 0; 
}

/* Tabs always visible */
.devops-tabs {
    flex-shrink: 0;
}

/* Scroll ONLY here */
.devops-content {
    flex: 1;
    overflow-y: auto;
    min-height: 0; 
}

/* Tab spacing */
.tab-pane {
    height: 100%;
}

.devops-description {
    height: 200px;
    overflow-y: auto;
}

/* Drop zone */
.drop-zone {
    border: 2px dashed #ccc;
    border-radius: 6px;
    padding: 30px;
    text-align: center;
    cursor: pointer;
    background: #fafafa;
}

.drop-zone.dragover {
    border-color: #0078d4;
    background: #eef6fc;
}

/* =================================================================
   Query results grid  (Azure DevOps look)

   Structure and behaviour live in js/table-layout.js; this is purely
   presentation. Rows are deliberately all the same white - no zebra
   striping - with the column dividers doing the separating instead.
================================================================== */

:root {
    --grid-line: #edebe9;        /* cell dividers */
    --grid-line-strong: #e1dfdd; /* outer frame, header underline */
    --grid-accent: #0078d4;      /* DevOps blue */
    --grid-accent-soft: rgba(0, 120, 212, 0.14);  /* chip behind an active icon */
    --grid-hover: #f3f2f1;
    --grid-text: #323130;
    --grid-muted: #605e5c;

    /* Typography of the dashboard grid. Nothing in the app set a font-family,
       so everything inherited Bootstrap's system stack by accident; naming it
       here makes "the same font" a guarantee rather than a coincidence. */
    --grid-font: var(--bs-body-font-family, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
    --grid-font-size: 0.8125rem;
}

/* Opt any block of UI into the grid's typography: the work item modal, the
   CRUD form, anywhere that should read as part of the same screen. Bootstrap
   sets its own size on controls and buttons, hence the explicit descendants. */
.devops-text,
.devops-text .form-control,
.devops-text .form-select,
.devops-text .form-label,
.devops-text .form-check-label,
.devops-text .btn,
.devops-text .nav-link,
.devops-text table,
.devops-text .card-body,
.devops-text small {
    font-family: var(--grid-font);
    font-size: var(--grid-font-size);
}

/* Headings stay a step above the body text, but scaled down to sit with it. */
.devops-text h1, .devops-text h2 { font-size: 1.25rem; }
.devops-text h3, .devops-text h4, .devops-text h5 { font-size: 1rem; }
.devops-text h6 { font-size: 0.875rem; }

.devops-text h1, .devops-text h2, .devops-text h3,
.devops-text h4, .devops-text h5, .devops-text h6 {
    font-family: var(--grid-font);
    color: var(--grid-text);
}

.devops-grid-scroll {
    max-height: calc(100vh - 230px);
    overflow: auto;
    background: #fff;
    border: 1px solid var(--grid-line-strong);
    border-radius: 2px;
}

.devops-modal .devops-grid-scroll {
    max-height: 260px;
}

.devops-grid {
    width: 100%;
    margin-bottom: 0;
    background: #fff;
    color: var(--grid-text);
    font-family: var(--grid-font);
    font-size: var(--grid-font-size);
    /* "separate" rather than "collapse" so the sticky header keeps its
       borders while the body scrolls underneath it. */
    border-collapse: separate;
    border-spacing: 0;
}

/* ---- header ---- */

.devops-grid thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: #fff;
    color: var(--grid-muted);
    font-weight: 600;
    text-align: left;
    padding: 0.45rem 0.5rem;
    border-bottom: 1px solid var(--grid-line-strong);
    border-right: 1px solid var(--grid-line);
}

.devops-grid thead th:last-child,
.devops-grid thead th.col-last {
    border-right: 0;
}

.devops-grid.table-layout thead th:hover {
    background: var(--grid-hover);
}

/* ---- body: every row the same white ---- */

.devops-grid tbody td {
    background: #fff;
    padding: 0.4rem 0.5rem;
    border-bottom: 1px solid var(--grid-line);
    border-right: 1px solid var(--grid-line);
    vertical-align: middle;
}

.devops-grid tbody td:last-child,
.devops-grid tbody td.col-last {
    border-right: 0;
}

.devops-grid tbody tr:last-child td {
    border-bottom: 0;
}

.devops-grid tbody tr:hover td {
    background: var(--grid-hover);
}

/* ---- the sorted column, called out three ways: accent underline,
        a solid caret, and the header text going full strength ---- */

.devops-grid thead th[aria-sort] {
    color: var(--grid-text);
    box-shadow: inset 0 -2px 0 0 var(--grid-accent);
}

.devops-grid thead th {
    transition: box-shadow 80ms linear;
}

/* Both header icons are sized and placed the same way, so they read as a pair
   of controls rather than two bits of punctuation - big enough to say what they
   are, and to be worth aiming at. */
.devops-grid thead th .sort-indicator,
.table-layout thead th .col-filter {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    width: 1.2rem;
    height: 1.2rem;
    align-items: center;
    justify-content: center;
    border-radius: 3px;
    opacity: 1;                  /* always on */
    color: var(--grid-muted);    /* grey until this column is the active one */
}

.devops-grid thead th .sort-indicator {
    right: 0.35rem;
    font-size: 0.85em;
    line-height: 1;
}

/* A neutral up-down glyph while unsorted: an always-visible solid triangle
   would read as "sorted ascending" on every column. */
.devops-grid thead th .sort-indicator::after {
    content: "\2195";
}

/* When a column IS sorted or filtered, colour alone is a thin signal at this
   size, so the icon also sits on a tinted chip: it reads from across the row
   rather than only when you go looking for it. */
.devops-grid thead th[aria-sort] .sort-indicator,
.table-layout thead th.is-filtered .col-filter {
    color: var(--grid-accent);
    background: var(--grid-accent-soft);
}

.devops-grid thead th[aria-sort="ascending"] .sort-indicator::after {
    content: "\25B2";
}

.devops-grid thead th[aria-sort="descending"] .sort-indicator::after {
    content: "\25BC";
}

/* ---- shared column mechanics (sort / drag / resize) ---- */

.table-layout {
    table-layout: fixed;
}

.table-layout thead th {
    position: relative;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right: 1.6rem; /* room for the caret and the resize grip */
}

.devops-grid.table-layout thead th {
    position: sticky;
    top: 0;
}

.table-layout tbody td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.table-layout thead th .col-resizer {
    position: absolute;
    top: 0;
    right: -1px;
    width: 7px;
    height: 100%;
    cursor: col-resize;
    z-index: 1;
}

.table-layout thead th .col-resizer:hover,
body.is-col-resizing .table-layout thead th .col-resizer {
    background: var(--grid-accent);
    opacity: 0.6;
}

.table-layout thead th[data-nosort] {
    cursor: default;
}

.table-layout thead th.dragging {
    opacity: 0.4;
}

.table-layout thead th.drag-over-left {
    box-shadow: inset 2px 0 0 0 var(--grid-accent);
}

.table-layout thead th.drag-over-right {
    box-shadow: inset -2px 0 0 0 var(--grid-accent);
}

body.is-col-resizing {
    cursor: col-resize;
    user-select: none;
}

/* =================================================================
   Work-item specifics for the dashboard grid
================================================================== */

.devops-workitems tbody td[data-key="title"] {
    color: var(--grid-accent);
}

.devops-workitems tbody tr:hover td[data-key="title"] {
    text-decoration: underline;
}

.devops-workitems tbody td[data-key="id"] {
    color: var(--grid-muted);
}

/* Effort reads as a quantity, so the figures line up on the right, in both
   views. The heading stays left: every header carries a funnel and a sort
   caret pinned to its right edge, and a right-aligned "Original estimate"
   would run straight under them.

   Not in card mode either - there the cell is a "Label  value" row, and the
   value belongs beside its label rather than pushed to the far edge. */
.devops-workitems:not(.is-compact) tbody td[data-key="originalEstimate"],
.devops-workitems:not(.is-compact) tbody td[data-key="remainingWork"] {
    text-align: right;
}

/* ---- work item type icon ----

   A glyph in front of the type name, the way the State column carries its dot.
   The shape and the colour both come off data-wi-type and the span holds no
   text, so the cell still reads as the plain type name: that is what a screen
   reader announces, what the column sorts and filters on, and what lands in the
   Excel export. The silhouettes are our own; the colours are the ones Azure
   DevOps gives each type, so the column reads at a glance to anyone who works
   in the board. */
.wi-type-icon {
    display: inline-block;
    width: 0.85rem;
    height: 0.85rem;
    margin-right: 0.4rem;
    vertical-align: -2px;
    background: var(--wi-type-colour, #b2b2b2);
}

/* A row with no type - a parent the query did not return - shows nothing, and
   gives back the space as well. */
[data-wi-type=""] .wi-type-icon {
    display: none;
}

[data-wi-type="Bug"] {
    --wi-type-colour: #cc293d;
}

[data-wi-type="Bug"] .wi-type-icon {
    border-radius: 50%;
}

[data-wi-type="Task"] {
    --wi-type-colour: #f2cb1d;
}

[data-wi-type="Task"] .wi-type-icon {
    border-radius: 2px;
}

/* The backlog item, whatever a given process template calls it. */
[data-wi-type="User Story"],
[data-wi-type="Product Backlog Item"],
[data-wi-type="Requirement"],
[data-wi-type="Issue"] {
    --wi-type-colour: #009ccc;
}

[data-wi-type="User Story"] .wi-type-icon,
[data-wi-type="Product Backlog Item"] .wi-type-icon,
[data-wi-type="Requirement"] .wi-type-icon,
[data-wi-type="Issue"] .wi-type-icon {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 72%, 0 100%);   /* bookmark */
}

[data-wi-type="Epic"] {
    --wi-type-colour: #ff7b00;
}

[data-wi-type="Epic"] .wi-type-icon {
    clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);          /* diamond */
}

[data-wi-type="Feature"] {
    --wi-type-colour: #773b93;
}

[data-wi-type="Feature"] .wi-type-icon {
    clip-path: polygon(50% 0, 100% 38%, 82% 100%, 18% 100%, 0 38%); /* pentagon */
}

[data-wi-type="Test Case"],
[data-wi-type="Test Plan"],
[data-wi-type="Test Suite"] {
    --wi-type-colour: #004b50;
}

[data-wi-type="Test Case"] .wi-type-icon,
[data-wi-type="Test Plan"] .wi-type-icon,
[data-wi-type="Test Suite"] .wi-type-icon {
    clip-path: polygon(50% 0, 100% 100%, 0 100%);                   /* triangle */
}

/* State dot, as in the DevOps query grid. Pseudo-element content is not
   part of innerText, so sorting and filtering still see the plain state. */
/* Shared by the grid cell and the tree row. */
[data-state] {
    --state-dot: #b2b2b2;
}

.devops-workitems tbody td[data-key="state"]::before {
    content: "";
    display: inline-block;
    width: 0.625rem;
    height: 0.625rem;
    margin-right: 0.4rem;
    border-radius: 50%;
    vertical-align: -1px;
    background: var(--state-dot);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}

.devops-workitems tbody td[data-state=""]::before {
    display: none;
}

[data-state="Active"],
[data-state="Committed"],
[data-state="Approved"] {
    --state-dot: #007acc;
}

[data-state="Resolved"] {
    --state-dot: #ff9d00;
}

[data-state="Closed"],
[data-state="Done"] {
    --state-dot: #339933;
}

[data-state="Removed"] {
    --state-dot: #6c6c6c;
}

/* =================================================================
   Phones: the grid becomes a list of cards

   Six columns cannot be read on a 375px screen, and sideways-scrolling
   to find a work item is worse than no table at all. Below the md
   breakpoint each row becomes a card of "Label  value" lines. The label
   is a ::before on data-label (stamped by table-layout.js) rather than a
   real element, deliberately: pseudo-element text stays out of
   textContent, so sorting, the filters and the Excel export are
   unaffected.
================================================================== */

/* Sort control: only useful once the clickable header is hidden. */
.grid-sort {
    display: none;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.grid-sort-label {
    color: var(--grid-muted);
}

.grid-sort-field {
    flex: 1 1 auto;
    min-height: 44px;  /* px, not rem: the root font is 14px below 768px */
    padding: 0 0.5rem;
    border: 1px solid #ced4da;
    border-radius: 4px;
    background: #fff;
    font-size: 16px;   /* under 16px iOS Safari zooms the page on focus */
}

.grid-sort-dir {
    flex: 0 0 auto;
    width: 44px;
    min-height: 44px;
    border: 1px solid #ced4da;
    border-radius: 4px;
    background: #fff;
    color: var(--grid-accent);
    font-size: 16px;
}

@media (max-width: 767.98px) {

    .grid-sort {
        display: flex;
    }

    /* The frame and the internal scroller belong to the table layout. */
    .devops-grid-scroll {
        max-height: none;
        overflow: visible;
        border: 0;
        background: transparent;
    }

    .devops-grid,
    .devops-grid tbody {
        display: block;
        width: auto;
    }

    .devops-grid colgroup,
    .devops-grid thead {
        display: none;
    }

    .devops-grid tbody tr {
        display: flex;
        flex-direction: column;
        background: #fff;
        border: 1px solid var(--grid-line-strong);
        border-radius: 4px;
        margin-bottom: 0.5rem;
        padding: 0.6rem 0.75rem;
    }

    .devops-grid tbody tr:last-child {
        margin-bottom: 0;
    }

    .devops-grid tbody tr:active {
        background: var(--grid-hover);
    }

    .devops-grid tbody td {
        display: flex;
        gap: 0.5rem;
        width: auto;
        padding: 0.2rem 0;
        border: 0;
        font-size: 0.875rem;
        /* Values wrap on a card instead of being clipped to an ellipsis. */
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }

    .devops-grid tbody td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 5.5rem;
        color: var(--grid-muted);
    }

    /* A cell with no value would otherwise show a lonely label. */
    .devops-grid tbody td:empty,
    .devops-grid tbody td.is-blank {
        display: none;
    }

    /* Nothing to reset when the columns are gone. */
    [data-table-reset] {
        display: none;
    }

    /* Card heading: the title leads, unlabelled and at full width. */
    .devops-workitems tbody td[data-key="title"] {
        order: -1;
        margin-bottom: 0.25rem;
        font-size: 1rem;
        line-height: 1.3;
    }

    .devops-workitems tbody td[data-key="title"]::before {
        display: none;
    }

    /* The state dot moves to ::after so ::before can carry the label;
       flex order puts it back between the label and the text. */
    .devops-workitems tbody td[data-key="state"]::before {
        content: attr(data-label);
        order: -2;
        width: auto;
        height: auto;
        margin: 0;
        border-radius: 0;
        background: none;
        box-shadow: none;
        flex: 0 0 5.5rem;
        color: var(--grid-muted);
    }

    .devops-workitems tbody td[data-key="state"]::after {
        content: "";
        order: -1;
        flex: 0 0 auto;
        align-self: center;
        width: 0.625rem;
        height: 0.625rem;
        border-radius: 50%;
        background: var(--state-dot);
        box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
    }

    .devops-workitems tbody td[data-state=""]::after {
        display: none;
    }

    /* Toolbar: full-width controls, 16px so iOS does not zoom on focus. */
    .devops-text .form-control,
    .devops-text .form-select {
        min-height: 44px;
        font-size: 16px;
    }

    .btn {
        min-height: 40px;
    }
}

/* =================================================================
   Phones: the work item modal

   .devops-modal pins width:1100px and the content to 85vh, which on a
   375px screen means a modal wider than the viewport. Below the md
   breakpoint it fills the screen instead (the dialog also carries
   Bootstrap's modal-fullscreen-sm-down).
================================================================== */

@media (max-width: 767.98px) {

    .devops-modal {
        width: auto;
        max-width: 100%;
        margin: 0;
    }

    .devops-modal .modal-content {
        height: auto;
        min-height: 100vh;
        border: 0;
        border-radius: 0;
    }

    .devops-modal .modal-header,
    .devops-modal .modal-footer {
        padding: 0.75rem;
    }

    .devops-modal .modal-title {
        font-size: 1rem;
        line-height: 1.3;
    }

    .devops-modal .modal-body {
        margin: 0 !important;  /* the m-2 utility on .devops-body */
        padding: 0.5rem;
    }

    /* Three tabs do not fit across 375px: let them scroll sideways rather
       than wrap into a second row that pushes the content down. */
    .devops-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
    }

    .devops-tabs .nav-link {
        white-space: nowrap;
        padding: 0.5rem 0.65rem;
    }

    /* A fixed 200px box wastes a phone screen when short and traps text
       when long. */
    .devops-description {
        height: auto;
        max-height: 45vh;
    }

    /* More specific than the generic rule, so it needs repeating here. */
    .devops-modal .devops-grid-scroll {
        max-height: none;
    }

    /* The attachment Download button is the only thing in its cell; give it
       the full card width rather than a stranded right-aligned column. */
    .devops-grid tbody td.attachment-actions {
        margin-top: 0.4rem;
    }

    .devops-grid tbody td.attachment-actions .btn {
        width: 100%;
    }

    .devops-modal .drop-zone {
        padding: 18px;
    }

    /* The comment box and its button, comfortably sized for thumbs. */
    #newCommentText {
        font-size: 16px;
    }
}

/* =================================================================
   Navigation

   The navbar carries no navbar-expand-* class, so the menu is always
   behind the burger. On a phone it drops in as a full-width list, which
   is the familiar behaviour; from md up a full-width list under a
   right-hand button looks stranded, so it becomes a panel anchored
   under the button instead.
================================================================== */

.navbar > .container-fluid {
    position: relative;
}

@media (min-width: 768px) {
    .navbar .navbar-collapse {
        position: absolute;
        top: 100%;
        left: 0;      /* anchored under the burger, which now leads the bar */
        z-index: 1030;
        flex-basis: auto;
        min-width: 240px;
        padding: 0.25rem 0.5rem;
        background: #fff;
        border: 1px solid var(--grid-line-strong);
        border-top: 0;
        border-radius: 0 0 4px 4px;
        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
    }
}

/* Nothing fills this in yet; without this it would add a stray flex gap
   between the brand and the Logout button. */
#currentUser:empty {
    display: none;
}

.navbar .navbar-nav .nav-link {
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
}

/* Sticky: the bar follows the page down. bg-white on the nav is what keeps
   content from showing through, and the shadow separates it from whatever
   scrolls underneath. */
header.sticky-top {
    background: #fff;  /* nothing shows through the pinned bar */
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}

/* A long project list must not run off a short screen once the bar is
   pinned - let the open menu scroll instead of the page. */
.navbar .navbar-collapse {
    max-height: calc(100vh - 4rem);
    overflow-y: auto;
}

/* =================================================================
   Column picker (see js/table-layout.js)
================================================================== */

/* Both the cells AND the <col> must go. Hiding only the cells leaves the
   column in the table's column set, so under table-layout:fixed every cell
   after it inherits the wrong <col>'s width. Dropping the <col> too keeps the
   column-to-cell mapping 1:1; freezeWidths then sets the table's width to the
   sum of the visible columns so they land at their exact sizes. */
.table-layout th.col-hidden,
.table-layout td.col-hidden,
.table-layout col.col-hidden {
    display: none;
}

.grid-columns {
    position: relative;
    display: inline-block;
}

.grid-columns-panel {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 1025;
    min-width: 12rem;
    padding: 0.35rem 0;
    text-align: left;
    background: #fff;
    border: 1px solid var(--grid-line-strong);
    border-radius: 4px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}

.grid-columns-panel[hidden] {
    display: none;
}

.grid-columns-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.75rem;
    margin: 0;
    white-space: nowrap;
    cursor: pointer;
}

.grid-columns-item:hover {
    background: var(--grid-hover);
}

.grid-columns-item input {
    cursor: pointer;
}

@media (max-width: 767.98px) {
    /* Choosing which fields appear on a card is useful on a phone, so unlike
       "Reset columns" this stays. Left-anchored to stay on screen. */
    .grid-columns-panel {
        right: auto;
        left: 0;
        min-width: 14rem;
    }

    .grid-columns-item {
        padding: 0.6rem 0.75rem;  /* touch target */
    }
}

/* =================================================================
   Column filters (see js/table-layout.js)
================================================================== */

.table-layout thead th {
    padding-right: 3rem;  /* caret + funnel + resize grip, all a size bigger */
}

.table-layout thead th .col-filter {
    right: 1.65rem;
    cursor: pointer;
}

.table-layout thead th .col-filter svg {
    width: 13px;
    height: 13px;
    fill: currentColor;
}

/* Darken only the inactive icons on hover; an active one keeps its colour and
   its chip. The pointer also lifts the icon under it onto a chip of its own,
   so it reads as something to click rather than a mark on the heading. */
.table-layout thead th:hover .sort-indicator,
.table-layout thead th:hover .col-filter {
    color: var(--grid-text);
}

.table-layout thead th .sort-indicator:hover,
.table-layout thead th .col-filter:hover {
    background: var(--grid-line-strong);
}

.table-layout thead th:hover[aria-sort] .sort-indicator,
.table-layout thead th:hover.is-filtered .col-filter {
    color: var(--grid-accent);
    background: var(--grid-accent-soft);
}

.table-layout thead th.is-filtered {
    color: var(--grid-text);
}

.col-filter-panel {
    position: fixed;
    z-index: 1040;
    width: 15rem;
    padding: 0.5rem;
    background: #fff;
    border: 1px solid var(--grid-line-strong);
    border-radius: 4px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
    font-size: var(--grid-font-size);
    font-family: var(--grid-font);
}

.col-filter-search {
    width: 100%;
    min-height: 32px;
    padding: 0 0.4rem;
    margin-bottom: 0.4rem;
    border: 1px solid #ced4da;
    border-radius: 4px;
}

.col-filter-list {
    max-height: 14rem;
    overflow-y: auto;
    border-top: 1px solid var(--grid-line);
    border-bottom: 1px solid var(--grid-line);
}

.col-filter-item {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.2rem;
    margin: 0;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.col-filter-item:hover {
    background: var(--grid-hover);
}

.col-filter-item[hidden] {
    display: none;
}

/* (Unselect all) is the twin of (Select all): same row, same tick box, same
   weight - they are the two ends of one question. */
.col-filter-all,
.col-filter-none {
    font-weight: 600;
}

.col-filter-footer {
    padding-top: 0.5rem;
    text-align: right;
}

@media (max-width: 767.98px) {
    /* The header is hidden in card mode, so there is no funnel to reach; the
       toolbar search and the user dropdown are the filters there. */
    .col-filter-panel {
        width: calc(100vw - 2rem);
    }
}

/* =================================================================
   Tree view - the same grid as the table above, with a hierarchy in it.
   Everything structural (header, filters, widths, card mode) comes from
   .devops-grid / .table-layout; only the Title cell knows about depth.
================================================================== */

.grid-empty {
    padding: 1rem;
    background: #fff;
    color: var(--grid-muted);
}

/* The one place depth shows: the title indents, every other column stays in
   line so the grid still reads as columns. */
.wi-tree tbody td[data-key="title"] {
    padding-left: calc(0.5rem + var(--wi-depth, 0) * 1.1rem);
}

.wi-tree tbody .wi-tree-label {
    color: var(--grid-accent);
}

.wi-tree tbody tr:hover .wi-tree-label {
    text-decoration: underline;
}

.wi-tree-twisty {
    display: inline-block;
    width: 1rem;
    margin-right: 0.15rem;
    line-height: 1;
    text-align: center;
    color: var(--grid-muted);
    cursor: pointer;
}

.wi-tree-twisty[role]::before {
    content: "\25BE";          /* pointing down: children shown */
}

.wi-tree-row.is-collapsed .wi-tree-twisty[role]::before {
    content: "\25B8";          /* pointing right: children hidden */
}

.wi-tree-twisty:not([role]) {
    cursor: inherit;
}

/* A parent the query did not return: shown so the hierarchy reads, set in
   italic because it is not one of the results. */
.wi-tree-row.is-placeholder .wi-tree-label {
    color: var(--grid-text);
    font-style: italic;
}

/* A figure summed up from the branch below, rather than one the item carries
   itself. Bold italic, so a total is never read as somebody's estimate. */
.wi-tree tbody td.is-sum {
    font-weight: 600;
    font-style: italic;
}

/* Filtering keeps a parent whose own row did not match, so the children that
   did still read as a hierarchy - greyed, to say it is context, not a result. */
.wi-tree tbody tr.is-context td {
    opacity: 0.55;
}

@media (max-width: 767.98px) {
    /* Card mode, with the indent moved to the whole card: it is the only thing
       left saying where a work item sits in the hierarchy. */
    .wi-tree tbody tr {
        margin-left: calc(var(--wi-depth, 0) * 0.85rem);
    }

    /* Laid out as a line of text rather than a flex row, so the twisty and
       the child count sit with the title instead of at the card's far edge. */
    .wi-tree tbody td[data-key="title"] {
        display: block;
        padding-left: 0;
    }
}
