/* ============================================================
   Design system — components, scoped to `.hx`.

   Every class name is the one used in
   /home/service/org/frontend_template/styles.css, so markup can be lifted from
   the reference unchanged. Each selector is prefixed `.hx ` for two reasons:

   * `.card`, `.btn`, `.nav`, `.active` are also Bootstrap's, and Bootstrap
     stays loaded until the last page is migrated. `.hx .card` is 0,2,0 and wins
     over `.card` at 0,1,0 without a single !important.
   * A page opts in by wrapping its content in one `<div class="hx">`. A page
     that has not been migrated never gets the class and renders exactly as it
     did before.

   Specificity alone is not enough against static/css/theme.css, which carries
   66 !important declarations covering .btn, .card, .table td, table.dataTable
   and a:hover. That file is dropped per page via `{% block legacy_css %}{% endblock %}`;
   see django_common/templates/base.html.

   Sections marked NEW have no counterpart in the reference stylesheet, which
   contains no <table>, no <select>, and no pagination at all. They are written
   in its vocabulary.
   ============================================================ */

.hx {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}

.hx a { text-decoration: none; color: inherit; }
.hx button, .hx input, .hx select, .hx textarea { font-family: inherit; }

.hx .mono { font-family: var(--mono); font-feature-settings: "tnum" 1; }
.hx .muted { color: var(--ink-3); }
.hx .faint { color: var(--ink-4); }
.hx .tabnum { font-variant-numeric: tabular-nums; }

/* ============================================================
   Buttons / chips / pills
   ============================================================ */

.hx .btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 12px;
  border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--surface-2); color: var(--ink-2);
  font-size: 12.5px; font-weight: 500;
  cursor: pointer; white-space: nowrap; user-select: none;
}
.hx .btn:hover { background: var(--hover); color: var(--ink); border-color: var(--line); }
.hx .btn.sm { height: 27px; padding: 0 9px; font-size: 12px; }
.hx .btn.icon { width: 32px; padding: 0; justify-content: center; }
.hx .btn-accent {
  background: var(--acc); color: var(--acc-ink); border-color: var(--acc-dim); font-weight: 600;
}
.hx .btn-accent:hover { background: oklch(0.73 0.075 245); color: var(--acc-ink); }
.hx .btn-ghost { background: transparent; border-color: transparent; color: var(--ink-3); }
.hx .btn-ghost:hover { background: var(--hover); color: var(--ink); }

/* A chip is a filter toggle. Wrapping a hidden checkbox in a <label class="chip">
   makes a real multi-select filter with no JavaScript at all — the reference
   drives these from React state, which a form post does not need. */
.hx .chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 10px; border-radius: 20px;
  border: 1px solid var(--line-soft); background: var(--surface);
  color: var(--ink-3); font-size: 12px; cursor: pointer;
  user-select: none; white-space: nowrap;
}
.hx .chip:hover { color: var(--ink); border-color: var(--line); }
.hx .chip.active { background: var(--acc-soft); color: var(--acc); border-color: var(--acc-dim); }
.hx .chip input { position: absolute; opacity: 0; pointer-events: none; }
.hx .chip:has(input:checked) {
  background: var(--acc-soft); color: var(--acc); border-color: var(--acc-dim);
}
.hx .chip:focus-within { border-color: var(--acc); box-shadow: 0 0 0 2px var(--acc-soft); }
.hx .chip .c-count { font-family: var(--mono); font-size: 10.5px; opacity: 0.7; }

.hx .chip-set { display: flex; flex-wrap: wrap; gap: 6px; }

.hx .pill {
  display: inline-flex; align-items: center; gap: 5px;
  height: 19px; padding: 0 7px; border-radius: 4px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.02em;
  border: 1px solid var(--line-soft); color: var(--ink-3); background: var(--surface);
  white-space: nowrap; text-transform: uppercase;
}
.hx .pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.hx .pill.acc    { color: var(--acc);    border-color: oklch(0.68 0.075 245 / 0.3); background: var(--acc-soft); }
.hx .pill.amber  { color: var(--amber);  border-color: oklch(0.80 0.10 78 / 0.3);   background: oklch(0.80 0.10 78 / 0.12); }
.hx .pill.red    { color: var(--red);    border-color: oklch(0.66 0.125 25 / 0.3);  background: oklch(0.66 0.125 25 / 0.12); }
.hx .pill.blue   { color: var(--blue);   border-color: oklch(0.70 0.085 244 / 0.3); background: oklch(0.70 0.085 244 / 0.12); }
.hx .pill.violet { color: var(--violet); border-color: oklch(0.70 0.10 300 / 0.3);  background: oklch(0.70 0.10 300 / 0.12); }
.hx .pill.cyan   { color: var(--cyan);   border-color: oklch(0.78 0.08 200 / 0.3);  background: oklch(0.78 0.08 200 / 0.12); }

/* ============================================================
   Cards
   ============================================================ */

.hx .card {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: flex; flex-direction: column;
  min-width: 0;
}
.hx .card-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 15px; border-bottom: 1px solid var(--line-soft);
  flex: 0 0 auto; flex-wrap: wrap;
}
.hx .card-title {
  font-size: 13px; font-weight: 600; letter-spacing: -0.005em;
  display: flex; align-items: center; gap: 8px;
}
.hx .card-title .hx-ico { color: var(--ink-3); }
.hx .card-sub { color: var(--ink-4); font-size: 11.5px; font-family: var(--mono); }
.hx .card-head .right { margin-left: auto; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.hx .card-body { padding: 6px; min-width: 0; }
.hx .card-body.pad { padding: 15px; }
.hx .card-body.flush { padding: 0; }
.hx .card-foot {
  border-top: 1px solid var(--line-soft); padding: 9px 14px;
  display: flex; align-items: center; gap: 10px; color: var(--ink-3); font-size: 12px;
}
.hx .card-foot a { color: var(--ink-2); }
.hx .card-foot a:hover { color: var(--acc); }

.hx .section-title {
  display: flex; align-items: center; gap: 9px;
  font-size: 11px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-4);
  margin: 0 0 11px 2px;
}
.hx .section-title .line { flex: 1; height: 1px; background: var(--line-soft); }

/* ============================================================
   Layout grids
   ============================================================ */

.hx .grid { display: grid; gap: 16px; }
.hx .cols-main { grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr); }
.hx .cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.hx .cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.hx .stack { display: flex; flex-direction: column; gap: 16px; }
.hx .metric-charts { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
.hx .metric-values { margin-top: 14px; }
.hx .metric-values summary { cursor: pointer; color: var(--ink-3); margin-bottom: 8px; }

@media (max-width: 1080px) {
  .hx .cols-main { grid-template-columns: 1fr; }
  .hx .cols-4 { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================
   NEW — tables

   The reference has no <table> at all; it renders CSS-grid pseudo-rows with no
   header. A real table is what these pages are, and it is what DataTables needs
   to attach to, so this is written from the same tokens: 11px uppercase heads
   in --ink-4, hairline row separators, hover on the row, numerals in mono.
   ============================================================ */

.hx .data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}
.hx .data-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2);
  text-align: left; white-space: nowrap;
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-4);
  padding: 9px 11px;
  border-bottom: 1px solid var(--line);
}
.hx .data-table tbody td {
  padding: 9px 11px;
  border-top: 1px solid var(--line-soft);
  color: var(--ink-2);
  vertical-align: middle;
}
.hx .data-table tbody tr:hover td { background: var(--hover); }
.hx .data-table tbody tr:first-child td { border-top: none; }

/* `ruled` draws the grid in both directions. Hairlines only: the point is to
   let the eye follow a row across twenty columns, not to draw a spreadsheet. */
.hx .data-table.ruled thead th,
.hx .data-table.ruled tbody td { border: 1px solid var(--line-soft); }
.hx .data-table.ruled thead th { border-bottom-color: var(--line); }
.hx .data-table.ruled tbody tr:first-child td { border-top: 1px solid var(--line-soft); }
.hx .data-table.ruled thead th:first-child,
.hx .data-table.ruled tbody td:first-child { border-left: none; }
.hx .data-table.ruled thead th:last-child,
.hx .data-table.ruled tbody td:last-child { border-right: none; }

/* The per-column filter row table_autoinit.js appends to <thead>. */
.hx .data-table thead tr.dt-filter-row th {
  padding: 5px 7px; text-transform: none; letter-spacing: 0;
  position: static; background: var(--surface);
}
.hx .data-table thead tr.dt-filter-row select,
.hx .data-table thead tr.dt-filter-row input { width: 100%; min-width: 90px; }

/* Numeric columns line up only if the digits are tabular. */
.hx .data-table .num {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap;
}
.hx .data-table .nowrap { white-space: nowrap; }
.hx .data-table .strong { color: var(--ink); font-weight: 500; }
/* A cell holding an identifier: long, monospaced, and never worth wrapping. */
.hx .data-table .id-cell {
  font-family: var(--mono); font-size: 11px; color: var(--ink-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 22ch;
}
.hx .data-table .wrap-cell {
  max-width: 42ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* `fit` keeps a wide table inside its card: every cell wraps, long identifiers break. */
.hx .data-table.fit td { overflow-wrap: anywhere; }
.hx .data-table.fit .nowrap,
.hx .data-table.fit .wrap-cell,
.hx .data-table.fit .id-cell { white-space: normal; max-width: none; overflow: visible; }
/* A cell holding an argument rather than a value: it wraps, and each answer is
   indented under the line it answers. */
.hx .data-table .thread-cell { max-width: 46ch; white-space: normal; }
.hx .data-table .thread-reply {
  margin-top: 6px; padding-left: 10px; border-left: 2px solid var(--line);
  font-size: 12px;
}
.hx .data-table .thread-reply .faint { margin-left: 6px; }
.hx .data-table a { color: var(--ink); }
.hx .data-table a:hover { color: var(--acc); }

.hx .table-scroll { overflow-x: auto; }

/* A colour swatch, for AnnotationClass.color — which stores whatever the source
   called it ('red', '#ff8800'), so it is set inline and only framed here. */
.hx .swatch {
  display: inline-block; width: 12px; height: 12px;
  border-radius: 3px; border: 1px solid var(--line);
  vertical-align: -2px; margin-right: 6px;
}

.hx .empty { padding: 26px; text-align: center; color: var(--ink-4); font-size: 12.5px; }

/* A JSON column drawn by platform_ui|json_view: nested tables, sized to the field table. */
.hx .json-kv th { width: 1%; white-space: nowrap; }
.hx .json-table { width: auto; font-size: 12px; }
.hx .json-list { margin: 0; padding-left: 18px; }

/* Rendered Markdown of a free-text field, sized to the text around it. */
.hx .data-table .md-cell { max-width: 42ch; white-space: normal; overflow-wrap: anywhere; }
.hx .md { line-height: 1.5; }
.hx .md > :first-child { margin-top: 0; }
.hx .md > :last-child { margin-bottom: 0; }
.hx .md p, .hx .md ul, .hx .md ol, .hx .md pre, .hx .md table { margin: 0 0 6px; }
.hx .md ul, .hx .md ol { padding-left: 18px; }
.hx .md h1, .hx .md h2, .hx .md h3, .hx .md h4 { margin: 8px 0 4px; font-size: 1em; font-weight: 600; color: var(--ink); }
.hx .md code { font-family: var(--mono); font-size: 0.92em; color: var(--ink); }
.hx .md pre { padding: 6px 8px; border-radius: var(--radius-sm); background: var(--bg); overflow-x: auto; }
.hx .md blockquote { margin: 0 0 6px; padding-left: 10px; border-left: 2px solid var(--line); color: var(--ink-3); }

/* A message above the content (django_common/_alert.html): red error, amber warning. */
.hx .alert {
  display: block; padding: 9px 12px; margin-bottom: 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--ink-2); font-size: 12.5px; line-height: 1.5;
  white-space: pre-line; overflow-wrap: anywhere;
}
.hx .alert[hidden] { display: none; }
/* A live slot: three lines high, empty or not, so a message appearing shifts nothing. */
.hx .alert.reserved {
  box-sizing: border-box; height: calc(4.5em + 20px); overflow-y: auto;
}
.hx .alert.is-empty { visibility: hidden; }
.hx .alert strong { color: var(--ink); font-weight: 600; margin-right: 6px; }
.hx .alert.red {
  color: var(--red); border-color: oklch(0.66 0.125 25 / 0.35); background: oklch(0.66 0.125 25 / 0.12);
}
.hx .alert.amber {
  color: var(--amber); border-color: oklch(0.80 0.10 78 / 0.35); background: oklch(0.80 0.10 78 / 0.12);
}

/* ============================================================
   NEW — form controls

   The reference styles exactly one input (the topbar search) and no select at
   all. The recipe below is that input's, applied to bare controls: 32px tall,
   the page background rather than the card's, hairline border, accent focus
   ring.
   ============================================================ */

.hx input[type="text"],
.hx input[type="search"],
.hx input[type="password"],
.hx input[type="email"],
.hx input[type="url"],
.hx input[type="tel"],
.hx input[type="number"],
.hx input[type="date"],
.hx input[type="time"],
.hx input[type="datetime-local"],
.hx select,
.hx textarea {
  height: 32px; padding: 0 10px;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line-soft); border-radius: var(--radius);
  font-size: 12.5px; outline: none;
}
.hx textarea { height: auto; padding: 7px 10px; line-height: 1.45; }
/* Without it the browser draws the calendar icon and picker light on a dark page. */
.hx input[type="date"], .hx input[type="datetime-local"] { color-scheme: dark; }
.hx input:focus, .hx select:focus, .hx textarea:focus {
  border-color: var(--acc); box-shadow: 0 0 0 2px var(--acc-soft);
}
.hx input::placeholder, .hx textarea::placeholder { color: var(--ink-4); }
.hx select {
  appearance: none;
  padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 15px) 14px, calc(100% - 10px) 14px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.hx select.sm, .hx input.sm { height: 27px; font-size: 12px; }
.hx .field { display: flex; flex-direction: column; gap: 5px; }
.hx .field > label {
  font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-4);
}
.hx .filter-bar {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px;
  padding: 13px 15px; border-bottom: 1px solid var(--line-soft);
}

/* ============================================================
   NEW — pagination, composed from the reference's footer idiom
   ============================================================ */

.hx .pager { display: flex; align-items: center; gap: 8px; }
.hx .pager .pager-at {
  font-family: var(--mono); font-size: 11px; color: var(--ink-4);
}
.hx .pager .btn.is-disabled { opacity: 0.35; pointer-events: none; }

/* ============================================================
   Small pieces carried over verbatim
   ============================================================ */

.hx .bar { height: 5px; border-radius: 4px; background: var(--surface-3); overflow: hidden; position: relative; }
.hx .bar > i { display: block; height: 100%; border-radius: 4px; }

.hx .kpi { display: flex; flex-direction: column; gap: 5px; padding: 14px 15px; }
.hx .kpi .k-label { font-size: 11px; color: var(--ink-3); letter-spacing: 0.03em; display: flex; align-items: center; gap: 6px; }
.hx .kpi .k-val { font-family: var(--mono); font-size: 25px; font-weight: 500; letter-spacing: -0.02em; line-height: 1; }
.hx .kpi .k-val small { font-size: 13px; color: var(--ink-3); }

.hx .status-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; display: inline-block; }
.hx .divider { height: 1px; background: var(--line-soft); }

/* ============================================================
   NEW — DataTables chrome.

   DataTables ships a light stylesheet; the widgets it generates (search box,
   length menu, info line, pager, sort arrows) are restyled here so a table
   looks the same as the card it sits in. `dom` in table_autoinit.js puts the
   search and the length menu in .dt-top and the count and pager in .dt-bottom.
   ============================================================ */

.hx .dataTables_wrapper { color: var(--ink-3); font-size: 12px; }
.hx .dt-top {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; border-bottom: 1px solid var(--line-soft);
}
.hx .dt-top .dataTables_filter { margin-left: auto; }
.hx .dt-bottom {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 8px 12px; border-top: 1px solid var(--line-soft);
}
.hx .dataTables_filter input { min-width: 240px; }
.hx .dataTables_filter label,
.hx .dataTables_length label {
  display: flex; align-items: center; gap: 7px; margin: 0;
  font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-4);
}
/* DataTables pads its select by 4px, which puts the arrow over the value. */
.hx .dataTables_wrapper .dataTables_length select { padding: 0 28px 0 10px; }
.hx .dataTables_info { font-family: var(--mono); font-size: 11px; color: var(--ink-4); }
.hx .dataTables_paginate { margin-left: auto; display: flex; gap: 4px; }
.hx .dataTables_paginate .paginate_button {
  padding: 3px 9px;
  border: 1px solid var(--line-soft); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink-2) !important; cursor: pointer;
}
.hx .dataTables_paginate .paginate_button:hover { background: var(--hover); }
.hx .dataTables_paginate .paginate_button.current { background: var(--acc-soft); color: var(--acc) !important; border-color: var(--acc-dim); }
.hx .dataTables_paginate .paginate_button.disabled { opacity: 0.35; pointer-events: none; }
.hx table.dataTable thead th.sorting,
.hx table.dataTable thead th.sorting_asc,
.hx table.dataTable thead th.sorting_desc { cursor: pointer; }
.hx table.dataTable thead .sorting:after,
.hx table.dataTable thead .sorting_asc:after,
.hx table.dataTable thead .sorting_desc:after { opacity: 0.4; }
.hx .dataTables_empty { padding: 26px !important; text-align: center; color: var(--ink-4); }

/* ============================================================
   NEW — Tom Select, the searchable multi-select.

   Its stylesheet is written for a light page; these rules put it on the same
   tokens as a bare <input>, so a filter control does not announce that it is a
   third-party widget.
   ============================================================ */

.hx .ts-wrapper { min-width: 220px; }
.hx .ts-control {
  background: var(--bg) !important; color: var(--ink);
  border: 1px solid var(--line-soft) !important; border-radius: var(--radius);
  min-height: 32px; padding: 3px 8px !important; font-size: 12.5px;
  box-shadow: none !important;
}
.hx .ts-wrapper.focus .ts-control { border-color: var(--acc) !important; box-shadow: 0 0 0 2px var(--acc-soft) !important; }
.hx .ts-control input { color: var(--ink) !important; }
.hx .ts-control > .item {
  background: var(--acc-soft); color: var(--acc);
  border: 1px solid var(--acc-dim); border-radius: 20px;
  padding: 0 7px; margin: 2px 4px 2px 0; font-size: 11.5px; line-height: 19px;
}
.hx .ts-control > .item .remove { border-left-color: var(--acc-dim); padding: 0 5px; }
.hx .ts-dropdown {
  background: var(--surface-2); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: none; margin-top: 3px; font-size: 12.5px;
}
.hx .ts-dropdown .option { padding: 6px 10px; }
.hx .ts-dropdown .active { background: var(--hover); color: var(--ink); }
/* The vendored stylesheet sets `scroll-behavior: smooth` here, and the widget
   scrolls to the highlighted option on every rebuild — so each keystroke played
   an animation the reader had to wait out. The list jumps instead. */
.hx .ts-dropdown-content { scroll-behavior: auto; }
.hx .ts-dropdown .no-results { padding: 8px 10px; color: var(--ink-4); }

/* flatpickr: its dark theme re-tinted with the shell tokens. The calendar is appended to
   <body>, outside .hx, so these rules carry no .hx prefix. */
.flatpickr-calendar {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); font-family: var(--sans);
}
.flatpickr-calendar.arrowTop:before, .flatpickr-calendar.arrowTop:after { border-bottom-color: var(--line); }
.flatpickr-calendar.arrowBottom:before, .flatpickr-calendar.arrowBottom:after { border-top-color: var(--line); }
.flatpickr-months .flatpickr-month, .flatpickr-weekdays, span.flatpickr-weekday,
.flatpickr-current-month .flatpickr-monthDropdown-months { background: var(--surface-2); color: var(--ink); fill: var(--ink); }
.flatpickr-months .flatpickr-prev-month, .flatpickr-months .flatpickr-next-month { color: var(--ink-3); fill: var(--ink-3); }
span.flatpickr-weekday { color: var(--ink-4); }
.flatpickr-day { color: var(--ink-2); }
.flatpickr-day:hover, .flatpickr-day:focus { background: var(--hover); border-color: var(--hover); }
.flatpickr-day.today { border-color: var(--acc-dim); }
.flatpickr-day.selected, .flatpickr-day.selected:hover { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); }
.flatpickr-day.prevMonthDay, .flatpickr-day.nextMonthDay { color: var(--ink-4); }
.flatpickr-time { border-top: 1px solid var(--line-soft) !important; }
.flatpickr-time input, .flatpickr-time .flatpickr-time-separator { color: var(--ink); }
.flatpickr-time input:hover, .flatpickr-time input:focus { background: var(--hover); }

/* ============================================================
   NEW — thumbnails and the image grid
   ============================================================ */

.hx .thumb {
  display: block; border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft); background: var(--surface-2);
  object-fit: cover;
}
.hx .thumb-sm { width: 96px; height: 54px; }
/* The whole width of the block it stands in, at the frame's own proportions. */
.hx .thumb-fill { width: 100%; height: auto; object-fit: contain; }
.hx .thumb-missing.thumb-fill { aspect-ratio: 16 / 9; }
.hx .video-player { display: block; width: 100%; max-height: 70vh; background: #000; border-radius: var(--radius); }
/* A frame saying the row exists but its file does not — the registry can be
   empty while the rows are real, and a broken <img> would read as a bug. */
.hx .thumb-missing {
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  color: var(--ink-4); font-family: var(--mono); font-size: 9.5px;
  letter-spacing: 0.04em; background: var(--surface-2);
}

/* Image search: compact filters, then rows of photos each as wide as its aspect ratio
   gives it at --row-h, which the zoom slider sets; the last row keeps that height. */
/* Image search: the filters in a column on the left, the photos in all the width left.
   Every control is one height and one width, so the column reads as one ruled list. */
.hx .img-search { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 12px; align-items: start; }
.hx .img-filters {
  --ctl-h: 34px; position: sticky; top: 0; margin: 0; padding: 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.hx .img-filters-head { display: flex; align-items: center; justify-content: space-between; }
.hx .img-filters .img-filters-head .btn { width: auto; height: 26px; min-height: 26px; }
.hx .img-filter { display: flex; flex-direction: column; gap: 4px; min-width: 0; margin: 0; }
.hx .img-filter > span, .hx .img-filter-label {
  font-size: 10.5px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-4);
}
.hx .img-filter-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.hx .img-filters input[type="text"], .hx .img-filters input.flatpickr-input,
.hx .img-filters input[type="datetime-local"], .hx .img-filters select:not([multiple]),
.hx .img-filters .btn {
  box-sizing: border-box; width: 100%; height: var(--ctl-h); min-height: var(--ctl-h); margin: 0;
}
.hx .img-filters .btn { display: inline-flex; align-items: center; justify-content: center; }
.hx .img-filters-saved {
  display: flex; flex-direction: column; gap: 6px; padding-top: 10px; border-top: 1px solid var(--line-soft);
}
/* Each chosen value is a row of its own; the control grows downward as values are added. */
.hx .img-filters .ts-wrapper { min-width: 0; width: 100%; }
.hx .img-filters .ts-control {
  box-sizing: border-box; height: auto; min-height: var(--ctl-h); padding: 4px 6px;
  display: flex; flex-direction: column; align-items: stretch; flex-wrap: nowrap; gap: 4px;
}
.hx .img-filters .ts-control > .item {
  display: flex; align-items: center; justify-content: space-between; min-width: 0;
  margin: 0; height: 24px; line-height: 22px; padding: 0 2px 0 8px; font-size: 12px;
  overflow: hidden; white-space: nowrap;
}
.hx .img-filters .ts-control > .item > :first-child { overflow: hidden; text-overflow: ellipsis; }
.hx .img-filters .ts-control > input { flex: none; width: 100% !important; min-width: 0; height: 24px; margin: 0; }
.hx .img-save { position: relative; }
.hx .img-save-pop {
  position: absolute; z-index: 20; top: calc(100% + 6px); left: 0; right: 0;
  display: flex; flex-direction: column; gap: 8px; padding: 10px;
  background: var(--surface-3); border: 1px solid var(--line); border-radius: var(--radius);
}
.hx .img-save-pop[hidden] { display: none; }
.hx .img-save-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
@media (max-width: 900px) {
  .hx .img-search { grid-template-columns: 1fr; }
  .hx .img-filters { position: static; }
}
.hx .img-zoom { display: flex; align-items: center; gap: 6px; color: var(--ink-3); }
.hx .img-zoom input { width: 160px; accent-color: var(--acc); }
.hx .img-results { padding: 6px; --row-h: 180px; }
.hx .img-flow { display: flex; flex-wrap: wrap; gap: 4px; }
.hx .img-flow .img-tile {
  position: relative; display: block; overflow: hidden; border-radius: 3px;
  flex: var(--ar) 1 calc(var(--ar) * var(--row-h)); aspect-ratio: var(--ar);
  background: var(--surface-2);
}
.hx .img-flow .img-tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The registry holds no bytes for the row: the icon states that, where the hidden
   image left a blank tile that reads as a page still loading. */
.hx .img-flow .img-tile img.is-broken {
  background: var(--surface-2) url("../img/no_file.svg") center / 34% auto no-repeat;
  border: 1px dashed var(--line-soft); color: transparent;
}
.hx .img-flow .img-tile:hover { outline: 2px solid var(--acc); outline-offset: -2px; }
.hx .img-flow-end { flex: 1000000 1 0; }
.hx .img-flow .empty { flex: 1 1 100%; }
.hx .img-tile-cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 16px 8px 5px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.78)); color: #fff;
  font-family: var(--mono); font-size: 11px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; opacity: 0; transition: opacity 0.12s; pointer-events: none;
}
.hx .img-tile:hover .img-tile-cap, .hx .img-tile:focus-visible .img-tile-cap { opacity: 1; }
.hx [aria-busy="true"] { opacity: 0.55; }
.hx .grid-more { display: flex; justify-content: center; padding: 14px 0 2px; }
.hx .thumb.is-broken {
  background: var(--surface-2) url("../img/no_file.svg") center / 44% auto no-repeat;
  border-style: dashed; color: transparent;
}

/* ============================================================
   NEW — entity detail page
   ============================================================ */

.hx .kv { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.hx .kv th {
  text-align: left; vertical-align: top; white-space: nowrap;
  width: 1%; padding: 7px 14px 7px 15px;
  font-size: 10.5px; font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-4);
  border-top: 1px solid var(--line-soft);
}
.hx .kv td {
  padding: 7px 15px; color: var(--ink-2);
  border-top: 1px solid var(--line-soft); word-break: break-word;
}
.hx .kv tr:first-child th, .hx .kv tr:first-child td { border-top: none; }
.hx .kv pre {
  margin: 0; padding: 8px 10px; max-height: 260px; overflow: auto;
  background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: 11px; color: var(--ink-2); white-space: pre-wrap;
}

/* ============================================================
   NEW — <dialog>, which replaced the Bootstrap modal
   ============================================================ */

.hx dialog, dialog.hx-dialog {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  padding: 0; max-width: min(900px, 92vw); max-height: 84vh; overflow: auto;
  font-family: var(--sans); font-size: 12.5px;
}
dialog.hx-dialog::backdrop { background: oklch(0 0 0 / 0.55); }
dialog.hx-dialog .card-head { position: sticky; top: 0; background: var(--surface); z-index: 1; }
dialog.hx-dialog pre {
  margin: 0; padding: 15px; white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
}

/* ============================================================
   NEW — the chrome-less layout used by base_simple.html
   ============================================================ */

body.hx-plain { background: var(--bg); }
.hx-plain-main {
  min-height: 100vh; display: grid; place-items: center; padding: 40px 20px;
}
.hx-plain-main .card { width: min(380px, 100%); }

/* ============================================================
   NEW — the activity strip on statistics/annotation_time_spent/
   ============================================================ */

.hx .activity-row { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.hx .activity-date {
  width: 84px; flex: none; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3);
}
.hx .activity-strip {
  display: flex; flex: 1 1 auto; gap: 1px; height: 16px; overflow-x: auto;
}
.hx .activity-cell {
  flex: 1 0 2px; min-width: 2px; background: var(--line-soft); border-radius: 1px;
}
.hx .activity-colour-0 { background: #43a047; }
.hx .activity-colour-1 { background: #1e88e5; }
.hx .activity-colour-2 { background: #fb8c00; }
.hx .activity-colour-3 { background: #8e24aa; }
.hx .activity-colour-4 { background: #00acc1; }
.hx .activity-colour-5 { background: #e53935; }
.hx .activity-colour-6 { background: #7cb342; }
.hx .activity-colour-7 { background: #6d4c41; }

/* The photo viewer of lightbox.js: the image over the whole window, the page dimmed,
   the metadata of the photo on the left when the link names it, the controls top right. */
.hx img.thumb-fill[src], .hx a[data-lightbox] { cursor: zoom-in; }
.lightbox { position: fixed; inset: 0; z-index: 1000; display: flex; background: rgba(0, 0, 0, 0.88); }
.lightbox-meta {
  flex: 0 0 320px; overflow: auto; padding: 56px 14px 16px; background: var(--surface);
  border-right: 1px solid var(--line); cursor: default;
}
.lightbox-stage {
  position: relative; flex: 1; min-width: 0; overflow: hidden; display: flex;
  align-items: center; justify-content: center; padding: 56px 24px 24px;
}
.lightbox-stage img {
  max-width: 100%; max-height: 100%; object-fit: contain; transform-origin: 0 0;
  user-select: none; cursor: zoom-in;
}
.lightbox-video { max-width: 100%; max-height: 100%; background: #000; }
.lightbox-stage.is-zoomed img { cursor: grab; }
.lightbox-stage.is-zoomed:active img { cursor: grabbing; }
.lightbox-bar { position: absolute; top: 12px; right: 16px; display: flex; align-items: center; gap: 6px; }
.lightbox-level { min-width: 52px; text-align: center; font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
@media (max-width: 760px) { .lightbox { flex-direction: column; } .lightbox-meta { flex-basis: auto; max-height: 40vh; } }

/* A Tom Select chip in the colour of its annotation class (multiselect.js renderChip). */
.ts-wrapper.multi .ts-control > div.chip-colored {
  --chip-bg: color-mix(in oklab, var(--chip) 55%, var(--surface-2));
  background: var(--chip-bg);
  border: 1px solid var(--chip); color: oklch(from var(--chip-bg) clamp(0, (0.62 - l) * 1000, 1) 0 0);
}

/* annotations/_class_crops.html: square cells, each crop whole inside its cell. */
.hx .crop-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
.hx .crop-cell {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  background: var(--bg); border: 1px solid var(--crop-border); border-radius: 4px; overflow: hidden;
}
.hx .crop-cell img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* The filters of the visualisation pages: two searchable selects and the buttons, one row. */
.hx .viz-filters {
  --ctl-h: 34px; margin-bottom: 12px; padding: 10px 12px; display: grid; gap: 10px; align-items: end;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
}
.hx .viz-filters-actions { display: flex; gap: 8px; }
.hx .viz-filters .btn { height: var(--ctl-h); display: inline-flex; align-items: center; }
.hx .viz-filters .ts-control {
  box-sizing: border-box; height: var(--ctl-h); min-height: var(--ctl-h); padding: 0 8px;
  flex-wrap: nowrap; align-items: center; gap: 4px; overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.hx .viz-filters .ts-control > .item { flex: none; margin: 0; height: 24px; line-height: 22px; font-size: 12px; }

/* A video in a table row: its first frame, which opens the player in the viewer. */
.hx .video-thumb { position: relative; display: inline-block; line-height: 0; border-radius: 3px; overflow: hidden; }
.hx .video-thumb img { width: 128px; height: 72px; object-fit: cover; background: var(--surface-2); }
.hx .video-thumb::after {
  content: "▶"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 18px; line-height: 1; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); opacity: 0.85;
}
.hx .video-thumb:hover::after { opacity: 1; }

/* My tasks: "Take new task" at the right of the page head, its disabled reason beside it. */
.hx .take-next { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }

/* platform_ui.entity_card: a row shown by its picture, public_id[:8] under it. The box is
   4:3 at the card size; an absent picture leaves the box as its placeholder. */
.hx .entity-card {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  vertical-align: middle; font-family: var(--mono); font-size: 11px; line-height: 1.2;
}
.hx .entity-card--80 { --card-w: 80px; }
.hx .entity-card--160 { --card-w: 160px; }
.hx .entity-card--320 { --card-w: 320px; }
.hx .entity-card-pic {
  display: block; width: var(--card-w); height: calc(var(--card-w) * 3 / 4); overflow: hidden;
  border-radius: var(--radius-sm); border: 1px solid var(--line-soft); background: var(--surface-2);
}
.hx .entity-card-pic img { display: block; width: 100%; height: 100%; object-fit: contain; }
.hx a.entity-card:hover .entity-card-pic { border-color: var(--acc); }
.hx .entity-card-id { color: var(--ink-3); }
.hx a.entity-card .entity-card-id { color: inherit; }
.hx .entity-card-pic { position: relative; }
.hx .entity-card-badge {
  position: absolute; top: 3px; left: 3px; padding: 0 5px; border-radius: 3px;
  background: var(--acc); color: #fff; font-size: 10px; line-height: 15px; text-transform: uppercase;
}

/* ============================================================
   View switch (NEW) — two links naming the modes of one page
   ============================================================ */

.hx .view-switch { display: inline-flex; gap: 4px; margin-left: auto; }
.hx .view-switch .btn.is-active { background: var(--acc-soft); color: var(--acc); border-color: var(--acc-dim); }

/* ============================================================
   Preview loading state — static/js/thumb_loader.js
   ============================================================ */

/* A preview spins while its picture loads (.thumb-loading on its holder) and shows the
   no_file icon over "No image" when there is no picture for it (.thumb-none). */
@keyframes thumb-spin { to { transform: rotate(360deg); } }
:where(.thumb-loading, .thumb-none) { position: relative; }
.thumb-box { display: inline-block; vertical-align: middle; line-height: 0; }
.thumb-fill-box { display: block; }
.thumb-box > img.thumb-fill:not([src]) { aspect-ratio: 16 / 9; }
.thumb-loading::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 18px; height: 18px;
  margin: -9px 0 0 -9px; box-sizing: border-box; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--acc);
  animation: thumb-spin 0.8s linear infinite; pointer-events: none;
}
/* An img waiting for its src would show its alt text; a failed one is not shown. */
.thumb-loading > img:not([src]) { visibility: hidden; }
:is(.hx .img-flow .img-tile, .hx .entity-card-pic, .hx .thumb-missing, .thumb-box,
    .lightbox-stage, .iov-stage).thumb-none {
  background: var(--surface-2) url("../img/no_file.svg") center calc(50% - 7px) / auto min(34%, 96px) no-repeat;
}
:is(.hx .img-flow .img-tile, .hx .entity-card-pic, .hx .thumb-missing, .thumb-box,
    .lightbox-stage, .iov-stage).thumb-none > :is(img, canvas, video) { visibility: hidden; }
.thumb-none::after {
  content: "No image"; position: absolute; left: 0; right: 0; bottom: 3px;
  overflow: hidden; white-space: nowrap; text-align: center; pointer-events: none;
  color: var(--ink-4); font-family: var(--mono); font-size: 10px; line-height: 12px;
}
