@charset "UTF-8";

body {
  font-family: 'InterDisplay', sans-serif;
}

/* Navigation */

.navbar .nav-item {
  margin: 0em 0.4em;
  pointer: cursor;
}
.navbar .strike a.dropdown-item {
  text-decoration: line-through;
  opacity: 0.5;
}
.table-container .pagination {
  margin-top: 1em;
}
.table-container .pagination li {
  margin-right: 1em;
}

.d-contents { display: contents; }

.phone-number {
  white-space: nowrap;
}

/* Contact detail: has_mobile / has_laptop = No → show badge struck through */
.contact-device-badge--no {
  text-decoration: line-through;
  opacity: 0.65;
}

.dan-icon {
  font-size: 0.8em;
}

/* Column resize */

/* Every table shows its data in full: data cells wrap onto as many lines as
   they need instead of being ellipsized. This is the default for every table
   on the site — resizable or not, inside .table-wrap or a bare
   .table-container — and replaces the per-table opt-in lists that used to
   repeat these declarations for each list view. !important is needed because
   both styles.css's ".table-wrap th, .table-wrap td" rule (loaded for every
   table) and Bootstrap's ".text-nowrap" utility (applied per column in the
   various tables.py) set white-space: nowrap. Action/icon columns opt back
   into nowrap below — they are controls, not data. */
.table-container td,
.table-wrap td,
table[data-col-resize-init] > tbody > tr > td {
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: normal !important;
  word-break: break-word;
}

/* Header cells are not data: they keep the single-line, ellipsized rendering
   they have always had. A header is a column *name*, and under
   table-layout: fixed a column can end up narrower than its label — the
   contact list spreads twenty columns across the page — where wrapping
   broke the name into a ragged stack of word fragments down the header row.
   The list views whose headers genuinely need the room opt back in below. */
.table-container th,
.table-wrap th,
table[data-col-resize-init] > thead > tr:first-child > th {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Happenings, happening-registrations, contact-notes, user list, event list,
   project list & call list tables: their headers are long enough that
   ellipsizing them would hide the column name, so they wrap like the cells. */
#happenings-table-results table[data-col-resize-init] > thead > tr > th,
#registration-results table[data-col-resize-init] > thead > tr > th,
#contact-notes-table-results table[data-col-resize-init] > thead > tr > th,
#user-list-results table[data-col-resize-init] > thead > tr > th,
#event-list-results table[data-col-resize-init] > thead > tr > th,
#project-list-results table[data-col-resize-init] > thead > tr > th,
#callListTableWrap table[data-col-resize-init] > thead > tr > th,
#join-online-table-results table[data-col-resize-init] > thead > tr > th {
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: normal !important;
  word-break: break-word;
}

/* Wrappers stay overflow-x: clip rather than scroll: now that cells wrap,
   nothing overflows a table any more, so clip hides nothing — and unlike
   auto it does not turn the wrapper into a scroll container (which would
   trap the sticky thead). col-resize.js still switches a wrapper to auto in
   the one case that needs it, when a column really cannot fit. */
.table-container,
.table-wrap,
.stats-table-wrap {
  overflow-x: clip;
}

/* Contact list keeps optional horizontal scroll when columns are widened */
#contactTableWrap,
#contactTableWrap .table-container {
  overflow-x: auto;
}

/* The resize handle sits inside the header cell — keep it clear of the label. */
table[data-col-resize-init] > thead > tr:first-child > th {
  padding-right: 1.1rem !important;
}

/* Join online's table opts out of column resizing and is sized to its
   content, so its wrapper needs an explicit horizontal scroll. */
#join-online-table-results.event-contact-table-wrap {
  overflow-x: auto;
}

#join-online-table-results table {
  width: auto !important;
  min-width: 100%;
  table-layout: auto !important;
}

/* Membership stats: many columns, so size to the label and scroll sideways
   instead of ellipsizing headers and the date column. */
.table-wrap.membership-stats-table {
  overflow-x: auto !important;
}

.table-wrap.membership-stats-table table {
  width: max-content !important;
  min-width: 100%;
  table-layout: auto !important;
}

.table-wrap.membership-stats-table th,
.table-wrap.membership-stats-table td {
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: nowrap !important;
}

.table-wrap.membership-stats-table th:first-child,
.table-wrap.membership-stats-table td:first-child {
  min-width: 9rem;
}

/* Comparison heatmap: each month is a fixed column. The table scrolls
   sideways inside the normal page width. */
.table-wrap.membership-stats-compare {
  display: block;
  width: 100%;
  max-width: 100%;
  overflow-x: auto !important;
}

.table-wrap.membership-stats-compare table {
  width: max-content !important;
  max-width: none !important;
  table-layout: fixed !important;
}

.table-wrap.membership-stats-compare th,
.table-wrap.membership-stats-compare td {
  padding: 0.35rem 0.4rem !important;
  vertical-align: top;
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: normal !important;
  word-break: break-word;
}

.table-wrap.membership-stats-compare th:first-child,
.table-wrap.membership-stats-compare td:first-child {
  width: 12rem;
  min-width: 12rem;
  max-width: 12rem;
  /* Long row labels wrap inside the fixed 12rem label column instead of
     running over the month columns next to it */
  white-space: normal !important;
  word-break: break-word;
}

.table-wrap.membership-stats-compare th.membership-stats-date,
.table-wrap.membership-stats-compare td:not(:first-child) {
  width: 7.5rem;
  min-width: 7.5rem;
  max-width: 7.5rem;
}

/* ...except action columns: they hold controls, not data, so they stay on
   one line and never clip. Covers headers marked data-column="actions" and
   cells carrying an icon button (including Contact list's actions column,
   whose column name is "pk") — one rule for every table instead of the
   per-list repeat, since the wrapping default above is now global. */
.table-container thead th[data-column="actions"],
.table-container tbody td[data-column="actions"],
.table-container tbody td:has(.conversation-header__icon-btn),
.table-wrap thead th[data-column="actions"],
.table-wrap tbody td[data-column="actions"],
.table-wrap tbody td:has(.conversation-header__icon-btn),
table[data-col-resize-init] > thead > tr:first-child > th[data-column="actions"],
table[data-col-resize-init] > tbody > tr > td:has(.conversation-header__icon-btn) {
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: nowrap !important;
  word-break: normal;
  min-width: max-content;
}

/* Truncate mode: the opt-in opposite of the wrapping default, switched one
   table at a time by the chips js/table-mode-toggle.js puts above it. Rows
   go back to a single line and long values are ellipsized; the script puts
   the full text in a title attribute so a clipped value stays readable on
   hover. Declared after the per-table rules above on purpose: several of
   them (membership stats, the compare grid) carry the same class count, so
   order is what makes an explicit truncate choice win the tie.
   Both wrapper levels start scrolling sideways here: resizable tables are
   table-layout: fixed so they never exceed the wrapper, but the ones that
   opt out of resizing (data-no-col-resize) are sized by their content and
   would otherwise just be clipped, with no way to reach the rest of a row. */
.table-container.table-mode--truncate,
.table-wrap.table-mode--truncate,
.table-wrap.table-mode--truncate .table-container {
  overflow-x: auto;
}

.table-container.table-mode--truncate th,
.table-container.table-mode--truncate td,
.table-wrap.table-mode--truncate th,
.table-wrap.table-mode--truncate td {
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
  word-break: normal;
}

/* Wrap / truncate chips above each table (js/table-mode-toggle.js) */
.table-mode-toggle {
  display: flex;
  justify-content: flex-end;
  gap: 0.35rem;
  padding: 0.5rem 0.65rem;
  border-bottom: 1px solid var(--light-gray);
  background: var(--background-color);
}

.table-mode-toggle__btn {
  line-height: 1.25;
}

/* Outline list: its "Edit"/"Copy" action column holds text-label buttons,
   not icons -- col-resize.js's actionColumnNeedPx() only measures
   .conversation-header__icon-btn elements, so it floors this column at
   just 56px (the icon-only minimum), too narrow to fit both buttons on
   one line. min-width: max-content above targets the <th> and isn't
   reliably honoured for actual column sizing under table-layout: fixed --
   !important here is needed to win over the <col style="width: X%">
   col-resize.js sets inline on init/resize. Actions is this table's only
   column besides Name, so :last-child reliably targets it without a
   data-column selector (django_tables2 only puts data-column on <th>,
   not <td>/<col>). */
#outline-list-results table > colgroup > col:last-child {
  width: 9rem !important;
  min-width: 9rem !important;
}
#outline-list-results table > thead > tr > th:last-child,
#outline-list-results table > tbody > tr > td:last-child {
  white-space: nowrap !important;
  overflow: visible !important;
}

/* All contacts / My address book: denser rows, room for phone numbers,
   and sort arrows that stay attached to the header label. */
#contactTableWrap th,
#contactTableWrap td {
  padding: 0.4rem 0.65rem !important;
}

#contactTableWrap td.phone-number,
#contactTableWrap th.phone-number-col {
  min-width: 8.5rem;
}

/* Selection column: keep it in layout when hidden so col-resize % widths
   don't leave an empty gap (display:none orphans that column's share). */
#contactTableWrap .contact-selection-col {
  width: 0 !important;
  min-width: 0 !important;
  max-width: 0 !important;
  padding: 0 !important;
  border: none !important;
  overflow: hidden !important;
  line-height: 0;
  font-size: 0;
}

body.contact-list--selecting #contactTableWrap .contact-selection-col {
  width: 2.25rem !important;
  min-width: 2.25rem !important;
  max-width: 2.25rem !important;
  padding: 0.4rem 0.35rem !important;
  line-height: normal;
  font-size: inherit;
  overflow: visible !important;
  text-align: center;
}

/* Active selection mode: tint the icon only (no filled background).
   Reinforced in styles.css so it wins over later icon-btn rules. */
body.contact-list--selecting #contactSelectionModeToggle {
  background: transparent !important;
  box-shadow: none !important;
}

body.contact-list--selecting #contactSelectionModeToggle .conversation-bar__icon {
  color: var(--primary-color) !important;
}

.contact-selection-bar {
  border: 1px solid var(--light-gray);
  border-radius: 10px;
  padding: 0.65rem 0.9rem;
  background: var(--background-color);
}

/* Mass-action modals: select2 needs a real width inside the dialog. */
#contactMassAssignCampaignerModal .select2-container,
#contactMassAddAddressBookModal .select2-container {
  width: 100% !important;
}

/* Select2 results appended to <body> (see forms.js) must sit above
   Bootstrap's modal (1055) and backdrop (1050).
   Not a direct child: AttachBody.render() (select2.full.js) wraps the
   dropdown in a plain, classless <span> before appending it to <body>, and
   AttachBody.position() clones the widget's own .select2-container classes
   onto the dropdown itself (replacing "select2-dropdown"). So the real
   target here is a grandchild of <body>, carrying .select2-container, not
   .select2-dropdown — a "body > ..." child combinator never matches it,
   silently no-opping this whole rule and leaving the dropdown to render
   below the modal/backdrop's z-index instead of above it. */
body .select2-container--open {
  z-index: 2000 !important;
}

/* jQuery-QueryBuilder's own root class is "query-builder" (kebab-case) —
   the pre-existing ".queryBuilder ..." rule in mtool.css never actually
   matches anything (typo'd class name). Rule rows are flex (see styles.css),
   so Select2 can take 100% of the value/filter slot instead of a fixed
   min-width that wrapped the row. */
.query-builder .select2-container {
  width: 100% !important;
  min-width: 0;
  max-width: 100%;
}

/* Contact note forms: the "Specific user" (reminder_addr_user) field starts
   hidden (see reminder-addr-toggle.js) until "Reminder addressee" is set to
   "Specific user" — select2 measures 0 width when it initializes on a
   display:none ancestor, and the global ".select2-container { width: 100%
   !important; }" rule (100% of 0) leaves it visually broken/unusable even
   after the field becomes visible. Same fix as the query-builder rule above.
   Scoped by field name (adjacent-sibling off the actual <select>, which is
   where select2 always inserts its container), not by a wrapper class: this
   field is rendered from three different templates (the standalone edit
   page, the two "add note" modals, and the per-row "Edit note" modal, which
   carries none of the previous .contact-note/.edit-contact-note wrapper
   classes) and every one of them needs the same fix. The "Contact" field
   (ContactWidget) shares the .contact-note wrapper in the add-note modals
   but has no hidden-ancestor problem, so it must keep the global 100% width
   rule instead of collapsing to this auto/250px override too — hence scoping
   this to the reminder_addr_user select specifically rather than the wrapper. */
select[name="reminder_addr_user"] + .select2-container {
  width: auto !important;
  min-width: 250px;
}

.col-resize-handle {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  touch-action: none;
  pointer-events: auto;
  z-index: 60;
}

.col-resize-handle::after {
  content: "";
  position: absolute;
  top: 20%;
  right: 4px;
  width: 2px;
  height: 60%;
  border-radius: 1px;
  background: currentColor;
  opacity: 0.25;
  transition: opacity 0.15s ease;
}

.col-resize-handle:hover::after,
th.col-resize-active .col-resize-handle::after {
  opacity: 0.65;
}

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

body.col-resizing * {
  cursor: col-resize !important;
}

/* Forms */

/* Width belongs on the Select2 container. Forcing position/width on
   select.django-select2 overrides .select2-hidden-accessible and leaves the
   native <select> visible on top of (or instead of) the typeahead UI. */
.select2-container {
  width: 100% !important;
}

/* Bootstrap's is-invalid/is-valid styling (red/green border) is only ever
   applied to the actual <select>, which select2 hides and replaces with its
   own .select2-container UI, inserted as the next sibling (see the
   reminder_addr_user rule above for the same DOM fact). The error text
   itself already shows correctly -- Bootstrap's ".is-invalid ~
   .invalid-feedback { display: block; }" uses a general sibling combinator,
   which still matches across the select2-container in between -- but
   without this, a required select2 field left empty (e.g. "Projekt" on
   "Anlass erstellen") shows no red highlighting on the field itself at all,
   unlike a plain <input>. Reported live: green (valid) styling was visible
   on other fields, but nothing marked which select2 field was the actual
   problem. */
select.is-invalid + .select2-container--default .select2-selection {
  border-color: #dc3545;
}

select.is-valid + .select2-container--default .select2-selection {
  border-color: #198754;
}

/* Collapsible card headers (see js/collapsible-card-header.js) */

.card-header--collapsible {
  cursor: pointer;
  user-select: none;
}

.card-header--collapsible:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

.card-header--collapsible .card-header__toggle {
  pointer-events: none;
  line-height: 1;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

.card-header__toggle .card-header__caret {
  display: inline-block;
  margin-left: 0.35em;
  vertical-align: middle;
  transition: transform 0.2s ease;
}

.card-header--collapsible[aria-expanded="true"] .card-header__caret {
  transform: rotate(180deg);
}

/* Model browser: show full field/model comments without column-resize truncation */
.model-browser-table th,
.model-browser-table td {
  overflow: visible !important;
  text-overflow: clip !important;
  white-space: normal !important;
  word-break: break-word;
  vertical-align: top;
}

.model-browser-detail-table th:first-child,
.model-browser-detail-table td:first-child {
  width: 420px;
  max-width: 420px;
}

.table-wrap .model-browser-list-table {
  table-layout: auto !important;
}

.model-browser-list-table th:nth-child(3),
.model-browser-list-table td:nth-child(3) {
  width: 420px;
  max-width: 420px;
}

.model-browser-list-table .model-browser-audit-col {
  width: 1%;
  min-width: 8rem;
  max-width: 10rem;
  white-space: nowrap;
  text-align: center;
  vertical-align: middle;
}

.model-browser-list-table .model-browser-audit-col .badge {
  font-size: 0.7rem;
  font-weight: 500;
  white-space: nowrap;
  line-height: 1.2;
  text-align: center;
}
