/* ═══════════════════════════════════════════════════════════════════════
 * main.bundle.css — GENERADO POR App\Support\CssBundler — NO EDITAR A MANO
 * Generado: 2026-04-20 10:38:15
 * Fuente:   public/assets/css/main.css (+ @imports recursivos)
 * ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   main.css — Entry point CSS
   Importa todas las capas en el orden correcto de CUBE CSS.
   El orden es crítico — no alterar sin revisar las implicaciones de cascada.

   Orden: tokens → reset → composition → utilities → blocks
   ═══════════════════════════════════════════════════════════════════════════ */

/* 1. Design tokens — custom properties base del sistema */

/* ── tokens.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   tokens.css — Design tokens del proyecto
   Basado en Ant Design v5 (Daybreak Blue + Sunset Orange).
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── open-props.min.css ───────────────────────────────── */
:where(html){--font-system-ui:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;--font-transitional:Charter,Bitstream Charter,Sitka Text,Cambria,serif;--font-old-style:Iowan Old Style,Palatino Linotype,URW Palladio L,P052,serif;--font-humanist:Seravek,Gill Sans Nova,Ubuntu,Calibri,DejaVu Sans,source-sans-pro,sans-serif;--font-geometric-humanist:Avenir,Montserrat,Corbel,URW Gothic,source-sans-pro,sans-serif;--font-classical-humanist:Optima,Candara,Noto Sans,source-sans-pro,sans-serif;--font-neo-grotesque:Inter,Roboto,Helvetica Neue,Arial Nova,Nimbus Sans,Arial,sans-serif;--font-monospace-slab-serif:Nimbus Mono PS,Courier New,monospace;--font-monospace-code:Dank Mono,Operator Mono,Inconsolata,Fira Mono,ui-monospace,SF Mono,Monaco,Droid Sans Mono,Source Code Pro,Cascadia Code,Menlo,Consolas,DejaVu Sans Mono,monospace;--font-industrial:Bahnschrift,DIN Alternate,Franklin Gothic Medium,Nimbus Sans Narrow,sans-serif-condensed,sans-serif;--font-rounded-sans:ui-rounded,Hiragino Maru Gothic ProN,Quicksand,Comfortaa,Manjari,Arial Rounded MT,Arial Rounded MT Bold,Calibri,source-sans-pro,sans-serif;--font-slab-serif:Rockwell,Rockwell Nova,Roboto Slab,DejaVu Serif,Sitka Small,serif;--font-antique:Superclarendon,Bookman Old Style,URW Bookman,URW Bookman L,Georgia Pro,Georgia,serif;--font-didone:Didot,Bodoni MT,Noto Serif Display,URW Palladio L,P052,Sylfaen,serif;--font-handwritten:Segoe Print,Bradley Hand,Chilanka,TSCu_Comic,casual,cursive;--font-sans:var(--font-system-ui);--font-serif:ui-serif,serif;--font-mono:var(--font-monospace-code);--font-weight-1:100;--font-weight-2:200;--font-weight-3:300;--font-weight-4:400;--font-weight-5:500;--font-weight-6:600;--font-weight-7:700;--font-weight-8:800;--font-weight-9:900;--font-lineheight-00:.95;--font-lineheight-0:1.1;--font-lineheight-1:1.25;--font-lineheight-2:1.375;--font-lineheight-3:1.5;--font-lineheight-4:1.75;--font-lineheight-5:2;--font-letterspacing-0:-.05em;--font-letterspacing-1:.025em;--font-letterspacing-2:.050em;--font-letterspacing-3:.075em;--font-letterspacing-4:.150em;--font-letterspacing-5:.500em;--font-letterspacing-6:.750em;--font-letterspacing-7:1em;--font-size-00:.5rem;--font-size-0:.75rem;--font-size-1:1rem;--font-size-2:1.1rem;--font-size-3:1.25rem;--font-size-4:1.5rem;--font-size-5:2rem;--font-size-6:2.5rem;--font-size-7:3rem;--font-size-8:3.5rem;--font-size-fluid-0:max(.75rem,min(2vw,1rem));--font-size-fluid-1:max(1rem,min(4vw,1.5rem));--font-size-fluid-2:max(1.5rem,min(6vw,2.5rem));--font-size-fluid-3:max(2rem,min(9vw,3.5rem));--size-000:-.5rem;--size-00:-.25rem;--size-1:.25rem;--size-2:.5rem;--size-3:1rem;--size-4:1.25rem;--size-5:1.5rem;--size-6:1.75rem;--size-7:2rem;--size-8:3rem;--size-9:4rem;--size-10:5rem;--size-11:7.5rem;--size-12:10rem;--size-13:15rem;--size-14:20rem;--size-15:30rem;--size-px-000:-8px;--size-px-00:-4px;--size-px-1:4px;--size-px-2:8px;--size-px-3:16px;--size-px-4:20px;--size-px-5:24px;--size-px-6:28px;--size-px-7:32px;--size-px-8:48px;--size-px-9:64px;--size-px-10:80px;--size-px-11:120px;--size-px-12:160px;--size-px-13:240px;--size-px-14:320px;--size-px-15:480px;--size-fluid-1:max(.5rem,min(1vw,1rem));--size-fluid-2:max(1rem,min(2vw,1.5rem));--size-fluid-3:max(1.5rem,min(3vw,2rem));--size-fluid-4:max(2rem,min(4vw,3rem));--size-fluid-5:max(4rem,min(5vw,5rem));--size-fluid-6:max(5rem,min(7vw,7.5rem));--size-fluid-7:max(7.5rem,min(10vw,10rem));--size-fluid-8:max(10rem,min(20vw,15rem));--size-fluid-9:max(15rem,min(30vw,20rem));--size-fluid-10:max(20rem,min(40vw,30rem));--size-content-1:20ch;--size-content-2:45ch;--size-content-3:60ch;--size-header-1:20ch;--size-header-2:25ch;--size-header-3:35ch;--size-xxs:240px;--size-xs:360px;--size-sm:480px;--size-md:768px;--size-lg:1024px;--size-xl:1440px;--size-xxl:1920px;--size-relative-000:-.5ch;--size-relative-00:-.25ch;--size-relative-1:.25ch;--size-relative-2:.5ch;--size-relative-3:1ch;--size-relative-4:1.25ch;--size-relative-5:1.5ch;--size-relative-6:1.75ch;--size-relative-7:2ch;--size-relative-8:3ch;--size-relative-9:4ch;--size-relative-10:5ch;--size-relative-11:7.5ch;--size-relative-12:10ch;--size-relative-13:15ch;--size-relative-14:20ch;--size-relative-15:30ch;--ease-1:cubic-bezier(.25,0,.5,1);--ease-2:cubic-bezier(.25,0,.4,1);--ease-3:cubic-bezier(.25,0,.3,1);--ease-4:cubic-bezier(.25,0,.2,1);--ease-5:cubic-bezier(.25,0,.1,1);--ease-in-1:cubic-bezier(.25,0,1,1);--ease-in-2:cubic-bezier(.50,0,1,1);--ease-in-3:cubic-bezier(.70,0,1,1);--ease-in-4:cubic-bezier(.90,0,1,1);--ease-in-5:cubic-bezier(1,0,1,1);--ease-out-1:cubic-bezier(0,0,.75,1);--ease-out-2:cubic-bezier(0,0,.50,1);--ease-out-3:cubic-bezier(0,0,.3,1);--ease-out-4:cubic-bezier(0,0,.1,1);--ease-out-5:cubic-bezier(0,0,0,1);--ease-in-out-1:cubic-bezier(.1,0,.9,1);--ease-in-out-2:cubic-bezier(.3,0,.7,1);--ease-in-out-3:cubic-bezier(.5,0,.5,1);--ease-in-out-4:cubic-bezier(.7,0,.3,1);--ease-in-out-5:cubic-bezier(.9,0,.1,1);--ease-elastic-out-1:cubic-bezier(.5,.75,.75,1.25);--ease-elastic-out-2:cubic-bezier(.5,1,.75,1.25);--ease-elastic-out-3:cubic-bezier(.5,1.25,.75,1.25);--ease-elastic-out-4:cubic-bezier(.5,1.5,.75,1.25);--ease-elastic-out-5:cubic-bezier(.5,1.75,.75,1.25);--ease-elastic-in-1:cubic-bezier(.5,-0.25,.75,1);--ease-elastic-in-2:cubic-bezier(.5,-0.50,.75,1);--ease-elastic-in-3:cubic-bezier(.5,-0.75,.75,1);--ease-elastic-in-4:cubic-bezier(.5,-1.00,.75,1);--ease-elastic-in-5:cubic-bezier(.5,-1.25,.75,1);--ease-elastic-in-out-1:cubic-bezier(.5,-.1,.1,1.5);--ease-elastic-in-out-2:cubic-bezier(.5,-.3,.1,1.5);--ease-elastic-in-out-3:cubic-bezier(.5,-.5,.1,1.5);--ease-elastic-in-out-4:cubic-bezier(.5,-.7,.1,1.5);--ease-elastic-in-out-5:cubic-bezier(.5,-.9,.1,1.5);--ease-step-1:steps(2);--ease-step-2:steps(3);--ease-step-3:steps(4);--ease-step-4:steps(7);--ease-step-5:steps(10);--ease-elastic-1:var(--ease-elastic-out-1);--ease-elastic-2:var(--ease-elastic-out-2);--ease-elastic-3:var(--ease-elastic-out-3);--ease-elastic-4:var(--ease-elastic-out-4);--ease-elastic-5:var(--ease-elastic-out-5);--ease-squish-1:var(--ease-elastic-in-out-1);--ease-squish-2:var(--ease-elastic-in-out-2);--ease-squish-3:var(--ease-elastic-in-out-3);--ease-squish-4:var(--ease-elastic-in-out-4);--ease-squish-5:var(--ease-elastic-in-out-5);--ease-spring-1:linear(0,0.006,0.025 2.8%,0.101 6.1%,0.539 18.9%,0.721 25.3%,0.849 31.5%,0.937 38.1%,0.968 41.8%,0.991 45.7%,1.006 50.1%,1.015 55%,1.017 63.9%,1.001);--ease-spring-2:linear(0,0.007,0.029 2.2%,0.118 4.7%,0.625 14.4%,0.826 19%,0.902,0.962,1.008 26.1%,1.041 28.7%,1.064 32.1%,1.07 36%,1.061 40.5%,1.015 53.4%,0.999 61.6%,0.995 71.2%,1);--ease-spring-3:linear(0,0.009,0.035 2.1%,0.141 4.4%,0.723 12.9%,0.938 16.7%,1.017,1.077,1.121,1.149 24.3%,1.159,1.163,1.161,1.154 29.9%,1.129 32.8%,1.051 39.6%,1.017 43.1%,0.991,0.977 51%,0.974 53.8%,0.975 57.1%,0.997 69.8%,1.003 76.9%,1);--ease-spring-4:linear(0,0.009,0.037 1.7%,0.153 3.6%,0.776 10.3%,1.001,1.142 16%,1.185,1.209 19%,1.215 19.9% 20.8%,1.199,1.165 25%,1.056 30.3%,1.008 33%,0.973,0.955 39.2%,0.953 41.1%,0.957 43.3%,0.998 53.3%,1.009 59.1% 63.7%,0.998 78.9%,1);--ease-spring-5:linear(0,0.01,0.04 1.6%,0.161 3.3%,0.816 9.4%,1.046,1.189 14.4%,1.231,1.254 17%,1.259,1.257 18.6%,1.236,1.194 22.3%,1.057 27%,0.999 29.4%,0.955 32.1%,0.942,0.935 34.9%,0.933,0.939 38.4%,1 47.3%,1.011,1.017 52.6%,1.016 56.4%,1 65.2%,0.996 70.2%,1.001 87.2%,1);--ease-bounce-1:linear(0,0.004,0.016,0.035,0.063,0.098,0.141,0.191,0.25,0.316,0.391 36.8%,0.563,0.766,1 58.8%,0.946,0.908 69.1%,0.895,0.885,0.879,0.878,0.879,0.885,0.895,0.908 89.7%,0.946,1);--ease-bounce-2:linear(0,0.004,0.016,0.035,0.063,0.098,0.141 15.1%,0.25,0.391,0.562,0.765,1,0.892 45.2%,0.849,0.815,0.788,0.769,0.757,0.753,0.757,0.769,0.788,0.815,0.85,0.892 75.2%,1 80.2%,0.973,0.954,0.943,0.939,0.943,0.954,0.973,1);--ease-bounce-3:linear(0,0.004,0.016,0.035,0.062,0.098,0.141 11.4%,0.25,0.39,0.562,0.764,1 30.3%,0.847 34.8%,0.787,0.737,0.699,0.672,0.655,0.65,0.656,0.672,0.699,0.738,0.787,0.847 61.7%,1 66.2%,0.946,0.908,0.885 74.2%,0.879,0.878,0.879,0.885 79.5%,0.908,0.946,1 87.4%,0.981,0.968,0.96,0.957,0.96,0.968,0.981,1);--ease-bounce-4:linear(0,0.004,0.016 3%,0.062,0.141,0.25,0.391,0.562 18.2%,1 24.3%,0.81,0.676 32.3%,0.629,0.595,0.575,0.568,0.575,0.595,0.629,0.676 48.2%,0.811,1 56.2%,0.918,0.86,0.825,0.814,0.825,0.86,0.918,1 77.2%,0.94 80.6%,0.925,0.92,0.925,0.94 87.5%,1 90.9%,0.974,0.965,0.974,1);--ease-bounce-5:linear(0,0.004,0.016 2.5%,0.063,0.141,0.25 10.1%,0.562,1 20.2%,0.783,0.627,0.534 30.9%,0.511,0.503,0.511,0.534 38%,0.627,0.782,1 48.7%,0.892,0.815,0.769 56.3%,0.757,0.753,0.757,0.769 61.3%,0.815,0.892,1 68.8%,0.908 72.4%,0.885,0.878,0.885,0.908 79.4%,1 83%,0.954 85.5%,0.943,0.939,0.943,0.954 90.5%,1 93%,0.977,0.97,0.977,1);--ease-circ-in:cubic-bezier(.6,.04,.98,.335);--ease-circ-in-out:cubic-bezier(.785,.135,.15,.86);--ease-circ-out:cubic-bezier(.075,.82,.165,1);--ease-cubic-in:cubic-bezier(.55,.055,.675,.19);--ease-cubic-in-out:cubic-bezier(.645,.045,.355,1);--ease-cubic-out:cubic-bezier(.215,.61,.355,1);--ease-expo-in:cubic-bezier(.95,.05,.795,.035);--ease-expo-in-out:cubic-bezier(1,0,0,1);--ease-expo-out:cubic-bezier(.19,1,.22,1);--ease-quad-in:cubic-bezier(.55,.085,.68,.53);--ease-quad-in-out:cubic-bezier(.455,.03,.515,.955);--ease-quad-out:cubic-bezier(.25,.46,.45,.94);--ease-quart-in:cubic-bezier(.895,.03,.685,.22);--ease-quart-in-out:cubic-bezier(.77,0,.175,1);--ease-quart-out:cubic-bezier(.165,.84,.44,1);--ease-quint-in:cubic-bezier(.755,.05,.855,.06);--ease-quint-in-out:cubic-bezier(.86,0,.07,1);--ease-quint-out:cubic-bezier(.23,1,.32,1);--ease-sine-in:cubic-bezier(.47,0,.745,.715);--ease-sine-in-out:cubic-bezier(.445,.05,.55,.95);--ease-sine-out:cubic-bezier(.39,.575,.565,1);--layer-1:1;--layer-2:2;--layer-3:3;--layer-4:4;--layer-5:5;--layer-important:2147483647;--shadow-color:220 3% 15%;--shadow-strength:1%;--shadow-strength-3:calc(var(--shadow-strength) + 2%);--shadow-strength-4:calc(var(--shadow-strength) + 3%);--shadow-strength-5:calc(var(--shadow-strength) + 4%);--shadow-strength-6:calc(var(--shadow-strength) + 5%);--shadow-strength-7:calc(var(--shadow-strength) + 6%);--shadow-strength-8:calc(var(--shadow-strength) + 7%);--shadow-strength-10:calc(var(--shadow-strength) + 9%);--inner-shadow-highlight:inset 0 -.5px 0 0 #fff,inset 0 .5px 0 0 rgba(0,0,0,.067);--shadow-1:0 1px 2px -1px hsl(var(--shadow-color)/var(--shadow-strength-10));--shadow-2:0 3px 5px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 7px 14px -5px hsl(var(--shadow-color)/var(--shadow-strength-6));--shadow-3:0 -1px 3px 0 hsl(var(--shadow-color)/var(--shadow-strength-3)),0 1px 2px -5px hsl(var(--shadow-color)/var(--shadow-strength-3)),0 2px 5px -5px hsl(var(--shadow-color)/var(--shadow-strength-5)),0 4px 12px -5px hsl(var(--shadow-color)/var(--shadow-strength-6)),0 12px 15px -5px hsl(var(--shadow-color)/var(--shadow-strength-8));--shadow-4:0 -2px 5px 0 hsl(var(--shadow-color)/var(--shadow-strength-3)),0 1px 1px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 2px 2px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 5px 5px -2px hsl(var(--shadow-color)/var(--shadow-strength-5)),0 9px 9px -2px hsl(var(--shadow-color)/var(--shadow-strength-6)),0 16px 16px -2px hsl(var(--shadow-color)/var(--shadow-strength-7));--shadow-5:0 -1px 2px 0 hsl(var(--shadow-color)/var(--shadow-strength-3)),0 2px 1px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 5px 5px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 10px 10px -2px hsl(var(--shadow-color)/var(--shadow-strength-5)),0 20px 20px -2px hsl(var(--shadow-color)/var(--shadow-strength-6)),0 40px 40px -2px hsl(var(--shadow-color)/var(--shadow-strength-8));--shadow-6:0 -1px 2px 0 hsl(var(--shadow-color)/var(--shadow-strength-3)),0 3px 2px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 7px 5px -2px hsl(var(--shadow-color)/var(--shadow-strength-4)),0 12px 10px -2px hsl(var(--shadow-color)/var(--shadow-strength-5)),0 22px 18px -2px hsl(var(--shadow-color)/var(--shadow-strength-6)),0 41px 33px -2px hsl(var(--shadow-color)/var(--shadow-strength-7)),0 100px 80px -2px hsl(var(--shadow-color)/var(--shadow-strength-8));--inner-shadow-0:inset 0 0 0 1px hsl(var(--shadow-color)/var(--shadow-strength-10));--inner-shadow-1:inset 0 1px 2px 0 hsl(var(--shadow-color)/var(--shadow-strength-10)),var(--inner-shadow-highlight);--inner-shadow-2:inset 0 1px 4px 0 hsl(var(--shadow-color)/var(--shadow-strength-10)),var(--inner-shadow-highlight);--inner-shadow-3:inset 0 2px 8px 0 hsl(var(--shadow-color)/var(--shadow-strength-10)),var(--inner-shadow-highlight);--inner-shadow-4:inset 0 2px 14px 0 hsl(var(--shadow-color)/var(--shadow-strength-10)),var(--inner-shadow-highlight);--ratio-square:1;--ratio-landscape:4/3;--ratio-portrait:3/4;--ratio-widescreen:16/9;--ratio-ultrawide:18/5;--ratio-golden:1.6180/1;--gray-0:#f8f9fa;--gray-1:#f1f3f5;--gray-2:#e9ecef;--gray-3:#dee2e6;--gray-4:#ced4da;--gray-5:#adb5bd;--gray-6:#868e96;--gray-7:#495057;--gray-8:#343a40;--gray-9:#212529;--gray-10:#16191d;--gray-11:#0d0f12;--gray-12:#030507;--stone-0:#f8fafb;--stone-1:#f2f4f6;--stone-2:#ebedef;--stone-3:#e0e4e5;--stone-4:#d1d6d8;--stone-5:#b1b6b9;--stone-6:#979b9d;--stone-7:#7e8282;--stone-8:#666968;--stone-9:#50514f;--stone-10:#3a3a37;--stone-11:#252521;--stone-12:#121210;--red-0:#fff5f5;--red-1:#ffe3e3;--red-2:#ffc9c9;--red-3:#ffa8a8;--red-4:#ff8787;--red-5:#ff6b6b;--red-6:#fa5252;--red-7:#f03e3e;--red-8:#e03131;--red-9:#c92a2a;--red-10:#b02525;--red-11:#962020;--red-12:#7d1a1a;--pink-0:#fff0f6;--pink-1:#ffdeeb;--pink-2:#fcc2d7;--pink-3:#faa2c1;--pink-4:#f783ac;--pink-5:#f06595;--pink-6:#e64980;--pink-7:#d6336c;--pink-8:#c2255c;--pink-9:#a61e4d;--pink-10:#8c1941;--pink-11:#731536;--pink-12:#59102a;--purple-0:#f8f0fc;--purple-1:#f3d9fa;--purple-2:#eebefa;--purple-3:#e599f7;--purple-4:#da77f2;--purple-5:#cc5de8;--purple-6:#be4bdb;--purple-7:#ae3ec9;--purple-8:#9c36b5;--purple-9:#862e9c;--purple-10:#702682;--purple-11:#5a1e69;--purple-12:#44174f;--violet-0:#f3f0ff;--violet-1:#e5dbff;--violet-2:#d0bfff;--violet-3:#b197fc;--violet-4:#9775fa;--violet-5:#845ef7;--violet-6:#7950f2;--violet-7:#7048e8;--violet-8:#6741d9;--violet-9:#5f3dc4;--violet-10:#5235ab;--violet-11:#462d91;--violet-12:#3a2578;--indigo-0:#edf2ff;--indigo-1:#dbe4ff;--indigo-2:#bac8ff;--indigo-3:#91a7ff;--indigo-4:#748ffc;--indigo-5:#5c7cfa;--indigo-6:#4c6ef5;--indigo-7:#4263eb;--indigo-8:#3b5bdb;--indigo-9:#364fc7;--indigo-10:#2f44ad;--indigo-11:#283a94;--indigo-12:#21307a;--blue-0:#e7f5ff;--blue-1:#d0ebff;--blue-2:#a5d8ff;--blue-3:#74c0fc;--blue-4:#4dabf7;--blue-5:#339af0;--blue-6:#228be6;--blue-7:#1c7ed6;--blue-8:#1971c2;--blue-9:#1864ab;--blue-10:#145591;--blue-11:#114678;--blue-12:#0d375e;--cyan-0:#e3fafc;--cyan-1:#c5f6fa;--cyan-2:#99e9f2;--cyan-3:#66d9e8;--cyan-4:#3bc9db;--cyan-5:#22b8cf;--cyan-6:#15aabf;--cyan-7:#1098ad;--cyan-8:#0c8599;--cyan-9:#0b7285;--cyan-10:#095c6b;--cyan-11:#074652;--cyan-12:#053038;--teal-0:#e6fcf5;--teal-1:#c3fae8;--teal-2:#96f2d7;--teal-3:#63e6be;--teal-4:#38d9a9;--teal-5:#20c997;--teal-6:#12b886;--teal-7:#0ca678;--teal-8:#099268;--teal-9:#087f5b;--teal-10:#066649;--teal-11:#054d37;--teal-12:#033325;--green-0:#ebfbee;--green-1:#d3f9d8;--green-2:#b2f2bb;--green-3:#8ce99a;--green-4:#69db7c;--green-5:#51cf66;--green-6:#40c057;--green-7:#37b24d;--green-8:#2f9e44;--green-9:#2b8a3e;--green-10:#237032;--green-11:#1b5727;--green-12:#133d1b;--lime-0:#f4fce3;--lime-1:#e9fac8;--lime-2:#d8f5a2;--lime-3:#c0eb75;--lime-4:#a9e34b;--lime-5:#94d82d;--lime-6:#82c91e;--lime-7:#74b816;--lime-8:#66a80f;--lime-9:#5c940d;--lime-10:#4c7a0b;--lime-11:#3c6109;--lime-12:#2c4706;--yellow-0:#fff9db;--yellow-1:#fff3bf;--yellow-2:#ffec99;--yellow-3:#ffe066;--yellow-4:#ffd43b;--yellow-5:#fcc419;--yellow-6:#fab005;--yellow-7:#f59f00;--yellow-8:#f08c00;--yellow-9:#e67700;--yellow-10:#b35c00;--yellow-11:#804200;--yellow-12:#663500;--orange-0:#fff4e6;--orange-1:#ffe8cc;--orange-2:#ffd8a8;--orange-3:#ffc078;--orange-4:#ffa94d;--orange-5:#ff922b;--orange-6:#fd7e14;--orange-7:#f76707;--orange-8:#e8590c;--orange-9:#d9480f;--orange-10:#bf400d;--orange-11:#99330b;--orange-12:#802b09;--choco-0:#fff8dc;--choco-1:#fce1bc;--choco-2:#f7ca9e;--choco-3:#f1b280;--choco-4:#e99b62;--choco-5:#df8545;--choco-6:#d46e25;--choco-7:#bd5f1b;--choco-8:#a45117;--choco-9:#8a4513;--choco-10:#703a13;--choco-11:#572f12;--choco-12:#3d210d;--brown-0:#faf4eb;--brown-1:#ede0d1;--brown-2:#e0cab7;--brown-3:#d3b79e;--brown-4:#c5a285;--brown-5:#b78f6d;--brown-6:#a87c56;--brown-7:#956b47;--brown-8:#825b3a;--brown-9:#6f4b2d;--brown-10:#5e3a21;--brown-11:#4e2b15;--brown-12:#422412;--sand-0:#f8fafb;--sand-1:#e6e4dc;--sand-2:#d5cfbd;--sand-3:#c2b9a0;--sand-4:#aea58c;--sand-5:#9a9178;--sand-6:#867c65;--sand-7:#736a53;--sand-8:#5f5746;--sand-9:#4b4639;--sand-10:#38352d;--sand-11:#252521;--sand-12:#121210;--camo-0:#f9fbe7;--camo-1:#e8ed9c;--camo-2:#d2df4e;--camo-3:#c2ce34;--camo-4:#b5bb2e;--camo-5:#a7a827;--camo-6:#999621;--camo-7:#8c851c;--camo-8:#7e7416;--camo-9:#6d6414;--camo-10:#5d5411;--camo-11:#4d460e;--camo-12:#36300a;--jungle-0:#ecfeb0;--jungle-1:#def39a;--jungle-2:#d0e884;--jungle-3:#c2dd6e;--jungle-4:#b5d15b;--jungle-5:#a8c648;--jungle-6:#9bbb36;--jungle-7:#8fb024;--jungle-8:#84a513;--jungle-9:#7a9908;--jungle-10:#658006;--jungle-11:#516605;--jungle-12:#3d4d04;--gradient-space: ;--gradient-1:linear-gradient(to bottom right var(--gradient-space),#1f005c,#5b0060,#870160,#ac255e,#ca485c,#e16b5c,#f39060,#ffb56b);--gradient-2:linear-gradient(to bottom right var(--gradient-space),#48005c,#8300e2,#a269ff);--gradient-3:radial-gradient(circle at top right var(--gradient-space),#0ff,rgba(0,255,255,0)),radial-gradient(circle at bottom left var(--gradient-space),#ff1492,rgba(255,20,146,0));--gradient-4:linear-gradient(to bottom right var(--gradient-space),#00f5a0,#00d9f5);--gradient-5:conic-gradient(from -270deg at 75% 110% var(--gradient-space),#f0f,#fffaf0);--gradient-6:conic-gradient(from -90deg at top left var(--gradient-space),#000,#fff);--gradient-7:linear-gradient(to bottom right var(--gradient-space),#72c6ef,#004e8f);--gradient-8:conic-gradient(from 90deg at 50% 0% var(--gradient-space),#111,50%,#222,#111);--gradient-9:conic-gradient(from .5turn at bottom center var(--gradient-space),#add8e6,#fff);--gradient-10:conic-gradient(from 90deg at 40% -25% var(--gradient-space),gold,#f79d03,#ee6907,#e6390a,#de0d0d,#d61039,#cf1261,#c71585,#cf1261,#d61039,#de0d0d,#ee6907,#f79d03,gold,gold,gold);--gradient-11:conic-gradient(at bottom left var(--gradient-space),#ff1493,cyan);--gradient-12:conic-gradient(from 90deg at 25% -10% var(--gradient-space),#ff4500,#d3f340,#7bee85,#afeeee,#7bee85);--gradient-13:radial-gradient(circle at 50% 200% var(--gradient-space),#000142,#3b0083,#b300c3,#ff059f,#ff4661,#ffad86,#fff3c7);--gradient-14:conic-gradient(at top right var(--gradient-space),lime,cyan);--gradient-15:linear-gradient(to bottom right var(--gradient-space),#c7d2fe,#fecaca,#fef3c7);--gradient-16:radial-gradient(circle at 50% -250% var(--gradient-space),#374151,#111827,#000);--gradient-17:conic-gradient(from -90deg at 50% -25% var(--gradient-space),blue,#8a2be2);--gradient-18:linear-gradient(0deg var(--gradient-space),rgba(255,0,0,.8),rgba(255,0,0,0) 75%),linear-gradient(60deg var(--gradient-space),rgba(255,255,0,.8),rgba(255,255,0,0) 75%),linear-gradient(120deg var(--gradient-space),rgba(0,255,0,.8),rgba(0,255,0,0) 75%),linear-gradient(180deg var(--gradient-space),rgba(0,255,255,.8),rgba(0,255,255,0) 75%),linear-gradient(240deg var(--gradient-space),rgba(0,0,255,.8),rgba(0,0,255,0) 75%),linear-gradient(300deg var(--gradient-space),rgba(255,0,255,.8),rgba(255,0,255,0) 75%);--gradient-19:linear-gradient(to bottom right var(--gradient-space),#ffe259,#ffa751);--gradient-20:conic-gradient(from -135deg at -10% center var(--gradient-space),orange,#ff7715,#ff522a,#ff3f47,#ff5482,#ff69b4);--gradient-21:conic-gradient(from -90deg at 25% 115% var(--gradient-space),red,#f06,#f0c,#c0f,#60f,#00f,#00f,#00f,#00f);--gradient-22:linear-gradient(to bottom right var(--gradient-space),#acb6e5,#86fde8);--gradient-23:linear-gradient(to bottom right var(--gradient-space),#536976,#292e49);--gradient-24:conic-gradient(from .5turn at 0% 0% var(--gradient-space),#00c476,10%,#82b0ff,90%,#00c476);--gradient-25:conic-gradient(at 125% 50% var(--gradient-space),#b78cf7,#ff7c94,#ffcf0d,#ff7c94,#b78cf7);--gradient-26:linear-gradient(to bottom right var(--gradient-space),#9796f0,#fbc7d4);--gradient-27:conic-gradient(from .5turn at bottom left var(--gradient-space),#ff1493,#639);--gradient-28:conic-gradient(from -90deg at 50% 105% var(--gradient-space),#fff,orchid);--gradient-29:radial-gradient(circle at top right var(--gradient-space),#bfb3ff,rgba(191,179,255,0)),radial-gradient(circle at bottom left var(--gradient-space),#86acf9,rgba(134,172,249,0));--gradient-30:radial-gradient(circle at top right var(--gradient-space),#00ff80,rgba(0,255,128,0)),radial-gradient(circle at bottom left var(--gradient-space),#adffd6,rgba(173,255,214,0));--noise-1:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.005' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");--noise-2:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 300 300' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.05' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");--noise-3:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.25' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");--noise-4:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 2056 2056' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.5' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");--noise-5:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 2056 2056' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");--noise-filter-1:contrast(300%) brightness(100%);--noise-filter-2:contrast(200%) brightness(150%);--noise-filter-3:contrast(200%) brightness(250%);--noise-filter-4:contrast(200%) brightness(500%);--noise-filter-5:contrast(200%) brightness(1000%);--animation-fade-in:fade-in .5s var(--ease-3);--animation-fade-in-bloom:fade-in-bloom 2s var(--ease-3);--animation-fade-out:fade-out .5s var(--ease-3);--animation-fade-out-bloom:fade-out-bloom 2s var(--ease-3);--animation-scale-up:scale-up .5s var(--ease-3);--animation-scale-down:scale-down .5s var(--ease-3);--animation-slide-out-up:slide-out-up .5s var(--ease-3);--animation-slide-out-down:slide-out-down .5s var(--ease-3);--animation-slide-out-right:slide-out-right .5s var(--ease-3);--animation-slide-out-left:slide-out-left .5s var(--ease-3);--animation-slide-in-up:slide-in-up .5s var(--ease-3);--animation-slide-in-down:slide-in-down .5s var(--ease-3);--animation-slide-in-right:slide-in-right .5s var(--ease-3);--animation-slide-in-left:slide-in-left .5s var(--ease-3);--animation-shake-x:shake-x .75s var(--ease-out-5);--animation-shake-y:shake-y .75s var(--ease-out-5);--animation-shake-z:shake-z 1s var(--ease-in-out-3);--animation-spin:spin 2s linear infinite;--animation-ping:ping 5s var(--ease-out-3) infinite;--animation-blink:blink 1s var(--ease-out-3) infinite;--animation-float:float 3s var(--ease-in-out-3) infinite;--animation-bounce:bounce 2s var(--ease-squish-2) infinite;--animation-pulse:pulse 2s var(--ease-out-3) infinite;--border-size-1:1px;--border-size-2:2px;--border-size-3:5px;--border-size-4:10px;--border-size-5:25px;--radius-1:2px;--radius-2:5px;--radius-3:1rem;--radius-4:2rem;--radius-5:4rem;--radius-6:8rem;--radius-drawn-1:255px 15px 225px 15px/15px 225px 15px 255px;--radius-drawn-2:125px 10px 20px 185px/25px 205px 205px 25px;--radius-drawn-3:15px 255px 15px 225px/225px 15px 255px 15px;--radius-drawn-4:15px 25px 155px 25px/225px 150px 25px 115px;--radius-drawn-5:250px 25px 15px 20px/15px 80px 105px 115px;--radius-drawn-6:28px 100px 20px 15px/150px 30px 205px 225px;--radius-round:1e5px;--radius-blob-1:30% 70% 70% 30%/53% 30% 70% 47%;--radius-blob-2:53% 47% 34% 66%/63% 46% 54% 37%;--radius-blob-3:37% 63% 56% 44%/49% 56% 44% 51%;--radius-blob-4:63% 37% 37% 63%/43% 37% 63% 57%;--radius-blob-5:49% 51% 48% 52%/57% 44% 56% 43%;--radius-conditional-1:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-1));--radius-conditional-2:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-2));--radius-conditional-3:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-3));--radius-conditional-4:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-4));--radius-conditional-5:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-5));--radius-conditional-6:clamp(0px,calc(100vw - 100%) * 1e5,var(--radius-6));--palette-hue:250;--palette-hue-rotate-by:0;--palette-chroma:0.15;--color-1:oklch(98% calc(var(--palette-chroma)*0.03) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*0));--color-2:oklch(97% calc(var(--palette-chroma)*0.06) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*1));--color-3:oklch(93% calc(var(--palette-chroma)*0.1) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*2));--color-4:oklch(84% calc(var(--palette-chroma)*0.12) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*3));--color-5:oklch(80% calc(var(--palette-chroma)*0.16) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*4));--color-6:oklch(71% calc(var(--palette-chroma)*0.19) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*5));--color-7:oklch(66% calc(var(--palette-chroma)*0.2) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*6));--color-8:oklch(58% calc(var(--palette-chroma)*0.21) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*7));--color-9:oklch(53% calc(var(--palette-chroma)*0.2) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*8));--color-10:oklch(49% calc(var(--palette-chroma)*0.19) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*9));--color-11:oklch(42% calc(var(--palette-chroma)*0.17) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*10));--color-12:oklch(35% calc(var(--palette-chroma)*0.15) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*11));--color-13:oklch(27% calc(var(--palette-chroma)*0.12) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*12));--color-14:oklch(20% calc(var(--palette-chroma)*0.09) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*13));--color-15:oklch(16% calc(var(--palette-chroma)*0.07) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*14));--color-16:oklch(10% calc(var(--palette-chroma)*0.05) calc(var(--palette-hue) + var(--palette-hue-rotate-by)*15))}@media (prefers-color-scheme:dark){:where(html){--shadow-color:220 40% 2%;--shadow-strength:25%;--inner-shadow-highlight:inset 0 -.5px 0 0 hsla(0,0%,100%,.067),inset 0 .5px 0 0 rgba(0,0,0,.467)}}@supports (background:linear-gradient(to right in oklab,#000,#fff)){:where(html){--gradient-space:in oklab}}@keyframes fade-in{to{opacity:1}}@keyframes fade-in-bloom{0%{filter:brightness(1) blur(20px);opacity:0}10%{filter:brightness(2) blur(10px);opacity:1}to{filter:brightness(1) blur(0);opacity:1}}@keyframes fade-out{to{opacity:0}}@keyframes fade-out-bloom{to{filter:brightness(1) blur(20px);opacity:0}10%{filter:brightness(2) blur(10px);opacity:1}0%{filter:brightness(1) blur(0);opacity:1}}@keyframes scale-up{to{transform:scale(1.25)}}@keyframes scale-down{to{transform:scale(.75)}}@keyframes slide-out-up{to{transform:translateY(-100%)}}@keyframes slide-out-down{to{transform:translateY(100%)}}@keyframes slide-out-right{to{transform:translateX(100%)}}@keyframes slide-out-left{to{transform:translateX(-100%)}}@keyframes slide-in-up{0%{transform:translateY(100%)}}@keyframes slide-in-down{0%{transform:translateY(-100%)}}@keyframes slide-in-right{0%{transform:translateX(-100%)}}@keyframes slide-in-left{0%{transform:translateX(100%)}}@keyframes shake-x{0%,to{transform:translateX(0)}20%{transform:translateX(-5%)}40%{transform:translateX(5%)}60%{transform:translateX(-5%)}80%{transform:translateX(5%)}}@keyframes shake-y{0%,to{transform:translateY(0)}20%{transform:translateY(-5%)}40%{transform:translateY(5%)}60%{transform:translateY(-5%)}80%{transform:translateY(5%)}}@keyframes shake-z{0%,to{transform:rotate(0deg)}20%{transform:rotate(-2deg)}40%{transform:rotate(2deg)}60%{transform:rotate(-2deg)}80%{transform:rotate(2deg)}}@keyframes spin{to{transform:rotate(1turn)}}@keyframes ping{90%,to{opacity:0;transform:scale(2)}}@keyframes blink{0%,to{opacity:1}50%{opacity:.5}}@keyframes float{50%{transform:translateY(-25%)}}@keyframes bounce{25%{transform:translateY(-20%)}40%{transform:translateY(-3%)}0%,60%,to{transform:translateY(0)}}@keyframes pulse{50%{transform:scale(.9)}}@media (prefers-color-scheme:dark){@keyframes fade-in-bloom{0%{filter:brightness(1) blur(20px);opacity:0}10%{filter:brightness(.5) blur(10px);opacity:1}to{filter:brightness(1) blur(0);opacity:1}}}@media (prefers-color-scheme:dark){@keyframes fade-out-bloom{to{filter:brightness(1) blur(20px);opacity:0}10%{filter:brightness(.5) blur(10px);opacity:1}0%{filter:brightness(1) blur(0);opacity:1}}}


/* ── fonts.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   fonts.css — Inter auto-hospedado (fuente única, sans-serif)
   Subsets: latin + latin-ext. Pesos: 400, 500, 600, 700.
   Origen: Google Fonts. Auto-hospedado para CSP estricto y offline en Capacitor.
   ═══════════════════════════════════════════════════════════════════════════ */

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/inter-400-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/inter-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('../fonts/inter-500-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('../fonts/inter-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('../fonts/inter-600-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('../fonts/inter-600-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/inter-700-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/inter-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}



:root {

    /* ── Paleta — Ant Design v5 ──────────────────────────────────────────── */

    /* Primary — Daybreak Blue */
    --color-primary:        #1677FF;
    --color-primary-hover:  #4096FF;
    --color-primary-active: #0958D9;
    --color-primary-soft:   #E6F4FF;    /* blue-1 */
    --color-primary-border: #91CAFF;    /* blue-3 */

    /* Estados semánticos — paleta AntD */
    --color-success:        #52C41A;
    --color-success-hover:  #73D13D;
    --color-success-soft:   #F6FFED;

    --color-warning:        #FAAD14;
    --color-warning-hover:  #FFC53D;
    --color-warning-soft:   #FFFBE6;

    --color-danger:         #FF4D4F;
    --color-danger-hover:   #FF7875;
    --color-danger-soft:    #FFF1F0;

    --color-info:           var(--color-primary);
    --color-info-soft:      var(--color-primary-soft);

    /* Fondos / superficies */
    --color-bg:             #FFFFFF;
    --color-bg-alt:         #FAFAFA;     /* gris-1 AntD */
    --color-bg-layout:      #F0F2F5;     /* fondo del body — AntD Pro Classic (más contraste con cards) */
    --color-bg-elevated:    #FFFFFF;

    /* Sidebar dark — AntD Pro */
    --color-sidebar-bg:         #001529;
    --color-sidebar-bg-darker:  #000C17;
    --color-sidebar-text:       rgba(255, 255, 255, 0.88);
    --color-sidebar-text-muted: rgba(255, 255, 255, 0.55);
    --color-sidebar-border:     rgba(255, 255, 255, 0.08);
    --color-sidebar-hover:      rgba(255, 255, 255, 0.08);
    --color-sidebar-active:     var(--color-primary);

    /* Bordes */
    --color-border:         #D9D9D9;
    --color-border-subtle:  #E8E8E8;
    --color-border-strong:  #BFBFBF;

    /* Texto — alpha-on-black (patrón AntD, modo oscuro friendly) */
    --color-text:           rgba(0, 0, 0, 0.88);    /* primary */
    --color-text-muted:     rgba(0, 0, 0, 0.65);    /* secondary */
    --color-text-subtle:    rgba(0, 0, 0, 0.45);    /* tertiary / disabled */
    --color-text-on-dark:   #FFFFFF;

    /* ── Tipografía — Inter (fuente única sans) ──────────────────────────── */
    --font-sans:
        'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
        'Helvetica Neue', Arial, sans-serif;

    --font-body:    var(--font-sans);
    --font-heading: var(--font-sans);

    /* Escala de tamaños */
    --font-size-xs:    0.75rem;    /* 12px */
    --font-size-sm:    0.875rem;   /* 14px */
    --font-size-base:  0.875rem;   /* 14px — AntD usa 14 como base */
    --font-size-md:    1rem;       /* 16px */
    --font-size-lg:    1.125rem;   /* 18px */
    --font-size-xl:    1.25rem;    /* 20px — AntD heading-3 */
    --font-size-2xl:   1.5rem;     /* 24px — AntD heading-2 */
    --font-size-3xl:   1.875rem;   /* 30px — AntD heading-1 */

    /* Pesos */
    --font-weight-regular:  400;
    --font-weight-medium:   500;
    --font-weight-semibold: 600;
    --font-weight-bold:     700;

    /* Line heights */
    --line-height-tight:   1.3;
    --line-height-base:    1.5715;   /* line-height de AntD */

    --letter-spacing-wide: 0.05em;

    /* ── Espaciados ──────────────────────────────────────────────────────── */
    --space-xs:   4px;
    --space-sm:   8px;
    --space-md:   16px;
    --space-lg:   24px;
    --space-xl:   32px;
    --space-2xl:  48px;
    --space-3xl:  64px;

    /* ── Radius — AntD v5 (sutiles, menos generosos) ─────────────────────── */
    --radius-sm:  4px;      /* chips, badges */
    --radius-md:  6px;      /* inputs, botones */
    --radius-lg:  8px;      /* cards, modales */
    --radius-xl:  12px;     /* elementos grandes */
    --radius-full: 999px;   /* pills, avatares */

    /* ── Sombras — AntD v5 (neutras, no tintadas) ────────────────────────── */
    --shadow-sm:
        0 1px 2px 0 rgba(0, 0, 0, 0.05),
        0 1px 6px -1px rgba(0, 0, 0, 0.04),
        0 2px 4px 0 rgba(0, 0, 0, 0.03);

    --shadow-md:
        0 6px 16px 0 rgba(0, 0, 0, 0.08),
        0 3px 6px -4px rgba(0, 0, 0, 0.12),
        0 9px 28px 8px rgba(0, 0, 0, 0.05);

    --shadow-lg:
        0 6px 16px 0 rgba(0, 0, 0, 0.08),
        0 9px 28px 8px rgba(0, 0, 0, 0.05),
        0 12px 48px 16px rgba(0, 0, 0, 0.03);

    --shadow-xl: var(--shadow-lg);

    /* Focus ring — AntD usa 2px solid primary con soft outline */
    --focus-ring: 0 0 0 2px var(--color-primary-soft);

    /* ── Transiciones ────────────────────────────────────────────────────── */
    --transition-fast:   0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
    --transition-normal: 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
    --transition-slow:   0.3s cubic-bezier(0.645, 0.045, 0.355, 1);

    /* ── Layout ──────────────────────────────────────────────────────────── */
    --header-height:           56px;
    --bottom-nav-height:       64px;
    --footer-height:           40px;  /* aprox del .app-footer — usado por overlay de modal/drawer */
    --sidebar-width:           240px;
    --sidebar-width-collapsed: 64px;
    --content-max-width:       1280px;
    --form-max-width:          480px;

    --container-xs:            28rem;
    --container-sm:            32rem;
    --container-md:            40rem;
    --container-lg:            48rem;
    --container-xl:            var(--content-max-width);

    /* Breakpoints */
    --bp-sm:                   40rem;
    --bp-md:                   48rem;
    --bp-lg:                   64rem;
    --bp-xl:                   80rem;
}

/* Fondo del body — gris muy suave tipo AntD Pro */
body {
    background-color: var(--color-bg-layout);
}



/* 2. Reset — normalización mínima del navegador */

/* ── reset.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   reset.css — Reset mínimo y estilos base
   Solo corrige comportamientos del navegador, sin opinión de diseño.
   ═══════════════════════════════════════════════════════════════════════════ */

*,
*::before,
*::after {
    box-sizing: border-box;
}

* {
    margin: 0;
    padding: 0;
}

html {
    /* Escala fluida de tipografía base */
    font-size: 100%;
    -webkit-text-size-adjust: 100%;
    hanging-punctuation: first last;
    -webkit-tap-highlight-color: transparent;   /* quita el flash azul al tocar en Android */
}

/* Scroll lock — el body no scrollea cuando hay un modal/drawer abierto.
   Se aplica via JS añadiendo la clase cuando un overlay se hace visible. */
html.is-scroll-locked,
html.is-scroll-locked body {
    overflow: hidden;
    touch-action: none;
    overscroll-behavior: contain;
}

/* Accesibilidad — respeta la preferencia del sistema operativo de reducir animaciones */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Focus-visible consistente en todos los controles interactivos.
   Solo aparece con navegación por teclado (no con clicks de ratón). */
:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Anti-flash en restauración de scroll tras F5.
   El navegador pinta el <main> en scrollTop=0 antes de que app.js aplique
   la posición guardada en sessionStorage, causando un parpadeo de los KPIs.
   Mientras html tiene el atributo, ocultamos el main; app.js lo retira tras
   aplicar scrollTop dentro de un doble requestAnimationFrame. */
html[data-scroll-restoring] .layout-app__main,
html[data-scroll-restoring] .layout-cliente__main {
    visibility: hidden;
}

/* (Revisado) Page fade-in entre navegaciones — descartado.
   Causaba un flash desagradable porque el sidebar/header se pintaban primero
   con el main aún en opacity:0. AntD Pro v5+, Linear, GitHub no animan
   transiciones de página justamente por esto. La velocidad percibida gana. */

body {
    font-family: var(--font-body);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-regular);
    color: var(--color-text);
    background-color: var(--color-bg);
    line-height: var(--line-height-base);
    /* min-height emparejado con el layout: svh (smallest viewport).
       Con `100dvh` (dinámico), cuando la URL bar del browser se retraía,
       dvh crecía y body se hacía más alto que .layout-app (que ahora es
       100svh fijo). Resultado: body overflowea → scroll root activo →
       el usuario scrolleaba todo el body arriba/abajo, el header "se
       escondía". Con 100svh emparejado, body y layout son exactamente
       del mismo tamaño → no hay overflow root → solo scrollea main. */
    min-height: 100dvh;   /* fallback */
    min-height: 100svh;   /* moderno, estable */
    overflow: hidden;     /* blindaje extra: nunca scroll a nivel root */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    /* Safe-area strip en mobile — ver body::after abajo */
}

@media (max-width: 48rem) {
    body::after {
        content: '';
        position: fixed;
        inset-inline: 0;
        inset-block-end: 0;
        block-size: env(safe-area-inset-bottom, 0);
        background-color: var(--color-bg-elevated);
        pointer-events: none;
        z-index: 50;
    }

    /* Dentro de Capacitor WebView nativo, la NavigationBar del plugin
       Capacitor maneja el color de la zona inferior. La strip web sería
       redundante (y podría no coincidir en color con la nav nativa), así
       que la ocultamos. Se mantiene el safe-area respect en layouts
       (calc(svh - env)) — solo el pintado visual delegado al plugin. */
    html[data-capacitor="true"] body::after {
        display: none;
    }
}

/* ── Jerarquía de títulos — semántica acorde a docs/diseno.md punto 2 ── */
/* Jerarquía AntD — todos los títulos en Inter semibold.
   Escala más compacta que antes, base 14px. */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    color: var(--color-text);
}

h1 { font-size: var(--font-size-3xl); }   /* 30px */
h2 { font-size: var(--font-size-2xl); }   /* 24px */
h3 { font-size: var(--font-size-xl); }    /* 20px */
h4 { font-size: var(--font-size-lg); }    /* 18px */
h5 { font-size: var(--font-size-md); }    /* 16px */
h6 { font-size: var(--font-size-base); }  /* 14px */

/* Texto pequeño semántico */
small {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Code inline */
code, kbd, samp {
    font-family: var(--font-mono, ui-monospace, 'SF Mono', 'Cascadia Mono', 'Roboto Mono', Consolas, monospace);
    font-size: 0.9em;
}

/* Imágenes y medios responsivos por defecto */
img,
picture,
video,
canvas,
svg {
    display: block;
    max-width: 100%;
}

/* Formularios heredan tipografía */
input,
button,
textarea,
select {
    font: inherit;
}

/* Evitar desbordamiento de texto en elementos de bloque */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
    overflow-wrap: break-word;
}

/* Eliminar estilos de lista cuando se usan con role="list" */
ul[role='list'],
ol[role='list'] {
    list-style: none;
}

/* ── Focus visible global — accesibilidad WCAG ─────────────────────────────
   Outline solo cuando el usuario navega con teclado (no con click de ratón).
   Color del primary para mantener coherencia con la marca.                  */
:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Mejorar rendimiento de animaciones */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}



/* 3. Composition — layouts y estructuras espaciales */

/* ── composition.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   composition.css — CUBE CSS: Composition
   Define estructuras de layout reutilizables.
   No aplica colores, tipografía ni estética — solo disposición espacial.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Contenedor centrado ─────────────────────────────────────────────────── */
.container {
    max-width: none;
    margin-inline: auto;
    padding-inline: var(--space-md);
}

@media (min-width: 48rem) {
    .container {
        padding-inline: var(--space-lg);
    }
}

/* Anchos canónicos — solo se aplican si la página los pide explícitamente
   (auth, perfil, confirmaciones). Por defecto el container es full-width
   estilo AntD Pro (aprovecha todo el ancho entre sidebar y borde). */

/* Anchos canónicos del container */
.container[data-width="xs"] { max-width: var(--container-xs); }
.container[data-width="sm"] { max-width: var(--container-sm); }
.container[data-width="md"] { max-width: var(--container-md); }
.container[data-width="lg"] { max-width: var(--container-lg); }
.container[data-width="xl"] { max-width: var(--container-xl); }

/* Padding vertical del container — para páginas que lo necesiten sin hijos adicionales */
.container[data-pad-y="md"]  { padding-block: var(--space-md); }
.container[data-pad-y="lg"]  { padding-block: var(--space-lg); }
.container[data-pad-y="xl"]  { padding-block: var(--space-xl); }
.container[data-pad-y="2xl"] { padding-block: var(--space-2xl); }

/* ── Layout de aplicación con sidebar ───────────────────────────────────── */
.layout-app {
    display: grid;
    grid-template-columns: var(--sidebar-width) 1fr;
    grid-template-rows: var(--header-height) 1fr auto;
    grid-template-areas:
        "sidebar header"
        "sidebar main"
        "sidebar footer";
    /* Layout altura estable + respeta safe-area inferior.
         · svh (no dvh): el layout NO se resizea cuando Chrome Mobile /
           iOS Safari esconden la URL bar durante el scroll. Sticky/fixed
           estables, sin jitter.
         · calc(100svh - env(safe-area-inset-bottom)): con viewport-fit=
           cover, svh incluye la zona bajo la barra Android/iOS. Si no
           restamos, contenido al final del layout queda bajo la barra
           (letras con descenders cortadas, etc.). Restando, el layout
           termina SOBRE la barra; el gap inferior lo cubre body::after
           strip en color elevated.
       Fallback chain: dvh → svh → svh con resta (browsers nuevos). */
    height: 100dvh;
    height: 100svh;
    height: calc(100svh - env(safe-area-inset-bottom, 0));
    overflow: hidden;     /* Previene doble scroll si algún hijo se desborda */
    transition: grid-template-columns var(--transition-normal);
}

.layout-app__footer { grid-area: footer; }

/* Collapse solo aplica en desktop — en móvil el sidebar es drawer.
   Se lee del <html> (seteado por script síncrono en _base.html.twig)
   para que aplique ANTES de que Alpine bootee — evita flash de expandido. */
@media (min-width: 48rem) {
    html[data-sidebar-collapsed="true"] .layout-app,
    .layout-app[data-sidebar-collapsed="true"] {
        grid-template-columns: var(--sidebar-width-collapsed) 1fr;
    }
}

.layout-app__sidebar { grid-area: sidebar; }
.layout-app__header  { grid-area: header; }
.layout-app__main    {
    grid-area: main;
    overflow-y: auto;
    /* Desactiva el rubber-band / overscroll bounce nativo al llegar al
       borde del scroll. Sin esto, el browser anima un "rebote elástico"
       en Chrome Android / iOS Safari — durante ese rebote los sticky
       interiores (config-footer) parecen "saltar" un par de pixels
       arriba/abajo. `contain` también bloquea el scroll-chaining hacia
       el body/html (defensa en profundidad ante cualquier overflow). */
    overscroll-behavior: contain;
}

/* En móvil (admin): sidebar como drawer, SIN bottom-nav.
   BP: --bp-md (48rem / 768px) — ver tokens.css */
@media (max-width: 48rem) {  /* --bp-md */
    .layout-app {
        grid-template-columns: 1fr;
        grid-template-rows: var(--header-height) 1fr;
        grid-template-areas:
            "header"
            "main";
    }

    .layout-app__sidebar {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        z-index: 100;
        transform: translateX(-100%);
        transition: transform var(--transition-normal);
        max-width: 280px;            /* deja parte del contenido visible detrás */
    }

    .layout-app__sidebar[data-open="true"] {
        transform: translateX(0);
    }

    /* Backdrop — se muestra al abrir el drawer en móvil. Click en él cierra
       el sidebar (patrón estándar AntD Pro / Material).                      */
    .layout-app__backdrop {
        position: fixed;
        inset: 0;
        background-color: rgba(0, 0, 0, 0.45);
        z-index: 99;                 /* justo debajo del sidebar (z:100) */
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--transition-normal);
    }

    .layout-app__backdrop[data-open="true"] {
        opacity: 1;
        pointer-events: auto;
    }

    .layout-app__bottom { grid-area: bottom; }
}

/* En desktop el backdrop nunca aparece — sidebar siempre integrado en grid */
@media (min-width: 48rem) {
    .layout-app__backdrop {
        display: none;
    }
}

/* ── Flow — espaciado vertical entre hijos ──────────────────────────────── */
.flow > * + * {
    margin-block-start: var(--flow-space, var(--space-md));
}

/* Variantes de separación del flow */
.flow[data-space="xs"]  { --flow-space: var(--space-xs); }
.flow[data-space="sm"]  { --flow-space: var(--space-sm); }
.flow[data-space="md"]  { --flow-space: var(--space-md); }
.flow[data-space="lg"]  { --flow-space: var(--space-lg); }
.flow[data-space="xl"]  { --flow-space: var(--space-xl); }

/* ── Cluster — grupo de elementos con espacio entre ellos ───────────────── */
.cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cluster-gap, var(--space-sm));
    align-items: var(--cluster-align, center);
    justify-content: var(--cluster-justify, flex-start);
}

/* Variantes del cluster */
.cluster[data-gap="xs"]  { --cluster-gap: var(--space-xs); }
.cluster[data-gap="sm"]  { --cluster-gap: var(--space-sm); }
.cluster[data-gap="md"]  { --cluster-gap: var(--space-md); }
.cluster[data-gap="lg"]  { --cluster-gap: var(--space-lg); }

.cluster[data-justify="start"]   { --cluster-justify: flex-start; }
.cluster[data-justify="center"]  { --cluster-justify: center; }
.cluster[data-justify="end"]     { --cluster-justify: flex-end; }
.cluster[data-justify="between"] { --cluster-justify: space-between; }

.cluster[data-align="start"]    { --cluster-align: flex-start; }
.cluster[data-align="center"]   { --cluster-align: center; }
.cluster[data-align="end"]      { --cluster-align: flex-end; }
.cluster[data-align="stretch"]  { --cluster-align: stretch; }

/* ── Stack — columna con espacio entre hijos ────────────────────────────── */
.stack {
    display: flex;
    flex-direction: column;
    gap: var(--stack-gap, var(--space-md));
}

/* Variantes del stack */
.stack[data-gap="xs"]  { --stack-gap: var(--space-xs); }
.stack[data-gap="sm"]  { --stack-gap: var(--space-sm); }
.stack[data-gap="md"]  { --stack-gap: var(--space-md); }
.stack[data-gap="lg"]  { --stack-gap: var(--space-lg); }
.stack[data-gap="xl"]  { --stack-gap: var(--space-xl); }

/* ── Grid automático responsive ─────────────────────────────────────────── */
.auto-grid {
    display: grid;
    grid-template-columns: repeat(
        auto-fill,
        minmax(var(--auto-grid-min, 16rem), 1fr)
    );
    gap: var(--space-md);
}

/* ── Sidebar layout (contenido + aside) ─────────────────────────────────── */
.with-sidebar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
}

.with-sidebar > :first-child {
    flex-basis: var(--sidebar-width);
    flex-grow: 1;
}

.with-sidebar > :last-child {
    flex-basis: 0;
    flex-grow: 999;
    min-inline-size: 50%;
}



/* 4. Utilities — clases de un propósito */

/* ── utilities.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   utilities.css — CUBE CSS: Utilities
   Clases de un único propósito. Selectivas — no generar utilidades para todo.
   Si necesitas más de 3 utilidades juntas en un elemento, crea un bloque.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Alpine — ocultar hasta que Alpine inicialice ────────────────────────── */
/* Reemplaza `style="display: none"` en elementos con x-show para evitar
   flash de contenido sin estilar antes de que Alpine se cargue.            */
[x-cloak] { display: none !important; }

/* ── Margen reset — formularios sueltos dentro de flex/grid ──────────────── */
.no-margin { margin: 0; }

/* ── Anchos máximos — centran el contenido horizontalmente ──────────────── */
/* Útil cuando el elemento NO es el container principal (que ya se ocupa
   del max-width + padding-inline). Solo restringen el ancho.               */
.max-w-xs { max-width: var(--container-xs); margin-inline: auto; }
.max-w-sm { max-width: var(--container-sm); margin-inline: auto; }
.max-w-md { max-width: var(--container-md); margin-inline: auto; }
.max-w-lg { max-width: var(--container-lg); margin-inline: auto; }

/* ── Accesibilidad ───────────────────────────────────────────────────────── */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

/* ── Visibilidad ─────────────────────────────────────────────────────────── */
.hidden  { display: none; }

/* ── Alineación de texto ─────────────────────────────────────────────────── */
.text-center { text-align: center; }
.text-right  { text-align: right; }
.text-left   { text-align: left; }

/* ── Tamaño de texto ─────────────────────────────────────────────────────── */
.text-xs   { font-size: var(--font-size-xs); }
.text-sm   { font-size: var(--font-size-sm); }
.text-base { font-size: var(--font-size-base); }
.text-lg   { font-size: var(--font-size-lg); }
.text-xl   { font-size: var(--font-size-xl); }
.text-2xl  { font-size: var(--font-size-2xl); }
.text-3xl  { font-size: var(--font-size-3xl); }

/* ── Peso de texto ───────────────────────────────────────────────────────── */
.font-bold    { font-weight: var(--font-weight-bold); }
.font-medium  { font-weight: var(--font-weight-medium); }
.font-regular { font-weight: var(--font-weight-regular); }

/* ── Estilo de texto ─────────────────────────────────────────────────────── */
.text-uppercase {
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
}

/* ── Colores de texto ────────────────────────────────────────────────────── */
.text-muted   { color: var(--color-text-muted); }
.text-subtle  { color: var(--color-text-subtle); }
.text-danger  { color: var(--color-danger); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-primary { color: var(--color-primary); }
.text-info    { color: var(--color-info); }

/* ── Colores de fondo ────────────────────────────────────────────────────── */
.bg-alt           { background-color: var(--color-bg-alt); }
.bg-elevated      { background-color: var(--color-bg-elevated); }
.bg-primary-soft  { background-color: var(--color-primary-soft); }
.bg-success-soft  { background-color: var(--color-success-soft); }
.bg-warning-soft  { background-color: var(--color-warning-soft); }
.bg-danger-soft   { background-color: var(--color-danger-soft); }

/* ── Márgenes selectivos ─────────────────────────────────────────────────── */
.mt-auto { margin-block-start: auto; }
.mb-auto { margin-block-end: auto; }
.ms-auto { margin-inline-start: auto; }
.me-auto { margin-inline-end: auto; }
.m-auto  { margin: auto; }

/* ── Truncado de texto ───────────────────────────────────────────────────── */
.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Iconos (Lucide) — SVG inline desde resources/views/icons/ ───────────── */
/* Por defecto el SVG hereda color (currentColor) y tamaño (1em).
   Variantes con clase modificadora para tamaños fijos.                       */
.icon {
    display: inline-block;
    width: 1em;
    height: 1em;
    flex-shrink: 0;
    vertical-align: -0.125em;       /* alinea con el baseline del texto */
}

.icon--sm { width: 14px; height: 14px; }
.icon--md { width: 18px; height: 18px; }
.icon--lg { width: 24px; height: 24px; }
.icon--xl { width: 32px; height: 32px; }

/* ── Indicador de carga HTMX ─────────────────────────────────────────────── */
.htmx-indicator {
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
    opacity: 1;
}

/* ── Line clamp — truncar texto a N líneas ──────────────────────────── */
.line-clamp-1 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; }
.line-clamp-2 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.line-clamp-3 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }



/* 5. Blocks — componentes (un archivo por bloque) */

/* ── button.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/button.css — Bloque: botón
   Variantes mediante data-variant y data-size (CUBE CSS Exceptions).
   ═══════════════════════════════════════════════════════════════════════════ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    padding: 0.625rem var(--space-md);   /* 10px / 16px — algo más alto que el default */
    border: 1px solid transparent;
    border-radius: var(--radius-md);     /* 10px — estilo referencia */
    font-family: var(--font-body);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1.2;
    cursor: pointer;
    text-decoration: none;
    transition:
        background var(--transition-fast),
        border-color var(--transition-fast),
        color var(--transition-fast),
        box-shadow var(--transition-fast),
        transform var(--transition-fast);
    user-select: none;
}

.btn:active:not(:disabled) {
    transform: translateY(1px);
}

.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Variantes (data-variant) ────────────────────────────────────────────── */

.btn[data-variant="primary"] {
    background-color: var(--color-primary);
    color: var(--color-text-on-dark);
    box-shadow: var(--shadow-sm);
}

.btn[data-variant="primary"]:hover:not(:disabled) {
    background-color: var(--color-primary-hover);
}

.btn[data-variant="primary"]:active:not(:disabled) {
    background-color: var(--color-primary-active);
}

.btn[data-variant="danger"] {
    background-color: var(--color-danger);
    color: var(--color-text-on-dark);
}

.btn[data-variant="danger"]:hover:not(:disabled) {
    background-color: var(--color-danger-hover);
}

.btn[data-variant="success"] {
    background-color: var(--color-success);
    color: var(--color-text-on-dark);
}

.btn[data-variant="success"]:hover:not(:disabled) {
    background-color: var(--color-success-hover);
}

.btn[data-variant="ghost"] {
    background-color: var(--color-bg-elevated);
    border-color: var(--color-border);
    color: var(--color-text);
}

.btn[data-variant="ghost"]:hover:not(:disabled) {
    background-color: var(--color-bg-alt);
    border-color: var(--color-border-strong);
}

/* Ghost activo — para botones tipo "Filtros (3)" cuando hay estado aplicado.
   Borde + texto en primary, fondo soft. Indica que está "encendido". */
.btn[data-variant="ghost-active"] {
    background-color: var(--color-primary-soft);
    border-color: var(--color-primary);
    color: var(--color-primary);
}

.btn[data-variant="ghost-active"]:hover:not(:disabled) {
    background-color: var(--color-primary-soft);
    border-color: var(--color-primary-hover);
    color: var(--color-primary-hover);
}

.btn[data-variant="link"] {
    background-color: transparent;
    color: var(--color-primary);
    padding-inline: 0;
    border-radius: 0;
}

.btn[data-variant="link"]:hover:not(:disabled) {
    text-decoration: underline;
    color: var(--color-primary-hover);
}

/* ── Tamaños (data-size) ─────────────────────────────────────────────────── */

.btn[data-size="sm"] {
    padding: 0.375rem var(--space-sm);
    font-size: var(--font-size-xs);
    border-radius: var(--radius-sm);
}

.btn[data-size="lg"] {
    padding: 0.875rem var(--space-lg);
    font-size: var(--font-size-base);
}

/* Botón circular — solo icono */
.btn[data-variant="icon"] {
    background-color: transparent;
    color: var(--color-text-muted);
    padding: var(--space-sm);
    border-radius: var(--radius-full);
    width: 2.5rem;
    height: 2.5rem;
}

.btn[data-variant="icon"]:hover:not(:disabled) {
    background-color: var(--color-bg-alt);
    color: var(--color-text);
}



/* ── card.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/card.css — Bloque: tarjeta de contenido
   Radius generoso (14px) y sombra suave — diferenciador visual de la
   referencia. Sin borde por defecto; la sombra y el radius hacen el trabajo.
   ═══════════════════════════════════════════════════════════════════════════ */

.card {
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);         /* 8px AntD */
    padding: var(--space-lg);
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--transition-normal);
}

.card[data-interactive="true"]:hover {
    box-shadow: var(--shadow-md);
}

.card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    margin-block-end: var(--space-md);
    padding-block-end: var(--space-md);
    border-block-end: 1px solid var(--color-border);
}

.card__title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
}

.card__body {
    /* Sin estilos propios — el contenido define su layout */
}

.card__footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-sm);
    margin-block-start: var(--space-md);
    padding-block-start: var(--space-md);
    border-block-start: 1px solid var(--color-border);
}

/* ── Variantes ───────────────────────────────────────────────────────────── */

.card[data-variant="destacado"] {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-md);
}

.card[data-variant="flat"] {
    box-shadow: none;
}

.card[data-variant="elevated"] {
    border-color: transparent;
    box-shadow: var(--shadow-lg);
}

/* En móvil: padding interno más compacto para aprovechar mejor el ancho útil.
   AntD Pro mobile sigue este patrón. */
@media (max-width: 48rem) {
    .card {
        padding: var(--space-md);
    }
}



/* ── form.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/form.css — Bloque: formularios

   Visual cuidado tipo Slack: inputs con buen padding, focus con ring suave
   en lugar de outline plano, estados error/disabled bien diferenciados,
   labels claramente jerárquicos.
   ═══════════════════════════════════════════════════════════════════════════ */

.form {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

/* ── Campo de formulario ─────────────────────────────────────────────────── */
.form__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    /* Contexto de posicionamiento para dropdowns anclados al campo
       (flatpickr static, combobox del compositor...). */
    position: relative;
}

/* Variante en fila (label a la izquierda, input a la derecha) — desktop only */
.form__field[data-layout="row"] {
    display: grid;
    grid-template-columns: minmax(8rem, 1fr) 2fr;
    align-items: center;
    gap: var(--space-md);
}

@media (max-width: 48rem) {  /* --bp-md */
    .form__field[data-layout="row"] {
        display: flex;
        flex-direction: column;
    }
}

/* ── Label ───────────────────────────────────────────────────────────────── */
.form__label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);    /* AntD Pro: medium (500) — no semibold */
    color: var(--color-text);
}

.form__label[data-required]::after {
    content: ' *';
    color: var(--color-danger);
}

/* ── Inputs / Selects / Textarea — base común ────────────────────────────── */
.form__input,
.form__select,
.form__textarea {
    width: 100%;
    padding: 0.625rem 0.875rem;                  /* 10px 14px */
    font-size: var(--font-size-sm);
    font-family: inherit;
    line-height: 1.4;
    color: var(--color-text);
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    transition:
        border-color var(--transition-fast),
        box-shadow   var(--transition-fast),
        background-color var(--transition-fast);
}

.form__input::placeholder,
.form__textarea::placeholder {
    color: var(--color-text-subtle);
}

/* ── Hover ───────────────────────────────────────────────────────────────── */
.form__input:hover:not(:disabled):not(:focus),
.form__select:hover:not(:disabled):not(:focus),
.form__textarea:hover:not(:disabled):not(:focus) {
    border-color: var(--color-border-strong);
}

/* ── Focus — ring suave en lugar de outline plano ────────────────────────── */
.form__input:focus,
.form__select:focus,
.form__textarea:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: var(--focus-ring);
}

/* ── Estado error ────────────────────────────────────────────────────────── */
.form__input[data-error="true"],
.form__select[data-error="true"],
.form__textarea[data-error="true"],
.form__input[aria-invalid="true"],
.form__select[aria-invalid="true"],
.form__textarea[aria-invalid="true"] {
    border-color: var(--color-danger);
}

.form__input[data-error="true"]:focus,
.form__select[data-error="true"]:focus,
.form__textarea[data-error="true"]:focus {
    box-shadow: 0 0 0 3px rgba(245, 34, 45, 0.15);
}

/* ── Estado disabled ─────────────────────────────────────────────────────── */
.form__input:disabled,
.form__select:disabled,
.form__textarea:disabled {
    background-color: var(--color-bg-alt);
    color: var(--color-text-subtle);
    cursor: not-allowed;
}

/* ── Tipos específicos ───────────────────────────────────────────────────── */
.form__textarea {
    resize: vertical;
    min-height: 6rem;
    line-height: var(--line-height-base);
}

.form__select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23616061' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.6rem center;
    background-size: 16px;
    padding-inline-end: 2rem;
}

/* Checkboxes y radios — alinear con texto */
.form__check {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    cursor: pointer;
    user-select: none;
}

.form__check input[type="checkbox"],
.form__check input[type="radio"] {
    width: 1rem;
    height: 1rem;
    accent-color: var(--color-primary);
    cursor: pointer;
    flex-shrink: 0;
}

/* Checkbox con título + descripción (AntD Checkbox con hint) */
.form__check:has(.form__check-content) {
    align-items: flex-start;
}

.form__check-content {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.3;
}

.form__check-title {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.form__check-desc {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* ── Mensajes de error y ayuda ───────────────────────────────────────────── */
.form__error {
    font-size: var(--font-size-sm);
    color: var(--color-danger);
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    min-height: 21px;                   /* reserva altura para no desplazar layout */
    line-height: 1.4;
}

/* ── Grid de 12 columnas para formularios (AntD Row/Col) ─────────────────── */
.form-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--space-md);
    align-items: start;                 /* nunca estirar — error no arrastra hermanos */
}

.form-grid > .form__field {
    grid-column: span 12;               /* full width por defecto */
    min-width: 0;
}

/* Helpers por columnas — `data-cols="N"` en el .form__field */
.form-grid > .form__field[data-cols="2"]  { grid-column: span 2; }
.form-grid > .form__field[data-cols="3"]  { grid-column: span 3; }
.form-grid > .form__field[data-cols="4"]  { grid-column: span 4; }
.form-grid > .form__field[data-cols="5"]  { grid-column: span 5; }
.form-grid > .form__field[data-cols="6"]  { grid-column: span 6; }
.form-grid > .form__field[data-cols="7"]  { grid-column: span 7; }
.form-grid > .form__field[data-cols="8"]  { grid-column: span 8; }
.form-grid > .form__field[data-cols="9"]  { grid-column: span 9; }
.form-grid > .form__field[data-cols="10"] { grid-column: span 10; }
.form-grid > .form__field[data-cols="12"] { grid-column: span 12; }

/* Mobile: todo a ancho completo para legibilidad */
@media (max-width: 48rem) {
    .form-grid > .form__field[data-cols] {
        grid-column: span 12;
    }
}

/* ── Sub-sección dentro de una tab de formulario ─────────────────────────── */
.form-subsection {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding-block-start: var(--space-sm);
}

.form-subsection + .form-subsection {
    padding-block-start: var(--space-md);
}

.form-subsection__title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
    padding-inline-start: var(--space-sm);
    border-inline-start: 2px solid var(--color-primary);
    line-height: 1.2;
}

/* ── Subgrupo dentro de un form-grid ──────────────────────────────────────
   Header full-width que parte un form-grid en secciones. Se inyecta como
   hijo del grid con data-cols="12" y visualmente separa bloques de campos
   (ej: "Alta por invitación" vs "Verificación en 2 pasos" en Configuración). */
.form-grid > .form__subgroup[data-cols="12"] {
    grid-column: span 12;
    padding-block-start: var(--space-md);
    margin-block-start: var(--space-xs);
    border-block-start: 1px solid var(--color-border-subtle);
}

/* El primer subgrupo no necesita separador superior — ya viene el padding
   del contenedor de la sección. Mantiene proporciones limpias. */
.form-grid > .form__subgroup[data-cols="12"]:first-child {
    padding-block-start: 0;
    margin-block-start: 0;
    border-block-start: none;
}

.form__subgroup-title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
    padding-inline-start: var(--space-sm);
    border-inline-start: 2px solid var(--color-primary);
    line-height: 1.2;
}

.form__hint {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* ── Acciones del formulario ─────────────────────────────────────────────── */
.form__actions {
    display: flex;
    gap: var(--space-sm);
    justify-content: flex-end;
    margin-block-start: var(--space-sm);
    padding-block-start: var(--space-md);
    border-block-start: 1px solid var(--color-border);
}

.form__actions[data-justify="between"] { justify-content: space-between; }
.form__actions[data-justify="start"]   { justify-content: flex-start; }
.form__actions[data-justify="center"]  { justify-content: center; }

/* ── Input con icono interno ─────────────────────────────────────────────── */
.form__input-group {
    position: relative;
    display: flex;
    align-items: center;
}

/* Variantes de ancho fijo — para searches en barras de filtros */
.form__input-group[data-width="sm"] { flex: 0 0 240px; max-width: 240px; }
.form__input-group[data-width="md"] { flex: 0 0 320px; max-width: 320px; }
.form__input-group[data-width="lg"] { flex: 0 0 400px; max-width: 400px; }

/* En móvil los anchos fijos de filtro pasan a 100% — caben bien y evitan
   cortes extraños en viewports estrechos. */
@media (max-width: 48rem) {
    .form__input-group[data-width="sm"],
    .form__input-group[data-width="md"],
    .form__input-group[data-width="lg"] {
        flex: 1 1 100%;
        max-width: 100%;
    }
}

.form__input-group .form__input {
    padding-inline-start: 2.25rem;
}

.form__input-group__icon {
    position: absolute;
    inset-inline-start: 0.75rem;
    color: var(--color-text-subtle);
    pointer-events: none;
    width: 16px;
    height: 16px;
}

.form__input-group .form__input:focus + .form__input-group__icon,
.form__input-group:focus-within .form__input-group__icon {
    color: var(--color-primary);
}

/* ── Fieldset / Legend ───────────────────────────────────────────────────────
   Grupos visuales dentro de un campo (ej: roles por área en el form de user).
   Se resetea el estilo nativo del navegador y se aplica el propio del sistema. */
.form__fieldset {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md) var(--space-md);
    margin: 0;
    background: var(--color-bg-alt);
}

.form__fieldset > legend {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}



/* ── table.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/table.css — Bloque: tabla de datos

   Pensada para listados de gestión: header sticky, hover de fila, paddings
   generosos en densidad comfortable, columnas alineables, indicador de
   ordenación opcional, scroll horizontal en móvil.

   Variantes:
       data-density="compact"   → padding reducido para mostrar más filas
       data-bordered="true"     → bordes verticales entre celdas
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Wrapper para scroll horizontal en móvil + sticky header ─────────────── */
.data-table-wrapper {
    overflow-x: auto;
    overflow-y: visible;
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    background-color: var(--color-bg-elevated);
    font-size: var(--font-size-sm);
}

/* Si la tabla NO está dentro del wrapper, mantener bordes y radio en la propia tabla */
.data-table:not(.data-table-wrapper > .data-table) {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}

/* ── Cabecera ────────────────────────────────────────────────────────────── */
.data-table thead {
    background-color: var(--color-bg-alt);
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
}

.data-table th {
    padding: var(--space-md);
    text-align: start;
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
    color: var(--color-text);
    border-block-end: 1px solid var(--color-border-subtle);
    white-space: nowrap;
    background-color: var(--color-bg-alt);
}

/* Header ordenable — clickable */
.data-table th[data-sortable] {
    cursor: pointer;
    user-select: none;
}

.data-table th[data-sortable]:hover {
    color: var(--color-text);
    background-color: var(--color-border);
}

.data-table th[data-sort-active]::after {
    content: '';
    display: inline-block;
    margin-inline-start: var(--space-xs);
    border: 4px solid transparent;
    vertical-align: middle;
}

.data-table th[data-sort-active="asc"]::after {
    border-block-end-color: currentColor;
    border-block-start: none;
    margin-block-end: 2px;
}

.data-table th[data-sort-active="desc"]::after {
    border-block-start-color: currentColor;
    border-block-end: none;
    margin-block-start: 2px;
}

/* ── Filas y celdas ──────────────────────────────────────────────────────── */
.data-table td {
    padding: var(--space-md);
    color: var(--color-text);
    border-block-end: 1px solid var(--color-border-subtle);
    vertical-align: middle;
}

.data-table tbody tr:last-child td {
    border-block-end: none;
}

.data-table tbody tr {
    transition: background-color var(--transition-fast),
                opacity 200ms ease,
                transform 200ms ease;
}

/* HTMX añade .htmx-swapping durante el swap-out de la fila eliminada.
   Fade + colapso suave (~200ms) en lugar de desaparición brusca. */
.data-table tbody tr.htmx-swapping {
    opacity: 0;
    transform: translateX(-12px);
    background-color: var(--color-danger-soft, rgba(255, 77, 79, 0.08));
}

.data-table tbody tr:hover {
    background-color: var(--color-bg-alt);
}

.data-table tbody tr[data-selected="true"] {
    background-color: var(--color-primary-soft);
}

/* ── Acciones (última columna) ───────────────────────────────────────────── */
.data-table__actions {
    display: flex;
    gap: var(--space-xs);
    justify-content: flex-end;
    flex-wrap: nowrap;
}

/* ── Variantes ───────────────────────────────────────────────────────────── */

/* Densidad compacta — más filas en pantalla */
.data-table[data-density="compact"] td {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--font-size-sm);
}

.data-table[data-density="compact"] th {
    padding: var(--space-xs) var(--space-md);
}

/* Bordes verticales entre celdas */
.data-table[data-bordered="true"] th,
.data-table[data-bordered="true"] td {
    border-inline-end: 1px solid var(--color-border);
}

.data-table[data-bordered="true"] th:last-child,
.data-table[data-bordered="true"] td:last-child {
    border-inline-end: none;
}

/* ── Mensaje cuando la tabla está vacía (legacy — preferir empty-state) ──── */
.data-table__empty {
    text-align: center;
    padding: var(--space-2xl);
    color: var(--color-text-muted);
}



/* ── badge.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/badge.css — Bloque: etiqueta corta de estado o categoría

   Estilo Slack: usa fondo "soft" del color con texto del color saturado.
   Variante por defecto = neutra. Variantes con data-variant.
   ═══════════════════════════════════════════════════════════════════════════ */

.badge {
    display: inline-block;
    padding: 0.2rem var(--space-sm);
    border-radius: var(--radius-full);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    line-height: 1.4;
    background-color: var(--color-bg-alt);
    color: var(--color-text-muted);
}

/* ── Variantes "soft" — estilo Slack ─────────────────────────────────────── */

.badge[data-variant="primary"] {
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
}

.badge[data-variant="success"] {
    background-color: var(--color-success-soft);
    color: var(--color-success-hover);
}

.badge[data-variant="warning"] {
    background-color: var(--color-warning-soft);
    color: var(--color-warning-hover);
}

.badge[data-variant="danger"] {
    background-color: var(--color-danger-soft);
    color: var(--color-danger-hover);
}

.badge[data-variant="info"] {
    background-color: var(--color-info-soft);
    color: var(--color-info);
}


/* ── Variante "solid" — fondo saturado, texto blanco ─────────────────────── */

.badge[data-variant="primary-solid"] {
    background-color: var(--color-primary);
    color: var(--color-text-on-dark);
}

.badge[data-variant="danger-solid"] {
    background-color: var(--color-danger);
    color: var(--color-text-on-dark);
}



/* ── alert.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/alert.css — Bloque: alerta inline en página

   Distinto del toast (flotante temporal). El alert ocupa espacio en el flujo
   normal del documento y persiste hasta que el usuario lo descarta o navega.

   Variantes con data-variant: success | warning | danger | info
   ═══════════════════════════════════════════════════════════════════════════ */

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    padding: var(--space-md);
    border-radius: var(--radius-lg);
    border: 1px solid transparent;
    border-inline-start-width: 4px;
    background-color: var(--color-bg-alt);
    color: var(--color-text);
}

.alert__icon {
    flex-shrink: 0;
    margin-block-start: 0.1em;          /* alinea con la primera línea de texto */
}

.alert__body {
    flex: 1;
    min-width: 0;                       /* permite truncar texto largo */
}

.alert__title {
    display: block;
    font-weight: var(--font-weight-medium);
    margin-block-end: var(--space-xs);
}

.alert__close {
    flex-shrink: 0;
    background: transparent;
    border: none;
    padding: var(--space-xs);
    margin: calc(var(--space-xs) * -1);
    cursor: pointer;
    color: inherit;
    border-radius: var(--radius-sm);
}

.alert__close:hover {
    background-color: rgba(0, 0, 0, 0.05);
}

/* ── Variantes — fondo soft + borde + icono coloreado ───────────────────── */

.alert[data-variant="success"] {
    background-color: var(--color-success-soft);
    border-color: var(--color-success);
    color: var(--color-success-hover);
}

.alert[data-variant="warning"] {
    background-color: var(--color-warning-soft);
    border-color: var(--color-warning);
    color: var(--color-warning-hover);
}

.alert[data-variant="danger"] {
    background-color: var(--color-danger-soft);
    border-color: var(--color-danger);
    color: var(--color-danger-hover);
}

.alert[data-variant="info"] {
    background-color: var(--color-info-soft);
    border-color: var(--color-info);
    color: var(--color-info);
}



/* ── toast.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/toast.css — AntD Message style

   Feedbacks cortos flotantes arriba-centro, auto-descartables a los 3s.
   Sin botón de cerrar (el usuario no interactúa, solo lee).
   Variantes con data-variant: success | warning | danger | info

   Canonical AntD Message: https://ant.design/components/message
   ═══════════════════════════════════════════════════════════════════════════ */

.toast-stack {
    position: fixed;
    inset-block-start: var(--space-lg);
    inset-inline: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    z-index: 1100;                    /* por encima de modales/drawers */
    pointer-events: none;
    padding-inline: var(--space-md);
}

.toast {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 10px 16px;
    background-color: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
                0 3px 6px -4px rgba(0, 0, 0, 0.12),
                0 9px 28px 8px rgba(0, 0, 0, 0.05);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    max-width: min(90vw, 480px);
}

/* Icono circular coloreado a la izquierda (AntD) */
.toast__icon {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    display: inline-grid;
    place-items: center;
}

.toast__icon .icon {
    width: 16px;
    height: 16px;
}

.toast__body {
    flex: 1;
    min-width: 0;
    line-height: 1.4;
}

.toast__title {
    display: block;
    font-weight: var(--font-weight-medium);
    margin-block-end: 2px;
}

/* ── Colores por variante — icon-only (AntD no colorea ni fondo ni borde) ─── */
.toast[data-variant="success"] .toast__icon { color: var(--color-success); }
.toast[data-variant="warning"] .toast__icon { color: var(--color-warning); }
.toast[data-variant="danger"]  .toast__icon { color: var(--color-danger);  }
.toast[data-variant="info"]    .toast__icon { color: var(--color-primary); }



/* ── modal.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/modal.css — Bloque: diálogo modal centrado

   Estructura HTML típica (con Alpine.js para apertura/cierre):

   <div x-data="{ open: false }">
       <button @click="open = true">Abrir</button>

       <div x-show="open" x-transition.opacity
            @keydown.escape.window="open = false"
            @click="open = false"
            class="modal__overlay">
           <div class="modal" @click.stop role="dialog" aria-modal="true">
               <header class="modal__header">
                   <h2 class="modal__title">Título</h2>
                   <button @click="open = false" class="modal__close" aria-label="Cerrar">
                       {% include 'icons/x.svg.twig' %}
                   </button>
               </header>
               <div class="modal__body">...</div>
               <footer class="modal__footer">
                   <button class="btn" data-variant="ghost" @click="open = false">Cancelar</button>
                   <button class="btn" data-variant="primary">Confirmar</button>
               </footer>
           </div>
       </div>
   </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.modal__overlay {
    position: fixed;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.5);
    display: grid;
    place-items: center;
    z-index: 200;

    /* Padding del overlay = espacio que el modal NO puede ocupar. Mantiene el
       modal dentro del área de contenido principal, sin pisar sidebar/header/
       footer. El overlay sigue dimando todo el viewport para mantener foco.
       Derecha usa space-xl (32px) para compensar visualmente que a la
       izquierda el modal tiene el "cuerpo visual" del sidebar al lado.     */
    padding-block-start:  calc(var(--header-height) + var(--space-md));
    padding-block-end:    calc(var(--footer-height) + var(--space-md));
    padding-inline-start: calc(var(--sidebar-width) + var(--space-md));
    padding-inline-end:   var(--space-xl);
}

/* Sidebar colapsado: el modal gana espacio a la izquierda.
   SOLO desktop — en mobile el sidebar es un drawer fuera del flow,
   no hay espacio que compensar. Sin el @media, el atributo
   data-sidebar-collapsed (persistido en localStorage desde sesión
   desktop previa) filtraba al mobile → modal con padding-inline-start
   de 80px vs padding-inline-end de 16px → modal desplazado a la derecha. */
@media (min-width: 48rem) {
    html[data-sidebar-collapsed="true"] .modal__overlay {
        padding-inline-start: calc(var(--sidebar-width-collapsed) + var(--space-md));
    }
}

/* Móvil (≤768px) — overlay con padding asimétrico DELIBERADO para centrar
   el modal en la zona VISIBLE del viewport (arriba de la barra Android/iOS).

   Con viewport-fit=cover, el viewport se extiende bajo la barra del
   sistema. Si usamos padding simétrico, el modal queda centrado en
   "viewport/2" = centro TÉCNICO del viewport, que cae parcialmente
   detrás de la barra → el usuario lo percibe como "desplazado hacia
   abajo".

   Compensación: padding-block-end = padding-block-start + env(safe-area).
   Eso desplaza el centro del modal hacia ARRIBA exactamente por la
   altura de la barra → el modal queda en el centro del área visible.

   Los md/lg full-screen pasan a padding: 0 más abajo mediante selectores
   scoped — para ellos el centrado no importa (cubren el viewport). */
@media (max-width: 48rem) {
    .modal__overlay {
        padding-inline-start: var(--space-md);
        padding-inline-end:   var(--space-md);
        padding-block-start:  var(--space-md);
        padding-block-end:    calc(var(--space-md) + env(safe-area-inset-bottom, 0));
    }
}

/* Layer superior — para modales que deben aparecer SOBRE otros modales/drawers.
   Uso canónico: modal de confirmación global (confirm destructivo) que puede
   dispararse desde cualquier contexto, incluido otro modal abierto.         */
.modal__overlay[data-layer="top"] {
    z-index: 500;
}

.modal {
    background-color: var(--color-bg-elevated);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xl);
    width: 100%;
    max-width: var(--container-sm);
    max-height: calc(100dvh - var(--space-2xl));
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    border-block-end: 1px solid var(--color-border);
}

.modal__title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
}

/* Botón de cerrar — área clickable 40x40 (AntD ~46x56, WCAG 2.1 AA mínimo 44).
   Hover con fondo suave redondeado estilo AntD. Icono centrado a 16x16.      */
.modal__close {
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    margin: calc(-1 * var(--space-xs));   /* compensa visualmente para alinear con el título */
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--color-text-muted);
    border-radius: var(--radius-md);
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.modal__close .icon {
    width: 16px;
    height: 16px;
}

.modal__close:hover {
    background-color: var(--color-bg-alt);
    color: var(--color-text);
}

.modal__close:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* Cuando header/body/footer están envueltos en un wrapper (patrón habitual
   con Alpine: <form>, <div x-data>, etc.), el wrapper debe heredar el
   layout flex-column de .modal para que el body scrollee dentro y el
   footer quede fijo. Sin esto, un body con mucho contenido (o con un
   <details>/disclosure expandido) empuja el footer fuera del viewport.

   Se aplica a cualquier hijo directo de .modal que NO sea header/body/
   footer (los tres elementos "conocidos") — cubre <form>, <div x-data>,
   y cualquier otro wrapper futuro. */
.modal > form,
.modal > [x-data] {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

.modal__body {
    padding: var(--space-lg);
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}

/* Modales con tabs dentro — anclar altura para que el cambio de tab no salte
   el layout (patrón AntD Pro). La tab más alta define el alto; las cortas
   dejan aire debajo pero el footer queda fijo.

   SOLO en desktop: en mobile el modal ya es full-screen (height: 100dvh) y
   el body con flex:1 gestiona el layout. Aplicar min-height: 500px aquí
   sumaba con header (~60px) + footer apilado (~140px) + safe-areas,
   excediendo 100dvh en viewports típicos (640-720px) y empujando el
   footer (Cancelar/Guardar) fuera del viewport. Es el síntoma "el botón
   Cancelar se corta" que solo pasaba en modales con tabs (form empleado).*/
@media (min-width: 48rem) {
    .modal > form:has(.tabs) .modal__body,
    .modal:has(> .tabs) .modal__body {
        min-height: 500px;
    }
}

/* Scrollbar fino estilo AntD — visible pero discreto.
   Firefox usa scrollbar-width, Chromium usa ::-webkit-scrollbar. */
.modal__body {
    scrollbar-width: thin;
    scrollbar-color: var(--color-border-strong) transparent;
}

.modal__body::-webkit-scrollbar {
    width: 6px;
}
.modal__body::-webkit-scrollbar-track {
    background: transparent;
}
.modal__body::-webkit-scrollbar-thumb {
    background-color: var(--color-border);
    border-radius: 3px;
}
.modal__body::-webkit-scrollbar-thumb:hover {
    background-color: var(--color-border-strong);
}

/* Modificador: cuerpo sin padding interno.
   Útil cuando el contenido (ej. galería con tabs) gestiona su propio padding.
   Mantiene el overflow-y: auto por defecto del body — el scroll es único en
   el cuerpo, no anidado en hijos. */
.modal__body[data-padding="none"] {
    padding: 0;
}

.modal__footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    border-block-start: 1px solid var(--color-border-subtle);
    background-color: var(--color-bg-elevated);
    flex-shrink: 0;    /* el body es quien scrolea; footer siempre visible */
    /* Respeta teclado virtual en móvil — el footer queda por encima del keyboard.
       Fallback a 0 para browsers sin env() — así el max() siempre evalúa a
       var(--space-md) como mínimo, nunca se invalida la propiedad. */
    padding-block-end: max(var(--space-md), env(safe-area-inset-bottom, 0));
}

/* ── Móvil: full-screen + footer apilado (AntD móvil) ────────────────────── */
@media (max-width: 40rem) {
    /* Si el modal va a ir full-screen (NO size="sm"), el overlay no
       debe tener padding — si no, el modal de `height: 100dvh` centrado
       con grid se sale del área padded y se recorta ~80px por abajo
       (el síntoma "no se ve centrado / cortado"). Con `:has()` lo
       aplicamos solo cuando corresponde; size="sm" sigue siendo pill
       centrado con su padding. */
    .modal__overlay:has(> .modal:not([data-size="sm"])) {
        padding: 0;
        place-items: stretch;
    }

    /* modales md/sm → full width/height (excepto size="sm" que mantiene pill).
       100dvh ya se adapta al viewport dinámico (browser UI), pero con
       viewport-fit=cover + Android gesture bar, el viewport "cubre" la barra.
       Restamos env(safe-area-inset-*) para que el modal no quede DEBAJO
       de ella — el footer con sus botones queda siempre dentro del área
       visible.

       Defensiva con `, 0` como fallback:
         - Navegadores modernos sin safe-areas (dispositivo plano): env=0,
           modal = 100dvh edge-to-edge. Funciona.
         - Navegadores muy antiguos que NO soportan env(): el fallback 0
           evita invalidar la propiedad entera (que haría revertir a las
           reglas desktop de max-width: 520px → modal tiny en mobile).
         - iOS con home indicator / Android con gesture bar (con
           viewport-fit=cover): env reporta el inset real, el modal se
           acorta y el footer queda visible. */
    .modal:not([data-size="sm"]):not([data-size="full"]) {
        max-width: 100%;
        max-height: calc(100dvh - env(safe-area-inset-top, 0) - env(safe-area-inset-bottom, 0));
        width: 100%;
        height: calc(100dvh - env(safe-area-inset-top, 0) - env(safe-area-inset-bottom, 0));
        margin-block-start: env(safe-area-inset-top, 0);
        margin-block-end:   env(safe-area-inset-bottom, 0);
        border-radius: 0;
    }

    /* Footer en columna, botones full-width, submit arriba (pulgar accesible) */
    .modal__footer {
        flex-direction: column-reverse;
        gap: var(--space-xs);
    }
    .modal__footer .btn {
        width: 100%;
        justify-content: center;
    }
}

/* ── Variantes de tamaño ─────────────────────────────────────────────────── */

.modal[data-size="sm"]  { max-width: 24rem; }
.modal[data-size="md"]  { max-width: 520px; }   /* AntD Modal default */
.modal[data-size="lg"]  { max-width: var(--container-lg); }

/* full = ocupa todo el área de contenido disponible (ya limitada por el
   padding del overlay). Mantiene radius y sombra — no es edge-to-edge.     */
.modal[data-size="full"] {
    max-width: 100%;
    max-height: 100%;
    width: 100%;
    height: 100%;
}



/* ── dropdown.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/dropdown.css — Bloque: menú desplegable contextual

   Estructura HTML típica (con Alpine.js):

   <div x-data="{ open: false }" @click.outside="open = false" class="dropdown">
       <button @click="open = !open" :aria-expanded="open" class="dropdown__trigger">
           Acciones
           {% include 'icons/chevron-down.svg.twig' %}
       </button>

       <ul x-show="open" x-transition class="dropdown__menu" role="menu">
           <li><a class="dropdown__item" href="#" role="menuitem">Editar</a></li>
           <li><a class="dropdown__item" href="#" role="menuitem">Duplicar</a></li>
           <li class="dropdown__separator" role="separator"></li>
           <li><a class="dropdown__item" data-variant="danger" href="#" role="menuitem">Eliminar</a></li>
       </ul>
   </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.dropdown {
    position: relative;
    display: inline-block;
}

.dropdown__trigger {
    /* No tiene estilos propios — suele combinarse con .btn */
    cursor: pointer;
}

.dropdown__menu {
    position: absolute;
    inset-block-start: calc(100% + var(--space-xs));
    inset-inline-end: 0;
    min-width: 12rem;
    padding: var(--space-xs);
    margin: 0;
    list-style: none;
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: 50;
}

/* Variante: alinear a la izquierda en lugar de derecha */
.dropdown__menu[data-align="start"] {
    inset-inline-end: auto;
    inset-inline-start: 0;
}

.dropdown__item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: var(--space-sm) var(--space-md);
    font-size: var(--font-size-sm);
    color: var(--color-text);
    text-decoration: none;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-align: start;
}

.dropdown__item:hover,
.dropdown__item:focus-visible {
    background-color: var(--color-bg-alt);
    outline: none;
}

.dropdown__item[data-variant="danger"] {
    color: var(--color-danger);
}

.dropdown__item[data-variant="danger"]:hover {
    background-color: var(--color-danger-soft);
}

.dropdown__item:disabled,
.dropdown__item[aria-disabled="true"] {
    color: var(--color-text-subtle);
    cursor: not-allowed;
}

.dropdown__separator {
    height: 1px;
    margin-block: var(--space-xs);
    background-color: var(--color-border-subtle);
}

/* Header del dropdown — info usuario, no clickable */
.dropdown__header {
    display: flex;
    flex-direction: column;
    padding: var(--space-sm) var(--space-md);
    line-height: 1.3;
}

.dropdown__header strong {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.dropdown__header-sub {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Item tipo submit-form — sin fondo de botón */
.dropdown__item[type="submit"],
button.dropdown__item {
    width: 100%;
    font: inherit;
}



/* ── avatar.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/avatar.css — Bloque: avatar circular

   Acepta texto (iniciales) o imagen como hijo. Variantes de tamaño con
   data-size: sm | md (default) | lg | xl

   Uso:
       <span class="avatar" aria-label="Juan Pérez">JP</span>
       <img class="avatar" data-size="lg" src="..." alt="Juan Pérez">
   ═══════════════════════════════════════════════════════════════════════════ */

.avatar {
    /* Tamaño base — md */
    --_avatar-size: 36px;
    --_avatar-font: var(--font-size-sm);

    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--_avatar-size);
    height: var(--_avatar-size);
    border-radius: var(--radius-full);
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
    font-size: var(--_avatar-font);
    font-weight: var(--font-weight-medium);
    line-height: 1;
    object-fit: cover;
    flex-shrink: 0;
    text-transform: uppercase;
    user-select: none;
}

.avatar[data-size="sm"] {
    --_avatar-size: 24px;
    --_avatar-font: var(--font-size-xs);
}

.avatar[data-size="lg"] {
    --_avatar-size: 48px;
    --_avatar-font: var(--font-size-base);
}

.avatar[data-size="xl"] {
    --_avatar-size: 64px;
    --_avatar-font: var(--font-size-lg);
}

/* Stack de avatares solapados — útil para listas de participantes */
.avatar-stack {
    display: inline-flex;
}

.avatar-stack > .avatar {
    border: 2px solid var(--color-bg);
    margin-inline-start: -8px;
}

.avatar-stack > .avatar:first-child {
    margin-inline-start: 0;
}



/* ── spinner.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/spinner.css — Bloque: indicador de carga circular

   Animación CSS pura, sin JS. Hereda color del padre via currentColor.
   Variantes con data-size: sm | md (default) | lg

   Uso:
       <span class="spinner" aria-label="Cargando"></span>
       <span class="spinner" data-size="lg"></span>

   Combinable con HTMX — añadir clase "htmx-indicator" para que solo aparezca
   durante peticiones activas:
       <span class="spinner htmx-indicator"></span>
   ═══════════════════════════════════════════════════════════════════════════ */

.spinner {
    --_spinner-size: 18px;
    --_spinner-thickness: 2px;

    display: inline-block;
    width: var(--_spinner-size);
    height: var(--_spinner-size);
    border: var(--_spinner-thickness) solid currentColor;
    border-block-end-color: transparent;
    border-radius: var(--radius-full);
    animation: spinner-rotate 0.8s linear infinite;
    flex-shrink: 0;
    vertical-align: -0.2em;
}

.spinner[data-size="sm"] {
    --_spinner-size: 14px;
    --_spinner-thickness: 2px;
}

.spinner[data-size="lg"] {
    --_spinner-size: 32px;
    --_spinner-thickness: 3px;
}

@keyframes spinner-rotate {
    to { transform: rotate(360deg); }
}

/* Respeta usuarios con prefers-reduced-motion: hace pulsar en lugar de rotar */
@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation: spinner-pulse 1.5s ease-in-out infinite;
    }

    @keyframes spinner-pulse {
        0%, 100% { opacity: 1; }
        50%      { opacity: 0.4; }
    }
}



/* ── empty-state.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/empty-state.css — Bloque: estado vacío

   Patrón unificado para "no hay datos aún" / "sin resultados".
   Componer con icono grande + título + descripción + CTA opcional.

   Estructura HTML:

   <div class="empty-state">
       <div class="empty-state__icon">
           {% include 'icons/users.svg.twig' with { class: 'icon--xl' } %}
       </div>
       <h2 class="empty-state__title">Sin socios aún</h2>
       <p class="empty-state__desc">Crea el primer socio para empezar.</p>
       <div class="empty-state__actions">
           <a class="btn" data-variant="primary">Nuevo socio</a>
       </div>
   </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: var(--space-sm);
    padding: calc(var(--space-2xl) * 1.5) var(--space-lg);
    color: var(--color-text-muted);
}

/* Ilustración — círculo grande con icono centrado. AntD Empty usa una
   ilustración simple + gradiente suave. Aquí combinamos icono + fondo. */
.empty-state__icon {
    display: inline-grid;
    place-items: center;
    width: 96px;
    height: 96px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--color-primary-soft) 0%, var(--color-bg-alt) 100%);
    color: var(--color-primary);
    margin-block-end: var(--space-sm);
}

.empty-state__icon .icon,
.empty-state__icon svg {
    width: 40px;
    height: 40px;
    opacity: 0.85;
}

.empty-state__title {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
}

.empty-state__desc {
    max-width: 360px;
    margin: 0;
    line-height: 1.5;
}

.empty-state__actions {
    display: flex;
    gap: var(--space-sm);
    margin-block-start: var(--space-md);
    flex-wrap: wrap;
    justify-content: center;
}

/* Variante compacta — para cuando el empty está dentro de un modal/card pequeño */
.empty-state[data-size="sm"] {
    padding: var(--space-xl) var(--space-md);
}
.empty-state[data-size="sm"] .empty-state__icon {
    width: 64px;
    height: 64px;
}
.empty-state[data-size="sm"] .empty-state__icon .icon,
.empty-state[data-size="sm"] .empty-state__icon svg {
    width: 28px;
    height: 28px;
}



/* ── nav-sidebar.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/nav-sidebar.css — Sidebar dark estilo Ant Design Pro
   Fondo navy oscuro `#001529`. Link activo con bg primary saturado.
   ═══════════════════════════════════════════════════════════════════════════ */

.nav-sidebar {
    background-color: var(--color-sidebar-bg);
    color: var(--color-sidebar-text);
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
}

/* ── Estado colapsado — solo iconos (64px) ──────────────────────────────
   Los textos NO cambian de layout. Siempre renderizados (no display:none)
   para que estén "ya ahí" cuando el sidebar se expande — el propio overflow
   del sidebar los revela progresivamente al ensanchar.
   Doble selector: html[...] activa antes de Alpine (evita flash en F5);
   .nav-sidebar[...] mantiene sync cuando Alpine togglea en caliente.

   Solo aplica en desktop. En móvil la sidebar es un drawer full-slide,
   el estado colapsado del desktop no tiene sentido (desencadenaba que
   al abrir el drawer solo se vieran las cabeceras si el user había
   colapsado la sidebar en una sesión desktop previa). */
@media (min-width: 48rem) {
    html[data-sidebar-collapsed="true"] .nav-sidebar__brand-text,
    html[data-sidebar-collapsed="true"] .nav-sidebar__section-title,
    html[data-sidebar-collapsed="true"] .nav-sidebar__link span,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__brand-text,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__section-title,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__link span {
        opacity: 0;
        pointer-events: none;
    }
}

/* ── Brand (arriba) ───────────────────────────────────────────────────────
   Es un <a href="/app/dashboard"> — click navega al dashboard.
   Reset de estilos link + transición sutil de hover.                         */
.nav-sidebar__brand {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md);
    height: var(--header-height);
    background-color: var(--color-sidebar-bg-darker);
    flex-shrink: 0;
    text-decoration: none;
    color: inherit;
    transition: background-color var(--transition-fast);
}

.nav-sidebar__brand:hover {
    background-color: rgba(255, 255, 255, 0.04);
}

.nav-sidebar__brand-logo {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-md);
    background-color: var(--color-primary);
    display: grid;
    place-items: center;
    color: var(--color-text-on-dark);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-md);
    flex-shrink: 0;
}

.nav-sidebar__brand-text {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    min-width: 0;
    white-space: nowrap;
}

.nav-sidebar__brand-name {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-on-dark);
    font-family: var(--font-heading);
}

.nav-sidebar__brand-sub {
    font-size: var(--font-size-xs);
    color: var(--color-sidebar-text-muted);
}

/* ── Nav (scrollable, expande para empujar user abajo) ─────────────────── */
.nav-sidebar__nav {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-md) var(--space-sm);
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

/* Sección — grupo de links con heading */
.nav-sidebar__section {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav-sidebar__section-title {
    padding: var(--space-xs) var(--space-md);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    color: var(--color-sidebar-text-muted);
    margin-block-end: var(--space-xs);
    white-space: nowrap;
}

.nav-sidebar__link {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0.5rem var(--space-md);
    color: var(--color-sidebar-text);
    text-decoration: none;
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-regular);
    white-space: nowrap;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

/* Mobile drawer: labels completos con wrap a 2 líneas si no caben.
   Patrón AntD Pro — truncar con "..." en nav items hace perder contexto
   (el label ES la información principal, no hay pista visual adyacente
   como en celdas de tabla). Mejor 2 líneas que ellipsis. */
@media (max-width: 48rem) {
    .nav-sidebar__link {
        white-space: normal;
        line-height: 1.25;
    }
}

.nav-sidebar__link .icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    opacity: 0.85;
}

.nav-sidebar__link:hover {
    background-color: var(--color-sidebar-hover);
    color: var(--color-text-on-dark);
}

.nav-sidebar__link[aria-current="page"] {
    background-color: var(--color-primary);
    color: var(--color-text-on-dark);
    transition: none;
}

.nav-sidebar__link[aria-current="page"] .icon {
    opacity: 1;
    color: var(--color-text-on-dark);
}




/* ── nav-bottom.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/nav-bottom.css — Bloque: navegación inferior móvil

   Estilo Material Design / Slack móvil. Solo visible en mobile.
   En desktop (breakpoint 48rem) queda oculto y se usa la sidebar.

   Estructura HTML:

   <nav class="nav-bottom" aria-label="Navegación principal">
       <a href="..." class="nav-bottom__link" data-active="true">
           {% include 'icons/layout-dashboard.svg.twig' %}
           <span class="nav-bottom__label">Inicio</span>
       </a>
       ...
   </nav>
   ═══════════════════════════════════════════════════════════════════════════ */

.nav-bottom {
    display: flex;
    justify-content: space-around;
    align-items: stretch;
    height: var(--bottom-nav-height);
    background-color: var(--color-bg-elevated);
    border-block-start: 1px solid var(--color-border);
    box-shadow: 0 -2px 8px rgba(26, 95, 122, 0.06);
    /* El safe-area bottom lo gestiona ahora `body::after` strip + layout
       a 100svh (el nav queda SOBRE la barra automáticamente). Añadir
       padding-block-end: env() aquí aplastaría los iconos dentro de los
       64px del grid row — síntoma que reportó el user: "nav cortado". */
}

@media (min-width: 48rem) {  /* --bp-md */
    /* En admin el bottom-nav se oculta (admin usa drawer). En cliente SÍ se
       mantiene visible en desktop — es el patrón "app móvil en canvas".     */
    .layout-app .nav-bottom {
        display: none;
    }
}

.nav-bottom__link {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    flex: 1;
    /* Padding suficiente para tap target ≥ 44px (WCAG 2.1 AA) */
    padding: var(--space-sm);
    color: var(--color-text-muted);
    text-decoration: none;
    transition: color var(--transition-fast);
    min-width: 0;
    position: relative;
}

.nav-bottom__link:hover,
.nav-bottom__link:focus-visible {
    color: var(--color-text);
    outline: none;
}

.nav-bottom__link[data-active="true"] {
    color: var(--color-primary);
}

.nav-bottom__link .icon {
    width: 22px;
    height: 22px;
}

.nav-bottom__label {
    font-size: var(--font-size-xs);
    line-height: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

/* Badge de contador (notificaciones sin leer). Se posiciona sobre el
   icono del link. Mínimo 20×20px para visibilidad en móvil. */
.nav-bottom__badge {
    position: absolute;
    top: 4px;
    right: 50%;
    margin-right: -20px;
    background: var(--color-danger, #ff4d4f);
    color: white;
    min-width: 20px;
    height: 20px;
    border-radius: 10px;
    padding: 0 5px;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

/* Respetar el atributo `hidden` del HTML. Sin esto, el `display: inline-flex`
   de la clase sobreescribe el default del browser para [hidden] y el badge
   se pinta brevemente como círculo rojo vacío al cargar la página, aunque
   el server haya renderizado `<span hidden>` por tener 0 notificaciones.
   Síntoma reportado: "pestañeo del icono de notificaciones al entrar al
   portal del cliente". */
.nav-bottom__badge[hidden] {
    display: none;
}



/* ── app-header.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/app-header.css — Bloque: cabecera de la zona autenticada
   ═══════════════════════════════════════════════════════════════════════════ */

.app-header {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0 var(--space-sm);
    background-color: var(--color-bg-elevated);
    border-bottom: 1px solid var(--color-border-subtle);
}

.app-header__slot {
    flex: 1;
    min-width: 0;
}

/* Botón genérico del header — cuadrado 40x40, hover bg-alt */
.app-header__action {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    border-radius: var(--radius-md);
    transition: background-color var(--transition-fast), color var(--transition-fast);
    text-decoration: none;
}

.app-header__action:hover {
    background-color: var(--color-bg-alt);
    color: var(--color-text);
}

.app-header__action .icon {
    width: 18px;
    height: 18px;
}

/* Badge numérico sobre el icono (notificaciones) */
.app-header__badge {
    position: absolute;
    inset-block-start: 4px;
    inset-inline-end: 4px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    font-size: 10px;
    font-weight: var(--font-weight-semibold);
    line-height: 16px;
    text-align: center;
    color: var(--color-text-on-dark);
    background-color: var(--color-danger);
    border-radius: var(--radius-full);
    border: 2px solid var(--color-bg-elevated);
    box-sizing: content-box;
}

/* Trigger del dropdown de usuario — avatar + nombre + chevron */
.app-header__user {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0 var(--space-sm) 0 4px;
    height: 40px;
    border: none;
    background: transparent;
    color: var(--color-text);
    cursor: pointer;
    border-radius: var(--radius-md);
    transition: background-color var(--transition-fast);
}

.app-header__user:hover {
    background-color: var(--color-bg-alt);
}

.app-header__user-name {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-header__user-chevron {
    width: 14px;
    height: 14px;
    color: var(--color-text-muted);
}

@media (max-width: 48rem) {
    .app-header__user-name,
    .app-header__user-chevron {
        display: none;
    }
    .app-header__user {
        padding: 0;
        width: 40px;
        justify-content: center;
    }
}



/* ── error-page.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/error-page.css — Bloque: páginas de error (404, 403, 500, etc.)
   ═══════════════════════════════════════════════════════════════════════════ */

.error-page {
    min-height: 100dvh;   /* fallback */
    min-height: 100svh;   /* estable ante retracción de URL bar en mobile */
    display: grid;
    place-items: center;
    padding: var(--space-lg);
    background-color: var(--color-bg-alt);
}

.error-page__inner {
    max-width: var(--container-sm);
    text-align: center;
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-xl) var(--space-lg);
    box-shadow: var(--shadow-md);
}

.error-page__code {
    font-size: 6rem;
    font-weight: var(--font-weight-bold);
    line-height: 1;
    color: var(--color-primary);
    letter-spacing: -0.05em;
}

.error-page__title {
    font-size: var(--font-size-xl);
    color: var(--color-text);
}

.error-page__mensaje {
    color: var(--color-text-muted);
    max-width: var(--container-xs);
    margin-inline: auto;
}

.error-page__acciones {
    margin-block-start: var(--space-lg);
}



/* ── datepicker.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/datepicker.css — Theme override de flatpickr

   Sobre el CSS base (vendor/flatpickr/flatpickr.min.css), aplicamos
   nuestros tokens de color, tipografía y radios para que el calendario
   se integre con el resto del sistema.

   Solo aplica a flatpickr — el input nativo type="date" no se usa.
   ═══════════════════════════════════════════════════════════════════════════ */

.flatpickr-calendar {
    font-family: var(--font-sans);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    background-color: var(--color-bg-elevated);
    color: var(--color-text);
}

/* Modo static (calendario anclado al input, dentro del flujo del DOM).
   El vendor aplica `z-index: 999` a `.flatpickr-calendar.static.open` — hay
   que matchear esa misma especificidad para que nuestro override gane. Así
   el calendario queda por DEBAJO de sticky navs de tabs (z-index 20+) sin
   necesidad de !important.                                                  */
.flatpickr-calendar.static,
.flatpickr-calendar.static.open {
    z-index: 10;
}

/* Static + open: por defecto flatpickr lo pone con position:relative dentro
   del parent, lo que DESPLAZA el contenido de abajo (se ve raro en forms
   largos). Forzamos posición absoluta para que SOBREPONGA el contenido en
   vez de empujarlo, manteniendo la ventaja de seguir al input en el layout
   (no se mueve con el scroll como en el modo float).
   Requisito: el contenedor del input debe ser position:relative — los
   `.form__field` ya lo son por CUBE CSS. */
.flatpickr-calendar.static.open {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
}

/* Variante: desplegar hacia ARRIBA. Usar clase `flatpickr-above` en el
   `.form__field` contenedor. El calendario se posiciona con `bottom` en
   vez de `top`, abriéndose sobre el input. Útil cuando el campo está al
   final de la página y el calendario no cabe abajo. */
.flatpickr-above .flatpickr-calendar.static.open {
    top: auto;
    bottom: calc(100% + 4px);
}

/* Selector de año — <select> que inyectamos en app.js (convertirAnyoADropdown).
   Se integra al lado del mes en la cabecera del calendario. */
.flatpickr-year-select {
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 1px solid transparent;
    color: inherit;
    font-family: inherit;
    font-size: inherit;
    font-weight: var(--font-weight-semibold);
    padding: 2px var(--space-sm) 2px 4px;
    margin-inline-start: 4px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23595959' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: right 2px center;
    background-size: 12px;
    padding-inline-end: 18px;
    transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.flatpickr-year-select:hover {
    background-color: var(--color-bg-alt);
}

.flatpickr-year-select:focus {
    outline: none;
    border-color: var(--color-primary);
    background-color: var(--color-bg-alt);
}

/* Cabecera del calendario (mes + flechas) */
.flatpickr-months,
.flatpickr-month {
    background-color: var(--color-bg-elevated);
    color: var(--color-text);
    border-block-end: 1px solid var(--color-border);
    height: 42px;
}

.flatpickr-current-month {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    padding: var(--space-xs) 0;
}

.flatpickr-current-month input.cur-year,
.flatpickr-current-month .flatpickr-monthDropdown-months {
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.flatpickr-monthDropdown-months {
    background-color: transparent;
    border: none;
}

/* Flechas prev/next */
.flatpickr-prev-month,
.flatpickr-next-month {
    color: var(--color-text-muted);
    padding: var(--space-xs);
}

.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
    color: var(--color-primary);
}

.flatpickr-prev-month svg,
.flatpickr-next-month svg {
    fill: currentColor;
}

/* Días de la semana (lun, mar, ...) */
.flatpickr-weekdays,
.flatpickr-weekday {
    background-color: var(--color-bg-elevated);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
}

/* Días del mes */
.flatpickr-day {
    color: var(--color-text);
    border-radius: var(--radius-sm);
    border: none;
    max-width: 38px;
    height: 36px;
    line-height: 36px;
}

.flatpickr-day:hover,
.flatpickr-day:focus {
    background-color: var(--color-bg-alt);
    border: none;
}

.flatpickr-day.today {
    border: 1px solid var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-medium);
}

.flatpickr-day.today:hover {
    background-color: var(--color-primary-soft);
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
    background-color: var(--color-primary);
    color: var(--color-text-on-dark);
    border: none;
    font-weight: var(--font-weight-medium);
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--color-text-subtle);
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    color: var(--color-text-subtle);
    background: transparent;
    cursor: not-allowed;
}



/* ── filter-bar.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/filter-bar.css — Bloque: barra de filtros sobre listados

   Patrón consistente para listados: barra de filtros + chips de filtros
   activos + acciones (limpiar todos, exportar, etc.). Diseñado para HTMX:
   cada filtro dispara un GET al endpoint que devuelve el listado actualizado.

   Estructura típica:

   <div class="filter-bar">
       <div class="filter-bar__main">
           <input class="form__input" type="search" hx-get="..." hx-trigger="...">
           <select class="form__select" hx-get="..." hx-trigger="change">...</select>
       </div>

       <div class="filter-bar__chips">
           <span class="filter-chip">
               Categoría: general
               <button class="filter-chip__remove" hx-get="...">×</button>
           </span>
           <button class="filter-chip__clear">Limpiar filtros</button>
       </div>
   </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.filter-bar {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

/* ── Fila principal de filtros ──────────────────────────────────────────── */
.filter-bar__main {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    align-items: center;
}

.filter-bar__main > * {
    flex: 0 1 auto;
}

/* El primer input/search ocupa el espacio sobrante */
.filter-bar__main > input[type="search"],
.filter-bar__main > .filter-bar__search {
    flex: 1 1 16rem;
    min-width: 12rem;
}

/* Selects más estrechos en la barra de filtros */
.filter-bar__main .form__select {
    flex: 0 0 auto;
    width: auto;
    min-width: 10rem;
}

/* ── Chips de filtros activos ────────────────────────────────────────────── */
.filter-bar__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    align-items: center;
    min-height: 1.75rem;        /* reserva espacio para evitar layout shift */
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: 0.2rem var(--space-sm);
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
    border-radius: var(--radius-full);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
}

.filter-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: none;
    background: transparent;
    color: inherit;
    cursor: pointer;
    border-radius: var(--radius-full);
    opacity: 0.7;
}

.filter-chip__remove:hover {
    background-color: var(--color-primary);
    color: var(--color-text-on-dark);
    opacity: 1;
}

.filter-chip__clear {
    background: transparent;
    border: none;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    cursor: pointer;
    padding: 0.2rem var(--space-sm);
    text-decoration: underline;
}

.filter-chip__clear:hover {
    color: var(--color-text);
}



/* ── page-header.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/page-header.css — Bloque: cabecera de página

   Patrón: icono/avatar + título + subtítulo a la izquierda, acciones a la
   derecha. Uso típico arriba de listados (ej. "Gestión de Socios").

   Estructura:
     <div class="page-header">
       <div class="page-header__title-group">
         <span class="page-header__icon">{icon}</span>
         <div>
           <h1 class="page-header__title">Gestión de Socios</h1>
           <p class="page-header__subtitle">Gestiona la base de socios</p>
         </div>
       </div>
       <div class="page-header__actions">
         <button class="btn" data-variant="ghost">Importar</button>
         <button class="btn" data-variant="secondary">+ Nuevo</button>
       </div>
     </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-lg);
    flex-wrap: wrap;
}

.page-header__title-group {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    min-width: 0;
}

.page-header__icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
    display: grid;
    place-items: center;
}

.page-header__icon .icon {
    width: 22px;
    height: 22px;
}

.page-header__title {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    line-height: 1.2;
    margin: 0;
}

.page-header__subtitle {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin: 0;
    margin-block-start: 2px;
}

.page-header__actions {
    display: flex;
    gap: var(--space-sm);
    flex-wrap: wrap;
    align-items: center;
}

@media (max-width: 48rem) {
    .page-header__actions {
        width: 100%;
    }
    .page-header__actions .btn {
        flex: 1;
    }

    /* Título y icono más compactos en móvil — sin perder jerarquía visual.
       AntD Pro baja al siguiente tamaño en la escala cuando el viewport
       es estrecho. */
    .page-header__title {
        font-size: var(--font-size-xl);   /* 20px en vez de 24px */
    }
    .page-header__icon {
        width: 36px;
        height: 36px;
    }
    .page-header__icon .icon {
        width: 18px;
        height: 18px;
    }
}



/* ── stat-card.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/stat-card.css — Bloque: tarjeta de métrica (KPI)

   Uso: fila de 4 cards con métricas arriba de un listado.
   Cada card: icono coloreado + número grande + label pequeño.
   Variantes via data-variant: primary (default) | success | warning | danger
   ═══════════════════════════════════════════════════════════════════════════ */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--space-md);
}

.stat-card {
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    box-shadow: var(--shadow-sm);
    display: flex;
    align-items: center;
    gap: var(--space-md);
    transition: box-shadow var(--transition-normal), transform var(--transition-normal);
}

/* Cuando la grid tiene contenido de altura variable (ej: cards de cron
   con textos de longitud distinta), alinear al top evita que las cards
   más cortas centren su contenido dejando gaps visuales. */
.stat-grid[data-align="start"] .stat-card {
    align-items: flex-start;
}

.stat-card:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}

.stat-card__icon {
    flex-shrink: 0;
    width: 48px;
    height: 48px;
    border-radius: var(--radius-md);
    display: grid;
    place-items: center;
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
}

.stat-card__icon .icon {
    width: 22px;
    height: 22px;
}

.stat-card__body {
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* Texto técnico largo (URLs, hostnames, mensajes de error) se parte en
       cualquier carácter — evita que overflow horizontal el card. */
    overflow-wrap: anywhere;
    word-break: break-word;
}

.stat-card__value {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    line-height: 1;
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-xs);
}

/* Indicador de tendencia vs periodo anterior (AntD Pro) */
.stat-card__trend {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1;
}

.stat-card__trend[data-trend="up"]   { color: var(--color-success); }
.stat-card__trend[data-trend="down"] { color: var(--color-danger); }

.stat-card__label {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    font-weight: var(--font-weight-regular);
    margin-block-start: 4px;
}

/* ── Variantes — color del icono y fondo del icono ──────────────────────── */

.stat-card[data-variant="success"] .stat-card__icon {
    background-color: var(--color-success-soft);
    color: var(--color-success);
}

.stat-card[data-variant="warning"] .stat-card__icon {
    background-color: var(--color-warning-soft);
    color: var(--color-warning);
}

.stat-card[data-variant="danger"] .stat-card__icon {
    background-color: var(--color-danger-soft);
    color: var(--color-danger);
}

/* ── Responsive móvil ────────────────────────────────────────────────── */

@media (max-width: 480px) {
    .stat-card {
        padding: var(--space-md);
    }

    .stat-card__icon {
        width: 40px;
        height: 40px;
    }

    .stat-card__icon .icon {
        width: 18px;
        height: 18px;
    }

    .stat-card__value {
        font-size: var(--font-size-xl);
    }
}




/* ── tab-filter.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/tab-filter.css — Bloque: tabs tipo segmented control

   Uso: filtrar listados por categorías predefinidas. Los tabs disparan HTMX.
   data-active="true" marca el tab seleccionado.
   ═══════════════════════════════════════════════════════════════════════════ */

.tab-filter {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 4px;
    background-color: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

.tab-filter__item {
    padding: 0.375rem var(--space-md);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
    transition: background-color var(--transition-fast), color var(--transition-fast);
    white-space: nowrap;
}

.tab-filter__item:hover {
    color: var(--color-text);
}

.tab-filter__item[data-active="true"] {
    background-color: var(--color-bg-elevated);
    color: var(--color-primary);
    box-shadow: var(--shadow-sm);
}

.tab-filter__count {
    display: inline-block;
    margin-inline-start: 6px;
    padding: 1px 8px;
    font-size: var(--font-size-xs);
    background-color: var(--color-bg-alt);
    border-radius: var(--radius-full);
    color: var(--color-text-muted);
}

.tab-filter__item[data-active="true"] .tab-filter__count {
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
}



/* ── list-toolbar.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/list-toolbar.css — Toolbar de listado estilo AntD Pro

   Patrón canónico AntD Pro para list pages: una "card header" que agrupa
   búsqueda + tabs rápidos + acciones (primary + secundarias) arriba de la
   tabla. Reemplaza el antipatrón de meter acciones en el page-header, que
   en mobile quedaba desordenado (filtros/exportar separados del CTA).

   Estructura Twig:
     <div class="list-toolbar">
       <div class="list-toolbar__row">
         <div class="list-toolbar__search"> <input type=search/> </div>
         <div class="list-toolbar__actions">
           <button data-variant="primary">+ Nuevo</button>
           <button>Filtros</button>
           <div class="dropdown">Exportar ▾</div>
         </div>
       </div>
       <div class="list-toolbar__row list-toolbar__row--tabs">
         {% include 'partials/_tabs_xxx.html.twig' %}
       </div>
     </div>

   Desktop (>=768px):
     Row 1: [search 1fr] [actions auto] — search estira, actions a la derecha.
     Row 2: tabs a lo largo de toda la fila.

   Mobile (<768px):
     Row 1 wrap → search arriba full-width, actions abajo full-width en grid.
     Row 2 tabs con scroll horizontal (evita wrap a 2 líneas).
   ═══════════════════════════════════════════════════════════════════════════ */

.list-toolbar {
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.list-toolbar__row {
    display: flex;
    gap: var(--space-sm);
    align-items: center;
    flex-wrap: wrap;
}

/* Separador sutil entre row 1 (search+actions) y row 2 (tabs). Solo si
   existe la segunda fila — se aplica al --tabs para evitar ruido visual
   cuando el toolbar no tiene tabs. */
.list-toolbar__row--tabs {
    border-block-start: 1px solid var(--color-border-subtle);
    padding-block-start: var(--space-sm);
    margin-block-start: 0;
}

/* Buscador — se estira al máximo disponible, min 240px para que el input
   no quede enano cuando hay muchas acciones al lado. */
.list-toolbar__search {
    flex: 1 1 240px;
    min-width: 0;
}

.list-toolbar__search .form__input-group {
    width: 100%;
}

/* Acciones — agrupadas a la derecha (margin-inline-start: auto). En mobile
   pasan a ocupar fila completa con grid equidistante. */
.list-toolbar__actions {
    display: flex;
    gap: var(--space-sm);
    margin-inline-start: auto;
    flex-wrap: wrap;
    align-items: center;
}

/* Tabs — ocupan la fila completa. Overflow-x auto para scroll horizontal
   en mobile cuando hay muchos. */
.list-toolbar__tabs-slot {
    width: 100%;
    overflow-x: auto;
    scrollbar-width: thin;
}

/* Los .tab-filter dentro del slot no deben wrapear — scroll horizontal */
.list-toolbar__tabs-slot > .tab-filter {
    flex-wrap: nowrap;
    min-width: max-content;
}

@media (max-width: 48rem) {
    .list-toolbar {
        padding: var(--space-sm);
    }

    .list-toolbar__search {
        flex: 1 1 100%;
    }

    /*
       Layout móvil de las acciones — patrón AntD Pro:
         · Primary (+ Nuevo): ocupa el ancho disponible con label + icon.
         · Secundarias (Filtros/Exportar/Importar): icon-only, compactas.

       El usuario ve qué debería hacer (CTA primario prominente) y las
       acciones auxiliares quedan accesibles sin robar protagonismo ni
       hacer wrap a múltiples líneas de texto.
    */
    .list-toolbar__actions {
        width: 100%;
        margin-inline-start: 0;
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-sm);
        align-items: center;
    }

    /* Primary — crece desde 0 para que los 4 botones (primary + 3
       secundarias) quepan SIEMPRE en una sola línea, aunque el label
       sea largo ("Nuevo empleado", "Nuevo usuario"). flex-basis: 0
       ignora el ancho intrínseco del contenido; white-space nowrap +
       text-overflow ellipsis truncan con "…" si el viewport es muy
       estrecho. Alternativa a wrap a 2 líneas — preferimos corte
       visual que reordenamiento vertical. */
    .list-toolbar__actions > .btn[data-variant="primary"] {
        flex: 1 1 0;
        min-width: 0;
        white-space: nowrap;
    }

    .list-toolbar__actions > .btn[data-variant="primary"] > span {
        overflow: hidden;
        text-overflow: ellipsis;
        min-width: 0;
    }

    /* Secundarias — tamaño fijo cuadrado (target touch 44x44 mínimo) */
    .list-toolbar__actions > .btn:not([data-variant="primary"]),
    .list-toolbar__actions > .dropdown {
        flex: 0 0 auto;
    }

    .list-toolbar__actions > .btn:not([data-variant="primary"]),
    .list-toolbar__actions > .dropdown > .btn {
        min-width: 2.75rem;
        padding-inline: var(--space-sm);
    }

    /* Ocultar label de texto en secundarias — usar sr-only para mantener
       accesibilidad (screen readers siguen anunciando el propósito). */
    .list-toolbar__actions > .btn:not([data-variant="primary"]) > span,
    .list-toolbar__actions > .dropdown > .btn > span {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    /* Dropdown menu (Exportar ▾) en mobile — flip a alineación izquierda.
       Por defecto el menu tiene inset-inline-end: 0 (ancla a la derecha
       del botón). En desktop los botones son anchos, así que el menu
       queda ancla y extiende hacia la izquierda dentro del viewport.
       En mobile el botón es icon-only 44px y vive en medio del row —
       si el menu de 12rem se ancla a la derecha del botón, su borde
       izquierdo sale del viewport ~80px. Flip: ancla a la izquierda
       del botón, extiende hacia la derecha (fits en viewport porque
       los botones están en el lado izquierdo del row junto al search). */
    .list-toolbar__actions > .dropdown > .dropdown__menu {
        inset-inline-end: auto;
        inset-inline-start: 0;
    }
}



/* ── configuracion.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/configuracion.css — estilos específicos de /app/admin/configuracion

   Patrón AntD Pro Settings: footer de acciones sticky al fondo del viewport
   en mobile. En forms largos (el de configuración tiene 7-8 tabs con
   múltiples settings cada una), forzar al user a scrollear hasta el final
   para encontrar "Guardar cambios" es fricción — mismo motivo por el que
   GitHub, Slack, Google Settings tienen botón sticky.

   Desktop: cluster justify-end normal al final del form.
   Mobile (<768px): sticky al bottom del viewport con backdrop sutil.
   ═══════════════════════════════════════════════════════════════════════════ */

.config-footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-sm);
    margin-block-start: var(--space-md);
}

@media (max-width: 48rem) {
    /*
       En mobile usamos position: fixed (no sticky). Motivo: sticky
       reaccionaba a micro-cambios de layout o a env() durante scroll
       → el user veía el footer "desplazarse hacia arriba al llegar abajo".
       Fixed pinza el footer al viewport y no toca nada durante scroll.

       Compensación: el form necesita padding-block-end para que el
       contenido último no quede oculto detrás del footer fijo (el
       .config-footer-spacer en el HTML).
    */
    .config-footer {
        position: fixed;
        inset-inline: 0;
        inset-block-end: 0;
        z-index: 60;

        background-color: var(--color-bg-elevated);
        border-block-start: 1px solid var(--color-border);
        padding: var(--space-md);
        padding-block-end: calc(var(--space-md) + env(safe-area-inset-bottom, 0));

        /* Sin margin-inline ni margin-block-start — fixed ya ancla al
           viewport, no necesita compensar padding del contenedor padre. */
        margin: 0;

        justify-content: stretch;
    }

    .config-footer .btn {
        width: 100%;
        min-height: 2.75rem;   /* target touch 44px */
        justify-content: center;
    }

    /* Con el footer fijo, el último contenido del form quedaría oculto
       detrás. Añadimos un spacer en el final del form que reserva una
       altura aproximada del footer + safe-area. */
    .config-footer-spacer {
        block-size: calc(4.5rem + env(safe-area-inset-bottom, 0));
    }
}

/* Desktop: el spacer no ocupa espacio (el footer es normal flow) */
.config-footer-spacer {
    display: none;
}
@media (max-width: 48rem) {
    .config-footer-spacer {
        display: block;
    }
}



/* ── socio-row.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/socio-row.css — Componentes de celdas para tabla de socios

   Patrón reutilizable para cualquier tabla con: avatar + texto multi-línea,
   dot de estado, botones de acción compactos. Aplicable a futuras entidades.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Celda: avatar + nombre bold + sub-texto muted */
.user-cell {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    min-width: 0;
}

.user-cell__avatar {
    position: relative;
    flex-shrink: 0;
}

/* Dot de estado sobre la esquina del avatar */
.user-cell__dot {
    position: absolute;
    inset-block-end: -2px;
    inset-inline-end: -2px;
    width: 12px;
    height: 12px;
    border-radius: var(--radius-full);
    background-color: var(--color-text-subtle);
    border: 2px solid var(--color-bg-elevated);
}

.user-cell__dot[data-status="active"]   { background-color: var(--color-success); }
.user-cell__dot[data-status="inactive"] { background-color: var(--color-text-subtle); }
.user-cell__dot[data-status="pending"]  { background-color: var(--color-warning); }

.user-cell__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}

.user-cell__title {
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.user-cell__sub {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Grupo de botones de acción en la última columna */
.row-actions {
    display: inline-flex;
    gap: 4px;
    justify-content: flex-end;
}

.row-action {
    display: inline-grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-bg-elevated);
    color: var(--color-text-muted);
    cursor: pointer;
    text-decoration: none;
    transition:
        background-color var(--transition-fast),
        border-color var(--transition-fast),
        color var(--transition-fast);
}

/* Hit target 44x44 en dispositivos táctiles (WCAG AA + iOS HIG) */
@media (pointer: coarse) {
    .row-action {
        width: 44px;
        height: 44px;
    }
}

.row-action:active {
    background-color: var(--color-primary-soft);
    transform: scale(0.96);
}

.row-action .icon {
    width: 16px;
    height: 16px;
}

.row-action:hover {
    background-color: var(--color-primary-soft);
    border-color: var(--color-primary-border);
    color: var(--color-primary);
}

.row-action[data-variant="danger"]:hover {
    background-color: var(--color-danger-soft);
    border-color: var(--color-danger);
    color: var(--color-danger);
}



/* ── pagination.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/pagination.css — Bloque: paginación AntD

   Estructura:
     .pagination
       .pagination__info    — "Mostrando X-Y de Z"
       .pagination__pages   — números de página + prev/next
       .pagination__size    — selector de resultados por página
   ═══════════════════════════════════════════════════════════════════════════ */

.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    flex-wrap: wrap;
    padding: var(--space-md) 0;
}

.pagination__info {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* ── Bloque de números de página + prev/next ───────────────────────────── */
.pagination__pages {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.pagination__item {
    display: inline-grid;
    place-items: center;
    min-width: 32px;
    height: 32px;
    padding: 0 6px;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-regular);
    color: var(--color-text);
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    cursor: pointer;
    text-decoration: none;
    transition:
        color var(--transition-fast),
        border-color var(--transition-fast),
        background-color var(--transition-fast);
}

.pagination__item:hover:not([aria-disabled="true"]):not([aria-current="true"]) {
    color: var(--color-primary);
    border-color: var(--color-primary);
}

.pagination__item[aria-current="true"] {
    color: var(--color-primary);
    border-color: var(--color-primary);
    font-weight: var(--font-weight-medium);
    cursor: default;
}

.pagination__item[aria-disabled="true"] {
    color: var(--color-text-subtle);
    cursor: not-allowed;
    background-color: var(--color-bg-alt);
}

/* Prev/Next — solo icono, sin borde visible (estilo AntD) */
.pagination__item[data-variant="nav"] {
    border-color: transparent;
    background: transparent;
}

.pagination__item[data-variant="nav"]:hover:not([aria-disabled="true"]) {
    background-color: var(--color-bg-alt);
    border-color: transparent;
    color: var(--color-primary);
}

.pagination__item[data-variant="nav"] .icon {
    width: 14px;
    height: 14px;
}

/* Ellipsis — no interactivo */
.pagination__ellipsis {
    display: inline-grid;
    place-items: center;
    min-width: 32px;
    height: 32px;
    color: var(--color-text-subtle);
    font-size: var(--font-size-sm);
    cursor: default;
    user-select: none;
}

/* ── Selector de tamaño de página ──────────────────────────────────────── */
.pagination__size {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.pagination__size select {
    padding: 4px var(--space-sm);
    padding-inline-end: 1.75rem;
    font-size: var(--font-size-sm);
    line-height: 1.4;
    color: var(--color-text);
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23595959' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.5rem center;
    background-size: 14px;
    transition: border-color var(--transition-fast);
}

.pagination__size select:hover {
    border-color: var(--color-primary);
}

.pagination__size select:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: var(--focus-ring);
}

/* En móvil: apilar verticalmente, centrar */
@media (max-width: 48rem) {
    .pagination {
        justify-content: center;
        gap: var(--space-sm);
    }
    .pagination__info,
    .pagination__size {
        width: 100%;
        justify-content: center;
    }
}



/* ── drawer.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/drawer.css — Bloque: panel deslizante lateral (AntD Drawer)

   Se abre desde el borde (derecho por defecto) con backdrop semi-transparente.
   Uso típico: filtros avanzados, formularios largos, detalles sin salir de la
   lista actual. Estructura similar al modal pero anclado al borde.

   Patrón Alpine + x-teleport="body" (mismo que modal, escapa del grid).
   ═══════════════════════════════════════════════════════════════════════════ */

.drawer__overlay {
    position: fixed;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.45);
    z-index: 300;
    display: flex;
    justify-content: flex-end;

    /* Misma política que el modal: dim fullscreen pero el drawer se ancla
       al área de contenido, respetando sidebar/header/footer.               */
    padding-block-start:  var(--header-height);
    padding-block-end:    var(--footer-height);
}

html[data-sidebar-collapsed="true"] .drawer__overlay {
    /* El drawer sale desde la derecha — el sidebar izquierdo no le afecta */
}

@media (max-width: 48rem) {
    .drawer__overlay {
        padding-block-end: var(--bottom-nav-height);
    }
}

.drawer {
    background-color: var(--color-bg-elevated);
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 400px;
    /* height: 100% del overlay (que tiene padding por header+footer globales).
       NO usar 100dvh — desbordaría por debajo y ocultaría el footer del drawer. */
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Variante desde la izquierda (por si algún día) */
.drawer__overlay[data-placement="left"] {
    justify-content: flex-start;
}

/* ── Header ───────────────────────────────────────────────────────────── */
.drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    border-block-end: 1px solid var(--color-border-subtle);
    flex-shrink: 0;
}

.drawer__title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
}

.drawer__close {
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    margin: calc(-1 * var(--space-xs));
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--color-text-muted);
    border-radius: var(--radius-md);
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.drawer__close .icon {
    width: 16px;
    height: 16px;
}

.drawer__close:hover {
    background-color: var(--color-bg-alt);
    color: var(--color-text);
}

.drawer__close:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* ── Body (scrollable) ────────────────────────────────────────────────── */
.drawer__body {
    padding: var(--space-lg);
    overflow-y: auto;
    flex: 1;
}

/* ── Footer (sticky abajo con acciones) ───────────────────────────────── */
.drawer__footer {
    display: flex;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    border-block-start: 1px solid var(--color-border-subtle);
    background-color: var(--color-bg-elevated);
    flex-shrink: 0;
}

/* Flatpickr portalizado al body desde drawer: garantizar que quede por
   encima del overlay del drawer (z-index 300). Sin esto, el calendario se
   ve pero los clicks se comen. */
.flatpickr-calendar.open {
    z-index: 10000;
}

/* ── Grupos de campos dentro del drawer (con separadores sutiles) ─────── */
.drawer__group {
    padding-block-end: var(--space-lg);
    margin-block-end: var(--space-lg);
    border-block-end: 1px solid var(--color-border-subtle);
}

.drawer__group:last-child {
    padding-block-end: 0;
    margin-block-end: 0;
    border-block-end: none;
}

.drawer__group-title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0 0 var(--space-sm) 0;
}

/* Rango de 2 inputs en línea (min/max, desde/hasta) */
.drawer__range {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: var(--space-sm);
    align-items: center;
}

.drawer__range-sep {
    color: var(--color-text-subtle);
    font-size: var(--font-size-sm);
}

/* Lista vertical de checkboxes/radios — queda más AntD que multi-select */
.drawer__options {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}



/* ── app-footer.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/app-footer.css — Footer del shell autenticado.
   Vive en la fila final del grid `.layout-app`. Discreto, tipo AntD Pro Waterline.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background-color: var(--color-bg-elevated);
    border-block-start: 1px solid var(--color-border-subtle);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    flex-wrap: wrap;
}

.app-footer__links {
    display: inline-flex;
    gap: var(--space-md);
    list-style: none;
    margin: 0;
    padding: 0;
}

.app-footer__links a {
    color: inherit;
    text-decoration: none;
    transition: color var(--transition-fast);
}

.app-footer__links a:hover {
    color: var(--color-primary);
}

/* En móvil el bottom-nav ocupa el lugar del footer — escondemos éste */
@media (max-width: 48rem) {
    .app-footer {
        display: none;
    }
}



/* ── progress-bar.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/progress-bar.css — Barra de progreso global tipo NProgress
   (como usa AntD Pro). Aparece arriba durante peticiones HTMX iniciadas
   por el usuario. Lógica en app.js; polls de fondo la saltan.

   Anatomía NProgress:
     - Barra de 2px de alto en el color primario del sistema.
     - "Peg": banda inclinada ~3° al final de la barra que hace glow solo
       en el borde que avanza. Da sensación de "brocha dejando rastro".
   ═══════════════════════════════════════════════════════════════════════════ */

.progress-bar {
    position: fixed;
    inset-block-start: 0;
    inset-inline-start: 0;
    height: 2px;
    width: 0;
    background-color: var(--color-primary);
    z-index: 9999;
    pointer-events: none;
    opacity: 0;
    transition: width 0.2s ease-out, opacity 0.2s ease-out 0.15s;
}

.progress-bar[data-active="true"] {
    opacity: 1;
    transition: width 0.4s cubic-bezier(0.1, 0.5, 0.2, 1), opacity 0.1s;
}

/* Peg — glow direccional sobre el borde que avanza. Solo visible mientras
   la barra está activa (si no, arrastra un resplandor extraño fuera de ella). */
.progress-bar[data-active="true"]::after {
    content: '';
    position: absolute;
    inset-inline-end: 0;
    inset-block-start: 0;
    width: 100px;
    height: 100%;
    box-shadow: 0 0 10px var(--color-primary),
                0 0 5px  var(--color-primary);
    transform: rotate(3deg) translate(0, -4px);
    opacity: 1;
}



/* ── nav-submenu.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/nav-submenu.css — Submenús multi-nivel del sidebar AntD Pro.

   Estructura:
     <div class="nav-sidebar__group">
       <button class="nav-sidebar__group-trigger" @click="toggle">
         <icon> · <span>Texto</span> · <chevron>
       </button>
       <ul class="nav-sidebar__group-children">
         <li><a class="nav-sidebar__link nav-sidebar__link--child">...</a></li>
       </ul>
     </div>
   ═══════════════════════════════════════════════════════════════════════════ */

.nav-sidebar__group {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nav-sidebar__group-trigger {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0.5rem var(--space-md);
    background: transparent;
    border: none;
    color: var(--color-sidebar-text);
    text-align: start;
    cursor: pointer;
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-regular);
    font-family: inherit;
    white-space: nowrap;
    transition: background-color var(--transition-fast), color var(--transition-fast);
    width: 100%;
}

.nav-sidebar__group-trigger:hover {
    background-color: var(--color-sidebar-hover);
    color: var(--color-text-on-dark);
}

.nav-sidebar__group-trigger .icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    opacity: 0.85;
}

.nav-sidebar__group-trigger > span {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Mobile drawer: label completo con wrap (mismo patrón AntD Pro que los
   links). En desktop rail colapsado ya estaría oculto via otra regla;
   aquí aplica al drawer expandido en phone/tablet. */
@media (max-width: 48rem) {
    .nav-sidebar__group-trigger {
        white-space: normal;
    }
    .nav-sidebar__group-trigger > span {
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
        line-height: 1.25;
    }
}

.nav-sidebar__group-chevron {
    display: inline-grid;
    place-items: center;
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    opacity: 0.6;
    transition: transform var(--transition-fast);
    transform-origin: 50% 50%;
}

/* El SVG interno hereda tamaño del wrapper — así rota sobre su propio centro
   en vez de desbordar y moverse al aplicar el transform. */
.nav-sidebar__group-chevron .icon {
    width: 12px;
    height: 12px;
    display: block;
}

.nav-sidebar__group[data-open="true"] .nav-sidebar__group-chevron {
    transform: rotate(180deg);
}

/* Default: ABIERTO (max-height: 800px). Así el HTML recién renderizado
   (sin `data-open` aún, Alpine no ha corrido) muestra los grupos expandidos
   — es la preferencia por defecto del localStorage — evitando el flash de
   expansión al cargar la página. Alpine luego aplicará data-open="false"
   a los grupos que el usuario había colapsado. */
.nav-sidebar__group-children {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow: hidden;
    max-height: 800px;          /* suficiente para listas razonables */
    transition: max-height var(--transition-normal);
}

.nav-sidebar__group[data-open="false"] .nav-sidebar__group-children {
    max-height: 0;
}

/* Hasta que Alpine marca el documento como listo, NO animar — así la
   aplicación inicial de data-open="false" (para grupos colapsados por
   el usuario) pasa al instante, sin transición visible. Tras alpine:initialized
   el listener en alpine-data.js añade `data-alpine-ready` al <html> y las
   transiciones vuelven a estar activas para clicks posteriores. */
html:not([data-alpine-ready]) .nav-sidebar__group-children {
    transition: none;
}

/* Items hijos — indentación y barra lateral */
.nav-sidebar__link--child {
    padding-inline-start: calc(var(--space-md) + 16px + var(--space-sm));
    font-size: var(--font-size-sm);
}

/* Cuando el sidebar está colapsado (SOLO desktop), ocultamos la pestaña
   children + chevron y el grupo se comporta como un link plano (solo icono).
   En móvil el sidebar es drawer, no rail — el estado colapsado no aplica. */
@media (min-width: 48rem) {
    html[data-sidebar-collapsed="true"] .nav-sidebar__group-children,
    html[data-sidebar-collapsed="true"] .nav-sidebar__group-chevron,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__group-children,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__group-chevron {
        display: none;
    }

    /* Group trigger en estado colapsado: igual de estrecho que un link normal */
    html[data-sidebar-collapsed="true"] .nav-sidebar__group-trigger,
    .nav-sidebar[data-collapsed="true"] .nav-sidebar__group-trigger {
        padding-inline: var(--space-md);
    }
}



/* ── notifications.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/notifications.css — Dropdown de notificaciones AntD Pro.
   Estructura: dropdown__menu pero más ancho + items con contenido rico.
   ═══════════════════════════════════════════════════════════════════════════ */

.notif-dropdown {
    width: 360px;
    padding: 0;
    overflow: hidden;
}

/* En móvil el dropdown de 360px se salía del viewport por la izquierda
   (está anclado al borde derecho del botón de la campana). Lo pasamos a
   `position: fixed` anclado a ambos bordes del viewport con un margen
   pequeño — queda centrado debajo del header sin recortarse. */
@media (max-width: 48rem) {
    .dropdown__menu.notif-dropdown {
        position: fixed;
        inset-block-start: calc(var(--header-height) + 4px);
        inset-inline-start: var(--space-sm);
        inset-inline-end: var(--space-sm);
        width: auto;
        max-width: 420px;
        margin-inline: auto;
    }
}

.notif-dropdown__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border-block-end: 1px solid var(--color-border-subtle);
    background-color: var(--color-bg-alt);
}

.notif-dropdown__title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.notif-dropdown__action {
    background: transparent;
    border: none;
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: var(--radius-sm);
}

.notif-dropdown__action:hover {
    background-color: var(--color-primary-soft);
}

.notif-dropdown__list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 360px;
    overflow-y: auto;
}

.notif-item {
    border-block-end: 1px solid var(--color-border-subtle);
    transition: background-color var(--transition-fast);
}

.notif-item:last-child {
    border-block-end: none;
}

/* Toda la fila es un <a> — el link ocupa todo el área clickable. */
.notif-item__link {
    display: flex;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.notif-item__link:hover,
.notif-item__link:focus-visible {
    background-color: var(--color-bg-alt);
    outline: none;
}

.notif-item__icon {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-full);
    display: grid;
    place-items: center;
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
}

.notif-item__icon[data-variant="success"] {
    background-color: var(--color-success-soft);
    color: var(--color-success);
}
.notif-item__icon[data-variant="warning"] {
    background-color: var(--color-warning-soft);
    color: var(--color-warning);
}
.notif-item__icon[data-variant="danger"] {
    background-color: var(--color-danger-soft);
    color: var(--color-danger);
}

.notif-item__icon .icon {
    width: 16px;
    height: 16px;
}

.notif-item__body {
    flex: 1;
    min-width: 0;
}

.notif-item__title {
    display: block;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    line-height: 1.3;
}

.notif-item__desc {
    display: block;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    margin-block-start: 2px;
    line-height: 1.4;
}

.notif-item__time {
    display: block;
    font-size: var(--font-size-xs);
    color: var(--color-text-subtle);
    margin-block-start: 4px;
}

/* Punto azul para "no leídas" */
.notif-item[data-unread="true"] {
    background-color: var(--color-primary-soft);
}
.notif-item[data-unread="true"] .notif-item__title::after {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background-color: var(--color-primary);
    margin-inline-start: var(--space-xs);
    vertical-align: middle;
}

/* Footer del dropdown */
.notif-dropdown__footer {
    padding: var(--space-sm) var(--space-md);
    text-align: center;
    border-block-start: 1px solid var(--color-border-subtle);
    background-color: var(--color-bg-alt);
}

.notif-dropdown__footer a {
    color: var(--color-primary);
    font-size: var(--font-size-sm);
    text-decoration: none;
    font-weight: var(--font-weight-medium);
}

.notif-dropdown__footer a:hover {
    text-decoration: underline;
}

/* Empty state */
.notif-dropdown__empty {
    padding: var(--space-2xl) var(--space-md);
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}



/* ── tabs.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/tabs.css — Tabs horizontales estilo AntD.

   Estructura (Alpine + data-active):
     <div class="tabs" x-data="{ activa: 'uno' }">
       <nav class="tabs__nav" role="tablist">
         <button class="tabs__tab" :data-active="activa === 'uno'"
                 @click="activa = 'uno'" role="tab">Uno</button>
         <button class="tabs__tab" :data-active="activa === 'dos'"
                 @click="activa = 'dos'" role="tab">Dos</button>
       </nav>
       <div class="tabs__panel" x-show="activa === 'uno'" role="tabpanel">...</div>
       <div class="tabs__panel" x-show="activa === 'dos'" role="tabpanel">...</div>
     </div>

   Distinto de `.tab-filter` (segmented control para filtros). Esta es
   navegación entre secciones de contenido (como AntD Tabs).
   ═══════════════════════════════════════════════════════════════════════════ */

.tabs {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Modificador: las tabs se pegan al borde superior del contenedor (útil
   justo debajo del modal__header para no generar un hueco grande). Compensa
   el padding-top del modal body. */
.tabs[data-flush="top"] {
    margin-block-start: calc(var(--space-lg) * -1);
}

.tabs[data-flush="top"] .tabs__nav {
    margin-block-end: var(--space-md);
    padding-block-start: var(--space-sm);
}

/* Modificador: rellena el contenedor padre con padding propio (útil dentro
   de un modal donde el body no tiene padding). El nav queda sticky arriba
   — así al scrollear el body del modal sigue visible. */
.tabs[data-fill="true"] {
    padding: var(--space-lg);
}

.tabs[data-fill="true"] .tabs__nav {
    position: sticky;
    inset-block-start: 0;
    background-color: var(--color-bg-elevated);
    z-index: 20;     /* sobre popups del propio panel (datepickers, dropdowns internos) */
    margin-block-start: calc(-1 * var(--space-lg));
    padding-block-start: var(--space-lg);
}

.tabs__nav {
    display: flex;
    gap: var(--space-xl);                       /* 32px — match AntD v5 */
    border-block-end: 1px solid var(--color-border-subtle);
    margin-block-end: var(--space-lg);

    /* overflow-x: auto + overflow-y: visible provoca que el navegador
       active scroll vertical "fantasma" también. Forzar hidden en Y lo
       evita. X queda auto para scroll horizontal cuando hay muchas tabs. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    flex-shrink: 0;
}

.tabs__nav::-webkit-scrollbar {
    height: 0;
    display: none;
}

.tabs__tab {
    position: relative;
    padding: 0.75rem 0;                         /* 12px — match AntD v5 */
    background: transparent;
    border: none;
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-regular);
    color: var(--color-text-muted);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--transition-fast);
}

.tabs__tab:hover {
    color: var(--color-primary);
}

.tabs__tab[data-active="true"] {
    color: var(--color-primary);
    font-weight: var(--font-weight-medium);
}

/* Punto rojo que avisa de errores en la tab (AntD pattern) */
.tabs__tab[data-has-error="true"]::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: var(--color-danger);
    margin-inline-end: 6px;
    vertical-align: 1px;
}

/* Línea inferior que marca la tab activa — 2px solid primary,
   anclada al borde inferior del nav para tapar parte del border-block-end. */
.tabs__tab[data-active="true"]::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    inset-block-end: -1px;
    height: 2px;
    background-color: var(--color-primary);
    border-radius: 2px 2px 0 0;
}

/* Iconos opcionales dentro de una tab */
.tabs__tab .icon {
    width: 14px;
    height: 14px;
    margin-inline-end: 6px;
    vertical-align: -2px;
}

/* Panel — por defecto se expande pero NO scrolea por sí mismo. El contenedor
   padre (modal body, página, card) gestiona el scroll. Evita scrollbars
   anidados feos. */
.tabs__panel {
    flex: 1;
    min-height: 0;
}

/* Variante "pills" — tabs con fondo suave en activo (alternativa AntD card) */
.tabs[data-variant="pills"] .tabs__nav {
    border-block-end: none;
    gap: var(--space-xs);
    padding: 4px;
    background-color: var(--color-bg-alt);
    border-radius: var(--radius-md);
}

.tabs[data-variant="pills"] .tabs__tab {
    padding: 0.375rem var(--space-md);
    border-radius: var(--radius-sm);
}

.tabs[data-variant="pills"] .tabs__tab[data-active="true"] {
    background-color: var(--color-bg-elevated);
    box-shadow: var(--shadow-sm);
}

.tabs[data-variant="pills"] .tabs__tab[data-active="true"]::after {
    display: none;
}



/* ── file-upload.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/file-upload.css — Componentes de subida de ficheros AntD-style.

   Dos variantes:

   A) `.form__file`        — botón compacto "Seleccionar archivo" con lista
                             de archivos elegidos debajo. Para 1-2 archivos.

   B) `.file-dropzone`     — área drag-and-drop con icono, título y descripción.
                             Para múltiples archivos o UX más visible.

   Ambas esconden el <input type="file"> nativo (feo + inconsistente entre
   navegadores) y exponen un elemento estilado. El input sigue siendo funcional
   para accesibilidad (click y teclado funcionan vía <label>).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── A) Botón compacto ──────────────────────────────────────────────────── */

.form__file {
    display: inline-flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: flex-start;
}

.form__file input[type="file"] {
    /* Oculta visualmente pero sigue siendo clickable via label + accesible */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.form__file-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: 0.625rem var(--space-md);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-bg-elevated);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition:
        border-color var(--transition-fast),
        background-color var(--transition-fast),
        color var(--transition-fast);
}

.form__file-label:hover,
.form__file input[type="file"]:focus-visible + .form__file-label {
    border-color: var(--color-primary);
    color: var(--color-primary);
}

.form__file-label .icon {
    width: 16px;
    height: 16px;
}

/* Lista de archivos elegidos (poblada por JS o servidor) */
.form__file-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    list-style: none;
    margin: 0;
    padding: 0;
}

.form__file-item {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 4px var(--space-sm);
    background-color: var(--color-bg-alt);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.form__file-item .icon {
    width: 14px;
    height: 14px;
    color: var(--color-text-subtle);
}

.form__file-item__remove {
    margin-inline-start: auto;
    background: none;
    border: none;
    padding: 2px;
    cursor: pointer;
    color: var(--color-text-subtle);
    border-radius: var(--radius-sm);
    display: inline-grid;
    place-items: center;
}

.form__file-item__remove:hover {
    color: var(--color-danger);
}

.form__file-item__remove .icon {
    width: 12px;
    height: 12px;
}

/* ─── B) Dropzone drag-and-drop ──────────────────────────────────────────── */

.file-dropzone {
    position: relative;
    display: block;
    padding: var(--space-xl) var(--space-lg);
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--radius-lg);
    background-color: var(--color-bg-alt);
    text-align: center;
    cursor: pointer;
    transition:
        border-color var(--transition-fast),
        background-color var(--transition-fast);
}

.file-dropzone:hover,
.file-dropzone:focus-within {
    border-color: var(--color-primary);
    background-color: var(--color-primary-soft);
}

.file-dropzone[data-dragover="true"] {
    border-color: var(--color-primary);
    background-color: var(--color-primary-soft);
    border-style: solid;
}

.file-dropzone input[type="file"] {
    /* Oculto pero funcional */
    position: absolute;
    width: 100%;
    height: 100%;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

.file-dropzone__icon {
    display: inline-grid;
    place-items: center;
    width: 48px;
    height: 48px;
    border-radius: var(--radius-full);
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
    margin-block-end: var(--space-sm);
}

.file-dropzone__icon .icon {
    width: 24px;
    height: 24px;
}

.file-dropzone__title {
    display: block;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
    margin-block-end: 4px;
}

.file-dropzone__desc {
    display: block;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}



/* ── layout-cliente.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/layout-cliente.css — Shell del área cliente (empleado).

   Responsive:
     - Móvil (< 1024px): header + main + bottom-nav. Sin sidebar.
     - Desktop (≥ 1024px): sidebar colapsable + header + main. Sin bottom-nav.

   Patrón AntD Pro:
     - Logo solo en sidebar (desktop) o header (móvil, sin sidebar)
     - Header: collapse toggle + slot + avatar dropdown
     - Móvil: bottom-nav con 4 tabs, sin hamburger ni drawer
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Variables ────────────────────────────────────────────────────────── */
.layout-cliente {
    --sidebar-width: 240px;
    --sidebar-width-collapsed: 64px;
}

/* ── Móvil (default) ─────────────────────────────────────────────────── */

.layout-cliente {
    display: grid;
    grid-template-rows: var(--header-height) 1fr var(--bottom-nav-height);
    grid-template-columns: 1fr;
    grid-template-areas:
        "header"
        "main"
        "bottom";
    /* svh (no dvh) para evitar jitter con la URL bar del browser.
       Y restamos env(safe-area-inset-bottom) porque con viewport-fit=
       cover, svh incluye la zona bajo la barra Android / home indicator
       — sin restar, el nav-bottom queda con su parte inferior bajo la
       barra y las letras con descenders (Avisos, página) se cortan.
       Fallback: dvh + svh sin resta para browsers sin soporte env(). */
    height: 100dvh;
    height: 100svh;
    height: calc(100svh - env(safe-area-inset-bottom, 0));
    overflow: hidden;
}

.layout-cliente__header  { grid-area: header; }
.layout-cliente__header.app-header { padding-inline: var(--space-md); }
.layout-cliente__main    {
    grid-area: main;
    overflow-y: auto;
    /* Desactiva el rubber-band / overscroll bounce nativo — mismo
       patrón que .layout-app__main. Evita el "salto" de sticky interiors
       al hacer bounce en los límites del scroll en mobile. */
    overscroll-behavior: contain;
}
.layout-cliente__bottom  { grid-area: bottom; }
.layout-cliente__sidebar { display: none; }

/* Elementos solo desktop */
.layout-cliente__desktop-only { display: none; }

/* Marca en header — solo visible en móvil (desktop la tiene en sidebar) */
.layout-cliente__mobile-brand { display: flex; }

/* Contenido centrado en móvil */
.layout-cliente__main > .container {
    max-width: var(--container-md);
    margin-inline: auto;
    padding-inline: var(--space-md);
    padding-block: var(--space-md);
}

.layout-cliente__logo {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    object-fit: contain;
}

/* ── Desktop (≥ 1024px) ──────────────────────────────────────────────── */

@media (min-width: 1024px) {
    .layout-cliente {
        grid-template-rows: var(--header-height) 1fr;
        grid-template-columns: var(--sidebar-width) 1fr;
        grid-template-areas:
            "sidebar header"
            "sidebar main";
    }

    .layout-cliente[data-sidebar-collapsed="true"] {
        grid-template-columns: var(--sidebar-width-collapsed) 1fr;
    }

    .layout-cliente__sidebar {
        grid-area: sidebar;
        display: block;
    }

    .layout-cliente__sidebar-nav {
        display: flex;
        flex-direction: column;
        height: 100%;
        padding: var(--space-md);
        background: var(--color-bg-elevated);
        border-right: 1px solid var(--color-border-subtle);
        overflow-y: auto;
        overflow-x: hidden;
    }

    .layout-cliente__bottom { display: none; }
    .layout-cliente__desktop-only { display: inline-flex; }
    .layout-cliente__mobile-brand { display: none; }


    .layout-cliente__main > .container {
        max-width: var(--container-xl, 1200px);
        padding-inline: var(--space-xl);
        padding-block: var(--space-lg);
    }
}

/* ── Sidebar — collapse ──────────────────────────────────────────────── */

.layout-cliente[data-sidebar-collapsed="true"] .layout-cliente__sidebar-text,
.layout-cliente[data-sidebar-collapsed="true"] .nav-sidebar__link span,
.layout-cliente[data-sidebar-collapsed="true"] .nav-sidebar__link .badge {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition-fast);
}

.layout-cliente[data-sidebar-collapsed="true"] .layout-cliente__sidebar-nav {
    padding: var(--space-sm);
}

.layout-cliente[data-sidebar-collapsed="true"] .layout-cliente__sidebar-footer {
    justify-content: center;
}

/* ── Sidebar — navigation ────────────────────────────────────────────── */

.layout-cliente__nav-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
}

.layout-cliente__sidebar-brand {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-xs);
    margin-bottom: var(--space-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
}

.layout-cliente__sidebar-footer {
    padding-top: var(--space-md);
    border-top: 1px solid var(--color-border-subtle);
    margin-top: auto;
    overflow: hidden;
    white-space: nowrap;
}

/* ── Overrides .nav-sidebar__link para sidebar claro ─────────────────── */

.layout-cliente .nav-sidebar__link {
    color: var(--color-text);
}

.layout-cliente .nav-sidebar__link:hover {
    background: var(--color-bg-alt);
    color: var(--color-text);
}

.layout-cliente .nav-sidebar__link[aria-current="page"] {
    background: var(--color-primary-soft);
    color: var(--color-primary);
    font-weight: var(--font-weight-semibold);
}

.layout-cliente .nav-sidebar__link .icon {
    opacity: 0.65;
}

.layout-cliente .nav-sidebar__link[aria-current="page"] .icon {
    opacity: 1;
    color: var(--color-primary);
}

/* ── Avatar dropdown (patrón AntD Pro) ───────────────────────────────── */

.layout-cliente__user-menu {
    position: relative;
}

.avatar--header {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--color-primary-soft);
    color: var(--color-primary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
}

.layout-cliente__user-dropdown {
    position: absolute;
    top: calc(100% + var(--space-xs));
    right: 0;
    z-index: 100;
    min-width: 220px;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    padding: var(--space-2xs);
}

.layout-cliente__user-dropdown-header {
    display: flex;
    flex-direction: column;
    padding: var(--space-sm);
    gap: 2px;
}

.layout-cliente__user-dropdown-divider {
    border: none;
    border-top: 1px solid var(--color-border-subtle);
    margin: var(--space-2xs) 0;
}

.layout-cliente__user-dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    color: var(--color-text);
    cursor: pointer;
    text-decoration: none;
    background: transparent;
    border: none;
    width: 100%;
    font-family: inherit;
    transition: background-color var(--transition-fast);
}

.layout-cliente__user-dropdown-item:hover {
    background: var(--color-bg-alt);
}

.layout-cliente__user-dropdown-item .icon {
    width: 16px;
    height: 16px;
    opacity: 0.65;
    flex-shrink: 0;
}

.layout-cliente__user-dropdown-item--danger {
    color: var(--color-danger);
}

.layout-cliente__user-dropdown-item--danger:hover {
    background: var(--color-danger-soft);
}

/* ── Transición fade entre páginas (hx-boost) ────────────────────────── */
/* HTMX añade .htmx-swapping durante el swap-out y .htmx-settling tras
   el swap-in. Usamos opacity para un fade suave tipo SPA (100ms).
   Solo en el main del cliente — admin usa MPA sin transiciones. */

#main-content {
    transition: opacity 0.1s ease;
}

#main-content.htmx-swapping {
    opacity: 0;
}

/* ── Viewport muy pequeño (iPhone SE / 320px) ────────────────────────── */

@media (max-width: 375px) {
    .layout-cliente__main > .container {
        padding-inline: var(--space-sm);
    }
}

/* ── Safe areas iOS (notch + home indicator) ─────────────────────────── */

@supports (padding: env(safe-area-inset-top)) {
    .layout-cliente__header {
        padding-inline: env(safe-area-inset-left, 0) env(safe-area-inset-right, 0);
    }

    .layout-cliente__main > .container {
        padding-left: max(var(--space-md), env(safe-area-inset-left));
        padding-right: max(var(--space-md), env(safe-area-inset-right));
    }
}



/* ── descriptions.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/descriptions.css — AntD Descriptions

   Dos variantes:
     Default (plain)     → sin bordes, label pequeño gris arriba + valor abajo.
                           Apta para modales "ver" y páginas de detalle modernas.
     data-bordered="true"→ con bordes tipo tabla. Para cards de dashboard cuando
                           se prefiere lectura tabular compacta.

   Columnas (data-columns): 1 | 2 | 3 (default 2 desktop, 1 móvil).
   ═══════════════════════════════════════════════════════════════════════════ */

.descriptions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-md) var(--space-xl);
    margin: 0;
    padding: 0;
}

.descriptions[data-columns="1"] { grid-template-columns: 1fr; }
.descriptions[data-columns="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* ── Item default (plain) ────────────────────────────────────────────────── */
.descriptions__item {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.descriptions__label {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    font-weight: var(--font-weight-regular);
    margin: 0;
    letter-spacing: 0.01em;
}

.descriptions__value {
    font-size: var(--font-size-base);
    color: var(--color-text);
    font-weight: var(--font-weight-medium);    /* 500 — destaca sobre el label */
    margin: 0;
    word-break: break-word;
    line-height: 1.5;
}

.descriptions__value strong {
    font-weight: var(--font-weight-semibold);
}

/* ── Variante bordered (tabla estilo AntD) ───────────────────────────────── */
.descriptions[data-bordered="true"] {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    overflow: hidden;
    gap: 0;
}

.descriptions[data-bordered="true"] .descriptions__item {
    flex-direction: row;
    gap: 0;
    border-bottom: 1px solid var(--color-border);
}

.descriptions[data-bordered="true"]:not([data-columns="1"]) .descriptions__item:nth-last-child(-n+2) {
    border-bottom: none;
}

.descriptions[data-bordered="true"][data-columns="1"] .descriptions__item:last-child {
    border-bottom: none;
}

.descriptions[data-bordered="true"] .descriptions__label {
    width: 140px;
    flex-shrink: 0;
    padding: var(--space-sm) var(--space-md);
    background-color: var(--color-bg-subtle);
    border-right: 1px solid var(--color-border);
}

.descriptions[data-bordered="true"] .descriptions__value {
    flex: 1;
    padding: var(--space-sm) var(--space-md);
}

/* ── Responsive: 1 columna en móvil ──────────────────────────────────────── */
@media (max-width: 48rem) {
    .descriptions,
    .descriptions[data-columns="3"] {
        grid-template-columns: 1fr;
    }

    .descriptions[data-bordered="true"] .descriptions__item {
        flex-direction: column;
    }

    .descriptions[data-bordered="true"] .descriptions__label {
        width: auto;
        border-right: none;
        border-bottom: 1px solid var(--color-border);
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Detail View — hero + secciones con título (AntD Pro pattern)
   Usado en modales "ver" para dar jerarquía visual sin abusar de bordes.
   ═══════════════════════════════════════════════════════════════════════════ */

.detail-hero {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding-block-end: var(--space-md);
    border-block-end: 1px solid var(--color-border-subtle);
}

.detail-hero__avatar {
    flex-shrink: 0;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background-color: var(--color-primary-soft);
    color: var(--color-primary);
    display: inline-grid;
    place-items: center;
    font-size: 20px;
    font-weight: var(--font-weight-semibold);
    position: relative;
}

.detail-hero__dot {
    position: absolute;
    inset-block-end: 2px;
    inset-inline-end: 2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid var(--color-bg-elevated);
}
.detail-hero__dot[data-status="active"]   { background-color: var(--color-success); }
.detail-hero__dot[data-status="inactive"] { background-color: var(--color-text-muted); }

.detail-hero__body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.detail-hero__title {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
    line-height: 1.3;
}

.detail-hero__subtitle {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin: 0;
}

.detail-hero__badges {
    display: flex;
    gap: var(--space-xs);
    flex-wrap: wrap;
    margin-block-start: 4px;
}

/* Sección dentro del detalle — título destacado con accent bar + contenido */
.detail-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding-block: var(--space-sm);
}

.detail-section + .detail-section {
    border-block-start: 1px solid var(--color-border-subtle);
    padding-block-start: var(--space-lg);
}

.detail-section__title {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
    padding-inline-start: var(--space-sm);
    border-inline-start: 3px solid var(--color-primary);
    line-height: 1.3;
}



/* ── switch.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/switch.css — Patrón AntD Switch: toggle on/off visual para estados
   binarios (activo/inactivo, visible/oculto, etc.).

   Uso:
     <label class="switch">
         <input type="checkbox" name="active" value="1" checked>
         <span class="switch__track"></span>
         <span class="switch__label">Cuenta activa</span>
     </label>
   ═══════════════════════════════════════════════════════════════════════════ */

.switch {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    cursor: pointer;
    user-select: none;
}

.switch input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

.switch__track {
    position: relative;
    display: inline-block;
    width: 44px;
    height: 22px;
    background-color: rgba(0, 0, 0, 0.25);
    border-radius: 11px;
    transition: background-color var(--transition-fast);
    flex-shrink: 0;
}

.switch__track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 2px 4px rgba(0, 35, 11, 0.2);
    transition: left var(--transition-fast), background var(--transition-fast);
}

.switch input:checked + .switch__track {
    background-color: var(--color-primary);
}

.switch input:checked + .switch__track::after {
    left: 24px;
}

.switch input:focus-visible + .switch__track {
    box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2);
}

.switch input:disabled + .switch__track {
    opacity: 0.5;
    cursor: not-allowed;
}

.switch__label {
    font-size: var(--font-size-base);
    color: var(--color-text-primary);
}



/* ── impersonation-banner.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/impersonation-banner.css — banner persistente durante impersonation

   Se muestra arriba de TODAS las páginas mientras un admin está viendo la
   app como otro usuario. Color rojo intenso para que sea imposible olvidar
   que se está en este modo.
   ═══════════════════════════════════════════════════════════════════════════ */

.impersonation-banner {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background: #dc2626;           /* rojo AntD error */
    color: white;
    font-weight: 500;
    font-size: 0.9rem;
    position: sticky;
    top: 0;
    z-index: 500;                   /* encima de header/sidebar */
    box-shadow: 0 2px 4px rgb(0 0 0 / 0.15);
}

.impersonation-banner__icon {
    font-size: 1.2rem;
}

.impersonation-banner__text {
    flex: 1;
}

.impersonation-banner__btn {
    background: white;
    color: #dc2626;
    border: none;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background 0.15s;
}

.impersonation-banner__btn:hover {
    background: #fef2f2;
}

.impersonation-banner__btn:focus-visible {
    outline: 2px solid white;
    outline-offset: 2px;
}

/* Empuja el header/sidebar hacia abajo para que el banner no los tape */
body:has(.impersonation-banner) .layout-app__header {
    top: var(--impersonation-banner-height, 44px);
}



/* ── banner-2fa-gracia.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/banner-2fa-gracia.css — banner countdown para 2FA obligatoria
   en periodo de gracia.

   Se muestra cuando el user está sujeto a política de 2FA obligatoria, no
   la tiene activa aún, pero todavía está dentro del plazo. Color ámbar
   (warning) — sin ser tan agresivo como el de impersonation (rojo).
   Dismissible por sesión (sessionStorage, no cookie).
   ═══════════════════════════════════════════════════════════════════════════ */

.banner-2fa-gracia {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    background: #fffbe6;                  /* fondo AntD warning */
    border-bottom: 1px solid #ffe58f;
    color: #ad6800;                       /* texto contraste sobre amarillo */
    font-size: 0.9rem;
    position: sticky;
    top: 0;
    z-index: 499;                         /* justo debajo del banner de impersonation */
    box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);
}

.banner-2fa-gracia__icon {
    font-size: 1.1rem;
}

.banner-2fa-gracia__text {
    flex: 1;
}

.banner-2fa-gracia__btn {
    background: var(--color-primary);
    color: white;
    text-decoration: none;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: 0.85rem;
    transition: background 0.15s;
}

.banner-2fa-gracia__btn:hover {
    background: var(--color-primary-hover, #4096ff);
}

.banner-2fa-gracia__close {
    background: transparent;
    border: 0;
    color: inherit;
    font-size: 1.25rem;
    line-height: 1;
    padding: 0 var(--space-xs);
    cursor: pointer;
    opacity: 0.7;
}

.banner-2fa-gracia__close:hover {
    opacity: 1;
}



/* ── login-admin.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/login-admin.css — Pantalla de login backoffice (split-screen AntD Pro)

   Layout 2 columnas en desktop: izquierda panel de marca oscuro, derecha
   formulario compacto. En móvil el panel de marca se oculta.
   ═══════════════════════════════════════════════════════════════════════════ */

.login-admin-body {
    background: var(--color-bg);
    margin: 0;
    min-height: 100dvh;   /* fallback */
    min-height: 100svh;   /* estable ante retracción de URL bar en mobile */
}

.login-admin {
    display: grid;
    grid-template-columns: 1fr 1fr;
    min-height: 100dvh;   /* fallback */
    min-height: 100svh;   /* estable ante retracción de URL bar en mobile */
}

@media (max-width: 48rem) {
    .login-admin {
        grid-template-columns: 1fr;
    }
}

/* ── Panel izquierdo — marca ────────────────────────────────────────────── */
.login-admin__brand {
    background: linear-gradient(135deg, #001529 0%, #003a8c 100%);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xl);
    position: relative;
    overflow: hidden;
}

@media (max-width: 48rem) {
    .login-admin__brand { display: none; }
}

.login-admin__brand::before {
    content: '';
    position: absolute;
    top: -50%;
    right: -30%;
    width: 80%;
    height: 200%;
    background: radial-gradient(circle, rgb(22 119 255 / 0.15) 0%, transparent 70%);
    pointer-events: none;
}

.login-admin__brand-content {
    position: relative;
    max-width: 420px;
    width: 100%;
}

.login-admin__brand-logo {
    width: 64px;
    height: 64px;
    border-radius: var(--radius-md);
    background: rgb(255 255 255 / 0.1);
    display: grid;
    place-items: center;
    font-size: 1.8rem;
    font-weight: 700;
    margin-bottom: var(--space-lg);
    backdrop-filter: blur(10px);
}

.login-admin__brand-title {
    font-size: 2rem;
    margin: 0 0 var(--space-xs);
    color: white;
}

.login-admin__brand-sub {
    font-size: 1rem;
    opacity: 0.8;
    margin: 0 0 var(--space-xl);
}

.login-admin__brand-features {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.login-admin__brand-features li {
    display: flex;
    gap: var(--space-sm);
    align-items: flex-start;
    font-size: 0.95rem;
    opacity: 0.9;
}

.login-admin__brand-bullet {
    color: #1677FF;
    font-weight: 700;
}

.login-admin__brand-footer {
    position: absolute;
    bottom: var(--space-xl);
    left: var(--space-xl);
    font-size: 0.8rem;
    opacity: 0.5;
}

/* ── Panel derecho — form ───────────────────────────────────────────────── */
.login-admin__form-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xl);
}

.login-admin__form {
    width: 100%;
    max-width: 400px;
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.login-admin__form-header {
    margin-bottom: var(--space-md);
}

.login-admin__form-title {
    font-size: 1.5rem;
    margin: 0 0 var(--space-xs);
}

.login-admin__form-sub {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    margin: 0;
}



/* ── diagnostico.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/diagnostico.css — estilos del panel de diagnóstico

   Reutiliza .stat-card del estándar canónico. Solo añade:
   - Estado loading (spinner centrado)
   - Viewer de logs (monospace + scroll)
   ═══════════════════════════════════════════════════════════════════════════ */

.stat-card--loading {
    opacity: 0.7;
}

/* Transición suave en el swap HTMX (hx-swap="outerHTML transition:true").
   Evita el "pop" brusco al pasar de skeleton a card resuelta.
   Interpola background/border/opacity durante 150ms. */
.stat-card {
    transition:
        background-color 150ms ease,
        border-color     150ms ease,
        opacity          150ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .stat-card { transition: none; }
}

.stat-card__meta {
    margin-top: var(--space-sm);
}

.stat-card__meta summary {
    cursor: pointer;
    padding: var(--space-xs) 0;
}

.stat-card__meta[open] summary {
    margin-bottom: var(--space-xs);
}

/* ── Action toolbar ──────────────────────────────────────────────────────
   Agrupador de botones con título + hint, usado arriba del grid de cards.
   Separa acciones por intención (ej: Mantenimiento vs Pruebas de entrega)
   para que el usuario sepa qué hace cada grupo y —en las pruebas— a dónde
   llega el mensaje. */
.action-toolbar {
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-md) var(--space-lg);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.action-toolbar__head {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.action-toolbar__title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.action-toolbar__hint {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin: 0;
}

.action-toolbar__buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    align-items: stretch;
}

.action-toolbar__buttons .btn {
    min-height: 2.5rem;
    /* Si el label se parte en dos líneas, centrar verticalmente el texto
       y el icono dentro del botón para que todo el grupo quede alineado. */
    white-space: normal;
    line-height: 1.2;
    text-align: start;
}

/* En móvil: grid 2 columnas con altura uniforme — sin más wraps raros. */
@media (max-width: 48rem) {
    .action-toolbar__buttons {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: var(--space-sm);
    }
    .action-toolbar__buttons .btn {
        width: 100%;
        min-height: 3rem;
    }
}

/* Pantallas muy estrechas → 1 columna (botones más legibles). */
@media (max-width: 380px) {
    .action-toolbar__buttons {
        grid-template-columns: 1fr;
    }
}

/* ── Log viewer (modal de diagnóstico → Ver logs) ─────────────────────── */
.log-viewer {
    font-family: var(--font-mono, ui-monospace, 'Cascadia Mono', Consolas, monospace);
    font-size: 0.82rem;
    background: #0e1117;
    color: #e6edf3;
    padding: var(--space-md);
    border-radius: var(--radius-md);
    max-height: 60vh;
    overflow: auto;
    white-space: pre;
    line-height: 1.5;
    margin: 0;
}

/* Resaltar niveles típicos de Monolog */
.log-viewer {
    /* Fallback simple — el coloreado completo requeriría JS */
}



/* ── cron-comando.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/cron-comando.css — Input + botón "Copiar" alineados del panel cron

   Usado en cada card del setup para mostrar el comando a pegar en el
   sistema de crons del servidor. Input readonly + botón de copiar a la
   derecha con misma altura y sin gap entre ellos.
   ═══════════════════════════════════════════════════════════════════════════ */

.cron-comando {
    display: flex;
    width: 100%;
    margin-block: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-bg-alt, #f5f5f5);
}

.cron-comando__input {
    flex: 1;
    min-width: 0;                    /* permite que shrink correctamente */
    border: 0;
    padding: var(--space-sm) var(--space-md);
    font-family: var(--font-mono, ui-monospace, 'Cascadia Mono', Consolas, monospace);
    font-size: 0.82rem;
    color: var(--color-text);
    background: transparent;
    outline: none;
}

.cron-comando__input:focus {
    background: var(--color-bg);
}

.cron-comando__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
    border: 0;
    border-inline-start: 1px solid var(--color-border);
    background: var(--color-bg-elevated, white);
    color: var(--color-text);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s;
}

.cron-comando__btn:hover {
    background: var(--color-primary);
    color: white;
}

.cron-comando__btn:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}

.cron-comando__btn svg {
    width: 1rem;
    height: 1rem;
}

/* ── Header del card de tag (título + badge cron en la misma línea) ─────── */
.cron-tag-card__header {
    display: flex;
    align-items: flex-start;         /* top-align para que si el título ocupa
                                        2 líneas, el badge quede arriba a la derecha */
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
    /* Reserva espacio para 2 líneas del título — así la sección siguiente
       (descripción Tag/Frecuencia) arranca a la misma altura en todas las
       cards del grid, incluso si un título ocupa 2 líneas. */
    min-height: calc(var(--font-size-lg) * 2 * 1.25);
}

.cron-tag-card__title {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    line-height: 1.25;
    margin: 0;
    min-width: 0;                    /* evita que el título empuje al badge */
    /* Limita a 2 líneas con ellipsis si el nombre fuera muy largo */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.cron-tag-card__cron {
    flex-shrink: 0;                  /* el badge nunca se comprime ni baja */
    font-family: var(--font-mono, ui-monospace, monospace);
}



/* ── combo.css ───────────────────────────────── */
/* ── combo — searchable combobox ─────────────────────────────────────────
   Dropdown estilizado reemplazando la <datalist> nativa. Ver
   comboBuscador() en app.js para el comportamiento. */

.combo {
    position: relative;
}

.combo__dropdown {
    position: absolute;
    top: calc(100% + var(--space-xs));
    left: 0;
    right: 0;
    z-index: 100;
    max-height: 18rem;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-2xs);
    list-style: none;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}

.combo__option {
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: var(--font-size-sm);
    color: var(--color-text);
    transition: background-color 0.12s ease, color 0.12s ease;
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.4;
}

/* Hover y highlight (keyboard nav) — patrón AntD: fondo azul suave
   (blue-1) + texto primary. Contraste claro frente al fondo blanco. */
.combo__option:hover,
.combo__option.is-highlighted {
    background: var(--color-primary-soft);
    color: var(--color-primary-active);
}

.combo__option:hover .combo__option-secondary,
.combo__option.is-highlighted .combo__option-secondary {
    color: var(--color-primary);
}

.combo__option.is-empty {
    color: var(--color-text-muted);
    font-style: italic;
    cursor: default;
}

.combo__option.is-empty:hover {
    background: transparent;
    color: var(--color-text-muted);
}

.combo__option-secondary {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}



/* ── pull-refresh.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/pull-refresh.css — Indicador pull-to-refresh (Capacitor)

   Spinner circular tipo Material Design que aparece al arrastrar hacia
   abajo desde el top. Sigue el dedo proporcionalmente y rota al soltar.
   ═══════════════════════════════════════════════════════════════════════════ */

.pull-refresh {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%) translateY(-60px);
    z-index: 9999;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--color-bg-elevated, #fff);
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
    display: grid;
    place-items: center;
    transition: transform 0.2s ease, opacity 0.2s ease;
    opacity: 0;
    pointer-events: none;
}

.pull-refresh[data-visible="true"] {
    opacity: 1;
}

.pull-refresh[data-refreshing="true"] .pull-refresh__spinner {
    animation: pull-refresh-spin 0.6s linear infinite;
}

/* AntD Spin — 4 puntos en rotación (patrón Ant Design) */
.pull-refresh__spinner {
    width: 22px;
    height: 22px;
    position: relative;
}

.pull-refresh__spinner::before,
.pull-refresh__spinner::after,
.pull-refresh__spinner i::before,
.pull-refresh__spinner i::after {
    content: '';
    position: absolute;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-primary, #1677ff);
    animation: pull-refresh-antd 1s ease-in-out infinite;
}

.pull-refresh__spinner::before  { top: 0;    left: 50%; margin-left: -3px; animation-delay: 0s; }
.pull-refresh__spinner::after   { right: 0;  top: 50%;  margin-top: -3px;  animation-delay: 0.25s; }
.pull-refresh__spinner i::before { bottom: 0; left: 50%; margin-left: -3px; animation-delay: 0.5s; }
.pull-refresh__spinner i::after  { left: 0;   top: 50%;  margin-top: -3px;  animation-delay: 0.75s; }

@keyframes pull-refresh-antd {
    0%, 40%, 100% { opacity: 0.3; transform: scale(0.7); }
    20%           { opacity: 1;   transform: scale(1); }
}



/* ── offline-banner.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   blocks/offline-banner.css — Banner "Sin conexión" persistente

   Aparece/desaparece automáticamente según el estado de la red.
   Se coloca justo debajo del header, empujando el contenido.
   ═══════════════════════════════════════════════════════════════════════════ */

.offline-banner {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    background: #fff2e8;
    color: #d4380d;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    text-align: center;
    border-bottom: 2px solid #ff7a45;
    min-height: 44px;
}

.offline-banner[data-visible="true"] {
    display: flex;
}

.offline-banner__spinner {
    width: 16px;
    height: 16px;
    border: 2px solid #ffbb96;
    border-top-color: #d4380d;
    border-radius: 50%;
    animation: offline-spin 0.8s linear infinite;
    flex-shrink: 0;
}

@keyframes offline-spin {
    to { transform: rotate(360deg); }
}

.offline-banner--reconnected {
    background: #f6ffed;
    color: #389e0d;
    border-color: #52c41a;
}

.offline-banner--reconnected .offline-banner__spinner {
    animation: none;
    border-color: #52c41a;
    border-top-color: #52c41a;
}



/* 6. Theme overrides — se aplican al final para ganar en cascada sobre blocks */

/* ── theme-dark.css ───────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   theme-dark.css — Overrides para modo oscuro (AntD Dark Algorithm).
   Se activa con [data-theme="dark"] en <html>. Los componentes no cambian:
   solo los custom properties.
   ═══════════════════════════════════════════════════════════════════════════ */

html[data-theme="dark"] {

    /* ── Paleta primary — AntD Dark mantiene el mismo brand ──────────── */
    /* El primary queda igual; los "soft" y hover se invierten en luminancia */
    --color-primary-soft:   rgba(22, 119, 255, 0.15);
    --color-primary-border: #1668DC;

    /* ── Estados semánticos — versiones "soft" oscuras ───────────────── */
    --color-success-soft:   rgba(82, 196, 26, 0.12);
    --color-warning-soft:   rgba(250, 173, 20, 0.12);
    --color-danger-soft:    rgba(255, 77, 79, 0.14);

    /* ── Fondos / superficies — escala invertida ─────────────────────── */
    --color-bg:             #141414;
    --color-bg-alt:         #1F1F1F;
    --color-bg-layout:      #000000;
    --color-bg-elevated:    #1F1F1F;

    /* ── Bordes — tono oscuro pero visible sobre bg-elevated ─────────── */
    --color-border-subtle:  #303030;
    --color-border:         #424242;
    --color-border-strong:  #595959;

    /* ── Texto — alpha sobre blanco (patrón AntD dark) ───────────────── */
    --color-text:           rgba(255, 255, 255, 0.88);
    --color-text-muted:     rgba(255, 255, 255, 0.65);
    --color-text-subtle:    rgba(255, 255, 255, 0.45);

    /* ── Sombras — más marcadas sobre fondo oscuro ───────────────────── */
    --shadow-sm:
        0 1px 2px 0 rgba(0, 0, 0, 0.25),
        0 1px 6px -1px rgba(0, 0, 0, 0.20);
    --shadow-md:
        0 6px 16px 0 rgba(0, 0, 0, 0.35),
        0 3px 6px -4px rgba(0, 0, 0, 0.40);
    --shadow-lg:
        0 9px 28px 0 rgba(0, 0, 0, 0.45),
        0 12px 48px 16px rgba(0, 0, 0, 0.30);
    --shadow-xl: var(--shadow-lg);

    --focus-ring: 0 0 0 2px rgba(22, 119, 255, 0.35);

    /* Sidebar — en dark queda aún más oscuro */
    --color-sidebar-bg:         #000000;
    --color-sidebar-bg-darker:  #141414;
    --color-sidebar-border:     rgba(255, 255, 255, 0.06);
    --color-sidebar-hover:      rgba(255, 255, 255, 0.06);
}

/* Fondo del body en dark — tokens.css lo define con var(--color-bg-layout) */

/* Ajustes puntuales donde el valor rgba/alpha no basta y hace falta override */
html[data-theme="dark"] .form__input,
html[data-theme="dark"] .form__select,
html[data-theme="dark"] .form__textarea {
    color-scheme: dark;
}

html[data-theme="dark"] input[type="date"],
html[data-theme="dark"] input[type="number"],
html[data-theme="dark"] input[type="search"] {
    color-scheme: dark;
}

/* Flecha del select — svg con stroke adaptado al modo */
html[data-theme="dark"] .form__select {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23bbbbbb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
}

html[data-theme="dark"] .pagination__size select {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23bbbbbb' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
}


