
/* Cualquier clase de aquí en adelante que ponga su propio "display"
   (como .field{display:grid}) gana por defecto al estilo nativo del
   atributo hidden del navegador, aunque tengan la misma especificidad —
   así que un elemento con class="field ..." hidden se seguía viendo tal
   cual, como si "hidden" no existiera. Esta regla lo deja sin ambigüedad
   en toda la app. */
[hidden]{display:none !important}
:root{
  --bg:#f4f6f3; --surface:#fff; --surface-2:#f7f9f6; --ink:#111713; --muted:#68726b;
  --line:#e2e8e3; --dark:#0b0f0d; --dark-2:#131a16; --green:#25cf79; --green-2:#9bf5c6;
  --green-dark:#0f6f43; --green-soft:#e9fbf1; --cream:#f5efe4; --warning:#f5bd63; --danger:#e85b5b;
  --shadow:0 16px 50px rgba(19,33,25,.08); --shadow-sm:0 8px 24px rgba(19,33,25,.06);
  --radius:20px; --radius-sm:14px; --sidebar:264px;
}
*{box-sizing:border-box} html{scroll-behavior:smooth} body{margin:0;background:var(--bg);color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none} button,input,textarea,select{font:inherit} button{cursor:pointer}
/* Veil de carga: la página estática lleva contenido de ejemplo/placeholder hasta que
   el JS trae los datos reales del negocio (logo, colores, cifras...). Sin esto se ve
   un parpadeo del contenido de ejemplo antes de que aparezca el de verdad. Se oculta
   el contenido y se muestra un spinner hasta que app.js añade la clase rf-ready. */
body:not(.rf-ready) .content{min-height:70vh;position:relative}
body:not(.rf-ready) .content>*{opacity:0}
body:not(.rf-ready) .content::after{content:"";position:absolute;top:180px;left:50%;width:34px;height:34px;margin-left:-17px;border-radius:50%;border:3px solid var(--line);border-top-color:var(--green);animation:rf-spin .75s linear infinite}
body.rf-ready .content>*{opacity:1;transition:opacity .28s ease-out}
@keyframes rf-spin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){body:not(.rf-ready) .content::after{animation:none}}
.app{min-height:100vh}.sidebar{position:fixed;inset:0 auto 0 0;width:var(--sidebar);background:linear-gradient(180deg,#0a0f0c 0%,#101812 100%);color:#fff;padding:24px 18px;display:flex;flex-direction:column;z-index:50;border-right:1px solid rgba(255,255,255,.06)}
.brand{height:58px;display:flex;align-items:center;padding:0 8px;margin-bottom:24px}.brand img{width:160px;max-height:48px;object-fit:contain;object-position:left center}
.nav{display:grid;gap:6px}.nav-label{font-size:11px;text-transform:uppercase;letter-spacing:.14em;color:#748078;padding:10px 12px 6px}.nav a{display:flex;gap:12px;align-items:center;min-height:46px;padding:0 12px;border-radius:12px;color:#c2cbc5;font-weight:650;font-size:14px;transition:.2s}.nav a:hover{background:rgba(255,255,255,.06);color:#fff}.nav a.active{background:linear-gradient(90deg,rgba(37,207,121,.18),rgba(37,207,121,.06));color:#dffbed;border:1px solid rgba(37,207,121,.18)}.nav svg{width:18px;height:18px;flex:0 0 18px}.nav a.active svg{color:var(--green)}
.sidebar-foot{margin-top:auto;padding:14px 8px 2px}.shop-mini{border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.04);border-radius:15px;padding:12px}.shop-mini strong{display:block;font-size:13px}.shop-mini span{font-size:12px;color:#849087}.privacy-pill{margin-top:14px;display:flex;align-items:center;gap:8px;color:#8d9991;font-size:11px}.privacy-dot{width:7px;height:7px;border-radius:999px;background:var(--green);box-shadow:0 0 0 4px rgba(37,207,121,.12)}
.main{margin-left:var(--sidebar);min-height:100vh}.topbar{height:76px;position:sticky;top:0;z-index:30;background:rgba(244,246,243,.85);backdrop-filter:blur(16px);display:flex;align-items:center;justify-content:space-between;padding:0 34px;border-bottom:1px solid rgba(214,223,216,.8)}.top-title{font-size:14px;color:var(--muted)}.top-title strong{color:var(--ink)}.top-actions{display:flex;gap:10px;align-items:center}.avatar{width:38px;height:38px;border-radius:12px;background:linear-gradient(135deg,var(--green-2),var(--green));display:grid;place-items:center;font-weight:800;color:#0b4f31;border:1px solid rgba(15,111,67,.18)}
.content{max-width:1480px;margin:0 auto;padding:30px 34px 54px}.page-head{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:24px}.eyebrow{display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--green-dark);margin-bottom:8px}.eyebrow:before{content:"";width:18px;height:2px;background:var(--green);border-radius:999px}.page-head h1{font-size:32px;line-height:1.1;letter-spacing:-.03em;margin:0 0 6px}.page-head p{margin:0;color:var(--muted);max-width:760px;font-size:14px;line-height:1.55}.head-actions{display:flex;gap:10px;flex-wrap:wrap}.btn{border:0;border-radius:13px;min-height:44px;padding:0 16px;font-weight:760;display:inline-flex;align-items:center;gap:9px;justify-content:center;transition:.18s}.btn:hover{transform:translateY(-1px)}.btn svg{width:17px;height:17px}
/* Antes no había ningún estilo para :disabled, así que un botón desactivado
   por JS (en algún guardado en curso, o en "Oportunidades" cuando el
   recuento es 0 — ver renderOportunidades en app.js) se veía IDÉNTICO a uno
   activo, solo que dejaba de responder al clic: nada avisaba con la vista
   de que no había nadie detrás de ese botón. */
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
.btn:disabled:hover{transform:none}.btn-primary{background:linear-gradient(135deg,#23cb75,#31dd86);color:#062b19;box-shadow:0 9px 22px rgba(37,207,121,.2)}.btn-dark{background:var(--dark);color:#fff}.btn-light{background:#fff;border:1px solid var(--line);color:var(--ink)}.btn-soft{background:var(--green-soft);color:var(--green-dark)}.btn-danger{background:#fff1f1;color:#a33131;border:1px solid #f4d3d3}.btn-sm{min-height:36px;padding:0 12px;border-radius:10px;font-size:13px}.icon-btn{width:42px;height:42px;border-radius:12px;border:1px solid var(--line);background:#fff;display:grid;place-items:center}.icon-btn svg{width:18px;height:18px}
.grid{display:grid;gap:16px}.metrics{grid-template-columns:repeat(4,minmax(0,1fr));margin-bottom:16px}.metric{background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:18px;min-height:138px;box-shadow:var(--shadow-sm);position:relative;overflow:hidden}
/* Tarjeta de métrica que, encima, lleva a una vista filtrada relacionada
   (p.ej. "Premios listos para canjear" -> Clientes filtrado a "Premio
   listo") — antes el número no llevaba a ningún sitio, así que no había
   forma de saber DE QUIÉN eran esos premios listos sin ir mirando la lista
   entera a mano. */
.metric-clickable{cursor:pointer;transition:.18s}
.metric-clickable:hover{border-color:#b7dcc7;box-shadow:var(--shadow)}
.metric-clickable:focus-visible{outline:2px solid var(--green);outline-offset:2px}.metric:after{content:"";position:absolute;width:80px;height:80px;border-radius:50%;right:-30px;top:-30px;background:radial-gradient(circle,rgba(37,207,121,.09),transparent 68%)}.metric-top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}.metric-icon{width:38px;height:38px;border-radius:11px;background:var(--green-soft);color:var(--green-dark);display:grid;place-items:center}.metric-icon svg{width:18px;height:18px}.metric .label{font-size:12px;color:var(--muted);font-weight:700;margin-top:13px}.metric .value{font-size:30px;font-weight:850;letter-spacing:-.04em;margin-top:3px}.delta{font-size:11px;font-weight:750;border-radius:999px;padding:5px 8px;background:var(--green-soft);color:var(--green-dark);white-space:nowrap}.delta.neutral{background:#f1f3f1;color:#66716a}.delta.warn{background:#fff6e8;color:#925d08}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-sm);padding:20px}.card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:18px}.card-head h2,.card-head h3{margin:0;letter-spacing:-.02em}.card-head h2{font-size:18px}.card-head h3{font-size:16px}.card-head p{margin:4px 0 0;color:var(--muted);font-size:12px;line-height:1.45}.card-head>span[data-icon]{color:var(--muted);flex:0 0 auto}.card-head>span[data-icon] svg{width:18px;height:18px;display:block}.two-col{grid-template-columns:minmax(0,1.6fr) minmax(300px,.9fr)}.three-col{grid-template-columns:repeat(3,minmax(0,1fr))}.span-2{grid-column:span 2}.span-3{grid-column:span 3}
.chart-wrap{height:252px;position:relative}.line-chart{width:100%;height:100%;overflow:visible}.chart-grid line{stroke:#e8ede9;stroke-width:1}.chart-area{fill:url(#areaGrad)}.chart-line{fill:none;stroke:#21c975;stroke-width:4;stroke-linecap:round;stroke-linejoin:round}.chart-dot{fill:#fff;stroke:#21c975;stroke-width:3}.axis{display:flex;justify-content:space-between;color:#8b958e;font-size:11px;padding:0 5px;margin-top:-6px}.legend{display:flex;gap:14px;align-items:center;font-size:11px;color:var(--muted)}.legend span{display:flex;align-items:center;gap:6px}.legend i{width:8px;height:8px;border-radius:99px;background:var(--green)}
/* Selector de semana de "Actividad conocida" (panel.html): antes eran 3
   botones .btn sueltos pegados unos a otros (una flecha, una píldora con
   fondo, otra flecha) que con los otros botones de al lado quedaban
   apretados y sin ningún criterio visual en común — de ahí que "se viera
   fatal". Ahora es un grupo minimalista (dos flechas de icono + una
   etiqueta central) y, al pulsar la etiqueta, se abre un calendario real de
   verdad: pulsando cualquier día se selecciona ESA SEMANA entera (la fila
   completa se resalta), no solo ese día suelto. */
.week-nav{display:flex;align-items:center;gap:2px}
.week-nav-arrow{width:26px;height:26px;border:0;background:transparent;border-radius:8px;display:grid;place-items:center;color:var(--ink);font-size:15px;line-height:1;cursor:pointer;transition:.15s}
.week-nav-arrow:hover{background:var(--surface-2)}
.week-nav-arrow:disabled{opacity:.3;cursor:not-allowed;background:none}
.week-nav-label{border:0;background:var(--surface-2);color:var(--ink);font:inherit;font-weight:800;font-size:11px;padding:6px 12px;border-radius:999px;cursor:pointer;white-space:nowrap}
.week-nav-label:hover{background:#e7ece8}
.week-picker{position:absolute;top:calc(100% + 8px);right:0;z-index:20;background:var(--surface);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);padding:14px;width:264px}
.week-picker[hidden]{display:none}
.week-picker-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.week-picker-head strong{font-size:12.5px;text-transform:capitalize;font-weight:800}
.wp-nav-btn{width:24px;height:24px;border:0;background:var(--surface-2);border-radius:8px;display:grid;place-items:center;cursor:pointer;color:var(--ink);font-size:13px;line-height:1}
.wp-nav-btn:hover{background:#e7ece8}
.wp-dow{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;font-size:9.5px;font-weight:800;color:var(--muted);margin-bottom:4px}
.wp-row{display:grid;grid-template-columns:repeat(7,1fr);border-radius:10px;transition:background .1s}
.wp-row.wp-row-sel{background:var(--green-soft)}
.wp-row.wp-row-hover:not(.wp-row-sel){background:#eef1ee}
.wp-day{border:0;background:transparent;font:inherit;font-size:11.5px;padding:6px 0;cursor:pointer;color:var(--ink);border-radius:8px}
.wp-day:hover:not(:disabled){background:rgba(0,0,0,.06)}
.wp-day.wp-otro-mes{color:#c3cac5}
.wp-day.wp-hoy{box-shadow:inset 0 0 0 1.5px var(--green);font-weight:800}
.wp-day:disabled{opacity:.3;cursor:not-allowed}
@media(max-width:520px){.week-picker{right:auto;left:50%;transform:translateX(-50%)}}
.opportunities{display:grid;gap:10px}.opp{border:1px solid var(--line);border-radius:14px;padding:13px 14px;display:flex;gap:11px;align-items:flex-start;background:#fbfcfb}.opp-icon{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;flex:0 0 34px;background:var(--green-soft);color:var(--green-dark)}.opp-icon svg{width:18px;height:18px}.opp-icon.warn{background:#fff5e4;color:#9a6000}.opp h4{margin:1px 0 3px;font-size:13px}.opp p{margin:0;color:var(--muted);font-size:11px;line-height:1.45}.opp button{margin-left:auto;align-self:center;white-space:nowrap}
.donut-box{display:flex;align-items:center;gap:24px;min-height:210px}.donut{width:150px;height:150px;border-radius:50%;background:conic-gradient(#25cf79 0 42%,#8cebb8 42% 69%,#dce5df 69% 87%,#f1c36c 87% 100%);position:relative;flex:0 0 150px}.donut:after{content:"";position:absolute;inset:27px;border-radius:50%;background:#fff}.donut-center{position:absolute;inset:0;z-index:2;display:grid;place-content:center;text-align:center}.donut-center strong{font-size:25px}.donut-center span{font-size:10px;color:var(--muted)}.segment-list{display:grid;gap:10px;flex:1}.segment{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px}.seg-left{display:flex;align-items:center;gap:8px}.seg-dot{width:9px;height:9px;border-radius:99px}.seg-dot.a{background:#25cf79}.seg-dot.b{background:#8cebb8}.seg-dot.c{background:#dce5df}.seg-dot.d{background:#f1c36c}.segment strong{font-size:12px}
.heat{display:grid;grid-template-columns:60px repeat(7,1fr);gap:6px;align-items:center;font-size:10px;color:#77817a}.heat .day{text-align:center;font-weight:700}.heat .time{text-align:right;padding-right:4px}.heat-cell{height:28px;border-radius:7px;background:rgba(37,207,121,var(--v,.15));border:1px solid rgba(17,92,55,.035)}
.table-wrap{overflow:auto;border:1px solid var(--line);border-radius:15px}.table{width:100%;border-collapse:collapse;min-width:760px}.table th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:#87918a;background:#f7f9f7;padding:12px 14px}.table td{padding:13px 14px;border-top:1px solid var(--line);font-size:12px}.client-cell{display:flex;align-items:center;gap:10px}.client-avatar{width:34px;height:34px;border-radius:10px;background:#eef3ef;color:#3f4b43;display:grid;place-items:center;font-weight:800}.status{display:inline-flex;padding:5px 9px;border-radius:99px;font-size:10px;font-weight:800}.status.good{background:var(--green-soft);color:var(--green-dark)}.status.warn{background:#fff4de;color:#8a5706}.status.muted{background:#f0f2f0;color:#647069}.progress{height:7px;background:#edf1ee;border-radius:999px;overflow:hidden;min-width:90px}.progress i{display:block;height:100%;background:linear-gradient(90deg,var(--green-2),var(--green));border-radius:inherit}
.quick-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.quick{border:1px solid var(--line);background:#fff;border-radius:15px;padding:14px;text-align:left;transition:.18s}.quick:hover{border-color:#b7dcc7;box-shadow:var(--shadow-sm);transform:translateY(-1px)}.quick svg{width:20px;height:20px;color:var(--green-dark)}.quick strong{display:block;font-size:12px;margin-top:9px}.quick span{font-size:10px;color:var(--muted)}
.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.field{display:grid;gap:7px}.field.full{grid-column:1/-1}.field label{font-size:11px;font-weight:800;color:#4e5a52}.input,.textarea,.select{width:100%;border:1px solid var(--line);background:#fff;border-radius:12px;min-height:44px;padding:10px 12px;color:var(--ink);outline:none;transition:.2s}.textarea{min-height:112px;resize:vertical}.input:focus,.textarea:focus,.select:focus{border-color:#8dd8ae;box-shadow:0 0 0 4px rgba(37,207,121,.08)}.help{font-size:10px;color:#8b958e;line-height:1.4}.switch-row{display:flex;justify-content:space-between;gap:16px;align-items:center;border:1px solid var(--line);border-radius:13px;padding:12px 13px}.switch-row strong{font-size:12px}.switch-row p{font-size:10px;color:var(--muted);margin:3px 0 0}.switch{width:40px;height:24px;border-radius:99px;background:#dde3df;position:relative;transition:.2s}.switch:before{content:"";position:absolute;width:18px;height:18px;background:#fff;border-radius:50%;top:3px;left:3px;box-shadow:0 2px 6px rgba(0,0,0,.15);transition:.2s}.switch.on{background:var(--green)}.switch.on:before{left:19px}
.notice-composer{background:linear-gradient(145deg,#0d1510,#17231b);color:#fff;border:none;position:relative;overflow:hidden}.notice-composer:after{content:"";position:absolute;width:320px;height:320px;border-radius:50%;right:-150px;top:-160px;background:radial-gradient(circle,rgba(37,207,121,.26),transparent 66%)}.notice-composer .card-head p{color:#93a098}.notice-composer .textarea{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.1);color:#fff;position:relative;z-index:2}.notice-composer .textarea::placeholder{color:#738078}.notice-composer .counter{color:#839088;font-size:10px;text-align:right;margin-top:6px;position:relative;z-index:2}
.phone{width:250px;height:505px;border-radius:38px;padding:10px;background:#0b0c0c;box-shadow:0 24px 80px rgba(7,14,10,.22);margin:auto}.screen{height:100%;border-radius:30px;background:linear-gradient(160deg,#eef5f0,#dbe5de);padding:34px 14px 14px;overflow:hidden}.lock-time{text-align:center;font-size:42px;font-weight:700;letter-spacing:-.04em;color:#1c2620}.lock-date{text-align:center;font-size:11px;color:#526058;margin-bottom:40px}.push{background:rgba(255,255,255,.86);backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,.9);border-radius:18px;padding:12px;box-shadow:0 14px 30px rgba(37,55,44,.12)}.push-head{display:flex;gap:8px;align-items:center}.push-icon{width:27px;height:27px;border-radius:8px;background:#0b1510;display:grid;place-items:center}.push-icon img{width:20px;height:20px;object-fit:contain}.push-head strong{font-size:10px}.push-head span{margin-left:auto;font-size:8px;color:#7c8780}.push p{font-size:10px;line-height:1.4;margin:8px 0 0;color:#36423a}
.qr-layout{grid-template-columns:minmax(0,1fr) 400px;align-items:start}.qr-card{text-align:center;padding:32px}.qr-frame{width:min(340px,100%);aspect-ratio:1;margin:6px auto 18px;border-radius:26px;background:#fff;border:1px solid var(--line);display:grid;place-items:center;box-shadow:inset 0 0 0 10px #fafbfa}.qr-frame img{width:82%;height:82%;object-fit:contain}.code-pill{display:inline-flex;align-items:center;gap:8px;background:#f2f6f3;border:1px solid var(--line);border-radius:999px;padding:7px 12px;font-size:11px;color:var(--muted)}
.scanner-shell{max-width:720px;margin:auto}.scanner{background:#050806;border-radius:26px;overflow:hidden;aspect-ratio:4/3;position:relative;display:grid;place-items:center;box-shadow:var(--shadow)}.scanner video{position:absolute;width:100%;height:100%;object-fit:cover}.scanner-overlay{position:absolute;inset:0;background:radial-gradient(circle at center,transparent 0 26%,rgba(0,0,0,.58) 60%)}.scan-box{width:48%;aspect-ratio:1;border:2px solid rgba(255,255,255,.9);border-radius:24px;position:relative;z-index:2}.scan-box:before,.scan-box:after{content:"";position:absolute;left:8%;right:8%;height:2px;background:linear-gradient(90deg,transparent,var(--green),transparent);box-shadow:0 0 14px var(--green)}.scan-box:before{top:30%;animation:scan 2.4s linear infinite}.scan-box:after{display:none}@keyframes scan{0%{top:14%}50%{top:84%}100%{top:14%}}.scanner-empty{position:relative;z-index:3;color:#d8e0db;text-align:center}.scanner-empty svg{width:34px;height:34px;margin-bottom:8px;color:var(--green)}.scanner-empty strong{display:block;font-size:14px}.scanner-empty span{font-size:11px;color:#7e8b83}
.scanner-empty.ok{background:rgba(6,12,9,.72);border-radius:22px;padding:22px 18px;backdrop-filter:blur(3px)}.scanner-empty.ok svg{width:54px;height:54px;margin-bottom:12px;color:var(--green);filter:drop-shadow(0 0 14px rgba(53,219,131,.55))}.scanner-empty.ok strong{font-size:23px;margin-bottom:5px}.scanner-empty.ok span{font-size:13.5px;color:#c3ccc6}
/* Mientras se ve el resultado (sello añadido / premio pendiente) se oculta
   la guía de encuadre (el cuadrado blanco con la línea animada) — antes se
   quedaba siempre visible, superpuesta justo encima del check y el texto,
   dando la sensación de que el aviso salía "cortado" o amontonado con el
   recuadro de apuntar. También se oscurece un poco más el fondo para que
   el aviso destaque con más claridad sobre la imagen de la cámara. */
.scanner.resultado .scan-box{display:none}
.scanner.resultado .scanner-overlay{background:rgba(5,8,6,.66)}
.scanner.flash-ok{animation:scannerFlashOk .7s ease}@keyframes scannerFlashOk{0%{box-shadow:inset 0 0 0 0 rgba(53,219,131,0)}25%{box-shadow:inset 0 0 0 10px rgba(53,219,131,.85)}100%{box-shadow:inset 0 0 0 0 rgba(53,219,131,0)}}.scanner-actions{display:flex;gap:10px;justify-content:center;margin-top:14px;flex-wrap:wrap}
#qrReaderReal video{width:100%!important;height:100%!important;object-fit:cover;display:block!important}
#qrReaderReal{background:#050806;position:absolute!important;inset:0!important;width:100%!important;height:100%!important}
.qr-frame canvas{width:82%;height:82%}
.loyalty-preview{background:linear-gradient(150deg,#eef3ee,#e2eae4);border:1px solid var(--line);border-radius:24px;padding:26px;position:relative;overflow:hidden;min-height:260px}.loyalty-preview:before{content:"";position:absolute;width:240px;height:240px;right:-90px;top:-100px;background:radial-gradient(circle,rgba(37,207,121,.14),transparent 65%)}.preview-label{position:relative;z-index:2;margin:0 0 16px;text-align:center;font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--green-dark)}
.wallet-card{position:relative;z-index:2;max-width:360px;margin:auto;border-radius:20px;overflow:hidden;background:var(--wallet-accent,#D9782D);color:var(--wallet-ink,#fff);box-shadow:0 24px 50px rgba(0,0,0,.24);padding:20px 22px 22px}
/* Marca y título van CENTRADOS en una tarjeta real de Google Wallet, no a
   la izquierda — se ve clarísimo en la foto real que mandó el negocio. */
.wallet-top{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:16px}
.wallet-brand{display:flex;align-items:center;gap:9px;min-width:0}.wallet-brand img{width:26px;height:26px;object-fit:contain;border-radius:7px;background:rgba(255,255,255,.18);flex:0 0 26px}.wallet-brand span{font-size:11px;font-weight:700;opacity:.85}
/* "Logo ancho de cabecera" en Google: Wallet de verdad lo usa para
   SUSTITUIR el logo redondo + nombre de arriba por el logo entero, sin
   máscara circular (ver wideProgramLogo en src/wallet/googleWallet.js) —
   antes el panel ignoraba esto y siempre enseñaba el logo redondo con
   "rondafy" al lado, aunque el negocio hubiera subido uno ancho. Mismo
   criterio que ya seguía Apple (.apple-header img) — nada de máscara ni
   fondo, solo el logo tal cual, más grande que el redondo de 26px.  */
.wallet-top.has-wide-logo .wallet-brand img{width:auto;height:34px;max-width:220px;border-radius:0;background:none;flex:0 0 auto}
.wallet-top.has-wide-logo .wallet-brand span{display:none}
.wallet-title{font-size:19px;font-weight:800;letter-spacing:-.01em;line-height:1.2;overflow-wrap:break-word;margin-bottom:16px;text-align:center}
/* El QR + su texto + "Sellos X/Y" viven dentro de este panel propio — en la
   tarjeta real no es un color plano seguido, es un módulo con las esquinas
   redondeadas y un verde ligeramente más claro (una superposición blanca
   suave), separado de lo que hay debajo (el subtítulo, los sellos y el
   premio, que sí van sobre el verde normal de la tarjeta). Antes esto
   estaba resuelto con un <div class="divider">, pero en la foto real esa
   "línea" es en realidad el borde redondeado de este panel. */
.wallet-module{background:rgba(255,255,255,.09);border-radius:18px;padding:20px 14px 16px;margin-bottom:18px}
/* El subtítulo (wallet-tagline) va aquí, pequeño, justo encima de los
   sellos — no debajo del título como antes. */
.wallet-tagline{font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--wallet-tagline-color,inherit);opacity:.85;margin-bottom:14px;text-align:center}
.wallet-points{margin-bottom:0;text-align:center}.wallet-points span{font-size:9.5px;opacity:.75;text-transform:uppercase;letter-spacing:.05em}.wallet-points strong{display:block;font-size:17px;font-weight:800;letter-spacing:-.01em;margin-top:1px}
.wallet-barcode{display:flex;flex-direction:column;align-items:center;gap:9px;margin-bottom:14px}
.wallet-qr-box{background:#fff;border-radius:14px;padding:10px;line-height:0}
.wallet-qr-box canvas{width:104px!important;height:104px!important;display:block}
.wallet-barcode span{font-size:9.5px;opacity:.8;text-align:center}
.divider{height:1px;background:currentColor;opacity:.28;margin:18px 0}
/* aspect-ratio:1032/336 (la misma proporción que la imagen real, ver más
   abajo "Vista previa REAL") va en la regla base, no solo en has-libre —
   así la imitación en CSS SIEMPRE mide lo mismo que la imagen real, tenga
   foto o no. Antes solo has-libre/showing-real la llevaban: en cuanto
   se enseñaba esta imitación (al entrar al panel un instante, o si fallaba
   la petición al servidor) con una altura calculada por el contenido en
   vez de esta proporción fija, la tarjeta entera se agrandaba o encogía de
   golpe al pasar de una a otra — justo el "salto" que se veía en cada
   cambio. Con la misma proporción siempre, pasar de una a otra ya no mueve
   ni un pixel el tamaño de la tarjeta, solo lo que hay dibujado dentro. */
.wallet-hero{border-radius:14px;transition:background .2s;position:relative;
  aspect-ratio:1032/336;overflow:hidden;margin:0 -10px;padding:0;
  display:flex;flex-direction:column;justify-content:center;gap:6px}
/* background-position/background-size se ponen en línea desde app.js
   (renderWalletPreview) según card_photo_position — así la vista previa
   se mueve de verdad al elegir izquierda/centro/derecha, en vez de quedar
   fija aquí en CSS. background-size:cover es la excepción: para el modo
   "foto normal" siempre se quiere llenar toda la tira. */
.wallet-hero.has-photo{background-size:cover}
.wallet-hero.has-photo .stamp-row,.wallet-hero.has-photo .reward{position:relative;z-index:1}
.wallet-hero.has-photo:after{content:"";position:absolute;inset:0;border-radius:14px;background:linear-gradient(180deg,rgba(0,0,0,.32),rgba(0,0,0,.4) 55%,rgba(0,0,0,.62));z-index:0}
/* Foto(s) con el fondo ya quitado (o "quitar fondo blanco" marcado): en vez
   de cubrir toda la tira, se apoyan como una pegatina — igual que hace
   fondoConRecorte() en src/wallet/stampCardImage.js — sin velo oscuro,
   porque el resto de la tira sigue siendo el color de marca de siempre.
   Puede haber dos capas (dos fondos separados por comas) cuando el negocio
   tiene una 2ª foto puesta. */
.wallet-hero.has-cutout{background-repeat:no-repeat}
/* "Fondo libre" (card_bg_url, ver programa.html): la imagen del negocio ya
   viene diseñada entera, así que se estira a toda la tira SIN el velo oscuro
   de .has-photo (ese velo es para que el texto blanco de siempre se lea
   sobre una foto cualquiera; aquí el negocio ya ha pensado el contraste él
   mismo). Los sellos y el premio se sacan del flujo normal (position:
   absolute) porque su sitio ya no lo decide este CSS sino dónde los haya
   arrastrado el negocio (ver configuraArrastreLibre en app.js). */
/* margin: se deja SIN poner aqui a proposito (antes tenia margin:0) -- la
   base de .wallet-hero (mas arriba) ya pone margin:0 -10px, y como el alto
   sale de aspect-ratio a partir del ANCHO, si este modo tuviera un margen
   distinto al de los demas (showing-real incluido) el ancho cambiaria al
   entrar/salir de este modo y con el el alto -- el mismo "salto" que ya se
   arreglo para has-photo/clasico. Todos los modos deben compartir el mismo
   margen para que el tamano de la tira nunca cambie al pasar de uno a otro. */
.wallet-hero.has-libre{background-size:cover;background-position:center;padding:0;aspect-ratio:1032/336;overflow:hidden}
.wallet-hero.has-libre .stamp-row,.wallet-hero.has-libre .reward{position:absolute;transform:translate(-50%,-50%);margin:0}
/* z-index EXPLÍCITO (antes ninguno de los dos lo llevaba, y "ganaba" el
   .reward solo porque va después en el HTML) -- el texto del premio tiene
   que leerse siempre por encima de los círculos de sello si el negocio los
   arrastra hasta que se toquen, nunca al revés; dejarlo al orden del HTML
   es frágil (ver el mismo fallo, ya de verdad, en #appleLibrePremio más
   abajo -- ahí SÍ se veía el texto tapado por los sellos). */
.wallet-hero.has-libre .stamp-row{z-index:1}
.wallet-hero.has-libre .reward{z-index:2}
.wallet-hero.has-libre .reward[hidden]{display:none}
.libre-draggable.arrastrando{cursor:grabbing;outline:2px dashed rgba(255,255,255,.9);outline-offset:4px;border-radius:10px}
.wallet-hero.has-libre .libre-draggable,.apple-strip.has-libre .libre-draggable{cursor:grab;touch-action:none}
/* Arrastrar la propia foto (fuera de los sellos/premio) para elegir qué
   parte se recorta — ver configuraArrastreLibre() en app.js. touch-action
   none: sin esto, arrastrar con el dedo en móvil hace scroll de la página en
   vez de mover la foto. */
.wallet-hero.has-libre,.apple-strip.has-libre{cursor:move;touch-action:none}
.wallet-hero.has-libre.arrastrando-foto,.apple-strip.has-libre.arrastrando-foto{outline:2px dashed rgba(255,255,255,.9);outline-offset:-4px}
.stamp-row{display:flex;gap:9px;flex-wrap:wrap;justify-content:center;max-width:320px;margin:0 auto}
/* width/height: var(--stamp-size), puesta en linea desde app.js
   (renderWalletPreview) para que el circulo mida EXACTAMENTE lo mismo que
   en la imagen real (mismo calculo que calculaCuadricula() en
   src/wallet/stampCardImage.js, escalado al ancho real de la tira en
   pantalla) -- 42px es solo el valor de respaldo por si --stamp-size no
   llegara a ponerse (p.ej. en modo "fondo libre", donde no hay imagen real
   con la que igualar el tamano). Antes este 42px era fijo siempre, sin
   importar cuantos sellos hubiera ni el ancho real -- por eso el circulo
   cambiaba de tamano de golpe al pasar de la imitacion a la imagen real
   (o al reves) en cada retoque: ese era el "zoom en la zona de los sellos"
   que se veia en todas las modificaciones, no ya el tamano de la tarjeta
   entera (eso son otros dos arreglos, en .wallet-hero y en la columna). */
.stamp{width:var(--stamp-size,42px);height:var(--stamp-size,42px);border-radius:50%;display:grid;place-items:center;flex:0 0 auto;background:rgba(255,255,255,.12);border:1.5px dashed rgba(255,255,255,.85);color:rgba(255,255,255,.7)}
.stamp svg{width:60%;height:60%}.stamp img{width:100%;height:100%;border-radius:50%;object-fit:cover;opacity:.6}
.stamp.filled{background:#fff;border-style:solid;border-color:transparent;color:var(--wallet-accent,#D9782D);box-shadow:0 3px 6px rgba(0,0,0,.22)}
.stamp.filled svg{color:var(--wallet-accent,#D9782D)}.stamp.filled img{opacity:1;object-fit:contain;padding:5px}
/* "Sin círculo" (ver stamp_icon_sin_circulo, circuloRelleno/circuloVacio en
   stampCardImage.js): mismo tratamiento aquí, en la imitación en CSS de esta
   vista previa — sin esto, un negocio en "Fondo libre" (card_bg_url, ver
   pideImagenesReales en app.js) nunca llega a ver el efecto real, porque ahí
   la imagen real del servidor NUNCA se pide (a propósito, para poder
   arrastrar los sellos en vivo sobre la foto) y esta imitación es la ÚNICA
   vista previa que existe. Se quita el círculo entero (fondo, borde,
   sombra) y el dibujo se ve suelto — los conseguidos a todo color, los que
   faltan en gris (mismo feColorMatrix saturate=0 que FILTRO_DESATURA en el
   servidor, aquí con filter:grayscale). object-fit:contain sin padding:
   mismo encaje "a tamaño completo" que preserveAspectRatio="xMidYMid meet"
   en el <image> real, dentro del mismo hueco --stamp-size de siempre (el
   servidor tampoco agranda el hueco, solo quita el círculo de dentro). */
.stamp.sin-circulo{background:none;border:none;box-shadow:none}
.stamp.sin-circulo img{border-radius:0;object-fit:contain;padding:0;opacity:1}
.stamp.sin-circulo:not(.filled) img{filter:grayscale(1);opacity:.55}
.reward{display:flex;align-items:center;justify-content:center;gap:9px;text-align:center;margin-top:16px}
/* Dentro de .wallet-hero ya no hace falta este margen — el gap del propio
   flex (ver regla base de .wallet-hero) ya separa la fila de sellos del
   premio, y con la tira ahora a proporción fija (mucho más baja que antes)
   no sobra sitio para los dos espaciados a la vez. has-libre no entra aquí:
   ese modo posiciona el premio con position:absolute (más arriba), así que
   este margen no le afecta para nada. */
.wallet-hero:not(.has-libre) .reward{margin-top:0}
.reward svg{width:19px;height:19px;flex:0 0 19px;color:var(--wallet-reward-color,inherit)}
.reward img{width:19px;height:19px;flex:0 0 19px;object-fit:contain}
.reward strong{font-size:13px;font-weight:750}
.wallet-textmodule{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;font-weight:750}
.wallet-textmodule svg{width:16px;height:16px}

/* Vista previa REAL de la tira (fuera del modo "fondo libre"): en vez de
   imitar a mano el tamaño/recorte de la foto, se pide al servidor la MISMA
   imagen PNG que de verdad se manda a Google/Apple Wallet (ver
   src/routes/previewCardImage.js, que llama literalmente a
   renderStampCardImage/renderStampCardImageApple — el mismo código que
   genera las tarjetas reales) y se enseña tal cual. Antes esto se
   aproximaba con background-position/background-size sobre una tira cuyo
   alto dependía del contenido (nº de filas de sellos, si el premio ocupaba
   una o dos líneas...) — como ese alto casi nunca coincidía exactamente con
   el lienzo real (1032x336, proporción ~3:1), una foto podía verse mucho
   más grande o más pequeña en el panel que en el móvil. Con la imagen real
   y este mismo aspect-ratio, panel y móvil quedan siempre idénticos.
   "Fondo libre" (card_bg_url) no entra aquí: ese modo se deja tal cual, con
   su imitación en CSS de siempre, porque ahí los sellos/premio se arrastran
   en vivo con el ratón y hace falta respuesta instantánea que un PNG
   generado en el servidor en cada movimiento no podría dar. */
.wallet-hero-real{display:none;width:100%;height:100%;object-fit:cover;border-radius:14px}
/* margin: sin poner aqui a proposito, mismo motivo que en .has-libre de mas
   arriba -- este modo tiene que medir EXACTAMENTE el mismo ancho que el modo
   clasico/has-photo (margin:0 -10px, puesto en la base de .wallet-hero) para
   que el aspect-ratio no cambie el alto al aparecer/desaparecer esta clase,
   que es justo lo que pasa en cada retoque (ver actualizarVistaPrevia). Antes
   este margin:0 propio hacia la tira 20px mas estrecha que en los demas
   modos, y por tanto tambien mas baja (misma proporcion, menos ancho) -- eso
   era el "salto"/zoom que se veia al ensenar de golpe la imitacion en CSS y
   que la imagen real la sustituyera un instante despues (o al reves). */
.wallet-hero.showing-real{aspect-ratio:1032/336;overflow:hidden;padding:0;background:none}
.wallet-hero.showing-real .wallet-hero-real{display:block}
.wallet-hero.showing-real .stamp-row,.wallet-hero.showing-real .reward,.wallet-hero.showing-real:after{display:none}

/* Selector Google/Apple: cambia qué maqueta se ve, no cambia los datos —
   los dos lados se pintan siempre a la vez en renderWalletPreview(), este
   botón solo decide cuál de los dos queda visible (ver toggleWalletPreview
   en app.js). */
.wallet-toggle{position:relative;z-index:2;display:flex;gap:6px;background:#e1e8e2;border-radius:12px;padding:4px;margin:0 auto 16px;max-width:300px}
.wallet-toggle-btn{flex:1;border:0;background:none;padding:8px 10px;border-radius:9px;font-size:11.5px;font-weight:750;color:#5e6a62;cursor:pointer;transition:.15s}
.wallet-toggle-btn.active{background:#fff;color:var(--ink);box-shadow:0 2px 6px rgba(0,0,0,.1)}

/* Maqueta de una storeCard real de Apple Wallet — misma estructura que
   arma buildPkpassBuffer() en src/wallet/appleWallet.js: strip.png arriba
   (con los sellos dibujados dentro), primaryFields/secondaryFields debajo,
   barcode al fondo, "ⓘ" para el reverso (backFields). A propósito NO es el
   mismo layout que la tarjeta de Google con otro nombre — así se distingue
   de un vistazo cuál va a ver un cliente de iPhone y cuál uno de Android. */
.apple-card{position:relative;z-index:2;max-width:360px;margin:auto;border-radius:26px;overflow:hidden;background:var(--wallet-accent,#D9782D);color:var(--wallet-ink,#fff);box-shadow:0 24px 50px rgba(0,0,0,.24);font-family:-apple-system,'SF Pro Text',system-ui,sans-serif}
/* Cabecera nativa de Apple (logo.png + logoText en el propio .pkpass, ver
   buildPkpassBuffer en appleWallet.js) — antes de esto la tarjeta no tenía
   ningún texto arriba del todo aparte del logo suelto; ahora el logo se ve
   grande y con el nombre del negocio al lado, igual que en cualquier
   storeCard real (Starbucks, tarjetas de fidelización típicas...). */
.apple-header{position:relative;z-index:1;display:flex;align-items:center;padding:16px 18px 4px}
/* Caja panorámica (misma proporción ~3:1 que logo.png de verdad, 120x40) —
   ya no lleva texto al lado (ver comentario sobre applePreview más arriba):
   toda ella es el logo, "contain" para no deformarlo, alineada a la
   izquierda igual que hace Wallet de verdad. */
.apple-header img{height:45px;max-width:188px;object-fit:contain;object-position:left center}
/* La tira (strip.png real) ya NO lleva el logo ni el premio dentro — solo el
   subtítulo (si lo hay) y los sellos, tal cual construyeSvgSoloSellos() en
   stampCardImage.js: por eso puede aprovechar todo el alto para centrar los
   sellos en vez de dejar una banda reservada abajo para texto que ahora
   sale como campo nativo aparte (ver .apple-fields más abajo). */
/* Proporción 375:144, la del hueco REAL de la tira de una storeCard en el
   iPhone (ver APPLE_STRIP_PT en stampCardImage.js) — antes 128px fijos, que
   según el ancho de la tarjeta daban otra proporción y no enseñaban lo
   mismo que el móvil. */
.apple-strip{position:relative;aspect-ratio:375/144;background-color:var(--wallet-accent,#D9782D);background-size:cover;background-position:center;background-repeat:no-repeat;display:flex;flex-direction:column;justify-content:center;gap:10px}
.apple-strip:after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.05),rgba(0,0,0,.28));pointer-events:none}
/* z-index:2, no 1 -- tiene que quedar SIEMPRE por encima de .apple-stamp-row
   (z-index:1, justo debajo) si alguna vez se tocan visualmente: antes los
   dos llevaban el mismo z-index:1 y, al empatar, ganaba quien fuera después
   en el HTML (el propio subtítulo, aquí, así que esto en concreto ya salía
   bien) -- pero #appleLibrePremio (el premio en modo "Fondo libre", más
   abajo) NO llevaba ningún z-index, así que ahí SÍ perdía siempre contra
   los sellos aunque fuera después en el HTML: un empate se rompe por orden
   en el documento, pero .apple-stamp-row con z-index EXPLÍCITO (1) le gana
   a cualquier cosa sin z-index (0 implícito), sea cual sea su orden. Un
   z-index explícito y más alto en todo el texto evita depender de este
   detalle tan frágil del orden del HTML.
*/
.apple-strip-tagline{position:relative;z-index:2;text-align:center;font-size:13px;font-weight:600;letter-spacing:.02em;opacity:.92;padding:0 16px}
.apple-stamp-row{position:relative;z-index:1;display:flex;gap:7px;flex-wrap:wrap;justify-content:center;max-width:300px;margin:0 auto}
/* Igual que .stamp mas arriba: --stamp-size la pone app.js en #appleStampRow
   (con el alto real de .apple-strip, no el ancho -- ver el comentario de
   renderWalletPreview) para igualar el circulo con el de la imagen real de
   Apple. 30px es solo el valor de respaldo (modo "fondo libre" y similar). */
.apple-stamp-row .stamp{width:var(--stamp-size,30px);height:var(--stamp-size,30px)}
/* Mismo criterio que .wallet-hero.has-libre de arriba, para la tira de
   Apple: fondo propio del negocio, sellos y premio libres por encima. */
.apple-strip.has-libre:after{content:none}
.apple-strip.has-libre .apple-stamp-row,.apple-strip.has-libre #appleLibrePremio{position:absolute;transform:translate(-50%,-50%);padding-top:0;margin:0;max-width:none}
/* El subtítulo (.apple-strip-tagline) es el único hijo de .apple-strip que
   NO se mueve a donde el negocio arrastre los sellos/premio — va fijo arriba
   del todo (igual que ahora se dibuja en la imagen real, ver taglineSvg en
   construyeSvgLibre, stampCardImage.js). Antes, en modo "fondo libre",
   .apple-strip lo seguía centrando verticalmente por su flex de base (ver
   regla .apple-strip más arriba, justify-content:center) igual que a
   cualquier otro hijo en flujo normal — como los sellos aquí SÍ salen del
   flujo (position:absolute, justo arriba), el subtítulo se quedaba solo como
   único hijo "normal" y el flex lo centraba en medio de toda la tira,
   cayendo encima de los sellos en vez de por encima de ellos. */
.apple-strip.has-libre .apple-strip-tagline{position:absolute;top:10px;left:0;right:0;margin:0;z-index:2}
/* El bug de verdad: #appleLibrePremio no llevaba NINGÚN z-index, así que
   perdía siempre contra .apple-stamp-row (z-index:1 explícito, ver más
   arriba) en cuanto el negocio arrastraba el premio hasta tocar los sellos
   -- el texto del premio ("1 consumición gratis" + su icono) se quedaba
   tapado DETRÁS de los círculos en vez de leerse por encima, que es lo que
   se espera de un texto sobre unos adornos. z-index:2 (por encima del 1 de
   los sellos) en vez de dejarlo en el 0 por defecto de siempre. */
.apple-strip.has-libre #appleLibrePremio{z-index:2}
/* Color del círculo del sello, solo Apple (apple_stamp_circle_color, ver
   db.js) — variable con el blanco de siempre como valor por defecto, así que
   si el negocio no ha elegido ninguno esto no cambia nada. Google (.stamp
   dentro de .wallet-hero) nunca lee esta variable — solo se define dentro de
   .apple-strip. */
.apple-strip{--apple-stamp-circle:#fff}
.apple-strip .stamp.filled{background:var(--apple-stamp-circle)}
/* Misma prioridad (mismas 3 clases-selector que la regla de arriba, así que
   gana por venir después) que .stamp.sin-circulo de más arriba, pero hacía
   falta repetirla aquí: sin esto, en Apple el círculo de color elegido a
   mano (apple_stamp_circle_color) seguía ganando por venir con un selector
   más específico (.apple-strip .stamp.filled), y "sin círculo" no se veía
   nunca en la tira de Apple aunque en Google sí. */
.apple-strip .stamp.sin-circulo{background:none}
#appleLibrePremio{display:flex;align-items:center;gap:7px}
#appleLibrePremio strong{font-size:12px;font-weight:750}
#appleLibrePremio svg,#appleLibrePremio img{width:16px;height:16px;flex:0 0 16px}
/* Premio (secondaryField) y Sellos/Titular/Instagram (auxiliaryFields) son
   dos arrays nativos distintos en el .pkpass (ver appleWallet.js), pero
   Apple Wallet los junta en una ÚNICA fila visual, sin ningún hueco entre
   "filas" — confirmado con una tarjeta real. justify-content:space-between
   (no un gap fijo): reparte los campos a lo ancho de toda la fila, el
   primero pegado al borde izquierdo y el último al derecho.
   flex-wrap:nowrap a propósito: con wrap, al no caber los 4 campos en una
   sola línea, Sellos/Titular/Instagram se bajaban a una segunda línea
   entera en vez de quedarse a la derecha del Premio en la MISMA línea (que
   es lo que hace de verdad un pase real).
   Con nowrap, alguien tiene que encogerse para que quepan los 4 — Premio es
   el único candidato razonable (flex-shrink:1, los demás a 0): Sellos/
   Titular/Instagram son valores cortos y casi siempre SIN espacios donde
   partir ("3/6", "@usuario"), así que si les tocara encogerse acababan
   envueltos letra a letra ("Is"/"ha"/"ak") en vez de quedarse en su ancho
   natural, que es justo el escenario absurdo que se quiere evitar. Aun así,
   Premio necesita un tamaño de letra propio, más pequeño (14px, ver
   .apple-field-premio strong) y un ancho mínimo (min-width): con las cuatro
   etiquetas puestas y el tamaño de 17px que llevaba antes (pensado para
   solo 1-2 campos), a Premio no le quedaba ancho ni para una palabra suelta
   como "próximo", y volvía a partirla letra a letra igualmente. */
.apple-fields{display:flex;flex-wrap:nowrap;justify-content:space-between;gap:6px 10px;padding:14px 20px}
.apple-field{display:flex;flex-direction:column;gap:2px;min-width:0;flex:0 0 auto}
.apple-field-premio{flex:1 1 0%;min-width:88px}
/* Sellos/Titular/Instagram: pensados solo para textos cortos ("3/6",
   "@usuario"), sin tope de ancho — con un nombre de cliente o un Instagram
   largos, el campo crecía sin límite, empujaba la fila entera más allá del
   borde de la tarjeta (.apple-card tiene overflow:hidden) y el texto se
   veía cortado a medio carácter, en vez de recortado "de verdad" con
   puntos suspensivos. Ahora tienen un ancho máximo y, si no cabe, se
   recorta con "…" — Premio queda fuera de esta regla porque a él sí le
   interesa partir en varias líneas (ver .apple-field-premio strong).
*/
.apple-field:not(.apple-field-premio){flex:0 1 auto;max-width:96px}
.apple-field:not(.apple-field-premio) .apple-field-label,
.apple-field:not(.apple-field-premio) strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.apple-field-label{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;opacity:.75}
.apple-field strong{font-size:15px;font-weight:800;letter-spacing:-.01em;overflow-wrap:break-word}
.apple-field-premio strong{font-size:14px}
.apple-barcode{display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 20px 22px}
.apple-qr-box{background:#fff;border-radius:12px;padding:9px;line-height:0}
.apple-qr-box canvas{width:92px!important;height:92px!important;display:block}
.apple-barcode span{font-size:9.5px;opacity:.8;text-align:center}
.apple-info-dot{position:absolute;right:14px;bottom:14px;width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;line-height:1}

/* Misma vista previa REAL que .wallet-hero-real de arriba, pero para la tira
   de Apple — mismo motivo, mismo criterio (no entra en modo "fondo libre"). */
.apple-strip-real{display:none;position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.apple-strip.showing-real .apple-strip-real{display:block}
.apple-strip.showing-real .apple-strip-tagline,.apple-strip.showing-real .apple-stamp-row,.apple-strip.showing-real:after{display:none}
.upload-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.upload-thumb{width:44px;height:44px;border-radius:11px;overflow:hidden;flex:0 0 44px;background:#f2f6f3;border:1px solid var(--line)}.upload-thumb img{width:100%;height:100%;object-fit:cover}.upload-thumb-wide{background:var(--wallet-accent,#1B2430)}.upload-thumb-wide img{object-fit:contain;padding:6px}.upload-btn input{display:none}
.upload-progress{display:none;align-items:center;gap:9px;margin-top:9px}.upload-progress.show{display:flex}.upload-progress-track{flex:1;height:6px;border-radius:99px;background:#eef3ef;overflow:hidden}.upload-progress-bar{height:100%;width:0%;background:var(--green);border-radius:99px;transition:width .15s linear}.upload-progress-label{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--muted);min-width:126px;text-align:right}
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(42px,1fr));gap:8px;margin-bottom:10px}.icon-opt{aspect-ratio:1;display:flex;align-items:center;justify-content:center;border-radius:12px;border:1.5px solid var(--line);background:#fbfcfb;padding:0;color:#7b857e;cursor:pointer;transition:.15s}.icon-opt svg{width:19px;height:19px}.icon-opt:hover{border-color:var(--green)}.icon-opt.active{border-color:var(--green);background:var(--green-soft);color:var(--green-dark)}
.icon-custom-btn{display:flex;align-items:center;gap:10px;border:1.5px dashed var(--line);border-radius:12px;padding:10px 12px;font-size:12.5px;color:var(--muted);cursor:pointer;transition:.15s;margin-bottom:8px}.icon-custom-btn:hover{border-color:var(--green)}.icon-custom-btn.active{border-style:solid;border-color:var(--green);background:var(--green-soft);color:var(--green-dark)}.icon-custom-preview img{width:28px;height:28px;border-radius:8px;object-fit:cover;display:block}
.pos-row{display:flex;gap:8px;margin-top:10px}.pos-btn{flex:1;border:1.5px solid var(--line);background:#fbfcfb;border-radius:10px;padding:8px 6px;font-size:12px;font-weight:650;color:#7b857e;cursor:pointer;transition:.15s;text-align:center}.pos-btn:hover{border-color:var(--green)}.pos-btn.active{border-color:var(--green);background:var(--green-soft);color:var(--green-dark)}
.stat-strip{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);border-radius:16px;overflow:hidden;background:#fff}.stat-strip>div{padding:16px;border-right:1px solid var(--line)}.stat-strip>div:last-child{border-right:0}.stat-strip strong{font-size:21px;display:block;letter-spacing:-.03em}.stat-strip span{font-size:10px;color:var(--muted)}
.empty-state{text-align:center;padding:42px 20px;color:var(--muted)}.empty-state svg{width:34px;height:34px;color:#8f9a93}.empty-state h3{color:var(--ink);margin:10px 0 4px}.empty-state p{font-size:12px;max-width:420px;margin:0 auto 16px;line-height:1.5}
/* Aviso persistente cuando falla la carga de datos del servidor (panel,
   clientes...) — a diferencia de un toast, se queda en pantalla hasta que
   se reintenta con éxito, para que no pase desapercibido si en ese momento
   nadie está mirando la pantalla. Los números/filas de ejemplo que trae la
   página de fábrica pueden seguir debajo mientras tanto, así que este aviso
   deja claro que no son de fiar ahora mismo. */
.data-error-banner{display:flex;align-items:flex-start;gap:12px;background:#fff1f1;border:1px solid #f4c9c9;border-radius:15px;padding:14px 16px;margin-bottom:16px}
.data-error-banner svg{width:20px;height:20px;color:var(--danger);flex:0 0 20px;margin-top:1px}
.data-error-banner>div{flex:1}
.data-error-banner strong{display:block;font-size:13px;color:#7a2323}
.data-error-banner p{margin:3px 0 0;font-size:12px;color:#8a3b3b;line-height:1.5}
.data-error-banner .btn{flex:0 0 auto;align-self:center}
.badge{display:inline-flex;align-items:center;gap:6px;padding:6px 9px;border-radius:999px;background:#eef3ef;color:#5e6a62;font-size:10px;font-weight:800}.badge.green{background:var(--green-soft);color:var(--green-dark)}
.toast{position:fixed;right:24px;bottom:24px;background:#101612;color:#fff;padding:13px 15px;border-radius:13px;box-shadow:0 18px 50px rgba(0,0,0,.22);z-index:100;display:flex;align-items:center;gap:10px;font-size:12px;transform:translateY(30px);opacity:0;pointer-events:none;transition:.25s}.toast.show{transform:none;opacity:1}.toast i{width:8px;height:8px;border-radius:50%;background:var(--green)}
.mobile-bar{display:none}.mobile-menu{display:none}

/* ===== Avisos → Automatizaciones: tarjetas ===== */
.auto-stats{margin-bottom:18px}
.auto-filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.chip{border:1px solid var(--line);background:#fff;color:#5e6a62;font-size:12px;font-weight:750;padding:8px 12px;border-radius:999px;cursor:pointer;transition:.15s;font-family:inherit}
.chip:hover{border-color:#b7dcc7}
.chip.active{background:var(--green-soft);border-color:#c9eedb;color:var(--green-dark)}
.chip:disabled{cursor:not-allowed;opacity:.55}
.automations-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
.automation{border:1px solid var(--line);border-radius:var(--radius-sm);background:#fff;overflow:hidden;transition:.2s}
.automation.auto-off{opacity:.7}
.automation-head{display:grid;grid-template-columns:44px 1fr auto;gap:13px;align-items:start;padding:16px}
.auto-icon{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto}
.auto-icon svg{width:20px;height:20px}
.auto-icon.i-green{background:var(--green-soft);color:var(--green-dark)}
.auto-icon.i-warn{background:#fff6e8;color:#925d08}
.auto-icon.i-blue{background:#eaf1fe;color:#3568c9}
.auto-icon.i-gray{background:#eef3ef;color:#5e6a62}
.automation-title{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}
.automation-title h3{margin:0;font-size:15px;letter-spacing:-.01em}
.automation-head p{margin:0;font-size:12.5px;color:var(--muted);line-height:1.45}
.automation-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 16px;border-top:1px solid #edf1ee;background:#fbfcfb}
.automation-preview{font-size:11.5px;color:#556059;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%}
.automation-edit{border:0;background:transparent;color:var(--green-dark);font-weight:800;font-size:11.5px;cursor:pointer;padding:6px 0;font-family:inherit}
.automation-details{display:none;padding:0 16px 16px;border-top:1px dashed #e4e9e6;background:#fbfcfb}
.automation-details .help a{color:var(--green-dark);font-weight:700}
.automation.auto-open .automation-details{display:block}
.automation-status{font-size:10px;font-weight:850;text-transform:uppercase;letter-spacing:.05em;border-radius:999px;padding:4px 8px;background:#eef3ef;color:#667169}
.automation-status.on{background:var(--green-soft);color:var(--green-dark)}

/* ===== Límites por plan ===== */
.color-palette{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:6px}
.color-swatch{width:38px;height:38px;border-radius:50%;border:2px solid var(--line);cursor:pointer;padding:0;transition:.15s;position:relative}
.color-swatch:hover{transform:translateY(-1px)}
.color-swatch.active{border-color:var(--ink);box-shadow:0 0 0 2px #fff inset,0 0 0 3px var(--ink)}
.nav-lock{margin-left:auto;font-size:8.5px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;color:#a98a3a;background:#fdf3da;border-radius:999px;padding:2px 7px}
.plan-upgrade-banner{display:flex;align-items:center;gap:9px;background:#fdf3da;border:1px solid #f0dfa8;color:#8a6a1f;border-radius:13px;padding:11px 13px;font-size:12.5px;margin-bottom:14px;line-height:1.5}
.plan-upgrade-banner svg{width:16px;height:16px;flex:none}
.plan-upgrade-banner a{color:#8a6a1f;font-weight:800;text-decoration:underline;white-space:nowrap}
.plan-upgrade-card{padding:34px 28px;text-align:center;max-width:520px;margin:12px auto}
.plan-upgrade-card h2{font-size:20px;margin:8px 0 8px}
.plan-upgrade-card .btn{margin-top:14px}
.plan-locked{opacity:.55;filter:grayscale(.15)}
.plan-locked input,.plan-locked textarea,.plan-locked select,.plan-locked button{cursor:not-allowed}
.field.plan-locked-field{opacity:.5}
.field.plan-locked-field .upload-btn,.field.plan-locked-field input{cursor:not-allowed;pointer-events:none}
.icon-custom-btn.locked{opacity:.5;cursor:not-allowed}
.icon-custom-btn.locked input{pointer-events:none}
.plan-blur-wrap{position:relative;max-height:min(78vh,720px);overflow:hidden;border-radius:var(--radius)}
.plan-blur-content{filter:blur(7px);pointer-events:none;user-select:none}
.plan-blur-wrap::after{content:"";position:absolute;left:0;right:0;bottom:0;height:120px;background:linear-gradient(to bottom,rgba(244,246,243,0),var(--bg));z-index:1}
.plan-blur-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;z-index:2}
.plan-blur-card{background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:var(--radius);padding:34px 28px;text-align:center;max-width:440px;width:100%}
.plan-blur-card svg{width:26px;height:26px;color:#a98a3a}
.plan-blur-card h2{font-size:19px;margin:12px 0 8px}
.plan-blur-card p{color:var(--muted);font-size:13.5px;line-height:1.55;margin:0}
.plan-blur-card .btn{margin-top:16px}
@media(max-width:640px){.plan-blur-content{filter:blur(5px)}.plan-blur-card{padding:26px 20px}.plan-blur-wrap{max-height:min(82vh,620px)}}
.plan-tag-inline{font-size:9.5px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;color:#a98a3a;background:#fdf3da;border-radius:999px;padding:2px 7px;margin-left:6px;vertical-align:middle}
@media(max-width:1150px){.metrics{grid-template-columns:repeat(2,1fr)}.two-col,.qr-layout{grid-template-columns:1fr}.three-col{grid-template-columns:repeat(2,1fr)}.span-3{grid-column:span 2}}
.mobile-more-backdrop{position:fixed;inset:0;background:rgba(6,10,8,.55);z-index:80;opacity:0;pointer-events:none;transition:.2s}.mobile-more-backdrop.show{opacity:1;pointer-events:auto}
.mobile-more-sheet{position:fixed;left:10px;right:10px;bottom:10px;background:var(--dark-2);border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:10px;z-index:81;transform:translateY(115%);transition:.25s;box-shadow:0 -16px 50px rgba(0,0,0,.35)}.mobile-more-sheet.show{transform:none}
.mobile-more-sheet a,.mobile-more-sheet button{display:flex;align-items:center;gap:13px;width:100%;text-align:left;color:#e7ede9;font-weight:700;font-size:14.5px;padding:13px 12px;border-radius:13px;background:none;border:0;font-family:inherit}
.mobile-more-sheet a:active,.mobile-more-sheet button:active{background:rgba(255,255,255,.06)}
.mobile-more-sheet svg{width:19px;height:19px;color:#9bf5c6}
.mobile-more-sheet .mms-divider{height:1px;background:rgba(255,255,255,.08);margin:6px 4px}
/* Separa visualmente cada forma alternativa de poner fondo (foto / 2ª foto
   / fondo libre, y foto/color de Apple) dentro de una misma tarjeta, para
   que no se vea todo como un único bloque de golpe ("un pelotón"). */
.field-divider{border-top:1px solid var(--line);padding-top:18px;margin-top:2px}
/* ===== Pestañas principales + stepper de "Personalizar tarjeta" =====
   Reutiliza el mismo lenguaje visual que el resto del panel: la pastilla
   segmentada ya existente para elegir Google/Apple Wallet (.wallet-toggle)
   inspira el stepper de abajo, y el verde de marca (var(--green)) marca lo
   activo en ambos, igual que en el resto de la app (.nav a.active, etc). */
.section-tabs{display:flex;gap:26px;margin-bottom:22px;border-bottom:1px solid var(--line);overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.section-tabs::-webkit-scrollbar{display:none}
.section-tab{background:none;border:0;border-bottom:2.5px solid transparent;padding:0 0 13px;font-weight:750;font-size:14px;color:var(--muted);display:flex;align-items:center;gap:8px;transition:.18s;white-space:nowrap}
.section-tab svg{width:17px;height:17px}
.section-tab:hover{color:var(--ink)}
.section-tab.active{color:var(--green-dark);border-bottom-color:var(--green)}
.tab-pane{display:grid;gap:16px}
.stepper{display:flex;gap:6px;background:#eef2ee;border-radius:13px;padding:4px;margin-bottom:16px;max-width:620px;overflow-x:auto}
.step-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;border:0;background:none;padding:9px 8px;border-radius:10px;font-size:12.5px;font-weight:700;color:#5e6a62;cursor:pointer;transition:.15s;white-space:nowrap}
.step-btn i{width:19px;height:19px;border-radius:50%;background:#dde3df;color:#5e6a62;display:inline-flex;align-items:center;justify-content:center;font-size:10.5px;font-weight:800;font-style:normal;flex:0 0 auto}
.step-btn.active{background:#fff;color:var(--ink);box-shadow:0 2px 6px rgba(0,0,0,.1)}
.step-btn.active i{background:var(--green);color:#062b19}
.design-step{display:grid;gap:16px}
@media(max-width:640px){.section-tabs{gap:18px}.section-tab{font-size:13px}.section-tab span[data-icon]{display:none}.stepper{max-width:none}.step-btn span{display:none}}
/* Vista previa "pegada": en Personalizar tarjeta, la columna de la derecha
   (la tarjeta de verdad) sigue el scroll en vez de quedarse arriba del
   todo — así se ve el resultado al momento sin tener que subir cada vez
   que se cambia algo del formulario, que era la queja original. Solo en
   programa.html (data-page) y solo en escritorio: en el layout de una sola
   columna (<=1150px) no tiene sentido, ahí va todo en el orden normal.
   max-height + overflow-y:auto es imprescindible: la tarjeta + el QR + el
   "Consejo" de debajo miden más que muchas pantallas de portátil, así que
   sin esto, al quedar "pegada" a top:96px, la parte de abajo (QR, sellos,
   Consejo) quedaba fuera de la ventana sin manera de verla hasta llegar
   al final absoluto de la página — justo lo que se reportó ("no se ve
   entero"). Con esto, si no cabe entera, se puede hacer scroll DENTRO de
   esa columna, en el sitio, sin perder de vista el formulario. */
body[data-page="programa"] .two-col{align-items:start}
body[data-page="programa"] .two-col>article:last-child{
  position:sticky;top:96px;max-height:calc(100vh - 116px);overflow-y:auto;
  overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  /* scrollbar-gutter:stable -- ESTE es el verdadero "zoom" que se seguia
     viendo en cada retoque, no el tamano de .wallet-hero (eso ya estaba
     arreglado y medido con Playwright, siempre igual). Esta columna tiene
     su PROPIA barra de scroll interna (overflow-y:auto, arriba) porque la
     tarjeta + QR + Consejo no caben enteros en pantallas bajas. Cada vez
     que se actualiza la vista previa (la imitacion en CSS al instante, y
     luego la imagen real un momento despues) su alto total cambia un poco
     -- a veces lo justo para cruzar el limite de max-height y que esa barra
     de scroll aparezca o desaparezca. Una barra de scroll OCUPA sitio: en
     cuanto aparece o desaparece, el ancho disponible para todo lo de dentro
     (la tarjeta, con max-width:360px) cambia de golpe unos 15px -- y como
     la tarjeta escala con ese ancho, se ve crecer o encoger entera, no solo
     la foto de cabecera. Con scrollbar-gutter:stable ese hueco para la
     barra se reserva SIEMPRE, se vea la barra o no, asi el ancho de dentro
     ya no cambia nunca por esto. */
  scrollbar-gutter:stable;
}
@media(max-width:1150px){body[data-page="programa"] .two-col>article:last-child{position:static;max-height:none;overflow-y:visible}}
@media(max-width:840px){:root{--sidebar:0px}.sidebar{display:none}.main{margin-left:0}.topbar{height:66px;padding:0 18px}.top-title{font-size:12px}.content{padding:22px 16px 94px}.page-head{align-items:flex-start;flex-direction:column}.page-head h1{font-size:27px}.metrics{grid-template-columns:repeat(2,1fr)}.metric{min-height:120px;padding:15px}.metric .value{font-size:25px}.two-col,.three-col,.form-grid,.automations-grid{grid-template-columns:1fr}.span-2,.span-3{grid-column:auto}.mobile-bar{position:fixed;display:grid;grid-template-columns:repeat(6,1fr);left:10px;right:10px;bottom:10px;background:rgba(10,15,12,.94);backdrop-filter:blur(18px);border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:7px;z-index:70;box-shadow:0 16px 42px rgba(0,0,0,.25)}.mobile-bar a,.mobile-bar button{color:#99a49d;display:grid;place-items:center;gap:3px;font-size:9px;font-weight:700;padding:4px;border-radius:12px;background:none;border:0;font-family:inherit;text-align:center;line-height:1.15}.mobile-bar a.active,.mobile-bar button.active{color:#dcfbe9;background:rgba(37,207,121,.12)}.mobile-bar svg{width:19px;height:19px}
.mobile-menu{display:grid}.stat-strip{grid-template-columns:repeat(2,1fr)}.stat-strip>div:nth-child(2){border-right:0}.stat-strip>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.donut-box{flex-direction:column}.quick-actions{grid-template-columns:1fr}.phone{transform:scale(.9);margin-top:-20px;margin-bottom:-25px}}
@media(max-width:520px){.metrics{grid-template-columns:1fr 1fr;gap:10px}.metric .label{font-size:10px}.metric{min-height:112px}.card{padding:16px;border-radius:17px}.top-actions .btn{display:none}.heat{grid-template-columns:45px repeat(7,1fr);gap:4px}.heat-cell{height:23px}.page-head{margin-bottom:18px}.head-actions{width:100%}.head-actions .btn{flex:1}.qr-card{padding:18px}.phone{width:230px;height:465px}}

/* ===== Ajustes para móvil (revisión completa de octubre 2026) =====
Todo lo de aquí abajo solo afecta a pantallas pequeñas (salvo el mapa), y
está al final del archivo a propósito para ganar a las reglas anteriores.
Objetivo: nada cortado ni saliéndose de la pantalla, textos legibles y
botones cómodos de pulsar con el dedo. */
.leaflet-container{isolation:isolate;z-index:0}
/* El interruptor no se puede encoger (con textos largos al lado quedaba
en una rayita de 11px de ancho). */
.switch{flex:0 0 40px}
@media(max-width:840px){
/* Los hijos de una rejilla no pueden ser más anchos que la pantalla (antes
una gráfica o una tabla ensanchaba la tarjeta entera y se salía por la derecha). */
.grid>*,.card,.metric,.two-col>*,.three-col>*{min-width:0}
/* Rejillas de 4 columnas puestas en línea en el HTML: en móvil, de 2 en 2 */
.metrics[style],[style*="repeat(4,"]{grid-template-columns:repeat(2,minmax(0,1fr))!important}
.card-head{flex-wrap:wrap}
.card-head>div:first-child{min-width:0;flex:1 1 100%}
.card-head>.badge{order:-1}
/* Si al lado del título solo hay un icono, se queda a la derecha (no baja de línea) */
.card-head:has(>span[data-icon])>div:first-child{flex:1 1 0}
.card-head p{font-size:13px}
.card-head>div[style*="display:flex"]{flex-wrap:wrap;width:100%}
.card-head>div[style*="display:flex"]>.input,.card-head>div[style*="display:flex"]>.select{flex:1 1 140px;width:auto!important;min-height:44px!important}
.card-head>.input,.card-head>input,.card-head>.select{width:100%!important;flex:1 1 100%}
/* Textos algo más grandes: en el móvil 10-11px no se leen bien */
.help{font-size:12px;line-height:1.45}
.field label{font-size:12.5px}
.field label .help,label .help{font-weight:400}
.metric .label{font-size:11.5px}
.metric-top{flex-wrap:wrap}
.delta{white-space:normal;max-width:100%;font-size:11px}
.opp{flex-wrap:wrap}
.opp>div:not(.opp-icon){flex:1 1 160px;min-width:0}
.opp h4{font-size:14px}
.opp p{font-size:12px}
.opp button{min-height:38px}
.quick strong{font-size:14px}
.quick span{font-size:12px}
.switch-row strong{font-size:13px}
.switch-row p{font-size:12px}
.stat-strip span .delta{display:table;margin:6px 0 0!important}
/* 16px en los campos: con menos, el iPhone hace zoom al escribir */
.input,.select,.textarea,input[type=text],input[type=number],input[type=email],input[type=search],input[type=tel],input[type=date],input[type=datetime-local],input[type=url],select,textarea{font-size:16px;max-width:100%}
.content input[type=checkbox],.content input[type=radio]{width:20px;height:20px;accent-color:var(--green)}
.content input[type=file]{width:100%;font-size:13px;color:var(--muted)}
.content input[type=file]::file-selector-button{border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:10px;padding:10px 14px;margin-right:10px;font-weight:700;font-family:inherit;font-size:13px;cursor:pointer}
/* Botones pequeños, más fáciles de pulsar */
.week-nav-arrow{width:36px;height:36px;font-size:18px}
.week-nav-label{padding:9px 14px;font-size:12px}
.chip{min-height:38px}
.wallet-toggle-btn{min-height:40px;font-size:13px}
.step-btn{min-height:40px}
/* Pestañas (Avisos, Personalizar tarjeta...): antes la última quedaba
cortada a la derecha y no se veía que existía; ahora bajan de línea. */
.section-tabs{flex-wrap:wrap;gap:8px;border-bottom:0;overflow:visible;margin-bottom:18px}
.section-tab{border:1px solid var(--line);border-radius:999px;padding:9px 14px;background:#fff;min-height:40px;font-size:13px}
.section-tab.active{background:var(--green-soft);border-color:#bfe9d2;color:var(--green-dark)}
/* Escáner: más alto para que el recuadro y el texto no se monten */
.scanner{aspect-ratio:1/1}
.scanner-actions .btn{flex:1 1 100%;min-height:50px;font-size:15px}
/* Avisos → Automatizaciones */
.auto-stats{grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:8px}
.auto-stats .metric{min-height:0;padding:12px}
.auto-stats .metric-top{flex-direction:column-reverse;align-items:flex-start;gap:6px}
.auto-stats .metric-icon{display:none}
.auto-stats .metric .label{margin-top:4px;font-size:11px;line-height:1.3;hyphens:auto;-webkit-hyphens:auto;overflow-wrap:anywhere}
.auto-stats .metric .value{font-size:22px}
.auto-stats .delta{font-size:10px;padding:3px 7px}
.automation-preview{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;max-width:none;flex:1}
.automation-edit{padding:10px 4px;font-size:13px}
/* Aviso flotante: por encima de la barra de abajo y a lo ancho */
.toast{left:12px;right:12px;bottom:92px;justify-content:center;text-align:center}
/* Tarjetas de puntos: etiquetas del editor de premios */
.pt-ed label.help{font-weight:700;color:#4e5a52}
label.help>.input,label.help>.select,label.help>.textarea{font-weight:400}
}
@media(max-width:640px){
/* Tablas → fichas: en el móvil una tabla de 6-8 columnas solo dejaba ver
las 2 primeras. Cada fila pasa a ser una ficha con "ETIQUETA  valor"
(la etiqueta la pone app.js en data-label, copiada de la cabecera). */
.table-wrap{border:0;border-radius:0;overflow:visible}
.table{min-width:0!important;display:block}
.table thead{display:none}
.table tbody{display:grid;gap:10px}
.table tr{display:block;border:1px solid var(--line);border-radius:15px;background:#fff;padding:6px 14px}
.table td{display:flex;justify-content:space-between;align-items:center;gap:12px;border-top:1px solid #eef2ef;padding:10px 0;text-align:right;min-width:0;overflow-wrap:anywhere;font-size:13px}
.table td:first-child{border-top:0;text-align:left;justify-content:flex-start}
.table td[data-label]:not([data-label=""])::before{content:attr(data-label);font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#87918a;text-align:left;flex:0 0 auto;max-width:45%}
.table td:first-child::before{display:none}
.table td[data-label=""]{justify-content:flex-end}
.table td .progress{flex:1;max-width:160px}
.table td:empty{display:none}
/* Celda con varias cosas (p. ej. «nº 0001» + «No guardada» + «Caduca…»):
   la etiqueta a la izquierda y las cosas apiladas a la derecha, sin partirse. */
.table td[data-label]:not([data-label=""]):not(:first-child){display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:12px;row-gap:0;align-items:center}
.table td[data-label]:not([data-label=""]):not(:first-child)::before{grid-column:1;grid-row:1/span 6;max-width:none}
.table td[data-label]:not([data-label=""]):not(:first-child)>*{grid-column:2;justify-self:end;text-align:right}
.table td[data-label]:not([data-label=""]):not(:first-child)>.progress{justify-self:stretch;width:100%;max-width:none}
/* Tarjetas regalo → botones de carga: cada botón en su propio bloque */
.content .rg-btn-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;padding:10px;border:1px solid var(--line);border-radius:14px;background:#fbfcfb}
.content .rg-btn-row>.select{grid-column:1/-1}
.content .rg-btn-row>div{min-width:0}
.content .rg-btn-row>div>*{min-width:0;flex:1 1 0}
.content .rg-btn-row>div>input:first-child:not(:only-child){flex:0 0 84px}
.content .rg-btn-row>.btn{min-height:44px;min-width:44px}
}

/* Menú: oculto hasta que JS lo coloca en su orden (ver menuDesdeCache en
app.js) — así, al cambiar de página, no se ve un instante el orden antiguo.
Si el JS fallara, aparece igualmente a los 2,5 s. */
body:not(.menu-listo) .sidebar .nav,body:not(.menu-listo) .mobile-bar>a,body:not(.menu-listo) .mobile-bar>button{visibility:hidden;animation:rfMenuVisible 0s 2.5s forwards}
@keyframes rfMenuVisible{to{visibility:visible}}
