/* --- Global ------------------------------------------------------------------------------------------------------- */
/*
body {
  background-color: #2b2b2b;
}
*/

.text-right {
  text-align: right;
}

abbr[title] {
  -webkit-text-decoration: underline dotted;
  text-decoration: underline dotted;
  cursor: help;
  -webkit-text-decoration-skip-ink: none;
  text-decoration-skip-ink: none;
}

/* --- Layout width ------------------------------------------------------------------------------------------------- */
/* Vuexy pins the app column at 1440px from 1400px up, so a large monitor spends more than half its
   width on empty margin. The column now follows the window instead - but it keeps a margin either
   side rather than running to the edges, and it stops at 2400px, past which a form row is no easier
   to read for being wider.

   Read the expression outwards: never narrower than the 1440px Vuexy already gives, otherwise the
   room the app has less a 7rem margin each side, and never wider than 2400px. The floor is what
   leaves the smaller screens alone: it only stops winning once 1440px plus the menu plus the margin
   fits, which with the menu open is 1924px - so every window up to and including 1920px renders
   exactly as it does today, and the growth starts only where there is surplus to share. With the
   menu collapsed that crossover moves down to 1734px, which is the point: collapsing is a request
   for more room, and this hands it to the content rather than to the margins.

   Spelled out in viewport units rather than `100%` because the content and the detached navbar sit
   in different containing blocks: the navbar is fixed, so a percentage there measures the window
   while the content's measures the menu-inset page, and the two would stop lining up. The window
   less the menu is the one width both can agree on.

   Scoped to `layout-compact` (set on <html> by TemplateHelper) so the centred card pages - login,
   selection, the error pages - keep the width they were drawn for: they render `wide` yet hardcode
   their own `container-xxl` to centre a card. No vendor rule keys off either marker. */
.layout-compact {
  --cajc-menu-width: var(--bs-menu-width, 16.25rem);
  --cajc-container-max: min(2400px, max(1440px, 100vw - var(--cajc-menu-width) - 14rem));
}

/* Collapsing the sidebar should hand the room to the content, not to the margins */
.layout-compact.layout-menu-collapsed {
  --cajc-menu-width: var(--bs-menu-collapsed-width, 4.375rem);
}

.layout-compact .container-xxl {
  max-width: var(--cajc-container-max);
}

/* The detached navbar carries a second cap of its own, independent of `.container-xxl`. Without
   this it stays at 1440px while the content grows, and the two visibly stop lining up. */
.layout-compact .layout-navbar.navbar-detached.container-xxl {
  max-inline-size: calc(var(--cajc-container-max) - 1.5rem * 2);
}

/* For a page that is one column of cards rather than a grid: tables and lists read fine at any
   width, a stack of form rows does not. 90rem is the measure such a page already has today, so this
   freezes it there and lets the wider column benefit the pages built to use it. It sits on the whole
   tabbed block, not just the panes, so the tabs keep the same edge as the content under them. */
.content-readable {
  max-width: 90rem;
}

/* --- Sidebar Menus ------------------------------------------------------------------------------------------------ */
/* Overwrite Tabler Icons src/assets/vendor/fonts/iconify-icons.css */
.tabler-user-euro {
  --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 7a4 4 0 1 0 8 0a4 4 0 0 0-8 0M6 21v-2a4 4 0 0 1 4-4h3m8 0c-.523-.502-1.172-.8-1.875-.8C17.398 14.2 16 15.991 16 18.2s1.398 4 3.125 4c.703 0 1.352-.298 1.874-.8M15 18.2h4'/%3E%3C/svg%3E");
}

/* --- Sidebar selectors -------------------------------------------------------------------------------------------- */
/* One visual language shared by the three sidebar dropdowns: the company row and the area select
   box at the top (brand_selector.html) and the settings menu at the bottom (vertical_menu.html).
   Everything is derived from the menu variables, so the light skin and the per-company primary
   colour keep working without a second set of rules. */
.layout-menu {
  /* Every surface is mixed from the menu's own text colour, which is what makes one set of rules
     work on the dark sidebar and on the light skin: `--bs-base-color` is too close to
     `--bs-menu-bg` under the semi-dark menu to tint anything visibly. */
  --selector-surface: color-mix(in sRGB, var(--bs-menu-color) 7%, var(--bs-menu-bg));
  --selector-surface-hover: color-mix(in sRGB, var(--bs-menu-color) 12%, var(--bs-menu-bg));
  /* The selected row has to stand out from a hovered one, which is already a step up */
  --selector-surface-active: color-mix(in sRGB, var(--bs-menu-color) 18%, var(--bs-menu-bg));
  --selector-border: color-mix(in sRGB, var(--bs-menu-color) 18%, transparent);
  --selector-border-hover: color-mix(in sRGB, var(--bs-menu-color) 34%, transparent);
  /* Matches the inline margin of the menu items below, so all three line up */
  --selector-inset: 0.75rem;
  --selector-radius: 0.625rem;
}

.area-selector + .menu-inner-shadow {
  position: relative;
  inset-block-start: auto;
  flex-shrink: 0;
  margin-block: -0.75rem calc(0.75rem - 3rem);
}

.app-brand-link,
.area-select {
  cursor: pointer;
}

/* Both switchers take the menu text colour. `--bs-heading-color` (Vuexy's default for
   .app-brand-text) resolves to a dark tone below the XL breakpoint, which is unreadable
   on the dark sidebar, so it is overridden here rather than relied upon. */
.menu .app-brand .app-brand-text.selector-title,
.area-select {
  color: var(--bs-menu-color);
}

/* The name owns the slack between the logo and the collapse toggle, and truncates rather than
   pushing the toggle off the edge. Vuexy spaces the text with a margin; the gap owns it now. */
.menu .app-brand .app-brand-link {
  min-width: 0;
  gap: 0.5rem;
}

.menu .app-brand .app-brand-text {
  margin-inline-start: 0;
}

.app-brand-logo .avatar {
  --bs-avatar-size: 1.75rem;
  --bs-avatar-initial: 0.875rem;
}

/* Long company names wrap to at most two lines, then ellipsize */
.app-brand-text.selector-title {
  display: -webkit-box;
  overflow: hidden;
  font-size: 0.9375rem;
  line-height: 1.3;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

/* The caret keeps its size and takes the label colour, not the theme colour. Bootstrap keeps
   `aria-expanded` on the trigger, so the flip follows the panel on its own. */
.selector-caret {
  flex: 0 0 auto;
  color: var(--bs-menu-color);
  font-size: 1rem;
  line-height: 1;
  opacity: 0.5;
  transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
}

.app-brand-link:hover .selector-caret,
.area-select:hover .selector-caret,
.menu-link:hover .selector-caret {
  opacity: 1;
}

[aria-expanded="true"] > .selector-caret {
  opacity: 1;
  transform: rotate(180deg);
}

/* --- Area select box --- */
.area-selector {
  padding-inline: var(--selector-inset);
  padding-block-end: 0.75rem;
}

/* The height is stated rather than left to the content: the collapsed rail drops the label, and
   without a fixed height the box would shrink and shift every menu icon below it. */
.area-select {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  min-height: 2.25rem;
  padding: 0.4375rem 0.625rem;
  border: 1px solid var(--selector-border);
  border-radius: var(--bs-border-radius);
  background-color: var(--selector-surface);
  font-size: 0.8125rem;
  font-weight: 500;
  transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out;
}

.area-select:hover,
.area-select[aria-expanded="true"] {
  border-color: var(--selector-border-hover);
  background-color: var(--selector-surface-hover);
}

.area-select-label {
  min-width: 0;
}

/* --- Dropdown panels --- */
/* Popper positions the panel, so a percentage width would resolve against the sidebar rather
   than the trigger: the width is stated outright and matches the menu items' own gutters. */
.selector-menu {
  min-width: 0;
  width: calc(var(--bs-menu-width) - var(--selector-inset) * 2);
  max-width: none;
  padding: 0.375rem;
  border: 1px solid var(--selector-border);
  border-radius: var(--selector-radius);
  background-color: var(--selector-surface);
}

/* Dropdown items are submit buttons inside per-option forms */
.selector-menu .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.4375rem 0.5rem;
  border: 0;
  border-radius: 0.5rem;
  background: none;
  color: var(--bs-menu-color);
  font-size: 0.875rem;
  text-align: start;
}

.selector-menu .dropdown-item .avatar {
  --bs-avatar-size: 1.5rem;
  --bs-avatar-initial: 0.75rem;
}

.selector-menu .dropdown-item:hover,
.selector-menu .dropdown-item:focus {
  background-color: var(--selector-surface-hover);
  color: var(--bs-menu-hover-color);
}

.selector-menu .dropdown-item.active {
  background-color: var(--selector-surface-active);
  color: var(--bs-menu-hover-color);
  font-weight: 500;
}

.selector-check {
  flex: 0 0 auto;
  color: var(--bs-primary);
}

.selector-menu .dropdown-divider {
  border-color: var(--selector-border);
}

/* --- Company search --- */
.selector-search-wrapper {
  padding: 0.125rem 0.125rem 0.375rem;
}

/* The field is inset into the panel, so it drops back to the sidebar's own colour */
.selector-search-group {
  border: 1px solid var(--selector-border);
  border-radius: 0.5rem;
  background-color: var(--bs-menu-bg);
}

.selector-search-group:focus-within {
  border-color: var(--bs-primary);
}

.selector-search-group .input-group-text,
.selector-search-group .selector-search {
  border: 0;
  background: none;
  color: var(--bs-menu-color);
  box-shadow: none;
}

.selector-search-group .input-group-text {
  padding-inline-end: 0;
  opacity: 0.5;
}

.selector-search::placeholder {
  color: var(--bs-menu-color);
  opacity: 0.5;
}

/* Option lists are capped at three and a half rows, so a half-cut row signals there are more.
   `--row-height` is one option's height and is overridden where the rows are taller. */
.selector-options,
.selection-options {
  --row-height: 2.5rem;

  overflow-y: auto;
  max-height: calc(var(--row-height) * 3.5);
}

/* The standalone picker (selection.html) keeps the roomier rows it was built with */
.selection-options {
  --row-height: 3.1875rem;
}

/* Area options carry a subtitle there, so those rows are taller still */
#moduleDropdown ~ .selection-options {
  --row-height: 3.5rem;
}

/* --- Swatches --- */
/* 19px on the standalone picker, a dot inside the sidebar */
.area-swatch {
  display: inline-block;
  flex: 0 0 auto;
  width: 19px;
  height: 19px;
}

.area-selector .area-swatch,
.layout-menu .selector-menu .area-swatch {
  width: 10px;
  height: 10px;
}

/* --- Collapsed sidebar --- */
/* Only the logo and the area dot survive. Scoped to XL and up because `layout-menu-collapsed`
   is always set below that breakpoint, where the sidebar is an off-canvas drawer that shows
   its labels in full. */
@media (min-width: 1200px) {
  .layout-menu-collapsed:not(.layout-menu-hover) .layout-menu,
  .menu-collapsed:not(:hover) {
    /* Taken out of the flow rather than faded: the label still reserved its width, which pushed
       the swatch off the centre of the collapsed tile. The toggle goes with them — the rail is
       too narrow to hold it beside the logo, and hovering the menu brings it back. */
    .selector-caret,
    .area-select-label,
    .app-brand-text,
    .app-brand .layout-menu-toggle {
      display: none;
    }

    .app-brand-link {
      justify-content: center;
    }

    .area-select {
      justify-content: center;
      padding-inline: 0;
    }
  }

  /* With the name and the toggle gone the logo is all that is left, so the row gives up Vuexy's
     padding and centres it on the same axis as the icons below. Vuexy sets its own
     `padding-inline-start` on this element from a `.menu-vertical` selector, which is why the
     extra class is needed to outweigh it. */
  .layout-menu-collapsed:not(.layout-menu-hover) .layout-menu.menu-vertical .app-brand,
  .menu-collapsed:not(:hover).menu-vertical .app-brand {
    justify-content: center;
    padding-inline: 0;
  }
}

/* --- Tables ------------------------------------------------------------------------------------------------------- */
.table > :not(caption) > * > * {
  padding: 0.4rem 1.25rem !important;
}

/* A cell held to one line still lets the muted line under it wrap. Several list columns pair a
   value that must not break - a period, a date - with a sentence describing the row, and
   `text-nowrap` on the cell applied to both: the sentence then set the column's width. That matters
   because DataTables Responsive shows columns in priority order and stops at the first one that
   will not fit, hiding it and every column after it - so on a phone a 325px wide "Período" left the
   SAF-T table showing nothing but its expand buttons. */
.table td.text-nowrap .d-block,
.table th.text-nowrap .d-block {
  white-space: normal;
}

.avatar {
  --bs-avatar-size: 2.1rem;
}

/* --- Page Content - Lazy Load ------------------------------------------------------------------------------------- */
#content {
  transition: min-height 0.4s ease;
}

#content.page-loading {
  position: relative;
  min-height: calc(100vh - 10rem);
  overflow: hidden;
}

#content.page-loading > * {
  opacity: 0;
}

#content.page-loading::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2rem;
  height: 2rem;
  margin: -1rem 0 0 -1rem;
  border: 2px solid var(--bs-border-color, #d9dee3);
  border-top-color: var(--bs-primary, #696cff);
  border-radius: 50%;
  animation: page-spinner 0.6s linear infinite;
  z-index: 10;
}

@keyframes page-spinner {
  to {
    transform: rotate(360deg);
  }
}

#content.page-loaded > * {
  animation: page-fade-in 0.3s ease both;
}

@keyframes page-fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* --- Tabs --------------------------------------------------------------------------------------------------------- */
.tab-content .card {
  box-shadow: none;
}

.tab-content:has(.card) {
  padding: 0;
}

/* --- Forms -------------------------------------------------------------------------------------------------------- */
.alert-error {
  --bs-alert-color: var(--bs-danger-text-emphasis);
  --bs-alert-bg: var(--bs-danger-bg-subtle);
  --bs-alert-border-color: var(--bs-danger-border-subtle);
  --bs-alert-link-color: var(--bs-danger-text-emphasis);
}

.alert-error {
  --bs-alert-color: var(--bs-danger);
  --bs-alert-close-icon: var(--bs-danger);
  --bs-alert-link-color: var(--bs-danger);
  --bs-alert-link-hover-color: var(--bs-danger);
  --bs-alert-hr: var(--bs-danger);
  --bs-alert-icon-bg: var(--bs-danger);
  --bs-alert-border-color: var(--bs-danger-bg-subtle);
}

/* --- Searchable selects --- */
/* The list search-select.js opens over a native <select>. Built on .dropdown-menu so it takes the
   theme's panel colours, and laid out as a column so the options scroll while the search field
   stays put. Its width, position and max-height are inline: only the script knows the field.

   Vuexy re-tokenises the whole z stack, so the numbers are not Bootstrap's: modal and offcanvas
   1090, popover 1091, toast 1095, tooltip 1099 (vendor/css/core.css), the bug-report tab 1086 and
   the import drop overlay 1200 (both below). 1092 is therefore the first value that clears a
   dialog and a popover while still letting a toast, a tooltip and the drop overlay through. */
:root {
  --cajc-search-zindex: 1092;
}

/* The class is doubled for the structural properties only: they contradict .dropdown-menu's own
   `position: absolute` and `display: none/block` at equal specificity, and pageInit resolves vendor
   stylesheets lazily - so source order is not something to rely on winning. */
.ss-popup.ss-popup {
  position: fixed;
  z-index: var(--cajc-search-zindex);
  display: none;
  flex-direction: column;
  min-width: 0;
  /* The gutters live on the children instead, so a pinned group heading can bleed to both edges */
  padding: 0;
  overflow: hidden;
}

.ss-popup.ss-popup.show {
  display: flex;
}

/* --- Search field --- */
/* An .input-group, not a padded field with an icon laid over it: as a flex sibling the icon takes
   real width, so the text can never run under it. Reserving the room with padding instead loses the
   cascade to `.form-control.form-control-sm:focus` in vendor/css/core.css - and the field holds
   focus for as long as the list is open, so that rule is never not in play. Same construction as
   the company switcher below, which is why `form-control-sm` is absent from the field: inside an
   input group the small sizing comes from `.input-group-sm`, out of rules that know the icon. */
.ss-search-wrap {
  flex: 0 0 auto;
  padding: 0.3125rem 0.375rem 0.375rem;
}

.ss-no-search .ss-search-wrap {
  display: none;
}

/* Inset into the panel, the way .selector-search-group insets its own: the border and the
   background belong to the group, and both children give theirs up. */
.ss-search-group {
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-body-bg);
}

/* The theme rings a focused input group in primary, on the group and again on its ::before
   overlay. In here that ring would never be off - the field is focused the whole time the list is
   open - so it says nothing and only adds weight. The border colour alone is enough. */
.ss-search-group:focus-within {
  border-color: var(--bs-primary);
  box-shadow: none;
}

.ss-search-group:focus-within::before {
  box-shadow: none;
}

.ss-search-group .input-group-text,
.ss-search-group .ss-search {
  border: 0;
  background: none;
  box-shadow: none;
}

/* Doubled for the reason .ss-popup is doubled: `.input-group.input-group-sm .input-group-text`
   writes this padding from three classes up, and a two-class selector loses to it. The numbers put
   the icon inside the field rather than beside it as a button. */
.ss-search-group.ss-search-group .input-group-text {
  padding-inline: 0.4375rem 0.375rem;
  color: var(--bs-secondary-color);
}

/* --- Options --- */
/* The scroll lives here rather than on the panel, so the search field never scrolls away.
   `min-height: 0` is what lets a flex child actually shrink to its scrollport.

   `position: relative` makes it the rows' offset parent, which is what lets `reveal()` bring a row
   into view by moving this one element rather than through `scrollIntoView`, which would scroll
   every scroller the row sits in - the dialog, and the page, smoothly.

   `overscroll-behavior` is the other half of the scroll lock: a wheel over the list is the one
   gesture `onGesture` does not cancel, so without this it chains on into whatever is behind the
   panel once the list reaches its end. */
.ss-list {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  padding: 0 0.25rem 0.25rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  outline: 0;
}

.ss-no-search .ss-list {
  padding-top: 0.25rem;
}

/* Compact on purpose: the point of the list is to show enough rows at once that the chart can be
   scanned rather than scrolled. Truncation is safe here - every option leads with its code - and a
   row long enough for the ellipsis to bite is given a title carrying the rest. */
.ss-option {
  padding: 0.25rem 0.5rem;
  overflow: hidden;
  border-radius: 0.375rem;
  font-size: 0.8125rem;
  line-height: 1.375rem;
  text-overflow: ellipsis;
  cursor: pointer;
}

.ss-option.disabled {
  color: var(--bs-dropdown-link-disabled-color);
  cursor: default;
}

/* One highlight for the mouse and the keyboard both: the script moves .ss-active with the arrow
   keys and with the pointer, so there is never a second row claiming to be next. */
.ss-option.ss-active {
  background-color: var(--bs-dropdown-link-hover-bg);
  color: var(--bs-dropdown-link-hover-color);
}

/* The value the field already holds, marked the way the company switcher marks its own */
.ss-option.ss-chosen {
  color: var(--bs-primary);
  font-weight: 500;
}

/* --- Groups --- */
/* Sticky, so the heading a member belongs to is still on screen after scrolling past it. The
   negative inline margins are what let its background cover the list's own gutters, instead of
   leaving two slivers of scrolling text either side of it. */
.ss-group {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 0.375rem 0.75rem 0.125rem;
  margin-inline: -0.25rem;
  overflow: hidden;
  background-color: var(--bs-dropdown-bg);
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.125rem;
  text-transform: uppercase;
  text-overflow: ellipsis;
}

/* A member is indented and carries the group's rail, which is what tells it apart from a row that
   belongs to nobody. The rail is inset rather than on the edge so it reads as a branch. */
.ss-option.ss-member {
  padding-inline-start: 1.375rem;
  background-image: linear-gradient(var(--bs-border-color), var(--bs-border-color));
  background-repeat: no-repeat;
  background-position: 0.75rem 0;
  background-size: 1px 100%;
}

/* --- Footers --- */
.ss-empty,
.ss-hint {
  flex: 0 0 auto;
  padding: 0.375rem 0.75rem;
  color: var(--bs-secondary-color);
  font-size: 0.75rem;
}

/* --- The field itself --- */
/* Focus is on the search field while the list is open, so the select has to be told to keep
   looking like the thing being edited. Mirrors .form-select:focus. */
.ss-owner.form-select {
  border-color: var(--bs-primary);
  box-shadow: 0 0.125rem 0.375rem 0 rgba(var(--bs-primary-rgb), 0.3);
}

/* --- Pages -------------------------------------------------------------------------------------------------------- */
@media (max-width: 1199.98px) {
  .drag-target {
    position: fixed;
    z-index: 1036;
    block-size: 100%;
    inline-size: 25px;
    inset-block-start: 0;
    inset-inline-start: 0;
  }
}

/* --- Page: Dashboard ---------------------------------------------------------------------------------------------- */
/* A queue with nothing in it keeps its place on the board, but steps back so the eye lands on the
   stages that still need attention. */
.dashboard-card-clear {
  opacity: 0.55;
}

/* One segment per stage: the bar is the shape of the backlog, so it carries no empty track */
.dashboard-backlog {
  height: 0.625rem;
  gap: 2px;
  background: none;
}

.dashboard-backlog .progress-bar {
  border-radius: var(--bs-border-radius-pill);
}

.dashboard-legend-dot {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
}

/* --- Page: Entities ------------------------------------------------------------------------------------------------- */
/* --- Modal --- */
#entity-modal.modal .modal-content {
  padding: 2rem;
}

#entity-modal .btn-close {
  top: -1.3rem;
  right: -1.3rem;
}

#entity-modal .ql-editor {
  min-height: 300px;
  resize: vertical;
  overflow: auto;
}

/* --- Page: Document Import ---------------------------------------------------------------------------------------- */
.import-dropzone {
  border: 2px dashed var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.import-dropzone:hover,
.import-dropzone:focus-visible {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), 0.04);
}

.import-drag-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.import-drag-overlay-inner {
  padding: 3rem 4rem;
  border: 3px dashed rgba(255, 255, 255, 0.8);
  border-radius: var(--bs-border-radius-lg);
  color: #fff;
  text-align: center;
}

.import-source-btn {
  border: var(--bs-border-width) solid var(--bs-border-color);
  color: var(--bs-body-color);
}

.import-source-btn:hover {
  border-color: var(--bs-primary);
}

.import-source-btn.active {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  color: var(--bs-primary);
}

/* The caret follows the collapse on its own: Bootstrap keeps aria-expanded on the trigger */
#import-zone-caret {
  display: inline-block;
  transition: transform 0.2s ease;
}

#import-zone-toggle[aria-expanded="false"] #import-zone-caret {
  transform: rotate(180deg);
}

.import-preview-frame {
  min-height: 420px;
  background-color: var(--bs-secondary-bg);
}

.import-preview-frame iframe,
.import-preview-frame img {
  width: 100%;
  height: 100%;
  min-height: 420px;
  border: 0;
  object-fit: contain;
}

/* The pill counting applied filters is taller than a small button's line, so without this Filtrar - and the
   whole bar with it - would grow by 3px whenever a filter is applied */
#bulk-filter .badge {
  margin-block: -0.25rem;
}

/* Wherever the preview is not beside the table it runs full width, so it gives up height to
   keep the approve/discard buttons — and the table stacked below it — within reach */
@media (max-width: 1399.98px) {
  .import-preview-frame,
  .import-preview-frame iframe,
  .import-preview-frame img {
    min-height: 40vh;
  }
}

/* Only worth pinning once the preview sits beside the table */
@media (min-width: 1400px) {
  .import-preview-card {
    position: sticky;
    top: 5.5rem;
  }
}

/* These are portrait pages, so what the preview can usefully show is bounded by the screen's height,
   not the column's width: past a full-height page the extra only becomes letterboxing either side.
   So the document stops at the width such a page needs and the table absorbs the rest. The 44rem
   floor keeps the cap from ever making the document smaller than it already is on a short screen,
   and `min-width: 0` because a flex child otherwise refuses to shrink below its content. Held to
   1925px because that is where the container itself starts growing; below it there is nothing spare
   to hand over. */
@media (min-width: 1925px) {
  .import-preview-col {
    max-width: max(44rem, calc((100vh - 10rem) / 1.425));
  }

  .import-table-col {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: none;
  }
}

/* Shared by the import and documents lists. A fixed box with `cover` is what keeps every row the
   same height whatever the thumbnail's own aspect ratio, and the border gives a document with a
   white background an edge to be seen against. */
.doc-thumb {
  object-fit: cover;
  flex-shrink: 0;
  border: 1px solid var(--bs-border-color);
}

/* A set-aside file's own name stands where a document number would be, and runs far longer than one:
   kept to a line, or it wraps letter by letter in a column the table sizes for numbers */
#documents-table .document-file-name {
  display: inline-block;
  max-width: 24rem;
  vertical-align: bottom;
}

/* The Documentos column of boxes holds nothing but a box, and most of its rows not even that: without the
   table's roomy cell padding it costs the other columns as little width as it can. Written like the rule
   that sets that padding, so as to outweigh it, and not under #documents-table: DataTables sizes the
   columns on a copy of the table that has no id */
.table > :not(caption) > * > .document-select-cell {
  width: 1.5rem;
  padding-inline: 0.25rem !important;
}

/* --- Comunicar retenções: the dialog's list of what goes --- */

/* A file name is as long as its sender made it: kept to a line, with the whole of it in the title */
#withholding-rows .withholding-file {
  max-width: 18rem;
}

/* --- The documents list's expanded row --- */

/* Driven off aria-expanded rather than a class, so the caret cannot disagree with the state
   DataTables actually holds - the same reason .classify-line-toggle does it. */
.document-expand i {
  transition: transform 0.2s ease-in-out;
}

.document-expand[aria-expanded='true'] i {
  transform: rotate(90deg);
}

/* The page's own background rather than the table's white, so the two blocks read as cards laid
   on it - and so the space beside a card that ends short is background, not an empty white box. */
#documents-table > tbody > tr.child > td {
  background-color: var(--bs-body-bg);
  padding: 1rem 1.25rem;
}

/* Deliberately not stretched to the row's height: when one table is longer than the other, the
   shorter card ends where its content does and the strip shows beside it. Stretching left a tall
   panel of empty card behind the shorter table, which read as a table that had lost its rows. */
.document-detail-card {
  background-color: var(--bs-paper-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  align-self: flex-start;
}

/* Tinted like the totals row at the other end: the table is then framed top and bottom, and the
   white body is only ever the data itself. */
.document-detail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
  background-color: var(--bs-light-bg-subtle);
}

/* The theme's table is built for a page, not for a card inside a row: without this the cells are
   roomy enough that the two blocks no longer fit side by side on a laptop. */
.document-detail-table > :not(caption) > * > * {
  padding: 0.5rem 1rem;
}

.document-detail-table thead th {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--bs-secondary-color);
}

.document-detail-table tfoot td {
  font-weight: 500;
  border-top: 1px solid var(--bs-border-color);
  background-color: var(--bs-light-bg-subtle);
}

/* The row already ends the table; a border under its last cell reads as a stray rule. */
.document-detail-table > tbody > tr:last-child > td {
  border-bottom: 0;
}

.document-detail-num {
  width: 2rem;
}

/* The rate qualifies the amount beside it rather than standing as a figure of its own. */
.document-detail-rate {
  font-size: 0.75rem;
  margin-right: 0.25rem;
}

.document-detail-note {
  font-size: 0.75rem;
}

/* The description takes whatever the amounts leave and truncates inside it; the full text stays
   in the title. `max-width: 0` is the part that matters: without it a long supplier description
   sets the column's width and the whole card scrolls sideways instead. */
.document-detail-desc-col {
  width: 100%;
  max-width: 0;
}

.document-detail-desc {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.document-detail-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.25rem;
}

.document-detail-tags .badge {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}

/* The account's name explains its code, and must give way to it rather than wrap onto a second
   line when the card is narrow. Same `max-width: 0` trick as the description column, with a flex
   row so the code keeps its width and only the name is cut. */
.document-detail-account-col {
  width: 100%;
  max-width: 0;
}

.document-detail-account-cell {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  min-width: 0;
}

.document-detail-account {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* A fixed layout is what lets the cells ignore long unbroken filenames and subjects;
   without it the column stretches to fit them and text-truncate never engages. */
#imports-table {
  table-layout: fixed;
}

/* Flex children refuse to shrink past their content unless told to */
.import-doc-cell {
  min-width: 0;
}

/* Every column is narrow here, so the roomy default padding is what pushed dates onto a
   second line and stacked the action buttons */
#imports-table > :not(caption) > * > * {
  padding-inline: 0.75rem !important;
  white-space: nowrap;
}

#imports-table tbody tr {
  cursor: pointer;
}

#imports-table tbody tr.import-row-focused {
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-primary);
}

/* The chip counting the documents still in triage. Its spinner is sized to the badge's own text: Bootstrap's
   smallest is made for a button, and a badge grows to hold it. */
#import-triage {
  display: inline-flex;
  align-items: center;
}

#import-triage .spinner-border {
  --bs-spinner-width: 0.75rem;
  --bs-spinner-height: 0.75rem;
  --bs-spinner-border-width: 0.125rem;
}

/* Juntar: the handle a row is dragged by - which must not scroll the dialog on a touch screen - and the
   row being dragged, marked the way the list marks its focused row. */
.import-merge-handle {
  cursor: grab;
  touch-action: none;
}

.import-merge-ghost {
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-primary);
}

/* Sortable's floating copy of the dragged row lands inside the numbered list itself: it would take the list's next
   number, and, transparent like every row, show the rows beneath through its text. The placeholder already shows
   where the row is heading. */
.import-merge-item.sortable-fallback {
  background-color: var(--bs-paper-bg);
  box-shadow: var(--bs-box-shadow);
}

.import-merge-item.sortable-fallback::before {
  visibility: hidden;
}

/* --- Page: Reconcile ---------------------------------------------------------------------------------------------- */
/* Four row states across two panes, all built the same way as the import focus above: a tint plus
   an inset bar. Colour is never the only cue - a linked row also carries a pill naming its
   counterpart, and a dismissed one says so in words. */

#reconcile-documents tbody tr,
#reconcile-entries tbody tr {
  cursor: pointer;
}

/* Two tables sharing one screen: the default 1.25rem inline padding costs more width than the
   suggestion column can spare. */
#reconcile-documents > :not(caption) > * > *,
#reconcile-entries > :not(caption) > * > * {
  padding-inline: 0.6rem !important;
}

#reconcile-documents tbody tr.reconcile-row-linked,
#reconcile-entries tbody tr.reconcile-row-linked {
  background-color: rgba(var(--bs-success-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-success);
}

#reconcile-documents tbody tr.reconcile-row-dismissed,
#reconcile-entries tbody tr.reconcile-row-dismissed {
  box-shadow: inset 3px 0 0 var(--bs-secondary);
  opacity: 0.65;
}

/* The document driving the page, and the entry waiting to be linked to it. Both stay legible on
   top of a linked or dismissed tint, which is why they are listed after it. */
#reconcile-documents tbody tr.reconcile-row-focused {
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-primary);
}

#reconcile-entries tbody tr.reconcile-row-selected {
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-primary);
}

/* --- Pedir em falta ----------------------------------------------------------------------------------------------- */
/* The modal Conciliar, Documentos and e-Fatura share (templates/components/missing_data_*.html). */

/* The button's holder sits among the page header's flex items: no box of its own, so the button swapped in takes
   part in that layout directly, and an empty holder adds no gap */
#missing-data-button {
  display: contents;
}

/* A supplier row opens its compose from anywhere on it (a stretched link), so it answers the pointer as one */
.missing-data-supplier:hover,
.missing-data-sent:hover {
  background-color: rgba(var(--bs-primary-rgb), 0.04);
}

/* The name side owns the slack and lets a long subject truncate rather than push the status off the row */
.missing-data-who {
  flex: 1 1 auto;
  min-inline-size: 0;
}

/* Above the row's stretched link, so its two answers stay clickable */
.missing-data-uncertain {
  position: relative;
  z-index: 2;
}

/* The caret turns with the collapse; Bootstrap keeps .collapsed on the trigger */
.missing-data-recent-toggle i {
  transition: transform 0.2s ease;
}

.missing-data-recent-toggle:not(.collapsed) i {
  transform: rotate(90deg);
}

.missing-data-editor .ql-editor {
  min-block-size: 10rem;
  max-block-size: 22rem;
}

/* The editor has no input of its own to carry .is-invalid, so its frame takes the colour */
.missing-data-editor.is-invalid {
  border-color: var(--bs-form-invalid-border-color) !important;
}

/* The token marking where the tables go, drawn as a chip so it reads as a placeholder the server fills in rather
   than as words the supplier will read */
.missing-data-editor .ql-missing-table {
  display: inline-block;
  padding: 0 0.5rem;
  border: 1px dashed var(--bs-primary);
  border-radius: var(--bs-border-radius);
  background-color: rgba(var(--bs-primary-rgb), 0.08);
  color: var(--bs-primary);
  font-size: 0.8125rem;
  user-select: none;
}

.missing-data-items .missing-data-kind > td {
  background-color: rgba(var(--bs-secondary-rgb), 0.06);
}

.missing-data-envelope dt {
  color: var(--bs-secondary-color);
  font-weight: 400;
}

/* The email as sent. A fixed height that scrolls: a sandboxed frame cannot be measured from outside it. White in
   either theme, because that is what the supplier's mail reader shows. */
.missing-data-frame {
  inline-size: 100%;
  block-size: 26rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: #fff;
}

/* --- Page: Document Classify -------------------------------------------------------------------------------------- */
/* An aspect ratio rather than a height: whatever the column width, the frame is exactly tall
   enough for one portrait page at the viewer's automatic zoom. 1:1.414 is A4; the rest absorbs
   the PDF viewer's own toolbar. */
.classify-preview-frame {
  aspect-ratio: 1 / 1.425;
  background-color: var(--bs-secondary-bg);
}

.classify-preview-frame iframe,
.classify-preview-frame img {
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: contain;
}

@media (min-width: 1200px) {
  .classify-preview-card {
    position: sticky;
    top: 5.5rem;
  }
}

/* The same trade as on the import page, and here the cap earns its keep twice over: this frame is
   locked to an A4 aspect ratio, so width it cannot use is also height it does not have - a column
   left to grow freely pushes the card well past the bottom of the screen. */
@media (min-width: 1925px) {
  .classify-preview-col {
    max-width: max(44rem, calc((100vh - 10rem) / 1.425));
  }

  .classify-form-col {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: none;
  }
}

/* The account column takes the slack; the amounts stay narrow enough to scan down */
.classify-journal .classify-amount-col {
  width: 7.5rem;
}

.classify-journal .classify-row-actions {
  width: 2.5rem;
}

.classify-journal > :not(caption) > * > * {
  padding-block: 0.35rem !important;
}

.classify-balance th {
  border-top: 2px solid var(--bs-border-color);
}

/* Red only while the entry does not close - the one thing that must not be missed */
.classify-balance.classify-unbalanced th {
  color: var(--bs-danger);
}

.classify-queue-list {
  overflow-y: auto;
  max-height: 22rem;
}

/* --- Multi-invoice files --- */
/* A tint plus an inset bar, the same construction as the reconcile row states above: the strip has
   to read as "this document is one of several" before any of its words are read. Colour is never
   the only cue - the strip also counts the invoices in words and the preview names the page. */
.classify-parts {
  padding: 0.75rem 1rem;
  border-radius: var(--bs-border-radius);
  background-color: rgba(var(--bs-warning-rgb), 0.08);
  box-shadow: inset 3px 0 0 var(--bs-warning);
}

.classify-parts-scroll {
  overflow-y: auto;
  max-height: 18rem;
  background-color: var(--bs-body-bg);
}

/* Sits under the preview header, so it stays quiet: the document itself is what should be read. */
.classify-part-origin {
  padding: 0.5rem 0.75rem;
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-secondary-bg-subtle);
}

/* --- Tabs --- */
/* One centred flex row per trigger. Left to the default inline flow the badge on the first tab
   sets that button's line box, and the two labels stop sharing a baseline. */
.classify-tabs .nav-link {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  line-height: 1.5;
}

/* The count pill is taller than the label's own line box, so left alone it sets the height of the
   tab it sits in and the two underlines end up a few pixels apart. */
.classify-tabs .nav-link .badge {
  padding-block: 0;
  line-height: inherit;
}

/* --- Document header tabs --- */
/* "Dados do Documento" and "Observações" are two panes of one grid cell, so the row is as tall as
   the taller of them: the card never resizes when the tab is switched, and the notes box comes out
   exactly as tall as the fields it covers with no measured height anywhere to keep in step.

   `visibility` rather than the `display: none` Bootstrap hides a pane with, for two reasons. A pane
   that leaves the layout cannot lend the row its height, which is the whole trick. And the theme
   already puts `opacity: 0` on an inactive pane - an element at zero opacity is still hit-testable,
   and this one is second in the DOM over the same cell, so it would paint invisibly on top of the
   fields and swallow every click meant for them.

   The class keyed on is `.active`, never `.show`: Bootstrap removes `.active` from the outgoing pane
   on the same tick it adds it to the incoming one, and defers `.show` until the transition ends - so
   a rule on `.show` would leave both panes visible and stacked for its duration. */
.classify-doc-panes {
  display: grid;
}

.classify-doc-panes > .tab-pane {
  display: flex;
  flex-direction: column;
  grid-area: 1 / 1;
  visibility: hidden;
}

.classify-doc-panes > .tab-pane.active {
  visibility: visible;
}

/* The notes pane is handed the fields' height and gives all of it to the textarea. */
.classify-notes {
  flex: 1 1 auto;
  min-height: 6rem;
  resize: vertical;
}

/* Observações live behind a tab, so the tab itself has to say the field is not empty - otherwise a
   note the extraction found is invisible to anyone who does not think to look. */
.classify-notes-dot {
  flex-shrink: 0;
  inline-size: 0.375rem;
  block-size: 0.375rem;
  border-radius: 50%;
  background-color: var(--bs-primary);
}

/* --- Lines tab --- */
/* Sixteen fields per line do not fit half a page, so the compact row carries the decisions worth
   scanning and the rest opens underneath it. Below this width the table scrolls sideways rather
   than crushing the columns - or the reviewer presses "Largura total". */
.classify-lines-table {
  min-width: 56rem;
}

/* Both hold at most a checkbox or two digits, so they take the narrowest padding in the table. */
.classify-line-check-col,
.classify-line-number-col {
  width: 1px;
  white-space: nowrap;
}

.classify-lines-table .classify-line-check-col,
.classify-lines-table .classify-line-number-col {
  padding-inline: 0.4rem !important;
}

.classify-line-qty-col {
  width: 6rem;
}

.classify-line-rate-col {
  width: 4.5rem;
}

.classify-line-tags-col {
  width: 13rem;
}

.classify-line-actions-col {
  width: 3rem;
}

/* Every line is two rows, and Bootstrap gives both of them a bottom border - which reads as one
   split line under a collapsed row. The detail row owns the separator; the compact one has none. */
.classify-line-row > td {
  padding-block: 0.4rem !important;
  border-bottom: 0 !important;
  vertical-align: middle;
}

/* The title takes the space that is left and ellipsises; the icons keep their own. Given plain
   inline boxes instead, the title's width wins and the icon wraps to a line of its own. */
.classify-line-desc {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
}

/* The cap has to be stated: `table-layout: auto` treats a column width as a suggestion, so an
   uncapped title widens the column until the amounts are pushed off the far side. `min-width: 0`
   then lets it shrink below that when the pane is narrower. */
.classify-line-title {
  overflow: hidden;
  min-width: 0;
  max-width: 22rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.classify-line-desc > .badge,
.classify-line-desc > .classify-line-hint {
  flex: 0 0 auto;
}

/* Each badge carries its whole "SNC: 6242 - Combustíveis"; the title attribute shows the rest. */
.classify-line-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
}

/* Stated in rem rather than as a percentage: under `table-layout: auto` the column width is only
   a suggestion, so a badge left to fill its cell just widens the column instead of ellipsising. */
.classify-line-tags .badge {
  overflow: hidden;
  max-width: 12rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.classify-line-invalid > td {
  background-color: var(--bs-danger-bg-subtle);
}

/* The collapse is the div inside this cell, never the row: Bootstrap cannot animate a <tr>. */
.classify-line-detail > td {
  padding: 0 !important;
  border-top: 0 !important;
}

.classify-line-editor {
  padding: 1rem 1.25rem 1.25rem;
  background-color: var(--bs-secondary-bg-subtle);
}

/* The ground is barely off-white, so the controls need to stay white to read as controls. */
.classify-line-editor .form-control,
.classify-line-editor .form-select {
  background-color: #fff;
}

.classify-line-toggle[aria-expanded='false'] i {
  transform: rotate(-90deg);
}

.classify-line-toggle i {
  transition: transform 0.2s ease-in-out;
}

.classify-line-hint {
  cursor: help;
}

/* Wider than Bootstrap's default: an explanation is a sentence or two, not a label. */
.popover.classify-hint {
  --bs-popover-max-width: 22rem;
}

/* Derived from the lines, so it is read rather than filled in. */
.classify-journal-derived tbody td {
  background-color: var(--bs-secondary-bg-subtle);
}

/* --- Full width --- */
/* The toggle and this class both live outside #classify-pane, so the state survives every swap
   without any JavaScript reapplying it. */
.classify-wide .classify-preview-col {
  display: none;
}

.classify-wide .classify-form-col {
  flex: 0 0 100%;
  max-width: 100%;
}

.classify-wide .classify-line-tags .badge {
  max-width: 18rem;
}

.classify-wide .classify-line-title {
  max-width: 46rem;
}

/* --- Page: Prices ------------------------------------------------------------------------------------------------- */
tr#creation-row td {
  padding: 1.25rem !important;
  border-top: 2px solid rgba(0, 0, 0, .2);
}

/* --- Plugins ------------------------------------------------------------------------------------------------------ */
/* --- Pickr --- */
.pickr {
  width: 100%;
  height: 38px;
}

.pickr .pcr-button {
  width: 100%;
  height: 38px;
}

/* --- Flatpickr --- */
/* Position calendars opened with `static: true` (used inside modals) below their input */
.flatpickr-calendar.static {
  position: absolute;
  top: calc(100% + 2px);
}

.flatpickr-calendar.static.open {
  display: block;
  z-index: 999;
}

/* Clear (x) button shown inside date inputs */
.datepicker-clearable {
  position: relative;
}

.datepicker-clearable .form-control {
  padding-right: 2.5rem;
}

.datepicker-clearable .datepicker-clear {
  position: absolute;
  top: 50%;
  right: .25rem;
  transform: translateY(-50%);
  z-index: 2;
}

/* --- DataTables --- */
/* Hide "Search:" label and "Show/entries" label */
div.dt-container div.dt-search label,
div.dt-container div.dt-length label {
  display: none !important;
}

/* Remove extra margins from hidden labels */
div.dt-container div.dt-search input {
  margin-inline: 0 !important;
}

div.dt-container div.dt-length select {
  margin-inline: 0 !important;
}

/* Align search and buttons horizontally */
div.dt-container .dt-layout-end {
  display: flex !important;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

div.dt-container .dt-layout-end > *:not(:first-child) {
  margin-inline: 0 !important;
}

/* --- Coded charts (Contas SNC, Centros de Custo, Contas Analíticas, Plano de IVA, Obras) --- */
/* The list is flex rows rather than a table: a row and everything under it has to be one element so
   htmx can swap it, and Bootstrap's collapse cannot animate a <tbody>. */
.dimension-head {
  font-size: 0.75rem;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.dimension-caret {
  flex: 0 0 auto;
  width: 1.75rem;
}

/* Each level steps in under the one above it; --depth comes from the row, 1 at the top. */
.dimension-line > .dimension-caret {
  margin-inline-start: calc((var(--depth, 1) - 1) * 1.25rem);
}

.dimension-code {
  flex: 0 0 auto;
  width: 8rem;
}

.dimension-rate {
  flex: 0 0 auto;
  width: 4rem;
}

.dimension-account {
  flex: 0 0 auto;
  width: 15rem;
}

/* The Plano de IVA goes eight levels deep and has a rate column too. Its SNC accounts read short
   (2432 · IVA - Dedutível), and its deepest descriptions need the room: at 15rem they vanished at 1280px. */
.dimension-list-vat-accounts .dimension-account {
  width: 10rem;
}

.dimension-state {
  flex: 0 0 auto;
  width: 9.5rem;
}

/* Three buttons: add under, edit, delete. A row without one keeps an invisible placeholder in its place. */
.dimension-actions {
  flex: 0 0 auto;
  width: 7.25rem;
}

.dimension-line-top {
  background-color: var(--bs-secondary-bg-subtle);
}

.dimension-line:not(.dimension-line-top):hover {
  background-color: var(--bs-light-bg-subtle);
}

/* Withdrawn from circulation: still listed, still editable, just not available to post against. */
.dimension-off .dimension-code,
.dimension-off > .flex-grow-1,
.dimension-off .dimension-rate,
.dimension-off .dimension-account {
  opacity: 0.55;
}

/* The caret points down while the group is open and rotates as Bootstrap collapses it. */
.dimension-caret[aria-expanded='false'] i {
  transform: rotate(-90deg);
}

.dimension-caret i {
  transition: transform 0.2s ease-in-out;
}

/* A chart of one level (Obras) is a plain list: nothing to expand, and no row heads a branch. */
.dimension-list-flat .dimension-caret {
  display: none;
}

.dimension-list-flat .dimension-line-top {
  background-color: transparent;
}

.dimension-list-flat .dimension-line:hover {
  background-color: var(--bs-light-bg-subtle);
}

/* The official SNC chart in the modal: each level steps in, and a search opens every class. */
.snc-official-row {
  margin-inline-start: calc((var(--depth, 2) - 2) * 1.25rem);
}

.snc-official.is-searching .collapse {
  display: block;
}

.dimension-preview-code {
  padding-inline-start: calc((var(--depth, 1) - 1) * 0.75rem);
}

.dimension-preview,
.einvoice-upload-preview {
  max-height: 22rem;
  overflow-y: auto;
}

/* Five or six columns in a dialog. The roomy default padding and a supplier name - or a chart's
   description - left to run pushed the last column, what happens to each line and the one the preview
   is for, out of sight */
.einvoice-upload-preview .table > :not(caption) > * > *,
.dimension-preview .table > :not(caption) > * > * {
  padding-inline: 0.75rem !important;
}

.einvoice-upload-preview td.text-truncate,
.dimension-preview td.text-truncate {
  max-width: 14rem;
}

/* --- Page: SAF-T -------------------------------------------------------------------------------------------------- */

/* The modal dropzone has no full-page overlay to fall back on, so it lights up itself. */
.import-dropzone.is-dragging {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), 0.08);
}

/* A document's lines, its totals and both ends of its chain do not fit the default 400px. */
.offcanvas-lg-wide {
  width: 100%;
}

@media (min-width: 992px) {
  .offcanvas-lg-wide {
    width: 46rem;
  }
}

/* The theme capitalises every word of a nav pill, which in Portuguese turns "Documentos de
   conferência" into "Documentos De Conferência". These labels are already written as they read. */
#saft-tabs .nav-link,
#saft-sales-chart-tabs .nav-link {
  text-transform: none;
}

/* Numbers in the document pane read as a column, which they cannot do in a proportional face. */
#saft-document-body table td.text-end,
#saft-document-body table tfoot td {
  font-variant-numeric: tabular-nums;
}

/* --- Page: Pedidos ------------------------------------------------------------------------------------------------ */

/* The imported emails behind a request. Bounded so a long thread scrolls inside the modal instead
   of pushing the submit buttons off the screen. */
.request-thread-panel {
  max-block-size: 18rem;
  overflow-y: auto;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding-block: .75rem;
  padding-inline: .75rem;
  background-color: var(--bs-body-bg);
}

/* Quoted mail arrives with its own line breaks; keep them without letting a long URL widen the
   modal. */
.request-thread-body {
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* --- Page: User Profile -------------------------------------------------------------------------------------------- */

/* The signature editor is a fixed box rather than one that grows with the text: a signature is a
   few lines, and a growing editor pushes the save button off the card while it is being typed in. */
.signature-editor .ql-editor {
  max-block-size: 16rem;
  min-block-size: 8rem;
}

/* The preview shows what the recipient gets, so it carries none of the editor's chrome */
.signature-preview {
  background-color: var(--bs-body-bg);
}

.signature-preview :last-child {
  margin-block-end: 0;
}

/* An untreated notice is marked on its leading edge rather than by a background tint, which would
   fight the level colour already carried by the icon */
.notification-untreated {
  border-inline-start: 2px solid var(--bs-primary);
}

/* The profile photo, at the size the template it came from used. Vuexy sizes every avatar from
   these two custom properties, so a variant is a pair of values rather than a new rule */
.avatar.user-profile-photo {
  --bs-avatar-size: 6.25rem;
  --bs-avatar-initial: 2.5rem;
}

/* The stored photo is square-cropped server-side; this only keeps the picked file from stretching
   while it is being previewed, before the upload comes back */
#profile-avatar img {
  object-fit: cover;
}

/* The company logo on its settings card, at the profile photo's size. A logo keeps its own proportions
   (company_avatar.html sets object-fit: contain), so a wide one sits letterboxed rather than cropped */
.avatar.company-logo-photo {
  --bs-avatar-size: 6.25rem;
  --bs-avatar-initial: 2.5rem;
}

/* The Notificações tab carries a badge, which makes its button taller than the others. A .nav-link
   sizes to its own content rather than to the row, and Vuexy draws the active tab's indicator as an
   inset shadow on that button's bottom edge - so every shorter tab's indicator floated above the
   strip's border instead of sitting on it. Stretching each button to the row puts the indicator back
   on the line whatever a tab happens to contain. Scoped to this page: elsewhere the tab buttons
   rely on whitespace between icon and label, which flex would collapse. */
#profile-tabs .nav-item {
  display: flex;
}

#profile-tabs .nav-item > .nav-link {
  display: flex;
  align-items: center;
}

/* --- Bug report --------------------------------------------------------------------------------------------------- */

/* The handle sits directly above the template customizer's own, in the same 38x38 left-rounded shape, so the two read
   as one column of page-level tools rather than two unrelated tabs. Deliberately not --bs-primary: that is the
   customizer's colour and it is also the per-company accent, so an amber tab would change meaning from one company to
   the next. --bs-gray-700 and --bs-body-bg are both theme-aware and move in opposite directions, so one pair of tokens
   keeps the icon legible on the light and the dark skin with no second rule. */
.bug-report-tab {
  position: fixed;

  /* Above the navbar (1080) and the sidebar (1085), below the modal backdrop (1089), so the handle is dimmed by its own
     modal rather than floating on top of it. The offset is 38px of handle plus 8px of air above the customizer's 180px. */
  z-index: 1086;
  inset-block-start: 134px;
  inset-inline-end: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-end-start-radius: var(--bs-border-radius);
  border-start-start-radius: var(--bs-border-radius);
  block-size: 38px;
  inline-size: 38px;
  background-color: var(--bs-gray-700);
  box-shadow: 0 0.125rem 0.375rem rgba(0, 0, 0, 0.2);
  color: var(--bs-body-bg);
  transition: background-color 0.2s ease;
}

.bug-report-tab:hover,
.bug-report-tab:focus-visible {
  background-color: var(--bs-gray-800);
  color: var(--bs-body-bg);
}

/* Below XL the customizer hides itself entirely and the gap it leaves would put this handle in mid-air, so it takes the
   customizer's own slot instead. */
@media (max-width: 1199.98px) {
  .bug-report-tab {
    inset-block-start: 180px;
  }
}

/* The capture is annotated at the size it was taken and only ever shown smaller, so the element is scaled by CSS and
   its backing store is left alone: assigning to canvas.width clears the bitmap, and a redraw on every modal resize
   would be a redraw of every stroke. */
.bug-report-canvas-wrap {
  overflow: hidden;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-secondary-bg);
}

.bug-report-canvas {
  display: block;

  /* A finger or a pen on the canvas draws; it does not scroll the modal underneath it. */
  touch-action: none;
  block-size: auto;
  inline-size: 100%;
  cursor: crosshair;
}

/* The swatches are the only controls in the toolbar with no label and no icon, so their line box
   collapses and they end up shorter than everything beside them. The row stretches instead, which
   keeps them in step with the buttons whatever padding the theme gives a .btn-sm. */
.bug-report-swatch {
  border: 2px solid transparent;
  inline-size: 2rem;
}

.bug-report-swatch.active {
  border-color: var(--bs-body-color);
}

.bug-report-swatch[data-bug-report-color="danger"] {
  background-color: var(--bs-danger);
}

.bug-report-swatch[data-bug-report-color="warning"] {
  background-color: var(--bs-warning);
}

/* --bs-pure-black rather than --bs-black or --bs-dark: those two are a dark navy here and the second
   flips to a light tone on the dark skin, and ink drawn onto a screenshot must not change with the
   theme of the page it is drawn on. */
.bug-report-swatch[data-bug-report-color="pure-black"] {
  background-color: var(--bs-pure-black);
}

/* One attached document: icon, name, size and remove, on one line whatever the name's length. */
.bug-report-file {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* A flex item will not shrink below its content without this, and a long name would push the remove button out. */
.bug-report-file-name {
  overflow: hidden;
  min-inline-size: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A report with documents can take a while to upload, and a button that only greys out reads as stuck. htmx keeps
   .htmx-request on the form for as long as the request is in flight. */
#bug-report-form .bug-report-sending,
#bug-report-form.htmx-request .bug-report-send-icon {
  display: none;
}

#bug-report-form.htmx-request .bug-report-sending {
  display: inline-block;
}
