/* ---- Self-hosted fonts (Comfortaa for display, Inter for everything else) ---- */
@font-face {
  font-family: 'Comfortaa';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(/static/fonts/comfortaa-latin-600-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Comfortaa';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(/static/fonts/comfortaa-latin-700-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Inter Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(/static/fonts/inter-latin-wght-normal.woff2) format('woff2-variations');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ---- Palette (ported from frontend/src/theme.ts) ---- */
:root {
  --color-navy: #0A2A3D;
  --color-navy-dark: #071D2B;
  --color-cyan: #0DB8D8;
  --color-bone: #FBF9F5;
  --color-layout-bg: #F1EEE8;
  --color-danger: #D92D20;
  --color-success: #12B76A;
  --color-warning: #FBBF24;

  --bracket-color: rgba(10, 42, 61, 0.25);
  --bracket-color-active: var(--color-cyan);
  --bracket-bg: rgba(13, 184, 216, 0.04);
  --bracket-bg-active: rgba(13, 184, 216, 0.08);
  --password-toggle-color: rgba(10, 42, 61, 0.5);
}

* { box-sizing: border-box; }

/* Thin, rounded, no visible track everywhere in the app (dashboard widgets,
   líneas tables, data tables, formwindow bodies...) instead of the
   browser's default chunky scrollbar - scrollbar-color is inherited, so
   setting it once on :root covers every scrollable element for Firefox;
   Chrome/Safari/Edge need the ::-webkit- rules repeated per element instead
   (not inherited), hence the universal selector (see conversation with the
   user). */
:root { scrollbar-width: thin; scrollbar-color: rgba(10, 42, 61, 0.25) transparent; }
*::-webkit-scrollbar { width: 4px; height: 4px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: rgba(10, 42, 61, 0.25); border-radius: 4px; }
*::-webkit-scrollbar-thumb:hover { background: rgba(10, 42, 61, 0.4); }

html, body { height: 100%; margin: 0; }

body {
  font-family: 'Inter Variable', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--color-layout-bg);
  color: var(--color-navy);
  /* Wide content (a data table on a narrow screen) must scroll inside its
     own .table-scroll wrapper, never stretch the page itself sideways. */
  overflow-x: hidden;
}

.font-display {
  font-family: 'Comfortaa', -apple-system, sans-serif;
  font-weight: 700;
}

/* Data is stored lowercase (see backend internal/platform/normalize) - these
   capitalize/uppercase it for display only, never touch the stored value.
   .cap-display for name-like fields (person/place names, currency names);
   .upper-display for identifiers conventionally shown uppercase (an ISO
   4217 currency code: "gtq" stored, "GTQ" displayed). */
.cap-display { text-transform: capitalize; }
.upper-display { text-transform: uppercase; }

a { color: inherit; }

/* ---- Page shell: every protected page's body wraps its content in this,
   leaving room below the fixed-height area the floating menu might overlap
   on small screens (menu itself floats, doesn't reserve layout space). ---- */
.page {
  padding: 24px;
}
@media (max-width: 640px) {
  .page { padding: 16px; }
}

.card {
  background: var(--color-bone);
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(10, 42, 61, 0.12);
  padding: 20px;
}

.card-title {
  font-size: 18px;
  margin: 0;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

/* ---- Section grouping within a tab (e.g. Configuración > General) ---- */
.section-heading { font-size: 14px; margin-bottom: 12px; color: rgba(10, 42, 61, 0.85); }
.section-divider { border-top: 1px solid rgba(10, 42, 61, 0.1); margin: 28px 0 20px; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 18px;
  border-radius: 8px;
  border: none;
  background: var(--color-cyan);
  color: var(--color-navy-dark);
  font-family: 'Comfortaa', -apple-system, sans-serif;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.btn:hover { filter: brightness(0.95); }
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  filter: none;
}
/* .btn's own display:inline-flex above beats the browser's own [hidden]
   rule (same gotcha as .fm-dropdown/.tab-panel elsewhere in this file) - a
   .btn hidden via JS stayed fully visible AND clickable without this,
   which is how CompraForm's "+ Añadir línea" (afecta_almacen toggle) and
   VentaForm's "+ Pago" (create vs edit) both hide themselves (see
   conversation with the user). */
.btn[hidden] { display: none; }
.btn-block { display: flex; width: 100%; }
.btn-danger { background: var(--color-danger); color: #fff; }
.btn-secondary { background: transparent; color: var(--color-navy); border: 1px solid rgba(10,42,61,0.2); }
.btn-informe-ventas { background: var(--color-navy); color: #fff; }

/* ---- Bracket-style input: no box, just [ marks that light up on focus.
   Ported from frontend/src/index.css - same visual language, minus the
   AntD-specific selectors (plain <input>/<select> here instead).
   ".bracket-field" is just the label+box container; the brackets themselves
   live on the inner ".bracket-box" so a <label> sitting above it (a sibling,
   not a child of the bracket-marked element) never ends up inside the
   brackets. ---- */
/* position:relative so a .tag-input-dropdown inside (a sibling of
   .bracket-box.tag-input-options, not its child - .tag-input-options'
   own position:relative doesn't apply to it) anchors here instead of
   falling back to a distant positioned ancestor (e.g. .formwindow itself),
   which rendered the dropdown pinned near the window's top-left instead of
   under its own input (see conversation with the user). */
.bracket-field { margin-bottom: 16px; position: relative; }
.bracket-field label {
  display: block;
  font-size: 12px;
  padding: 0 2px 4px;
  opacity: 0.7;
}
/* Swaps into a phone field's own label (see clientes.js/proveedores.js) to
   warn about a rejected letter - reuses the label's existing space instead
   of adding/removing a separate message element, so the layout never shifts
   when the warning appears or disappears (see conversation with the user). */
.bracket-field label.label-warning {
  color: var(--color-danger);
  opacity: 1;
}
/* Required-field marker - general convention, every form's required label
   gets this prefix (see conversation with the user). */
.field-required {
  color: var(--color-danger);
  opacity: 1;
  margin-right: 2px;
}
.form-hint {
  margin: 4px 2px 0;
  font-size: 12px;
  color: var(--color-cyan);
}
.bracket-box {
  position: relative;
  background: var(--bracket-bg);
  border-radius: 6px;
  transition: background-color 0.15s;
}
.bracket-box:focus-within { background: var(--bracket-bg-active); }
.bracket-box::before,
.bracket-box::after {
  content: '';
  position: absolute;
  top: 2px;
  bottom: 2px;
  width: 6px;
  border: 1px solid var(--bracket-color);
  pointer-events: none;
  transition: border-color 0.15s, border-width 0.15s;
}
.bracket-box::before { left: 0; border-right: none; }
.bracket-box::after { right: 0; border-left: none; }
.bracket-box:focus-within::before,
.bracket-box:focus-within::after {
  border-color: var(--bracket-color-active);
  border-width: 2px;
}
.bracket-box .bracket-input {
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  padding: 8px 14px;
  font: inherit;
  color: inherit;
}
.bracket-box.field-error { background: rgba(217, 45, 32, 0.06); }
/* A disabled field that's still unlockable (e.g. Almacén's stock via
   double-click, see almacen.js) - visibly locked, not just plain grey. */
/* pointer-events: none - a disabled input still captures the hit-test at
   its own position even though it fires no events itself, so without this
   a click/dblclick meant for the wrapping .bracket-box (see almacen.js's
   stock double-click unlock) never reaches it either. */
.bracket-box .bracket-input:disabled { opacity: 0.55; cursor: not-allowed; pointer-events: none; }
/* Show/hide eye icon (see static/js/passwordToggle.js) - the input needs
   room on the right so its own text never runs under the button. */
.bracket-box:has(.password-toggle) .bracket-input { padding-right: 34px; }
.password-toggle {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 2px;
  cursor: pointer;
  color: var(--password-toggle-color);
  display: flex;
  align-items: center;
}
.password-toggle svg { width: 16px; height: 16px; }
.password-toggle:hover { color: var(--color-navy); }
/* Browsers (and password managers) force their own opaque background on an
   autofilled field via a UA style a plain "background" override can't beat -
   paint over it with an inset shadow the size of the field instead, and
   force the text color (autofill locks that too). The huge transition delay
   stops the color flashing back to the UA default right after autofill. */
.bracket-box input:-webkit-autofill,
.bracket-box input:-webkit-autofill:hover,
.bracket-box input:-webkit-autofill:focus,
.bracket-box input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
  box-shadow: 0 0 0px 1000px transparent inset !important;
  -webkit-text-fill-color: currentColor !important;
  caret-color: currentColor;
  transition: background-color 600000s 0s, color 600000s 0s;
}
.bracket-box.field-error::before,
.bracket-box.field-error::after { border-color: var(--color-danger); }
.field-error-message {
  color: var(--color-danger);
  font-size: 12px;
  margin: -12px 0 12px 14px;
}
/* Swapped into a field's own <label> in place of its normal text, for an
   error that's specific to that one field (e.g. stock insuficiente) -
   scrolling straight to the field with the message already on it reads
   better than a banner the user has to correlate back to a field, and it's
   the same DOM either way regardless of viewport size (ver conversación
   con el usuario: pensado sobre todo para móvil, pero igual en PC). */
.field-label-error { color: var(--color-danger); }

/* ---- Floating draggable nav menu ---- */
.floating-menu {
  position: fixed;
  /* Above every FormWindow (those start at z-index 3000 and only climb
     higher as they're brought to front - see formWindow.js) so the menu
     stays reachable/clickable no matter how many edit windows are open,
     but below Modal/Toast (z-index 9000) which must interrupt everything,
     the menu included. */
  z-index: 8000;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 10px;
  background: rgba(10, 42, 61, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 14px;
  backdrop-filter: blur(8px);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
  max-width: calc(100vw - 40px);
  user-select: none;
}
.fm-drag-handle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  color: rgba(255, 255, 255, 0.5);
  cursor: grab;
  flex-shrink: 0;
  touch-action: none;
}
.fm-drag-handle:active { cursor: grabbing; }
.fm-logo {
  position: relative;
  display: flex;
  align-items: center;
  cursor: pointer;
  padding: 2px;
  border-radius: 8px;
  flex-shrink: 0;
}
.fm-logo:hover { background: rgba(255, 255, 255, 0.08); }
.fm-logo-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: #fff;
  flex-shrink: 0;
  overflow: hidden;
}
.fm-logo-badge img { width: 22px; height: 22px; object-fit: contain; }
.fm-dropdown {
  position: absolute;
  top: 46px;
  left: 8px;
  z-index: 1001;
  display: flex;
  flex-direction: column;
  min-width: 140px;
  background: var(--color-bone);
  border-radius: 8px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}
/* "display: flex" above beats the browser's own [hidden] rule (same
   specificity, but author styles always win over the UA stylesheet) -
   restate it explicitly so JS toggling the hidden attribute actually hides
   this. */
.fm-dropdown[hidden] { display: none; }
.fm-dropdown a,
.fm-dropdown button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  background: none;
  border: none;
  font: inherit;
  color: var(--color-navy);
  cursor: pointer;
  text-decoration: none;
}
.fm-dropdown a:hover,
.fm-dropdown button:hover { background: rgba(13, 184, 216, 0.1); }
.fm-dropdown-danger { color: var(--color-danger) !important; }
.fm-title {
  color: #fff;
  font-size: 14px;
  white-space: nowrap;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
}
.fm-divider {
  width: 1px;
  height: 28px;
  background: rgba(255, 255, 255, 0.15);
  flex-shrink: 0;
}
.fm-items {
  display: flex;
  align-items: center;
  gap: 4px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  max-width: max-content;
  scroll-behavior: smooth;
}
.fm-items::-webkit-scrollbar { display: none; }
.fm-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  flex-shrink: 0;
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.fm-item svg { width: 18px; height: 18px; }
.fm-item:hover { background: rgba(13, 184, 216, 0.2); color: var(--color-cyan); }
.fm-item-active { background: rgba(13, 184, 216, 0.25); color: var(--color-cyan); }
.fm-more-hint {
  display: flex;
  align-items: center;
  color: rgba(13, 184, 216, 0.7);
  flex-shrink: 0;
}
.fm-more-hint[hidden] { display: none; }
.fm-collapse-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  border-radius: 6px;
  flex-shrink: 0;
}
.fm-collapse-toggle:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.fm-collapsed .fm-title,
.fm-collapsed .fm-divider,
.fm-collapsed .fm-items,
.fm-collapsed .fm-more-hint { display: none; }
.fm-collapsed .fm-collapse-toggle svg { transform: rotate(180deg); }

/* ---- Login page ---- */
.login-screen {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  background: var(--color-navy);
  padding: 16px;
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 14px;
  color: #fff;
}
.login-brand-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 12px;
  background: #fff;
  overflow: hidden;
}
.login-brand-badge img { width: 48px; height: 48px; object-fit: contain; }
.login-brand-name { font-size: 28px; }
.login-card {
  width: 100%;
  max-width: 360px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  padding: 32px 28px;
}
.login-card-heading {
  text-align: center;
  color: var(--color-cyan);
  letter-spacing: 2px;
  font-size: 14px;
  margin-bottom: 24px;
}
.login-card form {
  --bracket-color: rgba(245, 247, 250, 0.35);
  --bracket-color-active: var(--color-cyan);
  --bracket-bg: rgba(13, 184, 216, 0.06);
  --bracket-bg-active: rgba(13, 184, 216, 0.12);
  --password-toggle-color: rgba(245, 247, 250, 0.55);
  color: #F5F7FA;
}
.login-card .bracket-input { color: #F5F7FA; }
.login-card .field-error-message { color: #F87171; }
.login-card .password-toggle:hover { color: #fff; }

/* ---- Modal (InfoModal): centered, non-draggable, for confirmations/info. ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  /* Must always beat FormWindow's z-index (starts at 3000, +1 per window
     opened - see formWindow.js) - otherwise a Modal opened while a
     FormWindow is up (e.g. a failed save's error) renders hidden behind
     it, looking like nothing happened. */
  z-index: 9000;
  background: rgba(7, 29, 43, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.modal-box {
  background: var(--color-bone);
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  max-width: 420px;
  width: 100%;
  padding: 24px;
}
.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.modal-title { margin: 0; font-size: 17px; }
.modal-close {
  background: none;
  border: none;
  color: rgba(10, 42, 61, 0.5);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
  flex-shrink: 0;
}
.modal-close:hover { color: var(--color-navy); }
.modal-body { margin: 0 0 20px; color: rgba(10, 42, 61, 0.85); }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* ---- Form window: draggable + resizable floating panel for create/edit
   forms - opens over the current page without navigating away from it or
   dimming it, so the page underneath stays exactly as the user left it. ---- */
.formwindow {
  position: fixed;
  display: flex;
  flex-direction: column;
  background: var(--color-bone);
  border-radius: 10px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
  min-width: 280px;
  min-height: 160px;
  overflow: hidden;
}
.formwindow-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--color-navy);
  color: #fff;
  cursor: grab;
  touch-action: none;
  flex-shrink: 0;
}
.formwindow-titlebar:active { cursor: grabbing; }
/* Unsaved changes (see formWindow.js's dirty-tracking) - amber instead of
   the navy default, distinct from --color-danger (that means "error", not
   "unsaved"). Dark text/close-button since amber is too light for white. */
.formwindow-titlebar.fw-dirty {
  background: var(--color-warning);
  color: var(--color-navy-dark);
}
.formwindow-titlebar.fw-dirty .formwindow-close { color: rgba(10, 42, 61, 0.6); }
.formwindow-titlebar.fw-dirty .formwindow-close:hover { color: var(--color-navy-dark); }
.formwindow-close {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.7);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
}
.formwindow-close:hover { color: #fff; }
/* Dashboard's floating Tareas pendientes window (see dashboard.js) - dark red
   titlebar, and the tasks' text in the same red (see conversation with the
   user). */
.formwindow.fw-tareas .formwindow-titlebar { background: #880000; }
.formwindow.fw-tareas .dashboard-row-name,
.formwindow.fw-tareas .dashboard-row-code,
.formwindow.fw-tareas .dashboard-row-date,
.formwindow.fw-tareas .dashboard-row-arrow { color: #880000; }
.formwindow.fw-tareas .dashboard-row-name { font-weight: 700; }
.formwindow-body {
  padding: 20px;
  overflow: auto;
  flex: 1 1 auto;
}
.formwindow-resize-handle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
}
.formwindow-resize-handle::after {
  content: '';
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 8px;
  height: 8px;
  border-right: 2px solid rgba(10, 42, 61, 0.3);
  border-bottom: 2px solid rgba(10, 42, 61, 0.3);
}

/* ---- Simple tab widget (Configuración) - no Bootstrap tabs, JS just
   toggles which panel is visible. ---- */
.tabs-bar { display: flex; gap: 4px; border-bottom: 1px solid rgba(10,42,61,0.12); margin-bottom: 20px; }
.tab-btn {
  background: none;
  border: none;
  padding: 10px 16px;
  font: inherit;
  font-weight: 600;
  color: rgba(10,42,61,0.6);
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.tab-btn:hover { color: var(--color-navy); }
.tab-btn.tab-active { color: var(--color-navy); border-bottom-color: var(--color-cyan); }
.tab-panel[hidden] { display: none; }

.link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--color-cyan);
  cursor: pointer;
  font: inherit;
  text-decoration: underline;
}
.link-btn:hover { color: var(--color-navy); }

/* ---- Search-first list pattern (mirrors useSearchList.ts) - the search box
   uses the same bracket-box language as form fields (standalone, no
   .bracket-field/<label> wrapper needed here). ---- */
/* Mobile-first, like .url-check-row in Configuración: stacked (text field's
   own line, then a row with whatever else - ámbito select, Buscar) by
   default, row layout only from 641px up. flex-wrap+flex-basis was tried
   here first and turned out unreliable (see conversation with the user) -
   this is the pattern that actually held up. */
.search-bar { display: block; margin-bottom: 16px; }
.search-bar-box { margin-bottom: 8px; }
.search-bar-actions { display: flex; gap: 8px; }
@media (min-width: 641px) {
  .search-bar { display: flex; gap: 8px; max-width: 640px; }
  .search-bar-box { flex: 1; margin-bottom: 0; }
  .search-bar-actions { display: contents; }
}

/* ---- CRUD forms hosted inside a FormWindow ---- */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 4px 16px;
}
.form-full { grid-column: 1 / -1; }
.form-span2 { grid-column: span 2; }

/* ---- Lightweight 12-column grid (same mental model as Bootstrap's grid,
   ~20 lines instead of a whole library) - CSS Grid, not flexbox: with 12
   equal 1fr tracks, the browser subtracts the row's gap from the available
   width before sizing them, so columns summing to 12 (e.g. 8+2+2) fill the
   row exactly without overflowing past 100% the way flex item widths +
   gap would (that was the bug making the last column in a full row wrap
   early). A .col-N spans exactly N of those 12 tracks; a row that doesn't
   use all 12 just leaves the remaining tracks blank - nothing stretches to
   fill them. Used the way Bootstrap's "container-fluid" is used (no
   max-width wrapper around the row), per the user. */
.grid-row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 4px 16px; }
/* min-width:0 on every column - a grid item defaults to min-width:auto,
   so wide content (e.g. a .lineas-table, min-width:600px) inside a column
   pushes the whole row/page wider instead of scrolling inside its own
   .lineas-scroll/.table-scroll wrapper like it's supposed to (bit us on
   Simulaciones' costes fijos/productos tables in a col-4 on mobile - ver
   conversación con el usuario). Doesn't affect columns with normal-width
   content, since min-width:0 only matters once something inside is wider
   than the column and needs to know it's allowed to clip/scroll. */
.col-1 { grid-column: span 1; min-width: 0; } .col-2 { grid-column: span 2; min-width: 0; } .col-3 { grid-column: span 3; min-width: 0; }
.col-4 { grid-column: span 4; min-width: 0; } .col-5 { grid-column: span 5; min-width: 0; } .col-6 { grid-column: span 6; min-width: 0; }
.col-7 { grid-column: span 7; min-width: 0; } .col-8 { grid-column: span 8; min-width: 0; } .col-9 { grid-column: span 9; min-width: 0; }
.col-10 { grid-column: span 10; min-width: 0; } .col-11 { grid-column: span 11; min-width: 0; } .col-12 { grid-column: span 12; min-width: 0; }
/* Informes: centra la card de la moneda predeterminada (col-6, en su propia
   fila) - con columnas 1fr justify-content no la centra (siempre rellenan
   el ancho), así que se coloca a mano en las columnas 4 a 9. Menor
   especificidad que la regla de abajo (.grid-row > [class*="col-"]), así
   que en móvil pasa a ocupar todo el ancho igual que las demás - ver
   conversación con el usuario, se veía más estrecha que el resto en móvil
   porque antes esto era un style inline, que ninguna regla puede ganarle.
*/
.informe-destacada { grid-column: 4 / span 6; }
@media (max-width: 640px) {
  .grid-row { grid-template-columns: 1fr; }
  .grid-row > [class*="col-"] { grid-column: 1 / -1; }
}
/* A .grid-row inside a FormWindow needs to collapse when the WINDOW itself
   gets narrow (dragged/resized smaller by the user), not just when the
   browser viewport does - a media query only ever sees the viewport, so a
   narrow floating window on a wide screen never triggered the rule above,
   and its columns just got squeezed proportionally thinner instead of
   stacking (illegible labels/selects) - see conversation with the user.
   container-type here turns .formwindow-body into its own query context;
   same collapse rule as the media query above, just container-scoped. */
.formwindow-body { container-type: inline-size; }
@container (max-width: 640px) {
  .formwindow-body .grid-row { grid-template-columns: 1fr; }
  .formwindow-body .grid-row > [class*="col-"] { grid-column: 1 / -1; }
}
/* A field that stands alone (not part of a .grid-row) but shouldn't
   stretch to the container's full width - e.g. a lone number input. */
.field-narrow { max-width: 160px; }

/* Input + "Detectar"/"Comprobar" buttons (Configuración > Preferencias) -
   mobile-first: stacked (input's own line, buttons below) by default, row
   layout only from 641px up - avoids depending on a max-width override
   winning against the base rule. */
.url-check-row { display: block; }
.url-check-row .bracket-box { margin-bottom: 8px; }
.url-check-actions { display: flex; gap: 8px; }
@media (min-width: 641px) {
  .url-check-row { display: flex; gap: 8px; }
  .url-check-row .bracket-box { flex: 1; margin-bottom: 0; }
}
.bracket-field select.bracket-input,
.bracket-field textarea.bracket-input { width: 100%; font: inherit; }
.bracket-field textarea.bracket-input { resize: vertical; }
.form-actions {
  display: flex;
  justify-content: flex-end;
  /* Without this, a .form-actions sharing a .grid-row with a taller sibling
     (e.g. Compras' Notas textarea) stretches to that height by CSS Grid's
     default align-items:stretch, and its own buttons then stretch to fill
     it too (flex's own default) - keeps them their natural height instead. */
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}
/* Nudges the "Nuevo proveedor"/"Nuevo producto" quick-add buttons down to
   line up with the bracket-field's input next to them (which has a label
   above it these buttons don't) - a touch less than .bracket-field label's
   own rendered height, which was overshooting it a little (see conversation
   with the user). */
.cf-quick-add-row { margin-top: 16px; }
.btn-quick-add { background: rgba(13, 184, 216, 0.12); border-color: transparent; }
/* Sits directly in a .bracket-field with a blank label instead of the
   margin-top trick above - lines up with any neighboring input regardless
   of that field's own label text/length (see conversation with the user).
   .btn's own 8px vertical padding already matches .bracket-input's, so no
   extra offset is needed once the label height matches. */
.vf-quick-add-btn { width: 100%; }
.form-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px 0 16px;
}
/* width:100% + no min-width + nowrap gives the best of both: columns spread
   out to fill the card when content is narrow (short "Sí"/"No" tags, say),
   but still grow past 100% (into .table-scroll's overflow-x) instead of
   wrapping/squeezing when content genuinely needs more room (a long
   Guatemalan address) - auto table layout takes the larger of the two. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.data-table { width: 100%; border-collapse: collapse; white-space: nowrap; }
table.data-table th,
table.data-table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(10, 42, 61, 0.08);
}
table.data-table th { font-size: 12px; text-transform: uppercase; opacity: 0.6; }
.table-actions { display: flex; gap: 10px; align-items: center; }
.table-actions button {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-navy);
  opacity: 0.7;
  padding: 4px;
  font-size: 22px;
  line-height: 1;
}
.table-actions button:hover { opacity: 1; color: var(--color-cyan); }
.table-actions .btn-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--color-danger);
  color: #fff;
  opacity: 1;
  font-size: 12px;
}
.table-actions .btn-delete:hover { background: #b32418; color: #fff; }
/* Borrar definitivamente una fila ya anulada - una X roja en negrita, sin el
   círculo de "Anular", para que no se confundan (ver conversación con el
   usuario). */
.table-actions .btn-eliminar { color: var(--color-danger); opacity: 1; font-size: 18px; font-weight: 900; }
.table-actions .btn-eliminar:hover { color: #b32418; }
.empty-state { text-align: center; padding: 40px 0; opacity: 0.6; }

.tag {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(10, 42, 61, 0.08);
  color: var(--color-navy);
}
.tag-success {
  background: rgba(18, 183, 106, 0.15);
  color: #0a7a45;
  cursor: default;
}
.tag-blue { background: rgba(13, 110, 253, 0.12); color: #0a4fb3; }
.tag-danger { background: rgba(217, 45, 32, 0.12); color: #a3241a; }
.tag-warning { background: rgba(245, 158, 11, 0.15); color: #92610a; }
.tag-purple { background: rgba(147, 51, 234, 0.12); color: #6b21a8; }

/* ---- Toast: auto-dismissing save confirmation ---- */
.toast {
  position: fixed;
  top: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(-20px);
  background: var(--color-navy);
  color: #fff;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
  /* Same reasoning as .modal-overlay above - stay above any open FormWindow. */
  z-index: 9000;
  opacity: 0;
  transition: opacity 0.2s, transform 0.2s;
  pointer-events: none;
}
.toast-visible { opacity: 1; transform: translateX(-50%) translateY(0); }
/* On mobile, anchor to the bottom instead of the top - if the person is
   scrolled down interacting with something near the bottom of a long page
   (e.g. a save button), a toast at the top is easy to miss entirely (see
   conversation with the user). Desktop keeps top, chosen earlier for its
   own visibility. */
@media (max-width: 640px) {
  .toast { top: auto; bottom: 24px; transform: translateX(-50%) translateY(20px); }
  .toast-visible { transform: translateX(-50%) translateY(0); }
}

/* ---- Configuración tabs + logo row + right-aligned action bar ---- */
.actions-bar-right { display: flex; justify-content: flex-end; margin-bottom: 16px; }
.logo-row { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.logo-preview {
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(10, 42, 61, 0.12);
  border-radius: 8px;
  background: #fff;
  overflow: hidden;
}

/* ---- Print (PresupuestoImprimir) ---- */
@media print {
  .no-print { display: none !important; }
}

/* ---- Dashboard widgets ---- */
.widget {
  border-radius: 8px;
  padding: 16px;
}
.widget[data-tint="cyan"] { background: rgba(13, 184, 216, 0.06); border: 1px solid rgba(13, 184, 216, 0.25); }
.widget[data-tint="navy"] { background: rgba(10, 42, 61, 0.06); border: 1px solid rgba(10, 42, 61, 0.25); }
.widget[data-tint="danger"] { background: rgba(217, 45, 32, 0.06); border: 1px solid rgba(217, 45, 32, 0.25); }
.widget[data-tint="warning"] { background: rgba(251, 191, 36, 0.08); border: 1px solid rgba(251, 191, 36, 0.35); }
.widget[data-tint="success"] { background: rgba(18, 183, 106, 0.06); border: 1px solid rgba(18, 183, 106, 0.25); }
.widget-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.widget-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: rgba(10, 42, 61, 0.65);
}
.widget-title svg { width: 14px; height: 14px; }
.widget-count { font-size: 20px; color: var(--color-navy); }
/* ~2 rows tall before scrolling - keeps every widget card the same compact
   height regardless of how many records it has (see conversation with the
   user). */
.widget-body { max-height: 110px; overflow-y: auto; }
.dashboard-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 8px 4px;
  border-bottom: 1px solid rgba(10, 42, 61, 0.08);
  cursor: pointer;
}
.dashboard-row:hover { background: rgba(13, 184, 216, 0.06); }
.dashboard-row-name {
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.dashboard-row-code { font-size: 11px; color: rgba(10, 42, 61, 0.5); }
.dashboard-row-meta { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.dashboard-row-date { font-size: 11px; color: rgba(10, 42, 61, 0.5); }
.dashboard-row-arrow { font-size: 10px; color: rgba(10, 42, 61, 0.35); }
/* Dashboard's "Compras pendientes de recibir" widget - two stacked lines
   (proveedor-código/fecha, then producto/nº líneas) instead of the other
   widgets' name-block + meta-block side by side (see conversation with the
   user). */
.dashboard-row-compra { flex-direction: column; align-items: stretch; gap: 2px; }
.dashboard-row-compra .dashboard-row-line { display: flex; justify-content: space-between; align-items: center; gap: 8px; }

/* ---- Presupuestos: líneas editor ---- */
.lineas-scroll { max-height: 220px; overflow-y: auto; overflow-x: auto; padding-right: 4px; margin-bottom: 12px; }
/* Simulaciones' Productos/Gastos start with just one or two filas, well
   under max-height - adding rows later makes the vertical scrollbar appear
   and steal width from the columns that was there a moment ago, shifting
   everything (see conversation with the user). scrollbar-gutter:stable
   reserves that space from the start regardless of whether a scrollbar is
   actually needed yet, so it never shows up later as a layout shift.
   Scoped to these two tables only, not every .lineas-scroll in the app. */
.lineas-scroll:has(#sf-lineas-table),
.lineas-scroll:has(#sf-costes-table) { scrollbar-gutter: stable; }
/* Pagos (Ventas): más baja que el editor de líneas - menos filas ocultas
   detrás del header pegajoso al hacer scroll dentro (ver conversación con
   el usuario). */
.vf-pagos-scroll { max-height: 150px; }
/* border-collapse:collapse rompe position:sticky en las th de algunos
   navegadores - separate + spacing:0 se ve igual pero deja el sticky
   funcionando (ver conversación con el usuario: las filas tapaban las
   labels de las columnas al hacer scroll). */
table.lineas-table { width: 100%; min-width: 600px; border-collapse: separate; border-spacing: 0; }
table.lineas-table th {
  text-align: left;
  font-size: 12px;
  color: rgba(10, 42, 61, 0.65);
  font-weight: 400;
  padding: 4px;
  position: sticky;
  top: 0;
  /* Sin esto, las esquinas [] de .bracket-box (position:relative, más
     abajo en el DOM que el thead) ganaban el pintado por venir después,
     aunque el header ya estuviera sticky (ver conversación con el
     usuario). */
  z-index: 2;
  background: var(--color-bone);
}
table.lineas-table th:not(:first-child),
table.lineas-table td:not(:first-child) { text-align: right; }
table.lineas-table td { padding: 4px; vertical-align: top; }
table.lineas-table .bracket-field { margin-bottom: 0; }
/* Importe is a bare td (no bracket-box around it), which is shorter than a
   bracket-input's own 8px top padding - nudged down so its text lands at the
   same height as Cantidad/Pr. unitario's input text (see conversation with
   the user). */
table.lineas-table td.cf-l-importe { padding-top: 12px; }
.cf-l-cant, .cf-l-precio { text-align: right; }
.vf-l-cant, .vf-l-precio, .vf-l-desc-pct, .vf-p-importe { text-align: right; }
/* Small unit hint ("Kg"/"Li"/"Un"...) inside Cantidad's own box, next to
   this línea's producto - each línea can carry a different unidad, so this
   lives per-row rather than in the shared column header (see conversation
   with the user). */
.bracket-box .cf-l-cant, .bracket-box .vf-l-cant { padding-right: 26px; }
.cf-l-unidad-suffix {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  font-size: 11px;
  color: rgba(10, 42, 61, 0.5);
  pointer-events: none;
}
/* Left-aligned (not the column's default right-align) and the same on both
   the header checkbox and every línea's own - so the master checkbox lines
   up with a marked línea's checkbox instead of sitting off to the right
   while the per-línea ones center themselves (see conversation with the
   user). */
table.lineas-table th#cf-recibido-head,
table.lineas-table td.cf-l-recibido-cell { text-align: left; }
/* Nudged down to land at the same height as Importe's own 12px offset above
   (see conversation with the user) - margin-top alone didn't move a native
   checkbox in every browser, position+top does reliably. */
.cf-l-recibido { position: relative; top: 5px; }
/* Simulaciones' costes fijos/productos tables sit inside a .col-4/.col-5
   (roughly a third to two fifths of the card), not a full-width FormWindow
   like every other .lineas-table - the default 600px min-width forced an
   unnecessary horizontal scrollbar on desktop even though the (fewer,
   narrower) fields fit comfortably at that width (see conversation with
   the user). .lineas-scroll's overflow-x:auto stays as a fallback for
   genuinely narrow viewports. */
#sf-costes-table, #sf-lineas-table { min-width: 0; }
/* Gastos crams Concepto + el <select> de Producto + Importe into a narrower
   column than any other .lineas-table in the app - the shared bracket-input
   padding (8px 14px, sized for a standalone form field) left almost no room
   for the fields themselves. Scoped to #sf-costes-table only, not the
   .lineas-table class, so every other domain's line-item tables (and
   Simulaciones' own Productos table) keep the normal padding (see
   conversation with the user). */
#sf-costes-table .bracket-input { padding: 4px 8px; }
/* El editor de simulaciones necesita priorizar los campos editables. En
   escritorio Productos gana una columna a costa del resumen; en tablet el
   resumen baja a la segunda fila, dejando más anchura a ambas tablas. */
#sf-lineas-table .bracket-input { padding-left: 8px; padding-right: 8px; }
.simulacion-editor-grid .sf-resumen-col .resumen-row { gap: 8px; padding-left: 5px; padding-right: 5px; }
.simulacion-editor-grid .sf-resumen-col .resumen-row span:last-child { flex-shrink: 0; }
@media (min-width: 641px) and (max-width: 1100px) {
  .simulacion-editor-grid .sf-productos-col { grid-column: span 7; }
  .simulacion-editor-grid .sf-gastos-col { grid-column: span 5; }
  .simulacion-editor-grid .sf-resumen-col { grid-column: span 9; }
  #sf-lineas-table .sf-l-concepto { min-width: 130px; }
  #sf-lineas-table .sf-l-precio,
  #sf-lineas-table .sf-l-margen-pct { min-width: 78px; }
  #sf-lineas-table .sf-l-cantidad { min-width: 72px; }
  #sf-lineas-table .sf-l-coste-unitario,
  #sf-lineas-table .sf-l-beneficio { min-width: 88px; }
  .simulacion-editor-grid .sf-acciones-col {
    grid-column: span 3;
    align-self: start;
    flex-direction: column;
    align-items: stretch;
    margin-top: 24px;
  }
  .simulacion-editor-grid .sf-acciones-col .btn { width: 100%; }
}
/* Same font/color as every other bracket-input date field, without the
   full bracket-box wrapper around it - too heavy for a compact inline
   field sitting right next to a checkbox (see conversation with the
   user). Smaller font too - at the field's own width the year was getting
   clipped by the browser's native date-picker rendering otherwise. */
.cf-l-fecha-recibido {
  position: relative;
  top: 5px;
  font: inherit;
  font-size: 12px;
  color: inherit;
  border: none;
  background: transparent;
  padding: 2px 4px;
}
/* Mobile only - on a phone-width viewport the % column widths above squeeze
   Cantidad/Pr. unitario's digits down to nothing and wrap Recibido's
   checkbox+fecha onto a second line. .lineas-scroll already scrolls
   sideways for exactly this case, so columns are left free to grow past the
   viewport there instead. Scoped to this breakpoint only - doing this
   unconditionally changed the (working) desktop column widths too (see
   conversation with the user). */
@media (max-width: 640px) {
  table.lineas-table th,
  table.lineas-table td { white-space: nowrap; }
  .cf-l-cant, .cf-l-precio,
  .vf-l-cant, .vf-l-precio, .vf-l-desc-pct { min-width: 90px; }
  /* Simulaciones' Productos/Gastos columns hold <input>/<select> fields at
     width:100% of their cell, not plain text - nowrap above only forces a
     text node to keep a column from shrinking, it does nothing for a
     replaced control sized to its (shrinkable) column, so those inputs were
     still getting squeezed to near-zero at phone width even after removing
     the <th> width:% hint. Same fix as .cf-l-cant/.vf-l-cant elsewhere:
     min-width directly on the field forces the column - and therefore the
     table - past the viewport, so .lineas-scroll's horizontal scroll kicks
     in instead (see conversation with the user). */
  #sf-lineas-table .sf-l-concepto, #sf-costes-table .sf-c-concepto { min-width: 140px; }
  #sf-lineas-table .sf-l-precio, #sf-lineas-table .sf-l-margen-pct { min-width: 80px; }
  #sf-lineas-table .sf-l-cantidad { min-width: 70px; }
  #sf-lineas-table .sf-l-coste-unitario, #sf-lineas-table .sf-l-beneficio { min-width: 90px; }
  #sf-costes-table .sf-c-producto { min-width: 130px; }
  #sf-costes-table .sf-c-importe { min-width: 90px; }
}
.linea-remove {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-danger);
  opacity: 0.6;
  padding: 6px 4px;
}
.linea-remove:hover { opacity: 1; }
.linea-remove:disabled { opacity: 0.2; cursor: not-allowed; }
.totales-box { text-align: right; margin: 16px 0; }
.totales-box .total-final { font-size: 16px; }
/* Simulaciones' Resumen - filas alternas con un fondo clarito para que se
   lean más fácil (ver conversación con el usuario); Beneficio estimado
   lleva la misma tipografía que el resto, sin destacarlo aparte. */
.resumen-row { display: flex; justify-content: space-between; padding: 4px 8px; border-radius: 4px; }
.resumen-row:nth-child(odd) { background: rgba(10, 42, 61, 0.05); }

/* ---- Simulaciones: modo tactil de venta rapida ---- */
body.venta-rapida-open { overflow: hidden; }
.venta-rapida-content {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 16px;
}
.venta-rapida-content[hidden] { display: none; }
.fw-venta-rapida .formwindow-body { padding: 0; overflow: hidden; }
.venta-rapida-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
  flex-shrink: 0;
}
.venta-rapida-overlay {
  position: fixed;
  inset: 0;
  z-index: 8500;
  background: rgba(7, 29, 43, 0.58);
  padding: 18px;
  display: flex;
  align-items: stretch;
  justify-content: center;
}
.venta-rapida-overlay[hidden] { display: none; }
.venta-rapida-panel {
  width: min(1100px, 100%);
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--color-bone);
  border-radius: 16px;
  box-shadow: 0 12px 42px rgba(0, 0, 0, 0.35);
  padding: 22px;
}
.venta-rapida-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 16px;
}
.venta-rapida-header h2 { margin: 0 0 5px; font-size: 21px; }
.venta-rapida-header-actions { display: flex; align-items: center; gap: 10px; }
.venta-rapida-status { font-size: 13px; min-height: 18px; color: rgba(10, 42, 61, 0.65); }
.venta-rapida-status.is-saving { color: #8a6500; }
.venta-rapida-status.is-saved { color: #087443; }
.venta-rapida-status.is-error { color: var(--color-danger); font-weight: 700; }
.venta-rapida-cerrar {
  width: 52px;
  height: 52px;
  border: 1px solid rgba(10, 42, 61, 0.2);
  border-radius: 12px;
  background: transparent;
  color: var(--color-navy);
  font-size: 34px;
  line-height: 1;
  cursor: pointer;
}
.venta-rapida-grid {
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  padding: 2px 4px 8px 2px;
}
.venta-producto-card {
  min-height: 190px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 142px;
  gap: 14px;
  overflow: hidden;
  border: 2px solid var(--color-navy-dark);
  border-radius: 18px;
  background: #fff;
  padding: 12px 10px 12px 20px;
}
.venta-producto-info { min-width: 0; display: flex; flex-direction: column; }
.venta-producto-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.venta-producto-nombre {
  margin: 0 0 5px;
  overflow-wrap: anywhere;
  font-size: 21px;
  line-height: 1.2;
}
.venta-card-min {
  flex: 0 0 38px;
  height: 34px;
  border: 1px solid rgba(10, 42, 61, 0.2);
  border-radius: 8px;
  background: transparent;
  color: var(--color-navy);
  font-size: 20px;
  cursor: pointer;
}
.venta-min-resumen { display: none; color: rgba(10, 42, 61, 0.68); font-size: 13px; }
.venta-producto-precio { display: flex; align-items: center; gap: 8px; color: rgba(10, 42, 61, 0.72); font-size: 14px; }
.venta-producto-precio input {
  width: 105px;
  border: 1px solid rgba(10, 42, 61, 0.25);
  border-radius: 8px;
  background: var(--color-bone);
  color: var(--color-navy-dark);
  padding: 8px;
  font: inherit;
  font-weight: 700;
  text-align: right;
}
.venta-producto-cantidad {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: auto;
}
.venta-producto-cantidad span { font-size: 14px; }
.venta-producto-cantidad strong { font-size: 34px; color: var(--color-navy-dark); }
.venta-producto-total { min-height: 30px; display: flex; align-items: center; gap: 6px; font-size: 14px; }
.venta-producto-total strong { font-size: 17px; }
.venta-total-ojo {
  min-width: 44px;
  min-height: 38px;
  border: 1px solid rgba(10, 42, 61, 0.18);
  border-radius: 9px;
  background: transparent;
  cursor: pointer;
  font-size: 18px;
}
.venta-producto-actions { display: grid; grid-template-rows: 1fr 48px 1fr; gap: 8px; }
.venta-operacion-cantidad {
  width: 100%;
  min-width: 0;
  border: 2px solid var(--color-navy-dark);
  border-radius: 12px;
  background: #fff;
  color: var(--color-navy-dark);
  font-size: 25px;
  font-weight: 800;
  text-align: center;
}
.venta-cantidad-btn {
  width: 100%;
  min-height: 74px;
  border: 2px solid var(--color-navy-dark);
  border-radius: 20px;
  color: #fff;
  font-size: 58px;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.venta-cantidad-btn:active { transform: scale(0.97); filter: brightness(0.92); }
.venta-cantidad-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.venta-mas { background: #079413; }
.venta-menos { background: #f20d15; }
.venta-producto-card.is-minimized {
  min-height: 70px;
  grid-template-columns: 1fr;
  padding: 12px 16px;
}
.venta-producto-card.is-minimized .venta-producto-precio,
.venta-producto-card.is-minimized .venta-producto-cantidad,
.venta-producto-card.is-minimized .venta-producto-total,
.venta-producto-card.is-minimized .venta-producto-actions { display: none; }
.venta-producto-card.is-minimized .venta-producto-nombre { margin: 10px 0; }
.venta-producto-card.is-minimized .venta-min-resumen { display: inline; }

.fw-cambio-rapido .formwindow-body { padding: 18px; }
.cambio-rapido { display: flex; flex-direction: column; gap: 15px; }
.cambio-total, .cambio-devuelto { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.cambio-total span, .cambio-devuelto span { color: rgba(10, 42, 61, 0.65); font-size: 14px; }
.cambio-total strong { font-size: 25px; }
.cambio-devuelto {
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(13, 184, 216, 0.1);
}
.cambio-devuelto strong { color: #1769aa; font-size: 30px; }
.cambio-entregado { font-size: 22px; font-weight: 800; text-align: right; }

/* ---- Simulaciones: informe de ventas reales ---- */
.informe-subtitle { margin-top: 5px; color: rgba(10, 42, 61, 0.62); font-size: 13px; }
.informe-actions, .informe-filtros { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.informe-filtros { margin: 6px 0 20px; padding: 12px; background: rgba(10, 42, 61, 0.04); border-radius: 9px; }
.informe-fecha-field { width: 170px; margin-bottom: 0; }
.informe-fecha-field label { font-size: 12px; }
.informe-kpis { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
.informe-kpi { display: flex; flex-direction: column; gap: 7px; padding: 15px; background: rgba(13, 184, 216, 0.07); border-radius: 10px; }
.informe-kpi span { color: rgba(10, 42, 61, 0.65); font-size: 12px; }
.informe-kpi strong { font-size: 21px; }
.informe-kpi-resultado.is-loss strong { color: var(--color-danger); }
.informe-kpi-resultado.is-profit strong { color: #1769aa; }
.informe-heading { margin: 26px 0 10px; font-size: 16px; }
.informe-heading-count { font-family: 'Inter Variable', sans-serif; font-size: 14px; font-weight: 800; color: var(--color-navy); }
.informe-ventas-page .data-table th:not(:first-child),
.informe-ventas-page .data-table td:not(:first-child) { text-align: right; }
.informe-ventas-page .data-table th:first-child,
.informe-ventas-page .data-table td:first-child { text-align: left; }
.informe-ventas-page #ivr-operaciones {
  max-height: 330px;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.informe-ventas-page #ivr-operaciones .data-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-bone);
}
.informe-negativo { color: var(--color-danger); }

@media (max-width: 1050px) {
  .informe-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 820px) {
  .informe-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media print {
  html, body { background: #fff !important; }
  .informe-ventas-page { padding: 0; }
  .informe-ventas-page .card { background: #fff !important; box-shadow: none; }
  .informe-actions, .informe-filtros, .menu-container { display: none !important; }
  .informe-ventas-page #ivr-operaciones {
    max-height: none;
    overflow: visible;
  }
  .informe-ventas-page #ivr-operaciones .data-table th {
    position: static;
  }
}

@media (max-width: 820px) {
  .venta-rapida-overlay { padding: 0; }
  .venta-rapida-panel { width: 100%; border-radius: 0; padding: 16px; }
  .venta-rapida-grid { grid-template-columns: 1fr; }
  .venta-rapida-header-actions { flex-wrap: wrap; justify-content: flex-end; }
  .venta-producto-card { min-height: 180px; }
}
@media (max-width: 480px) {
  .venta-producto-card { grid-template-columns: minmax(0, 1fr) 112px; padding-left: 14px; }
  .venta-cantidad-btn { font-size: 48px; }
  .venta-producto-nombre { font-size: 18px; }
}

/* ---- Quill (rich text editor for oferta/publicidad) - stripped border so
   only the bracket-field accents frame it, matching every other input. ---- */
.bracket-box .ql-toolbar.ql-snow,
.bracket-box .ql-container.ql-snow { border: none; font-family: inherit; }
.bracket-box .ql-container.ql-snow { min-height: 160px; }
.ql-editor p:empty { min-height: 1.42em; }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before { content: 'Pequeño'; }
.ql-snow .ql-picker.ql-size .ql-picker-label:not([data-value])::before,
.ql-snow .ql-picker.ql-size .ql-picker-item:not([data-value])::before { content: 'Normal'; }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before { content: 'Grande'; }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before { content: 'Enorme'; }

/* ---- Tag input (envío por email: pick a cliente or type any address) ---- */
.tag-input-box {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  min-height: 40px;
  padding: 6px 10px;
}
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: rgba(13, 184, 216, 0.12);
  color: var(--color-navy);
  border-radius: 4px;
  padding: 2px 6px 2px 10px;
  font-size: 13px;
}
.tag-chip button {
  background: none;
  border: none;
  cursor: pointer;
  color: rgba(10, 42, 61, 0.5);
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
}
.tag-chip button:hover { color: var(--color-danger); }
.tag-input-field {
  flex: 1 1 120px;
  min-width: 120px;
  border: none;
  outline: none;
  background: transparent;
  font: inherit;
  color: inherit;
  padding: 4px 0;
}
.tag-input-options {
  position: relative;
}
.tag-input-dropdown {
  position: absolute;
  /* Debajo de la caja, no encima - con top:2px quedaba prácticamente
     tapando el propio input (visible sobre todo en el de destinatarios de
     email de Presupuestos, donde "Sin resultados" se queda abierto
     mientras se sigue escribiendo un email) - ver conversación con el
     usuario. Afecta a todos los combobox que comparten esta clase
     (proveedor/cliente/producto...), no solo a ese caso. */
  top: 100%;
  margin-top: 4px;
  left: 0;
  right: 0;
  z-index: 10;
  background: var(--color-bone);
  border-radius: 8px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.2);
  max-height: 200px;
  overflow-y: auto;
}
.tag-input-option {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 13px;
}
.tag-input-option:hover { background: rgba(13, 184, 216, 0.1); }
.tag-input-option-all { border-bottom: 1px solid #EAE5DC; }
.tag-input-option-empty { padding: 8px 12px; font-size: 12px; opacity: 0.6; }
.logo-preview img { width: 40px; height: 40px; object-fit: contain; }

/* ---- PresupuestoImprimir: standalone printable view, no app chrome ---- */
.print-page { max-width: 800px; margin: 0 auto; padding: 32px; color: #1a1a1a; background: #fff; }
.print-toolbar { text-align: right; margin-bottom: 24px; }
.print-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 32px; gap: 16px; }
.print-brand { display: flex; align-items: center; gap: 12px; }
.print-brand img,
.print-brand svg { width: 48px; height: 48px; object-fit: contain; }
.print-brand-name { font-size: 22px; }
.print-contact { font-size: 12px; color: rgba(0, 0, 0, 0.65); margin-top: 4px; }
.print-tipo { text-align: right; }
.print-tipo-label { font-size: 20px; }
.print-tipo-codigo { color: rgba(0, 0, 0, 0.65); }
.print-meta { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 16px; margin-bottom: 24px; }
.print-meta-label { color: rgba(0, 0, 0, 0.5); font-size: 12px; }
.print-divider { border: none; border-top: 2px solid #0A2A3D; margin: 0 0 24px; }
table.print-lineas { width: 100%; border-collapse: collapse; margin-bottom: 24px; }
table.print-lineas th {
  padding: 8px 4px;
  white-space: nowrap;
  text-align: left;
  border-bottom: 2px solid #0A2A3D;
}
table.print-lineas th:not(:first-child) { text-align: right; }
table.print-lineas td { padding: 8px 4px; border-bottom: 1px solid #EAE5DC; }
table.print-lineas td:not(:first-child) { text-align: right; }
.print-totales { display: flex; justify-content: flex-end; }
.print-totales-box { min-width: 240px; }
.print-totales-row { display: flex; justify-content: space-between; padding: 4px 0; }
.print-totales-final {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-top: 2px solid #0A2A3D;
  font-size: 18px;
}
.print-notas { margin-top: 32px; white-space: pre-wrap; }
.print-proveedor-nombre { font-weight: 600; margin-top: 4px; }
.print-footer { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; margin-top: 8px; }
.print-notas-box { white-space: pre-wrap; max-width: 60%; }
.print-footer-total { font-size: 18px; white-space: nowrap; }
@media print {
  .print-page { padding: 0; }
  /* body's app-wide cream background (see :root) shows through below the
     content on a physical page taller than a short presupuesto - print
     output should just be white paper regardless. */
  body { background: #fff; }
}
