/* ============================================================================
   Juicy — Dashboard Design System · FIELD (shell) + TEXT + SELECT + TEXTAREA
   ----------------------------------------------------------------------------
   Figma: "Text Field" (9:4382) 80 variantes · "Select Field" (9:4981) 16 ·
   "Textarea" (9:5065) 16. Los tres comparten `.field` / `.field__control` /
   `.input`; solo cambia el control de adentro.
   Text Field:
     State (Default|Filled|Focus|Error|Disabled) × Display Label × Display Helper
           × Add Icon × Add Action

   Las 80 son ~6 reglas aquí, y no es un atajo — es que Figma no sabe expresar
   estado y tiene que dibujar cada combinación:
     · `Filled` no es una variante: es `:not(:placeholder-shown)`.
     · `Focus`  no es una variante: es `:focus-within`.
     · `Display Label` / `Display Helper` no son variantes: es si el elemento está.
   Quedan como CSS de verdad solo `--error`, `:disabled`, el icono y la acción.

   Estructura — el shell (.field) es el mismo para Text, Select y Textarea; solo
   cambia el control de adentro. Por eso esos dos van a costar casi nada:

     <label class="field">
       <span class="field__label">Producto</span>
       <div class="field__control">
         <div class="input">
           <svg class="input__icon">…</svg>          <!-- Add Icon -->
           <input class="input__el" placeholder="Write something">
           <button class="icon-chip icon-chip--sm tone-neutral-lv1">…</button>
         </div>                                       <!-- Add Action -->
         <p class="field__helper">Helper Text</p>
       </div>
     </label>

   El botón de acción NO se define aquí: es un Icon Button que ya existe. En
   Figma está dibujado a mano (30px, radius 5, fondo #3E4349, icono 16) y resulta
   ser EXACTAMENTE `.icon-chip.icon-chip--sm.tone-neutral-lv1`. Ver nota 4.

   SELECTORES HIJO A PROPÓSITO (`.input > .input__el`, no `.input__el`): main.css
   conserva reglas de ELEMENTO (`input[type=text]` = 0,1,1) para los controles a
   pelo que quedan en toolbars de archives y POS; una clase sola (0,1,0) las
   PIERDE aunque cargue después. El hijo (0,2,0) gana siempre — y de paso
   documenta la estructura. Cuando esas reglas mueran (F2c/F2g) el combinador
   queda solo como documentación. Ver nota 27.

   Los campos que SELECTIZE u otra librería re-renderiza NO usan `.input`: la
   librería pinta su propio DOM y el pegamento de gwfields.css lo alinea a estas
   métricas. El DS no sabe que selectize existe.
   ============================================================================ */

.field {
  position: relative; /* ancla de .input_actions y overlays del logic engine (nota 26) */
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.field__label {
  font-size: 13px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 1px;
  color: var(--text-primary);
}

.field__control {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.field__helper {
  margin: 0;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--text-secondary);
}

/* ---- Input --------------------------------------------------------------- */

.input {
  display: flex;
  align-items: center;
  gap: 8px;

  height: var(--control-md);
  padding-inline: 10px;
  border: var(--stroke-thin) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-field);
}

/* `Focus` en Figma. El borde sale de neutral/500 — NO de `border/focus`. Ver nota 3. */
.input:focus-within {
  border-color: var(--border-field-focus);
}

.input > .input__el {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: none;
  height: auto;
  width: auto;

  font-family: inherit;
  font-size: 12px;
  font-weight: 400;
  line-height: normal;
  color: var(--text-field);   /* la tinta del papel que pinta `.input`, no la del ancestro — nota 33 */
}

/* `Filled` en Figma es esto: el valor en text/primary y el placeholder atenuado.
   En CSS no hace falta una variante — el navegador ya lo sabe.
   `opacity:1` porque Firefox aún atenúa el placeholder por su cuenta. */
.input > .input__el::placeholder {
  color: var(--text-placeholder);
  opacity: 1;
}

/* Un punto por debajo del placeholder a propósito: es decorativo y no debe
   competir con el texto. Solo el de `Add Icon` — el de `Add Action` es un
   Icon Button y toma su color del tono.
   Se pinta con `color` y se dimensiona con `font-size` porque es una TIPOGRAFÍA
   (convención en foundation.css). Antes decía `fill: var(--icon-field)` y no se
   aplicaba nunca: el `$` de los campos de precio salía en --text-primary — tan
   oscuro como el valor, o sea exactamente lo que este comentario prometía
   evitar. Ver nota 33. */
.input__icon {
  flex-shrink: 0;
  font-size: 14px;
  line-height: 1;
  color: var(--icon-field);
}

/* El svg, si algún día lo hay: caja propia y el fill atado al mismo color. */
svg.input__icon {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

/* El botón de acción vive pegado al borde: Figma baja el padding derecho a 5. */
.input:has(> .icon-chip) {
  padding-right: 5px;
}

/* ---- Select -------------------------------------------------------------- */

/* Reusa .field / .field__control / .input tal cual. Lo único propio es matar el
   chrome nativo y poner el chevron:
     <div class="input">
       <select class="input__el input__el--select">
         <option value="" selected>Choose an option</option> …
       </select>
       <svg class="input__chevron">…</svg>
     </div>
   Figma lo dibujó como un componente aparte y por eso divergió en 5 cosas.
   Ver notas 8-11. */

.input > .input__el--select {
  appearance: none;
  cursor: pointer;
  background-image: none; /* mata el chevron-fondo de la regla de elemento de main.css */
}

/* El "Choose an option" es un placeholder, no un valor: se atenúa mientras no
   haya nada elegido, igual que en el Text Field. En Figma va a text/primary y
   por eso un select vacío parece lleno. Ver nota 9. */
.input > .input__el--select:has(option[value=""]:checked) {
  color: var(--text-placeholder);
}

/* Tipografía, como todo icono de aquí (foundation.css). Sin caja. */
.input__chevron {
  flex-shrink: 0;
  font-size: 16px;
  line-height: 1;
  color: var(--icon-field);
  pointer-events: none;
}

svg.input__chevron {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

/* El chevron pega al borde: Figma baja el padding derecho a 5. */
.input:has(> .input__el--select) {
  padding-right: 5px;
}

/* ---- Textarea ------------------------------------------------------------ */

/* Mismo shell otra vez. Lo único propio: alto mínimo, contenido arriba y que
   pueda crecer.
     <div class="input input--multiline">
       <textarea class="input__el input__el--multiline"></textarea>
     </div> */

.input--multiline {
  height: auto;
  min-height: 85px;
  align-items: stretch;
  padding-block: 12px;
}

.input--multiline > .input__el--multiline {
  min-height: 0;
  resize: vertical;
}

/* ---- Search -------------------------------------------------------------- */

/* Figma: "Search Input" (9:6266). 2 variantes: Status = Default | Fill.
   NO reusa `.input`: es un grupo pegado (campo + botón que comparten borde).
     <div class="search">
       <input class="search__input" placeholder="Buscar">
       <button class="search__btn"><svg>…</svg></button>
     </div>
   `Fill` en Figma no es un estado nuevo: oscurece borde y texto a neutral/500,
   que es lo que aquí hacen `:focus-within` y tener valor. Ver nota 18. */

.search {
  display: inline-flex;
  align-items: center;
}

.search__input,
.search__btn {
  height: var(--control-md);
  border: var(--stroke-thin) solid var(--border-default);
  background: var(--surface-field);
  color: var(--text-primary);
}

.search > .search__input {
  flex: 1;
  min-width: 0;
  padding-inline: 11px 10px;
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  outline: 0;
  font-family: inherit;
  font-size: 12px;
  font-weight: 400;
  line-height: normal;
}

.search__input::placeholder {
  color: var(--text-secondary);
  opacity: 1;
}

/* Su tinta sale del contrato de tonos, con el icono del campo de default: el
   botón de "limpiar búsqueda" del archive es el mismo control en `tone-error`.
   En :where() para que el tono siempre gane (ver nota 30). */
:where(.search__btn) {
  --tone-line: var(--icon-field);
}

.search__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--control-md);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--tone-line);
  cursor: pointer;
}

/* La lupa es tipografía: font-size y ya. SIN caja — dársela la descentraba, y
   el `::before{line-height:1px}` que lo compensaba se pasaba de rosca en el
   otro sentido (medido: caja −2.0 · caja sin parche +0.0 · sin caja −1.0, y en
   un chip de 20 la caja se iba a +4.5). Ver foundation.css y nota 31. */
.search__btn > i {
  flex-shrink: 0;
  font-size: 15px;
  line-height: 1;
}

.search__btn > svg {
  display: block;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  fill: currentColor;
}

/* El grupo entero reacciona: es una sola caja partida en dos, no dos controles. */
.search:focus-within .search__input,
.search:focus-within .search__btn {
  border-color: var(--border-field-focus);
}

/* Al enfocar, el icono se oscurece — salvo que lleve tono propio (el de
   limpiar es rojo y tiene que seguir siéndolo). */
.search:focus-within .search__btn:not([class*="tone-"]) {
  --tone-line: var(--text-primary);
}

.search__btn:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* ---- Spinner ------------------------------------------------------------- */

/* Figma: "Spinner Field" (9:6277). 8 variantes: Status × Display Label.
   SÍ es un field: `flex-col gap-8` + label, y mide 61/40 — idéntico al Text
   Field. Así que reusa `.field` y `.field__label`, y con eso su label deja de
   ser ilegible solo (ver nota 19).
     <label class="field">
       <span class="field__label">Cantidad</span>
       <div class="spinner">
         <button class="spinner__btn"><svg>…</svg></button>
         <input class="spinner__num" type="number" value="1">
         <button class="spinner__btn"><svg>…</svg></button>
       </div>
     </label> */

.spinner {
  display: inline-flex;
  align-items: stretch;
  height: var(--control-md);
  border: var(--stroke-thin) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-field);
  overflow: clip;
}

.spinner__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-secondary);
  cursor: pointer;
}

.spinner__btn > svg {
  display: block;
  width: 24px;
  height: 24px;
  fill: currentColor;
}

/* `.disabled` además de `:disabled`: los botones del spinner de gw-fields son
   <span> (no <button>) y su JS apaga/enciende con la clase, no con el atributo. */
.spinner__btn:disabled,
.spinner__btn.disabled {
  background: var(--surface-field-disabled);
  color: var(--text-muted);
  cursor: not-allowed;
  pointer-events: none;
}

.spinner__btn:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: -2px;
}

.spinner > .spinner__num {
  width: 60px;
  padding-inline: 11px;
  border: 0;
  border-inline: var(--stroke-thin) solid var(--border-default);
  outline: 0;
  background: none;

  font-size: 14px;
  font-weight: 400;
  line-height: normal;
  text-align: center;
  color: var(--text-secondary);

  appearance: textfield;
  -moz-appearance: textfield;
}

.spinner__num::-webkit-outer-spin-button,
.spinner__num::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

/* Disabled COMPLETO. Figma pinta el borde del mismo color que el fondo y borra
   los divisores; aquí se conservan para que no se vuelva una mancha (nota 21). */
.spinner:has(> .spinner__num:disabled) {
  background: var(--surface-field-disabled);
}

.spinner__num:disabled {
  color: var(--text-muted);
  cursor: not-allowed;
}

/* ---- Error --------------------------------------------------------------- */

.field--error .input {
  border-color: var(--feed-error-default);
}

.field--error .field__helper {
  color: var(--feed-error-default);
}

/* ---- Disabled ------------------------------------------------------------ */

.input:has(> .input__el:disabled),
.input:has(> .input__el:read-only),
.input--disabled {
  background: var(--surface-field-disabled);
  cursor: not-allowed;
}

.input > .input__el:disabled,
.input > .input__el:read-only {
  cursor: not-allowed;
  color: var(--text-muted);
}

/* ---- LOS CONTROLES A PELO (reglas de ELEMENTO) -----------------------------
   Vinieron de main.css (2026-07-26), donde estaban marcadas "TRANSICIONAL, ver
   ds/field.css" y con su deuda contada: lo que cuelga de ellas son los ~26
   `<select>` de los toolbars de los archives y los controles del POS, que no
   tienen la envoltura `.input` (era la fase F3 del plan del DS).

   NO se han matado: se han MOVIDO, y por una razón nueva que la nota original no
   podía prever. El front nuevo (React) pinta las mismas pantallas y **no importa
   main.css a propósito** — es el CSS legacy, y hay una prueba que lo verifica.
   Mientras estas reglas vivieran allá, cualquier control a pelo del front nuevo
   salía con el borde y el radio del NAVEGADOR: medido con el espejo
   (`juicy-core/e2e/espejo.mjs`), el campo de la paginación tenía borde de 2px
   gris y radio 0 donde el producto lleva 1px y 3px.

   Los campos de gw-fields siguen sin depender de esto: su contrato es
   `.input > .input__el` (arriba), que le gana por especificidad (0,2,0 contra
   0,1,1). Esto es el suelo de lo que no pasa por el componente.
   ---------------------------------------------------------------------------- */

input[type=text],
input[type=email],
input[type=password],
input[type=number] {
  display: inline-block;
  width: 100%;
  height: var(--control-md);
  padding: 0 10px;
  border-radius: var(--radius-sm);
  border: var(--stroke-thin) solid var(--border-default);
  background: var(--surface-field);
  color: var(--text-primary);
  font-weight: 400;
  font-size: 12px;
}

/* Un number no ocupa todo el ancho: mide lo que mide su contenido. */
input[type=number] { width: auto; }

/* El foco del campo es el del DS: el borde a neutral/500 y nada más (decisión del
   dueño 2026-07-17 — se quitó el `outline` de `:focus-visible` que había aquí).
   Ojo con lo que eso deja: la nota 3 de este archivo dice que un cambio de color
   en un borde de 1px es la ÚNICA señal de foco, y para teclado es sutilísimo. El
   resto de controles del DS (btn, icon-chip, choice) sí conservan el suyo. */
input[type=text]:focus,
input[type=email]:focus,
input[type=password]:focus,
input[type=number]:focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--border-field-focus);
}

input[type=text]:read-only,
input[type=email]:read-only,
input[type=password]:read-only,
input[type=number]:read-only,
textarea:read-only {
  background: var(--surface-field-disabled);
  color: var(--text-muted);
}

input[type=text]::placeholder,
input[type=email]::placeholder,
input[type=password]::placeholder,
input[type=number]::placeholder,
textarea::placeholder {
  color: var(--text-placeholder);
  opacity: 1;
}

select {
  display: inline-block;
  width: 100%;
  min-width: 130px;
  height: var(--control-md);
  /* pl 10 = el mismo que el input: pegados en el toolbar de una archive, su texto
     tiene que arrancar en la misma x. El pr deja aire al chevron. */
  padding: 0 30px 0 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: var(--stroke-thin) solid var(--border-default);
  /* La ruta sube DOS niveles porque este archivo vive en `assets/css/ds/` y el
     `url()` se resuelve desde el ARCHIVO, no desde la página. Es la misma razón
     por la que el sincronizador del front espeja el layout en vez de aplanarlo. */
  background-image: url(../../images/angle-down.svg);
  background-size: 17px;
  background-position: calc(100% - 3px) center;
  background-repeat: no-repeat;
  font-weight: 400;
  font-size: 12px;
  /* Los prefijos se CONSERVAN tal cual venían: esto es una mudanza, no una
     limpieza, y quitarlos aquí sería cambiar el comportamiento en un navegador
     viejo dentro de un cambio que dice no tocar nada. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

select.readonly {
  background-color: var(--surface-field-disabled);
  pointer-events: none;
}

textarea {
  display: inline-block;
  width: 100%;
  min-height: 85px;
  padding: 12px 10px;
  border-radius: var(--radius-sm);
  border: var(--stroke-thin) solid var(--border-default);
  background: var(--surface-field);
  color: var(--text-primary);
  font-weight: 400;
  font-size: 12px;
  resize: vertical;
}

/* ============================================================================
   NOTAS

   1. BUG EN EL COMPONENT SET: a 5 variantes se les perdió el helper.
      La fila `Display Label=No · Display Helper=Yes · Add Icon=Yes · Add
      Action=No` (9:4768–9:4792) declara alto 40 cuando sus 15 hermanas cuadran
      con el modelo `input 40 + label 21 + helper 19`. Debería medir 59. En
      Figma no se nota; en código habría sido "el helper no sale, pero solo con
      icono y sin label". Aquí el helper aparece siempre que exista el elemento.

   2. `Add Icon` mete el icono DENTRO del input, a la izquierda del texto, a
      14px. Es decorativo y va en `--icon-field` (neutral/300, elegido por el
      dueño): un punto por debajo del placeholder, para no competir con el texto.
      No confundir con `Add Action`, que es un Icon Button de verdad.

   3. EL FOCUS NO USA SU PROPIO TOKEN. El input bindea `neutral/500` (#3E4349),
      pero `border/focus` existe y vale neutral/600 (#282B2E). Uno de los dos
      está mal. Además el focus solo cambia el color del borde de 1px: es un
      cambio sutilísimo, y para navegación por teclado no basta. Los Button e
      Icon Button de este DS llevan `:focus-visible` con outline; el campo
      debería llevar algo equivalente.

   4. El botón de acción de Figma está DIBUJADO A MANO (30x30, radius 5, fondo
      `text/primary`, icono 16) en vez de instanciar el Icon Chip — y coincide
      exacto con `.icon-chip--sm.tone-neutral-lv1`. Dos consecuencias: (a) allá
      hay que instanciarlo, no redibujarlo; (b) usa `text/primary` como FONDO,
      que es un token de texto. El correcto es `action/neutral-lv1/default`
      (mismo valor, #3E4349).

   5. PLACEHOLDER: `--text-placeholder` = `text-muted` (neutral/300). Hubo un
      desvío intermedio (un color-mix al 76% de text/primary, "por si hay campos
      sin label") que el dueño REVIRTIÓ 2026-07-16: oscurecía tanto el
      placeholder que un campo vacío y uno lleno se leían igual — el placeholder
      es una PISTA, no contenido. La historia completa y las medidas viven en
      semantics.css junto al token. Sigue divergiendo de Figma (que usa
      neutral/250, 1.45:1, ilegible); falta crear `text/placeholder` allá.

   6. Se evaluó un sólido y no alcanza: la rampa neutral SALTA de 400 (#78838F,
      3.86:1 — no llega a AA) a 500 (#3E4349, 9.98:1 — indistinguible del valor
      real). No hay primitivo en medio, de ahí el alpha. Si prefieres un sólido,
      hace falta un `neutral/450` alrededor de #6C7075.

   7. `Select` NO tiene estado Filled y `Text Field` sí. Asimetría sin razón
      aparente — aunque en CSS da igual, porque Filled no existe.

   ---- SELECT: 5 divergencias contra Text Field, todas normalizadas aquí ------
   El Select se dibujó como componente aparte en vez de reusar el shell, y
   divergió en cinco cosas. Ninguna parece intencional. Todas se corrigen solas
   al reusar `.field` / `.input`; se listan para poder revertir una por una.

   8. NO TIENE "Input wrapper". Su helper cuelga directo del `.field`, así que
      hereda gap 8 en vez del 5 del wrapper → mide 3px más de alto que un Text
      Field idéntico. La aritmética cierra exacta:
        Text Field  13 + 8 + 40 + 5 + 14 = 80  (Figma declara 80)
        Select      13 + 8 + 40 + 8 + 14 = 83  (Figma declara 83)
      Aquí usa el shell → 80, igual que el Text Field.

   9. EL PLACEHOLDER VA A `text/primary`. "Choose an option" al 100% del color
      del texto: un select vacío se ve igual que uno con valor elegido. Aquí usa
      `--text-placeholder` como el Text Field, y se atenúa solo mientras la
      opción vacía siga seleccionada.

  10. PADDING IZQUIERDO 15 vs 10 del Text Field. Un select y un input en la misma
      barra (p.ej. el toolbar de la Archive Page) arrancarían su texto con 5px de
      diferencia. Normalizado a 10.

  11. EL BORDE DEL DISABLED ES MÁS FUERTE QUE EL NORMAL: usa `neutral/250`
      (#CFD7E1 = --border-strong) mientras el estado normal usa `border/default`
      (#E5EAEF). Al revés de lo esperable. Normalizado a border/default, igual
      que el Text Field disabled.

  12. EL CHEVRON TIENE 4 COLORES SIN LÓGICA, recoloreado a mano por variante:
        default  #E5EAEF (¡un color de BORDE!)  1.24:1  — invisible
        focus    #CFD7E1                        1.45:1
        error    #3E4349                        9.98:1  — el más oscuro
        disabled #CFD7E1                        1.45:1
      El normal es el más claro y el de error el más oscuro; no hay razón para
      que un error oscurezca la flecha. Y esa flecha es la ÚNICA señal de que el
      campo es un desplegable — a 1.24:1 no se ve. Unificado en `--icon-field`
      (neutral/300), el mismo tono que elegiste para el icono del campo.

   ---- TEXTAREA --------------------------------------------------------------
  13. TAMPOCO TIENE "Input wrapper": mismo +3 que el Select.
        Figma  13 + 8 + 85 + 8 + 14 = 128  ·  con el shell: 125 (gap 5)
        Figma       85 + 8 + 14 = 107      ·  con el shell: 104
      Dos de tres campos traen el bug; el Text Field es el único bien armado.

  14. TERCER PADDING HORIZONTAL DISTINTO: Text Field 10 · Select 15 · Textarea
      14. Tres campos, tres valores, sin razón. Un textarea debajo de un input
      en el mismo formulario arrancaría su texto 4px corrido. Normalizado a 10.

  15. EL LABEL BINDEA `neutral/500` (PRIMITIVO) en vez de `text/primary`, que es
      lo que usan el Text Field y el Select. Mismo valor (#3E4349), pero el label
      del Textarea se salta la capa semántica. Aquí los tres usan `text/primary`.

  16. Disabled usa `border/default` y Error usa `red/500` — o sea CONSISTENTE con
      el Text Field. El raro era el Select (nota 11), no éste.

  17. NINGUNA de las 16 variantes muestra más de UNA línea de texto. En un
      textarea eso es justo lo que hay que definir: el interlineado del texto
      multilínea NO existe en el diseño. Aquí hereda `line-height: normal`
      (~1.2 en Montserrat), que es apretado para leer un párrafo. Cuando dibujes
      un textarea con texto de verdad, ese número se decide solo.

   ---- SEARCH ----------------------------------------------------------------
  18. `Status = Fill` no es un estado propio: oscurece borde Y texto a
      neutral/500 — o sea hace de golpe lo que el Text Field reparte entre
      `Filled` (texto) y `Focus` (borde). Aquí lo cubren `:focus-within` y tener
      valor, sin variante. Y ojo con el nombre: el Text Field ya usa "Filled"
      para otra cosa, y "Fill" en Button/Badge/Icon Chip significa "relleno
      sólido". Tres significados para la misma palabra en el mismo DS.

  19. CUARTO COLOR DE BORDE DE CAMPO: `neutral/250` (#CFD7E1), cuando el Text
      Field, el Select y el Spinner usan `border/default` (#E5EAEF). En el
      toolbar de la Archive Page el Search va PEGADO a un Select — con los
      valores de Figma tendrían el borde de distinto tono, uno al lado del otro.
      Normalizado a `border/default`.

  20. MONTSERRAT LIGHT (300). Aparece en el placeholder del Search y el número
      del Spinner — y también en el valor de celda de la Tabla, o sea que NO es
      un caso aislado: son tres, y el patrón que se dibuja es
        Light = DATO que se lee · Regular = etiqueta · Medium = acción.
      Excepción: el input del Text Field es Regular 12, no Light. Si el patrón es
      a propósito, vale la pena nombrarlo en Figma. Transcrito.
      El placeholder va a `text/secondary` (3.86:1) — Figma ya lo tenía así, más
      oscuro que el del Text Field, que era `text/muted` (1.45:1).

   ---- SPINNER ---------------------------------------------------------------
  21. SU LABEL ES ILEGIBLE: Figma lo bindea a `neutral/300` = 2.69:1 sobre
      blanco, mientras el label del Text Field va a `text/primary` = 9.98:1.
      Un label de formulario a 2.69:1 es peor que un placeholder malo: es el que
      dice QUÉ va en el campo. Se corrige solo al reusar `.field__label`.

  22. DESHABILITAR UN BOTÓN OSCURECE EL OTRO. Medido en el asset y el binding:
        Default          número neutral/400 · iconos #78838F
        "Disabled Minus" número neutral/500 · minus #CFD7E1 · plus #3E4349
      O sea que el estado "normal" tiene DOS colores según qué hermano esté
      apagado. Recoloreado a mano. Aquí lo habilitado siempre es text/secondary
      y lo deshabilitado siempre text/muted.

  23. EL DISABLED COMPLETO BORRA LA CAJA: Figma pinta el borde y los divisores
      del mismo `neutral/100` que el fondo, así que un spinner deshabilitado se
      vuelve una mancha gris sin estructura — no se ve que sea un spinner.
      Aquí se conservan borde y divisores.

  24. TRES ESCRITURAS DE "DISABLED" en los nombres de variante: `Disbaled`,
      `Disabled Minus`, `Disbaled Plus`. Dos con dedazo.

  25. El icono del Spinner mide 24px: es un SEXTO tamaño de icono en el sistema
      (Button 20 · Icon Button 20/16 · Badge 14 · Field 14 · Search 15 · aquí 24).
      Transcrito. Cuando toque, la escala razonable sería 14/16/20/24.

   ---- AÑADIDOS DEL ATERRIZAJE (no vienen de Figma) ---------------------------
  26. `.field { position: relative }`: gw-fields ancla `.input_actions` (los
      toggles absolutos de la esquina del campo) y overlays contra el wrapper.
      El borrador no lo traía porque el styleguide no los muestra.

  27. TODOS los selectores de control van con COMBINADOR HIJO (ver cabecera):
      no es estilo, es armadura de especificidad contra las reglas de elemento
      que main.css conserva para archives/POS. `height/width: auto` en
      `.input > .input__el` neutraliza el 40/100% que esas reglas imponen.

  28. `:read-only` se trata como disabled visual: el producto usa `readonly`
      (no `disabled`) para campos que viajan en el POST pero no se editan —
      main.css ya lo pintaba así y los formularios dependen de eso.

  29. Los botones del spinner de gw-fields son <span> y su JS usa la clase
      `.disabled`; el componente acepta ambas señales (`:disabled` y `.disabled`).

  30. EL BOTÓN DEL SEARCH CONSUME TONO (F2c). El toolbar del archive tenía un
      clon del `.search`: un `.search_input_wrapper` con un icon-chip de botón
      pegado con border-radius a mano. Al traerlo a este componente aparecieron
      dos huecos reales que el Figma no cubre porque solo dibuja el estado
      normal: el botón de LIMPIAR (una X roja) y los iconos `<i>` de uicons.
      Por eso su tinta pasó de `--icon-field` fijo a `--tone-line` con ese
      mismo default: sin tono se ve idéntico, y con `tone-error` es la X roja.
      El `:not([class*="tone-"])` del focus existe para que enfocar no le
      apague el rojo.

  31. EL ::before DE UICONS — y la lección es que la caja sobraba. Los iconos de
      flaticon son un `::before` con `line-height` propio (1.5em), que manda
      sobre cualquier caja que le des al `<i>`. Al principio taparon el síntoma
      dos parches distintos (un `<i>` flex con caja en el bubble; un
      `line-height:1px` en el ::before aquí). Medido por píxeles el desvío de la
      TINTA, la causa era la CAJA:
                                lupa (15 en 40)   chip (20 en 40)
        caja + parche                 −2.0             −0.5
        caja, sin parche              +0.0             +4.5
        SIN CAJA, sin parche          −1.0             −0.5   ← la buena
      O sea: a un `<i>` no se le da caja y no hace falta parche. Queda UNA
      excepción, y por otra razón: `.bubble__item` crece con su contenido, así
      que ahí la caja SÍ hace falta para que el line-box de 1.5em no estire la
      fila (medido: 42 → 51) — y por eso allá el `<i>` va como flex. La regla
      completa, en foundation.css.

  33. `.input__icon` PINTABA DEL COLOR EQUIVOCADO, y llevaba así desde que se
      escribió. Decía `fill: var(--icon-field)` — pero el producto emite un `<i>`
      de uicons, y un `fill` sobre una tipografía no hace nada: el glifo lo pinta
      `color`, que nadie declaraba, así que heredaba `--text-primary`. El `$` de
      los campos de precio salía tan oscuro como el valor: exactamente lo que el
      comentario de la regla juraba evitar ("un punto por debajo del placeholder,
      para no competir con el texto"). Tampoco tenía `font-size`, así que el
      glifo salía a 16 (el del body) dentro de una caja de 14.
      Lo cazó el dueño LEYENDO el CSS, no mirando la pantalla — el bug era
      invisible salvo que supieras de qué color debía ser. Ver foundation.css.

  32. EL CAMPO YA NO TIENE `:focus-visible` (decisión del dueño 2026-07-17).
      main.css tenía un outline de 2px para input/select/textarea y se quitó: el
      foco del campo es el borde a `--border-field-focus` y nada más, que es lo
      que dice el Figma. Queda en pie lo que advierte la nota 3: ese cambio de
      color en un borde de 1px es sutilísimo para navegar con teclado. Los demás
      controles del DS (btn, icon-chip, choice, spinner, search) SÍ conservan el
      suyo — el campo es la excepción, no la regla nueva.

  33. EL CAMPO SE PINTABA EL PAPEL Y HEREDABA LA TINTA (F2f). `.input` declaraba
      `background: var(--surface-field)` —blanco, y `surface/field` no tiene
      variante oscura: un campo es blanco SIEMPRE— pero el valor escrito salía en
      `--text-primary`, que es del ANCESTRO. O sea: el componente se pintaba medio
      par. Nunca se notó porque hasta ahora todos los ancestros eran claros; el
      login es la primera superficie inversa del producto y ahí el campo habría
      salido BLANCO SOBRE BLANCO. Ahora consume `--text-field` (semantics.css),
      que se resuelve en `:root` y por eso no lo arrastra ningún volteo de abajo.

      Es el MISMO error que los iconos con `fill` (foundation.css) y que el
      Dialog con `--brand-500` (lightbox.css): media declaración que se ve bien
      solo mientras el entorno la tape. El patrón que deja el DS es que el color
      va en PARES —papel+tinta, fill+fg— y que un componente que declara uno
      declara el otro. Si un componente se pinta el fondo y no el texto, es un
      bug esperando una superficie distinta; y el contrato de portabilidad (D2,
      Electron) es exactamente eso: que no dependa de quién lo contenga.
   ============================================================================ */

/* ---- FIELD SET (`fieldset` / `legend`) --------------------------------------
   DE DÓNDE VINO: `gw-fields/assets/css/gwfields.css`, bloque "FIELD SET". Movido, no copiado.

   POR QUÉ: los emite el tipo `section` de gw-fields, y el front nuevo no importa gwfields.css, así
   que en React salían con los estilos por defecto del navegador. Medido con `foto-estilos.mjs`
   contra `/usuarios/registrar`: 12 diferencias — borde `2px groove` en vez de 1px del DS, leyenda a
   16px normal en vez de 13px en negrita, y sin versalitas ni interletraje.

   POR QUÉ AQUÍ Y NO EN `field-types.css`: **el Hub también pinta un `<fieldset>`**
   (`juicy-core-hub/includes/account-settings.php`, campo `password_fieldset`) y lo recibía gratis de
   gwfields.css por tener gw-fields symlinkeado. `field.css` es la capa que el Hub YA encola, así que
   el bloque le llega sin registrarle una capa nueva —y sin mandarle de paso los layouts de tipos que
   no usa—. La copia vendorizada del Hub se actualiza en el MISMO cambio: allí nada falla
   ruidosamente si se olvida.
   ---------------------------------------------------------------------------- */

/*** FIELD SET ***/
fieldset{
	border: 1px solid var(--neutral-200, var(--gray250));
	padding: 20px;
	box-sizing: border-box;
	margin: 0 0 10px 0;
	align-content: start;
}
legend{
	font-size: 13px;
	text-transform: uppercase;
	padding: 0 10px;
	color: var(--text-primary, var(--neutral-500));
	letter-spacing: 2px;
	font-weight: bold;
}
