﻿/* Light is the application's own palette; dark is Bootstrap's, adjusted below
   only where this project painted over it.

   These used to answer to `:root` as well, which is why dark mode could not
   work: `:root` matches the html element whatever data-bs-theme says, this file
   loads after Bootstrap, and equal specificity plus later source order means
   every light value here was beating Bootstrap's dark one. The attribute is set
   on <html> in index.html, so nothing is lost by dropping `:root`. */
[data-bs-theme="light"] {
  --bs-blue: #0d6efd;
  --bs-indigo: #6610f2;
  --bs-purple: #6f42c1;
  --bs-pink: #d63384;
  --bs-red: #dc3545;
  --bs-orange: #fd7e14;
  --bs-yellow: #ffc107;
  --bs-green: #198754;
  --bs-teal: #20c997;
  --bs-cyan: #0dcaf0;
  --bs-black: #000;
  --bs-white: #fff;
  --bs-gray: #6c757d;
  --bs-gray-dark: #343a40;
  --bs-gray-100: #f8f9fa;
  --bs-gray-200: #e9ecef;
  --bs-gray-300: #dee2e6;
  --bs-gray-400: #ced4da;
  --bs-gray-500: #adb5bd;
  --bs-gray-600: #6c757d;
  --bs-gray-700: #495057;
  --bs-gray-800: #343a40;
  --bs-gray-900: #212529;
  --bs-primary: #0d6efd;
  --bs-secondary: #6c757d;
  /* NOTE: this override is very nearly inert, and it disagrees with
     --bs-success-rgb below (#198754). Bootstrap's own success surfaces do not
     read it: .btn-success compiles a literal hex, and .bg-success / .text-success
     read --bs-success-rgb. So anything written as var(--bs-success) comes out a
     different green from every button and badge beside it. Either make the two
     agree or drop this line — but that repaints success across the whole app, so
     it wants a deliberate decision rather than a drive-by fix. */
  --bs-success: #03ad5f;
  --bs-info: #3793c4 !important;
  --bs-warning: #ff910a !important;
  --bs-danger: #dc3545;
  --bs-light: #f8f9fa;
  --bs-dark: #212529;
  --bs-primary-rgb: 13, 110, 253;
  --bs-secondary-rgb: 108, 117, 125;
  --bs-success-rgb: 25, 135, 84;
  --bs-info-rgb: 72, 224, 255 !important;
  --bs-warning-rgb: 250, 111, 12;
  --bs-danger-rgb: 220, 53, 69;
  --bs-light-rgb: 248, 249, 250;
  --bs-dark-rgb: 33, 37, 41;
  --bs-primary-text-emphasis: #052c65;
  --bs-secondary-text-emphasis: #2b2f32;
  --bs-success-text-emphasis: #0a3622;
  --bs-info-text-emphasis: #055160;
  --bs-warning-text-emphasis: #664d03;
  --bs-danger-text-emphasis: #58151c;
  --bs-light-text-emphasis: #495057;
  --bs-dark-text-emphasis: #495057;
  --bs-primary-bg-subtle: #cfe2ff;
  --bs-secondary-bg-subtle: #e2e3e5;
  --bs-success-bg-subtle: #d1e7dd;
  --bs-info-bg-subtle: #cff4fc;
  --bs-warning-bg-subtle: #fff3cd;
  --bs-danger-bg-subtle: #f8d7da;
  --bs-light-bg-subtle: #fcfcfd;
  --bs-dark-bg-subtle: #ced4da;
  --bs-primary-border-subtle: #9ec5fe;
  --bs-secondary-border-subtle: #c4c8cb;
  --bs-success-border-subtle: #a3cfbb;
  --bs-info-border-subtle: #9eeaf9;
  --bs-warning-border-subtle: #ffe69c;
  --bs-danger-border-subtle: #f1aeb5;
  --bs-light-border-subtle: #e9ecef;
  --bs-dark-border-subtle: #adb5bd;
  --bs-white-rgb: 255, 255, 255;
  --bs-black-rgb: 0, 0, 0;
  --bs-font-sans-serif:
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans",
    "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
    "Segoe UI Symbol", "Noto Color Emoji";
  --bs-font-monospace:
    SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
    monospace;
  --bs-gradient: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.15),
    rgba(255, 255, 255, 0)
  );
  --bs-body-font-family: var(--bs-font-sans-serif);
  --bs-body-font-size: 1rem;
  --bs-body-font-weight: 400;
  --bs-body-line-height: 1.5;
  --bs-body-color: #212529;
  --bs-body-color-rgb: 33, 37, 41;
  /* Not #fff: this application's light ground is a shade off white, which is
     what the near-white !important rule at the top of the file used to force.
     Declared here, in the block that owns the light tokens, so it is not fighting
     a second declaration of itself further down. */
  --bs-body-bg: hsl(0, 0%, 99%, 1);
  --bs-body-bg-rgb: 252, 252, 252;
  --bs-emphasis-color: #000;
  --bs-emphasis-color-rgb: 0, 0, 0;
  --bs-secondary-color: rgba(33, 37, 41, 0.75);
  --bs-secondary-color-rgb: 33, 37, 41;
  --bs-secondary-bg: #e9ecef;
  --bs-secondary-bg-rgb: 233, 236, 239;
  --bs-tertiary-color: rgba(33, 37, 41, 0.5);
  --bs-tertiary-color-rgb: 33, 37, 41;
  --bs-tertiary-bg: #f8f9fa;
  --bs-tertiary-bg-rgb: 248, 249, 250;
  --bs-heading-color: inherit;
  --bs-link-color: #0d6efd;
  --bs-link-color-rgb: 13, 110, 253;
  --bs-link-decoration: underline;
  --bs-link-hover-color: #0a58ca;
  --bs-link-hover-color-rgb: 10, 88, 202;
  --bs-code-color: #d63384;
  --bs-highlight-color: #212529;
  --bs-highlight-bg: #fff3cd;
  --bs-border-width: 1px;
  --bs-border-style: solid;
  --bs-border-color: #dee2e6;
  --bs-border-color-translucent: rgba(0, 0, 0, 0.175);
  --bs-border-radius: 0.375rem;
  --bs-border-radius-sm: 0.25rem;
  --bs-border-radius-lg: 0.5rem;
  --bs-border-radius-xl: 1rem;
  --bs-border-radius-xxl: 2rem;
  --bs-border-radius-2xl: var(--bs-border-radius-xxl);
  --bs-border-radius-pill: 50rem;
  --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
  --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
  --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
  --bs-focus-ring-width: 0.25rem;
  --bs-focus-ring-opacity: 0.25;
  --bs-focus-ring-color: rgba(13, 110, 253, 0.25);
  --bs-form-valid-color: #198754;
  --bs-form-valid-border-color: #198754;
  --bs-form-invalid-color: #dc3545;
  --bs-form-invalid-border-color: #dc3545;
  --bs-nav-link-color: #444444 !important;
  --bs-nav-link-hover-color: #666666 !important;
  --bs-nav-link-disabled-color: #adb5bd !important;
  --bs-nav-link-active-color: #000000 !important;
}

.btn-warning {
  --bs-btn-color: #fff;
  --bs-btn-bg: #ff910a !important;
  --bs-btn-border-color: #ff910a !important;
  --bs-btn-hover-color: #000;
  --bs-btn-hover-bg: #ffb257 !important;
  --bs-btn-hover-border-color: #ffb257 !important;
  --bs-btn-focus-shadow-rgb: 217, 164, 6;
  --bs-btn-active-color: #000;
  --bs-btn-active-bg: #ff910a !important;
  --bs-btn-active-border-color: #ffb257 !important;
  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
  --bs-btn-disabled-color: #000;
  --bs-btn-disabled-bg: #ffb257 !important;
  --bs-btn-disabled-border-color: #ffb257 !important;
}

.btn-sm {
  --bs-btn-padding-y: 0.25rem;
  --bs-btn-padding-x: 0.5rem;
  --bs-btn-font-size: 0.75rem;
  text-wrap-mode: nowrap;
  height: 1.625rem !important; /* Forces a consistent height */
}

.btn-sm span {
  vertical-align: middle;
  line-height: 1; /* Prevents the icon from adding extra phantom spacing */
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin-bottom: 0;
  line-height: 1;
  padding: 0;
}
p {
  line-height: 1;
}

.fs-6875 {
  font-size: 0.875rem !important;
}

.fs-65 {
  font-size: 0.8rem !important;
}

.fs-7 {
  font-size: 0.75rem !important;
}

.fs-8 {
  font-size: 0.7rem !important;
}

.fs-9 {
  font-size: 0.65rem !important;
}

.fs-10 {
  font-size: 0.6rem !important;
}

.fs-11 {
  font-size: 0.55rem !important;
}

.fs-12 {
  font-size: 0.5rem !important;
}

.fs-13 {
  font-size: 0.45rem !important;
}

.fs-14 {
  font-size: 0.4rem !important;
}

.form-control {
  font-size: 0.75rem;
}

.form-select-sm {
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
  padding-left: 0.4rem;
  font-size: 0.75rem;
  border-radius: 0.2rem;
}

.form-control-sm {
  min-height: auto;
  padding: 0rem;
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
  padding-left: 0.4rem;
  font-size: 0.75rem;
  border-radius: 0.2rem;
}

.form-control-sm,
.form-select-sm {
  height: 1.625rem !important; /* Forces a consistent height */
  line-height: 1.5; /* Standardizes the text baseline */
}

.btn:focus,
.btn:active:focus,
.btn.active:focus,
.form-control:focus,
.form-select:focus,
[focus]:focus {
  outline: none !important;
  box-shadow: none !important;
}

.btn-basic:hover {
  background-color: var(--bs-secondary-bg);
  box-shadow: none;
}

.form-check-input:checked {
  background-color: #198754; /* Your custom color (e.g., success green) */
  border-color: #198754;
}

/* 2. Change the glow/outline when clicking (optional) */
.form-check-input:focus {
  border-color: #198754;
  box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}

.form-control:focus {
  border-color: var(--bs-border-color);
}

.top-75 {
  top: 75%;
}

*:focus-visible {
  outline: 1px solid var(--bs-emphasis-color);
  outline-offset: 0.2rem;
  border-radius: 3px;
}

.navbar {
  height: 47px;

  /* No padding on the leading edge: the menu button and the logo are full-height
     targets that meet the corner on purpose, and the nav rail below lines up
     with them.

     The trailing edge is different — the profile control and the sign-in button
     are ordinary buttons, and with Bootstrap's own padding zeroed they sat flush
     against the window edge with nothing between. */
  padding: 0 0.75rem 0 0;

  /* Top bar of the app shell's flex column. Without this it is a shrinkable
     flex item and loses height as soon as the content below asks for room. */
  flex-shrink: 0;
}

.nav-scrollable {
  width: 47px;
  height: 100%;
  background-color: var(--bs-body-bg);
  transition: width 0.2s ease-in-out;
  overflow-x: hidden;
  position: absolute; /* Allows it to overlap content */
  left: 0;
  border-top: 1px solid var(--bs-border-color);
  border-right: 1px solid var(--bs-border-color);
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Hover effect: Only the scrollable area grows */
.nav-scrollable:hover {
  width: 260px;
}

.menu-item-active {
  background-color: var(--pv-brand-navy) !important;
  cursor: pointer;
}

.menu-item-active i {
  color: white;
}

.nav-link-item i {
  display: flex !important;
  width: min-content !important;
  height: 47px !important;
  font-size: 0.85rem;
  letter-spacing: -0.01rem;
  margin: 0 !important;
}

.nav-link-item p {
  font-family: "DM Sans", sans-serif;
  font-size: 0.475rem;
  letter-spacing: -0.01rem;
  padding: 0rem 0.25rem;
  line-height: 0.5rem;
  word-wrap: break-word;
  overflow-wrap: break-word;
  white-space: normal;
  text-align: center;
}

.nav-link-item-selected {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 30px;
  height: 30px;
  border-radius: 0.25rem;
}

.nav-link-item-selected i {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 60px;
  height: 60px;
}

.nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 43px;
  padding: 0rem !important;
  width: 100%;
}

.nav-link {
  color: var(--bs-secondary-color) !important;
  padding: 0rem;
  display: flex !important;
  flex-direction: column;
  align-items: center !important;
  justify-content: center;
  height: 100% !important;
  width: 100% !important;
  line-height: 1;
  font-size: 0.875rem;
}

.nav-link:hover {
  background-color: var(--bs-secondary-bg) !important;
  padding: 0rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.nav-link-override {
  align-items: start !important;
  justify-content: center !important;
  padding-top: 0.5rem !important;
  padding-bottom: 0.5rem !important;
  padding-left: 0.5rem !important;
  padding-right: 0.5rem !important;
  border-radius: 0.25rem;
}

.nav-link-override:hover {
  background-color: var(--bs-secondary-bg) !important;
  color: var(--bs-secondary-color) !important;
  justify-content: center !important;
}

.nav-link-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 30px;
  height: 30px;
}

.nav-icon-box {
  min-width: 47px;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Label logic */
.nav-label-text {
  margin-left: 12px;
  opacity: 0;
  white-space: nowrap;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.nav-scrollable:hover .nav-label-text {
  opacity: 1;
  pointer-events: auto;
}

/* Fix for the NavLink layout */
.nav-link-content {
  display: flex;
  align-items: center;
  height: 47px;
  width: 100%;
}

.sidebar {
  width: 47px !important;
  /* And it never gives that width back. As a flex item of `.page` it shrank
     with everything else when a page overflowed — down to 11px — while the
     absolutely-positioned `.nav-scrollable` inside it stayed 47px and covered
     the content that had moved left into the gap. The rail is a fixed column;
     `main` is what absorbs a layout that runs out of room. (R-RSP-014) */
  flex: 0 0 47px;
  position: relative; /* Acts as an anchor for the child */
  z-index: 100;
}

/* The rail expands on hover, which a touch screen cannot do, and it was hidden
   outright below 490px leaving no navigation at all. Below md it steps aside for
   the drawer the top bar opens; from md up it is the rail as before.
   (Bootstrap breakpoint, replacing the stray 490px — R-RSP-010) */
@media (max-width: 767.98px) {
  .sidebar {
    display: none;
  }
}

/* ===========================================================================
   Navigation drawer (below md)
   The same MainBar markup as the rail, re-laid-out: full width, labels always
   visible, no hover growth and no absolute positioning to overlap content with.
   =========================================================================== */

.nav-drawer {
  max-width: 17rem;
}

/* Top bar controls share the rail's 47px column so the logo, the menu button and
   the rail below it line up. */
/* The profile control. Transparent with an outline rather than Bootstrap's
   .btn-light, which is a near-white surface: correct on the light theme and a
   glaring disc on the dark one, where every other control in the bar is quiet.
   Tokens on both sides, so it follows the theme instead of being told twice
   (R-TOK-012). */
.top-bar-profile {
  background-color: transparent;
  border: var(--bs-border-width) solid var(--bs-border-color);
  color: var(--bs-body-color);
}

.top-bar-profile:hover,
.top-bar-profile:focus-visible {
  background-color: var(--bs-secondary-bg);
  border-color: var(--bs-border-color);
  color: var(--bs-body-color);
}

/* Was an inline style on the img. Fixed and square so the circle is a circle
   whatever the source photo is shaped like. */
.top-bar-profile-avatar {
  width: 36px;
  height: 36px;
  object-fit: cover;
}

.top-bar-button {
  width: 47px;
  flex-shrink: 0;
}

/* The two ends of the top bar. Equal and elastic below md, which is what puts
   the wordmark in the actual centre of the bar rather than merely after the left
   group — a menu button on one side and a profile menu on the other are
   different widths, and "centred" has to survive that. */
.top-bar-side {
  flex: 1 1 0;
  min-width: 0;
}

.top-bar-wordmark {
  flex: 0 1 auto;
  min-width: 0;
  text-align: center;
}

/* From md the mark is back and the name belongs beside it, reading as one lockup
   on the leading edge. The ends stop stretching so the wordmark sits where the
   logo leaves it. */
@media (min-width: 768px) {
  .top-bar-side {
    flex: 0 0 auto;
  }

  .top-bar-wordmark {
    flex: 1 1 auto;
    text-align: left;
  }
}

.nav-drawer .nav-scrollable,
.nav-drawer .nav-scrollable:hover {
  position: static;
  width: 100%;
  height: auto;
  align-items: stretch;
  border: 0;
  transition: none;
}

.nav-drawer .nav-label-text {
  opacity: 1;
  pointer-events: auto;
}

/* Both of these veil a region of the page while it is being replaced.

   They sit at z-index 3 — above the content they cover and below everything
   Bootstrap stacks: the filter drawer is an offcanvas at 1045 over a backdrop at
   1040, and at the 1060 these used to carry, a list reloading behind an open
   drawer painted straight over the drawer the operator was still working in.
   1060 is Bootstrap's own tooltip layer; nothing in this application belongs
   there.

   The ground is the theme's, not white: a hardcoded white veil flashes on every
   filter change in dark mode. */
.saving-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(var(--bs-body-bg-rgb), 0.9);
  z-index: 3;
  transition: all 0.3s ease;
}

.overlay {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: rgba(var(--bs-body-bg-rgb), 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
}

::-webkit-scrollbar {
  width: 5px;
  /* Without an explicit height a horizontal scrollbar keeps the browser default
     — roughly 15px with a dark thumb — which reads as a grey bar cutting across
     the layout rather than as a scrollbar. */
  height: 5px;
}

::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.5);
  border-radius: 0px;
  padding: 0.1rem;
}

::-webkit-scrollbar-track {
  background: transparent;
}

/* This project's primary button is a quiet bordered control, not Bootstrap's
   solid blue. Written in tokens rather than in greys, so it follows the theme:
   the literals it used to carry — #505050 text on a white active ground — were
   unreadable the moment the ground went dark.

   `transparent` is also unquoted now. It was written as the string
   "transparent", which is not a colour, so the background silently fell back to
   Bootstrap's own. */
.btn-primary {
  --bs-btn-color: var(--bs-body-color);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--bs-border-color);
  --bs-btn-hover-color: var(--bs-body-color);
  --bs-btn-hover-bg: var(--bs-secondary-bg);
  --bs-btn-hover-border-color: var(--bs-border-color);
  --bs-btn-focus-shadow-rgb: 49, 132, 253;
  --bs-btn-active-color: var(--bs-body-color);
  --bs-btn-active-bg: var(--bs-tertiary-bg);
  --bs-btn-active-border-color: var(--bs-border-color);
  --bs-btn-active-shadow: inset 0 3px 5px rgba(var(--bs-black-rgb), 0.125);
  --bs-btn-disabled-color: var(--bs-secondary-color);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--bs-border-color);
}

.no-arrows::-webkit-inner-spin-button,
.no-arrows::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.no-arrows {
  -moz-appearance: textfield;
}

.no-chevron::after {
  display: none !important;
}

.offcanvas.offcanvas-sidebar {
  background-clip: padding-box;
  pointer-events: auto; /* Ensures the drawer itself is interactive */
  box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1); /* Adds depth since there's no backdrop */
}

/* Prevent the main page from being 'unclickable' or 'frozen' */
/* Bootstrap often adds 'overflow: hidden' to the body. This forces it back to normal 
   ONLY when our specific sidebar is detected as active. */
body:has(.offcanvas-sidebar.show) {
  overflow: auto !important;
  padding-right: 0 !important; /* Prevents the 'jumping' layout shift */
}

.toast-container .toast {
  transition: transform 0.3s ease-in-out;
  /* Optional: Slide in from the right */
  animation: slideIn 0.3s forwards;
}

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* The active-filter count on the "Filtros" control.

   It replaces `.badge-sm`, which pinned `height: 1.625rem !important` — 26px,
   against the ~21px line box of the `.btn-sm` it sits inside. The badge could
   not shrink to the button's content height, so the button grew to the badge
   and ended up taller than the control beside it, with the digit riding high in
   its own box. This one is sized by the line it sits on and can never change
   the height of its host. */
.filter-count-badge {
  /* A count, so a disc rather than a label: equal width and height with no
     padding of its own, which Bootstrap's default badge padding would otherwise
     stretch into a pill. At 11px a two-digit count still fits inside the 20px
     box, and this control never counts past a handful of filters. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border-radius: 50%;
  font-size: 0.6875rem;
  /* Centred by the flex box, not by a line box: a line-height equal to the disc
     centres the *line*, which leaves a digit sitting a pixel high because its
     glyph does not fill that line symmetrically. */
  line-height: 1;
}

/* ===========================================================================
   Layout utilities Bootstrap 5.3 does not ship
   =========================================================================== */

/* Required on every flex child that must be allowed to shrink below its
   content size so an inner region can scroll instead of the page. */
.min-h-0 {
  min-height: 0 !important;
}

/* Counterpart for horizontal flex: without it a flex child refuses to shrink
   past its content and `text-truncate` on a descendant never engages. */
.min-w-0 {
  min-width: 0 !important;
}

/* Bootstrap ships min-vh-100 but no min-h-100, and a page inside the app shell
   must never be sized in viewport units — the shell is already shorter than the
   viewport by the height of the top bar (R-RSP-003). This is the percentage
   equivalent: fill the scroll container at least once, grow past it when the
   content is taller. */
.min-h-100 {
  min-height: 100% !important;
}

/* ===========================================================================
   Index screens — filter bar, table, pagination footer
   Referenced by UsersIndex / OccupationsIndex / WorkersData.
   =========================================================================== */

.background-table-controls {
  background-color: var(--bs-tertiary-bg);
}

.form-input-background {
  background-color: var(--bs-body-bg);
}

/* Filter dropdowns and selects: full width while the bar is stacked,
   fixed once it becomes a single row at lg. */
.filter-control {
  width: 100%;
}

@media (min-width: 992px) {
  .filter-control {
    width: 13.75rem;
  }
}

.filter-control-label {
  text-align: left;
  flex-grow: 1;
}

.filter-dropdown-menu {
  max-height: 18rem;
  overflow-y: auto;
  /* Explicit: an omitted overflow-x computes to auto once overflow-y is set,
     which shows a horizontal scrollbar the moment an item is too wide. */
  overflow-x: hidden;
}

/* Bootstrap sets .dropdown-item to white-space: nowrap. Occupation and project
   names are long enough to overflow a filter dropdown, so let them wrap rather
   than be clipped by the overflow-x above. */
.filter-dropdown-menu .dropdown-item {
  white-space: normal;
  overflow-wrap: break-word;
}

/* Search field pinned to the top of a scrolling filter dropdown. Bootstrap
   keeps the menu open while the click target is an input, so the field
   filters the list in place. */
/* Read-only rendering of an editable form: the same controls stay in place but
   read as text, so view and edit modes share one field definition. Disabled is
   what makes them read-only; this only removes the "unavailable" styling. */
.form-view-mode .form-control:disabled,
.form-view-mode .form-select:disabled {
  background-color: transparent;
  border-color: transparent;
  color: var(--bs-body-color);
  opacity: 1;
  padding-left: 0;
}

.form-view-mode .form-select:disabled {
  background-image: none;
}

.form-view-mode .form-check-input:disabled {
  opacity: 1;
}

.form-view-mode .form-check-input:disabled ~ .form-check-label {
  opacity: 1;
}

/* Scrim behind a modal rendered without Bootstrap's JS backdrop element. */
.modal-scrim {
  background-color: rgba(var(--bs-black-rgb), 0.5);
}

.dropdown-search-header {
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--bs-dropdown-bg);
}

.pagination-page-input {
  width: 3rem;
  text-align: right;
  padding: 0.3rem;
}

.table-cell-actions {
  white-space: nowrap;
}

/* ===========================================================================
   Comunicaciones — noticia, aviso and slider cards
   These are the only screens in the product whose record is a picture. The
   image is fixed-ratio rather than intrinsic so a grid of cards does not
   reflow as images of different shapes arrive (R-PERF-035, CLS), and the body
   is clamped rather than cut in C#: the API allows 2 000 characters and any
   cut in code is either arbitrary or lands inside a word.
   =========================================================================== */

/* Noticias and avisos share one treatment because the mobile client draws them
   the same way — a square inside a modal — so one rule rather than two that
   have to be kept in step. The rotator is the odd one and keeps its own below.

   Contained rather than cropped, and a ground behind it, so that: a record from
   before the proportion was enforced shows its own letterboxing here (which is
   what the phone will do to it), and a record with no picture at all keeps the
   same height as one with, leaving a row of cards a row rather than a step. */
.comms-card-media {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background-color: var(--bs-tertiary-bg);
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
  border-top-left-radius: var(--bs-card-inner-border-radius);
  border-top-right-radius: var(--bs-card-inner-border-radius);
}

.comms-card-media > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Three lines, then an ellipsis. Clamped in CSS rather than cut in C#: the API
   allows 2 000 characters and any cut in code is either arbitrary or lands
   inside a word. Used by all three catalogues. */
.comms-card-body {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The preview inside the image picker.
   The frame carries the required proportion when the screen set one — the ratio
   is a parameter, so the aspect-ratio itself is an inline style, which is the
   computed-value case R-TOK-023 allows. A correct image fills the frame exactly
   and a wrong one shows its own letterboxing, which is the point: the operator
   sees the mismatch before reading the message about it.
   Capped by height because these are portrait; a width cap would make the
   preview taller than the dialog. */
.image-input-frame {
  max-height: 16rem;
  width: fit-content;
  max-width: 100%;
  overflow: hidden;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

.image-input-frame > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.module-pending-note {
  max-width: 32rem;
}

/* ===========================================================================
   Gestión de documentos
   =========================================================================== */

/* The categoría stays readable while its documents scroll past, which is the
   one thing the flat list it replaces could not do. */
.documents-group-heading {
  position: sticky;
  top: 0;
  z-index: 1;
  background-color: var(--bs-tertiary-bg);
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* A row is a button so it is reachable by keyboard and announced as a control
   (R-A11Y-004); these strip the button styling back to a list row. */
.documents-row {
  background-color: transparent;
}

.documents-row:hover,
.documents-row:focus-visible {
  background-color: var(--bs-secondary-bg);
}

.documents-row.active {
  background-color: var(--bs-secondary-bg);
  box-shadow: inset 3px 0 0 0 var(--bs-primary);
}

/* The box the preview fills. Positioned, so the frame inside can be absolute
   rather than resolving a percentage height against a flex item — which is the
   reliable way to end up with a viewer zero pixels tall and no error to explain
   it. A height in vh would be wrong twice over: the pane already knows its own
   size, and vh on a phone excludes the collapsing URL bar (R-RSP-003). */
/* The checkbox column in a bulk export. Sized to its content so the columns
   that carry meaning keep the width. */
/* The running-export indicator in the top bar. Tinted so it reads as a state
   the application is in rather than as another control, and bounded so a long
   feature name cannot push the profile menu off the bar. */
.bulk-export-indicator {
  background-color: var(--bs-tertiary-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  max-width: 22rem;
  min-width: 0;
}

/* Below md the indicator is a spinner and a stop button, so it needs no width of
   its own — and must claim none. It shares the bar with the wordmark and the
   profile menu, neither of which has room to give on a phone. */
@media (max-width: 767.98px) {
  .bulk-export-indicator {
    max-width: none;
    flex-shrink: 0;
  }
}

.bulk-select-cell {
  width: 1%;
  white-space: nowrap;
}

/* Slimmer than Bootstrap's default: it sits in an action bar beside a line of
   text, not on its own. */
.bulk-progress {
  height: 0.5rem;
}

.documents-preview-box {
  position: relative;
  overflow: hidden;
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background-color: var(--bs-tertiary-bg);
}

.documents-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* An image preview fills the same box as the PDF frame, contained rather than
   cropped: a scanned document cut to fill would lose its margins, which on a
   form is where the signatures are. */
.documents-preview-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* A slider is a phone screen, so its card carries the phone's proportions and
   contains the image rather than cropping it: a rotator image cropped in the
   admin screen would hide the defect this screen exists to catch. The other two
   catalogues crop, because there the picture illustrates the record — here it
   is the record. */
.slider-card-media {
  position: relative;
  /* 4:5 — the rotator's own proportion (1081 × 1351 ≈ 0.8), confirmed by the
     product owner and enforced on upload by SliderDialog. The card and the rule
     have to agree, or a valid image would be letterboxed in the very screen
     that exists to check it. */
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background-color: var(--bs-tertiary-bg);
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
  border-top-left-radius: var(--bs-card-inner-border-radius);
  border-top-right-radius: var(--bs-card-inner-border-radius);
}

.slider-card-media > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* The position sits over the image because that is what it describes. Sized by
   its content and centred by flex, not by a line-height (R-TOK-028/029). */
.slider-card-order {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 0.375rem;
  line-height: 1;
  border-radius: var(--bs-border-radius-sm);
  color: var(--bs-body-bg);
  background-color: color-mix(in srgb, var(--bs-body-color) 75%, transparent);
}

/* A `thead.table-light` in dark mode is a pale slab across the top of a dark
   table — Bootstrap's contextual classes name a fixed colour, not a role, so
   `.table-light` stays light whatever `data-bs-theme` says. Thirty-five theads
   carry it, so the correction belongs here rather than in thirty-five files
   (R-TOK-012: the variable is what is wrong, not the markup).

   Transparent, not "dark grey": the header still reads as a header from its
   weight and the border under it, which is how it separates from the body in
   light mode too. The state variables are cleared alongside the base one
   because `.table-light` sets striped, active and hover as well, and a light
   hover fill would come back on the first pointer move. */
[data-bs-theme="dark"] .table-light {
  --bs-table-color: var(--bs-body-color);
  --bs-table-bg: transparent;
  --bs-table-border-color: var(--bs-border-color);
  --bs-table-striped-color: var(--bs-body-color);
  --bs-table-striped-bg: transparent;
  --bs-table-active-color: var(--bs-body-color);
  --bs-table-active-bg: transparent;
  --bs-table-hover-color: var(--bs-body-color);
  --bs-table-hover-bg: transparent;
}

/* ===========================================================================
   Project identity colours
   The brand colour of each contracted project, used for the avatar initials on
   list items. Tokens rather than literals so the five values live in one place
   instead of being repeated in every component that draws an avatar.
   (R-TOK-010)
   =========================================================================== */

/* The brand's own two colours, from the logo. Theme-independent values — a
   brand colour does not change with the palette — but which of the two is used
   does: see .app-wordmark below. */
:root {
  --pv-brand-navy: #242e5d;
  --pv-brand-yellow: #ffc629;
}

/* The PREVEBSA wordmark beside the logo.

   Navy on light. On dark that navy is very nearly the ground it sits on, so it
   takes the theme's body colour — the same grey the landing's own heading uses,
   so the name of the product reads identically wherever it appears. The brand
   yellow was tried here first and read as a warning: it is the loudest thing in
   a bar whose job is to be still. */
.app-wordmark {
  color: var(--pv-brand-navy);
}

[data-bs-theme="dark"] .app-wordmark {
  color: var(--bs-body-color);
}

:root {
  --pv-project-1-bg: #242e5d; /* FACTURACION ATI */
  --pv-project-1-fg: #ffffff;
  --pv-project-2-bg: #e52e38; /* PERPE */
  --pv-project-2-fg: #ffffff;
  --pv-project-3-bg: #ffc629; /* EBSA */
  --pv-project-3-fg: #000000;
  --pv-project-4-bg: #242e5d; /* SUBESTACIONES */
  --pv-project-4-fg: #ffffff;
  --pv-project-default-bg: #c3f2a5;
  --pv-project-default-fg: #000000;
}

.project-avatar {
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  background-color: var(--pv-project-default-bg);
  color: var(--pv-project-default-fg);
}

.project-avatar-1 {
  background-color: var(--pv-project-1-bg);
  color: var(--pv-project-1-fg);
}

.project-avatar-2 {
  background-color: var(--pv-project-2-bg);
  color: var(--pv-project-2-fg);
}

.project-avatar-3 {
  background-color: var(--pv-project-3-bg);
  color: var(--pv-project-3-fg);
}

.project-avatar-4 {
  background-color: var(--pv-project-4-bg);
  color: var(--pv-project-4-fg);
}

/* ===========================================================================
   Mail layout — list pane beside a detail pane
   Used by the Plannings index. Below md only one pane is on screen at a time;
   from md up both are, with the list at a fixed width.
   =========================================================================== */

/* `flex-basis: 0`, not `auto`: with `auto` the basis comes from the content, so
   a wide child — the status filter row, a long activity name — would set the
   pane's width and push the layout out instead of scrolling inside it. */
.mail-list-pane {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  /* Named container so the status filter grid can size its columns from this
     pane's width rather than the viewport's — from md up the two stop agreeing. */
  container-type: inline-size;
  container-name: mail-list;
}

.mail-detail-pane {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
}

@media (min-width: 768px) {
  .mail-list-pane {
    flex: 0 0 20rem;
    width: 20rem;
  }
}

/* A list pane whose detail side holds a nested outline splits at lg, not md
   (R-RSP-035) — three indented levels cannot be read in the ~384px an md split
   leaves. Through that range the list is the whole screen, so the fixed 20rem
   above is wrong for it: it would sit at 20rem with empty space beside it. From
   lg up the shared rule takes over again. */
@media (min-width: 768px) and (max-width: 991.98px) {
  .mail-list-pane-lg {
    flex: 1 1 0;
    width: auto;
  }
}

@media (min-width: 1200px) {
  .mail-list-pane {
    flex: 0 0 24rem;
    width: 24rem;
  }
}

/* The status filter is a plain Bootstrap dropdown now — no bespoke layout left
   to style. Its menu items need the count pinned right while the label
   truncates, which the utility classes already handle. */

/* Row controls — the options kebab, and the matrix outline's edit pencil that
   sits beside it. A square target rather than a bare glyph: at 2rem it is a
   credible touch target and, with a surface on hover, it reads as a button
   instead of another status icon. Kept at full contrast — fading it until hover
   made it look disabled and left touch users with no cue at all.

   The pencil follows that same decision rather than appearing on hover: it is
   the most-used action in the matrix, and a row that shows no sign of being
   editable is exactly the problem it was added to solve. */
.side-list-item-options,
.matrix-node-edit {
  width: 2rem;
  height: 2rem;
  border-radius: var(--bs-border-radius);
  background-color: transparent;
  transition: background-color 120ms ease-in-out;
}

.side-list-item-options:hover,
.side-list-item-options:focus-visible,
.matrix-node-edit:hover,
.matrix-node-edit:focus-visible {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

@media (prefers-reduced-motion: reduce) {
  .side-list-item-options,
  .matrix-node-edit {
    transition: none;
  }
}

/* ===========================================================================
   Signature capture
   =========================================================================== */

.signature-pad-surface {
  background-color: var(--bs-body-bg);
  height: 10rem;
}

.signature-pad-canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The browser would otherwise pan or scroll the page instead of letting the
     stroke through on a touch screen. */
  touch-action: none;
  cursor: crosshair;
}

/* Centred prompt while the pad is blank. Must not intercept the pointer, or the
   first stroke would land on the hint instead of the canvas. */
.signature-pad-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

/* ===========================================================================
   Activity matrix outline
   Nesting is shown by an indent and a rule down the left of each level, rather
   than by nested cards — three levels of card border inside a 20rem-narrower
   pane leaves almost no room for the text itself.
   =========================================================================== */

.matrix-node-children {
  padding-left: 0.75rem;
  border-left: 2px solid var(--bs-border-color);
  margin-left: 0.5rem;
}

/* Three nested levels cost 3.75rem of indent, which is affordable in a full-width
   pane and is not on a phone: the text that gets squeezed is the only part of the
   row that carries meaning. Halved below sm — the tinted rule still says which
   depth this is, so the indent does not have to say it twice. */
@media (max-width: 575.98px) {
  .matrix-node-children {
    padding-left: 0.375rem;
    margin-left: 0.25rem;
  }
}

/* The rule down each nested level is tinted by what that level holds, so an
   expanded outline says which depth you are reading without counting indents.
   Riesgo takes the hazard end of the palette, barrera the protective end.

   Mixed well down towards transparent on purpose: this is a structural rule,
   not a chip. It should be findable at a glance and invisible when you are
   reading the text beside it. */
.matrix-node-children-risks {
  border-left-color: color-mix(in srgb, var(--pvb-matrix-rule-risk) 45%, transparent);
}

.matrix-node-children-barriers {
  border-left-color: color-mix(in srgb, var(--pvb-matrix-rule-barrier) 45%, transparent);
}

.matrix-node-row {
  padding: 0.25rem 0;
}

/* Fixed-width ordinal so descriptions line up down the outline regardless of
   whether the order is 1 or 12. */
.matrix-node-order {
  min-width: 1.25rem;
  text-align: right;
  color: var(--bs-secondary-color);
}

.matrix-node-toggle {
  width: 1.5rem;
  height: 1.5rem;
  color: var(--bs-secondary-color);
  border-radius: var(--bs-border-radius);
}

.matrix-node-toggle:hover,
.matrix-node-toggle:focus-visible {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

/* .matrix-node-edit — the row's edit pencil — is styled further up, grouped with
   .side-list-item-options. The two sit next to each other on every row and have
   to match exactly, so they are declared together rather than kept in step by
   hand. */

/* Catalogue picker inside the add-node dialogs, and the row list inside the
   reorder dialog. Capped so the modal keeps its footer on screen on a laptop:
   the list is searched, not scrolled end to end, and an uncapped list of every
   paso on record would push the buttons under the fold. dvh rather than vh so a
   phone's collapsing address bar does not clip it (R-RSP-001). */
.matrix-picker-list {
  max-height: min(18rem, 40dvh);
  overflow-y: auto;
}

/* The barrera TIPO picker. Wide enough for "BARRERA DE SEGURIDAD" and capped
   there, so a queued row keeps the picker and the regla-de-oro toggle on one
   line. */
.matrix-barrier-type {
  max-width: 15rem;
}

/* Matrix chip colours.

   Bootstrap's five semantic roles cover status — active, failed, warning. They
   do not cover "which of four attributes is this", and using them for that makes
   four attributes read as four different severities. These hues carry identity
   instead: they say which attribute, not how bad.

   Every chip still carries its text, so the colour is never the only signal
   (R-UX-121). The barrier ones are the mobile client's own; the rest are chosen
   here and are the first thing to replace if the product has real ones. */
:root {
  /* Barrier types — supplied by the product owner from the mobile client. */
  --pvb-barrier-control: #00a98a;
  --pvb-barrier-safety: #00688d;
  --pvb-barrier-support: #f47121;

  /* Activity attributes. */
  --pvb-chip-route: #2f7d62;
  --pvb-chip-oneline: #8a5a00;
  --pvb-chip-graphic: #6a4c93;
  --pvb-chip-certification: #1f5fa9;

  /* Step attributes. Regla de oro keeps Bootstrap's warning role: it is closer
     to a flag than to an attribute, and gold is what it means. */
  --pvb-chip-heights: #0e7490;

  /* An activity shared by every process rather than owned by one. Deliberately
     the most muted of the set: it qualifies the row rather than describing what
     the activity involves. */
  --pvb-chip-shared: #5b6472;

  /* The rules down the nested levels of the outline. */
  --pvb-matrix-rule-risk: #c2410c;
  --pvb-matrix-rule-barrier: #0f766e;
}

/* One tinting rule for all of them, so a corrected colour needs one edit and the
   background, border and text cannot drift apart. `!important` on the border is
   to beat Bootstrap's `.border` utility, which is itself `!important`; the custom
   properties are never marked (R-TOK-005). */
.chip-tint {
  background-color: color-mix(in srgb, var(--pvb-chip) 14%, transparent);
  border-color: color-mix(in srgb, var(--pvb-chip) 40%, transparent) !important;
  color: var(--pvb-chip);
}

.chip-barrier-control {
  --pvb-chip: var(--pvb-barrier-control);
}

.chip-barrier-safety {
  --pvb-chip: var(--pvb-barrier-safety);
}

.chip-barrier-support {
  --pvb-chip: var(--pvb-barrier-support);
}

.chip-route {
  --pvb-chip: var(--pvb-chip-route);
}

.chip-oneline {
  --pvb-chip: var(--pvb-chip-oneline);
}

.chip-graphic {
  --pvb-chip: var(--pvb-chip-graphic);
}

.chip-certification {
  --pvb-chip: var(--pvb-chip-certification);
}

.chip-heights {
  --pvb-chip: var(--pvb-chip-heights);
}

.chip-shared {
  --pvb-chip: var(--pvb-chip-shared);
}

/* The matrix's selected row marks itself in success rather than primary, matching
   the level's add controls. Scoped to the matrix on purpose: Plannings and
   Workers keep the primary stripe.

   Both classes are written on the same element and both set box-shadow, so this
   is deliberately the two-class selector: one class each would tie on
   specificity and leave the winner to whichever rule sits lower in the file.

   `rgb(var(--bs-success-rgb))` and not `var(--bs-success)`, so the stripe is the
   same green the buttons actually paint. Bootstrap compiles `.btn-success` with a
   literal hex rather than reading `--bs-success`, and the `--bs-success` override
   at the top of this file is a different green from `--bs-success-rgb` — the
   utilities and buttons all follow the rgb one. See the note there. */
.side-list-item-selected.matrix-item-selected {
  box-shadow: inset 0.1875rem 0 0 0 rgb(var(--bs-success-rgb));
}

/* The position box in the reorder dialog. Sized for three digits and no more:
   it sits at the head of every row, and a full-width number input would push
   the description of a long list off to one side for no gain. */
.matrix-reorder-position {
  width: 4.5rem;
}

/* The order column in a diagnostic's actividades, riesgos and materiales. Sized
   for two digits: it is a sequence number, and a full column of it pushes the
   description — the only thing being read — into a narrow ribbon. */
.diagnostic-order-column {
  width: 3rem;
}

/* Caption under an evidence thumbnail. Held to the thumbnail's own width so a
   long title truncates instead of widening the tile and breaking the grid. */
.evidence-caption {
  max-width: 6rem;
}

/* The section-button row in a detail pane: one line that scrolls, never two
   that wrap. Its own scrollbar is hidden — the arrows beside it say the row
   moves, and a 5px bar under six buttons only steals height from the pane. */
.scrollable-tabs {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

.scrollable-tabs::-webkit-scrollbar {
  display: none;
}

/* Every button in that row keeps its width: a shrunk section button truncates to
   nothing and the row stops being readable rather than starting to scroll. */
.scrollable-tabs > .btn {
  flex-shrink: 0;
}

/* The arrows. Square, so a row of six buttons does not end in two wide ones. */
.tab-scroll-button {
  width: 1.75rem;
}

/* Evidence and diagram thumbnails across the detail sections. */
.evidence-thumb {
  width: 6rem;
  height: 6rem;
  object-fit: cover;
}

/* A row whose record carries an alert — COMPROBACION on a planning, meaning a
   preliminary question was answered in a way that wants checking.

   The tint is the subtle danger ground, not the solid one: the row still has to
   read as a row in a list, and the glyph and its screen-reader text are what
   carry the meaning (R-UX-121). Selection wins over it — the rule below is
   ordered after this one, and both are single classes — so an open record still
   looks open. */
.side-list-item-alert {
  background-color: var(--bs-danger-bg-subtle);
}

/* The selected row in a mail-style list. Bootstrap's .active on a
   .list-group-item-action inverts to solid primary, which is far too loud for a
   row that stays selected the whole time a record is open. */
.side-list-item-selected {
  background-color: var(--bs-secondary-bg);
  box-shadow: inset 0.1875rem 0 0 0 var(--bs-primary);
}

/* The section button for a tab whose content carries that alert. Bootstrap's own
   button variables are redefined rather than a competing rule written, so the
   button keeps every other behaviour it has (R-TOK-012). Outline while the tab is
   closed, filled once it is the active one, matching how the other section
   buttons switch. */
.tab-button-alert {
  --bs-btn-color: var(--bs-danger-text-emphasis);
  --bs-btn-bg: var(--bs-danger-bg-subtle);
  --bs-btn-border-color: var(--bs-danger-border-subtle);
  --bs-btn-hover-color: var(--bs-danger-text-emphasis);
  --bs-btn-hover-bg: var(--bs-danger-border-subtle);
  --bs-btn-hover-border-color: var(--bs-danger-border-subtle);
  --bs-btn-active-color: var(--bs-danger-text-emphasis);
  --bs-btn-active-bg: var(--bs-danger-border-subtle);
  --bs-btn-active-border-color: var(--bs-danger-border-subtle);
}

/* Bottom drawer holding the filter fields on small screens.

   Bootstrap fixes `.offcanvas-bottom` at `height: 30vh`, which clipped the
   fields and forced a scroll inside the drawer. Sizing to content lets the whole
   form show; the cap only engages on a very short viewport, and `overflow:
   hidden` is what lets the rounded top corners actually clip the header.

   The selector carries both classes so it outranks `.offcanvas-bottom` on
   specificity rather than relying on stylesheet order. */
.filter-drawer.offcanvas-bottom {
  height: auto;
  max-height: 90dvh;
  overflow: hidden;
  border-top-left-radius: var(--bs-border-radius-lg);
  border-top-right-radius: var(--bs-border-radius-lg);
}

.filter-drawer .offcanvas-body {
  overflow-y: auto;
}

/* Same drawer, but on an element that is only a drawer part of the time: the
   workers listing uses Bootstrap's responsive offcanvas, so from lg up the very
   same node is plain inline content. The rules therefore have to stop at the
   breakpoint — `overflow: hidden` on the inline form would clip the dropdown
   menus that open out of it, and a `max-height` would cap a row that has no
   business being capped. 991.98px is Bootstrap's own `lg` lower bound, the value
   its `offcanvas-lg` rules already switch on. */
@media (max-width: 991.98px) {
  .filter-drawer-responsive.offcanvas-bottom {
    height: auto;
    max-height: 90dvh;
    overflow: hidden;
    border-top-left-radius: var(--bs-border-radius-lg);
    border-top-right-radius: var(--bs-border-radius-lg);
  }

  .filter-drawer-responsive .offcanvas-body {
    overflow-y: auto;
  }
}

/* ---------------------------------------------------------------------------
   Tables that become cards below lg (R-RSP-040)

   The shared DataTable that is supposed to own this transformation does not
   exist yet (R-FE-040), and the settings catalogues alone hand-write thirteen
   tables. Rather than write thirteen card lists, the row is restacked in CSS:
   the head is dropped, each row becomes a block, and the cells flow.

   Layout of a stacked row: the first cell — always the record's name in these
   tables — takes the full width, and every later cell shares the line under it,
   with the last one (the actions) pushed to the far end. Cells carrying
   `data-label` print it above their value, for tables whose later columns do not
   already drop out on their own.

   `.table > :not(caption) > * > *` is Bootstrap's cell rule at (0,1,2); the
   selectors here run to (0,1,4) and win on specificity rather than order.
   --------------------------------------------------------------------------- */
@media (max-width: 991.98px) {

  .table-cards > table,
  .table-cards > table > tbody {
    display: block;
  }

  .table-cards > table > thead {
    display: none;
  }

  .table-cards > table > tbody > tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 0.5rem;
    border-bottom: var(--bs-border-width) solid var(--bs-border-color);
  }

  .table-cards > table > tbody > tr > td {
    flex: 0 0 auto;
    max-width: 100%;
    padding: 0;
    border: 0;
    box-shadow: none;
  }

  /* The name, on its own line. */
  .table-cards > table > tbody > tr > td:first-child {
    flex: 1 1 100%;
    font-weight: 600;
  }

  /* The actions, at the far end of the line below it. */
  .table-cards > table > tbody > tr > td:last-child {
    margin-left: auto;
  }

  /* A row that spans the table — an empty state — keeps the full width. */
  .table-cards > table > tbody > tr > td[colspan] {
    flex: 1 1 100%;
    margin-left: 0;
  }

  /* Labelled cells pair up, two to a line, rather than running on until the
     row wraps wherever it happens to run out of width. In the users listing that
     puts the document beside the telephone and the two state chips on the line
     under them, instead of four values and a chip strung across one line and a
     half. 45% because two of them plus the 0.5rem gap fit and three cannot. */
  .table-cards > table > tbody > tr > td[data-label] {
    flex: 1 1 45%;
    min-width: 0;
  }

  .table-cards > table > tbody > tr > td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.7rem;
    line-height: 1.2;
    color: var(--bs-secondary-color);
  }

}

/* The role list inside the user's roles dialog. A deployment with many roles
   would otherwise push the dialog's footer off a phone screen. */
.user-roles-list {
  max-height: 40dvh;
  overflow-y: auto;
}

/* The numbered step markers on the support page's deletion flow. A circle needs
   both dimensions; without them the digit sets the width and each marker is a
   slightly different oval. */
.deletion-step-number {
  width: 1.75rem;
  height: 1.75rem;
}

/* The standalone account screens — recover, reset, resend, confirm, change.
   One short form each, held to a readable column rather than the width of the
   window. */
.auth-panel {
  max-width: 26rem;
}

.auth-panel-logo {
  width: 1.75rem;
  height: auto;
}

/* ===========================================================================
   Landing
   =========================================================================== */

/* The heading scales with the viewport instead of switching at a breakpoint.
   Bootstrap has no responsive font-size utilities — the markup here once said
   `fs-3 fs-md-2`, and `fs-md-2` does not exist, so the size never changed at
   all. clamp() covers the whole range in one declaration. */
.landing-title {
  font-size: clamp(1.5rem, 1.05rem + 1.9vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  /* A measure, not a column width: past about 20 words a line stops being
     scannable however wide the screen is. */
  max-width: 18ch;
}

@media (max-width: 991.98px) {
  .landing-title {
    max-width: none;
  }
}

.landing-lead {
  max-width: 52ch;
  line-height: 1.5;
}

.landing-logo {
  width: 2.25rem;
  height: auto;
}

/* No plate: the artwork sits on the page ground. This is only a centring box —
   the illustration is capped below its column's width, so without it the image
   would sit against the left edge instead of in the middle of its half. */
.landing-media {
  display: flex;
  align-items: center;
  justify-content: center;
}

.landing-illustration {
  display: block;
  width: 100%;
  max-width: 30rem;
  max-height: 22rem;
  height: auto;
  object-fit: contain;
}

/* Dark artwork on transparency: on the dark ground it draws in its own colour.
   Inverted, with the hue put back where it started so the illustration's own
   palette survives — plain invert(1) flips hue as well as lightness. */
[data-bs-theme="dark"] .landing-illustration {
  filter: invert(1) hue-rotate(180deg);
}

/* Below lg the illustration shares the screen with everything else rather than
   half of it, so the plate tightens and the artwork is capped much shorter. At
   its desktop size a phone opened on artwork alone, with the heading and the way
   in both under the fold. */
@media (max-width: 991.98px) {
  .landing-illustration {
    max-height: 13rem;
  }
}

@media (max-width: 575.98px) {
  .landing-illustration {
    max-height: 10rem;
  }
}

/* Partner logos on the landing — ATI and EBSA.

   They were drawn with `mix-blend-mode: multiply`, which knocks the white box
   out of a logo by darkening whatever is behind it. That only works on a light
   ground: on the dark one it multiplies the artwork into the background and the
   logo disappears. Light keeps the blend; dark drops it and inverts instead, so
   dark artwork on transparency reads as light. */
.partner-logo {
  height: 2.25rem;
  width: auto;
  max-width: 100%;
  /* Knocks the white box out of a logo by darkening what is behind it. Only
     works on a light ground — see the dark rule below. */
  mix-blend-mode: multiply;
}

/* Both marks come as a pair: the light file, and one drawn for a dark ground.
   Only one of each is ever in the layout, and nothing is filtered — a supplied
   asset beats inverting the other, which shifts every colour in it.

   The blend mode goes with the light copy: on a dark ground `multiply` would
   darken the artwork into the background rather than knocking a white box out
   of it. */
.partner-logo-dark {
  display: none;
}

[data-bs-theme="dark"] .partner-logo {
  mix-blend-mode: normal;
}

[data-bs-theme="dark"] .partner-logo-light {
  display: none;
}

[data-bs-theme="dark"] .partner-logo-dark {
  display: inline-block;
}

/* Captured signatures come back at whatever resolution and aspect the tablet
   recorded — some portrait, some landscape — so the box is fixed and the image is
   fitted into it rather than the other way round.

   A landscape box, because a signature is written across: constraining only the
   height, as this used to, left a portrait capture rendering tall and narrow with
   the name unreadable. `contain` means nothing is cropped or stretched to achieve
   it, and the leftover space sits to the right of the ink. */
.signature-image {
  width: 12rem;
  max-width: 100%;
  height: 4.5rem;
  object-fit: contain;
  object-position: left center;
}

/* Signatures are black ink, usually on transparency: on the dark ground they were
   ink-on-ink and read as an empty box. Inverting turns the strokes light; the
   hue-rotate puts colour back where it started, so a signature captured in blue
   stays blue rather than turning orange. A white-backed capture inverts to a dark
   card, which is what the rest of the surface already is. */
[data-bs-theme="dark"] .signature-image {
  filter: invert(1) hue-rotate(180deg);
}

/* Same fields inside a dropdown from md up. Wide enough for a date range on one
   row; never wider than the viewport. */
.filter-dropdown-panel {
  width: min(22rem, calc(100vw - 2rem));
}

/* ===========================================================================
   Preferencias / Sistema — the settings shell
   =========================================================================== */

/* The group list. Seven groups and sixteen entries no longer fit a laptop
   viewport, so the menu scrolls on its own rather than pushing the module
   beside it off the bottom of the page. min-height: 0 is what lets a flex
   child scroll at all. */
.settings-modules-pane {
  min-width: 250px;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* The module column. Capped for line length rather than stretched across a
   wide monitor: these are forms and tables, and a 2 000px-wide table row is
   unreadable from the left edge to the right. */
.settings-module-column {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  min-height: 0;
}

@media (min-width: 1200px) {
  .settings-module-column {
    max-width: 1200px;
  }
}

/* Only inset the column once there is width to spare. Below xl the rail already
   takes 250px, so the remaining space goes to the content rather than to
   margins around it. */
@media (min-width: 1200px) {
  .settings-module-column {
    width: 90%;
  }
}

/* The rail is a rail from lg up. Below that the group list is a select and this
   pane is not rendered at all. */
@media (max-width: 991.98px) {
  .settings-modules-pane {
    min-width: 0;
  }
}

/* Row actions in a catalogue table.

   .btn-sm is pinned to 1.625rem (26px) high, which clears WCAG 2.2's 24px floor
   and misses the 44px a gloved thumb needs (R-RSP-050). On a touch-primary
   viewport the row's controls grow to 44px and gain the 8px of separation
   R-RSP-051 asks for; on a mouse they stay compact, because 44px buttons in
   every row of a dense table would halve how much of the catalogue fits.

   Keyed on the pointer, not the width: a small window on a desktop still has a
   mouse, and a large tablet still has a thumb. */
@media (hover: none) and (pointer: coarse) {
  .catalogue-row-actions {
    gap: 0.5rem !important;
  }

  .catalogue-row-actions .btn {
    min-height: 2.75rem;
    min-width: 2.75rem;
    height: auto !important;
  }
}

/* The settings group picker, below lg.

   A <select> takes its intrinsic width from its widest option — and here that is
   an optgroup label like "MATRIZ DE ACTIVIDADES · FT-MS-20". As a flex item the
   wrapper defaults to min-width:auto, so it refuses to shrink below that and the
   whole block pushes out of the viewport. min-width:0 lets it shrink; the cap on
   the select itself stops the control from doing the same thing on its own. */
.settings-modules-picker {
  min-width: 0;
  max-width: 100%;
}

.settings-modules-picker .form-select {
  max-width: 100%;
  /* Long entry names truncate rather than widen the control. */
  text-overflow: ellipsis;
}

/* The heading of a nested screen — the parent section and the screen name on
   one line, with the way back beside them (Shared/Headings SectionHeading).

   Size and wrapping are one problem, not two: "Plan diario · FT-MS-14 ·
   Descarga masiva" does not fit a phone at heading size, and on a wide screen
   it must not wrap either, because the row it sits in carries the description
   under it and a second line pushes the list down. So below lg it steps down to
   body size and is allowed to wrap; from lg it is one line that truncates.

   A media query rather than utilities because Bootstrap ships no responsive
   fs-*: .fs-5 is .fs-5 at every width. The truncation needs the min-width:0 the
   component already puts on the element (min-w-0) — without it a flex item
   refuses to shrink below its text and there is nothing to ellipsise. */
.section-heading {
  font-size: 1rem;
  line-height: 1.3;
}

@media (min-width: 992px) {
  .section-heading {
    font-size: 1.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* ------------------------------------------------------------------ dashboard */

/* A band-1 tile. The whole card is the link, so it carries the hover and the
   focus ring rather than any text inside it — and the focus ring is Bootstrap's
   own, because a keyboard user should not have to learn a second one per screen. */
.dashboard-tile {
  background-color: var(--bs-body-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  color: var(--bs-body-color);
  height: 100%;
}

.dashboard-tile:hover,
.dashboard-tile:focus-visible {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

/* The family a tile belongs to, above the count. Two of the five families are
   both "plan diario" and differ only by format code, so this line wraps rather
   than truncating: losing its tail would lose the only thing that distinguishes
   FT-MS-14 from FT-MS-20. */
.dashboard-tile-family {
  letter-spacing: 0.04em;
  line-height: 1.2;
}

/* Tabular figures so a row of counts does not jitter as they change, and so the
   digits line up down a column. */
.dashboard-tile-count {
  font-size: 1.5rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* Nothing pending. Deliberately quiet: "all clear" is good news and good news
   does not need to shout in infrastructure software. */
.dashboard-clear {
  background-color: var(--bs-success-bg-subtle);
  border: var(--bs-border-width) solid var(--bs-success-border-subtle);
}

/* One trend row. A link, like the tiles, so the fortnight leads to the listing. */
.dashboard-trend {
  color: var(--bs-body-color);
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
}

.dashboard-trend:last-child {
  border-bottom: 0;
}

.dashboard-trend:hover,
.dashboard-trend:focus-visible {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-body-color);
}

/* Fixed so the bar strips share a left edge and can be read as a stack rather
   than as five unrelated pictures. Wide enough for the longest family name —
   "Plan diario · FT-MS-20" — because the format code is the only thing telling
   the two plan families apart and must never be the part that truncates. */
.dashboard-trend-label {
  flex: 0 0 10rem;
}

/* One bar per day, sitting on a shared baseline — align-items-end on the strip is
   what puts them there, so a short bar grows upward from the floor rather than
   floating. Fixed height so every row's floor and ceiling line up down the band. */
.dashboard-bars {
  height: 2rem;
  min-width: 0;
}

/* A bar. Its height is set inline because it is genuinely computed per day, which
   is the one case R-TOK-023 allows an inline style for. Everything else about it
   belongs here. */
.dashboard-bar {
  flex: 1 1 0;
  min-width: 2px;
  border-radius: 1px;
  background-color: var(--bs-primary);
  opacity: 0.55;
}

/* A day with no records still draws its floor: a missing bar and a zero bar are
   different statements, and the gap version reads as "no data". */
.dashboard-bar-empty {
  background-color: var(--bs-border-color);
  opacity: 1;
}

/* Today, at full strength. The band is read right-to-left from "now", so the last
   bar is the one the eye needs to find first. */
.dashboard-bar-today {
  opacity: 1;
}

/* Below md the bars keep their place — they are the point of the row — but the
   figures wrap to their own line rather than squeezing them into a sliver. */
@media (max-width: 767.98px) {
  .dashboard-trend-label {
    flex: 0 0 8.5rem;
  }

  .dashboard-bars {
    height: 1.5rem;
  }

  .dashboard-trend-figures {
    display: none;
  }
}
