/* =====================================================================
   CUTLINE - SISTEMA DE DISENO
   ---------------------------------------------------------------------
   Software B2B premium para una empresa de land clearing. La referencia
   es Procore / ServiceTitan, no un panel de SaaS generico: verde bosque
   sobre crema, cromo oscuro, tablas sobrias y densidad de informacion
   alta. Nada de morados por todos lados, degradados ni glassmorphism.

   TODO sale de los tokens de la seccion 1. Si un componente necesita un
   color, un radio o una sombra que no este ahi, el que esta mal es el
   componente.

   iOS-FIRST SIGUE VALIENDO. El cliente es 100 % iPhone, asi que el
   telefono no es el caso degradado. El escritorio (la referencia) y el
   telefono salen del MISMO CSS cambiando --navh y --sidew.

   Decisiones que NO se deben revertir, cada una por un bug real:

   1) SCROLL DE DOCUMENTO, nunca un scroller interno en .app.
      Algia v27-v32 fijaba el alto a 100dvh con el scroll adentro; en el
      iPhone real esa altura calculaba mal y el final de la lista quedaba
      tapado por la barra de pestanas.

   2) 100dvh, nunca 100vh.

   3) La barra de pestanas va con FLEX, no con grilla fija de columnas.

   4) CERO backdrop-filter sobre algo que haga scroll: en iPhone se
      re-muestrea en cada cuadro y traba la lista entera.

   5) Inputs a 16px o mas, y el tamano NUNCA dentro del shorthand 'font'
      ('font: 16px/1.4 inherit' no es CSS valido y el navegador tira la
      regla entera; los inputs caian a 13.3px y iOS hacia zoom al
      enfocar).
   ===================================================================== */

/* =====================================================================
   1. TOKENS
   ===================================================================== */
:root {
  /* --- Superficies --- */
  --bg:          #F5F6F3;   /* crema calida, se lee al sol */
  --surface:     #FFFFFF;
  --surface-2:   #FAFBF9;   /* encabezados de tabla, filas alternas */
  --sidebar:     #14221B;   /* verde-carbon del cromo */
  --sidebar-2:   #1C2E24;   /* hover en el cromo */
  --sidebar-ink: #DCE5DF;
  --sidebar-dim: #8A9A90;

  /* --- Tinta --- */
  --ink:    #16211B;
  --ink-2:  #3E4F46;
  --mut:    #697A70;
  --dim:    #95A29A;

  /* --- Lineas --- */
  --line:   #E3E7E1;
  --line-2: #EFF1ED;

  /* --- Marca --- */
  --primary:      #1F5E3B;
  --primary-700:  #184A2E;
  --primary-300:  #4C9168;
  --primary-soft: rgba(31, 94, 59, .085);
  --primary-ring: rgba(31, 94, 59, .16);

  /* --- Semanticos. Se usan CON CUENTAGOTAS: el verde manda. --- */
  --info:         #26608F;
  --info-soft:    rgba(38, 96, 143, .10);
  --warn:         #A8701C;
  --warn-soft:    rgba(168, 112, 28, .12);
  --danger:       #AC3B2F;
  --danger-soft:  rgba(172, 59, 47, .10);
  /* Morado SOLO para cifras de dinero cobrado. En ningun otro lado. */
  --revenue:      #5C4A8A;
  --revenue-soft: rgba(92, 74, 138, .10);

  /* TINTES de las tarjetas de cifra. Van aparte de los *-soft a
     proposito: los soft son para chips y bandas y tienen que
     quedarse discretos; estos pintan una tarjeta entera y
     necesitan verse de un vistazo, como en la referencia. */
  --primary-tinte: rgba(31, 94, 59, .17);
  --info-tinte:    rgba(38, 96, 143, .17);
  --warn-tinte:    rgba(168, 112, 28, .19);
  --danger-tinte:  rgba(172, 59, 47, .15);
  --revenue-tinte: rgba(92, 74, 138, .16);

  /* --- Forma --- */
  --r:    12px;
  --r-sm: 9px;
  --r-xs: 7px;
  --r-pill: 999px;

  /* --- Sombra: muy suave. Las tarjetas se separan por BORDE, no por
         sombra; la sombra solo da un milimetro de elevacion. --- */
  --sh-xs: 0 1px 2px rgba(20, 34, 27, .04);
  --sh:    0 1px 2px rgba(20, 34, 27, .05), 0 2px 6px rgba(20, 34, 27, .04);
  --sh-md: 0 4px 14px rgba(20, 34, 27, .09);
  --sh-lg: 0 18px 44px rgba(20, 34, 27, .20);

  /* --- Color de ambiente, tambien en CLARO ---
         Un halo luminoso no existe sobre papel: en claro el mismo gesto
         se hace con SOMBRA TENIDA (la sombra toma el color del elemento
         en vez de ser gris). Se lee igual de vivo al sol y no le baja el
         contraste a nada, que es la regla que manda en este producto.
         Los mismos nombres que en oscuro, para que las reglas de abajo
         sean una sola y no dos. */
  --glow-primary: 0 0 0 1px rgba(31, 94, 59, .20), 0 6px 18px rgba(31, 94, 59, .30);
  --glow-info:    0 5px 16px rgba(38, 96, 143, .30);
  --glow-warn:    0 5px 16px rgba(168, 112, 28, .32);
  --glow-danger:  0 5px 16px rgba(172, 59, 47, .28);
  --glow-revenue: 0 5px 16px rgba(92, 74, 138, .30);

  /* El anillo que recorre el borde de las tarjetas (seccion 31). En
     claro va con los verdes y azules PROFUNDOS de la paleta, no con los
     claros: sobre blanco un color palido se ve como suciedad. */
  --anillo: conic-gradient(from 210deg,
      rgba(31, 94, 59, .9),  rgba(38, 96, 143, .8),
      rgba(92, 74, 138, .75), rgba(168, 112, 28, .8),
      rgba(31, 94, 59, .9));
  /* Mas alto que en oscuro (.6 alla): sobre blanco el mismo hilo se
     "apaga" -- el ojo lo compara contra papel, no contra negro. */
  --anillo-op: .72;

  --curva: cubic-bezier(.22, .61, .36, 1);

  /* --- Medidas del cascaron ---
     --navh pasa a 0 en escritorio y con eso se apaga de golpe TODO el
     layout movil: el espacio reservado abajo, la barra y el boton
     flotante. --sidew hace lo inverso. */
  --navh: 58px;
  --sidew: 0px;
  --px: 16px;
  --toph: 58px;
}

/* El oscuro existe pero NO es el modo por defecto: el dueno abre esto en
   la camioneta, al sol, donde una pantalla oscura se vuelve un espejo. */
:root[data-tema="oscuro"] {
  --bg:        #0F1713;
  --surface:   #16211B;
  --surface-2: #1A2720;
  --sidebar:   #0B120E;
  --sidebar-2: #16231B;
  --sidebar-ink: #DCE5DF;
  --sidebar-dim: #7E8E85;

  --ink:    #E9EFEA;
  --ink-2:  #BCC8C0;
  --mut:    #93A39A;
  --dim:    #6D7D74;

  --line:   #26332C;
  --line-2: #1E2A23;

  --primary:      #4C9168;
  --primary-700:  #3C7A54;
  --primary-300:  #76B890;
  --primary-soft: rgba(76, 145, 104, .13);
  --primary-ring: rgba(76, 145, 104, .24);

  --info:    #5E9BC9;  --info-soft:    rgba(94, 155, 201, .14);
  --warn:    #D1A05A;  --warn-soft:    rgba(209, 160, 90, .14);
  --danger:  #D98072;  --danger-soft:  rgba(217, 128, 114, .14);
  --revenue: #9B88C9;  --revenue-soft: rgba(155, 136, 201, .14);

  /* En oscuro el tinte va MUCHO mas bajo: el mismo valor del claro
     sobre un fondo casi negro se ve como una mancha de color, no
     como un bano. */
  --primary-tinte: rgba(76, 145, 104, .10);
  --info-tinte:    rgba(94, 155, 201, .10);
  --warn-tinte:    rgba(209, 160, 90, .10);
  --danger-tinte:  rgba(217, 128, 114, .10);
  --revenue-tinte: rgba(155, 136, 201, .10);

  --sh-xs: 0 1px 2px rgba(0, 0, 0, .35);
  --sh:    0 1px 2px rgba(0, 0, 0, .4), 0 2px 8px rgba(0, 0, 0, .3);
  --sh-md: 0 6px 18px rgba(0, 0, 0, .45);
  --sh-lg: 0 20px 48px rgba(0, 0, 0, .6);

  /* --- Glow: los MISMOS nombres que en claro, pero aqui si es luz.
         Sobre un fondo casi negro un halo se lee; sobre papel no, y por
         eso el claro define estos mismos tokens como sombra tenida.
         Las reglas que los usan (secciones 27 y 31) son una sola para
         los dos temas: el token hace la diferencia, no el selector. */
  --glow-primary: 0 0 0 1px rgba(76, 145, 104, .4), 0 0 20px rgba(76, 145, 104, .35);
  --glow-info:    0 0 16px rgba(94, 155, 201, .4);
  --glow-warn:    0 0 16px rgba(209, 160, 90, .4);
  --glow-danger:  0 0 16px rgba(217, 128, 114, .4);
  --glow-revenue: 0 0 16px rgba(155, 136, 201, .4);

  --anillo: conic-gradient(from 210deg,
      rgba(76, 145, 104, .95), rgba(94, 155, 201, .85),
      rgba(155, 136, 201, .8),  rgba(209, 160, 90, .85),
      rgba(76, 145, 104, .95));
  --anillo-op: .6;
}

/* =====================================================================
   2. BASE
   ===================================================================== */
* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  overflow-x: clip;          /* clip, no hidden: no rompe position:sticky */
  max-width: 100vw;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter",
               "Segoe UI", system-ui, Roboto, Arial, sans-serif;
  font-size: 14.5px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
  max-width: 100vw;

  touch-action: manipulation;       /* sin zoom por doble tap */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  transition: background .25s var(--curva), color .25s var(--curva);
}

/* Se reactiva la seleccion donde hace falta: un numero de estimate que no
   se puede copiar es una llamada a soporte. */
input, textarea, [contenteditable], .seleccionable, .num, pre, code {
  -webkit-user-select: text; user-select: text;
}

body.applock { overscroll-behavior-y: none; }
img { max-width: 100%; height: auto; display: block; }

/* Scrollbars con la marca. Nada del cromo del navegador se queda por
   defecto. */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 99px;
  border: 3px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--dim); background-clip: content-box; }

::selection { background: var(--primary-ring); color: var(--ink); }

/* =====================================================================
   3. TIPOGRAFIA
   ---------------------------------------------------------------------
   Jerarquia por PESO y COLOR antes que por tamano. Un titulo enorme en
   software de trabajo roba espacio a los datos, que es a lo que el dueno
   viene.
   ===================================================================== */
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -.016em; color: var(--ink); }
h1 { font-size: 1.32rem; }
h2 { font-size: 1.1rem; }
h3 { font-size: .97rem; }
h4 { font-size: .9rem; font-weight: 600; }
p  { margin: 0 0 10px; }
a  { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-700); }

.mut { color: var(--mut); }
.dim { color: var(--dim); }
.sm  { font-size: 13px; }
.xs  { font-size: 12px; }
.cero-m { margin: 0; }
.der { text-align: right; }
.nowrap { white-space: nowrap; }
.lnk { color: var(--primary); cursor: pointer; background: none; border: 0;
       font: inherit; padding: 0; }
.lnk:hover { text-decoration: underline; }

/* La etiqueta pequena en versalitas de los KPI y los encabezados de
   tabla. Es la firma tipografica del sistema. */
.et-kpi, .tabla th, .sec-label {
  font-size: 10.5px;
  font-weight: 680;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--mut);
}

/* Cifras alineadas en columna. Sin esto una tabla de dinero baila. */
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

.traza { white-space: pre-wrap; font-size: 12px; color: var(--mut);
         margin: 8px 0 0; font-family: ui-monospace, Menlo, monospace; }
.cita { color: var(--sidebar-dim); font-size: 11px; letter-spacing: .02em; }

/* =====================================================================
   4. EL CASCARON
   ===================================================================== */
.app { min-height: 100dvh; }

/* ---------- Barra lateral (escritorio) + cajon (telefono) ---------- */
.sidenav {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 70;
  width: 248px;
  display: flex;
  flex-direction: column;
  background: var(--sidebar);
  color: var(--sidebar-ink);
  padding: 20px 14px calc(16px + env(safe-area-inset-bottom));
  padding-left: calc(14px + env(safe-area-inset-left));
  /* En el telefono entra como cajon desde la izquierda. */
  transform: translateX(-100%);
  transition: transform .26s var(--curva);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sidenav.abierta { transform: none; }

/* La textura de crestas del pie del cromo. Es lo unico decorativo de la
   barra y va MUY tenue: firma de marca, no adorno. */
.sidenav::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 190px;
  pointer-events: none;
  opacity: .5;
  background:
    linear-gradient(to top, rgba(76,145,104,.11), transparent 70%),
    url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 248 190' preserveAspectRatio='none'>\
<path d='M0 150 L34 104 L58 132 L92 76 L126 128 L150 100 L186 142 L214 112 L248 152 L248 190 L0 190 Z' fill='%234C9168' fill-opacity='.14'/>\
<path d='M0 170 L40 136 L74 162 L110 124 L142 158 L178 134 L212 164 L248 138 L248 190 L0 190 Z' fill='%234C9168' fill-opacity='.10'/>\
</svg>") bottom / 100% auto no-repeat;
}

/* La marca va APILADA Y CENTRADA, no en una linea pegada a la esquina.
   El logo grande arriba y el nombre debajo le dan al menu un encabezado
   de verdad; antes todo se amontonaba en la esquina superior izquierda y
   la barra se veia como una lista suelta sin cabeza. */
.side-marca {
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  padding: 10px 8px 20px;
  margin-bottom: 16px;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  position: relative; z-index: 1;
  text-align: center;
}
.side-marca img {
  width: 58px; height: 58px; border-radius: 15px; flex: 0 0 58px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .3);
}
.side-marca .t { min-width: 0; max-width: 100%; }
.side-marca b {
  display: block; font-size: 16px; font-weight: 700; color: #fff;
  letter-spacing: .02em; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.side-marca span {
  display: block; font-size: 9.5px; font-weight: 600; letter-spacing: .15em;
  text-transform: uppercase; color: var(--sidebar-dim); margin-top: 3px;
}

/* flex:1 + el margin-top:auto del separador empujan "Sign out" al fondo
   de la barra. Las rutas quedan arriba y la salida abajo, que es donde
   el ojo la busca -- y de paso el menu deja de verse apelotonado. */
.sidenav nav { display: flex; flex-direction: column; gap: 3px; flex: 1;
               position: relative; z-index: 1; }
.sidenav a {
  display: flex; align-items: center; gap: 12px;
  min-height: 46px; padding: 0 12px;
  border-radius: var(--r-sm);
  color: var(--sidebar-dim);
  font-size: 14.5px; font-weight: 550;
  transition: background .16s var(--curva), color .16s var(--curva);
}
.sidenav a .ic { width: 20px; height: 20px; flex: 0 0 20px;
                 display: grid; place-items: center; }
.sidenav a .ic svg { width: 19px; height: 19px; }
.sidenav a:hover { background: var(--sidebar-2); color: var(--sidebar-ink); }
.sidenav a.on {
  background: var(--primary); color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
}
.sidenav a.on .ic { color: #fff; }

.side-sep { height: 1px; background: rgba(255,255,255,.08); margin: auto 8px 12px;
            position: relative; z-index: 1; }
.sidenav .pie-ver {
  position: relative; z-index: 1;
  margin: auto 0 0; padding: 14px 10px 0;
  color: var(--sidebar-dim); font-size: 11px; line-height: 1.7;
}

.velo-nav {
  position: fixed; inset: 0; z-index: 65;
  background: rgba(12, 20, 16, .5);
  animation: aparece .18s var(--curva);
}

/* ---------- Area de contenido ---------- */
.main { margin-left: var(--sidew); min-height: 100dvh; }

/* ---------- Barra superior ---------- */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 12px;
  min-height: var(--toph);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: calc(env(safe-area-inset-top) + 9px)
           calc(env(safe-area-inset-right) + var(--px)) 9px
           calc(env(safe-area-inset-left) + var(--px));
}
.topbar h1 {
  flex: 1; min-width: 0; font-size: 1.04rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar .acciones { display: flex; gap: 8px; align-items: center; }

.hamb {
  width: 38px; height: 38px; flex: 0 0 38px;
  display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--r-sm); color: var(--ink-2); cursor: pointer;
}
.hamb svg { width: 19px; height: 19px; }

/* La fecha, la campana y el perfil. En el telefono la fecha se esconde:
   ya esta en el hero del tablero. */
.top-meta { display: none; align-items: center; gap: 7px; }
.top-fecha {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 550; color: var(--ink-2);
  padding: 7px 12px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface);
}
.top-fecha svg { width: 15px; height: 15px; color: var(--mut); }

.icon-btn {
  position: relative;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--r-sm); color: var(--ink-2); cursor: pointer;
  transition: background .16s var(--curva), border-color .16s var(--curva);
}
.icon-btn:hover { background: var(--surface-2); border-color: var(--dim); }
.icon-btn svg { width: 18px; height: 18px; }
.icon-btn .punto {
  position: absolute; top: 7px; right: 8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--primary-300);
  box-shadow: 0 0 0 2px var(--surface);
}

.avatar {
  width: 34px; height: 34px; flex: 0 0 34px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--primary); color: #fff;
  font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
  cursor: pointer; border: 0;
}

/* ---------- Lienzo ---------- */
.view {
  padding: 18px calc(env(safe-area-inset-right) + var(--px))
           calc(var(--navh) + env(safe-area-inset-bottom) + 28px)
           calc(env(safe-area-inset-left) + var(--px));
  display: flex; flex-direction: column; gap: 16px;
}

#bandas {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px calc(env(safe-area-inset-right) + var(--px)) 0
           calc(env(safe-area-inset-left) + var(--px));
}
#bandas:empty { display: none; }

/* Encabezado de pagina: titulo + descripcion + accion. Lo comparten
   Clients, Estimates, Rates y Settings, que es parte de que todo se
   sienta del mismo producto. */
.pagina-cab {
  display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap;
}
.pagina-cab .t { flex: 1; min-width: 200px; }
.pagina-cab h2 { margin-bottom: 2px; }
.pagina-cab p { color: var(--mut); font-size: 13.5px; margin: 0; max-width: 62ch; }
.pagina-cab .acc { display: flex; gap: 8px; flex-wrap: wrap; }

/* En el telefono la accion principal ya vive en el boton flotante, que es
   donde el pulgar la alcanza. Repetirla en el encabezado es ofrecer dos
   veces lo mismo y robar una linea de pantalla. */
@media (max-width: 1023px) {
  .pagina-cab .acc .con-fab { display: none; }
  /* Y el contenido deja sitio para que el flotante no tape la ultima
     fila de la lista. */
  .view { padding-bottom: calc(var(--navh) + env(safe-area-inset-bottom) + 86px); }
}

/* =====================================================================
   5. BARRA DE PESTANAS (telefono)
   ---------------------------------------------------------------------
   safe-area en los CUATRO lados: en horizontal el iPhone tiene inset a
   izquierda y derecha, y sin eso los extremos se meten bajo la curva.
   ===================================================================== */
.botnav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  height: calc(var(--navh) + env(safe-area-inset-bottom));
  padding: 3px calc(6px + env(safe-area-inset-right))
           calc(env(safe-area-inset-bottom) + 3px)
           calc(6px + env(safe-area-inset-left));
  display: flex; align-items: stretch;
  /* Solido a proposito. El translucido deja ver la lista pasando por
     debajo y se ve sucio; y el desenfoque en vivo traba el pintado. */
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.botnav a {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px;
  color: var(--dim); font-size: 10px; font-weight: 600;
  letter-spacing: .01em;
  transition: color .18s var(--curva);
}
.botnav a .ic {
  width: 30px; height: 22px; display: grid; place-items: center;
  border-radius: var(--r-xs);
  transition: background .18s var(--curva);
}
.botnav a .ic svg { width: 20px; height: 20px; }
.botnav a .et { max-width: 100%; overflow: hidden; text-overflow: ellipsis;
                white-space: nowrap; }
.botnav a.on { color: var(--primary); }
.botnav a.on .ic { background: var(--primary-soft); }

/* =====================================================================
   6. TARJETAS
   ---------------------------------------------------------------------
   Se separan por BORDE, no por sombra. Nada de rectangulos flotantes por
   todos lados: la estructura la da el espaciado.
   ===================================================================== */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--sh-xs);
}
.card-p, .card > .card-cuerpo { padding: 18px; }

.cab-card {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 15px 18px;
  border-bottom: 1px solid var(--line-2);
}
.cab-card h3 { display: flex; align-items: center; gap: 9px; }
.cab-card h3 .ic {
  width: 26px; height: 26px; flex: 0 0 26px;
  display: grid; place-items: center;
  border-radius: var(--r-xs);
  background: var(--primary-soft); color: var(--primary);
}
.cab-card h3 .ic svg { width: 15px; height: 15px; }
.cab-card .pista { color: var(--dim); font-size: 12.5px; }
.cab-card .acc { display: flex; align-items: center; gap: 8px; }

/* Tarjeta sin encabezado propio: el padding va directo */
.card.plana { padding: 18px; }

/* ---------------------------------------------------------------------
   RED DE SEGURIDAD DEL RELLENO.
   Los elementos de ancho completo (encabezado, lista, tabla, estado
   vacio) van pegados al borde a proposito. Cualquier OTRO hijo directo
   de una tarjeta -- un parrafo, un campo, un aviso -- se separa solo.

   Existe para que agregar una tarjeta nueva en cualquier pantalla no
   exija acordarse de envolver el contenido en .card-cuerpo: si se
   olvida, igual se ve bien. Sin esto, el olvido sale como texto pegado
   al borde, que es el defecto que mas grita "a medio hacer".
   --------------------------------------------------------------------- */
.card > :not(.cab-card):not(.bloque-cab):not(.card-cuerpo):not(.filas):not(.tabla-wrap):not(.vacio):not(.sk-fila):not(.opciones):not(.hero-escena) {
  margin-left: 18px;
  margin-right: 18px;
}
.card > .cab-card + :not(.card-cuerpo):not(.filas):not(.tabla-wrap):not(.vacio):not(.sk-fila) {
  margin-top: 16px;
}
.card > :last-child:not(.cab-card):not(.bloque-cab):not(.card-cuerpo):not(.filas):not(.tabla-wrap):not(.vacio):not(.sk-fila) {
  margin-bottom: 18px;
}

.grid { display: grid; gap: 14px; }
.grid.c2 { grid-template-columns: 1fr; }
.grid.c3, .grid.c4 { grid-template-columns: repeat(2, 1fr); }
.grid.principal { grid-template-columns: 1fr; }

/* =====================================================================
   7. KPI
   ---------------------------------------------------------------------
   Icono en chip, etiqueta en versalitas, cifra grande, contexto debajo, y
   una marca de agua tenue del mismo icono al fondo. Es la tarjeta de la
   referencia.
   ===================================================================== */
.kpi {
  position: relative; overflow: hidden;
  padding: 16px 17px;
  display: block;
  color: inherit;
  transition: border-color .18s var(--curva), box-shadow .18s var(--curva),
              transform .18s var(--curva);
}
a.kpi:hover, button.kpi:hover {
  border-color: var(--primary-300);
  box-shadow: var(--sh);
  transform: translateY(-1px);
}
.kpi-top { display: flex; align-items: center; gap: 10px; margin-bottom: 13px; }
.kpi-ic {
  width: 32px; height: 32px; flex: 0 0 32px;
  display: grid; place-items: center;
  border-radius: 50%;
  /* Chip SOLIDO, no el tinte pastel que usan .tag/.banda en el resto del
     sistema. Es la unica excepcion a proposito: en una fila de 4 numeros
     grandes, el chip es lo primero que el ojo agarra, y pastel se perdia
     contra el resto de la tarjeta. */
  background: var(--primary); color: #fff;
}
.kpi-ic svg { width: 17px; height: 17px; }
.kpi .et-kpi { flex: 1; min-width: 0; }
.kpi .fle { color: var(--dim); display: flex; }
.kpi .fle svg { width: 15px; height: 15px; }
.kpi .val {
  font-size: 1.95rem; font-weight: 700; letter-spacing: -.03em;
  line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.kpi .sub { color: var(--mut); font-size: 12.5px; margin-top: 4px; }

/* En el telefono las tarjetas van de dos en dos y la etiqueta no cabe en
   una linea. Se deja respirar en dos lineas y se quita la flecha: el
   espacio que gana la etiqueta vale mas que un adorno que ademas
   chocaba con el texto. */
@media (max-width: 719px) {
  .kpi { padding: 14px; }
  .kpi .et-kpi { letter-spacing: .05em; line-height: 1.3; }
  .kpi .fle { display: none; }
  .kpi .kpi-top { align-items: flex-start; margin-bottom: 10px; }
  .kpi .val { font-size: 1.7rem; }
  .kpi .agua { width: 86px; height: 86px; right: -12px; bottom: -14px; }
}

/* La marca de agua. Decorativa y casi invisible: da profundidad sin
   gritar. */
.kpi .agua {
  position: absolute; right: -14px; bottom: -18px;
  width: 104px; height: 104px;
  color: var(--primary); opacity: .09;
  pointer-events: none;
}
.kpi .agua svg { width: 100%; height: 100%; }

/* Variantes semanticas: SOLO cambian el chip y la marca de agua, nunca la
   cifra. Un tablero donde cada tarjeta es de un color distinto es ruido.
   "ok" va mas claro que el chip por defecto (primary-300 vs primary) para
   que "Won" se lea distinto de "Open estimates" sin salirse de la familia
   verde. */
.kpi.ok      .kpi-ic { background: var(--primary-300); color: #fff; }
.kpi.info    .kpi-ic { background: var(--info);    color: #fff; }
.kpi.info    .agua   { color: var(--info); }
.kpi.alerta  .kpi-ic { background: var(--warn);    color: #fff; }
.kpi.alerta  .agua   { color: var(--warn); }
.kpi.peligro .kpi-ic { background: var(--danger);  color: #fff; }
.kpi.peligro .agua   { color: var(--danger); }
.kpi.dinero  .kpi-ic { background: var(--revenue); color: #fff; }
.kpi.dinero  .agua   { color: var(--revenue); }

/* =====================================================================
   8. EL HERO DEL TABLERO
   ---------------------------------------------------------------------
   El fondo es la foto (assets/hero.jpg, o config.heroUrl si el cliente
   manda la suya). El SVG vectorial queda de respaldo automatico para
   cuando el archivo no esta.

   La foto es MUCHO mas ancha que alta y el hero es una franja todavia
   mas ancha: con object-position al centro, cover recortaba el suelo y
   la maquina quedaba fuera. Se ancla abajo del centro.
   ===================================================================== */
.hero {
  position: relative; overflow: hidden;
  border-radius: var(--r);
  border: 1px solid var(--line);
  min-height: 186px;
  display: flex; align-items: flex-end;
  padding: 22px;
  color: #fff;
  box-shadow: var(--sh-xs);
}
.hero-escena { position: absolute; inset: 0; z-index: 0; }
.hero-escena svg, .hero-escena img { width: 100%; height: 100%; object-fit: cover; }
.hero-escena img { object-position: 50% 58%; }
/* El velo oscuro desde la izquierda. Sin esto el texto blanco se pierde
   contra el cielo del amanecer.
   OJO: este degradado tapaba casi la MITAD de la foto (seguia al 40% de
   opacidad pasado el 56% del ancho, y nunca llegaba a 0). Se achica al
   cuadrante donde va el texto y cae a transparente del todo antes de
   la mitad, para que el resto de la foto -- la maquina, el sol -- se
   vea completa. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  /* Fuerte solo pegado al texto; para el 45% del ancho ya no tapa nada. */
  background: linear-gradient(100deg,
    rgba(11, 24, 17, .88) 0%,
    rgba(11, 24, 17, .60) 18%,
    rgba(11, 24, 17, .24) 34%,
    rgba(11, 24, 17, 0) 45%);
}
/* Pie oscuro: una foto real tiene zonas claras abajo (ramas al sol) y
   los botones blancos del hero se perdian contra ellas. El velo diagonal
   solo no alcanza porque se apaga justo a la derecha. */
.hero::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 58%;
  z-index: 1; pointer-events: none;
  background: linear-gradient(to top,
    rgba(11, 24, 17, .55) 0%, rgba(11, 24, 17, 0) 100%);
}
.hero-txt { position: relative; z-index: 2; max-width: 56ch; }
/* Cada linea lleva su propia barra vertical a la izquierda (align-items:
   stretch la hace crecer sola al alto de la linea, sin medir nada a mano
   por tipo de linea). Puramente decorativo: la barra no es un link ni
   lleva texto, por eso no hace falta tocar nada de accesibilidad. */
.hero-ln { display: flex; align-items: stretch; gap: 10px; margin-bottom: 7px; }
.hero-ln:last-of-type { margin-bottom: 0; }
.hero-barra {
  flex: 0 0 3px; width: 3px; border-radius: 2px;
  background: var(--primary-300);
}
.hero-saludo {
  font-size: 11px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: rgba(255, 255, 255, .75);
}
.hero-txt h2 {
  color: #fff; font-size: 2rem; letter-spacing: -.028em;
  line-height: 1.08;
}
.hero-lema { color: rgba(255, 255, 255, .82); font-size: 14px; margin: 0; }
.hero-acc { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 16px; }

/* El mismo .hero, reusado dentro de la columna principal del tablero
   para el banner de estado ("N estimates are waiting..."). Ahi el h2 es
   una frase, no una palabra ("Owner"), asi que va mas chico -- a 2rem
   una frase larga se comia las dos lineas del card en movil. */
.hero.hero-sec .hero-txt h2 { font-size: 1.5rem; line-height: 1.22; }

/* Banda promocional estrecha: misma escena, menos alto. */
.promo {
  position: relative; overflow: hidden;
  border-radius: var(--r); border: 1px solid var(--line);
  padding: 18px 20px; color: #fff; min-height: 104px;
  display: flex; align-items: center;
}
.promo .hero-escena { position: absolute; inset: 0; z-index: 0; }
.promo::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(95deg, rgba(12,22,17,.9), rgba(12,22,17,.45));
}
.promo .t { position: relative; z-index: 2; }
.promo .barra {
  width: 30px; height: 2.5px; border-radius: 2px;
  background: var(--primary-300); margin-bottom: 10px;
}
.promo b { display: block; font-size: 15.5px; font-weight: 650; }
.promo span { color: rgba(255, 255, 255, .78); font-size: 12.5px; }

/* =====================================================================
   9. FILAS DE LISTA
   ---------------------------------------------------------------------
   El patron de lista de toda la app. Area de toque de 44px o mas: es el
   minimo de Apple, y abajo de eso el dueno con guantes falla el toque.
   ===================================================================== */
.filas { display: flex; flex-direction: column; }
.filas > * + * { border-top: 1px solid var(--line-2); }

.fila {
  display: flex; align-items: center; gap: 11px;
  min-height: 54px; padding: 11px 18px;
  color: var(--ink); background: none; border: 0;
  font: inherit; text-align: left; cursor: pointer; width: 100%;
  transition: background .14s var(--curva);
}
.fila:hover { background: var(--surface-2); }
.fila .que { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.fila .que b { font-weight: 600; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; }
.fila .que span { color: var(--mut); font-size: 12.5px; overflow: hidden;
                  text-overflow: ellipsis; white-space: nowrap; }
.fila .n { font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fila .ch { color: var(--dim); display: flex; }
.fila .ch svg { width: 16px; height: 16px; }

/* El punto de estado: dice el estado sin gastar una palabra. */
.fila .pt { width: 8px; height: 8px; border-radius: 50%;
            background: var(--dim); flex: 0 0 8px; }
.fila.ok      .pt { background: var(--primary); }
.fila.alerta  .pt { background: var(--warn); }
.fila.peligro .pt { background: var(--danger); }
.fila.info    .pt { background: var(--info); }

.ic.cuadro {
  width: 34px; height: 34px; flex: 0 0 34px;
  display: grid; place-items: center; border-radius: var(--r-xs);
  background: var(--primary-soft); color: var(--primary);
  font-weight: 700; font-size: 13px;
}
.ic.cuadro svg { width: 17px; height: 17px; }

/* Avatar circular del cliente */
.avatar-cli {
  width: 36px; height: 36px; flex: 0 0 36px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--primary-soft); color: var(--primary);
  font-weight: 700; font-size: 13.5px;
}

/* =====================================================================
   10. INSIGNIAS DE ESTADO
   ---------------------------------------------------------------------
   Sobrias: fondo tenue, texto del mismo tono. Nada fosforescente.
   ===================================================================== */
.tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; font-weight: 680; letter-spacing: .055em;
  text-transform: uppercase; white-space: nowrap;
  padding: 4px 9px; border-radius: var(--r-pill);
  background: var(--line-2); color: var(--mut);
}
.tag::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: .9;
}
.tag.sin-punto::before { display: none; }
.tag.ok      { background: var(--primary-soft); color: var(--primary); }
.tag.info    { background: var(--info-soft);    color: var(--info); }
.tag.alerta  { background: var(--warn-soft);    color: var(--warn); }
.tag.peligro { background: var(--danger-soft);  color: var(--danger); }
.tag.dinero  { background: var(--revenue-soft); color: var(--revenue); }

.pill {
  margin-left: auto; font-size: 10.5px; font-weight: 700;
  min-width: 19px; padding: 1px 6px; border-radius: var(--r-pill);
  background: var(--danger); color: #fff; text-align: center;
}

/* =====================================================================
   11. TABLAS
   ---------------------------------------------------------------------
   Blancas, con separadores muy tenues y encabezado en versalitas. No
   deben parecer una hoja de calculo: por eso cero bordes verticales.
   ===================================================================== */
.tabla-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tabla { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.tabla th {
  padding: 10px 16px; text-align: left; white-space: nowrap;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
}
.tabla td {
  padding: 13px 16px; text-align: left;
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
}
.tabla tbody tr { transition: background .14s var(--curva); }
.tabla tbody tr:hover { background: var(--surface-2); }
.tabla tbody tr:last-child td { border-bottom: 0; }
.tabla td.n, .tabla th.n { text-align: right; font-variant-numeric: tabular-nums; }
.tabla td.fuerte { font-weight: 600; }
.tabla .celda-2 { display: flex; flex-direction: column; gap: 1px; }
.tabla .celda-2 b { font-weight: 600; }
.tabla .celda-2 span { color: var(--mut); font-size: 12px; }
.tabla .con-avatar { display: flex; align-items: center; gap: 11px; }
.tabla tr.clic { cursor: pointer; }
.tabla .total td { font-weight: 680; border-top: 2px solid var(--line);
                   background: var(--surface-2); }

/* En el telefono una tabla ancha no se encoge: se convierte en fichas.
   Cada celda lleva su etiqueta en data-et. Esto es lo que separa
   "pensado para movil" de "encogido". */
@media (max-width: 719px) {
  .tabla.a-fichas thead { display: none; }
  .tabla.a-fichas, .tabla.a-fichas tbody, .tabla.a-fichas tr,
  .tabla.a-fichas td { display: block; width: 100%; }
  .tabla.a-fichas tr { padding: 13px 16px; border-bottom: 1px solid var(--line-2); }
  .tabla.a-fichas tr:last-child { border-bottom: 0; }
  .tabla.a-fichas td {
    padding: 3px 0; border: 0;
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; text-align: right;
  }
  .tabla.a-fichas td[data-et]::before {
    content: attr(data-et);
    font-size: 10.5px; font-weight: 680; letter-spacing: .07em;
    text-transform: uppercase; color: var(--mut);
    text-align: left; flex: 0 0 auto;
  }
  /* La primera celda es el titulo de la ficha: ocupa el ancho y no lleva
     etiqueta. */
  .tabla.a-fichas td.ficha-t { display: block; text-align: left; margin-bottom: 7px; }
  .tabla.a-fichas td.ficha-t::before { display: none; }
  .tabla.a-fichas td.solo-ancho { display: block; text-align: left; }
  .tabla.a-fichas td.ocultar-movil { display: none; }
}

/* =====================================================================
   12. BOTONES
   ===================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 42px; padding: 0 17px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: var(--primary); color: #fff;
  font-family: inherit; font-size: 14px; font-weight: 600; line-height: 1;
  cursor: pointer; white-space: nowrap;
  box-shadow: var(--sh-xs);
  transition: background .16s var(--curva), transform .12s var(--curva),
              border-color .16s var(--curva), color .16s var(--curva);
  -webkit-user-select: none; user-select: none;
}
.btn:hover { background: var(--primary-700); }
.btn:active { transform: scale(.985); }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; }
.btn svg { width: 16px; height: 16px; flex: 0 0 16px; }

.btn.sec { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn.sec:hover { background: var(--surface-2); border-color: var(--dim); }
.btn.peligro { background: var(--danger); }
.btn.peligro:hover { background: #93322a; }
.btn.plano { background: none; color: var(--primary); box-shadow: none; padding: 0 10px; }
.btn.plano:hover { background: var(--primary-soft); }
.btn.chico { min-height: 34px; padding: 0 12px; font-size: 13px; }
.btn.chico svg { width: 14px; height: 14px; flex: 0 0 14px; }
.btn.ancho { width: 100%; }
.btn.oscuro { background: var(--sidebar); }
.btn.oscuro:hover { background: var(--sidebar-2); }
/* Sobre el hero, donde el fondo es la foto.
   Fondo OSCURO, no blanco translucido: con el SVG plano el blanco al 15%
   se leia, pero sobre una foto real el boton cayo justo encima de la
   maquina iluminada y se borro. El velo diagonal del hero se apaga hacia
   la derecha, que es exactamente donde queda este boton en el telefono. */
.btn.sobre-foto {
  background: rgba(13, 28, 20, .58); color: #fff;
  border-color: rgba(255, 255, 255, .34);
  /* SIN backdrop-filter: el hero scrollea con el documento y eso es lo
     que traba el iPhone (ver la regla de rendimiento de este archivo). */
}
.btn.sobre-foto:hover { background: rgba(13, 28, 20, .72); }

.acciones-dia { display: flex; flex-wrap: wrap; gap: 9px; }

/* Segmentos: pestanas dentro de una vista */
.segs {
  display: inline-flex; gap: 3px; padding: 3px;
  background: var(--line-2); border-radius: var(--r-sm);
  overflow-x: auto; scrollbar-width: none; max-width: 100%;
}
.segs::-webkit-scrollbar { display: none; }
.segs button {
  flex: 0 0 auto; min-height: 34px; padding: 0 15px;
  border: 0; border-radius: var(--r-xs); background: none;
  color: var(--mut); font-family: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer; white-space: nowrap;
  transition: background .16s var(--curva), color .16s var(--curva);
}
.segs button:hover { color: var(--ink); }
.segs button.on { background: var(--surface); color: var(--ink); box-shadow: var(--sh-xs); }
.segs.ancho { display: flex; }
.segs.ancho button { flex: 1 1 auto; }

/* =====================================================================
   13. FORMULARIOS
   ===================================================================== */
.campo { display: block; margin-bottom: 14px; }
.campo > span {
  display: block; font-size: 12.5px; font-weight: 600;
  color: var(--ink-2); margin-bottom: 6px;
}
.campo .pista { font-weight: 400; color: var(--dim); }

input, select, textarea {
  width: 100%; min-height: 44px; padding: 10px 13px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  /* 16px es el MINIMO: por debajo iOS hace zoom al enfocar. Y va SEPARADO
     del shorthand a proposito: 'font: 16px/1.4 inherit' no es CSS valido
     (el shorthand exige una familia al final) y el navegador tiraba la
     regla entera. */
  font-family: inherit; font-size: 16px; line-height: 1.4;
  appearance: none; -webkit-appearance: none;
  transition: border-color .16s var(--curva), box-shadow .16s var(--curva);
}
textarea { min-height: 86px; resize: vertical; line-height: 1.55; }
input:hover, select:hover, textarea:hover { border-color: var(--dim); }
input:focus, select:focus, textarea:focus {
  outline: 0; border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
}
input[readonly], input:disabled, select:disabled, textarea:disabled {
  background: var(--line-2); color: var(--mut); cursor: not-allowed;
}
input::placeholder, textarea::placeholder { color: var(--dim); }
input.err, select.err { border-color: var(--danger); }
input.err:focus { box-shadow: 0 0 0 3px var(--danger-soft); }

select {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--mut) 50%),
    linear-gradient(135deg, var(--mut) 50%, transparent 50%);
  background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
  padding-right: 34px; cursor: pointer;
}

/* Dinero y cantidad SIEMPRE son numeros cortos (nadie cobra $999,999.99
   por acre). Sin este tope, un campo asi dentro de una tarjeta ancha
   -- Mobilization, Contingency, un margen -- se estira de punta a punta
   con el numero pegado a la derecha y una franja vacia enorme a la
   izquierda: eso es lo que se ve "desordenado". El unico campo que
   necesita el ancho completo (el placeholder largo de "Round the
   price") pide salirse con .ancho. */
input.money, input.cant { text-align: right; font-variant-numeric: tabular-nums;
                           max-width: 240px; }
input.money.ancho, input.cant.ancho { max-width: none; }

/* Prefijo/sufijo pegado al campo: "$" a la izquierda, "acres" a la
   derecha. Dice la unidad sin gastar una etiqueta. */
.con-afijo { position: relative; display: block; }
.con-afijo .afijo {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: 13.5px; font-weight: 600; color: var(--mut); pointer-events: none;
}
.con-afijo .afijo.izq { left: 13px; }
.con-afijo .afijo.der { right: 13px; }
.con-afijo input.pre { padding-left: 28px; }
.con-afijo input.post { padding-right: 58px; }

/* margin-bottom: 14px iguala el de .campo suelto. Sin esto, una fila de
   dos campos no separaba NADA de lo que viniera despues (0px: .campo ya
   pone su 14px en 0 adentro de la fila, y la fila misma no ponia nada) --
   la caja de "Acres" quedaba pegada al boton de abajo. */
.fila-campos { display: grid; gap: 12px; grid-template-columns: 1fr; margin-bottom: 14px; }
.fila-campos.dos { grid-template-columns: 1fr 1fr; }
.fila-campos .campo { margin-bottom: 0; }

/* Ver / ocultar contrasena. En TODO campo de password. */
.con-ojo { position: relative; display: block; }
.con-ojo input { padding-right: 46px; }
.ojo {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px; display: grid; place-items: center;
  border: 0; background: none; color: var(--dim); cursor: pointer;
  border-radius: var(--r-xs);
}
.ojo svg { width: 19px; height: 19px; display: block; }
.ojo:hover { color: var(--ink-2); }
.ojo.on { color: var(--primary); }
.ojo.on::after {
  content: ""; position: absolute; left: 8px; right: 8px; top: 50%;
  height: 2px; margin-top: -1px; background: currentColor;
  border-radius: 2px; transform: rotate(-22deg);
}

.ayuda { color: var(--mut); font-size: 12.5px; margin: 6px 0 0; line-height: 1.5; }

.check {
  display: flex; align-items: flex-start; gap: 11px;
  min-height: 44px; padding: 8px 0; cursor: pointer;
}
/* Switch de verdad, no el checkbox nativo del navegador (un cuadrito
   casi invisible que no parece parte del sistema). appearance:none quita
   el dibujo del navegador; el resto lo pinta este CSS -- sin tocar el
   markup, sin JS nuevo, sigue siendo un <input type="checkbox"> comun
   para quien lea la pantalla. */
.check input {
  appearance: none; -webkit-appearance: none;
  width: 38px; height: 22px; min-height: 22px; flex: 0 0 38px;
  margin-top: 1px; border-radius: var(--r-pill);
  background: var(--line-2); border: 1px solid var(--line);
  position: relative; cursor: pointer;
  transition: background .16s var(--curva), border-color .16s var(--curva);
}
.check input::before {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff; box-shadow: var(--sh-xs);
  transition: transform .16s var(--curva);
}
.check input:checked { background: var(--primary); border-color: var(--primary); }
.check input:checked::before { transform: translateX(16px); }
.check input:focus-visible {
  outline: 0; box-shadow: 0 0 0 3px var(--primary-ring);
}
.check input:disabled { opacity: .5; cursor: not-allowed; }
.check span { flex: 1; font-size: 14px; }
.check span small { display: block; color: var(--mut); font-size: 12.5px; margin-top: 2px; }

/* Bloque de formulario numerado: la estructura del constructor de
   estimates y de Settings. */
.bloque + .bloque { margin-top: 16px; }
.bloque-cab {
  display: flex; align-items: center; gap: 11px;
  padding: 15px 18px; border-bottom: 1px solid var(--line-2);
}
.bloque-cab .n {
  width: 24px; height: 24px; flex: 0 0 24px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--primary); color: #fff;
  font-size: 11.5px; font-weight: 700;
}
.bloque-cab .t { flex: 1; min-width: 0; }
.bloque-cab .t b { display: block; font-size: 14.5px; font-weight: 650; }
.bloque-cab .t span { color: var(--mut); font-size: 12.5px; }
.bloque-cab .monto { font-weight: 680; font-variant-numeric: tabular-nums; }

/* =====================================================================
   14. BANDAS Y AVISOS
   ===================================================================== */
.banda {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 11px 14px; border-radius: var(--r-sm);
  font-size: 13.5px; line-height: 1.5;
  border: 1px solid; border-left-width: 3px;
}
/* El :not(.ic) es lo que hace que la banda CON icono funcione. Sin el,
   el icono tambien es span:first-child, y esa regla le gana por
   especificidad a la de abajo: el icono se estiraba a 180 px y empujaba
   el texto a la derecha de la banda. Se vio al estrenar el calendario. */
.banda > span:first-child:not(.ic) { flex: 1; min-width: 180px; }
/* Si la banda lleva icono, el que se estira es el texto, no el icono. */
.banda > .ic { flex: 0 0 17px; min-width: 0; display: flex; margin-top: 1px;
               align-self: flex-start; }
.banda > .ic svg { width: 17px; height: 17px; }
.banda > .ic + span { flex: 1; min-width: 180px; }
/* La banda que explica "margin vs markup" en Rates: sin este margen el
   siguiente campo (Minimum profit) quedaba pegado justo debajo, sin
   respirar -- el unico lugar de la pantalla donde faltaba el espacio de
   14px que SI lleva todo .campo. */
.nota-margen { margin: 2px 0 14px; }

.banda.info    { background: var(--info-soft);    border-color: var(--info);    color: var(--ink); }
.banda.alerta  { background: var(--warn-soft);    border-color: var(--warn);    color: var(--ink); }
.banda.peligro { background: var(--danger-soft);  border-color: var(--danger);  color: var(--ink); }
.banda.ok      { background: var(--primary-soft); border-color: var(--primary); color: var(--ink); }
.banda.neutra  { background: var(--surface-2); border-color: var(--line);
                 border-left-color: var(--dim); color: var(--ink-2); }

/* Nota explicativa dentro de una tarjeta (la de "reference price"). */
.nota-info {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 13px 15px; border-radius: var(--r-sm);
  background: var(--info-soft); border: 1px solid rgba(38, 96, 143, .2);
  font-size: 13px; line-height: 1.55; color: var(--ink-2);
}
.nota-info .ic { color: var(--info); flex: 0 0 17px; margin-top: 1px; display: flex; }
.nota-info .ic svg { width: 17px; height: 17px; }
.nota-info b { color: var(--ink); }

#toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--navh) + env(safe-area-inset-bottom) + 18px);
  z-index: 120; display: flex; flex-direction: column; gap: 8px;
  align-items: center; pointer-events: none;
  width: calc(100% - 32px); max-width: 420px;
}
.toast {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 17px; border-radius: var(--r-pill);
  background: var(--sidebar); color: #fff;
  font-size: 13.5px; font-weight: 550;
  box-shadow: var(--sh-lg);
  animation: sube .22s var(--curva);
}
.toast.ok  { background: var(--primary); }
.toast.err { background: var(--danger); }
@keyframes sube { from { opacity: 0; transform: translateY(10px); } }
@keyframes aparece { from { opacity: 0; } }

/* =====================================================================
   15. MODALES
   ---------------------------------------------------------------------
   En el telefono entran como hoja desde abajo, que es lo que el pulgar
   alcanza. En escritorio van centrados.
   ===================================================================== */
.modal-bg {
  position: fixed; inset: 0; z-index: 130;
  display: flex; align-items: flex-end; justify-content: center;
  /* Fondo solido traslucido SIN backdrop-filter: el desenfoque en vivo
     sobre el contenido traba iPhone y la hoja entra a tirones. */
  background: rgba(13, 22, 17, .52);
  animation: aparece .18s var(--curva);
}
.modal {
  width: 100%; max-width: 560px;
  max-height: calc(100dvh - env(safe-area-inset-top) - 20px);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: 16px 16px 0 0;
  box-shadow: var(--sh-lg);
  animation: hoja .24s var(--curva);
}
.modal.ancho { max-width: 880px; }
@keyframes hoja { from { transform: translateY(16px); } }

.modal-cab {
  display: flex; align-items: center; gap: 11px;
  padding: 16px 18px 13px; border-bottom: 1px solid var(--line);
}
.modal-cab h3 { flex: 1; }
.cerrar-x {
  width: 34px; height: 34px; display: grid; place-items: center;
  border: 0; background: var(--line-2); color: var(--mut);
  border-radius: 50%; font-size: 19px; line-height: 1; cursor: pointer;
  transition: background .14s var(--curva);
}
.cerrar-x:hover { background: var(--line); color: var(--ink); }
.modal-cuerpo {
  padding: 17px 18px; overflow-y: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.modal-pie {
  display: flex; gap: 9px; justify-content: flex-end;
  padding: 13px 18px calc(13px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--surface-2);
  border-radius: 0 0 16px 16px;
}
.modal-pie .btn { flex: 1; }

/* =====================================================================
   16. ESTADOS VACIOS
   ---------------------------------------------------------------------
   Con una escena de crestas propia, no una ilustracion generica que no
   tiene nada que ver con el oficio.
   ===================================================================== */
.vacio {
  text-align: center; padding: 42px 22px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.vacio .escena {
  width: 112px; height: 62px; margin-bottom: 14px;
  color: var(--primary); opacity: .3;
}
.vacio .escena svg { width: 100%; height: 100%; }
.vacio b { font-size: 15.5px; font-weight: 650; color: var(--ink); }
.vacio p { color: var(--mut); font-size: 13.5px; max-width: 46ch; margin: 2px 0 0; }
.vacio .acciones-dia { margin-top: 16px; justify-content: center; }

/* =====================================================================
   17. ESQUELETOS DE CARGA
   ---------------------------------------------------------------------
   Mejor que un "Cargando...": el ojo ya sabe donde va a aparecer cada
   cosa y la espera se siente mas corta.
   ===================================================================== */
.sk {
  background: linear-gradient(90deg, var(--line-2) 25%, var(--line) 37%,
              var(--line-2) 63%);
  background-size: 400% 100%;
  animation: brilla 1.3s ease-in-out infinite;
  border-radius: 6px;
}
@keyframes brilla { 0% { background-position: 100% 50%; }
                    100% { background-position: 0 50%; } }
.sk-linea { height: 11px; margin-bottom: 9px; }
.sk-linea.corta { width: 42%; }
.sk-linea.media { width: 68%; }
.sk-fila {
  display: flex; align-items: center; gap: 12px; padding: 15px 18px;
  border-bottom: 1px solid var(--line-2);
}
.sk-circ { width: 36px; height: 36px; border-radius: 50%; flex: 0 0 36px; }
.sk-fila .c { flex: 1; }
.sk-fila .c .sk-linea:last-child { margin-bottom: 0; }

/* =====================================================================
   18. EL COTIZADOR
   ---------------------------------------------------------------------
   Es la pantalla mas dificil del producto: diez campos numericos en un
   iPhone es donde un sistema se abandona. Dos decisiones lo salvan: el
   resumen va PEGADO y siempre a la vista, y el costo va en bloques con
   su subtotal visible.
   ===================================================================== */
.constructor { display: grid; gap: 16px; grid-template-columns: 1fr; }
.constructor > .col-izq { display: flex; flex-direction: column; gap: 16px;
                          min-width: 0; }
.constructor > .col-der { display: flex; flex-direction: column; gap: 14px;
                          min-width: 0; }

/* ---------------------------------------------------------------------
   EL RESUMEN DE PRECIO, EN DOS FORMAS.

   EN EL TELEFONO va FIJO sobre la barra de pestanas, como el total de un
   carrito. Antes vivia al final del formulario y eso era justo lo
   contrario de lo que tiene que hacer: el dueno toca diez campos de
   costo y el numero que le importa quedaba a dos pantallas de scroll.
   Fijo abajo esta siempre a la vista y encima del pulgar.

   EN ESCRITORIO vuelve a ser una tarjeta normal en la columna derecha,
   donde ya se ve sin ayuda.
   --------------------------------------------------------------------- */
.resumen-precio {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--navh) + env(safe-area-inset-bottom));
  z-index: 48;
  background: var(--surface);
  border-top: 3px solid var(--primary);
  border-bottom: 1px solid var(--line);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -6px 22px rgba(20, 34, 27, .13);
  padding: 11px calc(env(safe-area-inset-right) + var(--px))
           11px calc(env(safe-area-inset-left) + var(--px));
}
/* Compacto: costo y precio en la MISMA linea, y el detalle debajo.
   El contenedor pasa a flex para que las dos lineas sean items que se
   encogen a su contenido; con ellas como bloques quedaban apiladas y la
   barra se comia un tercio de la pantalla. */
@media (max-width: 1023px) {
  .resumen-precio {
    display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 20px; row-gap: 2px;
  }
  .resumen-precio .linea { flex: 0 1 auto; gap: 8px; justify-content: flex-start; }
  .resumen-precio .linea + .linea { margin-top: 0; margin-left: auto; }
  .resumen-precio .costo { font-size: .95rem; }
  .resumen-precio .precio { font-size: 1.45rem; }
  .resumen-precio .detalle {
    flex: 1 0 100%; margin-top: 7px; padding-top: 7px;
    font-size: 11.5px; gap: 2px 13px;
  }
  .resumen-precio .banda { flex: 1 0 100%; }
  /* El formulario deja sitio para la barra: sin esto el ultimo campo
     queda debajo y no se puede tocar. */
  .constructor { padding-bottom: 110px; }
}
.resumen-precio .linea {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.resumen-precio .linea + .linea { margin-top: 3px; }
.resumen-precio .et {
  font-size: 10.5px; font-weight: 680; letter-spacing: .085em;
  text-transform: uppercase; color: var(--mut);
}
.resumen-precio .costo {
  font-size: 1rem; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.resumen-precio .precio {
  font-size: 1.95rem; font-weight: 700; letter-spacing: -.03em;
  color: var(--primary); font-variant-numeric: tabular-nums;
}
.resumen-precio .detalle {
  display: flex; flex-wrap: wrap; gap: 5px 16px;
  margin-top: 11px; padding-top: 11px;
  border-top: 1px solid var(--line-2);
  color: var(--mut); font-size: 12.5px;
}
.resumen-precio .detalle b { color: var(--ink); font-variant-numeric: tabular-nums; }
.resumen-precio.bajo-costo { border-top-color: var(--danger); }
.resumen-precio.bajo-costo .precio { color: var(--danger); }

/* La ecuacion que explica de donde sale el numero. Es lo que convierte el
   cotizador de una caja negra en algo que el dueno entiende. */
.ecuacion {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; flex-wrap: wrap;
  padding: 13px 15px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--line-2);
}
.ecuacion .t { text-align: center; }
.ecuacion .t b { display: block; font-size: 15px; font-weight: 680;
                 font-variant-numeric: tabular-nums; }
.ecuacion .t span { font-size: 10.5px; color: var(--mut);
                    text-transform: uppercase; letter-spacing: .06em;
                    font-weight: 650; }
.ecuacion .op { color: var(--dim); font-size: 15px; font-weight: 500; }
.ecuacion .t.res b { color: var(--primary); }

/* Bloques de costo, en acordeon con su subtotal visible cerrado */
.acordeon {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); overflow: hidden;
}
.acordeon + .acordeon { margin-top: 10px; }
.acordeon > summary {
  display: flex; align-items: center; gap: 11px;
  min-height: 56px; padding: 11px 17px; cursor: pointer;
  list-style: none; font-weight: 600;
  transition: background .14s var(--curva);
}
.acordeon > summary:hover { background: var(--surface-2); }
.acordeon > summary::-webkit-details-marker { display: none; }
.acordeon > summary .ic-b {
  width: 28px; height: 28px; flex: 0 0 28px;
  display: grid; place-items: center; border-radius: var(--r-xs);
  background: var(--primary-soft); color: var(--primary);
}
.acordeon > summary .ic-b svg { width: 15px; height: 15px; }
.acordeon > summary .sub-t { flex: 1; min-width: 0; }
.acordeon > summary .sub-t small {
  display: block; font-weight: 400; color: var(--mut); font-size: 12.5px;
}
.acordeon > summary .monto {
  font-variant-numeric: tabular-nums; font-weight: 680; white-space: nowrap;
}
.acordeon > summary .fle { color: var(--dim); display: flex;
                           transition: transform .2s var(--curva); }
.acordeon > summary .fle svg { width: 16px; height: 16px; }
.acordeon[open] > summary { border-bottom: 1px solid var(--line-2); }
.acordeon[open] > summary .fle { transform: rotate(90deg); }
.acordeon .cuerpo { padding: 16px 17px; }

/* La maquina dentro del costeo */
.maq {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 13px; margin-bottom: 11px; background: var(--surface);
  transition: border-color .16s var(--curva), background .16s var(--curva);
}
.maq.activa { border-color: var(--primary-300); background: var(--primary-soft); }
.maq-cab { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
.maq-cab b { flex: 1; }

/* El selector de margen: botones grandes, porque es LA decision */
.margenes { display: flex; gap: 7px; flex-wrap: wrap; }
.margenes button {
  /* 28% de base: con los seis atajos quedan 3 y 3 en el telefono. Con una
     base en px entraban 5 y el sexto caia solo en una fila, que se ve
     como un error de maquetado y no como una decision. */
  flex: 1 1 28%; min-height: 46px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font-family: inherit; font-size: 15px; font-weight: 650;
  cursor: pointer; font-variant-numeric: tabular-nums;
  transition: background .14s var(--curva), border-color .14s var(--curva),
              color .14s var(--curva);
}
.margenes button:hover { border-color: var(--primary-300); }
.margenes button.on { background: var(--primary); color: #fff; border-color: var(--primary); }

/* Desglose en modo lectura */
.desglose { display: flex; flex-direction: column; }
.desglose .d {
  display: flex; align-items: baseline; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line-2); font-size: 13.5px;
}
.desglose .d .et { flex: 1; }
.desglose .d .et small { display: block; color: var(--dim); font-size: 12px; }
.desglose .d .v { font-variant-numeric: tabular-nums; white-space: nowrap;
                  font-weight: 550; }
.desglose .d.sumatoria {
  border-top: 2px solid var(--line); border-bottom: 0;
  font-weight: 680; margin-top: 5px; padding-top: 11px; font-size: 15px;
}
.desglose .d.sumatoria .v { color: var(--ink); }

/* Las opciones A/B/C */
.opciones { display: grid; gap: 12px; grid-template-columns: 1fr; }
/* Con una sola opcion ocupa el ancho entero: dejarla en una columna
   de tres se ve como maquetado roto, no como diseno. */
.opciones.una { grid-template-columns: 1fr !important; }
.opcion {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 16px; background: var(--surface);
  position: relative; transition: border-color .16s var(--curva);
}
.opcion.recomendada { border-color: var(--primary); box-shadow: var(--sh); }
.opcion .op-cab { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.opcion .op-cab b { flex: 1; font-size: 14.5px; }
.opcion .op-precio {
  font-size: 1.55rem; font-weight: 700; letter-spacing: -.028em;
  font-variant-numeric: tabular-nums;
}
.opcion .op-sub { color: var(--mut); font-size: 12.5px; margin-top: 2px; }

.alcance { list-style: none; padding: 0; margin: 11px 0 0; }
.alcance li {
  display: flex; gap: 9px; padding: 4px 0; font-size: 13.5px; line-height: 1.5;
}
.alcance li .mk { flex: 0 0 15px; color: var(--primary); margin-top: 2px; display: flex; }
.alcance li .mk svg { width: 14px; height: 14px; }
.alcance.no li { color: var(--mut); }
.alcance.no li .mk { color: var(--dim); }

/* =====================================================================
   19. BUSCADOR Y FILTROS
   ===================================================================== */
.barra-filtros { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.barra-filtros .buscador { flex: 1 1 230px; min-width: 0; }
.barra-filtros select { width: auto; min-width: 145px; }

.buscador { position: relative; }
.buscador input { padding-left: 40px; }
.buscador .ic-b {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: var(--dim); pointer-events: none; display: flex;
}
.buscador .ic-b svg { width: 17px; height: 17px; }

/* =====================================================================
   20. BOTON FLOTANTE (telefono)
   ===================================================================== */
.fab {
  position: fixed;
  right: calc(var(--px) + env(safe-area-inset-right));
  bottom: calc(var(--navh) + env(safe-area-inset-bottom) + 16px);
  z-index: 55;
  width: 52px; height: 52px;
  border: 0; border-radius: 50%;
  background: var(--primary); color: #fff;
  display: grid; place-items: center;
  box-shadow: var(--sh-lg); cursor: pointer;
  transition: transform .14s var(--curva), background .16s var(--curva);
}
.fab:hover { background: var(--primary-700); }
.fab:active { transform: scale(.94); }
.fab svg { width: 22px; height: 22px; }

/* =====================================================================
   21. LOGIN
   ---------------------------------------------------------------------
   Pantalla partida: la escena ocupa un lado entero y el formulario el
   otro. Es el patron del software B2B serio, y resuelve un problema real
   que tuvo la primera version -- una escena de fondo detras de la caja
   hay que velarla tanto para que el texto se lea que termina siendo una
   mancha gris: ni se ve la escena ni aporta nada.

   Partida, la escena se ve ENTERA y el formulario queda sobre fondo
   limpio. Por debajo de 900px la escena desaparece: en un telefono
   robaria la mitad de la pantalla y lo que el dueno quiere es entrar.
   ===================================================================== */
/* El acceso directo del tema en el login. Flota sobre la esquina y
   respeta el notch; se queda arriba del todo porque en el telefono la
   caja del login sube hasta el borde y si no se le monta encima. */
.tema-flotante {
  position: fixed; z-index: 60;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  right: calc(env(safe-area-inset-right, 0px) + 12px);
  box-shadow: var(--sh);
}

.login-pantalla {
  min-height: 100dvh;
  display: grid;
  grid-template-columns: 1fr;
  background: var(--bg);
}

.login-escena {
  display: none;              /* solo escritorio */
  position: relative;
  overflow: hidden;
  background: var(--sidebar);
}
.login-escena svg { width: 100%; height: 100%; object-fit: cover; display: block; }

/* El texto de marca sobre la escena */
.login-escena .marca-sobre {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: 44px 46px;
  color: #fff;
  background: linear-gradient(to top,
    rgba(10, 20, 15, .88) 0%, rgba(10, 20, 15, .45) 55%, transparent 100%);
}
.login-escena .marca-sobre .barra {
  width: 34px; height: 3px; border-radius: 2px;
  background: var(--primary-300); margin-bottom: 16px;
}
.login-escena .marca-sobre h2 {
  color: #fff; font-size: 1.9rem; letter-spacing: -.025em; line-height: 1.12;
  margin-bottom: 8px; max-width: 16ch;
}
.login-escena .marca-sobre p {
  color: rgba(255, 255, 255, .78); font-size: 14px; margin: 0; max-width: 42ch;
}

.login-caja-wrap {
  display: flex; align-items: center; justify-content: center;
  padding: max(28px, env(safe-area-inset-top)) 20px
           max(28px, env(safe-area-inset-bottom));
}
.login-caja {
  width: 100%; max-width: 392px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 16px; padding: 28px 24px; box-shadow: var(--sh-md);
}
.login-marca { text-align: center; margin-bottom: 22px; }
.login-marca img { width: 54px; height: 54px; border-radius: 13px; margin: 0 auto; }
.login-marca h1 { font-size: 1.28rem; margin-top: 12px; }
.login-marca p { color: var(--mut); font-size: 13px; margin: 3px 0 0; }
.login-pie {
  text-align: center; margin-top: 20px;
  color: var(--dim); font-size: 12px; line-height: 1.9;
}
.login-pie a { color: var(--mut); }
.login-pie a:hover { color: var(--primary); }

@media (min-width: 900px) {
  .login-pantalla { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .login-escena { display: block; }
  /* Sin sombra ni borde: el contraste ya lo da la escena al lado. */
  .login-caja { border: 0; box-shadow: none; background: none; max-width: 374px; }
}

/* =====================================================================
   22. EL DOCUMENTO IMPRIMIBLE EN PANTALLA (camino de iOS)
   ===================================================================== */
.doc-overlay {
  position: fixed; inset: 0; z-index: 140;
  background: var(--bg); display: flex; flex-direction: column;
}
.doc-barra {
  display: flex; align-items: center; gap: 9px;
  padding: calc(env(safe-area-inset-top) + 11px) 15px 11px;
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.doc-barra b { flex: 1; font-size: 14.5px; }
.doc-scroll {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 16px;
}
.doc-hoja {
  background: #fff; color: #111;
  max-width: 8.5in; margin: 0 auto; padding: .5in;
  box-shadow: var(--sh-lg);
  -webkit-user-select: text; user-select: text;
}
.doc-hoja * { color: inherit; }

/* =====================================================================
   23. TABLETA
   ===================================================================== */
@media (min-width: 720px) {
  :root { --px: 24px; }
  .grid.c2 { grid-template-columns: 1fr 1fr; }
  .grid.c3 { grid-template-columns: repeat(3, 1fr); }
  .grid.c4 { grid-template-columns: repeat(4, 1fr); }
  .fila-campos.tres { grid-template-columns: repeat(3, 1fr); }
  .opciones { grid-template-columns: repeat(2, 1fr); }
  .top-meta { display: flex; }
  .hero { min-height: 210px; padding: 26px; }
  .hero-txt h2 { font-size: 2.3rem; }
  .hero.hero-sec .hero-txt h2 { font-size: 1.7rem; }
}

/* =====================================================================
   24. ESCRITORIO
   ---------------------------------------------------------------------
   DOS tokens cambian el layout entero: --sidew abre el espacio de la
   barra lateral fija y --navh apaga de golpe todo lo movil (el espacio
   reservado abajo, la barra de pestanas y el boton flotante).
   El escritorio de la secretaria y el iPhone del dueno son el MISMO CSS.
   ===================================================================== */
@media (min-width: 1024px) {
  :root { --navh: 0px; --sidew: 248px; --px: 28px; --toph: 62px; }

  .sidenav { transform: none; }
  .velo-nav, .hamb, .botnav, .fab { display: none !important; }

  /* En escritorio el titulo de la ruta ya lo dice la barra lateral
     marcada y el encabezado de la pagina. Repetirlo en la barra superior
     es decir lo mismo tres veces. En el telefono SI se queda: ahi no hay
     barra lateral a la vista y es lo unico que dice donde estas. */
  .topbar h1 { display: none; }
  .topbar .acciones { margin-left: auto; }

  .view { padding-top: 22px; gap: 18px; }

  .grid.principal { grid-template-columns: minmax(0, 1.72fr) minmax(0, 1fr); }
  .opciones { grid-template-columns: repeat(3, 1fr); }

  .hero { min-height: 232px; padding: 30px; }
  .hero-txt h2 { font-size: 2.5rem; }
  .hero.hero-sec .hero-txt h2 { font-size: 1.9rem; }

  /* El modal deja de ser hoja de abajo y se centra */
  .modal-bg { align-items: center; }
  .modal { border-radius: 16px; max-height: calc(100dvh - 80px); }
  .modal-pie { padding-bottom: 13px; border-radius: 0 0 16px 16px; }
  .modal-pie .btn { flex: 0 0 auto; }

  #toast { bottom: 26px; }

  /* En el constructor el resumen vive en la columna derecha, como en un
     presupuestador de verdad: los campos a la izquierda y el numero
     siempre al lado. */
  .constructor { grid-template-columns: minmax(0, 1fr) 342px; align-items: start;
                 padding-bottom: 0; }
  .constructor > .col-der { position: sticky; top: calc(var(--toph) + 16px); }
  .constructor > .col-der .resumen-precio {
    position: static;
    border: 1px solid var(--line);
    border-top: 3px solid var(--primary);
    border-radius: var(--r);
    box-shadow: var(--sh);
    padding: 15px 17px;
  }
  .constructor > .col-der .resumen-precio.bajo-costo { border-top-color: var(--danger); }
}

@media (min-width: 1400px) { :root { --px: 34px; } }

/* =====================================================================
   25. MOVIMIENTO REDUCIDO
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* =====================================================================
   26. IMPRESION
   ---------------------------------------------------------------------
   Del cascaron de la app NO sale nada: ni barra, ni pestanas, ni
   botones. Solo la hoja.
   ===================================================================== */
@media print {
  :root { --navh: 0px; --sidew: 0px; }
  body { background: #fff; color: #000; }
  .app, .topbar, .botnav, .sidenav, .fab, #toast, #bandas,
  .modal-bg, .doc-barra, .velo-nav, .noprint { display: none !important; }
  .doc-overlay { position: static; inset: auto; background: #fff; display: block; }
  .doc-scroll { overflow: visible; padding: 0; }
  .doc-hoja { max-width: none; margin: 0; padding: 0; box-shadow: none;
              background: #fff; color: #000; }
  tr { break-inside: avoid; }
  thead { display: table-header-group; }
  a { color: #000; text-decoration: none; }
}

@page { margin: 0.5in; }

/* =====================================================================
   27. EL COLOR DE AMBIENTE (LOS DOS TEMAS)
   ---------------------------------------------------------------------
   Detras de lo que ya es importante -- la foto del hero, el boton
   principal, las insignias de los KPI, la ruta activa -- va un acento de
   color. Eso es lo que separa el sistema de un formulario plano.

   EL MISMO GESTO, TRADUCIDO A CADA TEMA (oct 2026, v12):
     - en OSCURO es luz: un halo que irradia sobre el fondo casi negro.
     - en CLARO es sombra TENIDA: la sombra toma el color del elemento en
       vez de ser gris. Un halo sobre papel no existe -- lo unico que
       logra es ensuciar el blanco.
   Por eso aqui hay UNA sola regla por elemento y la diferencia vive en
   los tokens --glow-* de la seccion 1. Antes esta seccion era solo del
   oscuro; el claro quedo sin color y se veia como un formulario.

   LO QUE NO CAMBIA: el claro sigue siendo la camioneta al sol. Ningun
   acento de estos toca el contraste del texto ni tapa un dato; todos
   viven en sombras y bordes.

   Va al FINAL del archivo a proposito: mismas clases, misma
   especificidad que sus reglas base, y en CSS con empate de
   especificidad gana el que esta despues. Ponerlo aqui evita el juego
   de !important.
   ===================================================================== */
.hero { box-shadow: var(--sh-lg), 0 0 46px rgba(76, 145, 104, .16); }
:root:not([data-tema="oscuro"]) .hero {
  box-shadow: var(--sh-lg), 0 14px 38px rgba(31, 94, 59, .22);
}

.kpi-ic { box-shadow: var(--glow-primary); }
.kpi.ok      .kpi-ic { box-shadow: var(--glow-primary); }
.kpi.info    .kpi-ic { box-shadow: var(--glow-info); }
.kpi.alerta  .kpi-ic { box-shadow: var(--glow-warn); }
.kpi.peligro .kpi-ic { box-shadow: var(--glow-danger); }
.kpi.dinero  .kpi-ic { box-shadow: var(--glow-revenue); }

/* El boton principal: el acento esta apenas en reposo y sube al pasar
   el mouse o enfocar. Los demas (.sec, .peligro, .plano, .oscuro,
   .sobre-foto) se quedan sin nada -- son acciones secundarias, y un
   boton que grita menos que el otro es la jerarquia haciendo su
   trabajo. */
.btn:not(.sec):not(.peligro):not(.plano):not(.oscuro):not(.sobre-foto):not(:disabled) {
  box-shadow: var(--sh-xs), 0 0 0 1px rgba(76, 145, 104, .3),
              0 0 14px rgba(76, 145, 104, .22);
}
.btn:not(.sec):not(.peligro):not(.plano):not(.oscuro):not(.sobre-foto):not(:disabled):hover {
  box-shadow: var(--sh-xs), 0 0 0 1px rgba(76, 145, 104, .5),
              0 0 26px rgba(76, 145, 104, .4);
}
/* En claro el boton ya es verde oscuro sobre blanco: lo que lo levanta
   es una sombra verde debajo, no un anillo alrededor. */
:root:not([data-tema="oscuro"]) .btn:not(.sec):not(.peligro):not(.plano):not(.oscuro):not(.sobre-foto):not(:disabled) {
  box-shadow: 0 2px 4px rgba(31, 94, 59, .18), 0 8px 20px rgba(31, 94, 59, .26);
}
:root:not([data-tema="oscuro"]) .btn:not(.sec):not(.peligro):not(.plano):not(.oscuro):not(.sobre-foto):not(:disabled):hover {
  box-shadow: 0 3px 6px rgba(31, 94, 59, .24), 0 12px 28px rgba(31, 94, 59, .34);
}

/* La ruta activa de la barra lateral: el verde ya la marca, el halo
   solo la separa un poco mas del resto. La barra es verde-carbon en LOS
   DOS temas, asi que aqui el gesto si es el mismo. */
.sidenav a.on {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1),
              0 0 22px rgba(76, 145, 104, .3);
}

/* Tarjetas con link (las 4 del KPI, filas de listas): el acento llega
   SOLO al pasar el mouse -- en reposo el tablero se queda quieto. */
a.kpi:hover, button.kpi:hover {
  box-shadow: var(--sh), 0 0 30px rgba(76, 145, 104, .18);
}
:root:not([data-tema="oscuro"]) a.kpi:hover,
:root:not([data-tema="oscuro"]) button.kpi:hover {
  box-shadow: var(--sh-md), 0 10px 26px rgba(31, 94, 59, .18);
}

/* =====================================================================
   28. CONTRASTE DEL MODO OSCURO -- ELEMENTOS ANIDADOS
   ---------------------------------------------------------------------
   Bug real, reportado desde la pantalla del cotizador: los inputs, el
   boton "Work out time and cost" y los bloques de costo usaban el MISMO
   --surface que la .card que los contiene. En claro eso no se nota
   (--surface es blanco puro sobre un fondo crema, siempre se despega),
   pero en oscuro --surface es un verde casi negro IGUAL al de la
   tarjeta que lo rodea, asi que el campo desaparece contra su propio
   contenedor -- no hay borde ni relieve que lo salve.

   La correccion NO es una pasada de "subir --surface": esto cambiaria
   tablas, encabezados y todo lo demas que ya esta bien. En vez de eso,
   cada pieza anidada pinta un lavado BLANCO translucido encima de lo
   que tenga detras (su .card, su .modal, su .acordeon). Un bano de
   blanco siempre aclara, sin importar que tan oscuro sea el fondo real
   detras -- por eso funciona igual dentro de una tarjeta, un modal o un
   acordeon anidado dos niveles, sin tener que calcular el tono exacto
   de cada contenedor padre. Mientras mas anidado, un poco mas de
   blanco: así cada nivel se separa del anterior en vez de fundirse.
   ===================================================================== */

/* Campos de formulario: HUNDIDOS, no iluminados -- se pintan mas
   OSCUROS que la tarjeta, con una sombra adentro, como un hueco donde se
   escribe. ":not([type=checkbox]):not([type=radio])" es a proposito: el
   switch de .check ya tiene su propio fondo (gris apagado / verde
   encendido) definido mas arriba, y esta regla generica empataba en
   especificidad con ".check input:checked" -- como esta regla va
   DESPUES en el archivo, ganaba ella, y el switch se quedaba siempre del
   mismo gris sin importar si estaba prendido o apagado. Comprobado
   leyendo el color ya calculado en pantalla, no a ojo. */
:root[data-tema="oscuro"] input:not([type="checkbox"]):not([type="radio"]),
:root[data-tema="oscuro"] select,
:root[data-tema="oscuro"] textarea {
  background: rgba(0, 0, 0, .22);
  border-color: rgba(255, 255, 255, .14);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .35);
}
:root[data-tema="oscuro"] input:not([type="checkbox"]):not([type="radio"]):hover,
:root[data-tema="oscuro"] select:hover,
:root[data-tema="oscuro"] textarea:hover {
  border-color: rgba(255, 255, 255, .24);
}
:root[data-tema="oscuro"] input[readonly],
:root[data-tema="oscuro"] input:disabled:not([type="checkbox"]):not([type="radio"]),
:root[data-tema="oscuro"] select:disabled,
:root[data-tema="oscuro"] textarea:disabled {
  background: rgba(0, 0, 0, .12); box-shadow: none;
}

/* Los botones, al reves que los campos: LEVANTADOS, mas claros que la
   tarjeta y sin la sombra de adentro. Es la misma logica que un
   formulario de papel -- la casilla donde se escribe se ve hundida, el
   botón que se aprieta se ve de relieve. Con los dos tratamientos
   opuestos, "Work out time and cost" deja de confundirse con el campo
   de "Acres" de arriba. */
:root[data-tema="oscuro"] .btn.sec {
  background: rgba(255, 255, 255, .08);
  border-color: rgba(255, 255, 255, .24);
}
:root[data-tema="oscuro"] .btn.sec:hover {
  background: rgba(255, 255, 255, .13);
  border-color: rgba(255, 255, 255, .34);
}

/* Los switch on/off: fondo explicito en los dos estados para que NADA
   mas adelante en el archivo pueda volver a pisarlos por empate de
   especificidad (el bug de arriba). */
:root[data-tema="oscuro"] .check input { background: rgba(255, 255, 255, .1); }
:root[data-tema="oscuro"] .check input:checked { background: var(--primary); }

/* Los acordeones de costo (Labor / Machines / Other) y, adentro, cada
   maquina: dos niveles anidados dentro de la misma tarjeta. */
:root[data-tema="oscuro"] .acordeon {
  background: rgba(255, 255, 255, .03);
  border-color: rgba(255, 255, 255, .14);
}
:root[data-tema="oscuro"] .maq {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .16);
}

/* Los atajos de margen (5% / 10% / 15%...): tambien son botones, mismo
   tratamiento levantado que .btn.sec -- si no, se leen como mas campos
   de formulario en vez de atajos para tocar. */
:root[data-tema="oscuro"] .margenes button {
  background: rgba(255, 255, 255, .08);
  border-color: rgba(255, 255, 255, .22);
}
:root[data-tema="oscuro"] .margenes button:hover {
  background: rgba(255, 255, 255, .13);
  border-color: rgba(255, 255, 255, .32);
}

/* Las tarjetas de opcion A/B/C: van dentro de card-cuerpo, mismo bug. */
:root[data-tema="oscuro"] .opcion {
  background: rgba(255, 255, 255, .035);
  border-color: rgba(255, 255, 255, .15);
}

/* El control segmentado (7/30/90 dias): el chip activo tiene que verse
   MAS CLARO que su propia pista, y en oscuro --surface quedaba mas
   oscuro que --line-2 -- la pastilla "activa" se hundia en vez de
   resaltar. */
:root[data-tema="oscuro"] .segs button.on {
  background: rgba(255, 255, 255, .1);
}

/* Los botones redondos de la barra superior (menu, notificaciones,
   perfil) y el chip de la fecha: viven dentro de .topbar, que ya es
   --surface, mismo bug otra vez. */
:root[data-tema="oscuro"] .hamb,
:root[data-tema="oscuro"] .top-fecha,
:root[data-tema="oscuro"] .icon-btn {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .16);
}
:root[data-tema="oscuro"] .icon-btn:hover {
  background: rgba(255, 255, 255, .09);
  border-color: rgba(255, 255, 255, .28);
}

/* =====================================================================
   29. IDENT DE MARCA (cortinilla cinematica)
   ---------------------------------------------------------------------
   La cortinilla de arranque (ver assets/ident.js). Toda la secuencia va
   coreografiada como una cortinilla de cine:

     letterbox que abre -> dolly continuo de camara -> amanece detras del
     macizo -> el macizo sale del suelo -> brillo especular -> se traza
     el horizonte -> el nombre entra letra por letra con el interletrado
     abriendose -> el lema -> la escena sigue de largo y se disuelve.

   DOS REGLAS DURAS AQUI
     - Solo transform / opacity / clip-path. Nada de filter: blur ni
       backdrop-filter: es una capa a pantalla completa y en el iPhone
       eso se re-muestrea en cada cuadro.
     - La imagen es assets/icon.svg: la geometria NO se repite aqui, sale
       de _scripts/marca.py como todo lo demas.

   El "dolly" es lo que hace la diferencia: .ident-escena escala de 1.07
   a 1 durante los 2.65 s completos, con una curva casi plana al final,
   asi que la camara nunca se detiene mientras las piezas entran.
   ===================================================================== */
.ident {
  position: fixed; inset: 0; z-index: 200; overflow: hidden;
  background: radial-gradient(118% 88% at 50% 36%,
              #1F3A2B 0%, #14221B 56%, #0B1310 100%);
  animation: identEntra .2s linear both;
}
/* La vineta. Es lo que termina de sacarle el aire de "pagina web": el
   borde se cierra despacio y la mirada queda en el centro. Un gradiente,
   sin filter -- barato hasta en el iPhone. */
.ident::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: radial-gradient(118% 92% at 50% 46%,
              transparent 38%, rgba(4, 10, 7, .34) 74%, rgba(4, 10, 7, .62) 100%);
  animation: identVineta 2.2s var(--curva) .3s both;
}

/* Salida: la escena sigue su camino hacia arriba mientras se disuelve.
   Un corte en seco se siente como un error; esto se siente como un
   encadenado. */
.ident.va { opacity: 0; transition: opacity .56s var(--curva); pointer-events: none; }
.ident.va .ident-escena {
  transform: scale(1.045) translateY(-14px);
  transition: transform .56s cubic-bezier(.4, 0, .7, .3);
}

/* La escena: todo lo que la camara mueve va adentro. Las barras del
   letterbox quedan AFUERA, porque son el marco y el marco no se mueve
   con la camara. */
.ident-escena {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 24px;
  animation: identDolly 2.65s cubic-bezier(.16, .74, .2, 1) both;
  will-change: transform;
}

/* --- Letterbox: abre la escena y no vuelve --- */
.ident-barra {
  position: absolute; left: 0; right: 0; height: 50%;
  background: #080E0B; z-index: 3; pointer-events: none;
  animation: identBarra .92s cubic-bezier(.16, .84, .24, 1) both;
}
.ident-barra.arriba { top: 0;    transform-origin: 50% 0; }
.ident-barra.abajo  { bottom: 0; transform-origin: 50% 100%; }

/* --- Amanece detras del macizo --- */
.ident-alba {
  position: absolute; left: 50%; top: 50%;
  width: min(760px, 155vw); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle at 50% 50%,
              rgba(216, 242, 198, .50) 0%,
              rgba(103, 170, 118, .27) 34%,
              rgba(47, 107, 70, 0) 68%);
  pointer-events: none;
  animation: identAlba 2.1s var(--curva) .18s both;
}

.ident-tile {
  position: relative; width: 112px; height: 112px;
  border-radius: 28px; overflow: hidden; z-index: 1;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55),
              0 0 0 1px rgba(255, 255, 255, .08);
  animation: identTile 1.15s cubic-bezier(.18, .8, .24, 1) .2s both;
}
.ident-tile img { width: 100%; height: 100%; display: block;
                  animation: identSube 1.05s cubic-bezier(.3, .78, .2, 1) .3s both; }

/* El brillo especular: una banda clara que cruza en diagonal. Limpia,
   sin refraccion ni desenfoque -- el filtro granuloso ya se descarto
   antes en la casa. */
.ident-brillo {
  position: absolute; inset: -40% -120%; pointer-events: none;
  background: linear-gradient(100deg, transparent 38%,
              rgba(255, 255, 255, .5) 50%, transparent 62%);
  animation: identBrillo 1.05s cubic-bezier(.36, .18, .2, 1) .92s both;
}

/* La linea del horizonte: se traza de centro hacia afuera y ata el
   macizo con el nombre. Es el mismo gesto que el trazo curvo del suelo
   en la marca. */
.ident-horizonte {
  width: min(248px, 56vw); height: 1px; z-index: 1;
  margin: -6px 0 -8px; transform-origin: 50% 50%;
  background: linear-gradient(90deg, transparent,
              rgba(255, 255, 255, .42) 22%,
              rgba(255, 255, 255, .42) 78%, transparent);
  animation: identTraza .78s cubic-bezier(.2, .8, .22, 1) .86s both;
}

.ident-txt { text-align: center; z-index: 1; }

/* El tratamiento de titulo: el interletrado del conjunto se cierra de
   .34em a .05em mientras cada letra sube por separado. Las dos cosas a
   la vez son lo que lo hace ver filmado. */
.ident-nombre {
  display: block; color: #fff; font-size: 1.62rem; font-weight: 700;
  animation: identKern 1.5s cubic-bezier(.16, .76, .2, 1) 1.1s both;
}
.ident-nombre span {
  display: inline-block;
  animation: identLetra .62s cubic-bezier(.18, .82, .22, 1) both;
  animation-delay: inherit;   /* lo pisa el delay que pone ident.js */
}
/* Un espacio dentro de un inline-block se colapsa; va con ancho propio. */
.ident-nombre .ident-esp { width: .34em; }

.ident-lema {
  display: block; color: rgba(255, 255, 255, .58); font-size: 11.5px;
  font-weight: 600; letter-spacing: .18em; text-transform: uppercase;
  margin-top: 9px;
  animation: identTxt .7s var(--curva) 1.62s both;
}

@keyframes identEntra  { from { opacity: 0 } to { opacity: 1 } }
/* El dolly. Que la curva termine casi plana es a proposito: la camara
   sigue viva hasta el ultimo cuadro, nunca se "estaciona". */
@keyframes identDolly  { from { transform: scale(1.07) }
                         to   { transform: scale(1) } }
@keyframes identBarra  { from { transform: scaleY(1) }
                         to   { transform: scaleY(0) } }
@keyframes identAlba   { 0%   { opacity: 0; transform: translate(-50%, -34%) scale(.52) }
                         58%  { opacity: 1 }
                         100% { opacity: .62; transform: translate(-50%, -50%) scale(1) } }
@keyframes identTile   { from { opacity: 0; transform: scale(.82) translateY(16px) }
                         to   { opacity: 1; transform: none } }
/* inset(100% ...) recorta TODO desde arriba; al bajar a 0 la zona
   visible crece desde el borde de abajo: el cerro sale del suelo. */
@keyframes identSube   { from { clip-path: inset(100% 0 0 0) }
                         to   { clip-path: inset(0 0 0 0) } }
@keyframes identBrillo { from { transform: translateX(-62%) }
                         to   { transform: translateX(62%) } }
@keyframes identTraza  { from { transform: scaleX(0); opacity: 0 }
                         to   { transform: scaleX(1); opacity: 1 } }
@keyframes identKern   { from { letter-spacing: .34em }
                         to   { letter-spacing: .05em } }
@keyframes identLetra  { from { opacity: 0; transform: translateY(16px) }
                         to   { opacity: 1; transform: none } }
@keyframes identVineta { from { opacity: 0 } to { opacity: 1 } }
@keyframes identTxt    { from { opacity: 0; transform: translateY(10px) }
                         to   { opacity: 1; transform: none } }

/* Sin movimiento: solo el fundido. El ident igual se ve -- es el momento
   de marca -- pero quieto y corto. */
.ident.quieto .ident-escena,
.ident.quieto .ident-alba,
.ident.quieto .ident-tile,
.ident.quieto .ident-tile img,
.ident.quieto .ident-horizonte,
.ident.quieto .ident-nombre,
.ident.quieto .ident-nombre span,
.ident.quieto .ident-lema { animation: none; }
.ident.quieto .ident-nombre { letter-spacing: .05em; }
.ident.quieto::after { animation: none; }
.ident.quieto .ident-brillo,
.ident.quieto .ident-barra { display: none; }
.ident.quieto.va .ident-escena { transform: none; transition: none; }

@media (prefers-reduced-motion: reduce) {
  .ident-escena, .ident-alba, .ident-tile, .ident-tile img,
  .ident-horizonte, .ident-nombre, .ident-nombre span,
  .ident-lema { animation: none; }
  .ident-nombre { letter-spacing: .05em; }
  .ident::after { animation: none; }
  .ident-brillo, .ident-barra { display: none; }
  .ident.va .ident-escena { transform: none; transition: none; }
}


/* =====================================================================
   30. VIDRIO (LIQUID GLASS)
   ---------------------------------------------------------------------
   EL VIDRIO DE VERDAD (backdrop-filter) VA SOLO EN EL CROMO FIJO Y SOLO
   EN ESCRITORIO. No es timidez: en el iPhone, un elemento fijo con
   backdrop-filter encima de una lista que scrollea se vuelve a muestrear
   en CADA cuadro y traba el scroll entero. Esta documentado arriba en la
   regla 4 de este archivo y costo un post-mortem real. El cliente es
   100 % iPhone.

   En telefono, y en todo lo que scrollea, va VIDRIO OPTICO: un brillo
   especular arriba + un borde claro + sombra. Se lee igual de vidrio
   porque es asi como se ve el vidrio (la luz pega arriba), pero no
   cuesta nada de pintado.

   Si algun dia se quiere literal en todo, se cambia --vidrio-blur aqui.
   ===================================================================== */
:root {
  --vidrio-brillo: linear-gradient(180deg, rgba(255, 255, 255, .72),
                   rgba(255, 255, 255, 0) 56%);
  --vidrio-borde:  rgba(255, 255, 255, .85);
  --vidrio-velo:   rgba(255, 255, 255, .78);
}
:root[data-tema="oscuro"] {
  --vidrio-brillo: linear-gradient(180deg, rgba(255, 255, 255, .07),
                   rgba(255, 255, 255, 0) 58%);
  --vidrio-borde:  rgba(255, 255, 255, .14);
  --vidrio-velo:   rgba(22, 33, 27, .72);
}

/* El brillo va como CAPA DE FONDO, no como pseudo-elemento encima: un
   ::before absoluto se pinta por delante del contenido en flujo y
   terminaria lavando el texto. Como background-image queda debajo
   siempre, sin riesgo. */
/* OJO con a quien se le pone: solo a lo que TIENE fondo propio.
   .login-caja quedo fuera a proposito -- en escritorio es transparente
   por diseno (el formulario va directo sobre el panel partido), y el
   brillo le pintaba una barra blanca flotando sobre la nada.
   .promo tambien: su fondo es la foto, que es un hijo y tapa cualquier
   capa de fondo. */
.card, .kpi, .acordeon, .opcion, .maq, .modal {
  background-image: var(--vidrio-brillo);
  background-repeat: no-repeat;
  background-size: 100% 70px;
}
.card, .modal { border-top-color: var(--vidrio-borde); }

@media (min-width: 1024px) {
  /* Aqui si: escritorio, cromo fijo, nada que scrollee por debajo que
     se re-muestree en un telefono. */
  .topbar {
    background: var(--vidrio-velo);
    backdrop-filter: saturate(180%) blur(16px);
    -webkit-backdrop-filter: saturate(180%) blur(16px);
  }
  .modal-bg { backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
}


/* =====================================================================
   31. EL HILO DE COLOR EN EL BORDE (LOS DOS TEMAS)
   ---------------------------------------------------------------------
   Un hilo de color que recorre el borde de cada tarjeta y va cambiando
   de tono. Desde oct 2026 (v12) tambien en claro: ahi va con los verdes
   y azules PROFUNDOS de la paleta y mas bajo de opacidad (--anillo y
   --anillo-op, seccion 1), porque sobre blanco un color palido no se lee
   como un reflejo sino como suciedad.

   COMO, SIN TRABAR NADA: el anillo es un conic-gradient fijo de 1 px
   recortado con mascara, y lo unico que se anima es filter: hue-rotate,
   que la GPU resuelve sola. No se anima el gradiente (eso obligaria a
   repintar), ni box-shadow (carisimo), ni posicion.

   Cada tarjeta arranca con un desfase distinto: en sincronia se ve como
   un cartel de neon; desfasadas se ve como un reflejo recorriendo el
   cristal.
   ===================================================================== */
.card, .promo, .opcion, .hero { position: relative; }

.card::after,
.promo::after,
.opcion::after,
.hero::after {
  /* inset:0, NO -1px: las tarjetas de cifra llevan overflow:hidden (por
     la marca de agua que se sale por la esquina) y recortaban justo el
     pixel de afuera, que era todo el anillo -- se veia en unas tarjetas
     si y en otras no. */
  content: ""; position: absolute; inset: 0;
  border-radius: inherit; padding: 1px;
  pointer-events: none; z-index: 3;
  background: var(--anillo);
  -webkit-mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: var(--anillo-op);
  animation: glowTono 18s linear infinite;
}
/* El hero ya tiene su propio ::after (el velo del pie): el anillo le
   quitaria ese velo y los botones se perderian sobre la foto. */
.hero::after {
  inset: auto 0 0 0; height: 58%; padding: 0;
  background: linear-gradient(to top,
    rgba(11, 24, 17, .55) 0%, rgba(11, 24, 17, 0) 100%);
  -webkit-mask: none; mask: none; opacity: 1; animation: none;
  border-radius: 0; z-index: 1;
}

@keyframes glowTono { to { filter: hue-rotate(360deg); } }

.card:nth-child(2n)::after   { animation-delay: -3s; }
.card:nth-child(3n)::after   { animation-delay: -7s; }
.card:nth-child(4n)::after   { animation-delay: -11s; }
.opcion:nth-child(2n)::after { animation-delay: -6s; }
.opcion:nth-child(3n)::after { animation-delay: -12s; }

@media (prefers-reduced-motion: reduce) {
  .card::after, .promo::after, .opcion::after { animation: none; }
}


/* =====================================================================
   32. EL COLOR DE LAS TARJETAS DE CIFRA
   ---------------------------------------------------------------------
   Cada KPI lleva un bano muy tenue de su propio color semantico, que
   baja hasta desaparecer. Es lo que hace que la fila de cuatro no se vea
   como cuatro rectangulos blancos iguales.

   Va SOBRE el brillo de vidrio de la seccion 30, no en vez de el: las
   dos capas se declaran juntas aqui porque background-image no se suma
   entre reglas, la ultima gana.
   ===================================================================== */
.kpi         { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--primary-tinte), transparent 72%); }
.kpi.ok      { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--primary-tinte), transparent 72%); }
.kpi.info    { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--info-tinte), transparent 72%); }
.kpi.alerta  { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--warn-tinte), transparent 72%); }
.kpi.peligro { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--danger-tinte), transparent 72%); }
.kpi.dinero  { background-image: var(--vidrio-brillo),
               linear-gradient(168deg, var(--revenue-tinte), transparent 72%); }
.kpi { background-size: 100% 70px, 100% 100%; }


/* =====================================================================
   33. VIDA EN LAS PANTALLAS
   ---------------------------------------------------------------------
   Las vistas que no son el tablero se veian planas: aparecian de golpe,
   enteras, sin jerarquia de entrada. Cada bloque entra ahora con un
   pequeno desfase, de arriba hacia abajo.

   Funciona SOLO con CSS porque enrutar() reemplaza el innerHTML de la
   vista en cada ruta: los elementos son nuevos cada vez, asi que la
   animacion vuelve a correr sola. Nada de JS.

   EXCEPCION: el constructor de estimates NO se anima. Lleva adentro el
   resumen de precio en position:fixed, y un transform en un ancestro lo
   convierte en un fixed relativo A ESE ANCESTRO -- la barra se iria de
   lugar durante la animacion, justo en la pantalla mas importante.
   ===================================================================== */
.view > *:not(.constructor) { animation: entraVista .4s var(--curva) both; }
.view > *:nth-child(2):not(.constructor) { animation-delay: .05s; }
.view > *:nth-child(3):not(.constructor) { animation-delay: .1s; }
.view > *:nth-child(4):not(.constructor) { animation-delay: .15s; }
.view > *:nth-child(5):not(.constructor) { animation-delay: .2s; }
.view > *:nth-child(n+6):not(.constructor) { animation-delay: .25s; }

@keyframes entraVista {
  from { opacity: 0; transform: translateY(9px) }
  to   { opacity: 1; transform: none }
}

/* El cursor se levanta de la tarjeta que puede tocar. Solo donde hay
   mouse: en el telefono :hover se queda pegado despues de tocar. */
@media (hover: hover) and (min-width: 1024px) {
  a.card, a.fila, .opcion {
    transition: transform .18s var(--curva), box-shadow .18s var(--curva),
                border-color .18s var(--curva);
  }
  a.card:hover, .opcion:hover { transform: translateY(-2px); box-shadow: var(--sh-md); }
}

@media (prefers-reduced-motion: reduce) {
  .view > * { animation: none; }
  a.card:hover, .opcion:hover { transform: none; }
}


/* =====================================================================
   34. EL FONDO DE MARCA DE LAS PANTALLAS
   ---------------------------------------------------------------------
   Clients, Estimates, Rates y Settings terminaban en un vacio enorme
   cuando la lista es corta: cabecera, tabla, y media pantalla de nada.
   Se veian sin terminar.

   La solucion es CSS puro, sin tocar el markup de ninguna vista: el area
   de contenido lleva la misma cresta tenue que ya firma la barra lateral,
   clavada al pie. Llena el vacio, amarra todas las pantallas a la misma
   marca y no mueve un solo elemento de su lugar.

   VA EN .main::after, NO en body: tiene que empezar despues de la barra
   lateral, no por debajo de ella.
   Y va con position:fixed + z-index 0: asi no crece el alto de la pagina
   ni se mete entre el contenido.
   ===================================================================== */
.main { position: relative; }
.main::after {
  content: "";
  position: fixed;
  left: var(--sidew); right: 0; bottom: 0;
  height: 240px;
  z-index: 0;
  pointer-events: none;
  opacity: .55;
  background:
    linear-gradient(to top, var(--primary-soft), transparent 72%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 240' preserveAspectRatio='none'><path d='M0 196 L120 128 L210 170 L330 96 L450 164 L540 124 L660 178 L780 118 L900 168 L1010 132 L1120 182 L1200 150 L1200 240 L0 240 Z' fill='%231F5E3B' fill-opacity='.07'/><path d='M0 220 L140 180 L260 210 L390 166 L510 204 L640 170 L760 212 L880 174 L1000 208 L1130 178 L1200 206 L1200 240 L0 240 Z' fill='%231F5E3B' fill-opacity='.05'/></svg>")
    bottom / 100% auto no-repeat;
}
/* El contenido tiene que quedar POR ENCIMA del fondo.
   SOLO .view: la barra superior es position:sticky y ya trae z-index 40.
   Meterle "position:relative" aqui le quitaba el sticky y la barra se iba
   con el scroll. */
.view { position: relative; z-index: 1; }

/* En oscuro la cresta se pinta con el verde claro del tema, si no
   desaparece contra el fondo casi negro. */
:root[data-tema="oscuro"] .main::after {
  opacity: .5;
  background:
    linear-gradient(to top, rgba(76, 145, 104, .12), transparent 72%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 240' preserveAspectRatio='none'><path d='M0 196 L120 128 L210 170 L330 96 L450 164 L540 124 L660 178 L780 118 L900 168 L1010 132 L1120 182 L1200 150 L1200 240 L0 240 Z' fill='%234C9168' fill-opacity='.13'/><path d='M0 220 L140 180 L260 210 L390 166 L510 204 L640 170 L760 212 L880 174 L1000 208 L1130 178 L1200 206 L1200 240 L0 240 Z' fill='%234C9168' fill-opacity='.09'/></svg>")
    bottom / 100% auto no-repeat;
}

/* En impresion no va NADA de esto: el papel lleva el documento y punto. */
@media print { .main::after { display: none !important; } }


/* =====================================================================
   35. LA CABECERA DE CADA PANTALLA
   ---------------------------------------------------------------------
   Un halo verde muy suave detras del titulo. Le da peso a la cabecera
   para que la pagina arranque con algo, en vez de con texto suelto
   sobre el fondo.
   ===================================================================== */
.pagina-cab { position: relative; }
.pagina-cab::before {
  content: "";
  position: absolute;
  left: -28px; top: -34px; width: 320px; height: 150px;
  z-index: -1; pointer-events: none;
  background: radial-gradient(60% 60% at 22% 40%,
              var(--primary-soft), transparent 70%);
}


/* =====================================================================
   36. LAS FILAS RESPONDEN
   ---------------------------------------------------------------------
   Una tabla donde nada se mueve al pasar por encima se siente muerta.
   La fila se aclara y le entra un filo verde por la izquierda.

   El filo se hace con box-shadow inset y no con border-left: un borde de
   verdad empuja el contenido 3 px al pasar el mouse y la tabla entera
   tiembla.
   ===================================================================== */
@media (hover: hover) {
  .tabla tbody tr { transition: background .14s var(--curva),
                                box-shadow .14s var(--curva); }
  .tabla tbody tr.clic:hover { box-shadow: inset 3px 0 0 var(--primary); }

  .filas .fila { transition: background .14s var(--curva),
                             box-shadow .14s var(--curva); }
  .filas a.fila:hover { box-shadow: inset 3px 0 0 var(--primary); }
}


/* =====================================================================
   37. LOS ESTADOS VACIOS RESPIRAN
   ---------------------------------------------------------------------
   La escena del estado vacio flota muy despacio. Es el unico movimiento
   permanente del sistema fuera del glow, y esta justo donde no hay nada
   que leer, asi que no distrae de ningun dato.
   ===================================================================== */
.vacio .escena { animation: flota 7s ease-in-out infinite; }
@keyframes flota {
  0%, 100% { transform: translateY(0) }
  50%      { transform: translateY(-7px) }
}
@media (prefers-reduced-motion: reduce) {
  .vacio .escena { animation: none; }
}

/* =====================================================================
   38. EL CALENDARIO (ver assets/schedule.js)
   ---------------------------------------------------------------------
   Una reja de siete columnas. Lo unico que tiene que leerse de un
   vistazo, de lejos y al sol, es QUE DIAS ESTAN OCUPADOS -- asi que el
   estado va en el FONDO de la celda, no en un puntito ni en el borde.

   En el telefono la reja no se vuelve lista: una lista de 31 dias no
   responde "cuando me desocupo". Se encoge, se quedan los numeros y el
   color, y los nombres de los clientes pasan a la lista "Coming up" de
   abajo, que ahi si es la forma correcta.
   ===================================================================== */
.cal-nav { display: flex; align-items: center; gap: 6px; }
/* El chevron de "mes anterior" es el mismo icono volteado: un solo
   camino en ICONOS, sin un gemelo que se olvide de actualizar. */
.cal-nav .icon-btn:first-child svg { transform: rotate(180deg); }

.cal-pista {
  margin: 0 0 12px; color: var(--mut); font-size: 13px;
}
.cal-pista b { color: var(--ink); }

.cal-sem {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  margin-bottom: 4px;
}
.cal-sem span {
  text-align: center; font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
  padding: 2px 0;
}

.cal-rej { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }

.cal-dia {
  position: relative;
  min-height: 74px; padding: 6px 7px;
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font: inherit; text-align: left; cursor: pointer;
  transition: border-color .14s var(--curva), transform .14s var(--curva);
}
.cal-dia:hover { border-color: var(--primary); transform: translateY(-1px); }
.cal-dia .n {
  font-size: 12.5px; font-weight: 650; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* Los dias de relleno del mes vecino: ocupan su lugar en la reja para
   que las columnas no se corran, pero no se pueden tocar. */
.cal-dia.fuera {
  border-color: transparent; background: none; cursor: default;
  pointer-events: none;
}

/* Lo que ya paso se apaga: no se puede agendar hacia atras y no tiene
   que competir por la atencion con lo que viene. */
.cal-dia.pasado { opacity: .5; }

.cal-dia.libre   { background: var(--surface); }
.cal-dia.ocupado {
  background: var(--primary-soft); border-color: var(--primary-ring);
}
.cal-dia.choque  {
  background: var(--danger-soft); border-color: var(--danger);
}

/* Hoy: anillo, no relleno -- el relleno ya dice si esta ocupado y no se
   le puede quitar ese trabajo al color. */
.cal-dia.hoy { box-shadow: 0 0 0 2px var(--primary); }
.cal-dia.hoy .n { color: var(--primary); }

.cal-trabajos { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cal-tr {
  font-size: 11px; font-weight: 600; line-height: 1.3;
  padding: 2px 5px; border-radius: var(--r-xs);
  background: var(--primary); color: #fff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-dia.choque .cal-tr { background: var(--danger); }
.cal-mas { font-size: 10.5px; font-weight: 600; color: var(--mut); }

.cal-leyenda {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: 13px; font-size: 12px; color: var(--mut);
}
.cal-leyenda span { display: flex; align-items: center; gap: 6px; }
.cal-leyenda i {
  width: 12px; height: 12px; border-radius: 3px;
  border: 1px solid var(--dim); background: var(--surface);
}
.cal-leyenda .pt-ocupado {
  background: var(--primary-soft); border-color: var(--primary-ring);
}
.cal-leyenda .pt-choque {
  background: var(--danger-soft); border-color: var(--danger);
}

/* --- Telefono: la reja se encoge y suelta los nombres --- */
@media (max-width: 680px) {
  .cal-rej, .cal-sem { gap: 3px; }
  .cal-dia { min-height: 46px; padding: 4px; }
  .cal-dia .n { font-size: 11.5px; }
  /* El nombre del cliente no cabe en 44 px. El color dice "ocupado" y el
     nombre lo da la lista de abajo o el toque en el dia. */
  .cal-tr { font-size: 0; padding: 0; height: 4px; border-radius: 2px; }
  .cal-mas { display: none; }
  .cal-trabajos { gap: 2px; }
}

/* =====================================================================
   39. SI SE PUEDE TOCAR, SE VE  (v14)
   ---------------------------------------------------------------------
   El problema que cierra esta seccion: habia cosas que se podian tocar y
   no lo parecian, y -- peor -- filas que NO se podian tocar y se veian
   igual que las que si. Las dos mitades son el mismo bug de confianza:
   el dueno termina picando la pantalla a ver que pasa.

   LA REGLA DE LA CASA, de aqui en adelante:
   todo lo que responde al toque tiene que decirlo en REPOSO, no solo al
   pasar el mouse. En un iPhone NO HAY hover: una fila que solo se pinta
   al pasar el cursor, en el telefono no existe. Por eso lo que se agrega
   aqui son senales quietas -- chevron, borde, subrayado -- y no efectos.

   Las tres capas:
     reposo   chevron en las filas, borde en los botones planos,
              subrayado en los enlaces
     toque    :active, que en el telefono es el unico "si, te oi"
     teclado  :focus-visible con anillo, en todo lo interactivo
   ===================================================================== */

/* --- 1. Foco visible. Va primero porque es lo unico que ademas es
       accesibilidad y no estetica: sin esto, con teclado no se sabe
       donde se esta parado. --- */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* --- 2. La mitad que faltaba: lo que NO se puede tocar no se pinta.
       `.fila` se usa igual para filas de datos (un div) y para filas que
       navegan (a / button). El hover estaba en las dos, asi que una ficha
       de solo lectura se encendia al pasarle el mouse y prometia algo que
       no iba a pasar. --- */
.fila:hover { background: none; }
a.fila:hover, button.fila:hover, label.fila:hover { background: var(--surface-2); }
div.fila { cursor: default; }

/* --- 3. El chevron de las filas que llevan a algun lado.
       Muchas ya traen `.ch` en el marcado; a las que no, se les dibuja
       aqui. Es la convencion de iOS y es lo que el dueno ya sabe leer sin
       que nadie se lo explique.

       :has() es lo que evita el chevron doble. Si algun navegador viejo
       no lo soporta, simplemente no aparece el chevron dibujado -- se
       pierde la mejora, no se rompe la fila. --- */
a.fila:not(:has(.ch))::after,
button.fila:not(:has(.ch))::after {
  content: "";
  width: 7px; height: 7px; flex: 0 0 7px;
  margin-left: 3px;
  border-right: 2px solid var(--dim);
  border-bottom: 2px solid var(--dim);
  border-radius: 1px;
  transform: rotate(-45deg);
  transition: border-color .14s var(--curva), transform .14s var(--curva);
}
a.fila:hover::after, button.fila:hover::after { border-color: var(--primary); }

/* --- 4. El toque. En el telefono esto es TODO el acuse de recibo que
       hay: no existe hover y el dedo tapa la mitad de la fila. --- */
a.fila:active, button.fila:active { background: var(--primary-soft); }
.btn:active:not(:disabled),
.icon-btn:active,
.segs button:active,
.margenes button:active { transform: scale(.97); }
.cal-dia:active { transform: scale(.96); }
.acordeon > summary:active { background: var(--primary-soft); }

/* --- 5. Los botones planos. Eran texto verde suelto: no se distinguian
       de una etiqueta de color. Un borde tenue basta para que se lean
       como boton sin que compitan con el principal. --- */
.btn.plano {
  border-color: var(--line);
  padding: 0 13px;
  box-shadow: var(--sh-xs);
}
.btn.plano:hover { border-color: var(--primary-300); }
:root[data-tema="oscuro"] .btn.plano {
  border-color: rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .05);
}

/* --- 6. Los enlaces de texto, subrayados SIEMPRE. El color solo no
       alcanza: en esta paleta el verde tambien es el color de los datos
       buenos, y encima hay gente que no distingue el verde del gris. --- */
.lnk, .pista.lnk { text-decoration: underline; text-underline-offset: 2px; }
.lnk:hover { text-decoration-thickness: 2px; }

/* Y los enlaces sueltos dentro de un parrafo -- el pie del login, las
   notas, los estados vacios. Eran texto gris: no habia UN solo indicio
   de que "Terms & privacy" y "Support" se pudieran tocar. */
.login-pie a,
.pie-ver a,
.ayuda a,
.nota-info a,
.vacio p a,
p a:not(.btn) { text-decoration: underline; text-underline-offset: 2px; }
.login-pie a:hover { color: var(--primary); }

/* --- 7. El ojo de la contrasena: era un icono gris flotando dentro del
       campo. Con un fondo propio se lee como boton. --- */
.ojo { background: var(--surface-2); border: 1px solid var(--line); }
.ojo:hover { background: var(--line-2); color: var(--ink); }
:root[data-tema="oscuro"] .ojo {
  background: rgba(255, 255, 255, .06);
  border-color: rgba(255, 255, 255, .16);
}

/* --- 8. Las filas de tabla que abren una ficha. La tabla no tiene una
       columna para el chevron, asi que se dibuja al final de la ultima
       celda. --- */
.tabla tr.clic td:last-child { position: relative; padding-right: 26px; }
.tabla tr.clic td:last-child::after {
  content: "";
  position: absolute; right: 10px; top: 50%;
  width: 6px; height: 6px;
  margin-top: -4px;
  border-right: 2px solid var(--dim);
  border-bottom: 2px solid var(--dim);
  transform: rotate(-45deg);
}
.tabla tr.clic:hover td:last-child::after { border-color: var(--primary); }

/* --- 9. Los dias del calendario. La reja ya se lee como reja, pero un
       dia vacio no decia que se podia tocar. Un realce en la esquina lo
       dice sin meter ruido en los 31 dias. --- */
.cal-dia:not(.fuera)::before {
  content: "";
  position: absolute; right: 5px; bottom: 5px;
  width: 4px; height: 4px; border-radius: 50%;
  /* --dim y no --line: con --line el punto quedaba del color del borde,
     es decir invisible, y un indicio que no se ve no es un indicio. */
  background: var(--dim);
  transition: background .14s var(--curva);
}
.cal-dia:not(.fuera):hover::before { background: var(--primary); }
/* En los dias ocupados el punto estorba: ahi ya hay fichas de colores
   diciendo que pasa, y el dia se ve tocable de sobra. */
.cal-dia.ocupado::before, .cal-dia.choque::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  .btn:active:not(:disabled), .icon-btn:active, .segs button:active,
  .margenes button:active, .cal-dia:active { transform: none; }
}
