@khipu/design-system 0.2.0-alpha.9 → 0.2.0-alpha.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
13
13
  * Source: design-system/src/tokens/tokens.json
14
- * Generated: 2026-05-15T15:18:22.603Z
14
+ * Generated: 2026-07-03T17:19:01.966Z
15
15
  *
16
16
  * To regenerate:
17
17
  * cd design-system && npm run tokens:generate
@@ -48,15 +48,21 @@
48
48
  --kds-color-success-main: #2E7D32;
49
49
  --kds-color-success-light: #4CAF50;
50
50
  --kds-color-success-dark: #1B5E20;
51
+ --kds-color-success-contrast: #FFFFFF;
51
52
  --kds-color-warning-main: #EF6C00;
52
53
  --kds-color-warning-light: #FF9800;
53
54
  --kds-color-warning-dark: #E65100;
55
+ --kds-color-warning-contrast: #FFFFFF;
56
+ --kds-color-warning-warm: #8A6D1A;
54
57
  --kds-color-error-main: #D32F2F;
55
58
  --kds-color-error-light: #EF5350;
56
59
  --kds-color-error-dark: #C62828;
60
+ --kds-color-error-contrast: #FFFFFF;
57
61
  --kds-color-info-main: #0288D1;
58
62
  --kds-color-info-light: #03A9F4;
59
63
  --kds-color-info-dark: #01579B;
64
+ --kds-color-info-contrast: #FFFFFF;
65
+ --kds-color-info-blue: #5A5FE0;
60
66
 
61
67
  /* Text colors */
62
68
  --kds-color-text-primary: #333333;
@@ -134,6 +140,8 @@
134
140
  --kds-font-weight-bold: 700;
135
141
 
136
142
  /* Font sizes (base scale xs–4xl: see responsive section below) */
143
+ --kds-font-size-decimal-sup: 0.5em;
144
+
137
145
  /* Line heights */
138
146
  --kds-line-height-tight: 1.2;
139
147
  --kds-line-height-snug: 1.375;
@@ -355,6 +363,7 @@
355
363
  RESPONSIVE TYPOGRAPHY (MOBILE BASE)
356
364
  ========================================================================== */
357
365
 
366
+ --kds-font-size-2xs: 0.5625rem;
358
367
 
359
368
  /* Base font-size scale */
360
369
  --kds-font-size-xs: 0.6875rem;
@@ -402,6 +411,7 @@
402
411
  /* Tablet (600px+) - Typography */
403
412
  @media (min-width: 600px) {
404
413
  :root {
414
+ --kds-font-size-2xs: 0.625rem;
405
415
 
406
416
  /* Base font-size scale */
407
417
  --kds-font-size-xs: 0.75rem;
@@ -439,6 +449,7 @@
439
449
  /* Desktop (840px+) - Typography */
440
450
  @media (min-width: 840px) {
441
451
  :root {
452
+ --kds-font-size-2xs: 0.625rem;
442
453
 
443
454
  /* Base font-size scale */
444
455
  --kds-font-size-xs: 0.75rem;
@@ -839,6 +850,10 @@ a.kds-btn {
839
850
  /* Spacing - Using design tokens */
840
851
  padding: var(--kds-spacing-button-padding); /* 0 16px */
841
852
  height: var(--kds-spacing-button-min-height); /* 44px */
853
+ /* min-height además de height: en contenedores flex-column (p.ej. pantallas de
854
+ resultado/timeout) el botón es flex-item y, con solo `height`, flex-shrink lo
855
+ colapsaba a la altura del texto (~21px). min-height impide que se encoja. */
856
+ min-height: var(--kds-spacing-button-min-height); /* 44px */
842
857
 
843
858
  /* Border - Using design token */
844
859
  border-radius: var(--kds-radius-button); /* 10px */
@@ -846,7 +861,10 @@ a.kds-btn {
846
861
 
847
862
  /* Interaction */
848
863
  cursor: pointer;
849
- transition: background .15s, box-shadow .15s, transform .05s;
864
+ transition: background-color var(--kds-transition-shorter) var(--kds-easing-standard),
865
+ color var(--kds-transition-shorter) var(--kds-easing-standard),
866
+ box-shadow var(--kds-transition-shorter) var(--kds-easing-standard),
867
+ transform var(--kds-transition-shortest) var(--kds-easing-standard);
850
868
  box-sizing: border-box;
851
869
  vertical-align: middle;
852
870
  }
@@ -978,7 +996,7 @@ a.kds-btn-text {
978
996
 
979
997
  button.kds-btn-text:hover:not(:disabled),
980
998
  a.kds-btn-text:hover:not(:disabled) {
981
- background: var(--kds-color-primary-hover); /* rgba(131, 71, 173, 0.04) */
999
+ color: var(--kds-color-primary-dark);
982
1000
  }
983
1001
 
984
1002
  button.kds-btn-text:disabled,
@@ -988,6 +1006,30 @@ a.kds-btn-text:disabled {
988
1006
  box-shadow: none;
989
1007
  }
990
1008
 
1009
+ /* Error text (destructive, ghost) — red text, no fill/border. Para acciones
1010
+ destructivas secundarias que no deben competir con el CTA primario. */
1011
+ button.kds-btn-error-text,
1012
+ a.kds-btn-error-text {
1013
+ background: transparent;
1014
+ color: var(--kds-color-error-main);
1015
+ border: none;
1016
+ padding: 6px var(--kds-spacing-2);
1017
+ min-height: 32px;
1018
+ box-shadow: none;
1019
+ }
1020
+
1021
+ button.kds-btn-error-text:hover:not(:disabled),
1022
+ a.kds-btn-error-text:hover:not(:disabled) {
1023
+ color: var(--kds-color-error-dark);
1024
+ }
1025
+
1026
+ button.kds-btn-error-text:disabled,
1027
+ a.kds-btn-error-text:disabled {
1028
+ background: transparent;
1029
+ color: var(--kds-color-action-disabled);
1030
+ box-shadow: none;
1031
+ }
1032
+
991
1033
  /* Icon wrapper - control size via span container */
992
1034
  button.kds-btn > .kds-icon,
993
1035
  a.kds-btn > .kds-icon {
@@ -1073,63 +1115,90 @@ a.kds-btn.kds-btn-md {
1073
1115
  }
1074
1116
  }
1075
1117
 
1076
- /* Selection card */
1118
+ /* Selection card.
1119
+ IMPORTANTE: este componente se renderiza como `<button>`. BeerCSS aplica al
1120
+ `button` base: `display: inline-flex; align-items: center; justify-content: center;
1121
+ block-size: 2.5rem; padding: 0 1rem; background: var(--primary); color: on-primary`.
1122
+ Todas esas reglas hay que sobreescribirlas para que el card-selector funcione como
1123
+ un contenedor flex column de altura variable y alineado a la izquierda.
1124
+
1125
+ Convención del DS: spacing entre hijos via `gap` del flex padre + padding.
1126
+ Los elementos internos (.kds-card-selector-icon/-title/-description) NO usan margin. */
1077
1127
  .kds-card-selector {
1128
+ /* Reset BeerCSS button */
1129
+ display: flex;
1130
+ flex-direction: column;
1131
+ align-items: stretch;
1132
+ justify-content: flex-start;
1133
+ block-size: auto;
1134
+ inline-size: 100%;
1135
+ text-align: left;
1136
+ line-height: 1.5;
1137
+ color: var(--kds-color-text-primary);
1138
+ /* Estilos propios del card-selector */
1078
1139
  padding: var(--kds-spacing-3);
1140
+ gap: var(--kds-spacing-1-5);
1079
1141
  border-radius: var(--kds-radius-md);
1080
- border: 2px solid var(--kds-border-light);
1142
+ border: var(--kds-border-width-md) solid var(--kds-border-light);
1081
1143
  background: var(--kds-color-background-paper);
1082
1144
  transition: all 0.3s ease;
1083
- text-align: left;
1084
1145
  cursor: pointer;
1085
- display: flex;
1086
- flex-direction: column;
1087
1146
  }
1088
1147
 
1089
- .kds-card-selector:hover {
1090
- border-color: #D1D5DB; /* gray-300 */
1148
+ .kds-card-selector:hover:not(.selected) {
1149
+ border-color: var(--kds-color-primary-light);
1150
+ background: var(--kds-color-primary-hover);
1151
+ }
1152
+
1153
+ /* Override del ripple/overlay default de BeerCSS:
1154
+ `:is(.wave,.chip,.button,button,nav.tabbed>a,...):not([class*=ripple])::after`
1155
+ tiene specificity (0,2,4) según DevTools (Chrome cuenta cada selector dentro
1156
+ de :is() individualmente para algunos motores). Usamos !important — consistente
1157
+ con cómo se overridean otros estilos de BeerCSS en este archivo (67 usos previos).
1158
+ Antes pintaba negro porque `currentColor` resolvía a text-primary del card. */
1159
+ .kds-card-selector::after {
1160
+ background-image: radial-gradient(circle, var(--kds-color-primary-main) 1%, transparent 1%) !important;
1091
1161
  }
1092
1162
 
1093
1163
  .kds-card-selector.selected {
1094
- border-color: #3B82F6; /* blue-600 */
1095
- background: #EFF6FF; /* blue-50 */
1164
+ border-color: var(--kds-color-primary-main);
1165
+ background: var(--kds-color-primary-faint);
1096
1166
  box-shadow: var(--kds-shadow-md);
1097
1167
  }
1098
1168
 
1099
- /* Icon container */
1169
+ /* Icon container — spacing manejado por gap del padre, no margin */
1100
1170
  .kds-card-selector-icon {
1101
- width: 48px;
1102
- height: 48px;
1103
- background: #DBEAFE; /* blue-100 */
1104
- border-radius: 8px;
1171
+ width: var(--kds-spacing-6);
1172
+ height: var(--kds-spacing-6);
1173
+ background: var(--kds-color-primary-focus);
1174
+ border-radius: var(--kds-radius-sm);
1105
1175
  display: flex;
1106
1176
  align-items: center;
1107
1177
  justify-content: center;
1108
- margin-bottom: 16px;
1109
1178
  }
1110
1179
 
1111
1180
  .kds-card-selector-icon i,
1112
1181
  .kds-card-selector-icon svg {
1113
- width: 24px;
1114
- height: 24px;
1115
- min-width: 24px;
1116
- min-height: 24px;
1117
- color: #3B82F6; /* blue-600 */
1182
+ width: var(--kds-spacing-3);
1183
+ height: var(--kds-spacing-3);
1184
+ min-width: var(--kds-spacing-3);
1185
+ min-height: var(--kds-spacing-3);
1186
+ font-size: var(--kds-spacing-3);
1187
+ color: var(--kds-color-primary-main);
1118
1188
  }
1119
1189
 
1120
- /* Title */
1190
+ /* Title — spacing manejado por gap del padre, no margin */
1121
1191
  .kds-card-selector-title {
1122
- font-weight: 600;
1192
+ font-weight: var(--kds-font-weight-semibold);
1123
1193
  color: var(--kds-color-text-primary);
1124
- margin-bottom: 8px;
1125
- font-size: 16px;
1194
+ font-size: var(--kds-font-size-lg);
1126
1195
  }
1127
1196
 
1128
1197
  /* Description */
1129
1198
  .kds-card-selector-description {
1130
- font-size: 14px;
1131
- color: #4B5563; /* gray-600 */
1132
- line-height: 1.5;
1199
+ font-size: var(--kds-font-size-sm);
1200
+ color: var(--kds-color-text-secondary);
1201
+ line-height: var(--kds-line-height-normal);
1133
1202
  }
1134
1203
 
1135
1204
  /* Selected state - visual feedback via border only */
@@ -1141,14 +1210,22 @@ a.kds-btn.kds-btn-md {
1141
1210
  display: flex;
1142
1211
  flex-direction: column;
1143
1212
  gap: var(--kds-spacing-2);
1144
- padding: var(--kds-spacing-4);
1213
+ padding: var(--kds-spacing-2);
1145
1214
  border-radius: var(--kds-radius-lg);
1146
1215
  border: 2px solid var(--kds-border-medium);
1147
- max-width: 400px; /* Increased from 365px for better text spacing */
1216
+ /* Ancho mínimo aplicado a TODOS los planes así un plan sin badge en una
1217
+ grilla con un plan recomendado mantiene la misma anchura, y el badge nunca
1218
+ se superpone al título por estrechez del card. */
1219
+ min-width: 260px;
1220
+ max-width: 400px;
1148
1221
  background: var(--kds-surface-base);
1149
1222
  transition: all 0.3s ease;
1150
1223
  cursor: pointer;
1151
- height: 100%; /* Ensure cards fill grid height */
1224
+ /* Alto fijo: el card se estira al alto de su row del grid (align-items stretch
1225
+ es el default) — todas las cards de la misma fila quedan a la misma altura
1226
+ sin importar el contenido. `align-self: stretch` lo hace explícito. */
1227
+ height: 100%;
1228
+ align-self: stretch;
1152
1229
  }
1153
1230
 
1154
1231
  .kds-card-plan:hover {
@@ -1168,18 +1245,19 @@ a.kds-btn.kds-btn-md {
1168
1245
  box-shadow: var(--kds-shadow-elevation-4);
1169
1246
  }
1170
1247
 
1171
- /* Recommended Badge */
1248
+ /* Recommended Badge — inline dentro del card (no flotante).
1249
+ Es el primer hijo del .kds-card-plan, así que aparece arriba de todo
1250
+ el contenido, alineado a la izquierda. */
1172
1251
  .kds-card-plan-badge {
1173
- position: absolute;
1174
- top: -12px;
1175
- right: var(--kds-spacing-6);
1176
- padding: var(--kds-spacing-1) var(--kds-spacing-4);
1252
+ align-self: flex-start;
1253
+ padding: var(--kds-spacing-0-5) var(--kds-spacing-1-5);
1177
1254
  background: var(--primary);
1178
1255
  color: white;
1179
- font-size: var(--kds-font-size-sm);
1256
+ font-size: var(--kds-font-size-xs);
1180
1257
  font-weight: var(--kds-font-weight-semibold);
1258
+ line-height: 1;
1181
1259
  border-radius: var(--kds-radius-full);
1182
- box-shadow: var(--kds-shadow-elevation-1);
1260
+ white-space: nowrap;
1183
1261
  }
1184
1262
 
1185
1263
  /* Plan Header */
@@ -1207,13 +1285,11 @@ a.kds-btn.kds-btn-md {
1207
1285
  display: flex;
1208
1286
  align-items: baseline;
1209
1287
  gap: var(--kds-spacing-2);
1210
- padding: var(--kds-spacing-4) 0;
1211
1288
  border-bottom: 1px solid var(--kds-border-light);
1212
- min-height: 88px; /* Standardize price area height */
1213
1289
  }
1214
1290
 
1215
1291
  .kds-price {
1216
- font-size: 42px;
1292
+ font-size: var(--kds-font-size-4xl);
1217
1293
  font-weight: var(--kds-font-weight-bold);
1218
1294
  color: var(--primary);
1219
1295
  line-height: 1.1;
@@ -1732,9 +1808,6 @@ a.kds-btn.kds-btn-md {
1732
1808
 
1733
1809
  .snackbar {
1734
1810
  position: fixed;
1735
- bottom: var(--kds-spacing-3);
1736
- left: 50%;
1737
- transform: translateX(-50%);
1738
1811
  z-index: var(--kds-z-index-snackbar);
1739
1812
  min-width: 344px;
1740
1813
  max-width: var(--kds-snackbar-max-width);
@@ -2032,6 +2105,221 @@ a.kds-btn-google:disabled {
2032
2105
  height: var(--kds-spacing-button-icon-size);
2033
2106
  }
2034
2107
 
2108
+ /* ========================================
2109
+ KDS LINEAR PROGRESS
2110
+ Native <progress> (KdsLinearProgress). Additive over BeerCSS `progress`,
2111
+ scoped to .kds-progress so other consumers are unaffected.
2112
+ ======================================== */
2113
+
2114
+ /* `progress` is inline-block by default → leaves a baseline (descender) gap
2115
+ below the bar. Block removes that gap so it sits flush with siblings. */
2116
+ .kds-progress {
2117
+ display: block;
2118
+ }
2119
+
2120
+ /* Animate the filled value when it changes (e.g. advancing a step). BeerCSS
2121
+ sets the value background but no transition, so the width jumps abruptly. */
2122
+ .kds-progress::-webkit-progress-value {
2123
+ transition: width var(--kds-transition-standard) var(--kds-easing-standard);
2124
+ }
2125
+
2126
+ .kds-progress::-moz-progress-bar {
2127
+ transition: width var(--kds-transition-standard) var(--kds-easing-standard);
2128
+ }
2129
+
2130
+ /* ========================================
2131
+ KDS SECURE LOADER
2132
+ Loader de espera: anillo circular + candado centrado + frases opcionales.
2133
+ El anillo reutiliza el spinner circular de BeerCSS, escalado.
2134
+ ======================================== */
2135
+
2136
+ .kds-secure-loader {
2137
+ display: flex;
2138
+ flex-direction: column;
2139
+ align-items: center;
2140
+ justify-content: center;
2141
+ gap: var(--kds-spacing-4);
2142
+ text-align: center;
2143
+ /* Llena y centra dentro de contenedores flex (pantalla de carga); en flujo
2144
+ normal (contenedor no-flex) se reduce a su contenido. */
2145
+ flex: 1 1 auto;
2146
+ }
2147
+
2148
+ .kds-secure-loader-text {
2149
+ display: flex;
2150
+ flex-direction: column;
2151
+ gap: var(--kds-spacing-0-25);
2152
+ }
2153
+
2154
+ .kds-secure-loader-title {
2155
+ margin: 0;
2156
+ font-family: var(--kds-font-family-primary);
2157
+ font-size: var(--kds-font-size-h6);
2158
+ font-weight: var(--kds-font-weight-semibold);
2159
+ line-height: var(--kds-line-height-snug);
2160
+ color: var(--kds-color-text-primary);
2161
+ }
2162
+
2163
+ .kds-secure-loader-message {
2164
+ margin: 0;
2165
+ font-family: var(--kds-font-family-secondary);
2166
+ font-size: var(--kds-font-size-body1);
2167
+ line-height: var(--kds-line-height-normal);
2168
+ color: var(--kds-color-primary-main);
2169
+ }
2170
+
2171
+ /* Caja del spinner con tamaño fijo: la animación no altera el layout.
2172
+ 6.25rem (100px) = tamaño del loader original (CircularProgress size=100). */
2173
+ .kds-secure-loader-spinner {
2174
+ position: relative;
2175
+ display: inline-grid;
2176
+ place-items: center;
2177
+ inline-size: var(--kds-secure-loader-size, 6.25rem);
2178
+ block-size: var(--kds-secure-loader-size, 6.25rem);
2179
+ }
2180
+
2181
+ /* Anillo: SVG nativo con arco vía stroke-dasharray, rotando. Replica el
2182
+ CircularProgress original (viewBox 44u, thickness 2, arco ~63%, disableShrink). */
2183
+ .kds-secure-loader-ring {
2184
+ inline-size: var(--kds-secure-loader-size, 6.25rem);
2185
+ block-size: var(--kds-secure-loader-size, 6.25rem);
2186
+ animation: kds-secure-loader-spin 1.2s linear infinite;
2187
+ }
2188
+
2189
+ .kds-secure-loader-ring circle {
2190
+ fill: none;
2191
+ stroke: var(--kds-color-primary-main);
2192
+ stroke-width: 2;
2193
+ stroke-linecap: round;
2194
+ /* circunferencia ≈ 2π·20.2 ≈ 127; arco visible ≈ 80 (~63%) */
2195
+ stroke-dasharray: 80 127;
2196
+ }
2197
+
2198
+ @keyframes kds-secure-loader-spin {
2199
+ to {
2200
+ transform: rotate(360deg);
2201
+ }
2202
+ }
2203
+
2204
+ /* Candado centrado sobre el anillo (~0.3 del diámetro). Centrado explícito
2205
+ (top/left 50% + translate) para que no dependa del grid ni se recorte. */
2206
+ .kds-secure-loader-lock {
2207
+ position: absolute;
2208
+ top: 50%;
2209
+ left: 50%;
2210
+ transform: translate(-50%, -50%);
2211
+ color: var(--kds-color-text-secondary);
2212
+ font-size: calc(var(--kds-secure-loader-size, 6.25rem) * 0.3);
2213
+ line-height: 1;
2214
+ pointer-events: none;
2215
+ }
2216
+
2217
+ /* ========================================
2218
+ KDS COPY BUTTON
2219
+ Chip compacto que muestra un valor + icono copy y lo copia al click.
2220
+ Acción inline (distinto de KdsCopyRow). Look del copy button de khenshin.
2221
+ ======================================== */
2222
+
2223
+ .kds-copy-button {
2224
+ display: inline-flex;
2225
+ align-items: center;
2226
+ gap: var(--kds-spacing-1);
2227
+ padding: var(--kds-spacing-0-25) var(--kds-spacing-1-5);
2228
+ /* No usar el alto de botón táctil de BeerCSS (~44px): es un chip compacto,
2229
+ su alto lo define el contenido + padding. */
2230
+ block-size: auto;
2231
+ min-block-size: 0;
2232
+ border: none;
2233
+ border-radius: var(--kds-radius-button);
2234
+ background: var(--kds-alert-info-bg);
2235
+ color: var(--kds-color-info-dark);
2236
+ font-family: var(--kds-font-family-secondary);
2237
+ font-size: var(--kds-font-size-sm);
2238
+ font-weight: var(--kds-font-weight-medium);
2239
+ line-height: var(--kds-line-height-normal);
2240
+ cursor: pointer;
2241
+ transition: background-color var(--kds-transition-shorter) var(--kds-easing-standard),
2242
+ color var(--kds-transition-shorter) var(--kds-easing-standard);
2243
+ }
2244
+
2245
+ /* active = hover: sin el aro de focus/active (outline morado de BeerCSS). */
2246
+ .kds-copy-button:focus,
2247
+ .kds-copy-button:focus-visible,
2248
+ .kds-copy-button:active {
2249
+ outline: none;
2250
+ }
2251
+
2252
+ /* value y copiedText se apilan en la MISMA celda de grid: la celda toma el
2253
+ ancho del texto más largo, por lo que el botón conserva su ancho al alternar
2254
+ entre "valor" y "copiado" (sin reflow). */
2255
+ .kds-copy-button-label {
2256
+ display: inline-grid;
2257
+ }
2258
+
2259
+ .kds-copy-button-value,
2260
+ .kds-copy-button-copied {
2261
+ grid-area: 1 / 1;
2262
+ white-space: nowrap;
2263
+ }
2264
+
2265
+ /* Estado por defecto: visible el valor, oculto (pero ocupando espacio) el copiado */
2266
+ .kds-copy-button-copied {
2267
+ visibility: hidden;
2268
+ }
2269
+
2270
+ /* Al copiar se invierte la visibilidad; el ancho no cambia */
2271
+ .kds-copy-button.copied .kds-copy-button-value {
2272
+ visibility: hidden;
2273
+ }
2274
+
2275
+ .kds-copy-button.copied .kds-copy-button-copied {
2276
+ visibility: visible;
2277
+ }
2278
+
2279
+ .kds-copy-button .material-symbols-outlined {
2280
+ font-size: 1rem;
2281
+ color: var(--kds-color-info-main);
2282
+ flex-shrink: 0;
2283
+ }
2284
+
2285
+ .kds-copy-button.copied {
2286
+ background: var(--kds-alert-success-bg);
2287
+ color: var(--kds-alert-success-text);
2288
+ animation: kds-copy-button-pulse var(--kds-transition-short) var(--kds-easing-standard);
2289
+ }
2290
+
2291
+ .kds-copy-button.copied .material-symbols-outlined {
2292
+ color: var(--kds-color-success-main);
2293
+ /* el check entra con un pop al copiar */
2294
+ animation: kds-copy-button-pop var(--kds-transition-short) var(--kds-easing-standard);
2295
+ }
2296
+
2297
+ @keyframes kds-copy-button-pulse {
2298
+ 0% {
2299
+ transform: scale(1);
2300
+ }
2301
+ 50% {
2302
+ transform: scale(1.04);
2303
+ }
2304
+ 100% {
2305
+ transform: scale(1);
2306
+ }
2307
+ }
2308
+
2309
+ @keyframes kds-copy-button-pop {
2310
+ 0% {
2311
+ transform: scale(0.3);
2312
+ opacity: 0;
2313
+ }
2314
+ 60% {
2315
+ transform: scale(1.25);
2316
+ opacity: 1;
2317
+ }
2318
+ 100% {
2319
+ transform: scale(1);
2320
+ }
2321
+ }
2322
+
2035
2323
  /* ========================================
2036
2324
  Link Styles
2037
2325
  ======================================== */
@@ -2133,6 +2421,16 @@ footer.primary a:hover {
2133
2421
  margin-right: var(--kds-spacing-2);
2134
2422
  }
2135
2423
 
2424
+ /* Ícono(s) en color primary. Para íconos MUI/SVG dentro del scope que, por el reset
2425
+ `.kds-theme-root svg { fill: currentColor }`, terminan heredando el color de texto
2426
+ (#333) en vez de su color propio — el `color="primary"` de MUI no le gana al contexto.
2427
+ Aplica al contenedor y a sus i/svg hijos. */
2428
+ .kds-icon-primary,
2429
+ .kds-icon-primary i,
2430
+ .kds-icon-primary svg {
2431
+ color: var(--kds-color-primary-main);
2432
+ }
2433
+
2136
2434
  /* Section title icon */
2137
2435
  .kds-stage-section-title i {
2138
2436
  vertical-align: middle;
@@ -2164,6 +2462,25 @@ body.dark {
2164
2462
  transition: border-color 0.2s, box-shadow 0.2s, outline-color 0.2s;
2165
2463
  }
2166
2464
 
2465
+ /* === TEXTAREA FIX === */
2466
+ /* BeerCSS base sets minimal block padding; add breathing room for textarea */
2467
+ .field > textarea {
2468
+ padding-block: 0.75rem;
2469
+ resize: vertical;
2470
+ }
2471
+
2472
+ .field.label > textarea {
2473
+ padding-block-start: 1.5rem;
2474
+ }
2475
+
2476
+ /* Label en reposo (estado "abajo", como placeholder): font-size menor al 1rem de BeerCSS
2477
+ para que un label largo no estorbe con un ícono (prefix/suffix) del input. Token sm
2478
+ (13–14px, siempre < 1rem del input incluso en desktop). Al flotar, BeerCSS lo reduce a
2479
+ .75rem con mayor especificidad, así que esto solo afecta el reposo. */
2480
+ .field.label > label {
2481
+ font-size: var(--kds-font-size-sm);
2482
+ }
2483
+
2167
2484
  /* ==========================================
2168
2485
  NOTCH VISIBILITY (opacity)
2169
2486
  ========================================== */
@@ -2285,6 +2602,23 @@ body.dark {
2285
2602
  color: var(--error); /* Rojo error */
2286
2603
  }
2287
2604
 
2605
+ /* ==========================================
2606
+ PASSWORD REVEAL TOGGLE (KdsTextField `revealable`)
2607
+ El <a role="button"> reusa el posicionamiento de icono de BeerCSS
2608
+ (.field > a, pointer-events: all). Aquí solo: color del icono
2609
+ (on-surface-variant) y un focus ring accesible para el toggle
2610
+ navegable por teclado (BeerCSS hace `outline: inherit` en <a>).
2611
+ ========================================== */
2612
+ .field > a.kds-field-reveal {
2613
+ color: var(--on-surface-variant);
2614
+ }
2615
+
2616
+ .field > a.kds-field-reveal:focus-visible {
2617
+ outline: 2px solid var(--primary);
2618
+ outline-offset: 2px;
2619
+ border-radius: 50%;
2620
+ }
2621
+
2288
2622
  /* ==========================================
2289
2623
  LABEL COLOR
2290
2624
  Mantener sincronizado con border
@@ -2392,6 +2726,71 @@ body.dark {
2392
2726
  display: inline-flex;
2393
2727
  }
2394
2728
 
2729
+ /* ========================================
2730
+ Radix Tooltip (.kds-tooltip)
2731
+ Espejo de `.tooltip` pero para popovers de Radix:
2732
+ bg oscuro, texto blanco, sombra + animación fade/slide direccional.
2733
+ ======================================== */
2734
+
2735
+ .kds-tooltip {
2736
+ background-color: var(--kds-color-text-primary);
2737
+ color: var(--kds-color-background-paper);
2738
+ font-family: var(--kds-font-family-primary);
2739
+ font-size: var(--kds-font-size-xs);
2740
+ font-weight: var(--kds-font-weight-medium);
2741
+ line-height: var(--kds-line-height-normal);
2742
+ border-radius: var(--kds-radius-sm);
2743
+ padding: var(--kds-spacing-0-75) var(--kds-spacing-1-5);
2744
+ box-shadow: var(--kds-shadow-4);
2745
+ max-width: 280px;
2746
+ z-index: var(--kds-z-index-tooltip, 1500);
2747
+ user-select: none;
2748
+ /* Animación direccional según Radix data-side — coordinada con copy-row
2749
+ (cubic-bezier 0.2, 0.8, 0.2, 1) para un feel consistente del DS. */
2750
+ animation-duration: 240ms;
2751
+ animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
2752
+ will-change: transform, opacity;
2753
+ }
2754
+
2755
+ .kds-tooltip[data-state='closed'] {
2756
+ animation-duration: 140ms;
2757
+ animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
2758
+ }
2759
+
2760
+ /* Direccional según data-side (Radix añade el attr en runtime).
2761
+ Cada lado entra desde su dirección con ~8px de offset (más visible que 4px). */
2762
+ .kds-tooltip[data-side='top'][data-state='delayed-open'],
2763
+ .kds-tooltip[data-side='top'][data-state='instant-open'] { animation-name: kds-tooltip-in-top; }
2764
+ .kds-tooltip[data-side='bottom'][data-state='delayed-open'],
2765
+ .kds-tooltip[data-side='bottom'][data-state='instant-open'] { animation-name: kds-tooltip-in-bottom; }
2766
+ .kds-tooltip[data-side='left'][data-state='delayed-open'],
2767
+ .kds-tooltip[data-side='left'][data-state='instant-open'] { animation-name: kds-tooltip-in-left; }
2768
+ .kds-tooltip[data-side='right'][data-state='delayed-open'],
2769
+ .kds-tooltip[data-side='right'][data-state='instant-open'] { animation-name: kds-tooltip-in-right; }
2770
+
2771
+ /* Fallback cuando no hay data-side resuelto aún */
2772
+ .kds-tooltip[data-state='delayed-open']:not([data-side]),
2773
+ .kds-tooltip[data-state='instant-open']:not([data-side]) {
2774
+ animation-name: kds-tooltip-in-top;
2775
+ }
2776
+
2777
+ .kds-tooltip[data-state='closed'] { animation-name: kds-tooltip-out; }
2778
+
2779
+ @keyframes kds-tooltip-in-top { from { opacity: 0; transform: translateY(8px) scale(0.96); } to { opacity: 1; transform: none; } }
2780
+ @keyframes kds-tooltip-in-bottom { from { opacity: 0; transform: translateY(-8px) scale(0.96); } to { opacity: 1; transform: none; } }
2781
+ @keyframes kds-tooltip-in-left { from { opacity: 0; transform: translateX(8px) scale(0.96); } to { opacity: 1; transform: none; } }
2782
+ @keyframes kds-tooltip-in-right { from { opacity: 0; transform: translateX(-8px) scale(0.96); } to { opacity: 1; transform: none; } }
2783
+
2784
+ @keyframes kds-tooltip-out {
2785
+ from { opacity: 1; transform: none; }
2786
+ to { opacity: 0; transform: scale(0.96); }
2787
+ }
2788
+
2789
+ /* Arrow (SVG path generado por Radix) */
2790
+ .kds-tooltip-arrow {
2791
+ fill: var(--kds-color-text-primary);
2792
+ }
2793
+
2395
2794
  /* ========================================
2396
2795
  Material Symbols Icon Sizing
2397
2796
  ======================================== */
@@ -2426,6 +2825,16 @@ body.dark {
2426
2825
  width: 100%;
2427
2826
  }
2428
2827
 
2828
+ /* Truncado en una línea con ellipsis. Incluye min-width:0 para que funcione
2829
+ dentro de contenedores flex/grid (el ítem puede encogerse por debajo de su
2830
+ contenido). Útil para nombres/asuntos largos en headers y filas. */
2831
+ .kds-truncate {
2832
+ min-width: 0;
2833
+ overflow: hidden;
2834
+ text-overflow: ellipsis;
2835
+ white-space: nowrap;
2836
+ }
2837
+
2429
2838
  .kds-max-w-sm {
2430
2839
  max-width: 400px;
2431
2840
  }
@@ -2469,6 +2878,11 @@ body.dark {
2469
2878
  display: flex;
2470
2879
  flex-direction: column;
2471
2880
  gap: var(--kds-spacing-3);
2881
+ /* Reset del fieldset nativo: sin borde/padding/margin por defecto del navegador. */
2882
+ margin: 0;
2883
+ padding: 0;
2884
+ border: 0;
2885
+ min-inline-size: 0;
2472
2886
  }
2473
2887
 
2474
2888
  /* Checkbox Label - BeerCSS override */
@@ -2480,7 +2894,7 @@ label.radio {
2480
2894
  border-radius: 8px;
2481
2895
  cursor: pointer;
2482
2896
  transition: background-color 0.2s;
2483
- font-size: 16px;
2897
+ font-size: var(--kds-font-size-sm);
2484
2898
  line-height: 1.5;
2485
2899
  color: var(--kds-color-text-primary);
2486
2900
  }
@@ -2519,20 +2933,56 @@ label.radio:has(input:disabled) {
2519
2933
  }
2520
2934
 
2521
2935
  /* ========================================
2522
- ONBOARDING COMPONENTS
2523
- Multi-stage onboarding flow with form validation
2936
+ RADIO GROUP — variante card (.kds-radio-group--card)
2937
+ Cada opción como fila con borde (estilo selección de banco/cuenta),
2938
+ manteniendo el control radio. Hover + opción seleccionada resaltada.
2524
2939
  ======================================== */
2525
-
2526
- /* Stage Layout - Individual stage wrapper */
2527
- .kds-stage {
2528
- max-width: var(--kds-onboarding-max-width);
2529
- margin: 0 auto;
2530
- width: 100%;
2940
+ .kds-radio-group--card {
2941
+ gap: var(--kds-spacing-2);
2531
2942
  }
2532
2943
 
2533
- /* Stage Header - Title and subtitle */
2534
- .kds-stage-header {
2535
- display: flex;
2944
+ .kds-radio-group--card label.radio {
2945
+ gap: var(--kds-spacing-1-5);
2946
+ /* Vertical con un poco más de aire; horizontal reducido para pegar el radio a la izquierda. */
2947
+ padding: var(--kds-spacing-2) var(--kds-spacing-1-5);
2948
+ /* Bordes con los valores del bank selector. */
2949
+ border: 1px solid var(--kds-color-border-subtle);
2950
+ border-radius: var(--kds-radius-card);
2951
+ background: var(--kds-color-surface);
2952
+ transition: border-color 0.2s, background 0.2s;
2953
+ }
2954
+
2955
+ /* Leve separación extra entre el radio y el texto de la opción. */
2956
+ .kds-radio-group--card label.radio span {
2957
+ margin-left: var(--kds-spacing-0-5);
2958
+ }
2959
+
2960
+ .kds-radio-group--card label.radio:hover {
2961
+ border-color: var(--kds-color-border-default);
2962
+ }
2963
+
2964
+ /* Seleccionada: poco color primario — solo un fondo faint sutil + borde gris
2965
+ (el punto del radio ya comunica la selección). */
2966
+ .kds-radio-group--card label.radio:has(input:checked) {
2967
+ border-color: var(--kds-color-border-default);
2968
+ background: var(--kds-color-primary-faint);
2969
+ }
2970
+
2971
+ /* ========================================
2972
+ ONBOARDING COMPONENTS
2973
+ Multi-stage onboarding flow with form validation
2974
+ ======================================== */
2975
+
2976
+ /* Stage Layout - Individual stage wrapper */
2977
+ .kds-stage {
2978
+ max-width: var(--kds-onboarding-max-width);
2979
+ margin: 0 auto;
2980
+ width: 100%;
2981
+ }
2982
+
2983
+ /* Stage Header - Title and subtitle */
2984
+ .kds-stage-header {
2985
+ display: flex;
2536
2986
  flex-direction: column;
2537
2987
  align-items: flex-start;
2538
2988
  gap: var(--kds-spacing-1);
@@ -3015,19 +3465,15 @@ footer > .kds-container-center {
3015
3465
  display: flex;
3016
3466
  align-items: center;
3017
3467
  gap: var(--kds-spacing-1);
3018
- padding: var(--kds-spacing-2);
3468
+ /* Padding compacto: vertical spacing-1 (8px), horizontal spacing-2 (16px). */
3469
+ padding: var(--kds-spacing-1) var(--kds-spacing-2);
3019
3470
  border-radius: var(--kds-radius-md);
3020
3471
  border: 1px solid;
3021
3472
  font-family: var(--kds-font-family-primary);
3022
3473
  min-width: 0;
3023
- flex: 1 1 100%;
3024
- }
3025
-
3026
- @media (min-width: 768px) {
3027
- .kds-alert {
3028
- padding: var(--kds-spacing-2);
3029
- flex: 1 1 auto;
3030
- }
3474
+ /* Bloque full-width (display:flex ya llena el ancho); sin flex-grow para no estirarse
3475
+ verticalmente cuando es hijo de un contenedor flex-column. */
3476
+ flex: 0 1 auto;
3031
3477
  }
3032
3478
 
3033
3479
  .kds-alert:last-child {
@@ -3053,7 +3499,9 @@ footer > .kds-container-center {
3053
3499
  font-size: var(--kds-font-size-sm);
3054
3500
  font-weight: var(--kds-font-weight-semibold);
3055
3501
  line-height: var(--kds-line-height-normal);
3056
- margin-bottom: var(--kds-spacing-0-5);
3502
+ /* Solo margin-bottom: el padding del alert da el espaciado. Reset del resto para
3503
+ evitar el margin-block que BeerCSS aplica a <p> dentro del scope. */
3504
+ margin: 0 0 var(--kds-spacing-0-5);
3057
3505
  word-break: normal;
3058
3506
  overflow-wrap: break-word;
3059
3507
  }
@@ -3063,6 +3511,9 @@ footer > .kds-container-center {
3063
3511
  font-size: var(--kds-font-size-sm);
3064
3512
  font-weight: var(--kds-font-weight-regular);
3065
3513
  line-height: var(--kds-line-height-relaxed);
3514
+ /* Sin margen propio: el padding del alert es el único espaciado (evita el
3515
+ margin-block de <p> de BeerCSS dentro del scope). */
3516
+ margin: 0;
3066
3517
  word-break: normal;
3067
3518
  overflow-wrap: break-word;
3068
3519
  }
@@ -3108,6 +3559,32 @@ footer > .kds-container-center {
3108
3559
  color: var(--kds-color-error-dark);
3109
3560
  }
3110
3561
 
3562
+ /* Close button — icon-only, discreto, no usa kds-btn para no romper el flex layout del alert */
3563
+ .kds-alert-close {
3564
+ flex-shrink: 0;
3565
+ display: inline-flex;
3566
+ align-items: center;
3567
+ justify-content: center;
3568
+ width: 28px;
3569
+ height: 28px;
3570
+ padding: 0;
3571
+ background: transparent;
3572
+ border: 0;
3573
+ border-radius: 50%;
3574
+ color: inherit;
3575
+ cursor: pointer;
3576
+ transition: background-color 0.15s ease;
3577
+ }
3578
+
3579
+ .kds-alert-close:hover {
3580
+ background: rgba(0, 0, 0, 0.06);
3581
+ }
3582
+
3583
+ .kds-alert-close i {
3584
+ font-size: 18px;
3585
+ line-height: 1;
3586
+ }
3587
+
3111
3588
  /* Lists inside alerts */
3112
3589
  .kds-alert ul {
3113
3590
  padding-left: var(--kds-spacing-2);
@@ -4104,6 +4581,14 @@ dialog#surveyModal button.circle {
4104
4581
  text-align: center;
4105
4582
  }
4106
4583
 
4584
+ .kds-text-left {
4585
+ text-align: left;
4586
+ }
4587
+
4588
+ .kds-text-right {
4589
+ text-align: right;
4590
+ }
4591
+
4107
4592
  .kds-text-underline {
4108
4593
  text-decoration: underline;
4109
4594
  }
@@ -4120,6 +4605,54 @@ dialog#surveyModal button.circle {
4120
4605
  font-size: var(--kds-font-size-sm);
4121
4606
  }
4122
4607
 
4608
+ /* ── Font-size scale utilities (additive — opt-in companions to .kds-text-sm) ── */
4609
+ .kds-text-base {
4610
+ font-size: var(--kds-font-size-base);
4611
+ }
4612
+
4613
+ .kds-text-lg {
4614
+ font-size: var(--kds-font-size-lg);
4615
+ }
4616
+
4617
+ .kds-text-xl {
4618
+ font-size: var(--kds-font-size-xl);
4619
+ }
4620
+
4621
+ .kds-text-2xl {
4622
+ font-size: var(--kds-font-size-2xl);
4623
+ }
4624
+
4625
+ /* ── Font-weight utilities ── */
4626
+ .kds-fw-regular {
4627
+ font-weight: var(--kds-font-weight-regular);
4628
+ }
4629
+
4630
+ .kds-fw-medium {
4631
+ font-weight: var(--kds-font-weight-medium);
4632
+ }
4633
+
4634
+ .kds-fw-semibold {
4635
+ font-weight: var(--kds-font-weight-semibold);
4636
+ }
4637
+
4638
+ .kds-fw-bold {
4639
+ font-weight: var(--kds-font-weight-bold);
4640
+ }
4641
+
4642
+ /* ── Text-transform utilities ── */
4643
+ .kds-text-transform-none {
4644
+ text-transform: none;
4645
+ }
4646
+
4647
+ /* ── Soft background utilities (semantic muted backgrounds for callouts) ── */
4648
+ .kds-bg-warning-soft {
4649
+ background-color: var(--kds-color-warning-soft);
4650
+ }
4651
+
4652
+ .kds-bg-info-soft {
4653
+ background-color: var(--kds-color-info-soft);
4654
+ }
4655
+
4123
4656
  /* ── Typography variants ── */
4124
4657
  .kds-text-display1 {
4125
4658
  font-family: var(--kds-font-family-primary, 'Public Sans', sans-serif);
@@ -4197,6 +4730,35 @@ dialog#surveyModal button.circle {
4197
4730
  text-transform: uppercase;
4198
4731
  }
4199
4732
 
4733
+ /* Value — dato/valor destacado (ej. nombre del comercio en el header del cobro). */
4734
+ .kds-text-value {
4735
+ font-family: var(--kds-font-family-primary, 'Public Sans', sans-serif);
4736
+ font-size: var(--kds-font-size-sm);
4737
+ font-weight: var(--kds-font-weight-semibold);
4738
+ line-height: var(--kds-line-height-relaxed);
4739
+ color: var(--kds-color-gray-800);
4740
+ }
4741
+
4742
+ /* Code — texto tipo código/referencia (mono, uppercase, gris), como el "CÓDIGO" del header. */
4743
+ .kds-text-code {
4744
+ font-family: var(--kds-font-family-mono);
4745
+ font-size: var(--kds-font-size-xs);
4746
+ font-weight: var(--kds-font-weight-regular);
4747
+ letter-spacing: var(--kds-letter-spacing-wider);
4748
+ font-variant-numeric: tabular-nums;
4749
+ color: var(--kds-color-gray-400);
4750
+ text-transform: uppercase;
4751
+ }
4752
+
4753
+ /* Caption — texto muy chico y liviano para pie de página (ej. número de versión). */
4754
+ .kds-text-caption {
4755
+ font-family: var(--kds-font-family-primary, 'Public Sans', sans-serif);
4756
+ font-size: var(--kds-font-size-2xs);
4757
+ font-weight: var(--kds-font-weight-regular);
4758
+ line-height: var(--kds-line-height-normal);
4759
+ color: var(--kds-color-gray-400);
4760
+ }
4761
+
4200
4762
  .kds-text-link {
4201
4763
  font-family: var(--kds-font-family-primary, 'Public Sans', sans-serif);
4202
4764
  font-size: inherit;
@@ -4210,6 +4772,12 @@ dialog#surveyModal button.circle {
4210
4772
  color: var(--kds-color-primary-dark, #5B3179);
4211
4773
  }
4212
4774
 
4775
+ /* Inline emphasis: bold weight only, inherits size/color/family from context so it
4776
+ works as a highlighted fragment inside any other variant (body, heading, etc.). */
4777
+ .kds-text-strong {
4778
+ font-weight: var(--kds-font-weight-bold, 700);
4779
+ }
4780
+
4213
4781
  /* Display utilities */
4214
4782
  .kds-hidden {
4215
4783
  display: none;
@@ -4330,7 +4898,9 @@ dialog#surveyModal button.circle {
4330
4898
  ======================================== */
4331
4899
 
4332
4900
  .kds-badge {
4333
- display: inline-block;
4901
+ display: inline-flex;
4902
+ align-items: center;
4903
+ gap: 4px;
4334
4904
  padding: 2px 8px;
4335
4905
  border-radius: var(--kds-radius-sm);
4336
4906
  font-size: var(--kds-font-size-xs);
@@ -4338,6 +4908,40 @@ dialog#surveyModal button.circle {
4338
4908
  line-height: 1.5;
4339
4909
  }
4340
4910
 
4911
+ /* Icon prefix inside chip — small inline icon */
4912
+ .kds-badge > i.material-symbols-outlined {
4913
+ font-size: 14px;
4914
+ line-height: 1;
4915
+ }
4916
+
4917
+ /* Deletable chip close button — discreto, sin kds-btn (eso lo agrandaba ridículo) */
4918
+ .kds-badge-close {
4919
+ display: inline-flex;
4920
+ align-items: center;
4921
+ justify-content: center;
4922
+ width: 16px;
4923
+ height: 16px;
4924
+ margin-right: -2px;
4925
+ padding: 0;
4926
+ background: transparent;
4927
+ border: 0;
4928
+ border-radius: 50%;
4929
+ color: inherit;
4930
+ cursor: pointer;
4931
+ opacity: 0.7;
4932
+ transition: opacity 0.15s, background-color 0.15s;
4933
+ }
4934
+
4935
+ .kds-badge-close:hover {
4936
+ opacity: 1;
4937
+ background: rgba(0, 0, 0, 0.1);
4938
+ }
4939
+
4940
+ .kds-badge-close i {
4941
+ font-size: 14px;
4942
+ line-height: 1;
4943
+ }
4944
+
4341
4945
  .kds-badge.success {
4342
4946
  background-color: var(--kds-color-success-container);
4343
4947
  color: var(--kds-color-success-dark);
@@ -4549,21 +5153,46 @@ dialog#surveyModal button.circle {
4549
5153
  flex-shrink: 0;
4550
5154
  }
4551
5155
 
4552
- /* Merchant tile with logo variant */
5156
+ /* Merchant tile with logo variant — el logo (avatar del comercio) llena TODO el tile.
5157
+ `display: block` evita el desborde del auto-grid; `overflow: hidden` recorta la imagen
5158
+ a las esquinas redondeadas del tile. La imagen va CUADRADA (sin border-radius propio):
5159
+ solo el contenedor recorta, evitando el hairline sub-pixel del doble redondeo. El fondo
5160
+ se deja transparente bajo el logo como respaldo (no debe verse púrpura en los bordes). */
4553
5161
  .kds-merchant-tile.logo {
4554
- padding: var(--kds-spacing-0-5);
5162
+ padding: 0;
5163
+ display: block;
5164
+ overflow: hidden;
5165
+ background: transparent;
4555
5166
  }
4556
5167
 
4557
5168
  .kds-merchant-tile.logo img {
4558
5169
  width: 100%;
4559
5170
  height: 100%;
4560
5171
  object-fit: cover;
4561
- border-radius: var(--kds-radius-md);
5172
+ border-radius: 0;
5173
+ display: block;
5174
+ }
5175
+
5176
+ /* Variante para logos que traen su propio marco/forma (logos de comercio/servicio,
5177
+ no fotos): `contain` + padding para que el logo entre completo sin recortar sus
5178
+ esquinas. Opt-in: pasar className="kds-merchant-tile--contain". */
5179
+ .kds-merchant-tile.logo.kds-merchant-tile--contain {
5180
+ padding: var(--kds-spacing-1);
5181
+ }
5182
+ .kds-merchant-tile.logo.kds-merchant-tile--contain img {
5183
+ object-fit: contain;
4562
5184
  }
4563
5185
 
4564
5186
  .kds-merchant-meta {
4565
5187
  display: flex;
4566
5188
  flex-direction: column;
5189
+ gap: var(--kds-spacing-0-5);
5190
+ }
5191
+
5192
+ /* El meta controla su propio espaciado: anula el margin sibling de BeerCSS
5193
+ (`… + :is(p,h*,…) { margin-block-start: 1rem }`) que separaba de más las líneas. */
5194
+ .kds-merchant-meta > * {
5195
+ margin-block: 0 !important;
4567
5196
  }
4568
5197
 
4569
5198
  .kds-merchant-label {
@@ -4616,6 +5245,11 @@ dialog#surveyModal button.circle {
4616
5245
  color: var(--kds-color-text-primary);
4617
5246
  }
4618
5247
 
5248
+ /* Opt-in color modifier — switches the value to the LigoPay/khipu-blue tone */
5249
+ .kds-amount-value--info {
5250
+ color: var(--kds-color-info-blue); /* #5A5FE0 */
5251
+ }
5252
+
4619
5253
  .kds-amount-value small {
4620
5254
  font-size: 18px;
4621
5255
  font-weight: 500;
@@ -4901,6 +5535,144 @@ dialog#surveyModal button.circle {
4901
5535
  box-shadow: 0 -4px 16px rgba(16,24,40,.08);
4902
5536
  }
4903
5537
 
5538
+ /* ========================================
5539
+ 08b - FAB (.kds-fab)
5540
+ Circular, icon-only floating action button with an animated hide state.
5541
+ Presentational: drive `.kds-fab--hidden` from useHideOnScroll for scroll-away.
5542
+ ======================================== */
5543
+
5544
+ .kds-fab {
5545
+ display: inline-flex;
5546
+ align-items: center;
5547
+ justify-content: center;
5548
+ width: var(--kds-spacing-5);
5549
+ height: var(--kds-spacing-5);
5550
+ padding: 0;
5551
+ border: none;
5552
+ border-radius: var(--kds-radius-full);
5553
+ background: var(--kds-color-background-paper);
5554
+ color: var(--kds-color-text-primary);
5555
+ box-shadow: var(--kds-shadow-elevated);
5556
+ cursor: pointer;
5557
+ z-index: 100;
5558
+ /* Neutralize native button rendering so the pressed state doesn't flash a square. */
5559
+ -webkit-appearance: none;
5560
+ appearance: none;
5561
+ -webkit-tap-highlight-color: transparent;
5562
+ transition: opacity var(--kds-transition-short) ease, transform var(--kds-transition-short) ease,
5563
+ background-color var(--kds-transition-shorter) ease;
5564
+ }
5565
+
5566
+ /* Keep the circle in EVERY interaction state. Uses `button.kds-fab` (element + class) to
5567
+ out-specify BeerCSS's base `button`/`button:active` rules — same trick as `.kds-btn`. */
5568
+ button.kds-fab,
5569
+ button.kds-fab:hover,
5570
+ button.kds-fab:active,
5571
+ button.kds-fab:focus,
5572
+ button.kds-fab:focus-visible {
5573
+ border-radius: var(--kds-radius-full);
5574
+ background: var(--kds-color-background-paper);
5575
+ }
5576
+
5577
+ button.kds-fab:hover:not(:disabled) {
5578
+ background: var(--kds-color-background-muted);
5579
+ }
5580
+
5581
+ /* Subtle press feedback that preserves the round shape. */
5582
+ button.kds-fab:active:not(:disabled) {
5583
+ transform: translateY(var(--kds-spacing-0-25));
5584
+ }
5585
+
5586
+ button.kds-fab:focus-visible {
5587
+ outline: var(--kds-spacing-0-25) solid var(--kds-color-primary-main);
5588
+ outline-offset: var(--kds-spacing-0-25);
5589
+ }
5590
+
5591
+ .kds-fab > i.material-symbols-outlined {
5592
+ font-size: var(--kds-spacing-button-icon-size);
5593
+ line-height: 1;
5594
+ }
5595
+
5596
+ /* Pinned corners — fixed to the viewport (stays visible on scroll-up, iframe-friendly).
5597
+ Horizontally the top-right FAB hugs the right edge of the centered content column set via
5598
+ `--kds-fab-column-width` (default 100% → viewport edge). The consumer sets that var to its
5599
+ widget width so on desktop the FAB overlaps the header's top-right corner instead of drifting
5600
+ to the far viewport edge. `max(...)` keeps at least a token inset on narrow (mobile) viewports. */
5601
+ .kds-fab--top-right,
5602
+ .kds-fab--top-left,
5603
+ .kds-fab--bottom-right,
5604
+ .kds-fab--bottom-left {
5605
+ position: fixed;
5606
+ }
5607
+
5608
+ .kds-fab--top-right {
5609
+ top: var(--kds-spacing-2);
5610
+ right: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5611
+ }
5612
+
5613
+ .kds-fab--top-left {
5614
+ top: var(--kds-spacing-2);
5615
+ left: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5616
+ }
5617
+
5618
+ .kds-fab--bottom-right {
5619
+ bottom: var(--kds-spacing-2);
5620
+ right: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5621
+ }
5622
+
5623
+ .kds-fab--bottom-left {
5624
+ bottom: var(--kds-spacing-2);
5625
+ left: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5626
+ }
5627
+
5628
+ /* Desktop: nudge the top-right FAB up so it sits partially over the header's top edge. */
5629
+ @media (min-width: 768px) {
5630
+ .kds-fab--top-right {
5631
+ top: var(--kds-spacing-1);
5632
+ }
5633
+ }
5634
+
5635
+ /* Corner-overlap: mount the FAB centered OVER the top-right corner of the content column
5636
+ (`--kds-fab-column-width`), overlapping outward — vs `--top-right` which insets inside the
5637
+ column. Mobile keeps a small inset from the viewport via the `--kds-fab-inset` token. */
5638
+ .kds-fab--corner {
5639
+ --kds-fab-inset: 5px;
5640
+ position: fixed;
5641
+ top: var(--kds-fab-inset);
5642
+ right: var(--kds-fab-inset);
5643
+ }
5644
+
5645
+ @media (min-width: 768px) {
5646
+ .kds-fab--corner {
5647
+ right: max(
5648
+ var(--kds-fab-inset),
5649
+ calc((100% - var(--kds-fab-column-width, 100%)) / 2 - var(--kds-spacing-5) / 2)
5650
+ );
5651
+ }
5652
+ }
5653
+
5654
+ /* Hidden (scroll-away) state — fade + slide toward the nearest edge */
5655
+ .kds-fab--hidden {
5656
+ opacity: 0;
5657
+ transform: translateY(calc(-1 * var(--kds-spacing-1))) scale(0.9);
5658
+ pointer-events: none;
5659
+ }
5660
+
5661
+ .kds-fab--bottom-right.kds-fab--hidden,
5662
+ .kds-fab--bottom-left.kds-fab--hidden {
5663
+ transform: translateY(var(--kds-spacing-1)) scale(0.9);
5664
+ }
5665
+
5666
+ @media (prefers-reduced-motion: reduce) {
5667
+ .kds-fab {
5668
+ transition: opacity var(--kds-transition-short) ease;
5669
+ }
5670
+
5671
+ .kds-fab--hidden {
5672
+ transform: none;
5673
+ }
5674
+ }
5675
+
4904
5676
  /* ========================================
4905
5677
  09 - SUCCESS MARK (.kds-success-mark)
4906
5678
  Large success icon for completion screens
@@ -4973,29 +5745,29 @@ dialog#surveyModal button.circle {
4973
5745
  display: flex;
4974
5746
  align-items: center;
4975
5747
  justify-content: center;
4976
- gap: var(--kds-spacing-1);
5748
+ gap: var(--kds-spacing-0-5);
4977
5749
  color: var(--kds-color-gray-400);
4978
5750
  font-size: var(--kds-font-size-xs);
4979
5751
  letter-spacing: var(--kds-letter-spacing-wide);
4980
5752
  padding: var(--kds-spacing-1) 0;
4981
5753
  }
4982
5754
 
4983
- /* Desktop: footer inside card hidden; Mobile: footer outside card hidden */
5755
+ /* Footer SIEMPRE fuera del card body (desktop y mobile): el footer interno
5756
+ (variant="inside") queda oculto en todos los breakpoints; se muestra el
5757
+ externo, hermano directo del card bajo `.kds-screen`. */
4984
5758
  .kds-card-elevated > .kds-secure-footer {
4985
5759
  display: none;
4986
5760
  }
4987
5761
 
4988
- @media (max-width: 767px) {
4989
- .kds-card-elevated > .kds-secure-footer {
4990
- display: flex;
4991
- }
4992
- .kds-screen > .kds-secure-footer {
4993
- display: none;
4994
- }
5762
+ .kds-screen > form {
5763
+ margin-block-start: 0 !important;
5764
+ }
4995
5765
 
4996
- .kds-screen > form {
4997
- margin-block-start: 0 !important;
4998
- }
5766
+ /* Footer externo adyacente a la card: cancela el gap del .kds-screen para que
5767
+ quede pegado debajo de la card y no flotando lejos. Las dos cards
5768
+ (header/body) sí conservan su separación. */
5769
+ .kds-screen > .kds-secure-footer {
5770
+ margin-top: calc(-1 * var(--kds-spacing-3));
4999
5771
  }
5000
5772
 
5001
5773
  .kds-secure-footer-lock {
@@ -5017,6 +5789,32 @@ dialog#surveyModal button.circle {
5017
5789
  display: block;
5018
5790
  object-fit: contain;
5019
5791
  overflow: visible;
5792
+ /* Corrección óptica: la masa visible del wordmark (el cuerpo de "Khipu") queda sobre el
5793
+ centro geométrico de su viewBox (45x15) porque el descendente de la "p" ocupa la parte
5794
+ baja. Al centrar por caja (align-items:center) el logo se ve ~1px alto respecto al texto,
5795
+ así que se baja 0.75px para alinear el centro óptico. Alto fijo (11px) → nudge consistente. */
5796
+ position: relative;
5797
+ top: 0.75px;
5798
+ }
5799
+
5800
+ /* Divisor entre el logo de Khipu y el del PSP ("Khipu | klap") */
5801
+ .kds-secure-footer-sep {
5802
+ width: 1px;
5803
+ height: 0.85em;
5804
+ background: currentColor;
5805
+ opacity: 0.35;
5806
+ flex: 0 0 auto;
5807
+ }
5808
+
5809
+ /* Logo del PSP (proveedor de pagos): lo provee el consumidor, lo alineamos al mismo
5810
+ alto que el mark de Khipu. Sirve para <img> o un elemento con .kds-psp-mark. */
5811
+ .kds-secure-footer img,
5812
+ .kds-secure-footer .kds-psp-mark {
5813
+ height: 11px;
5814
+ width: auto;
5815
+ flex: 0 0 auto;
5816
+ display: block;
5817
+ object-fit: contain;
5020
5818
  }
5021
5819
 
5022
5820
  .kds-secure-footer-code {
@@ -5032,25 +5830,45 @@ dialog#surveyModal button.circle {
5032
5830
  background: none;
5033
5831
  border: 0;
5034
5832
  padding: 0;
5833
+ /* Reset del bare `button { height: 2.5rem }` de BeerCSS (scoped en .kds-theme-root):
5834
+ sin esto el toggle quedaba en una caja de 40px con el texto centrado → espacio
5835
+ irregular arriba (pegado al resumen) y abajo (~26px hasta el panel). Con height
5836
+ auto el botón ajusta a su contenido y el ritmo lo gobiernan los márgenes. */
5837
+ height: auto;
5035
5838
  color: var(--kds-color-primary-main);
5036
5839
  font-weight: var(--kds-font-weight-medium);
5037
5840
  font-size: var(--kds-font-size-sm);
5038
5841
  cursor: pointer;
5039
5842
  display: inline-flex;
5040
5843
  align-items: center;
5041
- gap: var(--kds-spacing-0-75);
5844
+ gap: 0;
5042
5845
  }
5043
5846
 
5044
- .kds-expand-toggle:hover {
5847
+ .kds-expand-toggle:hover,
5848
+ .kds-expand-toggle:focus,
5849
+ .kds-expand-toggle:active {
5045
5850
  background: transparent;
5851
+ outline: none;
5852
+ box-shadow: none;
5046
5853
  }
5047
5854
 
5048
- .kds-expand-toggle:hover span {
5855
+ .kds-expand-toggle:hover span,
5856
+ .kds-expand-toggle:focus span,
5857
+ .kds-expand-toggle:active span {
5049
5858
  text-decoration: underline;
5050
5859
  }
5051
5860
 
5052
5861
  .kds-expand-toggle i {
5053
5862
  font-size: var(--kds-font-size-base);
5863
+ /* BeerCSS estiliza el `i` bare como caja flex de 30px (height/width/min: 30px), que
5864
+ inflaba el toggle y dejaba aire vertical de más alrededor del label. La achicamos a
5865
+ 1.25em MANTENIENDO el centrado flex de BeerCSS (display:flex/align/justify) — así el
5866
+ caret queda compacto Y ópticamente centrado con el texto (no usar height:auto ni
5867
+ line-height:1: descentran el glifo). */
5868
+ width: 1.25em;
5869
+ height: 1.25em;
5870
+ min-width: 0;
5871
+ min-height: 0;
5054
5872
  transition: transform var(--kds-transition-shorter, 0.2s);
5055
5873
  }
5056
5874
 
@@ -5070,7 +5888,10 @@ dialog#surveyModal button.circle {
5070
5888
  }
5071
5889
 
5072
5890
  .kds-expand-panel.open {
5073
- max-height: 800px;
5891
+ /* Fallback cap for no-JS consumers. With JS (React useExpandToggle / vanilla
5892
+ initExpandToggle) the panel's inline max-height is set to its exact scrollHeight,
5893
+ which overrides this and prevents clipping regardless of content length. */
5894
+ max-height: 2000px;
5074
5895
  margin-top: 0;
5075
5896
  }
5076
5897
 
@@ -5188,13 +6009,17 @@ dialog#surveyModal button.circle {
5188
6009
 
5189
6010
  .kds-status-block {
5190
6011
  text-align: center;
5191
- padding: var(--kds-spacing-6) 0;
6012
+ /* Padding vertical estándar: spacing-2 (16px), el mismo rhythm inter-elementos
6013
+ que usan kds-btn-stack y kds-hr-dashed. NO usar spacing-6 (48px): genera
6014
+ demasiado espacio cuando el bloque se compone con header/divider alrededor. */
6015
+ padding: var(--kds-spacing-2) 0;
5192
6016
  }
5193
6017
 
5194
6018
  .kds-status-block-icon {
5195
6019
  width: var(--kds-status-icon-size);
5196
6020
  height: var(--kds-status-icon-size);
5197
- margin: 0 auto var(--kds-spacing-3);
6021
+ /* spacing-2 (16px) icono→título: rhythm estándar inter-elementos. */
6022
+ margin: 0 auto var(--kds-spacing-2);
5198
6023
  border-radius: var(--kds-radius-full);
5199
6024
  display: grid;
5200
6025
  place-items: center;
@@ -5218,6 +6043,12 @@ dialog#surveyModal button.circle {
5218
6043
  margin: 0;
5219
6044
  }
5220
6045
 
6046
+ /* Los hijos llevan kds-margin-0 (escape del auto-espaciado de BeerCSS); estas
6047
+ reglas compuestas (mayor especificidad) restauran el rhythm de diseño. */
6048
+ .kds-status-block .kds-status-block-title {
6049
+ margin: 0 0 var(--kds-spacing-1);
6050
+ }
6051
+
5221
6052
  /* Variant: success */
5222
6053
  .kds-status-block[data-status="success"] .kds-status-block-icon {
5223
6054
  background: var(--kds-color-success-soft);
@@ -5225,11 +6056,11 @@ dialog#surveyModal button.circle {
5225
6056
  color: var(--kds-color-success-main);
5226
6057
  }
5227
6058
 
5228
- /* Variant: pending (animated spinner) */
6059
+ /* Variant: pending (animated blue spinner, sin icono interno) */
5229
6060
  .kds-status-block[data-status="pending"] .kds-status-block-icon {
5230
6061
  background: transparent;
5231
6062
  border: 3px solid var(--kds-color-divider);
5232
- border-top-color: var(--kds-color-primary-main);
6063
+ border-top-color: var(--kds-color-info-main);
5233
6064
  animation: kds-spin 1s linear infinite;
5234
6065
  }
5235
6066
 
@@ -5247,12 +6078,21 @@ dialog#surveyModal button.circle {
5247
6078
  color: var(--kds-color-error-main);
5248
6079
  }
5249
6080
 
6081
+ /* Variant: info — usar SIEMPRE con icon "info_i" (i minúscula sin glyph circular)
6082
+ para que solo se vea nuestro círculo decorativo. Iconos como "info" tienen
6083
+ su propio círculo built-in → causarían doble círculo. */
6084
+ .kds-status-block[data-status="info"] .kds-status-block-icon {
6085
+ background: var(--kds-color-info-soft);
6086
+ border: var(--kds-border-width-md) solid var(--kds-color-info-main);
6087
+ color: var(--kds-color-info-main);
6088
+ }
6089
+
5250
6090
  /* Inline variant: icon left + text right */
5251
6091
  .kds-status-block.inline {
5252
6092
  text-align: left;
5253
6093
  display: flex;
5254
6094
  align-items: center;
5255
- gap: var(--kds-spacing-3);
6095
+ gap: var(--kds-spacing-2);
5256
6096
  padding: var(--kds-spacing-1) 0;
5257
6097
  }
5258
6098
 
@@ -5314,6 +6154,40 @@ dialog#surveyModal button.circle {
5314
6154
  background: var(--kds-color-divider);
5315
6155
  }
5316
6156
 
6157
+ /* Close button — top-right discreto, NO usa kds-btn (rompía el flex layout) */
6158
+ .kds-bottom-sheet-close {
6159
+ position: absolute;
6160
+ top: var(--kds-spacing-1);
6161
+ right: var(--kds-spacing-1);
6162
+ width: 32px;
6163
+ height: 32px;
6164
+ display: inline-flex;
6165
+ align-items: center;
6166
+ justify-content: center;
6167
+ padding: 0;
6168
+ background: transparent;
6169
+ border: 0;
6170
+ border-radius: 50%;
6171
+ color: var(--kds-color-text-secondary);
6172
+ cursor: pointer;
6173
+ transition: background-color 0.15s ease;
6174
+ }
6175
+
6176
+ .kds-bottom-sheet-close:hover {
6177
+ background: rgba(0, 0, 0, 0.06);
6178
+ }
6179
+
6180
+ .kds-bottom-sheet-close i {
6181
+ font-size: 20px;
6182
+ line-height: 1;
6183
+ }
6184
+
6185
+ /* Body container — separa el content del title/actions */
6186
+ .kds-bottom-sheet-body {
6187
+ text-align: left;
6188
+ margin-top: var(--kds-spacing-1);
6189
+ }
6190
+
5317
6191
  .kds-bottom-sheet-icon {
5318
6192
  font-size: var(--kds-font-size-4xl);
5319
6193
  }
@@ -5547,6 +6421,30 @@ dialog#surveyModal button.circle {
5547
6421
  /* No max-height — clip-path on parent handles visual collapse without layout changes */
5548
6422
  .kds-invoice-sticky .kds-invoice-collapsible {
5549
6423
  opacity: calc(1 - var(--collapse-progress) * 1.5);
6424
+
6425
+ /* Scroll interno discreto (mobile): cuando el detalle abierto excede el viewport, el
6426
+ collapsible scrollea internamente (monto/merchant quedan fijos arriba) para poder leer
6427
+ todo el detalle. Al tocar el fondo, `overscroll-behavior: auto` ENCADENA el gesto al
6428
+ scroll del body → dispara useStickyInvoiceCollapse → colapsa el header y aparece el form
6429
+ (flujo natural). Se auto-desactiva con el detalle cerrado (contenido más corto que el
6430
+ max-height → sin scroll ni efecto visual). */
6431
+ max-height: calc(100vh - var(--kds-spacing-12) - var(--kds-spacing-4));
6432
+ max-height: calc(100dvh - var(--kds-spacing-12) - var(--kds-spacing-4));
6433
+ overflow-y: auto;
6434
+ overflow-x: hidden;
6435
+ overscroll-behavior-y: auto;
6436
+ scrollbar-width: thin;
6437
+ scrollbar-color: var(--kds-color-gray-300) transparent;
6438
+ }
6439
+ .kds-invoice-sticky .kds-invoice-collapsible::-webkit-scrollbar {
6440
+ width: 4px;
6441
+ }
6442
+ .kds-invoice-sticky .kds-invoice-collapsible::-webkit-scrollbar-thumb {
6443
+ background: var(--kds-color-gray-300);
6444
+ border-radius: var(--kds-radius-full);
6445
+ }
6446
+ .kds-invoice-sticky .kds-invoice-collapsible::-webkit-scrollbar-track {
6447
+ background: transparent;
5550
6448
  }
5551
6449
 
5552
6450
  /* Amount: 30px → 24px (progressive) */
@@ -5618,6 +6516,14 @@ dialog#surveyModal button.circle {
5618
6516
  margin-top: var(--kds-spacing-1);
5619
6517
  }
5620
6518
 
6519
+ /* Ritmo uniforme alrededor del toggle: spacing-2 arriba (resumen↔"Ver detalle")
6520
+ iguala el padding-top spacing-2 que la .kds-detail-list da debajo
6521
+ ("Ver detalle"↔primer label del panel). Antes el alto fijo del bare button
6522
+ (40px) hacía que el espacio de arriba y de abajo fueran distintos. */
6523
+ .kds-invoice-collapsible > .kds-expand-toggle {
6524
+ margin-top: var(--kds-spacing-2);
6525
+ }
6526
+
5621
6527
  /* -- Invoice Summary -- */
5622
6528
  .kds-invoice-summary {
5623
6529
  display: flex;
@@ -5641,6 +6547,22 @@ div.kds-invoice-header,
5641
6547
  padding: 0;
5642
6548
  border-radius: 0;
5643
6549
  align-content: initial;
6550
+ /* Override BeerCSS `header { min-height: ... }` — el header debe ajustarse a su
6551
+ contenido (sigue el alto del merchant tile: 64px→50px al colapsar). Sin esto
6552
+ quedaba un min-height fijo de 64px → hueco muerto debajo al colapsar en mobile. */
6553
+ min-height: 0;
6554
+ }
6555
+
6556
+ /* Mobile: center-align el header (merchant tile vs bloque monto+código) tanto
6557
+ expandido como colapsado. Al quitar el logo del header, el bloque de texto quedaba
6558
+ más bajo que el merchant tile y con `flex-start` dejaba hueco abajo; centrado los
6559
+ alinea. CSS-only (no depende de la clase .is-collapsed, que el JS de collapse no setea). */
6560
+ @media (max-width: 767px) {
6561
+ header.kds-invoice-header,
6562
+ div.kds-invoice-header,
6563
+ .kds-invoice-header {
6564
+ align-items: center;
6565
+ }
5644
6566
  }
5645
6567
 
5646
6568
  /* Reset BeerCSS sibling margin on <p> inside invoice header — specificity (0,4,1) ties BeerCSS, wins by source order */
@@ -5650,7 +6572,7 @@ div.kds-invoice-header,
5650
6572
  }
5651
6573
 
5652
6574
  .kds-invoice-amount {
5653
- font-weight: var(--kds-font-weight-bold);
6575
+ font-weight: var(--kds-font-weight-semibold);
5654
6576
  font-size: var(--kds-font-size-3xl);
5655
6577
  line-height: var(--kds-line-height-tight);
5656
6578
  letter-spacing: var(--kds-letter-spacing-wide);
@@ -5671,10 +6593,16 @@ div.kds-invoice-header,
5671
6593
  color: var(--kds-color-gray-400);
5672
6594
  text-transform: uppercase;
5673
6595
  margin: 0;
5674
- margin-top: var(--kds-spacing-1);
6596
+ margin-block: unset;
6597
+ display: flex;
6598
+ gap: var(--kds-spacing-1-5);
5675
6599
  }
5676
6600
 
5677
6601
  .kds-invoice-code-value {
6602
+ color: var(--kds-color-gray-400);
6603
+ }
6604
+
6605
+ .kds-invoice-code-value--lowercase {
5678
6606
  text-transform: lowercase;
5679
6607
  }
5680
6608
 
@@ -5687,6 +6615,9 @@ div.kds-invoice-header,
5687
6615
  place-items: center;
5688
6616
  color: #fff;
5689
6617
  flex-shrink: 0;
6618
+ /* Clip the slightly-oversized logo (see `.kds-invoice-merchant img`) to the
6619
+ rounded square so it can fully cover the deep-purple background. */
6620
+ overflow: hidden;
5690
6621
  }
5691
6622
 
5692
6623
  .kds-invoice-merchant i {
@@ -5706,28 +6637,43 @@ div.kds-invoice-header,
5706
6637
  }
5707
6638
 
5708
6639
  .kds-invoice-merchant img {
5709
- width: 100%;
5710
- height: 100%;
6640
+ /* Slightly larger than the container (+2px, centered by the grid) so the
6641
+ logo overflows under the parent's rounded clip and the deep-purple
6642
+ background never peeks through at the corners when the logo shrinks. */
6643
+ width: calc(100% + 2px);
6644
+ height: calc(100% + 2px);
5711
6645
  object-fit: cover;
5712
6646
  border-radius: var(--kds-radius-md);
5713
6647
  }
5714
6648
 
5715
6649
  /* -- Card Title -- */
5716
6650
  .kds-card-title {
5717
- font-weight: 700;
6651
+ font-weight: var(--kds-font-weight-semibold);
5718
6652
  font-size: var(--kds-font-size-base);
5719
6653
  line-height: 1.5;
5720
6654
  letter-spacing: -0.31px;
5721
6655
  color: var(--kds-color-text-primary);
5722
- margin-bottom: var(--kds-spacing-2) !important;
6656
+ /* sin !important: no hay regla de margin-bottom para h1 en BeerCSS que competir, y así un
6657
+ consumidor puede anularlo (p.ej. FormTitle dentro de un Stack que ya da el espacio). */
6658
+ margin-bottom: var(--kds-spacing-2);
6659
+ }
6660
+
6661
+ /* Opt-in size modifiers (additive — do NOT change default 16/600) */
6662
+ .kds-card-title--lg {
6663
+ font-size: var(--kds-font-size-xl); /* 20px */
6664
+ font-weight: var(--kds-font-weight-semibold);
6665
+ }
6666
+
6667
+ .kds-card-title--xl {
6668
+ font-size: var(--kds-font-size-2xl); /* 24px */
6669
+ font-weight: var(--kds-font-weight-semibold);
5723
6670
  }
5724
6671
 
5725
6672
  /* -- Key-Value Grid -- */
5726
6673
  .kds-kv {
5727
6674
  display: grid;
5728
6675
  grid-template-columns: auto 1fr;
5729
- row-gap: var(--kds-spacing-1);
5730
- column-gap: var(--kds-spacing-1-5);
6676
+ column-gap: var(--kds-spacing-1);
5731
6677
  align-items: baseline;
5732
6678
  }
5733
6679
 
@@ -5744,7 +6690,7 @@ div.kds-invoice-header,
5744
6690
  margin: 0;
5745
6691
  font-size: var(--kds-font-size-sm);
5746
6692
  line-height: var(--kds-line-height-relaxed);
5747
- font-weight: var(--kds-font-weight-bold);
6693
+ font-weight: var(--kds-font-weight-semibold);
5748
6694
  letter-spacing: normal;
5749
6695
  color: var(--kds-color-gray-800);
5750
6696
  min-width: 0;
@@ -5772,16 +6718,28 @@ div.kds-invoice-header,
5772
6718
  .kds-detail-list {
5773
6719
  display: flex;
5774
6720
  flex-direction: column;
5775
- gap: var(--kds-spacing-1-5);
6721
+ /* spacing-2 entre grupos del detalle (separación estándar entre piezas de info
6722
+ distintas, ej. "Emisor cobro" vs "Descripción"). El padding-top da la misma
6723
+ separación spacing-2 arriba del primer grupo (Asunto↔Emisor cobro). */
6724
+ gap: var(--kds-spacing-2);
5776
6725
  margin: 0;
5777
- padding: 0 0 var(--kds-spacing-1);
6726
+ padding: var(--kds-spacing-2) 0 0;
5778
6727
  }
5779
6728
 
5780
- .kds-detail-group {
5781
- margin-bottom: var(--kds-spacing-1-5);
6729
+ @media (max-width: 767px) {
6730
+ .kds-detail-list {
6731
+ padding-bottom: var(--kds-spacing-2);
6732
+ }
5782
6733
  }
5783
6734
 
5784
- .kds-detail-group:last-child {
6735
+ .kds-detail-group {
6736
+ /* El espacio ENTRE grupos lo da el `gap` de .kds-detail-list (spacing-2).
6737
+ Dentro del grupo, label↔value llevan un gap chico (spacing-0-5) para un
6738
+ ritmo parejo: antes el dt iba pegado al dd (0px) y se veía desigual frente
6739
+ a los 16px entre los demás elementos del panel. */
6740
+ display: flex;
6741
+ flex-direction: column;
6742
+ gap: var(--kds-spacing-0-5);
5785
6743
  margin-bottom: 0;
5786
6744
  }
5787
6745
 
@@ -5790,7 +6748,7 @@ div.kds-invoice-header,
5790
6748
  letter-spacing: var(--kds-letter-spacing-wide);
5791
6749
  color: var(--kds-color-text-hint);
5792
6750
  text-transform: uppercase;
5793
- margin: 0 0 var(--kds-spacing-0-5);
6751
+ margin: 0;
5794
6752
  }
5795
6753
 
5796
6754
  .kds-detail-group dd {
@@ -5800,6 +6758,19 @@ div.kds-invoice-header,
5800
6758
  color: var(--kds-color-text-primary);
5801
6759
  }
5802
6760
 
6761
+ /* Imagen adjunta del cobro dentro del panel de detalle (ej. comprobante/producto).
6762
+ Reemplaza los inline styles del antiguo modal MerchantInfo: ancho contenido, borde
6763
+ y radio del DS. Va como un grupo más de la .kds-detail-list (hereda su gap). */
6764
+ .kds-detail-image {
6765
+ display: block;
6766
+ max-width: 100%;
6767
+ width: 160px;
6768
+ height: auto;
6769
+ object-fit: contain;
6770
+ border-radius: var(--kds-radius-md);
6771
+ border: 1px solid var(--kds-color-border);
6772
+ }
6773
+
5803
6774
  /* -- Amount Summary Row -- */
5804
6775
  .kds-amount-row {
5805
6776
  display: flex;
@@ -5859,16 +6830,16 @@ div.kds-invoice-header,
5859
6830
  font-size: var(--kds-font-size-sm);
5860
6831
  }
5861
6832
 
5862
- .kds-recap-list .k {
6833
+ .kds-recap-list .kds-key {
5863
6834
  color: var(--kds-color-text-secondary);
5864
6835
  }
5865
6836
 
5866
- .kds-recap-list .v {
6837
+ .kds-recap-list .kds-value {
5867
6838
  color: var(--kds-color-text-primary);
5868
6839
  font-weight: 500;
5869
6840
  }
5870
6841
 
5871
- .kds-recap-list .v.placeholder {
6842
+ .kds-recap-list .kds-value.placeholder {
5872
6843
  color: var(--kds-color-text-hint);
5873
6844
  }
5874
6845
 
@@ -5895,6 +6866,72 @@ a.kds-btn-success:disabled {
5895
6866
  box-shadow: none;
5896
6867
  }
5897
6868
 
6869
+ /* -- Button Warning (amber CTA) -- */
6870
+ button.kds-btn-warning,
6871
+ a.kds-btn-warning {
6872
+ background: var(--kds-color-warning-main);
6873
+ color: var(--kds-color-warning-contrast);
6874
+ border: none;
6875
+ }
6876
+
6877
+ button.kds-btn-warning:hover:not(:disabled),
6878
+ a.kds-btn-warning:hover:not(:disabled) {
6879
+ background: var(--kds-color-warning-dark);
6880
+ }
6881
+
6882
+ button.kds-btn-warning:active:not(:disabled),
6883
+ button.kds-btn-warning:focus-visible:not(:disabled),
6884
+ a.kds-btn-warning:active:not(:disabled),
6885
+ a.kds-btn-warning:focus-visible:not(:disabled) {
6886
+ background: var(--kds-color-warning-dark);
6887
+ outline: 2px solid var(--kds-color-warning-main);
6888
+ outline-offset: 2px;
6889
+ }
6890
+
6891
+ button.kds-btn-warning:disabled,
6892
+ a.kds-btn-warning:disabled {
6893
+ background-color: var(--kds-color-action-disabled-bg);
6894
+ color: var(--kds-color-action-disabled);
6895
+ box-shadow: none;
6896
+ }
6897
+
6898
+ button.kds-btn-warning::after {
6899
+ display: none;
6900
+ }
6901
+
6902
+ /* -- Button Error (red / destructive CTA) -- */
6903
+ button.kds-btn-error,
6904
+ a.kds-btn-error {
6905
+ background: var(--kds-color-error-main);
6906
+ color: var(--kds-color-error-contrast);
6907
+ border: none;
6908
+ }
6909
+
6910
+ button.kds-btn-error:hover:not(:disabled),
6911
+ a.kds-btn-error:hover:not(:disabled) {
6912
+ background: var(--kds-color-error-dark);
6913
+ }
6914
+
6915
+ button.kds-btn-error:active:not(:disabled),
6916
+ button.kds-btn-error:focus-visible:not(:disabled),
6917
+ a.kds-btn-error:active:not(:disabled),
6918
+ a.kds-btn-error:focus-visible:not(:disabled) {
6919
+ background: var(--kds-color-error-dark);
6920
+ outline: 2px solid var(--kds-color-error-main);
6921
+ outline-offset: 2px;
6922
+ }
6923
+
6924
+ button.kds-btn-error:disabled,
6925
+ a.kds-btn-error:disabled {
6926
+ background-color: var(--kds-color-action-disabled-bg);
6927
+ color: var(--kds-color-action-disabled);
6928
+ box-shadow: none;
6929
+ }
6930
+
6931
+ button.kds-btn-error::after {
6932
+ display: none;
6933
+ }
6934
+
5898
6935
  /* -- Field Helper Text -- */
5899
6936
  .kds-field-helper {
5900
6937
  font-size: var(--kds-font-size-xs);
@@ -5937,6 +6974,8 @@ button.kds-btn-success::after {
5937
6974
  /* -- Alert inline (borderless, used inside cards) -- */
5938
6975
  .kds-alert-inline {
5939
6976
  border: none;
6977
+ padding: var(--kds-spacing-1) var(--kds-spacing-1-5);
6978
+ margin-bottom: var(--kds-spacing-1-5);
5940
6979
  }
5941
6980
 
5942
6981
  /* -- Card dimmed (behind overlays) -- */
@@ -6038,17 +7077,42 @@ button.kds-btn-success::after {
6038
7077
  }
6039
7078
 
6040
7079
  .kds-bill-description-picture {
6041
- width: 120px;
6042
- height: 120px;
7080
+ max-width: 100px;
6043
7081
  object-fit: contain;
6044
7082
  background: var(--kds-color-background-paper);
6045
- padding: var(--kds-spacing-1-25);
6046
- border-radius: var(--kds-radius-md);
6047
- margin-top: var(--kds-spacing-1);
7083
+ margin-top: var(--kds-spacing-1-25);
6048
7084
  display: block;
6049
7085
  box-sizing: content-box;
6050
7086
  }
6051
7087
 
7088
+ /* -- Bill Attachments -- */
7089
+ .kds-bill-attachments {
7090
+ display: flex;
7091
+ flex-direction: column;
7092
+ gap: var(--kds-spacing-0-75);
7093
+ }
7094
+
7095
+ .kds-bill-attachment {
7096
+ display: inline-flex;
7097
+ align-items: center;
7098
+ justify-content: flex-start;
7099
+ width: fit-content;
7100
+ color: var(--kds-color-info-main);
7101
+ font-size: var(--kds-font-size-sm);
7102
+ font-weight: var(--kds-font-weight-medium);
7103
+ text-decoration: none;
7104
+ }
7105
+
7106
+ .kds-bill-attachment:hover span {
7107
+ text-decoration: underline;
7108
+ }
7109
+
7110
+ .kds-bill-attachment i {
7111
+ font-size: var(--kds-font-size-lg);
7112
+ min-width: unset;
7113
+ justify-content: flex-start;
7114
+ }
7115
+
6052
7116
  /* -- Share Card (mustContinue) -- */
6053
7117
  .kds-share-card {
6054
7118
  margin-top: var(--kds-spacing-2);
@@ -6174,12 +7238,43 @@ button.kds-btn-success::after {
6174
7238
  padding-bottom: 0;
6175
7239
  }
6176
7240
 
6177
- /* Payment stage full-viewport centered wrapper */
7241
+ /* Body card: alto mínimo estable para que NO salte entre estados de distinta altura
7242
+ (loader/progress ↔ formulario). Solo la body card = hijo directo de `.kds-screen`
7243
+ (los headers van dentro de `.kds-invoice-sticky-wrap`, no la afecta).
7244
+ Flex-column para que el contenido quede arriba y el footer interno (versión / secure
7245
+ footer) pueda pinearse al fondo con `margin-top: auto` aunque el contenido sea corto. */
7246
+ .kds-screen > .kds-card-elevated {
7247
+ min-height: 36rem;
7248
+ display: flex;
7249
+ flex-direction: column;
7250
+ /* mismo radio que el header (invoice-sticky usa radius-card); el base card-elevated
7251
+ es radius-md (8px) y quedaba distinto al header en desktop. */
7252
+ border-radius: var(--kds-radius-card);
7253
+ }
7254
+
7255
+ /* Variante sin radio superior: pantallas de resultado (éxito/fallo/timeout) que van
7256
+ SIN header arriba — la body card va sola y su tope queda plano (flush) en vez de
7257
+ con esquinas redondeadas sueltas. El padding del card sigue manejando el inset. */
7258
+ /* Tope plano SOLO en mobile (< 768px): la body card se pega al borde superior del widget
7259
+ (igual que el header sticky, que también aplana el tope solo en mobile). En desktop la card
7260
+ va centrada y separada, así que conserva el border-radius superior redondeado. */
7261
+ @media (max-width: 767px) {
7262
+ .kds-screen > .kds-card-elevated.kds-card-elevated--flush-top,
7263
+ .kds-card-elevated.kds-card-elevated--flush-top {
7264
+ border-top-left-radius: 0;
7265
+ border-top-right-radius: 0;
7266
+ }
7267
+ }
7268
+
7269
+ /* Payment stage — full-viewport centered wrapper.
7270
+ Fondo muted (gris) para que las cards blancas (header + body) y el gap entre
7271
+ ellas se distingan; sin esto, card blanca sobre fondo blanco se funden. */
6178
7272
  .kds-payment-stage {
6179
7273
  min-height: 100vh;
6180
7274
  display: flex;
6181
7275
  justify-content: center;
6182
7276
  padding: var(--kds-spacing-2-5) var(--kds-spacing-2) var(--kds-spacing-8);
7277
+ background: var(--kds-color-background-muted);
6183
7278
  }
6184
7279
 
6185
7280
  /* Payment flow — narrow column */
@@ -6218,10 +7313,16 @@ button.kds-btn-success::after {
6218
7313
  padding-bottom: var(--kds-spacing-2);
6219
7314
  }
6220
7315
 
6221
- /* Invoice sticky card: progressive padding-top (expanded 20px → collapsed 8px).
6222
- Overrides base rule at same specificity; source order wins here. */
7316
+ /* Invoice sticky card padding (mobile).
7317
+ Top: estático 12px. Bottom: progresivo 16px (expandido, = desktop) → 8px (colapsado).
7318
+ El padding-bottom animado se reduce al colapsar — sin depender de que el clip-path
7319
+ corte el padding abruptamente. */
6223
7320
  .kds-payment-flow .kds-card-elevated.kds-invoice-sticky {
6224
7321
  padding-top: var(--kds-spacing-1-5);
7322
+ padding-bottom: calc(
7323
+ var(--kds-spacing-2)
7324
+ - var(--kds-spacing-1) * var(--collapse-progress, 0)
7325
+ );
6225
7326
  }
6226
7327
 
6227
7328
 
@@ -6480,7 +7581,7 @@ button.kds-btn-success::after {
6480
7581
  inset: 0;
6481
7582
  background: var(--kds-modal-backdrop);
6482
7583
  display: none;
6483
- align-items: center;
7584
+ align-items: flex-start;
6484
7585
  justify-content: center;
6485
7586
  z-index: var(--kds-z-index-modal, 50);
6486
7587
  animation: kds-fade 0.2s ease both;
@@ -6508,7 +7609,7 @@ button.kds-btn-success::after {
6508
7609
  display: flex;
6509
7610
  align-items: center;
6510
7611
  justify-content: space-between;
6511
- padding: var(--kds-spacing-2) var(--kds-spacing-2) var(--kds-spacing-1);
7612
+ padding: var(--kds-spacing-1) var(--kds-spacing-2) var(--kds-spacing-1);
6512
7613
  }
6513
7614
 
6514
7615
  .kds-bank-modal-header h3 {
@@ -6533,11 +7634,8 @@ button.kds-btn-success::after {
6533
7634
  display: none;
6534
7635
  }
6535
7636
 
6536
- .kds-bank-modal-search {
6537
- padding: 0 var(--kds-spacing-2) var(--kds-spacing-1-5);
6538
- }
6539
-
6540
- .kds-bank-modal-search input {
7637
+ /* Search field reutilizable (standalone o dentro del KdsBankModal). */
7638
+ .kds-search-field {
6541
7639
  width: 100%;
6542
7640
  padding: var(--kds-spacing-1-25) var(--kds-spacing-1-5);
6543
7641
  border: 1px solid var(--kds-color-divider);
@@ -6549,15 +7647,57 @@ button.kds-btn-success::after {
6549
7647
  background: var(--kds-color-surface);
6550
7648
  }
6551
7649
 
6552
- .kds-bank-modal-search input::placeholder {
7650
+ .kds-search-field::placeholder {
6553
7651
  color: var(--kds-color-text-hint);
6554
7652
  }
6555
7653
 
6556
- .kds-bank-modal-search input:focus {
7654
+ .kds-search-field:focus {
6557
7655
  border-color: var(--kds-color-primary-main);
6558
7656
  box-shadow: 0 0 0 2px var(--kds-color-primary-faint);
6559
7657
  }
6560
7658
 
7659
+ /* Variante con ícono (withIcon): lupa posicionada DENTRO del input, a la derecha. */
7660
+ .kds-search-field-wrapper {
7661
+ position: relative;
7662
+ width: 100%;
7663
+ }
7664
+
7665
+ .kds-search-field-icon {
7666
+ position: absolute;
7667
+ right: var(--kds-spacing-1-5);
7668
+ top: 50%;
7669
+ transform: translateY(-50%);
7670
+ font-size: 20px;
7671
+ color: var(--kds-color-text-hint);
7672
+ pointer-events: none;
7673
+ }
7674
+
7675
+ /* Espacio a la derecha del texto para no solaparse con la lupa. */
7676
+ .kds-search-field-wrapper .kds-search-field {
7677
+ padding-right: calc(var(--kds-spacing-1-5) + var(--kds-spacing-3));
7678
+ }
7679
+
7680
+ /* Variante angosta del field (prop `narrow` de KdsTextField): ancho reducido y texto
7681
+ centrado, para inputs de pocos caracteres dispuestos en fila (ej. segmentos de
7682
+ coordenadas o código). No crece ni se encoge dentro de un flex row. */
7683
+ .field.kds-field--narrow {
7684
+ width: 6rem;
7685
+ flex: 0 0 auto;
7686
+ /* En fila horizontal no aplica el margin vertical que BeerCSS da a fields
7687
+ hermanos (regla de "flow" `... + :is(.field,...)`, specificity ~0,3,1);
7688
+ sin el reset los siguientes quedan desalineados hacia abajo. `!important`
7689
+ para ganarle a esa regla de framework. */
7690
+ margin-block: 0 !important;
7691
+ }
7692
+
7693
+ .field.kds-field--narrow > input {
7694
+ text-align: center;
7695
+ }
7696
+
7697
+ .kds-bank-modal-search {
7698
+ padding: 0 var(--kds-spacing-2) var(--kds-spacing-1-5);
7699
+ }
7700
+
6561
7701
  .kds-bank-modal-body {
6562
7702
  flex: 1;
6563
7703
  overflow-y: auto;
@@ -6655,7 +7795,7 @@ button.kds-btn-success::after {
6655
7795
  min-width: 0;
6656
7796
  }
6657
7797
 
6658
- .kds-qr-text .title {
7798
+ .kds-qr-text .kds-qr-title {
6659
7799
  font-family: var(--kds-font-family-primary);
6660
7800
  font-weight: 600;
6661
7801
  font-size: var(--kds-font-size-sm);
@@ -6663,10 +7803,15 @@ button.kds-btn-success::after {
6663
7803
  color: var(--kds-color-text-primary);
6664
7804
  }
6665
7805
 
6666
- .kds-qr-text .sub {
7806
+ .kds-qr-text .kds-qr-subtitle {
6667
7807
  font-family: var(--kds-font-family-primary);
6668
7808
  font-size: var(--kds-font-size-caption);
6669
7809
  color: var(--kds-color-text-secondary);
7810
+ /* Trunca con ellipsis en lugar de wrap a 2 líneas — evita apretar el row
7811
+ cuando el contenedor es angosto (mobile o decorator constrained). */
7812
+ overflow: hidden;
7813
+ text-overflow: ellipsis;
7814
+ white-space: nowrap;
6670
7815
  }
6671
7816
 
6672
7817
  /* QR Badge - "Rápido" indicator */
@@ -6689,46 +7834,26 @@ button.kds-btn-success::after {
6689
7834
  font-size: 20px;
6690
7835
  }
6691
7836
 
6692
- /* Mobile responsive: Ajustes para pantallas pequeñas */
7837
+ /* Mobile responsive (≤480px): respira más mantenemos padding/gap/avatar igual
7838
+ que desktop y solo agregamos truncation al subtitle y `flex-shrink: 0` en los
7839
+ ítems que deben mantener su tamaño (avatar, badge, chevron). */
6693
7840
  @media (max-width: 480px) {
6694
- .kds-qr-row {
6695
- gap: var(--kds-spacing-1-5);
6696
- padding: var(--kds-spacing-1-5);
6697
- }
6698
-
6699
7841
  .kds-qr-avatar {
6700
- width: var(--kds-spacing-4-5);
6701
- height: var(--kds-spacing-4-5);
6702
7842
  flex-shrink: 0;
6703
7843
  }
6704
7844
 
6705
- .kds-qr-avatar .material-symbols-outlined,
6706
- .kds-qr-avatar .material-icons-round {
6707
- font-size: var(--kds-font-size-lg);
6708
- }
6709
-
6710
- .kds-qr-text .title {
6711
- font-size: var(--kds-font-size-xs);
6712
- line-height: var(--kds-line-height-tight);
6713
- }
6714
-
6715
7845
  .kds-qr-text .sub {
6716
- font-size: var(--kds-font-size-caption);
6717
- line-height: var(--kds-line-height-tight);
6718
7846
  overflow: hidden;
6719
7847
  text-overflow: ellipsis;
6720
7848
  white-space: nowrap;
6721
7849
  }
6722
7850
 
6723
7851
  .kds-qr-badge {
6724
- font-size: var(--kds-font-size-caption);
6725
- padding: var(--kds-spacing-0-25) var(--kds-spacing-0-75);
6726
7852
  flex-shrink: 0;
6727
7853
  }
6728
7854
 
6729
7855
  .kds-qr-row .material-symbols-outlined:last-child,
6730
7856
  .kds-qr-row .material-icons-round:last-child {
6731
- font-size: var(--kds-font-size-base);
6732
7857
  flex-shrink: 0;
6733
7858
  }
6734
7859
  }
@@ -6757,7 +7882,11 @@ button.kds-btn-success::after {
6757
7882
  border-radius: var(--kds-spacing-0-75);
6758
7883
  font-size: var(--kds-font-size-sm);
6759
7884
  cursor: pointer;
6760
- transition: background 0.12s ease;
7885
+ /* Transición más amplia y suave: bg + color + border ease-out */
7886
+ transition:
7887
+ background-color 280ms cubic-bezier(0.2, 0.8, 0.2, 1),
7888
+ color 280ms cubic-bezier(0.2, 0.8, 0.2, 1),
7889
+ border-top-color 280ms ease-out;
6761
7890
  position: relative;
6762
7891
  user-select: none;
6763
7892
  }
@@ -6766,19 +7895,21 @@ button.kds-btn-success::after {
6766
7895
  border-top: 0;
6767
7896
  }
6768
7897
 
6769
- .kds-copyable-table-row:hover {
7898
+ /* Hover NO aplica mientras la row esté en `.copied` o `.settling`
7899
+ (evita el flicker del hover state al desaparecer el verde de "copiado") */
7900
+ .kds-copyable-table-row:not(.copied):not(.settling):hover {
6770
7901
  background: var(--kds-color-primary-faint);
6771
7902
  }
6772
7903
 
6773
- .kds-copyable-table-row:hover + .kds-copyable-table-row {
7904
+ .kds-copyable-table-row:not(.copied):not(.settling):hover + .kds-copyable-table-row {
6774
7905
  border-top-color: transparent;
6775
7906
  }
6776
7907
 
6777
- .kds-copyable-table-row:active {
7908
+ .kds-copyable-table-row:not(.copied):not(.settling):active {
6778
7909
  background: var(--kds-color-primary-selected);
6779
7910
  }
6780
7911
 
6781
- .kds-copyable-table-row .k {
7912
+ .kds-copyable-table-row .kds-key {
6782
7913
  color: var(--kds-color-text-secondary);
6783
7914
  font-size: var(--kds-font-size-caption);
6784
7915
  letter-spacing: 0.3px;
@@ -6787,7 +7918,7 @@ button.kds-btn-success::after {
6787
7918
  flex: 0 0 auto;
6788
7919
  }
6789
7920
 
6790
- .kds-copyable-table-row .v {
7921
+ .kds-copyable-table-row .kds-value {
6791
7922
  color: var(--kds-color-text-primary);
6792
7923
  font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace;
6793
7924
  font-weight: var(--kds-font-weight-medium);
@@ -6803,17 +7934,18 @@ button.kds-btn-success::after {
6803
7934
  flex: 1 1 auto;
6804
7935
  }
6805
7936
 
6806
- .kds-copyable-table-row .v::after {
7937
+ .kds-copyable-table-row .kds-value::after {
6807
7938
  content: 'content_copy';
6808
7939
  font-family: 'Material Symbols Outlined';
6809
7940
  font-size: var(--kds-font-size-sm);
6810
7941
  color: var(--kds-color-primary-main);
6811
7942
  opacity: 0;
6812
- transition: opacity 0.12s ease;
7943
+ /* Transición coordinada con la row */
7944
+ transition: opacity 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
6813
7945
  flex: 0 0 auto;
6814
7946
  }
6815
7947
 
6816
- .kds-copyable-table-row:hover .v::after {
7948
+ .kds-copyable-table-row:not(.copied):not(.settling):hover .kds-value::after {
6817
7949
  opacity: 0.7;
6818
7950
  }
6819
7951
 
@@ -6821,22 +7953,76 @@ button.kds-btn-success::after {
6821
7953
  background: var(--kds-color-success-soft);
6822
7954
  }
6823
7955
 
6824
- .kds-copyable-table-row.copied .v {
7956
+ .kds-copyable-table-row.copied .kds-value {
6825
7957
  color: var(--kds-color-success-dark);
6826
7958
  }
6827
7959
 
6828
- .kds-copyable-table-row.copied .v::after {
7960
+ .kds-copyable-table-row.copied .kds-value::after {
6829
7961
  content: 'check';
6830
7962
  font-family: 'Material Symbols Outlined';
6831
7963
  color: var(--kds-color-success-dark);
6832
7964
  opacity: 1;
6833
7965
  }
6834
7966
 
7967
+ /* `.settling`: estado de "recién copiado" — la row vuelve al normal sin
7968
+ activar hover. Dura lo mismo que la transición (~300ms) y se aplica
7969
+ desde React tras quitar `.copied`. */
7970
+ .kds-copyable-table-row.settling .kds-value::after {
7971
+ /* Mantén el check con opacity 0 mientras settling, fade out suave */
7972
+ opacity: 0;
7973
+ }
7974
+
6835
7975
  /* Disable BeerCSS ripple on copyable table rows */
6836
7976
  .kds-copyable-table-row::after {
6837
7977
  display: none;
6838
7978
  }
6839
7979
 
7980
+ /* ========================================
7981
+ GRID VARIANT (.kds-copyable-table--grid)
7982
+ Read-only grid of N text cells per row. No copy,
7983
+ no role=button, no "copy all". Cells share the row
7984
+ width equally. Used inside radio options, etc.
7985
+ ======================================== */
7986
+
7987
+ /* La grilla es solo columnas de texto (como el DataTable original): sin caja,
7988
+ borde, fondo ni sombra del contenedor copyable. */
7989
+ .kds-copyable-table--grid {
7990
+ border: 0;
7991
+ border-radius: 0;
7992
+ background: none;
7993
+ padding: 0;
7994
+ box-shadow: none;
7995
+ }
7996
+
7997
+ .kds-copyable-table-row--grid {
7998
+ display: flex;
7999
+ align-items: center;
8000
+ gap: var(--kds-spacing-1-5);
8001
+ padding: 0;
8002
+ /* Sin el divisor border-top que trae la fila base del copyable. */
8003
+ border-top: 0;
8004
+ cursor: default;
8005
+ user-select: text;
8006
+ }
8007
+
8008
+ /* Separación entre filas de la grilla (sin divisores). */
8009
+ .kds-copyable-table-row--grid + .kds-copyable-table-row--grid {
8010
+ margin-top: var(--kds-spacing-0-5);
8011
+ }
8012
+
8013
+ .kds-grid-cell {
8014
+ flex: 1 1 0;
8015
+ min-width: 0;
8016
+ font-size: var(--kds-font-size-sm);
8017
+ font-weight: var(--kds-font-weight-medium);
8018
+ color: var(--kds-color-text-primary);
8019
+ word-break: break-word;
8020
+ }
8021
+
8022
+ .kds-grid-cell--disabled {
8023
+ color: var(--kds-color-text-disabled);
8024
+ }
8025
+
6840
8026
  /* ========================================
6841
8027
  COPY-ALL BUTTON (.kds-copy-all-btn)
6842
8028
  Modifier for kds-btn kds-btn-outlined that adds
@@ -6894,6 +8080,14 @@ a.kds-copy-all-btn.copied:hover {
6894
8080
  min-height: unset;
6895
8081
  }
6896
8082
 
8083
+ /* Severity variants — default stays warning (backward-compatible) */
8084
+ .kds-section-note.kds-info { color: var(--kds-color-info-dark); }
8085
+ .kds-section-note.kds-info > i { color: var(--kds-color-info-dark); }
8086
+ .kds-section-note.kds-success { color: var(--kds-color-success-dark); }
8087
+ .kds-section-note.kds-success > i { color: var(--kds-color-success-dark); }
8088
+ .kds-section-note.kds-error { color: var(--kds-color-error-dark); }
8089
+ .kds-section-note.kds-error > i { color: var(--kds-color-error-dark); }
8090
+
6897
8091
  /* ========================================
6898
8092
  INFO TOOLTIP (.kds-info-tip)
6899
8093
  Inline info button with hover/click tooltip bubble.
@@ -6989,7 +8183,7 @@ a.kds-copy-all-btn.copied:hover {
6989
8183
  .kds-monto-row {
6990
8184
  display: flex;
6991
8185
  justify-content: space-between;
6992
- align-items: flex-end;
8186
+ align-items: flex-start;
6993
8187
  padding: var(--kds-spacing-1-75) 0 var(--kds-spacing-1);
6994
8188
  border-top: 1px dashed var(--kds-color-divider);
6995
8189
  margin-top: var(--kds-spacing-1-75);
@@ -7013,3 +8207,209 @@ a.kds-copy-all-btn.copied:hover {
7013
8207
  color: var(--kds-color-text-primary);
7014
8208
  }
7015
8209
 
8210
+ /* ========================================
8211
+ PAYMENT TOTAL (.kds-payment-total)
8212
+ Bloque "monto a pagar" para QR view y email templates.
8213
+ Portado desde paylink-ligopay → <kh:paymentTotal> + materialize-config.css.
8214
+ Variantes:
8215
+ - default: amount grande (3rem) en color primario Khipu.
8216
+ - email: amount compacto (1.5rem) en color texto primario.
8217
+ Responsive (default): mobile (≤ 1024px) centra y oculta titleMobile.
8218
+ ======================================== */
8219
+
8220
+ .kds-payment-total {
8221
+ /* Component-scoped tokens — overrideables sin tocar el CSS global. */
8222
+ --kds-payment-total-title-size: var(--kds-font-size-2xl); /* 24px */
8223
+ --kds-payment-total-label-size: var(--kds-font-size-xl); /* 20px */
8224
+ --kds-payment-amount-size: 3rem; /* 48px — destacado */
8225
+ --kds-payment-amount-line-height: var(--kds-line-height-snug);
8226
+ --kds-payment-amount-color: var(--kds-color-primary-main);
8227
+ --kds-payment-amount-weight: var(--kds-font-weight-medium);
8228
+
8229
+ text-align: left;
8230
+ }
8231
+
8232
+ .kds-payment-total-title,
8233
+ .kds-payment-total-title-mobile {
8234
+ font-size: var(--kds-payment-total-title-size);
8235
+ font-weight: var(--kds-font-weight-semibold);
8236
+ color: var(--kds-color-text-primary);
8237
+ margin-bottom: var(--kds-spacing-4);
8238
+ }
8239
+
8240
+ .kds-payment-total-title-mobile {
8241
+ display: none;
8242
+ }
8243
+
8244
+ .kds-payment-total .kds-payment-label {
8245
+ font-size: var(--kds-payment-total-label-size);
8246
+ font-weight: var(--kds-font-weight-semibold);
8247
+ color: var(--kds-color-text-primary);
8248
+ margin-bottom: var(--kds-spacing-1);
8249
+ padding-top: 0;
8250
+ }
8251
+
8252
+ .kds-payment-total .kds-payment-amount {
8253
+ font-size: var(--kds-payment-amount-size);
8254
+ font-weight: var(--kds-payment-amount-weight);
8255
+ line-height: var(--kds-payment-amount-line-height);
8256
+ color: var(--kds-payment-amount-color);
8257
+ margin-bottom: var(--kds-spacing-4);
8258
+ }
8259
+
8260
+ .kds-payment-total .kds-payment-amount .kds-payment-total-decimal-sup,
8261
+ .kds-invoice-amount .kds-payment-total-decimal-sup {
8262
+ font-size: var(--kds-font-size-decimal-sup);
8263
+ font-weight: var(--kds-font-weight-semibold);
8264
+ position: relative;
8265
+ top: 0;
8266
+ }
8267
+
8268
+ /* Variante email: amount compacto en color texto primario, alineación izquierda fija */
8269
+ .kds-payment-total--email {
8270
+ --kds-payment-amount-size: var(--kds-font-size-2xl); /* 24px */
8271
+ --kds-payment-amount-line-height: var(--kds-line-height-snug);
8272
+ --kds-payment-amount-color: var(--kds-color-text-primary);
8273
+ --kds-payment-amount-weight: var(--kds-font-weight-semibold);
8274
+ }
8275
+
8276
+ /* Tone modifier: swaps the amount color to the khipu-blue / LigoPay info blue.
8277
+ Combinable with --email or with the default variant. */
8278
+ .kds-payment-total--tone-info {
8279
+ --kds-payment-amount-color: var(--kds-color-info-blue); /* #5A5FE0 */
8280
+ }
8281
+
8282
+ /* Centered modifier: removes the asymmetric desktop padding (which exists for
8283
+ LigoPay QR view) and centers all internal text. Composable with --email and
8284
+ --tone-info. */
8285
+ .kds-payment-total--centered {
8286
+ padding-left: 0;
8287
+ padding-top: 0;
8288
+ text-align: center;
8289
+ }
8290
+ .kds-payment-total--centered .kds-payment-total-title,
8291
+ .kds-payment-total--centered .kds-payment-total-title-mobile,
8292
+ .kds-payment-total--centered .kds-payment-label,
8293
+ .kds-payment-total--centered .kds-payment-amount {
8294
+ text-align: center;
8295
+ }
8296
+
8297
+ /* Responsive: mobile ≤ 1024px — centra y conmuta los títulos (sólo variante default) */
8298
+ @media (max-width: 1024px) {
8299
+ .kds-payment-total {
8300
+ text-align: center;
8301
+ }
8302
+
8303
+ .kds-payment-total .kds-payment-total-title {
8304
+ display: none;
8305
+ }
8306
+
8307
+ .kds-payment-total .kds-payment-total-title-mobile {
8308
+ display: block;
8309
+ text-align: center;
8310
+ }
8311
+
8312
+ .kds-payment-total .kds-payment-label,
8313
+ .kds-payment-total .kds-payment-amount {
8314
+ text-align: center;
8315
+ }
8316
+
8317
+ /* Email mantiene alineación izquierda incluso en mobile (matchea email clients) */
8318
+ .kds-payment-total--email,
8319
+ .kds-payment-total--email .kds-payment-label,
8320
+ .kds-payment-total--email .kds-payment-amount {
8321
+ text-align: left;
8322
+ }
8323
+ }
8324
+
8325
+ /* ==========================================================================
8326
+ GRID UTILITIES — semantic aliases for BeerCSS .grid / .s* / .m* / .l*
8327
+
8328
+ Estas utilidades son ALIAS de las clases cortas de BeerCSS para que
8329
+ consumers (payment, LigoPay, Grails legacy) puedan escribir markup con
8330
+ nombres descriptivos sin renunciar al grid de 12 columnas de BeerCSS.
8331
+
8332
+ Convención (estilo Bootstrap responsive):
8333
+ - kds-grid → .grid (12-col CSS grid + 1rem gap)
8334
+ - kds-col-sm-{1..12} → .s{1..12} (mobile, siempre aplica)
8335
+ - kds-col-md-{1..12} → .m{1..12} (>=601px)
8336
+ - kds-col-lg-{1..12} → .l{1..12} (>=993px)
8337
+
8338
+ Las definiciones originales de BeerCSS NO se tocan; estas son aditivas.
8339
+ ========================================================================== */
8340
+
8341
+ .kds-grid {
8342
+ --_gap: 1rem;
8343
+ display: grid;
8344
+ grid-template-columns: repeat(12, 1fr);
8345
+ gap: var(--_gap);
8346
+ block-size: auto;
8347
+ }
8348
+
8349
+ .kds-grid.kds-grid-no-space { --_gap: 0rem; }
8350
+ .kds-grid.kds-grid-medium-space { --_gap: 1.5rem; }
8351
+ .kds-grid.kds-grid-large-space { --_gap: 2rem; }
8352
+
8353
+ .kds-grid > * { margin: 0; }
8354
+
8355
+ .kds-col-sm-1 { grid-area: auto/span 1; }
8356
+ .kds-col-sm-2 { grid-area: auto/span 2; }
8357
+ .kds-col-sm-3 { grid-area: auto/span 3; }
8358
+ .kds-col-sm-4 { grid-area: auto/span 4; }
8359
+ .kds-col-sm-5 { grid-area: auto/span 5; }
8360
+ .kds-col-sm-6 { grid-area: auto/span 6; }
8361
+ .kds-col-sm-7 { grid-area: auto/span 7; }
8362
+ .kds-col-sm-8 { grid-area: auto/span 8; }
8363
+ .kds-col-sm-9 { grid-area: auto/span 9; }
8364
+ .kds-col-sm-10 { grid-area: auto/span 10; }
8365
+ .kds-col-sm-11 { grid-area: auto/span 11; }
8366
+ .kds-col-sm-12 { grid-area: auto/span 12; }
8367
+
8368
+ @media only screen and (min-width: 601px) {
8369
+ .kds-col-md-1 { grid-area: auto/span 1; }
8370
+ .kds-col-md-2 { grid-area: auto/span 2; }
8371
+ .kds-col-md-3 { grid-area: auto/span 3; }
8372
+ .kds-col-md-4 { grid-area: auto/span 4; }
8373
+ .kds-col-md-5 { grid-area: auto/span 5; }
8374
+ .kds-col-md-6 { grid-area: auto/span 6; }
8375
+ .kds-col-md-7 { grid-area: auto/span 7; }
8376
+ .kds-col-md-8 { grid-area: auto/span 8; }
8377
+ .kds-col-md-9 { grid-area: auto/span 9; }
8378
+ .kds-col-md-10 { grid-area: auto/span 10; }
8379
+ .kds-col-md-11 { grid-area: auto/span 11; }
8380
+ .kds-col-md-12 { grid-area: auto/span 12; }
8381
+ }
8382
+
8383
+ @media only screen and (min-width: 993px) {
8384
+ .kds-col-lg-1 { grid-area: auto/span 1; }
8385
+ .kds-col-lg-2 { grid-area: auto/span 2; }
8386
+ .kds-col-lg-3 { grid-area: auto/span 3; }
8387
+ .kds-col-lg-4 { grid-area: auto/span 4; }
8388
+ .kds-col-lg-5 { grid-area: auto/span 5; }
8389
+ .kds-col-lg-6 { grid-area: auto/span 6; }
8390
+ .kds-col-lg-7 { grid-area: auto/span 7; }
8391
+ .kds-col-lg-8 { grid-area: auto/span 8; }
8392
+ .kds-col-lg-9 { grid-area: auto/span 9; }
8393
+ .kds-col-lg-10 { grid-area: auto/span 10; }
8394
+ .kds-col-lg-11 { grid-area: auto/span 11; }
8395
+ .kds-col-lg-12 { grid-area: auto/span 12; }
8396
+ }
8397
+
8398
+ /* ========================================
8399
+ Resets de composición (layouts con gap)
8400
+ ======================================== */
8401
+
8402
+ /* Anulan el espaciado propio del elemento cuando el layout lo gestiona el
8403
+ contenedor (flex + gap). "margin" en el nombre es deliberado: BeerCSS exime
8404
+ a [class*='margin'] de su regla de auto-espaciado entre hermanos
8405
+ (margin-block-start: 1rem), que de otro modo gana por especificidad.
8406
+ Van al final del archivo para ganar los empates de especificidad. */
8407
+ .kds-margin-0 {
8408
+ margin: 0;
8409
+ }
8410
+
8411
+ .kds-py-0 {
8412
+ padding-top: 0;
8413
+ padding-bottom: 0;
8414
+ }
8415
+