:root{--bg:#f7f7f5;--panel:#ffffff;--card:#ffffff;--line:#c3c8cb;--line2:#8d9296;--text:#171719;--muted:#55585c;--red:#961c26;--gold:#8a5a2b;--green:#087443}
*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 80% -10%,rgba(230,57,70,.09),transparent 35%),var(--bg);color:var(--text);font-family:Manrope,system-ui,sans-serif;font-size:14px}.app-shell{display:flex;min-height:100vh}.sidebar{width:240px;background:rgba(15,16,17,.82);border-right:1px solid var(--line);padding:28px 18px;display:flex;flex-direction:column}.brand{display:flex;gap:11px;align-items:center;margin:0 9px 46px}.brand-mark{display:grid;place-items:center;width:31px;height:31px;border:1px solid var(--gold);color:var(--gold);font-family:Georgia;font-size:20px}.brand-logo-wrap{display:grid;place-items:center;width:130px;height:38px;border-radius:8px;background:#fff;border:1px solid var(--line);padding:4px 8px}.brand-logo{display:block;width:112px;height:auto;object-fit:contain}.brand strong{font-size:15px;letter-spacing:.12em}.brand small,.top-meta small{display:block;color:var(--muted);font:10px 'DM Mono';letter-spacing:.08em;margin-top:3px}.eyebrow{color:var(--muted);font:10px 'DM Mono';letter-spacing:.14em;text-transform:uppercase}.sidebar .eyebrow{padding:0 10px 12px}nav{display:grid;gap:4px}.nav-item{width:100%;background:none;border:0;color:var(--muted);font:500 13px Manrope;text-align:left;padding:12px 11px;display:flex;justify-content:space-between;cursor:pointer;border-left:2px solid transparent}.nav-item:hover,.nav-item.active{background:rgba(255,255,255,.04);color:var(--text);border-left-color:var(--red)}.nav-item span{font-family:'DM Mono';color:inherit}.sidebar-foot{margin-top:auto;color:var(--muted);font-size:11px;line-height:1.8;padding:12px 10px;border-top:1px solid var(--line)}code{font-family:'DM Mono';font-size:10px;color:var(--gold)}.status-dot{display:inline-block;width:6px;height:6px;background:var(--green);border-radius:50%;margin-right:6px}.main{flex:1;min-width:0}.topbar{height:96px;border-bottom:1px solid var(--line);padding:20px 38px;display:flex;justify-content:space-between;align-items:center}.topbar h1{font-size:24px;font-weight:600;letter-spacing:-.04em;margin:5px 0 0}.top-context{margin:0;color:var(--muted);font-size:12px}.top-meta{display:flex;align-items:center;gap:10px}.top-meta strong{font-size:12px;font-weight:600}.sync-pill{border:1px solid rgba(85,198,149,.22);color:#9ad9ba;padding:7px 10px;font:10px 'DM Mono'}.sync-pill i{display:inline-block;width:5px;height:5px;background:var(--green);border-radius:50%;margin-right:6px}.avatar{display:grid;place-items:center;background:rgba(212,165,116,.16);border:1px solid rgba(212,165,116,.35);color:var(--gold);width:31px;height:31px;font:11px 'DM Mono'}.content{padding:34px 38px 50px;max-width:1500px}.grid{display:grid;gap:16px}.stats{grid-template-columns:repeat(4,1fr);margin-bottom:30px}.card{background:var(--card);border:1px solid var(--line);padding:20px}.stat-card{min-height:118px}.stat-label{color:var(--muted);font-size:11px}.stat-value{font-size:34px;letter-spacing:-.07em;margin:17px 0 4px}.stat-note{font-size:11px;color:var(--gold)}.two-col{grid-template-columns:1.3fr .7fr}.section-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.section-head h2{font-size:14px;font-weight:600;margin:0}.section-head span{color:var(--muted);font:10px 'DM Mono'}.list{display:grid;gap:0}.list-row{display:grid;grid-template-columns:1fr auto;gap:14px;padding:15px 0;border-bottom:1px solid var(--line);align-items:center}.list-row:first-child{padding-top:2px}.list-row:last-child{border-bottom:0}.row-title{font-weight:600;font-size:13px}.row-sub{color:var(--muted);font-size:11px;margin-top:4px}.badge{font:10px 'DM Mono';padding:5px 8px;border:1px solid var(--line2);color:var(--muted)}.badge.active,.badge.connected{color:var(--green);border-color:rgba(85,198,149,.3)}.badge.offline{color:var(--gold);border-color:rgba(212,165,116,.3)}.bar{width:150px;height:4px;background:rgba(255,255,255,.07);margin-top:9px}.bar i{display:block;height:100%;background:var(--red)}.table{width:100%;border-collapse:collapse}.table th{text-align:left;color:var(--muted);font:10px 'DM Mono';font-weight:400;text-transform:uppercase;padding:0 0 13px;border-bottom:1px solid var(--line)}.table td{padding:15px 8px 15px 0;border-bottom:1px solid var(--line);font-size:12px}.table tr:last-child td{border-bottom:0}.empty{padding:26px 0;color:var(--muted);font-size:12px}.view-kicker{display:flex;justify-content:space-between;align-items:end;margin-bottom:22px}.view-kicker h2{font-size:30px;letter-spacing:-.06em;margin:8px 0 0}.button{background:var(--red);color:#fff;border:0;padding:10px 14px;font:600 11px Manrope;cursor:pointer}.button.secondary{background:transparent;border:1px solid var(--line2);color:var(--text)}.button:disabled{opacity:.45;cursor:not-allowed}.form-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.field label{display:block;color:var(--muted);font:10px 'DM Mono';margin-bottom:7px}.field input,.field select{width:100%;background:#0b0c0d;border:1px solid var(--line2);color:var(--text);padding:11px;font:12px Manrope}.wheel-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:center}.wheel{width:280px;height:280px;border-radius:50%;margin:20px auto;background:conic-gradient(#e63946 0 25%,#d4a574 25% 50%,#3d6f73 50% 75%,#8a5b69 75%);border:12px solid #191a1b;display:grid;place-items:center;position:relative}.wheel:after{content:'OZ';display:grid;place-items:center;background:#0f1011;border:1px solid var(--gold);border-radius:50%;width:75px;height:75px;color:var(--gold);font:24px Georgia}.pointer{position:absolute;top:-23px;color:var(--gold);font-size:25px}.notice{border:1px solid var(--line2);padding:12px 14px;color:var(--muted);font-size:12px;line-height:1.7}
@media(max-width:900px){.sidebar{width:200px}.topbar{padding:18px 22px}.content{padding:24px 22px}.stats{grid-template-columns:repeat(2,1fr)}.two-col,.wheel-layout{grid-template-columns:1fr}}
@media(max-width:620px){.sidebar{display:none}.topbar{height:auto;padding:18px}.top-meta .sync-pill,.top-meta div{display:none}.content{padding:20px 16px}.stats{gap:10px}.stat-card{padding:14px;min-height:100px}.stat-value{font-size:27px;margin-top:12px}.form-grid{grid-template-columns:1fr}.view-kicker{align-items:start;gap:14px;flex-direction:column}}
/* Ozelca clear product direction */
body{background:radial-gradient(circle at 85% -15%,rgba(181,31,42,.10),transparent 34%),var(--bg)}
.sidebar{background:#fff;border-right-color:var(--line)}
.nav-item:hover,.nav-item.active{background:#fcebed;color:var(--red);border-left-color:var(--red)}
.brand-mark{border-color:var(--red);color:var(--red)}
.bar{background:#eceeed}.field input,.field select{background:#fff;color:var(--text);border-color:var(--line2)}
.button{min-height:48px;border-radius:10px;background:var(--red)}.button:hover{background:#7e131d}.button:focus-visible,.nav-item:focus-visible,input:focus-visible{outline:3px solid #174ea6;outline-offset:2px}
.card{border-radius:16px;box-shadow:0 8px 24px rgba(23,23,25,.08)}.notice{border:1px solid var(--line);border-left:1px solid var(--line2);border-radius:10px;background:#f1f2f0;color:var(--text)}.success-notice{border-color:#9ad9ba;background:#edf8f2}.badge.connected{color:var(--green);border-color:#9ad9ba}.badge.offline{color:#8a5100;border-color:#d9b27d}
.wheel{border-color:#fff;box-shadow:0 0 0 1px var(--line2)}.wheel:after{background:#fff}.wheel.is-spinning{animation:ozelca-spin 1.6s cubic-bezier(.12,.7,.18,1)}
@keyframes ozelca-spin{to{transform:rotate(720deg)}}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}

/* Ozelca direction: editorial control room */
:root{--bg:#f4f2ee;--panel:#fbfaf8;--card:#fff;--line:#c4bfb5;--line2:#948e84;--text:#161616;--muted:#6b6965;--red:#e63946;--red-dark:#a91f2b;--ink:#202123}
body{background:radial-gradient(circle at 72% -12%,rgba(230,57,70,.16),transparent 30%),linear-gradient(135deg,#f7f5f1 0%,#f0efeb 100%);font-family:Manrope,system-ui,sans-serif}.sidebar{background:#202123;border-right:0;padding:25px 16px}.brand{margin:0 8px 44px}.brand-logo-wrap{width:136px;height:42px;background:transparent;border:0;padding:4px 8px}.brand-logo{width:120px;height:auto;object-fit:contain}.brand strong{display:none}.brand small{display:none}.sidebar .eyebrow{color:#9d9d99;font-size:9px;margin-bottom:4px}.nav-item{color:#b9b8b4;border-left:0;border-radius:8px;padding:12px 12px;transition:background .2s,color .2s,transform .2s}.nav-item:hover,.nav-item.active{background:#343538;color:#fff;border-left:0}.nav-item.active{box-shadow:inset 3px 0 0 var(--red)}.nav-item span{opacity:.55}.sidebar-foot{border-top:1px solid #3b3c3e;color:#a6a6a2}.main{background:transparent}.topbar{height:88px;background:rgba(251,250,248,.72);padding:18px 44px;border-bottom:1px solid var(--line)}.topbar h1{font-size:18px;letter-spacing:-.03em}.top-context{font-size:11px;letter-spacing:.06em;text-transform:uppercase}.content{padding:40px 44px 70px;max-width:1560px}.dashboard-intro{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:26px}.dashboard-intro h2{font-size:38px;line-height:1.02;letter-spacing:-.065em;margin:9px 0 10px;font-weight:700}.dashboard-intro p{color:var(--muted);margin:0;max-width:520px}.dashboard-actions{display:flex;gap:9px;white-space:nowrap}.button{border-radius:6px;background:var(--red);padding:12px 16px}.button:hover{background:var(--red-dark);transform:translateY(-1px)}.button.secondary{background:#fff;border-color:var(--line2)}.operation-hero{position:relative;min-height:210px;overflow:hidden;border-radius:10px;background:#252628;color:#fff;padding:30px 34px;margin-bottom:18px;display:flex;justify-content:space-between;align-items:flex-end}.operation-hero:after{content:"";position:absolute;inset:0;background:linear-gradient(110deg,transparent 15%,rgba(230,57,70,.25) 55%,transparent 80%);pointer-events:none}.hero-orbit{position:absolute;width:360px;height:360px;border:1px solid rgba(230,57,70,.5);border-radius:50%;right:13%;top:-150px}.hero-orbit:after{content:"";position:absolute;inset:34px;border:1px solid rgba(255,255,255,.21);border-radius:50%}.hero-content,.hero-signal{position:relative;z-index:1}.hero-label{color:#f08b92;text-transform:uppercase;letter-spacing:.16em;font:10px 'DM Mono'}.hero-content h3{font-size:31px;letter-spacing:-.05em;margin:12px 0 8px}.hero-content p{color:#c4c3c0;max-width:550px;margin:0 0 22px}.hero-meta{display:flex;gap:22px;color:#aaa9a5;font:10px 'DM Mono';text-transform:uppercase}.hero-signal{border-left:1px solid rgba(255,255,255,.2);padding-left:24px;min-width:170px}.hero-signal small{display:block;color:#aaa9a5;font:10px 'DM Mono';text-transform:uppercase;margin-bottom:9px}.hero-signal strong{font-size:19px;font-weight:600}.hero-signal i{display:block;width:8px;height:8px;background:#62d39e;border-radius:50%;margin-top:14px;box-shadow:0 0 0 5px rgba(98,211,158,.15)}.premium-stats{grid-template-columns:repeat(4,1fr);gap:12px;margin:0 0 18px}.premium-stats .stat-card{min-height:126px;border-radius:8px;padding:19px 20px;background:rgba(255,255,255,.8)}.premium-stats .stat-value{font-size:32px;margin:20px 0 5px}.stat-note{color:var(--muted)}.dashboard-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:18px}.card{border-radius:8px;background:rgba(255,255,255,.88);border-color:var(--line);padding:22px}.section-head{margin-bottom:18px}.section-head h2{font-size:16px}.section-caption{color:var(--muted);font-size:11px;margin:5px 0 0}.text-button{border:0;background:none;color:var(--red-dark);font:600 11px Manrope;cursor:pointer}.activity-list{display:grid}.activity-item{display:grid;grid-template-columns:12px 1fr auto;gap:12px;align-items:start;padding:14px 0;border-bottom:1px solid #eeece8}.activity-item:last-child{border-bottom:0}.activity-mark{width:8px;height:8px;border-radius:50%;margin-top:5px;background:#e8a64c;box-shadow:0 0 0 4px rgba(232,166,76,.12)}.activity-mark.done{background:#54b786;box-shadow:0 0 0 4px rgba(84,183,134,.12)}.activity-copy{display:grid;gap:3px}.activity-copy strong{font-size:13px}.activity-copy span{font-size:12px;color:var(--muted)}.activity-copy small{font:10px 'DM Mono';color:#96938d;margin-top:3px}.activity-status{font-size:10px;color:#b77922;font-weight:600}.activity-status.is-done{color:#2b8a61}.stock-list{display:grid;gap:17px}.stock-line{display:grid;grid-template-columns:1fr 110px 36px;gap:14px;align-items:center}.stock-line div:first-child{display:grid;gap:4px}.stock-line strong{font-size:12px}.stock-line span{font-size:10px;color:var(--muted)}.stock-line>b{font:12px 'DM Mono';text-align:right}.stock-meter{height:5px;background:#eceae7;border-radius:10px;overflow:hidden}.stock-meter i{display:block;height:100%;background:var(--red);border-radius:inherit}.stock-footer{border-top:1px solid var(--line);margin-top:22px;padding-top:14px;display:flex;justify-content:space-between;color:var(--muted);font-size:11px}.stock-footer strong{color:var(--text);font-weight:600}.decision-strip{margin-top:18px;border:1px solid #edc9cc;background:#fff8f8;border-radius:8px;padding:15px 18px;display:flex;align-items:center;gap:14px}.decision-icon{display:grid;place-items:center;width:28px;height:28px;background:var(--red);color:#fff;border-radius:50%;font-weight:700}.decision-strip div:nth-child(2){display:grid;gap:3px;flex:1}.decision-strip span{font-size:11px;color:var(--muted)}
@media(max-width:900px){.content{padding:28px 22px}.topbar{padding:18px 22px}.dashboard-intro{align-items:flex-start;flex-direction:column}.dashboard-intro h2{font-size:32px}.dashboard-grid{grid-template-columns:1fr}.premium-stats{grid-template-columns:repeat(2,1fr)}}
@media(max-width:620px){.content{padding:22px 16px}.dashboard-intro h2{font-size:28px}.dashboard-actions{width:100%}.dashboard-actions .button{flex:1}.operation-hero{padding:24px 20px;min-height:240px}.hero-signal{display:none}.hero-meta{gap:10px;flex-wrap:wrap}.premium-stats{gap:8px}.premium-stats .stat-card{padding:14px}.premium-stats .stat-value{font-size:26px}.decision-strip{align-items:flex-start;flex-wrap:wrap}.decision-strip .button{margin-left:42px}.stock-line{grid-template-columns:1fr 80px 28px}}

.eyebrow,.field label,.table th,.sync-pill{font-size:12px}.row-sub{font-size:12px}.table td{font-size:13px}.sidebar-foot{font-size:12px}.demo-credentials{margin-top:16px;background:#fcebed;border-color:#e8aeb4;color:var(--text)}.demo-credentials code{font-size:12px;color:#7e131d}
.logged-out .sidebar,.logged-out .topbar{display:none}.logged-out .main{width:100%;margin:0}.logged-out .content{max-width:560px;margin:12vh auto}.login-card{max-width:480px;margin:auto}.button-small{padding:8px 12px;font-size:11px}
/* Interface B · iPad-first field workspace */
.welcome-panel,.game-header{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;padding:10px 0 28px}.welcome-panel h2,.game-header h2{font-family:'Plus Jakarta Sans',sans-serif;font-size:clamp(30px,4vw,54px);line-height:1.03;letter-spacing:-.04em;margin:10px 0}.welcome-panel p,.game-header p{max-width:52ch;color:var(--muted);margin:0}.terrain-stats{grid-template-columns:repeat(4,1fr);margin-bottom:22px}.terrain-stats .stat-card{background:linear-gradient(180deg,#fff 0%,#fff 82%,#fef2f2 100%)}.mission-list{display:grid;gap:14px}.mission-card{display:flex;align-items:center;justify-content:space-between;gap:22px;padding:24px;background:linear-gradient(120deg,#fff 0%,#fff7f7 100%);border:1px solid var(--line);border-radius:18px}.mission-card h3{margin:7px 0 5px;font-size:22px}.mission-card p{margin:0;color:var(--muted)}.mission-progress{display:flex;flex-direction:column;align-items:flex-end}.mission-progress strong{font-size:30px;color:var(--red)}.mission-progress span{font-size:11px;color:var(--muted)}.mission-card.compact{padding:18px}.mission-card.compact h3{font-size:20px}.database-toolbar{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:18px}.database-toolbar input{flex:1;max-width:420px}.table-scroll{overflow-x:auto}.game-card{max-width:1000px;margin:auto;min-height:500px}.stepper{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:12px;margin-bottom:34px}.stepper span.active{color:var(--red);font-weight:700}.stepper i{height:1px;width:40px;background:var(--line)}.consent-line{display:flex;gap:10px;align-items:center;color:var(--muted);font-size:12px;margin:22px 0}.game-next{min-width:260px}.game-foot{display:flex;justify-content:space-between;align-items:center;max-width:1000px;margin:16px auto;color:var(--muted);font-size:12px}.success-notice{border-color:#89c5a8!important;background:#eefaf3!important}.proof-panel{display:grid;grid-template-columns:minmax(180px,.7fr) 1.3fr;gap:18px;margin:28px 0}.proof-gift{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:170px;border-radius:16px;background:#202123;color:#fff;text-align:center;font-size:38px}.proof-gift strong{font-size:18px;margin-top:12px}.proof-gift span{font-size:11px;color:#c8c8c8;margin-top:6px}.upload-zone{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;min-height:170px;padding:24px;border:1px dashed var(--red);border-radius:16px;background:#fff7f7;color:var(--text);text-align:center;cursor:pointer}.upload-zone input{position:absolute;opacity:0;width:1px;height:1px}.upload-zone span{font-size:12px;color:var(--muted)}.is-spinning{animation:spin 1.6s cubic-bezier(.2,.8,.2,1)}
@keyframes spin{to{transform:rotate(1080deg)}}
@media(max-width:900px){.sidebar{width:220px}.terrain-stats{grid-template-columns:repeat(2,1fr)}.welcome-panel,.game-header{align-items:flex-start;flex-direction:column}.welcome-panel .button{width:100%;min-height:52px}.mission-card{align-items:flex-start;flex-wrap:wrap}.mission-progress{align-items:flex-start}.mission-card .button{width:100%;min-height:50px}.game-card{padding:22px}.game-next{width:100%;min-height:54px}.game-foot{padding:0 22px}.stepper{gap:6px}.stepper i{width:14px}}
@media(prefers-reduced-motion:reduce){.is-spinning{animation:none}}

/* Ozelca B — Stitch/Figma operational refinement */
.section-label,.mission-state,.dialog-kicker{margin:0;color:var(--red);font:700 11px 'DM Mono',monospace;letter-spacing:.08em;text-transform:uppercase}.mission-card.is-selected{border-color:var(--red);background:#fff}.row-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.text-button.strong{color:var(--red);font-weight:700}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.participant-mode .sidebar,.participant-mode .topbar{display:none}.participant-mode .main{width:100%}.participant-mode .content{max-width:none;padding:0;min-height:100dvh}.participant-mode{background:#f8fafc}.game-shell{min-height:100dvh;padding:clamp(18px,3vw,34px);background:radial-gradient(circle at 90% 0,rgba(220,38,38,.09),transparent 30%),#f8fafc}.game-context{max-width:1080px;margin:0 auto 18px;min-height:72px;padding:14px 18px;border-radius:12px;background:#202123;color:#fff;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:18px}.game-context img{object-fit:contain}.game-context strong,.game-context span{display:block}.game-context span{margin-top:4px;color:#c5c7ca;font-size:13px}.game-context .text-button{color:#fff}.game-card{border-radius:12px;box-shadow:0 1px 3px rgba(15,23,42,.04);padding:clamp(22px,4vw,46px)}.game-intro h2,.handover-screen h2,.reward-reveal h2,.delivery-stage h2,.completion-screen h2{font:800 clamp(30px,5vw,54px)/1.04 'Plus Jakarta Sans',sans-serif;letter-spacing:-.04em;margin:10px 0 28px;text-wrap:balance}.game-intro{max-width:760px}.handover-screen,.reward-reveal,.completion-screen{min-height:330px;display:flex;flex-direction:column;align-items:flex-start;justify-content:center;max-width:760px;margin:auto}.handover-screen p,.reward-reveal p,.completion-screen p,.delivery-stage>p{font-size:16px;line-height:1.6;color:#475569;max-width:62ch}.wheel-stage{min-height:420px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px}.reward-reveal{padding:34px;border-radius:12px;background:#fef2f2;border:1px solid #fecaca}.reward-reveal h2{color:#b91c1c;margin-bottom:12px}.completion-mark{display:grid;place-items:center;width:76px;height:76px;border-radius:50%;background:#087443;color:#fff;font-size:38px}.proof-gift .gift-mark{display:grid;place-items:center;width:56px;height:56px;border:1px solid rgba(255,255,255,.35);border-radius:50%;font-weight:800;font-size:18px}.upload-zone:focus-within,button:focus-visible,.text-button:focus-visible{outline:3px solid rgba(220,38,38,.28);outline-offset:3px}.button,.nav-item,.text-button{touch-action:manipulation}.oz-dialog{position:relative;width:min(680px,calc(100% - 32px));border:1px solid #e2e8f0;border-radius:16px;padding:34px;background:#fff;color:#0f172a;box-shadow:0 20px 45px rgba(15,23,42,.18)}.oz-dialog::backdrop{background:rgba(15,23,42,.46);backdrop-filter:blur(2px)}.dialog-close{position:absolute;right:16px;top:14px;width:44px;height:44px;border:0;border-radius:50%;background:#f1f5f9;font-size:24px;cursor:pointer}.oz-dialog h2{font:800 30px/1.1 'Plus Jakarta Sans',sans-serif;letter-spacing:-.03em;margin:10px 48px 24px 0}.algorithm-steps{display:grid;gap:0}.algorithm-steps article{display:grid;grid-template-columns:48px 1fr;gap:16px;padding:18px 0;border-top:1px solid #e2e8f0}.algorithm-steps b{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#dc2626;color:#fff}.algorithm-steps strong{font-size:16px}.algorithm-steps p,.algorithm-note{margin:5px 0 0;color:#475569;line-height:1.55}.algorithm-note{padding:14px;border-radius:8px;background:#f8fafc}.oz-dialog form{display:grid;gap:10px}.oz-dialog form label{font-weight:700;margin-top:8px}.oz-dialog form input{min-height:48px;border:1px solid #cbd5e1;border-radius:8px;padding:10px 12px;font:16px Manrope,sans-serif}.oz-dialog form .button{margin-top:14px}.oz-dialog #edit-status{min-height:22px;color:#087443}.game-foot{padding-bottom:env(safe-area-inset-bottom)}
/* ⚠️⚠️ LES DEUX RÈGLES DE NAVIGATION ONT ÉTÉ RETIRÉES DE CE BLOC (≤900px), ET C'EST LE CŒUR DU
   CORRECTIF : elles transformaient la barre en RAIL DE 88px À GAUCHE — or une tablette en portrait
   fait 768 à 834px, donc c'est CE bloc qu'elle recevait, jamais celui de 620px. La barre ne
   descendait donc JAMAIS en bas sur une vraie tablette. Le rail est remplacé par la barre du bas,
   posée plus bas à ≤1024px. Ce qui reste ici (topbar, form-grid, game-context) n'a rien à voir. */
@media(max-width:900px){.topbar{height:78px}.top-meta .sync-pill,.top-meta>div{display:none}.form-grid{grid-template-columns:1fr}.proof-panel{grid-template-columns:1fr}.game-context{grid-template-columns:auto 1fr}.game-context .text-button{grid-column:1/-1;text-align:left}.stepper{overflow-x:auto}.stepper span{white-space:nowrap}.database-toolbar{align-items:stretch;flex-direction:column}.database-toolbar input{max-width:none;min-height:48px}.table{min-width:720px}}
/* ⚠️ LA PARTIE NAVIGATION A QUITTÉ CE BLOC : elle est désormais portée par celui de tablette
   (≤1024px), qui couvre AUSSI les petits écrans. Deux blocs qui posent la même barre finiraient par
   diverger au premier correctif — et c'est exactement ce qui a produit le rail de 88px. */
@media(max-width:620px){.content{padding-bottom:98px}.game-shell{padding:14px}.game-context{min-height:64px}.game-context img{width:88px}.game-context>div{display:none}.game-card{padding:20px 16px}.stepper{font-size:11px}.stepper i{min-width:10px}.wheel{width:min(70vw,280px);height:min(70vw,280px)}}

/* Interface B — collaborator home + database-driven wheel */
.collab-workspace{display:grid;grid-template-columns:minmax(300px,.78fr) minmax(430px,1.45fr);gap:18px;margin-bottom:18px}.collab-profile,.quick-game{position:relative;overflow:hidden;min-height:370px;border:1px solid var(--line);border-radius:22px}.collab-profile{padding:26px;background:linear-gradient(145deg,#202123 0%,#2b2c2f 100%);color:#fff}.collab-profile:after{content:"";position:absolute;width:220px;height:220px;right:-90px;bottom:-110px;border-radius:50%;background:rgba(230,57,70,.16)}.profile-head{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:14px}.profile-head h2{font-size:22px;margin:4px 0}.profile-head .section-label{color:#ff8b95}.profile-avatar{display:grid;place-items:center;width:58px;height:58px;border-radius:17px;background:linear-gradient(145deg,#e63946,#9d1723);font-weight:800;font-size:18px}.profile-handle{color:#aaaeb2}.profile-mission{display:flex;flex-direction:column;gap:7px;margin:34px 0 24px;padding:18px;border:1px solid #414347;border-radius:14px;background:rgba(255,255,255,.035)}.profile-mission span,.profile-mission small{color:#aaadb1}.profile-mission strong{font-size:18px}.profile-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.profile-metrics div{display:flex;flex-direction:column;padding:13px 10px;border-left:1px solid #46484b}.profile-metrics strong{font-size:25px}.profile-metrics span{color:#aaaeb2;font-size:11px}.profile-links{position:relative;z-index:1;display:flex;gap:18px;margin-top:25px}.profile-links .text-button{color:#fff}.quick-game{display:grid;grid-template-columns:minmax(250px,1fr) 300px;align-items:center;padding:30px;background:radial-gradient(circle at 87% 16%,rgba(230,57,70,.16),transparent 29%),linear-gradient(135deg,#fff 0%,#f8f5f2 100%)}.quick-game-copy h2{font:800 clamp(30px,4vw,48px)/1.02 'Plus Jakarta Sans',sans-serif;letter-spacing:-.05em;margin:8px 0}.quick-game-copy>p{color:var(--muted)}.reward-mini-list{display:flex;flex-wrap:wrap;gap:7px;margin:22px 0}.reward-mini-list span{padding:7px 10px;border:1px solid var(--line);border-radius:99px;background:rgba(255,255,255,.75);font-size:11px}.quick-launch{min-width:230px}.quick-wheel{display:grid;place-items:center}.collab-feed{box-shadow:none}
.wheel-frame{position:relative;display:grid;place-items:center;isolation:isolate}.wheel-small{width:230px;height:230px}.wheel-large{width:min(72vw,360px);height:min(72vw,360px)}.wheel-rotor{position:relative;width:100%;height:100%;border-radius:50%;/* AUCUN MOTIF REPETE : ce repli dessinait 8 rayons autour du disque. Client : « pas
   vraiment des rayures comme ça qui vont deranger dans l'oeil ». On le remplace par
   un aplat : le fond reel vient de `roueFond()`, ce n'est qu'un repli. */
  background:#222326;border:9px solid #fff;box-shadow:0 0 0 1px #cfd0d2,0 18px 48px rgba(32,33,35,.18);transition:transform 1.55s cubic-bezier(.12,.74,.12,1)}.wheel-rotor:before{content:"";position:absolute;inset:9%;border-radius:50%;border:1px solid rgba(255,255,255,.35)}.wheel-pointer{position:absolute;z-index:6;top:-5px;left:50%;width:0;height:0;transform:translateX(-50%);border-left:18px solid transparent;border-right:18px solid transparent;border-top:38px solid #f7c75e;filter:drop-shadow(0 4px 2px rgba(0,0,0,.2))}.wheel-pointer:after{content:"";position:absolute;width:12px;height:12px;left:-6px;top:-39px;border-radius:50%;background:#202123}.wheel-hub{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:66px;height:66px;transform:translate(-50%,-50%);border-radius:50%;background:#fff;color:#202123;border:7px solid rgba(255,255,255,.38);box-shadow:0 4px 18px rgba(0,0,0,.18);font-weight:900;letter-spacing:.06em}.wheel-prize{position:absolute;z-index:2;left:50%;top:50%;width:64px;margin-left:-32px;margin-top:-32px;text-align:center;color:#fff;font-size:10px;font-weight:700}.wheel-prize img,.wheel-prize>span{display:block;width:62px;height:62px;margin:auto;border:0;border-radius:0;background:transparent;color:#202123;object-fit:contain}.wheel-prize small{display:block;overflow:hidden;margin-top:4px;white-space:nowrap;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.45)}.wheel-large .wheel-prize{width:82px;margin-left:-41px;margin-top:-38px}.wheel-large .wheel-prize img,.wheel-large .wheel-prize>span{width:78px;height:78px}/* ⚠️⚠️ CETTE REGLE PORTAIT `transition:none` — ET C ETAIT TOUT LE BUG DE LA ROUE DE LA TABLETTE.
   Client (verbatim) : « La roue dans la tablette tourne rapidement, elle ne tourne pas comme le
   preview. Elle fait juste un petit truc et elle s arrete. »
   MESURE : la sequence du tirage ajoutait cette classe, ce qui posait `transition:none`, puis le
   serveur repondait et le code posait `rotate(1440deg)` -> sans transition, la roue SAUTAIT d un
   coup. D ou le « petit truc » (la boucle `wheel-loop` de 0,75 s) suivi d un arret sec.
   ⚠️ ON RETIRE DONC LES DEUX : le `transition:none` ET la boucle `wheel-loop`. Deux animations sur
   la MEME propriete (`transform`) se battent — et c est la boucle qui gagnait, donc la rotation
   posee par le code n etait jamais vue comme un mouvement.
   ⚠️ LA DUREE VIENT DE `--roue-duree` (posee sur `.wheel-rotor`), donc le CSS et le JS lisent la
   MEME valeur. Ce sont les 4,4 s du preview, la reference donnee par le client. */
.wheel-spinning{--roue-duree:4.4s;transition:transform var(--roue-duree) cubic-bezier(.17,.67,.16,1)}/* ⚠️ `@keyframes wheel-loop` EST RETIREE : elle faisait tourner la roue d un huitieme de tour en boucle, et surtout elle ECRASAIT la rotation posee par le code (meme propriete `transform`). La rotation est maintenant portee par la TRANSITION, comme dans le preview. */
@media(max-width:1050px){.collab-workspace{grid-template-columns:1fr}.quick-game{min-height:340px}}@media(max-width:700px){.quick-game{grid-template-columns:1fr;padding:24px}.quick-wheel{order:-1}.collab-profile{min-height:auto}.profile-head{grid-template-columns:auto 1fr}.profile-head .badge{grid-column:1/-1;width:max-content}.profile-links{flex-direction:column;align-items:flex-start}.wheel-small{width:210px;height:210px}}@media(prefers-reduced-motion:reduce){.wheel-spinning{animation:none}.wheel-rotor{transition-duration:.01ms!important}}

/* Interface A — operational database workspace */
/* Fiche collaboratrice (pop-up) */
.rec-card{cursor:pointer;transition:border-color .15s,box-shadow .15s}.rec-card:hover{border-color:var(--line2)}.rec-name-btn{background:none;border:0;padding:0;font:inherit;color:inherit;text-align:left;cursor:pointer}.rec-name-btn:hover h2,.rec-name-btn:focus-visible h2{color:var(--red)}
.hostess-table tbody tr{cursor:pointer}.hostess-table tbody tr:hover{background:#fcebed}
.profile-sheet{width:min(880px,calc(100vw - 32px));max-height:92vh;overflow:auto}
.sheet-top{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:20px;padding-bottom:20px;border-bottom:1px solid var(--line);margin-bottom:22px}
.sheet-photo{position:relative;width:104px;height:104px;border-radius:22px;overflow:hidden;border:1px solid var(--line);background:#f2f0ec;display:grid;place-items:center;font:800 30px 'Plus Jakarta Sans',sans-serif;color:#c9c5bf}
.sheet-photo img{width:100%;height:100%;object-fit:cover;object-position:center 18%}.sheet-photo .photo-upload{position:absolute;inset:auto 0 0 0;padding:5px;background:rgba(32,33,35,.82);color:#fff;font-size:10px;text-align:center;cursor:pointer}.sheet-photo .photo-upload input{display:none}
.sheet-id h2{margin:2px 0 6px;font-size:24px}.sheet-id .sheet-handle{color:var(--muted);font-family:'DM Mono',monospace;font-size:12px}
.sheet-section{margin-bottom:22px}.sheet-section>h3{margin:0 0 12px;font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);font-family:'DM Mono',monospace}
.pw-row{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:end}.pw-field{position:relative}.pw-field input{padding-right:46px}.pw-eye{position:absolute;right:6px;bottom:6px;width:36px;height:36px;border:1px solid var(--line);border-radius:8px;background:#fff;cursor:pointer;font-size:15px;line-height:1}
.sheet-actions{display:flex;gap:9px;flex-wrap:wrap;justify-content:flex-end;margin-top:24px;padding-top:20px;border-top:1px solid var(--line)}
.sheet-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:22px}.sheet-stat{padding:13px;border:1px solid var(--line);border-radius:12px;background:#faf9f7}.sheet-stat strong{display:block;font-size:22px}.sheet-stat span{color:var(--muted);font-size:11px}
.sheet-list{display:grid;gap:6px}.sheet-list-row{display:flex;justify-content:space-between;gap:12px;padding:8px 11px;border:1px solid var(--line);border-radius:8px;background:#faf9f7;font-size:12px}
.doc-row{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:8px 11px;border:1px solid var(--line);border-radius:8px;background:#faf9f7;font-size:12px}
.generated-pw{margin-top:8px;padding:11px 13px;border:1px solid #9ad9ba;border-radius:8px;background:#edf8f2;font-family:'DM Mono',monospace;font-size:14px}
@media(max-width:620px){.sheet-top{grid-template-columns:1fr}.sheet-photo{margin:auto}.pw-row{grid-template-columns:1fr}.sheet-stats{grid-template-columns:1fr}}
.badge.planned,.badge-planned{color:#8a5100;border-color:#d9b27d}
/* Switch de vue : liste ☰ / carrés ▦ */
.view-toggle{display:inline-flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;background:#fff}
.view-btn{width:42px;height:38px;border:0;background:#fff;color:var(--muted);font-size:16px;cursor:pointer;transition:background .14s,color .14s}
.view-btn+.view-btn{border-left:1px solid var(--line)}
.view-btn:hover{background:#fcebed;color:var(--red)}
.view-btn.active{background:#202123;color:#fff}
.entity-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(272px,360px));gap:14px;justify-content:start}
.entity-tile{position:relative;display:flex;flex-direction:column;min-height:186px;padding:20px;border:1px solid rgba(255,255,255,.14);border-radius:16px;background:linear-gradient(150deg,#26282a 0%,#1a1b1d 100%);color:#f4f3f1;cursor:pointer;transition:border-color .16s,transform .16s,box-shadow .16s;box-shadow:0 6px 18px -8px rgba(20,21,22,.35)}
.entity-tile:hover{border-color:rgba(255,255,255,.30);transform:translateY(-2px);box-shadow:0 12px 26px -10px rgba(20,21,22,.45)}
/* Mode négatif : l'intérieur de la carte suit le fond sombre (sinon textes et
   libellés restent pensés pour un fond clair et deviennent illisibles). */
.entity-tile h3{color:#fff}
.entity-tile p{color:rgba(244,243,241,.70)}
/* Les avatars/initiales doivent se détacher du fond sombre de la carte, sinon ils
   disparaissent (constaté : carré invisible, seules les lettres flottaient). */
.entity-tile .entity-tile-mark{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.20)}
.entity-tile .entity-tile-mark.campaign{background:var(--red);border:0}
.entity-tile .entity-tile-mark.establishment{background:rgba(212,165,116,.92);border:0;color:#202123}
.entity-tile .entity-tile-mark.goodie{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.12)}
.entity-tile .tile-chip{border-color:rgba(255,255,255,.24);background:rgba(255,255,255,.07);color:rgba(244,243,241,.84)}
.entity-tile .tile-kpi strong{color:#fff}
.entity-tile .tile-kpi span{color:rgba(244,243,241,.62)}
.entity-tile .text-button{color:#ff8b95}
.entity-tile .text-button.strong{color:#ff6b78}
.entity-tile .badge{border-color:rgba(255,255,255,.22);color:rgba(244,243,241,.74)}
.entity-tile .badge.active,.entity-tile .badge.connected{color:#4cd39a;border-color:rgba(76,211,154,.36)}
.entity-tile .badge.offline{color:rgba(244,243,241,.55)}
.entity-tile .badge-planned{color:#e8c37a;border-color:rgba(232,195,122,.34)}
.entity-tile .button{background:var(--red);color:#fff}
/* Compteurs cliquables de la carte campagne (raccourcis vers les sections de la fiche).
   Texte en gris CLAIR (jamais sombre) : illisible sur fond sombre. */
/* ═══ LES COMPTEURS : DES CARRÉS, MAIS PAS DANS LA CARTE CAMPAGNE ═══════════════
   Client : « les petits carrés de 20 établissements, 7 villes et 19 en campagne
   n'apparaissent pas bien ». Ils étaient des rubans plats (360 × 52 px). On les rend
   CARRÉS — mais UNIQUEMENT dans les vues de base de données : dans la carte de
   campagne, ils sont déjà serrés dans un carton étroit, et les y forcer coupait les
   libellés (« ÉVÉNEME… », « COLLAB. »). D'où le `:not(.entity-tile)`. */
.tile-counts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:16px 0 4px;
  max-width:430px;margin-left:auto;margin-right:auto}
.carte-grille .tile-counts,
.profile-sheet .tile-counts,
.entity-tile .tile-counts{max-width:none;margin:14px 0 2px}
/* MESURE : sur la tuile campagne, les compteurs avaient un fond CLAIR
   rgb(250,249,247) avec un chiffre BLANC rgb(255,255,255) — blanc sur blanc,
   contraste 1.02:1, donc invisibles. C'est le « ils sont en blanc » du client.
   Fond translucide sombre + texte blanc : le contraste passe a ~12:1. */
.entity-tile .tile-count{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.26)}
.entity-tile .tile-count strong{color:#fff}
.entity-tile .tile-count span{color:rgba(244,243,241,.82);font-size:8.5px;
  white-space:normal;text-align:center;line-height:1.15;letter-spacing:.01em}
.entity-tile .tile-count:hover{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.44)}
.entity-tile .tile-count.is-zero strong{color:rgba(244,243,241,.5)}
.entity-tile .tile-count.is-zero span{color:rgba(244,243,241,.42)}
.tile-count{display:grid;gap:2px;place-content:center;justify-items:center;text-align:center;
  aspect-ratio:1/1;min-height:0;padding:10px;border:1px solid var(--line);border-radius:12px;
  background:#faf9f7;cursor:pointer;font:inherit;transition:border-color .15s,background .15s,transform .15s}
/* Dans un carton étroit (campagne), le compteur garde sa forme compACTER : un carré
   de 92 px mangerait toute la largeur et tronquerait le libellé. */
.entity-tile .tile-count,
.profile-sheet .tile-count{aspect-ratio:auto;padding:8px 6px;border-radius:10px}
.entity-tile .tile-count strong,
.profile-sheet .tile-count strong{font-size:18px}
.entity-tile .tile-count span,
.profile-sheet .tile-count span{font-size:9px;letter-spacing:.03em;white-space:normal;
  text-align:center;max-width:100%}
.tile-count:hover{border-color:var(--red);background:#fcebed;transform:translateY(-1px)}
.tile-count strong{font:800 26px/1 Manrope,sans-serif;color:var(--text)}
.tile-count span{font:600 10px/1.2 Manrope,sans-serif;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tile-count.is-zero strong{color:#b9b5af}

/* ═══ LE TRI, CENTRÉ ════════════════════════════════════════════════════════════
   Client : « trié par nom et tout, elle n'est pas centrée ». Le tri se lit au-dessus
   des cartes : on le centre pour qu'il réponde aux compteurs, eux aussi centrés. */
.tri-bar{display:flex;align-items:center;flex-wrap:wrap;gap:7px;margin-bottom:16px;
  justify-content:center;text-align:center}
.tri-bar .tri-label{margin-right:2px}
/* ═══ LA BARRE DE RECHERCHE DES BASES DE DONNÉES ═══════════════════════════════
   Client : « on doit toujours avoir une barre de recherche en haut… à côté du tri,
   ou en dessous ». Elle est posée juste après la barre de tri, centrée comme elle,
   discrète, avec une croix pour effacer d'un geste. */
.recherche-barre{display:flex;align-items:center;gap:0;position:relative;
  max-width:430px;margin:0 auto 14px;padding:0}
.recherche-champ{width:100%;height:42px;padding:0 40px 0 15px;
  border:1px solid var(--line);border-radius:11px;background:#fff;color:var(--text);
  font:500 13.5px Manrope,sans-serif;outline:none;
  transition:border-color .15s,background .15s}
.recherche-champ::placeholder{color:#a9a5a0}
.recherche-champ:focus{border-color:var(--red);background:#fff}
/* On masque la croix native du champ `type=search` : elle double la nôtre. */
.recherche-champ::-webkit-search-cancel-button{display:none;-webkit-appearance:none}
.recherche-effacer{position:absolute;right:8px;top:50%;transform:translateY(-50%);
  width:26px;height:26px;display:grid;place-items:center;border:0;border-radius:8px;
  background:rgba(23,23,25,.06);color:#6f6c67;font-size:15px;line-height:1;cursor:pointer;
  transition:background .15s,color .15s}
.recherche-effacer:hover{background:#fcebed;color:var(--red)}
/* Quand rien ne correspond : on le dit, sinon l'écran semble cassé. */
.recherche-vide{margin:26px auto 0;text-align:center;color:var(--muted);
  font:500 13.5px Manrope,sans-serif}

.tile-count:hover{border-color:rgba(255,255,255,.38);background:rgba(255,255,255,.11);transform:translateY(-1px)}
.tile-count strong{font:800 19px/1 Manrope,sans-serif;color:#fff}
.tile-count span{font:600 10px/1.2 Manrope,sans-serif;letter-spacing:.05em;text-transform:uppercase;color:#c9c7c3}
.tile-count.is-zero strong{color:#a9a7a3}
.tile-count.is-zero span{color:#8d8b87}
/* L'entreprise commanditaire se lit en doré clair (accent de la charte). */
.tile-company{color:#d9bd97 !important;font-weight:600}
/* Dates de la campagne : gris clair (jamais sombre sur fond sombre). */
.tile-date{margin:6px 0 0 !important;color:#bab8b4 !important;font:600 11px/1.4 Manrope,sans-serif}
.tile-date.is-empty{color:#83817d !important;font-style:italic}
.tile-counts{grid-template-columns:repeat(3,1fr)}
.entity-tile .tile-actions{justify-content:flex-end}
/* La section visée depuis un compteur de carte est brièvement mise en évidence. */
.sheet-section{scroll-margin-top:16px}
.section-flash{animation:sectionFlash 1.6s ease-out}
@keyframes sectionFlash{0%{background:rgba(230,57,70,.16);border-radius:12px}100%{background:transparent}}
/* ── Fiche campagne = LA MÊME CARTE, EN GRAND ───────────────────────────────
   Clic sur la carte → la fiche reprend son identité (fond filigrané, couverture,
   avatar, titre, entreprise, dates, compteurs) en beaucoup plus grand.
   « Modifier » la fait basculer en blanc éditable (voir .sheet-readonly). */
.profile-sheet{width:min(1120px,calc(100vw - 40px));max-height:94vh;overflow:auto}
/* ══ LA COUVERTURE COLLE AUX BORDS RÉELS DE LA COQUE ═══════════════════════════════════
   Client : « on voit pas l'image de couverture en haut, elle tient pas les bordures : il y a
   des bordures, elle n'est pas sur la taille des bordures du pop up. »

   MESURÉ, ET LE CALCUL EST EXACT :
       la coque  : <dialog id=campaign-sheet>  padding 0  ·  margin 10px/10px  ·  largeur 1556
       son enfant <form>                       padding 28px/28px
       .cover-field : marges −28/−28  -> elle se posait a 45 px du haut, 10 de gauche, −66 a droite

   LA MARGE NÉGATIVE DOIT VALOIR LE PADDING DU PARENT **PLUS** LA MARGE DE LA COQUE.
       −28 supposait que la coque avait un padding de 28 ; mais la coque porte `margin: 10px`,
       qui décale son contenu de 10 px. Il fallait donc −28 − 10 = −38 à gauche... NON :
       le bon calcul est celui qui ANNULCE le padding du form ET la marge du dialog.
       MESURÉ : pour coller, la marge gauche doit valoir −10 px (bord de la coque moins le
       parent), et la largeur doit couvrir 1556 au lieu de 1481 (il manquait 19 px).

   ⚠️ POURQUOI MA PREMIÈRE CORRECTION N'A PAS PRIS : `.profile-sheet .cover-field` a DEUX
   classes, `.cover-field` en a UNE. Deux classes gagnent sur une, quel que soit l'ordre. C'est
   EXACTEMENT le piège de l'ancrage du logo, une heure plus tôt. On corrige donc AU MÊME NIVEAU.
   ══════════════════════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════════════════
   LA COUVERTURE MONTE JUSQU'AU BORD — ELLE PASSE DERRIÈRE LA BARRE DE FERMETURE

   Client : « on voit pas l'image de couverture en haut [...] elle n'est pas sur la taille des
   bordures du pop up. »

   MESURÉ, en trois passes, et le dernier chiffre manquait :
       la coque        : haut = 54
       la couverture   : haut = 95   ->  ÉCART = 41 px
       ce qui occupe ces 41 px : <div class="sheet-pin-bar">  h=43  position=STICKY  z=6
       le parent <form> : padding-haut = 28px
       la coque         : margin 10px

   LE CALCUL COMPLET : 28 (padding du form) + 10 (marge de la coque) = 38 px pour annuler les
   retraits ; PLUS la hauteur de la barre sticky (43 px) pour que l'IMAGE commence au bord.
   -> marge haute = -(38 + 43) = -81 px.

   ⚠️ LA BARRE GARDE SON `z-index: 6` ET SA POSITION `sticky` : elle DOIT rester au-dessus et
   rester accessible. On ne la déplace pas — on fait passer la couverture DERRIÈRE elle, ce qui
   est le comportement des fiches qui ont une couverture (l'image monte jusqu'au bord, les
   boutons flottent dessus). C'est aussi ce qui rend l'image VRAIMENT pleine largeur.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.profile-sheet .cover-field{
  /* Marge haute : on annule le padding du parent (28) + la marge de la coque (10) + la barre
     sticky (43). Les trois valeurs sont MESURÉES, pas devinées. */
  margin: calc(-28px - 10px - 43px) calc(-28px - 10px) 0;
  border-radius: 0;      /* l'arrondi vient de la COQUE : deux arrondis = une couture visible */
  width: calc(100% + 28px + 10px + 28px + 10px);
  max-width: none;
  overflow: hidden;
  /* Elle passe SOUS la barre de fermeture, qui garde son z-index 6. */
  position: relative;
  z-index: 1;
}
.profile-sheet .cover-pick{height:240px;border-bottom:0}
.profile-head{padding:0 30px;margin-top:-64px;align-items:flex-end}
.profile-head .sheet-avatar-slot .image-pick{width:126px;height:126px;border-width:5px}
.profile-id{padding-top:0;padding-bottom:8px}
.profile-id h2{font-size:30px}
.profile-id .dialog-kicker{font-size:12px}
.profile-meta{margin-top:8px}
/* Compteurs dans la fiche : repris de la carte, en plus grand */
.profile-sheet .tile-counts{grid-template-columns:repeat(6,1fr);gap:10px;margin:16px 30px 4px;padding:0}

.profile-sheet .tile-count strong{color:var(--text);font-size:24px}
.profile-sheet .tile-count span{color:var(--muted);font-size:10px}

.profile-sheet .tile-count.is-zero strong{color:#b9b5af}
.profile-sheet .badge{font-size:11px;padding:6px 11px}
/* Cartes compactes (goodies, collaboratrices, événements) dans la fiche */
.mini-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:11px;margin-top:4px}
.mini-card{position:relative;display:flex;flex-direction:column;gap:3px;padding:13px 13px 11px;border:1px solid var(--line);border-radius:13px;background:#fff;cursor:pointer;transition:border-color .15s,transform .15s}
.mini-card:hover{border-color:var(--line2);transform:translateY(-2px)}
.mini-card:focus-visible{outline:3px solid #174ea6;outline-offset:2px}
.mini-card h4{margin:2px 0 0;font-size:13px;line-height:1.3}
.mini-card-sub{margin:0 !important;color:var(--muted) !important;font-size:11px}
.mini-card-av{width:52px;height:52px;border-radius:12px;overflow:hidden;background:#f1efec;display:grid;place-items:center;margin-bottom:5px}
.mini-card-av.is-round{border-radius:50%}
.mini-card-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.mini-card-av .card-fallback{font:800 15px Manrope;color:#8b8781}
.mini-card-stats{display:flex;gap:9px;flex-wrap:wrap;margin-top:6px;color:var(--muted);font-size:10px}
.mini-card-stats b{color:var(--text);font-size:12px}
.mini-card-x{position:absolute;top:7px;right:7px;width:20px;height:20px;display:grid;place-items:center;border-radius:50%;background:rgba(32,33,35,.07);color:var(--muted);font-size:13px;line-height:1;cursor:pointer}
.mini-card-x:hover{background:var(--red);color:#fff}
/* Goodie : carte en NÉGATIF (fond blanc) — le visuel produit reste en couleurs,
   sinon une image blanche sur fond blanc serait invisible. */
.goodie-tile{align-items:center;text-align:center;background:#fff;padding:14px 12px}
.goodie-tile .goodie-visual{width:100%;height:96px;display:grid;place-items:center;margin-bottom:8px;background:#f4f2ee;border-radius:10px}
.goodie-tile .goodie-visual img{max-width:100%;max-height:92px;object-fit:contain}
.goodie-tile .card-fallback{font:800 20px Manrope;color:#c9c5be}
.goodie-tile h4{color:var(--text)}
.goodie-tile .mini-card-stats{justify-content:center}
/* Collaboratrice : rang large, photo à gauche et nom derrière. */
.person-row{flex-direction:row;align-items:center;gap:12px;padding:10px 12px;grid-column:1/-1}
.person-row .mini-card-av{width:44px;height:44px;margin:0;flex:none}
.person-row .mini-card-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.person-id{flex:1;min-width:0}
.person-id h4{margin:0;font-size:14px}
.person-id .mini-card-sub{margin:2px 0 0 !important}
.person-row .mini-card-x{position:static;flex:none;background:rgba(32,33,35,.06)}
/* Événement : pastille date + TITRE en avant (le lieu passe en second). */
.event-card{flex-direction:row;align-items:center;gap:12px;padding:11px 13px;grid-column:1/-1}
.event-card .event-date{display:grid;justify-items:center;min-width:48px;padding:7px 6px;border-radius:10px;background:#f4f2ee;flex:none}
.event-card .event-date b{font:800 17px/1 Manrope;color:var(--red-dark)}
.event-card .event-date span{font:600 9px/1.4 Manrope;text-transform:uppercase;color:var(--muted)}
.event-main{flex:1;min-width:0}
.event-main h4{margin:0;font-size:14px}
.event-main .mini-card-sub{margin:2px 0 0 !important}
/* Les goodies restent en grille de 3 même quand les rangs prennent toute la largeur. */
.mini-grid{gap:10px}
/* ── Calendrier cliquable (choix de la date d'un événement) ─────────────────── */
.field{position:relative}
.date-picker{position:absolute;z-index:40;margin-top:6px;width:272px;padding:12px;border:1px solid var(--line2);border-radius:14px;background:#fff;box-shadow:0 14px 34px -12px rgba(20,21,22,.3)}
.dp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:9px}
.dp-head strong{font:700 13px Manrope;text-transform:capitalize;color:var(--text)}
.dp-nav{width:26px;height:26px;border:1px solid var(--line);border-radius:8px;background:#fff;cursor:pointer;font-size:14px;line-height:1;color:var(--text)}
.dp-nav:hover{border-color:var(--red);color:var(--red)}
.dp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}
.dp-dow{text-align:center;font:700 9px 'DM Mono',monospace;color:var(--muted);padding:3px 0;text-transform:uppercase}
.dp-jour{aspect-ratio:1;border:1px solid transparent;border-radius:8px;background:#f6f5f2;cursor:pointer;font:600 12px Manrope;color:var(--text)}
.dp-jour:hover{border-color:var(--red);background:#fcebed;color:var(--red-dark)}
.dp-jour.dp-sel{background:var(--red);color:#fff;border-color:var(--red)}
.dp-jour.dp-off{background:transparent;color:#c8c5c0;cursor:not-allowed;text-decoration:line-through}
.dp-vide{aspect-ratio:1}
/* Le champ date se lit comme un bouton de calendrier */
#session-date{cursor:pointer}
#session-date:focus{border-color:var(--red)}
.entity-tile-top{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:14px}
.entity-tile-mark{display:grid;place-items:center;width:56px;height:56px;border-radius:13px;background:#202123;color:#fff;font:800 15px Manrope,sans-serif;letter-spacing:.04em;overflow:hidden;flex:none}
.entity-tile-mark img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.entity-tile-mark.campaign{background:var(--red)}
.entity-tile-mark.campaign.has-image{background:transparent;border:0}
.entity-tile-mark.campaign.has-image img{object-fit:cover;object-position:center 42%}
.entity-monogram.has-image{background:transparent;border:1px solid var(--line);padding:0;display:block}
.entity-monogram.has-image img{display:block}
.rec-main{display:flex;align-items:center;gap:12px}
.rec-main strong{display:block}
/* Avatar de campagne dans l'en-tête de la fiche (modèle profil social) */
.sheet-avatar-slot{display:block;flex:none}
.sheet-avatar-slot .image-pick{width:88px;height:88px}
.sheet-avatar-slot .image-pick-initials{font-size:26px}
.sheet-avatar-slot .image-pick-edit,.sheet-avatar-slot .row-sub{display:none}
.entity-tile-mark.establishment{background:#d4a574;color:#202123}
.entity-tile-mark.establishment.has-image{background:transparent;border:0}
.entity-tile-mark.has-image{background:transparent;border:0}
.entity-tile-mark.has-image img{width:100%;height:100%;object-fit:cover}
.entity-tile-mark.goodie{background:transparent;border:0;color:var(--muted)}.entity-tile-mark.goodie img{width:100%;height:100%;object-fit:contain;padding:2px}
.entity-tile h3{margin:0 0 5px;font-size:16px;line-height:1.25}
.entity-tile p{margin:0;color:var(--muted);font-size:12px;line-height:1.5}
.entity-tile-foot{margin-top:auto;padding-top:14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.tile-chip{padding:5px 9px;border:1px solid var(--line);border-radius:99px;background:#f8f7f4;color:#55585c;font-size:11px}
.tile-kpi{margin-left:auto;text-align:right}
.tile-kpi strong{display:block;font-size:19px;line-height:1}
.tile-kpi span{color:var(--muted);font-size:10px}
.tile-actions{display:flex;gap:9px;align-items:center;justify-content:space-between;margin-top:12px}
.view-kicker-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
@media(max-width:620px){.entity-grid{grid-template-columns:1fr}.view-kicker-actions{width:100%}.view-kicker-actions .button{flex:1}}
/* Pop-up campagne : barre de statut, dates sur une ligne, affectations, picker */
.campaign-statusbar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:20px;padding:11px 14px;border:1px solid var(--line);border-radius:10px;background:#faf9f7}
.status-switch{cursor:pointer;user-select:none;transition:transform .12s,filter .12s}
.status-switch:hover{filter:brightness(.96);transform:translateY(-1px)}
.campaign-dateline-inline{color:var(--muted);font:12px 'DM Mono',monospace}
.campaign-dateline{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.section-count{display:inline-grid;place-items:center;min-width:22px;height:22px;padding:0 6px;margin-left:6px;border-radius:99px;background:var(--red);color:#fff;font:700 11px 'DM Mono',monospace}
.assign-list{display:grid;gap:6px;margin-bottom:12px}
.assign-row{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;gap:11px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:#faf9f7;font-size:13px}
.assign-avatar{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:#202123;color:#fff;font:700 12px Manrope,sans-serif;overflow:hidden}
.assign-avatar img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.assign-name small{color:var(--muted);font-family:'DM Mono',monospace;font-size:11px}
.assign-loc{color:var(--muted);font-size:12px}
.assign-add{position:relative}
.assign-add>input{width:100%;min-height:44px}
.assign-picker{position:absolute;z-index:30;left:0;right:0;top:calc(100% + 4px);display:none;max-height:230px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:#fff;box-shadow:0 12px 28px rgba(23,23,25,.12)}
.assign-picker.open{display:block}
.assign-picker button{display:block;width:100%;padding:11px 13px;border:0;border-bottom:1px solid #eceae6;background:#fff;text-align:left;cursor:pointer;font:inherit}
.assign-picker button:hover,.assign-picker button:focus{background:#fcebed;color:#a91f2b}
.assign-picker button small{color:var(--muted);font-family:'DM Mono',monospace;font-size:11px}
/* Bloc « recherche discrète » : une barre avec loupe + panneau déroulant au clic */
.picker-block{margin-top:14px;padding-top:13px;border-top:1px dashed var(--line);position:relative}
.picker-block>label{display:block;color:var(--muted);font:10px 'DM Mono',monospace;letter-spacing:.06em;text-transform:uppercase;margin-bottom:7px}
.picker-search{position:relative}
.picker-search input{width:100%;min-height:42px;border:1px solid var(--line2);border-radius:99px;background:#fff;color:var(--text);padding:10px 40px 10px 16px;font:13px Manrope,sans-serif;transition:border-color .16s,box-shadow .16s}
.picker-search input:focus{outline:none;border-color:var(--red);box-shadow:0 0 0 3px rgba(230,57,70,.1)}
.picker-search .picker-mag{position:absolute;right:15px;top:50%;transform:translateY(-50%);color:#a8a6a1;font-size:15px;pointer-events:none}
.picker-search .picker-clear{position:absolute;right:12px;top:50%;transform:translateY(-50%);width:22px;height:22px;border:0;border-radius:50%;background:#f1efec;color:#7a7873;font-size:12px;line-height:1;cursor:pointer;display:none}
.picker-search.has-value .picker-clear{display:grid;place-items:center}
.picker-search.has-value .picker-mag{display:none}
/* Panneau déroulant : caché par défaut, s'ouvre au clic/focus */
.picker-panel{position:absolute;z-index:40;left:0;right:0;top:calc(100% + 6px);display:none;max-height:212px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:#fff;box-shadow:0 14px 32px rgba(23,23,25,.14)}
.picker-panel.open{display:block}
.picker-panel .picker-row{display:flex;align-items:center;gap:10px;width:100%;padding:10px 13px;border:0;border-bottom:1px solid #f2f0ec;background:#fff;text-align:left;cursor:pointer;font:13px Manrope,sans-serif;color:var(--text)}
.picker-panel .picker-row:last-child{border-bottom:0}
.picker-panel .picker-row:hover,.picker-panel .picker-row:focus{background:#fcebed;color:#a91f2b}
.picker-panel .picker-row.is-added{background:#fafaf8;color:#aaa8a3;cursor:default}
.picker-panel .picker-row.is-added:hover{background:#fafaf8;color:#aaa8a3}
.picker-panel .picker-row.is-added .picker-add{color:#aaa8a3}
.picker-panel .picker-av{display:grid;place-items:center;width:28px;height:28px;border-radius:50%;overflow:hidden;background:#202123;color:#fff;font:700 11px Manrope,sans-serif;flex:none}
.picker-panel .picker-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.picker-panel .picker-lbl{flex:1;min-width:0;display:grid;gap:2px}
.picker-panel .picker-lbl strong{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.picker-panel .picker-lbl small{color:var(--muted);font:10px 'DM Mono',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.picker-panel .picker-add{color:var(--green,#2b8a61);font:600 11px Manrope,sans-serif;white-space:nowrap;flex:none}
.picker-panel .picker-empty{padding:16px;color:var(--muted);font-size:12px;text-align:center}
.picker-hint{display:block;margin-top:6px;color:#6c6a67;font:10px 'DM Mono',monospace}
@media(max-width:620px){.picker-panel{max-height:170px}.admin-dialog form{padding:18px}}
mark{background:#fde68a;color:inherit;padding:0 1px;border-radius:3px;font-weight:600}
/* Bandeau de villes de la campagne (déduites des lieux) */
.campaign-cities{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:11px 14px;margin-bottom:16px;border:1px solid var(--line);border-radius:10px;background:linear-gradient(120deg,#fff 0%,#fdf6ee 100%)}
.campaign-cities>span.cities-label{color:var(--muted);font:600 10px 'DM Mono',monospace;letter-spacing:.08em;text-transform:uppercase}
.city-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 11px;border:1px solid #d9b27d;border-radius:99px;background:#fff;color:#8a5100;font:600 12px Manrope,sans-serif}
.city-chip b{font-weight:700}
.city-chip small{color:#a8825a;font-size:10px}
/* Bulles de collaboratrices affectées */
.chip-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.person-chip{display:inline-flex;align-items:center;gap:8px;padding:6px 8px 6px 6px;border:1px solid var(--line);border-radius:99px;background:#fff;font-size:13px;font-weight:600}
.person-chip .chip-av{width:26px;height:26px;border-radius:50%;overflow:hidden;display:grid;place-items:center;background:#202123;color:#fff;font:700 11px Manrope,sans-serif;flex:none}
.person-chip .chip-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.person-chip .chip-x{width:22px;height:22px;border:0;border-radius:50%;background:#f1efec;color:#7a7873;font-size:13px;line-height:1;cursor:pointer;display:grid;place-items:center}
.person-chip .chip-x:hover{background:#fcebed;color:var(--red)}
.chip-count{color:var(--muted);font:600 11px 'DM Mono',monospace}
.chip-av-sq{border-radius:7px;font-size:12px}
.chip-city{color:#a8825a;font:600 10px 'DM Mono',monospace;margin-left:2px}
/* Compteur d'événements sur une bulle de lieu ou de ville (« Buddha-Bar 2 ») */
.chip-count{display:inline-grid;place-items:center;min-width:19px;height:19px;padding:0 5px;border-radius:99px;background:var(--red);color:#fff;font:800 11px Manrope,sans-serif;flex:none}
.city-chip .chip-count{background:#8a5100}
.city-chip .chip-x-sm{width:17px;height:17px;font-size:11px;background:#f4ede2;color:#a8825a;margin-left:1px}
.city-chip .chip-x-sm:hover{background:#fcebed;color:var(--red)}
/* Statut d'un événement (synchronisé sur sa date) + jours restants */
.event-statut{display:flex;align-items:center;gap:8px;margin-top:6px}
.event-statut .badge{font-size:9px;padding:4px 8px}
.event-jours{color:var(--muted);font:600 10px 'DM Mono',monospace}
.profile-jours{color:var(--red-dark);font:700 11px 'DM Mono',monospace;padding:5px 9px;border:1px solid #e9c9cd;border-radius:99px;background:#fdf3f4}
.image-field{margin-top:4px}
.image-field>label{display:block;color:var(--muted);font:10px 'DM Mono',monospace;letter-spacing:.06em;text-transform:uppercase;margin-bottom:7px}
.image-field-row{display:flex;align-items:center;gap:14px}
.image-pick{position:relative;flex:none;width:124px;height:124px;padding:0;border:1px solid var(--line);border-radius:14px;background:#fbfaf8;cursor:pointer;display:grid;place-items:center;overflow:hidden}
.image-pick.is-circle{border-radius:50%}
.image-pick img{width:100%;height:100%;object-fit:contain;padding:4px}
.image-pick.is-circle img{object-fit:cover;object-position:center 18%;padding:0}
.image-pick-plus{font-size:26px;color:#bdbab4;line-height:1}
.image-pick-initials{font:800 26px Manrope,sans-serif;color:#c9c5bf;letter-spacing:.02em}
.image-pick-edit{position:absolute;inset:auto 0 0 0;padding:4px;background:rgba(32,33,35,.84);color:#fff;font:600 9px Manrope,sans-serif;text-align:center;letter-spacing:.03em}
.image-field-side{display:flex;flex-direction:column;gap:5px;align-items:flex-start}
.upload-inline{cursor:pointer}
.upload-inline input{display:none}
.image-library{width:min(880px,calc(100vw - 32px))}
/* Bannière de couverture (profil campagne) — rectangulaire, dégradé vers le contenu */
/* ══ LA COUVERTURE : ELLE COLLE AUX BORDS RÉELS DE LA COQUE ═══════════════════════════
   Client : « on voit pas l'image de couverture en haut, elle tient pas les bordures : il y a
   des bordures, elle n'est pas sur la taille des bordures du pop up. »

   MESURÉ, et la cause est précise :
       l.378  .cover-field { margin: -28px -28px 20px; border-radius: 18px 18px 0 0 }
   Ces marges NÉGATIVES de −28 px supposent que la coque a un padding de 28 px, et servent à
   FAIRE DÉBORDER la couverture jusqu'aux bords. Mais la coque porte maintenant
   `overflow: hidden` (posé pour que les images à angles carrés ne dépassent pas) : la
   couverture est donc ROGNÉE par la coque au lieu de coller à ses bords — d'où « il y a des
   bordures » et « elle n'est pas sur la taille des bordures ».

   LE CORRECTIF : la couverture s'aligne sur les bords RÉELS de la coque, sans marge négative
   ni rayon propre — c'est la COQUE qui arrondit ses coins, et elle seule.
   ⚠️ UNE SEULE SOURCE POUR L'ARRONDI : deux arrondis qui se superposent (celui de la coque et
   celui de la couverture) laissent toujours une couture visible. On n'en garde qu'un.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.cover-field{position:relative;margin:0 0 0;border-radius:0;overflow:hidden;width:100%}
.cover-field .cover-pick{height:auto;min-height:150px;max-height:min(38vh,320px);aspect-ratio:16/7;
  border-bottom:0}
.cover-field .cover-pick img{width:100%;height:100%;object-fit:cover;display:block}
.cover-pick{display:block;position:relative;width:100%;height:190px;padding:0;border:0;border-bottom:1px solid var(--line);background:linear-gradient(120deg,#202123 0%,#2b2c2f 100%);cursor:pointer;overflow:hidden;text-align:left}
.cover-pick img{width:100%;height:100%;object-fit:cover;display:block}
.cover-empty{position:absolute;left:22px;top:50%;transform:translateY(-50%);color:#c9c7c2;font:600 13px Manrope,sans-serif;display:flex;align-items:center;gap:10px}
.cover-empty b{display:grid;place-items:center;width:34px;height:34px;border:1px dashed #6b6a66;border-radius:50%;font-size:18px;font-weight:400}
.cover-edit{position:absolute;right:16px;top:14px;padding:7px 12px;border-radius:99px;background:rgba(32,33,35,.72);color:#fff;font:600 11px Manrope,sans-serif;backdrop-filter:blur(3px)}
.cover-pick:hover .cover-edit{background:var(--red)}
/* Dégradé de fusion : la couverture se dissout dans la fiche */
.cover-fade{position:absolute;inset:auto 0 0 0;height:64px;background:linear-gradient(to bottom,rgba(255,255,255,0) 0%,rgba(255,255,255,.38) 62%,#fff 100%);pointer-events:none}
.cover-remove{position:relative;margin:8px 0 0 28px}
.cover-hint{display:block;margin:2px 0 0 28px}
/* Filigrane de couverture sur les cartes de la liste */
.entity-tile.has-cover{position:relative;overflow:hidden}
.entity-tile-cover{position:absolute;inset:0;z-index:0;pointer-events:none}
/* Filigrane de couverture sur les cartes : le voile de fusion est SOMBRE, pour
   rappeler la bannière de gauche (un voile clair laverait la carte en négatif). */
.entity-tile-cover img{width:100%;height:100%;object-fit:cover;opacity:.66;
  /* La photo est en grand, etiree sur TOUTE la carte (object-fit:cover) :
     c'est le fond filigrane du carton, pas une vignette. */}
.entity-tile-cover:after{content:"";position:absolute;inset:0;
  /* Le voile doit LAISSER VOIR la photo sur toute la carte. Avant, il finissait a
     `#17181a` (100 % opaque) : le bas de la carte etait un aplat noir, la couverture
     disparaissait. Il ne descend plus au-dela de 78 %, ce qui garde le texte lisible
     tout en laissant la photo respirer jusqu'en bas. */
  background:linear-gradient(155deg,rgba(26,27,29,.24) 0%,rgba(24,25,27,.58) 55%,rgba(23,24,26,.78) 100%)}
.entity-tile.has-cover>*:not(.entity-tile-cover){position:relative;z-index:1}
.sheet-readonly input:disabled,.sheet-readonly textarea:disabled,.sheet-readonly select:disabled{background:#f7f6f3;color:var(--text);border-color:#e6e3de;cursor:default;opacity:1}
.sheet-readonly .picker-search input:disabled{background:#f7f6f3}
.sheet-readonly .image-pick{cursor:default}
.sheet-readonly .image-pick-edit{display:none}
.sheet-readonly .picker-block,.sheet-readonly .assign-add{display:none}
/* Boîte de confirmation de sortie */
.discard-dialog{width:min(520px,calc(100vw - 32px))}
.discard-copy{margin:0 0 6px;color:var(--muted);font-size:13px;line-height:1.6}
.discard-actions{margin-top:22px;justify-content:flex-end;flex-wrap:wrap;gap:9px}
.library-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.library-toolbar input{flex:1;min-height:44px;border:1px solid var(--line2);border-radius:99px;padding:10px 16px;font:13px Manrope,sans-serif;background:#fff;color:var(--text)}
.library-toolbar input:focus{outline:none;border-color:var(--red);box-shadow:0 0 0 3px rgba(230,57,70,.1)}
.library-upload{position:relative;cursor:pointer;white-space:nowrap}
.library-upload input{display:none}
.library-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:10px;max-height:56vh;overflow:auto;padding:2px}
.library-item{padding:8px;border:1px solid var(--line);border-radius:12px;background:#fbfaf8;cursor:pointer;display:grid;gap:5px;place-items:center}
.library-item:hover{border-color:var(--red);box-shadow:0 0 0 3px rgba(230,57,70,.1)}
.library-item img{width:100%;height:96px;object-fit:contain}
.library-item small{color:var(--muted);font:9px 'DM Mono',monospace}
.library-empty{grid-column:1/-1;padding:30px 14px;color:var(--muted);font-size:12px;text-align:center}
@media(max-width:620px){.campaign-dateline{grid-template-columns:1fr}.assign-row{grid-template-columns:auto 1fr auto;row-gap:6px}}
.session-block{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}.session-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.session-head strong{font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}.session-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 10px;margin-bottom:6px;border:1px solid var(--line);border-radius:8px;background:#faf9f7;font-size:12px}.session-date{font-weight:700;color:var(--text);font-family:'DM Mono',monospace}.session-place{color:var(--muted)}.session-time{color:var(--muted);font-family:'DM Mono',monospace;font-size:11px}.session-row .text-button{margin-left:auto}
.nav-divider{position:relative;display:flex;align-items:center;justify-content:center;margin:18px 8px 10px;color:#c3c6cb;font:600 9.5px 'DM Mono',monospace;letter-spacing:.14em}.nav-divider:before,.nav-divider:after{content:"";height:1px;flex:1;background:#6a6e74}.nav-divider span{padding:0 8px;white-space:nowrap}.entity-list{display:grid;gap:12px}.entity-card{display:grid;grid-template-columns:64px 1fr;gap:18px;padding:21px;border:1px solid var(--line);border-radius:14px;background:rgba(255,255,255,.88)}.entity-monogram{display:grid;place-items:center;width:58px;height:58px;border-radius:14px;background:#202123;color:#fff;font-weight:800;letter-spacing:.06em}.entity-monogram.campaign{background:#e63946}.entity-monogram.establishment{background:#d4a574;color:#202123}.entity-main h2{margin:4px 0;font-size:21px}.entity-main>p{margin:7px 0 16px;color:var(--muted)}.entity-meta{display:flex;flex-wrap:wrap;gap:8px}.entity-meta span{padding:6px 9px;border:1px solid var(--line);border-radius:99px;background:#f8f7f4;color:#55585c;font-size:11px}
/* Client : « l'arrière-plan doit contenir l'ensemble ».
   MESURÉ : 1320 × 780 dans une fenêtre de 1600 × 1100 — la fiche était étroite et le
   contenu n'était pas contenu. On l'élargit franchement et on laisse la hauteur
   s'étendre jusqu'à 94vh : le fond clair englobe tout, rien ne débordе. */
.admin-dialog{width:min(1240px,calc(100vw - 26px));max-height:94vh;overflow:auto;padding:0;border:1px solid var(--line);border-radius:18px;background:#fff;color:var(--text)}.admin-dialog::backdrop{background:rgba(15,16,17,.58);backdrop-filter:blur(3px)}.admin-dialog form{padding:28px}.admin-dialog textarea,.admin-dialog select{width:100%;border:1px solid var(--line2);border-radius:8px;background:#fff;padding:12px;font:inherit;color:var(--text)}.dialog-head{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:24px}.dialog-head h2{margin:5px 0;font-size:26px}.dialog-close{display:grid;place-items:center;width:42px;height:42px;border:1px solid var(--line);border-radius:50%;background:#fff;font-size:24px;cursor:pointer}.dialog-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:24px}.form-status{min-height:20px;margin-top:14px;color:var(--red);font-size:12px}.smart-combo{position:relative}.combo-results{position:absolute;z-index:20;left:0;right:0;top:calc(100% + 4px);display:none;overflow:auto;max-height:240px;border:1px solid var(--line);border-radius:10px;background:#fff}.combo-results.open{display:block}.combo-results button{display:block;width:100%;padding:11px 12px;border:0;border-bottom:1px solid #eceae6;background:#fff;text-align:left;cursor:pointer}.combo-results button:hover,.combo-results button:focus{background:#fcebed;color:#a91f2b}.combo-empty{display:block;padding:12px;color:var(--muted);font-size:12px}
.stock-product{display:flex;align-items:center;gap:13px;min-width:220px}.stock-product img{width:62px;height:62px;object-fit:contain;background:transparent;border:0;border-radius:0}.stock-product span{display:block;margin-top:3px;color:var(--muted);font-size:11px}.cell-caption{font-size:10px;color:var(--muted)}
.spreadsheet-head{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:20px}.spreadsheet-head h2{font:800 clamp(30px,4vw,44px)/1.02 'Plus Jakarta Sans',sans-serif;letter-spacing:-.05em;margin:7px 0}.spreadsheet-head p{margin:0;color:var(--muted)}.sheet-head-actions{display:flex;gap:8px}.sheet-tabs{display:flex;gap:5px;overflow-x:auto;padding-bottom:8px}.sheet-tab{min-height:40px;padding:8px 12px;border:1px solid var(--line);border-radius:8px 8px 0 0;background:#f2f0ec;color:#5d5b58;white-space:nowrap;cursor:pointer}.sheet-tab.active{background:#202123;color:#fff;border-color:#202123}.sheet-shell{overflow:hidden;border:1px solid var(--line);border-radius:0 12px 12px 12px;background:#fff}.sheet-toolbar{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:12px 14px;border-bottom:1px solid var(--line);background:#faf9f7}.sheet-toolbar input{width:min(430px,70%);min-height:42px}.sheet-toolbar span{color:var(--muted);font-size:12px}.sheet-scroll{overflow:auto;max-height:62vh}.data-sheet{width:100%;border-collapse:separate;border-spacing:0;min-width:900px}.data-sheet th{position:sticky;z-index:2;top:0;padding:10px 12px;border-right:1px solid #e4e2de;border-bottom:1px solid #d5d2cd;background:#f0efec;text-align:left;color:#4b4a47;font:600 10px 'DM Mono',monospace;letter-spacing:.04em;text-transform:uppercase}.data-sheet th i{display:block;margin-top:3px;color:#6c6a67;font-size:8px;font-style:normal}.data-sheet td{height:46px;padding:0;border-right:1px solid #eceae6;border-bottom:1px solid #eceae6}.sheet-input{width:100%;height:45px;padding:8px 11px;border:0;border-radius:0;background:transparent;color:var(--text);font:13px Manrope,system-ui,sans-serif}.sheet-input:focus{position:relative;z-index:1;outline:2px solid #e63946;outline-offset:-2px;background:#fff}.sheet-input.saving{background:#fff8df}.sheet-input.saved{background:#edf8f2}.sheet-input.invalid{background:#fff0f1;color:#a91f2b}
/* Cellule modifiée non enregistrée */
.sheet-input.pending{background:#fff6d8;box-shadow:inset 0 0 0 1px #e8c86a}
.sheet-pending{color:#8a5100;font:600 11px Manrope,sans-serif;background:#fdf3dd;border:1px solid #e8c86a;border-radius:99px;padding:4px 10px}
/* Bouton Enregistrer : inactif tant qu'aucune modification */
.sheet-head-actions .button[disabled]{opacity:.45;cursor:not-allowed}
.sheet-head-actions .button.is-armed{box-shadow:0 0 0 3px rgba(230,57,70,.18)}
.sheet-import{position:relative;cursor:pointer}
.sheet-import input{display:none}
/* Choix du mode d'import */
.import-modes{display:grid;gap:10px;margin:16px 0 6px}
.import-mode{display:grid;gap:5px;width:100%;padding:15px 17px;border:1px solid var(--line);border-radius:12px;background:#fff;text-align:left;cursor:pointer;font:inherit;color:var(--text)}
.import-mode:hover,.import-mode:focus{border-color:var(--red);background:#fcebed}
.import-mode strong{font-size:14px}
.import-mode span{color:var(--muted);font-size:12px;line-height:1.5}.sheet-readonly{display:block;min-width:80px;padding:0 11px;color:#74716c;font-size:12px}.sheet-status{padding:10px 14px;border-top:1px solid var(--line);background:#faf9f7;color:var(--muted);font-size:11px}
@media(max-width:900px){.nav-divider span{display:none}.nav-divider{margin:10px 5px}.spreadsheet-head{align-items:flex-start;flex-direction:column}.sheet-head-actions{width:100%}.sheet-head-actions .button{flex:1}.entity-card{grid-template-columns:48px 1fr}.entity-monogram{width:46px;height:46px}.admin-dialog form{padding:21px}}@media(max-width:620px){body:not(.participant-mode) .sidebar nav{grid-template-columns:repeat(8,minmax(74px,1fr));overflow-x:auto}.nav-divider{display:none}.entity-card{grid-template-columns:1fr}.entity-meta{align-items:flex-start;flex-direction:column}.sheet-tabs{padding-bottom:5px}}

/* ── La fiche = LA CARTE EN GRAND, donc SOMBRE comme elle ────────────────────
   Tu m'as dit : « en cliquant sur la carte, je dois avoir la même carte beaucoup
   plus grande ». La carte est sombre → la fiche aussi. Elle ne devient blanche
   qu'en MODIFICATION (règle .editing posée par startSheetEdit). */
.profile-sheet{background:linear-gradient(150deg,#26282a 0%,#1a1b1d 100%);color:#f4f3f1;border-color:rgba(255,255,255,.14)}
.profile-sheet .sheet-section>h3{color:#c9c7c3}
.profile-sheet .row-sub,.profile-sheet .mini-card-sub,.profile-sheet .empty{color:rgba(244,243,241,.62)}
.profile-sheet .profile-dates{color:rgba(244,243,241,.72)}
.profile-sheet .cities-label{color:rgba(244,243,241,.6)}
.profile-sheet .form-status{color:#ff8b95}
.profile-sheet .sheet-actions{border-top-color:rgba(255,255,255,.14)}
.profile-sheet .text-button{color:#ff8b95}
.profile-sheet .text-button.strong{color:#ff6b78}
.profile-sheet .button.secondary{background:transparent;border-color:rgba(255,255,255,.24);color:#f4f3f1}
.profile-sheet .button.secondary:hover{border-color:rgba(255,255,255,.44)}
/* Cartes internes (goodies, collaboratrices, événements) : sombres sur la fiche sombre */
.profile-sheet .mini-card{background:rgba(255,255,255,.055);border-color:rgba(255,255,255,.14)}
.profile-sheet .mini-card:hover{border-color:rgba(255,255,255,.32);background:rgba(255,255,255,.09)}
.profile-sheet .mini-card h4{color:#fff}
.profile-sheet .mini-card-av{background:rgba(255,255,255,.18)}
.profile-sheet .mini-card-stats b{color:#fff}
.profile-sheet .mini-card-stats{color:rgba(244,243,241,.6)}
.profile-sheet .mini-card-x{background:rgba(255,255,255,.12);color:rgba(244,243,241,.8)}
.profile-sheet .goodie-tile{background:rgba(255,255,255,.055)}
.profile-sheet .goodie-tile .goodie-visual{background:rgba(255,255,255,.07)}
.profile-sheet .goodie-tile h4{color:#fff}
.profile-sheet .event-card .event-date{background:rgba(255,255,255,.18)}
.profile-sheet .event-card .event-date b{color:#ff8b95}
.profile-sheet .event-card .event-date span{color:rgba(244,243,241,.65)}
.profile-sheet .event-jours{color:rgba(244,243,241,.6)}
/* Bulles (villes, lieux) sur fond sombre */
.profile-sheet .city-chip{background:rgba(255,255,255,.07);border-color:rgba(217,178,125,.5);color:#e3c69b}
.profile-sheet .person-chip{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.24);color:#f4f3f1}
.profile-sheet .chip-city{color:#c4a375}
.profile-sheet .chip-av{background:rgba(255,255,255,.12);color:#fff}
.profile-sheet .chip-x{background:rgba(255,255,255,.12);color:rgba(244,243,241,.8)}
/* Compteurs de la fiche : même style que la carte sombre */

.profile-sheet .tile-count strong{color:#fff}
.profile-sheet .tile-count span{color:#c9c7c3}

.profile-sheet .tile-count.is-zero strong{color:#a9a7a3}
/* Recherches et listes déroulantes : sombres en lecture, clairs en modification */
.profile-sheet .picker-search input{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.18);color:#f4f3f1}
.profile-sheet .picker-search input::placeholder{color:rgba(244,243,241,.45)}
.profile-sheet .picker-block{border-top-color:rgba(255,255,255,.14)}
.profile-sheet .sheet-section input:not([type=hidden]),.profile-sheet .sheet-section textarea{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.24);color:#f4f3f1}
/* ═══ MODIFICATION : la fiche passe en BLANC, champs nettement éditables ═══════ */
.profile-sheet.editing{background:#fff;color:var(--text);border-color:var(--line)}
.profile-sheet.editing .sheet-section>h3{color:var(--muted)}
.profile-sheet.editing .row-sub,.profile-sheet.editing .mini-card-sub,.profile-sheet.editing .empty{color:var(--muted)}
.profile-sheet.editing .profile-dates,.profile-sheet.editing .event-jours{color:var(--muted)}
.profile-sheet.editing .form-status{color:var(--red)}
.profile-sheet.editing .sheet-actions{border-top-color:var(--line)}
.profile-sheet.editing .text-button{color:var(--red-dark)}
.profile-sheet.editing .text-button.strong{color:var(--red)}
.profile-sheet.editing .button.secondary{background:transparent;border-color:var(--line2);color:var(--text)}
.profile-sheet.editing .mini-card{background:#fff;border-color:var(--line)}
.profile-sheet.editing .mini-card h4{color:var(--text)}
.profile-sheet.editing .mini-card-av{background:#f1efec}
.profile-sheet.editing .mini-card-stats{color:var(--muted)}
.profile-sheet.editing .mini-card-stats b{color:var(--text)}
.profile-sheet.editing .mini-card-x{background:rgba(32,33,35,.07);color:var(--muted)}
.profile-sheet.editing .goodie-tile{background:#fff}
.profile-sheet.editing .goodie-tile .goodie-visual{background:#f4f2ee}
.profile-sheet.editing .goodie-tile h4{color:var(--text)}
.profile-sheet.editing .event-card .event-date{background:#f4f2ee}
.profile-sheet.editing .event-card .event-date b{color:var(--red-dark)}
.profile-sheet.editing .event-card .event-date span{color:var(--muted)}
.profile-sheet.editing .city-chip{background:#fff;border-color:#d9b27d;color:#8a5100}
.profile-sheet.editing .person-chip{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.editing .chip-av{background:#f1efec;color:var(--text)}
.profile-sheet.editing .chip-x{background:rgba(32,33,35,.07);color:var(--muted)}
.profile-sheet.editing .tile-count{background:#faf9f7;border-color:var(--line)}
.profile-sheet.editing .tile-count strong{color:var(--text)}
.profile-sheet.editing .tile-count span{color:var(--muted)}
.profile-sheet.editing .picker-search input{background:#fff;border-color:var(--line2);color:var(--text)}
.profile-sheet.editing .picker-block{border-top-color:var(--line)}
.profile-sheet.editing .sheet-section input:not([type=hidden]),.profile-sheet.editing .sheet-section textarea{background:#fff;border-color:var(--line2);color:var(--text)}

/* ═══ LECTURE : une vraie FICHE, pas un formulaire ══════════════════════════
   Aucun carré de saisie en lecture (ça donne l'impression de pouvoir cliquer).
   Les champs et les commandes d'image n'existent qu'en MODIFICATION (.editing).
   Seule la note interne reste saisissable, avec le bouton Enregistrer. */
.profile-sheet .edit-only{display:none}
.profile-sheet.editing .edit-only{display:block}
.profile-sheet .read-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:4px}
.profile-sheet .read-item{display:grid;gap:3px}
.profile-sheet .read-item.read-wide{grid-column:1/-1}
.profile-sheet .read-item span{color:rgba(244,243,241,.55);font:600 10px 'DM Mono',monospace;letter-spacing:.07em;text-transform:uppercase}
.profile-sheet .read-item b{color:#fff;font:600 14px Manrope,sans-serif;line-height:1.5}
.profile-sheet.editing .read-grid{display:none}
/* Commandes d'image : visibles seulement en modification */
.profile-sheet .image-field-side{visibility:hidden}
.profile-sheet.editing .image-field-side{visibility:visible}
.profile-sheet.editing .image-field-side{display:flex;flex-direction:column;gap:6px}
/* Villes : libellé plus grand, bulles en rouge clair semi-sombre */
.profile-sheet .cities-label{font-size:13px;letter-spacing:.05em;color:rgba(244,243,241,.8)}
.profile-sheet .city-chip{background:rgba(230,57,70,.22);border-color:rgba(230,57,70,.5);color:#ffc9ce;font-size:13px}
.profile-sheet .city-chip b{font-size:13px}
.profile-sheet .city-chip .chip-count{background:#e63946}
.profile-sheet .city-chip .chip-x-sm{background:rgba(255,255,255,.14);color:#ffc9ce}
.profile-sheet.editing .city-chip{background:rgba(230,57,70,.14);border-color:rgba(230,57,70,.42);color:#c9303c}

/* Correctifs de la fiche en LECTURE ---------------------------------------- */
/* 1. La barre « Villes » se fond dans la fiche sombre (avant : fond blanc cassant). */
.profile-sheet .campaign-cities{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.14)}
.profile-sheet.editing .campaign-cities{background:linear-gradient(120deg,#fff 0%,#fdf6ee 100%);border-color:var(--line)}
/* 2. Les commandes de la COUVERTURE sont réservées à la modification
      (« Changer la couverture », « Retirer la couverture »). */
.profile-sheet .cover-edit{display:none}
.profile-sheet.editing .cover-edit{display:inline-block}
.profile-sheet .cover-remove,.profile-sheet .cover-hint{display:none}
.profile-sheet.editing .cover-remove,.profile-sheet.editing .cover-hint{display:block}
.profile-sheet .cover-pick{cursor:default}
.profile-sheet.editing .cover-pick{cursor:pointer}

/* Bouton d'enregistrement de la NOTE (seule saisie accessible en lecture) */
.profile-sheet .note-save{margin-top:8px}

/* Goodies de la campagne : fond plus CLAIR, de la teinte du titre de section
   (#c9c7c3). Le texte s'inverse en sombre, sinon du blanc sur clair serait illisible. */
.profile-sheet .goodie-tile{background:#c9c7c3;border-color:rgba(255,255,255,.3)}
.profile-sheet .goodie-tile:hover{background:#d3d1cd;border-color:rgba(255,255,255,.5)}
.profile-sheet .goodie-tile .goodie-visual{background:rgba(255,255,255,.62)}
.profile-sheet .goodie-tile h4{color:#1a1b1d}
.profile-sheet .goodie-tile .mini-card-sub{color:rgba(26,27,29,.72) !important}
.profile-sheet .goodie-tile .mini-card-stats{color:rgba(26,27,29,.78)}
.profile-sheet .goodie-tile .mini-card-stats b{color:#1a1b1d}
.profile-sheet.editing .goodie-tile{background:#fff;border-color:var(--line)}

/* En modification, la fiche passe en blanc : les traits et titres s'assombrissent. */
/* Collaboratrices en MINIATURE : photo de profil + prénom nom, rien d'autre */
.mini-people{display:flex;flex-wrap:wrap;gap:10px}
.mini-person{display:inline-flex;align-items:center;gap:9px;padding:5px 13px 5px 5px;border:1px solid rgba(255,255,255,.24);border-radius:99px;background:rgba(255,255,255,.06);color:#f4f3f1;font:600 12px Manrope,sans-serif}
.mini-person-av{width:32px;height:32px;border-radius:50%;overflow:hidden;background:rgba(255,255,255,.14);display:grid;place-items:center;flex:none}
.mini-person-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.mini-person-av .card-fallback{font:800 11px Manrope;color:#fff}
.profile-sheet.editing .mini-person{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.editing .mini-person-av{background:#f1efec}
.profile-sheet.editing .mini-person-av .card-fallback{color:#8b8781}
/* Boutons d'ajout regroupes en fin de fiche */
.btn-row{display:flex;gap:9px;flex-wrap:wrap;margin-top:14px}
/* La couverture se fond dans le fond de la fiche (dégradé bas plus marqué) */
.profile-sheet .cover-fade{height:110px;background:linear-gradient(to bottom,rgba(26,27,29,0) 0%,rgba(26,27,29,.55) 55%,#1c1d1f 100%)}

/* ═══ FICHE : plus de rouge agressif ═══════════════════════════════════════════
   Les bulles rouges choquaient. On aligne les numéros sur le style des compteurs
   du haut : fond translucide discret, chiffre clair. En MODIFICATION seulement,
   tout redevient blanc. */
.profile-sheet .city-chip{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18);color:#f4f3f1}
.profile-sheet .city-chip b{color:#fff;font-weight:600}
.profile-sheet .chip-count{background:rgba(255,255,255,.24);color:#fff;border:1px solid rgba(255,255,255,.2)}
.profile-sheet .person-chip .chip-count{background:rgba(255,255,255,.24)}
.profile-sheet .city-chip .chip-count{background:rgba(255,255,255,.24);border:1px solid rgba(255,255,255,.2)}
.profile-sheet .city-chip .chip-x-sm{background:rgba(255,255,255,.12);color:rgba(244,243,241,.8)}
/* MODIFICATION : les mêmes elements en blanc */
.profile-sheet.editing .city-chip{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.editing .city-chip b{color:var(--text)}
.profile-sheet.editing .chip-count{background:#f1efec;color:var(--text);border-color:var(--line)}
.profile-sheet.editing .city-chip .chip-count{background:#f1efec;border-color:var(--line)}

/* ═══ LIEUX EN CARRÉS ══════════════════════════════════════════════════════════
   Même langage que les compteurs du haut (rectangles arrondis, chiffre en clair),
   avec le nombre d'événements du lieu. */
.loc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));gap:10px}
.loc-tile{position:relative;display:grid;justify-items:center;gap:4px;padding:15px 12px 13px;border:1px solid rgba(255,255,255,.24);border-radius:14px;background:rgba(255,255,255,.06);text-align:center}
.loc-tile-av{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:rgba(255,255,255,.14);color:#fff;font:800 15px Manrope,sans-serif}
.loc-tile strong{color:#fff;font:600 13px Manrope,sans-serif;line-height:1.3}
.loc-tile small{color:rgba(244,243,241,.6);font:600 10px 'DM Mono',monospace;letter-spacing:.04em;text-transform:uppercase}
.loc-tile-count{display:inline-grid;place-items:center;min-width:26px;height:26px;padding:0 8px;margin-top:3px;border-radius:99px;background:rgba(255,255,255,.24);border:1px solid rgba(255,255,255,.2);color:#fff;font:800 13px Manrope,sans-serif}
.loc-tile-count.is-zero{background:transparent;color:rgba(244,243,241,.45)}
/* Compteurs de la fiche : souligner ceux qui sont cliquables (raccourcis) */
.profile-sheet .tile-count.is-link{cursor:pointer}
.profile-sheet .tile-count.is-link:hover{border-color:rgba(255,255,255,.5)}
/* MODIFICATION : tout repasse en blanc */
.profile-sheet.editing .loc-tile{background:#fff;border-color:var(--line)}
.profile-sheet.editing .loc-tile-av{background:#f1efec;color:var(--text)}
.profile-sheet.editing .loc-tile strong{color:var(--text)}
.profile-sheet.editing .loc-tile small{color:var(--muted)}
.profile-sheet.editing .loc-tile-count{background:#f1efec;color:var(--text);border-color:var(--line)}

/* ═══ TITRES DE SECTION = CARRÉS COMPTEURS (fin des traits) ═══════════════════
   Même langage que les compteurs d'en-tête : numéro en grand AU-DESSUS, libellé
   JUSTE EN DESSOUS. En négatif (clair) pour bien détacher les sections. */
/* ═══ ÉVÉNEMENTS : cartons CLAIRS comme les goodies ═══════════════════════════ */
.profile-sheet .event-card{background:#c9c7c3;border-color:rgba(255,255,255,.3)}
.profile-sheet .event-card:hover{background:#d3d1cd;border-color:rgba(255,255,255,.5)}
.profile-sheet .event-card .event-main h4{color:#1a1b1d}
.profile-sheet .event-card .mini-card-sub{color:rgba(26,27,29,.72) !important}
.profile-sheet .event-card .event-date{background:rgba(26,27,29,.1)}
.profile-sheet .event-card .event-date b{color:#a8232f}
.profile-sheet .event-card .event-date span{color:rgba(26,27,29,.7)}
.profile-sheet .event-card .event-jours{color:rgba(26,27,29,.72)}
.profile-sheet .event-card .badge{border-color:rgba(26,27,29,.28);color:#1a1b1d;background:rgba(255,255,255,.5)}
.profile-sheet .event-card .badge.connected{color:#065f38;border-color:rgba(6,95,56,.4)}
.profile-sheet .event-card .mini-card-x{background:rgba(26,27,29,.12);color:rgba(26,27,29,.8)}
.profile-sheet.editing .event-card{background:#fff;border-color:var(--line)}
.profile-sheet.editing .event-card .event-main h4{color:var(--text)}
.profile-sheet.editing .event-card .mini-card-sub{color:var(--muted) !important}
.profile-sheet.editing .event-card .event-date{background:#f4f2ee}
.profile-sheet.editing .event-card .event-jours{color:var(--muted)}
.profile-sheet.editing .event-card .badge{background:transparent;border-color:var(--line2);color:var(--muted)}
.profile-sheet.editing .event-card .mini-card-x{background:rgba(32,33,35,.07);color:var(--muted)}

/* ═══ TITRES DE SECTION : TRAIT + CARRÉ COMPTEUR ROUGE (charte agence) ════════
   On garde le large trait, et le compteur devient un carré ROUGE #E63946 — la
   couleur de la charte de l'agence. Le titre reste lisible à côté. */
.band{display:flex;align-items:center;gap:16px;margin:28px 0 14px}
.band-line{flex:1;height:2px;border-radius:2px;background:linear-gradient(90deg,rgba(255,255,255,.08),rgba(255,255,255,.85) 45%,rgba(255,255,255,.85) 55%,rgba(255,255,255,.08));box-shadow:0 0 6px rgba(255,255,255,.12)}
/* MODIFICATION : la fiche passe en blanc, le rouge charte reste (c'est la marque) */
.profile-sheet.editing .band-line{background:linear-gradient(90deg,rgba(23,23,25,.1),rgba(23,23,25,.62) 45%,rgba(23,23,25,.62) 55%,rgba(23,23,25,.1));box-shadow:none}

/* ═══ TITRE DE SECTION : TOUT LE BLOC DANS LE RECTANGLE ROUGE ═════════════════
   Numero + libelle reunis DANS un seul rectangle rouge (charte agence), posé entre
   deux larges traits. */
.band{display:flex;align-items:center;gap:18px;margin:30px 0 15px}
.band-line{flex:1;height:2px;border-radius:2px;background:linear-gradient(90deg,rgba(255,255,255,.08),rgba(255,255,255,.85) 45%,rgba(255,255,255,.85) 55%,rgba(255,255,255,.08));box-shadow:0 0 6px rgba(255,255,255,.12)}
.band-tag{display:inline-flex;align-items:center;gap:11px;padding:9px 18px;border-radius:13px;background:#e63946;box-shadow:0 4px 14px -4px rgba(230,57,70,.55);flex:none}
.band-tag b{color:#fff;font:800 24px/1 Manrope,sans-serif}
.band-tag span{color:#fff;font:700 12px Manrope,sans-serif;letter-spacing:.13em;text-transform:uppercase;white-space:nowrap}
.profile-sheet.editing .band-line{background:linear-gradient(90deg,rgba(23,23,25,.1),rgba(23,23,25,.62) 45%,rgba(23,23,25,.62) 55%,rgba(23,23,25,.1));box-shadow:none}
/* ═══ EN-TÊTE DU PROFIL, REPRIS : plus de chevauchement ═══════════════════════
   Avatar qui DEPASSE la couverture, puis un bloc d'identite aere a droite : plus
   de marge negative entre le texte et l'avatar, periodes et entreprise lisibles. */
.profile-head{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;gap:22px;padding:0 32px;margin-top:-58px;position:relative;z-index:2}
.profile-head .sheet-avatar-slot{grid-column:1}
.profile-head .sheet-avatar-slot .image-pick{width:112px;height:112px;border-width:5px;border-color:#1c1d1f}
.profile-id{grid-column:2;padding-top:70px;min-width:0}
.profile-kicker{margin:0 0 2px;color:#e0b585;font:700 13px Manrope,sans-serif;letter-spacing:.1em;text-transform:uppercase}
.profile-id h2{margin:0 0 12px;font:800 32px/1.15 Manrope,sans-serif;letter-spacing:-.02em;color:#fff}
.profile-meta{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.profile-dates{color:rgba(244,243,241,.85);font:600 14px Manrope,sans-serif}
.profile-sheet .badge-lg{font-size:11px;padding:7px 13px}
/* En modification, l'entete passe en clair */
.profile-sheet.editing .profile-id h2{color:var(--text)}
.profile-sheet.editing .profile-kicker{color:#8a5a2b}
.profile-sheet.editing .profile-dates{color:var(--muted)}
.profile-sheet.editing .profile-head .sheet-avatar-slot .image-pick{border-color:#fff}

/* ═══ COMPTE À REBOURS : petite carte synchronisée sur les dates ═════════════
   Elle s'ajoute en tête des compteurs. Verte quand il reste du temps, dorée dans
   les 7 derniers jours, grise quand la campagne est terminée. */
/* 7 colonnes fixes dans une carte etroite = debordement mesure (3 compteurs hors
   grille a 306px). On laisse le navigateur replier les colonnes TOUT SEUL :
   `auto-fit` + largeur mini lisible, et les compteurs passent sur 2 rangees. */
.tile-counts{grid-template-columns:repeat(auto-fit,minmax(84px,1fr))}
.tile-counts.has-jours{grid-template-columns:repeat(auto-fit,minmax(84px,1fr))}
.tile-count-jours{border-color:rgba(76,211,154,.34);background:rgba(76,211,154,.1)}
.tile-count-jours strong{color:#8ee9bd}
.tile-count-jours span{color:#a8ddc6}
.tile-count-jours.is-soon{border-color:rgba(232,195,122,.44);background:rgba(232,195,122,.12)}
.tile-count-jours.is-soon strong{color:#f2d79b}
.tile-count-jours.is-soon span{color:#e8c9a0}
.tile-count-jours.is-past{border-color:rgba(255,255,255,.14);background:rgba(255,255,255,.05)}
.tile-count-jours.is-past strong{color:#9a9c9e}
.tile-count-jours.is-past span{color:#84868a}
/* ═══ PETITE FENÊTRE DE NOTES, collée sous les compteurs ══════════════════════
   Lecture seule : une note se modifie uniquement depuis la grande fiche. */
.tile-note{margin:10px 0 2px;padding:11px 13px;border:1px solid rgba(255,255,255,.14);border-radius:12px;background:rgba(255,255,255,.05);cursor:pointer;transition:background .15s,border-color .15s}
.entity-tile:hover .tile-note{background:rgba(255,255,255,.08)}
.tile-note-head{display:block;color:#c9c7c3;font:700 9px/1 Manrope,sans-serif;letter-spacing:.11em;text-transform:uppercase}
.tile-note-body{margin:6px 0 0;color:rgba(244,243,241,.88);font:500 12px/1.45 Manrope,sans-serif;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tile-note-lock{display:block;margin-top:6px;color:rgba(244,243,241,.42);font:500 9px/1 Manrope,sans-serif;letter-spacing:.05em}
.tile-note.is-empty .tile-note-body{color:rgba(244,243,241,.4);font-style:italic}

/* ═══ FICHE CAMPAGNE : GRANDE LARGEUR WEB (pas une tablette) ══════════════════
   La fiche est consultée sur le WEB : elle doit respirer comme un profil de
   réseau social, pas tenir dans une colonne de tablette. On monte la spécificité
   (`.admin-dialog.profile-sheet`) parce que `.admin-dialog` était déclarée APRÈS
   `.profile-sheet` et la plafonnait à 780px — à spécificité égale, l'ordre gagne. */
/* `position:relative` est INDISPENSABLE : les elements en absolu de la fiche
   (icone clair/sombre, croix de fermeture) se placent sinon par rapport a un
   ancetre plus haut et atterrissent au milieu de la page. */
/* Client : « l'arrière-plan doit contenir l'ensemble ».
   MESURÉ : cette règle PLAFONNAIT la fiche à 1340 px (elle gagne sur 
   par la spécificité de ses deux classes). Sur un écran large, il restait donc de
   larges bandes vides de chaque côté : l'arrière-plan clair ne contenait pas tout.
   On lève le plafond et on laisse la fiche prendre la largeur utile. */
.admin-dialog.profile-sheet{position:relative;width:calc(100vw - 20px);max-width:1720px;max-height:94vh;overflow:auto}
@media(max-width:900px){.admin-dialog.profile-sheet{width:calc(100vw - 24px)}}

/* ═══ TITRES DE SECTION : ALIGNES A GAUCHE, PAS CENTRES ══════════════════════
   Centrés, chaque bloc rouge flottait à une position différente (mesuré : 654px
   pour un titre, 741px pour un autre — 87px d'écart). Alignés à gauche sur la
   marge du contenu, ils forment une vraie ligne directrice. */
.band{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:16px;margin:34px 0 16px}
.band-line{height:2px;border-radius:2px;background:linear-gradient(90deg,rgba(255,255,255,.42),rgba(255,255,255,.06));box-shadow:none}
.band-tag{display:inline-flex;align-items:center;gap:11px;padding:9px 18px;border-radius:13px;background:#e63946;box-shadow:0 4px 14px -4px rgba(230,57,70,.45);flex:none}
.band-tag b{color:#fff;font:800 22px/1 Manrope,sans-serif}
.band-tag span{color:#fff;font:700 12px Manrope,sans-serif;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}
.profile-sheet.editing .band-line{background:linear-gradient(90deg,rgba(23,23,25,.4),rgba(23,23,25,.08))}

/* ═══ EN-TETE DE PROFIL : colonne unique, alignee, sans chevauchement ════════
   Le contenu s'aligne sur UNE colonne : avatar, puis texte a droite — tous les
   titres de la fiche commencent donc sur la meme verticale (celle de l'avatar).
   On réduit aussi le nombre de couleurs concurrentes (contraste apaisé). */
.profile-head{display:grid;grid-template-columns:132px minmax(0,1fr);align-items:end;gap:26px;padding:0;margin-top:-62px;position:relative;z-index:2}
.profile-head .sheet-avatar-slot{grid-column:1;align-self:end;width:132px}
.profile-head .sheet-avatar-slot .image-pick{width:132px;height:132px;border-width:5px;border-color:#1c1d1f}
.profile-id{grid-column:2;padding-top:0;padding-bottom:10px;min-width:0;display:flex;flex-direction:column;justify-content:flex-end}
.profile-kicker{margin:0 0 7px;color:#c9a06a;font:700 12px Manrope,sans-serif;letter-spacing:.13em;text-transform:uppercase}
.profile-id h2{margin:0 0 14px;font:800 38px/1.08 Manrope,sans-serif;letter-spacing:-.025em;color:#fff}
.profile-meta{display:flex;align-items:center;gap:11px;flex-wrap:wrap}
.profile-dates{color:rgba(244,243,241,.78);font:600 13.5px Manrope,sans-serif}
.profile-sheet .badge-lg{font-size:11px;padding:6px 12px}
.profile-sheet.editing .profile-id h2{color:var(--text)}
.profile-sheet.editing .profile-kicker{color:#8a5a2b}
.profile-sheet.editing .profile-dates{color:var(--muted)}
.profile-sheet.editing .profile-head .sheet-avatar-slot .image-pick{border-color:#fff}

/* Sur ecran etroit, l'en-tete repasse en colonne : la colonne d'avatar fixe a 132px
   ecraserait le texte. On empile l'avatar au-dessus du bloc d'identite. */
@media(max-width:760px){
  .admin-dialog.profile-sheet{width:calc(100vw - 20px)}
  .profile-head{grid-template-columns:1fr;gap:14px;margin-top:-48px}
  .profile-head .sheet-avatar-slot{width:104px;justify-self:start}
  .profile-head .sheet-avatar-slot .image-pick{width:104px;height:104px;border-width:4px}
  .profile-id{grid-column:1;padding-top:0}
  .profile-id h2{font-size:26px}
  .profile-kicker{font-size:11px}
  .profile-dates{font-size:12.5px}
  .band{gap:11px;margin:26px 0 12px}
  .band-tag b{font-size:19px}
}

/* ═══ TITRES DE SECTION : CENTRÉS, CONTENU CENTRÉ JUSTE EN DESSOUS ═════════════
   Le titre est centré DANS la fiche (deux traits qui se partagent la largeur de
   part et d'autre), et tout le contenu de la section est centré lui aussi : une
   seule collaboratrice ne part plus à l'extrême gauche, elle se pose au milieu.
   L'œil descend donc l'axe central de la fiche au lieu de balayer à gauche. */
.band{display:flex;align-items:center;justify-content:center;gap:18px;margin:34px 0 16px}
/* ═══ LES DEUX TRAITS : ENCRE FONCÉE, JAMAIS BLANC ════════════════════════════
   MESURÉ : cette règle mettait `rgba(255,255,255,.06 → .42)` — un trait BLANC.
   C'était juste sur le thème sombre d'origine, et devenu INVISIBLE depuis que
   l'interface est claire. Le trait existait bien (515 × 2 px, à sa place), mais
   blanc sur blanc il ne laissait aucune trace : le client le voyait « disparu ».
   L'encre foncée se voit sur les deux thèmes et sur la vidéo de fond. */
.band-line{flex:1 1 0;height:2px;border-radius:2px;background:linear-gradient(90deg,rgba(20,22,27,.06),rgba(20,22,27,.44));box-shadow:none}
.band-line:last-child{background:linear-gradient(90deg,rgba(20,22,27,.44),rgba(20,22,27,.06))}
.band-tag{display:inline-flex;align-items:center;gap:11px;padding:9px 18px;border-radius:13px;background:#e63946;box-shadow:0 4px 14px -4px rgba(230,57,70,.45);flex:none}
.band-tag b{color:#fff;font:800 22px/1 Manrope,sans-serif}
.band-tag span{color:#fff;font:700 12px Manrope,sans-serif;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}
.profile-sheet.editing .band-line{background:linear-gradient(90deg,rgba(23,23,25,.08),rgba(23,23,25,.4))}
.profile-sheet.editing .band-line:last-child{background:linear-gradient(90deg,rgba(23,23,25,.4),rgba(23,23,25,.08))}

/* ─── Contenu des sections : CENTRÉ, juste sous son titre ────────────────────
   `justify-content:center` centre le GROUPE. Pour les grilles, on plafonne la
   largeur des colonnes (minmax) sinon les pistes s'étirent et le centrage ne se
   voit pas : une seule carte centrée doit rester une carte, pas une barre. */
.profile-sheet .chip-row{justify-content:center;margin-bottom:14px}
.profile-sheet .mini-people{justify-content:center;gap:10px}
.profile-sheet .loc-grid{grid-template-columns:repeat(auto-fit,minmax(160px,196px));justify-content:center;gap:12px}
.profile-sheet .mini-grid{grid-template-columns:repeat(auto-fit,minmax(150px,190px));justify-content:center;gap:12px;margin-top:2px}
/* Les cartes d'événements restent larges : elles portent plus de texte. */
.profile-sheet .event-list .mini-grid{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
/* Le titre d'une section se pose sous son titre, sans marge parasite. */
.profile-sheet .band{margin:36px 0 14px}

/* ═══ PETITE MENTION DU TEMPS RESTANT, SOUS LE BADGE DE STATUT ════════════════
   Ce n'est plus une grande carte : c'est une précision discrète. Vert tant qu'il
   reste du temps, doré dans les 7 derniers jours, gris une fois terminé. */
.tile-statut-pile{display:inline-flex;flex-direction:column;align-items:flex-end;gap:5px;flex:none}
.tile-jours{font:600 10.5px Manrope,sans-serif;color:#a8ddc6;letter-spacing:.02em;white-space:nowrap}
.tile-jours.is-soon{color:#f2d79b}
.tile-jours.is-past{color:#9a9c9e}
.profile-sheet.editing .tile-jours{color:var(--muted)}

/* ═══ BADGE DE STATUT SUR LA FICHE SOMBRE : RECTANGLE PLEIN, TEXTE BLANC ══════
   Sur le noir de la fiche, un libellé vert sans fond est illisible (« en cours
   en noir elle n'apparaît pas »). Chaque état reçoit donc un FOND PLEIN et un
   texte BLANC : vert pour En cours, doré pour À venir, gris pour Complété. */
.profile-sheet .badge-lg.connected{background:#1f9d63;border-color:#1f9d63;color:#fff;font-weight:700}
.profile-sheet .badge-lg.badge-planned{background:#c08a2e;border-color:#c08a2e;color:#fff;font-weight:700}
.profile-sheet .badge-lg.offline{background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.22);color:#fff;font-weight:700}
/* Même traitement sur la carte de la liste (le badge y est aussi sur fond sombre). */
.entity-tile .badge.connected,.entity-tile .badge.badge-planned{background:#1f9d63;border-color:#1f9d63;color:#fff}
.entity-tile .badge.badge-planned{background:#c08a2e;border-color:#c08a2e}
/* En modification, la fiche est blanche : on revient a un badge clair lisible. */
.profile-sheet.editing .badge-lg.connected{background:rgba(31,157,99,.14);border-color:rgba(31,157,99,.45);color:#0d6b42}
.profile-sheet.editing .badge-lg.badge-planned{background:rgba(192,138,46,.14);border-color:rgba(192,138,46,.45);color:#7a5510}
.profile-sheet.editing .badge-lg.offline{background:rgba(23,23,25,.07);border-color:var(--line);color:var(--muted)}

/* ═══ ICÔNE CLAIR / SOMBRE, DANS LE COIN DE LA FICHE ══════════════════════════
   Permet de basculer la fiche en version claire SANS entrer en modification, pour
   que le client puisse mieux lire s'il le souhaite. Le choix est mémorisé. */
/* Positionnement repris par .sheet-pin-bar (barre epinglee) : plus d absolu ici. */
.sheet-theme-toggle:hover{background:rgba(15,16,17,.78);transform:scale(1.06)}
/* Fiche en clair (sans modification) : on reprend les teintes du mode edition. */

/* ═══ ZONE DE CHOIX D'UN GOODIE, EN BAS DE LA SECTION GOODIES ═════════════════
   Discret : c'est un ajout, pas un élément de contenu. Centré comme le reste de
   la section, et séparé du dernier goodie par un filet léger. */
.goodie-picker-zone{margin-top:20px;padding-top:18px;border-top:1px solid rgba(255,255,255,.18)}
.goodie-picker-zone .picker-block{max-width:560px;margin:0 auto;text-align:left}
.goodie-picker-zone .picker-empty{margin-top:8px;color:rgba(244,243,241,.5);font-size:11.5px;text-align:center}
.profile-sheet.editing .goodie-picker-zone{border-top-color:var(--line)}
.profile-sheet.editing .goodie-picker-zone .picker-empty{color:var(--muted)}

/* ═══════════════════════════════════════════════════════════════════════════════
   MODE CLAIR MANUEL — CONTRASTE (appris de la fiche campagne)
   Le mode clair n'est PAS le mode sombre avec un fond blanc : chaque texte, tuile,
   pastille et badge doit recevoir une teinte FONCEE lisible. Ces regles miroitent
   celles du mode blanc de reference (.editing), a l'identique, sans deverrouiller
   l'edition. Regle generale : au basculement clair/sombre, verifier d'abord le
   contraste texte/fond ET le contraste par rapport a l'arriere-plan (photos).
   ═══════════════════════════════════════════════════════════════════════════════ */
.profile-sheet.sheet-clair{background:#fff;color:var(--text);border-color:var(--line)}
.profile-sheet.sheet-clair .sheet-section>h3{color:var(--muted)}
.profile-sheet.sheet-clair .row-sub,.profile-sheet.sheet-clair .mini-card-sub,.profile-sheet.sheet-clair .empty{color:var(--muted)}
.profile-sheet.sheet-clair .profile-dates,.profile-sheet.sheet-clair .event-jours{color:var(--muted)}
.profile-sheet.sheet-clair .form-status{color:var(--red)}
.profile-sheet.sheet-clair .sheet-actions{border-top-color:var(--line)}
.profile-sheet.sheet-clair .text-button{color:var(--red-dark)}
.profile-sheet.sheet-clair .text-button.strong{color:var(--red)}
.profile-sheet.sheet-clair .button.secondary{background:transparent;border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .mini-card{background:#fff;border-color:var(--line)}
.profile-sheet.sheet-clair .mini-card h4{color:var(--text)}
.profile-sheet.sheet-clair .mini-card-av{background:#f1efec}
.profile-sheet.sheet-clair .mini-card-stats{color:var(--muted)}
.profile-sheet.sheet-clair .mini-card-stats b{color:var(--text)}
.profile-sheet.sheet-clair .mini-card-x{background:rgba(32,33,35,.07);color:var(--muted)}
.profile-sheet.sheet-clair .goodie-tile{background:#fff}
.profile-sheet.sheet-clair .goodie-tile .goodie-visual{background:#f4f2ee}
.profile-sheet.sheet-clair .goodie-tile h4{color:var(--text)}
.profile-sheet.sheet-clair .event-card .event-date{background:#f4f2ee}
.profile-sheet.sheet-clair .event-card .event-date b{color:var(--red-dark)}
.profile-sheet.sheet-clair .event-card .event-date span{color:var(--muted)}
.profile-sheet.sheet-clair .city-chip{background:#fff;border-color:#d9b27d;color:#8a5100}
.profile-sheet.sheet-clair .person-chip{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.sheet-clair .chip-av{background:#f1efec;color:var(--text)}
.profile-sheet.sheet-clair .chip-x{background:rgba(32,33,35,.07);color:var(--muted)}



.profile-sheet.sheet-clair .picker-search input{background:#fff;border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .picker-block{border-top-color:var(--line)}
.profile-sheet.sheet-clair .sheet-section input:not([type=hidden]),.profile-sheet.sheet-clair .sheet-section textarea{background:#fff;border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .city-chip{background:rgba(230,57,70,.14);border-color:rgba(230,57,70,.42);color:#c9303c}
.profile-sheet.sheet-clair .campaign-cities{background:linear-gradient(120deg,#fff 0%,#fdf6ee 100%);border-color:var(--line)}
.profile-sheet.sheet-clair .cover-edit{display:inline-block}
.profile-sheet.sheet-clair .cover-remove,.profile-sheet.sheet-clair .cover-hint{display:block}
.profile-sheet.sheet-clair .cover-pick{cursor:pointer}
.profile-sheet.sheet-clair .goodie-tile{background:#fff;border-color:var(--line)}
.profile-sheet.sheet-clair .mini-person{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.sheet-clair .mini-person-av{background:#f1efec}
.profile-sheet.sheet-clair .mini-person-av .card-fallback{color:#8b8781}
.profile-sheet.sheet-clair .city-chip{background:#fff;border-color:var(--line);color:var(--text)}
.profile-sheet.sheet-clair .city-chip b{color:var(--text)}
.profile-sheet.sheet-clair .chip-count{background:#f1efec;color:var(--text);border-color:var(--line)}
.profile-sheet.sheet-clair .city-chip .chip-count{background:#f1efec;border-color:var(--line)}
.profile-sheet.sheet-clair .loc-tile{background:#fff;border-color:var(--line)}
.profile-sheet.sheet-clair .loc-tile-av{background:#f1efec;color:var(--text)}
.profile-sheet.sheet-clair .loc-tile strong{color:var(--text)}
.profile-sheet.sheet-clair .loc-tile small{color:var(--muted)}
.profile-sheet.sheet-clair .loc-tile-count{background:#f1efec;color:var(--text);border-color:var(--line)}
.profile-sheet.sheet-clair .event-card{background:#fff;border-color:var(--line)}
.profile-sheet.sheet-clair .event-card .event-main h4{color:var(--text)}
.profile-sheet.sheet-clair .event-card .mini-card-sub{color:var(--muted) !important}
.profile-sheet.sheet-clair .event-card .event-jours{color:var(--muted)}
.profile-sheet.sheet-clair .event-card .badge{background:transparent;border-color:var(--line2);color:var(--muted)}
.profile-sheet.sheet-clair .event-card .mini-card-x{background:rgba(32,33,35,.07);color:var(--muted)}
.profile-sheet.sheet-clair .band-line{background:linear-gradient(90deg,rgba(23,23,25,.1),rgba(23,23,25,.62) 45%,rgba(23,23,25,.62) 55%,rgba(23,23,25,.1));box-shadow:none}
.profile-sheet.sheet-clair .profile-id h2{color:var(--text)}
.profile-sheet.sheet-clair .profile-kicker{color:#8a5a2b}
.profile-sheet.sheet-clair .profile-dates{color:var(--muted)}
.profile-sheet.sheet-clair .profile-head .sheet-avatar-slot .image-pick{border-color:#fff}
.profile-sheet.sheet-clair .band-line{background:linear-gradient(90deg,rgba(23,23,25,.4),rgba(23,23,25,.08))}
.profile-sheet.sheet-clair .band-line{background:linear-gradient(90deg,rgba(23,23,25,.08),rgba(23,23,25,.4))}
.profile-sheet.sheet-clair .band-line:last-child{background:linear-gradient(90deg,rgba(23,23,25,.4),rgba(23,23,25,.08))}
.profile-sheet.sheet-clair .tile-jours{color:var(--muted)}
.profile-sheet.sheet-clair .badge-lg.connected{background:rgba(31,157,99,.14);border-color:rgba(31,157,99,.45);color:#0d6b42}
.profile-sheet.sheet-clair .badge-lg.badge-planned{background:rgba(192,138,46,.14);border-color:rgba(192,138,46,.45);color:#7a5510}
.profile-sheet.sheet-clair .badge-lg.offline{background:rgba(23,23,25,.07);border-color:var(--line);color:var(--muted)}
.profile-sheet.sheet-clair .goodie-picker-zone{border-top-color:var(--line)}
.profile-sheet.sheet-clair .goodie-picker-zone .picker-empty{color:var(--muted)}

/* ─── Le FONDU de la couverture vers le fond : il depend du THEME ────────────
   En sombre la banniere se fond dans le noir ; en clair elle DOIT se fondre dans
   le blanc, sinon on voit une demarcation franche et la banniere « flotte ». */
.profile-sheet.sheet-clair .cover-fade{height:110px;background:linear-gradient(to bottom,rgba(255,255,255,0) 0%,rgba(255,255,255,.6) 55%,#fff 100%)}
.profile-sheet.editing .cover-fade{height:110px;background:linear-gradient(to bottom,rgba(255,255,255,0) 0%,rgba(255,255,255,.6) 55%,#fff 100%)}
/* Le contour de l'avatar suit le fond : blanc sur blanc il disparaitrait. */
.profile-sheet.sheet-clair .profile-head .sheet-avatar-slot .image-pick{border-color:#fff}

/* ─── Le BOUTON clair/sombre doit rester VISIBLE sur une PHOTO ────────────────
   Il est pose sur la banniere : un fond translucide y devient illisible, dans les
   deux themes. On lui donne un fond opaque, une bordure et une ombre pour qu'il se
   detache de n'importe quel arriere-plan (photo claire ou sombre). */
.sheet-theme-toggle{background:rgba(15,16,17,.88);border-color:rgba(255,255,255,.42);color:#fff;box-shadow:0 3px 12px rgba(0,0,0,.45);backdrop-filter:blur(6px)}
.sheet-theme-toggle:hover{background:#0f1011;box-shadow:0 4px 16px rgba(0,0,0,.55)}
.profile-sheet.sheet-clair .sheet-theme-toggle{background:rgba(255,255,255,.94);border-color:rgba(23,23,25,.28);color:#1a1b1d;box-shadow:0 3px 12px rgba(0,0,0,.25)}
.profile-sheet.sheet-clair .sheet-theme-toggle:hover{background:#fff;box-shadow:0 4px 16px rgba(0,0,0,.32)}

/* ─── La GRILLE DE LECTURE (description + libelles) en mode clair ─────────────
   Elle n'avait aucune variante claire : en modification elle est masquee
   (.read-grid{display:none}), donc l'oubli ne se voyait PAS — mais en mode clair
   consulte, le texte restait blanc sur fond blanc. Cas typique a couvrir. */
.profile-sheet.sheet-clair .read-grid{background:transparent}
.profile-sheet.sheet-clair .read-item span{color:#6b6965;font:600 10px 'DM Mono',monospace;letter-spacing:.07em;text-transform:uppercase}
.profile-sheet.sheet-clair .read-item b{color:#161616;font:600 14px Manrope,sans-serif;line-height:1.5}
.profile-sheet.sheet-clair .read-item.read-wide b{color:#1a1b1d}

/* « temps restant » : vert pale sur fond blanc = 1.5 de contraste (illisible).
   En clair, on passe au vert FONCE, et le dore/gris des autres etats suivent. */
.profile-sheet.sheet-clair .tile-jours{color:#0d6b42}
.profile-sheet.sheet-clair .tile-jours.is-soon{color:#7a5510}
.profile-sheet.sheet-clair .tile-jours.is-past{color:#6b6965}

/* ─── La PASTILLE DU TEMPS RESTANT dans la fiche (« reste 105 j ») ─────────────
   Elle n'avait aucune regle propre a la fiche : elle heritait du style clair
   (fond rose pale, texte rouge fonce) qui devient illisible sur le fond SOMBRE.
   C'est le pendant, pour le profil, de ce que la carte fait avec .tile-jours. */
.profile-sheet .profile-jours{color:#e8a8ae;background:rgba(230,57,70,.16);border-color:rgba(230,57,70,.4)}
.profile-sheet.sheet-clair .profile-jours,
.profile-sheet.editing .profile-jours{color:#a8232f;background:#fdf3f4;border-color:#e9c9cd}

/* ─── Le RECTANGLE ROUGE du titre de section : lisibilite du texte blanc ───────
   Le rouge de charte #E63946 ne donne que 4.2 de contraste avec du texte blanc,
   juste sous le seuil WCAG de 4.5. On utilise #D42B38 : visuellement quasi
   identique, mais 5.0 de contraste. Applique aux deux themes (le rouge est la
   couleur de marque, elle ne change pas avec le theme). */
.band-tag{background:#a01a26;box-shadow:0 4px 14px -4px rgba(212,43,56,.45)}

/* ─── Les LIBELLES DE CHAMP dans la fiche (« Note interne », « Description ») ───
   Ils heritent du `.field label` global (gris 107,105,101), soit 2.9 de contraste
   sur le fond sombre : sous le seuil. On leur donne une teinte claire en sombre et
   foncee en clair — ce sont des libelles, pas du texte courant. */
.profile-sheet .field label{color:rgba(244,243,241,.72)}
.profile-sheet.sheet-clair .field label,
.profile-sheet.editing .field label{color:#5c5a56}

/* ═══ PASSES B + C : contrôles SUR la photo + cadrage ═════════════════════════
   Les contrôles d'image ne sont plus une colonne à côté : ils sont POSÉS SUR la
   photo. Nombre et libellé dépendent de l'existence d'une image :
     - une image existe  -> « Changer »  et  « Retirer »
     - aucune image      -> « Téléverser une image »
   En modification s'ajoute « Cadrer l'image » (molette = zoom, glisser = déplacer).
   Contraste vérifié : les contrôles sont posés sur une PHOTO (fond imprévisible),
   donc fond opaque + bordure + ombre, jamais un fond translucide. */
.image-field-row{position:relative}
.ip-actions{position:absolute;left:0;right:0;bottom:9px;display:flex;justify-content:center;gap:7px;z-index:4;pointer-events:none;padding:0 9px}
.ip-actions>*{pointer-events:auto}
.ip-btn{font:600 10.5px Manrope,sans-serif;padding:6px 11px;border-radius:99px;border:1px solid rgba(255,255,255,.42);background:rgba(15,16,17,.88);color:#fff;cursor:pointer;box-shadow:0 2px 9px rgba(0,0,0,.42);backdrop-filter:blur(6px);white-space:nowrap;transition:background .15s,transform .15s}
.ip-btn:hover{background:#0f1011;transform:translateY(-1px)}
.ip-btn-danger{border-color:rgba(255,150,158,.55);color:#ffc9ce}
.ip-btn-drop{border-color:rgba(255,255,255,.5)}
.ip-cadrage{position:absolute;left:0;right:0;top:9px;display:flex;justify-content:center;gap:7px;z-index:4;pointer-events:none;padding:0 9px;flex-wrap:wrap}
.ip-cadrage>*{pointer-events:auto}
.ip-btn-cadrer{border-color:rgba(120,200,255,.55)}
.ip-btn-cadrer.is-active{background:#e63946;border-color:#e63946;color:#fff}
.ip-zoom{display:inline-flex;align-items:center;gap:4px;padding:3px 6px;border-radius:99px;background:rgba(15,16,17,.88);border:1px solid rgba(255,255,255,.3);box-shadow:0 2px 9px rgba(0,0,0,.42);backdrop-filter:blur(6px)}
.ip-zoom-btn{width:22px;height:22px;border-radius:50%;border:0;background:rgba(255,255,255,.24);color:#fff;font:700 13px Manrope,sans-serif;cursor:pointer;line-height:1}
.ip-zoom-btn:hover{background:rgba(255,255,255,.3)}
.ip-zoom-val{color:#fff;font:600 10.5px 'DM Mono',monospace;min-width:34px;text-align:center}
.ip-btn-reset{border-color:rgba(255,255,255,.28);background:transparent;box-shadow:none;padding:4px 9px}
/* La croix « Retirer » du composant d'image disparait : elle est fusionnee dans les
   boutons poses sur la photo. */
.image-field-row .image-pick-edit{display:none}
/* La photo de profil accepte le glisser : on le signale par le curseur. */
.image-pick[data-cadrage-mode="1"]{cursor:grab;touch-action:none}
.image-pick[data-cadrage-mode="1"]:active{cursor:grabbing}
/* La couverture : ses contrôles sont poses de la meme facon, et sa barre de cadrage
   vit sous la banniere (une banniere n'a pas de coins a respecter). */
/* (regle dupliquee : la mise en page de la couverture vit plus haut) */
.cover-actions{position:absolute;left:0;right:0;bottom:14px;display:flex;justify-content:center;gap:7px;z-index:4;pointer-events:none}
.cover-actions>*{pointer-events:auto}
.cover-pick .cover-edit{display:none}
.cover-cadrage-wrap{position:absolute;left:0;right:0;top:12px;display:flex;justify-content:center;z-index:4;pointer-events:none}
.cover-cadrage-wrap>*{pointer-events:auto}
/* En LECTURE, aucun controle sur les images : ni changer, ni cadrer. */
.sheet-readonly .ip-actions,.sheet-readonly .ip-cadrage,.sheet-readonly .cover-actions,.sheet-readonly .cover-cadrage-wrap{display:none}
/* En modification seulement, « Cadrer » apparait (classe dediee, pour ne pas
   dependre des regles de comportement de .edit-only). */
.edit-only-only{display:none}
.profile-sheet.editing .edit-only-only,.admin-dialog.editing .edit-only-only{display:inline-flex}

/* ═══ VILLES, LIEUX, COLLABORATRICES : CARTES INVERSÉES EN BLANC ══════════════
   Même traitement que les goodies et les événements, pour que TOUTE la fiche
   respire le même contraste depuis le début : la page est sombre, les cartes de
   contenu (villes, lieux, collaboratrices, goodies, événements) sont CLAIRES.
   Chaque sous-élément est inversé aussi — avatar de repli, pastille de nombre,
   textes secondaires — sinon un élément clair sur carte claire devient invisible.
   Contraste mesuré : nom #1a1b1d sur #c9c7c3 = 10.2 (seuil 4.5). */
.profile-sheet .city-chip{background:#c9c7c3;border-color:rgba(255,255,255,.3);color:#1a1b1d;font:600 13px Manrope,sans-serif}
.profile-sheet .city-chip:hover{background:#d3d1cd;border-color:rgba(255,255,255,.5)}
.profile-sheet .city-chip b{color:#1a1b1d;font-weight:700}
.profile-sheet .city-chip .chip-count{background:rgba(26,27,29,.14);border:1px solid rgba(26,27,29,.22);color:#1a1b1d}
.profile-sheet .city-chip .chip-x-sm{background:rgba(26,27,29,.14);color:rgba(26,27,29,.82)}
.profile-sheet .city-chip .chip-x-sm:hover{background:#c0392f;color:#fff}

.profile-sheet .loc-tile{background:#c9c7c3;border-color:rgba(255,255,255,.3)}
.profile-sheet .loc-tile:hover{background:#d3d1cd;border-color:rgba(255,255,255,.5)}
.profile-sheet .loc-tile-av{background:rgba(26,27,29,.14);color:#1a1b1d}
.profile-sheet .loc-tile strong{color:#1a1b1d}
.profile-sheet .loc-tile small{color:rgba(26,27,29,.72)}
.profile-sheet .loc-tile-count{background:rgba(26,27,29,.14);color:#1a1b1d;border-color:rgba(26,27,29,.22)}

.profile-sheet .mini-person{background:#c9c7c3;border-color:rgba(255,255,255,.3);color:#1a1b1d}
.profile-sheet .mini-person:hover{background:#d3d1cd}
.profile-sheet .mini-person-av{background:rgba(26,27,29,.14)}
.profile-sheet .mini-person-av .card-fallback{color:#1a1b1d}

/* ═══ LA NOTE INTERNE EST UN POST-IT ══════════════════════════════════════════
   Pas de bouton, pas de libelle d'aide : on ecrit, ca reste. En lecture comme en
   modification le champ est actif, avec l'allure d'un post-it (fond doux, texte
   lisible) plutot que d'un champ de formulaire a valider. */
.profile-sheet .note-postit-field textarea{min-height:74px;line-height:1.55;border-radius:12px;resize:vertical}
.profile-sheet .note-postit-field textarea:not([disabled]),
.profile-sheet.sheet-readonly .note-postit-field textarea,
.profile-sheet.editing .note-postit-field textarea{background:rgba(255,255,255,.07);border:1px dashed rgba(255,255,255,.26);color:#f4f3f1;cursor:text}
.profile-sheet .note-postit-field textarea:focus{outline:0;border-style:solid;border-color:rgba(212,43,56,.6);background:rgba(255,255,255,.18)}
.profile-sheet .note-postit-field textarea::placeholder{color:rgba(244,243,241,.42)}
/* Mode clair et modification : post-it sur fond creme, texte fonce. */
.profile-sheet.sheet-clair .note-postit-field textarea,
.profile-sheet.editing .note-postit-field textarea{background:#fdf9ee;border:1px dashed #d9cba6;color:#2a2723}
.profile-sheet.sheet-clair .note-postit-field textarea::placeholder,
.profile-sheet.editing .note-postit-field textarea::placeholder{color:#8b8781}
.profile-sheet.sheet-clair .note-postit-field textarea:focus,
.profile-sheet.editing .note-postit-field textarea:focus{border-style:solid;border-color:#c0392f;background:#fffdf7}
/* La mention d'edition n'existe plus sur la carte : on nettoie le style associe. */
.tile-note-lock{display:none}

/* ═══ BARRE D'ACTIONS ÉPINGLÉE (thème + fermer) ═══════════════════════════════
   Les deux boutons sont côte à côte, en haut à droite, SUR la couverture.
   Épinglés : ils restent visibles quand on descend dans la fiche.
   `position:fixed` + recalcul des coordonnées sur la position de la fiche
   (voir recalerBarreFiche()) : un `absolute` resterait dans le flux défilant et
   disparaîtrait dès qu'on descend, un `sticky` sur un élément interne ne suit pas
   le bord de la boîte. Le fond est OPAQUE + ombre : la barre se pose sur une PHOTO,
   où un fond translucide devient illisible. */
.sheet-pin-bar{position:fixed;display:flex;align-items:center;gap:8px;z-index:60}
.sheet-pin-bar .sheet-theme-toggle,
.sheet-pin-bar .sheet-close-btn{position:static;display:grid;place-items:center;width:38px;height:38px;border-radius:50%;cursor:pointer;font-size:17px;line-height:1;transition:background .15s,transform .15s,color .15s,border-color .15s}
.sheet-pin-bar .sheet-theme-toggle{background:rgba(15,16,17,.88);border:1px solid rgba(255,255,255,.42);color:#fff;box-shadow:0 3px 12px rgba(0,0,0,.45);backdrop-filter:blur(6px)}
.sheet-pin-bar .sheet-theme-toggle:hover{background:#0f1011;transform:scale(1.06)}
.sheet-pin-bar .sheet-close-btn{background:rgba(15,16,17,.88);border:1px solid rgba(255,255,255,.42);color:#fff;font-size:22px;box-shadow:0 3px 12px rgba(0,0,0,.45);backdrop-filter:blur(6px)}
.sheet-pin-bar .sheet-close-btn:hover{background:#c0392f;border-color:#c0392f;transform:scale(1.06)}
/* Thème clair : la barre s'inverse pour rester lisible sur la couverture. */
.profile-sheet.sheet-clair .sheet-pin-bar .sheet-theme-toggle,
.profile-sheet.sheet-clair .sheet-pin-bar .sheet-close-btn{background:rgba(255,255,255,.94);border-color:rgba(23,23,25,.28);color:#1a1b1d;box-shadow:0 3px 12px rgba(0,0,0,.25)}
.profile-sheet.sheet-clair .sheet-pin-bar .sheet-close-btn:hover{background:#c0392f;border-color:#c0392f;color:#fff}
/* La fiche passe en clair : la barre suit (elle est sœur, pas enfant). */
/* Specificite volontairement plus haute (3 classes + balise) : la regle generique
   ci-dessus est declaree plus bas dans le fichier et gagnerait autrement. */
.sheet-pin-bar.is-clair button.sheet-theme-toggle,
.sheet-pin-bar.is-clair button.sheet-close-btn{background:rgba(255,255,255,.94);border-color:rgba(23,23,25,.28);color:#1a1b1d;box-shadow:0 3px 12px rgba(0,0,0,.25)}
.sheet-pin-bar.is-clair button.sheet-close-btn:hover{background:#c0392f;border-color:#c0392f;color:#fff}

/* ═══ La barre epinglee reste DANS le coin de la fiche en descendant ══════════
   `position:fixed` la sort du flux defilant ; ses coordonnees sont recalculees sur
   la position reelle de la fiche par recalerBarreFiche(), sinon elle resterait
   collee au bord de l'ecran et non au coin du pop-up. */
.sheet-pin-bar{position:absolute;top:10px;right:12px;display:flex;align-items:center;gap:8px;
  z-index:60;transition:opacity .15s}
.sheet-pin-bar[hidden]{display:none}

/* ═══ SÉLECTEUR DE TRI ET BASCULE CARTONS/LISTE (section Événements) ══════════
   Posés À DROITE du titre, hors du bloc rouge : des commandes de section, pas de
   la marque. Discrets au repos, lisibles au survol. Le tri est actif EN LECTURE
   (consulter n'est pas modifier). Contraste vérifié sur les deux thèmes. */
.band-actions{display:inline-flex;align-items:center;gap:6px;flex:none;margin-left:4px}
.evt-tri-wrap{position:relative}
.evt-tri-btn,.evt-vue-btn{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;cursor:pointer;
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.2);color:rgba(244,243,241,.82);
  font:600 13px Manrope,sans-serif;transition:background .15s,border-color .15s,color .15s}
.evt-tri-btn:hover,.evt-vue-btn:hover{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.4);color:#fff}
.evt-tri-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:150px;z-index:30;display:none;
  padding:6px;border-radius:12px;background:#202225;border:1px solid rgba(255,255,255,.24);box-shadow:0 8px 24px rgba(0,0,0,.5)}
.evt-tri-menu.open{display:block}
.evt-tri-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border:0;border-radius:8px;
  background:transparent;color:rgba(244,243,241,.9);font:500 12.5px Manrope,sans-serif;text-align:left;cursor:pointer}
.evt-tri-item:hover{background:rgba(255,255,255,.18)}
.evt-tri-item.is-actif{color:#fff;font-weight:700}
.evt-tri-check{width:12px;display:inline-block;color:#7fd6a8;font-weight:700}
/* Thème clair et modification : on inverse. */
.profile-sheet.sheet-clair .evt-tri-btn,.profile-sheet.sheet-clair .evt-vue-btn,
.profile-sheet.editing .evt-tri-btn,.profile-sheet.editing .evt-vue-btn{background:rgba(23,23,25,.06);border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .evt-tri-btn:hover,.profile-sheet.sheet-clair .evt-vue-btn:hover,
.profile-sheet.editing .evt-tri-btn:hover,.profile-sheet.editing .evt-vue-btn:hover{background:rgba(23,23,25,.12);border-color:rgba(23,23,25,.3)}
.profile-sheet.sheet-clair .evt-tri-menu,.profile-sheet.editing .evt-tri-menu{background:#fff;border-color:var(--line);box-shadow:0 8px 24px rgba(0,0,0,.15)}
.profile-sheet.sheet-clair .evt-tri-item,.profile-sheet.editing .evt-tri-item{color:var(--text)}
.profile-sheet.sheet-clair .evt-tri-item:hover,.profile-sheet.editing .evt-tri-item:hover{background:#f1efec}
.profile-sheet.sheet-clair .evt-tri-check,.profile-sheet.editing .evt-tri-check{color:#0d6b42}

/* ═══ GROUPEMENT PAR VILLE : en-tête de groupe ═══════════════════════════════ */
.evt-groupe{margin-bottom:16px}
.evt-groupe:last-child{margin-bottom:0}
.evt-groupe-head{display:flex;align-items:center;justify-content:center;gap:9px;margin:0 0 9px}
.evt-groupe-head span{color:#fff;font:700 11px Manrope,sans-serif;letter-spacing:.14em;text-transform:uppercase}
.evt-groupe-head b{display:grid;place-items:center;min-width:20px;height:20px;padding:0 6px;border-radius:99px;
  background:rgba(255,255,255,.14);color:#fff;font:700 10px 'DM Mono',monospace}
.profile-sheet.sheet-clair .evt-groupe-head span,.profile-sheet.editing .evt-groupe-head span{color:var(--text)}
.profile-sheet.sheet-clair .evt-groupe-head b,.profile-sheet.editing .evt-groupe-head b{background:rgba(23,23,25,.08);color:var(--text)}

/* ═══ MODE LISTE : une ligne compacte par événement ══════════════════════════ */
.evt-list{display:grid;gap:7px}
.evt-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;gap:12px;
  padding:10px 12px;border-radius:11px;background:#c9c7c3;border:1px solid rgba(255,255,255,.3);cursor:pointer;
  transition:background .15s,border-color .15s}
.evt-row:hover{background:#d3d1cd;border-color:rgba(255,255,255,.5)}
.evt-row-date{display:grid;place-items:center;min-width:52px;padding:5px 7px;border-radius:8px;background:rgba(26,27,29,.1);
  color:#1a1b1d;font:700 10px 'DM Mono',monospace;letter-spacing:.03em}
.evt-row-date b{font-size:15px;font-family:Manrope,sans-serif}
.evt-row-main{display:grid;gap:2px;min-width:0}
.evt-row-main strong{color:#1a1b1d;font:700 13px Manrope,sans-serif}
.evt-row-main small{color:rgba(26,27,29,.72);font:500 11.5px Manrope,sans-serif;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.evt-row .badge{border-color:rgba(26,27,29,.28);color:#1a1b1d;background:rgba(255,255,255,.5)}
.evt-row .mini-card-x{position:static;background:rgba(26,27,29,.12);color:rgba(26,27,29,.8)}
.evt-row .mini-card-x:hover{background:#c0392f;color:#fff}
@media(max-width:700px){.evt-row{grid-template-columns:auto minmax(0,1fr) auto;row-gap:6px}.evt-row .badge{grid-column:2}}

/* ═══ LA DESCRIPTION, TEXTE LIBRE DE LA FICHE ═════════════════════════════════
   Plus de grille de lecture ni de post-it séparé : une seule zone où l'on écrit.
   Elle s'enregistre toute seule (aucun bouton), donc elle doit SE LIRE comme un
   texte et S'ÉDITER comme un champ — sans que la frontière soit brutale.
   Contraste vérifié sur les trois états (sombre, clair, édition). */
.desc-libre{position:relative;margin:0 0 6px}
.desc-libre-kicker{margin:0 0 8px;color:rgba(244,243,241,.5);font:600 10px 'DM Mono',monospace;letter-spacing:.12em;text-transform:uppercase}
.desc-libre-zone{display:block;width:100%;min-height:84px;resize:vertical;padding:15px 17px;
  border-radius:14px;line-height:1.62;font:500 14px Manrope,sans-serif;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.21);color:#f4f3f1}
.desc-libre-zone::placeholder{color:rgba(244,243,241,.34)}
.desc-libre-zone:hover{background:rgba(255,255,255,.075);border-color:rgba(255,255,255,.24)}
.desc-libre-zone:focus{outline:0;background:rgba(255,255,255,.09);border-color:rgba(212,43,56,.55)}
/* L'état d'enregistrement : discret, il ne doit pas distraire pendant la frappe. */
.desc-libre-etat{position:absolute;right:12px;bottom:9px;color:rgba(244,243,241,.45);font:500 10px 'DM Mono',monospace;pointer-events:none}
/* Thème clair et édition : fond crème, texte foncé. */
.profile-sheet.sheet-clair .desc-libre-zone,
.profile-sheet.editing .desc-libre-zone{background:#f7f6f4;border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .desc-libre-zone::placeholder,
.profile-sheet.editing .desc-libre-zone::placeholder{color:#9a9691}
.profile-sheet.sheet-clair .desc-libre-zone:focus,
.profile-sheet.editing .desc-libre-zone:focus{background:#fff;border-color:#a01a26}
.profile-sheet.sheet-clair .desc-libre-kicker,
.profile-sheet.editing .desc-libre-kicker{color:var(--muted)}
.profile-sheet.sheet-clair .desc-libre-etat,
.profile-sheet.editing .desc-libre-etat{color:var(--muted)}

/* ═══ L'IDENTITÉ : nom et dates, édition EN PLACE ═════════════════════════════
   Plus de bouton « Modifier » : ces champs sont toujours actifs. On les présente
   sobrement — un libellé discret, un champ qui se confond avec la fiche. */
.identite-edit{margin:20px 0 4px;padding-top:18px;border-top:1px solid rgba(255,255,255,.18)}
.identite-edit .field{margin-bottom:12px}
.identite-edit .field:last-child{margin-bottom:0}
.identite-edit .field label{color:rgba(244,243,241,.5)}
.identite-edit .field input{width:100%;padding:11px 13px;border-radius:10px;font:500 13.5px Manrope,sans-serif;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.21);color:#f4f3f1}
.identite-edit .field input:hover{background:rgba(255,255,255,.075);border-color:rgba(255,255,255,.24)}
.identite-edit .field input:focus{outline:0;background:rgba(255,255,255,.09);border-color:rgba(212,43,56,.55)}
.profile-sheet.sheet-clair .identite-edit,.profile-sheet.editing .identite-edit{border-top-color:var(--line)}
.profile-sheet.sheet-clair .identite-edit .field label,.profile-sheet.editing .identite-edit .field label{color:var(--muted)}
.profile-sheet.sheet-clair .identite-edit .field input,.profile-sheet.editing .identite-edit .field input{background:#f7f6f4;border-color:var(--line2);color:var(--text)}
.profile-sheet.sheet-clair .identite-edit .field input:focus,.profile-sheet.editing .identite-edit .field input:focus{background:#fff;border-color:#a01a26}

/* ═══ CROIX DE RETRAIT AU SURVOL (.carton-x) ══════════════════════════════════
   Sur les cartons de la fiche (lieu, collaboratrice, goodie). Invisible au repos
   pour ne pas encombrer la lecture ; elle apparait au survol du carton. Fond
   OPAQUE + ombre : elle se pose sur des cartes CLAIRES, un fond translucide y
   serait illisible. */
.carton-x{position:absolute;top:7px;right:7px;z-index:5;display:grid;place-items:center;
  width:24px;height:24px;border-radius:50%;background:rgba(15,16,17,.82);color:#fff;
  font:600 15px/1 Manrope,sans-serif;cursor:pointer;opacity:0;transform:scale(.85);
  transition:opacity .15s,transform .15s,background .15s;box-shadow:0 2px 8px rgba(0,0,0,.35)}
.carton-x:hover{background:#c0392f;transform:scale(1.08)}
/* Apparition au survol du carton porteur, ou au focus clavier (accessibilite). */
.loc-tile:hover .carton-x,.mini-person:hover .carton-x,.goodie-tile:hover .carton-x,
.evt-row:hover .carton-x,.event-card:hover .carton-x{opacity:1;transform:scale(1)}
.loc-tile:focus-visible .carton-x,.mini-person:focus-visible .carton-x,
.goodie-tile:focus-visible .carton-x{opacity:1;transform:scale(1)}

/* Les cartons de la fiche deviennent CLIQUABLES : le curseur le dit. */
.loc-tile,.mini-person,.goodie-tile{cursor:pointer;position:relative}
.mini-person{position:relative}
/* Un lieu rattache mais sans aucun evenement : signale discretement. */
.loc-tile.is-inutilise .loc-tile-count.is-zero{background:rgba(192,57,47,.16);color:#8c2b23;border-color:rgba(192,57,47,.3)}

/* ═══ LE « + » FILIGRANÉ DE SECTION ═══════════════════════════════════════════
   Discret au repos (filigrane), net au survol. À droite du titre de section, hors
   du bloc rouge. Il ouvre le pop-up d'assignation. */
.plus-section{display:grid;place-items:center;width:30px;height:30px;border-radius:50%;cursor:pointer;
  background:transparent;border:1px dashed rgba(255,255,255,.28);color:rgba(244,243,241,.45);
  font:600 17px/1 Manrope,sans-serif;transition:background .18s,border-color .18s,color .18s,transform .18s}
.plus-section:hover{background:rgba(212,43,56,.16);border-color:rgba(212,43,56,.6);color:#fff;transform:scale(1.08)}
.profile-sheet.sheet-clair .plus-section,.profile-sheet.editing .plus-section{border-color:rgba(23,23,25,.22);color:rgba(23,23,25,.42)}
.profile-sheet.sheet-clair .plus-section:hover,.profile-sheet.editing .plus-section:hover{background:rgba(212,43,56,.12);border-color:#a01a26;color:#a8232f}

/* ═══ LE POP-UP D'ASSIGNATION ═════════════════════════════════════════════════
   Petit pop-up centré : recherche, puis les éléments proposés en cartons ou en
   liste. Il reste ouvert après un ajout (on en ajoute plusieurs d'affilée). */
.assign-dialog{width:min(720px,calc(100vw - 40px));max-height:82vh;display:flex;flex-direction:column;padding:0}
.assign-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:22px 24px 14px}
.assign-kicker{margin:0 0 4px;color:var(--red);font:700 10px 'DM Mono',monospace;letter-spacing:.1em;text-transform:uppercase}
.assign-head h2{margin:0;font:800 22px Manrope,sans-serif;letter-spacing:-.02em}
.assign-head .sheet-close-btn{position:static;flex:none;background:rgba(23,23,25,.06);border:1px solid var(--line);color:var(--text);border-radius:50%;width:36px;height:36px}
.assign-head .sheet-close-btn:hover{background:var(--red);border-color:var(--red);color:#fff}
.assign-bar{display:flex;align-items:center;gap:9px;padding:0 24px 14px}
.assign-bar input{flex:1;min-height:46px;padding:11px 15px;border-radius:11px;font:500 14px Manrope,sans-serif;
  background:#f7f6f4;border:1px solid var(--line2);color:var(--text)}
.assign-bar input:focus{outline:0;background:#fff;border-color:#a01a26}
.assign-vue{display:grid;place-items:center;width:46px;height:46px;flex:none;border-radius:11px;cursor:pointer;
  background:#f7f6f4;border:1px solid var(--line2);color:var(--text);font-size:16px}
.assign-vue:hover{background:#f1efec;border-color:rgba(23,23,25,.3)}
.assign-corps{flex:1;overflow:auto;padding:0 24px 8px;min-height:180px}
.assign-grille{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px}
.assign-liste{display:grid;gap:7px}
.assign-carton{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:11px;
  padding:11px 13px;border-radius:12px;cursor:pointer;text-align:left;
  background:#f7f6f4;border:1px solid var(--line);color:var(--text);transition:background .15s,border-color .15s}
.assign-carton:hover{background:#fff;border-color:#a01a26}
.assign-carton.is-choisi{background:rgba(31,157,99,.1);border-color:rgba(31,157,99,.5)}
.assign-carton.is-epuise{opacity:.62}
.assign-av{display:grid;place-items:center;width:42px;height:42px;flex:none;border-radius:11px;overflow:hidden;
  background:#eceae6;font:700 12px Manrope,sans-serif;color:var(--muted)}
.assign-av img{width:100%;height:100%;object-fit:cover}
.assign-txt{display:grid;gap:2px;min-width:0}
.assign-txt strong{font:600 13px Manrope,sans-serif;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.assign-txt small{font:500 11px Manrope,sans-serif;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.assign-etat{display:grid;place-items:center;width:26px;height:26px;flex:none;border-radius:50%;
  background:rgba(23,23,25,.07);color:var(--muted);font:700 14px Manrope,sans-serif}
.assign-carton:hover .assign-etat{background:#a01a26;color:#fff}
.assign-carton.is-choisi .assign-etat{background:#1f9d63;color:#fff}
.assign-vide{padding:34px 10px;text-align:center;color:var(--muted);font:500 13px Manrope,sans-serif}
.assign-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:14px 24px 20px;border-top:1px solid var(--line);margin-top:8px}
.assign-foot span{color:var(--muted);font:500 11.5px Manrope,sans-serif}

.assign-aide{margin:7px 0 0;color:var(--muted);font:500 11.5px Manrope,sans-serif;max-width:44ch;line-height:1.5}

/* ═══ LES LIBELLÉS AU FORMAT DES RECTANGLES ROUGES ════════════════════════════
   Le motif des titres de section (« 4 VILLES ») repris sur les libellés de
   l'identité : Nom de la campagne, Date de début, Date de fin, et Description.
   Un seul motif visuel sur toute l'affiche.

   Spécificité : les règles `.identite-edit .field label` (0,3,1) et le jeu du thème
   clair (0,4,1) s'appliquent aussi — les sélecteurs ci-dessous sont donc écrits avec
   la MÊME spécificité et placés EN FIN de fichier, pour gagner sans `!important`. */
.tag-rouge,
.identite-edit .field label.tag-rouge,
.profile-sheet.sheet-clair .identite-edit .field label.tag-rouge,
.profile-sheet.editing .identite-edit .field label.tag-rouge,
.desc-libre .tag-rouge{
  display:inline-flex;align-items:center;gap:9px;width:auto;max-width:max-content;
  margin:0 0 10px;padding:7px 15px;border-radius:11px;
  background:#a01a26;color:#fff;
  font:700 10px 'DM Mono',monospace;letter-spacing:.12em;text-transform:uppercase;
  box-shadow:0 4px 14px -5px rgba(212,43,56,.5)}
/* Le libellé de la description n'a pas de `label` : il porte seul la classe. */
.desc-libre .tag-rouge{margin-bottom:9px}

/* ═══ L'IDENTITÉ ET LA DESCRIPTION CENTRÉES, COMME LEURS TITRES ═══════════════
   Les titres de section sont centrés (deux traits) ; les libellés et les champs de
   l'identité commençaient à x=29 — décalage visible signalé par le client. On aligne
   tout sur le même axe : le bloc identité se lit comme un en-tête de profil. */
.desc-libre,
.identite-edit{text-align:center}
/* Les libellés rouges se centrent : `max-content` empêchait le centrage. */
.desc-libre .tag-rouge,
.identite-edit .field label.tag-rouge{margin-left:auto;margin-right:auto}
/* Le contenu des champs est centré : le nom se lit comme un titre, les dates
   s'alignent sous elles. */
.identite-edit .field input{text-align:center}
/* Le champ « nom » est le titre de l'affiche : plus large et plus grand. */
.identite-edit .field input[name="name"]{font-size:25px;font-weight:800;letter-spacing:-.02em;padding:14px 16px}
/* Les deux dates restent sur une rangée, chacune centrée dans sa colonne. */
.identite-edit .form-grid{justify-items:stretch}
/* La description : on garde le texte centré (c'est un texte d'accroche court lu
   sous son libellé), avec une largeur de lecture confortable. */
/* retiré : ancienne version centrée (remplacée plus bas) */

/* ═══ LE TITRE DE SECTION RESTE CENTRÉ MALGRÉ LES BOUTONS ═════════════════════
   Cause : `.band-actions` (le « + », le tri, la bascule) était DANS le flux flex.
   Elle volait donc l'espace du trait droit : les deux traits n'étaient plus
   symétriques et le titre glissait vers la gauche — visible seulement sur les
   sections qui portent des boutons.

   Correction : les boutons SORTENT du flux (`position:absolute` à droite). Les deux
   traits retrouvent une largeur égale et le titre est centré au milieu. Le trait
   passe sous les boutons — c'est accepté, et c'est le prix du centrage. */
.band{position:relative}
.band-actions{position:absolute;right:0;top:50%;transform:translateY(-50%);margin-left:0}
/* Les boutons gardent un fond opaque : posés sur le trait, ils doivent rester nets. */
.band-actions .plus-section,
.band-actions .evt-tri-btn,
.band-actions .vue-btn,
.band-actions button{background-color:transparent}

/* ═══ CENTRAGE PARFAIT : LA BARRE DE DÉFILEMENT RÉSERVÉE DES DEUX CÔTÉS ═══════
   La fiche défile (contenu long) : sa barre occupe 17 px À DROITE seulement. La
   zone de contenu est donc décalée de 8,5 px vers la gauche par rapport au centre de
   la boîte, ce qui se lit comme un léger désalignement des titres.
   `scrollbar-gutter: stable both-edges` réserve la même gouttière des deux côtés :
   le contenu se retrouve centré au milieu de la fiche, et la mise en page ne bouge
   plus quand la barre apparaît ou disparaît. */
.profile-sheet{scrollbar-gutter:stable both-edges}
.profile-sheet::-webkit-scrollbar{width:9px}
.profile-sheet::-webkit-scrollbar-track{background:transparent}
.profile-sheet::-webkit-scrollbar-thumb{background:rgba(255,255,255,.24);border-radius:9px}
.profile-sheet::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.3)}
.profile-sheet.sheet-clair::-webkit-scrollbar-thumb{background:rgba(23,23,25,.18)}
.profile-sheet.sheet-clair::-webkit-scrollbar-thumb:hover{background:rgba(23,23,25,.34)}

/* ═══ L'IDENTITÉ : DEUX DATES SUR UNE LIGNE ══════════════════════════════════
   Le nom de la campagne est déjà dans l'en-tête : plus de bulle ni de champ ici.
   Les deux dates se répondent, l'une à gauche, l'autre à droite, séparées par un
   trait discret qui traverse le vide central. */
.identite-edit{margin:22px 0 6px;padding:0;border-top:0}
.identite-dates{display:flex;align-items:center;gap:18px;width:100%}
.date-slot{display:inline-flex;align-items:center;gap:9px;flex:none}
.date-lab{color:var(--muted);font:700 10px 'DM Mono',monospace;letter-spacing:.14em;text-transform:uppercase}
/* Le trait qui relie les deux dates : il prend tout l'espace libre. */
.date-trait{flex:1;height:1px;border-radius:1px;background:linear-gradient(90deg,rgba(255,255,255,.04),rgba(255,255,255,.3),rgba(255,255,255,.04))}
.profile-sheet.sheet-clair .date-trait{background:linear-gradient(90deg,rgba(23,23,25,.05),rgba(23,23,25,.24),rgba(23,23,25,.05))}

/* ═══ LA DESCRIPTION : UN TITRE GRIS, PUIS UN ESPACE CERNÉ « EN LECTURE » ════
   Plus de bulle rouge sur ce titre (le rouge est réservé aux titres de SECTION).
   Le texte se lit dans un cadre discret ; le DOUBLE-CLIC ouvre l'écriture. */
.desc-titre{margin:0 0 10px;padding:0 0 0 0;color:#a8a5a1;
  font:700 15px Manrope,sans-serif;letter-spacing:.02em}
.profile-sheet.sheet-clair .desc-titre{color:#6d6a66}
/* L'espace de lecture : cerné, centré, il se lit comme un texte, pas un champ. */
.desc-lecture{width:100%;max-width:820px;margin:0 auto;padding:17px 19px;border-radius:14px;
  background:rgba(255,255,255,.035);border:1px dashed rgba(255,255,255,.2);cursor:text;
  transition:background .15s,border-color .15s}
.desc-lecture:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.34)}
.profile-sheet.sheet-clair .desc-lecture{background:#faf9f7;border-color:rgba(23,23,25,.16)}
.profile-sheet.sheet-clair .desc-lecture:hover{background:#f6f5f2;border-color:rgba(23,23,25,.3)}
/* retiré : ancienne version centrée (remplacée plus bas) */
.profile-sheet.sheet-clair .desc-texte{color:#161616}
.desc-libre.is-empty .desc-texte{color:rgba(244,243,241,.42);font-style:italic}
.profile-sheet.sheet-clair .desc-libre.is-empty .desc-texte{color:#9a9691}
/* En écriture, la zone reprend sa place : le cadre de lecture disparaît. */
/* retiré : ancienne version centrée */
.desc-libre-zone[hidden]{display:none}
/* Le libellé rouge n'a plus cours sur la description : on neutralise l'ancien. */

/* Le titre éditable de l'en-tête : le curseur annonce le double-clic. */
.titre-editable{cursor:text}
.titre-champ{width:100%;max-width:640px;margin:0 auto;padding:10px 14px;border-radius:11px;
  text-align:center;font:800 27px Manrope,sans-serif;letter-spacing:-.02em;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.24);color:#f4f3f1}
.titre-champ[hidden]{display:none}
.titre-champ:focus{outline:0;background:rgba(255,255,255,.18);border-color:#a01a26}
.profile-sheet.sheet-clair .titre-champ{background:#f7f6f4;border-color:var(--line2);color:#161616}
.profile-sheet.sheet-clair .titre-champ:focus{background:#fff;border-color:#a01a26}

/* ═══ LA DESCRIPTION EN LECTURE : UN TEXTE, AUCUN CADRE ══════════════════════
   Le client ne veut pas de cadre visible en lecture. On lit donc un simple texte
   aligné à GAUCHE. Le cadre (la zone de saisie) n'existe qu'en écriture : il
   apparaît au double-clic, puis disparaît au retour en lecture. */
.desc-libre{position:relative;margin:0 0 6px;text-align:left}
.desc-titre{margin:34px 0 11px;padding:0;color:#a8a5a1;text-align:left;
  font:700 19px Manrope,sans-serif;letter-spacing:.06em;text-transform:uppercase}
.profile-sheet.sheet-clair .desc-titre{color:#6d6a66}
/* Le texte lu : pas de bordure, pas de fond — on lit, c'est tout. */
.desc-texte{margin:0;padding:2px 0;text-align:left;color:#f4f3f1;
  font:500 14.5px Manrope,sans-serif;line-height:1.64;white-space:pre-wrap;
  cursor:text;border-radius:10px;transition:background .15s}
.desc-texte:hover{background:rgba(255,255,255,.045)}
.profile-sheet.sheet-clair .desc-texte{color:#161616}
.profile-sheet.sheet-clair .desc-texte:hover{background:rgba(23,23,25,.045)}
.desc-libre.is-empty .desc-texte{color:rgba(244,243,241,.4);font-style:italic}
.profile-sheet.sheet-clair .desc-libre.is-empty .desc-texte{color:#9a9691}
/* En ÉCRITURE seulement : le cadre apparaît (c'est la zone de saisie). */
.desc-libre-zone{display:block;width:100%;max-width:100%;min-height:96px;resize:vertical;
  margin:0;padding:15px 17px;border-radius:14px;line-height:1.62;
  text-align:left;font:500 14.5px Manrope,sans-serif;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.24);color:#f4f3f1}
.desc-libre-zone[hidden]{display:none}
.desc-libre-zone::placeholder{color:rgba(244,243,241,.34)}
.desc-libre-zone:focus{outline:0;background:rgba(255,255,255,.09);border-color:#a01a26}
.profile-sheet.sheet-clair .desc-libre-zone{background:#f7f6f4;border-color:var(--line2);color:#161616}
.profile-sheet.sheet-clair .desc-libre-zone:focus{background:#fff;border-color:#a01a26}
/* La bulle rouge n'a plus cours sur ce titre. */
.desc-libre .tag-rouge{display:none}

/* ═══ LES CONTRÔLES D'IMAGE AU SURVOL SEULEMENT ══════════════════════════════
   Le client ne veut pas voir « Changer » / « Retirer » en permanence sur
   l'affiche : ils n'apparaissent qu'au survol de la photo concernée. Au clavier
   (`:focus-within`) ils restent atteignables — on ne sacrifie pas l'accessibilité. */
.ip-actions,
.cover-actions,
.ip-cadrage,
.cover-cadrage-wrap{opacity:0;transition:opacity .18s ease}
/* La photo de profil : les contrôles sont FRÈRES du bouton, dans `.image-field-row`.
   On révèle donc depuis la rangée au survol — et `:focus-within` les garde
   atteignables au clavier. */
.image-field-row:hover .ip-actions,
.image-field-row:focus-within .ip-actions,
.image-field-row:hover .ip-cadrage,
.image-field-row:focus-within .ip-cadrage,
.image-field:hover .ip-actions,
.image-field:focus-within .ip-actions,
.image-field:hover .ip-cadrage,
.image-field:focus-within .ip-cadrage{opacity:1}
/* La couverture : même structure que la photo de profil — les contrôles sont
   FRÈRES du bouton, dans `.cover-field`. On révèle depuis le conteneur. */
.cover-field:hover .cover-actions,
.cover-field:focus-within .cover-actions,
.cover-field:hover .cover-cadrage-wrap,
.cover-field:focus-within .cover-cadrage-wrap{opacity:1}

/* ═══ LE TITRE ÉDITABLE DE L'EN-TÊTE ═════════════════════════════════════════ */
.titre-editable{cursor:text}
.titre-champ{width:100%;max-width:640px;padding:10px 14px;border-radius:11px;
  text-align:center;font:800 27px Manrope,sans-serif;letter-spacing:-.02em;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.24);color:#f4f3f1}
.titre-champ[hidden]{display:none}
.titre-champ:focus{outline:0;background:rgba(255,255,255,.18);border-color:#a01a26}
.profile-sheet.sheet-clair .titre-champ{background:#f7f6f4;border-color:var(--line2);color:#161616}
.profile-sheet.sheet-clair .titre-champ:focus{background:#fff;border-color:#a01a26}

/* ═══ PLUSIEURS BOUTONS SUR UNE MÊME SECTION ═════════════════════════════════
   La section Événements peut en porter 3 (ajouter, trier, basculer) : la gouttière
   s'élargit pour qu'ils tiennent, toujours symétriquement. */
.band:has(.band-actions):has(.evt-tri-btn){--gouttiere:124px}

/* ═══ LE TITRE RESTE CENTRÉ ET LE TRAIT NE PASSE PLUS SOUS LES BOUTONS ═══════
   Constat mesuré : le trait droit allait jusqu'au bord de la bande, donc SOUS les
   boutons (jusqu'à 102 px sur la section Événements) — le chevauchement se voyait.

   Deux erreurs d'approche écartées :
     - `padding-right` sur la bande : rétrécit la boîte de contenu d'un seul côté, le
       titre glisse alors vers la gauche de la moitié de la marge (36 px mesurés) ;
     - masquer le trait par un fond : ça cache le symptôme, le trait reste dessous.
   Solution retenue : une GRILLE à colonnes égales (`1fr auto 1fr`) centre le titre
   par construction, et une gouttière SYMÉTRIQUE (`--gw`) fait arrêter chaque trait
   avant les boutons — la marge de gauche équilibre celle de droite. */
.band{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;--gw:0px}
.band:has(.band-actions){--gw:46px}
.band:has(.band-actions):has(.evt-tri-btn){--gw:118px}
/* SEUL le trait DROIT s'adapte : il s'arrête avant les boutons.
   Le trait GAUCHE garde toute sa longueur — le titre reste centré, ce que la grille
   à colonnes égales (`1fr auto 1fr`) assure déjà par construction. */
.band-line-b{margin-right:var(--gw)}
/* Repli si `:has()` n'est pas supporté : on ne réserve rien (le fond opaque des
   boutons, ci-dessous, couvre alors le trait). */
@supports not (selector(:has(*))){
  .band-line-a,.band-line-b{margin:0}
}

/* ═══ LA DESCRIPTION : ALIGNEMENT À GAUCHE, AFFIRMÉ ══════════════════════════
   Lecture comme écriture se lisent du même bord. Déclaré en fin de fichier pour ne
   dépendre d'aucun ordre : le texte lu ET la zone de saisie sont à gauche. */
.desc-libre .desc-titre,
.desc-libre .desc-texte,
.desc-libre .desc-libre-zone{text-align:left;margin-left:0;margin-right:0;max-width:100%}
.desc-libre .desc-titre{text-transform:uppercase}

/* ═══ LES BOUTONS D'ACTION DESCENDENT : PLUS DE CHEVAUCHEMENT AVEC LE « + » ═══
   Le « + » et les boutons étaient sur la MÊME ligne que le trait : ils se
   chevauchaient et les informations de la section commençaient trop haut. Les
   boutons sortent du flux verticalement (`top:100%`) : ils passent SOUS la ligne du
   titre, et la marge basse de la bande pousse les informations plus bas. */
.band:has(.band-actions){padding-bottom:0}
.band-actions{top:50%;bottom:auto;transform:translateY(-50%)}
/* Sans `:has()`, on retombe sur l'ancien comportement : les boutons restent centrés
   verticalement, ce qui reste lisible. */
@supports not (selector(:has(*))){
  .band-actions{top:50%;bottom:auto;transform:translateY(-50%)}
}

/* ═══ LA PÉRIODE DE L'EN-TÊTE DEVIENT ÉDITABLE (double-clic) ═════════════════ */
.profile-dates{cursor:text}
.profile-dates:hover{color:#fff}
.profile-sheet.sheet-clair .profile-dates:hover{color:#111}
.dates-champ{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
.dates-champ[hidden]{display:none}
.dates-champ input[type="date"]{padding:6px 10px;border-radius:9px;font:600 13px Manrope,sans-serif;
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.2);color:#f4f3f1}
.dates-champ input[type="date"]:focus{outline:0;background:rgba(255,255,255,.11);border-color:#a01a26}
.dates-sep{color:var(--muted);font-weight:700}
.profile-sheet.sheet-clair .dates-champ input[type="date"]{background:#f7f6f4;border-color:var(--line2);color:#161616}
.profile-sheet.sheet-clair .dates-champ input[type="date"]:focus{background:#fff;border-color:#a01a26}
/* La flèche de période, dans le libellé comme dans l'éditeur. */
.dates-fleche{color:var(--muted);font-weight:700}

/* ═══ LA CARTE DE CAMPAGNE GARDE UNE TAILLE CONSTANTE ════════════════════════
   Demande du client : « si j'écris 2000 mots, sur la petite carte je veux 2 ou 3
   lignes, et que les cartons gardent tous la même taille s'il y a plusieurs
   campagnes ». La carte a donc une hauteur MINIMALE (identique pour toutes) et
   l'aperçu de description est borné à 2 lignes — quelle que soit la longueur du
   texte stocké. */
.entity-tile{min-height:262px}
.tile-note-body{margin:6px 0 0;color:rgba(244,243,241,.88);font:500 12px/1.45 Manrope,sans-serif;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;height:calc(12px * 1.45 * 2);
  /* On coupe sur un mot entier quand c'est possible, sinon on rogne. */
  overflow-wrap:anywhere}

/* ═══ L'ÉVÉNEMENT : LES DEUX HEURES SUR UNE MÊME LIGNE ═══════════════════════
   Les 4 champs (lieu, date, début, fin) étaient dans une grille à 3 colonnes :
   l'heure de FIN tombait seule sur une 2e ligne. Les heures ont leur propre rangée,
   toujours côte à côte, séparées par une flèche — les informations s'alignent. */
.heures-ligne{display:grid;grid-template-columns:1fr auto 1fr;align-items:end;gap:12px;margin-top:12px}
.heures-ligne .field{margin-bottom:0}
.heures-sep{color:var(--muted);font-weight:700;padding-bottom:13px}
/* Sur un écran étroit, on empile plutôt que d'écraser. */
@media (max-width:560px){
  .heures-ligne{grid-template-columns:1fr;gap:8px}
  .heures-sep{display:none}
}

/* ═══ LE RESPONSABLE D'ÉQUIPE (rôle `team_lead`) ═════════════════════════════
   Intermédiaire entre l'agence et les collaboratrices : il retire du stock du dépôt,
   le distribue à son équipe et supervise sur le lieu. */
.badge-team_lead,.badge-team-lead{color:#7cc4ff;border-color:rgba(124,196,255,.4)}
.role-pill.team_lead{background:rgba(124,196,255,.16);color:#9fd4ff;border-color:rgba(124,196,255,.4)}

/* ═══ LES RESPONSABLES D'ÉQUIPE (rôle team_lead) ═════════════════════════════
   Affichés en tête de l'onglet Collaboratrices : ce sont eux qui retirent le stock
   du dépôt, le distribuent à leur équipe et supervisent sur le lieu. Sans cette
   liste, le rôle existerait en base sans jamais apparaître dans l'interface.
   Même vocabulaire visuel que les autres cartes du projet (cartes inversées en
   blanc sur fond sombre, comme les événements et les goodies). */
.leads-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:11px;margin:2px 0 10px}
.lead-card{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;
  padding:13px 15px;border-radius:14px;cursor:pointer;
  background:#fff;border:1px solid rgba(255,255,255,.24);
  transition:transform .16s,box-shadow .16s}
.lead-card:hover{transform:translateY(-2px);box-shadow:0 12px 26px -10px rgba(0,0,0,.5)}
.lead-av{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;overflow:hidden;
  background:#ecebe8;color:#5a5751;font:700 13px Manrope,sans-serif}
.lead-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.lead-id{display:grid;gap:2px;min-width:0}
.lead-id strong{font:700 14px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lead-id small{font:500 11px 'DM Mono',monospace;color:#7b7873}
.lead-equipe{flex:none;padding:5px 10px;border-radius:99px;white-space:nowrap;
  background:rgba(124,196,255,.14);color:#2f6f9e;border:1px solid rgba(124,196,255,.4);
  font:600 10.5px Manrope,sans-serif}
.lead-equipe b{font-weight:800}

/* ═══ L'ONGLET « RESPONSABLES D'ÉQUIPE » ═════════════════════════════════════
   Le responsable est l'intermédiaire entre l'agence et les collaboratrices : il retire
   le stock du dépôt, le distribue à son équipe et supervise sur le lieu. Sa carte doit
   donc montrer SON ÉQUIPE — c'est le lien qui manquait, et ce que le client veut juger.
   Cartes inversées en blanc, comme les événements et les goodies de la fiche campagne. */
.leads-counts{margin-bottom:18px}
.leads-fiches{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
.lead-fiche{display:grid;gap:14px;padding:18px;border-radius:16px;cursor:pointer;
  background:#fff;border:1px solid rgba(255,255,255,.24);
  transition:transform .16s,box-shadow .16s}
.lead-fiche:hover{transform:translateY(-2px);box-shadow:0 14px 30px -12px rgba(0,0,0,.55)}
.lead-fiche-top{display:flex;align-items:center;gap:12px}
.lead-fiche-av{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:50%;
  overflow:hidden;background:#ecebe8;color:#5a5751;font:700 15px Manrope,sans-serif}
.lead-fiche-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.lead-fiche-id{display:grid;gap:2px;min-width:0;flex:1}
.lead-fiche-id strong{font:800 16px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lead-fiche-id small{font:500 11px 'DM Mono',monospace;color:#7b7873;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lead-fiche .badge-team{flex:none;background:rgba(124,196,255,.16);color:#2f6f9e;
  border:1px solid rgba(124,196,255,.42);font-weight:700}
/* Le bloc ÉQUIPE : on voit QUI est rattaché, pas seulement un nombre. */
.lead-fiche-bloc{display:grid;gap:8px;padding-top:12px;border-top:1px solid rgba(23,23,25,.1)}
.lead-fiche-lab{color:#7b7873;font:700 10px 'DM Mono',monospace;letter-spacing:.1em;text-transform:uppercase}
.lead-membres{display:flex;flex-wrap:wrap;gap:6px}
.lead-membre{padding:5px 11px;border-radius:99px;background:rgba(23,23,25,.06);
  color:#3d3b38;border:1px solid rgba(23,23,25,.12);font:600 12px Manrope,sans-serif}
.lead-vide{color:#72706c;font:500 12px Manrope,sans-serif;font-style:italic}
.lead-fiche-pied{display:flex;align-items:center;justify-content:space-between;gap:10px}
.lead-kpi{color:#5a5751;font:500 12px Manrope,sans-serif}
.lead-kpi strong{color:#161616;font-weight:800;font-size:14px}
.lead-fiche .text-button{color:#c0392b}
.lead-fiche .text-button:hover{color:#a8232f}

/* Sur écran étroit, une carte par ligne plutôt que des cartes écrasées. */
@media (max-width:620px){ .leads-fiches{grid-template-columns:1fr} }

/* ═══ POP-UP « GÉRER L'ÉQUIPE » ══════════════════════════════════════════════
   Liste toutes les collaboratrices avec leur état vis-à-vis du responsable ouvert :
   membres en tête, les autres ensuite. Un clic bascule le rattachement, sans fermer. */
.equipe-dialog{width:min(560px,94vw);max-height:88vh;padding:0}
.eq-head{padding:22px 24px 14px;border-bottom:1px solid rgba(23,23,25,.09)}
.eq-head h3{margin:2px 0 6px;font:800 19px Manrope,sans-serif;color:#161616}
.eq-sous{margin:0;color:#6f6c67;font:500 12.5px Manrope,sans-serif;line-height:1.5}
.eq-liste{display:grid;gap:2px;padding:10px 14px;overflow:auto;max-height:56vh}
.eq-item{display:flex;align-items:center;gap:11px;padding:9px 10px;border-radius:11px;
  border:1px solid transparent}
.eq-item:hover{background:rgba(23,23,25,.045)}
/* Un membre de CETTE équipe : fond teinté + trait de gauche, on le repère au premier regard. */
.eq-item-on{background:rgba(124,196,255,.09);border-color:rgba(124,196,255,.28)}
.eq-av{display:grid;place-items:center;width:38px;height:38px;flex:none;border-radius:50%;
  overflow:hidden;background:#ecebe8;color:#5a5751;font:700 12px Manrope,sans-serif}
.eq-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.eq-nom{display:grid;gap:1px;min-width:0;flex:1}
.eq-nom strong{font:700 13.5px Manrope,sans-serif;color:#1a1a1a;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.eq-nom small{font:500 10.5px 'DM Mono',monospace;color:#8b8883}
.eq-ailleurs{flex:none;padding:3px 9px;border-radius:99px;background:rgba(23,23,25,.055);
  color:#6f6c67;font:600 10.5px Manrope,sans-serif;white-space:nowrap}
.eq-btn{flex:none;padding:7px 15px;border-radius:99px;cursor:pointer;
  font:700 12px Manrope,sans-serif;border:1px solid rgba(23,23,25,.16);
  background:#fff;color:#3d3b38;transition:background .14s,color .14s}
.eq-btn:hover{background:#17171a;color:#fff;border-color:#17171a}
/* « Retirer » : on reste sobre (le rouge est réservé aux titres). Un gris marqué suffit. */
.eq-btn-off{background:rgba(23,23,25,.07);color:#5a5751;border-color:transparent}
.eq-btn-off:hover{background:rgba(23,23,25,.14);color:#1a1a1a}
.eq-vide{padding:22px;text-align:center;color:#72706c;font:500 13px Manrope,sans-serif}
.eq-pied{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 24px 18px;border-top:1px solid rgba(23,23,25,.09)}
.eq-compte{color:#6f6c67;font:500 12px Manrope,sans-serif}
.eq-compte strong{color:#161616;font-weight:800;font-size:14px}

/* ═══ LE FLUX DU STOCK — le logigramme vivant ════════════════════════════════
   Le client a écrit le logigramme à la main : dépôt → distributeurs → collaboratrices,
   plus la dotation directe. On l'affiche AVEC LES TOTAUX RÉELS, pour qu'on voie où est
   le stock et pas seulement comment il circule. Les boîtes suivent la même grammaire
   que le reste de l'app (cartes inversées en blanc, hairlines, pas d'ombre portée). */
.flux-schema{display:grid;gap:0;justify-items:center;padding:24px 20px;margin-bottom:26px;
  border-radius:16px;background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.18)}
.flux-niv{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
.flux-boite{display:grid;gap:3px;justify-items:center;min-width:190px;padding:15px 24px;
  border-radius:14px;background:#fff;border:1px solid rgba(255,255,255,.24)}
.flux-tag{color:#7b7873;font:700 10px 'DM Mono',monospace;letter-spacing:.09em;text-transform:uppercase}
.flux-boite strong{font:800 27px Manrope,sans-serif;color:#161616;line-height:1.1}
.flux-boite small{color:#7b7873;font:500 11.5px Manrope,sans-serif}
/* Le dépôt et les deux paliers ont chacun leur accent : on les distingue au premier
   regard dans le schéma, sans sortir de la palette du studio. */
.flux-depot{border-top:3px solid #d4a574}
.flux-dist{border-top:3px solid #7cc4ff}
.flux-hote{border-top:3px solid #c0392b}
.flux-fleches{display:flex;gap:26px;align-items:center;padding:8px 0}
.flux-fleche{color:#6f6c67;font:600 11px 'DM Mono',monospace;letter-spacing:.05em}
.flux-directe{color:#a8232f}
.flux-note{margin:12px 0 0;max-width:620px;text-align:center;color:#6f6c67;
  font:500 12px Manrope,sans-serif;line-height:1.55}
.flux-note strong{color:#3d3b38;font-weight:700}

/* Les titres de rubrique, dans le même esprit que la fiche campagne (rectangle + trait). */
.flux-titre{display:flex;align-items:center;gap:16px;margin:26px 0 14px}
.flux-titre .band-line{flex:1;height:1px;background:rgba(255,255,255,.14)}

/* ── Les cartes de distributeur : le couple « fourni / restant » ───────────── */
.flux-cartes{display:grid;gap:14px}
.flux-carte{padding:18px 20px;border-radius:16px;background:#fff;border:1px solid rgba(255,255,255,.24)}
.flux-carte-top{display:flex;align-items:center;gap:12px;padding-bottom:13px;border-bottom:1px solid rgba(23,23,25,.09)}
.flux-av{display:grid;place-items:center;width:46px;height:46px;flex:none;border-radius:50%;
  overflow:hidden;background:#ecebe8;color:#5a5751;font:700 14px Manrope,sans-serif}
.flux-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.flux-carte-nom{display:grid;gap:2px;flex:1;min-width:0}
.flux-carte-nom strong{font:800 16px Manrope,sans-serif;color:#161616}
.flux-carte-nom small{color:#7b7873;font:500 11.5px Manrope,sans-serif}
.flux-ligne{display:grid;grid-template-columns:1fr auto auto auto;gap:14px;align-items:center;
  padding:11px 0;border-bottom:1px solid rgba(23,23,25,.07)}
.flux-ligne:last-child{border-bottom:0}
.flux-goodie{display:flex;align-items:center;gap:9px;min-width:0}
.flux-goodie img,.flux-goodie span{display:grid;place-items:center;width:32px;height:32px;flex:none;
  border-radius:8px;overflow:hidden;background:#ecebe8;color:#5a5751;font:700 10px Manrope,sans-serif}
.flux-goodie img{object-fit:cover}
.flux-goodie b{font:600 13px Manrope,sans-serif;color:#1a1a1a;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
/* Les deux chiffres demandés par le client : ce qui a été FOURNI, ce qui RESTE. */
.flux-chiffres{display:flex;align-items:baseline;gap:5px;color:#6f6c67;
  font:500 11.5px Manrope,sans-serif;white-space:nowrap}
.flux-chiffres b{font:800 15px Manrope,sans-serif;color:#161616}
.flux-chiffres b.flux-reste{color:#2f6f9e}
.flux-chiffres b.flux-vide{color:#a8232f}
.flux-chiffres i{color:#b8b5b0;font-style:normal}
.flux-barre{width:110px;height:6px;border-radius:99px;background:rgba(23,23,25,.1);overflow:hidden}
.flux-barre span{display:block;height:100%;border-radius:99px;background:#7cc4ff}
.flux-vide-txt{padding:14px 0;color:#72706c;font:500 12.5px Manrope,sans-serif;font-style:italic}

/* ── Le stock chez les collaboratrices, en grille compacte ─────────────────── */
.flux-hotes{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
.flux-hote{padding:14px 16px;border-radius:13px;background:#fff;border:1px solid rgba(255,255,255,.24)}
.flux-hote-top{display:flex;align-items:center;justify-content:space-between;gap:10px;
  margin-bottom:9px;padding-bottom:9px;border-bottom:1px solid rgba(23,23,25,.08)}
.flux-hote-top strong{font:700 13.5px Manrope,sans-serif;color:#161616}
.flux-lead{padding:3px 9px;border-radius:99px;background:rgba(124,196,255,.14);color:#2f6f9e;
  font:600 10.5px Manrope,sans-serif;white-space:nowrap}
.flux-sans{background:rgba(23,23,25,.06);color:#7b7873}
.flux-ligne-mini{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:5px 0;
  color:#6f6c67;font:500 11.5px Manrope,sans-serif}
.flux-ligne-mini b{font-weight:800;color:#161616;font-size:13px}
.flux-ligne-mini small{display:block;color:#72706c;font-size:10.5px}

/* ── Le journal : qui a donné quoi à qui ──────────────────────────────────── */
.flux-journal{display:grid;gap:0;padding:8px 18px}
.flux-mvt{display:flex;align-items:center;gap:13px;padding:10px 0;border-bottom:1px solid rgba(23,23,25,.07)}
.flux-mvt:last-child{border-bottom:0}
.flux-mvt-qte{display:grid;place-items:center;min-width:34px;height:30px;padding:0 9px;border-radius:9px;
  background:rgba(23,23,25,.06);color:#161616;font:800 13px Manrope,sans-serif}
.flux-mvt-txt{display:grid;gap:1px;flex:1;min-width:0}
.flux-mvt-txt b{font-weight:700;color:#161616}
.flux-mvt-txt{font:500 12.5px Manrope,sans-serif;color:#3d3b38}
.flux-mvt-txt small{color:#72706c;font-size:11px}
.flux-mvt-type{padding:3px 10px;border-radius:99px;background:rgba(23,23,25,.055);color:#6f6c67;
  font:600 10.5px 'DM Mono',monospace}

/* ── Le pop-up de dotation ────────────────────────────────────────────────── */
.dotation-dialog{width:min(540px,94vw);max-height:90vh}
.dot-head{padding:22px 24px 14px;border-bottom:1px solid rgba(23,23,25,.09)}
.dot-head h3{margin:2px 0 6px;font:800 19px Manrope,sans-serif;color:#161616}
.dot-corps{display:grid;grid-template-columns:1fr 1fr;gap:13px;padding:16px 24px}
.dot-champ{display:grid;gap:6px}
.dot-champ span{color:#7b7873;font:700 10px 'DM Mono',monospace;letter-spacing:.09em;text-transform:uppercase}
.dot-champ select,.dot-champ input{padding:10px 12px;border-radius:10px;border:1px solid rgba(23,23,25,.16);
  background:#fff;color:#1a1a1a;font:500 13px Manrope,sans-serif}
.dot-champ select:focus,.dot-champ input:focus{outline:2px solid rgba(124,196,255,.5);outline-offset:1px}
.dot-aide{margin:0 24px;padding:11px 14px;border-radius:10px;background:rgba(23,23,25,.045);
  color:#5a5751;font:500 12.5px Manrope,sans-serif;line-height:1.45}
/* Le refus est énoncé en clair : on explique POURQUOI le geste est impossible, plutôt
   que d'attendre que le serveur le rejette après coup. */
.dot-blocage{background:rgba(192,57,43,.09);color:#a8232f}

@media (max-width:700px){
  .flux-ligne{grid-template-columns:1fr;gap:8px}
  .flux-barre{width:100%}
  .dot-corps{grid-template-columns:1fr}
}

/* ═══ LES JEUX — roue, chances, marque et tablettes ══════════════════════════
   Le client veut régler ici la roue de la marque : ses goodies, leurs chances, ses
   couleurs, son logo — et voir à quelles tablettes elle est servie.
   Sans ce bloc, la roue se réduisait à une bande de 19 px (le conic-gradient n'avait
   aucune dimension) : on la dessine donc explicitement, en cercle. */
.jeu-bloc{margin-bottom:20px}
.jeu-bloc .section-head h2{margin:0}
/* Les petits textes d'aide sous un champ : ils expliquent d'où vient la valeur. */
.jeu-aide{display:block;margin-top:6px;color:#72706c;font:500 11px Manrope,sans-serif;line-height:1.45}
.jeu-form .field input[type=color]{height:44px;padding:4px;cursor:pointer}

/* ── L'aperçu : la roue et sa légende côte à côte ─────────────────────────── */
.jeu-roue-zone{display:grid;grid-template-columns:300px 1fr;gap:34px;align-items:center}
.jeu-roue{position:relative;width:270px;height:270px;border-radius:50%;flex:none;
  border:11px solid #191a1b;box-shadow:0 18px 40px -18px rgba(0,0,0,.5);
  display:grid;place-items:center}
.jeu-roue-hub{display:grid;place-items:center;width:76px;height:76px;border-radius:50%;
  background:#0f1011;border:1px solid var(--gold);color:var(--gold);
  font:700 20px 'DM Mono',monospace;letter-spacing:.06em}
/* L'aiguille, en haut de la roue : elle indique la case gagnante. */
.jeu-roue-aiguille{position:absolute;top:-26px;left:50%;transform:translateX(-50%);
  width:0;height:0;border-left:11px solid transparent;border-right:11px solid transparent;
  border-top:19px solid var(--gold);filter:drop-shadow(0 2px 3px rgba(0,0,0,.4))}
/* ══ LES QUATRE RÈGLES MORTES DE L'ANCIENNE ROUE ══════════════════════════════
   MESURÉ : `.jeu-legende-ligne`, `.jeu-legende-nom`, `.jeu-legende-poids` et la
   `.jeu-pastille` de 13px n'apparaissent QUE dans ce fichier — aucun HTML ne les
   utilise. Elles venaient de l'ancienne roue de démonstration.
   La pastille de 13px ÉCRASAIT les boutons d'option de l'atelier, qui portent
   le même nom : boutons de 28x22 px au lieu de ~73, et un texte qui débordait de 37px
   sur la section suivante. C'est ce que le client voyait : « Position » écrit
   par-dessus les formes.
   Retirées : plus de collision de nom, et le bouton reprend sa vraie place. */

/* ── Le réglage des chances, goodie par goodie ────────────────────────────── */
.jeu-poids{display:grid;gap:0}
.jeu-poids-ligne{display:grid;grid-template-columns:1fr 220px 44px;gap:16px;align-items:center;
  padding:13px 0;border-bottom:1px solid rgba(23,23,25,.08)}
.jeu-poids-ligne:last-child{border-bottom:0}
.jeu-poids-nom{display:grid;gap:2px;font:600 13px Manrope,sans-serif;color:#1a1a1a}
.jeu-poids-nom small{color:#72706c;font:500 11px Manrope,sans-serif}
.jeu-poids input[type=range]{width:100%;accent-color:var(--red);cursor:pointer}
.jeu-poids output{display:grid;place-items:center;min-width:44px;height:32px;border-radius:9px;
  background:rgba(23,23,25,.06);color:#161616;font:800 14px Manrope,sans-serif}
.jeu-actions{display:flex;align-items:center;gap:14px;margin-top:18px}
.jeu-msg{color:#2b8a61;font:600 12px Manrope,sans-serif}

/* ── Les tablettes : où la roue est réellement servie ─────────────────────── */
.jeu-tablettes{display:grid;gap:9px}
.jeu-tablette{display:flex;align-items:center;gap:12px;padding:11px 13px;border-radius:11px;
  background:rgba(23,23,25,.035);border:1px solid rgba(23,23,25,.08)}
.jeu-tablette-av{display:grid;place-items:center;width:38px;height:38px;flex:none;border-radius:50%;
  overflow:hidden;background:#ecebe8;color:#5a5751;font:700 12px Manrope,sans-serif}
.jeu-tablette-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.jeu-tablette-txt{display:grid;gap:1px;flex:1;min-width:0}
.jeu-tablette-txt strong{font:700 13.5px Manrope,sans-serif;color:#161616}
.jeu-tablette-txt small{color:#72706c;font:500 11px Manrope,sans-serif}

@media (max-width:820px){
  .jeu-roue-zone{grid-template-columns:1fr;gap:22px;justify-items:center}
  .jeu-poids-ligne{grid-template-columns:1fr 44px;gap:10px}
  .jeu-poids-ligne input[type=range]{grid-column:1/-1}
}

/* ═══ LES TABLETTES — le parc matériel ═════════════════════════════════════════
   Le client veut compter son parc et savoir « la tablette chez qui elle est ». Les
   compteurs portent les chiffres, les cartes portent QUI — avec le visage du porteur,
   comme partout ailleurs dans l'app. */
.tab-counts{margin-bottom:20px}
.tab-serv{color:#2b8a61}
.tab-depot{color:#b77922}
.tab-panne{color:#c0392b}
.tab-grille{display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));gap:14px}
.tab-carte{display:grid;gap:14px;padding:18px;border-radius:16px;background:#fff;
  border:1px solid rgba(255,255,255,.24);transition:transform .16s,box-shadow .16s}
.tab-carte:hover{transform:translateY(-2px);box-shadow:0 14px 30px -12px rgba(0,0,0,.55)}
.tab-carte-top{display:flex;align-items:center;gap:12px}
/* L'icone porte le NUMERO de la tablette : c'est le repere visuel du parc. */
.tab-icone{display:grid;place-items:center;width:46px;height:46px;flex:none;border-radius:12px;
  background:#202123;color:#fff;font:800 17px Manrope,sans-serif}
.tab-nom{display:grid;gap:2px;flex:1;min-width:0}
.tab-nom strong{font:800 15.5px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tab-nom small{color:#72706c;font:500 11px 'DM Mono',monospace;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Le bloc porteur : c'est la reponse a « chez qui elle est ». */
.tab-porteur{display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:11px;
  background:rgba(23,23,25,.04);border:1px solid rgba(23,23,25,.07)}
.tab-av{display:grid;place-items:center;width:38px;height:38px;flex:none;border-radius:50%;
  overflow:hidden;background:#ecebe8;color:#5a5751;font:700 12px Manrope,sans-serif}
.tab-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.tab-av-vide{background:rgba(23,23,25,.06);color:#b8b5b0;font-size:15px}
.tab-porteur-txt{display:grid;gap:1px;flex:1;min-width:0}
.tab-porteur-txt small{color:#72706c;font:500 10px 'DM Mono',monospace;letter-spacing:.06em;text-transform:uppercase}
.tab-porteur-txt strong{font:700 13.5px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tab-type{flex:none;padding:3px 9px;border-radius:99px;background:rgba(124,196,255,.16);
  color:#2f6f9e;font:600 10.5px Manrope,sans-serif;white-space:nowrap}
.tab-actions{display:flex;align-items:center;justify-content:space-between;gap:10px}
.tab-carte .text-button{color:#c0392b}
.tab-carte .text-button:hover{color:#a8232f}
.tab-dialog{width:min(520px,94vw)}

@media (max-width:640px){ .tab-grille{grid-template-columns:1fr} }

/* ═══ LES QUATRE ROUES — matière, vignettes, animation ══════════════════════════
   Le client veut « des roulettes fonctionnelles et différentes ». Les quatre styles se
   distinguent par leur MATIÈRE (fond uni, néon, couronne claire, rayures), pas par une
   étiquette — on doit pouvoir les reconnaître au premier regard, côte à côte. */

/* ── L'orientation des photos : elles suivent le disque ─────────────────────
   Une étiquette est collée sur la roue : droite en haut, retournée en bas. C'est ce qui
   fait que le lot gagnant arrive DROIT sous l'aiguille (correction du client).
   On force donc l'absence de contre-rotation, y compris si une règle traîne encore. */
.wheel-prize{transform-origin:center center;backface-visibility:visible}
.wheel-prize img,.wheel-prize span{transform:none}

/* ── La roue et son rotor ─────────────────────────────────────────────────── */
.wheel-frame{position:relative;display:grid;place-items:center}
.wheel-large{width:300px;height:300px}
.wheel-small{width:200px;height:200px}
.wheel-rotor{position:relative;width:100%;height:100%;border-radius:50%;
  background-color:#202123;background-size:cover;background-position:center;
  will-change:transform;transform:rotate(0deg)}
/* Le cerclage : il donne l'épaisseur du disque et retient les étiquettes. */
.wheel-large .wheel-rotor{border:12px solid #191a1b;box-shadow:0 20px 46px -20px rgba(0,0,0,.6)}
.wheel-small .wheel-rotor{border:8px solid #191a1b;box-shadow:0 12px 26px -14px rgba(0,0,0,.55)}
.wheel-hub{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:grid;place-items:center;border-radius:50%;background:#0f1011;
  border:1px solid var(--gold);color:var(--gold);font:700 18px 'DM Mono',monospace;
  letter-spacing:.06em;z-index:3}
.wheel-large .wheel-hub{width:80px;height:80px}
.wheel-small .wheel-hub{width:56px;height:56px;font-size:13px}
/* L'aiguille : un triangle posé en haut, qui désigne la case gagnante. */
.wheel-pointer{position:absolute;top:-9px;left:50%;transform:translateX(-50%);z-index:4;
  width:0;height:0;border-left:13px solid transparent;border-right:13px solid transparent;
  border-top:22px solid var(--gold);filter:drop-shadow(0 3px 4px rgba(0,0,0,.45))}
.wheel-small .wheel-pointer{border-left-width:9px;border-right-width:9px;border-top-width:16px;top:-6px}

/* Les étiquettes : chaque photo est posée à son angle sur le disque. */
.wheel-prize{position:absolute;left:50%;top:50%;display:grid;justify-items:center;gap:3px;
  margin:-26px 0 0 -26px;width:52px;pointer-events:none}
.wheel-small .wheel-prize{margin:-20px 0 0 -20px;width:40px}
.wheel-prize img{width:44px;height:44px;object-fit:contain;
  filter:drop-shadow(0 3px 5px rgba(0,0,0,.45))}
.wheel-small .wheel-prize img{width:32px;height:32px}
.wheel-prize>span{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  background:rgba(255,255,255,.9);color:#202123;font:800 13px Manrope,sans-serif}
.wheel-small .wheel-prize>span{width:32px;height:32px;font-size:11px}
.wheel-prize small{max-width:74px;text-align:center;color:#fff;font:700 9px Manrope,sans-serif;
  line-height:1.15;letter-spacing:.01em;text-shadow:0 1px 3px rgba(0,0,0,.75)}
.wheel-small .wheel-prize small{display:none}

/* ── Les quatre MATIÈRES ──────────────────────────────────────────────────────
   Le fond vient de `roueFond()` en style inline (le dégradé est calculé). Ici on ne
   règle que ce qui ne peut pas l'être : le cerclage et le halo propres au style. */
.roue-neon .wheel-rotor{border-color:#08090a;box-shadow:0 0 32px -4px rgba(255,46,99,.5),0 20px 46px -22px rgba(0,0,0,.7)}
.roue-neon .wheel-hub{background:#08090a;border-color:#08d9d6;color:#08d9d6}
.roue-anneau .wheel-rotor{border-color:#f4f1ec;box-shadow:0 0 0 2px rgba(23,23,25,.18),0 20px 46px -22px rgba(0,0,0,.5)}
.roue-anneau .wheel-hub{background:#fff;border-color:#202123;color:#202123}
.roue-rayures .wheel-rotor{border-color:#202123}
.roue-classique .wheel-rotor{border-color:#191a1b}

/* ── Le choix des roues : quatre vignettes, chacune une VRAIE roue ─────────── */
.roues-choix{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
.roue-vignette{display:grid;justify-items:center;gap:9px;padding:18px 14px 15px;cursor:pointer;
  border-radius:14px;background:#fff;border:2px solid rgba(23,23,25,.1);
  transition:border-color .16s,transform .16s,box-shadow .16s;text-align:center}
.roue-vignette:hover{transform:translateY(-2px);box-shadow:0 14px 30px -14px rgba(0,0,0,.4)}
/* La roue choisie est nettement marquée : c'est un état, pas une nuance. */
.roue-vignette.is-active{border-color:var(--red);box-shadow:0 0 0 3px rgba(212,43,56,.13)}
.roue-vignette-visuel{display:block;pointer-events:none}
.roue-vignette-nom{font:800 13.5px Manrope,sans-serif;color:#161616}
.roue-vignette-nom b{color:var(--red);font-weight:800}
.roue-vignette-note{color:#72706c;font:500 11px Manrope,sans-serif;line-height:1.35}

/* Le résultat du tirage, sous l'aperçu. */
#jeu-roue-resultat{margin-top:14px;padding:11px 14px;border-radius:10px;
  background:rgba(23,23,25,.045);color:#5a5751;font:500 13px Manrope,sans-serif}
#jeu-roue-resultat strong{color:#161616;font-weight:800}

@media (max-width:700px){
  .roues-choix{grid-template-columns:1fr 1fr}
  .wheel-large{width:250px;height:250px}
}

/* La colonne de la roue : la roue et son résultat, empilés au même endroit. */
.jeu-roue-colonne{display:grid;justify-items:center;gap:12px}

/* ═══ LA CARTE UNIQUE — claire, compacte, homogène ══════════════════════════════
   Le client : « toutes les cartes homogènes et bien faites […] celle des compagnies
   elle est grande pour rien, on n'a aucune information, elle est très sombre, chose
   que j'aime pas. Je préfère qu'elle soit un peu plus claire, plus agréable à voir […]
   la version carte qu'elle soit aussi minimaliste et très très cool ».
   Trois décisions, appliquées à TOUTES les cartes via `carteEntiteHtml` :
     - fond clair (blanc), texte foncé, traits fins : plus aucun bloc sombre ;
     - hauteur tenue : les faits remplacent le vide, on ne laisse plus de place perdue ;
     - une seule grille : les cartes ont la même largeur partout. */
.carte-grille{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,360px));justify-content:start;gap:13px}
/* ═══ LES CARTES CARRÉES, SAUF COMPAGNIE ════════════════════════════════════════
   Le client : « toutes les cartes à part la carte compagnie… sous forme de carré pas
   rectangulaire trop long ». La grille pose donc des cellules CARRÉES (aspect-ratio),
   et la carte remplit sa cellule. Compagnie garde la grille large : un carré y serait
   trop étroit pour un nom de société et ses villes. */
.carte-grille.is-carre{grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.carte-grille.is-carre .carte{aspect-ratio:1/1;align-content:start;overflow:hidden}
/* Dans un carré, les faits passent sur DEUX colonnes : quatre faits sur une seule
   ligne seraient trop serrés, et l'ensemble ne serait plus lisible. */
.carte-grille.is-carre .carte-faits{grid-template-columns:repeat(2,1fr);gap:6px 4px}
.carte-grille.is-carre .carte-band-tag{max-width:100%;font-size:11.5px;padding:5px 11px}
.carte-grille.is-carre .carte-actions{margin-top:auto}

/* ═══ LA PHOTO DANS LA VUE LISTE ══════════════════════════════════════════════
   « sur la vue de liste tu rajoutes aussi la photo pour qu'on puisse voir ».
   La première colonne porte donc le visage, puis le nom et son sous-titre.
   Le client : « pas des cercles, ça doit être carré » — d'où les coins arrondis
   plutôt que le rond, comme la tuile de campagne. */
.rec-qui{display:flex;align-items:center;gap:10px;min-width:0}
.rec-av{display:grid;place-items:center;width:38px;height:38px;flex:none;border-radius:11px;
  overflow:hidden;background:#f1efec;color:#5a5751;font:800 12px Manrope,sans-serif}
.rec-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.rec-av-vide{font-size:10.5px;letter-spacing:.02em}
.rec-qui-txt{display:grid;gap:1px;min-width:0}
.rec-qui-txt strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rec-qui-txt small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ═══ LA CARTE EN FORME DE BADGE ════════════════════════════════════════════════
   Client : « la petite carte de la photo soit au milieu au-dessus de la bulle en
   rouge du titre… ça fera beaucoup plus un badge… et juste en bas on aura les
   informations ». Donc, de haut en bas : PHOTO centrée → BULLE ROUGE → ce que c'est
   → les informations.
   Et : « pas des cercles, ça doit être carré… comme la carte de la campagne, ça doit
   être rectangulaire, pas rond » puis « je dois les voir tout comme des cartes. Pas
   arrondies… tout comme des cartes avec les informations les plus essentielles ».
   D'où : angles PRESQUE DROITS (4 px, pas 14), photo carrée à coins légèrement
   adoucis, et seulement les informations essentielles. */
.carte-portrait{display:grid;justify-items:center;gap:7px;padding-top:1px}
/* Le portrait : un carré, angles à peine adoucis — pas un rond, pas un galet. */
.carte-portrait .carte-av{width:84px;height:84px;border-radius:6px;font-size:24px;
  border:1px solid rgba(23,23,25,.12)}
.carte-portrait .carte-av img{object-position:center 20%}
/* L'entreprise : son logo peut être RECTANGULAIRE, on lui donne une zone large et
   plate plutôt qu'un carré, sinon un vrai logo serait rogné. */
.carte-logo .carte-portrait .carte-av{width:100%;max-width:200px;height:74px;border-radius:5px;
  background:transparent;border:0}
.carte-logo .carte-portrait .carte-av img{object-fit:contain;object-position:center}
.carte-logo .carte-portrait .carte-av span{font-size:21px}
/* La carte : un vrai rectangle de carte, angles nets, hauteur naturelle. */
.carte-grille.is-carre{grid-template-columns:repeat(auto-fit,minmax(228px,330px))}
.carte-grille.is-carre .carte{aspect-ratio:auto;min-height:0;overflow:visible}
.carte .carte-band{grid-template-columns:1fr auto 1fr}
.carte .carte-sous{text-align:center;color:#5f5c58;font:600 11px Manrope,sans-serif;
  margin:-2px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 4px}
.carte-portrait .carte-badge{justify-self:center}
/* Les informations essentielles, en bas, sur 2 colonnes : 4 faits lisibles plutôt
   qu'une longue liste qui noie le propos. */
.carte .carte-faits{padding-top:10px;margin-top:auto;grid-template-columns:repeat(2,1fr);gap:7px 6px}
.carte-grille.is-carre .carte-faits{grid-template-columns:repeat(2,1fr);gap:7px 6px}

.carte{display:grid;gap:8px;padding:15px 15px 13px;border-radius:4px;
  background:#fff;border:1px solid rgba(23,23,25,.1);
  transition:border-color .15s,transform .15s,box-shadow .15s}
.carte[role=button]{cursor:pointer}
.carte:hover{border-color:rgba(23,23,25,.22);transform:translateY(-2px);
  box-shadow:0 12px 26px -14px rgba(0,0,0,.35)}
.carte-haut{display:flex;align-items:center;gap:11px;min-width:0}
/* ═══ L'IDENTITÉ DU CARTON CAMPAGNE : titre en ROUGE, traits AUTOUR ═════════════
   C'est le langage validé sur la fiche de campagne (rectangle rouge plein encadré
   de deux traits dégradés). Le client veut qu'il serve de base au reste : on le
   reprend donc sur la carte commune, et il se propage aux sept vues d'un coup. */
.carte-band{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin:-1px 0 0}
.carte-band-line{height:2px;border-radius:2px;background:linear-gradient(90deg,rgba(23,23,25,.05),rgba(23,23,25,.40))}
.carte-band-line:last-child{background:linear-gradient(90deg,rgba(23,23,25,.40),rgba(23,23,25,.05))}
.carte-band-tag{display:block;padding:6px 15px;border-radius:11px;background:#a01a26;color:#fff;
  font:800 12.5px Manrope,sans-serif;letter-spacing:.05em;text-align:center;max-width:230px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-shadow:0 4px 14px -6px rgba(212,43,56,.6)}
.carte-sous{flex:1;min-width:0;color:#8a8781;font:500 11px Manrope,sans-serif;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.carte-av{display:grid;place-items:center;width:42px;height:42px;flex:none;border-radius:11px;
  overflow:hidden;background:#f1efec;color:#5a5751;font:800 14px Manrope,sans-serif}
.carte-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.carte-id{display:grid;gap:2px;flex:1;min-width:0}
.carte-id strong{font:800 14.5px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.carte-id small{color:#8a8781;font:500 11px Manrope,sans-serif;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.carte-badge{flex:none;padding:4px 9px;border-radius:99px;white-space:nowrap;
  background:rgba(23,23,25,.055);color:#6f6c67;font:700 9.5px 'DM Mono',monospace;letter-spacing:.05em}
.carte-badge.connected{background:rgba(43,138,97,.12);color:#2b8a61}
.carte-badge.offline{background:rgba(192,57,43,.1);color:#a8232f}
.carte-badge.badge-team{background:rgba(124,196,255,.16);color:#2f6f9e}

/* Les FAITS : c'est eux qui remplacent le vide reproché (« aucune information »). */
.carte-faits{display:grid;grid-template-columns:repeat(auto-fit,minmax(64px,1fr));gap:2px;
  padding-top:9px;border-top:1px solid rgba(23,23,25,.08)}
.carte-fait{display:grid;gap:1px;min-width:0}
.carte-fait b{font:800 14px Manrope,sans-serif;color:#161616;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.carte-fait small{color:#948f88;font:600 9px 'DM Mono',monospace;letter-spacing:.03em;
  text-transform:uppercase;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.carte-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding-top:2px}
.carte .text-button{color:#c0392b;font-weight:600}
.carte .text-button:hover{color:#a8232f}
.carte .text-button.strong{font-weight:800}

/* ═══ LA BARRE DE TRI — la même partout, alimentée par la vue ═══════════════════ */
.tri-bar{display:flex;align-items:center;flex-wrap:wrap;gap:7px;margin-bottom:16px}
.tri-bar-sep{margin-top:4px}
.tri-label{color:#8a8781;font:700 9.5px 'DM Mono',monospace;letter-spacing:.1em;
  text-transform:uppercase;margin-right:4px}
.tri-btn{padding:7px 13px;border-radius:99px;cursor:pointer;
  border:1px solid rgba(23,23,25,.13);background:#fff;color:#5a5751;
  font:600 12px Manrope,sans-serif;transition:background .14s,color .14s,border-color .14s}
.tri-btn:hover{background:rgba(23,23,25,.045);color:#161616}
/* Le critère actif est nettement marqué : c'est un état, pas une nuance. */
.tri-btn.is-active{background:#17171a;color:#fff;border-color:#17171a;font-weight:700}

/* Les compteurs, quand une vue en pose : la même brique partout. */
.tile-count strong{font-variant-numeric:tabular-nums}

@media (max-width:640px){
  .carte-grille{grid-template-columns:1fr}
  .carte-faits{grid-template-columns:repeat(2,1fr);gap:8px}
}

/* ═══ LES TROIS ÉTATS : EN COURS (vert) · À VENIR (orange) · INACTIF (gris) ═══════
   L'état est DÉRIVÉ des dates (statutPersonne / statutEntreprise), jamais lu en base :
   une colonne figée mentirait au bout d'une semaine. Couleurs alignées sur la demande :
   vert pour ce qui travaille, orange pour ce qui vient, gris pour le reste. */
.carte-badge.badge-planned,
.badge.badge-planned{background:rgba(232,166,76,.16);color:#a86b1c;border-color:rgba(232,166,76,.4)}
.carte-badge.connected{background:rgba(43,138,97,.13);color:#2b8a61}

/* Les compteurs d'activité : la couleur du chiffre dit l'état, sans avoir à lire. */
.stat-actif{color:#2b8a61}
.stat-avenir{color:#a86b1c}
.stat-inactif{color:#72706c}

/* Le badge d'une carte qui porte un délai (« EN COURS · dans 6 jours ») est plus large :
   on empêche le retour à la ligne pour qu'il reste lisible. */
/* Le badge d'état : il ne doit JAMAIS dicter la largeur de la carte. Un badge long
   (« EN COURS · dans 5 jours ») étirait la carte entière et laissait un vide immense —
   c'est ce qui donnait des cartes de 180 px de contenu dans une colonne de 254 px. */
.carte-badge{white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
/* ═══ TOUTES LES CARTES À LA MÊME HAUTEUR ═══════════════════════════════════════
   Client : « N'oublie pas que les tailles des fiches doivent être identiques. Toutes
   les fiches doivent être identiques. On a toutes les informations sûres ou pas, les
   fiches doivent avoir la même taille. »
   Mesuré : dans « Collab. & trices », les responsables faisaient 312 px et les
   collaboratrices 351 (une ligne de faits de plus). Un `align-items: stretch` sur la
   grille + une hauteur minimale commune les aligne, quel que soit le contenu. */
.carte-grille{align-items:stretch;grid-auto-rows:1fr}
.carte-grille .carte{height:100%;min-height:268px}
.carte-grille.is-carre .carte{height:100%;min-height:268px;aspect-ratio:auto}

/* ═══ LE TRI, CENTRÉ SUR SON CONTENU RÉEL ═══════════════════════════════════════
   La vue « Collab. & trices » met un libellé long (« Responsables · trier par »)
   dans la barre, ce qui décalait le centre mesuré. On centre la barre sur sa largeur
   propre, et on laisse le libellé se placer sans pousser l'ensemble. */
.tri-bar{justify-content:center}
.tri-bar-sep{margin-top:6px;justify-content:center}
.tri-bar .tri-label{flex:none}
/* ═══ UNE CARTE SEULE NE S'ÉTIRE PAS ════════════════════════════════════════════
   Avec `auto-fit`, une carte unique remplissait toute la largeur (1112 px) : un
   bandeau au lieu d'une carte. On borne donc la largeur d'une carte, quel que soit
   leur nombre — la grille s'aligne à gauche, sans vide béant. */
.carte-grille.is-carre{align-items:stretch;justify-content:start}
.carte-grille .carte{max-width:360px}

/* ═══ « Collab. & trices » : LES DEUX SECTIONS À LA MÊME HAUTEUR ════════════════
   Client : « les tailles des fiches doivent être identiques. Toutes les fiches
   doivent être identiques. » Cette vue porte DEUX grilles (responsables et
   collaboratrices) : l'égalisation par ligne ne joue qu'à l'intérieur de chacune.
   On fixe donc une hauteur commune : 4 faits comme 5 faits tiennent pareil. */
/* On force la HAUTEUR DE LIGNE, pas celle de la carte : `height:100%` gagnait
   sur une hauteur fixe. En imposant la ligne, toutes les cartes — 4 faits comme
   5 faits — remplissent exactement la meme hauteur. */
/* Client : « il y a la bulle de cadeau dans l'affiche des collaborateurs et
   collaboratrices qui déborde de la carte. Essayez d'agrandir l'affiche. »
   MESURÉ : « 0 cadeaux » (113x46) descend à 352 px dans une carte de 312 -> 40 px
   dehors. MESURÉ aussi : il y avait DEUX règles, pas une —
     * `grid-auto-rows:312px` fixait la hauteur de chaque RANGÉE : même en retirant le
       plafond de la carte, la rangée restait à 312 px ;
     * `max-height:312px` plafonnait la carte elle-même.
   Les deux sont retirées : la rangée prend la hauteur de son contenu, et la carte
   n'est plus rognée. `overflow:hidden` reste (il sert au shader, qui déborde
   légèrement) — mais sans plafond, il ne rogne plus rien. */
.section-personnes .carte-grille{grid-auto-rows:auto}
.section-personnes .carte{height:100%;min-height:300px;max-height:none;overflow:hidden}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA COQUE DE FICHE — le badge Heineken, à grande échelle
   ═══════════════════════════════════════════════════════════════════════════════
   Toutes les fiches des bases de données (entreprise, établissement, responsable,
   collaboratrice, tablette, goodie) partagent cette allure : le portrait carré en
   haut, la bulle ROUGE du nom encadrée de ses deux traits, les compteurs carrés
   cliquables, puis les sections. Exactement la fiche de campagne — sans couverture.
   Client : « une sorte de badge, mais à grande échelle. » */

/* La fiche est le repère du shader et des calques. */
.profile-sheet{position:relative}
.profile-sheet > form{position:relative;z-index:1}

/* ─── LE SHADER EN FILIGRANE ──────────────────────────────────────────────────
   Client : « voici un shader que tu peux mettre en arrière-plan, un peu filigrané ».
   Il vit DANS la fiche (pas dans le fond de l'app), il ne capte aucun clic, et il
   reste discret : on doit lire les informations sans effort. */
/* LE FOND D'UNE FICHE : il couvre TOUTE la hauteur du contenu.
   Client : « le fond doit être étendu jusqu'à la fin du pop-up, pas juste à la moitié ».
   MESURÉ : `inset:0;height:100%` se résolvait sur la BOÎTE VISIBLE d'une fiche qui défile
   (994 px) au lieu des 2 377 px de contenu — le fond s'arrêtait donc à mi-hauteur.
   On ancre le canvas en HAUT (`top:0`) et c'est le JS qui lui donne la hauteur du contenu ;
   `height:100%` ne sert plus que de repli tant que le script n'a pas tourné. */
.fiche-shader{position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;
  pointer-events:none;opacity:.13;mix-blend-mode:screen;border-radius:inherit}
/* Le contenu passe AU-DESSUS de son fond, quel que soit le chemin de construction de la
   fiche. Sans ça, le canvas d'une fiche à coque maison recouvrirait ses champs.
   ATTENTION : la barre des boutons (`sheet-pin-bar`) est EXCLUE de cette règle.
   MESURÉ : en l'incluant, son `position:absolute` était écrasé et les boutons « fermer »
   et « thème » retombaient dans le flux, poussés à droite au lieu d'être épinglés. */
.profile-sheet>*:not(.fiche-shader):not(.sheet-pin-bar){position:relative;z-index:1}
/* ═══ LA BARRE DES BOUTONS D'UNE FICHE : ÉPINGLÉE EN HAUT À DROITE ═══════════════
   Client : « les boutons fermer et mode clair et sombre ont sauté vers la droite ».
   MESURÉ : la barre gardait `position:relative` — donc `top:10px` et `right:12px` la
   DÉCALAIENT au lieu de l'épingler, et elle occupait toute la largeur de la fiche
   (x 41 → 1361) au lieu d'être un petit bloc à droite. On la fixe donc vraiment. */
/* Spécificité renforcée (`dialog.profile-sheet`) : elle doit gagner contre toute règle
   générique qui viserait les enfants d'une boîte de dialogue. */
dialog.profile-sheet>.sheet-pin-bar,
.profile-sheet>.sheet-pin-bar{
  position:absolute;top:12px;right:14px;left:auto;bottom:auto;
  display:flex;align-items:center;gap:8px;width:auto;max-width:max-content;
  z-index:60}
/* Un fond de fiche qui défile ne doit pas emporter les boutons : on les épingle à la
   fiche visible, pas au contenu. */
.profile-sheet>.sheet-pin-bar{position:sticky;top:12px;margin:-38px -14px 0 auto;
  width:max-content;float:right}
.profile-sheet>.sheet-pin-bar{position:absolute;top:12px;right:14px;margin:0;float:none}
/* En thème clair, `screen` blanchirait le fond : on assombrit à la place. */
.profile-sheet.sheet-clair .fiche-shader{mix-blend-mode:multiply;opacity:.07}

/* ─── LE PORTRAIT, CENTRÉ ─────────────────────────────────────────────────────
   Sans couverture, il n'y a plus de marge négative : le portrait ouvre la fiche. */
.sheet-portrait{display:flex;flex-direction:column;align-items:center;gap:10px;
  padding:22px 32px 0}
.sheet-portrait .sheet-avatar-slot .image-pick{width:120px;height:120px}
/* Carré à coins NETS — le client refuse les ronds : « pas des cercles, ça doit être
   carré. Tout comme la carte de la campagne, ça doit être rectangulaire, pas ronde. » */
.sheet-portrait .image-pick{border-radius:6px}
.sheet-portrait .image-pick img{object-fit:cover;padding:0;border-radius:5px}
.sheet-portrait .image-pick-initials{font-size:30px}
/* Une entreprise : la zone est plus LARGE et plus plate, son logo étant souvent
   rectangulaire. Client : « ça serait beaucoup plus large parce que parfois ça
   serait un vrai logo rectangulaire. » */
.sheet-portrait.is-logo .image-pick{width:210px;height:124px;border-radius:6px}
.sheet-portrait.is-logo .image-pick img{object-fit:contain;padding:6px}
.sheet-portrait-statut{display:inline-flex}

/* ─── LA BULLE ROUGE DU TITRE, AVEC SES DEUX TRAITS ───────────────────────────
   C'est le motif de la campagne : un bloc rouge, deux traits de part et d'autre.
   On réutilise les teintes et la géométrie de `.band` pour que ce soit identique. */
.sheet-bulle{display:flex;align-items:center;justify-content:center;gap:18px;
  margin:16px 0 6px;padding:0 32px}
.sheet-bulle-line{flex:1 1 0;height:2px;border-radius:2px;
  background:linear-gradient(90deg,rgba(255,255,255,.06),rgba(255,255,255,.52))}
.sheet-bulle-line:last-of-type{
  background:linear-gradient(90deg,rgba(255,255,255,.52),rgba(255,255,255,.06))}
.sheet-bulle-tag{display:inline-flex;align-items:center;padding:10px 22px;
  border-radius:13px;background:#a01a26;flex:none;max-width:72%;
  box-shadow:0 4px 14px -4px rgba(212,43,56,.5);
  color:#fff;font:800 20px/1.15 Manrope,sans-serif;letter-spacing:.02em;
  text-align:center;overflow-wrap:anywhere}
.profile-sheet.sheet-clair .sheet-bulle-tag{background:#961c26}
.profile-sheet.sheet-clair .sheet-bulle-line{
  background:linear-gradient(90deg,rgba(23,23,25,.08),rgba(23,23,25,.42))}
.profile-sheet.sheet-clair .sheet-bulle-line:last-of-type{
  background:linear-gradient(90deg,rgba(23,23,25,.42),rgba(23,23,25,.08))}

/* Le sous-titre (type · ville), sous la bulle. */
.sheet-sous{margin:2px 0 0;padding:0 32px;text-align:center;
  color:var(--muted,#6b6965);font:500 13px/1.5 Manrope,sans-serif}
/* Le pseudo et le téléphone du responsable sous son nom : sur la fiche SOMBRE, l'encre de la
   coque est claire — la mesure donnait 1,02:1 (rgb(23,23,26) sur rgb(20,21,23)). */
.profile-sheet:not(.sheet-clair) .sheet-sous { color: rgba(244,243,241,.66) !important; }

/* ─── LES COMPTEURS DE LA FICHE : DES CARRÉS, EUX AUSSI ───────────────────────
   Mesuré : ils tombaient en 192x66 parce qu'une règle `.profile-sheet` leur
   appliquait `aspect-ratio:auto`. Le client veut des carrés, comme sur les cartes. */
.profile-sheet .tile-counts{grid-template-columns:repeat(4,minmax(0,1fr));
  max-width:520px;margin:18px auto 6px}
.profile-sheet .tile-count{aspect-ratio:1/1;padding:12px;border-radius:12px}
.profile-sheet .tile-count strong{font-size:24px}
.profile-sheet .tile-count span{font-size:9.5px;letter-spacing:.04em;white-space:normal}
@media (max-width:620px){
  .profile-sheet .tile-counts{grid-template-columns:repeat(2,1fr);max-width:340px}
}

/* ─── LES SECTIONS DE LA FICHE ────────────────────────────────────────────────
   Le trait + la bulle rouge sont donnés par `.band` (déjà en place). On ne fait
   qu'ajouter le rythme interne. */
.profile-sheet .sheet-section{margin:0 0 4px;padding:0 32px}
.profile-sheet .sheet-section.liste{display:grid;gap:8px}
.sheet-list-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:10px 13px;border:1px solid var(--line);border-radius:10px;background:#faf9f7;
  font:600 13px Manrope,sans-serif;color:var(--text)}
.profile-sheet:not(.sheet-clair) .sheet-list-row{background:rgba(255,255,255,.05);
  border-color:rgba(255,255,255,.16);color:#f0eeeb}
.session-row{display:grid;grid-template-columns:106px minmax(0,1fr) auto;align-items:center;
  gap:12px;padding:9px 13px;border:1px solid var(--line);border-radius:10px;
  background:#faf9f7;font:600 12.5px Manrope,sans-serif;color:var(--text)}
.profile-sheet:not(.sheet-clair) .session-row{background:rgba(255,255,255,.05);
  border-color:rgba(255,255,255,.16);color:#f0eeeb}
.session-date{font-variant-numeric:tabular-nums;color:var(--muted)}
.profile-sheet:not(.sheet-clair) .session-date{color:#bab8b4}

/* ─── LA GRILLE D'INFORMATIONS ────────────────────────────────────────────────
   Saisie moderne : deux colonnes, libellés discrets, champs larges. */
.profile-sheet .form-grid.deux{display:grid;grid-template-columns:1fr 1fr;gap:13px}
.profile-sheet .form-grid.deux .field.large{grid-column:1 / -1}
@media (max-width:620px){.profile-sheet .form-grid.deux{grid-template-columns:1fr}}
/* ═══ LE MESSAGE NON BLOQUANT ═══════════════════════════════════════════════════
   Cause racine du « ça ne sort pas, ça bug » : `alert()` BLOQUE le thread JS.
   Appelée depuis un `.catch()`, elle fige la page entière — plus aucun clic ne
   répond. On la remplace par ce bandeau, qui ne bloque rien et se retire seul. */
#oz-message{position:fixed;left:50%;bottom:26px;transform:translate(-50%,18px);
  max-width:min(560px,88vw);padding:13px 20px;border-radius:12px;
  border:1px solid rgba(212,43,56,.42);background:#1a1416;color:#f3e9ea;
  font:600 13px/1.45 Manrope,sans-serif;text-align:center;
  opacity:0;pointer-events:none;z-index:9999;
  transition:opacity .22s ease,transform .22s ease}
#oz-message.visible{opacity:1;transform:translate(-50%,0)}
/* ═══ LE BOUTON « FICHE » : DISCRET, PAS UN DOUBLON ═════════════════════════════
   Client : « ça ne sert à rien d'avoir le bouton ouvrir. Il suffit de cliquer sur la
   fiche pour qu'elle s'ouvre. » La carte ENTIÈRE est déjà cliquable (`onclick` pose
   `role="button"`), donc un bouton plein faisait double emploi. On le laisse comme
   repère discret, et c'est la carte qu'on clique.
   La couleur de la marque reste : c'est un repère, pas un appel à l'action. */
.carte-actions .text-button.strong,
.carte-actions .text-button[data-principal="1"]{
  padding:4px 10px;border-radius:7px;border:1px solid rgba(212,43,56,.34);
  background:transparent;color:#c0392f;
  font:700 11px Manrope,sans-serif;letter-spacing:.02em;
  transition:background .15s,border-color .15s,color .15s}
.carte-actions .text-button.strong:hover,
.carte-actions .text-button[data-principal="1"]:hover{
  background:rgba(212,43,56,.09);border-color:#a01a26;color:#a82a35}

/* ═══ LA SÉPARATION ACTIF / INACTIF ═════════════════════════════════════════════
   Client : « une séparation avec le trait et avec une bulle actif en vert et en bas
   grise inactif ». Le trait et la bulle viennent de `bandHtml`, exactement comme les
   autres sections : c'est le même langage, donc rien de nouveau à comprendre. */
.eq-sep{margin:20px 0 12px}
.eq-sep .band-tag{background:#007d44;box-shadow:0 4px 14px -4px rgba(0,125,68,.5)}
.eq-sep.eq-sep-off .band-tag{background:#78756f;box-shadow:0 4px 14px -4px rgba(120,117,111,.4)}
/* La bulle d'une personne ACTIVE.
   MESURÉ : un vert sombre sur fond pâle se PERÇOIT comme du gris — la vision a
   décrit « dark grey » une pastille mesurée rgb(15,122,78). Un vert sombre a une
   saturation forte mais une faible luminance : l'œil le désature. D'où une
   PASTILLE PLEINE : texte blanc sur vert saturé. Mesuré 5.23:1 (WCAG AA). */
.badge-actif{background:#007d44;border-color:#007d44;color:#fff}
/* La bulle INACTIVE : pleine elle aussi, mais grise — elle ne doit pas attirer l'œil.
   Mesuré 4.59:1 sur blanc. */
.badge-inactif{background:#78756f;border-color:#78756f;color:#fff}
/* SUR LA COQUE SOMBRE, une pastille pleine sombre disparaîtrait dans le fond :
   on passe en teinte claire. Mesuré 9.30:1 pour le vert, 6.27:1 pour le gris. */
.profile-sheet:not(.sheet-clair) .badge-actif{background:rgba(76,211,154,.16);
  border-color:rgba(76,211,154,.5);color:#4cd39a}
.profile-sheet:not(.sheet-clair) .badge-inactif{background:rgba(255,255,255,.07);
  border-color:rgba(255,255,255,.2);color:#b4b1ac}
/* Une personne inactive se lit en gris léger : jamais de noir dur. */
.eq-item-inactif .eq-nom strong{color:#72706c;font-weight:600}
/* ═══ LE BOUTON ☀ / × FAIT PARTIE DU POP-UP ═════════════════════════════════════
   Client : « il doit rentrer un peu dans le pop-up. Il ne doit pas être dans le coin
   de la web. » Il était `position:fixed`, donc collé au coin de la FENÊTRE et
   détaché de la fiche : visuellement il appartenait à l'application, pas au pop-up.
   Désormais `absolute` dans la fiche, rentré de 10 px, au-dessus du contenu. */
.profile-sheet{position:relative}
/* Sans couverture, la barre n'a plus besoin d'être sur une image : on lui laisse le
   haut de la fiche, et on décale le portrait pour qu'ils ne se chevauchent pas. */
.profile-sheet .sheet-portrait{padding-top:30px}
/* Le corps de la fiche ne doit jamais recouvrir les boutons. */
.profile-sheet > form{position:relative;z-index:1}
.profile-sheet .sheet-pin-bar{z-index:60}

/* ═══ LE SHADER DANS LES PETITES CARTES ═════════════════════════════════════════
   Client : « cet effet de shader doit aussi être dans les petites cartes… j'aimerais
   bien avoir cette animation dans la carte afin de la décoller du fond. »
   La carte était blanche PLATE sur un fond clair : elle se confondait avec lui.
   On lui donne : un dégradé de fond, une bordure qui s'allume, une ombre portée
   courte, et surtout le shader animé en filigrane.

   ⚠️ Un canvas WebGL par carte échouerait : un navigateur ne tient que ~16 contextes
   WebGL, et la vue Établissements en affiche 20. On rend donc le shader UNE fois, et
   c'est ce dessin mis en cache qui habille chaque carte (voir `activerShadersCartes`). */
.carte{position:relative;overflow:hidden;
  background:linear-gradient(168deg,#ffffff,#fbfaf8)}
.carte-shader{position:absolute;inset:0;z-index:0;pointer-events:none;
  background-repeat:no-repeat;background-size:230% 230%;
  opacity:.14;mix-blend-mode:multiply;border-radius:inherit}
/* Le contenu passe AU-DESSUS du shader. */
.carte > *:not(.carte-shader){position:relative;z-index:1}
/* L'ombre courte + la bordure qui s'allume : c'est ce qui la « décolle » du fond. */
.carte{box-shadow:0 1px 2px rgba(23,23,25,.05),0 10px 22px -18px rgba(23,23,25,.5)}
.carte:hover{box-shadow:0 2px 4px rgba(23,23,25,.06),0 18px 34px -18px rgba(212,43,56,.42)}

/* Le MÊME traitement pour les tuiles de campagne (vue carré), qui ont leur propre
   classe `.entity-tile` : elles ne doivent pas rester plates quand les autres bougent. */
.entity-tile{position:relative;overflow:hidden}
/* Le shader d'une tuile reste SOUS la photo de couverture. Il etait passe au-dessus
   (une regle `.entity-tile.has-cover>*` lui donnait `z-index:1`), et son voile
   `multiply` lavait la couverture : le client a vu sa jolie photo s'effacer.
   On pose donc un ordre EXPLCITE : photo (0) < shader (1) < contenu (2). */
.entity-tile-shader{position:absolute;inset:0;z-index:1;pointer-events:none;
  background-repeat:no-repeat;background-size:230% 230%;
  opacity:.16;mix-blend-mode:multiply;border-radius:inherit}
.entity-tile.has-cover .entity-tile-cover{z-index:0}
/* On EXCLUT explicitement la couverture et le shader : sans ca, cette regle
   (specificite 0,2,0) battait `.entity-tile-cover{position:absolute}` (0,1,0) et
   la photo retombait en flux normal — une vignette au lieu d'un fond etire. */
.entity-tile > *:not(.entity-tile-shader):not(.entity-tile-cover){position:relative;z-index:2}
/* Sur une tuile qui a une PHOTO, le shader est redondant : la photo EST la matiere.
   On le rend presque invisible pour ne pas laver la couverture. */
/* Sur une tuile qui porte une PHOTO, le shader est redondant : la photo EST la
   matiere. On le neutralise pour ne pas laver la couverture. */
.entity-tile.has-cover .entity-tile-shader{opacity:0}

/* Sur la coque SOMBRE d'une fiche, `multiply` assombrirait : on éclaircit. */
.profile-sheet:not(.sheet-clair) .carte-shader{mix-blend-mode:screen;opacity:.10}
.profile-sheet:not(.sheet-clair) .carte{background:linear-gradient(168deg,#1a1b1d,#141516)}
/* ═══ LE SHADER ANIMÉ DANS LES CARTES ═══════════════════════════════════════════
   Le dessin est rendu une seule fois (voir `rendreShaderPartage`) puis posé en fond.
   Ici on le FAIT VIVRE : le fond glisse lentement en diagonale, ce qui donne une
   matière mouvante sans coûter un contexte WebGL par carte.
   Client : « j'aimerais bien avoir cette animation dans la carte afin de la décoller
   du fond. » On respecte aussi `prefers-reduced-motion`. */
@keyframes shaderCarteGlisse{
  0%  {background-position:0% 0%}
  50% {background-position:100% 60%}
  100%{background-position:0% 0%}
}
.carte-shader,.entity-tile-shader{animation:shaderCarteGlisse 26s ease-in-out infinite}
/* Chaque carte démarre décalée : sans cela, toutes onduleraient à l'unisson et la
   répétition sauterait aux yeux. On étale les départs sur le cycle. */
.carte:nth-child(3n+2) .carte-shader,.entity-tile:nth-child(3n+2) .entity-tile-shader{animation-delay:-9s}
.carte:nth-child(3n)   .carte-shader,.entity-tile:nth-child(3n)   .entity-tile-shader{animation-delay:-17s}
@media (prefers-reduced-motion:reduce){
  .carte-shader,.entity-tile-shader{animation:none}
}
/* ═══ L'ENTONNOIR DU STOCK : LES 4 PALIERS ══════════════════════════════════════
   Client : « un stock dans le dépôt, un stock chez le responsable d'équipe qui va
   ravitailler les collaborateurs… combien était remis aux gagnants ». On lit le
   parcours du goodie d'un seul regard : dépôt → distributeurs → collaboratrices →
   participants. Chaque palier porte sa part, et la barre se remplit d'autant. */
.entonnoir{display:grid;gap:9px;padding:2px 0 4px}
.palier{display:grid;grid-template-columns:170px minmax(0,1fr) auto;align-items:center;
  gap:12px}
.palier .p-nom{font:600 11.5px Manrope,sans-serif;color:var(--muted);
  display:flex;align-items:center;gap:7px}
.palier .p-nom i{width:8px;height:8px;border-radius:3px;flex:none}
.palier .p-jauge{height:9px;border-radius:5px;background:rgba(23,23,25,.07);overflow:hidden}
.palier .p-jauge span{display:block;height:100%;border-radius:5px;
  transition:width .4s ease}
.palier .p-val{font:800 15px Manrope,sans-serif;color:var(--text);
  font-variant-numeric:tabular-nums;min-width:52px;text-align:right}
.palier .p-pct{font:700 11px Manrope,sans-serif;color:#8a8781;min-width:44px;text-align:right}
/* Le total reçu : un cadre à part, au-dessus des paliers — c'est LE chiffre demandé. */
.recu-total{display:flex;align-items:baseline;gap:12px;padding:13px 16px;margin-bottom:14px;
  border:1px solid var(--line);border-radius:12px;
  background:linear-gradient(168deg,#fff,#faf9f7)}
.recu-total strong{font:800 30px/1 Manrope,sans-serif;color:var(--text)}
.recu-total span{font:600 11.5px Manrope,sans-serif;color:var(--muted)}
.recu-total .sortie{margin-left:auto;display:flex;align-items:baseline;gap:6px}
.recu-total .sortie b{font:800 20px Manrope,sans-serif;color:#007d44}
.recu-total .sortie small{font:600 10.5px Manrope,sans-serif;color:#8a8781;
  text-transform:uppercase;letter-spacing:.06em}
/* Sur la coque sombre de la fiche. */
.profile-sheet:not(.sheet-clair) .recu-total{background:rgba(255,255,255,.04);
  border-color:rgba(255,255,255,.16)}
.profile-sheet:not(.sheet-clair) .recu-total strong{color:#fff}
.profile-sheet:not(.sheet-clair) .palier .p-val{color:#fff}
.profile-sheet:not(.sheet-clair) .palier .p-jauge{background:rgba(255,255,255,.1)}
.profile-sheet:not(.sheet-clair) .palier .p-nom{color:#bab8b4}
/* ═══ LES BOUTONS D'ACTION : ALIGNÉS SUR LE TRAIT ═══════════════════════════════
   Client : « tu peux aussi remonter un peu les boutons d'action, les aligner par
   rapport au trait sans pouvoir affecter la position centrée des titres ».
   MESURÉ avant : le centre des boutons tombait 40 px SOUS le centre du titre, parce
   qu'ils étaient ancrés en bas du bloc (`bottom:0;transform:translateY(46%)`) et que
   le bloc recevait un `padding-bottom`. On les centre sur la même ligne que le titre,
   sans toucher au titre lui-même. */
.band{position:relative}
.band-actions{position:absolute;right:0;top:50%;bottom:auto;
  transform:translateY(-50%);display:inline-flex;align-items:center;gap:6px}
/* Le bloc-titre garde son centrage : on ne compense QUE la place des boutons, et on
   ne touche pas aux traits. */
.band:has(.band-actions) .band-tag{transform:none}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE CONTRASTE DES COMPTEURS DANS TOUTE LA BASE DE DONNÉES
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « je veux aussi ce contraste qu'on a sur les grandes fiches pop-up :
   les petits carrés en blanc en mode sombre, et en mode clair c'est le contraire. »
   Le contraire, exactement : le chiffre et le libellé SUIVENT le fond.

   MESURÉ : les compteurs des cartes avaient un fond clair rgb(250,249,247) et un
   chiffre BLANC rgb(255,255,255) — blanc sur blanc, 1.02:1, donc invisibles.
   C'est le defaut signale. On ne se contente pas de corriger ce cas : on rend le
   couple fond/texte SOLIDAIRE, pour que l'erreur ne puisse pas revenir. */

/* ─── SUR UNE CARTE CLAIRE : carre clair, texte sombre (le « contraire ») ───── */
.carte .tile-count{background:#faf9f7;border-color:var(--line)}
.carte .tile-count strong{color:var(--text)}
.carte .tile-count span{color:var(--muted)}
.carte .tile-count:hover{border-color:var(--red);background:#fcebed}
.carte .tile-count.is-zero strong{color:#b9b5af}

/* ─── SUR UNE CARTE SOMBRE : carre translucide, texte blanc ───────────────── */

/* ─── DANS UNE FICHE, EN MODE SOMBRE : carre translucide clair, texte blanc ── */
/* ─── DANS UNE FICHE, EN MODE CLAIR : l'inverse exact ───────────────────────── */
/* ─── SUR LA TUILE CAMPAGNE (toujours sombre, photo de couverture) ─────────── */
.entity-tile .tile-count{background:rgba(255,255,255,.10);
  border-color:rgba(255,255,255,.30)}
.entity-tile .tile-count strong{color:#fff}
.entity-tile .tile-count span{color:rgba(244,243,241,.82)}
.entity-tile .tile-count:hover{background:rgba(255,255,255,.18);
  border-color:rgba(255,255,255,.5)}
/* ═══ VERROU DU CONTRASTE — LES FICHIERS DE LA BASE ═════════════════════════════
   Les cartes de la base portent leurs chiffres en  (pas ) :
   on les couvre aussi, sinon une carte sombre afficherait un chiffre sombre.
   Et pour la fiche, on repete le couple en fin de feuille : a specificite egale,
   c'est la derniere regle qui gagne — on ne laisse pas le hasard decider. */
.carte .carte-fait b{color:#161616}
.carte /* MESURE : rgb(138,135,129) sur blanc ne donne que 3.6:1 — sous le seuil de 4.5
   pour du petit texte. Client : « ajuste le contraste du texte avec l'arrière-plan. »
   On descend le libellé a #383633 : 5.0:1, confortable. */
.carte-fait small{color:#383633}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE CONTRASTE DES COMPTEURS PAR VARIABLES
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « je veux aussi ce contraste qu'on a sur les grandes fiches pop-up de
   compagnie : les petits carrés, les petites bulles en BLANC en mode sombre, et en
   mode clair c'est le CONTRAIRE. »

   On pose donc le MODE, pas les couleurs de chaque element : le fond et le texte
   des compteurs sont des variables, et chaque mode les redefinit. Une variable ne
   peut pas « perdre » un match de specificite — c'est ce qui rend le couple
   fond/texte SOLIDAIRE, donc impossible a desynchroniser. */

/* Par defaut : le mode SOMBRE (le fond de la coque). */
.profile-sheet{
  --cpt-fond:rgba(255,255,255,.09);
  --cpt-bord:rgba(255,255,255,.28);
  --cpt-chiffre:#fff;
  --cpt-libelle:#cbc9c5;
  --cpt-fond-survol:rgba(255,255,255,.16);
}
/* Le mode CLAIR : exactement le contraire. */
.profile-sheet.sheet-clair{
  --cpt-fond:#faf9f7;
  --cpt-bord:var(--line);
  --cpt-chiffre:#161616;
  --cpt-libelle:#55585c;
  --cpt-fond-survol:#fcebed;
}
/* Les compteurs lisent les variables du mode en vigueur. */
.profile-sheet .tile-count{background:var(--cpt-fond);border-color:var(--cpt-bord)}
.profile-sheet .tile-count strong{color:var(--cpt-chiffre)}
.profile-sheet .tile-count span{color:var(--cpt-libelle)}
.profile-sheet .tile-count:hover{border-color:var(--red);background:var(--cpt-fond-survol)}
/* Les chiffres des cartes de la base suivent la meme regle. */
.profile-sheet .carte-fait b{color:var(--cpt-chiffre)}
.profile-sheet .carte-fait small{color:var(--cpt-libelle)}

/* Les CARTES de la base sont CLAIRES : chiffre sombre sur fond clair. */
.carte .tile-count{background:#faf9f7;border-color:var(--line)}
.carte .tile-count strong{color:#161616}
.carte .tile-count span{color:#55585c}
.carte .carte-fait b{color:#161616}
.carte .carte-fait small{color:#5f5c58}

/* La TUILE CAMPAGNE est SOMBRE (photo de couverture) : carres en blanc.
   MESURE avant : fond rgb(250,249,247) CLAIR + chiffre rgb(255,255,255) BLANC,
   soit 1.02:1 — invisible. C'est le defaut signale par le client. */
.entity-tile .tile-count{background:rgba(255,255,255,.10);
  border-color:rgba(255,255,255,.30)}
.entity-tile .tile-count strong{color:#fff}
.entity-tile .tile-count span{color:rgba(244,243,241,.82)}
.entity-tile .tile-count:hover{background:rgba(255,255,255,.18);
  border-color:rgba(255,255,255,.5)}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE SHADER MODÉRÉ · LA CARTE ÉCLAIRCIE · LE TEXTE DÉCOLLÉ
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « le shader doit être beaucoup plus clair… ne le garde pas très sombre,
   modère un peu… crée des bulles colorées afin de décoller le texte de l'arrière-plan ».

   MESURÉ AVANT : carte #26282a → #1a1b1d (presque noire). Le shader est un bruit
   rouge sombre : sur un fond quasi noir il ne se lit pas, et l'ensemble paraît
   TACHÉ au lieu de lumineux.
   APRÈS : anthracite moyen, shader modéré, et une pastille derrière chaque bloc de
   texte — le contraste tient alors quelle que soit la matière qui passe dessous. */

/* ─── 1. LA CARTE EST PLUS CLAIRE ────────────────────────────────────────────
   On remonte franchement : un anthracite moyen laisse enfin voir la matière rouge,
   au lieu de la noyer. */
.carte{
  background:linear-gradient(168deg,#3e4145 0%,#2c2e31 100%);
  border-color:rgba(255,255,255,.20)}
.carte:hover{border-color:rgba(255,255,255,.36)}

/* ─── 2. LE SHADER EST MODÉRÉ ────────────────────────────────────────────────
   `screen` AJOUTE la lumière : sur un fond désormais plus clair, une opacité
   modérée suffit — et évite l'effet « tache sombre » d'avant. */
.carte-shader{opacity:.24;mix-blend-mode:screen}

/* ─── 3. LE TEXTE SE DÉCOLLE SUR SA PASTILLE ─────────────────────────────────
   Chaque bloc d'information reçoit un fond translucide sombre : le texte garde son
   contraste même si le shader passe juste derrière. C'est la demande explicite
   (« des bulles pour décoller le texte »). */
.carte-faits{gap:7px}
.carte-fait{background:rgba(16,17,18,.50);border:1px solid rgba(255,255,255,.12);
  border-radius:9px;padding:6px 9px}
.carte-fait b{color:#fff}
.carte-fait small{color:rgba(244,243,241,.78)}

/* Le sous-titre (type · ville) : même traitement, en une ligne. */
.carte-sous{color:rgba(244,243,241,.88);text-shadow:0 1px 3px rgba(0,0,0,.55)}

/* ─── 4. LES PASTILLES D'ÉTAT SE DÉTACHENT AUSSI ─────────────────────────────
   Sur une carte plus claire, un badge sans fond se perdait dans la matière. */
.carte .badge{color:#fff;border-color:rgba(255,255,255,.36);
  background:rgba(16,17,18,.46)}
.carte .badge.active,.carte .badge.connected{color:#5fe0a8;
  border-color:rgba(95,224,168,.52);background:rgba(10,80,50,.42)}
.carte .badge.offline{color:rgba(244,243,241,.72);
  border-color:rgba(255,255,255,.24);background:rgba(16,17,18,.36)}
.carte .badge-planned{color:#f0d093;border-color:rgba(240,208,147,.46);
  background:rgba(80,60,10,.34)}

/* ─── 5. LE PORTRAIT GARDE SON FOND ──────────────────────────────────────────
   Une initiale blanche sur l'anthracite moyen manquerait de corps : la vignette
   du portrait reçoit elle aussi son fond. */
.carte-portrait .carte-av{background:rgba(16,17,18,.52);
  border-color:rgba(255,255,255,.24);color:#fff}

/* ─── 6. LES TRAITS DU TITRE, PLUS LISIBLES ──────────────────────────────────
   Sur un fond éclairci, un trait à 5 % disparaît : on remonte les extrémités. */
.carte-band-line{background:linear-gradient(90deg,rgba(255,255,255,.10),rgba(255,255,255,.55))}
.carte-band-line:last-child{background:linear-gradient(90deg,rgba(255,255,255,.55),rgba(255,255,255,.10))}
.carte-faits{border-top-color:rgba(255,255,255,.18)}

/* ─── 7. LA TUILE CAMPAGNE GARDE SA PHOTO ────────────────────────────────────
   Elle n'a pas de shader (la photo est sa matière) : on ne touche qu'à ses textes,
   qui ont besoin du même soulagement. */
.entity-tile .tile-count{background:rgba(16,17,18,.42);
  border-color:rgba(255,255,255,.30)}
.entity-tile .tile-ligne,.entity-tile .tile-stock-leg span{text-shadow:0 1px 3px rgba(0,0,0,.5)}
.entity-tile .tile-ville{background:rgba(16,17,18,.44);
  border-color:rgba(255,255,255,.24)}
/* ═══ LA PROGRESSION D'UNE CAMPAGNE, SUR SA TUILE ═══════════════════════════════
   Client : « je veux voir la durée de toute la campagne par rapport à nos
   événements : si on est à la moitié, si on est au deadline, si on est proche de
   finir. » La barre se remplit selon le TEMPS, et un repère vertical marque
   l'avancement des ÉVÉNEMENTS. L'écart entre les deux se lit d'un coup d'œil. */
.tile-prog{margin-top:11px;padding:10px 12px 9px;border-radius:10px;
  background:rgba(16,17,18,.42);border:1px solid rgba(255,255,255,.12)}
.tile-prog .prog-tete{display:flex;align-items:baseline;justify-content:space-between;
  gap:8px;margin-bottom:7px}
.tile-prog .prog-etat{font:700 11px Manrope,sans-serif;letter-spacing:.02em;color:#fff}
.tile-prog .prog-pct{font:600 10.5px Manrope,sans-serif;color:rgba(244,243,241,.72)}
.tile-prog .prog-pct b{color:#fff;font-weight:800;font-variant-numeric:tabular-nums}
/* La barre du TEMPS. */
.tile-prog .prog-barre{position:relative;height:8px;border-radius:5px;
  background:rgba(255,255,255,.13);overflow:visible}
.tile-prog .prog-barre i{display:block;height:100%;border-radius:5px;
  background:linear-gradient(90deg,#4cd39a,#e8c37a);transition:width .4s ease}
/* Le repère des ÉVÉNEMENTS : un trait vertical posé sur la barre. */
.tile-prog .prog-barre u{position:absolute;top:-3px;width:2px;height:14px;
  border-radius:1px;background:#fff;box-shadow:0 0 0 2px rgba(16,17,18,.55);
  transform:translateX(-1px)}
.tile-prog .prog-pied{display:flex;align-items:center;justify-content:space-between;
  gap:8px;margin-top:7px;font:600 10px Manrope,sans-serif;
  color:rgba(244,243,241,.62)}
.tile-prog .prog-evt b{font-weight:800;font-variant-numeric:tabular-nums}
.tile-prog .evt-passe{color:#fff}
.tile-prog .evt-venir{color:#e8c37a}

/* L'état colore la barre : plus on approche de la fin, plus elle tire sur l'ambre. */
.tile-prog.fin-proche .prog-barre i{background:linear-gradient(90deg,#e8c37a,#a01a26)}
.tile-prog.finie .prog-barre i{background:#8a8781}
.tile-prog.finie .prog-etat{color:rgba(244,243,241,.6)}
.tile-prog.planifiee .prog-barre i{background:rgba(255,255,255,.35)}

/* Sur la coque claire d'une fiche, l'inverse exact. */
.profile-sheet.sheet-clair .tile-prog{background:#f7f6f3;
  border-color:var(--line)}
.profile-sheet.sheet-clair .tile-prog .prog-etat{color:#161616}
.profile-sheet.sheet-clair .tile-prog .prog-pied{color:#6f6c67}
.profile-sheet.sheet-clair .tile-prog .evt-passe{color:#161616}
.profile-sheet.sheet-clair .tile-prog .prog-barre{background:rgba(23,23,25,.12)}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES CARTES DE LA BASE : CLAIRES, MAIS AVEC UNE TEINTE ET DES FILETS
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « les cartes en noir très foncé font très mal aux yeux. On va les faire
   revenir en clair. »

   On ne revient PAS au blanc plat : c'est lui qui posait le contraste FAUX (un
   chiffre BLANC rgb(255,255,255) sur un fond clair rgb(250,249,247) — 1.02:1,
   donc invisible). On passe en clair TEINTÉ, texte sombre, filet franc : la carte
   se détache du fond de page, et l'œil ne force pas. */
.carte{
  background:linear-gradient(168deg,#ffffff 0%,#f7f5f2 100%);
  border-color:rgba(23,23,25,.14);color:#171719;
  box-shadow:0 1px 2px rgba(23,23,25,.05),0 10px 22px -18px rgba(23,23,25,.45)}
.carte:hover{border-color:rgba(23,23,25,.28);
  box-shadow:0 2px 4px rgba(23,23,25,.06),0 16px 30px -18px rgba(212,43,56,.35)}

/* Le shader devient un FILIGRANE discret : en `multiply`, il teinte légèrement au
   lieu d'assombrir. Opacité basse : c'est une matière, pas un voile. */
.carte-shader{opacity:.075;mix-blend-mode:multiply}

/* ─── LE TEXTE, EN SOMBRE : le contraste est enfin juste ───────────────────── */
.carte-sous{color:#5f5c58}
.carte-band-line{background:linear-gradient(90deg,rgba(23,23,25,.05),rgba(23,23,25,.34))}
.carte-band-line:last-child{background:linear-gradient(90deg,rgba(23,23,25,.34),rgba(23,23,25,.05))}
.carte-portrait .carte-av{background:#f1efec;color:#5a5751;
  border-color:rgba(23,23,25,.12)}
.carte-faits{border-top-color:rgba(23,23,25,.10)}
/* Les pastilles de chiffres : fond clair, chiffre SOMBRE. C'est le couple qui
   garantit la lisibilité — le meme que la grande fiche. */
.carte-fait{background:#faf9f7;border:1px solid rgba(23,23,25,.09);border-radius:9px;
  padding:6px 9px}
.carte-fait b{color:#161616}
.carte-fait small{color:#5f5c58}
.carte .tile-count{background:#faf9f7;border-color:var(--line)}
.carte .tile-count strong{color:#161616}
.carte .tile-count span{color:#55585c}
.carte .tile-count:hover{background:#fcebed;border-color:var(--red)}
.carte .tile-count.is-zero strong{color:#b9b5af}
.carte .empty{color:#8a8781}

/* ─── LES PASTILLES D'ÉTAT, EN CLAIR ───────────────────────────────────────── */
.carte .badge{color:#5a5751;border-color:rgba(23,23,25,.18);background:#f7f6f3}
.carte .badge.active,.carte .badge.connected{color:#0a7d4f;
  border-color:rgba(10,125,79,.38);background:rgba(10,125,79,.09)}
.carte .badge.offline{color:#8a8781;border-color:rgba(23,23,25,.14);background:#f7f6f3}
.carte .badge-planned{color:#9a6f1a;border-color:rgba(154,111,26,.34);
  background:rgba(154,111,26,.09)}

/* Le bouton « Fiche » : discret, la carte entière est cliquable. */
.carte .carte-actions .text-button.strong,
.carte .carte-actions .text-button[data-principal="1"]{color:#c0392f;
  border-color:rgba(192,57,47,.32);background:transparent}

/* ═══ LA TUILE CAMPAGNE : ELLE GARDE SA PHOTO ═══════════════════════════════════
   Sa photo de couverture est SOMBRE (bannière Heineken) : ses textes restent donc
   en clair, sinon on les perdrait. On n'éclaircit que l'extérieur. */
.entity-tile{background:linear-gradient(150deg,#26282a 0%,#1a1b1d 100%);
  color:#f4f3f1}
.entity-tile .tile-count{background:rgba(16,17,18,.46);
  border-color:rgba(255,255,255,.30)}
.entity-tile .tile-count strong{color:#fff}
.entity-tile .tile-count span{color:rgba(244,243,241,.82)}
.entity-tile .tile-ligne,.entity-tile .tile-stock-leg span{color:rgba(244,243,241,.82)}
.entity-tile .tile-ligne b,.entity-tile .tile-stock-leg b{color:#fff}
.entity-tile .tile-villes .tile-ville{background:rgba(16,17,18,.48);
  border-color:rgba(255,255,255,.26);color:rgba(244,243,241,.86)}
.entity-tile .bloc-titre{color:rgba(244,243,241,.58)}
/* ═══ LES LIBELLÉS SE DÉCOLLENT AUSSI ═══════════════════════════════════════════
   Client : « ajuste-moi le contraste du texte avec l'arrière-plan du shader ».
   Le shader passe maintenant derrière chaque bloc de texte : ces valeurs sont donc
   mesurees sur le PIRE cas (shader le plus dense). */
.carte .tile-count span{color:#5a5751}
.carte .tile-count.is-zero strong{color:#a9a5a0}
.carte .badge{color:#4f4c48;border-color:rgba(23,23,25,.20);background:#f7f6f3}
.carte .badge.offline{color:#383633;border-color:rgba(23,23,25,.16);background:#f7f6f3}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES WIDGETS DE PILOTAGE : TOUTE LA LIGNE, ET COLORÉS
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « les widgets doivent profiter de toute la ligne… on met le maximum
   étiré… et on peut les colorer parce que là on ne les voit pas. »

   MESURÉ AVANT : le bloc faisait 430 px dans une page de 1112 px — 61 % de vide, et
   des carrés (aspect-ratio 1/1) qui ne pouvaient pas s'étirer.
   APRÈS : chaque widget prend sa part de la ligne, et porte une COULEUR.

   `#app > .tile-counts` vise les widgets de PAGE (ceux des vues). Les widgets qui
   vivent DANS une carte ou une fiche gardent leur forme compacte : ils sont déjà
   dans un espace étroit, et les étirer casserait la carte. */
#app > .tile-counts,
.section-page > .tile-counts{
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  width:100%;max-width:none;margin:16px 0 6px}
#app > .tile-counts .tile-count,
.section-page > .tile-counts .tile-count{
  /* Le widget s'étire : plus de carré imposé. Une forme large, généreuse. */
  aspect-ratio:auto;min-height:0;width:100%;
  padding:15px 18px;border-radius:13px;
  place-content:center;justify-items:flex-start;text-align:left;
  gap:4px}
#app > .tile-counts .tile-count strong,
.section-page > .tile-counts .tile-count strong{
  font:800 30px/1 Manrope,sans-serif;letter-spacing:-.02em;
  /* MESURE : le chiffre du 1er widget restait BLANC rgb(255,255,255) — heritage de
     la regle de la coque sombre (l.161 .tile-count strong{color:#fff}). Sur un fond
     clair, blanc sur blanc. On force donc une couleur SOMBRE, et les widgets 2 a 5
     la remplacent par leur teinte. */
  color:#161616}
#app > .tile-counts .tile-count span,
.section-page > .tile-counts .tile-count span{
  font:700 10.5px/1.25 Manrope,sans-serif;letter-spacing:.08em;
  /* MESURE : « chez une collaboratrice » demande 165 px dans un widget de
     211 px dont l'icone prend 52 px -> 26 px manquants, et l'ellipse le
     coupait. Un libelle est du TEXTE DU CLIENT : il se replie. */
  white-space:normal;overflow:visible;text-overflow:clip;}

/* ─── LES COULEURS : UNE PAR WIDGET ──────────────────────────────────────────
   Le client : « on peut les colorer parce que là on ne les voit pas ».
   Chaque widget prend une teinte de la charte, en fond comme en accent. Elles se
   suivent dans l'ordre : la première neutre, puis rouge, ambre, vert, or. */
#app > .tile-counts .tile-count:nth-child(1),
.section-page > .tile-counts .tile-count:nth-child(1){
  background:linear-gradient(168deg,#f4f2ef,#eceae6);border-color:rgba(23,23,25,.16)}
#app > .tile-counts .tile-count:nth-child(2),
.section-page > .tile-counts .tile-count:nth-child(2){
  background:linear-gradient(168deg,#fdf0f1,#fae3e5);border-color:rgba(212,43,56,.30)}
#app > .tile-counts .tile-count:nth-child(3),
.section-page > .tile-counts .tile-count:nth-child(3){
  background:linear-gradient(168deg,#fdf6ea,#f8eeda);border-color:rgba(180,130,40,.30)}
#app > .tile-counts .tile-count:nth-child(4),
.section-page > .tile-counts .tile-count:nth-child(4){
  background:linear-gradient(168deg,#eef7f2,#e2f0e9);border-color:rgba(10,125,79,.28)}
#app > .tile-counts .tile-count:nth-child(5),
.section-page > .tile-counts .tile-count:nth-child(5){
  background:linear-gradient(168deg,#fbf4ec,#f5e9dc);border-color:rgba(212,165,116,.42)}

/* Le CHIFFRE prend la couleur de son widget : c'est ce qui les rend lisibles. */
#app > .tile-counts .tile-count:nth-child(2) strong,
.section-page > .tile-counts .tile-count:nth-child(2) strong{color:#961c26}
#app > .tile-counts .tile-count:nth-child(3) strong,
.section-page > .tile-counts .tile-count:nth-child(3) strong{color:#8a6110}
#app > .tile-counts .tile-count:nth-child(4) strong,
.section-page > .tile-counts .tile-count:nth-child(4) strong{color:#0a7d4f}
#app > .tile-counts .tile-count:nth-child(5) strong,
.section-page > .tile-counts .tile-count:nth-child(5) strong{color:#a5762f}
/* Les libellés restent sombres : le chiffre porte la couleur. */
#app > .tile-counts .tile-count span,
.section-page > .tile-counts .tile-count span{color:#5f5c58}
/* Un widget a zero ne crie pas : on eteint un peu sa couleur. */
#app > .tile-counts .tile-count.is-zero strong,
.section-page > .tile-counts .tile-count.is-zero strong{color:#a9a5a0}

/* La page etroite : deux par ligne, jamais un widget de 150 px coince. */
@media (max-width:760px){
  #app > .tile-counts,
  .section-page > .tile-counts{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES GRILLES DE CARTES : PLUS D'ESPACE PERDU À DROITE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « n'oublie pas d'éviter cet espace qu'on a toujours à droite, ça doit
   être un peu centré les fiches par rapport à cet espace ».

   MESURÉ : les colonnes étaient bornées a 330 px et le bloc collé a gauche. 3
   colonnes dans une zone de 1097 px = 990 px, donc 424 px vides a droite (et 767 px
   sur Stock, 752 px sur Entreprise avec une seule carte).

   Ici les colonnes se PARTAGENT la largeur (`1fr`) : il en tient 4 au lieu de 3, et
   le vide disparait. Une carte seule reste bornee et se CENTRE, au lieu de s'etaler
   en bandeau. */
.carte-grille.is-carre{
  grid-template-columns:repeat(auto-fit,minmax(228px,1fr));
  gap:13px;justify-content:center;width:100%}
/* Une carte seule ne s'etale pas : elle est bornee, et centree dans sa colonne. */
.carte-grille.is-carre .carte{max-width:420px;width:100%;
  margin-left:auto;margin-right:auto}

/* La meme chose pour les cartes d'ENTREPRISE (logo large, pas de `.is-carre`). */
.carte-grille:not(.is-carre){
  grid-template-columns:repeat(auto-fit,minmax(290px,1fr));
  gap:14px;justify-content:center;width:100%}
.carte-grille:not(.is-carre) .carte{max-width:560px;width:100%;
  margin-left:auto;margin-right:auto}

/* Les tuiles CAMPAGNE : deja en `1fr`, mais collees a gauche. On centre. */
.entity-grid{justify-content:center;width:100%}
.entity-grid .entity-tile{max-width:700px;width:100%;
  margin-left:auto;margin-right:auto;justify-self:stretch}

/* Une grille qui ne contient qu'une carte ne doit pas laisser un vide beant :
   on borne alors la largeur de la GRILLE elle-meme. */
.carte-grille.is-carre:has(> .carte:only-child){max-width:460px;margin-left:auto;margin-right:auto}
.carte-grille:not(.is-carre):has(> .carte:only-child){max-width:600px;margin-left:auto;margin-right:auto}
.entity-grid:has(> .entity-tile:only-child){max-width:760px;margin-left:auto;margin-right:auto}
/* ═══ LES GROUPES : BANDEAU + GRILLE ════════════════════════════════════════════
   Client : « la séparation sera par cette manière » (bulle rouge + traits), et
   « vert pour les actifs, gris pour les inactifs en bas ».
   Le bandeau vient de `bandHtml` : c'est le langage deja en place, donc rien de
   nouveau a comprendre — seule la teinte de la bulle change selon le groupe. */
.groupe{margin-bottom:6px}
.groupe .groupe-bande{margin:22px 0 12px}
/* Le groupe ACTIF : bulle verte. Le contraste est mesure (blanc sur #007d44 = 5.23:1). */
.groupe.groupe-actif .band-tag{background:#007d44;
  box-shadow:0 4px 14px -4px rgba(0,125,68,.5)}
/* Le groupe INACTIF : bulle grise, discrete — il ne doit pas attirer l'oeil. */
.groupe.groupe-inactif .band-tag{background:#78756f;
  box-shadow:0 4px 14px -4px rgba(120,117,111,.4)}
.groupe.groupe-inactif .carte{opacity:.88}
.groupe .carte-grille{margin-bottom:4px}
/* ═══ LE TEXTE DES BANDEAUX DE GROUPE ═══════════════════════════════════════════
   MESURE : le texte de la bulle sortait en rgb(22,22,22) — SOMBRE. Sur la bulle
   verte rgb(0,125,68), cela donne 2.4:1 : illisible. Il doit etre BLANC.
   Cause : une regle globale de texte sombre l'emportait sur le `.band-tag`.
   On force donc la couleur, sur les trois teintes de bulle. */
.groupe .band-tag, .groupe .band-tag b, .groupe .band-tag span{color:#fff}
/* Les traits du bandeau de groupe : visibles sur fond clair. */
.groupe .band-line{background:linear-gradient(90deg,rgba(23,23,25,.06),rgba(23,23,25,.34));
  box-shadow:none}
.groupe .band-line:last-child{background:linear-gradient(90deg,rgba(23,23,25,.34),rgba(23,23,25,.06))}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES WIDGETS PORTENT UNE ICÔNE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « il peut y avoir des icônes sur les widgets histoire de rendre la vue
   plus agréable ».

   On les pose en CSS, pas en JS : les widgets sont construits dans huit vues
   differentes, et une regle unique les couvre toutes. La POSITION du widget decide
   de son icone — un widget ajoute plus tard en recoit une sans rien changer.

   Aucun emoji (la charte pro les exclut) : ce sont des SVG inline, donc nets a
   toutes les tailles, et colores par la teinte du widget. */
#app > .tile-counts .tile-count,
.section-page > .tile-counts .tile-count{position:relative;padding-right:52px}
#app > .tile-counts .tile-count::after,
.section-page > .tile-counts .tile-count::after{
  content:"";position:absolute;top:50%;right:16px;transform:translateY(-50%);
  width:26px;height:26px;background-repeat:no-repeat;background-position:center;
  background-size:26px 26px;opacity:.85}

/* 1. Le total — un colis (le volume). */
#app > .tile-counts .tile-count:nth-child(1)::after,
.section-page > .tile-counts .tile-count:nth-child(1)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235f5c58' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z'/%3E%3Cpath d='M3.3 7 12 12l8.7-5'/%3E%3Cpath d='M12 22V12'/%3E%3C/svg%3E")}
/* 2. Les personnes. */
#app > .tile-counts .tile-count:nth-child(2)::after,
.section-page > .tile-counts .tile-count:nth-child(2)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b51f2a' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Cpath d='M9.5 7a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z'/%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87'/%3E%3C/svg%3E")}
/* 3. Les lieux. */
#app > .tile-counts .tile-count:nth-child(3)::after,
.section-page > .tile-counts .tile-count:nth-child(3)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a6110' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 21h18'/%3E%3Cpath d='M5 21V6l7-3 7 3v15'/%3E%3Cpath d='M9 21v-5h6v5'/%3E%3C/svg%3E")}
/* 4. Ce qui est en service / actif. */
#app > .tile-counts .tile-count:nth-child(4)::after,
.section-page > .tile-counts .tile-count:nth-child(4)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230a7d4f' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 11.1V12a10 10 0 1 1-5.9-9.1'/%3E%3Cpath d='M22 4 12 14.1l-3-3'/%3E%3C/svg%3E")}
/* 5. Les missions / événements. */
#app > .tile-counts .tile-count:nth-child(5)::after,
.section-page > .tile-counts .tile-count:nth-child(5)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a5762f' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2v4'/%3E%3Cpath d='M16 2v4'/%3E%3Cpath d='M3 10h18'/%3E%3Cpath d='M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z'/%3E%3C/svg%3E")}
/* Le 6e reprend la premiere icone : la serie tourne, donc aucun widget n'est nu. */
#app > .tile-counts .tile-count:nth-child(6)::after,
.section-page > .tile-counts .tile-count:nth-child(6)::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235f5c58' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z'/%3E%3Cpath d='M3.3 7 12 12l8.7-5'/%3E%3Cpath d='M12 22V12'/%3E%3C/svg%3E")}

/* Sur une page etroite, l'icone prend de la place : on la retire. */
@media (max-width:760px){
  #app > .tile-counts .tile-count,
  .section-page > .tile-counts .tile-count{padding-right:16px}
  #app > .tile-counts .tile-count::after,
  .section-page > .tile-counts .tile-count::after{display:none}
}
/* ═══ LA VUE TABLETTES : 4 PAR LIGNE, DES CARTES ETIREES ════════════════════════
   Client : « au lieu de 6, mets-en juste 4, comme ça on aura des cartes etirees ou
   on peut voir le nom du porteur ».
   On force donc 4 colonnes EXACTES (`repeat(4,1fr)`) au lieu d'un `auto-fit` qui en
   mettait 5 ou 6 : chaque carte s'elargit, et le nom du porteur tient en entier. */
#app #tablets-grid,
#app .carte-grille.is-tablettes{grid-template-columns:repeat(4,minmax(0,1fr))}
/* Le porteur et l'etat : deux lignes lisibles, jamais tronquees. */
#app .carte-grille.is-tablettes .carte-fait b{white-space:normal;overflow:visible;
  text-overflow:clip;line-height:1.25}
#app .carte-grille.is-tablettes .carte-fait small{white-space:normal;overflow:visible}
/* NOTE : la vue Tablettes n'utilise PLUS `grid-template-columns` — les grilles de
   cartes sont en FLEXBOX (voir la regle « DERNIERE LIGNE INCOMPLETE SE CENTRE »).
   La largeur de carte (264 px) est fixe pour toutes les vues, donc les anciennes
   regles `is-tablettes` a 4/3/2 colonnes sont sans effet. On les laisse : elles ne
   genent pas, et elles documentent l'intention d'origine.
   La carte tablette : un peu plus haute, pour loger le porteur sur deux lignes. */
#app .carte-grille.is-tablettes .carte{min-height:0}
/* En dessous de 1100 px, on retombe a 3 puis 2 colonnes : un telephone ne doit
   jamais recevoir 4 colonnes de 80 px. */
@media (max-width:1100px){
  #app #tablets-grid,
  #app .carte-grille.is-tablettes{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (max-width:820px){
  #app #tablets-grid,
  #app .carte-grille.is-tablettes{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA TUILE CAMPAGNE : PLUS LARGE, DES WIDGETS QUI RESPIRENT
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « élargis la carte de campagne la petite, les informations sont
   chevauchées… je veux des widgets avec des icônes pour que ça ressemble à un petit
   tableau de bord ».
   MESURÉ AVANT : 360x615 — la tuile restait etroite, et « collaboratrices » etait
   coupe dans un widget de 99 px. Le `max-width` precedent etait ecrase par une regle
   plus specifique. On l'impose donc, avec assez de place pour 4 widgets lisibles. */
.entity-grid .entity-tile{
  max-width:100%;width:100%;
  grid-column:1 / -1}            /* une tuile occupe TOUTE la ligne : elle respire */
/* Les widgets de la tuile : larges, alignes, avec la place pour une icone. */
.entity-tile .tile-counts{
  grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:14px 0 8px}
.entity-tile .tile-count{
  padding:13px 14px 12px 46px;border-radius:12px;min-height:66px;
  place-content:center;justify-items:start;text-align:left;
  position:relative;background:rgba(16,17,18,.5);
  border-color:rgba(255,255,255,.24)}
.entity-tile .tile-count strong{font-size:24px;line-height:1.05}
.entity-tile .tile-count span{
  font-size:9.5px;white-space:normal;line-height:1.2;letter-spacing:.06em;
  color:rgba(244,243,241,.82);text-align:left}
/* L'icone : a gauche du widget, comme un vrai tableau de bord. */
.entity-tile .tile-count::before{
  content:"";position:absolute;left:14px;top:50%;transform:translateY(-50%);
  width:22px;height:22px;background-repeat:no-repeat;background-position:center;
  background-size:22px 22px;opacity:.9;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2v4'/%3E%3Cpath d='M16 2v4'/%3E%3Cpath d='M3 10h18'/%3E%3Cpath d='M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z'/%3E%3C/svg%3E")}
/* Le 2e : un lieu. Le 3e : des personnes. Le 4e : un carton. */
.entity-tile .tile-count:nth-child(2)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 21h18'/%3E%3Cpath d='M5 21V6l7-3 7 3v15'/%3E%3Cpath d='M9 21v-5h6v5'/%3E%3C/svg%3E")}
.entity-tile .tile-count:nth-child(3)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Cpath d='M9.5 7a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z'/%3E%3C/svg%3E")}
.entity-tile .tile-count:nth-child(4)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12v9H4v-9'/%3E%3Cpath d='M2 7h20v5H2z'/%3E%3Cpath d='M12 22V7'/%3E%3C/svg%3E")}

/* Le bloc d'informations de la tuile : plus d'air, plus de chevauchement. */
.entity-tile .tile-infos{
  grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;margin-top:14px;
  padding-top:14px}
.entity-tile .tile-lignes{gap:8px}
.entity-tile .tile-ligne{font-size:12px;line-height:1.4}
.entity-tile .tile-stock-leg{gap:7px}
.entity-tile .tile-stock-leg span{font-size:11px;line-height:1.35}

/* ═══════════════════════════════════════════════════════════════════════════════
   LA FICHE CAMPAGNE : SES WIDGETS, PROPRES
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « la grande carte de campagne est devenue très moche. »
   MESURÉ : 3 widgets de 123x123 tasses dans un conteneur borne a 520 px, prevu pour
   4 colonnes. On elargit le conteneur et on donne aux widgets la meme forme qu'aux
   autres : larges, chiffre a gauche, libelle dessous. */
.profile-sheet .tile-counts{
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  max-width:none;margin:18px 32px 8px}
.profile-sheet .tile-count{
  aspect-ratio:auto;min-height:76px;padding:14px 16px;border-radius:12px;
  place-content:center;justify-items:start;text-align:left;gap:3px}
.profile-sheet .tile-count strong{font-size:28px;line-height:1}
.profile-sheet .tile-count span{font-size:10px;letter-spacing:.07em;text-align:left}
/* Les villes : des pastilles lisibles, jamais coupees. */
.entity-tile .tile-villes{gap:6px;margin-top:9px}
.entity-tile .tile-ville{font-size:10.5px;padding:4px 11px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE SHADER SUR LES CARTES BLANCHES — ENFIN VISIBLE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « pour les petites cartes en blanc — goodies, collaboratrices et tout —
   applique le shader comme dans la base de données, un peu plus clair, la même
   couleur. »

   MESURÉ AVANT : `.carte-shader` valait `opacity:.075` en `multiply`. Sur un fond
   blanc, 7,5 % de matière rouge ne se distinguent pas du blanc : le client ne voyait
   rien. On monte donc à `.17` — la matière apparaît franchement, mais reste claire :
   c'est une teinte, pas un voile.
   On multiplie (et non `screen`) pour que le rouge TEINTE le blanc au lieu de
   l'éblouir : sur fond clair, `screen` ne dessinerait rien du tout.

   NOTE : cette règle est posée EN DERNIER dans le fichier. Elle bat donc les
   déclarations antérieures — dont le `.075` — par simple ordre de cascade, sans
   avoir à débusquer les 15 règles `.carte` qui se superposent. */
.carte .carte-shader{opacity:.17;mix-blend-mode:multiply}

/* En thème clair, la fiche garde un filigrane discret (elle est grande : la matiere
   y serait trop presente). */
.profile-sheet.sheet-clair .carte-shader{opacity:.10}

/* ═══════════════════════════════════════════════════════════════════════════════
   LES WIDGETS DE LA GRANDE CARTE DE CAMPAGNE : ICONES + LARGEUR
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « ajuste-moi aussi les widgets de la carte en mode grand… la grande carte
   de campagne est devenue très moche. »

   MESURÉ AVANT : 3 compteurs carrés de 123×123 tassés dans 520 px, sans icône, alors
   que les autres fiches montrent 4 compteurs larges. On aligne la campagne sur le
   reste : widgets larges, icône à gauche, chiffre et libellé a gauche.
   On cible `#campaign-sheet` UNIQUEMENT : les 5 autres fiches ont été validées et ne
   doivent pas bouger. */
#campaign-sheet .tile-counts{
  grid-template-columns:repeat(auto-fit,minmax(168px,1fr));
  max-width:none;margin:20px 30px 10px;gap:12px}
#campaign-sheet .tile-count{
  aspect-ratio:auto;min-height:80px;padding:14px 18px 14px 54px;border-radius:12px;
  place-content:center;justify-items:start;text-align:left;gap:4px;position:relative}
#campaign-sheet .tile-count strong{font-size:29px;line-height:1}
#campaign-sheet .tile-count span{
  font-size:9.5px;letter-spacing:.06em;line-height:1.25;white-space:normal;text-align:left}
/* L'icone, a gauche, comme un tableau de bord. */
#campaign-sheet .tile-count::before{
  content:"";position:absolute;left:17px;top:50%;transform:translateY(-50%);
  width:24px;height:24px;background-repeat:no-repeat;background-position:center;
  background-size:24px 24px;opacity:.85;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12v9H4v-9'/%3E%3Cpath d='M2 7h20v5H2z'/%3E%3Cpath d='M12 22V7'/%3E%3C/svg%3E")}
#campaign-sheet .tile-count:nth-child(2)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 21h18'/%3E%3Cpath d='M5 21V6l7-3 7 3v15'/%3E%3Cpath d='M9 21v-5h6v5'/%3E%3C/svg%3E")}
#campaign-sheet .tile-count:nth-child(3)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Cpath d='M9.5 7a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z'/%3E%3C/svg%3E")}
#campaign-sheet .tile-count:nth-child(4)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f3f1' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12v9H4v-9'/%3E%3Cpath d='M2 7h20v5H2z'/%3E%3Cpath d='M12 22V7'/%3E%3C/svg%3E")}
/* ⚠️ LES ICÔNES DE LA FICHE CAMPAGNE SONT TOUJOURS INVERSÉES DEPUIS QUE LA SURFACE EST BLANCHE.
   MESURÉ : ces icônes sont des SVG BLANCS (`stroke='%23f4f3f1'`), dessinés pour un widget sombre.
   L'inversion n'était prévue QUE pour le thème clair. Mais la surface des widgets est maintenant
   un dégradé CLAIR DANS LES DEUX THÈMES (la recette du dashboard) : une icône blanche y serait
   invisible. On inverse donc TOUJOURS — le thème de la coque n'a plus d'incidence, puisque c'est
   le fond du widget qui décide de la lisibilité de son icône, pas celui de la fiche. */
.profile-sheet #campaign-sheet .tile-count::before{filter:invert(1);opacity:.6}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES SOUS-WIDGETS D'UNE CAMPAGNE — enfin dessines
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « les sous-widgets n'apparaissent pas au niveau des tris par campagne des
   goodies. »
   MESURÉ : le HTML etait bien produit, mais `.sous-widgets` et `.sw` n'avaient AUCUNE
   regle CSS. Six <span> nus, donc invisibles en pratique.
   Chaque sous-widget porte sa pastille d'icone colorée : on lit la nature du chiffre
   avant de lire le chiffre. */
.sous-widgets{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0 18px}
.sous-widgets .sw{
  position:relative;flex:1 1 158px;min-height:74px;display:flex;flex-direction:column;
  justify-content:center;gap:2px;padding:12px 44px 12px 54px;border-radius:12px;
  background:#fff;border:1px solid rgba(23,23,25,.13);
  box-shadow:0 1px 2px rgba(23,23,25,.04)}
.sous-widgets .sw strong{font-size:26px;line-height:1;color:#171719;font-weight:600}
.sous-widgets .sw small{
  font-size:8.5px;letter-spacing:.07em;text-transform:uppercase;color:#383633;
  line-height:1.25;font-weight:500}
.sous-widgets .sw i{
  position:absolute;right:12px;top:50%;transform:translateY(-50%);
  font-size:11px;font-style:normal;font-weight:600;color:#8a8781}
.sous-widgets .sw::before{
  content:"";position:absolute;left:14px;top:50%;transform:translateY(-50%);
  width:26px;height:26px;border-radius:8px;background-repeat:no-repeat;
  background-position:center;background-size:15px 15px;background-color:#161616;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12v9H4v-9'/%3E%3Cpath d='M2 7h20v5H2z'/%3E%3Cpath d='M12 22V7'/%3E%3C/svg%3E")}
/* L'article : un carton. */
.sous-widgets .sw[data-teinte="neutre"]::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12v9H4v-9'/%3E%3Cpath d='M2 7h20v5H2z'/%3E%3Cpath d='M12 22V7'/%3E%3C/svg%3E")}
/* Le recu : une fleche vers le bas. */
.sous-widgets .sw[data-teinte="or"]::before{
  background-color:#a5762f;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v13'/%3E%3Cpath d='M6 11l6 6 6-6'/%3E%3Cpath d='M4 21h16'/%3E%3C/svg%3E")}
/* Le depot : un entrepot. */
.sous-widgets .sw[data-teinte="gris"]::before{
  background-color:#78756f;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 21h18'/%3E%3Cpath d='M5 21V6l7-3 7 3v15'/%3E%3Cpath d='M9 21v-5h6v5'/%3E%3C/svg%3E")}
/* Les distributeurs : un groupe. */
.sous-widgets .sw[data-teinte="rouge"]::before{
  background-color:#961c26;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Cpath d='M9.5 7a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z'/%3E%3C/svg%3E")}
/* Le remis : une coche. */
.sous-widgets .sw[data-teinte="vert"]::before{
  background-color:#0a7d4f;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E")}
/* La collaboration : une personne seule, pour la distinguer du groupe. */
.sous-widgets .sw:nth-child(5)::before{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Cpath d='M12 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8z'/%3E%3C/svg%3E")}

/* En theme clair, la matiere rouge reste discrète : ces widgets sont blancs, ils
   n'ont pas besoin d'etre teintes davantage. */

/* ═══════════════════════════════════════════════════════════════════════════════
   LA BANDE DE FAITS DE LA TUILE CAMPAGNE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « une liste de texte et de chiffre moche ». L'ancienne liste empilait 6
   lignes de 12 px, chacune « libelle ..... valeur ». On la remplace par des faits
   alignes horizontalement : le chiffre en gros, le libelle dessous, comme des
   pastilles. */
.entity-tile .tile-faits{
  display:flex;flex-wrap:wrap;gap:8px;margin:12px 0 4px}
.entity-tile .tile-fait{
  display:flex;flex-direction:column;gap:1px;padding:9px 15px;border-radius:9px;
  background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.20)}
.entity-tile .tile-fait b{font-size:17px;line-height:1;color:#fff;font-weight:600}
.entity-tile .tile-fait small{
  font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;
  color:rgba(244,243,241,.78);font-weight:500}
/* Le bloc titre : plus discret, il n'est plus un titre de colonne. */
.entity-tile .tile-infos{display:block;margin-top:6px;padding-top:0;border-top:0}
.entity-tile .bloc-titre{
  font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(244,243,241,.62);margin-bottom:8px}
/* La barre de progression de la campagne : elle reste, c'est la duree, pas le stock. */
.entity-tile .tile-prog{margin-top:12px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES WIDGETS DE GOODIES DE LA TUILE — EN BAS DE CARTE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « puis les widgets de goodie en bas : 330 reçus, dans le dépôt, sur
   terrain, et gagné. »
   Ils suivent le parcours du stock : reçu -> dépôt -> terrain -> gagné. Une bordure
   haute et un titre les separent des faits du terrain (evenements, lieux, equipe,
   villes) : on voit d'un coup d'oeil que le sujet change. */
.entity-tile .tile-goodies{
  margin-top:14px;padding-top:12px;border-top:1px solid rgba(255,255,255,.16)}
.entity-tile .tile-goodies-tete{
  font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(244,243,241,.6);margin-bottom:8px}
.entity-tile .tile-goodies-row{display:flex;gap:8px;flex-wrap:wrap}
.entity-tile .tg{
  flex:1 1 84px;display:flex;flex-direction:column;gap:1px;
  padding:9px 13px;border-radius:9px;
  background:rgba(16,17,18,.42);border:1px solid rgba(255,255,255,.20)}
.entity-tile .tg b{font-size:18px;line-height:1;color:#fff;font-weight:600}
.entity-tile .tg small{
  font-size:8.5px;letter-spacing:.05em;text-transform:uppercase;
  color:rgba(244,243,241,.76);font-weight:500}
.entity-tile .tg.is-zero b{color:rgba(244,243,241,.5)}

/* En theme clair : ces widgets vivent sur une tuile sombre (la photo), on n'y touche
   donc pas. */
/* ═══════════════════════════════════════════════════════════════════════════════
   UNE CARTE GARDERA LA MEME TAILLE DANS TOUS LES GROUPES
   ═══════════════════════════════════════════════════════════════════════════════
   MESURÉ AVANT — sur une seule page, trois tailles de carte :
     groupe de 3 cartes → 357×313 (ratio 1.14)
     groupe de 4 cartes → 265×313 (ratio 0.85)
     groupe de 2 cartes → 420×313 (ratio 1.34)

   CAUSE : `auto-fit` EFFONDRE les pistes vides. Chaque groupe ayant sa propre grille,
   chacun calcule un nombre de colonnes différent — donc une largeur différente.

   CORRECTIF : `auto-fill`, qui CONSERVE les pistes vides. Le nombre de colonnes ne
   dépend plus du nombre de cartes : toutes les cartes de tous les groupes ont la même
   largeur. Vaut pour les 3 vues qui groupent (Établissements, Dashboard,
   Collaboratrices) — les grilles hors groupe ne sont pas touchées. */
.groupe .carte-grille.is-carre{
  grid-template-columns:repeat(auto-fill,minmax(228px,330px));justify-content:start}
/* Et une carte seule ne s'étire jamais, même quand elle est seule dans son groupe. */
.groupe .carte-grille.is-carre .carte{max-width:330px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE BADGE VERT PASSE LE SEUIL DE CONTRASTE
   ═══════════════════════════════════════════════════════════════════════════════
   MESURÉ (alpha composité sur le fond réel — sans ça on compare du vert à du vert) :
     « EN CAMPAGNE »  rgb(43,138,97) sur rgb(227,240,234)  →  3.65:1   FAIBLE
     « EN RÉSERVE »   rgb(111,108,103) sur rgb(242,242,242) →  4.68:1   OK
   Seuil WCAG AA pour du petit texte : 4.5:1.

   Le vert `#2b8a61` est trop clair sur un fond teinté à 12 % de lui-même. On garde la
   TEINTE — l'œil doit toujours lire « vert = actif » — et on assombrit le texte.
   Concerne 6 vues : Établissements, Stock, Collaboratrices, Entreprises, Responsables,
   Tablettes. */
.carte-badge.connected{background:rgba(28,107,73,.14);color:#1c6b49}
/* Le badge gris « inactif » passe déjà (4.68:1), on le laisse tel quel. */
/* Le badge GRIS : 4.68:1 passait AA de justesse, mais c'est du PETIT texte sur une carte
   qu'on lit en un coup d'oeil. On l'assombrit pour un confort reel (mesure apres : ~5.9:1).
   Meme logique que le badge vert : on garde la teinte, on gagne du contraste. */
.carte-badge{color:#5f5c58}
/* ═══════════════════════════════════════════════════════════════════════════════
   UN GROUPE DE CAMPAGNES DANS UNE FICHE
   ═══════════════════════════════════════════════════════════════════════════════
   L'historique d'un lieu est decoupe par PHASE (En cours / A venir / Complete /
   Brouillon), chacune avec son bandeau bulle + traits. On donne a chaque groupe un peu
   d'air, et on separe ses lignes : sans cela les groupes se collent et le bandeau ne
   structure plus rien. */
.sheet-groupe{margin-bottom:16px}
.sheet-groupe:last-child{margin-bottom:0}
.sheet-groupe .band{margin-bottom:8px}
.sheet-groupe .sheet-list-row{border-top:1px solid rgba(23,23,25,.08)}
.sheet-groupe .sheet-list-row:first-of-type{border-top:0}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE NOM DE LA CAMPAGNE, ALIGNE A DROITE (VUE TABLETTES)
   ═══════════════════════════════════════════════════════════════════════════════
   MESURE AVANT : la bulle etait CENTREE (16 px d'ecart de chaque cote). Client :
   « le nom de la campagne doit etre centre par a droite » — donc a DROITE. La carte est
   une grille `justify-items:center` : on deplace donc la bulle seule avec `justify-self`.
   On garde le TEXTE centre dans la bulle (il l'est deja) — seule sa POSITION change.
   Cible : la vue Tablettes uniquement, ou la bulle est descendue sous le trait. */
/* Client : « maintenant tu la mets au centre ». `center` rend AUSSI la bulle compacte :
   elle prend la largeur de son texte (67 px) au lieu de s'étirer sur 232 px. C'est le
   rendu d'une pastille, et c'est celui qu'on veut pour un nom de campagne. */
#app .carte-grille.is-tablettes .carte > .carte-badge{justify-self:center}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA PASTILLE DE CAMPAGNE : UN TEXTE PLUS LISIBLE
   ═══════════════════════════════════════════════════════════════════════════════
   Le contraste est BON (mesure : 5.26:1, seuil 4.5) mais la pastille se lisait mal :
   son texte fait 9.5 px, et en monospace etroit a cette taille il parait delave.
   On l'agrandit done SANS toucher aux couleurs validees — c'est la taille qui etait
   en cause, pas le contraste. La pastille s'elargit d'elle-meme (elle est compacte). */
#app .carte-grille.is-tablettes .carte > .carte-badge{
  font-size:11px;letter-spacing:.04em;padding:5px 12px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA PHOTO DE LA TABLETTE : AUCUN FOND CARRE, JUSTE UNE OMBRE DOUCE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « insère-moi la photo de la tablette en PNG sans le fond carré derrière,
   juste avec un effet faible d'ombrage. »

   Le portrait d'une carte est un carre de 84x84 avec un fond gris (`background:#f1efec`).
   Pour une tablette, ce carre est un CADRE : il faut le retirer. On rend donc le fond
   transparent et on laisse l'image s'afficher en `contain` (entiere, jamais rognee).

   L'OMBRE : `filter: drop-shadow` et NON `box-shadow`. `box-shadow` dessine le rectangle
   de l'element ; `drop-shadow` suit la SILHOUETTE reelle du PNG detoure — c'est la seule
   facon d'avoir une ombre qui epouse la tablette et non son cadre. Faible : 0.28, floue,
   legerement vers le bas. */
#app .carte-grille.is-tablettes .carte-portrait{
  background:transparent;border:0;box-shadow:none}
#app .carte-grille.is-tablettes .carte-portrait .carte-av{
  width:52px;height:66px;border-radius:0;background:transparent;border:0;
  overflow:visible;
  filter:drop-shadow(0 3px 7px rgba(0,0,0,.28))}
#app .carte-grille.is-tablettes .carte-portrait .carte-av img{
  width:100%;height:100%;object-fit:contain;object-position:center}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES CARTES SE CENTRENT QUAND LA LIGNE N'EST PAS PLEINE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « quand il y a plus de carte à droite, ne laisse pas plus de vide à droite.
   Toujours centrer les cartes quand elles sont peu dans la ligne. »

   MESURÉ AVANT (dernière ligne de chaque grille, vide de chaque côté) :
     Responsables     2 cartes  ->   0 px | 424 px
     Collaboratrices  2 cartes  ->   0 px | 424 px
     Stock            3 cartes  ->   0 px | 278 px
     Tablettes        3 cartes  ->   0 px | 278 px

   CAUSE : `justify-content: normal` (= start) : les pistes vides restent à droite, donc
   tout le vide s'y accumule. `center` répartit le vide également des deux côtés.

   Établissements et Compagnie étaient DÉJÀ centrés (0/0 et 20/20) : on n'y touche pas.
   Les grilles de groupe (`.groupe .carte-grille`) et les grilles de tableau de bord
   sont donc ciblées séparément. */
/* MESURE APRES `justify-content:center` SEUL : les lignes COMPLETES se centrent, mais
   une ligne INCOMPLETE reste collee a gauche (41 px | 384 px). En cause :
   `justify-content` centre le BLOC de colonnes, pas la derniere ligne.
   On passe donc a `auto-fit` avec une piste FIXE : auto-fit EFFONDRE les pistes vides,
   donc une derniere ligne de 2 cartes ne cree que 2 pistes — et le centrage agit
   vraiment. La piste FIXE (ni `minmax`, ni `1fr`) garantit que toutes les cartes font
   exactement la meme largeur, quel que soit le groupe. */
/* ═══════════════════════════════════════════════════════════════════════════════
   LA DERNIERE LIGNE INCOMPLETE SE CENTRE — FLEXBOX, PAS GRID
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « quand il y a plus de carte à droite, ne laisse pas plus de vide à droite.
   Toujours centrer les cartes quand elles sont peu dans la ligne. »

   POURQUOI GRID NE POUVAIT PAS LE FAIRE — mesuré après `justify-content:center` :
     6 cartes sur 4 colonnes -> 4 + 2 ; les 2 dernières démarrent à la COLONNE 1
       -> 1 px à gauche  |  555 px à droite
   `justify-content` centre le BLOC DE COLONNES, jamais la dernière ligne incomplète.
   Aucune combinaison de `auto-fit`/`auto-fill` ni de `minmax` n'y change rien : dans
   une grille, les éléments d'une même ligne sont TOUJOURS rangés depuis la 1re piste.

   FLEXBOX le fait nativement : `flex-wrap` + `justify-content:center` centre CHAQUE
   ligne, pleine ou non. Mesuré après : 139/139, 278/278, 417/417 — toutes centrées.
   On garde une largeur FIXE par carte (264 px, ni `1fr` ni `%`) pour que toutes les
   cartes aient exactement la même taille quel que soit le groupe. */
/* Client : « quand il y a plus de carte à droite, ne laisse pas plus de vide
   à droite. Toujours centrer les cartes quand elles sont peu dans la ligne. »
   Le sélecteur vise TOUTE `.carte-grille`, sans exception de conteneur. Avant,
   il n'en visait que dans `#app` et `.section-page` : les règles plus bas
   (`justify-content:start` sur `.carte-grille` et `.groupe .carte-grille.is-carre`)
   reprenaient la main dans tout autre conteneur (une fiche, un onglet à venir)
   et les cartes repartaient à gauche. */
.carte-grille{
  display:flex !important;
  flex-wrap:wrap;
  justify-content:center !important;
  align-content:start;
  align-items:stretch;
  gap:13px}
#app .carte-grille > .carte{
  flex:0 0 264px;
  width:264px;
  max-width:264px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES IMAGES PAR DEFAUT — AUCUN FOND CARRE, UNE OMBRE DOUCE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « au lieu d'avoir les initiales quand on n'a pas sa photo, on aura la photo
   d'hôtesse par exemple — comme celle de l'iPad standard, applicable sur toutes les
   hôtesses — dans images. »

   Le portrait d'une carte est un carré de 84x84 avec un fond gris (`#f1efec`) et une
   bordure. Pour une illustration DÉTOURÉE, ce carré est un CADRE : le client l'a refusé
   pour la tablette, on applique donc la même recette ici.

   `filter: drop-shadow` et non `box-shadow` : `box-shadow` dessine le RECTANGLE de
   l'élément, `drop-shadow` suit la SILHOUETTE réelle du PNG détouré. */
.carte-av.is-defaut{
  background:transparent;border:0;overflow:visible;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.22))}
.carte-av.is-defaut img,
.carte-av img.avatar-defaut{
  width:100%;height:100%;object-fit:contain;object-position:center}
/* En thème clair, l'ombre reste discrète : ces cartes sont déjà claires. */
.profile-sheet.sheet-clair .carte-av.is-defaut{filter:drop-shadow(0 2px 5px rgba(0,0,0,.16))}
/* ═══════════════════════════════════════════════════════════════════════════════
   LES SYMBOLES DE PORTEUR — HÔTESSE ET DISTRIBUTEUR
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « rajoute-moi des photos ou icônes sur hôtesse et distributeur comme tu as
   fait sur tablette, avec des photos symboliques. »

   Deux endroits, deux tailles :
   - `.fait-img` : le symbole DANS un fait de carte (« Responsable · Hicham Bennani »).
     Petit, carré de 30 px, aligné à gauche du couple étiquette/valeur.
   - `.sheet-porteur-img` : le symbole dans la ligne « Porteur » d'une fiche. Plus grand,
     38 px, aligné avec le texte de la ligne.

   Mêmes règles que les autres défauts : aucun fond carré, l'ombre suit la silhouette
   (`drop-shadow`, pas `box-shadow`), et l'image est en `contain` pour ne jamais se
   déformer. */
.fait-img{
  width:30px;height:30px;flex:0 0 30px;object-fit:contain;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.20))}
/* Un fait qui porte un symbole : l'image à GAUCHE, et le nom AU-DESSUS de l'étiquette
   dans une COLONNE à droite. Sans cette colonne, nom et étiquette se disputent la même
   ligne flex : l'étiquette (« RESPONSABLE », en nowrap) ne cède pas et le nom s'écrase
   à ~11 px — MESURÉ — puis se casse lettre par lettre. La colonne supprime la
   concurrence : chacun a sa largeur.
   Le fait occupe TOUTE la largeur de la carte : c'est la seule information de la carte
   tablette, elle n'a pas à être comprimée dans une demi-colonne de grille. */
.carte-fait.is-img{
  display:flex;flex-direction:row;align-items:center;gap:10px;
  text-align:left;justify-content:flex-start;
  grid-column:1/-1}
.fait-txt{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1 1 auto}
.carte-fait.is-img b{
  white-space:normal;overflow:visible;text-overflow:clip;
  /* `break-word` et non `anywhere` : un mot ne se coupe QUE s'il ne peut pas tenir
     seul. « Hicham Bennani » se replie sur l'espace, jamais au milieu d'un prénom. */
  overflow-wrap:break-word;line-height:1.2}
.carte-fait.is-img small{
  white-space:normal;overflow:visible;text-overflow:clip}

.sheet-porteur-img{
  width:38px;height:38px;flex:0 0 38px;object-fit:contain;margin-right:10px;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,.20))}
/* Le symbole et le nom s'alignent : on ne veut pas que le badge et les boutons
   passent SOUS l'image. */
.sheet-list-row:has(.sheet-porteur-img){align-items:center}

/* En thème CLAIR, l'ombre est plus discrète : ces lignes sont claires. */
.profile-sheet.sheet-clair .sheet-porteur-img,
.profile-sheet.sheet-clair .fait-img{filter:drop-shadow(0 1px 3px rgba(0,0,0,.15))}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE PORTRAIT D'UNE FICHE SANS PHOTO — LE SYMBOLE DU TYPE
   ═══════════════════════════════════════════════════════════════════════════════
   `imagePickerHtml` sert aux 6 fiches. Son repli était des INITIALES ; c'est maintenant
   le symbole du type (hôtesse, distributeur, établissement, entreprise, goodie, tablette).
   Client : « au lieu d'avoir les initiales quand on n'a pas sa photo, on aura la photo
   d'hôtesse par exemple — comme celle de l'iPad standard. » */
.image-pick-defaut{
  width:100%;height:100%;object-fit:contain;object-position:center;
  filter:drop-shadow(0 3px 7px rgba(0,0,0,.22))}
/* La zone du portrait perd son fond gris quand elle porte un symbole détouré : un carré
   gris autour d'une illustration détourée, c'est le « cadre » que le client a refusé
   pour la tablette. On suit la même recette : fond transparent, pas de bordure. */
.image-pick:has(.image-pick-defaut){
  background:transparent;border:0;overflow:visible}
.image-pick:has(.image-pick-defaut)::after{display:none}
/* Le `+` des types sans symbole garde son cadre : c'est un bouton d'action, pas un
   portrait. Rien ne change pour lui. */
/* ═══════════════════════════════════════════════════════════════════════════════
   L'ESPACE ENTRE L'iPAD ET LE TITRE ROUGE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « l'image de l'icône est un peu collée sur le nom dans la bulle en rouge. Il
   faut juste un peu décaler pour qu'on puisse voir un espace entre l'icône PNG et le
   titre de la tablette numéro. »

   STRUCTURE : le PNG de l'iPad (`.carte-av`) et le titre rouge « Tablette N »
   (`.carte-band-tag`) vivent dans `.carte-portrait`, séparés par `gap:7px`. Mesuré :
   trop court. On l'ouvre à 15 px, et on ajoute une marge au titre pour que la bulle
   rouge ne colle jamais à l'image, même quand le portrait est détouré (sans cadre). */
#app .carte-grille.is-tablettes .carte-portrait{gap:15px}
#app .carte-grille.is-tablettes .carte-band{margin-top:2px}
/* Le portrait d'une tablette respire aussi en haut : le PNG ne touche pas le bord. */
#app .carte-grille.is-tablettes .carte-portrait{padding-top:8px}

/* ═══════════════════════════════════════════════════════════════════════════════
   LA CARTE D'UNE PERSONNE — LA VILLE NE DÉBORDE PLUS ET NE SAUTE PLUS SUR LE PROFIL
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « à côté de la ville, la bulle de la ville déborde de la carte. Il vaut mieux
   allonger la carte pour que la ville ne sorte pas du cadre de la carte et ne saute pas
   sur le profil qui est plus en bas. »

   Deux protections : les faits ne peuvent plus sortir de la carte (une valeur longue se
   replie au lieu de pousser), et la carte garde une hauteur minimale pour que les faits
   aient toujours leur place sous le profil. */
.carte-grille.is-carre .carte{min-height:auto}
.carte-faits{gap:7px 8px}
.carte-fait{overflow:hidden}
.carte-fait b,.carte-fait small{overflow-wrap:anywhere;word-break:normal}
/* Sur une carte SOMBRE de la base (toutes les personnes), le libellé « VILLE » est en
   monospace 9 px : un nom de ville long n'a pas la place de tenir en nowrap. On autorise
   le repli pour que RIEN ne sorte du cadre. */
.carte .carte-fait small{white-space:normal;overflow:visible;text-overflow:clip}
.carte .carte-fait b{white-space:normal;overflow:visible;text-overflow:clip}
/* ═══════════════════════════════════════════════════════════════════════════════
   L'ICÔNE D'UNE CARTE-LOGO — PLUS PETITE, ET DANS SA BOÎTE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « ajuste la taille de l'icône et rends-la plus petite en haut de Heineken ».

   MESURÉ AVANT (carte Heineken) :
     .carte-av           200 x  74      <- la boîte prévue
     l'image dedans      200 x 240      <- sa taille NATURELLE, elle débordait de 166 px
     l'image s'étendait de y=440 à y=680, le titre rouge était à y=521
     -> la bulle rouge « Heineken » passait PAR-DESSUS l'icône

   La cause : la règle ne donnait que `object-fit:contain`, sans largeur ni hauteur.
   Une image sans dimension imposée prend sa taille naturelle et sort de sa boîte.

   Le correctif fait les DEUX choses demandées : l'image remplit exactement sa boîte
   (`width/height:100%`), et la boîte devient plus petite (52 px au lieu de 74). Le
   portrait garde un vrai espace avant le titre. */
.carte-logo .carte-portrait{
  gap:12px;
  padding-top:6px;
  padding-bottom:4px}
.carte-logo .carte-portrait .carte-av{
  width:auto;
  max-width:104px;
  height:44px;
  border-radius:4px;
  background:transparent;
  border:0;
  overflow:visible;
  /* Une ombre douce : l'illustration est détourée, on suit sa silhouette. */
  filter:drop-shadow(0 2px 5px rgba(0,0,0,.18))}
/* `.carte-av` est un <span> : une hauteur en POURCENTAGE ne s'y résout pas (elle
   retombe sur `auto`, donc sur la taille naturelle de l'image). MESURÉ trois fois :
   240 px, puis 154, puis 125 dans une boîte de 44. La seule forme qui ne peut pas
   être mal résolue est une hauteur EXPLICITE. */
.carte-logo .carte-portrait .carte-av{display:block}
.carte-logo .carte-portrait .carte-av img{
  height:44px !important;
  width:auto !important;
  max-height:44px !important;
  max-width:104px !important;
  object-fit:contain !important;
  object-position:center}
/* Une carte-logo n'a pas besoin de la hauteur des autres : elle est plus compacte. */
.carte-grille.is-carre .carte-logo{min-height:0}
/* La pastille d'état (« ACTIVE ») collait au titre rouge « Heineken ». On lui donne un
   peu d'air SOUS elle : la composition se lit mieux, sans rien déplacer au-dessus. */
.carte-logo .carte-portrait .carte-badge{margin-bottom:8px}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE WIDGET « VILLES » D'UNE FICHE — UN TEXTE LONG, DONC PLUS PETIT
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « dans la fiche entreprise, ville rends-les de petite taille. »
   Le widget est un carré de 123x123 : il peut porter un NOMBRE (grand) ou des NOMS de
   villes (long). Un nom de ville n'a pas à crier comme un chiffre. On réduit donc la
   valeur quand elle est textuelle — le libellé « VILLES » garde sa taille. */

/* ═══════════════════════════════════════════════════════════════════════════════
   LE PORTRAIT D'UNE FICHE ENTREPRISE — LE LOGO EST TROP PETIT
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « le logo des fiches entreprise est très petit ». La zone du logo est large
   et plate (`is-logo`), et le logo s'y dessine en `contain` : une image étroite occupe
   donc une petite portion. On élargit la zone de dessin pour que le logo prenne sa
   place, sans le déformer (toujours `contain`). */
.profile-sheet .sheet-portrait.is-logo{max-width:230px}
.profile-sheet .sheet-portrait.is-logo .sheet-avatar-slot,
.profile-sheet .sheet-portrait.is-logo .image-pick{height:96px}
.profile-sheet .sheet-portrait.is-logo .image-pick img,
.profile-sheet .sheet-portrait.is-logo .sheet-avatar-slot img{
  max-height:96px;
  max-width:226px}
/* ═══════════════════════════════════════════════════════════════════════════════
   UNE VALEUR DE FAIT QUI EST UN TEXTE — PLUS PETITE QUE LES CHIFFRES
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « dans la fiche entreprise, ville, rends-les de petite taille. »
   Le fait « villes » porte des NOMS (« Agadir, Casablanca… »), pas un chiffre. À 14 px
   gras, un nom de ville se lit comme un titre et déborde. La classe `is-texte` (déclarée
   par la vue) le ramène au rang d'information secondaire. */
.carte-fait.is-texte b{
  font-size:11px !important;
  font-weight:600;
  line-height:1.3;
  color:#cfccc7}
/* Sur une carte CLAIRE, la même valeur en sombre lisible. */
.carte-grille:not(.is-carre) .carte-fait.is-texte b,
.profile-sheet .carte-fait.is-texte b{color:#5f5c58}

/* ═══════════════════════════════════════════════════════════════════════════════
   LE LOGO D'UNE CARTE-LOGO : UNE TAILLE MOYENNE
   ═══════════════════════════════════════════════════════════════════════════════
   Historique mesuré de cette icône :
     200 x 240  -> débordait de 166 px et passait SOUS la bulle rouge
      37 x  44  -> plus de débordement, mais « le logo est très petit » (client)
   On choisit donc une taille MOYENNE, qui ne déborde pas et qui se voit : 64 px de haut,
   jusqu'à 150 px de large pour un logo rectangulaire. La hauteur reste EXPLICITE —
   sur un <span>, un pourcentage ne se résout pas. */
.carte-logo .carte-portrait .carte-av{height:64px;max-width:150px}
.carte-logo .carte-portrait .carte-av img{
  height:64px !important;
  width:auto !important;
  max-height:64px !important;
  max-width:150px !important}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA RANGÉE D'ACTIONS D'UNE CARTE — CENTRÉE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « le bouton gérer l'équipe doit être centré aussi ». Mesuré : il était collé
   à gauche du bas de la carte. Le bouton est seul dans sa rangée : le centrer ne change
   rien d'autre que sa position. */
.carte-actions{
  display:flex;
  justify-content:center !important;
  align-items:center;
  flex-wrap:wrap;
  gap:8px}
.carte-actions .text-button{text-align:center}
/* ═══════════════════════════════════════════════════════════════════════════════
   L'AVATAR D'UNE CARTE — PLUS PETIT, ET DANS SA BOÎTE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « plus petit, ça doit pas monter sur la bulle de En cours. »

   MESURÉ : la boîte `.carte-av` fait 84 x 84, mais l'image dedans fait 84 x 101 —
   elle débordait de 17 px et montait de 10 px sur la bulle « EN COURS ».

   CAUSE : `.carte-av img` porte `height:100%`, et `.carte-av` est un <span> EN LIGNE.
   Un pourcentage de hauteur ne se résout pas sur un élément en ligne : il retombe sur
   `auto`, c'est-à-dire la hauteur NATURELLE du fichier (240 px de source).

   REMÈDE : une hauteur EXPLICITE en pixels — la seule forme qui ne peut pas être mal
   résolue — et une taille réduite (78 px) comme le client le demande. */
.carte-portrait .carte-av{display:block}
.carte-portrait .carte-av img{
  height:78px !important;
  width:78px !important;
  max-height:78px !important;
  max-width:78px !important;
  object-fit:contain !important;
  object-position:center}
/* Et la boîte suit : 78 px, pas 84. */
.carte-portrait .carte-av{width:78px;height:78px}
/* Un peu d'air entre l'avatar et la pastille d'état, pour que rien ne se touche. */
.carte-portrait{gap:9px}
/* ═══════════════════════════════════════════════════════════════════════════════
   L'ICÔNE PARAISSAIT PENCHÉE VERS LA GAUCHE — deux causes
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « je sens que les icônes ne sont pas centrées, elles sont parfois légèrement
   penchées vers la gauche. »

   MESURÉ, deux causes indépendantes :

   1) LE CADRE ÉTAIT DÉCALÉ DE 7 px VERS LA GAUCHE.
      `sheet-avatar-slot` : 134 px (centre 713)   `image-pick` : 120 px (centre 706)
      Le cadre est un `<button>` — un élément EN LIGNE. En ligne, un bouton s'aligne sur
      la ligne de base du texte : dans un conteneur plus large que lui, il se décale.
      -> `display:block` + `margin:0 auto` : le centrage ne dépend plus de la ligne.

   2) L'IMAGE ÉTAIT ROGNÉE PAR `object-fit:cover`.
      Le cadre carré de 120x120 + un avatar de 200x240 : `cover` REMPLIT le carré en
      COUPANT ~25 px de chaque côté. L'icône du client a les cheveux plus larges d'un
      côté : le VISAGE se retrouve donc décalé, tout en restant centré dans le cadre.
      -> `contain` : l'icône entière est visible, jamais rognée. C'est un avatar, pas une
         photo à recadrer. */
.sheet-portrait .image-pick{display:block;margin:0 auto}
.sheet-portrait .sheet-avatar-slot{display:flex;justify-content:center;width:100%}
.sheet-portrait .image-pick img{
  object-fit:contain !important;
  object-position:center !important;
  padding:0}
/* Le portrait d'une carte : même règle (l'avatar n'est jamais rogné). */
.carte-portrait .carte-av img{object-fit:contain !important;object-position:center !important}
/* Dans les mini-cartes et les puces (petites vignettes rondes), on garde `cover` : à
   40 px, rogner un avatar détouré le rendrait illisible. Rien ne change pour elles. */
/* ═══════════════════════════════════════════════════════════════════════════════
   LE CADRE DU PORTRAIT — PLUS AUCUN DÉCALAGE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « je sens que les icônes ne sont pas centrées, elles sont parfois légèrement
   penchées vers la gauche. »

   MESURÉ, la chaîne complète :
     .image-pick        120 px  centre 706
     .image-field       134 px  gauche 646   <- déjà décalé
     .sheet-avatar-slot 1200 px gauche 113   (justify center : le slot centre bien)
     .sheet-portrait    1264 px gauche  81   padding 30px 32px 0px

   DEUX CAUSES :
     1. `.sheet-portrait{ padding:30px 32px 0px }` : 32 px à GAUCHE, 0 à DROITE. La zone
        utile est donc décentrée de 32 px, et tout ce qui est centré dedans hérite du
        décalage.
     2. `.image-field{ margin:4px 0px 0px }` : la marge ne porte que sur le HAUT, mais le
        champ (134 px) est plus large que le cadre (120 px) — les 14 px restants ne sont
        pas répartis symétriquement.

   On symétrise le padding, on remet le champ dans l'axe, et on garantit que le cadre
   ne peut plus dépasser sa zone. */
.sheet-portrait{padding:30px 32px 0}
/* Le champ du portrait : centré, sans marge parasite. */
.sheet-portrait .image-field{margin:4px auto 0;width:120px;max-width:100%}
.sheet-portrait .image-field-row{justify-content:center;align-items:center}
.sheet-portrait .image-field-row > .image-pick{margin:0 auto}
/* Le cadre : il se centre tout seul, quelle que soit la largeur de son parent. */
.sheet-avatar-slot{display:flex;justify-content:center;align-items:center;width:100%}
.sheet-avatar-slot .image-pick{margin:0 auto;flex:none}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE DERNIER DÉCALAGE — `justify-content` SANS `display:flex` NE FAIT RIEN
   ═══════════════════════════════════════════════════════════════════════════════
   MESURE après le correctif précédent :
     .image-field  120 px   gauche 653   (bien centré : marges 540 / 540)
     .image-pick   120 px   gauche 646   <- 7 px À GAUCHE de son propre champ
   Le cadre est un `<button>` : un élément EN LIGNE. Dans un parent qui n'est pas flex,
   il s'aligne sur la ligne de base du texte — d'où le décalage. Et ma règle
   `justify-content:center` ne servait à rien : sans `display:flex`, cette propriété est
   IGNORÉE. Le remède : poser le flex ET le bloc.
   (C'est la 3e fois aujourd'hui que ce schéma apparaît : une propriété de centrage
   écrite sur un conteneur qui n'est pas de type flex.) */
.sheet-portrait .image-field,
.sheet-portrait .image-field-row{display:flex;flex-wrap:wrap;justify-content:center;align-items:center}
.sheet-portrait .image-field{flex-direction:column;gap:4px}
.sheet-portrait .image-pick{display:block;margin:0;flex:none}
/* Et le cadre reste centré quelle que soit la largeur du parent. */
.sheet-avatar-slot .image-pick{display:block;margin:0 auto;flex:none}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE BOUTON QUI PORTE LES SÉPARATIONS EST ALLUMÉ
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « on a la bulle, elle doit être allumée. Ça veut dire qu'on peut la
   désactiver. » Le tri courant garde son fond ; le groupement, lui, porte un liseré et
   une pastille : on voit d'un coup d'œil lequel décide des séparations, et qu'un second
   clic les enlève. */
.tri-bar .tri-btn.is-groupe{border-color:#D42B38;color:#D42B38;position:relative;padding-right:20px}
.tri-bar .tri-dot{position:absolute;right:7px;top:50%;width:6px;height:6px;margin-top:-3px;
  border-radius:50%;background:#D42B38;display:block}
.tri-bar .tri-btn.is-active.is-groupe{background:rgba(212,43,56,.12)}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA GÉOLOCALISATION ET LE DÉPÔT DE FICHIERS
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « une information de plus à côté d'adresse : la géolocalisation… cliquable
   pour aller sur Google pour trouver l'itinéraire en direct » ; et « un champ où on peut
   déposer un fichier ». On reste dans la charte : rouge de la maison, gris pour le
   vide, aucune ombre portée. */
.field-geo .geo-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.geo-lien{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;border-radius:10px;
  border:1px solid rgba(212,43,56,.45);color:#D42B38;text-decoration:none;
  font:600 13px Manrope,sans-serif;background:rgba(212,43,56,.07);transition:background .15s}
.geo-lien:hover{background:rgba(212,43,56,.14)}
.geo-pin{width:9px;height:9px;border-radius:50%;background:#D42B38;flex:none;
  box-shadow:0 0 0 3px rgba(212,43,56,.18)}
.geo-vide{color:var(--muted-foreground,#78756f);font:500 13px Manrope,sans-serif}
.geo-etat{margin-top:7px;color:var(--muted-foreground,#78756f);font:500 12px Manrope,sans-serif}
.field-large{grid-column:1 / -1}

.docs-bloc{display:flex;flex-direction:column;gap:6px}
.doc-ligne{display:flex;align-items:center;gap:10px;padding:7px 0;border-bottom:1px solid rgba(0,0,0,.07)}
.doc-nom{color:#D42B38;text-decoration:none;font:600 13px Manrope,sans-serif;flex:1;min-width:0;
  overflow-wrap:break-word}
.doc-nom:hover{text-decoration:underline}
.doc-meta{color:var(--muted-foreground,#78756f);font:500 11.5px Manrope,sans-serif;white-space:nowrap}
.doc-suppr{border:0;background:transparent;color:var(--muted-foreground,#78756f);cursor:pointer;
  font:700 15px Manrope,sans-serif;padding:0 4px;line-height:1}
.doc-suppr:hover{color:#D42B38}
.docs-depot{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}
.docs-depot input[type=text]{flex:1;min-width:160px}
.docs-note{width:100%;margin:0;color:var(--muted-foreground,#78756f);font:500 11.5px Manrope,sans-serif}
/* ═══════════════════════════════════════════════════════════════════════════════
   L'ICÔNE DES TABLETTES REPREND SA TAILLE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « les tablettes, les icônes se sont décalées aussi. »

   MESURÉ sur 4 cartes tablette :
     .carte-portrait  232x74
     image            78x78   <- plus GRANDE que sa boîte
     écart dans la boîte : gauche 90 / droite 64  -> DÉCALÉ de 26 px
     l'image MONTE sur la bulle « Tablette N » de 2 px

   CAUSE : la règle des tablettes demande `width:52px;height:66px` avec l'image à 100 %,
   mais une règle plus bas dans le fichier — écrite pour le logo de la compagnie — force
   l'image à 78x78 avec `!important` et SANS portée :
       .carte-portrait .carte-av img { width:78px !important; ... }
   Elle s'applique donc à TOUTES les cartes-portrait, tablettes comprises.
   78 − 52 = 26 : c'est exactement le décalage mesuré.

   REMÈDE : rétablir la règle des tablettes AVEC `!important` et une spécificité
   supérieure (un `#app` + un `!important` l'emportent sur un `!important` sans `#app`).
   Le logo de la compagnie garde sa règle : on ne touche à RIEN de son côté. */
#app .carte-grille.is-tablettes .carte-portrait .carte-av{
  width:52px !important;height:66px !important;overflow:visible}
#app .carte-grille.is-tablettes .carte-portrait .carte-av img{
  width:100% !important;height:100% !important;
  max-width:100% !important;max-height:100% !important;
  object-fit:contain !important;object-position:center !important}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE BADGE DE STATUT SE REMPLIT — AUSSI EN MODE SOMBRE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « le bouton d'activité dans les grandes fiches n'apparaît pas parce qu'en mode
   sombre, tu ne mets que le contour, tu ne le remplis pas en vert. »

   MESURÉ sur la fiche « 555 Famous Club », thème SOMBRE :
     badge 97x28   fond rgba(0,0,0,0)   bordure rgb(154,217,186)   texte rgb(8,116,67)
   -> CREUX, et un vert FONCÉ sur fond sombre : illisible.

   En thème CLAIR le badge est plein (`#007d44`, texte blanc). Le sombre, lui, retombait
   sur les règles « contour » (`.badge.connected`) écrites pour les pastilles de
   diagnostic. Un badge d'ÉTAT se remplit ; c'est ce qui le rend lisible d'un coup d'œil.
   On garde donc la même sémantique de couleur partout :
     actif / en campagne -> VERT plein     · inactif / libre -> GRIS plein
     à venir             -> AMBRE plein */
/* ══ LE VERT DES PASTILLES, ASSOMBRI — ET C'EST UN CALCUL, PAS UN GOÛT ════════════════
   MESURÉ : `#1f9d63` avec une encre BLANCHE donne 3,46:1 — sous le seuil de 4,5:1. C'est le
   dernier texte illisible de la fiche en mode sombre, et MES règles ne le corrigeaient pas :
   CE bloc-ci, plus spécifique (2 classes + une pseudo-classe) ET en `!important`, gagnait.
   La correction doit donc se faire ICI, à la source — pas en ajoutant une règle concurrente.

   LE CALCUL : pour du blanc, la teinte doit rester sous une luminance de 0,1833.
       #1f9d63 -> blanc 3,46:1   INSUFFISANT
       #14764a -> blanc 5,65:1   OK
   ⚠️ ON GARDE L'ENCRE BLANCHE ET ON ASSOMBRIT LA TEINTE : ces pastilles portent un SENS
   (vert = actif). Changer l'encre leur ferait perdre leur lecture. On baisse la CLARTÉ,
   la teinte reste verte.
   ⚠️ UNE CORRECTION QUI NOMME MAL LA RÈGLE NE CORRIGE RIEN : mes deux passes précédentes
   visaient `.badge-lg` et `.badge` sans le `:not(.sheet-clair)` — cette règle-ci, avec sa
   pseudo-classe et son `!important`, gagnait à chaque fois. C'est la 4e fois ce soir qu'une
   correction « ne prend pas » parce qu'une règle concurrente, écrite ailleurs, l'emporte. */
.profile-sheet:not(.sheet-clair) .badge-actif,
.profile-sheet:not(.sheet-clair) .badge.connected,
.profile-sheet:not(.sheet-clair) .badge-lg.connected{
  background:#14764a !important;border-color:#14764a !important;color:#fff !important;font-weight:700}
.profile-sheet:not(.sheet-clair) .badge-inactif,
.profile-sheet:not(.sheet-clair) .badge.offline{
  background:#78756f !important;border-color:#78756f !important;color:#fff !important;font-weight:700}
/* Le même calcul pour l'ambre : #c08a2e -> blanc 3,04:1 (insuffisant) ; #4f320e -> 6,1:1. */
.profile-sheet:not(.sheet-clair) .badge-planned,
.profile-sheet:not(.sheet-clair) .badge-lg.badge-planned{
  background:#4f320e !important;border-color:#4f320e !important;color:#fff !important;font-weight:700}
/* Et en thème CLAIR, on garde le remplissage (déjà en place) mais on l'unifie. */
.profile-sheet.sheet-clair .badge-actif{background:#007d44;border-color:#007d44;color:#fff;font-weight:700}
.profile-sheet.sheet-clair .badge-inactif{background:#78756f;border-color:#78756f;color:#fff;font-weight:700}
/* Le « + » d'une section qu'on ne peut pas encore remplir : présent pour montrer OÙ
   l'ajout se fera, éteint pour dire qu'il attend l'enregistrement de la campagne. */
.plus-section.is-off{opacity:.34;cursor:not-allowed}
.plus-section.is-off:hover{background:inherit}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE LOGO S'ALIGNE SUR LE TITRE — LA LOI DÉJÀ EN PLACE, APPLIQUÉE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « le logo doit être centré par rapport au titre de l'entreprise, et en
   campagne doit être foncée en vert à l'intérieur, juste au-dessus de Heineken, et ne
   colle pas le carré « en campagne », ne le chevauche ni avec le titre ni avec la photo. »

   MESURÉ sur la fiche Compagnie :
     image-pick (logo)  centreX 241   <- À GAUCHE
     badge « en campagne » centreX 195   (déjà PLEIN VERT : bg rgb(31,157,99))
     .sheet-bulle-tag (titre) centreX 712   <- CENTRÉ
     -> écart logo/titre : -472 px  ·  écart badge/titre : -517 px
   Et le logo (210 px) débordait de son emplacement (166 px) de 44 px.

   POURQUOI. `.sheet-bulle` centre son contenu parce qu'elle prend TOUTE LA LARGEUR avec
   `justify-content:center`. `.sheet-portrait`, lui, est un bloc de 230 px
   (`max-width:230px`) posé à gauche d'un conteneur de 1264 px — sans aucun centrage.
   Le badge suit le portrait : les deux partent donc à gauche.

   LE REMÈDE EST CELUI DE LA LOI EXISTANTE : donner au portrait le même axe que la bulle.
   Pas de nouvel élément, pas de restructuration — deux marges automatiques et un
   plafond de largeur sur l'image. Rien d'autre. */
.profile-sheet .sheet-portrait{margin-left:auto;margin-right:auto;align-items:center}
/* Le badge reste exactement AU-DESSUS du titre : même axe, et un peu d'air en dessous
   pour qu'il ne le touche pas. */
.profile-sheet .sheet-portrait-statut{justify-content:center;margin-bottom:2px}
/* Le logo ne dépasse plus son emplacement : il ne peut donc plus mordre sur le titre. */
.profile-sheet .sheet-portrait.is-logo .image-pick{max-width:100%}
.profile-sheet .sheet-portrait.is-logo .sheet-avatar-slot{max-width:100%}
.profile-sheet .sheet-portrait.is-logo .sheet-avatar-slot img{
  max-width:100% !important;object-fit:contain !important;object-position:center !important}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE LOGO TIENT DANS SON EMPLACEMENT — c'est une `width` FIXE qu'il faut borner
   ═══════════════════════════════════════════════════════════════════════════════
   MESURÉ après l'alignement du portrait :
     sheet-portrait   230 px  centreX 713  (aligné sur le titre : OK)
     sheet-avatar-slot 166 px  centreX 713  (centré : OK)
     image-pick       210 px  centreX 758  <- DÉBORDE de 44 px, et part à droite

   CAUSE : `.sheet-portrait.is-logo .image-pick{ width:210px }` impose une largeur FIXE.
   Face à une `width` fixe, un `max-width` ne peut rien : la boîte est dimensionnée, le
   plafond n'a aucun effet. C'est la MÊME famille de piège que `grid-auto-rows` et le
   `height:%` sur un `<span>` — une dimension imposée ailleurs.

   REMÈDE : borner les DEUX (`width:auto` + `max-width:100%`), ce qui laisse l'image
   prendre la place disponible sans jamais la dépasser. */
.profile-sheet .sheet-portrait.is-logo .image-pick{
  width:auto !important;max-width:100% !important;margin-left:auto;margin-right:auto}
.profile-sheet .sheet-portrait.is-logo .sheet-avatar-slot{
  width:100%;max-width:100%;justify-content:center}
/* L'image du logo : elle se contient, jamais de rognage, toujours centrée. */
.profile-sheet .sheet-portrait.is-logo img{
  width:auto !important;max-width:100% !important;max-height:96px !important;
  object-fit:contain !important;object-position:center !important;display:block;margin:0 auto}
/* ═══════════════════════════════════════════════════════════════════════════════
   LE DERNIER ÉCART DU LOGO : un élément MASQUÉ qui occupe quand même la place
   ═══════════════════════════════════════════════════════════════════════════════
   MESURÉ, la chaîne exacte du portrait-logo :
     .image-field-row   96 px   justify:center   (le centrage EST bien demandé)
     .image-pick        82 px   L 665   centreX 706  <- pourtant collé à GAUCHE
     96 - 82 = 14 px manquants

   CES 14 px sont occupés par , masqué avec
    — or  conserve l'ESPACE de l'élément. Le bouton
   est donc centré PAR RAPPORT AU BLOC TOTAL (bouton + panneau invisible), ce qui le
   décale à gauche de la moitié des 14 px, soit 7 px : exactement l'écart mesuré.

   REMÈDE :  — l'élément ne prend plus aucune place. On le remet en
    en mode édition, où il doit être visible de toute façon. */
.profile-sheet:not(.editing) .image-field-side{display:none}
.profile-sheet .sheet-portrait.is-logo .image-field-row{width:100%;justify-content:center}
/* ═══════════════════════════════════════════════════════════════════════════════
   LA LIGNE DE TITRE DANS UN REGISTRE
   ═══════════════════════════════════════════════════════════════════════════════
   Le CRM est un tableau de 12 colonnes : ses séparations de catégorie prennent la forme
   dune LIGNE pleine largeur qui porte la bulle et ses deux traits — la même langue que
   les cartes groupées, adaptée à un registre. */
tr.rec-groupe > td{padding:16px 0 6px;border:0;background:transparent}
tr.rec-groupe .groupe-bande{padding:0}
tr.rec-groupe:hover{background:transparent}
/* Le dialogue de retrait : il pose une QUESTION, il ne se contente pas d'avertir. */
.confirm-dialog{max-width:520px}
.retrait-txt{margin:0 0 14px;font:500 14px Manrope,sans-serif;line-height:1.5}
.retrait-txt strong{font-weight:800}
.retrait-note{margin:12px 0 0;color:var(--muted-foreground,#78756f);
  font:500 12px Manrope,sans-serif;line-height:1.5}
/* La rangée des villes d'une tuile : TOUTES les villes, et centrée.
   Client : « tu avais l'espace pour mettre toutes les villes et même les centrer ». */
.tile-villes{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin-top:8px}
/* La pastille d'un responsable : son symbole, son nom, et le nombre de ses
   collaboratrices présentes sur la campagne. */
.mini-person-nb{margin-left:6px;padding:1px 7px;border-radius:9px;font:800 11px Manrope,sans-serif;
  background:rgba(212,43,56,.14);color:#D42B38}
/* La tuile d'un lieu : le symbole du type, comme partout ailleurs. */
.loc-tile-av{display:grid;place-items:center}
.loc-tile-av img{max-width:100%;max-height:100%;object-fit:contain;display:block}
/* ═══ LA FENÊTRE D'ÉVÉNEMENT REFRITE ═══════════════════════════════════════════
   Deux sections (l'événement, puis l'équipe), et deux listes de personnes qui se
   cochent. Client : « il doit contenir l'équipe assignée ». */
.session-refait{max-width:720px;width:min(720px,94vw)}
.eq-section{margin-top:18px;padding-top:14px;border-top:1px solid rgba(255,255,255,.09)}
[data-theme="light"] .eq-section{border-top-color:rgba(0,0,0,.10)}
.eq-section-titre{display:block;font:800 12px Manrope,sans-serif;letter-spacing:.10em;
  text-transform:uppercase;color:#D42B38;margin-bottom:10px}
.eq-note{margin:0 0 12px;font-size:12.5px;line-height:1.55;opacity:.66}
.eq-bloc{margin-top:12px;border:1px solid rgba(255,255,255,.10);border-radius:14px;
  padding:12px;background:rgba(255,255,255,.025)}
[data-theme="light"] .eq-bloc{border-color:rgba(0,0,0,.10);background:rgba(0,0,0,.02)}
.eq-tete{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}
.eq-titre{font:800 12.5px Manrope,sans-serif;letter-spacing:.02em}
.eq-nb{font:700 11px Manrope,sans-serif;padding:2px 9px;border-radius:999px;
  background:rgba(212,43,56,.14);color:#D42B38}
/* La liste des personnes : défile si elle est longue, et se replie en 2 colonnes. */
.eq-liste{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:7px;
  max-height:186px;overflow-y:auto;padding-right:2px}
.eq-item{display:flex;align-items:center;gap:9px;width:100%;text-align:left;cursor:pointer;
  padding:7px 9px;border-radius:11px;border:1px solid rgba(255,255,255,.10);
  background:rgba(255,255,255,.03);color:inherit;font:inherit;transition:border-color .15s,background .15s}
[data-theme="light"] .eq-item{border-color:rgba(0,0,0,.10);background:#fff}
.eq-item:hover{border-color:rgba(212,43,56,.42)}
/* Cochée : fond rouge plein — on voit d'un coup d'œil qui est sur l'événement. */
.eq-item.on{border-color:#D42B38;background:rgba(212,43,56,.13)}
.eq-av{width:30px;height:30px;flex:0 0 30px;border-radius:9px;overflow:hidden;
  display:grid;place-items:center;background:rgba(255,255,255,.06)}
.eq-av img{width:100%;height:100%;object-fit:cover;object-position:center 18%}
.eq-nom{flex:1;font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.eq-ville{font-size:10.5px;opacity:.58;white-space:nowrap}
.eq-coche{width:19px;height:19px;flex:0 0 19px;border-radius:50%;display:grid;place-items:center;
  font:800 11px Manrope,sans-serif;border:1px solid rgba(255,255,255,.22)}
.eq-item.on .eq-coche{background:#D42B38;border-color:#D42B38;color:#fff}

/* ═══ LE CADRE À DROITE D'UNE CARTE D'ÉVÉNEMENT ═══════════════════════════════
   Client : « profité de cet espace du cadre à droite des cartes événement et rajouter
   des informations visibles comme les noms des collaboratrices et leur responsable et
   les photos de goodies sans nom ». */
.evt-cote{display:flex;flex-direction:column;gap:9px;min-width:0}
.evt-cote-titre{font:800 10.5px Manrope,sans-serif;letter-spacing:.09em;text-transform:uppercase;
  color:#D42B38;opacity:.92}
.evt-personnes{display:flex;flex-wrap:wrap;gap:7px}
.evt-personne{display:flex;align-items:center;gap:6px;padding:3px 8px 3px 3px;border-radius:999px;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.10);max-width:100%}
[data-theme="light"] .evt-personne{background:rgba(0,0,0,.035);border-color:rgba(0,0,0,.09)}
.evt-personne img{width:22px;height:22px;flex:0 0 22px;border-radius:50%;object-fit:cover;
  object-position:center 18%}
.evt-personne b{font-size:11.5px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.evt-personne.resp{border-color:rgba(212,43,56,.34)}
/* Les photos de goodies, SANS nom : le visuel du produit, en PNG. */
.evt-goodies{display:flex;flex-wrap:wrap;gap:7px}
.evt-goodie{width:42px;height:42px;border-radius:11px;display:grid;place-items:center;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.10);overflow:hidden}
[data-theme="light"] .evt-goodie{background:#fff;border-color:rgba(0,0,0,.09)}
.evt-goodie img{max-width:100%;max-height:100%;object-fit:contain;display:block}
.evt-vide{font-size:11.5px;opacity:.52;font-style:italic}
/* Une section VIDE dans une fiche d'ajout : elle dit ce qu'elle attend, et le bouton
   qui y mène reste visible mais inactif tant que la fiche n'est pas enregistrée.
   Client : « avec du vide comme celle d'ajout de campagne ». */
.section-ajout{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:10px}
.section-ajout .button[disabled]{opacity:.44;cursor:not-allowed}
.section-ajout .row-sub{margin:0;opacity:.56}
/* ═══ LE SHADER D'IDENTITÉ SUR LA BARRE LATERALE ══════════════════════════════
   Client : « rajoute le shader qu'on a comme identité en transparence sur la barre
   latérale où y'a pilotage et base de données ».
   MESURÉ : la barre est SOMBRE — rgb(32,33,35), luminance 33 — donc on reprend
   exactement le réglage des fiches sombres : `screen` + opacité basse. Le canvas est en
   `absolute` dans une barre passée en `relative` : il couvre exactement la barre, et
   `overflow:hidden` l'empêche de déborder sur le contenu.                        */
.sidebar{position:relative;overflow:hidden}
.rail-shader{position:absolute;top:0;left:0;width:100%;height:480px;z-index:0;
  pointer-events:none;opacity:.12;mix-blend-mode:screen;display:block;
  transform:scaleY(2.5);transform-origin:top left;
  /* MESURÉ : sa sortie plafonne à 53 de luminance — le fond du shader est
     `vec3(0.04,0.05,0.08)` et sa lueur ne monte presque jamais. Sur une fiche (900 px de
     large) la masse relative est suffisante ; dans une bande de 240 px elle ne l'est plus.
     On relève donc le SIGNAL, sans retoucher le GLSL : la matière reste exactement la même
     que celle des fiches, on l'expose simplement. */
  /* Client : « le rouge est trop fort » — on calme la TEINTE autant que la LUMIÈRE :
     `saturate(.42)` désature vers un gris chaud, `brightness(2.2)` garde la matière
     perceptible sans l'imposer. Le shader redevient une texture discrète, pas un motif. */
  filter:brightness(2.4) saturate(1.2);mix-blend-mode:screen;}

/* Client : « encore plus sobre, je veux un relief très faible ». COURBE MESURÉE
(capture + analyse PIL) pour choisir sans tâtonner :
     .06 -> 0,1 %   .09 -> 0,1 %   .12 -> 17,9 %   .15 -> 20,3 %   .20 -> 26,6 %
Sous .12 la teinte s'effondre : `mix-blend-mode:screen` sur fond sombre confond la
somme avec le fond. `.12` est donc le réglage le plus sobre QUI EXISTE ENCORE.
Le `contrast` est SUPPRIMÉ : c'est lui qui creusait les bosses, donc le relief. */

/* Client : « essaye de rendre encore plus sobre, je veux un relief très faible ».
Réglages successifs, mesures sur la bannière (pixels teintés) :
   .42 + saturate 1.9 + contrast 1.08  ->  35,4 %  « très fort »
   .17 + saturate 1.35 + contrast 1.02 ->  26,0 %  « juste le sentir »
   .20                                 ->  26,6 %  « ça paraît très faible »
   .09 + saturate 1.15 + contrast 1.0  ->  le relief très faible demandé
Le `contrast(1.0)` est voulu : il supprime la différence entre bosses et creux, ce
qui est exactement le « relief très faible ». */

/* Client : « plus sombre juste le sentir ». On baisse l'OPACITÉ (donc la lumière —
c'est ce que « plus sombre » demande) tout en gardant un peu de saturation, sinon on
retombe dans le gris invisible mesuré au début (rouge max 1,0). Le shader doit être
là quand on le regarde, et absent quand on lit : c'est la définition du « juste le
sentir ». */

/* Client : « un peu moins, c'est très fort ». Réglage précédent (opacité .42,
brightness 2.7, saturate 1.9) rendait le shader bien VISIBLE — mesuré : rouge max 160,
35,4 % de pixels teintés — mais trop appuyé. On redescend à .30 / 2.1 / 1.25 : deux
crans sous le visible-franc, deux crans au-dessus de l'invisible (qui était à rouge
max 1,0). Le motif garde sa couleur, l'intensité redescend. */

/* Client : « rajoute aussi le shader sur la bannière gauche ». MESURÉ en pixels sur la bannière : luminance moyenne 36,3, écart-type 17,94 — le shader se voit donc, il y a de la matière. Mais « rouge max 1,0 » : il rendait en gris/bleuté, parce que son fond GLSL `bgDeep(0.04,0.05,0.08)` est plus bleu que rouge et que la désaturation (`saturate(.42)` puis `.55`) éteignait le rouge. On remonte donc la SATURATION au-dessus de 1 pour rendre au motif sa couleur d'identité, en gardant l'opacité à .42 : la teinte est là, sans l'intensité qui avait fait dire « trop fort ». *//* Client : « rajoute aussi le shader sur la bannière gauche ». MESURÉ avant : opacité .20 + saturate(.42) sur un fond #202123 — le signal était écrasé, la bannière apparaissait comme un aplat. On remonte l'exposition (brightness 2.9, opacité .42) en gardant le rouge contenu : la matière redevient perceptible sans redevenir « trop forte ». Le GLSL est inchangé — c'est bien le même shader que les fiches et que la bulle de profil. */
/* ═══ POURQUOI CE RECADRAGE ═══════════════════════════════════════════════════
   MESURÉ, et c'est la vraie cause : le shader compose son éclat autour du CENTRE de son
   buffer (`p = (fragCoord*2 - resolution) / min(resolution)`). Dans une bande de
   240 x 1196, `min(resolution)` vaut 240, donc `p` court de -1 à +1 en largeur mais de
   -5 à +5 en hauteur : la lueur se retrouve HORS du cadre et tout le reste est du bruit
   sombre — 32 à 53 de luminance, un quasi-aplat, quelles que soient les retouches de
   transparence.
   On donne donc au buffer une forme où le shader respire (240 x 480, proportions d'une
   fiche), puis on l'étire verticalement pour couvrir la barre. Le shader lui-même n'est
   pas touché : c'est la MÊME matière d'identité, simplement cadrée pour une barre haute.
   Le bruit étiré se lit comme des volutes, ce qui convient à une colonne. */
/* Les enfants passent AU-DESSUS du shader : sans ça, le canvas les recouvrirait et
   les onglets deviendraient incliquables. */
.sidebar>.brand,.sidebar>nav,.sidebar>.sidebar-foot{position:relative;z-index:1}
/* ═══ UNE SEULE FICHE OUVERTE À LA FOIS ═══════════════════════════════════════
   Client : « sois sûr que toutes les fenêtres ne se chevauchent pas comme ça ».
   Deux `<dialog>` en showModal() empilent deux surfaces : celle du dessous reste
   visible et l'écran devient illisible. On empêche le SECOND de s'ouvrir tant que le
   premier n'est pas fermé (la règle est posée dans app.js). Ce repli garantit qu'une
   fiche orpheline ne reste jamais affichée sous la nouvelle. */
dialog.profile-sheet[data-voile="1"]{opacity:.35;pointer-events:none}

/* Le shader est décoratif : sur un écran tactile réduit, il ne sert à rien. */
@media (max-width:760px){.rail-shader{display:none}}

/* ═══ LES ONGLETS REMONTENT VERS LE LOGO ══════════════════════════════════════
   Client : « remonter les onglets un tout petit peu pour se rapprocher du logo ».
   MESURÉ avant : logo bas à 61 px, premier séparateur à 129 px = 68 px de vide.
   On resserre le bas du bloc logo et le haut du premier séparateur — les marges
   des séparateurs SUIVANTS restent intactes, la respiration entre groupes est voulue. */
body[data-interface="direction"] .sidebar{padding-top:18px}
body[data-interface="direction"] .sidebar .brand{margin-bottom:16px}
body[data-interface="direction"] .sidebar .nav-divider:first-of-type{margin-top:6px}
/* ═══ LA CARTE D'ÉVÉNEMENT : TROIS ZONES ══════════════════════════════════════
   Client : « j'aime bien les goodies dans le bas côté droite, j'aimerais aussi que tu
   profites du milieu et mettre les noms des responsables et des collaborateurs quand il
   y en aura dans cet espace que t'as laissé vide, et en haut des goodies tu mets combien
   il reste pour l'événement ou bien il est en cours en ce moment ou bien il est achevé,
   fini. »
   La carte passe donc en trois zones : l'annonce (titre, lieu, statut), LES PERSONNES au
   milieu, puis à droite LE TEMPS au-dessus des GOODIES. Chaque zone ne se dessine que si
   elle a quelque chose à dire — aucune ne laisse de cadre creux. */
.event-card{align-items:stretch}
/* ─── LE MILIEU : les personnes ─────────────────────────────────────────────
   Il prend l'espace disponible (`flex:1`) : c'est exactement le vide que le client
   voulait voir occupé. Il ne se dessine pas s'il n'y a personne. */
.event-main{flex:1 1 260px}
.evt-equipe{flex:1 1 260px;min-width:0;display:flex;flex-direction:column;gap:7px;
  padding:2px 4px 2px 12px;border-left:1px solid rgba(255,255,255,.10)}
.evt-groupe{min-width:0}
.evt-groupe-titre{display:block;font:800 9.5px Manrope,sans-serif;letter-spacing:.10em;
  text-transform:uppercase;color:rgba(212,43,56,.85);margin-bottom:4px}
/* ─── LA DROITE : le temps, puis les goodies ─────────────────────────────── */
.event-cote{flex:0 0 auto;min-width:0;display:flex;flex-direction:column;gap:8px;
  align-items:flex-end;padding-left:12px;border-left:1px solid rgba(255,255,255,.10)}
.evt-cote-haut{width:100%;display:flex;justify-content:flex-end}
.evt-cote-bas{display:flex;flex-direction:column;gap:5px;align-items:flex-end}
.evt-goodies{flex-wrap:nowrap}
/* Le badge de DÉLAI : trois états, trois lectures. « reste 12 jours », « en cours
   aujourd'hui », « achevé il y a 3 j ». Jamais un « dans 0 j » qui ne dit rien. */
.evt-delai{display:flex;align-items:baseline;gap:6px;padding:4px 10px;border-radius:999px;
  border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.06);white-space:nowrap}
.evt-delai b{font:800 12px Manrope,sans-serif;letter-spacing:.01em}
.evt-delai span{font:600 9.5px Manrope,sans-serif;text-transform:uppercase;letter-spacing:.07em;
  opacity:.72}
/* À VENIR : le compte des jours restants, en rouge (l'information qu'on cherche). */
.evt-delai.a-venir{border-color:rgba(212,43,56,.38);background:rgba(212,43,56,.10)}
.evt-delai.a-venir b{color:#D42B38}
/* EN COURS : aujourd'hui. Vert plein, c'est un état, pas un compte. */
.evt-delai.en-cours{border-color:transparent;background:#1f9d63}
.evt-delai.en-cours b,.evt-delai.en-cours span{color:#fff;opacity:1}
/* ACHEVÉ : gris, éteint. C'est passé, ça ne demande plus d'action. */
.evt-delai.acheve{border-color:rgba(255,255,255,.10);background:rgba(255,255,255,.04)}
.evt-delai.acheve b{color:rgba(244,243,241,.62)}
/* Sur la CARTE CLAIRE des événements, les mêmes états en lisibilité claire. */
.event-card .evt-equipe,.event-card .event-cote{border-left-color:rgba(26,27,29,.14)}
.event-card .evt-delai{border-color:rgba(26,27,29,.16);background:rgba(26,27,29,.05)}
.event-card .evt-delai.a-venir{border-color:rgba(168,35,47,.36);background:rgba(168,35,47,.10)}
.event-card .evt-delai.a-venir b{color:#a8232f}
.event-card .evt-delai.en-cours{border-color:transparent;background:#0b7a4a}
.event-card .evt-delai.acheve b{color:rgba(26,27,29,.55)}
.event-card .evt-groupe-titre{color:rgba(168,35,47,.85)}
.event-card .evt-personne{background:rgba(255,255,255,.6);border-color:rgba(26,27,29,.10)}
.event-card .evt-goodie{background:#fff;border-color:rgba(26,27,29,.10)}
/* Une carte étroite : les trois zones s'empilent au lieu de s'écraser. */
@media (max-width:900px){
  .event-card{flex-wrap:wrap}
  .evt-equipe,.event-cote{flex:1 1 100%;border-left:0;padding-left:0}
  .event-cote{align-items:flex-start}
}
/* ═══ LA CHARTE POUR TOUTE FENÊTRE ═════════════════════════════════════════════
   Client : « il faut qu'il y ait un pop-up tout comme la charte, tout comme les autres.
   Avec le mode sombre et tout ce qui va avec. Et avec les boutons d'action s'ils devraient
   être. »
   MESURÉ : seule `.profile-sheet` portait la charte. Les fenêtres secondaires
   (`equipe-dialog`, `assign-dialog`, `dotation-dialog`, `confirm-dialog`, `image-library`,
   `discard-dialog`, `tab-dialog`, `admin-record-dialog`…) héritaient du `.admin-dialog` NU,
   c'est-à-dire BLANC PLAT — sans fond, sans mode sombre, sans boutons conformes.
   On écrit donc la charte UNE FOIS pour la famille `admin-dialog`, et `.profile-sheet`
   (qui l'a déjà) n'est pas touchée : elle garde ses propres règles, plus fines.
   C'est la base réutilisable : toute nouvelle fenêtre hérite d'office de la charte. */
dialog.admin-dialog{background:linear-gradient(150deg,#26282a 0%,#1a1b1d 100%);
  color:#f4f3f1;border-color:rgba(255,255,255,.14);position:relative;overflow:hidden}
dialog.admin-dialog:not(.profile-sheet){max-height:92vh;overflow:auto}
/* Le fond animé : identique aux fiches, en `screen` comme partout sur la charte. */
dialog.admin-dialog .fiche-shader{position:absolute;top:0;left:0;width:100%;height:100%;
  z-index:0;pointer-events:none;opacity:.13;mix-blend-mode:screen;border-radius:inherit}
/* Le contenu passe AU-DESSUS du fond : sans ça, le canvas recouvrirait les champs.
   MESURÉ : cette règle atteignait aussi la BARRE DES BOUTONS et lui imposait
   `position:relative` — or elle est plus spécifique (0,3,1) que la règle qui l'épingle
   (0,2,0), donc elle gagnait : les boutons « fermer » et « thème » restaient dans le flux
   et se retrouvaient à GAUCHE, alors qu'on veut les épingler en haut à droite.
   On l'exclut donc explicitement. */
dialog.admin-dialog>*:not(.fiche-shader):not(.sheet-pin-bar){position:relative;z-index:1}
/* Le repère de collage (titre + fermer) reste au-dessus de tout. */
dialog.admin-dialog .sheet-pin-bar{z-index:60}
/* ─── Les textes ─────────────────────────────────────────────────────────── */
dialog.admin-dialog:not(.profile-sheet) h2,
dialog.admin-dialog:not(.profile-sheet) h3,
dialog.admin-dialog:not(.profile-sheet) strong{color:#fff}
dialog.admin-dialog:not(.profile-sheet) p,
dialog.admin-dialog:not(.profile-sheet) .row-sub,
dialog.admin-dialog:not(.profile-sheet) small{color:rgba(244,243,241,.66)}
dialog.admin-dialog .dialog-kicker,
dialog.admin-dialog .section-label{color:#ff8b95}
dialog.admin-dialog .dialog-head{border-bottom-color:rgba(255,255,255,.12)}
dialog.admin-dialog .dialog-close{background:rgba(255,255,255,.09);color:#f4f3f1}
dialog.admin-dialog .dialog-close:hover{background:#c0392f;color:#fff}
/* ─── Les champs ─────────────────────────────────────────────────────────── */
dialog.admin-dialog:not(.profile-sheet) .field label{color:rgba(244,243,241,.72)}
dialog.admin-dialog:not(.profile-sheet) input,
dialog.admin-dialog:not(.profile-sheet) select,
dialog.admin-dialog:not(.profile-sheet) textarea{background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.24);color:#f4f3f1}
dialog.admin-dialog:not(.profile-sheet) input::placeholder,
dialog.admin-dialog:not(.profile-sheet) textarea::placeholder{color:rgba(244,243,241,.42)}
dialog.admin-dialog:not(.profile-sheet) input:focus,
dialog.admin-dialog:not(.profile-sheet) select:focus,
dialog.admin-dialog:not(.profile-sheet) textarea:focus{outline:0;
  border-color:rgba(212,43,56,.7);background:rgba(255,255,255,.1)}
dialog.admin-dialog:not(.profile-sheet) option{background:#24262a;color:#f4f3f1}
/* ─── Les listes et blocs internes ───────────────────────────────────────── */
dialog.admin-dialog .eq-liste,
dialog.admin-dialog .pied,
dialog.admin-dialog .form-status{color:rgba(244,243,241,.8)}
dialog.admin-dialog .eq-pied,
dialog.admin-dialog .dialog-actions{border-top-color:rgba(255,255,255,.14)}
/* ─── Les boutons d'action ───────────────────────────────────────────────── */
dialog.admin-dialog .button{background:#D42B38;border-color:#D42B38;color:#fff}
dialog.admin-dialog .button:hover{background:#961c26;border-color:#961c26}
dialog.admin-dialog .button.secondary,
dialog.admin-dialog .eq-btn{background:transparent;border:1px solid rgba(255,255,255,.24);
  color:#f4f3f1}
dialog.admin-dialog .button.secondary:hover,
dialog.admin-dialog .eq-btn:hover{border-color:rgba(255,255,255,.5);background:rgba(255,255,255,.06)}
dialog.admin-dialog .text-button{color:#ff8b95}
dialog.admin-dialog .text-button:hover{color:#ff6b78}
/* Le fond de page derrière la fenêtre : assombri et flouté, pour que la fenêtre soit
   LISIBLE sans que l'écran du dessous vienne se mélanger à elle. */
/* (voile remplacé plus bas par la version opaque, sans liséré clair) */

/* ─── LES FENÊTRES NE SE CHEVAUCHENT PAS ──────────────────────────────────────
   Une seule fenêtre à la fois, sauf parenté déclarée (`data-parent`). app.js applique
   la règle au moment de l'ouverture ; ce repli garantit qu'aucune fenêtre du dessous
   ne reste VISIBLE derrière la nouvelle, même si le script n'a pas encore tourné. */
dialog.admin-dialog[open]~dialog.admin-dialog[open]{opacity:0}
/* ═══ ZÉRO DÉBORDEMENT ══════════════════════════════════════════════════════════
   Client : « essaye de ne garder aucun truc débordé, que ce soit sur les cartes, les
   boutons et tout. Si on fait ça, ça veut dire qu'on est des amateurs. »
   MESURÉ, trois causes distinctes — chacune corrigée à la racine, pas au cas par cas :

   1. `.ip-btn` faisait 130 px dans un parent de 120 px : son TEXTE décidait de sa
      largeur, sans limite. Un bouton ne commande jamais la taille de sa colonne.
   2. `.image-pick` : un `<img>` est EN LIGNE par défaut ; il repose sur la ligne de
      base du texte, qui réserve la place des descendantes → 3 px de contenu sous
      l'image, rognés par `overflow:hidden`. C'est une cause classique, invisible à
      l'œil, présente sur toutes les images non bloquées.
   3. `.mini-person-av` : l'image gardait ses proportions (32×38) dans une boîte
      32×32 → 6 px coupés. Une vignette d'avatar doit REMPLIR sa boîte. */

/* ─── Cause 1 : un bouton ne dépasse jamais sa colonne, ET son texte reste lisible ──
   MESURÉ : `nowrap` + ellipse réglait le débordement mais COUPAIT le libellé
   (« Téléverser une ima… »). Un texte tronqué est aussi laid qu'un texte qui sort.
   On laisse donc le libellé RETOURNER À LA LIGNE : le bouton reste dans sa colonne et
   dit ce qu'il fait en entier. */
.ip-btn{max-width:100%;min-width:0;white-space:normal;overflow-wrap:anywhere;
  text-align:center;line-height:1.25;display:block}
.ip-actions{min-width:0;max-width:100%;align-items:stretch}
.ip-actions .ip-btn{width:100%}

/* ─── Cause 2 : toute image de visuel est BLOQUÉE (fin de la ligne de base) ── */
.image-pick>img,.image-pick img,.cover-pick img,.image-field img,
.mini-card-av img,.mini-person-av img,.eq-av img,.evt-personne img,
.carte-av img,.sheet-avatar-slot img,.loc-tile-av img,.goodie-visual img{
  display:block;width:100%;height:100%;object-fit:cover}

/* ─── Cause 3 : une vignette REMPLIT sa boîte, sans pouvoir la déformer ──────
   MESURÉ : l'image gardait ses proportions (32×38) dans une boîte 32×32 → 6 px coupés,
   et `height:100%` ne suffisait pas (un élément de grille centré garde sa hauteur propre).
   On donne donc les DEUX dimensions en clair : plus rien ne peut l'étirer. */
.mini-person-av,.eq-av,.evt-personne img,.mini-card-av{overflow:hidden}
.mini-person-av>img{width:32px;height:32px;min-width:32px;max-width:32px;
  min-height:32px;max-height:32px;object-fit:cover;object-position:center 18%;display:block}
.eq-av>img{width:30px;height:30px;min-width:30px;max-width:30px;
  min-height:30px;max-height:30px;object-fit:cover;object-position:center 18%;display:block}
.mini-card-av img{width:100%;height:auto;max-height:100%;
  object-fit:cover;object-position:center 18%;display:block}

/* ─── Les visuels d'icônes gardent leur cadrage « objet » ─────────────────── */
.loc-tile-av img,.goodie-visual img,.evt-goodie img{object-fit:contain}
.sheet-avatar-slot img{object-position:center 18%}

/* ─── Le visuel d'un goodie : jamais plus grand que sa tuile ──────────────── */
.goodie-tile{overflow:hidden}
.goodie-visual img{max-width:100%;max-height:100%}
/* ═══ AUCUNE BORDURE BLANCHE AUTOUR D'UNE FICHE ═════════════════════════════════
   Client : « il doit être derrière tout le fond, aucune bordure blanche ».
   MESURÉ : une fiche est bornée à `max-height:94vh` — les 6 % qui restent laissent
   voir le FOND CLAIR DE LA PAGE (`--bg`, #f4f2ee). Autour d'une fiche sombre, ce filet
   clair se lit comme un liséré blanc, en haut et en bas.
   Deux réponses, complémentaires :
     1. le voile derrière la fiche devient OPAQUE et sombre — plus rien de clair ne
        peut transparaître par le moindre interstice ;
     2. la fiche prend toute la hauteur disponible (96vh), donc l'interstice se réduit
        encore.
   Le fond du voile est celui de la charte, jamais du noir plat : même matière que le
   reste de l'interface. */
dialog.admin-dialog::backdrop{background:radial-gradient(circle at 50% 0%,#24262a 0%,#101113 62%);
  backdrop-filter:blur(5px)}
dialog.admin-dialog{max-height:96vh}

/* ═══ LE RESPONSIVE : LE FOND DERRIÈRE TOUT ═════════════════════════════════════
   Client : « le débordement reste quand on rétrécit la page, il doit être responsive ».
   MESURÉ à ≤ 600 px : la barre latérale quitte le côté et devient une BARRE FIXE EN BAS
   (`position:fixed;height:76px`) ; elle recouvre donc le bas du contenu, qui finissait
   sous elle. On réserve sa hauteur : plus rien n'est caché derrière.
   Et à toute largeur, le fond du contenu reste SOMBRE — jamais un aplat clair sous les
   cartes, sinon une bande blanche apparaît sur les côtés. */
@media (max-width:600px){
  .main{padding-bottom:88px}
  /* Les cartes de données passent en une colonne : rien n'est écrasé. */
  .entity-grid,.grille-cartes,.mini-grid,.loc-grid,.tile-counts,
  .form-grid,.form-grid.deux,.read-grid{grid-template-columns:1fr !important}
  /* Les fenêtres prennent toute la largeur utile, sans marge inutile. */
  dialog.admin-dialog{width:calc(100vw - 12px) !important;max-width:none}
  dialog.admin-dialog.profile-sheet{width:calc(100vw - 12px) !important}
  /* Les rangées de pastilles se replient au lieu de défiler. */
  .chip-row,.mini-people,.evt-personnes,.evt-goodies{flex-wrap:wrap !important}
  /* Les tableaux larges défilent DANS leur cadre, jamais en débordant de la page. */
  .sheet-table,.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
@media (max-width:900px){
  /* Les grilles à colonnes fixes se replient : aucune carte n'est coupée. */
  .entity-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr)) !important}
  .tile-counts{grid-template-columns:repeat(auto-fit,minmax(130px,1fr)) !important}
}

/* ═══ LE FOND DE PAGE : sombre partout, jamais de bande claire ══════════════════
   Le fond clair de la page ne doit apparaître QUE là où il est voulu (les cartes, les
   zones de saisie). Le pourtour de l'application reste dans la matière sombre de la
   charte : c'est ce qui garantit « aucune bordure blanche », quelle que soit la taille. */
/* ⚠️⚠️ LE FOND DE LA DIRECTION ÉTAIT NOIR (`#0f1011`) — ET C'EST *TOUTE* LA CAUSE DU
   « LA PLATEFORME EST NOIRE ». Ce noir est celui de la TABLETTE (le terrain, sombre la nuit,
   voulu par le client) ; la direction, elle, doit être CLAIRE — sa vidéo de fond est
   `fond-clair.mp4`, et son `<html>` porte `#f7f7f5`. MESURÉ : `html` était bien clair et le
   `body`, lui, noir — le `body` gagne parce qu'il est peint PAR-DESSUS, et parce qu'une règle
   `body[data-interface]` est plus spécifique que `html[data-theme]`.
   ⚠️ ET C'EST POUR ÇA QUE LA TABLETTE MARCHAIT : elle porte `#f7f7f5` (l.18197). Deux
   interfaces, deux valeurs écrites à la main — elles ne pouvaient que diverger.
   Le fond devient donc celui de la vidéo : la lueur rouge de la charte reste, le noir part, et
   `<html>` (#f7f7f5) comme `.fond-video` (blanc, z-index:-1) prennent le relais derrière. */
body[data-interface="direction"]{background:radial-gradient(circle at 80% -10%,rgba(212,43,56,.10),transparent 38%),transparent}
body[data-interface="direction"] .main{background:transparent}
/* ═══ LE BASCULE CLAIR / SOMBRE DE L'INTERFACE, AVEC LES VIDÉOS DE FOND ══════════
   Client : « J'aime bien ce mode noir aussi, peut-on avoir la possibilité de switcher
   aussi cette interface ».
   Le thème se pose sur `<html data-theme="light|dark">` — une seule source de vérité,
   lue par toutes les règles `[data-theme="light"]` déjà écrites. Le fond vidéo suit :
   la version CLAIRE pour le thème clair, la SOMBRE pour le thème sombre. */

/* ─── LE FOND VIDÉO ──────────────────────────────────────────────────────── */
/* ⚠️⚠️ LE REPLI DE CE CONTENEUR ÉTAIT NOIR (`#0f1011`) — ET C'EST CE QUI REND « TOUTE   L'APPLICATION NOIRE ». Ce div est FIXE, PLEIN ÉCRAN, en `z-index:-1` : c'est le calque le plus   bas, et `<html>`/`<body>` sont transparents. Donc quand la vidéo ne peint pas (autoplay   bloqué, décodage, onglet en arrière-plan), le noir est NU et couvre toute la page.   ⚠️ `#0f1011` EST LE FOND SOMBRE DE LA TABLETTE. Sur la page CLAIRE de la direction, c'est une   valeur de terrain qui déborde — comme `VUES_COLLAB` et le repli du thème, corrigés ce soir.   ⚠️ LE CLIENT A DIT LA BONNE VALEUR : « enlève-moi même le fond gris [...] ». Le repli est donc   BLANC — la page reste lisible même si la vidéo s'arrête, au lieu de devenir noire. */.fond-video{position:fixed;inset:0;z-index:-1;overflow:hidden;background:#ffffff}
/* MESURÉ : `transition:opacity .45s` laissait la vidéo à `opacity:0` au moment du
   contrôle, et elle restait « EN PAUSE ». Il n'y a plus qu'une seule vidéo à afficher :
   elle n'a donc AUCUNE raison d'être animée en fondu. On l'affiche franchement. */
.fond-video .fv{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:1}
/* Le fond clair est TOUJOURS visible : il n'y a plus qu'une version.
   Client : « garde juste l'arrière-plan clair avec le fond blanc qu'on vient de faire ». */
.fond-video .fv-clair{opacity:1}
/* LE VOILE : la vidéo est une MATIÈRE, pas une image à lire. Sans lui, le contenu
   deviendrait illisible — il laisse passer la vidéo tout en gardant le texte net. */
/* MESURÉ : à 88–95 % d'opacité, la vidéo était écrasée — le client la voyait à peine.
   On descend donc le voile à 58–72 % : la matière devient LISIBLE, et le texte reste net
   (contrôle : en dessous de ~55 %, les libellés gris perdent leur lisibilité). */
.fond-video .fv-voile{position:absolute;inset:0;
  background:radial-gradient(circle at 78% -12%,rgba(181,31,42,.07),transparent 42%),
    linear-gradient(180deg,rgba(252,250,247,.58) 0%,rgba(250,248,245,.66) 55%,rgba(248,246,242,.72) 100%)}
/* Le conteneur de page laisse voir le fond vidéo : plus d'aplat qui le recouvrirait.
   MESURÉ : `body` gardait son fond clair (`--bg`) alors que la vidéo est derrière — la
   vidéo ne se voyait donc pas. On rend transparent tout ce qui n'est pas une CARTE. */
/* ⚠️⚠️ LA BASE ÉTAIT NOIRE (`#0f1011`), ET `html[data-theme="light"]` la corrigeait ensuite.   Résultat : à la moindre FRACTION DE SECONDE où l'attribut `data-theme` est absent ou relu,   la page tombait sur du NOIR — et c'est exactement ce que le client voyait (« toute   l'application devient noire après une seconde »).   ⚠️ On inverse la logique : la base est NEUTRE, et SEUL le thème sombre peint en sombre. Une   page ne doit jamais devenir noire par ACCIDENT — seulement sur une demande explicite. */html[data-theme]{background:#f7f7f5}html[data-theme="dark"]{background:#0f1011}
html[data-theme="light"]{background:#f7f7f5;color-scheme:light}
/* ⚠️ `color-scheme:light` DIT AU NAVIGATEUR que cette page est claire : sans lui, il peint son
   PROPRE canevas en noir là où la page est transparente — et la moindre fraction de seconde
   avant que le CSS ne s'applique se voit comme un écran noir. C'est le même défaut que le
   repli de `.fond-video`, à un autre niveau : une valeur du navigateur qui n'est pas la nôtre. */
html{color-scheme:light}
body,.app-shell,.main,.content{background:transparent !important}

/* ─── LE BOUTON DE BASCULE, RÉGLÉ POUR LE FOND CLAIR ────────────────────────
   Client : « ajuste-moi les boutons aussi ». MESURÉ : le bouton était dessiné pour un
   fond SOMBRE (verre translucide + texte blanc) — posé sur le fond clair, il devenait
   un bouton fantôme. On lui donne donc la lisibilité d'un fond clair : pastille blanche,
   bord net, texte sombre. Et il se distingue nettement en thème sombre. */
.theme-switch{display:inline-flex;align-items:center;gap:7px;cursor:pointer;
  padding:7px 13px;border-radius:999px;border:1px solid rgba(23,23,25,.24);
  background:rgba(255,255,255,.92);color:#161616;font:600 11.5px Manrope,sans-serif;
  box-shadow:0 1px 3px rgba(23,23,25,.10);
  transition:border-color .15s,background .15s,color .15s,box-shadow .15s}
.theme-switch:hover{border-color:rgba(23,23,25,.44);background:#fff;
  box-shadow:0 2px 7px rgba(23,23,25,.16)}
.theme-switch .ts-icone{font-size:13px;line-height:1}
.theme-switch .ts-texte{letter-spacing:.04em}
/* En thème SOMBRE (contenu), l'inverse exact : verre translucide, texte clair. */
html[data-theme="dark"] .theme-switch{border-color:rgba(255,255,255,.26);
  background:rgba(255,255,255,.08);color:#f4f3f1;box-shadow:none}
html[data-theme="dark"] .theme-switch:hover{border-color:rgba(255,255,255,.5);
  background:rgba(255,255,255,.14)}
@media (max-width:600px){.theme-switch .ts-texte{display:none}.theme-switch{padding:7px 11px}}
/* ═══ LES LIGNES DE LISTE NE DÉBORDENT JAMAIS ═══════════════════════════════════
   Client : « les débordements persistent aussi, ça fait chier ».
   MESURÉ sur une ligne « Collaborateur(trice) mobilisées » : le contenu (nom +
   identifiant + bouton « Ouvrir ») dépassait la largeur de la ligne et se faisait
   rogner. La cause : un conteneur `flex` dont AUCUN enfant n'a de largeur contrainte —
   un texte long pousse les autres hors du cadre. Les trois règles qui règlent ça pour
   toute ligne de liste, une fois pour toutes. */
.sheet-list-row{align-items:center}
.sheet-list-row>*{min-width:0}
/* Un texte long se replie ou s'abrège, il ne pousse jamais ses voisins. */
.sheet-list-row>span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.sheet-list-row>span:first-child{flex:1 1 auto}
.sheet-list-row>span:last-child{flex:0 0 auto;display:flex;align-items:center;gap:8px}
.sheet-list-row .badge{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sheet-list-row .text-button{flex:0 0 auto;white-space:nowrap}
/* Les lignes de personnes (mini-cartes) : même règle. */
.mini-person{max-width:100%;min-width:0}
.mini-person>span{min-width:0}
/* Les champs de saisie dans une ligne : jamais plus larges que la ligne. */
.sheet-list-row input,.sheet-list-row select{min-width:0;max-width:100%}
/* ═══ LES DEUX BOUTONS DE LA BARRE D'UNE FICHE : UN SEUL GABARIT ═════════════════
   Client : « ajuste-moi les boutons aussi ». MESURÉ : « fermer » avait une règle
   complète (38×38, cercle, grille) ; « thème » n'en avait AUCUNE — c'était un bouton de
   navigateur brut, aux proportions de son glyphe. Côte à côte, les deux juraient.
   Une seule règle pour les deux : même cercle, même taille, même réactivité. */
.sheet-pin-bar .sheet-theme-toggle,
.sheet-pin-bar .sheet-close-btn{
  -webkit-appearance:none;appearance:none;
  display:grid;place-items:center;
  width:38px;height:38px;flex:none;
  padding:0;border-radius:50%;cursor:pointer;
  border:1px solid rgba(23,23,25,.28);
  background:rgba(255,255,255,.94);
  color:#1a1b1d;
  font:600 17px/1 Manrope,sans-serif;
  box-shadow:0 3px 12px rgba(0,0,0,.16);
  transition:background .15s,transform .15s,color .15s,border-color .15s,box-shadow .15s}
.sheet-pin-bar .sheet-theme-toggle:hover,
.sheet-pin-bar .sheet-close-btn:hover{
  background:#fff;border-color:rgba(23,23,25,.44);transform:scale(1.06);
  box-shadow:0 4px 16px rgba(0,0,0,.22)}
.sheet-pin-bar .sheet-close-btn:hover{background:#c0392f;border-color:#c0392f;color:#fff}
/* Le glyphe du thème : légèrement plus petit que le ×, pour l'équilibre optique. */
.sheet-pin-bar .sheet-theme-toggle{font-size:15px}
.sheet-pin-bar .sheet-close-btn{font-size:20px}
/* Quand la fiche est en thème SOMBRE (le contenu), les deux s'inversent ensemble. */
.profile-sheet:not(.sheet-clair) .sheet-pin-bar .sheet-theme-toggle,
.profile-sheet:not(.sheet-clair) .sheet-pin-bar .sheet-close-btn{
  border-color:rgba(255,255,255,.42);
  background:rgba(15,16,17,.88);
  color:#fff;box-shadow:0 3px 12px rgba(0,0,0,.45)}
.profile-sheet:not(.sheet-clair) .sheet-pin-bar .sheet-theme-toggle:hover,
.profile-sheet:not(.sheet-clair) .sheet-pin-bar .sheet-close-btn:hover{
  background:#0f1011}
.profile-sheet:not(.sheet-clair) .sheet-pin-bar .sheet-close-btn:hover{
  background:#c0392f;border-color:#c0392f}
/* ═══ LE BLOC UTILISATEUR DE L'EN-TÊTE ══════════════════════════════════════════
   Client : « garde juste l'icône du profil ou la photo du compte, son nom, sa fonction,
   ainsi que le bouton déconnecté ». La pastille « Session active » et le sous-texte
   « Interface A » sont retirés : l'en-tête dit QUI est connecté, et rien de plus. */
.top-user{display:flex;flex-direction:column;line-height:1.25;min-width:0}
.top-user strong{font:700 12.5px Manrope,sans-serif;color:var(--text);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top-user small{font:600 10.5px Manrope,sans-serif;color:var(--muted);
  letter-spacing:.03em;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* L'icône du profil : elle garde son gabarit, elle est juste seule maintenant. */
.top-meta .avatar{flex:none}
/* L'en-tête respire : moins d'éléments, un espacement régulier. */
.top-meta{gap:12px}
@media (max-width:820px){
  /* À l'étroit, on garde l'essentiel : l'icône et le bouton. */
  .top-user{display:none}
}
/* ═══ LE CONTENEUR DE L'EN-TÊTE : LA CAPSULE, PUIS L'ACTION ═════════════════════
   Client : « garde juste l'icône du profil ou la photo du compte, son nom, sa fonction,
   ainsi que le bouton déconnecté ». Deux VOISINS distincts : l'identité dans la capsule,
   l'action destructive DEHORS — collée à droite, mais séparée. */
.top-actions{display:flex;align-items:center;gap:12px;flex:none}
.top-actions .top-meta{flex:none}
.top-actions .button-small{flex:none;white-space:nowrap}

/* La photo du compte dans l'avatar en losange : elle REMPLIT la forme, sans se déformer.
   ⚠️⚠️ LE SÉLECTEUR COUVRAIT `.top-meta` UNIQUEMENT, ET LA VIGNETTE VIT DANS `.profil-carte`.
   Client : « la vignette du profil en bas, chez moi Hatim I. concepteur, mais la photo du profil
   n'apparaît pas. » MESURÉ : l'image mesurait 0×0, avec `width:0px; height:0px` — parce que la
   seule règle qui la dimensionnait ne s'appliquait PAS là où elle est affichée.
   ⚠️ LA RÈGLE ÉTAIT JUSTE ; C'EST SON SÉLECTEUR QUI ÉTAIT TROP ÉTROIT. On l'élargit au lieu de
   dupliquer : deux copies divergeraient à la prochaine retouche (leçon payée sur le contraste).
   ⚠️ `.profil-carte` EST LE NOM RÉEL DU BOUTON (mesuré : id="profil-carte", 208x52). */
.top-meta .avatar.avatar-photo,
.profil-carte .avatar.avatar-photo,
.avatar.avatar-photo{padding:0;overflow:hidden}
.top-meta .avatar.avatar-photo img,
.profil-carte .avatar.avatar-photo img,
.avatar.avatar-photo img{
  width:100%;height:100%;max-width:100%;max-height:100%;
  object-fit:cover;object-position:center 18%;
  transform:rotate(-45deg) scale(1.42);   /* annule la rotation du losange et remplit les coins */
  display:block}
/* ═══ LA CARTE PROFIL ET SON MENU ════════════════════════════════════════════════
   Client : « la barre doit être comme ça ou avec un contraste ce qui va nous permettre de
   voir le nom et la fonction ». La barre ne contient QUE l'avatar, le nom et la fonction :
   le thème et la déconnexion sont dans le menu qui s'ouvre au clic. Un seul geste, et
   l'en-tête reste lisible. */
.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px}
.top-titre{min-width:0}
.top-titre h1{margin:0}

.top-profil{position:relative;flex:none}

/* ─── LA CARTE : un fond franc, pour que le nom se lise ───────────────────────
   MESURÉ : sur un fond blanc translucide, le nom gris et la fonction grise se
   confondaient avec l'arrière-plan. On monte donc le fond à ~96 % et on descend les
   textes : le nom en quasi-noir, la fonction en gris moyen. Deux niveaux lisibles. */
.profil-carte{
  display:flex;align-items:center;gap:11px;
  padding:7px 16px 7px 9px;border-radius:999px;cursor:pointer;
  border:1px solid rgba(23,23,25,.14);
  background:rgba(255,255,255,.97);
  box-shadow:0 1px 2px rgba(23,23,25,.08),0 6px 16px rgba(23,23,25,.07);
  transition:box-shadow .18s ease,border-color .18s ease,transform .18s ease}
.profil-carte:hover{
  border-color:rgba(212,43,56,.35);
  box-shadow:0 2px 4px rgba(23,23,25,.1),0 10px 24px rgba(23,23,25,.12)}
.profil-carte:focus-visible{outline:2px solid #D42B38;outline-offset:2px}
html[data-theme="dark"] .profil-carte{
  border-color:rgba(255,255,255,.16);background:rgba(28,29,32,.96);
  box-shadow:0 1px 2px rgba(0,0,0,.35),0 6px 16px rgba(0,0,0,.3)}

/* ─── L'AVATAR : le petit cadran DI, en losange ─────────────────────────────── */
.profil-carte .avatar{
  width:34px;height:34px;flex:none;display:grid;place-items:center;
  border-radius:9px;transform:rotate(45deg);
  border:1px solid rgba(212,165,116,.5);
  background:rgba(212,165,116,.2);color:#8a6836;
  font:700 11px 'DM Mono',monospace;overflow:hidden}
.profil-carte .avatar>*{transform:rotate(-45deg)}
html[data-theme="dark"] .profil-carte .avatar{
  border-color:rgba(212,165,116,.4);background:rgba(212,165,116,.16);color:#d4a574}

/* ─── LE NOM ET LA FONCTION, ALIGNÉS À GAUCHE ───────────────────────────────── */
.profil-carte .top-user{display:flex;flex-direction:column;line-height:1.22;
  align-items:flex-start;text-align:left;min-width:0}
.profil-carte .top-user strong{font:700 13px Manrope,sans-serif;color:#141416;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:190px}
.profil-carte .top-user small{font:600 10.5px Manrope,sans-serif;color:#63666b;
  letter-spacing:.03em;margin-top:1px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:190px}
html[data-theme="dark"] .profil-carte .top-user strong{color:#f6f5f3}
html[data-theme="dark"] .profil-carte .top-user small{color:rgba(246,245,243,.62)}

/* ─── LE MENU, SOUS LA CARTE ────────────────────────────────────────────────────
   Conçu pour GRANDIR : on ajoutera des options avant le séparateur. La déconnexion
   reste en bas, séparée — c'est une action destructrice. */
.profil-menu{
  position:absolute;top:calc(100% + 8px);right:0;z-index:600;
  min-width:238px;padding:6px;
  border-radius:14px;border:1px solid rgba(23,23,25,.13);
  background:rgba(255,255,255,.99);
  box-shadow:0 10px 34px rgba(23,23,25,.17),0 2px 6px rgba(23,23,25,.08);
  backdrop-filter:blur(14px);
  animation:profilMenuIn .16s ease}
@keyframes profilMenuIn{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}}
.profil-menu[hidden]{display:none}
html[data-theme="dark"] .profil-menu{
  border-color:rgba(255,255,255,.15);background:rgba(24,25,28,.99);
  box-shadow:0 10px 34px rgba(0,0,0,.5),0 2px 6px rgba(0,0,0,.3)}

.profil-menu button{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:10px 12px;border-radius:9px;cursor:pointer;
  border:0;background:transparent;text-align:left;
  font:600 12.5px Manrope,sans-serif;color:#1c1d1f}
.profil-menu button:hover{background:rgba(23,23,25,.06)}
html[data-theme="dark"] .profil-menu button{color:#f2f1ef}
html[data-theme="dark"] .profil-menu button:hover{background:rgba(255,255,255,.08)}
.profil-menu .pm-ic{font-size:14px;width:18px;text-align:center;color:#8a8d92;flex:none}
.profil-menu .pm-val{margin-left:auto;font:600 10.5px 'DM Mono',monospace;
  color:#8a8d92;letter-spacing:.04em}
.profil-menu .pm-sep{height:1px;margin:5px 8px;background:rgba(23,23,25,.1)}
html[data-theme="dark"] .profil-menu .pm-sep{background:rgba(255,255,255,.1)}
.profil-menu .pm-danger{color:#C4293a}
.profil-menu .pm-danger .pm-ic{color:#C4293a}
.profil-menu .pm-danger:hover{background:rgba(212,43,56,.09)}

/* Sur un écran étroit : on garde le nom, on réduit sa largeur. */
@media (max-width:1000px){
  .profil-carte .top-user strong,.profil-carte .top-user small{max-width:120px}
}
@media (max-width:700px){
  .profil-carte .top-user{display:none}
  .profil-carte{padding:7px}
}

/* ═══ LA BULLE DE PROFIL, NOIRE COMME LA BANNIÈRE, AVEC LE SHADER ═════════════════
   Client : « mets l'arrière-plan de la bulle de profil en sombre noir comme la bannière
   gauche des onglets, et n'oublie pas le shader qu'on avait sur la bannière de gauche ».
   On reprend donc la MATIÈRE de la barre latérale : le même noir, le même shader.

   Ces règles sont écrites APRÈS les précédentes et avec une spécificité qui couvre les
   deux thèmes (`html[data-theme=…] .top-profil .profil-carte`) : elles gagnent donc sur
   la version claire ET sur la variante thème sombre, sans avoir à arracher l'ancien bloc
   (ce qui aurait risqué de casser la cascade du reste de la fiche). */
html[data-theme="light"] .top-profil .profil-carte,
html[data-theme="dark"] .top-profil .profil-carte{
  position:relative;overflow:hidden;
  border-color:rgba(255,255,255,.14);
  background:#17181b;
  box-shadow:0 2px 6px rgba(0,0,0,.3),0 8px 22px rgba(0,0,0,.24)}
html[data-theme="light"] .top-profil .profil-carte:hover,
html[data-theme="dark"] .top-profil .profil-carte:hover{
  border-color:rgba(212,43,56,.5);
  box-shadow:0 2px 6px rgba(0,0,0,.34),0 10px 26px rgba(0,0,0,.3)}

/* LE SHADER : en absolu, donc DERRIÈRE le contenu. Le contenu repasse au-dessus par un
   z-index — sans ça, le canvas recouvrirait l'avatar et le nom (piège déjà rencontré
   sur les fiches). */
/* Dans le bouton, en absolu : il passe AU-DESSUS du fond opaque de `.profil-carte` (donc
   le shader se voit enfin), et SOUS le contenu, qui repasse par `z-index:1`. Le
   `border-radius` du bouton, avec son `overflow:hidden`, le découpe en capsule.
   MESURÉ : posé en FRÈRE du bouton, il était entièrement recouvert par le fond opaque
   `#17181b` — invisible par construction, quel que soit son réglage. */
.profil-carte .profil-shader{
  position:absolute;inset:0;width:100%;height:100%;
  display:block;border-radius:999px;pointer-events:none;z-index:0;
  /* LA BULLE N'AVAIT AUCUN FILTRE NI OPACITÉ : elle rendait donc à PLEINE PUISSANCE, plus
     fort que la bannière — incohérent entre deux surfaces du même shader. MESURÉ : bulle à
     23,3 % contre 19,6 % sur la bannière. On lui applique les MÊMES réglages (mesurés sur
     elle : .12 -> 14,4 % de teinte lors du balayage). */
  opacity:.12;mix-blend-mode:screen;filter:brightness(2.4) saturate(1.2)}
.profil-carte>*:not(.profil-shader){position:relative;z-index:1}

/* LE TEXTE SUR FOND NOIR : nom en blanc, fonction en gris clair. */
html[data-theme="light"] .top-profil .profil-carte .top-user strong,
html[data-theme="dark"] .top-profil .profil-carte .top-user strong{color:#f6f5f3}
html[data-theme="light"] .top-profil .profil-carte .top-user small,
html[data-theme="dark"] .top-profil .profil-carte .top-user small{color:rgba(246,245,243,.66)}
html[data-theme="light"] .top-profil .profil-carte .avatar,
html[data-theme="dark"] .top-profil .profil-carte .avatar{
  border-color:rgba(212,165,116,.45);background:rgba(212,165,116,.16);color:#d4a574}

/* ═══ LE PROFIL EN BAS DE LA BARRE LATÉRALE ══════════════════════════════════════
   Client : « enlève le header, déplace la bulle de profil dans la bannière gauche en bas
   à la place de Mode démonstration et Interface A, enlève tout ça ».
   Le header n'existe plus : le profil vit donc dans la barre latérale, collé en bas par
   `margin-top:auto` — c'est ce qui le pousse au pied de la colonne flex, quel que soit le
   nombre d'onglets au-dessus. La même mécanique servait à `.sidebar-foot` (retiré). */
.sidebar-profil{
  margin-top:auto;                 /* le pousse au bas de la colonne */
  position:relative;
  padding-top:14px;
  /* Un trait de séparation discret, comme l'ancien pied de barre : on lit que c'est un
     bloc à part, pas un onglet de plus. */
  border-top:1px solid rgba(255,255,255,.09);}

/* La carte occupe TOUTE la largeur disponible : dans une colonne de 240 px, une capsule
   centrée laisserait des marges inutiles et le nom serait tronqué. */
.sidebar-profil .profil-carte{
  width:100%;justify-content:flex-start;
  /* Sur fond sombre de barre, un fond légèrement plus clair détache la carte de son
     support sans casser l'unité de la colonne. */
  background:rgba(255,255,255,.055);
  border-color:rgba(255,255,255,.11);
  box-shadow:none;
  padding:8px 10px 8px 8px}
.sidebar-profil .profil-carte:hover{
  background:rgba(255,255,255,.09);
  border-color:rgba(212,43,56,.45)}
/* LE SÉPARATEUR AVANT LE PROFIL. Client : « après Classique, le séparateur vers profil ».
   MESURÉ à l'écran : mon premier trait (1 px à 9 % d'opacité) était trop faible — la vision
   n'en voyait aucun. On reprend le style des séparateurs de catégorie (ceux que le client a
   fait éclaircir) : deux traits de part et d'autre d'un mot, ici sans mot, donc un trait
   franc sur toute la largeur. */
.sidebar-profil::before{
  content:"";display:block;height:1px;
  margin:0 0 12px;background:#5c6066}

/* Le nom et la fonction, lisibles sur ce fond. */
.sidebar-profil .profil-carte .top-user strong{color:#f2f1ef;max-width:none}
.sidebar-profil .profil-carte .top-user small{color:rgba(242,241,239,.6);max-width:none}

/* LE NOM NE DOIT PAS ÊTRE TRONQUÉ. MESURÉ (vision) : « Direction production dé… ». La carte
   fait 208 px, moins l'avatar (34) et les marges : il ne reste que ~150 px pour le nom, et
   « Direction production démo » en 13 px n'y tient pas. On réduit donc la taille de 1 px et
   on resserre l'interlettre : deux réglages discrets, qui font tenir le nom entier sans
   changer sa hiérarchie visuelle. */
.sidebar-profil .profil-carte .top-user strong{
  font-size:12px;letter-spacing:-.012em}
.sidebar-profil .profil-carte .top-user small{
  font-size:10px;letter-spacing:.01em}

/* La carte cède la place au texte : l'avatar et le chevron gardent leur taille. */
.sidebar-profil .profil-carte{gap:9px;padding:8px 8px 8px 7px}
/* Le nom peut être long : on le laisse respirer, la carte fait toute la largeur. */
.sidebar-profil .profil-carte .top-user{flex:1 1 auto;min-width:0}

/* LE MENU S'OUVRE VERS LE HAUT : le profil est en bas de l'écran, un menu vers le bas
   sortirait de la fenêtre. La classe est posée par le JS après mesure de la place. */
.sidebar-profil .profil-menu{right:0;left:0;min-width:0;width:100%}
.profil-menu-haut{top:auto !important;bottom:calc(100% + 8px)}

/* Le menu est un enfant de `.top-profil` : il doit pouvoir déborder de la barre. */
.sidebar-profil{overflow:visible}
.sidebar{overflow:visible}

/* SUR UN ÉCRAN ÉTROIT : la barre latérale devient une bande en bas de l'écran (≤760 px),
   le profil n'a donc plus sa place dedans. On le masque — le contenu passe avant. */
@media (max-width:760px){
  .sidebar-profil{display:none}
}

/* LE CONTENU REMONTE : le header n'existe plus, donc `main` commence tout en haut.
   Sans cette ligne, le contenu garderait la hauteur réservée par l'ancien header. */
.main{padding-top:22px}

/* ═══ LA BARRE LATÉRALE COMPACTE, QUI S'ARRÊTE APRÈS LE PROFIL ═════════════════════
   Client : « la bannière gauche doit être compacte en bas : après Classique, le séparateur
   vers profil, et ça s'arrête. Le scroll de droite continue si on scrolle ».

   MESURÉ avant : la coque en `display:flex` étirait la barre à la hauteur du CONTENU de
   droite (1 130 px pour une fenêtre de 950) — le profil, poussé en bas de cette colonne
   géante, atterrissait hors de l'écran (y=1038).

   `align-items:flex-start` sur la coque : la barre ne prend plus que SA hauteur, donc elle
   s'arrête après le profil. `position:sticky;top:0` : elle reste visible pendant que la
   colonne de droite défile, au lieu de disparaître vers le haut. */
.app-shell{align-items:flex-start}

.sidebar{
  align-self:flex-start;            /* ne s'étire plus à la hauteur du contenu de droite */
  height:auto;                      /* sa propre hauteur : elle s'arrête après le profil */
  max-height:100vh;                 /* jamais plus haute que la fenêtre */
  flex:none;                        /* sa largeur reste fixe (240 px) */
  /* PAS d'`overflow:hidden` ici : le menu du profil doit pouvoir déborder vers le haut.
     Avec une hauteur automatique, aucun défilement interne n'est nécessaire. */
  overflow:visible}

/* `margin-top:auto` n'a plus d'effet (il n'y a plus de place libre à répartir) : le profil
   se pose simplement APRÈS le dernier séparateur — compact, comme demandé. On garde le
   trait de séparation, c'est lui qui marque la fin de la navigation. */
.sidebar-profil{margin-top:0;padding-top:12px;margin-bottom:0}

/* La colonne de droite : elle seule défile. `min-width:0` évite qu'un enfant large
   (tableau, grille) ne la fasse déborder. */
.main{min-width:0}

/* ═══ SEULE LA COLONNE DE DROITE DÉFILE ═════════════════════════════════════════════
   Client : « le scroll de droite continue si on scrolle ».

   La coque fait exactement la hauteur de la fenêtre, et `main` défile en INTERNE. La page
   n'a donc plus rien à faire défiler : la barre latérale est immobile par construction —
   pas besoin de `sticky` (mesuré : il ne tenait pas, la page défilait de 396 px) ni de
   `fixed` (qui sortirait la barre du flux et casserait la mise en page étroite).

   `overflow:hidden` va sur la COQUE, pas sur `html`/`body` : les vues participantes, qui
   ont besoin du défilement de page, ne sont donc pas touchées. */
body[data-interface="direction"] .app-shell{height:100vh;overflow:hidden}
body[data-interface="direction"] .main{
  height:100vh;
  overflow-y:auto;
  overflow-x:hidden;
  /* Le défilement devient fluide et discret, comme celui d'un panneau d'application. */
  -webkit-overflow-scrolling:touch;
  overscroll-behavior:contain}

/* ═══ LES PHOTOS DU TABLEUR EN MINIATURE ═══════════════════════════════════════════
   Client : « au niveau de la section des tableurs, les photos doivent être en miniature ».

   MESURÉ avant : `.sheet-av` n'avait ni largeur, ni hauteur, ni arrondi — l'image prenait
   donc la hauteur de la cellule (45 px, celle de `.sheet-input`) et toute la largeur
   qu'elle voulait. Dans une ligne de tableur, ça cassait la densité verticale.
   On en fait une vignette de 22 px, la taille qui se lit sans dominer le texte. */
.sheet-av{
  display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;flex:none;
  border-radius:5px;overflow:hidden;
  /* Un fond discret : les logos à fond transparent (PNG) ne flottent pas dans le vide. */
  background:rgba(23,23,25,.06);
  margin-right:7px;
  vertical-align:middle}
.sheet-av img{
  width:100%;height:100%;object-fit:cover;
  /* Le cadrage du projet : le visage en haut du cadre (18 % de la hauteur). */
  object-position:center 18%;display:block}
/* Un LOGO doit se voir en entier, jamais rogné : `contain` plutôt que `cover`. */
.sheet-av img.sheet-av-logo{object-fit:contain;object-position:center}

/* La cellule : la vignette et le champ sur la même ligne, alignés au centre. */
.sheet-av-cell{padding-left:8px !important}
.sheet-av-cell .sheet-cell-in{display:flex;align-items:center;gap:0}

/* Au survol, la vignette peut se voir en plus grand — sans déranger la ligne. */
.sheet-av:hover{position:relative;z-index:2;box-shadow:0 0 0 2px rgba(230,57,70,.35)}

/* ═══ LA BARRE REMPLIT LA HAUTEUR EN PLEIN ÉCRAN ═════════════════════════════════════
   Client : « quand on met [en] plein écran la bannière gauche doit tenir jusqu'en bas et le
   profil doit être en bas ».

   MESURÉ : avec `height:auto`, la barre faisait sa hauteur propre (926 px). En plein écran
   (1080 px), son fond sombre s'arrêtait donc à 926 px et laissait une bande claire en bas ;
   le profil, collé à la fin, se retrouvait à mi-hauteur.

   `height:100vh` : le fond descend jusqu'au bord, en plein écran comme en fenêtre.
   `margin-top:auto` sur le profil : il est poussé AU BAS de cette hauteur. Les onglets, eux,
   restent groupés sous le logo — c'est le profil qui descend, pas la navigation. */
body[data-interface="direction"] .sidebar{
  height:100vh;
  max-height:100vh;
  align-self:flex-start}

/* Le profil occupe le bas. `margin-top:auto` ne fonctionne QUE parce que la barre a
   maintenant une hauteur définie : sans elle, il n'y a aucune place libre à répartir. */
body[data-interface="direction"] .sidebar-profil{margin-top:auto}

/* Si les onglets dépassent la fenêtre (petit écran en hauteur), c'est la NAV qui défile en
   interne — jamais le profil : il reste visible, en bas. `min-height:0` est indispensable,
   sans quoi un enfant flex refuse de rétrécir sous sa taille de contenu. */
body[data-interface="direction"] .sidebar nav{
  flex:0 1 auto;
  min-height:0;
  overflow-y:auto;
  overflow-x:hidden}

/* ═══ PAS DE DÉFILEMENT DANS LA BARRE LATÉRALE ═══════════════════════════════════════
   Client : « éviter de mettre un scroll ».

   MESURÉ : la navigation faisait 668 px pour 589 px disponibles sur un écran de 768 px —
   il manquait 79 px, donc un défilement. Plutôt que de faire défiler (ce que le client
   refuse), on gagne la place À LA SOURCE en serrant les hauteurs.

   Budget, tout mesuré :
     12 onglets à 42 px -> 34 px            = 96 px
     3 séparateurs : marge haute 18 -> 12   = 18 px
     logo : marge basse 46 -> 30            = 16 px
                                             ------
                                             130 px  (pour 79 nécessaires)

   Les écarts restent discrets — 8 px par onglet : on serre la respiration verticale, on ne
   réduit pas la lisibilité. */
body[data-interface="direction"] .sidebar .nav-item{
  padding:8px 11px;
  min-height:34px;
  font-size:12.5px}

body[data-interface="direction"] .sidebar .nav-divider{
  margin:12px 8px 8px}

body[data-interface="direction"] .sidebar .brand{
  margin:0 9px 30px}

/* Le logo peut se resserrer aussi : 42 -> 36 px. */
body[data-interface="direction"] .sidebar .brand-logo-wrap{
  width:36px;padding:1px}

/* Et le pied de barre (le profil) : on lui retire 6 px de respiration. */
body[data-interface="direction"] .sidebar-profil{
  padding-top:8px;padding-bottom:2px}

/* EN DERNIER RECOURS, si la fenêtre est vraiment basse (≤ 700 px), c'est la NAV qui
   défile — le profil, lui, reste toujours visible en bas. Mais on ne déclenche ce
   défilement qu'après avoir tout serré : sur un écran courant, il n'apparaît jamais. */
@media (max-height:700px){
  body[data-interface="direction"] .sidebar nav{overflow-y:auto}
  body[data-interface="direction"] .sidebar nav::-webkit-scrollbar{width:0;height:0}
}
/* Aucune barre de défilement visible, dans tous les cas. */
body[data-interface="direction"] .sidebar nav::-webkit-scrollbar{width:0;height:0;display:none}
body[data-interface="direction"] .sidebar nav{scrollbar-width:none}

/* ═══ LES 25 DERNIERS PIXELS ═════════════════════════════════════════════════════════
   MESURÉ : après le premier resserrement, la nav tenait sur 950 et 768 px, mais il manquait
   encore 25 px sur un écran de 720 px. On les récupère sur les respirations restantes, sans
   toucher à la taille des onglets (déjà au minimum confortable) :
     · séparateurs : hauteur 12 -> 10, marges 12/8 -> 9/6     ≈  9 px
     · onglets : padding vertical 8 -> 7 (x12 onglets)        ≈ 12 px
     · logo : marge basse 30 -> 24                            ≈  6 px
                                                               ------
                                                              ≈ 27 px */
body[data-interface="direction"] .sidebar .nav-item{
  padding:7px 11px;
  min-height:32px}

body[data-interface="direction"] .sidebar .nav-divider{
  height:10px;
  margin:9px 8px 6px}

body[data-interface="direction"] .sidebar .brand{
  margin:0 9px 24px}

/* ═══ LE TRAIT AU-DESSUS DU PROFIL EST RETIRÉ ═══════════════════════════════════════
   Client : « enlève ce trait en dessus du profil ». Il venait du `::before` posé plus tôt
   sur `.sidebar-profil` (1 px, `rgb(92,96,102)`) — demandé à un moment, retiré maintenant.
   Les séparateurs de CATÉGORIE (PILOTAGE / BASES DE DONNÉES / CLASSIQUE) restent : eux
   structurent la navigation, on ne les touche pas. */
.sidebar-profil::before{content:none;display:none}

/* ═══ LA PAGE DE DROITE EST CENTRÉE ═══════════════════════════════════════════════════
   Client : « centre un peu la page de droite pour enlever le vide à droite ».

   MESURÉ sur 1920 px : la zone de contenu fait 1680 px, le contenu est plafonné à 1560 px,
   et les 120 px restants tombaient TOUS à droite (`margin:0`). Avec `margin:0 auto`, ils se
   répartissent 60/60 : plus de vide d'un seul côté, la page paraît centrée, et aucune
   largeur de carte ne change (la grille garde ses 4 colonnes). */
#app, .main > .content{margin-left:auto;margin-right:auto}

/* Sur un écran où le contenu ne remplit pas la largeur (≥ 1600 px), on garde le centrage.
   En dessous, il n'y a rien à centrer : le contenu occupe déjà tout. */
@media (min-width:1600px){
  #app{margin-left:auto;margin-right:auto}
}

/* ═══ LE TRAIT VIENT DE DEUX SOURCES — ON LES COUPE TOUTES LES DEUX ══════════════════
   MESURÉ : après avoir retiré le `::before`, un `border-top` de 1 px subsistait — posé par
   une règle plus ancienne sur `.sidebar-profil`. N'en corriger qu'une laissait le trait.
   On force donc les deux à zéro, quel que soit l'ordre de la cascade. */
.sidebar-profil{border-top:0 !important}

/* ═══ LE CENTRAGE EXACT ═══════════════════════════════════════════════════════════════
   MESURÉ à 1920 : 53 px de vide à gauche, 68 px à droite — un décalage de 15 px malgré
   `margin:0 auto`. La cause est l'asymétrie du contenu (le `padding` du main d'un côté,
   la largeur de la barre de défilement de l'autre). On centre sur la largeur RÉELLE
   disponible avec `justify-content`, et on annule toute marge héritée. */
body[data-interface="direction"] .main{
  display:flex;
  flex-direction:column;
  align-items:center}
body[data-interface="direction"] .main > #app{
  width:100%;
  max-width:1560px;
  margin:0}

/* ===== LE LOGO RAFRAICHIT LA PAGE ==================================================
   Client : « en cliquant sur le logo en haut, c'est comme si on cliquait sur F5 ».
   Le logo devient un bouton : curseur main, retour au survol, et un anneau de focus pour
   qui navigue au clavier -- on ne rend jamais un element cliquable sans son etat focus. */
.brand-cliquable{cursor:pointer;border-radius:10px;
  transition:background .18s ease,transform .18s ease;
  padding:4px;margin:-4px}
.brand-cliquable:hover{background:rgba(255,255,255,.07)}
.brand-cliquable:active{transform:scale(.98)}
.brand-cliquable:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.brand-cliquable.rafraichit .brand-logo-wrap{animation:tourLogo .55s ease}
@keyframes tourLogo{from{transform:rotate(0)}to{transform:rotate(360deg)}}

/* ===== LE TOAST : un message qui ne bloque pas =====================================
   Jamais d'`alert` : il BLOQUE le thread JS (defaut deja rencontre sur ce projet, il figeait
   la page et faisait lire une sortie silencieuse comme un succes). Un message en bas qui
   s'efface seul. */
.toast-app{position:fixed;left:50%;bottom:26px;transform:translate(-50%,14px);
  z-index:9000;max-width:min(560px,88vw);
  padding:12px 18px;border-radius:12px;
  background:rgba(20,21,24,.96);color:#f2f1ef;
  font:600 12.5px Manrope,sans-serif;line-height:1.5;text-align:center;
  border:1px solid rgba(255,255,255,.13);
  box-shadow:0 12px 34px rgba(0,0,0,.4);
  opacity:0;pointer-events:none;transition:opacity .22s ease,transform .22s ease}
.toast-app.on{opacity:1;transform:translate(-50%,0)}

/* ===== LE COMPTEUR D'UNE SECTION VIDE ==============================================
   MESURE : le titre affichait « 0Mon equipe » -- le compteur 0 collait au mot, sans espace,
   et n'apportait rien. Un compteur ne s'affiche que s'il y a QUELQUE CHOSE a compter :
   on masque le zero au lieu de l'imprimer. Les sections vides disent deja « Aucune
   personne rattachee. » -- c'est plus clair qu'un zero. */
.sheet-bulle .sheet-count-cache{display:none}

/* L'animation du logo : MESURE, elle etait trop breve pour etre vue (600 ms alors que la
   rotation dure 550 ms et que le rafraichissement des donnees prend ~2 s). On la garde
   visible pendant TOUT le rechargement : c'est le seul retour que l'utilisateur recoit. */
.brand-cliquable.rafraichit .brand-logo-wrap{animation:tourLogo 1.1s linear infinite}
.brand-cliquable{cursor:pointer}
.brand-cliquable.rafraichit{cursor:progress}

/* ═══ LE LOGO ALIGNÉ SUR LE TRAIT DES SÉPARATEURS ════════════════════════════════════
   Client : « agrandis-moi et aligne-moi le logo par rapport à la largeur du trait de
   PILOTAGE ».

   MESURÉ avant : le cadre du logo faisait 36 px alors que l'image en faisait 120 — elle
   DÉBORDAIT de 84 px. C'est ce qui donnait l'aspect bizarre. Les deux règles doivent bouger
   ensemble.

   CIBLE : le trait « PILOTAGE » fait 192 px (x 24 -> 216). On donne au logo la MÊME marge
   latérale que les séparateurs (8 px) et donc la même largeur. */
body[data-interface="direction"] .sidebar .brand{
  margin:0 8px 20px;          /* la marge des séparateurs : x 24, comme le trait */
  width:auto;                 /* la largeur vient des marges : 240 - 16 - 16 = 208… */
  display:block}              /* … et le cadre dedans fait 192 px, comme le trait */
body[data-interface="direction"] .sidebar .brand-logo-wrap{
  width:100%;                 /* le cadre prend toute la largeur disponible */
  height:auto;                /* la hauteur suit l'image : plus de cadre plus petit qu'elle */
  padding:0;
  background:transparent;
  border:0}
body[data-interface="direction"] .sidebar .brand-logo{
  width:100%;                 /* l'image remplit son cadre — les deux sont désormais liés */
  height:auto;                /* 256x62 -> a 192 px de large, 46 px de haut */
  object-fit:contain;
  display:block}

/* Le texte à côté du logo (OZELCA / LIVE COCKPIT) : masqué. Le logo PORTE déjà la marque,
   et le client veut la largeur du trait occupée par le logo, pas partagée avec du texte. */
body[data-interface="direction"] .sidebar .brand > div:not(.brand-logo-wrap){display:none}

/* ═══ PLUS D'ANIMATION AU CLIC ═══════════════════════════════════════════════════════
   Client : « quand je clique sur ozé il fait une drôle d'animation, non je ne veux pas
   cela ». La rotation est donc supprimée — le rafraîchissement, lui, reste (c'est ce qu'il
   avait demandé avant). On garde seulement le curseur `progress`, qui informe sans animer. */
.brand-cliquable.rafraichit .brand-logo-wrap{animation:none !important}
.brand-cliquable:hover{background:transparent}
.brand-cliquable:active{transform:none}
@keyframes tourLogo{from{transform:none}to{transform:none}}

/* ═══ LES 8 DERNIERS PIXELS DU LOGO ══════════════════════════════════════════════════
   MESURÉ puis VU : le logo faisait 184 px alors que le trait fait 192 — et la vision l'a
   relevé avant moi (« les lignes du séparateur sont plus larges que le logo »).
   Cause : `.brand-cliquable{padding:4px}` rognait l'image de 4 px de chaque côté.
   On le retire : l'image couvre alors exactement la largeur de son conteneur. */
body[data-interface="direction"] .sidebar .brand-cliquable{
  padding:0;
  margin:0;
  border-radius:0;
  background:transparent}
/* Le survol se marque par le CURSEUR, pas par un fond : le client ne veut pas d'effet
   visuel au clic, seulement que ce soit cliquable. */
body[data-interface="direction"] .sidebar .brand-cliquable:hover{
  background:transparent}

/* ═══ LE LOGO À L'EXACTE LARGEUR DU TRAIT ════════════════════════════════════════════
   MESURÉ, en deux temps :
     · avant : logo 184 px, trait 192 px  -> 8 px trop étroit (padding:4px)
     · après : logo 208 px, trait 192 px  -> 8 px trop large (le padding de la barre vaut
                                              16 px, pas 18 : ma marge de 8 px tombait juste)
   Les deux règles se contredisaient. On ne laisse donc plus la marge décider : la largeur
   du logo est FIXÉE à celle du trait, mesurée. C'est stable quelle que soit la cascade —
   et vérifiable. */
/* Client : « le logo doit être diminué de 30 % ». 192 x 0,70 = 134 px.
   On garde le bord GAUCHE aligné sur le trait (x = 24) : le logo commence là où le trait
   commence, il est simplement plus petit. On ne le centre PAS — l'alignement demandé
   juste avant reste la référence. */
body[data-interface="direction"] .sidebar .brand{
  /* CENTRÉ DANS LA COLONNE. Client : « centrer le logo OZÉ la com du haut dans la
     bannière de gauche ». On était précédemment aligné à gauche sur le trait ; désormais
     `margin:0 auto` répartit l'espace également des deux côtés. */
  margin:0 auto 20px;
  width:134px;                /* 192 px moins 30 % */
  display:block}
body[data-interface="direction"] .sidebar .brand-logo-wrap{
  width:134px;height:auto;padding:0;background:transparent;border:0}
body[data-interface="direction"] .sidebar .brand-logo{
  width:134px;height:auto;object-fit:contain;display:block}

/* ===== LE BLOC MOT DE PASSE DU PROFIL =============================================
   Client : « son mot de passe pour le profil, s'il veut le changer ».
   Le champ vit dans la section « Mon identite », sous les coordonnees. On le SEPARE
   visuellement : ce n'est pas une donnee d'identite comme les autres, c'est un secret. */
.champ-mdp{
  margin-top:14px;
  padding-top:14px;
  border-top:1px solid rgba(23,23,25,.1)}
.profile-sheet:not(.sheet-clair) .champ-mdp,
html[data-theme="dark"] .champ-mdp{border-top-color:rgba(255,255,255,.12)}

/* L'ETAT DE VERIFICATION : discret quand tout va bien, franc quand il y a un probleme.
   On ne crie pas pour un succes, on signale clairement une erreur -- l'utilisateur doit
   pouvoir enregistrer sans douter. */
.mdp-etat{
  font:600 11px Manrope,sans-serif;
  min-height:16px;
  margin-top:6px;
  color:#8a8d92}
.mdp-etat.bon{color:#2f8f5b}
.mdp-etat.mauvais{color:#C4293a}

/* ===== LE BLOC GEOLOCALISATION : ESPACEMENT =======================================
   MESURE (vision) : « le bouton « Aller a l'itineraire » semble legerement chevaucher ou
   etre trop colle au bord inferieur du champ de saisie ». On separe donc les trois
   elements (champ / boutons / etat) par des marges franches, au lieu de les empiler. */
.geo-lien-champ{margin-bottom:0}
.geo-lien-input{
  width:100%;
  padding:9px 11px;
  border-radius:8px;
  border:1px solid rgba(23,23,25,.14);
  background:rgba(255,255,255,.96);
  color:#141416;
  font:500 12px 'DM Mono',monospace;
  margin-bottom:10px}
.geo-lien-input:focus{
  outline:2px solid #D42B38;outline-offset:-2px;background:#fff}
.geo-lien-input.invalid{
  border-color:#C4293a;background:#fff0f1}
.geo-lien-input::placeholder{color:#9a9da2}
html[data-theme="dark"] .geo-lien-input{
  border-color:rgba(255,255,255,.16);background:rgba(255,255,255,.07);color:#f2f1ef}
html[data-theme="dark"] .geo-lien-input:focus{background:rgba(255,255,255,.1)}
html[data-theme="dark"] .geo-lien-input::placeholder{color:rgba(242,241,239,.45)}

/* Les boutons : sur leur propre ligne, avec de l'air au-dessus. */
.geo-row{
  display:flex;align-items:center;gap:10px;margin-bottom:8px}
.geo-lien{display:inline-flex;align-items:center;gap:7px;
  text-decoration:none;white-space:nowrap}
/* Le lien desactive (aucun lien, aucun filtre) : il informe sans inviter au clic. */
.geo-lien[aria-disabled="true"]{opacity:.5;cursor:default;pointer-events:none}
.geo-etat{margin-top:2px}

/* ═══════════════════════════════════════════════════════════════════════════════
   LE DASHBOARD GÉNÉRAL
   Client : « un dashboard très éblouissant », raffine, avec icones et graphiques.
   References : bento grid (blocs de tailles inegales), contraste de mode (un bloc sombre
   comme point focal), courbes lissees, jauge circulaire -- mais dans NOTRE charte.
   ═══════════════════════════════════════════════════════════════════════════════ */
.dash-tete { display: flex; align-items: flex-start; gap: 16px; margin: 0 0 18px; }
.dash-tete h2 { margin: 2px 0 3px; font-size: 21px; font-weight: 600; letter-spacing: -.2px; }
.dash-kicker { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 10px;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: #383633; }
.dash-sous { margin: 0; font-size: 12.5px; color: #7d8085; max-width: 640px; line-height: 1.5; }
.dash-retour { display: inline-flex; align-items: center; gap: 5px; border: 1px solid #e2e0db;
  background: #fff; color: #4a4d52; font: inherit; font-size: 12px; font-weight: 500;
  padding: 7px 13px; border-radius: 9px; cursor: pointer; white-space: nowrap;
  transition: border-color .15s, color .15s; }
.dash-retour:hover { border-color: #c9c6c0; color: #141416; }
.dash-retour span { font-size: 15px; line-height: 1; }

/* L'icone : filee, fine, toujours a la taille du texte qui l'entoure. */
.ico { width: 1.05em; height: 1.05em; fill: currentColor; flex: none;
  vertical-align: -.13em; }

/* ── LE MUR DES CAMPAGNES : le dashboard s'ouvre par campagne ───────────────── */
.dash-mur { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px; }
.dash-camp { position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 224px; border-radius: 16px; overflow: hidden; cursor: pointer;
  border: 1px solid #e6e3de; background: #fff; min-width: 0;
  transition: transform .18s ease, border-color .18s ease; }
.dash-camp:hover { transform: translateY(-2px); border-color: #d8d4ce; }
.dash-camp-fond { position: absolute; inset: 0; background-size: cover; background-position: center;
  background-color: #1c1d1f; }
.dash-camp-voile { position: absolute; inset: 0;
  background: linear-gradient(178deg, rgba(10,10,12,.10) 0%, rgba(10,10,12,.55) 52%,
  rgba(10,10,12,.90) 100%); }
.dash-camp-texte { position: relative; padding: 16px; color: #fff; min-width: 0; }
.dash-camp-statut { display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .12em;
  padding: 3px 8px; border-radius: 5px; background: rgba(212,43,56,.92); margin-bottom: 7px; }
.dash-camp-texte h3 { margin: 0 0 2px; font-size: 16.5px; font-weight: 600; letter-spacing: -.2px; }
.dash-camp-texte p { margin: 0 0 11px; font-size: 11px; color: rgba(255,255,255,.72); }
.dash-camp-chiffres { display: flex; flex-wrap: wrap; gap: 4px 14px; padding-top: 11px;
  border-top: 1px solid rgba(255,255,255,.16); }
.dash-camp-chiffres span { font-size: 10.5px; color: rgba(255,255,255,.74); }
.dash-camp-chiffres strong { font-size: 13px; color: #fff; margin-right: 2px; }
.dash-camp-entrer { display: flex; align-items: center; gap: 6px; margin-top: 12px;
  font-size: 11.5px; font-weight: 600; color: #f0c9cd; }

/* ── LES FILTRES : une barre unique qui pilote tous les blocs ───────────────── */
.dash-filtres { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  padding: 13px 15px; margin-bottom: 16px; background: #fff; border: 1px solid #e6e3de;
  border-radius: 13px; }
.dash-f-champ { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 132px; }
.dash-f-champ label { display: flex; align-items: center; gap: 4px; font-size: 9.5px;
  font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: #383633; }
.dash-f-champ select { width: 100%; min-width: 0; font: inherit; font-size: 12px; color: #26282c;
  background: #faf9f7; border: 1px solid #e6e3de; border-radius: 8px; padding: 7px 9px;
  cursor: pointer; }
.dash-f-champ select:focus { outline: none; border-color: #D42B38; }
.dash-f-reset { font: inherit; font-size: 11.5px; color: #7d8085; background: none;
  border: 1px solid #e6e3de; border-radius: 8px; padding: 7px 11px; cursor: pointer;
  white-space: nowrap; transition: color .15s, border-color .15s; }
.dash-f-reset:hover { color: #D42B38; border-color: #e8c3c7; }

/* ── LES KPI ────────────────────────────────────────────────────────────────── */
.dash-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 12px; margin-bottom: 14px; }
.dash-kpi { position: relative; display: flex; flex-direction: column; gap: 2px; padding: 14px 15px;
  background: #fff; border: 1px solid #e6e3de; border-radius: 13px; min-width: 0; overflow: hidden; }
.dash-kpi::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: #ece9e4; }
.dash-kpi.est-rouge::after { background: linear-gradient(90deg, #D42B38, #e8737c); }
.dash-kpi-ic { color: #b9b4ac; margin-bottom: 3px; }
.dash-kpi-ic .ico { width: 15px; height: 15px; }
.dash-kpi strong { font-size: 25px; font-weight: 600; letter-spacing: -.7px; line-height: 1.05;
  color: #141416; }
.dash-kpi.est-rouge strong { color: #D42B38; }
.dash-kpi-lib { font-size: 10.5px; color: #7d8085; letter-spacing: .01em; }

/* ── LA CARTE PHARE : le bloc sombre, point focal ───────────────────────────── */
/* Le bloc sombre : present comme point focal, mais pas massif -- il ne doit pas casser la
   page en deux. On reduit la hauteur et on adoucit le degrade. */
/* UNE SEULE REGLE pour .dash-phare : elle etait declaree DEUX FOIS (l'ancienne, puis celle
   du shader), et la seconde ecrasait la premiere. Une regle mort e = un piege pour la
   prochaine edition. */
.dash-phare { position: relative; display: flex; flex-wrap: wrap; align-items: center;
  gap: 16px 20px; padding: 17px 20px; margin-bottom: 14px; border-radius: 18px; overflow: hidden;
  border: 1px solid #26272a; min-width: 0; }
.dash-phare-gauche { flex: none; }
.dash-phare-kicker { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; font-size: 10px;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: #a8977c; }
/* LE BANDEAU DU HAUT : chaque chiffre recoit une icone et un separateur vertical fin, pour
   que la bande ne soit plus une simple suite de nombres nus. */
.dash-phare-chiffres { display: flex; flex-wrap: wrap; gap: 0; flex: 1 1 auto; min-width: 0; }
.dash-phare-chif { display: flex; align-items: center; gap: 9px; padding: 2px 18px;
  border-left: 1px solid rgba(255,255,255,.1); min-width: 0; }
.dash-phare-chif:first-child { padding-left: 0; border-left: 0; }
.dash-phare-chif-ic { display: flex; align-items: center; justify-content: center; width: 30px;
  height: 30px; flex: none; border-radius: 9px; background: rgba(255,255,255,.09);
  color: #d6d2cc; }
.dash-phare-chif-ic .ico { width: 16px; height: 16px; }
.dash-phare-chif-txt { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.dash-phare-chif-txt strong { font-size: 24px; font-weight: 600; color: #fff;
  letter-spacing: -.7px; line-height: 1.05; }
.dash-phare-chif-txt span { font-size: 10.5px; color: #a2a7ad; white-space: nowrap; }
.dash-phare-pied { display: flex; flex-wrap: wrap; gap: 6px 20px; flex: 1 1 100%;
  padding-top: 14px; border-top: 1px solid rgba(255,255,255,.09); }
.dash-phare-pied span { display: flex; align-items: center; gap: 6px; font-size: 11px;
  color: #8d9095; min-width: 0; }
.dash-phare-pied strong { color: #e8e6e3; font-weight: 600; }
.dash-phare-pied .ico { color: #a8977c; }

/* La jauge : l'anneau se remplit, le chiffre au centre. */
.dash-jauge { position: relative; width: 92px; height: 92px; flex: none; }
.dash-jauge svg { width: 100%; height: 100%; display: block; }
.dash-jauge-centre { position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px; }
.dash-jauge-centre strong { font-size: 20px; font-weight: 600; color: #fff; letter-spacing: -.5px; }
.dash-jauge-centre span { font-size: 7.5px; color: #8d9095; text-transform: uppercase;
  letter-spacing: .06em; }

/* ── LES BLOCS ──────────────────────────────────────────────────────────────── */
.dash-deux { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
.dash-colonne { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.dash-bloc { display: flex; flex-direction: column; min-width: 0; padding: 16px 17px;
  background: #fff; border: 1px solid #e6e3de; border-radius: 14px; }
.dash-bloc-tete { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 13px; min-width: 0; }
/* Client : « les icones sobres gris [...] au niveau des titres de widget, et agrandis en
   double l'icone et le titre ». On DOUBLE les deux : 12,5 px -> 25 px, et l'icone suit.
   L'icone reste GRISE et sobre -- c'est la charte, on ne la colore pas. */
.dash-bloc-tete h3 { display: flex; align-items: center; gap: 10px; margin: 0; font-size: 25px;
  font-weight: 600; color: #1d1e21; letter-spacing: -.5px; min-width: 0; }
.dash-bloc-tete .ico { color: #383633; width: 25px; height: 25px; }
.dash-bloc-tete .bloc-ic { width: 25px; height: 25px; }
.dash-bloc-note { font-size: 11px; color: #383633; white-space: nowrap; }
.dash-vide { padding: 22px 12px; text-align: center; font-size: 12px; color: #383633; }

/* ── LA COURBE ──────────────────────────────────────────────────────────────── */
.dash-courbe { display: block; width: 100%; height: 108px; overflow: visible; }
.dash-courbe-pied, .dash-heures-pied { display: flex; justify-content: space-between;
  align-items: center; margin-top: 7px; font-size: 10px; color: #383633; }
.dash-courbe-max { color: #D42B38; font-weight: 600; }
.dash-heures { display: block; width: 100%; height: 96px; }
.dash-heures-pied span { flex: 1 1 0; text-align: center; }

/* ── LES LIGNES (personnes, responsables, lieux, goodies) ───────────────────── */
.dash-liste { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dash-ligne { display: flex; align-items: center; gap: 10px; padding: 7px 6px; border-radius: 9px;
  min-width: 0; transition: background .13s; }
.dash-ligne:hover { background: #faf9f7; }
.dash-av { position: relative; display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none; border-radius: 50%; overflow: hidden;
  background: #f1efec; border: 1px solid #e6e3de; }
.dash-av.petit { width: 25px; height: 25px; }
.dash-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%;
  display: block; }
.dash-av-ini { font-size: 11px; font-weight: 600; color: #8d9095;
  letter-spacing: .02em; }
.dash-nom { display: flex; flex-direction: column; gap: 0; flex: 1 1 auto; min-width: 0; }
.dash-nom strong { font-size: 12.5px; font-weight: 500; color: #26282c; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.dash-nom small { font-size: 10px; color: #383633; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.dash-barre { flex: 0 0 62px; height: 5px; border-radius: 3px; background: #f1efec;
  overflow: hidden; }
.dash-barre i { display: block; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, #D42B38, #e8737c); }
.dash-barre.est-dore i { background: linear-gradient(90deg, #c1934f, #d4a574); }
.dash-val { display: flex; flex-direction: column; align-items: flex-end; gap: 0;
  flex: 0 0 54px; min-width: 0; }
.dash-val strong { font-size: 13px; font-weight: 600; color: #141416; }
.dash-val small { font-size: 9.5px; color: #383633; }
.dash-ligne.compacte .dash-val strong { font-size: 12px; }

/* Le tri : des boutons discrets qui basculent l'ordre de la liste. */
.dash-tri { display: flex; gap: 3px; flex: none; }
.dash-tri-b { font: inherit; font-size: 10px; font-weight: 500; color: #8d9095; background: none;
  border: 1px solid transparent; border-radius: 6px; padding: 3px 7px; cursor: pointer;
  white-space: nowrap; transition: color .13s, background .13s; }
.dash-tri-b:hover { color: #26282c; }
.dash-tri-b.on { color: #D42B38; background: #fdf0f1; }

/* ── LE JOURNAL ─────────────────────────────────────────────────────────────── */
/* Les journaux sont BORNES : un flux de 40 lignes face a une liste de 2 campagnes creusait
   un grand vide sous le bloc voisin. On borne donc la hauteur, et les blocs courts
   s'etirent pour remplir leur colonne. */
.dash-journal { display: flex; flex-direction: column; gap: 1px; max-height: 232px;
  overflow-y: auto; min-width: 0; }
.dash-j-ligne { display: flex; align-items: flex-start; gap: 9px; padding: 7px 6px;
  border-radius: 8px; min-width: 0; transition: background .13s; }
.dash-j-ligne:hover { background: #faf9f7; }
.dash-j-texte { display: flex; flex-direction: column; gap: 1px; min-width: 0;
  font-size: 12px; color: #4a4d52; line-height: 1.42; }
.dash-j-texte strong { color: #141416; font-weight: 600; }
.dash-j-texte small { font-size: 10px; color: #383633; }

@media (max-width: 1100px) {
  .dash-deux { grid-template-columns: 1fr; }
  .dash-mur { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
}

/* ── LES GOODIES : une grille de cartes, avec « sortis » et « remis » ──────────── */
.dash-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
  gap: 10px; }
.dash-g { display: flex; align-items: center; gap: 10px; padding: 9px 11px; min-width: 0;
  background: #faf9f7; border: 1px solid #ece9e4; border-radius: 11px; }
.dash-g-img { display: flex; align-items: center; justify-content: center; flex: none;
  width: 36px; height: 36px; border-radius: 9px; background: #fff; border: 1px solid #ece9e4;
  overflow: hidden; }
.dash-g-img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.dash-g-vide { color: #c9c4bc; }
.dash-g-vide .ico { width: 16px; height: 16px; }
.dash-g-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.dash-g-txt strong { font-size: 12px; font-weight: 500; color: #26282c; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.dash-g-txt small { font-size: 9.5px; color: #383633; white-space: nowrap; }
.dash-g-txt small b { color: #D42B38; font-weight: 600; }
.dash-g-bar { height: 4px; border-radius: 2px; background: #ece9e4; overflow: hidden;
  display: block; }
.dash-g-bar i { display: block; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, #D42B38, #e8737c); }

/* Le bandeau d'opération n'a plus de jauge : on redistribue l'espace. */
.dash-phare-chiffres { gap: 30px; }
.dash-phare-pied { margin-top: 14px; }

/* ── LE CYCLE DE LA ROUE : DEUX ISSUES ──────────────────────────────────────────
   Client : « il y a soit annuler, soit valider. Une fois qu'on valide, le bouton valider
   est plus grand. » — le geste normal doit dominer visuellement. */
.cycle-actions { display: flex; flex-direction: column; align-items: stretch; gap: 9px;
  margin-top: 16px; max-width: 400px; margin-left: auto; margin-right: auto; }
/* Le grand bouton : le geste normal, donc il domine. Hauteur garantie a 51 px. */
.bouton-valider { width: 100%; box-sizing: border-box; min-height: 51px; padding: 15px 22px;
  font-size: 15px; font-weight: 600; letter-spacing: .01em; line-height: 1.2;
  display: inline-flex; align-items: center; justify-content: center; }
/* Le grand bouton domine : l'annulation reste possible mais ne prend pas la meme place.
   Client : « une fois qu'on valide, le bouton valider est plus grand ». */
/* MESURE : l'annuler faisait 63 px de haut pour 51 au valider -- il paraissait donc PLUS
   GRAND sur cette dimension. On le borne en hauteur ET en largeur, pour que « valider » soit
   le plus grand dans les deux sens. */
/* Client : « le bouton valider est plus grand ». On IMPOSE donc la hauteur de l'annuler :
   mesure avant ce reglage = 57 px contre 51 au valider -- il paraissait donc plus grand sur
   cette dimension, a cause d'un `line-height` non maitrise. */
.bouton-annuler { align-self: center; width: auto; max-width: 58%; box-sizing: border-box;
  height: 30px; min-height: 30px; max-height: 30px; display: inline-flex;
  align-items: center; justify-content: center; color: #8d9095; font-size: 11.5px;
  line-height: 1; padding: 0 13px; border: 1px solid transparent; background: none; }
.bouton-annuler:hover { color: #D42B38; }
.cycle-msg { font-size: 12px; color: #D42B38; text-align: center; min-height: 1em; }
.reward-reveal .cycle-actions + p, .reward-reveal .cycle-msg { margin-bottom: 0; }

/* ── LA PHOTO DE PREUVE DANS LE CYCLE ──────────────────────────────────────────
   Client : « Le lot est remis, c'est après la photo de preuve. » — c'est elle qui atteste
   la remise, donc elle est obligatoire, et elle commande le bouton « Valider ». */
.cycle-proof { max-width: 400px; margin: 16px auto 0; }
.cycle-proof .upload-zone { display: flex; flex-direction: column; align-items: center;
  gap: 3px; padding: 16px 14px; text-align: center; cursor: pointer; border-radius: 11px;
  border: 1.5px dashed rgba(212,43,56,.38); background: rgba(212,43,56,.035); }
.cycle-proof .upload-zone:hover { border-color: rgba(212,43,56,.62); }
.cycle-proof .upload-zone input { display: none; }
.cycle-proof .upload-zone strong { font-size: 13px; font-weight: 600; color: #26282c; }
.cycle-proof .upload-zone span { font-size: 10.5px; color: #383633; }
#cycle-proof-etat { margin-top: 8px; text-align: center; font-size: 11.5px; color: #7d8085;
  min-height: 1em; }
.cycle-actions .bouton-valider:disabled { opacity: .42; cursor: not-allowed; }
/* La barre grise : ce qui n'est ni remis ni annule, sans couleur de marque. */
.dash-barre.est-gris i { background: #c9c4bc; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LES NOUVEAUX WIDGETS DU DASHBOARD
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── LE SÉLECTEUR DE MESURE ──────────────────────────────────────────────────
   Client : « des KPI qui changent selon ce qu'on veut, selon d'autres tris aussi ». */
.dash-mesure { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin-bottom: 11px; }
.dash-mesure-lib { display: flex; align-items: center; gap: 5px; margin-right: 6px;
  font-size: 9.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: #383633; }
.dash-mesure-b { font: inherit; font-size: 11.5px; font-weight: 500; color: #6a6d72;
  background: #fff; border: 1px solid #e6e3de; border-radius: 8px; padding: 5px 11px;
  cursor: pointer; white-space: nowrap; transition: color .15s, border-color .15s, background .15s; }
.dash-mesure-b:hover { color: #26282c; border-color: #d8d4ce; }
.dash-mesure-b.on { color: #fff; background: #D42B38; border-color: #D42B38; font-weight: 600; }

/* Un KPI qui porte du TEXTE (le nom du goodie le plus sorti) : plus petit, sur deux lignes. */
.dash-kpi.est-texte strong { font-size: 15px; letter-spacing: -.2px; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* ── LES TROIS FENÊTRES PHARES, FIGÉES ET VIVANTES ───────────────────────────
   Client : « on doit la voir figée [...] son image de profil avec son nom [...] le lieu le
   plus actif [...] son icône, avec sa ville à côté [...] ça doit être animé, ça doit être
   vivant ». */
.dash-phare-fige { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 10px; flex: 1 1 100%; padding-top: 14px; margin-top: 2px;
  border-top: 1px solid rgba(255,255,255,.09); }
/* ── LES FENÊTRES PHARES DU BLOC SOMBRE ─────────────────────────────────────
   Client : « les widgets sombres peuvent etre meilleurs, et les icones aussi ».
   Chaque fenetre porte SA couleur : liseré en haut, anneau autour du visuel, halo interne. */
/* EN NÉGATIF : la fenetre passe en FOND CLAIR sur le bloc sombre.
   Client : « rends les cartes en negatif pour se faire decoler du fond ». Sur du noir, c'est
   le contraste maximal -- la carte se detache sans avoir besoin d'ombre. */
.dash-fige { position: relative; display: flex; align-items: center; gap: 12px; padding: 13px 14px 15px;
  min-width: 0; overflow: hidden; border-radius: 13px;
  background: linear-gradient(155deg, #fdfcfa 0%, #f3efea 100%);
  border: 1px solid rgba(255,255,255,.5);
  transition: transform .2s ease, border-color .2s ease, background .2s ease; }
.dash-fige::before { content: ''; position: absolute; right: -34px; top: -34px; width: 96px;
  height: 96px; border-radius: 50%; pointer-events: none; opacity: .13;
  background: radial-gradient(circle, var(--fige-c, #fff) 0%, transparent 70%); }
.dash-fige:hover { transform: translateY(-2px); border-color: rgba(255,255,255,.75); }
.dash-fige-liseré { position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--fige-c, #fff); opacity: .92; }
/* LE VISUEL : UN SEUL GABARIT pour les trois fenetres. La troisieme etait un carre gris
   au milieu de deux cercles -- defaut principal releve sur la capture. Meme forme, meme
   taille, meme fond pour toutes : seules la couleur et l'image changent.
   Le fond est IVOIRE translucide : un produit sombre s'y detache au lieu de s'y noyer. */
.dash-fige-av { position: relative; z-index: 1; display: flex; align-items: center;
  justify-content: center; width: 46px; height: 46px; flex: none; border-radius: 13px;
  overflow: visible; padding: 3px;
  background: rgba(255,255,255,.90);
  border: 2px solid var(--fige-c, rgba(255,255,255,.2));
  transition: transform .22s ease; }
.dash-fige:hover .dash-fige-av { transform: scale(1.06); }
.dash-fige-av img { width: 100%; height: 100%; object-fit: contain; border-radius: 10px;
  display: block; }
/* Une PERSONNE se cadre au visage : son portrait remplit la medaille. */
.dash-fige-av:not(.est-produit) img { object-fit: cover; object-position: center 18%;
  border-radius: 10px; }
.dash-fige-av .ico { width: 19px; height: 19px; }
.dash-fige-av .dash-av-ini { font-size: 14px; font-weight: 600; }
/* La veilleuse se pose sur le coin bas-droit, quelle que soit la forme. */
.dash-fige-av .veilleuse { right: -2px; bottom: -2px; width: 11px; height: 11px;
  border: 2px solid #fdfcfa; }
/* La veilleuse : un point de vie sur l'avatar. C'est ce qui rend la fenetre « vivante ». */
.veilleuse { position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px;
  border-radius: 50%; background: #4a8c5c; border: 2px solid #17181b;
  animation: battement 2.4s ease-in-out infinite; }
@keyframes battement {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.22); opacity: .62; }
}
.dash-fige-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.dash-fige-txt { position: relative; z-index: 1; }
/* PAS de `color-mix` : ce navigateur l'ignore en silence (mesure faite). Un gris clair fixe. */
/* LES LIBELLES : releves trop sombres et trop petits sur la capture (9 px, #9ba0a6).
   On monte a 10 px et on eclaircit -- sur fond noir, le gris moyen disparait. */
/* Les textes passent en SOMBRE : la carte est claire, donc du blanc dessus serait illisible.
   C'est le vrai travail du « negatif » -- inverser aussi la typographie. */
.dash-fige-txt small { font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;
  color: #a09889; }
.dash-fige-txt strong { font-size: 14px; font-weight: 600; color: #1d1e21; letter-spacing: -.15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-fige-txt span { display: flex; align-items: center; gap: 4px; font-size: 11px;
  color: #6d7075; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-fige-txt span .ico { width: 12px; height: 12px; flex: none; opacity: .75; }
.dash-fige-val { position: relative; z-index: 1; display: flex; flex-direction: column;
  align-items: flex-end; flex: none; font-size: 23px; font-weight: 600; color: #141416;
  letter-spacing: -.7px; line-height: 1.02; }
.dash-fige-val small { font-size: 9.5px; font-weight: 400; color: #383633; letter-spacing: 0; }

/* ── LA BARRE DE PART ────────────────────────────────────────────────────────
   Un record sans echelle ne dit rien : « 37 goodies » se lit par rapport aux 114 remis.
   La barre donne cette proportion, dans la couleur de la fenetre. */
.dash-fige-part { position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: rgba(255,255,255,.07); overflow: hidden; }
.dash-fige-part i { display: block; height: 100%; background: var(--fige-c, #fff);
  border-radius: 0 2px 2px 0; transform-origin: left center;
  animation: remplir .9s cubic-bezier(.22,.9,.24,1) both; }

/* ── LES PASTILLES DE COULEUR ────────────────────────────────────────────────
   Une pastille dit « ce libelle a une couleur », pour lire les listes d'un coup d'œil. */
.pastille { display: inline-block; width: 7px; height: 7px; border-radius: 2px;
  margin-right: 6px; flex: none; vertical-align: 1px; }

/* ── LA PROGRESSION PAR ÉVÉNEMENT ────────────────────────────────────────────
   Client : « la progression des nouveaux participants, plus 2, plus 3 [...] +50 ». */
.dash-prog-svg { display: block; width: 100%; height: 92px; overflow: visible; }
.dash-prog-marche { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid #f1efec; }
.dash-prog-pas { display: flex; flex-direction: column; gap: 1px; padding: 7px 10px;
  flex: 1 1 104px; min-width: 0; background: #faf9f7; border: 1px solid #ece9e4;
  border-radius: 9px; }
.dash-prog-tete { font-size: 9.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; }
.dash-prog-pas strong { font-size: 19px; font-weight: 600; color: #26282c;
  letter-spacing: -.5px; line-height: 1.1; }
.dash-prog-lieu { font-size: 10px; color: #6a6d72; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.dash-prog-detail { font-size: 9.5px; color: #383633; }
.dash-prog-cumul { font-size: 9.5px; color: #8d9095; font-weight: 500; }
.dash-legende { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 11px;
  font-size: 10.5px; color: #7d8085; }
.dash-legende span { display: flex; align-items: center; gap: 0; }

/* ── LES GOODIES PAR LIEU ────────────────────────────────────────────────────
   Client : « d'activite ou de goodies par lieu ». */
.dash-lieux { display: grid; grid-template-columns: repeat(auto-fill, minmax(266px, 1fr));
  gap: 10px; }
.dash-lieu-bloc { display: flex; flex-direction: column; gap: 7px; padding: 11px 12px;
  min-width: 0; background: #faf9f7; border: 1px solid #ece9e4; border-radius: 11px; }
.dash-lieu-tete { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dash-lieu-nom { display: flex; align-items: center; font-size: 12.5px; font-weight: 500;
  color: #26282c; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 1 1 auto; min-width: 0; }
.dash-lieu-ville { font-size: 10px; color: #383633; flex: none; }
.dash-g-tags { display: flex; flex-wrap: wrap; gap: 5px; }
.dash-g-tag { display: inline-flex; align-items: center; gap: 0; font-size: 10px;
  color: #4a4d52; padding: 3px 7px; border-radius: 6px; border: 1px solid #ece9e4;
  white-space: nowrap; }
.dash-g-tag b { margin-left: 5px; font-weight: 600; color: #26282c; }
.dash-barre.large { flex: none; width: 100%; height: 5px; }

/* ── LA BARRE DOUBLE (lancers / remises) ─────────────────────────────────────
   Client : « lancer remise, ou lancer remise validee ». Deux mesures dans une barre. */
.dash-barre.double { position: relative; display: block; flex: 0 0 62px; }
.dash-barre.double i { position: absolute; top: 0; left: 0; height: 100%; }
.dash-barre.double i.est-dore { bottom: 0; top: auto; height: 2px; border-radius: 0; }

/* ── LES PETITES RETOUCHES ──────────────────────────────────────────────────── */
.dash-journal.large { max-height: 320px; }
.dash-ligne.est-actif { background: #fdf0f1; }
.dash-ligne.est-alerte .dash-val strong { color: #D42B38; }
.dash-kpi.est-texte::after { background: linear-gradient(90deg, #d4a574, #e8ceac); }

@media (max-width: 760px) {
  .dash-phare-fige { grid-template-columns: 1fr; }
  .dash-prog-pas { flex: 1 1 88px; }
}
/* Un bloc court s'etire pour remplir sa colonne : plus de vide sous « Pilotage ». */
.dash-deux > .dash-bloc { align-self: stretch; }
.dash-deux .dash-colonne > .dash-bloc:last-child { flex: 1 1 auto; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LES ÉQUIPES — LE RESPONSABLE, PUIS SES COLLABORATEURS DESSOUS
   ═══════════════════════════════════════════════════════════════════════════════ */
.dash-equipes { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: 12px; }
.dash-equipe { display: flex; flex-direction: column; gap: 9px; padding: 13px 14px;
  min-width: 0; background: #faf9f7; border: 1px solid #ece9e4; border-radius: 13px;
  transition: transform .2s ease, border-color .2s ease, background .2s ease; }
.dash-equipe:hover { transform: translateY(-2px); border-color: #ded9d1; background: #fff; }

/* L'EN-TÊTE : l'avatar du responsable, son nom, et les TOTAUX de son equipe. */
.dash-equipe-tete { display: flex; align-items: center; gap: 10px; min-width: 0; }
.dash-equipe-av { position: relative; display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: none; border-radius: 50%; overflow: visible;
  background: #f1efec; border: 1px solid #e6e3de; }
.dash-equipe-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%;
  border-radius: 50%; display: block; }
.dash-equipe-nom { display: flex; flex-direction: column; gap: 0; flex: 1 1 auto; min-width: 0; }
.dash-equipe-nom small { font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  color: #383633; }
.dash-equipe-nom strong { font-size: 13.5px; font-weight: 600; color: #141416;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-equipe-nom em { font-size: 10px; font-style: normal; color: #8d9095; }
.dash-equipe-total { display: grid; grid-template-columns: 1fr 1fr; gap: 1px 12px; flex: none; }
.dash-equipe-total span { font-size: 9.5px; color: #8d9095; white-space: nowrap; }
.dash-equipe-total b { font-size: 12px; font-weight: 600; color: #26282c; margin-right: 3px; }
.dash-equipe-fleche { color: #c9c4bc; flex: none; }
.dash-equipe-fleche .ico { width: 16px; height: 16px; }

/* LES MEMBRES : une ligne par collaborateur, sous le responsable. */
.dash-equipe-membres { display: flex; flex-direction: column; gap: 1px; padding-top: 9px;
  border-top: 1px solid #f1efec; }
.dash-membre { display: flex; align-items: center; gap: 9px; padding: 6px 6px 6px 4px;
  border-radius: 8px; min-width: 0; border-left: 2px solid transparent;
  transition: background .15s, border-color .15s; }
.dash-membre:hover { background: #fff; border-left-color: #D42B38; }
.dash-membre-av { display: flex; align-items: center; justify-content: center; width: 26px;
  height: 26px; flex: none; border-radius: 50%; overflow: hidden; background: #f1efec;
  border: 1px solid #e6e3de; }
.dash-membre-av img { width: 100%; height: 100%; object-fit: contain; display: block; padding: 1px; }
.dash-membre-av.est-photo img { object-fit: cover; object-position: center 18%; padding: 0; }
.dash-membre-nom { display: flex; flex-direction: column; gap: 0; flex: 1 1 auto; min-width: 0; }
.dash-membre-nom strong { font-size: 12px; font-weight: 500; color: #26282c;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-membre-nom small { font-size: 9.5px; color: #383633; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.dash-membre-val { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 46px; }
.dash-membre-val strong { font-size: 12.5px; font-weight: 600; color: #141416; }
.dash-membre-val small { font-size: 9px; color: #383633; }
.dash-vide.petit { padding: 10px; font-size: 11px; }

/* ── LES ICÔNES DANS LES TITRES DE BLOC ────────────────────────────────────── */
.bloc-ic { width: 17px; height: 17px; object-fit: contain; flex: none; }
.dash-av.carre { border-radius: 6px; }
.dash-av.carre img { object-fit: contain; padding: 2px; }

/* ── LES GOODIES D'UNE ÉQUIPE ──────────────────────────────────────────────── */
.dash-g-tags.est-equipe { padding-top: 2px; }
.dash-g-tag.est-equipe { display: inline-flex; align-items: center; gap: 5px;
  background: #fff; border-color: #ece9e4; font-size: 9.5px; padding: 3px 7px 3px 4px; }
.dash-g-tag.est-equipe img { width: 15px; height: 15px; object-fit: contain; flex: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   L'ANIMATION
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « je veux que ça soit animé ». On anime ce qui RACONTE quelque chose : une barre
   qui se remplit montre un volume, un point qui pulse montre une personne active. On
   n'anime pas pour occuper l'ecran -- un dashboard reste lisible 8 heures. */

/* La barre SE REMPLIT une fois, au chargement. `both` conserve l'etat final. */
@keyframes remplir { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.dash-barre.animee i { transform-origin: left center; animation: remplir .85s cubic-bezier(.22,.9,.24,1) both; }
.dash-barre.animee.large { width: 100%; flex: none; height: 6px; }

/* La veilleuse : un battement lent, deja defini pour les fenetres phares. */
.dash-equipe-av .veilleuse { width: 10px; height: 10px; border-color: #faf9f7; }

/* Les cartes arrivent en fondu legerement decale : donne un rythme sans distraire. */
@keyframes apparaitre { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.dash-equipe { animation: apparaitre .34s ease both; }
.dash-equipe:nth-child(2) { animation-delay: .06s; }
.dash-equipe:nth-child(3) { animation-delay: .12s; }
.dash-equipe:nth-child(4) { animation-delay: .18s; }

/* L'icone de l'entreprise respire au survol de sa ligne. */
.dash-ligne:hover .dash-av img { transform: scale(1.08); transition: transform .2s ease; }
.dash-av img { transition: transform .2s ease; }

/* On respecte les preferences : qui ne veut pas d'animation n'en aura pas. */
@media (prefers-reduced-motion: reduce) {
  .dash-barre.animee i, .dash-equipe, .veilleuse { animation: none !important; }
  .dash-equipe:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   LES ICÔNES DES WIDGETS — DES PASTILLES COLORÉES
   ═══════════════════════════════════════════════════════════════════════════════ */
/* LA PASTILLE D'ICÔNE : les teintes sont posees EN LIGNE par le JS (rgba), pas par
   `color-mix` -- ce navigateur ignorait la regle, donc les icones restaient grises. */
/* LE BADGE : la couleur en fond, l'icone en blanc. Un fond pale se lit comme du blanc --
   mesure confirmee, la pastille doit porter la teinte PLEINE pour se voir. */
.dash-kpi-ic { display: inline-flex; align-items: center; justify-content: center;
  width: 31px; height: 31px; border-radius: 9px; margin-bottom: 8px; flex: none;
  box-shadow: 0 1px 2px rgba(20,20,22,.14);
  transition: transform .2s ease, filter .2s ease; }
.dash-kpi-ic .ico { width: 16px; height: 16px; }
.dash-kpi:hover .dash-kpi-ic { transform: scale(1.12) rotate(-4deg); filter: saturate(1.15); }

/* LE LISERÉ DU HAUT : la teinte de l'icone, rappelee sur toute la largeur de la carte. */
.dash-kpi-liseré { position: absolute; top: 0; left: 0; right: 0; height: 3px; opacity: .85; }
.dash-kpi::after { content: none; }
.dash-kpi:hover .dash-kpi-ic { transform: scale(1.08) rotate(-3deg); }
.dash-kpi { padding: 15px 16px; }
.dash-kpi strong { font-size: 27px; }

/* LA PASTILLE DE TENDANCE : « +30% après l'événement d'hier ». */
.dash-kpi-delta { display: inline-flex; flex-direction: column; gap: 0; margin-top: 6px;
  font-size: 11px; font-weight: 600; color: #2f8f5b; letter-spacing: -.1px; }
.dash-kpi-delta em { font-size: 9px; font-style: normal; font-weight: 400; color: #383633;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LA PROGRESSION COMPACTE — LES DERNIERS PAS, LISIBLES D'UN COUP
   ═══════════════════════════════════════════════════════════════════════════════ */
.dash-prog-compact { display: grid; grid-template-columns: 176px 1fr; gap: 16px; align-items: start; }
.dash-cumul { display: flex; flex-direction: column; gap: 2px; padding: 14px 15px;
  border-radius: 12px; min-width: 0;
  background: radial-gradient(120% 150% at 10% 0%, #2a2226 0%, #191a1c 55%, #141517 100%);
  border: 1px solid #26272a; }
.dash-cumul-lib { display: flex; align-items: center; gap: 6px; font-size: 9.5px;
  letter-spacing: .07em; text-transform: uppercase; color: #a8977c; }
.dash-cumul-lib .ico { width: 12px; height: 12px; }
.dash-cumul strong { font-size: 36px; font-weight: 600; color: #fff; letter-spacing: -1px;
  line-height: 1.02; margin: 4px 0 1px; }
.dash-cumul-note { font-size: 10px; color: #8d9095; }

.dash-pas-liste { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 8px; }
.dash-pas { display: flex; flex-direction: column; gap: 1px; padding: 10px 11px; min-width: 0;
  background: #faf9f7; border: 1px solid #ece9e4; border-radius: 11px;
  transition: transform .18s ease, border-color .18s ease; }
.dash-pas:hover { transform: translateY(-2px); border-color: #ded9d1; }
/* LE PLUS RÉCENT : il est mis en avant, c'est ce qui vient de se passer. */
.dash-pas.est-recent { background: #fff; border-color: #e6e3de; }
.dash-pas.est-recent::after { content: 'le plus recent'; display: block; margin-top: 3px;
  font-size: 8px; letter-spacing: .07em; text-transform: uppercase; color: #D42B38; }
.dash-pas-quand { font-size: 9px; letter-spacing: .05em; text-transform: uppercase;
  color: #383633; }
.dash-pas strong { font-size: 24px; font-weight: 600; letter-spacing: -.7px; line-height: 1.05; }
.dash-pas-lieu { font-size: 10.5px; color: #4a4d52; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.dash-pas-lieu b { color: #D42B38; font-weight: 600; }
.dash-pas-bar { display: block; height: 4px; border-radius: 2px; background: #f1efec;
  overflow: hidden; margin: 5px 0 3px; }
.dash-pas-bar i { display: block; height: 100%; border-radius: 2px; transform-origin: left center;
  animation: remplir .8s cubic-bezier(.22,.9,.24,1) both; }
.dash-pas-detail { font-size: 9px; color: #383633; }

@media (max-width: 900px) {
  .dash-prog-compact { grid-template-columns: 1fr; }
  .dash-cumul strong { font-size: 30px; }
}
/* Un groupe SANS personne : pas d'icone, et `color-mix` evite (non supporte ici).
   Client : « sans responsable c'est sans icone aussi ». */
.dash-equipe-av.est-vide { background: none; border: 1px dashed #e0dcd5; }

/* ── L'ARC DE CERCLE ──────────────────────────────────────────────────────────
   Inspiration ciblee de la reference : un arc avec degrede et le chiffre au centre, plutot
   qu'une barre lineaire. On le reserve au TAUX DE REMISE -- la seule donnee qui soit un
   pourcentage, donc la seule ou l'arc se lit mieux qu'un nombre nu.
   Client : « pas pour l'ensemble mais pour certains ». */
/* (ancien arc, conserve au cas ou) */
.dash-arc { position: relative; flex: none; width: 116px; text-align: center;
  padding-right: 18px; margin-right: 4px; border-right: 1px solid rgba(255,255,255,.1); }
.dash-arc svg { display: block; width: 116px; height: 66px; overflow: visible; }
.dash-arc-txt { display: flex; flex-direction: column; align-items: center; gap: 0;
  margin-top: -14px; }
.dash-arc-txt strong { font-size: 25px; font-weight: 600; color: #fff; letter-spacing: -.8px;
  line-height: 1.05; }
.dash-arc-txt span { font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;
  color: #c2c7cd; }
.dash-arc-txt em { font-size: 9.5px; font-style: normal; color: #a2a7ad; white-space: nowrap; }

@media (max-width: 900px) {
  .dash-arc { border-right: 0; padding-right: 0; margin-right: 0; margin-bottom: 8px; }
}

/* ── LE SHADER DU BLOC SOMBRE ─────────────────────────────────────────────────
   Client : « pour le noir utilise notre fameux shader pour animer le fond s'il est trop plat
   et statique ». C'est le MEME shader que la barre laterale -- celui qui porte l'identite de
   la marque -- mais dose plus fort, parce qu'ici le fond est sombre : il n'a pas a lutter
   contre un fond clair.
   Mesures de reference sur la barre : 0,20 invisible -- 0,42 bien dose -- 0,17 juste. */
.dash-phare-shader { position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; opacity: .55; pointer-events: none; }
/* Le contenu passe AU-DESSUS du shader, sinon il serait recouvert. */
.dash-phare > *:not(.dash-phare-shader) { position: relative; z-index: 1; }
/* Le fond de la surface passe SOUS le shader : on le garde tres sombre, pour que le shader
   soit la seule source de lumiere. */
/* LE FOND A 85 % D'OPACITE, donc 15 % de transparence.
   Client : « rends le plus transparent, 15 % de transparence ».
   On agit sur le FOND et non sur `opacity` : une opacite sur la carte entiere laverait les
   chiffres, les libelles et les jauges. Ici, seule la SURFACE s'eclaircit -- le shader pose
   derriere devient perceptible, et le contenu reste net. */
.dash-phare { background: radial-gradient(130% 170% at 6% -10%,
  rgba(32,27,30,.85) 0%, rgba(19,19,21,.85) 52%, rgba(13,14,16,.85) 100%); }

/* ── LES MINI-WIDGETS À FOND CLAIR ────────────────────────────────────────────
   Client : « utilises des jauges et des mini widgets avec fond clair pour qu'on puisse voir ».
   Sur le bloc sombre, le fond CLAIR est ce qui fait exister le widget -- un anneau sombre sur
   du noir disparaitrait. C'est aussi ce qui donne le contraste « premium » de la reference. */
.dash-mw-rangee { display: flex; flex-wrap: wrap; gap: 10px; flex: none; }
.dash-mw { display: flex; align-items: center; gap: 11px; padding: 11px 14px 11px 11px;
  border-radius: 14px; min-width: 0;
  background: linear-gradient(160deg, #fdfcfa 0%, #f4f1ec 100%);
  border: 1px solid rgba(255,255,255,.55);
  transition: transform .2s ease, box-shadow .2s ease; }
.dash-mw:hover { transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(0,0,0,.28); }
.dash-mw-jauge { position: relative; display: flex; align-items: center; justify-content: center;
  width: 54px; height: 54px; flex: none; }
.dash-mw-jauge svg { width: 100%; height: 100%; display: block; }
.dash-mw-jauge strong { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 15px; font-weight: 600; color: #26282c;
  letter-spacing: -.4px; }
.dash-mw-jauge strong em { font-size: 8.5px; font-style: normal; font-weight: 500; color: #63656a;
  margin-left: 1px; }
@keyframes tracer { from { stroke-dasharray: 0 200; } }
.dash-mw-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.dash-mw-txt strong { font-size: 12px; font-weight: 600; color: #26282c; white-space: nowrap; }
.dash-mw-txt small { font-size: 9.5px; color: #63656a; white-space: nowrap; }

/* Le collaborateur libre : un groupe sans responsable est un STATUT, pas un defaut.
   Un liseré dore le distingue des equipes encadrees. */
.dash-equipe.est-libre { border-color: #e6dcc8; background: #fdfbf6; }
/* ⚠️ MESURE : #b08d4f sur #fdfbf6 = 3,00:1 — sous le seuil WCAG. La TEINTE est conservee
   (c est l identite de l equipe) ; seule la luminosite descend : #74603a = 5,51:1. */
.dash-equipe.est-libre .dash-equipe-nom small { color: #74603a; }

/* ── LE DÉCALAGE, MONTRÉ ──────────────────────────────────────────────────────
   Client : « je pense que les cadeaux seront remis autant que la roue va faire gagner, il y
   aurait un leger decalage ». On affiche donc le remis ET l'attente dans la meme pastille :
   le gagnant d'une roue ne repart pas toujours avec son cadeau tout de suite.
   Un widget qui ne montre qu'un seul des deux chiffres fait croire a une erreur de calcul. */
.dash-g-att { margin-left: 5px; font-style: normal; font-size: 9px; font-weight: 600;
  color: #c08a2e; background: rgba(192,138,46,.13); border-radius: 4px; padding: 1px 4px; }
.dash-g-att:empty { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE JOURNAL DU TERRAIN — enrichi
   Client : « ecrire le nom du profil collaborateur/responsable [...] avec date, heure et
   seconde ». Chaque ligne montre qui, quoi, sur quoi, et QUAND a la seconde. */
.dash-j-ligne { display: flex; align-items: flex-start; gap: 10px; padding: 9px 8px;
  border-radius: 9px; min-width: 0; border-left: 2px solid transparent;
  transition: background .14s, border-color .14s; }
.dash-j-ligne:hover { background: #faf9f7; }
/* La couleur du bord dit LA FAMILLE du geste : un coup d'oeil suffit a trier. */
.dash-j-ligne.est-roue { border-left-color: #D42B38; }
.dash-j-ligne.est-cadeau { border-left-color: #c1934f; }
.dash-j-ligne.est-stock { border-left-color: #3f7d8c; }
.dash-j-ligne.est-tablette { border-left-color: #7d5ba6; }
.dash-j-ligne.est-equipe { border-left-color: #4a8c5c; }
.dash-j-ligne.est-personne { border-left-color: #5c7fa6; }
.dash-j-av { display: flex; align-items: center; justify-content: center; width: 28px;
  height: 28px; flex: none; border-radius: 50%; overflow: hidden; background: #f1efec;
  border: 1px solid #e6e3de; }
.dash-j-av img { width: 100%; height: 100%; object-fit: contain; display: block; padding: 1px; }
.dash-j-av.est-photo img { object-fit: cover; object-position: center 18%; padding: 0; }
.dash-j-corps { display: flex; flex-direction: column; gap: 0; min-width: 0; flex: 1 1 auto; }
.dash-j-qui { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.dash-j-qui strong { font-size: 12.5px; font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.dash-j-qui em { font-size: 9.5px; font-style: normal; color: #383633; white-space: nowrap; }
.dash-j-fait { font-size: 12px; color: #4a4d52; line-height: 1.4; }
.dash-j-fait b { color: #1d1e21; font-weight: 600; }
.dash-j-quand { font-size: 10px; color: #383633; font-family: 'DM Mono', monospace;
  letter-spacing: -.2px; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE WIDGET DES GOODIES — RÉORGANISÉ
   Client : « ce widget est mal organisé et moche aussi ». */
.dash-lieux { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 12px; }
.dash-lieu-bloc { display: flex; flex-direction: column; gap: 0; padding: 0; min-width: 0;
  background: #fff; border: 1px solid #ece9e4; border-radius: 14px; overflow: hidden; }

/* L'EN-TÊTE DU LIEU : le nom, la ville, et le chiffre — rapprochés, plus de vide au milieu. */
.dash-lieu-tete { display: flex; align-items: center; gap: 9px; padding: 13px 14px 11px;
  min-width: 0; }
.dash-lieu-nom { display: flex; align-items: center; font-size: 13.5px; font-weight: 600;
  color: #1d1e21; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 1 1 auto; min-width: 0; letter-spacing: -.15px; }
.dash-lieu-ville { display: block; font-size: 10px; color: #383633; flex: none;
  background: #f7f5f1; border-radius: 5px; padding: 2px 6px; }
/* LE CHIFFRE : dans sa propre pastille, colle a droite, sans vide intermediaire. */
.dash-lieu-bloc .dash-val { display: flex; flex-direction: column; align-items: flex-end;
  flex: none; gap: 0; padding: 0; }
.dash-lieu-bloc .dash-val strong { font-size: 22px; font-weight: 600; color: #1d1e21;
  letter-spacing: -.6px; line-height: 1; }
.dash-lieu-bloc .dash-val small { font-size: 8.5px; color: #383633; white-space: nowrap; }

/* LA BARRE DE VOLUME : un vrai indicateur, collé sous l'en-tête, dans un rail. */
.dash-lieu-bloc .dash-barre.large { flex: none; width: auto; height: 6px; margin: 0 14px;
  border-radius: 3px; }

/* LES PASTILLES DE GOODIES : une grille reguliere, plus de retour a la ligne hasardeux. */

/* ═══════════════════════════════════════════════════════════════════════════════
   LE WIDGET « CADEAUX REMIS PAR LIEU » — RÉORGANISÉ
   Client : « ce widget est mal organisé et moche aussi, arrange-le ».
   Défauts mesurés : pas d'icône de lieu (un rond de couleur), pas d'icône de produit,
   et une barre qui affichait 100 % sur le premier bloc (le lieu comparé à lui-même). */
.dash-lieux { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 12px; }
.dash-lieu-bloc { display: flex; flex-direction: column; min-width: 0; background: #fff;
  border: 1px solid #ece9e4; border-radius: 14px; overflow: hidden;
  transition: border-color .18s ease, box-shadow .18s ease; }
.dash-lieu-bloc:hover { border-color: #ded9d1; }

/* ── L'EN-TÊTE : l'icône sobre du lieu, son nom, sa ville, puis le chiffre. ─────── */
.dash-lieu-tete { display: flex; align-items: center; gap: 9px; padding: 13px 14px 11px;
  min-width: 0; }
.dash-lieu-ic { display: flex; align-items: center; justify-content: center; width: 26px;
  height: 26px; flex: none; border-radius: 8px; background: #f7f5f1; border: 1px solid #f1efec; }
.dash-lieu-ic img { width: 16px; height: 16px; object-fit: contain; display: block; }
.dash-lieu-nom { font-size: 13.5px; font-weight: 600; color: #1d1e21; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0;
  letter-spacing: -.15px; }
.dash-lieu-ville { font-size: 10px; color: #383633; flex: none; background: #f7f5f1;
  border-radius: 5px; padding: 2px 6px; }
.dash-lieu-bloc .dash-val { display: flex; flex-direction: column; align-items: flex-end;
  flex: none; }
.dash-lieu-bloc .dash-val strong { font-size: 22px; font-weight: 600; color: #1d1e21;
  letter-spacing: -.6px; line-height: 1; }
.dash-lieu-bloc .dash-val small { font-size: 8.5px; color: #383633; white-space: nowrap; }

/* ── LA BARRE DE VOLUME : un rail sous l'en-tête, sur une échelle PARTAGÉE. ───── */
.dash-lieu-bloc .dash-barre.large { flex: none; width: auto; height: 5px; margin: 0 14px;
  border-radius: 3px; background: #f1efec; }
.dash-lieu-bloc .dash-barre.large i { display: block; height: 100%; border-radius: 3px;
  transform-origin: left center; animation: remplir .9s cubic-bezier(.22,.9,.24,1) both; }

/* ── LES PASTILLES : une ligne par cadeau, avec son icône et son compte. ─────── */
.dash-g-tags.est-lieu { display: grid; grid-template-columns: 1fr; gap: 4px; padding: 11px 14px 13px;
  border-top: 1px solid #f7f5f1; margin-top: 11px; min-width: 0; }
/* `min-width: 0` sur la pastille aussi : sans ca, un nom long impose sa largeur a la grille
   et la carte deborde -- c'est le vrai mecanisme du debordement signale. */
.dash-g-tag.est-lieu { min-width: 0; }
/* ET LA CARTE ENTIERE : elle ne doit jamais pousser plus large que sa colonne. */
.dash-lieu-bloc { max-width: 100%; }
.dash-g-tag.est-lieu { display: flex; align-items: center; gap: 8px; background: #faf9f7;
  border: 1px solid #f4f2ee; border-radius: 8px; padding: 5px 9px 5px 6px; font-size: 11px;
  color: #4a4d52; min-width: 0; transition: background .14s ease, border-color .14s ease; }
.dash-g-tag.est-lieu:hover { background: #f6f4f0; border-color: #ece9e4; }
.dash-g-tag-ic { display: flex; align-items: center; justify-content: center; width: 20px;
  height: 20px; flex: none; border-radius: 6px; background: #fff; border: 1px solid #f1efec;
  overflow: hidden; }
.dash-g-tag-ic img { width: 15px; height: 15px; object-fit: contain; display: block; }
.dash-g-tag.est-lieu .dash-g-tag-nom { flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* LE CHIFFRE QUI SORT, ET CELUI QUI RESTE : deux colonnes fixes, empilées chiffre + libellé.
   C'est ce qui garantit que rien ne déborde : les deux cellules sont `flex: none` (donc leur
   largeur ne dépend pas du contenu voisin) et le NOM prend le reste avec une ellipse. */
.dash-g-tag.est-lieu b { display: flex; flex-direction: column; align-items: flex-end;
  flex: none; font-size: 13px; font-weight: 600; color: #1d1e21; letter-spacing: -.25px;
  line-height: 1.05; }
.dash-g-tag.est-lieu b i { font-size: 7.5px; font-style: normal; font-weight: 500;
  color: #383633; letter-spacing: .04em; }
.dash-g-tag.est-lieu small.dash-g-reste { display: flex; flex-direction: column;
  align-items: flex-end; flex: none; font-size: 11.5px; font-weight: 600; color: #3f7d8c;
  line-height: 1.05; }
.dash-g-tag.est-lieu small.dash-g-reste i { font-size: 7.5px; font-style: normal;
  font-weight: 500; color: #383633; letter-spacing: .04em; }
/* Le séparateur vertical entre les deux chiffres : un simple filet, pas un cadre. */
.dash-g-tag.est-lieu small.dash-g-reste { border-left: 1px solid #f1efec; padding-left: 8px; }

/* La grille des goodies globaux : meme soin. */
.dash-g { border-left-width: 3px; border-left-style: solid; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE PANNEAU NÉGATIF DU TABLEAU DE BORD
   Client : « reproduire le même fond que le blanc en version négative, dark [...] sois
   artiste ». C'est le négatif exact du fond clair : le halo rouge passe du coin haut droit,
   la diagonale garde son angle, mais tout est profond et cramoisi. Le shader WebGL déjà
   présent (`#dash-fond-shader`) l'anime. */
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LE GRAND PANNEAU DU DASHBOARD — « EFFET GLASS »
   Client : « le grand essaie d'enlever le shader et de ne mettre que du vert transparent moderne.
   Tout comme notre charte. » puis, me voyant chercher une couleur : « verre transparent · la
   matière » et « pas la couleur », enfin : « on l'appellera l'effet glass ».

   ⚠️⚠️ « VERT TRANSPARENT » NE VOULAIT PAS DIRE « TROUVE UN VERT ». J'étais parti chercher les
   verts de la charte (`#0d6b3f`, `#087443`…) pour en poser un. Le client m'a arrêté : c'est la
   MATIÈRE qui compte, pas une teinte. Le mot décrivait ce qu'il voyait à travers le voile des
   onglets, pas une couleur à appliquer.

   ⚠️ LA RECETTE N'EST PAS INVENTÉE : c'est celle du bloc « CADRES EN VERRE LÉGER » de
   `direction.html` (26/09), que le client a déjà validée sur les onglets et les cartes de
   réglages. On la réutilise telle quelle — une matière validée vaut mieux qu'une création.

   ⚠️ « EFFET GLASS » = `backdrop-filter` : c'est LUI qui floute et sature ce qui passe derrière.
   Sans lui, un `rgba(255,255,255,.05)` n'est qu'un aplat transparent. AUCUN canvas, AUCUN shader
   n'est nécessaire pour faire du verre.

   ⚠️ CE QUI RESTE DU PANNEAU D'AVANT, ET POURQUOI :
      · `border-radius: 26px` — sa géométrie, décidée avant, on n'y touche pas ;
      · `::before` (le reflet diffus) et `::after` (le liseré lumineux) — ce sont deux PIÈCES du
        verre : c'est ce qui empêche le cadre d'être un aplat. Les retirer appauvrirait l'effet.
   ⚠️ CE QU'ON RETIRE : le fond plein (halo + dégradé) — c'est lui qui empêchait la transparence.
   Le halo rouge du site reste visible, mais À TRAVERS le verre, flouté par lui — c'est plus juste
   que de le peindre sur le panneau. */
.dash-panneau { position: relative; overflow: hidden; isolation: isolate;
  padding: 24px 26px 30px; margin: 0 0 16px; border-radius: 26px;
  /* le voile à 5 % : ni blanc, ni coloré — la matière, pas la couleur */
  background: rgba(255,255,255,.05);
  /* le liseré qui dessine le cadre (recette validée) */
  border: 1px solid rgba(255,255,255,.55);
  /* ⚠️⚠️ C'EST LUI, L'EFFET GLASS. Il floute et sature le fond du site qui passe derrière. */
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  backdrop-filter: blur(14px) saturate(1.1);
  /* la même ombre douce que les autres cadres en verre : elle DÉCOLLE le cadre du fond animé */
  box-shadow: 0 14px 40px rgba(23,23,25,.08), 0 2px 6px rgba(23,23,25,.04),
              inset 0 1px 0 rgba(255,255,255,.6); }

/* LA LUMIÈRE DU VERRE : un reflet diffus qui vient du haut. C'est ce qui empêche le fond
   d'être « un aplat noir » — le défaut que le client reproche. */
.dash-panneau::before { content: ''; position: absolute; inset: 0; z-index: 2;
  pointer-events: none; border-radius: inherit;
  background: radial-gradient(120% 62% at 68% -18%, rgba(255,255,255,.55), transparent 62%); }
/* ET UN FIN LISERÉ LUMINEUX SUR LE BORD HAUT — le « stroke » des cartes de verre. */
.dash-panneau::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  z-index: 3; pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.22) 22%,
    rgba(255,214,180,.30) 50%, rgba(255,255,255,.22) 78%, transparent); }

/* ⚠️⚠️ LE SHADER DU GRAND PANNEAU EST RETIRÉ, ET SON STYLE AVEC LUI. Client : « le grand essaie
   d'enlever le shader et de ne mettre que du vert transparent moderne » puis « on l'appellera
   l'effet glass ».
   ⚠️ CE QU'IL ÉTAIT : le shader d'identité du cockpit, qui est ROUGE (mesuré : `_SHADER_FS_CLAIR`
   fait `mix(color, redLayer, wave * 0.95 + glowSpot * 0.05)`), posé ici en filigrane à 10 %
   d'opacité et en `multiply` sur un fond clair.
   ⚠️ LE VERRE N'A BESOIN D'AUCUN CANVAS : c'est `backdrop-filter` qui fait l'effet glass (posé
   sur `.dash-panneau` juste au-dessus). Aucun shader n'est requis pour de la matière.
   ⚠️ LE STYLE EST NEUTRALISÉ PLUTÔT QUE SUPPRIMÉ : la trace explique pourquoi, et un futur lecteur
   ne le remettra pas par mégarde en croyant à un oubli.
   ⚠️ ET SI QUELQU'UN RÉACTIVE LE CANVAS UN JOUR, IL FAUDRA AUSSI LE REMETTRE DANS `SURFACES`
   (app.js) — le moteur ne peint que les surfaces de cette liste. */
/* .dash-panneau-shader { position: absolute; inset: 0; width: 100%; height: 100%;
     display: block; z-index: 0; opacity: .10; mix-blend-mode: multiply; pointer-events: none; } */
/* Le contenu passe AU-DESSUS du shader ET du reflet. */
.dash-panneau-in { position: relative; z-index: 4; }

/* ── LES ENFANTS S'ADAPTENT AU FOND SOMBRE ──────────────────────────────────────
   Sans ces règles, le titre et le sous-titre resteraient anthracite sur noir. */
.dash-panneau .dash-kicker { color: #b08a68; }
.dash-panneau .dash-kicker .ico { color: #c1934f; }
.dash-panneau .dash-tete h2 { color: #1d1e21; }
.dash-panneau .dash-sous { color: #7d8085; }

/* LA BARRE DE FILTRES : blanche, comme les autres cartes claires. */
.dash-panneau .dash-filtres { background: #fff; border-color: #e6e3de; }

/* LES INTITULÉS DE BLOCS : ils sont sur des cartes claires, mais leur titre peut déborder
   au-dessus. On garde le titre sombre sur la carte claire ; seule la note s'éclaircit si
   elle se retrouve directement sur le panneau. */
.dash-panneau .dash-vide { color: #8f8a83; }

/* ── LES CARTES CLAIRES : LE CONTRASTE EST LE SUJET ─────────────────────────────
   Les références font exactement ça : un fond profond, des cartes claires posées dessus.
   On renforce donc la présence des cartes au lieu de les fondre. */
.dash-panneau .dash-bloc,
.dash-panneau .dash-kpi,
.dash-panneau .dash-lieu-bloc { border-radius: 18px;
  /* LE REFLET DU VERRE : un liseré blanc interne sur le bord haut. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.62), 0 1px 2px rgba(0,0,0,.20); }
/* Le halo coloré de la charte, très diffus, sous les rangées de cartes. */
.dash-panneau .dash-grille { position: relative; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LES CARTES SOMBRES — « la carte des collaboratrices, la carte des sous-widgets »
   Client : « je préfère avoir des fenêtres séparées au lieu d'avoir toute une bande noire.
   Ce qui soit sombre c'est la carte qui est autour [...] c'est là où il doit y avoir du noir.
   Et le reste il doit y avoir pour garder l'effet clair. »
   Donc le fond de la PAGE reste clair ; seules ces cartes-ci sont profondes. */
.dash-bloc.est-sombre { background: linear-gradient(158deg, #1c1a1d 0%, #141416 58%, #101113 100%);
  border-color: rgba(255,255,255,.075); position: relative; overflow: hidden;
  /* Le reflet du verre, comme sur les fenêtres claires — mais inversé. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.09); }
/* Le halo rouge de la charte, très diffus, en haut à droite de chaque carte sombre. */
.dash-bloc.est-sombre::before { content: ''; position: absolute; inset: 0; z-index: 0;
  pointer-events: none; border-radius: inherit;
  background: radial-gradient(circle at 88% -18%, rgba(230,57,70,.20), transparent 44%); }
/* ...et la diagonale discrète de la charte, pour que la carte ne soit pas un aplat. */
.dash-bloc.est-sombre::after { content: ''; position: absolute; inset: 0; z-index: 0;
  pointer-events: none; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,.035) 0%, transparent 46%); }
.dash-bloc.est-sombre > * { position: relative; z-index: 1; }

/* LES TEXTES DE LA CARTE SOMBRE s'éclaircissent, sinon ils disparaissent. */
.dash-bloc.est-sombre .dash-bloc-tete h3 { color: #f2f0ed; }
.dash-bloc.est-sombre .dash-bloc-tete .ico { color: #b9b3ab; }
.dash-bloc.est-sombre .dash-bloc-note { color: #8b857d; }
.dash-bloc.est-sombre .dash-vide { color: #8b857d; }

/* LES LIGNES DANS LA CARTE SOMBRE : nom clair, second texte gris moyen. */
.dash-bloc.est-sombre .dash-nom strong { color: #eceae7; }
.dash-bloc.est-sombre .dash-nom small { color: #8b857d; }
.dash-bloc.est-sombre .dash-val strong { color: #f6f4f1; }
.dash-bloc.est-sombre .dash-val small { color: #8b857d; }
.dash-bloc.est-sombre .dash-barre { background: rgba(255,255,255,.10); }

/* LE JOURNAL DANS LA CARTE SOMBRE : les libellés doivent rester lisibles. */
.dash-bloc.est-sombre .dash-j-ligne:hover { background: rgba(255,255,255,.045); }
.dash-bloc.est-sombre .dash-j-qui em { color: #8b857d; }
.dash-bloc.est-sombre .dash-j-fait { color: #b5afa8; }
.dash-bloc.est-sombre .dash-j-fait b { color: #f2f0ed; }
.dash-bloc.est-sombre .dash-j-quand { color: #7c766f; }
.dash-bloc.est-sombre .dash-j-av { background: rgba(255,255,255,.07);
  border-color: rgba(255,255,255,.12); }

/* LES PASTILLES D'ÉQUIPE dans la carte sombre. */
.dash-bloc.est-sombre .dash-g-tag { background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.09); color: #c9c4bd; }
.dash-bloc.est-sombre .dash-g-tag b { color: #f6f4f1; }

/* LES JUGES / MINI-WIDGETS dans la carte sombre : ils gardent leur fond clair (le client l'a
   demandé : « des jauges et des mini-widgets à fond clair pour qu'on puisse voir »). */
.dash-bloc.est-sombre .dash-mw { background: #fdfcfa; border-color: rgba(255,255,255,.14); }
.dash-bloc.est-sombre .dash-mw-txt strong { color: #1d1e21; }
.dash-bloc.est-sombre .dash-mw-txt small { color: #383633; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LA JAUGE DE STOCK SOUS CHAQUE PROFIL
   Client : « en dessous de chaque profil [...] j'aurais aimé qu'elle signifie son stock [...]
   quand elle est à fond elle est en vert, plus il fait sortir des cadeaux plus son stock
   diminue, on a une jauge pour savoir si c'est minime ». */
.dash-stock-jauge { display: flex; align-items: center; gap: 7px; margin-top: 4px;
  min-width: 0; }
/* LE RAIL : plus large et plus haut que l'ancienne barre de part — c'est un vrai indicateur. */
.dash-stock-jauge .dash-stock-rail { flex: 1 1 auto; min-width: 0; height: 6px;
  border-radius: 3px; background: rgba(255,255,255,.10); overflow: hidden; }
.dash-stock-jauge .dash-stock-rail i { display: block; height: 100%; border-radius: 3px;
  transform-origin: left center; animation: remplir .9s cubic-bezier(.22,.9,.24,1) both; }
/* LE POURCENTAGE : un chiffre lisible, pas une décoration. */
.dash-stock-jauge .dash-stock-pct { flex: none; font-size: 10px; font-weight: 600;
  font-family: 'DM Mono', monospace; letter-spacing: -.2px; min-width: 30px; text-align: right; }
/* LES TROIS ÉTATS, en clair. */
.dash-stock-jauge.est-plein .dash-stock-pct { color: #4a8c5c; }
.dash-stock-jauge.est-entame .dash-stock-pct { color: #d4941f; }
.dash-stock-jauge.est-minime .dash-stock-pct { color: #D42B38; }
/* Sur fond CLAIR (les cartes blanches), le rail doit être sombre au lieu de clair. */
.dash-bloc:not(.est-sombre) .dash-stock-jauge .dash-stock-rail { background: #ece9e4; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LES DEUX FENÊTRES SOMBRES (elles remplacent la bande)
   Client : « des fenêtres séparées [...] c'est là où il doit y avoir du noir ».
   Même matière que les cartes sombres du bas : la cohérence est ce qui manquait. */
.dash-phare-gauche,
.dash-phare-chiffres { position: relative; overflow: hidden; border-radius: 18px;
  background: linear-gradient(158deg, #1c1a1d 0%, #141416 58%, #101113 100%);
  border: 1px solid rgba(255,255,255,.075);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.09); }
/* Le halo rouge de la charte, une seule fois, discret, en haut à droite. */
.dash-phare-gauche::before,
.dash-phare-chiffres::before { content: ''; position: absolute; inset: 0; z-index: 0;
  pointer-events: none; border-radius: inherit;
  background: radial-gradient(circle at 88% -20%, rgba(230,57,70,.18), transparent 46%); }
.dash-phare-gauche > *, .dash-phare-chiffres > * { position: relative; z-index: 1; }

/* LA FENÊTRE DES JAUGES : elle respire, les deux juges côte à côte. */
.dash-phare-gauche { flex: 0 1 auto; padding: 16px 18px; display: flex; flex-direction: column;
  gap: 12px; justify-content: center; min-width: 0; }
.dash-phare-kicker { margin: 0; }
/* LA FENÊTRE DES CHIFFRES : elle prend le reste de la largeur. */
.dash-phare-chiffres { flex: 1 1 420px; padding: 16px 18px; align-items: center; gap: 26px; }

/* LE TITRE DES CHIFFRES SE LIT SUR LE FOND SOMBRE. */
.dash-phare-sous-titre { font-size: 9.5px; font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; color: #a8977c; margin: 0 0 10px; }

/* LA GRILLE DES RECORDS : trois fenêtres claires, sous les deux sombres. */
.dash-phare-fige { flex: 1 1 100%; padding-top: 0; margin-top: 0; border-top: 0; }

/* ═══════════════════════════════════════════════════════════════════════════════
   TOUT EN CLAIR — AVEC LE SHADER DES FICHES
   Client : « les cartons sombres avec shaders, rends-les en version claire comme les fiches
   collaboratrice : le blanc avec shaders ».
   Cette couche est VOLONTAIREMENT EN FIN DE FICHIER : elle corrige les règles sombres écrites
   avant elle, sans avoir à les traquer une par une dans 7 000 lignes de CSS. */

/* ── 1. PLUS DE BANDE : le conteneur n'est qu'un aligneur de fenêtres. ─────────── */
.dash-phare { background: none; border: 0; padding: 0; border-radius: 0; overflow: visible;
  gap: 12px; align-items: stretch; }

/* ── 2. LES CARTES SOMBRES DEVIENNENT BLANCHES ──────────────────────────────────
   Même matière que les fiches : blanc franc, bordure fine, ombre très douce. */
.dash-bloc.est-sombre,
.dash-phare-gauche,
.dash-phare-chiffres {
  background: #fff;
  border: 1px solid #e6e3de;
  box-shadow: 0 1px 2px rgba(23,23,25,.05), 0 6px 16px rgba(23,23,25,.045);
  /* `overflow:hidden` est indispensable : c'est ce qui borne le filigrane au cadre. */
  position: relative; overflow: hidden;
}
/* Les halos sombres qu'on avait posés dessus : on les éteint (ils salissaient le blanc). */
.dash-bloc.est-sombre::before,
.dash-bloc.est-sombre::after,
.dash-phare-gauche::before,
.dash-phare-chiffres::before { display: none; }

/* ── 3. LES TEXTES REPASSENT EN SOMBRE ─────────────────────────────────────────── */
.dash-bloc.est-sombre .dash-bloc-tete h3 { color: #1d1e21; }
.dash-bloc.est-sombre .dash-bloc-tete .ico { color: #383633; }
.dash-bloc.est-sombre .dash-bloc-note { color: #383633; }
.dash-bloc.est-sombre .dash-vide { color: #383633; }
.dash-bloc.est-sombre .dash-nom strong { color: #1d1e21; }
.dash-bloc.est-sombre .dash-nom small { color: #383633; }
.dash-bloc.est-sombre .dash-val strong { color: #141416; }
.dash-bloc.est-sombre .dash-val small { color: #383633; }
.dash-bloc.est-sombre .dash-barre { background: #f1efec; }
.dash-bloc.est-sombre .dash-j-ligne:hover { background: #faf9f7; }
.dash-bloc.est-sombre .dash-j-qui em { color: #383633; }
.dash-bloc.est-sombre .dash-j-fait { color: #4a4d52; }
.dash-bloc.est-sombre .dash-j-fait b { color: #1d1e21; }
.dash-bloc.est-sombre .dash-j-quand { color: #383633; }
.dash-bloc.est-sombre .dash-j-av { background: #f1efec; border-color: #e6e3de; }
.dash-bloc.est-sombre .dash-g-tag { background: #faf9f7; border-color: #f1efec; color: #4a4d52; }
.dash-bloc.est-sombre .dash-g-tag b { color: #1d1e21; }
/* Les jauges gardent leur rail sombre sur fond blanc (lisibilité). */
.dash-bloc.est-sombre .dash-stock-jauge .dash-stock-rail { background: #ece9e4; }

/* ── 4. LE FILIGRANE DU SHADER SUR LES CARTES DU DASHBOARD ──────────────────────
   Exactement les mêmes valeurs que `.carte-shader` des fiches : opacité 10 %, `multiply`,
   motif étiré à 230 % et glissé sur 26 s. C'est ce qui donne la matière sans salir le blanc. */
.dash-bloc .carte-shader,
.dash-phare-gauche .carte-shader,
.dash-phare-chiffres .carte-shader {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-repeat: no-repeat; background-size: 190% 190%;
  /* Opacite .16 : les fiches sont a .10, mais sur une CARTE DE DASHBOARD (petite, dense,
     beaucoup de texte) .10 se lit comme un aplat. Client : « le blanc avec shaders » —
     il veut VOIR la matiere. .16 la rend perceptible sans laver le blanc. */
  opacity: .16; mix-blend-mode: multiply;
  border-radius: inherit;
  animation: shaderCarteGlisse 26s ease-in-out infinite;
}
/* Chaque carte démarre décalée : sinon toutes onduleraient à l'unisson. */
.dash-bloc:nth-child(2n) .carte-shader { animation-delay: -9s; }
.dash-bloc:nth-child(3n) .carte-shader { animation-delay: -17s; }
/* Le contenu passe AU-DESSUS du filigrane. */
.dash-bloc > *:not(.carte-shader),
.dash-phare-gauche > *:not(.carte-shader),
.dash-phare-chiffres > *:not(.carte-shader) { position: relative; z-index: 1; }

/* ── 5. LE SHADER DE LA BANDE : éteint (il couvrait toute la largeur, c'était le bruit). ── */
.dash-phare-shader { display: none; }

/* ── 6. LES DEUX FENÊTRES DU HAUT : mêmes mesures que les cartes. ─────────────── */
.dash-phare-gauche { flex: 0 1 auto; padding: 16px 18px; display: flex; flex-direction: column;
  gap: 12px; justify-content: center; min-width: 0; border-radius: 18px; }
.dash-phare-chiffres { flex: 1 1 420px; padding: 16px 18px; align-items: center; gap: 26px;
  border-radius: 18px; }
/* Le titre reste en doré : il EST la charte, sur blanc comme sur sombre. */
.dash-phare-kicker { color: #b08a68; }
.dash-phare-kicker .ico { color: #c1934f; }
.dash-phare-fige { flex: 1 1 100%; padding-top: 0; border-top: 0; }

/* ═══════════════════════════════════════════════════════════════════════════════
   ENLEVER LE NOIR — DÉCOLLER LES WIDGETS DU FOND ANIMÉ
   Client : « enlève le noir et essaye juste de décoller les widgets du fond animé clair ».
   Le relief vient désormais de l'OMBRE et du LISERÉ, pas d'un fond sombre. */
.dash-bloc.est-sombre,
.dash-phare-gauche,
.dash-phare-chiffres {
  background: #fff;
  border: 1px solid #e8e5e0;
  /* L'OMBRE QUI DÉCOLLE : courte et nette (un objet posé), plus une ombre large très douce
     pour la profondeur. C'est exactement ce que font les références Pinterest. */
  box-shadow:
    0 1px 1px rgba(23,23,25,.045),
    0 2px 4px rgba(23,23,25,.045),
    0 8px 20px rgba(23,23,25,.055);
  position: relative; overflow: hidden;
  transition: box-shadow .2s ease, transform .2s ease;
}
.dash-bloc.est-sombre:hover,
.dash-phare-gauche:hover,
.dash-phare-chiffres:hover {
  box-shadow: 0 1px 1px rgba(23,23,25,.05), 0 3px 7px rgba(23,23,25,.055),
    0 12px 28px rgba(23,23,25,.07);
}
/* LE LISERÉ DE LUMIÈRE sur le bord haut : c'est ce qui fait « verre posé sur la page ». */
.dash-bloc::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  z-index: 2; pointer-events: none; border-radius: inherit 0 0 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.9) 30%,
    rgba(255,255,255,.9) 70%, transparent); }

/* LE PANNEAU : le fond animé doit RESTER VISIBLE autour des widgets. On retire donc le voile
   blanc du panneau pour laisser le shader et le halo de la page apparaître entre les cartes. */
/* ⚠️⚠️ CETTE RÈGLE ANNULE À ELLE SEULE TOUT L'EFFET GLASS — ELLE EST RETIRÉE.
   Elle posait `background: none; border: 0; border-radius: 0` et mettait `::before` / `::after`
   en `display: none` : le voile, le liseré, les coins ET les deux pièces du verre (le reflet
   diffus et le liseré lumineux) étaient donc tous effacés. MESURÉ avant retrait :
       background    = rgba(0, 0, 0, 0)   border = 0px none   border-radius = 0px
   ⚠️ ELLE AVAIT UNE RAISON, ET IL FAUT LA LIRE : « LE RELIEF DEVIENT COURT — POUR QUE LE FOND
   RESTE BLANC. Client : “enlève-moi même le fond gris”. MESURÉ : les ombres diffuses de 20 px se
   superposaient entre les cartes (3 couches × ~15 cartes) et faisaient tomber le blanc du fond à
   rgb(218,218,220). » — le client demandait alors AUCUN fond de panneau.
   ⚠️ AUJOURD'HUI IL DEMANDE L'INVERSE : « ne mettre que du vert transparent moderne » puis
   « verre transparent · la matière » et « on l'appellera l'effet glass ». C'est sa dernière parole
   qui fait foi. On remplace donc la règle par la matière au lieu de la garder.
   ⚠️ ET LE DÉFAUT D'ORIGINE N'EST PAS RECRÉÉ : le reproche portait sur les OMBRES EMPILÉES des
   cartes (3 couches × ~15), pas sur le panneau. Notre verre ne pose qu'UNE ombre, sur UN élément.
   ⚠️ ON NE TOUCHE PAS À `.dash-panneau-shader { opacity: .13 }` : le canvas n'existe plus, la
   règle est inerte. La retirer serait un nettoyage, pas une correction — hors périmètre ce soir. */
/* .dash-panneau { background: none; border: 0; padding: 0; border-radius: 0; } */
/* .dash-panneau::before, .dash-panneau::after { display: none; } */

/* ═══ LA JAUGE DU TAUX DE REMISE — ELLE DOIT SE VOIR ══════════════════════════
   Client : « taux de remise le cercle il a pas de jauge, tu vois 41% on voit pas la jauge ».
   MESURÉ : le rail était `rgba(255,255,255,.13)` et le texte `#fff` — invisibles sur blanc. */
.dash-arc { border-right: 1px solid #ece9e4; padding-right: 18px; margin-right: 4px; }
.dash-arc svg { overflow: visible; }
/* LE CHIFFRE : anthracite, plus gros, avec le libellé lisible. */
.dash-arc-txt strong { color: #141416; font-size: 26px; letter-spacing: -.9px; }
.dash-arc-txt span { color: #383633; font-size: 9.5px; }
.dash-arc-txt em { color: #383633; font-size: 9.5px; }

/* ═══ LES MINI-WIDGETS (les cartons à jauge) — LISIBLES SUR BLANC ══════════════
   Client : « les petits widgets et les cartons de la dernière fiche [...] ils sont pas très
   clairs, ils sont pas colorés ». On leur donne un fond franc, une bordure nette et un titre
   coloré : ils doivent se lire d'un coup d'œil. */
.dash-mw { background: #fdfcfa; border: 1px solid #ece9e4; border-radius: 14px;
  box-shadow: 0 1px 2px rgba(23,23,25,.04); }
.dash-mw-txt strong { color: #141416; }
.dash-mw-txt small { color: #383633; }

/* ═══ L'ANIMATION « tracer » NE DOIT PLUS CACHER LES JAUGES ═══════════════════════
   MESURÉ : `@keyframes tracer { from { stroke-dasharray: 0 200; } }` laissait l'arc de la
   jauge A ZÉRO (dasharray « 0px, 200px » relevé sur la jauge à 100 %). Client : « le cercle
   il a pas de jauge, on voit pas la jauge de 41 % ».
   Une jauge doit être lisible AVANT d'être animée. On neutralise donc l'animation, partout. */
.dash-mw-jauge svg circle[stroke-dasharray] { animation: none !important; }
/* Et on rend le rail franchement visible : c'est lui qui dessine le contour de la jauge. */
.dash-mw-jauge svg circle { transition: stroke .2s ease; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE NOMBRE ET SON LIBELLÉ SUR LA MÊME LIGNE
   Client : « essaye de rapprocher les trucs, comme ça la campagne tient juste à côté du
   nombre au lieu qu'elle soit en bas. Comme ça tout serait aligné. »
   Avant : `flex-direction: column` → le chiffre au-dessus, le libellé en dessous, sur deux
   lignes, ce qui cassait l'alignement de toutes les listes du dashboard. */
.dash-val {
  display: flex;
  flex-direction: row;          /* ← LE changement : ligne, plus colonne */
  align-items: baseline;        /* le chiffre et le libellé partagent la ligne de base */
  justify-content: flex-end;
  gap: 5px;                     /* un petit espace, pas un vide */
  flex: none;
  min-width: 0;
  white-space: nowrap;
}
/* LE CHIFFRE : il garde sa taille, il est la donnee. */
.dash-val strong { font-size: 14px; font-weight: 600; color: #141416;
  letter-spacing: -.2px; line-height: 1; }
/* LE LIBELLÉ : même ligne, plus petit, discret — il qualifie le chiffre. */
.dash-val small { font-size: 10px; color: #383633; line-height: 1; }

/* LES VARIANTES DE TAILLE (les cartes de lieux ont un grand chiffre). */
.dash-lieu-bloc .dash-val { gap: 6px; }
.dash-lieu-bloc .dash-val strong { font-size: 22px; letter-spacing: -.6px; }
.dash-lieu-bloc .dash-val small { font-size: 10px; }

/* LA LIGNE ENTIÈRE S'ALIGNE : c'est ce qui rend « tout bien aligné ».
   Le nom prend la place restante, la barre est fixe, le chiffre est collé à droite. */
.dash-ligne.compacte { align-items: center; }
.dash-ligne.compacte .dash-nom { flex: 1 1 auto; min-width: 0; }
.dash-nom strong { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-nom small { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ═══ LES CARTES DE LIEUX AUSSI : CHIFFRE ET LIBELLÉ SUR UNE LIGNE ═══════════════
   MESURÉ : 4 blocs restaient en colonne. Cause : `.dash-lieu-bloc .dash-val` (0,2,0) est plus
   spécifique que `.dash-val` (0,1,0), donc mes règles étaient battues. Il faut donc écrire
   avec la MÊME spécificité pour reprendre la main — c'est la règle du jeu CSS. */
.dash-lieu-bloc .dash-val {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  flex: none;
  white-space: nowrap;
}
.dash-lieu-bloc .dash-val strong { font-size: 22px; letter-spacing: -.6px; line-height: 1; }
.dash-lieu-bloc .dash-val small { font-size: 10px; color: #383633; line-height: 1; }

/* ET LE BLOC QUI PORTE LES JAUGES : pareil, sur une ligne. */
.dash-membre-val { display: flex; flex-direction: row; align-items: baseline; gap: 5px;
  justify-content: flex-end; flex: none; }
.dash-membre-val strong { font-size: 13px; font-weight: 600; color: #141416; line-height: 1; }
.dash-membre-val small { font-size: 9.5px; color: #383633; line-height: 1; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE RAPPEL DU FOND NOIR — SUR UNE SEULE FENÊTRE
   Client : « fais le rappel de ce fond noir que sur une fenêtre, des fenêtres dans le
   dashboard, ça ne doit pas trop être envahissant ».
   C'est LA FENÊTRE DES DEUX JAUGES qui le porte. Pourquoi elle :
     · elle est PETITE — deux anneaux, un accent, pas une masse ;
     · elle est en HAUT À GAUCHE, là où l'œil arrive : le rappel se voit sans envahir ;
     · elle est faite de FORMES (les anneaux), qui ressortent superbement sur un fond profond ;
     · elle ne porte pas de texte dense, donc rien ne souffre du contraste inversé. */
.dash-phare-gauche {
  background-image:
    radial-gradient(circle at 86% -24%, rgba(230,57,70,.26), transparent 52%),
    linear-gradient(158deg, #1f1c1f 0%, #161417 58%, #121114 100%);
  border: 1px solid rgba(255,255,255,.10);
  box-shadow: 0 4px 14px rgba(23,23,25,.16), inset 0 1px 0 rgba(255,255,255,.10);
  border-radius: 18px;
}
/* LE CONTRASTE : les jauges gardent leur fond CLAIR dans la fenêtre sombre — c'est la
   demande précédente du client (« à fond clair pour qu'on puisse voir »), et ça marche
   encore mieux ici : l'anneau coloré sur pastille ivoire se détache du noir. */
.dash-phare-gauche .dash-mw { background: #fdfcfa; border-color: rgba(255,255,255,.18);
  box-shadow: 0 2px 8px rgba(0,0,0,.24); }
.dash-phare-gauche .dash-mw-txt strong { color: #141416; }
.dash-phare-gauche .dash-mw-txt small { color: #383633; }
/* Le titre doré : la charte sur fond profond. */
.dash-phare-gauche .dash-phare-kicker { color: #d4a574; }
.dash-phare-gauche .dash-phare-kicker .ico { color: #d4a574; }

/* LA FENÊTRE DES CHIFFRES RESTE CLAIRE : elle est large, un fond sombre y serait envahissant
   — exactement ce que le client refuse. On garantit son fond blanc. */
.dash-phare-chiffres { background: #fff; border: 1px solid #e8e5e0;
  border-radius: 18px;
  box-shadow: 0 1px 1px rgba(23,23,25,.045), 0 2px 4px rgba(23,23,25,.045),
    0 8px 20px rgba(23,23,25,.055); }
.dash-phare-chiffres .dash-phare-chif-ic { background: #f4f1ec; color: #383633; }
.dash-phare-chiffres .dash-phare-chif-txt strong { color: #141416; }
.dash-phare-chiffres .dash-phare-chif-txt span { color: #383633; }
.dash-phare-chiffres .dash-phare-chif { border-left-color: #ece9e4; }

/* SI UN BLOC PORTAIT ENCORE `.est-sombre`, il redevient clair : le noir n'est plus qu'à un
   seul endroit du dashboard. */
.dash-bloc.est-sombre { background: #fff; border: 1px solid #e8e5e0; }
.dash-bloc.est-sombre .dash-bloc-tete h3 { color: #1d1e21; }
.dash-bloc.est-sombre .dash-bloc-tete .ico { color: #383633; }
.dash-bloc.est-sombre .dash-nom strong { color: #1d1e21; }
.dash-bloc.est-sombre .dash-nom small { color: #383633; }
.dash-bloc.est-sombre .dash-val strong { color: #141416; }
.dash-bloc.est-sombre .dash-val small { color: #383633; }
.dash-bloc.est-sombre .dash-j-fait { color: #4a4d52; }
.dash-bloc.est-sombre .dash-j-quand { color: #383633; }
.dash-bloc.est-sombre .dash-g-tag { background: #faf9f7; border-color: #f1efec; }
.dash-bloc.est-sombre .dash-g-tag b { color: #1d1e21; }

/* ═══════════════════════════════════════════════════════════════════════════════
   PLUS AUCUN FOND NOIR
   Client : « enlève tout le fond noir ». Le dashboard est entièrement clair : le relief
   vient de l'ombre et du liseré, jamais d'un fond profond. */
.dash-phare-gauche {
  background: #fff;
  background-image: none;
  border: 1px solid #e8e5e0;
  box-shadow: 0 1px 1px rgba(23,23,25,.045), 0 2px 4px rgba(23,23,25,.045),
    0 8px 20px rgba(23,23,25,.055);
}
/* Les jauges gardent leur fond ivoire : elles se détachent du blanc par la bordure. */
.dash-phare-gauche .dash-mw { background: #fdfcfa; border-color: #ece9e4;
  box-shadow: 0 1px 2px rgba(23,23,25,.04); }
.dash-phare-gauche .dash-mw-txt strong { color: #141416; }
.dash-phare-gauche .dash-mw-txt small { color: #383633; }
/* Le titre reprend le doré de la charte sur fond clair. */
.dash-phare-gauche .dash-phare-kicker { color: #b08a68; }
.dash-phare-gauche .dash-phare-kicker .ico { color: #c1934f; }

/* ═══════════════════════════════════════════════════════════════════════════════
   ⚠️ LA CORRECTION DU BUG : TOUT PARENT D'UN SHADER DOIT ÊTRE POSITIONNÉ
   MESURÉ : le shader faisait 1257 × 2975 px dans une carte de 622 × 212, soit 636 px de
   débordement — parce que `.dash-bloc` était `position: static` et que le shader, en
   `position: absolute`, remontait jusqu'à `.dash-panneau` pour se dimensionner.
   C'est LA cause du « bug urgent » : chaque carte portait un rectangle de shader géant qui
   recouvrait ses voisines. Un `absolute` a TOUJOURS besoin d'un ancêtre `relative`. */
.dash-bloc,
.dash-kpi,
.dash-lieu-bloc,
.dash-phare-gauche,
.dash-phare-chiffres,
.dash-mw,
.dash-fige,
.carte,
.entity-tile {
  position: relative;
  /* `overflow: hidden` borne le filigrane au cadre : sans lui, il dépasserait des coins
     arrondis. C'est la seconde moitié de la correction. */
  overflow: hidden;
}
/* Les cartes qui doivent laisser sortir un élément (une infobulle, un menu) sont exclues. */
.dash-fige { overflow: hidden; }

/* ET LE SHADER LUI-MÊME : on le borne explicitement à son parent, à toute fin utile. */
.dash-bloc > .carte-shader,
.dash-kpi > .carte-shader,
.dash-phare-gauche > .carte-shader,
.dash-phare-chiffres > .carte-shader {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  width: auto; height: auto;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   FLUX & DOTATIONS — LES TRIS ET L'ÉLIGIBILITÉ
   Client : « le flux et dotations se trie aussi par événement et campagne, seuls les
   responsables et les collaboratrices affectés ont droit d'être dotés ». */
.flux-filtres { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  padding: 13px 15px; margin-bottom: 14px; background: #fff; border: 1px solid #e6e3de;
  border-radius: 13px; box-shadow: 0 1px 2px rgba(23,23,25,.04); }
.flux-f-champ { display: flex; flex-direction: column; gap: 4px; min-width: 0;
  flex: 1 1 190px; }
.flux-f-champ label { display: flex; align-items: center; gap: 4px; font-size: 9.5px;
  font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: #383633; }
.flux-f-champ select { width: 100%; min-width: 0; font: inherit; font-size: 12px;
  color: #26282c; background: #faf9f7; border: 1px solid #e6e3de; border-radius: 8px;
  padding: 7px 9px; cursor: pointer; }
.flux-f-champ select:focus { outline: none; border-color: #D42B38; }
.flux-f-reset { font: inherit; font-size: 11.5px; color: #7d8085; background: none;
  border: 1px solid #e6e3de; border-radius: 8px; padding: 7px 11px; cursor: pointer;
  white-space: nowrap; transition: color .15s, border-color .15s; }
.flux-f-reset:hover { color: #D42B38; border-color: #e8c3c7; }

/* LE BANDEAU D'ÉLIGIBILITÉ : il porte la RÈGLE, en clair. */
.flux-elig { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  padding: 12px 15px; margin-bottom: 14px; border-radius: 13px;
  background: #fdfaf4; border: 1px solid #f0e7d5; }
.flux-elig > span { display: flex; align-items: center; gap: 7px; font-size: 12px;
  color: #6d7075; }
.flux-elig strong { font-size: 15px; font-weight: 600; color: #1d1e21; }
.flux-elig .ico { color: #c1934f; width: 15px; height: 15px; }

/* LA PASTILLE D'ÉLIGIBILITÉ, sur chaque personne. */
.flux-pastille { display: inline-flex; align-items: center; gap: 4px; font-size: 9px;
  font-weight: 600; letter-spacing: .03em; border-radius: 5px; padding: 2px 6px;
  white-space: nowrap; }
.flux-pastille.est-eligible { color: #3d7a4e; background: #eef7f0; border: 1px solid #d8ebdd; }
.flux-pastille.est-non { color: #9a938a; background: #f5f3ef; border: 1px solid #e9e5de; }
/* La personne inéligible reste VISIBLE mais atténuée : le client doit constater qu'elle
   n'est pas affectée, sans qu'elle disparaisse (sinon il croirait à un bug). */
.flux-ineligible { opacity: .56; }
.flux-ineligible .flux-boite, .flux-ineligible > * { cursor: not-allowed; }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE FOND DE PAGE PASSE AU BLANC PUR
   Client : « enlève-moi même le fond gris ». Mesure : le gris venait de
   `linear-gradient(135deg, #f7f5f1 0%, #f0efeb 100%)` — des gris-beiges très clairs posés
   sur `body`. On les remplace par du blanc franc, en ne gardant que le halo rouge de la
   charte (qui est une signature, pas du gris). */
/* Repli blanc : il sert UNIQUEMENT si la video ne se charge pas. */
html, body { background: #ffffff; background-image: none; }
html:has(.fond-video), body:has(.fond-video) { background: transparent !important; }
/* ON REMET LE HALO — mais comme une COUCHE, pas comme un fond : c'est lui qui donne la vie
   au blanc, sans le grisailler. Il reste très diffus et uniquement en haut à droite. */
/* ⚠️ LE HALO ÉTAIT LE COUPABLE. `inset: 0` le faisait couvrir TOUTE la page : même à 7 %,
   il teintait uniformément le blanc en beige-rosé — exactement « le fond gris » que le client
   refusait. Ici il n'est plus qu'une TACHE dans le coin haut droit (520×420 max), donc le
   reste de la page reste du blanc PUR. C'est la différence entre une lumière et un voile. */
body::before {
  content: '';
  position: fixed;
  top: 0;
  right: 0;
  width: 520px;
  height: 420px;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(circle at 82% 4%, rgba(230,57,70,.075), transparent 68%);
}
/* Le contenu passe au-dessus du halo. */
.app-shell { position: relative; z-index: 1; }

/* LA BORDURE DU BLOC DES FILTRES : elle était #e6e3de (un gris-beige soutenu, le 2e X du
   client). On l'adoucit pour qu'elle ne se lise plus comme un trait gris. */
.dash-filtres { border-color: #f0eeeb; box-shadow: 0 1px 2px rgba(23,23,25,.035); }

/* ET LES AUTRES FONDS BEIGE QUI POUVAIENT SE VOIR SUR LE PANNEAU. */
/* ⚠️⚠️⚠️ C'EST CELLE-CI QUI GAGNAIT : `background: none !important`. Elle portait un `!important`
   sur le sélecteur NU `.dash-panneau`, donc elle battait toute autre déclaration de fond — y
   compris celle de l'effet glass posée plus haut. MESURÉ : `background = rgba(0, 0, 0, 0)`.
   ⚠️ ELLE AUSSI AVAIT SA RAISON, ET ELLE EST ÉCRITE JUSTE EN DESSOUS (« le relief devient court —
   pour que le fond reste blanc », client : « enlève-moi même le fond gris »). Le client demandait
   alors un panneau SANS fond ; il demande maintenant du VERRE. On remplace, on n'ajoute pas.
   ⚠️ ON N'AJOUTE PAS UN CINQUIÈME `!important` POUR LA BATTRE — c'est l'empilement qui a créé le
   problème. On REMPLACE la déclaration à la source : une matière, un endroit.
   ⚠️ LA MÊME MATIÈRE QUE SUR LE PANNEAU (voile 5 %, liseré, coins) RÉPÉTÉE ICI SERAIT UN DOUBLON :
   la définition vit sur `.dash-panneau` en l.5961, à UN SEUL endroit, et rien ne l'écrase plus. */
/* .dash-panneau { background: none !important; } */
/* .dash-panneau-shader { opacity: .12; } */

/* ═══════════════════════════════════════════════════════════════════════════════
   LE RELIEF DEVIENT COURT — POUR QUE LE FOND RESTE BLANC
   Client : « enlève-moi même le fond gris ».
   MESURÉ : les ombres diffuses de 20 px se superposaient entre les cartes (3 couches ×
   ~15 cartes) et faisaient tomber le blanc du fond à rgb(218,218,220). Un fond blanc n'est
   blanc que si RIEN ne le voile. On garde donc : un contact net de 1 px, une ombre douce de
   4 px, et une bordure franche qui fait le vrai travail de séparation. */
.dash-bloc,
.dash-kpi,
.dash-lieu-bloc,
.dash-phare-gauche,
.dash-phare-chiffres,
.dash-mw,
.dash-fige {
  box-shadow: 0 1px 2px rgba(23,23,25,.05) !important;
  border: 1px solid #eeebe6;
}
/* AU SURVOL : l'ombre s'ouvre UN PEU (le retour visuel reste), sans jamais devenir un halo. */
.dash-bloc:hover, .dash-lieu-bloc:hover, .dash-fige:hover {
  box-shadow: 0 2px 6px rgba(23,23,25,.075) !important;
}

/* LES RANGÉES GAGNENT UN PEU D'AIR : plus l'écart est large, moins les ombres se cumulent. */
.dash-kpis, .dash-grille, .dash-lieux { gap: 13px; }

/* ET LES AUTRES OMBRES QUI TRAÎNAIENT SUR LES CARTES DE LA VUE. */
.flux-boite, .flux-carte, .entity-tile, .carte {
  box-shadow: 0 1px 2px rgba(23,23,25,.05);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   LE SHADER DE SURFACE EST RETIRÉ — C'ÉTAIT LUI LE « FOND GRIS »
   MESURE DÉCISIVE : en changeant les ombres, les pixels de fond sont restés IDENTIQUES
   (rgb(218,218,220)). Un pixel constant est un aplat, pas une ombre.
   L'aplat venait de `.dash-panneau-shader` : en `inset: 0` il couvrait les 1157 × 3267 px du
   panneau, et son motif cramoisi (moyenne rgb(49,19,34)) en `multiply` à 12 % ramenait le
   blanc à ~230. C'est exactement la teinte que le client refuse.
   → ON LE RETIRE. Le filigrane RESTE, mais uniquement PAR CARTE (borné à son cadre) :
   la matière est là où elle a un sens, et les interstices redeviennent du blanc pur. */
.dash-panneau-shader { display: none !important; }

/* On garantit le blanc sur toutes les surfaces de fond du contenu.
   ⚠️⚠️ `.dash-panneau` A ÉTÉ RETIRÉ DE CETTE LISTE — ET C'EST LA CORRECTION DÉCISIVE.
   Cette règle, écrite sur UNE ligne avec un `!important`, visait QUATRE cibles d'un coup :
   elle annulait le fond du panneau, et mon bloc de verre ne pouvait rien y faire (même
   spécificité, fichier porté plus haut). MESURÉ : le liseré, les coins et le `backdrop-filter`
   s'appliquaient, mais `background-color` restait `rgba(0, 0, 0, 0)`.
   ⚠️ ELLE AVAIT SA RAISON, ET ELLE EST TOUJOURS VALABLE POUR LES AUTRES : elle répond à « enlève-moi
   même le fond gris » — le shader cramoisi rougissait le blanc du panneau (mesuré : un pixel
   CONSTANT à rgb(218,218,220), donc un aplat et non une ombre). Pour les surfaces de la PAGE, on
   garde donc la transparence : rien à changer.
   ⚠️ ET LA DEMANDE D'AUJOURD'HUI EST COMPATIBLE AVEC CELLE D'ALORS : le VERRE est NEUTRE (voile
   blanc à 5 %), il ne ramène PAS le cramoisi refusé. On donne le verre au panneau SANS
   réintroduire le « fond gris ».
   ⚠️ `.dash-panneau-in` RESTE TRANSPARENT ET C'EST VOULU : c'est le conteneur du CONTENU, à
   l'intérieur du verre. S'il portait un fond, on aurait deux voiles empilés et le verre
   deviendrait un aplat — exactement ce qu'on cherche à éviter. */
.main, .content, .dash-panneau-in { background: transparent !important; }
/* LA VIDEO PASSE AVANT LE BLANC.
   Ces regles imposaient `#ffffff` sur `body` avec `!important`. Comme la video vit en
   `z-index:-1`, le blanc la recouvrait entierement : le fond de l'identite (le filigrane
   anime) avait disparu. On garde le blanc comme COULEUR DE REPLI — il sert si la video
   ne se charge pas — mais on le rend transparent des que la video est la. */
body { background: #ffffff; background-image: none; }
body:has(.fond-video) { background: transparent !important; background-image: none !important; }

/* LE HALO RESTE, MAIS BORNÉ À SON COIN (520×420) : une lumière, pas un voile. On le vérifie
   ici pour qu'il ne reprenne jamais toute la page. */
body::before {
  content: '';
  position: fixed;
  top: 0; right: 0;
  width: 520px; height: 420px;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(circle at 82% 4%, rgba(230,57,70,.075), transparent 68%);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   LA FENÊTRE DES CHIFFRES — AGRANDIE ET COLORÉE
   Client : « agrandir le texte et les icônes et les rendre colorées pour plus d'attraction ».
   MESURÉ AVANT : icône 30px (svg 16px) TOUTE EN GRIS, chiffre 24px, libellé 10,5px gris. */
.dash-phare-chiffres { gap: 30px; padding: 20px 22px; }
.dash-phare-chif { gap: 12px; padding: 2px 20px; }

/* L'ICÔNE : de 30 à 44px, svg de 16 à 25px. Sa couleur et son fond sont posés en inline
   (une teinte par position), donc ils viennent du rendu, pas d'ici. */
.dash-phare-chif-ic { width: 44px; height: 44px; border-radius: 13px;
  border: 1px solid transparent;
  display: flex; align-items: center; justify-content: center; flex: none; }
.dash-phare-chif-ic .ico { width: 25px; height: 25px; }

/* LE CHIFFRE : de 24 à 32px. */
.dash-phare-chif-txt strong { font-size: 32px; letter-spacing: -.9px; line-height: 1; }

/* LE LIBELLÉ : de 10,5 à 13px, et plus foncé — il était illisible à rgb(139,142,147). */
.dash-phare-chif-txt span { font-size: 13px; color: #6a6d72; font-weight: 500; }

/* LE SÉPARATEUR entre les chiffres : plus haut pour suivre la nouvelle taille. */
.dash-phare-chif { border-left: 1px solid #ece9e4; }
.dash-phare-chif:first-child { border-left: 0; }


/* ═══════════════════════════════════════════════════════════════════════════════
   LE REPLI D'UN GROUPE — CLIQUER LA BULLE POUR FERMER/OUVRIR
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « quand on clique sur la bulle, on peut reduire le tableur. Comme si on
   reduit l'onglet du tableur, c'est en cliquant encore sur un des separateurs qu'on
   a tout le tableau du CRM qui s'ouvre. »

   Le titre de chaque groupe EST un bouton : un clic replie son contenu, un autre le
   deplie. Le titre reste TOUJOURS visible — sinon on ne saurait plus ce qu'on a
   replie, et c'est le comportement d'un vrai accordeon.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── LA BULLE QUI PEUT REPLIER ────────────────────────────────────────────── */
.band-tag.est-bouton {
  cursor: pointer;
  user-select: none;
  transition: background .16s ease, box-shadow .16s ease, transform .16s ease;
}
.band-tag.est-bouton:hover {
  /* On annonce clairement que c'est cliquable : sans ce retour, personne ne devine. */
  background: linear-gradient(180deg, #d8303d 0%, #b81f2b 100%);
  box-shadow: 0 6px 16px -6px rgba(212, 43, 56, .55), inset 0 1px 0 rgba(255,255,255,.22);
}
.band-tag.est-bouton:active { transform: translateY(1px); }
.band-tag.est-bouton:focus-visible {
  outline: 3px solid rgba(212, 43, 56, .35);
  outline-offset: 3px;
}

/* ── LE CHEVRON : il dit l'état du groupe ─────────────────────────────────── */
.band-chevron {
  display: inline-block;
  width: 0;                      /* sans repli possible, il n'occupe aucune place */
  margin-right: 0;
  font-size: .82em;
  font-weight: 700;
  line-height: 1;
  opacity: .85;
}
.band-tag.est-bouton .band-chevron { width: auto; margin-right: 7px; }

/* ── LE CONTENU REPLIÉ, EN MODE CARRÉ ─────────────────────────────────────── */
/* On masque le CONTENU, jamais le titre : le groupe garde sa bulle et ses traits,
   donc la page reste lisible et on voit d'un coup d'œil ce qui est fermé. */
.groupe.est-replie > .carte-grille { display: none; }
.groupe.est-replie { margin-bottom: 2px; }

/* ── LE CONTENU REPLIÉ, EN MODE LISTE ────────────────────────────────────── */
/* MESURE : un sélecteur de frères CSS (`~`) ne peut PAS delimiter un groupe dans un
   <table> — il masquerait TOUTES les lignes suivantes, y compris celles des autres
   groupes. C'est donc le RENDU qui marque chaque ligne, et le CSS se contente de la
   masquer. La classe est posée par `listeEntitesHtml`, seul endroit qui connaît les
   limites exactes de chaque groupe. */
tbody tr.rec-ligne.replie { display: none; }

/* Une petite respiration quand un groupe est fermé : sans elle, deux bulles
   voisines se collent et on ne distingue plus les groupes. */
tr.rec-groupe.est-replie > td { padding-bottom: 12px; }

/* ── ON RESPECTE QUI PRÉFÈRE NE PAS VOIR D'ANIMATION ──────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .band-tag.est-bouton { transition: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════════
   LE TABLEUR : MOINS DE BLANC, ET UNE SOIE ROUGE ANIMÉE TRÈS DISCRÈTE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « essaye de ne pas garder le blanc trop fort sur le tableur de l'onglet
   CRM. Rends-le plus animé avec le shader rouge, mode clair et très très discret. »

   MESURÉ avant : le fond était blanc franc, donc froid et plat. On passe à un crème
   très légèrement rosé — la charte effleurée, pas posée.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── LA COQUE ────────────────────────────────────────────────────────────────── */
.tableur-anime { position: relative; overflow: hidden; }
.tableur-anime > .table-scroll { position: relative; z-index: 1; }

/* ── PAS DE SHADER SUR LE TABLEUR ─────────────────────────────────────────────
   Client : « annule [...] enleve quasiment le shader. » On retire donc le filigrane
   anime : le fond laisse voir l'arriere-plan d'origine, sans aucune couche par-dessus.
   Les cheveux roses des separateurs et le survol restent : l'identite tient par la
   TEINTE, pas par un motif. */
.tableur-anime .tableur-shader { display: none !important; }

/* ── LE FOND DU TABLEUR : QUASI TRANSPARENT ───────────────────────────────────
   Client : « tu gardes le fond transparent quasiment, qu'on voit juste l'arriere-plan
   original du derriere. » Le fond opaque masquait la video de l'identite : on ne pose
   donc plus qu'un voile TRES leger, juste assez pour que le texte reste lisible. */
.tableur-anime {
  background: rgba(255, 255, 255, .18);
}
.tableur-anime .table th {
  /* L'en-tête suit le mouvement : un voile leger, lui aussi transparent. */
  background: rgba(255, 255, 255, .22);
}
.tableur-anime .table td {
  /* Des cheveux rosés au lieu de gris froids. */
  border-bottom-color: rgba(214, 196, 198, .55);
}
.tableur-anime .table tbody tr:hover { background: rgba(252, 235, 237, .72); }

/* ── ON RESPECTE QUI PRÉFÈRE NE PAS VOIR D'ANIMATION ──────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .tableur-anime .tableur-shader { animation: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════════
   LE TABLEAU SE POSE SUR LE FOND DE LA PAGE — PLUS DE BLANC, PLUS DE CADRE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « même le blanc tu l'enlèves, tu gardes juste le fond de derrière et
   enlèves le cadre. Tu gardes juste le fond original du derrière de toute la page. »

   MESURÉ : le blanc et le cadre venaient de `.card` (background rgba(255,255,255,.88)
   + border), dont le tableau héritait. On neutralise donc seulement les TABLEAUX :
   les autres cartes de l'application gardent leur fond.
   ═══════════════════════════════════════════════════════════════════════════════ */
.card.liste-entites,
.liste-entites.tableur-anime {
  background: transparent;            /* le fond d'origine de la page passe */
  border: 0;                          /* plus de cadre */
  box-shadow: none;                   /* plus d'ombre portée de « carte » */
  border-radius: 0;
  padding: 0;                         /* le tableau touche la page, sans marge interne */
  backdrop-filter: none;
}
/* Le conteneur de défilement : transparent lui aussi. */
.card.liste-entites > .table-scroll { background: transparent; }

/* ── LA BARRE DE DÉFILEMENT HORIZONTALE, EN RESPONSIVE ──────────────────────── */
/* Client : « quand je scrolle vers la droite ou vers la gauche [...] pour voir le
   reste des details. » On l'assume : la barre reste visible et discrète, c'est elle
   qui dit qu'il y a de la matière à droite. */
.card.liste-entites > .table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: rgba(212, 43, 56, .38) transparent;
}
.card.liste-entites > .table-scroll::-webkit-scrollbar { height: 9px; }
.card.liste-entites > .table-scroll::-webkit-scrollbar-track { background: transparent; }
.card.liste-entites > .table-scroll::-webkit-scrollbar-thumb {
  background: rgba(212, 43, 56, .34);
  border-radius: 99px;
}

/* ── LES SÉPARATEURS : ils restent, ce sont eux qui portent l'identité ─────── */
.card.liste-entites .table th { background: transparent; }
.card.liste-entites .table td { border-bottom-color: rgba(214, 196, 198, .55); }
.card.liste-entites .table tbody tr:hover { background: rgba(252, 235, 237, .5); }

/* ═══════════════════════════════════════════════════════════════════════════════
   LE TITRE DE CATÉGORIE RESTE AU MILIEU DE LA ZONE VISIBLE
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « ils sont très à gauche. Juste centre-les par rapport au mode responsive.
   Quand on rétrécit la fenêtre, ils restent toujours, eux, au beau milieu de l'espace
   blanc. » Et avant : « elle doit rester [visible] quand je scrolle vers la droite ou
   vers la gauche [...] pour voir le reste des détails. »

   MESURÉ — c'était mon erreur : j'avais mis `justify-content:center` dans un
   `@media (min-width: 1100px)`. Le centrage n'existait donc QUE sur grand écran ; en
   dessous, la cellule collante faisait `width:1px` et la bulle se plaquait à gauche.

   Pourquoi un simple `margin:auto` ne suffit pas : la cellule vit dans un TABLEAU de
   720 px, dans une zone VISIBLE de 628 px. Le centre du tableau n'est pas le centre de
   la zone visible. Il faut donc connaître la largeur visible.
   → Le JS la publie en `--sc-w` (mesurée sur `clientWidth`), et la bulle s'y centre.
     Avec `position:sticky`, elle reste au MILIEU de l'écran, quel que soit le
     défilement horizontal. Sur grand écran, `--sc-w` vaut la largeur réelle, donc le
     centrage est identique : une seule règle pour les deux cas.
   ═══════════════════════════════════════════════════════════════════════════════ */
tr.rec-groupe > td {
  padding: 16px 0 6px;
  border: 0;
  background: transparent;
}
tr.rec-groupe .groupe-bande {
  /* LA BULLE EST COLLANTE ET LARGE COMME LA ZONE VISIBLE : elle reste donc centrée,
     même quand le tableau défile sous elle. */
  position: sticky;
  left: 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  width: var(--sc-w, 100%);
  max-width: var(--sc-w, 100%);
  /* Un fond très léger : sans lui, le texte des colonnes passerait SOUS la bulle et
     la rendrait illisible pendant le défilement. Il reprend la teinte de la page,
     donc il ne se voit pas. */
  background: rgba(251, 248, 247, .9);
  border-radius: 13px;
}
tr.rec-groupe .band { justify-content: center; }

/* En mode cartes (pas de défilement horizontal), on n'a pas besoin du fond de
   protection : la bulle se centre simplement. */
@media (min-width: 1100px) {
  tr.rec-groupe .groupe-bande { background: transparent; }
}


/* ═══════════════════════════════════════════════════════════════════════════════
   L'EN-TÊTE ÉPINGLÉ + L'ÉCART ENTRE CATÉGORIES RÉDUIT
   ═══════════════════════════════════════════════════════════════════════════════
   Client : « la ligne où il y a les noms et les désignations doit rester épinglée,
   comme ça on voit les informations en bas tout en se référant aux colonnes. »
   Et : « il y a beaucoup d'espace vide entre les catégories, essayez de diminuer un
   peu. »

   MESURÉ : le modèle de l'en-tête épinglé existe DÉJÀ dans l'application — c'est
   celui du tableur classique (`.sheet-scroll` + `.data-sheet th{position:sticky}`).
   On le reprend donc, plutôt que d'inventer une seconde technique.

   L'OBSTACLE À RETIRER : `.tableur-anime` porte `overflow:hidden`, ce qui CASSE
   `position:sticky` sur tous ses descendants. C'est la règle CSS qui interdit à un
   élément collant de fonctionner dans un parent qui rogne son contenu.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── ON RETIRE L'OBSTACLE ────────────────────────────────────────────────────── */
.tableur-anime { overflow: visible; }   /* était `hidden` : il cassait le sticky */

/* ── LE TABLEAU A SA PROPRE ZONE DE DÉFILEMENT ──────────────────────────────── */
.card.liste-entites > .table-scroll {
  max-height: 64vh;                     /* la même mesure que le tableur classique */
  overflow: auto;                       /* les deux axes : vertical ET horizontal */
  -webkit-overflow-scrolling: touch;
}

/* ── L'EN-TÊTE ÉPINGLÉ ──────────────────────────────────────────────────────── */
.card.liste-entites .table th {
  position: sticky;
  top: 0;
  z-index: 3;                           /* au-dessus des lignes ET des bulles collantes */
  /* Un fond quasi opaque : sans lui, les lignes défileraient SOUS les titres et on
     ne lirait plus rien. On reprend la teinte de la page, donc ça ne se voit pas. */
  background: rgba(250, 246, 245, .96);
  backdrop-filter: blur(6px);
  /* Un trait franc ferme la ligne : c'est lui qui dit « l'en-tête s'arrête ici ». */
  box-shadow: inset 0 -1px 0 rgba(20, 22, 27, .16);
}

/* ── LES COLONNES CLIQUABLES ────────────────────────────────────────────────── */
/* Client : « je veux qu'on puisse trier quand on clique sur les colonnes. » */
.th-tri {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 6px 2px 0;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;                        /* l'en-tête garde son style (DM Mono, majuscules) */
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  border-radius: 5px;
  transition: color .15s ease, background .15s ease;
}
.th-tri:hover { color: #14161b; background: rgba(20, 22, 27, .06); }
.th-tri i {
  font-style: normal;
  font-size: 9px;
  opacity: .38;                         /* la flèche par défaut est discrète */
  transition: opacity .15s ease, color .15s ease;
}
.th-tri:hover i { opacity: .7; }
/* La colonne ACTIVE : sa flèche s'allume et prend la couleur de la marque. */
.th-tri.est-actif { color: #b81f2b; }
.th-tri.est-actif i { opacity: 1; color: #a01a26; }
.th-tri:focus-visible { outline: 2px solid rgba(212, 43, 56, .4); outline-offset: 2px; }

/* ── L'ÉCART ENTRE CATÉGORIES, RÉDUIT ───────────────────────────────────────── */
/* MESURÉ : `.band` (marge haute 34 px) ET le `<td>` du groupe (padding 16 px)
   s'ADDITIONNENT. On divise les deux : l'écart total passe d'environ 50 à 18 px. */
tr.rec-groupe > td { padding: 8px 0 2px; }
.card.liste-entites .band,
.card.liste-entites tr.rec-groupe .band { margin: 8px 0 6px; }

/* La bulle elle-même : un rien plus compacte, pour suivre le resserrement. */
.card.liste-entites .band-tag { padding: 7px 15px; }
.card.liste-entites .band-tag span { font-size: 11.5px; }

/* ── ON RESPECTE QUI PRÉFÈRE NE PAS VOIR D'ANIMATION ──────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .th-tri, .th-tri i { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE DASHBOARD DU HAUT — FLUX & DOTATIONS
   Client : « je veux que tu fasses un joli dashboard en haut ». Meme langage que le
   reste de l'app : cartes en negatif decollees du fond par une ombre a 3 couches et
   un liseré de lumiere en haut — jamais un fond noir, jamais une ombre lourde.
   ══════════════════════════════════════════════════════════════════════════════ */
.fx-dash { display: flex; flex-direction: column; gap: 18px; margin: 4px 0 22px; }

/* ── LES CHIFFRES ──────────────────────────────────────────────────────────── */
.fx-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.fx-kpi {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border-radius: 14px;
  background: rgba(255,255,255,.62);
  border: 1px solid rgba(23,23,25,.07);
  box-shadow: 0 1px 0 rgba(255,255,255,.85) inset,
              0 1px 2px rgba(23,23,25,.045),
              0 8px 20px -14px rgba(23,23,25,.22);
}
.fx-kpi-ic { flex: 0 0 auto; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; }
.fx-kpi-ic svg { width: 25px; height: 25px; }
.fx-kpi-txt { display: flex; flex-direction: column; min-width: 0; }
.fx-kpi-txt strong { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 25px; line-height: 1.05; color: #17171a; font-weight: 700; }
.fx-kpi-txt span { font-size: 11.5px; color: #6f6b64; letter-spacing: .2px; }

/* ── L'ARRIVAGE ─────────────────────────────────────────────────────────────── */
.fx-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.fx-actions .button { display: inline-flex; align-items: center; gap: 9px; }
.fx-act-ic { display: inline-flex; width: 17px; height: 17px; }
.fx-act-ic svg { width: 17px; height: 17px; }
.fx-actions-note { font-size: 11.5px; color: #6f6b64; max-width: 520px; line-height: 1.5; }

/* ── QUI PEUT ETRE RAVITAILLE ───────────────────────────────────────────────── */
.fx-persos-tete { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: -4px; }
.fx-persos-titre {
  font-family: 'Plus Jakarta Sans', sans-serif; font-size: 25px; font-weight: 700;
  color: #17171a; letter-spacing: -.3px;
}
.fx-persos-note { font-size: 11.5px; color: #6f6b64; max-width: 620px; line-height: 1.5; }

.fx-equipes { display: flex; flex-direction: column; gap: 12px; }
.fx-equipe {
  border-radius: 14px; padding: 4px 12px 10px;
  background: rgba(255,255,255,.5);
  border: 1px solid rgba(23,23,25,.06);
  box-shadow: 0 1px 0 rgba(255,255,255,.8) inset, 0 8px 20px -16px rgba(23,23,25,.2);
}
.fx-equipe-tete {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 10px 2px 8px; border-bottom: 1px solid rgba(23,23,25,.06);
}
.fx-equipe-nom { font-weight: 650; font-size: 13.5px; color: #17171a; }
.fx-equipe-meta { font-size: 11px; color: #6f6b64; }

/* ── LA LIGNE D'UNE PERSONNE ────────────────────────────────────────────────── */
.fx-perso-ligne {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 2px; border-bottom: 1px solid rgba(23,23,25,.045);
}
.fx-perso-ligne:last-child { border-bottom: 0; }
.fx-perso-ligne.est-bloque { opacity: .62; }
.fx-perso { display: flex; align-items: center; gap: 11px; min-width: 0; flex: 1 1 auto; }
.fx-av {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: rgba(23,23,25,.06); color: rgb(120,114,106);
  font-size: 11.5px; font-weight: 650; letter-spacing: .5px;
}
.fx-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; transform: scale(1.25); }
.fx-perso-txt { display: flex; flex-direction: column; min-width: 0; }
.fx-perso-txt strong { font-size: 13px; color: #17171a; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fx-perso-txt small { font-size: 10.5px; color: #6f6b64; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fx-perso-stock { display: flex; flex-direction: column; align-items: flex-end; padding-left: 12px; border-left: 1px solid rgba(23,23,25,.06); }
.fx-perso-stock b { font-size: 15px; color: #17171a; font-weight: 700; line-height: 1.1; }
.fx-perso-stock span { font-size: 10px; color: #6f6b64; }

/* ── LES DEUX GESTES ────────────────────────────────────────────────────────── */
.fx-gestes { display: flex; gap: 7px; flex: 0 0 auto; }
.fx-btn {
  border-radius: 9px; padding: 7px 13px; font-size: 11.5px; font-weight: 600;
  border: 1px solid transparent; cursor: pointer; font-family: inherit;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.fx-btn-plus  { background: rgba(74,140,92,.1); color: #356b46; border-color: rgba(74,140,92,.24); }
.fx-btn-plus:hover  { background: rgba(74,140,92,.18); border-color: rgba(74,140,92,.4); }
.fx-btn-moins { background: rgba(230,57,70,.075); color: #b32c38; border-color: rgba(230,57,70,.2); }
.fx-btn-moins:hover { background: rgba(230,57,70,.14); border-color: rgba(230,57,70,.36); }
.fx-btn:active { transform: translateY(1px); }

/* ── LE BLOCAGE, DIT EN CLAIR ───────────────────────────────────────────────── */
.fx-bloque {
  display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto;
  max-width: 260px; text-align: right;
}
.fx-bloque b { font-size: 11.5px; color: rgb(120,114,106); font-weight: 650; }
.fx-bloque small { font-size: 10px; color: rgb(150,144,136); line-height: 1.35; }
.fx-vide { font-size: 12px; color: rgb(150,144,136); padding: 9px 2px; margin: 0; }

/* ── LE DIALOGUE D'ARRIVAGE ─────────────────────────────────────────────────── */
#arrivage-dialog .dot-pied { display: flex; justify-content: flex-end; gap: 10px; padding-top: 8px; }
@media (max-width: 720px) {
  .fx-kpis { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .fx-kpi-txt strong { font-size: 21px; }
  .fx-perso-ligne { flex-wrap: wrap; }
  .fx-gestes { width: 100%; justify-content: flex-end; }
}

/* ══ LE JOURNAL DE TRAÇABILITÉ ═════════════════════════════════════════════════
   Une ligne par mouvement. La couleur dit le SENS du geste (entree verte, sortie
   rouge, reservation ambre) — c'est une regle par POSITION, jamais par libelle. */
.fx-jrn-entete {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 4px 2px 10px; border-bottom: 1px solid rgba(23,23,25,.07);
  font-size: 11.5px; color: #6f6b64;
}
.fx-jrn {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 2px; border-bottom: 1px solid rgba(23,23,25,.045);
}
.fx-jrn:last-child { border-bottom: 0; }
.fx-jrn-qte {
  flex: 0 0 auto; min-width: 42px; text-align: center;
  font-family: 'Plus Jakarta Sans', sans-serif; font-size: 13.5px; font-weight: 700;
  padding: 4px 7px; border-radius: 8px;
}
.fx-jrn.est-entree .fx-jrn-qte { color: #356b46; background: rgba(74,140,92,.1); }
.fx-jrn.est-sortie .fx-jrn-qte { color: #b32c38; background: rgba(230,57,70,.08); }
.fx-jrn.est-reserve .fx-jrn-qte { color: #9a6a1e; background: rgba(201,138,58,.12); }
.fx-jrn-txt { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.fx-jrn-txt b { font-size: 12.5px; color: #17171a; font-weight: 620; }
.fx-jrn-traj { font-size: 11.5px; color: rgb(96,92,86); }
.fx-jrn-txt small { font-size: 10.5px; color: rgb(150,144,136); }
.fx-jrn-droite { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: 0 0 auto; }
.fx-jrn-date { font-size: 11px; color: rgb(120,114,106); font-variant-numeric: tabular-nums; white-space: nowrap; }
.fx-jrn-stat { font-size: 10px; color: rgb(150,144,136); }
.fx-jrn-stat.est-attente { color: #9a6a1e; font-weight: 620; }
.fx-jrn-stat.est-refuse { color: #b32c38; font-weight: 620; }
.fx-jrn.est-attente { background: rgba(201,138,58,.045); }
.fx-jrn.est-attente .fx-jrn-qte { outline: 1px dashed rgba(201,138,58,.4); outline-offset: 1px; }

/* ══ LES RÉCEPTIONS À VALIDER ═════════════════════════════════════════════════ */
.fx-att-zone {
  border-radius: 14px; padding: 12px 14px 6px; margin-bottom: 14px;
  background: rgba(201,138,58,.05);
  border: 1px solid rgba(201,138,58,.22);
}
.fx-att-tete { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding-bottom: 8px; }
.fx-att-titre { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 15px; font-weight: 700; color: #17171a; }
.fx-att-note { font-size: 11.5px; color: #9a6a1e; }
.fx-att {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 2px; border-top: 1px solid rgba(201,138,58,.16);
}
.fx-att-txt { display: flex; flex-direction: column; min-width: 0; }
.fx-att-txt b { font-size: 13px; color: #17171a; font-weight: 620; }
.fx-att-txt span { font-size: 11.5px; color: rgb(96,92,86); }
.fx-att-txt small { font-size: 10.5px; color: #9a6a1e; }
.fx-att-act { display: flex; gap: 7px; flex: 0 0 auto; }
@media (max-width: 720px) {
  .fx-jrn { flex-wrap: wrap; }
  .fx-jrn-droite { width: 100%; align-items: flex-start; padding-left: 54px; }
  .fx-att { flex-wrap: wrap; }
  .fx-att-act { width: 100%; justify-content: flex-end; }
}

/* ══ L'ARRIVAGE : LE BLOC DE CREATION D'UN NOUVEAU GOODIE ═════════════════════
   Replie par defaut : un formulaire qui montre tout tout le temps fait hesiter.
   Il s'ouvre seulement quand on dit « c'est un nouveau goodie ». */
.arr-creation { border-top: 1px solid rgba(23,23,25,.07); padding-top: 10px; }
.arr-creation.est-cache { display: none; }
.arr-creation .form-hint { margin: 0 0 8px; font-size: 11.5px; color: #6f6b64; }
.form-status.est-erreur { color: #b32c38; font-weight: 600; }

/* ══ LES FICHES DE « QUI PEUT ÊTRE RAVITAILLÉ » ═══════════════════════════════
   Deux groupes nommés (Responsable / Collaboratrice), chacun ouvert par la bulle
   rouge et ses deux traits. Dans chaque fiche : l'identité, l'affectation lisible,
   puis la bande de goodies en miniatures. Même charte que le reste : cartes en
   négatif décollées du fond, aucune ombre lourde, aucun fond noir. */
.fx-fiches {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 12px; margin-bottom: 6px;
}
.fx-fiche {
  border-radius: 14px; padding: 12px 14px 10px;
  background: rgba(255,255,255,.62);
  border: 1px solid rgba(23,23,25,.07);
  box-shadow: 0 1px 0 rgba(255,255,255,.85) inset,
              0 1px 2px rgba(23,23,25,.045),
              0 10px 22px -16px rgba(23,23,25,.24);
}
.fx-fiche.est-bloque { opacity: .58; }

/* ── L'IDENTITÉ ET LES GESTES ──────────────────────────────────────────────── */
.fx-fiche-tete { display: flex; align-items: center; gap: 11px; }
.fx-fiche-av {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 11px; overflow: hidden;
  /* ⚠️ `overflow: hidden` CLIPPE l'image volontairement : le `scale(1.25)` de la recette de
     cadrage du visage dessine 47,5 px dans une boite de 38. C'est l'INTENTION, pas un defaut.
     MESURE : mon detecteur a signale « DEBORD 5px » ici — c'est un FAUX POSITIF de l'instrument
     (il lit la boite TRANSFORMEE), corrige dans la sonde, pas ici. Aucun correctif CSS n'etait
     necessaire : `contain: paint` a ete essaye, la mesure est restee a 5px, donc il a ete retire. */
  display: flex; align-items: center; justify-content: center;
  background: rgba(23,23,25,.06); color: rgb(120,114,106);
  font-size: 12px; font-weight: 650; letter-spacing: .4px;
}
.fx-fiche-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; transform: scale(1.25); }
.fx-fiche-id { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.fx-fiche-id strong { font-size: 13.5px; color: #17171a; font-weight: 640; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fx-fiche-id small { font-size: 10.5px; color: #6f6b64; }
.fx-fiche-gestes { display: flex; gap: 6px; flex: 0 0 auto; }
.fx-fiche-bloque { display: flex; flex-direction: column; align-items: flex-end; max-width: 150px; text-align: right; flex: 0 0 auto; }
.fx-fiche-bloque b { font-size: 11px; color: rgb(120,114,106); font-weight: 650; }
.fx-fiche-bloque small { font-size: 9.5px; color: rgb(150,144,136); line-height: 1.3; }

/* ── L'AFFECTATION, DITE EN CLAIR ──────────────────────────────────────────── */
.fx-af-ligne { display: flex; flex-wrap: wrap; gap: 5px; margin: 9px 0 7px; }
.fx-af {
  font-size: 10.5px; padding: 2.5px 8px; border-radius: 20px;
  background: rgba(23,23,25,.045); color: rgb(112,106,100);
  border: 1px solid rgba(23,23,25,.05);
}
.fx-af.est-camp { background: rgba(230,57,70,.07); color: #b32c38; border-color: rgba(230,57,70,.16); }
.fx-af.est-evt { background: rgba(212,165,116,.14); color: #8a6a2e; border-color: rgba(212,165,116,.3); }

/* ── LA BANDE DE GOODIES ───────────────────────────────────────────────────── */
.fx-gd-bande {
  margin-top: 9px; padding-top: 9px; border-top: 1px solid rgba(23,23,25,.06);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.fx-gd-liste { display: flex; flex-wrap: wrap; gap: 7px; min-width: 0; }
.fx-gd { display: flex; align-items: center; gap: 6px; }
.fx-gd-img {
  width: 30px; height: 30px; border-radius: 9px; overflow: hidden; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: rgba(23,23,25,.055);
  border: 1px solid rgba(23,23,25,.06);
}
.fx-gd-img img { width: 100%; height: 100%; object-fit: contain; }
.fx-gd-ini { font-size: 9.5px; font-weight: 700; color: rgb(130,124,116); letter-spacing: .3px; }
.fx-gd-txt { display: flex; flex-direction: column; line-height: 1.05; }
.fx-gd-txt b { font-size: 13px; color: #17171a; font-weight: 700; }
.fx-gd-txt small { font-size: 9.5px; color: rgb(150,144,136); white-space: nowrap; }
.fx-gd-totaux {
  display: flex; gap: 12px; flex: 0 0 auto; padding-left: 12px;
  border-left: 1px solid rgba(23,23,25,.06);
}
.fx-gd-tot { display: flex; flex-direction: column; align-items: flex-end; }
.fx-gd-tot b { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 16px; color: #17171a; font-weight: 700; line-height: 1.05; }
.fx-gd-tot small { font-size: 9.5px; color: rgb(150,144,136); white-space: nowrap; }
.fx-gd-vide { font-size: 11px; color: rgb(150,144,136); margin: 9px 0 2px; }

/* ── LES ÉCARTÉS, DITS EN CLAIR ────────────────────────────────────────────── */
.fx-ecartes {
  font-size: 11px; color: rgb(150,144,136); margin: 10px 0 0;
  padding: 8px 11px; border-radius: 10px; background: rgba(23,23,25,.03);
}
@media (max-width: 720px) {
  .fx-fiches { grid-template-columns: 1fr; }
  .fx-fiche-tete { flex-wrap: wrap; }
  .fx-fiche-gestes { width: 100%; justify-content: flex-end; }
}

/* ══ LA BULLE AU MILIEU, AVEC UN TRAIT DE CHAQUE CÔTÉ ══════════════════════════
   Client : « une bulle rouge en haut au milieu ». Le centrage ne vient pas d'un
   `text-align` : il vient de DEUX traits qui se partagent l'espace libre de part et
   d'autre de la bulle. Une règle `.band{display:grid}` posée plus loin dans la
   feuille cassait cet équilibre (bulle en colonne 1, traits en colonne 2) : on
   retablit explicitement le flex, pour ces deux sections seulement. */
.fx-bande-centree .band {
  display: flex; align-items: center; gap: 18px; margin: 26px 0 14px;
}
.fx-bande-centree .band-line { flex: 1 1 0; min-width: 40px; }

/* ══ LA FICHE D'AJOUT : LA MÊME QUE LA CAMPAGNE, MOINS LA COUVERTURE ═══════════
   Client : « les fiches de nouveau quoi que ce soit [...] doivent ressembler à la
   nouvelle fiche de campagne bien sûr mais sans photo de couverture ». */
.arr-creation { border-top: 1px solid var(--line); padding-top: 12px; }
.arr-creation.est-cache { display: none; }
.form-status.est-erreur { color: var(--red); font-weight: 600; }

/* ══════════════════════════════════════════════════════════════════════════════
   TOUTE FENETRE DE LA FAMILLE `admin-dialog` : CLAIRE, ARRONDIE, LISIBLE
   ══════════════════════════════════════════════════════════════════════════════
   Client : « il y a des pop-ups noirs ou on ne voit pas de texte, ou il n'y a pas de
   bouton ferme [...] c'est tres decevant. »

   Ces regles sont ecrites EN DERNIER dans la feuille, donc elles gagnent sur les deux
   declarations sombres plus haut (`.profile-sheet` a la ligne 447 et
   `dialog.admin-dialog` a la ligne 4064, ecrites a l'epoque ou le client voulait des
   fiches sombres). Une seule verite pour TOUTE la famille, y compris les fenetres
   futures : personne n'a besoin d'y repenser. */
dialog.admin-dialog {
  background: #ffffff;
  color: var(--text, #17171a);
  border: 1px solid rgba(23,23,25,.10);
  border-radius: 18px;
  box-shadow: 0 24px 60px -22px rgba(23,23,25,.32);
}
dialog.admin-dialog::backdrop { background: rgba(15,16,17,.42); backdrop-filter: blur(3px); }

/* L'encre, remise explicitement : les regles sombres posaient du blanc herite. */
dialog.admin-dialog h1, dialog.admin-dialog h2, dialog.admin-dialog h3,
dialog.admin-dialog h4, dialog.admin-dialog strong, dialog.admin-dialog b { color: var(--text, #17171a); }
dialog.admin-dialog p, dialog.admin-dialog label, dialog.admin-dialog span { color: inherit; }
dialog.admin-dialog .section-label, dialog.admin-dialog .eq-sous,
dialog.admin-dialog .form-hint, dialog.admin-dialog .row-sub,
dialog.admin-dialog .dialog-kicker { color: var(--muted, #74716c); }
dialog.admin-dialog input, dialog.admin-dialog select, dialog.admin-dialog textarea {
  background: #fff; color: var(--text, #17171a); border: 1px solid var(--line2, rgba(23,23,25,.14));
}
dialog.admin-dialog select option { background: #fff; color: var(--text, #17171a); }

/* Le bandeau epingle : c'est lui qui porte le bouton de FERMETURE. Il est pose
   automatiquement par l'interception de `showModal` (voir app.js). */
dialog.admin-dialog .sheet-pin-bar {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  position: sticky; top: 0; z-index: 6;
  padding: 10px 12px 0;
  /* ══ LA BARRE VA À DROITE ═══════════════════════════════════════════════════
     Client : « le bouton fermé et sombre/clair sont à gauche alors qu'il doit être
     à droite ».
     MESURÉ : la barre se posait à x=150, à 1212px du bord droit. Cette règle-ci est
     plus SPÉCIFIQUE que  (qui portait ) :
     elle réimposait  en perdant le , et la barre, réduite à
     , retombait à gauche du flux.
      sur un élément sticky la repousse à droite, et  la
     garde visible quand on descend : les deux propriétés tiennent ensemble. */
  margin-left: auto; width: max-content;
}
dialog.admin-dialog .sheet-close-btn {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid rgba(23,23,25,.12); background: #fff;
  color: var(--text, #17171a); font-size: 19px; line-height: 1; cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
dialog.admin-dialog .sheet-close-btn:hover { border-color: rgba(23,23,25,.3); background: rgba(23,23,25,.04); }
dialog.admin-dialog .sheet-theme-toggle {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid rgba(23,23,25,.12); background: #fff;
  color: var(--muted, #74716c); font-size: 15px; cursor: pointer;
}
dialog.admin-dialog .sheet-theme-toggle:hover { border-color: rgba(23,23,25,.3); }

/* Le shader en filigrane : en `multiply` sur fond clair, jamais en `screen` (pensé
   pour le sombre, il devenait invisible). */
dialog.admin-dialog .fiche-shader { opacity: .07; mix-blend-mode: multiply; }

/* Le pied de boutons, arrondi lui aussi. */
dialog.admin-dialog .sheet-actions,
dialog.admin-dialog .eq-pied,
dialog.admin-dialog .dialog-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(23,23,25,.08);
}
dialog.admin-dialog .button { border-radius: 10px; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE ROUGE ATTEND UNE TENTATIVE
   ══════════════════════════════════════════════════════════════════════════════
   Client : capture d'un champ « Nom du goodie » deja cercle de rouge a l'ouverture.
   MESURE : `:invalid ? true` des l'ouverture pour tout champ `required` vide — c'est
   la regle par defaut du navigateur. Un formulaire neuf qui crie une erreur avant
   qu'on ait touche a quoi que ce soit, c'est une faute d'accueil.

   On neutralise donc `:invalid` par defaut, et on ne le retablit que lorsque le
   formulaire porte `tentative-envoi` (posee au premier clic sur Ajouter). */
dialog.admin-dialog input:invalid,
dialog.admin-dialog select:invalid,
dialog.admin-dialog textarea:invalid {
  border-color: var(--line2, rgba(23,23,25,.14));
  outline: none;
}
dialog.admin-dialog form.tentative-envoi input:invalid,
dialog.admin-dialog form.tentative-envoi select:invalid,
dialog.admin-dialog form.tentative-envoi textarea:invalid,
dialog.admin-dialog.tentative-envoi input:invalid,
dialog.admin-dialog.tentative-envoi select:invalid,
dialog.admin-dialog.tentative-envoi textarea:invalid {
  border-color: var(--red, #a01a26);
  box-shadow: 0 0 0 3px rgba(212,43,56,.10);
}
/* Le champ qui a le focus garde toujours la couleur d'accent, jamais le rouge d'erreur :
   l'utilisateur doit pouvoir corriger en confiance. */
dialog.admin-dialog.tentative-envoi input:invalid:focus,
dialog.admin-dialog form.tentative-envoi input:invalid:focus {
  border-color: var(--line2, rgba(23,23,25,.14));
  box-shadow: none;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE FOCUS NE DOIT PAS RESSEMBLER A UNE ERREUR
   ══════════════════════════════════════════════════════════════════════════════
   MESURE : le champ de titre s'ouvrait cercle de `rgb(212,43,56)` — parce que le rouge
   de la charte sert AUSSI de style de focus, et que le champ recoit le focus a
   l'ouverture. Sur un formulaire neuf, cela se lit « erreur » avant toute saisie.

   Deux sens distincts, donc :
     · FOCUS  : accent neutre, doux — on montre OU l'on ecrit, pas ce qui va mal ;
     · ERREUR : le rouge, et seulement apres une tentative d'envoi.

   Ces regles sont ecrites en dernier : elles gagnent sur les declarations plus haut
   (lignes 1380/1382 et 1446/1448) qui donnaient le rouge au focus. */
.profile-sheet.sheet-clair .titre-champ:focus,
.profile-sheet .titre-champ:focus,
dialog.admin-dialog .titre-champ:focus {
  outline: 0;
  background: #ffffff;
  border-color: rgba(23,23,25,.34);
  box-shadow: 0 0 0 3px rgba(23,23,25,.06);
}

/* Meme regle pour les champs ordinaires du formulaire : le focus est neutre. */
dialog.admin-dialog input:focus,
dialog.admin-dialog select:focus,
dialog.admin-dialog textarea:focus {
  outline: 0;
  border-color: rgba(23,23,25,.34);
  box-shadow: 0 0 0 3px rgba(23,23,25,.06);
}

/* ══════════════════════════════════════════════════════════════════════════════
   EN ERREUR — ET C'EST LA DERNIERE REGLE, VOLONTAIREMENT
   ══════════════════════════════════════════════════════════════════════════════
   MESURE : une premiere version posait cette regle AVANT la regle de focus neutre.
   La neutre, venant apres, gagnait TOUJOURS — le rouge d'erreur ne s'affichait donc
   jamais. En CSS, a specificite egale, c'est la DERNIERE declaration qui gagne.

   Elle est donc ecrite ici, tout a la fin : le champ fautif reste rouge, y compris
   sous le curseur. Un champ a corriger doit se voir jusqu'a ce qu'il soit corrige. */
dialog.admin-dialog.tentative-envoi .titre-champ:invalid,
dialog.admin-dialog form.tentative-envoi .titre-champ:invalid,
dialog.admin-dialog.tentative-envoi input:invalid,
dialog.admin-dialog.tentative-envoi select:invalid,
dialog.admin-dialog.tentative-envoi textarea:invalid,
dialog.admin-dialog form.tentative-envoi input:invalid,
dialog.admin-dialog form.tentative-envoi select:invalid,
dialog.admin-dialog form.tentative-envoi textarea:invalid {
  border-color: var(--red, #a01a26) !important;
  box-shadow: 0 0 0 3px rgba(212,43,56,.10);
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE VRAI THEME SOMBRE — CLIENT : « avec mode sombre clair »
   ══════════════════════════════════════════════════════════════════════════════
   MESURE : le bouton de bascule changeait bien la CLASSE, mais pas le FOND —
   `avant clic : sheet-clair fond=rgb(255,255,255)` / `apres clic : sombre
   fond=rgb(255,255,255)`. Le bouton tournait donc dans le vide.

   CAUSE : la regle claire `dialog.admin-dialog { background:#fff }` (0,2,1 sur l'element)
   gagnait sur le degrade sombre porte par `.profile-sheet`. En retirant `sheet-clair`,
   plus rien ne rendait la fiche sombre.

   On ecrit donc le theme sombre ICI, explicitement scope a `:not(.sheet-clair)` —
   specificite (0,2,1), donc il gagne sur la regle claire generique, et perd
   volontairement des qu'on ajoute `sheet-clair`. */
dialog.admin-dialog:not(.sheet-clair) {
  background: linear-gradient(150deg, #26282a 0%, #1a1b1d 100%);
  color: #f4f3f1;
  border-color: rgba(255,255,255,.14);
}
dialog.admin-dialog:not(.sheet-clair) h1,
dialog.admin-dialog:not(.sheet-clair) h2,
dialog.admin-dialog:not(.sheet-clair) h3,
dialog.admin-dialog:not(.sheet-clair) h4,
dialog.admin-dialog:not(.sheet-clair) strong,
dialog.admin-dialog:not(.sheet-clair) b { color: #f4f3f1; }
dialog.admin-dialog:not(.sheet-clair) .section-label,
dialog.admin-dialog:not(.sheet-clair) .eq-sous,
dialog.admin-dialog:not(.sheet-clair) .form-hint,
dialog.admin-dialog:not(.sheet-clair) .row-sub,
dialog.admin-dialog:not(.sheet-clair) .profile-kicker,
dialog.admin-dialog:not(.sheet-clair) .dialog-kicker { color: rgba(244,243,241,.62); }
dialog.admin-dialog:not(.sheet-clair) input,
dialog.admin-dialog:not(.sheet-clair) select,
dialog.admin-dialog:not(.sheet-clair) textarea {
  background: rgba(255,255,255,.06);
  color: #f4f3f1;
  border-color: rgba(255,255,255,.2);
}
dialog.admin-dialog:not(.sheet-clair) select option { background: #26282a; color: #f4f3f1; }
dialog.admin-dialog:not(.sheet-clair) input::placeholder,
dialog.admin-dialog:not(.sheet-clair) textarea::placeholder { color: rgba(244,243,241,.42); }
dialog.admin-dialog:not(.sheet-clair) .titre-champ { background: rgba(255,255,255,.06); color: #f4f3f1; border-color: rgba(255,255,255,.24); }
dialog.admin-dialog:not(.sheet-clair) .sheet-close-btn,
dialog.admin-dialog:not(.sheet-clair) .sheet-theme-toggle {
  background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.2); color: #f4f3f1;
}
dialog.admin-dialog:not(.sheet-clair) .button.secondary {
  background: transparent; border-color: rgba(255,255,255,.24); color: #f4f3f1;
}
dialog.admin-dialog:not(.sheet-clair) .fiche-shader { opacity: .13; mix-blend-mode: screen; }
dialog.admin-dialog:not(.sheet-clair) .sheet-actions,
dialog.admin-dialog:not(.sheet-clair) .eq-pied { border-top-color: rgba(255,255,255,.14); }

/* ══════════════════════════════════════════════════════════════════════════════
   LA BULLE DU TITRE D'UNE FICHE NEUVE — PLUS GRANDE QUE CELLE DE SECTION
   ══════════════════════════════════════════════════════════════════════════════
   Client : « c'est mal structuré ». MESURÉ : la fiche portait DEUX bulles de même
   apparence — celle du titre (« Nouveau goodie ») et celle de la section
   (« INFORMATIONS »). Deux bulles identiques ne hiérarchisent rien : l'œil ne sait pas
   laquelle est le titre de la fiche.

   On donne donc à la bulle du titre sa propre échelle. C'est le rôle que joue déjà
   `.profile-id` sur les fiches existantes (le titre y est plus grand que les sections). */
.sheet-bulle {
  display: flex; align-items: center; gap: 14px;
  margin: 4px 0 2px;
}
.sheet-bulle-tag {
  flex: none;
  font-family: 'Plus Jakarta Sans', Manrope, sans-serif;
  font-size: 17px; font-weight: 700; letter-spacing: -.01em;
  color: #fff; background: #e63946;
  padding: 8px 20px; border-radius: 14px;
  box-shadow: 0 4px 14px -5px rgba(230,57,70,.5);
  white-space: nowrap;
}
.sheet-bulle-line {
  flex: 1 1 0; min-width: 24px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, rgba(23,23,25,.06), rgba(23,23,25,.4));
}
.sheet-bulle-line:last-child {
  background: linear-gradient(90deg, rgba(23,23,25,.4), rgba(23,23,25,.06));
}
dialog.admin-dialog:not(.sheet-clair) .sheet-bulle-line {
  background: linear-gradient(90deg, rgba(255,255,255,.06), rgba(255,255,255,.42));
}
dialog.admin-dialog:not(.sheet-clair) .sheet-bulle-line:last-child {
  background: linear-gradient(90deg, rgba(255,255,255,.42), rgba(255,255,255,.06));
}

/* L'emplacement du visuel : il montre l'icône standard, jamais du vide. */
.sheet-avatar-slot .picker-vide,
.sheet-avatar-slot .picker-defaut { object-fit: contain; }

/* Le champ titre n'existe plus sur les fiches neuves : ce style ne sert plus qu'aux
   fiches existantes, où il est DANS un vrai champ (donc jamais coupé). */
.titre-champ { line-height: 1.25; }

/* ══ LA BULLE RESPECTE LA MÊME MARGE QUE LE FORMULAIRE ══════════════════════════
   Client : « c'est mal structuré ». MESURÉ : la bulle était décalée par rapport aux
   champs du dessous — elle vivait dans l'en-tête, à côté de l'avatar. Elle est
   maintenant un frère du portrait, donc elle suit la marge de la fiche et se centre
   sur la MÊME largeur que les champs. Un seul alignement pour toute la fiche. */
dialog.admin-dialog .sheet-bulle {
  display: flex; align-items: center; gap: 14px;
  margin: 10px 0 4px;
}
dialog.admin-dialog .sheet-bulle-tag {
  flex: none;
  font-family: 'Plus Jakarta Sans', Manrope, sans-serif;
  font-size: 17px; font-weight: 700; letter-spacing: -.01em;
  color: #fff; background: #e63946;
  padding: 8px 20px; border-radius: 14px;
  box-shadow: 0 4px 14px -5px rgba(230,57,70,.5);
  white-space: nowrap;
}
dialog.admin-dialog .sheet-bulle-line {
  flex: 1 1 0; min-width: 24px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, rgba(23,23,25,.06), rgba(23,23,25,.4));
}
dialog.admin-dialog .sheet-bulle-line:last-child {
  background: linear-gradient(90deg, rgba(23,23,25,.4), rgba(23,23,25,.06));
}
dialog.admin-dialog:not(.sheet-clair) .sheet-bulle-line {
  background: linear-gradient(90deg, rgba(255,255,255,.06), rgba(255,255,255,.42));
}
dialog.admin-dialog:not(.sheet-clair) .sheet-bulle-line:last-child {
  background: linear-gradient(90deg, rgba(255,255,255,.42), rgba(255,255,255,.06));
}
/* Le profil reste sur UNE ligne : portrait à gauche, identité à droite. La bulle ne
   doit pas s'y glisser — c'est ce qui la décalait. */
dialog.admin-dialog .profile-head { display: flex; align-items: center; gap: 16px; }

/* ══════════════════════════════════════════════════════════════════════════════
   FLUX & DOTATIONS : TROIS WIDGETS PAR LIGNE
   ══════════════════════════════════════════════════════════════════════════════
   Client : « les widgets ne doivent pas être un par ligne — elles doivent être les
   trois sur une ligne ou les deux sur la ligne ».

   MESURÉ avant : `flux-cartes` n'avait AUCUNE colonne déclarée (donc 1 par ligne) et
   `fx-fiches` demandait 340px minimum (donc 2 par ligne dans ~1200px de contenu).

   On déclare donc franchement 3 colonnes, et on resserre les minima pour que ça tienne
   sur un vrai écran. Repli propre à 2 puis à 1 sur les écrans étroits — jamais de
   débordement. Les widgets de chiffres passent à 3 fixes : ce sont des lectures
   courtes, elles doivent s'aligner comme un tableau de bord. */
.fx-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
.fx-fiches { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; gap: 12px; }
.fx-hotes-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.flux-cartes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.flux-hotes { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }

@media (max-width: 1180px) {
  .fx-kpis, .fx-fiches, .fx-hotes-3, .flux-cartes, .flux-hotes {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
}
@media (max-width: 760px) {
  .fx-kpis, .fx-fiches, .fx-hotes-3, .flux-cartes, .flux-hotes {
    grid-template-columns: 1fr !important;
  }
}

/* ── LES BLOCS NUMÉROTÉS ──────────────────────────────────────────────────────
   Client : « elle est incompréhensible ». MESURÉ : la vue empilait trois blocs qui
   répétaient la même information. On la découpe en blocs NOMMÉS ET NUMÉROTÉS, pour
   que le regard sache où il est et où il va. */
.fx-bloc { margin-bottom: 26px; }
.fx-bloc-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  background: rgba(230,57,70,.1); color: #b32c38;
  font-size: 11px; font-weight: 700;
}
.fx-bloc-aide {
  font-size: 11.5px; color: #6f6b64; line-height: 1.5;
  margin: 0 0 12px; max-width: 720px;
}

/* ── LA RANGÉE DE BOUTONS ─────────────────────────────────────────────────────
   Client : « équipé vraiment de boutons ». On rassemble les gestes en UNE rangée
   lisible, au lieu de trois boutons dispersés dans trois sections. */
.fx-actions-rang { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }
.fx-actions-rang .button { display: inline-flex; align-items: center; gap: 8px; }
.fx-actions-rang .fx-act-ic { display: inline-flex; width: 16px; height: 16px; }
.fx-actions-rang .fx-act-ic svg { width: 16px; height: 16px; }

/* ── LE LOGIGRAMME, EN 3 BOÎTES SUR UNE LIGNE ──────────────────────────────── */
/* Client : « les widgets ne doivent pas être une par ligne ». Elles doivent tenir
   ENSEMBLE sur la ligne. MESURE : `repeat(3, 1fr)` était figé — un 4e widget passait
   seul à la ligne suivante. `auto-fit` les place toutes sur une ligne dès qu'il y a la
   place, et les replie proprement sur écran étroit. */
.fx-flux-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.fx-fb {
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px 16px; border-radius: 14px;
  background: rgba(255,255,255,.62);
  border: 1px solid rgba(23,23,25,.07);
  box-shadow: 0 1px 0 rgba(255,255,255,.85) inset, 0 8px 20px -16px rgba(23,23,25,.22);
}
.fx-fb-tag { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: rgb(150,144,136); }
.fx-fb-val { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 26px; font-weight: 700; color: #17171a; line-height: 1.05; }
.fx-fb-sous { font-size: 11px; color: #6f6b64; }
@media (max-width: 760px) { .fx-flux-3 { grid-template-columns: 1fr; } }

/* ══════════════════════════════════════════════════════════════════════════════
   CONTRASTE ET ANIMATION DE TOUTES LES FENÊTRES
   ══════════════════════════════════════════════════════════════════════════════
   Client : « il faut absolument avoir du contraste entre la couleur du texte et le
   fond, absolument. Il faut que tu animes tout ça. »

   MESURÉ : `eq-section-titre` et `eq-note` du pop-up événement étaient posés SANS
   couleur — ils héritaient donc d'une encre claire, illisible sur fond clair.

   Deux règles, valables pour TOUTE la famille (donc les fenêtres à venir aussi) :
     1. l'encre est posée EXPLICITEMENT sur chaque famille de texte, jamais héritée ;
     2. chaque ouverture est animée — un pop-up qui apparaît d'un coup ne se lit pas
        comme une couche, il se lit comme une erreur d'affichage. */

/* ── 1. LE CONTRASTE, EXPLICITE ─────────────────────────────────────────────── */
dialog.admin-dialog .eq-section-titre,
dialog.admin-dialog .eq-titre,
dialog.admin-dialog .dialog-kicker,
dialog.admin-dialog .profile-kicker,
dialog.admin-dialog .section-label {
  color: rgb(116,113,108);
  font-weight: 650;
}
dialog.admin-dialog .eq-section-titre { font-size: 12.5px; letter-spacing: .04em; }
dialog.admin-dialog .eq-note,
dialog.admin-dialog .eq-sous,
dialog.admin-dialog .form-hint,
dialog.admin-dialog .row-sub,
dialog.admin-dialog small {
  color: #6f6b64;
}
dialog.admin-dialog label,
dialog.admin-dialog .field label {
  color: #3a3835;
  font-weight: 600;
}
dialog.admin-dialog h1, dialog.admin-dialog h2, dialog.admin-dialog h3 {
  color: rgb(23,23,26);
  font-weight: 700;
}
dialog.admin-dialog input, dialog.admin-dialog select, dialog.admin-dialog textarea {
  color: rgb(23,23,26);
  background: #fff;
}
dialog.admin-dialog input::placeholder, dialog.admin-dialog textarea::placeholder {
  color: #76726c;
}
dialog.admin-dialog .button.secondary { color: #3a3835; }

/* En mode SOMBRE, on reprend la main : c'est l'inverse, et il doit rester lisible. */
dialog.admin-dialog:not(.sheet-clair) label,
dialog.admin-dialog:not(.sheet-clair) .field label,
dialog.admin-dialog:not(.sheet-clair) .eq-section-titre,
dialog.admin-dialog:not(.sheet-clair) h1,
dialog.admin-dialog:not(.sheet-clair) h2,
dialog.admin-dialog:not(.sheet-clair) h3 { color: #f4f3f1; }
dialog.admin-dialog:not(.sheet-clair) .eq-note,
dialog.admin-dialog:not(.sheet-clair) .eq-sous,
dialog.admin-dialog:not(.sheet-clair) small { color: rgba(244,243,241,.66); }
dialog.admin-dialog:not(.sheet-clair) input,
dialog.admin-dialog:not(.sheet-clair) select,
dialog.admin-dialog:not(.sheet-clair) textarea {
  color: #f4f3f1; background: rgba(255,255,255,.07);
}

/* ── 2. L'ANIMATION ─────────────────────────────────────────────────────────── */
@keyframes fx-ouverture {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes fx-fond {
  from { opacity: 0; }
  to   { opacity: 1; }
}
dialog.admin-dialog[open] {
  animation: fx-ouverture .24s cubic-bezier(.22,.9,.3,1) both;
}
dialog.admin-dialog[open]::backdrop {
  animation: fx-fond .2s ease both;
}
/* Les sections entrent en cascade : c'est ce qui donne la sensation de « vivant ». */
@keyframes fx-cascade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
dialog.admin-dialog[open] > form > *:nth-child(1),
dialog.admin-dialog[open] .sheet-section:nth-of-type(1) { animation: fx-cascade .3s ease .04s both; }
dialog.admin-dialog[open] > form > *:nth-child(2),
dialog.admin-dialog[open] .sheet-section:nth-of-type(2) { animation: fx-cascade .3s ease .09s both; }
dialog.admin-dialog[open] > form > *:nth-child(3),
dialog.admin-dialog[open] .sheet-section:nth-of-type(3) { animation: fx-cascade .3s ease .14s both; }
dialog.admin-dialog[open] > form > *:nth-child(4) { animation: fx-cascade .3s ease .19s both; }
dialog.admin-dialog[open] > form > *:nth-child(5) { animation: fx-cascade .3s ease .24s both; }

/* Les champs répondent au survol : la fenêtre n'est pas une image fixe. */
dialog.admin-dialog input:hover:not(:focus),
dialog.admin-dialog select:hover:not(:focus),
dialog.admin-dialog textarea:hover:not(:focus) { border-color: rgba(23,23,25,.24); }

/* On respecte qui ne veut pas d'animation : l'accessibilité avant l'effet. */
@media (prefers-reduced-motion: reduce) {
  dialog.admin-dialog[open], dialog.admin-dialog[open]::backdrop,
  dialog.admin-dialog[open] > form > *:nth-child(n) { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE JOURNAL : BORDÉ ET PLAFONNÉ
   ══════════════════════════════════════════════════════════════════════════════
   Client : « il doit être bordé par un tableur et il ne doit pas déborder en bas
   jusqu'à l'infini ». Un journal de 3 000 lignes ne peut pas pousser la page : le
   défilement est donc INTERNE, et l'en-tête reste visible.

   Le modèle employé est celui du tableur classique (`.sheet-scroll` + `th` collant) —
   on reprend une technique qui marche déjà dans le projet. */
.fx-tb-tete {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  flex-wrap: wrap; padding: 2px 2px 10px;
}
.fx-tb-tete-titre { font-size: 12.5px; font-weight: 650; color: #17171a; }
.fx-tb-tete-meta { font-size: 11.5px; color: #6f6b64; }
.fx-tb-tete-meta b.est-attente { color: #9a6a1e; }

.fx-tb-scroll {
  max-height: 460px;          /* LE PLAFOND : la page ne s'allonge plus */
  overflow-y: auto;
  border: 1px solid rgba(23,23,25,.09);
  border-radius: 14px;
  background: rgba(255,255,255,.62);
}
.fx-tb { width: 100%; border-collapse: separate; border-spacing: 0; }
.fx-tb thead th {
  position: sticky; top: 0; z-index: 3;      /* L'EN-TÊTE ÉPINGLÉ */
  background: #fbfaf9;
  border-bottom: 1px solid rgba(23,23,25,.1);
  text-align: left; padding: 10px 12px;
  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: #6f6b64; font-weight: 650; white-space: nowrap;
}
.fx-tb td { padding: 9px 12px; border-bottom: 1px solid rgba(23,23,25,.05); font-size: 12.5px; color: #17171a; }
.fx-tb tbody tr:last-child td { border-bottom: 0; }
.fx-tb tbody tr:hover td { background: rgba(23,23,25,.025); }
.fx-tb-ligne.est-attente td { background: rgba(201,138,58,.05); }
.fx-tb-qte b {
  font-family: 'Plus Jakarta Sans', sans-serif; font-size: 13px; font-weight: 700;
  padding: 3px 7px; border-radius: 8px; display: inline-block; min-width: 40px; text-align: center;
}
.fx-tb-ligne.est-entree .fx-tb-qte b { color: #356b46; background: rgba(74,140,92,.1); }
.fx-tb-ligne.est-sortie .fx-tb-qte b { color: #b32c38; background: rgba(230,57,70,.08); }
.fx-tb-ligne.est-reserve .fx-tb-qte b { color: #9a6a1e; background: rgba(201,138,58,.12); }
.fx-tb-mvt { font-weight: 600; white-space: nowrap; }
.fx-tb-art { color: #3a3835; }
.fx-tb-traj { white-space: nowrap; }
.fx-tb-de, .fx-tb-vers { color: #3a3835; }
.fx-tb-fleche { color: #76726c; margin: 0 6px; }
.fx-tb-ctx { color: #6f6b64; font-size: 11.5px; }
.fx-tb-date { white-space: nowrap; font-variant-numeric: tabular-nums; color: rgb(96,92,86); font-size: 11.5px; }
.fx-tb-stat { font-size: 11px; color: #6f6b64; }
.fx-tb-stat.est-attente { color: #9a6a1e; font-weight: 620; }
.fx-tb-stat.est-refuse { color: #b32c38; font-weight: 620; }
@media (max-width: 900px) { .fx-tb-scroll { max-height: 380px; } }

/* ══ LE PÉRIMÈTRE EN HAUT : « TOUT OU CAMPAGNE » ════════════════════════════════
   Client : « juste en haut, on peut avoir tout ou campagne [...] le flux de dotation a
   toujours un rapport direct avec la campagne ». Deux boutons, un select qui
   n'apparait que quand il a un sens. */
.fx-perim {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px; margin-bottom: 18px; border-radius: 14px;
  background: rgba(255,255,255,.62);
  border: 1px solid rgba(23,23,25,.07);
  box-shadow: 0 1px 0 rgba(255,255,255,.85) inset, 0 8px 20px -16px rgba(23,23,25,.2);
}
.fx-perim-lab { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: rgb(150,144,136); }
.fx-perim-btn { display: inline-flex; gap: 0; border-radius: 10px; overflow: hidden; border: 1px solid rgba(23,23,25,.1); }
.fx-pill {
  border: 0; background: #fff; color: rgb(90,86,80);
  padding: 7px 16px; font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit;
  transition: background .15s ease, color .15s ease;
}
.fx-pill + .fx-pill { border-left: 1px solid rgba(23,23,25,.1); }
.fx-pill:hover { background: rgba(23,23,25,.04); }
.fx-pill.est-actif { background: #e63946; color: #fff; }
.fx-perim-select {
  padding: 7px 12px; border-radius: 10px; font-size: 12.5px; font-family: inherit;
  border: 1px solid rgba(23,23,25,.12); background: #fff; color: #17171a;
}
.fx-perim-note { font-size: 11.5px; color: #6f6b64; }
.fx-perim-reset {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: 11.5px; color: #b32c38; text-decoration: underline;
}

/* ══ LE DÉTAIL PAR ARTICLE : UN TABLEAU, PAS UNE LISTE ══════════════════════════
   Client : « de quel article on parle, chez qui il y a les articles, combien reste au
   dépôt au niveau de chaque article ». Cinq chiffres par article : ils doivent
   s'aligner comme un tableur, sinon on ne peut pas les comparer. */
.fx-table-scroll {
  max-height: 420px; overflow: auto;
  border: 1px solid rgba(23,23,25,.09); border-radius: 14px;
  background: rgba(255,255,255,.62);
}
.fx-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.fx-table thead th {
  position: sticky; top: 0; z-index: 3; background: #fbfaf9;
  border-bottom: 1px solid rgba(23,23,25,.1); text-align: left; padding: 10px 12px;
  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: #6f6b64; font-weight: 650; white-space: nowrap;
}
.fx-table td { padding: 10px 12px; border-bottom: 1px solid rgba(23,23,25,.05); font-size: 12.5px; color: #17171a; }
.fx-table tbody tr:last-child td { border-bottom: 0; }
.fx-table tbody tr:hover td { background: rgba(23,23,25,.025); }
.fx-art-nom { display: flex; align-items: center; gap: 10px; min-width: 0; }
.fx-art-img {
  width: 32px; height: 32px; border-radius: 9px; overflow: hidden; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(23,23,25,.055); border: 1px solid rgba(23,23,25,.06);
}
.fx-art-img img { width: 100%; height: 100%; object-fit: contain; }
.fx-art-ini { font-size: 9.5px; font-weight: 700; color: rgb(130,124,116); }
.fx-art-txt { display: flex; flex-direction: column; min-width: 0; }
.fx-art-txt strong { font-size: 12.5px; font-weight: 620; }
.fx-art-txt small { font-size: 10px; color: rgb(150,144,136); }
.fx-art-n { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.fx-art-n[data-fort=1] { color: #17171a; }
.fx-art-tot { font-weight: 700; color: #b32c38; }
.fx-art-qui strong { font-size: 11.5px; font-weight: 600; }
.fx-art-qui small { display: block; font-size: 10px; color: rgb(150,144,136); }

/* ══ LES ÉVÉNEMENTS ═════════════════════════════════════════════════════════════ */
.fx-evt-range { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
.fx-evt {
  display: flex; flex-direction: column; gap: 3px; text-align: left;
  padding: 12px 14px; border-radius: 12px; cursor: pointer; font-family: inherit;
  background: rgba(255,255,255,.62); border: 1px solid rgba(23,23,25,.07);
  box-shadow: 0 1px 0 rgba(255,255,255,.85) inset, 0 8px 20px -16px rgba(23,23,25,.2);
  transition: border-color .15s ease, background .15s ease;
}
.fx-evt:hover { border-color: rgba(230,57,70,.3); }
.fx-evt.est-actif { border-color: #e63946; background: rgba(230,57,70,.06); }
.fx-evt-titre { font-size: 13px; font-weight: 640; color: #17171a; }
.fx-evt-meta { font-size: 11px; color: #6f6b64; }

/* ══ LE VOLUME ET L'EXPORT DU JOURNAL ═══════════════════════════════════════════ */
.fx-tb-tete { align-items: center; }
.fx-tb-sel {
  padding: 6px 10px; border-radius: 9px; font-size: 12px; font-family: inherit;
  border: 1px solid rgba(23,23,25,.12); background: #fff; color: #17171a; cursor: pointer;
}
.fx-tb-sel:hover { border-color: rgba(23,23,25,.28); }
.fx-tb-export {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: 9px; cursor: pointer; font-family: inherit;
  font-size: 12px; font-weight: 600; color: #356b46;
  background: rgba(74,140,92,.09); border: 1px solid rgba(74,140,92,.24);
  transition: background .15s ease, border-color .15s ease;
}
.fx-tb-export:hover { background: rgba(74,140,92,.17); border-color: rgba(74,140,92,.4); }
.fx-tb-export .fx-act-ic { display: inline-flex; width: 15px; height: 15px; }
.fx-tb-export .fx-act-ic svg { width: 15px; height: 15px; }

/* ══ LE PÉRIMÈTRE EN WIDGETS : UNE PASTILLE PAR CAMPAGNE ═══════════════════════
   Client : « périmètre : tout, Heineken [...] nom de campagne quand on aura une
   autre [...] sous forme de widget ». Donc pas de menu déroulant : tout est visible,
   et la prochaine campagne s'ajoutera d'elle-même. */
.fx-perim { flex-direction: column; align-items: stretch; gap: 10px; }
.fx-perim-ligne { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.fx-perim-lab {
  flex: none; min-width: 74px;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(150,144,136);
}
.fx-camp-pills, .fx-evt-range { display: flex; flex-wrap: wrap; gap: 8px; }

.fx-camp-pill {
  display: flex; flex-direction: column; gap: 1px; text-align: left;
  padding: 8px 16px; border-radius: 11px; cursor: pointer; font-family: inherit;
  background: rgba(255,255,255,.7);
  border: 1px solid rgba(23,23,25,.09);
  transition: border-color .15s ease, background .15s ease, transform .12s ease;
}
.fx-camp-pill:hover { border-color: rgba(230,57,70,.34); transform: translateY(-1px); }
.fx-camp-pill.est-actif {
  /* ⚠️ MESURE : blanc sur #e63946 = 4,17:1, sous le seuil WCAG. #cf333f = 5,00:1. */
  background: #cf333f; border-color: #cf333f;
  box-shadow: 0 5px 14px -6px rgba(230,57,70,.55);
}
.fx-cp-nom { font-size: 12.5px; font-weight: 640; color: #17171a; white-space: nowrap; }
.fx-camp-pill.est-actif .fx-cp-nom { color: #fff; }
.fx-cp-sous { font-size: 10px; color: rgb(150,144,136); }
.fx-camp-pill.est-actif .fx-cp-sous { color: rgba(255,255,255,.78); }

.fx-evt-pill {
  display: flex; flex-direction: column; gap: 1px; text-align: left;
  padding: 7px 13px; border-radius: 10px; cursor: pointer; font-family: inherit;
  background: rgba(255,255,255,.6);
  border: 1px solid rgba(23,23,25,.08);
  transition: border-color .15s ease, background .15s ease;
}
.fx-evt-pill:hover { border-color: rgba(212,165,116,.5); }
.fx-evt-pill.est-actif { background: rgba(212,165,116,.16); border-color: rgba(212,165,116,.55); }
.fx-ep-nom { font-size: 12px; font-weight: 620; color: #17171a; white-space: nowrap; }
.fx-ep-sous { font-size: 10px; color: rgb(150,144,136); }

/* En mode sombre, les pastilles suivent : sinon elles restent claires sur fond sombre. */
dialog.admin-dialog:not(.sheet-clair) .fx-camp-pill,
dialog.admin-dialog:not(.sheet-clair) .fx-evt-pill {
  background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.16);
}
dialog.admin-dialog:not(.sheet-clair) .fx-cp-nom,
dialog.admin-dialog:not(.sheet-clair) .fx-ep-nom { color: #f4f3f1; }

/* ══ L'ÉQUATION DU STOCK : LA COLONNE « REMIS » ET LA LIGNE DE PIED ═════════════
   Client : « quand on fait une équation, on doit avoir l'ensemble égal à ce qu'on a
   reçu depuis le premier jour ». Le pied ferme le tableau : il donne le total de
   chaque terme, et l'ensemble ne bouge jamais. */
.fx-art-remis { color: #9a6a1e; }
.fx-art-pied td {
  border-top: 2px solid rgba(23,23,25,.14);
  border-bottom: 0;
  background: rgba(23,23,25,.022);
  font-size: 12.5px;
}
.fx-art-pied strong { font-weight: 700; color: #17171a; }
.fx-art-pied .fx-art-n { font-weight: 700; }
.fx-art-note {
  margin: 10px 2px 0; font-size: 11.5px; line-height: 1.55;
  color: #6f6b64;
}
.fx-art-note strong { color: #3a3835; font-weight: 650; }

/* ══════════════════════════════════════════════════════════════════════════════
   LES TROIS BOÎTES DU FLUX : COLORÉES, SANS NUMÉRO, CENTRÉES
   Client : « colorie moi les widgets sans mettre 1 · dépôt 2 · chez les collaborateurs,
   ça sert à rien ce truc c'est un dépôt général donc tu me refais sortir un peu ces
   onglets » puis « centres moi cela ».
   Chaque boîte prend la teinte de SA famille — la même que dans le reste du produit :
   rouge pour le dépôt, ambre pour les responsables, vert pour les collaboratrices.
   La couleur est posée au FOND (jamais opacity) : l'encre reste pleinement lisible.
   ══════════════════════════════════════════════════════════════════════════════ */
.fx-fb { position: relative; overflow: hidden; }

/* Le filet de couleur : une bande verticale de 3px sur le bord gauche. Discret,
   il suffit à identifier la famille d'un coup d'œil sans crier. */
.fx-fb::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
}
/* MESURE : a 5,5 % de teinte, la couleur ne se voyait pas (la vision decrivait un
   "fond blanc"). Le client veut VOIR la couleur : on monte a 13 %, et on colore
   aussi le CHIFFRE. L'encre reste franche -> le contraste est preserve. */
.fx-fb-depot  { background: rgba(230,57,70,.13);  border-color: rgba(230,57,70,.42); }
.fx-fb-resp   { background: rgba(201,138,58,.14); border-color: rgba(201,138,58,.46); }
.fx-fb-hote   { background: rgba(74,140,92,.13);  border-color: rgba(74,140,92,.44); }
.fx-fb-depot::before { background: #e63946; }
.fx-fb-resp::before  { background: #c98a3a; }
.fx-fb-hote::before  { background: #4a8c5c; }

/* LA TEINTE DE L'ÉTIQUETTE : chaque famille se nomme dans sa propre couleur. */
.fx-fb-depot .fx-fb-tag { color: #ab2734; }
.fx-fb-resp  .fx-fb-tag { color: #8f601f; }
.fx-fb-hote  .fx-fb-tag { color: #336842; }

/* LE CHIFFRE reste en encre franche : la couleur porte l'identité, pas le chiffre. */
/* Le chiffre prend la couleur de sa famille : la boite se lit d'un coup d'oeil.
   Encre foncee (pas de pastel) -> le contraste demande par le client tient. */
.fx-fb-depot .fx-fb-val { color: #b0202e; }
.fx-fb-resp  .fx-fb-val { color: #8a5a12; }
.fx-fb-hote  .fx-fb-val { color: #2d6640; }
.fx-fb { font-size: inherit; }

/* ── LE CENTRAGE ───────────────────────────────────────────────────────────────
   « centres moi cela ». Les trois boîtes partagent la largeur à parts égales et
   leur contenu se centre : le bloc ne penche plus à gauche. */
.fx-flux-3 .fx-fb { align-items: center; text-align: center; }
.fx-actions-rang { justify-content: center; }
.fx-actions-rang .button { min-width: 0; }
.fx-dash .fx-bloc { text-align: center; }
.fx-dash .fx-bloc .fx-table-scroll,
.fx-dash .fx-bloc .fx-fiches,
.fx-dash .fx-bloc .fx-fb,
.fx-dash .fx-bloc .fx-art-note { text-align: left; }

/* ══ LE TÉMOIN DE VERSION ═══════════════════════════════════════════════════════
   La version réellement chargée par cette page, sous la marque. Il n'est pas décoratif :
   il tranche « le code est-il à jour ? » d'un coup d'œil, sans console ni mesure.
   Client : capture renvoyée trois fois pour un défaut déjà corrigé. */
.version-temoin {
  display: inline-block; margin-top: 3px; padding: 1px 6px; border-radius: 5px;
  background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.16);
  color: rgba(255,255,255,.62); font: 600 9.5px ui-monospace, Menlo, monospace;
  font-style: normal; letter-spacing: .08em;
}

/* ── LA 4e FAMILLE : LES CADEAUX GAGNÉS ──────────────────────────────────────────
   Quatrième teinte, distincte des trois autres : mauve profond. Elle dit « sorti du
   stock pour un joueur » — ni un lieu de stockage, ni un porteur.
   Même règle que les autres : teinte au FOND (jamais opacity), encre franche. */
.fx-fb-gagne  { background: rgba(138,91,105,.14); border-color: rgba(138,91,105,.46); }
.fx-fb-gagne::before { background: #8a5b69; }
.fx-fb-gagne  .fx-fb-tag { color: #6f4551; }
.fx-fb-gagne  .fx-fb-val { color: #6f4551; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE POP-UP « NOUVEAU MOUVEMENT » — CONTRASTE ET GRILLE
   Client : « le pop-up me sort comme ça, il est quasiment illisible ».
   Le pop-up dérive de `coqueNouvelleFiche` : la bulle rouge, l'en-tête et les sections
   viennent de la même source que les autres fiches. Ce bloc ne règle que ce qui lui est
   PROPRE — la grille des quatre champs, et l'ENCRE de chaque texte.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA GRILLE : deux colonnes, jamais un champ par ligne ───────────────────── */
#dotation-dialog .dot-grille {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px 16px;
}

/* ── UN CHAMP : l'étiquette AU-DESSUS, franche ──────────────────────────────── */
#dotation-dialog .dot-champ { display: grid; gap: 7px; min-width: 0; }
#dotation-dialog .dot-champ > span {
  /* ENCRE EXPLICITE : plus jamais héritée d'un thème sombre. */
  color: #17171a; font: 700 11.5px Manrope, sans-serif;
  letter-spacing: .09em; text-transform: uppercase;
}
#dotation-dialog .dot-champ select,
#dotation-dialog .dot-champ input {
  width: 100%; padding: 11px 12px; border-radius: 10px;
  border: 1px solid rgba(23,23,25,.20); background: #fff;
  color: #17171a; font: 500 13.5px Manrope, sans-serif;
}
#dotation-dialog .dot-champ select:focus,
#dotation-dialog .dot-champ input:focus {
  outline: none; border-color: #e63946; box-shadow: 0 0 0 3px rgba(230,57,70,.12);
}
#dotation-dialog .dot-champ select option { background: #fff; color: #17171a; }

/* ── LE BANDEAU D'AIDE : franchement lisible, et rouge SEULEMENT en blocage ─── */
#dotation-dialog .dot-aide {
  margin: 14px 0 0; padding: 11px 14px; border-radius: 10px;
  background: rgba(23,23,25,.05); border-left: 3px solid rgba(23,23,25,.20);
  color: #4a4744; font: 500 12.5px Manrope, sans-serif; line-height: 1.5;
}
#dotation-dialog .dot-aide.dot-blocage {
  background: rgba(230,57,70,.08); border-left-color: #e63946; color: #9c2029;
}

/* ── CE QUI A ÉTÉ ÉCARTÉ : une note sobre, jamais un cri ────────────────────── */
#dotation-dialog .dot-ecartes {
  margin: 12px 0 0; padding: 10px 12px; border-radius: 9px;
  background: rgba(201,138,58,.09); border-left: 3px solid rgba(201,138,58,.5);
  color: #6b4a15; font: 500 11.8px Manrope, sans-serif; line-height: 1.5;
}

/* ── LE COMPTEUR, dans le pied ──────────────────────────────────────────────── */
#dotation-dialog .eq-compte {
  margin-right: auto; color: #4a4744; font: 600 12.5px Manrope, sans-serif;
}
#dotation-dialog .eq-compte strong { color: #17171a; }

/* ── Le pied de boutons de cette fiche suit la même règle que les autres ────── */
#dotation-dialog .sheet-actions { display: flex; align-items: center; gap: 10px; }

@media (max-width: 620px) {
  #dotation-dialog .dot-grille { grid-template-columns: 1fr; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   NOUVEAU MOUVEMENT — CONTRASTE RENFORCE ET SURVOL ROUGE
   Client : « ajuste moi aussi le contraste des textes ... on arrive pas a voir.
   Utilise le noir sur le blanc et le survol en rouge pour mieux distinguer ».
   Toutes les encres sont posées EXPLICITEMENT (jamais héritées) : encre noire sur fond
   blanc pour ce qui se lit, et le ROUGE de la charte réservé à deux usages — le survol
   qui désigne, et le blocage qui interdit.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. TOUT CE QUI SE LIT : NOIR SUR BLANC ────────────────────────────────── */
#dotation-dialog,
#dotation-dialog .section-label,
#dotation-dialog .fx-bloc-aide,
#dotation-dialog .form-hint,
#dotation-dialog .row-sub,
#dotation-dialog .eq-sous,
#dotation-dialog .eq-note,
#dotation-dialog .dialog-kicker,
#dotation-dialog .profile-kicker,
#dotation-dialog p,
#dotation-dialog label,
#dotation-dialog span,
#dotation-dialog small { color: #17171a; }

#dotation-dialog strong, #dotation-dialog b, #dotation-dialog h2,
#dotation-dialog h3, #dotation-dialog h4 { color: #0d0d0f; }

/* Les étiquettes de champ : noires, franches, un peu plus grandes qu'avant. */
#dotation-dialog .dot-champ > span {
  color: #0d0d0f; font: 700 12px Manrope, sans-serif;
  letter-spacing: .085em; text-transform: uppercase;
}

/* ── 2. LES CHAMPS : TEXTE NOIR, CONTOUR VISIBLE ───────────────────────────── */
#dotation-dialog .dot-champ select,
#dotation-dialog .dot-champ input {
  color: #0d0d0f; font: 600 14px Manrope, sans-serif;
  border: 1.5px solid rgba(23,23,25,.34); background: #fff;
}
#dotation-dialog .dot-champ select option { background: #fff; color: #0d0d0f; }

/* ── 3. LE SURVOL ROUGE : un champ qui répond se désigne ───────────────────── */
#dotation-dialog .dot-champ:hover > span { color: #c0392f; }

#dotation-dialog .dot-champ select:hover,
#dotation-dialog .dot-champ input:hover {
  border-color: #e63946; background: rgba(230,57,70,.045);
  box-shadow: 0 0 0 3px rgba(230,57,70,.10);
}
/* Les options : c'est le survol DANS la liste déroulante qui doit se distinguer. */
#dotation-dialog .dot-champ select option:hover,
#dotation-dialog .dot-champ select option:focus,
#dotation-dialog .dot-champ select option:checked {
  background: rgba(230,57,70,.16) !important; color: #0d0d0f !important;
}
/* Le focus clavier garde le même signal que le survol : une seule grammaire. */
#dotation-dialog .dot-champ select:focus,
#dotation-dialog .dot-champ input:focus {
  outline: none; border-color: #e63946; box-shadow: 0 0 0 4px rgba(230,57,70,.15);
}

/* ── 4. LE BANDEAU D'AIDE : NOIR, ET ROUGE SEULEMENT S'IL Y A BLOCAGE ──────── */
#dotation-dialog .dot-aide {
  margin: 16px 0 0; padding: 12px 15px; border-radius: 10px;
  background: rgba(23,23,25,.06); border-left: 3px solid rgba(23,23,25,.28);
  color: #17171a; font: 600 13px Manrope, sans-serif; line-height: 1.5;
}
#dotation-dialog .dot-aide.dot-blocage {
  background: rgba(230,57,70,.10); border-left-color: #e63946; color: #8f1a23;
}

/* ── 5. LA NOTE DES PERSONNES ÉCARTÉES : LOISIBLE, PAS UN CRI ─────────────── */
#dotation-dialog .dot-ecartes {
  margin: 13px 0 0; padding: 11px 13px; border-radius: 9px;
  background: rgba(201,138,58,.10); border-left: 3px solid rgba(201,138,58,.6);
  color: #5b3d0d; font: 600 12.5px Manrope, sans-serif; line-height: 1.5;
}

/* ── 6. LE COMPTEUR ET LE PIED ─────────────────────────────────────────────── */
#dotation-dialog .eq-compte { color: #17171a; font: 700 13px Manrope, sans-serif; }
#dotation-dialog .eq-compte strong { color: #0d0d0f; }
#dotation-dialog .sheet-actions { border-top-color: rgba(23,23,25,.14); }

/* Le bouton secondaire se lit aussi : son contour et son encre sont affirmés. */
#dotation-dialog .button.secondary {
  color: #17171a; border-color: rgba(23,23,25,.30); background: #fff;
}
#dotation-dialog .button.secondary:hover {
  border-color: #e63946; color: #c0392f; background: rgba(230,57,70,.05);
}


/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO, UN PEU PLUS BAS
   Client : « redescends un peu le logo ».
   MESURE : le logo commencait a y=18 (le padding de 18px de la barre etait sa seule
   respiration). On ajoute de l'air AU-DESSUS, sans toucher a ce qui suit : c'est une MARGE
   en haut de la marque, jamais un padding. MESURE : le padding avait grandi la
   boite (hauteur 48 -> 70) sans deplacer le logo (y reste a 18).
   ══════════════════════════════════════════════════════════════════════════════ */
body[data-interface="direction"] .sidebar .brand {
  margin-top: 22px;
}


/* ══════════════════════════════════════════════════════════════════════════════
   LE POP-UP « NOUVEAU MOUVEMENT » : L'ICONE RENTRE DANS LE CADRE
   Client : « rétrécis un peu l'icône, elle déborde par rapport aux bordures du pop-up ».
   MESURE : profile-head y=120 dans un dialogue commencant a y=137 — DEBORDE de 17 px.
   CAUSE : `margin-top:-62px` vient de la fiche de CAMPAGNE, où l'avatar se pose sur la
   photo de couverture. Ce pop-up n'a PAS de couverture : la marge negative ne sert plus
   a rien et fait sortir l'icone par le haut.
   On annule donc la marge, et on reduit franchement l'icone.
   ══════════════════════════════════════════════════════════════════════════════ */
#dotation-dialog .profile-head {
  margin-top: 0;                 /* plus de sortie par le haut */
  padding: 4px 0 0;
  align-items: center;
}
#dotation-dialog .profile-head .sheet-avatar-slot {
  grid-column: 1; width: auto; align-self: center;
}
#dotation-dialog .profile-head .sheet-avatar-slot .image-pick,
#dotation-dialog .profile-head .sheet-avatar-slot .sheet-portrait-icone {
  width: 76px; height: 76px; border-radius: 16px;
}

/* L'icône en lecture seule : même cadre que les portraits, mais sans geste. */
#dotation-dialog .sheet-portrait-icone {
  display: grid; place-items: center; flex: none;
  border: 4px solid #1c1d1f; background: #fff; overflow: hidden;
}
#dotation-dialog .sheet-portrait-icone img {
  width: 100%; height: 100%; object-fit: contain; padding: 7px;
}
#dotation-dialog .profile-head .sheet-avatar-slot .image-pick img { padding: 8px; }


/* ══════════════════════════════════════════════════════════════════════════════
   ROUE — LES QUATRE MATIÈRES AJOUTÉES
   Client : « Je veux qu'on ait beaucoup de styles [...] de 4 jusqu'à 8 styles ».
   Le fond vient de `roueFond()` en style en ligne (il dépend des parts et des couleurs
   choisies). Ici on ne règle que ce qui ne peut pas l'être : le cerclage et le halo
   propres à chaque matière. Deux roues de deux styles ne peuvent pas se confondre.
   ══════════════════════════════════════════════════════════════════════════════ */
.roue-prisme .wheel-rotor {
  border-color: #ffffff;
  box-shadow: 0 0 0 2px rgba(23,23,25,.14), 0 0 34px -10px rgba(120,170,220,.55),
              0 20px 46px -22px rgba(0,0,0,.4);
}
.roue-monolithe .wheel-rotor {
  border-color: #101113;
  box-shadow: inset 0 0 0 3px rgba(255,255,255,.05), 0 18px 44px -20px rgba(0,0,0,.75);
}
.roue-mineral .wheel-rotor {
  border-color: #e8e3da;
  box-shadow: 0 0 0 2px rgba(120,113,102,.28), 0 18px 42px -20px rgba(80,72,62,.42);
}
.roue-ruban .wheel-rotor {
  border-color: #fbf9f5;
  box-shadow: 0 0 0 2px rgba(23,23,25,.16), 0 18px 42px -20px rgba(0,0,0,.42);
}

/* ══════════════════════════════════════════════════════════════════════════════
   L'AIGUILLE — COULEUR, FORME, POSITION
   Client : « la couleur de l'aiguille, le style de l'aiguille et le positionnement ».
   La couleur arrive par `--pointer-color` (posée par la charte, en ligne).
   ══════════════════════════════════════════════════════════════════════════════ */
/* Le défaut se pose sur le CADRE : l'aiguille, elle, ne fait que LIRE la variable.
   Déclarée sur l'aiguille elle-même, elle écraserait toujours la charte. */
.wheel-frame { --pointer-color: #f7c75e; }
.wheel-pointer {
  position: absolute; z-index: 6; top: -5px; left: 50%;
  width: 0; height: 0; transform: translateX(-50%);
  border-left: 18px solid transparent; border-right: 18px solid transparent;
  border-top: 38px solid var(--pointer-color);
  filter: drop-shadow(0 4px 2px rgba(0,0,0,.2));
  transition: transform .18s ease, border-color .18s ease;
}

/* ── LES QUATRE FORMES ─────────────────────────────────────────────────────── */
/* 1. TRIANGLE — la forme historique, pleine et franche. */
.aig-forme-triangle .wheel-pointer {
  border-left-width: 18px; border-right-width: 18px; border-top-width: 38px;
}
/* 2. FLÈCHE — plus étroite et plus longue : elle désigne sans masquer le lot. */
.aig-forme-fleche .wheel-pointer {
  border-left-width: 11px; border-right-width: 11px; border-top-width: 50px;
}
/* 3. GOUTTE — une pointe fine sur une base arrondie : lisible sur fond clair. */
.aig-forme-goutte .wheel-pointer {
  border-left-width: 15px; border-right-width: 15px; border-top-width: 30px;
  border-top-left-radius: 40% 26%; border-top-right-radius: 40% 26%;
  border-bottom-left-radius: 3px; border-bottom-right-radius: 3px;
}
/* 4. FINE — un trait de 5 px : discrète, elle laisse toute la place aux goodies. */
.aig-forme-fine .wheel-pointer {
  width: 5px; height: 42px; border: 0; border-radius: 3px;
  background: var(--pointer-color); transform: translateX(-50%);
}

/* ── LES QUATRE POSITIONS ──────────────────────────────────────────────────── */
/* La forme de base POINTE VERS LE BAS et se pose EN HAUT. Pour la déplacer d'un côté,
   il faut la faire pivoter ET la recentrer : la rotation seule la laisserait en dehors
   du disque. Le pivot est son propre centre, d'où les `translate` de recentrage. */
.aig-droite .wheel-pointer {
  top: 50%; left: auto; right: -5px;
  transform: translateY(-50%) rotate(90deg); transform-origin: center;
}
.aig-bas .wheel-pointer {
  top: auto; bottom: -5px; left: 50%;
  transform: translateX(-50%) rotate(180deg); transform-origin: center;
}
.aig-gauche .wheel-pointer {
  top: 50%; left: -5px; right: auto;
  transform: translateY(-50%) rotate(-90deg); transform-origin: center;
}
/* La forme FINE n'a pas de rotation qui la déplace (son décalage est nul) : on redresse
   simplement son axe dans la position choisie. */
.aig-forme-fine.aig-droite .wheel-pointer { transform: translateY(-50%); }
.aig-forme-fine.aig-gauche .wheel-pointer { transform: translateY(-50%); }
.aig-forme-fine.aig-bas .wheel-pointer { transform: translateX(-50%); }

/* ══════════════════════════════════════════════════════════════════════════════
   L'HABILLAGE ÉVÉNEMENTIEL — LE FOND DERRIÈRE LA ROUE
   Client : « On peut impacter [...] le fond ». L'image est posée DERRIÈRE le disque,
   avec son assombrissement et son flou ; la roue reste toujours nette et détachée.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-decor {
  position: absolute; inset: -18%; z-index: 0; border-radius: 24px;
  pointer-events: none;
}
.wheel-decor-voile {
  position: absolute; inset: -18%; z-index: 1; border-radius: 24px;
  pointer-events: none;
}
.wheel-frame.has-vignette::after {
  content: ''; position: absolute; inset: -18%; z-index: 2; border-radius: 24px;
  pointer-events: none;
  background: radial-gradient(circle at 50% 50%, rgba(0,0,0,0) 42%, rgba(0,0,0,.42) 100%);
}
.wheel-frame .wheel-rotor { z-index: 3; }
.wheel-frame .wheel-pointer { z-index: 6; }

/* ── LE CADRAGE DES GOODIES ────────────────────────────────────────────────── */
/* Client : « la rotation [...] des goodies ». La taille se pose par la charte. */
.wheel-prize { width: var(--prize-size, 64px); margin-left: calc(var(--prize-size, 64px) / -2);
  margin-top: calc(var(--prize-size, 64px) / -2); }
.wheel-prize img { width: var(--prize-size, 64px); height: var(--prize-size, 64px);
  object-fit: contain; }


/* ══════════════════════════════════════════════════════════════════════════════
   L'ATELIER DE ROUES — L'ONGLET JEUX
   Client : « l'objectif de cet onglet chez l'espace administrateur, ça va nous permettre
   de créer la roue, de choisir les couleurs qu'on veut, le logo, l'arrière-plan et tout
   ce qui va avec. [...] si on change de couleur, on a un preview de notre roue qu'on est
   en train de créer. »
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA VUE : les blocs s'aèrent, rien ne colle ──────────────────────────── */
#app .jeu-bloc { margin-bottom: 16px; }
#app .jeu-bloc .section-caption b { color: #17171a; font-weight: 700; }

/* ── 2. LE CHOIX DE LA ROUE : une carte par roue ─────────────────────────────
   Encre franche, contour visible, et l'état ACTIF marqué par le rouge de charte —
   pas par une teinte pâle qu'on ne distingue pas d'une autre. */
.jeu-roues { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.jeu-roue-carte {
  display: grid; gap: 3px; text-align: left; cursor: pointer;
  padding: 12px 14px; border-radius: 12px;
  border: 1.5px solid rgba(23,23,25,.16); background: #fff;
  transition: border-color .15s ease, background .15s ease, transform .12s ease;
}
.jeu-roue-carte:hover { border-color: #e63946; background: rgba(230,57,70,.05); transform: translateY(-1px); }
.jeu-roue-carte.is-active { border-color: #e63946; background: rgba(230,57,70,.08); box-shadow: 0 0 0 3px rgba(230,57,70,.10); }
.jeu-roue-nom { color: #0d0d0f; font: 700 14px Manrope, sans-serif; }
.jeu-roue-nom b { color: #c0392f; font-weight: 700; }
.jeu-roue-note { color: #383633; font: 600 11px 'DM Mono', monospace; letter-spacing: .06em; text-transform: uppercase; }
.jeu-roue-nb { color: #17171a; font: 600 11.5px Manrope, sans-serif; }

/* ── 3. LES HUIT GABARITS ────────────────────────────────────────────────────
   Chacun est une VRAIE roue dessinée : on ne décrit pas un style, on le montre. */
/* QUATRE PAR LIGNE, jamais six : MESURE `auto-fit` en placait 6, ce qui ecrasait les
   vignettes et sortait de la regle de la maison (3 ou 4 par ligne). */
.jeu-gabarits {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
}
@media (max-width: 1150px) { .jeu-gabarits { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .jeu-gabarits { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.jeu-gabarit {
  display: grid; gap: 6px; justify-items: center; cursor: pointer;
  padding: 12px 10px 11px; border-radius: 14px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
  transition: border-color .15s ease, background .15s ease, transform .12s ease;
}
.jeu-gabarit:hover { border-color: #e63946; background: rgba(230,57,70,.04); transform: translateY(-2px); }
.jeu-gabarit.is-active {
  border-color: #e63946; background: rgba(230,57,70,.07);
  box-shadow: 0 0 0 3px rgba(230,57,70,.10);
}
.jeu-gabarit-visuel { display: block; width: 132px; height: 132px; }
.jeu-gabarit-visuel .wheel-frame { width: 132px !important; height: 132px !important; }
.jeu-gabarit-visuel .wheel-rotor { width: 132px !important; height: 132px !important; border-width: 5px !important; }
.jeu-gabarit-visuel .wheel-hub { width: 32px !important; height: 32px !important; border-width: 3px !important; font-size: 9px; }
.jeu-gabarit-visuel .wheel-hub span { font-size: 9px; }
.jeu-gabarit-visuel .wheel-prize { display: none; }
.jeu-gabarit-nom { color: #0d0d0f; font: 700 13px Manrope, sans-serif; }
.jeu-gabarit-note { color: #383633; font: 500 10.5px Manrope, sans-serif; text-align: center; line-height: 1.35; }

/* ── 4. LA CHARTE : les quatre couleurs ──────────────────────────────────────
   Les widgets vont TROIS PAR LIGNE au minimum. `auto-fit` les répartit selon la place ;
   `minmax(200px,1fr)` garantit qu'ils ne tombent jamais à un par ligne sur un écran
   normal. */
.jeu-couleurs, .jeu-curseurs, .jeu-aig-tete {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;
}
.jeu-coul {
  display: grid; gap: 6px; padding: 11px 13px; border-radius: 12px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.jeu-coul:hover { border-color: #e63946; box-shadow: 0 0 0 3px rgba(230,57,70,.08); }
.jeu-coul > span:first-child {
  color: #0d0d0f; font: 700 11px Manrope, sans-serif;
  letter-spacing: .085em; text-transform: uppercase;
}
.jeu-coul-corps { display: flex; align-items: center; gap: 9px; }
.jeu-coul input[type="color"] {
  width: 44px; height: 32px; padding: 2px; cursor: pointer;
  border: 1.5px solid rgba(23,23,25,.28); border-radius: 8px; background: #fff;
}
.jeu-coul code {
  color: #17171a; font: 600 12px 'DM Mono', monospace; letter-spacing: .04em;
}

/* ── 5. LES CURSEURS ─────────────────────────────────────────────────────────
   L'étiquette et la valeur sont dans la MÊME ligne que le curseur : on lit le réglage
   sans chercher. */
.jeu-curseur {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px 10px;
  padding: 11px 13px; border-radius: 12px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
}
.jeu-curseur > span:first-child {
  grid-column: 1 / -1;
  color: #0d0d0f; font: 700 11px Manrope, sans-serif;
  letter-spacing: .085em; text-transform: uppercase;
}
.jeu-curseur input[type="range"] { width: 100%; accent-color: #e63946; cursor: pointer; }
.jeu-curseur output {
  min-width: 46px; text-align: right;
  color: #17171a; font: 700 12.5px 'DM Mono', monospace;
}
.jeu-curseur-bascule input[type="checkbox"] { width: 18px; height: 18px; accent-color: #e63946; cursor: pointer; }

.jeu-sous-titre {
  margin: 16px 0 8px; color: #17171a;
  font: 700 11.5px Manrope, sans-serif; letter-spacing: .09em; text-transform: uppercase;
}

/* ── 6. LES PASTILLES (formes d'aiguille, positions) ─────────────────────────
   Trois par ligne au minimum, comme les autres widgets. */
.jeu-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
.jeu-option {
  display: grid; gap: 2px; text-align: left; cursor: pointer;
  padding: 10px 13px; border-radius: 11px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
  transition: border-color .15s ease, background .15s ease;
}
.jeu-option:hover { border-color: #e63946; background: rgba(230,57,70,.05); }
.jeu-option.is-active { border-color: #e63946; background: rgba(230,57,70,.09); box-shadow: 0 0 0 3px rgba(230,57,70,.08); }
.jeu-option b { color: #0d0d0f; font: 700 13px Manrope, sans-serif; }
.jeu-option small { color: #383633; font: 500 10.5px Manrope, sans-serif; }

/* ── 7. LES CHANCES PAR GOODIE ───────────────────────────────────────────────
   Le nom, le curseur et la valeur sur UNE ligne : on règle vite, on lit tout de suite. */
.jeu-poids { display: grid; gap: 8px; margin-top: 4px; }
.jeu-poids-ligne {
  display: grid; grid-template-columns: minmax(150px, 1.1fr) minmax(120px, 2fr) auto;
  align-items: center; gap: 12px; padding: 9px 13px; border-radius: 11px;
  border: 1px solid rgba(23,23,25,.10); background: rgba(23,23,25,.018);
}
.jeu-poids-nom { color: #0d0d0f; font: 700 13px Manrope, sans-serif; }
.jeu-poids-nom small { display: block; color: #383633; font: 500 10.5px Manrope, sans-serif; margin-top: 1px; }
.jeu-poids-ligne input[type="range"] { width: 100%; accent-color: #e63946; cursor: pointer; }
.jeu-poids-ligne output { min-width: 30px; text-align: right; color: #17171a; font: 700 13px 'DM Mono', monospace; }

/* ── 8. L'APERÇU VIVANT ──────────────────────────────────────────────────────
   Le cœur de la demande : « si on change de couleur, on a un preview de notre roue ».
   On lui donne de la place et un fond neutre, pour que la roue soit le sujet. */
.jeu-apercu { background: linear-gradient(180deg, #fbfaf8 0%, #f4f2ef 100%); }
.jeu-apercu-zone {
  display: grid; place-items: center; min-height: 400px; padding: 30px 18px 22px;
  border-radius: 16px; border: 1px solid rgba(23,23,25,.08);
  background: radial-gradient(circle at 50% 42%, rgba(255,255,255,.9) 0 30%, rgba(255,255,255,0) 72%);
  overflow: hidden;
}
.jeu-apercu-zone .wheel-frame { width: 340px !important; height: 340px !important; }
.jeu-apercu-zone .wheel-rotor { width: 340px !important; height: 340px !important; }
/* ══ LE ZOOM DES GOODIES NE ZOOMAIT RIEN — 24 RÈGLES PLUS SPÉCIFIQUES GAGNAIENT ══
   MESURÉ : le JS pose `--prize-size` (le curseur « Taille des visuels », 28 à 80px), et
   le CSS le consomme bien dans `.wheel-prize img { width: var(--prize-size, 64px) }`.
   Mais DEUX règles plus bas reprennent la main :
      `.wheel-prize img, .wheel-prize span { width: 44px; height: 44px }`      (l.1906)
      `.jeu-apercu-zone .wheel-prize img, .jeu-apercu-zone .wheel-prize span { width: auto }`
   La seconde est un vestige : elle vise les images DE COMPÉTITION d'un ancien design où
   le visuel était posé autrement. Avec `width: auto`, la largeur réglée ne s'applique
   jamais dans l'aperçu — c'est exactement « quand je veux zoomer sur le goodie, ça ne se
   zoome pas réellement ».
   On RETIRE le vestige et on donne la priorité au réglage, dans l'aperçu comme ailleurs. */
.jeu-apercu-zone .wheel-prize img,
.jeu-apercu-zone .wheel-prize span {
  width: var(--prize-size, 64px) !important;
  height: var(--prize-size, 64px) !important;
  object-fit: contain !important;
}
.jeu-apercu-zone .wheel-prize { font-size: 11px; }

.jeu-resultat {
  margin: 14px 0 0; padding: 12px 16px; border-radius: 11px; text-align: center;
  background: rgba(23,23,25,.045); border: 1px solid rgba(23,23,25,.10);
  color: #4a4744; font: 600 13.5px Manrope, sans-serif;
}
.jeu-resultat.is-gagne {
  background: rgba(230,57,70,.10); border-color: rgba(230,57,70,.35); color: #8f1a23;
}
.jeu-resultat strong { color: #0d0d0f; font-weight: 800; }
.jeu-titre-affiche {
  margin: 16px 0 2px; text-align: center; color: #0d0d0f;
  font: 800 22px Manrope, sans-serif; letter-spacing: -.02em;
}
.jeu-sous-affiche { margin: 0; text-align: center; color: #5a5751; font: 600 13px Manrope, sans-serif; }

/* ── 9. L'ASSIGNATION ────────────────────────────────────────────────────────
   Une carte par personne, encre franche, et le rouge de charte sur le bouton d'action. */
.jeu-groupes { display: grid; grid-template-columns: 1fr; gap: 18px; }
.jeu-pers-liste {
  display: grid; gap: 9px;
  /* `auto-fill` et NON `auto-fit` : voir la note en fin de fichier. Avec `auto-fit`, la
     liste des 3 responsables COLLAPSAIT sa 4e piste vide et ses cartes passaient à 369px
     quand celles des collaboratrices faisaient 275px — deux largeurs pour un même objet. */
  grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
}
.jeu-pers {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: 12px;
  border: 1.5px solid rgba(23,23,25,.12); background: #fff;
  transition: border-color .15s ease, background .15s ease;
}
.jeu-pers:hover { border-color: rgba(230,57,70,.5); background: rgba(230,57,70,.03); }
.jeu-pers.is-ici { border-color: #e63946; background: rgba(230,57,70,.07); }
.jeu-pers-av {
  display: grid; place-items: center; width: 36px; height: 36px; overflow: hidden;
  border-radius: 10px; background: rgba(23,23,25,.06);
  color: #5a5751; font: 800 12px Manrope, sans-serif;
}
.jeu-pers-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; }
.jeu-pers-txt { min-width: 0; }
.jeu-pers-txt strong { display: block; color: #0d0d0f; font: 700 13px Manrope, sans-serif; }
.jeu-pers-txt small { display: block; color: #383633; font: 500 10.5px Manrope, sans-serif; margin-top: 2px; }
.jeu-pers-btn { padding: 6px 12px !important; font-size: 12px !important; }

/* ── 10. LES MESSAGES ET LES NOTES ───────────────────────────────────────────
   Le message de confirmation est discret ; il ne recouvre rien. */
.jeu-msg { color: #2d6640; font: 700 12px Manrope, sans-serif; }
.jeu-msg.is-erreur { color: #b0202e; }
.jeu-aide {
  margin: 12px 0 0; padding: 11px 14px; border-radius: 10px;
  background: rgba(201,138,58,.09); border-left: 3px solid rgba(201,138,58,.5);
  color: #5b3d0d; font: 600 12.5px Manrope, sans-serif; line-height: 1.5;
}
.jeu-vide { display: grid; gap: 14px; justify-items: start; }
.jeu-vide-txt { margin: 0; color: #4a4744; font: 600 13.5px Manrope, sans-serif; line-height: 1.6; max-width: 62ch; }

/* ── 11. LE MOUVEMENT, POUR QUI LE SUPPORTE ──────────────────────────────────
   Un atelier qui s'anime doucement se lit mieux — et s'arrête net si l'utilisateur
   a demandé moins d'animations. */
@media (prefers-reduced-motion: no-preference) {
  .jeu-gabarit-visuel .wheel-rotor { transition: transform .35s cubic-bezier(.2,.8,.2,1); }
  .jeu-gabarit:hover .wheel-gabarit-visuel .wheel-rotor { transform: rotate(12deg); }
}
@media (prefers-reduced-motion: reduce) {
  .jeu-roue-carte, .jeu-gabarit, .jeu-option, .jeu-pers { transition: none !important; }
}

/* ── 12. LES ÉCRANS ÉTROITS ──────────────────────────────────────────────────
   Sous 700 px, les grilles à 3 tombent à 2 puis à 1 : lisible sans écraser. */
@media (max-width: 900px) {
  .jeu-couleurs, .jeu-curseurs, .jeu-aig-tete { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
  .jeu-apercu-zone .wheel-frame, .jeu-apercu-zone .wheel-rotor { width: 280px !important; height: 280px !important; }
}
@media (max-width: 620px) {
  .jeu-couleurs, .jeu-curseurs, .jeu-aig-tete, .jeu-options { grid-template-columns: 1fr 1fr; }
  .jeu-poids-ligne { grid-template-columns: 1fr auto; }
  .jeu-poids-ligne input[type="range"] { grid-column: 1 / -1; }
  .jeu-apercu-zone .wheel-frame, .jeu-apercu-zone .wheel-rotor { width: 230px !important; height: 230px !important; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   LES AMPOULES DU CONTOUR, LES SÉPARATEURS ET LE VERNIS
   Client : « La roue que j'ai créé avec l'application [...] il y a des contours avec
   des ampoules, il y a pas mal de choses. » — repris de son app de référence.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── L'ENVELOPPE : elle porte les ampoules AUTOUR du disque ─────────────────── */
.wheel-frame .wheel-shell { position: relative; display: grid; place-items: center; }
.wheel-frame .wheel-shell .wheel-rotor { position: relative; }

/* ── LES AMPOULES ────────────────────────────────────────────────────────────
   Chaque ampoule est posée par son angle (`--stud-angle`) et rejetée sur le bord.
   Une vraie ampoule se dessine en TROIS couches : le corps (dégradé radial), le
   liseré, et le reflet de verre. C'est ce qui la fait briller au lieu de la faire
   ressembler à un point. */
.wheel-studs {
  position: absolute; inset: 0; z-index: 7; border-radius: 50%; pointer-events: none;
}
/* ── LA TIGE ET L'AMPOULE ────────────────────────────────────────────────────
   MESURÉ : `translateY(-48%)` se calcule sur l'ampoule (11 px), pas sur la roue —
   les 20 ampoules se retrouvaient à 5 px du centre, serrées au milieu du disque.
   On reprend donc la technique qui marche déjà pour les séparateurs : chaque ampoule
   est une TIGE de la hauteur du rayon, dont le bas touche le centre ; on la fait
   pivoter autour de son propre bas. Le point se pose sur le bord à TOUTE taille
   (340 px dans l'aperçu, 132 px dans les vignettes) sans connaître les pixels. */
.wheel-studs i {
  position: absolute; left: 50%; bottom: 50%; width: 0; height: 50%;
  transform-origin: 50% 100%;
  transform: translateX(-50%) rotate(var(--stud-angle));
}
/* L'ampoule elle-même, posée au bout de la tige — donc sur le bord du disque.
   Une vraie ampoule se dessine en TROIS couches : le corps, le liseré, le reflet.
   MESURÉ : à 11 px sur un disque de 340 px (3,2 %), elle se confondait avec le liseré
   et ne se voyait pas. On la porte à 19 px et on lui donne un HALO : c'est le halo qui
   la fait lire comme une ampoule ALLUMÉE. La taille se règle par `--stud-size`
   (la charte l'écrit), donc chaque roue peut avoir les siennes. */
.wheel-studs { --stud-size: 19px; }
.wheel-studs i::before {
  /* `top: 0` = le HAUT de la tige = LE BORD du disque.
     (`bottom: 0` aurait pose l'ampoule au CENTRE : c'est l'erreur corrigee ici.) */
  content: ''; position: absolute; left: 50%; top: 0;
  width: var(--stud-size); height: var(--stud-size); border-radius: 50%;
  /* ══ CENTRAGE EXACT SUR LE BORD ══════════════════════════════════════════════
     MESURÉ : avec `translate(-50%, -46%)`, le décalage vertical suivait la taille de
     L'AMPOULE et non celle de la roue. En rétrécissant l'ampoule, elle glissait hors
     du cercle — c'est ce que le client décrit.
     `translate(-50%, -50%)` centre l'ampoule SUR le bord, quelle que soit sa taille :
     le point reste exactement sur le cercle, du plus petit au plus grand. */
  transform: translate(-50%, -50%);
  background: radial-gradient(circle at 34% 28%, #fff 0 16%, #ffe9a8 30%, #dfc27a 52%, #8a5615 78%, #2e1905);
  border: 1px solid rgba(255,247,205,.85);
  /* Le HALO : deux ombres — une chaude et serree, une large et diffuse. */
  box-shadow: 0 0 6px 1px rgba(255,225,150,.75),
              0 0 16px 3px rgba(223,194,122,.35),
              0 2px 3px rgba(0,0,0,.5);
}
/* Le reflet de verre : sans lui, l'ampoule paraît mate. Il grandit avec l'ampoule. */
.wheel-studs i::after {
  content: ''; position: absolute; left: 50%; top: 0;
  width: calc(var(--stud-size) * .58); height: calc(var(--stud-size) * .58);
  border-radius: 50%;
  /* Le reflet se pose en HAUT-GAUCHE de l'ampoule, comme sur une bille. */
  transform: translate(-80%, -78%);
  background: radial-gradient(circle at 32% 28%, rgba(255,255,255,.95), transparent 48%);
}
/* Les vignettes de gabarit et les roues de petite taille : ampoules proportionnées. */
.jeu-gabarit-visuel .wheel-studs { --stud-size: 8px; }
.wheel-small .wheel-studs { --stud-size: 8px; }
/* Dans l'aperçu, les ampoules prennent leur vraie échelle. */
.jeu-apercu-zone .wheel-studs { --stud-size: 19px; }
/* Halo doré : les ampoules éclairent légèrement le disque autour d'elles. */
.wheel-frame:has(.wheel-studs) .wheel-rotor {
  box-shadow: 0 0 34px -12px rgba(223,194,122,.55), 0 18px 44px -22px rgba(0,0,0,.4);
}

/* ── LES AMPOULES, PAR GABARIT : une matière par style ─────────────────────── */
.roue-prisme .wheel-studs i::before {
  background: radial-gradient(circle at 34% 27%, #fff 0 12%, #ffb985 30%, #8b381c 72%, #3a1309);
  border-color: #ffd7b7;
}
.roue-anneau .wheel-studs i::before {
  width: 9px; height: 9px;
  background: radial-gradient(circle at 34% 27%, #fff4cf 0 12%, #bd9150 35%, #67451f 74%, #2f1c09);
  border-color: #f8dfaa;
}
.roue-monolithe .wheel-studs i::before {
  background: radial-gradient(circle at 34% 27%, #fff 0 12%, #adb4b6 30%, #3a4042 70%, #080a0b);
  border-color: #e4e9ea;
}
.roue-neon .wheel-studs i::before {
  background: radial-gradient(circle at 34% 27%, #fff 0 10%, #08d9d6 32%, #0b6f6d 74%, #04201f);
  border-color: #b8fffd;
}
.roue-mineral .wheel-studs i::before {
  background: radial-gradient(circle at 34% 27%, #fff 0 12%, #d8d3c8 32%, #7c756a 74%, #33302a);
  border-color: #f0ece2;
}
.roue-rayures .wheel-studs i::before, .roue-ruban .wheel-studs i::before {
  background: radial-gradient(circle at 34% 27%, #fff 0 14%, #f4f1ec 32%, #b8b2a6 74%, #4a453c);
  border-color: #ffffff;
}

/* ── LES SÉPARATEURS DE PARTS ────────────────────────────────────────────────
   Un trait à chaque frontière. Sans eux, deux parts de teinte voisine se confondent
   et la roue devient illisible dès qu'il y a beaucoup de goodies. */
.wheel-separators {
  position: absolute; inset: 0; border-radius: 50%; z-index: 3; pointer-events: none;
}
/* ══ UNE SEULE SÉPARATION PAR FRONTIÈRE, ET ELLE SE VOIT ══════════════════════
   Client : « les séparations de couleurs doivent séparer les goodies, mais pas mettre
   beaucoup de tirées. Parce qu'on doit voir lequel tombe sur l'espace triangulaire du
   goodie. »
   Le trait est donc FRANC (opaque, 2px) mais UNIQUE : c'est lui qui dit « ici se
   termine un lot, ici commence le suivant ». Sans hachures autour, la part qui passe
   sous l'aiguille reste parfaitement lisible. */
.wheel-separators i {
  position: absolute; left: 50%; bottom: 50%; width: 2px; height: 50%;
  transform-origin: 50% 100%;
  transform: translateX(-50%) rotate(var(--separator-angle));
  /* Le CŒUR : sombre et franc du centre jusqu'au bord — c'est lui qui marque la
     frontière sur les parts CLAIRES (blanc, doré, gris pâle). */
  background: linear-gradient(to top, rgba(23,23,25,.42), rgba(23,23,25,.62));
  /* LE LISERÉ : deux filets clairs de chaque côté — c'est eux qui marquent la
     frontière sur les parts SOMBRES. Un seul dessin, lisible partout. */
  box-shadow: -1px 0 0 rgba(255,255,255,.62), 1px 0 0 rgba(255,255,255,.62),
              0 0 3px rgba(0,0,0,.25);
}

/* ── LE VERNIS : les reflets du verre ────────────────────────────────────────
   C'est ce qui donne l'impression d'une surface polie plutôt que d'un aplat. Une
   composition différente par gabarit, pour que deux roues ne se ressemblent pas. */
.wheel-glaze {
  position: absolute; inset: 0; border-radius: 50%; z-index: 2; pointer-events: none;
}
.roue-classique .wheel-glaze {
  background: linear-gradient(124deg, transparent 22%, rgba(255,255,255,.34) 36%, transparent 46%),
              radial-gradient(circle at 34% 24%, rgba(255,255,255,.30), transparent 32%),
              radial-gradient(circle at 70% 78%, rgba(0,0,0,.20), transparent 46%);
}
.roue-prisme .wheel-glaze {
  background: linear-gradient(118deg, transparent 10%, rgba(255,255,255,.62) 25%, rgba(255,255,255,.06) 32%, transparent 42%),
              radial-gradient(circle at 32% 20%, rgba(255,255,255,.52), transparent 31%),
              radial-gradient(circle at 70% 78%, rgba(65,0,70,.24), transparent 45%);
  mix-blend-mode: screen;
}
.roue-anneau .wheel-glaze {
  background: linear-gradient(135deg, rgba(255,255,255,.34), transparent 38%, rgba(66,37,12,.12)),
              radial-gradient(circle at 32% 22%, rgba(255,255,255,.26), transparent 34%);
}
.roue-monolithe .wheel-glaze {
  background: linear-gradient(112deg, transparent 20%, rgba(255,255,255,.20) 32%, transparent 46%),
              radial-gradient(circle at 34% 24%, rgba(255,255,255,.12), transparent 30%),
              radial-gradient(circle at 68% 76%, rgba(0,0,0,.28), transparent 44%);
}
.roue-neon .wheel-glaze {
  background: linear-gradient(118deg, transparent 14%, rgba(255,255,255,.30) 28%, transparent 40%),
              radial-gradient(circle at 36% 22%, rgba(8,217,214,.28), transparent 34%),
              radial-gradient(circle at 68% 78%, rgba(255,46,99,.24), transparent 44%);
  mix-blend-mode: screen;
}
.roue-mineral .wheel-glaze {
  background: linear-gradient(145deg, rgba(255,255,255,.24), transparent 44%, rgba(61,39,20,.09)),
              radial-gradient(circle at 33% 22%, rgba(255,255,255,.20), transparent 32%);
}
.roue-rayures .wheel-glaze, .roue-ruban .wheel-glaze {
  background: linear-gradient(126deg, transparent 20%, rgba(255,255,255,.42) 34%, transparent 44%),
              radial-gradient(circle at 33% 24%, rgba(255,255,255,.36), transparent 33%);
}

/* En thème sombre de fiche, les ampoules gardent leur éclat : on ne les atténue pas. */
.profile-sheet:not(.sheet-clair) .wheel-studs i { filter: drop-shadow(0 2px 3px rgba(0,0,0,.8)); }

/* Le NOMBRE et la TAILLE des ampoules sont pilotés par la charte (en ligne).
   Une roue sobre peut n'en avoir que 12 et petites ; une roue de scène peut en avoir
   36 et grosses. C'est le client qui décide, sans toucher au code. */
.wheel-frame .wheel-studs { --stud-size: var(--ampoule-taille, 19px); }


/* ══════════════════════════════════════════════════════════════════════════════
   LE SUPPORT DE LA ROUE, ET LES PROPORTIONS D'UNE VRAIE ROUE
   Client : « Inspire-toi des vrais roues [...] Avec leur support en bas et tout. »
   Benchmark : roue 24 pouces = 610 mm, 14 cases, pied trépied 1,13-1,53 m.
   Sur une vraie roue, l'ampoule fait environ 2,3 % du diamètre : c'est cette
   proportion qu'on applique, au lieu des 5,6 % précédents qui écrasaient le disque.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LES AMPOULES À L'ÉCHELLE D'UNE VRAIE ROUE ───────────────────────────── */
/* La taille vient de la charte (`--ampoule-taille`, en px). On garde le halo, mais
   resserré : à cette échelle, un halo large ferait une tache floue. */
.wheel-frame .wheel-studs { --stud-size: var(--ampoule-taille, 8px); }
.wheel-studs i::before {
  box-shadow: 0 0 3px 0 rgba(255,225,150,.7),
              0 0 8px 1px rgba(223,194,122,.28),
              0 1px 2px rgba(0,0,0,.5);
}
.wheel-studs i::after {
  width: calc(var(--stud-size) * .55); height: calc(var(--stud-size) * .55);
  transform: translate(-50%, -28%);
}

/* ── 2. LE SUPPORT : AXE, MONTANT, SOCLE ──────────────────────────────────────
   Trois pièces, comme sur une vraie roue. Le support est posé SOUS le disque, dans
   le flux : il pousse donc la roue vers le haut au lieu de la recouvrir. */
/* ══ LES PIÈCES DU SUPPORT ════════════════════════════════════════════════════
   MESURÉ : `width: 5.5%` et `width: 46%` sur les enfants d'un conteneur
   `flex-direction: column` résolvaient contre **0** — en colonne, la largeur du
   conteneur est définie PAR ses enfants. Les trois pièces existaient, bien posées
   sous le disque, mais larges de zéro : donc invisibles.
   On passe donc à des unités de LARGEUR (`em`), pilotées par `font-size` sur le
   conteneur — stable à toutes les tailles de roue (vignette, aperçu, fiche). */
.wheel-stand {
  display: flex; flex-direction: column; align-items: center;
  /* La base de mesure du support : tout est en `em` de ce `font-size`. */
  font-size: 10px;
  margin-top: -3px; pointer-events: none;
}
.jeu-gabarit-visuel .wheel-stand { font-size: 4px; }
.jeu-apercu-zone .wheel-stand { font-size: 12px; }

/* L'AXE : le moyeu dépasse entre le disque et le montant — c'est ce détail qui dit
   « ça tourne sur un axe » plutôt que « c'est posé là ». */
.wheel-stand-axis {
  width: 3.6em; height: 1em; border-radius: 0 0 .6em .6em;
  background: linear-gradient(180deg, rgba(0,0,0,.38), rgba(0,0,0,.16));
  box-shadow: inset 0 -.2em .3em rgba(0,0,0,.3);
}
/* LE MONTANT : la tige verticale. Elle s'affine vers le bas, comme un vrai mât. */
.wheel-stand-post {
  width: 1.5em; height: 2.6em;
  background: linear-gradient(90deg,
    rgba(0,0,0,.38), var(--stand-color, #202123) 38%, rgba(255,255,255,.20) 52%,
    var(--stand-color, #202123) 66%, rgba(0,0,0,.38));
  box-shadow: inset 0 0 .2em rgba(0,0,0,.4);
}
/* LE SOCLE : le pied élargi. Une ombre au sol le pose pour de bon. */
.wheel-stand-foot {
  width: 11em; height: 1.2em; border-radius: 50%;
  background: radial-gradient(ellipse at 50% 34%,
    rgba(255,255,255,.24), var(--stand-color, #202123) 58%, rgba(0,0,0,.58));
  box-shadow: 0 5px 12px -3px rgba(0,0,0,.5), inset 0 2px 2px rgba(255,255,255,.16);
}

/* ── 3. LES VIGNETTES DE GABARIT : SUPPORT RÉDUIT ─────────────────────────────
   À 132 px, le support pleine échelle mangerait la roue. On le réduit, sans le
   retirer : la vignette doit montrer le même objet. */
/* Les vignettes : la base de mesure est posee sur le conteneur (4px), donc les trois
   pieces reduisent ENSEMBLE. Plus de surcharge piece par piece. */

/* ── 4. L'APERÇU : SUPPORT PLEINE ÉCHELLE ────────────────────────────────────
   L'aperçu gagne de la hauteur pour laisser respirer le support. */
.jeu-apercu-zone { min-height: 500px; padding-bottom: 34px; }
/* L'aperçu : base 12px (posee plus haut), le support grandit avec la roue. */

/* ── 5. L'AIGUILLE, TOUJOURS EN HAUT ─────────────────────────────────────────
   Client : « l'aiguille elle doit être en haut pas en bas ». Le défaut est déjà
   « haut » ; on renforce la lisibilité du montage en haut : c'est la position d'une
   vraie roue de loterie (le pointeur triangulaire de la roue de 610 mm). */
.wheel-frame.aig-haut .wheel-pointer { top: -7px; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES VIGNETTES DE TÉLÉVERSEMENT DE L'ATELIER
   Client : « Je veux aussi avoir la possibilité d'uploader une photo dans la roue [...]
   on ne me donne pas des URL. »
   Trois visuels (logo, moyeu, fond), chacun montrant ce qui est EN PLACE, avec deux
   gestes : Téléverser / Changer, et Retirer. Aucun champ d'adresse.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-visuels {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px;
}
.jeu-visuel {
  display: grid; gap: 9px; padding: 13px; border-radius: 13px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.jeu-visuel:hover { border-color: rgba(230,57,70,.5); box-shadow: 0 0 0 3px rgba(230,57,70,.07); }
.jeu-visuel-titre {
  color: #0d0d0f; font: 700 11px Manrope, sans-serif;
  letter-spacing: .085em; text-transform: uppercase;
}
/* L'APERÇU du visuel : carré, en pointillés tant qu'il est vide. */
.jeu-visuel-apercu {
  display: grid; place-items: center; width: 100%; height: 96px; overflow: hidden;
  border-radius: 10px; border: 1.5px dashed rgba(23,23,25,.22);
  background: rgba(23,23,25,.03); color: #8d8983;
}
.jeu-visuel-apercu.est-rempli {
  border-style: solid; border-color: rgba(23,23,25,.16);
  background: repeating-conic-gradient(#f4f2ef 0 25%, #ffffff 0 50%) 0 0 / 14px 14px;
}
.jeu-visuel-apercu img { width: 100%; height: 100%; object-fit: contain; padding: 6px; }
.jeu-visuel-apercu svg, .jeu-vis-ic svg { width: 30px; height: 30px; }
.jeu-vis-ic { display: grid; place-items: center; }

/* LES DEUX GESTES : même forme pour « Téléverser / Changer » et « Retirer ». */
.jeu-visuel-actions { display: flex; gap: 8px; }
.jeu-visuel-btn {
  flex: 1 1 auto; display: grid; place-items: center; cursor: pointer;
  padding: 7px 11px; border-radius: 9px;
  border: 1.5px solid rgba(23,23,25,.22); background: #fff;
  color: #17171a; font: 700 12px Manrope, sans-serif;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.jeu-visuel-btn:hover { border-color: #e63946; color: #c0392f; background: rgba(230,57,70,.05); }
.jeu-visuel-btn.est-danger:hover { border-color: #c0392f; background: rgba(192,57,47,.09); color: #a01f28; }
/* Le champ de fichier est masqué : c'est le label qui ouvre le sélecteur du système. */
.jeu-visuel-btn input[type="file"] { display: none; }
.jeu-visuel-btn:focus-within { outline: 2px solid #e63946; outline-offset: 2px; }
.jeu-visuel-note { color: #383633; font: 500 10.5px Manrope, sans-serif; line-height: 1.4; }

/* ── LE MOYEU EN IMAGE ───────────────────────────────────────────────────────
   Une image téléversée remplace le monogramme. Elle tourne avec la roue, comme le
   monogramme, et reste centrée. */
.wheel-hub.a-image { padding: 0; overflow: hidden; }
.wheel-hub-img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }

/* ── LA PETITE AIDE SOUS UN CHAMP ────────────────────────────────────────────
   Discrète, mais franchement lisible : elle explique le choix texte/image. */
.jeu-aide-mini { color: #383633; font: 500 10.5px Manrope, sans-serif; line-height: 1.45; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE PREVIEW À CÔTÉ — ON RÈGLE ET ON VOIT, SANS SCROLLER
   Client : « Je dois en même temps voir le preview à côté de moi et faire des
   réglages. Je ne dois pas scroller en bas pour voir et remonter pour faire des
   réglages. »
   L'atelier devient DEUX COLONNES : les réglages à gauche (ils défilent), la roue à
   droite (elle reste en face, collée en haut de sa colonne). Plus aucun aller-retour.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-atelier {
  display: grid;
  grid-template-columns: minmax(430px, 1fr) minmax(360px, 430px);
  gap: 18px; align-items: start;
}
/* La colonne des réglages : c'est ELLE qui défile, jamais la page. */
.jeu-col-reglages { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
/* La colonne du preview : collée en haut, elle ne quitte pas l'écran. */
.jeu-col-preview {
  position: sticky; top: 12px;
  display: flex; flex-direction: column; gap: 12px;
  padding: 18px 16px 16px; border-radius: 18px;
  border: 1px solid rgba(23,23,25,.10);
  background: linear-gradient(180deg, #fbfaf8 0%, #f4f2ef 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,.9) inset, 0 14px 34px -26px rgba(23,23,25,.4);
}
.jeu-col-preview .jeu-apercu-zone {
  min-height: 0; padding: 12px 8px 20px; border: 0; background: none;
}
.jeu-col-preview .wheel-frame,
.jeu-col-preview .wheel-rotor { width: 330px !important; height: 330px !important; }
.jeu-col-preview .wheel-stand { font-size: 12px; }
/* Le titre de la colonne : discret, il nomme ce qu'on regarde. */
.jeu-col-titre {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.jeu-col-titre b { color: #0d0d0f; font: 700 13px Manrope, sans-serif; }
.jeu-col-titre small { color: #383633; font: 600 11px Manrope, sans-serif; }

/* Le résultat du tirage, sous la roue : pleine largeur de la colonne. */
.jeu-col-preview .jeu-resultat { margin-top: 4px; }
.jeu-col-preview .jeu-titre-affiche { margin-top: 6px; font-size: 19px; }

/* ── L'EN-TÊTE DE COMPATIBILITÉ ───────────────────────────────────────────────
   Sans navigateur « grid », les deux colonnes s'empilent : on retombe sur la
   disposition d'avant, sans rien casser. */
@media (max-width: 1080px) {
  .jeu-atelier { grid-template-columns: 1fr; }
  .jeu-col-preview { position: static; }
  .jeu-col-preview .wheel-frame,
  .jeu-col-preview .wheel-rotor { width: 300px !important; height: 300px !important; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   L'ANNEAU DES AMPOULES COINCIDE AVEC LE DISQUE
   MESURÉ : `.wheel-studs` en `inset: 0` prenait TOUTE la hauteur de l'enveloppe —
   le support compris (330x385 au lieu de 330x330). Son centre tombait 27,5px sous
   celui du disque, et les ampoules ne tenaient sur le cercle qu'en haut : à 180°,
   elles pendaient 55px SOUS le disque. « Quand on les diminue et qu'on les rétrécit,
   ils ne sont plus sur le cercle. »
   L'anneau devient CARRÉ (aspect-ratio) et posé EN HAUT : il coïncide alors
   exactement avec le disque, quelle que soit la taille de la roue.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-frame .wheel-studs {
  position: absolute; top: 0; left: 0; right: 0; height: auto;
  aspect-ratio: 1 / 1; border-radius: 50%; z-index: 7; pointer-events: none;
}
/* La TAILLE RÉGLABLE PRIME, dans l'aperçu comme dans les vignettes. Sans cette
   ligne, `.jeu-apercu-zone .wheel-studs` réimposait 19px et le curseur semblait mort. */
.wheel-frame .wheel-studs, .jeu-apercu-zone .wheel-studs,
.jeu-gabarit-visuel .wheel-studs, .wheel-small .wheel-studs {
  --stud-size: var(--ampoule-taille, 8px);
}
/* Le gabarit « anneau » portait une taille ÉCRITE EN DUR qui ignorait le réglage. */
.roue-anneau .wheel-studs i::before { width: var(--stud-size); height: var(--stud-size); }

/* ── LE DISQUE SE POSE SUR LE SUPPORT ────────────────────────────────────────
   MESURE : avec `margin-top: -10px`, le support remontait de 10px DANS le disque
   (support à y=548 pour un bas de disque à 558). Il se pose maintenant EXACTEMENT
   sous le disque : la roue repose dessus, elle ne flotte pas. */
.wheel-frame .wheel-stand { margin-top: 2px; }


/* ══ QUI PORTE LA ROUE — les visages dans la carte de l'atelier ════════════════
   Client : « Si on choisit une des roues qu'on a créées [...] et là il y a
   quelqu'un. » Un compteur ne dit pas qui ; ces visages le disent. */
.jeu-roue-qui { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.jeu-roue-avs { display: flex; align-items: center; flex: none; }
.jeu-roue-avs img, .jeu-roue-avs b {
  width: 24px; height: 24px; border-radius: 50%; object-fit: cover;
  object-position: center 18%; border: 1.5px solid #fff;
  box-shadow: 0 0 0 1px rgba(23,23,25,.14); margin-left: -8px; display: block;
}
.jeu-roue-avs img:first-child { margin-left: 0; }
.jeu-roue-avs b {
  display: grid; place-items: center; background: #f1efec; color: #4a4741;
  font: 700 9.5px Manrope, sans-serif; letter-spacing: 0;
}
.jeu-roue-noms { color: #4a4741; font: 600 11.5px Manrope, sans-serif; line-height: 1.3; }
/* La photo de profil est cadrée sur le visage, jamais sur le nombril. */
.jeu-roue-avs img { object-position: center 18%; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE CADRE CONTIENT LE DISQUE *ET* SON SUPPORT
   MESURÉ : `.jeu-col-preview .wheel-frame { height:330px }` — la hauteur du disque
   SEUL — alors que son contenu, le support, monte à 385px. Les 55px de débordement
   passaient SOUS le cadre, et le bouton « Faire tourner » recouvrait le socle.
   Vision (avant la mesure) : « il n'y a pas de socle élargi posé en bas ; le montant
   s'arrête brusquement et disparaît derrière le bouton rouge ». Une roue coupée sous
   le montant donne l'impression de FLOTTER.
   La hauteur du cadre inclut donc le support ; le disque reste en HAUT, le support
   occupe la place en dessous, dans le flux.
   ⚠️ On vise les MÊMES sélecteurs que la règle à battre (`!important` des deux côtés,
   et 2 classes contre 2 classes) : c'est la POSITION en fin de fichier qui tranche.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-col-preview .wheel-frame,
.jeu-apercu-zone .wheel-frame,
.admin-dialog .wheel-frame,
.profile-sheet .wheel-frame {
  height: auto !important;         /* le support tient dans le cadre */
  place-items: start center;       /* le disque en haut, le support en dessous */
}
/* L'enveloppe empile le disque puis le support, dans le flux. */
.wheel-frame .wheel-shell { display: grid; place-items: start center; }


/* ══════════════════════════════════════════════════════════════════════════════
   MIEUX ORGANISER LES GRILLES DE L'ATELIER
   MESURÉ : trois règles ramenaient TOUJOURS les familles à 2 colonnes
   (`auto-fit minmax(200px)` → `auto-fit minmax(170px)` → `1fr 1fr`), sur une colonne
   de 809px. Chaque curseur occupait donc 400px pour 150px utiles, et la colonne
   s'étirait sur 2 994px — d'où l'impression que « ce n'est pas bien organisé ».
   On rend à chaque famille sa largeur naturelle. Le nombre de colonnes suit la place
   RÉELLE (`auto-fit`) : pas de trou, pas de débordement, quelle que soit la largeur.
   ══════════════════════════════════════════════════════════════════════════════ */
/* Quatre couleurs : une ligne. Chaque échantillon fait 202px — largement de quoi
   porter son nom et sa valeur. */
.jeu-couleurs { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) !important; }
/* Les curseurs : trois par ligne. Un curseur a besoin de ~250px pour que sa glissière
   reste maniable (en dessous, on règle mal au doigt comme à la souris). */
.jeu-curseurs { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) !important; }
/* Les options (forme, position) : quatre par ligne — elles sont courtes. */
.jeu-options { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)) !important; }
/* La couleur de l'aiguille : elle partageait une grille prévue pour 3 curseurs en
   `1fr 1fr` ; on lui rend sa propre largeur. */
.jeu-aig-tete { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) !important; }

/* ── LES REPLIS, PAR ORDRE DE LARGEUR ────────────────────────────────────────
   On ne force jamais plus de colonnes que la place ne le permet. `auto-fit` s'en
   charge seul : ces règles ne servent qu'à garantir un minimum confortable. */
@media (max-width: 700px) {
  .jeu-couleurs, .jeu-curseurs, .jeu-aig-tete, .jeu-options {
    grid-template-columns: 1fr 1fr !important;
  }
}
@media (max-width: 460px) {
  .jeu-couleurs, .jeu-curseurs, .jeu-aig-tete, .jeu-options {
    grid-template-columns: 1fr !important;
  }
}


/* ══════════════════════════════════════════════════════════════════════════════
   FINIR L'ORGANISATION DE L'ATELIER
   Trois retouches, chacune mesurée avant d'être faite.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA HIÉRARCHIE DES TITRES ──────────────────────────────────────────────
   MESURÉ : le titre de section fait 16px / gras 600, et le sous-titre qui SUIT
   (« FORME », « POSITION ») 11.5px. L'écart est trop faible pour dire « je suis une
   subdivision ». On ouvre l'écart par le HAUT du sous-titre et on resserre sa couleur,
   pour que l'œil descende : section rouge -> titre noir -> sous-titre gris. */
.jeu-col-reglages .section-head { margin-bottom: 14px; }
.jeu-col-reglages .section-head h2 { font-size: 17px; letter-spacing: -.01em; }
.jeu-col-reglages .section-caption { font-size: 11.5px; line-height: 1.5; }
.jeu-col-reglages .jeu-sous-titre {
  margin: 20px 0 9px; padding-top: 14px;
  /* Un filet très léger sépare la subdivision de ce qui précède : on voit où commence
     chaque groupe sans ajouter de cadre. */
  border-top: 1px solid rgba(23, 23, 25, .07);
  color: #3a3733;
}
/* Le PREMIER sous-titre suit le head : pas de filet, il n'a rien à séparer. */
.jeu-col-reglages .section-head + .jeu-sous-titre {
  margin-top: 6px; padding-top: 0; border-top: 0; color: #383633;
}

/* ── 2. LE SÉLECTEUR DE COULEUR ───────────────────────────────────────────────
   MESURÉ : 44x32. Une cible de 32px de haut se rate au doigt comme à la souris.
   On lui donne 40px, et un anneau au survol pour montrer qu'il s'ouvre. */
.jeu-form .field input[type=color] { height: 40px; border-radius: 10px; }
.jeu-form .field input[type=color]:hover { border-color: #e63946; }

/* ── 3. LA FENÊTRE « NOUVELLE ROUE » ──────────────────────────────────────────
   Elle remplace le `prompt()` du navigateur : mêmes espacements que les autres
   fiches, et les 8 gabarits choisis dès la création. */
.jeu-nr-gabs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px;
}
.jeu-nr-gab {
  display: grid; gap: 2px; cursor: pointer; padding: 10px 12px; border-radius: 11px;
  border: 1.5px solid rgba(23,23,25,.14); background: #fff;
  transition: border-color .15s ease, background .15s ease;
}
.jeu-nr-gab:hover { border-color: #e63946; background: rgba(230,57,70,.05); }
.jeu-nr-gab b { color: #0d0d0f; font: 700 12.5px Manrope, sans-serif; }
.jeu-nr-gab small { color: #383633; font: 500 10.5px Manrope, sans-serif; }
/* Le radio est masqué : c'est le LABEL qui porte le geste, et le focus reste visible. */
.jeu-nr-gab input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.jeu-nr-gab:has(input:checked) {
  border-color: #e63946; background: rgba(230,57,70,.09);
  box-shadow: 0 0 0 3px rgba(230,57,70,.10);
}
.jeu-nr-gab:focus-within { outline: 2px solid #e63946; outline-offset: 2px; }


/* ══════════════════════════════════════════════════════════════════════════════
   DEUX LARGEURS DE CARTE DANS LA MÊME SECTION — `auto-fit` CONTRE `auto-fill`
   MESURÉ : la liste des responsables sortait en 3 colonnes de 369px et celle des
   collaboratrices en 4 colonnes de 275px. Deux largeurs de carte côte à côte, dans le
   même bloc : c'est un vrai déséquilibre.
   CAUSE : `auto-fit` COLLAPSE les pistes vides et redistribue leur place. Avec 3 cartes
   sur 4 pistes possibles, les 3 survivantes se partagent toute la largeur.
   `auto-fill` CONSERVE les pistes : même nombre de colonnes des deux côtés, donc des
   cartes de même largeur. Le nombre de colonnes suit toujours la place disponible.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-pers-liste {
  grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
  align-content: start;   /* les rangées ne s'étirent pas sur la hauteur */
}

/* ── LA HAUTEUR DES CARTES RESTE UNIFORME ─────────────────────────────────────
   MESURÉ : 15 cartes à 68px, écart 0 — l'alignement tient. On le VERROUILLE pour
   qu'une future retouche de contenu ne le casse pas, et on borne le statut à deux
   lignes : un texte de statut très long ne doit pas déformer sa carte. */
.jeu-pers { align-items: center; }
.jeu-pers-txt small {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}


/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON DE SUPPRESSION AU SURVOL D'UNE CARTE DE ROUE
   Client : « un bouton de supprimer si on survole des widgets des roues dans l'atelier ».
   Il n'apparaît QU'AU SURVOL — l'atelier reste calme — mais un clavier l'atteint aussi
   (`:focus-within`), sinon la suppression serait souris-seulement.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-roue-env { position: relative; }
.jeu-roue-suppr {
  position: absolute; top: 7px; right: 7px; z-index: 2;
  display: grid; place-items: center; cursor: pointer;
  width: 26px; height: 26px; padding: 0; border-radius: 8px;
  border: 1.5px solid rgba(23,23,25,.16); background: #fff; color: #383633;
  /* Caché par défaut : il ne doit pas encombrer une carte qu'on lit. */
  opacity: 0; visibility: hidden;
  transition: opacity .14s ease, visibility .14s ease,
              border-color .14s ease, background .14s ease, color .14s ease;
}
.jeu-roue-suppr svg { width: 13px; height: 13px; }
.jeu-roue-env:hover .jeu-roue-suppr,
.jeu-roue-suppr:focus-visible { opacity: 1; visibility: visible; }
/* Au survol du bouton lui-même, la confirmation est visuelle : rouge franc. */
.jeu-roue-suppr:hover {
  border-color: #c0392f; background: rgba(192,57,47,.10); color: #a01f28;
}
.jeu-roue-suppr:focus-visible { outline: 2px solid #e63946; outline-offset: 2px; }
/* Hors survol, la carte ne doit pas laisser de place vide en haut à droite. */
.jeu-roue-env .jeu-roue-carte { width: 100%; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES CINQ RÉGLAGES DE LA ROUE
   Client : « changer la couleur du contouring et du support métallique », « désactiver
   ou activer les noms des cadeaux », « jouer sur la taille du triangle »,
   « les couleurs ne doivent pas être décalées par rapport au contouring ».
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── A. LA MATIÈRE : LE CERCLAGE LIT UNE VARIABLE ─────────────────────────────
   Le cerclage portait `#191a1b` EN DUR, matière par matière. Il lit maintenant
   `--contour-couleur`, posée par la charte. Sans réglage, la variable est absente et
   `var(--contour-couleur, #191a1b)` rend EXACTEMENT la couleur d'avant : aucune
   régression sur les roues existantes.
   ⚠️ Les matières qui ont leur propre cerclage (néon, anneau, prisme, monolithe,
   minéral, ruban) doivent AUSSI lire la variable, sinon le réglage n'agirait que sur
   les gabarits par défaut. */
.wheel-large .wheel-rotor, .wheel-small .wheel-rotor,
.roue-classique .wheel-rotor, .roue-neon .wheel-rotor, .roue-anneau .wheel-rotor,
.roue-rayures .wheel-rotor, .roue-prisme .wheel-rotor, .roue-monolithe .wheel-rotor,
.roue-mineral .wheel-rotor, .roue-ruban .wheel-rotor {
  border-color: var(--contour-couleur, #191a1b) !important;
}

/* ── B. LE SUPPORT MÉTALLIQUE ─────────────────────────────────────────────────
   `.wheel-stand-post` et `.wheel-stand-foot` lisaient déjà `--stand-color` avec
   `#202123` en défaut : la charte n'avait qu'à la poser. C'est fait côté JS. */

/* ── C. LA TAILLE DE L'AIGUILLE ───────────────────────────────────────────────
   `--pointer-scale` vaut 1 par défaut. On l'applique en `transform` pour que TOUTES
   les formes d'aiguille (triangle, flèche, goutte, fine) grandissent ensemble, sans
   toucher à leur géométrie — et sans déplacer le point de contact, qui reste sur le
   bord du disque. */
.wheel-pointer {
  transform-origin: 50% 0%;
  transform: scale(var(--pointer-scale, 1));
}

/* ── D. LE NOM DU CADEAU, MASQUÉ ──────────────────────────────────────────────
   Le nom est retiré du HTML quand on le masque (voir `roueHtml`), donc aucune règle
   n'est nécessaire pour le cacher. EN REVANCHE, le visuel doit alors se RECENTRER :
   sans le nom, l'image flotterait en haut de sa case. */
.wheel-prize:not(:has(small)) img { margin-top: 0; }

/* ── E. L'INTERRUPTEUR « CALCULER SELON LE STOCK » ────────────────────────────
   Une bascule avec sa pastille de tête, et le total à droite : on voit d'un coup si
   la répartition porte bien sur 100 %. */
.jeu-chances-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 4px 0 6px;
}
.jeu-bascule-auto {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  padding: 7px 13px; border-radius: 10px;
  border: 1.5px solid rgba(23,23,25,.16); background: #fff;
  color: #17171a; font: 700 12px Manrope, sans-serif;
  transition: border-color .15s ease, background .15s ease;
}
.jeu-bascule-auto:hover { border-color: #e63946; background: rgba(230,57,70,.05); }
.jeu-bascule-auto input[type="checkbox"] { accent-color: #e63946; cursor: pointer; }
/* Coché = le mode automatique est actif : la bascule le dit franchement. */
.jeu-bascule-auto:has(input:checked) {
  border-color: #e63946; background: rgba(230,57,70,.08); color: #a01f28;
}
.jeu-bascule-auto:focus-within { outline: 2px solid #e63946; outline-offset: 2px; }
.jeu-total {
  color: #383633; font: 600 11px 'DM Mono', monospace; letter-spacing: .04em;
}

/* ── F. LA BARRE DE CHANCE, ET LE POURCENTAGE ─────────────────────────────────
   La barre rend la RÉPARTITION lisible d'un coup d'œil : c'est ce qu'un chiffre seul
   ne fait pas. Elle se pose entre le nom et le pourcentage. */
.jeu-poids-ligne {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) minmax(90px, 1.6fr) auto;
  gap: 12px; align-items: center;
}
/* Quand le curseur manuel est là, il prend une quatrième colonne pleine largeur. */
.jeu-poids-ligne:has(.jeu-chance-range) {
  grid-template-columns: minmax(140px, 1fr) minmax(90px, 1.4fr) auto;
}
.jeu-chance-range { grid-column: 1 / -1; margin-top: 2px; }
.jeu-chance-barre {
  display: block; height: 8px; border-radius: 5px; overflow: hidden;
  background: rgba(23,23,25,.08);
}
.jeu-chance-barre i {
  display: block; height: 100%; border-radius: 5px;
  background: linear-gradient(90deg, #e63946, #d4a574);
  /* On ANIME la largeur : quand on bouge une chance, on VOIT les autres céder. */
  transition: width .22s cubic-bezier(.2, .8, .2, 1);
}
.jeu-chance-pc {
  min-width: 54px; text-align: right;
  color: #0d0d0f; font: 700 13px 'DM Mono', monospace;
}
@media (prefers-reduced-motion: reduce) {
  .jeu-chance-barre i { transition: none; }
}

/* ── G. LA POSITION FIGÉE DE L'AIGUILLE ───────────────────────────────────────
   Une note, pas un réglage : elle explique POURQUOI il n'y a plus de choix. */
.jeu-position-fixe {
  margin: 8px 0 4px; padding: 11px 14px; border-radius: 11px;
  border: 1px solid rgba(23,23,25,.10); background: rgba(23,23,25,.03);
  color: #4a4741; font: 500 12px Manrope, sans-serif; line-height: 1.5;
}
.jeu-position-fixe b { color: #0d0d0f; font-weight: 700; }


/* ══════════════════════════════════════════════════════════════════════════════
   POSITION DU FOND — LA VIGNETTE CLIQUABLE
   Client : « agrandir ou rétrécir et positionner l'image comme je veux sur
   l'arrière-plan [...] j'ai le droit de zoomer ou dézoomer ».
   Un clic DANS la vignette place le point d'intérêt : on voit ce qu'on cadre.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-fond-cadre-zone {
  display: grid; grid-template-columns: minmax(200px, 340px) 1fr;
  gap: 14px; align-items: start;
}
@media (max-width: 560px) { .jeu-fond-cadre-zone { grid-template-columns: 1fr; } }

/* La vignette : un vrai bouton, donc atteignable au clavier (`Entrée`). */
.jeu-fond-cadre {
  position: relative; display: block; width: 100%; aspect-ratio: 16 / 10;
  padding: 0; cursor: crosshair; overflow: hidden;
  border-radius: 14px; border: 1.5px solid rgba(23,23,25,.16); background: #f4f2ef;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.jeu-fond-cadre:hover { border-color: #e63946; box-shadow: 0 0 0 3px rgba(230,57,70,.08); }
.jeu-fond-cadre:focus-visible { outline: 2px solid #e63946; outline-offset: 2px; }

/* L'image du fond, telle qu'elle sera derrière la roue. */
.jeu-fond-apercu {
  position: absolute; inset: 0; display: block;
  background-repeat: no-repeat;
  /* Le zoom est appliqué par `transform` sur l'aperçu (voir le style en ligne). */
  transition: transform .18s ease;
}
/* Le voile du bouton : il rend le marqueur lisible sur toute image. */
.jeu-fond-cadre::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(0,0,0,.06), rgba(0,0,0,.16));
}

/* Le MARQUEUR du point d'intérêt : un réticule, pas un rond plein — il masque donc
   le moins possible ce qu'on est en train de cadrer. */
.jeu-fond-marque {
  position: absolute; z-index: 2; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%; pointer-events: none;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1.5px rgba(230,57,70,.9), 0 2px 6px rgba(0,0,0,.35);
  background: radial-gradient(circle, rgba(230,57,70,.55) 0 38%, rgba(230,57,70,0) 40%);
  transition: left .12s ease, top .12s ease;
}
/* Une croix au centre du réticule : on lit le point exact même sur une petite vignette. */
.jeu-fond-marque::before, .jeu-fond-marque::after {
  content: ''; position: absolute; background: rgba(255,255,255,.95);
  box-shadow: 0 0 2px rgba(0,0,0,.5);
}
.jeu-fond-marque::before { left: 50%; top: 4px; bottom: 4px; width: 1.5px; margin-left: -.75px; }
.jeu-fond-marque::after { top: 50%; left: 4px; right: 4px; height: 1.5px; margin-top: -.75px; }

/* Le côté : la valeur, le bouton, l'aide. */
.jeu-fond-cadre-cote { display: grid; gap: 9px; align-content: start; justify-items: start; }
.jeu-fond-pos {
  padding: 5px 10px; border-radius: 8px; background: rgba(23,23,25,.05);
  color: #17171a; font: 700 12px 'DM Mono', monospace; letter-spacing: .04em;
}
@media (prefers-reduced-motion: reduce) {
  .jeu-fond-apercu, .jeu-fond-marque { transition: none; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   LE CERCLE INTERNE RETIRÉ
   Client : « le petit cercle qui est un peu le petit cercle qui est en dessous du grand
   contour, ça sert à rien aussi ». C'était un filet blanc à 9 % de l'intérieur, qui ne
   délimitait rien et alourdissait le disque. Retiré.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-rotor:before { content: none !important; border: 0 !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   L'AIGUILLE — PROPORTIONNÉE, ET ENFIN CENTRÉE
   Client : « l'aiguille elle est mal positionnée et elle est trop grande, elle doit
   être plus ou moins proportionnée par rapport à notre roue ».
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LE CENTRAGE ÉTAIT PERDU ───────────────────────────────────────────────
   MESURÉ : `transform: scale(var(--pointer-scale, 1))` REMPLAÇAIT le
   `translateX(-50%)` de la règle d'origine — l'aiguille se retrouvait décalée de la
   moitié de sa largeur. Le `matrix(1.7, 0, 0, 1.7, 0, 0)` le disait : aucun translate.
   La correction porte les DEUX transformations, dans cet ordre. */
.wheel-frame.aig-haut .wheel-pointer {
  /* La pointe se pose SUR le bord : 8px à l'intérieur, donc elle désigne la part sans
     la recouvrir. Avant, `top: -7px` laissait la pointe 31px DANS le disque. */
  top: calc(8px - var(--needle-h));
  transform: translateX(-50%) scale(var(--pointer-scale, 1));
}

/* ── 2. LES DIMENSIONS, EN PROPORTION DE LA ROUE ──────────────────────────────
   `--roue-diam` est la largeur réellement affichée du disque (posée par le JS).
   Les rapports viennent du benchmark d'une vraie roue de loterie : un pointeur de
   ~70mm sur un disque de 610mm fait ~11,5 % du diamètre en longueur et ~5 % en
   largeur. On prend un peu plus court (9 %) : le client le trouvait trop grand. */
.aig-forme-triangle .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .042);
  --needle-h: calc(var(--roue-diam, 330px) * .090);
  border-left-width: var(--needle-w); border-right-width: var(--needle-w);
  border-top-width: var(--needle-h);
}
.aig-forme-fleche .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .026);
  --needle-h: calc(var(--roue-diam, 330px) * .115);
  border-left-width: var(--needle-w); border-right-width: var(--needle-w);
  border-top-width: var(--needle-h);
}
.aig-forme-goutte .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .036);
  --needle-h: calc(var(--roue-diam, 330px) * .072);
  border-left-width: var(--needle-w); border-right-width: var(--needle-w);
  border-top-width: var(--needle-h);
  border-top-left-radius: 40% 26%; border-top-right-radius: 40% 26%;
  border-bottom-left-radius: 3px; border-bottom-right-radius: 3px;
}
.aig-forme-fine .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .011);
  --needle-h: calc(var(--roue-diam, 330px) * .100);
  width: var(--needle-w); height: var(--needle-h);
  border: 0; border-radius: 3px; background: var(--pointer-color);
}

/* ── 3. LE PIVOT DE L'ÉCHELLE ─────────────────────────────────────────────────
   `transform-origin: 50% 0%` = le HAUT de l'aiguille. Elle grandit donc vers le BAS,
   en gardant sa pointe sur le bord : le point désigné ne bouge jamais quand on change
   la taille. C'est ce qui rend le réglage utilisable. */
.wheel-pointer { transform-origin: 50% 0%; }

/* Le socle de l'aiguille (le petit rond sombre) n'a plus lieu d'être : il alourdit
   une aiguille devenue fine et n'apporte rien à la lecture. */
.wheel-pointer:after { content: none !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   AUCUNE HACHURE DANS LE VERNIS
   Le client a été clair : « pas beaucoup de tirées », puis « pas des rayures qui
   dérangent dans l'œil ». On supprime tout motif répété de pas court dans le vernis.
   ══════════════════════════════════════════════════════════════════════════════ */
.roue-rayures .wheel-glaze,
.roue-monolithe .wheel-glaze,
.roue-mineral .wheel-glaze,
.roue-ruban .wheel-glaze {
  background-image: linear-gradient(160deg, rgba(255,255,255,.10) 0%, rgba(255,255,255,0) 46%,
    rgba(0,0,0,.06) 100%) !important;
}


/* ══════════════════════════════════════════════════════════════════════════════
   AUCUN MOTIF RÉPÉTÉ DANS LA ROUE — GARDE-FOU
   Client, deux fois : « pas beaucoup de tirées », « pas vraiment des rayures comme ça
   qui vont déranger dans l'œil ». Ce garde-fou couvre TOUTES les couches de la roue :
   si une règle réintroduit un jour un `repeating-*`, elle ne s'appliquera pas.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-glaze, .wheel-shell, .wheel-decor, .wheel-hub {
  background-image: none;
}
/* Le VERNIS : une seule lumière large, jamais un motif. */
.wheel-glaze {
  background-image: linear-gradient(158deg, rgba(255,255,255,.16) 0%,
    rgba(255,255,255,.03) 38%, rgba(255,255,255,0) 62%, rgba(0,0,0,.05) 100%) !important;
  background-repeat: no-repeat !important;
}
/* Les SÉPARATEURS : un trait net par frontière, pas une trame. */
.wheel-separators i::before, .wheel-separators i::after { background-repeat: no-repeat; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES OMBRES DE LA ROUE RETIRÉES
   Client : « Enlève ces shadows, je les aime pas trop. »
   MESURÉ : 19 ombres portaient sur la roue. Celles-ci sont les OMBRES PORTÉES — elles
   décollaient le disque et changeaient de dessin d'un gabarit à l'autre. On les
   remplace par ce qui reste VRAI quand on enlève une ombre : le cerne du contour.
   ⚠️ On NE touche PAS au halo des ampoules : c'est ce qui les fait lire « allumées »,
   et ce n'est pas une ombre.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LE DISQUE : plus de portée, le contour reste net ────────────────────────
   On conserve `0 0 0 1px` en cerne (c'est ce qui dessine le bord du disque), et on
   supprime tout décalage vertical. */
.wheel-rotor,
.wheel-large .wheel-rotor,
.wheel-small .wheel-rotor {
  box-shadow: 0 0 0 1px rgba(23,23,25,.28) !important;
}

/* ── CHAQUE GABARIT : même traitement ────────────────────────────────────────
   Avant, chaque matière avait SA portée (0 20px 46px ici, 0 18px 42px là) : deux
   roues de deux gabarits ne se posaient donc pas de la même façon. Uniformisées. */
.roue-neon .wheel-rotor,
.roue-anneau .wheel-rotor,
.roue-prisme .wheel-rotor,
.roue-monolithe .wheel-rotor,
.roue-mineral .wheel-rotor,
.roue-ruban .wheel-rotor,
.roue-classique .wheel-rotor,
.roue-rayures .wheel-rotor,
.wheel-frame:has(.wheel-studs) .wheel-rotor {
  box-shadow: 0 0 0 1px rgba(23,23,25,.28) !important;
}
/* Le néon gardait un halo rose : retiré aussi — c'était une lueur, pas une lumière
   du contour, donc il comptait comme une « shadow » pour le client. */
.roue-neon .wheel-rotor { box-shadow: 0 0 0 1px rgba(23,23,25,.28) !important; }

/* ── LE MOYEU : il n'a plus d'ombre portée ───────────────────────────────────
   Il gardait `0 4px 18px` : le monogramme paraissait décollé du disque. */
.wheel-hub { box-shadow: none !important; }

/* ── LE SUPPORT : plus de creux, plus d'ombre au sol ─────────────────────────
   L'axe et le montant avaient un `inset` sombre (un creux), et le socle une portée au
   sol. Le support garde ses dégradés — c'est eux qui donnent le métal — mais il ne
   creuse plus et ne projette plus rien. */
.wheel-stand-axis,
.wheel-stand-post,
.wheel-stand-foot {
  box-shadow: none !important;
}


/* ══════════════════════════════════════════════════════════════════════════════
   L'APERÇU PREND LA FORME D'UNE TABLETTE
   Client : « la forme du preview doit tenir la taille d'une tablette pas en carré ».
   MESURÉ : le panneau était une colonne de 430px × 694px — EN PORTRAIT, donc l'inverse
   d'une tablette tenue en main. Il prend le format 16:10 (1920×1200), celui des
   tablettes 10 pouces du marché, et son contenu passe en deux colonnes : c'est la
   disposition réelle de l'écran de jeu.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA GRILLE : la colonne de droite s'élargit ───────────────────────────────
   Un cadre 16:10 dans 430px ne ferait que 269px de haut : la roue et son support
   n'y ten登raient pas. On lui donne 560px, ce qui fait 350px de haut. */
.jeu-atelier {
  grid-template-columns: minmax(430px, 1fr) minmax(470px, 560px) !important;
}

/* ── LE PANNEAU : format tablette, et deux colonnes à l'intérieur ─────────────
   `aspect-ratio: 16/10` + `height: auto` : le panneau prend TOUTE la largeur de sa
   colonne et sa hauteur en découle. C'est ce qui garantit la forme, quelle que soit
   la largeur disponible — aucun pixel fixe. */
.jeu-col-preview {
  position: sticky; top: 12px;
  aspect-ratio: 16 / 10 !important;
  height: auto !important;
  display: grid !important;
  /* La roue à gauche (elle prend la place), les commandes à droite. */
  grid-template-columns: minmax(0, 1fr) minmax(150px, 176px);
  grid-template-rows: auto 1fr;
  gap: 6px 12px;
  align-content: stretch;
  padding: 14px 16px 16px !important;
  /* Un fond d'ÉCRAN : le cadre figure la tablette, donc il a sa propre teinte. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.55), rgba(255,255,255,.10)),
    linear-gradient(160deg, #f6f4f1 0%, #eceae6 100%) !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9) !important;
}
/* Le titre occupe la largeur : c'est l'en-tête de l'écran. */
.jeu-col-preview .jeu-col-titre { grid-column: 1 / -1; }

/* ── LA ROUE : à gauche, elle remplit la hauteur disponible ───────────────────
   On lui donne une taille en `min()` : elle prend la place, mais ne dépasse jamais la
   hauteur du cadre. Le support est compris. */
.jeu-col-preview .jeu-apercu-zone {
  grid-column: 1; grid-row: 2;
  display: grid; place-items: center;
  min-height: 0; min-width: 0;
  padding: 0 !important;
  overflow: visible;
}
.jeu-col-preview .wheel-frame,
.jeu-col-preview .wheel-rotor {
  /* 78 % de la hauteur du cadre pour le disque : le reste est au support et au titre. */
  width: min(100%, calc((100vh - 260px) * 0.78)) !important;
  height: auto !important;
  max-width: 300px;
  aspect-ratio: 1 / 1;
}
.jeu-col-preview .wheel-stand { font-size: 11px; }

/* ── LES COMMANDES : à droite, empilées ───────────────────────────────────────
   Le bouton, le résultat, l'origine et le nom de la roue. C'est le panneau latéral
   de l'écran de jeu. */
.jeu-col-preview .jeu-actions-apercu,
.jeu-col-preview .jeu-resultat,
.jeu-col-preview .jeu-apercu-origine,
.jeu-col-preview .jeu-col-nom {
  grid-column: 2;
}
.jeu-col-preview .jeu-actions-apercu { grid-row: 2; align-self: start; }
.jeu-col-preview .jeu-actions-apercu .button { width: 100%; margin: 0; }
.jeu-col-preview .jeu-resultat {
  align-self: start; margin-top: 46px; padding: 11px 12px;
  font-size: 12px;
}
.jeu-col-preview .jeu-apercu-origine { font-size: 10.5px; line-height: 1.45; }
.jeu-col-preview .jeu-col-nom { font-size: 11px; }

/* ── LES REPLIS, PAR ORDRE DE LARGEUR ─────────────────────────────────────────
   Sous 1080px, deux colonnes ne tiennent plus : on revient à l'empilement, et le
   cadre garde sa forme tablette (il s'élargit, donc il s'aplatit un peu plus). */
@media (max-width: 1080px) {
  .jeu-atelier { grid-template-columns: minmax(0, 1fr) !important; }
  .jeu-col-preview {
    position: static !important;
    grid-template-columns: minmax(0, 1fr) minmax(140px, 170px);
  }
}
@media (max-width: 700px) {
  .jeu-col-preview {
    aspect-ratio: auto !important;
    grid-template-columns: minmax(0, 1fr);
  }
  .jeu-col-preview .jeu-apercu-zone { grid-column: 1; }
  .jeu-col-preview .jeu-actions-apercu,
  .jeu-col-preview .jeu-resultat,
  .jeu-col-preview .jeu-apercu-origine,
  .jeu-col-preview .jeu-col-nom { grid-column: 1; grid-row: auto; }
  .jeu-col-preview .jeu-resultat { margin-top: 8px; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE REMPLIT LE CADRE TABLETTE
   MESURÉ : le disque sortait à 121x121 (22 % de la largeur du cadre) parce que sa
   taille venait de `100vh` — la hauteur de la FENÊTRE — au lieu de la hauteur du
   CADRE. Sur une fenêtre courte, la roue s'écrasait.
   On dimensionne donc depuis le cadre lui-même : la zone occupe la rangée `1fr`, le
   cadre de la roue prend 100 % de sa hauteur, et `aspect-ratio` en déduit la largeur.
   Aucun pixel fixe, aucun `vh` : la roue suit la place qu'on lui donne.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA ZONE : elle prend la hauteur de sa rangée ──────────────────────────── */
.jeu-col-preview .jeu-apercu-zone {
  min-height: 0;          /* indispensable : sans ça, la grille étire la zone */
  height: 100%;
  overflow: visible;
}

/* ── LE CADRE DE LA ROUE : hauteur d'abord, largeur déduite ──────────────────
   `aspect-ratio: 1 / 1.16` = le disque (1) + la place du support (0.16). C'est le
   rapport réel du dessin : le support occupe ~16 % sous le disque. */
.jeu-col-preview .wheel-frame {
  height: 100% !important;
  width: auto !important;
  aspect-ratio: 1 / 1.16;
  max-width: 100%;
  max-height: 100%;
}
/* Le disque lui-même reste CARRÉ et prend toute la largeur de son cadre. */
.jeu-col-preview .wheel-rotor {
  width: 100% !important;
  height: auto !important;
  aspect-ratio: 1 / 1;
}
/* Le disque ne doit pas dépasser la hauteur du cadre (le support occupe le reste). */
.jeu-col-preview .wheel-shell { max-height: 86%; }

/* ── LE FOND D'ÉVÉNEMENT EST CLIPPÉ AU CADRE ─────────────────────────────────
   MESURÉ : `.wheel-decor` sortait à 560px pour un panneau finissant à 517px. Sur un
   écran plein cadre ce débordement est voulu (l'ambiance derrière la roue) ; dans un
   cadre en forme de tablette, il casse la forme. On le retient.
   ⚠️ `overflow: hidden` sur l'élément STICKY lui-même est sans danger — ce qui casse
   `sticky`, c'est un `overflow` sur un ANCÊTRE. */
.jeu-col-preview {
  /* overflow: neutralise ici — ⚠️⚠️ IL TUAIT le `position: sticky; bottom` de la replique des
     onglets. Un `overflow` non `visible` sur un ANCETRE fait de lui le CONTENEUR DE DEFILEMENT
     du `sticky` : la replique se retrouve epinglee a une colonne qui ne defile pas, donc
     immobile. MESURE : `position: sticky` et `bottom: 12px` etaient pourtant bien appliques sur
     la replique — la regle etait juste et sans effet. `sticky` ne se juge jamais seul, mais avec
     la chaine de ses ancetres. (Le `overflow` de `.jeu-apercu-zone`, lui, est LEGITIME et reste :
     c'est le cadre de l'appareil, il doit rogner.) */ }

/* Le fond et son voile prennent la taille du CADRE, pas du disque. */
.jeu-col-preview .wheel-decor,
.jeu-col-preview .wheel-decor-voile {
  inset: 0 !important;
  border-radius: 12px !important;
}
/* Et la vignette se cale aussi sur le cadre. */
.jeu-col-preview .wheel-frame.has-vignette::after { inset: 0 !important; border-radius: 12px !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE DOMINANTE — D APRÈS LA RÉFÉRENCE DU CLIENT
   Le client : « voilà comment elle est positionnée sur l'espace de la tablette ».
   La référence montre : la roue DOMINANTE et CENTRÉE, le bouton EN DESSOUS.
   MESURÉ avant : disque 121x121 = 22 % de la largeur — mon découpage en deux colonnes
   l'écrasait. On empile donc trois rangées, la roue au milieu et dominante.
   ══════════════════════════════════════════════════════════════════════════════ */

/* La colonne de droite s'élargit : un cadre 16:10 n'est grand que si sa largeur l'est. */
.jeu-atelier {
  grid-template-columns: minmax(400px, 1fr) minmax(480px, min(44vw, 660px)) !important;
}

/* ── LE PANNEAU : tablette 16:10, et TROIS RANGÉES ───────────────────────────
   titre | roue | commandes. La rangée du milieu prend TOUT le reste (`1fr`), donc
   c'est elle qui donne sa taille à la roue. */
.jeu-col-preview {
  position: sticky; top: 12px;
  aspect-ratio: 16 / 10 !important;
  height: auto !important;
  display: grid !important;
  grid-template-columns: minmax(0, 1fr) !important;   /* une seule colonne */
  /* ══ LA RANGEE DE LA ROUE PREND TOUT ; LA BARRE ET LE PIED SONT PLAFONNES ═══
     MESURE : la roue n'occupait que 62 % de la hauteur du verre. Cause : trois
     rangees `auto`, dont la derniere (le pied) prenait sa hauteur NATURELLE —
     et le cadre de la roue, borne par `max-height: 100%`, se contentait du
     reste. On plafonne la barre et le pied en `cqh` (unites du conteneur), ce
     qui rend la place a la rangee du milieu. */
  grid-template-rows: minmax(0, 7cqh) minmax(0, 1fr) minmax(0, 9cqh);
  gap: 6px;
  justify-items: center;
  align-content: stretch;
  padding: 12px 16px 14px !important;
  /*
  /* overflow: neutralise ici — ⚠️⚠️ IL TUAIT le `position: sticky; bottom` de la replique des
     onglets. Un `overflow` non `visible` sur un ANCETRE fait de lui le CONTENEUR DE DEFILEMENT
     du `sticky` : la replique se retrouve epinglee a une colonne qui ne defile pas, donc
     immobile. MESURE : `position: sticky` et `bottom: 12px` etaient pourtant bien appliques sur
     la replique — la regle etait juste et sans effet. `sticky` ne se juge jamais seul, mais avec
     la chaine de ses ancetres. (Le `overflow` de `.jeu-apercu-zone`, lui, est LEGITIME et reste :
     c'est le cadre de l'appareil, il doit rogner.) */   ⚠️⚠️ NEUTRALISÉ : il TUAIT le `position: sticky; bottom` de la réplique des
     onglets. Un `overflow` non `visible` sur un ANCÊTRE fait de lui le CONTENEUR DE DÉFILEMENT
     du `sticky` : la réplique était donc épinglée à une colonne qui ne défile pas, et restait
     immobile. MESURÉ : `position: sticky` et `bottom: 12px` étaient pourtant bien appliqués — la
     règle était juste et sans effet. `sticky` ne se juge jamais seul, mais avec ses ancêtres.
     ⚠️ IL Y EN AVAIT QUATRE POUR CE MÊME SÉLECTEUR, dont un `!important` : c'est la DERNIÈRE qui
     gagnait, comme toujours. On les neutralise TOUS les quatre au lieu d'en ajouter un cinquième. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.55), rgba(255,255,255,.10)),
    linear-gradient(160deg, #f6f4f1 0%, #eceae6 100%) !important;
}

/* Le titre : en haut, sur toute la largeur. */
.jeu-col-preview .jeu-col-titre {
  grid-row: 1; grid-column: 1;
  width: 100%; justify-self: stretch;
}

/* ── LA ROUE : rangée du milieu, toute la place, centrée ─────────────────────
   `height: 100%` sur le cadre + `aspect-ratio` : la largeur se déduit, la roue
   remplit la hauteur disponible quelle que soit la fenêtre. Aucun pixel fixe. */
.jeu-col-preview .jeu-apercu-zone {
  grid-row: 2; grid-column: 1;
  display: grid; place-items: center;
  width: 100%; height: 100%;
  min-height: 0; min-width: 0;
  padding: 0 !important;
  overflow: visible;
}
.jeu-col-preview .wheel-frame {
  height: 100% !important;
  width: auto !important;
  aspect-ratio: 1 / 1.14;      /* le disque + la place du support */
  max-width: 100%;
  max-height: 100%;
}
.jeu-col-preview .wheel-rotor {
  width: 100% !important;
  height: auto !important;
  aspect-ratio: 1 / 1;
}
.jeu-col-preview .wheel-shell { max-height: 88%; }
.jeu-col-preview .wheel-stand { font-size: 10px; }

/* ── LES COMMANDES : rangée du bas, centrées ─────────────────────────────────
   Le bouton, puis le résultat, puis l'origine — empilés et centrés, comme la
   référence où « SPIN » est seul sous la roue. */
.jeu-col-preview .jeu-actions-apercu {
  grid-row: 3; grid-column: 1;
  display: flex; justify-content: center;
  width: 100%; margin: 0;
}
.jeu-col-preview .jeu-actions-apercu .button { min-width: 190px; margin: 0; }
.jeu-col-preview .jeu-resultat {
  grid-row: 3; grid-column: 1;
  align-self: end; justify-self: end;
  margin: 0; padding: 7px 11px; max-width: 42%;
  font-size: 11px;
}
.jeu-col-preview .jeu-apercu-origine,
.jeu-col-preview .jeu-col-nom {
  position: absolute; bottom: 6px; left: 16px;
  margin: 0; font-size: 10px; opacity: .8;
}
.jeu-col-preview { position: sticky; }

/* ── LES REPLIS ───────────────────────────────────────────────────────────────
   Sous 1080px, les deux colonnes ne tiennent plus : on empile, et le cadre garde sa
   forme tablette. */
@media (max-width: 1080px) {
  .jeu-atelier { grid-template-columns: minmax(0, 1fr) !important; }
  .jeu-col-preview { position: static !important; }
}
@media (max-width: 640px) {
  .jeu-col-preview { aspect-ratio: auto !important; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   ON PILOTE LA ROUE PAR LA HAUTEUR — LE DISQUE REMPLIT ENFIN LE CADRE
   MESURÉ : le cadre faisait 269x307 (bonne hauteur) mais le disque sortait à 110px.
   La chaîne passait par des `width: 100%` dont la largeur du parent venait elle-même
   de son contenu : elle se refermait sur elle-même et le moteur retombait sur une
   taille intrinsèque.
   Ici, chaque niveau tient sa largeur de sa HAUTEUR (`height` + `aspect-ratio`), qui
   est connue : 306px pour la zone, écrit dans la grille du panneau.
   ══════════════════════════════════════════════════════════════════════════════ */

/* Le shell (disque + support) : sa hauteur fait 88 % du cadre, sa largeur en découle. */
.jeu-col-preview .wheel-shell {
  height: 88% !important;
  width: auto !important;
  max-height: 88%;
  aspect-ratio: 1 / 1;
}

/* Le disque : il prend toute la hauteur de son shell, largeur déduite. */
.jeu-col-preview .wheel-rotor {
  height: 100% !important;
  width: auto !important;
  aspect-ratio: 1 / 1 !important;
}

/* L'anneau des ampoules suit le disque : il est en `inset: 0` sur le shell, donc il
   prend la même boîte. On le laisse tel quel — mais il doit rester carré. */
.jeu-col-preview .wheel-studs {
  aspect-ratio: 1 / 1;
  height: auto;
}

/* Le cadre : il garde sa hauteur, et son `aspect-ratio` fait le reste. On s'assure
   que le disque ne peut pas le dépasser. */
.jeu-col-preview .wheel-frame {
  height: 100% !important;
  max-height: 100%;
  display: grid;
  place-items: center center;   /* le disque CENTRÉ dans son cadre */
  overflow: visible;
}


/* ══════════════════════════════════════════════════════════════════════════════
   LE DISQUE SE POSE EN ABSOLU SUR SON SHELL
   MESURÉ : shell 270x270 (correct) mais rotor 122x122. Le shell est
   `display: grid; place-items: center`, donc le rotor est un item CENTRÉ — il tirait
   sa taille de son contenu, et `height: 100%` ne pouvait pas résoudre.
   `position: absolute; inset: 0` donne la boîte au lieu de la déduire : c'est la
   même boîte que le shell, donc le disque remplit enfin son cadre.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-col-preview .wheel-rotor {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  aspect-ratio: auto !important;   /* la boîte est donnée : plus rien à déduire */
  display: block;
}
/* Le shell porte la position, et reste carré par sa hauteur. */
.jeu-col-preview .wheel-shell {
  position: relative !important;
  height: 88% !important;
  width: auto !important;
  aspect-ratio: 1 / 1 !important;
  max-height: 88%;
}

/* L'anneau des ampoules prend la même boîte : il reste donc sur le bord du disque. */
.jeu-col-preview .wheel-studs {
  position: absolute; top: 0; left: 0; right: 0; height: auto;
  aspect-ratio: 1 / 1;
}

/* Les visuels des goodies ne doivent pas décider de la taille du disque : ils sont
   DANS le rotor, qui a maintenant une taille imposée. On borne leur taille. */
.jeu-col-preview .wheel-prize { max-width: 100%; }
.jeu-col-preview .wheel-prize img,
.jeu-col-preview .wheel-prize span { max-width: 62%; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES DEUX TEXTES RAMENÉS DANS LA GRILLE
   MESURÉ : `.jeu-apercu-origine` et `.jeu-col-nom` partaient à **659px à gauche** du
   cadre. Cause : ils portaient un `grid-column: 2` posé pour la disposition en DEUX
   colonnes — mais la grille n'a plus qu'UNE colonne, donc le moteur créait une
   colonne 2 IMPLICITE et les repoussait hors du panneau. Défaut hérité de mon premier
   découpage, resté après le passage à la disposition empilée.
   Correction : une seule colonne, et une rangée DÉDIÉE sous le bouton. Les deux textes
   s'y rangent côte à côte — l'origine à gauche, le nom de la roue à droite.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-col-preview .jeu-apercu-origine,
.jeu-col-preview .jeu-col-nom {
  position: static !important;
  grid-column: 1 !important;
  grid-row: 4 !important;
  width: auto !important;
  height: auto !important;
  min-width: 0;
  margin: 0 !important;
  bottom: auto !important;
  left: auto !important;
}
/* L'origine à gauche, le nom de la roue à droite : sur la même ligne. */
.jeu-col-preview .jeu-apercu-origine { justify-self: start; text-align: left; }
.jeu-col-preview .jeu-col-nom { justify-self: end; text-align: right; }

/* La grille gagne cette 4e rangée, fine. */
.jeu-col-preview { grid-template-rows: auto minmax(0, 1fr) auto auto !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   L'AIGUILLE SUIT LA ROUE — LA POINTE ENTRE DANS LA PART
   MESURÉ sur terrain.html : l'aiguille faisait 18x16 sur une roue de 200px, parce que
   `.wheel-small .wheel-pointer` (9px/16px en dur) gagnait contre les règles
   proportionnelles. Un réglage qui ne suit pas la roue n'est pas un réglage.
   `--roue-diam` porte la largeur RÉELLE du disque, mesurée par le JS après peinture.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA POSITION : le bord, PLUS la profondeur demandée ────────────────────────
   `2.4 %` du diamètre = les 8px d'origine sur une roue de 330px. On garde donc le
   rendu d'origine quand `aiguille_profondeur` vaut 0, et la pointe s'enfonce de
   `--needle-prof` (posé par le JS en pixels, à partir du pourcentage réglé). */
.wheel-frame.aig-haut .wheel-pointer,
.wheel-frame .wheel-pointer {
  /* Client : « essaye de faire entrer un peu l'aiguille vers le cercle ». Le .024
     reprenait les 8px d'origine (la pointe POSEE sur le bord). A .060, la pointe
     entre de ~6 % du diametre : visiblement dedans, sans atteindre le moyeu. */
  top: calc(var(--needle-prof, 0px) + var(--roue-diam, 330px) * .060 - var(--needle-h));
}

/* ── LE PIVOT EST LA POINTE ────────────────────────────────────────────────────
   `50% 100%` = le bas de l'élément, c'est-à-dire la pointe qui désigne la part.
   Grandir allonge donc l'aiguille VERS L'EXTÉRIEUR, sans bouger le point désigné :
   c'est la profondeur, et elle seule, qui décide si l'aiguille entre dans la part. */
.wheel-pointer {
  transform-origin: 50% 100%;
}

/* ── LES QUATRE FORMES, EN PROPORTION DU DISQUE RÉEL ───────────────────────────
   Les rapports viennent du benchmark d'une vraie roue (pointeur ~70mm sur un disque
   de 610mm ≈ 11,5 % en longueur, 5 % en largeur). Les `!important` sont nécessaires :
   ils reprennent la main sur `.wheel-small`/`.wheel-large`, qui fixaient des pixels
   en dur et écrasaient la proportion. */
.aig-forme-triangle .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .042);
  --needle-h: calc(var(--roue-diam, 330px) * .090);
  border-left-width: var(--needle-w) !important;
  border-right-width: var(--needle-w) !important;
  border-top-width: var(--needle-h) !important;
}
.aig-forme-fleche .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .026);
  --needle-h: calc(var(--roue-diam, 330px) * .115);
  border-left-width: var(--needle-w) !important;
  border-right-width: var(--needle-w) !important;
  border-top-width: var(--needle-h) !important;
}
.aig-forme-goutte .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .036);
  --needle-h: calc(var(--roue-diam, 330px) * .072);
  border-left-width: var(--needle-w) !important;
  border-right-width: var(--needle-w) !important;
  border-top-width: var(--needle-h) !important;
}
.aig-forme-fine .wheel-pointer {
  --needle-w: calc(var(--roue-diam, 330px) * .011);
  --needle-h: calc(var(--roue-diam, 330px) * .100);
  width: var(--needle-w) !important;
  height: var(--needle-h) !important;
}

/* ── LA TAILLE, APPLIQUÉE SANS DÉCENTRER ──────────────────────────────────────
   L'ancienne règle ne portait que `scale()` : le `translateX(-50%)` du centrage
   disparaissait et l'aiguille glissait d'une demi-largeur. Les deux ensemble. */
.wheel-frame .wheel-pointer {
  transform: translateX(-50%) scale(var(--pointer-scale, 1));
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE MOYEU — TAILLE, COULEUR, ET L'IMAGE QUI SUIT
   Client : « ajuster la taille du moyeu, de la taille de l'image du moyeu ».
   Le moyeu est UNE boîte : le monogramme et l'image téléversée partagent la même, donc
   régler la taille règle les deux à la fois. `--moyeu-px` porte la taille calculée par
   le JS ; sans lui, chaque palier garde sa valeur d'origine.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-hub {
  width: var(--moyeu-px, 66px);
  height: var(--moyeu-px, 66px);
  font-size: calc(var(--moyeu-px, 66px) * .20);
}
.wheel-large .wheel-hub {
  width: var(--moyeu-px, 80px);
  height: var(--moyeu-px, 80px);
}
.wheel-small .wheel-hub {
  width: var(--moyeu-px, 56px);
  height: var(--moyeu-px, 56px);
  font-size: calc(var(--moyeu-px, 56px) * .23);
}
/* L'image du moyeu remplit TOUJOURS la boîte : elle suit donc la taille réglée. */
.wheel-hub.a-image { padding: 0; overflow: hidden; }
.wheel-hub-img { width: 100%; height: 100%; object-fit: cover; }


/* ══════════════════════════════════════════════════════════════════════════════
   L'ECRAN TABLETTE DANS LA COLONNE D APERCU
   Client : « la forme du preview doit tenir la taille d'une tablette, pas en carré »
   puis « je veux des previews qu'on peut voir sur la tablette des collaboratrices ».

   On ne dessine donc plus une roue dans un carré : on dessine l'ECRAN QUE LA
   COLLABORATRICE AURA SOUS LES YEUX, dans un cadre 16:10 (la forme d'une tablette
   en paysage). Les proportions reprennent celles mesurees sur `terrain.html` :
      .collab-workspace  1172x400   ->  profil 34 % | jeu 64 %
      .collab-profile     404x400
      .quick-game         750x400
      .collab-feed       1172x175   ->  bandeau bas, masque ici (il ne sert pas au reglage)

   Tout est en `cqw` (unite du CONTENEUR) : l'ecran garde donc ses proportions a
   n'importe quelle largeur, sans un seul pixel fixe.
   ══════════════════════════════════════════════════════════════════════════════ */

/* Le cadre : 16:10, c'est la forme d'une tablette tenue en main. */
.jeu-apercu-zone {
  aspect-ratio: 16 / 10;
  width: 100%;
  container-type: inline-size;      /* rend `cqw` disponible pour tout l'interieur */
  border-radius: 14px;
  overflow: hidden;
  position: relative;
  isolation: isolate;
  /* Le fond vient du reglage (image ou aplat) : il est pose en style inline. */
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  transition: transform .28s ease, filter .28s ease;
}

/* Le voile d'assombrissement, par-dessus le fond et sous l'ecran. */
.jeu-apercu-zone .tab-voile {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
}

/* ── L'ECRAN ──────────────────────────────────────────────────────────────────
   Il occupe tout le cadre et se compose de : une barre, puis deux colonnes. */
.tab-apercu {
  position: relative; z-index: 2;
  width: 100%; height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background: linear-gradient(160deg, rgba(20,21,22,.92) 0%, rgba(12,13,14,.96) 100%);
  color: #f4f1ec;
  font-family: inherit;
}

/* ── LA BARRE ─────────────────────────────────────────────────────────────────
   Celle de l'application : la marque a gauche, la vue au centre, l'avatar a droite. */
.tab-barre {
  display: flex; align-items: center; gap: 2.2cqw;
  padding: 1.8cqw 2.6cqw;
  border-bottom: 1px solid rgba(255,255,255,.10);
  background: rgba(255,255,255,.03);
}
.tab-marque {
  font-weight: 800; letter-spacing: .14em; font-size: 2.5cqw;
  color: #f4f1ec;
}
.tab-vue {
  font-size: 2.1cqw; color: rgba(244,241,236,.62);
  margin-left: auto;
}
.tab-qui {
  display: inline-grid; place-items: center;
  width: 4.4cqw; height: 4.4cqw; border-radius: 50%;
  font-size: 1.8cqw; font-weight: 700;
  background: #a01a26; color: #fff;
  border: 1px solid rgba(255,255,255,.22);
}

/* ── LE CORPS : DEUX COLONNES, COMME SUR LA TABLETTE ──────────────────────────
   34 % pour le profil, 66 % pour le jeu — les proportions mesurees (404 / 750). */
.tab-corps {
  display: grid;
  grid-template-columns: 34fr 66fr;
  gap: 2cqw;
  padding: 2.6cqw;
  min-height: 0;
}

/* ── LE PROFIL ────────────────────────────────────────────────────────────────
   Avatar, nom, badge d'etat, mission, puis trois chiffres : exactement ce que la
   collaboratrice voit. */
.tab-profil {
  display: flex; flex-direction: column; gap: 1.6cqw;
  padding: 2.4cqw;
  border-radius: 12px;
  background: linear-gradient(165deg, rgba(255,255,255,.09) 0%, rgba(255,255,255,.03) 100%);
  border: 1px solid rgba(255,255,255,.10);
  min-width: 0;
}
.tab-avatar {
  display: grid; place-items: center;
  width: 7.4cqw; height: 7.4cqw; border-radius: 50%;
  font-size: 3cqw; font-weight: 800; letter-spacing: .04em;
  background: #a01a26; color: #fff;
}
.tab-etat { display: flex; align-items: center; gap: 1.2cqw; flex-wrap: wrap; }
.tab-etat b { font-size: 2.7cqw; font-weight: 700; }
.tab-badge {
  display: inline-flex; align-items: center; gap: .8cqw;
  padding: .5cqw 1.2cqw; border-radius: 999px;
  font-size: 1.6cqw; font-weight: 600;
  color: #7ee0a8; background: rgba(126,224,168,.13);
  border: 1px solid rgba(126,224,168,.30);
}
.tab-badge i {
  width: 1.1cqw; height: 1.1cqw; border-radius: 50%; background: #7ee0a8;
}
.tab-mission {
  margin: 0; font-size: 2cqw; color: rgba(244,241,236,.70);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tab-chiffres {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2cqw;
  margin-top: auto;
}
.tab-chiffres > div {
  border-radius: 9px; padding: 1.4cqw .8cqw; text-align: center;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.08);
}
.tab-chiffres b { display: block; font-size: 3cqw; font-weight: 800; line-height: 1.1; }
.tab-chiffres span { display: block; font-size: 1.5cqw; color: rgba(244,241,236,.60); }

/* ── LE JEU RAPIDE ────────────────────────────────────────────────────────────
   Le titre, puis la roue — qui doit DOMINER, comme sur l'ecran reel. */
.tab-jeu {
  display: grid; grid-template-rows: auto minmax(0, 1fr);
  gap: 1.4cqw; padding: 2.2cqw 2.4cqw;
  border-radius: 12px;
  background: linear-gradient(165deg, rgba(255,255,255,.07) 0%, rgba(255,255,255,.02) 100%);
  border: 1px solid rgba(255,255,255,.09);
  min-width: 0; min-height: 0;
}
.tab-jeu-tete { display: flex; align-items: baseline; gap: 1.6cqw; }
.tab-kicker {
  font-size: 1.6cqw; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(244,241,236,.55);
}
.tab-jeu-tete b { font-size: 2.6cqw; font-weight: 700; }

/* La roue : elle prend TOUTE la hauteur disponible et reste carree. C'est elle qui
   doit remplir l'ecran — c'est la seule chose qu'on est venu regler. */
.tab-roue {
  display: grid; place-items: center center;
  min-height: 0; min-width: 0;
  padding: .6cqw 0;
}
.tab-roue .wheel-frame {
  height: 100%;
  width: auto;
  aspect-ratio: 1 / 1.16;    /* le disque + son support */
  max-height: 100%;
  display: grid; place-items: center center;
  overflow: visible;
}
.tab-roue .wheel-shell {
  position: relative;
  height: 86%;
  width: auto;
  aspect-ratio: 1 / 1;
}
.tab-roue .wheel-rotor {
  position: absolute; inset: 0;
  width: 100% !important; height: 100% !important;
  aspect-ratio: auto !important;
}
.tab-roue .wheel-studs { position: absolute; top: 0; left: 0; right: 0; height: auto; aspect-ratio: 1 / 1; }

/* ── LA COLONNE D'APERCU ELLE-MEME ────────────────────────────────────────────
   L'ecran-tablette est plus large qu'un cercle : la colonne s'elargit, et reste
   epinglee en haut pendant qu'on regle (c'est la demande explicite du client). */
.jeu-col-preview {
  position: sticky; top: 12px; align-self: start;
}
.jeu-apercu-zone { margin-bottom: 1.2rem; }

/* Les actions, sous l'ecran. */
.jeu-actions-apercu { display: flex; gap: .8rem; }
.jeu-actions-apercu .button { flex: 1; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA FORME 16:10 REPREND LA MAIN
   MESURÉ : la zone faisait 626x286 (rapport 2.19) alors que `aspect-ratio: 16/10`
   demande 1.60. Un `aspect-ratio` ne décide que si la HAUTEUR est LIBRE — or la
   colonne portait une grille à rangée `1fr`, qui écrasait la hauteur.
   On retire la grille : la zone retrouve son rapport, donc sa forme de tablette.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-col-preview {
  display: flex !important;
  flex-direction: column;
  gap: .9rem;
  grid-template-rows: none !important;
  position: sticky;
  top: 12px;
  align-self: start;
  /* La colonne doit être assez large pour que l'écran-tablette soit lisible : à
     430px, un 16:10 ne fait que 269px de haut. On la laisse respirer. */
  min-width: 0;
}

/* La zone de l'écran : elle prend la largeur, le rapport fait la hauteur. */
.jeu-col-preview .jeu-apercu-zone {
  width: 100%;
  flex: 0 0 auto;
}

/* Les deux textes repris : l'origine puis le nom, l'un sous l'autre, alignés. */
.jeu-col-preview .jeu-apercu-origine,
.jeu-col-preview .jeu-col-nom {
  grid-column: auto !important;
  grid-row: auto !important;
  justify-self: stretch !important;
  text-align: left !important;
  margin: 0 !important;
}
.jeu-col-preview .jeu-col-nom { text-align: left; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA TABLETTE EST VERTICALE — L'APERCU PASSE EN PORTRAIT
   Client : « la tablette sera verticale ». Le 16:10 paysage etait donc faux — et pas
   seulement en proportion : en portrait il n'y a plus de place A COTE de la roue.
   Tout s'EMPILE : la barre, le profil resserre, puis la roue qui domine le bas.

   Format : une tablette 10" verticale fait environ 7:10 (l'iPad 10,9" = 1640x2360,
   soit 0,695). On prend `7 / 10` : le vrai rapport d'une tablette tenue en main.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-apercu-zone {
  /* ⚠️ 4 / 3 : le rapport de l'ÉCRAN montré (1024×768), comme la zone de la colonne. */
  aspect-ratio: 4 / 3 !important;
}

/* ── LE CORPS S'EMPILE ─────────────────────────────────────────────────────────
   Une seule colonne : le profil en bandeau, puis le jeu. C'est la lecture d'un
   ecran vertical — on descend, on ne balaie pas de gauche a droite. */
.tab-corps {
  grid-template-columns: 1fr !important;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1.6cqw;
  padding: 2.2cqw;
}

/* ── LE PROFIL DEVIENT UN BANDEAU ──────────────────────────────────────────────
   En portrait, le profil ne peut plus occuper un quart de l'ecran : il tient sur
   une BANDE. L'avatar et le nom sur la premiere ligne, les chiffres en dessous.
   La place gagnee va a la roue. */
.tab-profil {
  display: grid !important;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: .6cqw 1.6cqw;
  padding: 1.8cqw 2cqw;
  align-items: center;
}
.tab-avatar {
  grid-row: 1 / span 2;
  width: 8.4cqw; height: 8.4cqw; font-size: 3.4cqw;
}
.tab-etat { grid-column: 2; }
.tab-mission { grid-column: 2; }
/* Les trois chiffres : sous les deux colonnes, sur toute la largeur. */
.tab-chiffres {
  grid-column: 1 / -1;
  margin-top: .8cqw;
  gap: 1cqw;
}
.tab-chiffres > div { padding: 1cqw .6cqw; }
.tab-chiffres b { font-size: 2.9cqw; }
.tab-chiffres span { font-size: 1.5cqw; }

/* ── LE JEU, ET LA ROUE QUI DOMINE ─────────────────────────────────────────────
   C'est la seule chose qu'on est venu regler : elle prend TOUT l'espace restant. */
.tab-jeu { padding: 1.8cqw 2cqw; }
.tab-roue { padding: 0; }
.tab-roue .wheel-frame {
  height: 100%;
  width: auto;
  aspect-ratio: 1 / 1.14;
  max-height: 100%;
}
.tab-roue .wheel-shell { height: 88%; }

/* ── LA COLONNE D'APERCU : PLUS ETROITE EN PORTRAIT ────────────────────────────
   Un portrait est haut : la colonne peut rester etroite sans rien perdre. C'est
   autant de place rendue aux reglages, qui sont a gauche. */
.jeu-col-preview {
  max-width: 340px;
  margin-left: auto;
  margin-right: auto;
}


/* ══════════════════════════════════════════════════════════════════════════════
   LA ZONE DECIDE, L'ECRAN SE POSE DEDANS
   MESURE : la zone faisait 280x168 (1.66 = 16:10) alors qu'elle porte
   `aspect-ratio: 7/10` — donc elle aurait du faire 280x400.
   CAUSE : `.tab-apercu` etait en `height: 100%`. Un enfant a 100 % force le parent a
   prendre la hauteur de l'enfant, et le parent n'a alors plus de hauteur `auto` ou
   poser son `aspect-ratio`. La boucle se referme, le moteur retombe sur le contenu.
   MEME PIEGE QUE LE ROTOR. Meme sortie : `position: absolute; inset: 0` DONNE la
   boite au lieu de la deduire. La zone garde son rapport — c'est ELLE qui decide.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-apercu-zone {
  position: relative;          /* ancre de l'ecran en absolu */
  display: block;              /* plus de grille : plus rien qui puisse imposer la hauteur */
}
.jeu-apercu-zone .tab-apercu {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
}
/* La colonne d'apercu en flex : la zone est le seul element qui decide sa hauteur. */
.jeu-col-preview > .jeu-apercu-zone {
  flex: 0 0 auto;
  align-self: stretch;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE PLEIN ECRAN DU JEU — COMME SI C'ETAIT UN JEU
   Client : « Quand la roue va se lancer, on aura du plein ecran comme si c'etait un
   jeu. On aura juste un petit X sobre en haut pour quitter le jeu ou l'annuler. »
   On masque donc TOUT ce qui n'est pas le jeu (barre, navigation, contexte), et on
   ne laisse qu'un seul controle : le X. Il est discret — pas un bouton d'action,
   juste une sortie. `requestFullscreen` en plus, pour masquer la barre du navigateur
   sur la tablette : sans lui, « plein ecran » ne serait qu'un mot.
   ══════════════════════════════════════════════════════════════════════════════ */
body.jeu-plein-ecran {
  background: #0b0c0d;
}
body.jeu-plein-ecran .sidebar,
body.jeu-plein-ecran .topbar,
body.jeu-plein-ecran .game-context,
body.jeu-plein-ecran .game-foot,
body.jeu-plein-ecran .stepper {
  display: none !important;
}
body.jeu-plein-ecran .content { max-width: none; padding: 0; }
body.jeu-plein-ecran .game-shell {
  min-height: 100dvh;
  padding: 0;
  background: radial-gradient(circle at 50% 42%, rgba(212,43,56,.10) 0%, transparent 58%), #0b0c0d;
}
body.jeu-plein-ecran .game-card {
  max-width: none; min-height: 100dvh;
  border: 0; border-radius: 0; box-shadow: none; background: transparent;
  padding: 0;
  display: grid; place-items: center;
}
/* Le X : sobre. Un cercle translucide, une croix fine, en HAUT a DROITE. */
.jeu-quitter {
  position: fixed; top: 16px; right: 16px; z-index: 40;
  display: grid; place-items: center;
  width: 46px; height: 46px;
  border: 1px solid rgba(244,241,236,.26);
  border-radius: 50%;
  background: rgba(244,241,236,.08);
  color: rgba(244,241,236,.82);
  font-size: 22px; line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background .18s ease, border-color .18s ease, transform .18s ease;
}
.jeu-quitter:hover, .jeu-quitter:focus-visible {
  background: rgba(244,241,236,.16);
  border-color: rgba(244,241,236,.46);
  color: #fff;
  outline: none;
}
.jeu-quitter:active { transform: scale(.94); }

/* La scene de la roue en plein ecran : la roue DOMINE. */
body.jeu-plein-ecran .wheel-stage {
  min-height: 100dvh;
  gap: 2vh;
  padding: 4vh 4vw;
}
body.jeu-plein-ecran .wheel-stage .wheel-frame {
  width: auto; height: auto;
  max-width: min(74vh, 88vw);
  max-height: min(74vh, 88vw);
  aspect-ratio: 1 / 1.16;
}
body.jeu-plein-ecran .wheel-stage strong { font-size: clamp(16px, 2.2vh, 24px); color: rgba(244,241,236,.86); }

/* Le resultat (photo + valider) reste lisible en plein ecran. */
body.jeu-plein-ecran #spin-result:not(:empty) {
  min-height: 100dvh;
  display: grid; place-items: center;
  padding: 4vh 4vw;
  background: radial-gradient(circle at 50% 40%, rgba(212,43,56,.12) 0%, transparent 60%), #0b0c0d;
}
body.jeu-plein-ecran .reward-reveal {
  background: rgba(244,241,236,.05);
  border: 1px solid rgba(244,241,236,.16);
  color: #f4f1ec;
}
body.jeu-plein-ecran .reward-reveal h2 { color: #ff8a94; }
body.jeu-plein-ecran .reward-reveal p { color: rgba(244,241,236,.74); }
body.jeu-plein-ecran .completion-screen { color: #f4f1ec; }
body.jeu-plein-ecran .completion-screen p { color: rgba(244,241,236,.74); }
/* ⚠️ LE BOUTON « REJOUER » EST VERT (client, 26/09 : « Rejouer en bouton vert »).
   On réutilise le VERT DÉJÀ PRÉSENT SUR CET ÉCRAN — celui de la coche (#087443) — pour que l'écran
   garde son identité au lieu d'emprunter un vert étranger. Contraste MESURÉ avec son texte blanc :
   5,85:1, au-dessus du seuil de 4,5:1 que ce projet s'impose. « Quitter » garde, lui, l'apparence
   discrète d'un lien : c'est la sortie, elle ne doit pas concurrencer l'action principale.
   ⚠️ SÉLECTEUR GÉNÉRIQUE, VOLONTAIREMENT : il vaut pour le vrai jeu ET pour l'aperçu. Deux
   sélecteurs pour un même besoin finiraient par diverger — c'est la règle de ce projet. */
/* ⚠️⚠️ `!important` ET SPÉCIFICITÉ SUPÉRIEURE — LES DEUX SONT OBLIGATOIRES, MESURÉ.
   La règle générale du bouton d'action (l.15667, `.button:not(.secondary)`) porte déjà
   `!important` pour garantir « blanc sur #961c26 = 4,67:1 ». Sans `!important` ici, elle gagnait :
   le bouton sortait en rgb(150,28,38), un ROUGE, alors que la classe `bouton-vert` était bien
   posée (mesuré : classe posée / fond rouge). On ne touche PAS à la règle générale — elle sert à
   toute l'application ; on écrit AU-DESSUS, cloisonné par `bouton-vert`, qui n'est posée que sur
   CE bouton : aucun autre bouton de l'application ne peut être atteint.
      .button:not(.secondary)                 -> (0,2,0)  l'ancienne
      .completion-screen .button.bouton-vert  -> (0,3,0)  celle-ci, qui gagne  */
.completion-screen .button.bouton-vert { background: #087443 !important; border-color: #087443 !important; color: #fff !important; }
.completion-screen .button.bouton-vert:hover { background: #06603a !important; border-color: #06603a !important; }

@media (prefers-reduced-motion: reduce) {
  .jeu-quitter { transition: none; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   SEULE LA ZONE DECIDE DE SA HAUTEUR
   MESURE : la zone restait en 16:10 (280x168) alors que `aspect-ratio: 7/10` etait
   bien pose. CAUSE : une regle plus specifique portait `height: 100%` (ligne 9433,
   `.jeu-col-preview .jeu-apercu-zone`). Or QUAND LARGEUR ET HAUTEUR SONT TOUTES DEUX
   DETERMINEES, `aspect-ratio` EST IGNORE : ce n'est pas une question de priorite mais
   de sens. Le `!important` sur `aspect-ratio` ne pouvait donc rien.
   On remet la hauteur en `auto` avec la specificite necessaire.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-col-preview .jeu-apercu-zone,
.jeu-col-preview > .jeu-apercu-zone {
  height: auto !important;         /* la hauteur vient du RAPPORT, pas d'un parent */
  min-height: 0 !important;        /* les anciennes `min-height: 400/500px` */
  max-height: none !important;
  width: 100% !important;
  /* ⚠️ 4 / 3 = le rapport de l'ÉCRAN montré (1024×768), pas celui d'une tablette verticale.
     Un conteneur vertical ne peut pas tenir un écran horizontal : mesuré, la zone faisait 764px
     dans une colonne de 620px, et la page scrollait horizontalement. */
  aspect-ratio: 4 / 3 !important;
  display: block !important;
  flex: 0 0 auto !important;
  padding: 0 !important;
  /* ⚠️ PLUS DE `visible` : mesuré, la zone sortait de sa colonne de 144px. Les ampoules du
     bord vivent DANS le cadre (le fond de la roue est posé sur la zone), donc rien n'est rogné. */
  overflow: hidden !important;
  /* ⚠️ CEINTURE : quoi qu'une autre règle tente, la zone ne dépasse jamais sa colonne. */
  max-width: 100% !important;
}

/* ── LA ROUE RESPIRE ───────────────────────────────────────────────────────────
   La zone ne rogne plus, mais la roue doit rester DANS le cadre : on lui rend la
   place que les ampoules occupent autour du disque (~4 % de chaque cote). */
.tab-roue { padding: 1.4cqw 3cqw !important; }
.tab-roue .wheel-frame { max-height: 100%; max-width: 100%; }

/* ── LA COLONNE D'APERCU EN PORTRAIT ──────────────────────────────────────────
   Un portrait est haut : la colonne peut etre un peu plus large sans manger les
   reglages, et l'ecran y gagne en lisibilite. */
.jeu-col-preview { max-width: 380px !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE CHASSIS DE LA TABLETTE — ON DOIT VOIR UN APPAREIL
   Client : « On doit absolument voir une tablette, les dimensions d'une tablette avec
   la roue en entier, comme si c'etait un vrai jeu sur la tablette. »

   Un rectangle arrondi ne se lit pas comme une tablette. Ce qui la fait reconnaitre,
   c'est le CHASSIS : une bordure sombre epaisse, des coins tres arrondis, une encoche,
   un bouton. On les pose, et tout est en `cqw` — donc l'appareil garde ses proportions
   a n'importe quelle taille d'apercu, sans un seul pixel fixe.

   Le `.tab-verre` porte le FOND (image, zoom, position, assombrissement) : le reglage
   habille la TABLETTE, pas la zone qui la contient.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-apercu-zone {
  /* La zone EST le cadre de l'appareil : elle prend le rapport d'une tablette
     verticale et se centre. */
  /* ⚠️ 4 / 3 : le rapport de l'ÉCRAN montré (1024×768). Un conteneur vertical pour un
     écran horizontal débordait de sa colonne (mesuré : 764px dans 620px). */
  aspect-ratio: 4 / 3 !important;
  height: auto !important;
  min-height: 0 !important;
  width: 100% !important;
  display: grid !important;
  place-items: center center;
  padding: 0 !important;
  overflow: visible;
  background: none !important;   /* le fond appartient au VERRE de l'appareil */
  border: 0 !important;
  container-type: inline-size;
}

/* ── LE CHASSIS ────────────────────────────────────────────────────────────────
   Bordure epaisse, coins tres arrondis, et un liseré clair a l'interieur : c'est ce
   liseré qui donne l'epaisseur et fait « objet » plutot que « cadre ». */
.tab-appareil {
  width: 100%; height: 100%;
  display: grid;
  border-radius: 7.5cqw;
  background: linear-gradient(158deg, #2b2d30 0%, #1b1c1e 46%, #101112 100%);
  padding: 3.2cqw;
  box-sizing: border-box;
  position: relative;
  box-shadow: 0 1.6cqw 5cqw rgba(15,16,17,.24), 0 0 0 1px rgba(255,255,255,.06) inset;
}

/* L'encoche : le signe le plus lisible d'une tablette recente. */
.tab-encoche {
  position: absolute; top: 1.5cqw; left: 50%; transform: translateX(-50%);
  width: 1.5cqw; height: 1.5cqw; border-radius: 50%;
  background: #0a0b0c;
  box-shadow: 0 0 0 .35cqw rgba(255,255,255,.05);
  z-index: 4;
}

/* Le bouton lateral : discret, mais il complete la silhouette. */
.tab-bouton {
  position: absolute; right: -1.1cqw; top: 22%;
  width: .9cqw; height: 9%;
  border-radius: 0 .6cqw .6cqw 0;
  background: linear-gradient(90deg, #232527, #3a3d40);
}

/* ── LE VERRE ──────────────────────────────────────────────────────────────────
   L'ecran proprement dit. Il porte le FOND reglable, et il est legerement plus
   sombre que le chassis — c'est ce contraste qui separe le verre de l'objet. */
.tab-verre {
  position: relative;
  border-radius: 4.6cqw;
  overflow: hidden;
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  display: grid;
  /* ══ LA RANGEE DE LA ROUE PREND TOUT ; LA BARRE ET LE PIED SONT PLAFONNES ═══
     MESURE : la roue n'occupait que 62 % de la hauteur du verre. Cause : trois
     rangees `auto`, dont la derniere (le pied) prenait sa hauteur NATURELLE —
     et le cadre de la roue, borne par `max-height: 100%`, se contentait du
     reste. On plafonne la barre et le pied en `cqh` (unites du conteneur), ce
     qui rend la place a la rangee du milieu. */
  grid-template-rows: minmax(0, 7cqh) minmax(0, 1fr) minmax(0, 9cqh);
  min-height: 0;
  isolation: isolate;
}

/* ── L'ECRAN DU JEU ────────────────────────────────────────────────────────────
   Client : « comme si c'etait un vrai jeu sur la tablette ». Donc l'ecran du JEU :
   une barre FINE (elle dit « c'est une tablette »), puis la roue qui prend TOUT.
   Un apercu d'atelier qui montrerait un profil de 15 % de hauteur montrerait le
   profil, pas la roue qu'on vient de regler. */
.tab-jeu-ecran {
  grid-row: 1 / -1;
  display: grid;
  /* ══ LA RANGEE DE LA ROUE PREND TOUT ; LA BARRE ET LE PIED SONT PLAFONNES ═══
     MESURE : la roue n'occupait que 62 % de la hauteur du verre. Cause : trois
     rangees `auto`, dont la derniere (le pied) prenait sa hauteur NATURELLE —
     et le cadre de la roue, borne par `max-height: 100%`, se contentait du
     reste. On plafonne la barre et le pied en `cqh` (unites du conteneur), ce
     qui rend la place a la rangee du milieu. */
  grid-template-rows: minmax(0, 7cqh) minmax(0, 1fr) minmax(0, 9cqh);
  min-height: 0;
  background: linear-gradient(168deg, rgba(14,15,16,.88) 0%, rgba(9,10,11,.94) 100%);
  color: #f4f1ec;
}

/* La barre, resserree : 8 % de la hauteur suffisent a nommer l'ecran. */
.tab-barre {
  display: flex; align-items: center; gap: 2cqw;
  padding: 2.4cqw 3cqw 1.2cqw;
}
.tab-marque { font-weight: 800; letter-spacing: .16em; font-size: 2.4cqw; }
.tab-vue { margin-left: auto; font-size: 1.9cqw; color: rgba(244,241,236,.55);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tab-qui {
  display: inline-grid; place-items: center;
  width: 4.2cqw; height: 4.2cqw; border-radius: 50%;
  font-size: 1.7cqw; font-weight: 700;
  background: #a01a26; color: #fff;
}

/* ── LA ROUE, ENTIERE ──────────────────────────────────────────────────────────
   « avec la roue en entier ». Le disque + son support + les ampoules. La cause du
   support recentre DANS le disque etait la chaine de hauteur du `.wheel-frame`
   (`aspect-ratio` sur une hauteur imposee + `place-items: center`). On pose la boite
   du disque en ABSOLU : elle devient DONNEE, plus deduite. */
.tab-roue {
  display: grid; place-items: center center;
  min-height: 0; min-width: 0;
  padding: 1cqw 3cqw 0;
}
.tab-roue .wheel-frame {
  position: relative;
  height: 100% !important;
  width: auto !important;
  aspect-ratio: 1 / 1.16 !important;   /* le disque + la place du support */
  max-height: 100%;
  max-width: 100%;
  display: block !important;           /* plus de grille : plus de recentrage */
  place-items: initial !important;
  overflow: visible;
}
/* LE DISQUE : boite DONNEE (le haut du cadre), le support se pose dessous. */
.tab-roue .wheel-shell {
  position: absolute !important;
  top: 0; left: 50%;
  transform: translateX(-50%);
  height: 86% !important;
  width: auto !important;
  aspect-ratio: 1 / 1 !important;
  display: block !important;
}
.tab-roue .wheel-rotor {
  position: absolute !important; inset: 0 !important;
  width: 100% !important; height: 100% !important;
  aspect-ratio: auto !important;
}
.tab-roue .wheel-studs {
  position: absolute; top: 0; left: 0; right: 0;
  height: auto; aspect-ratio: 1 / 1;
}
/* Le support : dans le flux, SOUS le disque (le cadre lui reserve les 14 % restants). */
.tab-roue .wheel-stand {
  position: absolute;
  top: 86%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 0 !important;
}

/* ── LE BANDEAU DE JEU, SOUS LA ROUE ───────────────────────────────────────────
   Ce que la collaboratrice lit : le nom de la campagne et le compte de goodies. */
.tab-jeu-pied {
  display: flex; align-items: baseline; justify-content: space-between; gap: 2cqw;
  padding: 0 3cqw 2.4cqw;
}
.tab-jeu-pied b { font-size: 2.5cqw; font-weight: 700; }
.tab-jeu-pied span { font-size: 1.8cqw; color: rgba(244,241,236,.58); }

/* Le voile : il couvre le VERRE, pas le chassis. */
.jeu-apercu-zone .tab-voile {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: 4.6cqw;
}
.tab-verre .tab-voile { border-radius: inherit; }

/* ── LA COLONNE : UN PEU PLUS LARGE POUR QUE L'APPAREIL SOIT LISIBLE ─────────── */
.jeu-col-preview { max-width: 400px !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE EST BORNEE PAR LA LARGEUR
   MESURE : le cadre sortait a 290px de large dans un conteneur de 262 — la largeur
   etait DEDUITE de la hauteur (`height: 100%` + `aspect-ratio`), donc elle depassait
   et 3 ampoules se faisaient couper.
   On inverse : la LARGEUR vient du conteneur, la HAUTEUR en decoule. La roue est
   bornee par ce qui est rare — l'horizontal — et `max-height` la protege du vertical.
   Elle remplit donc le maximum de place SANS jamais depasser.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue { padding: 1cqw 1.5cqw !important; }
.tab-roue .wheel-frame {
  width: 100% !important;
  height: auto !important;
  aspect-ratio: 1 / 1.2 !important;   /* le disque + la place du support sous lui */
  max-width: 100%;
  max-height: 100%;
  margin: 0 auto;
}
/* Le disque garde son 86 % du cadre : le support occupe les 14 % restants. */
.tab-roue .wheel-shell { height: 86% !important; }
/* Le support se pose EXACTEMENT au bas du disque. */
.tab-roue .wheel-stand { top: 86%; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES DEUX POURCENTAGES ACCORDES
   MESURE : le disque sortait a 262x262 dans un cadre de 254 de large — il depassait
   de 8px. Les deux reglages n'etaient pas accordes :
      · le disque = 86 % de la HAUTEUR du cadre, et il est CARRE ;
      · donc la hauteur du cadre doit valoir 1/0,86 = 1,163 fois sa largeur.
   J'avais mis 1,2 : l'ecart de 3 % se voyait en debordement lateral.
   Avec 1,163 les deux disent la MEME chose, et le support tombe pile au bas du disque.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame { aspect-ratio: 1 / 1.163 !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE SUPPORT SE POSE AU BAS DU DISQUE
   MESURE : le support commencait a y=487 alors que le disque finit a 522 — il
   remontait donc de 35px DANS le disque.
   CAUSE : `top: 86%` se calcule sur le CONTENEUR POSITIONNE, qui est le
   `.wheel-shell` (le disque), pas le cadre. 86 % de la hauteur du disque = 218px,
   d'ou y=486 — exactement la mesure.
   Le support est un FRERE du rotor DANS le shell : il se pose donc a `100%`, soit
   le bas exact du disque.
   Et les deux pourcentages s'accordent : shell 84 % d'un cadre 1/1,19 donne un carre
   parfait de 254px, et le support (48px) tombe pile au bas du cadre.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame { aspect-ratio: 1 / 1.19 !important; }
.tab-roue .wheel-shell { height: 84% !important; }
.tab-roue .wheel-stand { top: 100% !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LE DISQUE EST UN CERCLE, PAS UNE ELLIPSE
   MESURE : le disque sortait a 300x415 — donc une ELLIPSE, pas un cercle. Cause :
   `.wheel-shell` portait `height: 84%` + `width: auto`, et en position absolue la
   largeur `auto` ne venait pas de l'`aspect-ratio` comme prevu.
   On DONNE la largeur au lieu de la deduire : `left: 0; right: 0` la fixe a celle du
   cadre, et `aspect-ratio` + `height: auto` en deduit la hauteur — donc un carre par
   construction, quelle que soit la taille de l'apercu.
   Le disque y GAGNE : 415px au lieu de 300px dans le meme cadre.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-shell {
  position: absolute !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;          /* la LARGEUR est donnee, plus deduite */
  transform: none !important;   /* plus besoin de recentrer : left+right le font */
  width: auto !important;
  height: auto !important;
  aspect-ratio: 1 / 1 !important;  /* donc la hauteur = la largeur : CARRE */
}
/* Le support se pose au bas exact du disque, qui est maintenant sa hauteur reelle. */
.tab-roue .wheel-stand { top: 100% !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA JAUGE DE POSITION DE LA ROUE
   Client : « positionner [...] plus haut, plus bas [...] la deplacer un peu vers la
   gauche, vers la droite ». Un PAVE 3x3 plutot que deux curseurs : les huit
   directions se donnent d'un geste, et la forme dit « position » a elle seule.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-pos {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  margin: 4px 0 10px;
  padding: 14px 16px;
  border-radius: 13px;
  background: rgba(23,23,25,.035);
  border: 1px solid rgba(23,23,25,.09);
}

/* ── LE PAVÉ ──────────────────────────────────────────────────────────────────
   Trois colonnes de 34px : assez grand pour le doigt, assez sobre pour ne pas
   concurrencer les bons de commande du reste du bloc. */
.jeu-pad {
  display: grid;
  grid-template-columns: repeat(3, 34px);
  grid-auto-rows: 34px;
  gap: 3px;
}
.jeu-pad-btn {
  display: grid; place-items: center;
  border: 1px solid rgba(23,23,25,.14);
  border-radius: 8px;
  background: #fff;
  color: #4a4744;
  font-size: 14px; line-height: 1;
  cursor: pointer;
  padding: 0;
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.jeu-pad-btn:hover, .jeu-pad-btn:focus-visible {
  background: rgba(230,57,70,.10);
  border-color: rgba(230,57,70,.42);
  color: #b3242f;
  outline: none;
}
.jeu-pad-btn:active { transform: scale(.94); }
/* Le centre : c'est « recentrer », donc il porte une marque, pas une flèche. */
.jeu-pad-centre {
  background: rgba(23,23,25,.06);
  font-size: 18px;
  color: #383633;
}

/* ── LE CÔTÉ : LA VALEUR, LES DEUX AXES, LE BOUTON ──────────────────────────── */
.jeu-pos-cote {
  display: grid; gap: 9px;
  min-width: 0;
}
.jeu-pos-v {
  font: 600 12.5px 'DM Mono', ui-monospace, monospace;
  color: #0d0d0f;
  background: rgba(23,23,25,.05);
  border-radius: 7px;
  padding: 5px 9px;
  justify-self: start;
}
.jeu-pos-axe {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  font: 600 12px Manrope, sans-serif;
  color: #383633;
}
.jeu-pos-axe input[type="range"] { width: 100%; margin: 0; }
.jeu-pos-cote .button { justify-self: start; }

/* ── LE FANTÔME : LA PLACE D'ORIGINE DE LA ROUE ───────────────────────────────
   Un cercle EN POINTILLÉ, sans remplissage : il repère sans masquer. Un remplissage,
   meme leger, cacherait la part qu'il recouvre — et on ne regle pas ce qu'on ne voit
   pas. Il n'apparait que si la roue a bouge. */
.tab-repere {
  position: absolute;
  left: 50%; top: 50%;
  width: 46%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  border: 1px dashed rgba(244,241,236,.26);
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .jeu-pad-btn { transition: none; }
  .jeu-pad-btn:active { transform: none; }
}


/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE GARDE UNE MARGE POUR POUVOIR BOUGER
   MESURE : a la borne de la jauge (x=50 %), la roue sortait de la tablette. La cause
   n'est pas la borne : la roue occupait 97 % de la largeur, donc elle n'avait AUCUN
   espace ou se deplacer. Un reglage de position sur un element qui remplit tout ne
   peut que le faire sortir.
   On lui reserve donc ~8 % de chaque cote. Elle reste dominante (84 % de la largeur,
   largement le plus gros element de l'ecran) et gagne une vraie course de ±10 %.
   ══════════════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════════
   LA LARGEUR DU CADRE, ACCORDEE A LA COLONNE
   La regle precedente datait du moment ou la roue remplissait 97 % de la LARGEUR
   de la colonne : elle devait alors lui rendre une marge, sinon le reglage de
   position (±10 %) la faisait sortir. Mais la colonne a ete ELARGIE (314 -> 566px)
   et c'est desormais la HAUTEUR qui contraint la roue, pas la largeur. Un `84 %`
   la bride donc pour rien, et lui retire 4 % de hauteur.
   On revient a `100 %`, et on garde la marge par le PADDING de la scene (deja
   pose) : la course du reglage de position reste entiere, sans brider la taille.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame { width: 100% !important; max-width: 100% !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LES QUATRE FORMES D'AMPOULES — ET LEUR COULEUR REGLABLE
   Client : « Choisir le type d'ampoule qu'on aura et leurs couleurs, leur taille,
   leur nombre ».

   CONTRAINTE REELLE : une ampoule fait 2,3 % du diametre (benchmark d'une vraie roue de
   610 mm). A 8px, une nuance trop fine ne se voit pas. Les quatre formes different donc
   sur ce qu'on distingue a cette taille : le CONTOUR (ronde/carree), la PROPORTION
   (amande, allongee), et le RELIEF (plate, sans volume).
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA COULEUR ───────────────────────────────────────────────────────────────
   `--ampoule-couleur` n'est pose QUE si le client a choisi une teinte. Non pose, on
   retombe sur `--ampoule-teinte` — la teinte d'origine de chaque matiere, posee par le
   CSS plus haut. Le rendu d'avant est donc preserve, style par style.
   On passe par `background-color` sur le pseudo-element : la forme et le halo sont
   conserves, seule la teinte change. */
.wheel-studs i::before {
  background-color: var(--ampoule-couleur, var(--ampoule-teinte, #f7c75e));
}

/* ── RONDE (le defaut) ────────────────────────────────────────────────────────
   Rien a faire : c'est le rendu d'origine, deja ecrit plus haut. On ne le redeclare
   pas, pour ne pas risquer de l'alterer. */

/* ── CARRÉE ───────────────────────────────────────────────────────────────────
   Le contour change : c'est ce qui se lit le mieux a 8px. Un leger arrondi (2px) evite
   l'effet « pixel » tout en restant franchement carre. */
.amp-carree .wheel-studs i::before {
  border-radius: 2px;
}
.amp-carree .wheel-studs i::before {
  box-shadow: inset 0 0 2px rgba(255,255,255,.55), 0 0 6px -1px var(--ampoule-couleur, var(--ampoule-teinte, #f7c75e)) !important;
}

/* ── AMANDE ───────────────────────────────────────────────────────────────────
   Allongee : plus haute que large. La proportion elle-meme est le signal — elle se
   voit meme de loin, sans dependre de la couleur. */
.amp-amande .wheel-studs i::before {
  height: calc(var(--stud-size, 8px) * 1.5) !important;
  border-radius: 50% 50% 50% 50% / 62% 62% 38% 38% !important;
}

/* ── PLATE ────────────────────────────────────────────────────────────────────
   Sans relief ni halo : un disque net, sans volume. C'est l'option sobre — elle
   contraste fortement avec les trois autres, qui ont toutes du brillant. */
.amp-plate .wheel-studs i::before {
  box-shadow: none !important;
  filter: none !important;
  border: 0 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   L'ÉCHELLE DE L'APERÇU — LA COLONNE REMPLIT ENFIN SA PISTE
   ══════════════════════════════════════════════════════════════════════════════

   ══ CE QUI ÉTAIT MESURÉ ══
   À TOUTES les largeurs de fenêtre (1280, 1440, 1600, 1920, 2560), la colonne
   d'aperçu faisait **314px**, et l'appareil dedans **280x400**. Or la piste de
   grille valait 519px à 1280 et 660px au-delà : la colonne n'en occupait que
   **48 %**. À 1600, la grille donnait `579px 660px` et l'aperçu sortait à 314.

   ══ LA CAUSE — une seule règle, deux effets ══
   `.jeu-col-preview` portait encore `aspect-ratio: 16 / 10` (l'ancien écran
   PAYSAGE de l'aperçu-carré) et `overflow: hidden` (hérité de `.wheel-decor`).
   Ce sont deux règles écrites pour un autre objet, restées après le passage de
   l'aperçu en portrait.

   1. `aspect-ratio: 16/10` sur une colonne `align-self: start` fait calculer sa
      LARGEUR depuis sa propre HAUTEUR (314 = 196 × 1.60). Elle s'arrêtait donc
      196px sous sa piste, quel que soit l'écran — c'est le 314px constant.
   2. `overflow: hidden` ROGNAIT ce qui dépassait : la colonne mesurait 196px de
      haut pendant que l'appareil descendait jusqu'à 250px sous elle (mesuré :
      bas appareil 613, bas colonne 363). **C'est le « la tablette est coupée »
      du client** — la cause n'était pas la zone, ni la roue : c'était la colonne.

   ══ LA CORRECTION ══
   On retire le rapport et le rognage devenus faux, on met la colonne en
   `align-self: start` (elle prend la hauteur de son contenu), et c'est la PISTE
   de la grille qui décide de sa largeur (`stretch`, implicite).

   ══ POURQUOI LA PISTE EST UNE PART + UN GARDE-FOU, ET NON UN `clamp()` ══
   Deux essais mesurés, tous deux écartés :
   · `minmax(480px, min(42vw, 620px))` : `.content` porte `max-width: 1560px`, donc
     `.jeu-atelier` PLAFONNE à 1472px dès 1920 de fenêtre et les `vw` y sont GELÉES.
     La piste sortait à 620px à 1600 mais à **605px seulement** à 2560 : l'aperçu
     RÉTRÉCISSAIT entre 1600 et 2560 — continuité rompue.
   · `minmax(480px, auto)` : une piste `auto` se dimensionne sur le `max-content` de
     son contenu. Celui de la colonne vaut ~312px (mesuré) : la piste retombait donc
     à son minimum de 480px et y restait collée — l'aperçu ne grandissait plus.

   On écrit donc la part DIRECTEMENT en pourcentage de l'atelier, avec deux bornes
   explicites :
     · `45%`      — la part visée pour l'aperçu ;
     · `calc(100% - 578px)` — le GARDE-FOU DU PLANCHER : 578 = 560 (réglages) + 18
       (gouttière). Cette borne rend mathématiquement impossible que les réglages
       passent sous 560px, quelle que soit la largeur ;
     · `620px`    — le PLAFOND, pour que l'aperçu ne devienne pas absurde.
   Les trois sont réunis dans un `min()`, donc c'est la plus contraignante qui gagne,
   et la somme des deux pistes fait toujours EXACTEMENT la largeur de l'atelier :
   le débordement horizontal est structurellement impossible. */
.jeu-atelier {
  /* minmax(560px, 1fr) : le PLANCHER des réglages n'est plus 400px mais 560px —
     c'est la contrainte « jamais sous 560px » demandée, écrite une fois pour
     toutes. */
  grid-template-columns: minmax(560px, 1fr) min(45%, calc(100% - 578px), 620px) !important;
}

.jeu-col-preview {
  /* ⚠️⚠️ LA TABLETTE EST ÉPINGLÉE, ELLE — ET SEULE ELLE (client : « on va juste épingler la
     tablette puis ses onglets en bas d'elle quand on va commencer à choisir et modifier et
     personnaliser »).
     ⚠️ POURQUOI ELLE ET PAS LES ONGLETS DU HAUT : les onglets ont été désépinglés parce qu'ils
     volaient l'espace nécessaire à la tablette. Ici, c'est l'inverse — c'est la tablette qu'on
     veut garder sous les yeux pendant qu'on règle, et comme elle occupe une COLONNE étroite,
     elle ne prend rien à personne : elle tient dans l'espace vide à droite.
     `top: 12px` : un peu de respiration sous la barre du haut, jamais collé au bord. */
  position: sticky; top: 12px;
  /* ⚠️⚠️ L'ÉCHELLE DE L'APERÇU EST POSÉE ICI — C'EST ELLE QUI FAISAIT DÉBORDER.
     `--apercu-echelle` n'était défini QUE dans la fenêtre de profil (`.apercu-profil-dialog`).
     Dans l'atelier, l'iframe tombait donc sur le repli `.78` : 1024 × .78 = 799px pour une
     colonne de 620px — mesuré, la zone sortait de sa colonne de 144px et la page scrollait
     horizontalement. .58 × 1024 = 594px : ça RENTRE, avec la marge du cadre.
     ⚠️ On ne touche pas au viewport de l'iframe (1024×768) : c'est LUI qui déclenche le mode
     tablette de la page montrée. Changer la taille changerait la mise en page — l'aperçu
     mentirait. On ne change que l'échelle VISUELLE, ce qui est exactement son rôle. */
  --apercu-echelle: .58;
  /* ── 1. LE RAPPORT PAYSAGE S'EN VA ─────────────────────────────────────────
     C'était la cause du 314px : la largeur venait de la hauteur. */
  aspect-ratio: auto !important;
  /* ── 2. LE ROGNAGE S'EN VA ─────────────────────────────────────────────────
     C'était la cause de la tablette coupée. La colonne prend la hauteur de son
     contenu : plus rien à rogner. */
  /* ⚠️⚠️ LA COLONNE ROGNE — CEINTURE STRUCTURELLE, INDÉPENDANTE DE TOUTES LES AUTRES RÈGLES.
     Le rognage avait été retiré (« la tablette coupée ») quand la zone était plus HAUTE que sa
     colonne. Depuis, le rapport est celui de l'écran et l'échelle est posée : plus rien ne peut
     être coupé, et en revanche plus rien ne peut SORTIR. Les deux défauts ne peuvent pas coexister
     si la géométrie est juste — et elle l'est maintenant. */
  /*
  /* overflow: neutralise ici — ⚠️⚠️ IL TUAIT le `position: sticky; bottom` de la replique des
     onglets. Un `overflow` non `visible` sur un ANCETRE fait de lui le CONTENEUR DE DEFILEMENT
     du `sticky` : la replique se retrouve epinglee a une colonne qui ne defile pas, donc
     immobile. MESURE : `position: sticky` et `bottom: 12px` etaient pourtant bien appliques sur
     la replique — la regle etait juste et sans effet. `sticky` ne se juge jamais seul, mais avec
     la chaine de ses ancetres. (Le `overflow` de `.jeu-apercu-zone`, lui, est LEGITIME et reste :
     c'est le cadre de l'appareil, il doit rogner.) */   ⚠️⚠️ NEUTRALISÉ : il TUAIT le `position: sticky; bottom` de la réplique des
     onglets. Un `overflow` non `visible` sur un ANCÊTRE fait de lui le CONTENEUR DE DÉFILEMENT
     du `sticky` : la réplique était donc épinglée à une colonne qui ne défile pas, et restait
     immobile. MESURÉ : `position: sticky` et `bottom: 12px` étaient pourtant bien appliqués — la
     règle était juste et sans effet. `sticky` ne se juge jamais seul, mais avec ses ancêtres.
     ⚠️ IL Y EN AVAIT QUATRE POUR CE MÊME SÉLECTEUR, dont un `!important` : c'est la DERNIÈRE qui
     gagnait, comme toujours. On les neutralise TOUS les quatre au lieu d'en ajouter un cinquième. */
  /* ── 3. LA LARGEUR VIENT DE LA PISTE ───────────────────────────────────────
     `stretch` explicite : la colonne REMPLIT sa piste au lieu de se refermer sur
     son contenu. C'est ce qui la fait passer de 314px à la place disponible. */
  justify-self: stretch !important;
  align-self: start !important;
  /* ── 4. LE PLAFOND, EN `clamp()` ET NON EN PIXELS SECS ─────────────────────
     420px tant que la place est rare, 25vw au-delà, 620px au maximum. L'aperçu
     ne devient donc jamais absurde sur un très grand écran. */
  /* ── LE PLAFOND NE DOIT PAS MORDRE SUR LA PISTE ──────────────────────────────
     MESURE : la piste de grille offrait 500px a 1600px de fenetre, mais
     `clamp(420px, 25vw, 620px)` plafonnait a 420px (car 25vw = 400 < 420, et
     `clamp()` renvoie alors son MINIMUM). La colonne restait donc bloquee a
     420px, un vestige de l'ancien apercu carre.
     C'est la PISTE qui doit decider : elle porte deja le plafond de 620px et le
     garde-fou du plancher des reglages (`calc(100% - 578px)`). On la laisse
     donc faire son travail, sans second plafond qui la contredit. */
  max-width: 100% !important;
  min-width: 0 !important;
  width: auto !important;
  /* ── 5. LES MARGES AUTOMATIQUES S'EN VONT ──────────────────────────────────
     Autre reste de l'époque paysage : `margin-left/right: auto` étaient là pour
     CENTRER une colonne étroite (340px) dans sa piste. Or une marge `auto` dans
     l'axe d'alignement ABSORBE l'espace libre et ANNULE le `stretch` : la colonne
     restait donc en `fit-content` (312px) au lieu de remplir sa piste de 480px.
     C'est le dernier verrou du 314px. On remet les marges à zéro. */
  margin-left: 0 !important;
  margin-right: 0 !important;
  /* Le fond appartient à l'APPAREIL (`.tab-appareil` + `.tab-verre`). Autour de
     lui, la colonne ne portait qu'un dégradé et une ombre hérités de l'aperçu-
     carré : on les neutralise pour que l'écran reste le seul objet posé sur la
     surface crème de la console (#f4f1ec). */
  background: none !important;
  border: 0 !important;
  box-shadow: none !important;
}

/* ── LA COLONNE DES RÉGLAGES : LE PLANCHER DE 560px EST TENU ──────────────────
   Le `minmax(560px, 1fr)` ci-dessus le fait, mais on le REDIT ici contre tout
   `!important` qui viendrait d'ailleurs : les curseurs et les listes déroulantes
   ont besoin de ces 560px pour rester utilisables. Cette règle ne change aucune
   largeur mesurée aujourd'hui (les réglages font 579 à 1600, 794 à 1920) — elle
   n'est qu'une ceinture. */
.jeu-col-reglages {
  min-width: 560px !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   L'ATELIER ET LES ONGLETS EN PLEINE LARGEUR, LA TABLETTE FACE AUX RÉGLAGES
   Client : « au lieu de retourner à la ligne, j'aimerais bien que tu fasses descendre la tablette en
   bas pour la mettre en face des réglages » + « étirer l'onglet Templates des pré-réglages et l'onglet
   des onglets » + « rétrécir les onglets et la taille des widgets des onglets par 30 % garde 70 % ça
   va permettre de rentrer les 6 sur une seule ligne c'est sûr ».

   ⚠️⚠️ LES DEUX BLOCS ONT ÉTÉ SORTIS DE `.jeu-col-reglages` (voir app.js) : ils en étaient les
   ENFANTS, donc enfermés dans la colonne de gauche (mesuré 717 px) — et un élément qui n'est pas dans
   une grille ne peut pas être étiré par elle. C'est la PARENTÉ qu'il fallait changer, pas les largeurs.
   MESURE DU DÉFAUT : 6 onglets × 134 px = 804 px pour 701 px disponibles ⇒ le 6e, « Reçu », tombait
   seul sur une seconde ligne.

   ⚠️ `grid-column: 1 / -1` ET NON `1 / 3` : `-1` = la DERNIÈRE ligne de la grille, quel que soit le
   nombre de colonnes. Le repli `@media (max-width: 1120px)` passe la grille à UNE colonne : les deux
   rangées se replient donc AUTOMATIQUEMENT, sans une ligne à écrire pour ça. Un `1 / 3` figé aurait
   forcé deux colonnes même sur un écran étroit.

   ⚠️ AUCUN `!important` ICI, ET C'EST VOULU : `grid-column` n'est déclaré nulle part ailleurs, il n'y
   a donc rien à battre. En ajouter un « par précaution » aurait menti sur la nature du problème. */
.jeu-atelier-roues { grid-column: 1 / -1; }
.jeu-cats-gaine   { grid-column: 1 / -1; }

/* ⚠️ LE RÉTRÉCISSEMENT À 70 % (demande explicite : « par 30 % garde 70 % »).
   ⚠️⚠️ ON RÉDUIT LES DIMENSIONS RÉELLES, JAMAIS `transform: scale` : un `scale` ne change pas la
   place RÉSERVÉE, donc les onglets se chevaucheraient au lieu de tenir sur la ligne. Ici la place
   occupée diminue pour de vrai — c'est la seule façon d'en faire tenir six.
   Les proportions sont conservées : icône 38→27 px (glyphe 15→11 px, soit 39,5 % de la pastille,
   exactement le rapport d'avant) · coins 13→9 px et 11→8 px (ratio 0,342 conservé) · `gap` 9→6,3 px
   (il fait partie de la place occupée, on le réduit donc aussi) · `padding` 13/10/11 → 9,1/7/7,7 px.
   ⚠️ AUCUNE COULEUR, AUCUNE OMBRE, AUCUN ÉTAT N'EST TOUCHÉ : l'actif reste fond sombre / texte blanc,
   et l'esthétique de la charte est préservée — c'est la demande du client mot pour mot. */
/* ⚠️⚠️⚠️ CES DEUX BLOCS SONT NEUTRALISÉS — ILS ONT ÉTÉ UN PIÈGE, PAS UNE SOLUTION.
   Ils ont été écrits ce soir pour réduire les onglets. MESURE : ils n'ont JAMAIS rien produit.
   Raison : `styles.css` porte DEUX définitions de `.jeu-cat` — celle-ci, et celle d'ORIGINE plus
   bas (l.23788). Le CSS ne choisit pas « la plus précise » : À SPÉCIFICITÉ ÉGALE, LA DERNIÈRE
   GAGNE. Celle du bas écrasait donc celle-ci, silencieusement, à chaque fois.
   ⚠️ ON LES VIDE AU LIEU DE LES SUPPRIMER : la trace explique pourquoi, et un futur lecteur qui les
   retrouverait remplis recommencerait l'erreur. La vraie définition reste l'ORIGINE (l.23788),
   corrigée au point 1 — UNE valeur, UN endroit.
   ⚠️ LEÇON RETENUE : avant d'écrire une règle CSS sur un composant, chercher TOUTES ses
   définitions dans le fichier (`grep -n "^.jeu-cat {"`), pas seulement celle qu'on croit viser. */
/* .jeu-cat { gap: 4.9px; padding: 9.1px 7px 7.7px; border-radius: 9px; font-size: 10.5px; } */
/*   ^ neutralisé : écrasé par la définition d'origine (l.23788), qui est plus bas dans le fichier. */
/* ⚠️⚠️ NEUTRALISÉ POUR LA MÊME RAISON QUE `.jeu-cat` CI-DESSUS (voir le commentaire juste avant) :
   cette pastille de 27 px n'a jamais été à l'écran — la définition d'origine (l.23793, plus bas)
   imposait 38 px. La taille réelle se règle donc LÀ-BAS, et nulle part ailleurs. */
/* .jeu-cat-ic { width: 27px; height: 27px; border-radius: 8px; font-size: 11px; } */
/* ⚠️ LE SVG PORTE `width="22" height="22"` EN ATTRIBUTS DANS LE HTML (app.js) : une règle CSS
   `width`/`height` sur l'élément les écrase — on l'écrit pour que l'intention soit lisible, et
   bornée à l'onglet (jamais un SVG d'ailleurs). */
/* ⚠️ LE GLYPHE REPREND 15 px — c'est LUI que le client appelle « les icônes des onglets »
   (« agrandis-moi les widgets ET ICONES »). Rapport conservé : 15/27 = 0,556, comme la pastille. */
.jeu-cat-ic svg { width: 15px; height: 15px; }
/* ⚠️ LE PLANCHER PASSE DE 118 À 106 px. Pourquoi : avec `1fr`, la piste recevait 701/6 ≈ 117 px —
   c'est donc bien le plancher de 118 qui décidait de rejeter le 6e. À 106 : 6 × 106 + 5 × 6,3 = 667 px,
   sous les 701 px mesurés. C'est le GARDE-FOU qui rend la tenue robuste, pas une marge devinée. */
/* ⚠️⚠️ LES COLONNES DES ONGLETS SE DÉCIDENT DANS `direction.html`, ET NULLE PART AILLEURS.
   La règle y porte `#jeu-cats.jeu-cats` (un identifiant) : à `!important` égal, elle gagne TOUJOURS
   contre un sélecteur de classe. En garder une seconde ici aurait créé DEUX sources de vérité pour le
   même calcul — et c'est précisément ce qui a produit le défaut (« repeat(5, …) » ici, « 6 onglets »
   dans la donnée). On laisse donc ce fichier muet sur ce point, et on le DIT. */

/* ── LE REPLI : SOUS LA SOMME DES DEUX PLANCHERS, ON EMPILE ───────────────────
   560 (réglages) + 480 (aperçu) + 18 (gouttière) = 1058px. En dessous, les deux
   colonnes ne peuvent PAS tenir : `minmax(560px,1fr)` refuserait de descendre et
   la page déborderait horizontalement. On empile donc, exactement comme le faisait
   déjà le `@media (max-width: 1080px)` existant — le palier est seulement relevé de
   1080 à 1120 pour dégager la marge. La colonne redevient alors fluide : sa
   largeur est bornée par le HAUT (`max-width`) et par le BAS (la fenêtre), donc
   elle ne peut pas déborder. */

/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   L'ATELIER ET LES ONGLETS EN PLEINE LARGEUR, LA TABLETTE FACE AUX RÉGLAGES
   Client : « au lieu de retourner à la ligne, j'aimerais bien que tu fasses descendre la tablette en
   bas pour la mettre en face des réglages » + « étirer l'onglet Templates des pré-réglages et l'onglet
   des onglets » + « rétrécir les onglets et la taille des widgets des onglets par 30 % garde 70 % ».

   ⚠️⚠️ AUCUNE DÉCLARATION `grid-template-areas` ICI, ET C'EST DÉLIBÉRÉ : ma première tentative en
   avait posé une (« roues » / « onglets » / « reglages preview ») et c'est INVALIDE — toutes les
   rangées d'un `grid-template-areas` doivent déclarer le MÊME nombre de colonnes. Le navigateur
   rejetait donc la déclaration ENTIÈRE, sans le dire, et la grille retombait sur ses rangées
   implicites. On obtient le même résultat sans nommer quoi que ce soit : `grid-column: 1 / -1`
   suffit à forcer une rangée entière, et le placement automatique fait le reste.

   ⚠️⚠️ LES DEUX BLOCS ONT ÉTÉ SORTIS DE `.jeu-col-reglages` (voir app.js) : ils en étaient les
   ENFANTS, donc enfermés dans la colonne de gauche — et un élément qui n'est pas dans une grille ne
   peut pas être étiré par elle. MESURE DU DÉFAUT : 6 onglets × 134 px = 804 px pour 701 px
   disponibles ⇒ le 6e, « Reçu », tombait SEUL sur une seconde ligne.

   ⚠️ `grid-column: 1 / -1` ET NON `1 / 3` : `-1` = la DERNIÈRE ligne de la grille, quel que soit le
   nombre de colonnes. Le repli `@media (max-width: 1120px)` passe la grille à UNE colonne : les deux
   rangées se replient donc AUTOMATIQUEMENT, sans une ligne à écrire pour ça. Un `1 / 3` figé aurait
   forcé deux colonnes même sur un écran étroit.

   ⚠️ AUCUN `!important` : `grid-column` n'est déclaré nulle part ailleurs, il n'y a donc rien à
   battre. En ajouter un « par précaution » aurait menti sur la nature du problème. */
.jeu-atelier-roues { grid-column: 1 / -1; }
.jeu-cats-gaine   { grid-column: 1 / -1; }

/* ⚠️ LE RÉTRÉCISSEMENT À 70 % (demande explicite : « par 30 % garde 70 % »).
   ⚠️⚠️ ON RÉDUIT LES DIMENSIONS RÉELLES, JAMAIS `transform: scale` : un `scale` ne change pas la
   place RÉSERVÉE, donc les onglets se chevaucheraient au lieu de tenir sur la ligne. Ici la place
   occupée diminue pour de vrai — c'est la seule façon d'en faire tenir six.
   Les proportions sont conservées : icône 38→27 px (glyphe 15→11 px, soit 39,5 % de la pastille,
   exactement le rapport d'avant) · coins 13→9 px et 11→8 px (ratio 0,342 conservé) · `gap` 9→6,3 px
   (il fait partie de la place occupée, on le réduit donc aussi) · `padding` 13/10/11 → 9,1/7/7,7 px.
   ⚠️ AUCUNE COULEUR, AUCUNE OMBRE, AUCUN ÉTAT N'EST TOUCHÉ : l'actif reste fond sombre / texte blanc. */
/* ⚠️ LE SVG PORTE `width="22" height="22"` EN ATTRIBUTS DANS LE HTML (app.js) : une règle CSS
   `width`/`height` sur l'élément les écrase — on l'écrit pour que l'intention soit lisible. */
/* ⚠️ LE PLANCHER PASSE DE 118 À 106 px. Pourquoi : avec `1fr`, la piste recevait 701/6 ≈ 117 px —
   c'est donc bien le plancher de 118 qui décidait de rejeter le 6e. À 106 : 6 × 106 + 5 × 6,3 = 667 px,
   sous les 701 px mesurés. C'est le GARDE-FOU qui rend la tenue robuste, pas une marge devinée. */
/* ⚠️⚠️ LES COLONNES DES ONGLETS SE DÉCIDENT DANS `direction.html`, ET NULLE PART AILLEURS.
   La règle y porte `#jeu-cats.jeu-cats` (un identifiant) : à `!important` égal, elle gagne TOUJOURS
   contre un sélecteur de classe. En garder une seconde ici aurait créé DEUX sources de vérité pour le
   même calcul — et c'est précisément ce qui a produit le défaut (« repeat(5, …) » ici, « 6 onglets »
   dans la donnée). On laisse donc ce fichier muet sur ce point, et on le DIT. */


@media (max-width: 1120px) {
  /* ⚠️⚠️ AUCUNE ZONE NOMMÉE ICI, ET C'EST VOULU : les zones de ma première tentative ont été retirées
     (elles étaient invalides et laissaient des noms orphelins). Le repli n'a donc besoin de RIEN
     d'autre que la mise en une colonne : les deux blocs en pleine largeur se placent d'eux-mêmes sur
     leurs propres rangées (grâce à `grid-column: 1 / -1`), et les deux colonnes de la rangée 3
     s'empilent parce qu'il n'y a plus qu'UNE colonne à occuper. C'est le même mécanisme qui décide
     dans les deux cas — il n'y a rien à resynchroniser. */
  .jeu-atelier {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .jeu-col-preview {
    justify-self: center !important;
    max-width: 100% !important;
  }
  .jeu-col-reglages {
    min-width: 0 !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE DISQUE EST UN CERCLE — LA LEÇON DU MATIN, APPLIQUÉE ICI
   MESURE : le disque sortait a 300x484 — une ELLIPSE. Cause : une regle plus haut
   posait `height: 100% !important` sur le cadre. Or QUAND LARGEUR ET HAUTEUR SONT
   TOUTES DEUX DETERMINEES, `aspect-ratio` EST IGNORE — ce n'est pas une question de
   priorite mais de sens (la meme lecon a coute 3 fois ce matin).
   Et le `300` vient de mon propre `width: 100%` : j'avais remplace le `84 %` sans
   retirer le `height: 100%` qui l'accompagnait.
   LA SORTIE : on ne laisse QUE l'`aspect-ratio` decider, en `height: auto`. Le cadre
   prend la largeur de la scene, et sa hauteur en decoule — donc un disque CARRE a
   l'ouverture, avec le support reserve en bas (1 / 1.19).
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame {
  width: 100% !important;
  max-width: 100% !important;
  height: auto !important;         /* la HAUTEUR vient du RAPPORT, plus d'un parent */
  max-height: 100% !important;     /* mais elle ne depasse jamais la scene */
  aspect-ratio: 1 / 1.19 !important;
  display: block !important;
  place-items: initial !important;
}
/* Le disque prend la largeur du cadre, la hauteur en decoule : CARRE par construction. */
.tab-roue .wheel-shell {
  position: relative !important;
  top: auto !important; left: auto !important; right: auto !important;
  transform: none !important;
  width: 100% !important;
  height: auto !important;
  aspect-ratio: 1 / 1 !important;
  margin: 0 auto;
}
/* Le rotor prend la boite de son shell : donnee, pas deduite. */
.tab-roue .wheel-rotor {
  position: absolute !important; inset: 0 !important;
  width: 100% !important; height: 100% !important;
  aspect-ratio: auto !important;
}
/* Le support se pose au bas exact du disque, dans le flux. */
.tab-roue .wheel-stand {
  position: absolute !important;
  top: 100% !important;
  left: 50% !important;
  transform: translateX(-50%) !important;
  margin-top: 0 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE ROTOR ÉTAIT PLAFONNÉ À 300px — `max-width` GAGNE TOUJOURS SUR `width`
   MESURE, demandée au navigateur (diag-regles-cadre-roue.js) :
      cadre  524x624   OK  (aspect 1/1.19)
      shell  524x524   OK  CARRE
      rotor  300x524   <<< c'est LUI
   Le rotor recevait `width: 100% !important` de ma règle — mais un `max-width: 300px`
   posé plus haut (vestige du moment où la colonne faisait 314px) le PLAFONNAIT.
   C'est une règle du langage, pas de priorité : **`max-width` gagne toujours sur
   `width`**, même quand le `width` est `!important` et pas le `max-width`. Les deux
   sont des contraintes SÉPARÉES dont la plus petite gagne.
   D'où une boîte de 300 de large et 524 de haut : une ELLIPSE au lieu d'un cercle.
   Il faut donc neutraliser le plafond, pas renforcer la largeur.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-rotor {
  max-width: none !important;
  min-width: 0 !important;
  max-height: none !important;
}
/* Même traitement pour le shell et les ampoules : aucun plafond hérité ne doit survivre. */
.tab-roue .wheel-shell {
  max-width: none !important;
  max-height: none !important;
}
.tab-roue .wheel-studs {
  max-width: none !important;
  max-height: none !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE GARDE SA COURSE — 84 %, ENFIN AU BON NIVEAU
   MESURE : a 100 %, le disque occupe 484px d'un verre de 500 → 97 %. Il ne restait
   que 3 % de marge, alors que le reglage de position deplace la roue de ±10 % : il
   ne pouvait donc quasiment pas agir.
   Un `84 %` avait deja ete pose pour ça, mais il BRIDAIT la roue quand la colonne
   faisait 314px. Ce n'est plus le cas : la colonne fait 566px et la roue y tient
   largement. Les 84 % rendent donc une marge de 8 % de chaque cote — exactement la
   course du reglage — sans rien retirer a la lisibilite (le disque reste le plus gros
   element de l'ecran).
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue { padding: 0 !important; }
/* ══ LE BON COMPROMIS : 90 % ═════════════════════════════════════════════════════
   MESURE des deux extremes :
     100 % -> la roue occupe 73 % de la hauteur du verre, mais il ne reste que 3 % de
              marge : le reglage de position (±10 %) ne peut quasiment pas agir ;
      84 % -> la roue tombe a 64 % de la hauteur.
   A 90 %, la roue reste DOMINANTE et il reste 5 % de marge de chaque cote — ce qui
   correspond exactement a la course du reglage, ramenee a ±5 % (voir app.js).
   Une marge utile et coherente vaut mieux qu'une marge theorique inutilisable. */
.tab-roue .wheel-frame {
  width: 90% !important;
  max-width: 90% !important;
  height: auto !important;
  aspect-ratio: 1 / 1.19 !important;
  margin: 0 auto !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES QUATRE DÉBORDEMENTS DE L'ATELIER — LA CAUSE EST UNE PISTE `1fr` SANS PLANCHER
   MESURÉ (audit-debordements.js, les seuls de toute l'application) :
      .jeu-fond-cadre-zone   +40 à +50px H
      .jeu-pos-cote          +10 à +23px H
      .jeu-pos-axe           +8px H
      INPUT (le curseur)     +8px H

   LE MÉCANISME, une règle du langage et non de priorité : dans une grille, une piste
   `1fr` a une taille MINIMUM égale à `min-content`. Un `<input type="range">` a une
   largeur intrinsèque d'environ 130px, et un libellé de 96px en face : la somme dépasse
   la place disponible, et la piste REFUSE de descendre — elle déborde au lieu de
   comprimer. Le remède est donc `minmax(0, …)` sur la piste, et `min-width: 0` sur les
   enfants : on autorise la piste à être plus petite que son contenu, et le contenu à
   se comprimer.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA POSITION DE L'IMAGE ──────────────────────────────────────────────────
   La vignette et sa colonne latérale. La piste fixe fait 340px au plus : sur une
   colonne de réglages étroite (1280px de fenêtre), il ne reste presque rien pour le
   côté — donc la piste `1fr` débordait. On la borne, et on autorise la compression. */
.jeu-fond-cadre-zone {
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
}
.jeu-fond-cadre-zone > * { min-width: 0; }
.jeu-fond-cadre-cote {
  min-width: 0;
  max-width: 100%;
}
.jeu-fond-cadre-cote > * { min-width: 0; max-width: 100%; }
.jeu-fond-pos {
  overflow-wrap: anywhere;   /* un pourcentage long ne pousse plus la colonne */
}

/* ── LA POSITION DE LA ROUE ──────────────────────────────────────────────────
   Le pavé et sa colonne latérale. `auto` sur la première piste prenait la largeur du
   pavé (3x34 + 2 gouttières = 108px) : correct. C'est la seconde qui débordait. */
.jeu-pos {
  grid-template-columns: auto minmax(0, 1fr);
}
.jeu-pos-cote, .jeu-pos-cote > * { min-width: 0; max-width: 100%; }

/* ── LES DEUX CURSEURS ───────────────────────────────────────────────────────
   `96px 1fr` : le libellé fixe plus un curseur qui ne descend pas. On passe à une
   grille COMPRESSIBLE, et le curseur reçoit `width: 100%` ET `min-width: 0` — un
   `<input type=range>` a une largeur intrinsèque qui gagne sinon sur la piste. */
.jeu-pos-axe {
  grid-template-columns: minmax(0, 96px) minmax(0, 1fr);
  min-width: 0;
}
.jeu-pos-axe > * { min-width: 0; }
.jeu-pos-axe input[type="range"] {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}
.jeu-pos-v {
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* ── LE PAVÉ GARDE SA TAILLE TACTILE ─────────────────────────────────────────
   Les 34px par case sont la cible du doigt : ils ne se réduisent pas. C'est pourquoi
   le pavé reste en `auto` — c'est la colonne latérale qui cède, pas lui. */
.jeu-pad { flex: 0 0 auto; }

/* ── SUR UNE COLONNE TRÈS ÉTROITE, ON EMPILE ─────────────────────────────────
   Sous 420px de colonne de réglages, les deux colonnes ne peuvent plus cohabiter :
   on empile, comme le fait déjà `.jeu-fond-cadre-zone` sous 560px de fenêtre. */
@media (max-width: 1400px) {
  .jeu-pos { grid-template-columns: minmax(0, 1fr); }
  .jeu-pos-cote { justify-items: stretch; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   MON DIAGNOSTIC PRÉCÉDENT ÉTAIT FAUX — VOICI LE VRAI
   MESURÉ (diag-chaine-debordement.js, fenêtre 1280px) :
      .jeu-fond-cadre-zone   largeur 251   grille = « 237px 0px »
                                                 ^^^^^^^^^^ LA 2e PISTE FAIT ZÉRO
      .jeu-fond-cadre-cote   largeur 0      scrollWidth 70   →  DEBORDE de 70px

   La cause n'est PAS l'absence de `min-width: 0` — celui-ci y était déjà, et mon
   correctif a EMPIRÉ la situation (+40 → +70px). La vraie cause :
   · la zone est un ENFANT de `.jeu-curseurs`, une grille à DEUX COLONNES de 251px ;
   · dedans, la vignette a un `minmax(200px, 340px)` qui prend 237px ;
   · il ne RESTE donc que 14px pour la colonne latérale, qui a un plancher intrinsèque
     d'environ 34px (ses boutons et son texte) ;
   · une piste qui refuse de descendre sous son contenu DÉBORDE au lieu de comprimer.

   La vraie sortie n'est pas de comprimer davantage : c'est d'EMPILER quand la place
   manque. La colonne latérale passe SOUS la vignette, sur toute la largeur — elle a
   alors 251px au lieu de 14, et plus rien ne déborde.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA POSITION DE L'IMAGE : ON EMPILE DÈS QUE LA COLONNE EST ÉTROITE ────────
   Le palier est en `@container`-style : on cible la largeur de la ZONE elle-même,
   pas de la fenêtre — car c'est elle qui manque de place (elle vit dans une grille
   à deux colonnes à l'intérieur du bloc). */
.jeu-fond-cadre-zone {
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
}
.jeu-fond-cadre-cote {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;   /* valeur | aide | bouton */
  align-items: center;
  gap: 10px;
  justify-items: start;
}
.jeu-fond-cadre-cote > .jeu-aide-mini { grid-column: 1 / -1; }   /* l'aide en dessous */
.jeu-fond-cadre-cote > * { min-width: 0; max-width: 100%; }

/* ── ET QUAND LA PLACE REVIENT (fenêtre large), ON REMET CÔTE À CÔTE ─────────
   Sur une colonne de réglages confortable (>= 900px de fenêtre), la vignette et son
   côté cohabitent sans se marcher dessus. */
/* ── PAS DE RETOUR AU CÔTÉ-À-CÔTÉ ═════════════════════════════════════════════
   J'avais prévu de remettre la vignette et son côté côte à côte sur les grands
   écrans. MESURÉ : à 1920px, la colonne fait 834px, la vignette 300px — et le côté
   déborde encore de 69px, parce que ses TROIS enfants (la valeur, l'aide, le bouton)
   ont chacun un plancher intrinsèque d'environ 34px qui s'additionne sur une seule
   ligne.
   L'empilement, lui, ne dépend d'aucune largeur : il ne peut pas déborder. On le
   garde donc PARTOUT. C'est aussi plus lisible : la vignette au-dessus, ses réglages
   en dessous, dans l'ordre où on s'en sert.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LA POSITION DE LA ROUE : MÊME TRAITEMENT ────────────────────────────────
   Mesure : `.jeu-pos-cote` tombait à 217px et débordait de 8px à certaines largeurs.
   La colonne latérale passe sous le pavé, sur toute la largeur. */
.jeu-pos {
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
}
.jeu-pos-cote { justify-items: stretch; }
.jeu-pos-cote > * { min-width: 0; max-width: 100%; }

/* ── LES CURSEURS : UNE GRILLE QUI PEUT SE COMPRIMER ─────────────────────────
   `96px 1fr` : le libellé fixe plus un `<input type=range>` dont la largeur
   intrinsèque (~130px) refuse de descendre. On rend la piste comprimable et on
   borne l'input. */
.jeu-pos-axe {
  grid-template-columns: minmax(0, 92px) minmax(0, 1fr);
  min-width: 0;
}
.jeu-pos-axe > * { min-width: 0; }
.jeu-pos-axe input[type="range"] { width: 100%; min-width: 0; max-width: 100%; }

/* ── LE PAVÉ GARDE SA TAILLE TACTILE ─────────────────────────────────────────
   Les 34px par case sont la cible du doigt : elles ne se réduisent pas. Le pavé est
   donc centré plutôt que comprimé. */
.jeu-pad { justify-self: start; flex: 0 0 auto; }

/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE GAGNE ENCORE UN PEU DE HAUTEUR
   MESURE : la roue occupe 68 % de la hauteur du verre. C'est deja « toute la roue
   visible », mais le client a ete clair : « on va voir TOUTE la roue avec son
   support ». On resserre donc la barre et le pied — ils disent « c'est une tablette »
   sans avoir besoin de prendre 14 % de la hauteur.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-verre { grid-template-rows: minmax(0, 5cqh) minmax(0, 1fr) minmax(0, 6cqh) !important; }
.tab-barre { padding: 1cqw 3cqw 0.4cqw !important; }
.tab-marque { font-size: 2.1cqw !important; }
.tab-vue { font-size: 1.7cqw !important; }
.tab-qui { width: 3.6cqw !important; height: 3.6cqw !important; font-size: 1.5cqw !important; }
.tab-jeu-pied { padding: 0 3cqw 1cqw !important; }
.tab-jeu-pied b { font-size: 2.2cqw !important; }
.tab-jeu-pied span { font-size: 1.6cqw !important; }
/* Le cadre reprend la place rendue : 90 % de la largeur, et la hauteur suit. */
.tab-roue .wheel-frame { width: 94% !important; max-width: 94% !important; }


/* ══════════════════════════════════════════════════════════════════════════════
   LA COUCHE DE FOND — LE FLOU CESSE DE FRAPPER LA ROUE
   Client : « la tablette preview est flou ».

   MESURE : le fond etait pose en `style` inline sur `.tab-verre`, avec son
   `filter: blur(Npx)`. Un filtre s'applique a TOUT le contenu de l'element : la roue
   etait floue parce que le FOND l'etait. Et son `scale(zoom)` agrandissait aussi la
   roue. En base, `blur` vaut 6, 4 et 3 sur trois des cinq roues : le defaut etait donc
   visible des l'ouverture.

   Le fond vit maintenant dans sa PROPRE couche, en absolu et DERRIERE le contenu.
   Elle porte l'image, le zoom, le flou, la position — et rien d'autre.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-verre {
  /* Le cadre de l'ecran : NET. Aucun filtre, aucune transformation ici — sinon tout
     ce qu'il contient les subirait. */
  filter: none !important;
  transform: none !important;
  position: relative;      /* ancre de la couche de fond */
  isolation: isolate;      /* le fond et le contenu restent sur deux plans distincts */
}

.tab-fond {
  position: absolute;
  /* Depasse legerement : un flou rogne aux bords laisserait un liseré clair sur le
     pourtour, la ou le flou est tronque. On deborde donc de 6 %, et `overflow: hidden`
     du verre ramene proprement au cadre. */
  inset: -6%;
  z-index: 0;
  pointer-events: none;    /* purement decoratif */
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;
  /* La couche porte le filtre ET le zoom : elle seule. */
  will-change: transform;
}

/* Le contenu de l'ecran passe AU-DESSUS du fond. */
.tab-verre > .tab-jeu-ecran,
.tab-verre > .tab-voile {
  position: relative;
  z-index: 1;
}

/* Le voile d'assombrissement : au-dessus du fond, sous le contenu. */
.tab-verre > .tab-voile { z-index: 1; }
.tab-verre > .tab-jeu-ecran { z-index: 2; }

/* ══════════════════════════════════════════════════════════════════════════════
   `cqh` NE SE RÉSOUT PAS SANS `container-type: SIZE` — LA BARRE VOLAIT 11,5 %
   MESURÉ (diag-budget-hauteur.js, fenêtre 1600px) :
      la BARRE         84px  = 11,5 % de la hauteur du verre
      le PIED         108px  = 14,8 %
      la ROUE         559px  = 77,0 %
   Ma règle `grid-template-rows: 5cqh 1fr 6cqh` visait 5 % et 6 %. Elle ne s'appliquait
   PAS : `cqh` (l'unité de hauteur du conteneur) n'existe que si le conteneur est déclaré
   `container-type: size`. Il était en `inline-size` — donc seules les unités `cqw`
   étaient disponibles, et les rangées retombaient sur leur contenu.

   On passe donc au POURCENTAGE : sur une rangée de grille dont le conteneur a une
   hauteur DÉFINIE (le verre est en `inset: 0`), un `%` de rangée se résout contre cette
   hauteur. C'est exactement ce qu'on veut, et ça ne dépend d'aucune unité de conteneur.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-verre {
  grid-template-rows: 6% minmax(0, 1fr) 7% !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   ET LE PLAFOND RÉEL DE LA ROUE : 82 %, PAS 88 %
   Le calcul, vérifié à la mesure :
      hauteur_verre / largeur_verre = 1,458   (une tablette 7:10 = 1,43)
      hauteur_cadre / largeur_cadre = 1,19    (disque carré + place du support)
   Donc  roue / verre en hauteur = 1,19 / 1,458 x (largeur_roue / largeur_verre)
                                = 0,816 x (largeur_roue / largeur_verre)
   A 90 % de largeur -> 73 %. A 100 % -> 82 %.
   **88 % est donc INATTEIGNABLE** : mon objectif était faux, pas le travail de l'agent.
   Et viser 100 % de largeur supprimerait la course du réglage de position.
   L'objectif juste est 78-80 % : la roue occupe alors tout ce qui reste une fois la
   barre et le pied réduits à leur rôle.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame { width: 97% !important; max-width: 97% !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   LES RANGÉES SONT SUR `.tab-jeu-ecran`, PAS SUR LE VERRE
   MESURÉ (diag-barre-hauteur.js) :
      le VERRE        gridRows = 43,7 / 633,6 / 51px   ← ma règle 6 % / 7 % S'APPLIQUE
      .tab-jeu-ecran  gridRows = 84 / 536 / 108px      ← MAIS IL A LES SIENNES
      la SCENE        536px alors que la roue fait 577 ← la roue DÉBORDE sa rangée de 41px

   La cause : `.tab-jeu-ecran` porte `grid-row: 1 / -1` — il couvre TOUTE la hauteur du
   verre et gère ses propres trois rangées. Mes règles en pourcentage sur `.tab-verre`
   n'avaient donc aucune prise : rien n'est placé dans ses rangées individuelles.
   On corrige donc sur le BON élément. Et ce n'est pas cosmétique : la roue débordait de
   41px de sa rangée, donc elle n'était pas centrée dans son espace.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-ecran {
  grid-template-rows: 6% minmax(0, 1fr) 7% !important;
}
/* On annule la règle sans effet sur le verre : une règle qui ne s'applique pas est une
   fausse piste pour le prochain lecteur. */
.tab-verre {
  grid-template-rows: auto minmax(0, 1fr) auto !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE DÉBORDEMENT VIENT DE MA COUCHE DE FOND — c'est moi qui l'ai créé
   MESURÉ (audit-debordements.js) : trois nouveaux débordements apparus avec la couche
   de fond, et qui n'existaient pas avant elle :
      .jeu-apercu-zone  +6px      .tab-appareil  +6px      .tab-verre  +29px

   La cause est mon `inset: -6%` sur `.tab-fond` : la couche DÉBORDE volontairement pour
   que le flou ne soit pas rogné aux bords. Mais elle déborde de son parent, et comme le
   verre est en `overflow: hidden`… la couche pousse la chaîne quand même, parce qu'un
   `inset` négatif agrandit la boîte de l'enfant et le navigateur le compte dans le
   `scrollWidth` des ancêtres.

   LA SORTIE : on garde le débordement VISUEL (nécessaire au flou) mais on l'isole de la
   mesure — la couche est dans un conteneur qui la rogne, et ce conteneur a EXACTEMENT la
   taille du verre. Un débordement à l'intérieur d'un `overflow: hidden` ne compte plus
   comme débordement de la page : c'est son métier.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-verre {
  overflow: hidden !important;     /* le flou ne bave pas hors du cadre, et la mesure est juste */
}
/* La couche reste en `inset: -6%` DANS le verre : elle déborde visuellement sans jamais
   peser sur la mise en page des ancêtres. */
.tab-fond {
  inset: -6% !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LA COUCHE DE FOND DÉBORDAIT DE 30px DE CHAQUE CÔTÉ — `inset: -6%` S'EN VA
   MESURÉ (diag-6px.js) :
      .tab-fond      larg=559  inset=-29,97px/-29,97px   ← 30px DE CHAQUE CÔTÉ
      .tab-verre     client=500  scroll=529  DEBORDE=29
      .tab-appareil  DEBORDE=6
      .jeu-apercu-zone DEBORDE=6

   Le `inset: -6%` était prévu pour que le flou ne soit pas rogné aux bords. Mais il
   agrandit la boîte de l'enfant, et le navigateur compte ce débordement dans le
   `scrollWidth` de tous les ancêtres — donc trois débordements, dont deux au-delà du
   châssis. Et mon `overflow: hidden` sur le verre n'a pas suffi à les isoler.

   LA SORTIE, plus simple et plus sûre : la couche prend EXACTEMENT la taille du verre
   (`inset: 0`). Le flou est alors rogné aux bords — un liseré très léger sur le
   pourtour, à peine visible à 3-6px de flou, et qui vaut mieux que trois débordements.
   Un réglage décoratif ne doit jamais faire déborder la page.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-fond {
  inset: 0 !important;
  transform-origin: center center;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES AMPOULES ÉLARGISSENT LE SHELL DE 12px — LA SOURCE UNIQUE DES 3 DÉBORDEMENTS
   MESURÉ (diag-qui-deborde.js), en remontant la chaîne :
      .wheel-shell   DEBORDE de 12px      <<< LA FEUILLE COUPABLE
      .wheel-frame   DEBORDE de 12px
      .tab-roue      DEBORDE de 5px
      .tab-verre     DEBORDE de 5px
      .tab-appareil  DEBORDE de 6px
      .jeu-apercu-zone DEBORDE de 6px

   La cause : les 24 ampoules du contour sont posées SUR le bord du disque. Celles de
   gauche et de droite (à 90° et 270°) dépassent donc du shell de la moitié de leur
   largeur — 12px au total, 6 de chaque côté. Ce débordement VISUEL est voulu (les
   ampoules doivent être sur le bord), mais il ne doit pas peser sur la mise en page.
   Or `overflow: visible` le laisse remonter jusqu'à la colonne.

   LA SORTIE : le cadre de la roue rogne ce débordement (`overflow: hidden`), et on lui
   rend la place par un `padding` latéral — ainsi les ampoules sont ENTIÈRES et visibles,
   mais elles ne débordent plus de la chaîne. C'est la même leçon que la couche de fond :
   un décor qui sort de sa boîte doit être rogné par SA boîte, jamais par la page.
   ══════════════════════════════════════════════════════════════════════════════ */
/* ══ LE CADRE NE DOIT PLUS ROGNER LE HAUT — C'EST MON PROPRE CORRECTIF QUI COUPAIT ══
   MESURÉ (et c'est le client qui a raison contre ma géométrie) :
      j'avais posé `overflow: hidden` + `padding: 0 2.6cqw` pour arrêter le débordement
      des 24 ampoules. Mais `padding: 0` en VERTICAL veut dire que le cadre rogne le haut
      et le bas — donc LE DISQUE et L'AIGUILLE, qui dépasse en `top: -5px`.
   Mes mesures disaient « 83px de marge au-dessus » : `getBoundingClientRect` donne la
   boîte NON rognée par un ancêtre, il ne voit donc pas le rognage. La géométrie ne
   pouvait pas le détecter — seule la capture du client le montrait.

   LA SORTIE : le cadre rend la place EN HAUT et EN BAS aussi, et on rogne seulement ce
   qui dépasse vraiment. Le padding vertical laisse respirer le disque et l'aiguille ;
   `overflow: clip` + `overflow-clip-margin` permettent de rogner plus loin que la boîte
   seulement là où c'est nécessaire (les ampoules latérales). */
/* ══ ROGNER EN LARGEUR SEULEMENT — L'AXE VERTICAL DOIT RESTER LIBRE ═══════════
   MESURÉ après un premier essai : le disque était entier, mais L'AIGUILLE restait rognée
   de **19 px**. La cause est structurelle : l'aiguille est posée en `top: -5px`, donc
   AU-DESSUS de la boîte de son cadre. Un `padding` horizontal ou vertical n'y change
   RIEN — les décalages d'un élément en `position: absolute` se mesurent depuis la boîte
   de PADDING du conteneur, pas depuis son bord extérieur. Le padding descend le contenu,
   il ne remonte pas la boîte.

   LA SORTIE : on ne rogne QUE l'axe horizontal (les ampoules latérales) et on laisse le
   vertical déborder librement. `overflow-x: clip` + `overflow-y: visible` est une
   combinaison légale — contrairement à `hidden` + `visible`, que le navigateur force à
   `auto`, `clip` n'impose pas de conteneur de défilement.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame {
  overflow-x: clip !important;
  overflow-y: visible !important;
  /* Le padding horizontal rend aux ampoules la place qu'elles occupent (12px de chaque
     côté à cette échelle). Le vertical reste à zéro : plus rien à rogner en haut et en bas. */
  padding: 0 2.6cqw !important;
  box-sizing: border-box !important;
}
/* La croix de sortie gagne un peu de cible tactile (28 -> 34 px). */
.tab-quitter {
  width: 2.55em !important;
  height: 2.55em !important;
}
/* ══ LE POINT SOMBRE AU-DESSUS DE L'AIGUILLE — LE DÉCOR QUI FAISAIT « COUPÉ » ══
   `.wheel-pointer:after` posait un rond sombre de 12px à `top: -39px`, donc 39px
   AU-DESSUS du triangle. À l'écran, ce rond flottait hors de la roue et se lisait comme
   un artefact — très exactement ce que le client décrit par « c'est coupé au niveau de
   l'aiguille ». Il ne sert à rien : l'aiguille est un triangle, elle n'a pas besoin d'un
   pivot apparent. On le retire. */
.tab-roue .wheel-pointer::after { display: none !important; }
/* Le disque, lui, reste dans le cadre : il ne profite pas du padding. */
.tab-roue .wheel-shell {
  margin: 0 auto;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES DEUX DERNIERS 6px : LE BOUTON LATÉRAL DE LA TABLETTE
   MESURÉ (diag-qui-deborde.js) : `.tab-bouton` « depasse de 6px a DROITE », avec
   `inset = 534,7px / -5,87px`. C'est un débordement VOLONTAIRE : le bouton de volume
   d'une tablette affleure le bord du châssis, c'est ce qui la fait lire comme un
   appareil plutôt qu'un cadre.

   Mais il remonte la chaîne des `scrollWidth` : `.tab-appareil` +6, `.jeu-apercu-zone`
   +6. La page ne défile pas (les 6px sont dans la marge), mais la mesure le signale —
   et une mesure qui crie au loup fait perdre du temps au prochain lecteur.

   LA SORTIE : le CHÂSSIS rogne son propre bouton. Le bouton reste visible (il déborde
   de 6px sur un châssis de 534, donc il affleure le bord extérieur), mais son débordement
   ne pèse plus sur la chaîne. Même principe que les ampoules : chaque décor est rogné par
   SA boîte.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-appareil {
  overflow: hidden !important;
  /* Le châssis a un padding de 3,2cqw : le bouton doit rester collé au BORD EXTÉRIEUR,
     donc on le repositionne par rapport au châssis et non au contenu. */
}
.tab-bouton {
  position: absolute !important;
  right: 0 !important;
  top: 22% !important;
  width: 1.1cqw !important;
  height: 9% !important;
  border-radius: .6cqw 0 0 .6cqw !important;
  background: linear-gradient(270deg, #232527, #3a3d40) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   BUG 1 — LA COULEUR DU MOYEU N'ÉTAIT LUE PAR PERSONNE
   MESURÉ :
      le JS pose       --moyeu-couleur  et  --moyeu-encre   (app.js:3929)
      le CSS consomme  **0 occurrence**                     <<< PERSONNE NE LES LIT
   Le réglage marchait donc « dans le vide » : la variable était bien calculée, posée
   sur le cadre, et aucune règle ne la reprenait. C'est la MÊME erreur que le flou et
   les ampoules : une valeur produite d'un côté, jamais consommée de l'autre.

   On les câble enfin. Deux variables :
      --moyeu-couleur  le FOND du moyeu  (et le fond de la lettre)
      --moyeu-encre    la COULEUR DU TEXTE, déduite de la luminosité par le JS — pour
                       qu'un monogramme sombre sur fond sombre reste lisible sans qu'on
                       ait à régler une seconde couleur.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-hub {
  background: var(--moyeu-couleur, #fff) !important;
  color: var(--moyeu-encre, #202123) !important;
}
/* Le monogramme hérite de l'encre du moyeu (il est dans un `<span>`). */
.wheel-hub > span { color: var(--moyeu-encre, #202123) !important; }
/* Si le moyeu porte une IMAGE, c'est l'image qui remplit — pas de fond de couleur
   derrière elle, sinon un liseré teinté apparaîtrait sur les bords arrondis. */
.wheel-hub.a-image { background: var(--moyeu-couleur, #fff) !important; }
.wheel-hub-img { background: transparent !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   BUG 2 — LA COULEUR DES AMPOULES ÉTAIT ÉCRASÉE PAR SEPT RÈGLES
   MESURÉ : ma règle posait `background-color: var(--ampoule-couleur, …)`, mais les six
   règles PAR GABARIT utilisent le RACCOURCI `background:` :
      .roue-prisme   .roue-anneau   .roue-monolithe   .roue-neon
      .roue-mineral  .roue-rayures  .roue-ruban
   Or un raccourci `background:` REMET `background-color` à sa valeur initiale. Ces
   règles étant plus spécifiques (deux classes contre une pseudo-classe), elles
   gagnaient — et la couleur choisie disparaissait.

   On ne peut pas se contenter d'un `!important` sur la couleur : il faudrait aussi
   préserver le DÉGRADÉ de chaque matière. La solution est donc de poser la teinte
   DANS le dégradé, par les deux variables : `--ampoule-couleur` remplace la teinte
   médiane du gradient, `--ampoule-halo` remplace son halo.
   Sans choix du client, les deux retombent sur les valeurs d'origine — le rendu est
   donc identique à avant, matière par matière.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-studs i::before {
  /* La teinte : `--ampoule-couleur` si le client a choisi, sinon la teinte d'origine
     du gabarit. On déclare les deux valeurs par défaut ici, et chaque gabarit peut
     les surcharger (voir les blocs suivants). */
  --ampoule-teinte: #dfc27a;
  --ampoule-halo: rgba(255,225,150,.75);
  /* Le dégradé garde sa CONSTRUCTION (le point de lumière en 34%/28%, l'assombrissement
     vers l'extérieur) — seule la teinte change. C'est ce qui préserve la matière. */
  background: radial-gradient(circle at 34% 28%,
    #fff 0 16%,
    color-mix(in srgb, var(--ampoule-couleur, var(--ampoule-teinte)) 55%, #fff) 30%,
    var(--ampoule-couleur, var(--ampoule-teinte)) 52%,
    color-mix(in srgb, var(--ampoule-couleur, var(--ampoule-teinte)) 42%, #000) 78%,
    color-mix(in srgb, var(--ampoule-couleur, var(--ampoule-teinte)) 18%, #000)) !important;
  border-color: color-mix(in srgb, var(--ampoule-couleur, #fff7cd) 60%, #fff) !important;
  box-shadow: 0 0 6px 1px var(--ampoule-halo),
              0 0 16px 3px rgba(223,194,122,.35),
              0 2px 3px rgba(0,0,0,.5) !important;
}
/* Chaque matière garde SA teinte d'origine comme défaut — donc, sans choix du client,
   le rendu est exactement celui d'avant. */
.roue-prisme   .wheel-studs i::before { --ampoule-teinte: #ffb985; }
.roue-anneau   .wheel-studs i::before { --ampoule-teinte: #bd9150; }
.roue-monolithe .wheel-studs i::before { --ampoule-teinte: #adb4b6; }
.roue-neon     .wheel-studs i::before { --ampoule-teinte: #08d9d6; --ampoule-halo: rgba(120,240,238,.75); }
.roue-mineral  .wheel-studs i::before { --ampoule-teinte: #d8d3c8; }
.roue-rayures  .wheel-studs i::before,
.roue-ruban    .wheel-studs i::before { --ampoule-teinte: #f4f1ec; }
.roue-classique .wheel-studs i::before { --ampoule-teinte: #dfc27a; }

/* ══════════════════════════════════════════════════════════════════════════════
   L'IMAGE DU MOYEU NE REMPLISSAIT PAS LE MOYEU
   MESURÉ (diag-moyeu-image.js, roue « Imane — Heineken ») :
      le moyeu         52 × 52
      l'image          50 × 87    <<< PLUS HAUTE QUE SON MOYEU
      l'image charge    naturalWidth = 1200, naturalHeight = 2078  (rapport 0,58)
   `height: 100%` était IGNORÉ : un pourcentage de hauteur ne se résout que si la
   hauteur du parent est définie. L'image retombait donc sur son rapport naturel
   (50 × 2078/1200 = 87) et le moyeu la rognait — on ne voyait qu'une bande au milieu
   de la photo. C'est exactement « quand on les a, on ne les a pas sur les roues » : la
   balise est là, l'image charge, mais on n'en voit qu'un fragment mal cadré.

   LA SORTIE : on ne compte plus sur un pourcentage de hauteur. L'image est posée en
   ABSOLU dans le moyeu (`inset: 0`), qui est déjà `position: absolute` donc conteneur
   de référence. Elle remplit alors le moyeu EXACTEMENT, quelle que soit sa forme —
   et `object-fit: cover` la recadre proprement au centre, sans la déformer.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-hub-img {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  object-position: center center !important;
  border-radius: 0 !important;   /* c'est le moyeu qui arrondit, pas l'image */
  display: block !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE FOND UPLOADÉ NE S'AFFICHAIT PAS — IL ÉTAIT CACHÉ DERRIÈRE L'ÉCRAN DE JEU
   MESURÉ :
      .tab-fond       z-index: 0
      .tab-jeu-ecran  background: linear-gradient(168deg, rgba(14,15,16,.88), rgba(9,10,11,.94))
   L'écran de jeu posait un aplat sombre à 88-94 % d'opacité, PAR-DESSUS la couche de
   fond. Résultat : l'image téléversée existait, était servie, portait son zoom et son
   flou — et le client ne la voyait jamais. C'est « les images que j'upload… et le
   background ne s'affichent pas ». Le changement de couleur du fond (`fond_uni`) subissait
   exactement le même sort : la même couche le recouvrait.

   LA SORTIE : l'écran de jeu ne peint PLUS rien. C'est la COUCHE DE FOND qui est la
   source unique du décor — image OU aplat — et l'écran n'est plus qu'un assemblage
   transparent (barre, roue, bouton). Ainsi les trois réglages du client (image, zoom,
   position) et la couleur de fond produisent enfin un effet visible.

   ⚠️ La lisibilité du texte ne tient plus à l'aplat de l'écran, mais à l'OMBRE portée
   sur chaque texte de l'écran : c'est plus robuste, ça fonctionne sur n'importe quelle
   image, claire ou sombre — y compris sur une photo blanche où un aplat sombre fixe
   aurait été inutile.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-ecran {
  background: none !important;
}
/* La lisibilité sur photo : une ombre portée, et rien qui recouvre le décor. */
.tab-barre .tab-marque,
.tab-barre .tab-vue,
.tab-barre .tab-qui {
  text-shadow: 0 1px 3px rgba(0,0,0,.85), 0 0 10px rgba(0,0,0,.5) !important;
}
/* L'assombrissement réglé (`overlay`) reste disponible : c'est LUI qui doit tamiser le
   fond, proportionnellement, quand le client le demande — et non un aplat fixe. */
.tab-fond-voile {
  position: absolute;
  inset: 0;
  background: #000;
  pointer-events: none;
  z-index: 1;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON DE LANCER, SOUS LE SOCLE
   Client : « le bouton de lancer doit être en dessous du socle de la roue ».
   Il occupe la rangée du pied — qui est SOUS la scène, donc sous le socle. L'ordre du
   regard suit le geste : la roue, son support, le bouton.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  padding: 0 !important;
}
.tab-lancer {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5em;
  padding: .62em 1.9em;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #ffe9a8, #d4a574 55%, #b07f3e);
  color: #201a10;
  font: 600 1em/1 'Inter', system-ui, sans-serif;
  letter-spacing: .01em;
  cursor: pointer;
  /* Un seul relief : c'est un bouton d'action, il doit se lire au premier regard. */
  box-shadow: 0 2px 0 rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.55);
  transition: transform .16s ease, filter .16s ease;
}
.tab-lancer:hover { filter: brightness(1.07); transform: translateY(-1px); }
.tab-lancer:active { transform: translateY(1px); box-shadow: 0 1px 0 rgba(0,0,0,.35); }
.tab-lancer:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE SOCLE, PLUS GRAND
   Client : « le socle de la roue doit être un peu plus grand aussi ». Il passe de 11em
   à 14em de large et de 1,2em à 1,55em de haut, en `em` donc il suit la taille de la
   roue — vignette, aperçu et tablette grandissent ensemble, sans réglage séparé.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-stand-foot {
  width: 14em !important;
  height: 1.55em !important;
}
/* La barre : sobre, une seule ligne, pas de fond. Le logo téléversé prend la place
   du mot « OZELCA » dès qu'il existe. */
.tab-logo {
  height: 1.5em;
  width: auto;
  max-width: 8em;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.7));
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO ÉTAIT RENDU, MAIS À 12 × 21 px — ILLISIBLE
   MESURÉ (diag-fond-logo.js) : le logo du client fait 1200 × 2078 nativement, soit un
   rapport de 0,58 (une image HAUTE). Avec `height: 1.5em; width: auto`, il tombait donc
   à 12 px de large : présent dans le DOM, chargé, et invisible à l'œil.

   On lui donne donc une BOÎTE de largeur définie, dans laquelle il se loge par
   `object-fit: contain` — l'image garde ses proportions (elle n'est pas déformée), mais
   elle est CENTRÉE dans un espace assez large pour qu'un logo, quel que soit son
   rapport, reste lisible. C'est le seul réglage qui tienne pour un logo large, carré
   ou haut, sans avoir à connaître son format à l'avance.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-logo {
  height: 1.7em !important;
  width: 5.2em !important;
  max-width: none !important;
  object-fit: contain !important;
  object-position: left center !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE SOCLE ENCORE PLUS GRAND, ET LE SUPPORT PLUS MASSIF
   Client : « il faut agrandir le socle, le support en bas. Il a un petit support,
   je n'aime pas trop. »
   MESURÉ avant : le socle faisait 140x16 px pour un disque de 457 — soit 31 %, et le
   montant 15x26 px, à peine visible. On élargit le socle ET on épaissit le montant :
   un support qu'on remarque, sans qu'il vole la vedette à la roue.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-stand-foot {
  width: 19em !important;
  height: 2.1em !important;
}
.tab-roue .wheel-stand-post {
  width: 22px !important;
}
.tab-roue .wheel-stand-axis {
  height: 15px !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON « LANCER LA ROUE » PLUS GRAND ET CENTRÉ EN BAS
   Client : « le bouton lancer la roue, tu l'agrandis et tu le centres en bas. »
   MESURÉ avant : 144x31 px, soit 29 % de la largeur du verre — petit pour une cible
   tactile. Il passe à ~62 % de la largeur, avec une hauteur de doigt (≥ 48 px), et il
   reste centré (l'écart mesuré était déjà de 0px : on le préserve).
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied .tab-lancer {
  width: 62% !important;
  min-height: 3.1em !important;
  font-size: 1.14em !important;
  letter-spacing: .015em !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LA CROIX ROUGE DE SORTIE — SEUL ÉLÉMENT DE LA BARRE
   Client : « tu gardes juste un bouton X en rouge. Ça veut dire quitter. »
   Rouge et sobre : c'est une sortie, elle doit se lire comme telle. Taille tactile,
   cible de 32px minimum, et rien d'autre dans la barre.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre {
  justify-content: flex-end !important;
  padding: 0 .5em !important;
}
.tab-quitter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1em;
  height: 2.1em;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #e63946;                       /* le rouge de la charte */
  cursor: pointer;
  transition: background .15s ease, transform .15s ease;
}
.tab-quitter svg { width: 1.15em; height: 1.15em; display: block; }
.tab-quitter:hover { background: rgba(230,57,70,.16); transform: scale(1.06); }
.tab-quitter:focus-visible { outline: 2px solid #e63946; outline-offset: 2px; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE RÉCAPITULATIF DES CHANCES, DANS LA SECTION D'ASSIGNATION
   Client : « on doit avoir le pourcentage à donner pour chaque goodie quand on assigne la
   roue à une campagne ou à un événement ».
   Le pourcentage vivait UNIQUEMENT dans la section Goodies, plus haut dans la colonne des
   réglages — donc hors de l'écran au moment où l'on choisit à qui donner la roue. Il est
   maintenant aussi là où la décision se prend, en pastilles compactes et lisibles.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-recap {
  margin: 0 0 18px; padding: 14px 16px;
  border: 1px solid rgba(23,23,25,.10); border-radius: 12px;
  background: rgba(23,23,25,.035);
}
.jeu-recap-liste {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 10px;
}
.jeu-recap-item {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  border: 1px solid rgba(23,23,25,.12); background: #fff;
  font: 500 12.5px/1 Manrope, sans-serif; color: #4a4744;
}
.jeu-recap-item b {
  font: 700 13px/1 Manrope, sans-serif; color: #202123;
  font-variant-numeric: tabular-nums;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE TRAIT EST RETIRÉ — LA BARRE DEVIENT LE PORTE-LOGO DE LA MARQUE
   Client : « enlève le trait en haut, et puis en haut son trait, c'est là où on mettra le
   logo en PNG de la marque avec quoi on joue ».
   MESURÉ : le trait venait du `border-bottom: 1px solid rgba(255,255,255,.10)` de
   `.tab-barre` — plus l'aplat `background: rgba(255,255,255,.03)`. Les deux partent : la
   barre n'est plus une bande séparée, c'est l'espace du logo, ouvert sur le fond.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre {
  border-bottom: 0 !important;
  background: transparent !important;
  /* ══ LA BARRE RESTE DANS LE FLUX DE LA GRILLE — C'EST MON ERREUR QUI EST CORRIGÉE ══
     MESURÉ : je l'avais passée en `position: absolute` pour libérer une rangée au profit de
     la roue. Conséquence NON VOULUE : sortie du flux, la barre a laissé la SCÈNE et le PIED
     remonter d'une rangée. La scène s'est retrouvée dans la rangée de 0px, et le pied dans
     celle de 632px — donc le bouton, centré dans le pied, s'est posé AU MILIEU DE LA ROUE.
     C'est exactement ce que la capture du client montre.
     La barre reprend donc SA rangée : trois rangées en flux, barre | scène | pied. */
  position: relative !important;
  padding: 2cqw 3cqw 1cqw !important;
  /* Le logo est CENTRÉ : « le logo en haut, centré en haut ». */
  justify-content: center !important;
  align-items: center !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO DE LA MARQUE, AVEC SA LUEUR RÉGLABLE
   Client : « une lueur qui soit blanche ou noire externe selon le fond, et on puisse avoir
   le choix de choisir aussi la couleur de la lumière derrière le logo ».
   La lueur est un HALO EXTERNE (un `drop-shadow` sans décalage) : il épouse le contour du
   PNG détouré au lieu de poser un rectangle derrière. C'est la seule technique qui marche
   pour un logo à fond transparent, quel que soit son dessin.
   Sur un fond clair, un halo blanc rend le logo invisible : le JS bascule donc sur un halo
   NOIR. Le client n'a rien à régler — et s'il veut forcer une couleur, il la choisit.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-logo-marque {
  height: 2.9em;
  width: auto;
  max-width: 13em;
  display: block;
  object-fit: contain;
  object-position: left center;
  /* Le halo : externe, sans décalage, donc il suit le contour. L'intensité est réglable. */
  filter: drop-shadow(0 0 calc(.55em * var(--logo-halo-pct, 1)) var(--logo-halo, #fff))
          drop-shadow(0 0 calc(1.5em * var(--logo-halo-pct, 1))
                      color-mix(in srgb, var(--logo-halo, #fff) 55%, transparent));
}
/* Sans logo téléversé : le nom de la marque porte la même lueur (par la couleur du texte). */
.tab-marque {
  font: 800 2.6cqw/1 'Inter', system-ui, sans-serif;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #f4f1ec;
  text-shadow: 0 0 calc(.7em * var(--logo-halo-pct, 1)) var(--logo-halo, #fff),
               0 0 calc(2em * var(--logo-halo-pct, 1))
                color-mix(in srgb, var(--logo-halo, #fff) 50%, transparent);
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON DE LA ROUE REMONTE, ET « LANCER LA ROUE » DEVIENT UN VRAI BOUTON DE JEU
   Client : « le bouton de la roue doit être encore un peu plus haut. Et lancer la roue doit
   être bien écrit, de façon à ce que c'est un jeu. Essayez d'arranger le graphic design. »
   Trois changements :
     · la barre ne prend plus de rangée (elle est en absolu) -> le pied remonte ;
     · le bouton est plus haut de profil et plus massif ;
     · le libellé passe en capitales espacées avec un traitement de JEU : un dégradé doré,
       une ombre interne, et un éclat qui balaie la surface au survol.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied {
  padding-bottom: 1.4cqw !important;
}
.tab-jeu-pied .tab-lancer {
  width: 66% !important;
  min-height: 3.6em !important;
  padding: .9em 2.4em !important;
  font: 800 1.3em/1 'Inter', system-ui, sans-serif !important;
  letter-spacing: .19em !important;
  text-transform: uppercase !important;
  color: #2a1d08 !important;
  background: linear-gradient(180deg, #fff2c4 0%, #f0cf87 42%, #d4a574 68%, #a9762f 100%) !important;
  border: 0 !important;
  border-radius: 999px !important;
  box-shadow:
    0 3px 0 #7d5518,                                  /* l'épaisseur : le bouton a un socle */
    0 6px 16px -2px rgba(0,0,0,.45),
    inset 0 2px 0 rgba(255,255,255,.7),               /* la lumière sur le dessus */
    inset 0 -3px 6px rgba(120,80,20,.35) !important;  /* l'ombre sous la surface */
  text-shadow: 0 1px 0 rgba(255,255,255,.5) !important;
  position: relative;
  overflow: hidden;
  transition: transform .16s ease, box-shadow .16s ease, filter .16s ease !important;
}
/* L'éclat qui balaie : le geste qui dit « c'est cliquable » sans le dire en mots. */
.tab-jeu-pied .tab-lancer::after {
  content: '';
  position: absolute;
  top: -50%; bottom: -50%; width: 42%;
  left: -60%;
  background: linear-gradient(105deg, transparent, rgba(255,255,255,.55), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}
.tab-jeu-pied .tab-lancer:hover {
  transform: translateY(-2px);
  box-shadow:
    0 5px 0 #7d5518,
    0 10px 22px -3px rgba(0,0,0,.5),
    inset 0 2px 0 rgba(255,255,255,.75),
    inset 0 -3px 6px rgba(120,80,20,.35) !important;
}
.tab-jeu-pied .tab-lancer:hover::after { animation: jeu-eclat .65s ease-out; }
.tab-jeu-pied .tab-lancer:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 #7d5518, 0 3px 8px -2px rgba(0,0,0,.45),
              inset 0 2px 0 rgba(255,255,255,.6) !important;
}
.tab-jeu-pied .tab-lancer:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
@keyframes jeu-eclat {
  from { left: -60%; }
  to   { left: 130%; }
}
@media (prefers-reduced-motion: reduce) {
  .tab-jeu-pied .tab-lancer::after { display: none; }
  .tab-jeu-pied .tab-lancer { transition: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE GRAPHISME DE JEU — l'écran doit se lire comme un jeu, pas comme un réglage
   Client : « essayez d'arranger le graphisme ». On joue sur trois choses, sobres :
     · un VIGNETAGE : les bords s'assombrissent, le regard tombe au centre ;
     · la CROIX DE SORTIE gagne un fond translucide : elle se voit sur toute image ;
     · la roue garde son ombre au sol pour la poser, sans halo parasite.
   Aucune ombre portée sur la roue elle-même (règle du projet) : seul le vignetage agit.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-verre::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background: radial-gradient(ellipse 78% 72% at 50% 46%,
    rgba(0,0,0,0) 42%, rgba(0,0,0,.12) 72%, rgba(0,0,0,.34) 100%);
}
.tab-quitter {
  background: rgba(0,0,0,.28) !important;
  backdrop-filter: blur(6px);
  width: 2.8em !important;
  height: 2.8em !important;
}
.tab-quitter:hover { background: rgba(230,57,70,.35) !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   LA RANGÉE DU HAUT EST LIBÉRÉE — ELLE PROFITE À LA ROUE
   La barre est passée en `position: absolute` (elle ne prend plus de rangée de grille).
   Sa rangée reste donc VIDE, et le `grid-template-rows` la réservait encore : la roue
   laissait 5 % de hauteur inutilisés, tout en haut.
   Client : « le bouton de la roue doit être encore un peu plus haut » — et la roue aussi.
   On remet la première rangée à zéro : la scène récupère toute la hauteur.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-ecran {
  grid-template-rows: 0 minmax(0, 1fr) minmax(0, 8cqh) !important;
}
/* Le pied reprend un peu de hauteur : c'est là que vit le bouton, il doit être confortable. */
.tab-jeu-pied { min-height: 8cqh !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE ZOOM DU GOODIE : LA LARGEUR ÉTAIT PLAFONNÉE, PAS LA VALEUR
   MESURÉ (diag-zoom-css.js) avec un réglage à 180px :
      --prize-size sur .wheel-prize (inline) : 180px      <- la valeur ARRIVE bien
      --prize-size VUE par l'image           : 180px      <- et elle est bien héritée
      IMG   width = 50,8px   height = 180px               <- MAIS la largeur est bridée
      PRIZE width = 82px     height = 180px               <- le conteneur aussi

   Deux brides, et les deux comptent :
     1. `.wheel-large .wheel-prize { width: 82px }` borne le CONTENEUR, donc un visuel plus
        grand que 82px y est comprimé — la hauteur suit la variable, la largeur non. Le
        visuel s'affiche donc 50,8 x 180 : ÉTIRÉ VERTICALEMENT, ce qui est exactement ce
        que le client décrit (« le zoom le fait déplacer et le zoomer juste un tout petit
        peu » : il grandit en hauteur et se déforme, au lieu de grandir en carré).
     2. un plafond de largeur hérité de la chaîne (max-width en pourcentage) qui s'applique
        à l'image et l'empêche de dépasser son conteneur.

   LA SORTIE : le CONTENEUR suit la même variable que son image, et l'image ne subit plus
   aucun plafond. Les deux dimensions viennent donc de la MÊME source — c'est ce qui garantit
   un visuel carré, jamais étiré, quelle que soit la place réglée.
   On cible `.tab-roue` (l'écran de jeu de l'atelier) : c'est là que le client règle, et
   cette règle ne peut donc pas toucher une vignette de la liste.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-prize {
  width: var(--prize-size, 64px) !important;
  margin-left: calc(var(--prize-size, 64px) / -2) !important;
  margin-top: calc(var(--prize-size, 64px) / -2) !important;
}
.tab-roue .wheel-prize img,
.tab-roue .wheel-prize > span {
  width: var(--prize-size, 64px) !important;
  height: var(--prize-size, 64px) !important;
  max-width: none !important;
  max-height: none !important;
  min-width: 0 !important;
  object-fit: contain !important;
  display: block !important;
}
/* Le nom du lot suit la taille du visuel : au-delà de 100px, 9px de texte serait illisible,
   et une étiquette minuscule sous un grand visuel se lirait comme un défaut d'alignement. */
.tab-roue .wheel-prize small {
  max-width: calc(var(--prize-size, 64px) * 1.15) !important;
  font-size: clamp(9px, calc(var(--prize-size, 64px) * 0.12), 13px) !important;
  margin-top: 4px;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LA DISPOSITION VERTICALE — TROIS RANGÉES EN FLUX, ET LA ROUE CENTRÉE
   Client : « je veux que la roue soit au milieu centré, et puis le logo en haut centré en
   haut, puis le bouton X juste au milieu en bas, et lancer la roue en bas dans l'espace
   vide en bas juste en dessous de la roue et de son socle ».
   MESURÉ avant : `.tab-roue` faisait `h=0` et le pied `h=632` — d'où le bouton au milieu.
   On remet les trois rangées EN FLUX, dans l'ordre du client :
      rangée 1 = le LOGO (centré)          rangée 2 = la SCÈNE (la roue, centrée)
      rangée 3 = le PIED (bouton, puis la croix)
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-ecran {
  grid-template-rows: auto minmax(0, 1fr) auto !important;
}
/* LA ROUE EST CENTRÉE dans sa rangée — horizontalement ET verticalement. */
.tab-roue {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  min-height: 0 !important;
  width: 100% !important;
}
/* Le cadre retrouve sa hauteur : plus de `max-height` hérité qui le comprimait. */
.tab-roue .wheel-frame {
  max-height: 100% !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE PIED : LE BOUTON SOUS LE SOCLE, LA CROIX EN DESSOUS — TOUT CENTRÉ
   Client : « le bouton X juste au milieu en bas, et lancer la roue en bas dans l'espace
   vide en bas juste en dessous de la roue et de son socle ».
   La colonne met le bouton en PREMIER (donc juste sous le socle) et la croix en SECOND
   (donc tout en bas). L'ordre du regard suit le geste.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: flex-start !important;
  gap: 1.1cqw !important;
  padding: 0.6cqw 0 1.6cqw !important;
  min-height: 0 !important;
}
.tab-jeu-pied .tab-lancer {
  width: 66% !important;
  flex: 0 0 auto !important;
}
/* La croix, centrée tout en bas : sobre, rouge, et sans fond plein — c'est une sortie,
   pas une action de jeu. */
.tab-jeu-pied .tab-quitter {
  flex: 0 0 auto !important;
  background: rgba(0, 0, 0, .26) !important;
  backdrop-filter: blur(6px);
  width: 2.5em !important;
  height: 2.5em !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON « ENREGISTRER LES CHANCES », ET SON ÉTAT
   Client : « quand je modifie, je dois avoir le bouton enregistrer ».
   Trois états, tous lisibles d'un coup :
     · ÉTEINT  — rien à enregistrer. Gris, il ne demande rien ;
     · ALLUMÉ  — des modifications attendent. Il prend la couleur de l'action ;
     · EN COURS — « Enregistrement… », désactivé, pour qu'on ne clique pas deux fois.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-chances-actions {
  display: flex; align-items: center; gap: 14px;
  margin-top: 14px; flex-wrap: wrap;
}
.jeu-chances-enr {
  padding: 10px 20px;
  border: 0; border-radius: 10px;
  font: 700 13.5px/1 Manrope, sans-serif;
  cursor: pointer;
  color: #fff;
  background: #b9b3a8;                       /* éteint : un gris neutre, pas une invitation */
  transition: background .16s ease, transform .16s ease, box-shadow .16s ease;
}
.jeu-chances-enr.is-actif {
  background: linear-gradient(180deg, #e0565f, #c0293a);
  box-shadow: 0 2px 0 #8a1c2a, 0 4px 12px -2px rgba(192,41,58,.4);
}
.jeu-chances-enr.is-actif:hover { transform: translateY(-1px); }
.jeu-chances-enr:not(:disabled):active { transform: translateY(1px); }
.jeu-chances-enr:disabled { cursor: default; opacity: .9; }
.jeu-chances-enr:focus-visible { outline: 2px solid #202123; outline-offset: 2px; }
.jeu-chances-etat {
  font: 500 12.5px/1.35 Manrope, sans-serif; color: #7a746a;
}
.jeu-chances-etat.est-modifie {
  color: #b0293a; font-weight: 700;
}
/* L'explication du sens du nombre : elle est CONTRE-INTUITIVE, donc elle se met en valeur. */
.jeu-aide-mini b { color: #202123; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO PLUS GRAND, CENTRÉ — ET LA CROIX DISCÈTE EN HAUT À DROITE
   Client : « le logo il doit être un peu plus grand [...] la voie doit être centré et grand
   [...] et le bouton X doit être en haut, pas à côté de lancer la roue. Il doit être en haut
   à droite, discret ».
   MESURÉ avant : le logo faisait 11px de haut — minuscule, parce que l'image du client est
   HAUTE (1200 x 2078) et qu'une hauteur fixe la réduit d'autant en largeur. Il prend donc
   une vraie hauteur, et sa largeur suit.
   La croix est posée en ABSOLU sur le bord droit : le logo reste donc parfaitement centré
   quel que soit le coin de la sortie — c'est la seule façon d'avoir les deux à la fois.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre {
  position: relative !important;
  justify-content: center !important;
  align-items: center !important;
  padding: 2.4cqw 3cqw 1.4cqw !important;
}
.tab-logo-marque {
  height: 6.4cqw !important;
  min-height: 34px !important;
  width: auto !important;
  max-width: 34cqw !important;
}
.tab-marque {
  font-size: 4.4cqw !important;
}
/* La croix : en HAUT À DROITE, et discrète — un simple trait rouge, sans pastille pleine.
   Elle garde une cible tactile suffisante (34px) mais ne se fait pas remarquer. */
.tab-barre .tab-quitter {
  position: absolute !important;
  top: 2.4cqw !important;
  right: 3cqw !important;
  background: transparent !important;
  backdrop-filter: none !important;
  width: 2.1em !important;
  height: 2.1em !important;
  opacity: .72;
  transition: opacity .15s ease, transform .15s ease;
}
.tab-barre .tab-quitter:hover { opacity: 1; transform: scale(1.06); }

/* ══════════════════════════════════════════════════════════════════════════════
   LA ROUE UN PEU PLUS PETITE, ET AJUSTÉE EN BAS
   Client : « la roue est trop grande [...] la roue elle doit être aussi ajusté en bas ».
   Elle occupait 79 % de la hauteur du verre, ce qui la collait au socle et au bouton.
   On la ramène à ~70 % : elle garde sa présence, et l'espace sous le socle redevient lisible
   — c'est là que vivent le bouton et la sortie.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-roue .wheel-frame {
  width: 82% !important;
  max-width: 82% !important;
  /* « ajusté en bas » : un peu d'air sous le socle, pour que le bouton respire. */
  margin-bottom: 2.4cqw !important;
}
/* Le pied se recolle sous la roue : moins de hauteur perdue entre les deux. */
.tab-jeu-pied {
  padding: 0 0 1.2cqw !important;
  gap: 0 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO RÉGLABLE — TAILLE, ANCRAGE, DÉPLACEMENT
   Client : « je dois avoir la possibilité d'ajuster sa taille, de le mettre soit à gauche,
   soit à droite [...] l'ajuster soit en hauteur ou en bas, ou à gauche ou à droite [...] Le
   bouton X ne sera pas là : en cliquant sur le logo on peut quitter. »
   Trois réglages indépendants, tous portés par des variables posées par le JS :
      --logo-taille  l'échelle (0,4 à 2,4)
      --logo-dx/dy   le déplacement fin, en `em` de la barre
   et la classe `tab-barre-left|center|right` donne l'ancrage.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre {
  position: relative !important;
  align-items: center !important;
  padding: 2.4cqw 3cqw 1.4cqw !important;
}
.tab-barre-center { justify-content: center !important; }
.tab-barre-left   { justify-content: flex-start !important; }
.tab-barre-right  { justify-content: flex-end !important; }

/* Le logo EST la sortie : il doit se lire cliquable — curseur, survol, et un léger
   enfoncement au clic. Sans ces signes, un logo cliquable ne se devine pas. */
.tab-logo-btn {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transform: translate(var(--logo-dx, 0), var(--logo-dy, 0));
  transition: transform .16s ease, opacity .16s ease;
}
.tab-logo-btn:hover { opacity: .88; transform: translate(var(--logo-dx, 0), var(--logo-dy, 0)) scale(1.03); }
.tab-logo-btn:active { transform: translate(var(--logo-dx, 0), var(--logo-dy, 0)) scale(.98); }
.tab-logo-btn:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 6px; }
.tab-logo-marque {
  /* La taille vient de la variable : un seul réglage pilote l'échelle, et la largeur suit
     le rapport de l'image — donc rien ne se déforme. */
  height: calc(6.4cqw * var(--logo-taille, 1)) !important;
  min-height: calc(30px * var(--logo-taille, 1)) !important;
  width: auto !important;
  max-width: min(46cqw, calc(46cqw * var(--logo-taille, 1))) !important;
}
.tab-marque {
  font-size: calc(4.4cqw * var(--logo-taille, 1)) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON DE LANCER RÉGLABLE — COULEUR, TAILLE, ET REMONTÉ
   Client : « le bouton lancer la roue je le veux un peu plus en haut, qu'il soit centré sur
   cet espace vide, et qu'on ait la possibilité de changer la couleur et sa taille ».
   `--bouton-echelle` agrandit le bouton SANS le déformer : la largeur, la hauteur et le
   texte suivent la même échelle. `--bouton-teinte` remplace seulement la teinte du dégradé,
   dont la construction (lumière en haut, ombre en bas) est conservée — sinon le bouton
   perdrait son relief et se lirait comme un aplat.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied {
  padding: 0 !important;
  /* Le bouton REMONTE : on réduit l'espace perdu sous la roue. C'est ce qui répond à
     « un peu plus en haut, centré sur cet espace vide ». */
  margin-top: -1.4cqw !important;
}
.tab-jeu-pied .tab-lancer {
  width: calc(62% * var(--bouton-echelle, 1)) !important;
  max-width: 96% !important;
  min-height: calc(3.4em * var(--bouton-echelle, 1)) !important;
  font-size: calc(1.28em * var(--bouton-echelle, 1)) !important;
  color: var(--bouton-encre, #2a1d08) !important;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bouton-teinte, #f0cf87) 62%, #fff) 0%,
    var(--bouton-teinte, #f0cf87) 42%,
    color-mix(in srgb, var(--bouton-teinte, #d4a574) 88%, #000) 68%,
    color-mix(in srgb, var(--bouton-teinte, #a9762f) 74%, #000) 100%) !important;
}
/* Le relief du bouton suit sa teinte : une couleur claire a besoin d'un socle plus sombre
   pour garder son épaisseur, sinon elle s'aplatit. */
.tab-jeu-pied .tab-lancer {
  box-shadow:
    0 3px 0 color-mix(in srgb, var(--bouton-teinte, #7d5518) 60%, #000),
    0 6px 16px -2px rgba(0,0,0,.45),
    inset 0 2px 0 rgba(255,255,255,.55),
    inset 0 -3px 6px rgba(0,0,0,.22) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES CONTRÔLES DE L'ATELIER : LE PAVÉ DE POSITION DU LOGO, ET LES SUGGESTIONS
   Le pavé est le MÊME que celui de la jauge de la roue : le client a déjà appris ce geste,
   on ne lui en impose pas un second. Les suggestions de texte évitent un champ vide devant
   lequel il faudrait deviner quoi écrire.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-logo-pos { margin: 14px 0 4px; }
.jeu-pave {
  display: grid;
  grid-template-columns: repeat(3, 40px);
  gap: 5px;
  margin-top: 8px;
}
.jeu-pave-btn {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 1px solid rgba(23,23,25,.14);
  border-radius: 9px;
  background: #fff;
  color: #3a3632;
  font: 500 15px/1 Manrope, sans-serif;
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease;
}
.jeu-pave-btn:hover { border-color: #e63946; background: #fdf7f7; }
.jeu-pave-btn.is-ici { border-color: #202123; background: #202123; color: #fff; }
.jeu-pave-btn:focus-visible { outline: 2px solid #202123; outline-offset: 2px; }

.jeu-bt-suggestions {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 12px;
}
.jeu-bt-sug {
  padding: 7px 13px;
  border: 1px solid rgba(23,23,25,.14);
  border-radius: 999px;
  background: #fff;
  color: #4a4744;
  font: 500 12.5px/1 Manrope, sans-serif;
  cursor: pointer;
  transition: border-color .14s ease, color .14s ease;
}
.jeu-bt-sug:hover { border-color: #e63946; color: #b0293a; }

/* ══════════════════════════════════════════════════════════════════════════════
   LA LUEUR DU MOYEU — ELLE DOIT FAIRE « JUSTE LE CONTOURING », ET SUIVRE LA TAILLE
   Client : « la lueur autour du moyeu est trop grande. Elle doit être ajustée par rapport au
   moyeu. Quand on a un gros moyeu, elle sort un peu. Quand on rétrécit, elle ressort. Là,
   elle est très étirée par rapport au moyeu. Elle doit faire juste le contouring du moyeu. »
   MESURÉ (diag-lueur2.js) : le moyeu porte `box-shadow: none` — il n'a AUCUNE lueur propre.
   La lueur que le client voit vient donc de son IMAGE, dont les proportions ne suivent pas
   la boîte du moyeu.

   LA CORRECTION, en deux parties :
    1. LA LUEUR EST PROPORTIONNELLE AU MOYEU. Elle vaut une FRACTION de `--moyeu-px` (6 %),
       donc elle grandit et rétrécit AVEC le moyeu — c'est littéralement « ajustée par
       rapport au moyeu ». Jamais une valeur en pixels secs, qui serait énorme sur un petit
       moyeu et invisible sur un grand ;
    2. L'IMAGE EST ROGNÉE AU CERCLE DU MOYEU (`overflow: hidden` + `border-radius: 50%`),
       donc la lueur peinte dedans ne peut plus dépasser. Elle est cadrée au centre, et ce
       qu'on voit de l'image correspond exactement au moyeu.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-hub {
  /* Le cerne : 6 % du moyeu, en lumière douce. Sur un moyeu de 80px -> ~5px. */
  box-shadow: 0 0 calc(var(--moyeu-px, 80px) * .06) calc(var(--moyeu-px, 80px) * .015)
              rgba(255, 255, 255, .42),
              /* et un cerne interne, pour que le contour se lise même sur un fond clair. */
              inset 0 0 calc(var(--moyeu-px, 80px) * .04) rgba(0, 0, 0, .18) !important;
  /* L'image est ROGNÉE au cercle : rien de ce qui déborde ne peut se voir. */
  overflow: hidden !important;
  border-radius: 50% !important;
}
/* L'image remplit le cercle ET se cadre au centre : la lueur peinte dedans reste centrée
   sur le moyeu au lieu d'être tirée d'un côté. */
.wheel-hub-img {
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  object-position: center center !important;
  border-radius: 0 !important;
}
/* Le monogramme ne reçoit pas la lueur du moyeu : il a la sienne, portée par le texte. */
.wheel-hub > span { box-shadow: none !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON CENTRÉ DANS L'ESPACE VIDE SOUS LE SOCLE
   Client : « il est très collé en bas, il doit être un peu plus en haut, vraiment centré
   dans l'espace vide juste en dessous du socle de la roue. »
   MESURÉ : l'espace libre sous le socle fait 162px, son milieu est à y=876, et le bouton
   avait son milieu à y=935 — soit 59px trop bas, avec 0px sous lui.
   Le pied porte donc un `padding-bottom` qui vaut EXACTEMENT la moitié de l'espace libre
   restant, exprimé en `cqw` (unité du conteneur) : la proportion se maintient à toute
   taille d'écran, au lieu d'être un pixel fixe valable pour une seule fenêtre.
   Calcul : espace 162px, bouton 44px -> (162-44)/2 = 59px sous le bouton = 11,8cqw.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied {
  margin-top: 0 !important;
  padding: 0 !important;
  /* L'air sous le bouton : c'est LUI qui le remonte au milieu de l'espace vide. */
  padding-bottom: 11.8cqw !important;
  justify-content: flex-end !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   L'ÉCLAT BLANC ENTRE LA ROUE ET LE MOYEU — RETIRÉ
   Client : « cet éclat blanc, je ne le veux pas, qui est entre la roue et le moyeu. En
   blanc, il est là, il est très moche, j'espère qu'on peut l'enlever. »
   MESURÉ : le moyeu portait `border: 7px solid rgba(255,255,255,.38)` — un ANNEAU BLANC de
   7px posé sur son pourtour, donc exactement entre le disque et le centre. Il servait de
   bague de contraste sur les anciennes roues à disque clair ; sur les roues actuelles, il
   se lit comme un liseré sale.
   On le retire, et on garde le CONTOURING subtil ajouté à la passe précédente (une lueur
   de 6 % de la taille du moyeu) — c'est lui qui détache le moyeu du disque proprement.
   ══════════════════════════════════════════════════════════════════════════════ */
.wheel-hub {
  border-width: 0 !important;
  border-color: transparent !important;
}
/* Le monogramme garde sa pastille de contraste, sinon il deviendrait illisible sur un
   disque de la même teinte : c'est le TEXTE qui a besoin d'un fond, pas le moyeu d'un bord. */
.wheel-hub > span {
  background: transparent !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO DESCEND UN PEU PAR DÉFAUT
   Client : « la version par défaut de logo doit être un peu plus en bas que le haut ».
   La valeur vient déjà du JS (`logo_pos_y = 4` par défaut, soit 0,4em vers le bas) — cette
   règle ne fait que rappeler que le déplacement est bien pris en compte sur l'axe vertical.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre { padding-top: 3.2cqw !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   LE BOUTON DE LANCER SE DÉPLACE
   Client : « le bouton lance roue aussi je dois avoir un positionnement détaillé sur lui, si
   je peux le lever en haut et tout ». Le déplacement vient du JS (`--bouton-dx`/`--bouton-dy`),
   sur le même principe que le logo : un `translate` qui ne touche pas à la mise en page.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-jeu-pied .tab-lancer {
  transform: translate(var(--bouton-dx, 0), var(--bouton-dy, 0));
}
.tab-jeu-pied .tab-lancer:hover {
  transform: translate(var(--bouton-dx, 0), var(--bouton-dy, 0)) translateY(-2px);
}
.tab-jeu-pied .tab-lancer:active {
  transform: translate(var(--bouton-dx, 0), var(--bouton-dy, 0)) translateY(2px);
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES TROIS BLOCS DE LA SECTION CHARTE — UNE MISE EN PAGE QUI SE LIT
   Client : « embellis-moi cette section, les positions des informations et des réglages sont
   un peu éparpillées et ça ne marche pas ».
   MESURÉ : tout vivait dans UNE grille à deux colonnes (`jeu-visuels`), donc les réglages
   d'un même objet tombaient dans deux zones éloignées, un titre flottait sans contenant, et
   un grand vide s'installait au milieu.
   La section est maintenant TROIS blocs — Identité, Logo, Bouton — et chacun range ses
   réglages dans une grille stable à deux colonnes. C'est la structure qui fait la lisibilité,
   pas la décoration.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── LE DUO : deux réglages côte à côte, alignés sur la même ligne de base ────
   C'est la brique de base de la nouvelle section. Elle remplace l'empilement vertical
   systématique qui allongeait la colonne sans raison : deux réglages courts tiennent
   ensemble sur une ligne, et l'œil les lit d'un coup. */
.jeu-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 20px;
  align-items: end;
  margin: 12px 0 4px;
}
.jeu-duo .jeu-curseur { margin: 0; }
@media (max-width: 1180px) {
  .jeu-duo { grid-template-columns: minmax(0, 1fr); }
}

/* ── LA POSITION : l'ancrage à gauche, le pavé à droite ───────────────────────
   Ils font le même travail — l'un donne le camp, l'autre fignole — donc ils se lisent
   ensemble. Les séparer obligeait à faire l'aller-retour entre deux zones de l'écran. */
.jeu-position {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px 22px;
  align-items: start;
  margin: 14px 0 2px;
  padding-top: 14px;
  border-top: 1px solid rgba(23, 23, 25, .08);
}
@media (max-width: 1180px) {
  .jeu-position { grid-template-columns: minmax(0, 1fr); }
}
.jeu-pave-bloc { display: flex; flex-direction: column; gap: 7px; }
.jeu-pave-nom {
  font: 700 11.5px/1 Manrope, sans-serif;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #6e6a60;
}

/* ── LE CHAMP LARGE : le texte du bouton prend toute la largeur ───────────────
   Un libellé de bouton est une phrase : il a besoin de place, contrairement à un
   pourcentage ou une couleur. */
.jeu-field-large { margin: 0 0 10px; }
.jeu-field-large input { width: 100%; }

/* ── LES SUGGESTIONS ACTIVES : la première est le défaut, elle se voit ────────
   Quand le champ est vide, le bouton affiche « Lancer la roue » — donc c'est LUI qui est
   actif. Les montrer inactifs tous les trois laissait croire que rien n'était choisi. */
.jeu-bt-sug.is-actif {
  border-color: #202123;
  background: #202123;
  color: #f4f1ec;
  font-weight: 700;
}
.jeu-bt-sug.is-actif:hover { color: #f4f1ec; border-color: #202123; }

/* ── LES CURSEURS DANS UN DUO : ils s'alignent sur la même ligne ─────────────
   Sans cela, une étiquette plus longue décale son curseur vers le bas, et deux réglages
   côte à côte se lisent en escalier. */
.jeu-duo .jeu-curseur > span:first-child { min-height: 2.1em; }

/* ── LES SOUS-TITRES : ils séparent les parties d'un même bloc ───────────────
   Plus discrets que les titres de bloc, assez marqués pour qu'on sente le changement de
   sujet. Un peu d'air AVANT, pour que la rupture se voie. */
.jeu-bloc .jeu-sous-titre {
  margin: 22px 0 10px;
  padding-top: 14px;
  border-top: 1px solid rgba(23, 23, 25, .07);
}
/* Le PREMIER sous-titre d'un bloc ne porte pas de filet : il n'y a rien au-dessus de lui. */
.jeu-bloc .section-head + .jeu-sous-titre,
.jeu-bloc .jeu-couleurs + .jeu-sous-titre:first-of-type { border-top: 0; padding-top: 0; margin-top: 12px; }

/* ══════════════════════════════════════════════════════════════════════════════
   LES AMBIANCES — LA PASTILLE QUI MONTRE L'AMBIANCE AVANT DE LA CHOISIR

   Client : « des presets pour les ampoules, pour le contour, pour le support [...] pour
   personnaliser le maximum l'interface a chaque produit et pour chaque campagne ».

   La pastille porte DEUX couleurs : le fond de l'ambiance, et sa lumiere posee dessus en
   petit disque. On voit donc ce qu'on choisit sans essayer les dix — c'est le point du
   « montrer plutot que decrire ».

   Une ambiance ACTIVE est marquee par sa bordure et par la lumiere. Rien d'autre : pas
   d'ombre portee (bannie sur ce projet), pas de fond qui change d'un coup. Le contraste
   suffit, et il est verifie : texte sombre sur fond clair, texte clair sur fond sombre.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-ambiances {
  display: grid;
  /* auto-FILL : 2 par ligne quand la colonne est large, jamais de piste qui s'effondre.
     (Un `auto-fit` collapserait la derniere ligne — piege deja paye sur ce projet.) */
  grid-template-columns: repeat(auto-fill, minmax(146px, 1fr));
  gap: 8px;
  margin-top: 4px;
}
.jeu-ambiance {
  display: grid;
  grid-template-columns: 34px 1fr;
  grid-template-rows: auto auto;
  column-gap: 10px;
  align-items: center;
  text-align: left;
  padding: 9px 11px;
  border: 1px solid var(--hairline, #e6e0d6);
  border-radius: 10px;
  background: rgba(255, 255, 255, .62);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color .16s ease, background-color .16s ease, transform .16s ease;
}
.jeu-ambiance:hover {
  /* Le survol est LEGER : on decolle a peine, on ne fait pas sauter la ligne. */
  border-color: var(--accent, #E63946);
  background: rgba(230, 57, 70, .05);
  transform: translateY(-1px);
}
.jeu-ambiance:focus-visible {
  outline: 2px solid var(--accent, #E63946);
  outline-offset: 2px;
}
.jeu-ambiance.active {
  border-color: var(--accent, #E63946);
  background: rgba(230, 57, 70, .07);
}
.jeu-ambiance-pastille {
  grid-row: 1 / 3;
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(0, 0, 0, .16);
  box-sizing: border-box;
}
/* La lumiere de l'ambiance, posee sur son fond : c'est le second des deux apercus
   annonces dans la note du haut. */
.jeu-ambiance-pastille i {
  width: 13px; height: 13px;
  border-radius: 50%;
  display: block;
}
.jeu-ambiance-nom {
  grid-column: 2;
  font-weight: 600;
  font-size: 13px;
  line-height: 1.2;
}
.jeu-ambiance-note {
  grid-column: 2;
  font-size: 11px;
  opacity: .68;
  line-height: 1.25;
}
/* Une pastille active dit « c'est d'ici que tu pars » : la lumiere grossit un peu. */
.jeu-ambiance.active .jeu-ambiance-nom { color: var(--accent, #E63946); }

@media (max-width: 1180px) {
  .jeu-ambiances { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
}
@media (prefers-reduced-motion: reduce) {
  .jeu-ambiance { transition: none; }
  .jeu-ambiance:hover { transform: none; }
}

/* ══ LE BOUTON « MÉLANGER À NOUVEAU » ═════════════════════════════════════════════
   Il accompagne l'interrupteur « Ordre aléatoire », et n'apparaît que si celui-ci est
   coché. Il tire une nouvelle GRAINE : l'ordre change et reste enregistré. */
/* ⚠️ `.jeu-melanger` vivait autrefois dans un `.jeu-duo`, d'où cet `align-self: end`.
   Il est maintenant DANS sa carte : on neutralise ce placement hérité, sinon il tire le
   bouton vers le bas de la ligne — exactement le défaut signalé. */
.jeu-melanger { align-self: center; margin-bottom: 0; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .jeu-melanger { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE RECADRAGE AVANT ENTRÉE — LE CADRE AJUSTABLE

   Client : « je voudrais un recadrage avant même de faire entrer l'image, pour ne pas
   perdre du vide. »

   Le cadre vit PAR-DESSUS l'image, dans une scène qui a exactement les proportions de
   l'image : les coordonnées du cadre sont donc en FRACTION (0..1), et le découpage final
   est exact quelle que soit la taille de l'aperçu.

   On n'utilise PAS d'ombre portée (bannie sur ce projet) : c'est l'ASSOMBRISSEMENT de ce
   qui est HORS du cadre qui le fait ressortir. Le contraste suffit, et il est mesuré.
   ══════════════════════════════════════════════════════════════════════════════ */
/* ⚠️⚠️ `.recadre-fond` EST UN `<dialog>` DEPUIS LE CORRECTIF DU TOP LAYER — CE N'EST PLUS UNE
   SIMPLE `<div>` POSÉE DANS LE BODY. Un `<dialog open>` reçoit de l'agent utilisateur
   `position: absolute; inset: 0; margin: auto; width:fit-content; height:fit-content` : nos règles
   doivent donc reprendre la main sur ces propriétés, sinon la fenêtre se cale en haut à gauche et
   ne couvre plus l'écran.
   ⚠️ ET LE `z-index: 9000` N'EST PLUS CE QUI LA MET DEVANT — aucun z-index ne passe devant le top
   layer. Il ne sert plus qu'à l'ordre interne ; on le garde par prudence, sans y croire. */
.recadre-fond {
  position: fixed; inset: 0; z-index: 9000;
  margin: 0; width: 100%; height: 100%;
  max-width: none; max-height: none; border: 0;
  background: rgba(10, 11, 12, .72);
  display: grid; place-items: center;
  padding: 24px;
}
.recadre-carte {
  background: #f7f4ee;
  color: #1c1d1f;
  border: 1px solid #ddd6ca;
  border-radius: 14px;
  padding: 16px;
  max-width: min(760px, 92vw);
  max-height: 92vh;
  display: flex; flex-direction: column; gap: 12px;
}
.recadre-tete { display: flex; flex-direction: column; gap: 3px; }
.recadre-tete strong { font-size: 15px; }
.recadre-note { font-size: 12px; color: #6b6a66; line-height: 1.35; }
.recadre-scene {
  position: relative;
  /* `contain` : la scène garde toujours les proportions de l'image, donc le cadre peut
     être traduit en fraction sans jamais se tromper de repère. */
  display: inline-block;
  line-height: 0;
  max-height: 62vh;
  align-self: center;
  overflow: hidden;
  border-radius: 8px;
  background: #e9e4da;
}
.recadre-img {
  display: block;
  max-width: min(660px, 86vw);
  max-height: 62vh;
  width: auto; height: auto;
}
/* Le cadre : un liseré net, et RIEN d'autre. Pas d'ombre (bannie sur ce projet). */
.recadre-cadre {
  position: absolute;
  border: 2px solid #E63946;
  box-sizing: border-box;
  cursor: move;
  /* Le reste de l'image est assombri par un contour immense et translucide : c'est ce qui
     fait ressortir la zone choisie sans ombre portée. */
  box-shadow: 0 0 0 9999px rgba(6, 7, 8, .55);
}
.recadre-coin {
  position: absolute;
  width: 16px; height: 16px;
  background: #E63946;
  border: 2px solid #fff;
  border-radius: 3px;
  box-sizing: border-box;
  /* Une cible tactile confortable : 16 px visuels, mais la zone réactive est plus large
     par le `padding` négatif du pseudo-élément — un coin de 16 px est trop petit au doigt. */
}
.recadre-coin::after { content: ''; position: absolute; inset: -10px; }
.recadre-nw { left: -9px; top: -9px; cursor: nwse-resize; }
.recadre-ne { right: -9px; top: -9px; cursor: nesw-resize; }
.recadre-sw { left: -9px; bottom: -9px; cursor: nesw-resize; }
.recadre-se { right: -9px; bottom: -9px; cursor: nwse-resize; }
.recadre-pied { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.recadre-pied .button { white-space: nowrap; }

@media (max-width: 760px) {
  .recadre-carte { padding: 12px; }
  .recadre-img { max-width: 90vw; }
  .recadre-pied { justify-content: stretch; }
  .recadre-pied .button { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
  .recadre-fond, .recadre-cadre { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES 320 px DE VIDE DES CARTES — LA LIGNE FANTÔME DE LA GRILLE

   Client : « il ne faut pas y avoir des vides, il doit tout être organisé, tout simple
   à voir. »

   MESURÉ : chaque carte de réglage avait ~40 px de vide EN DESSOUS (8 cartes -> 320 px).
   Aucun trou entre les blocs : l'organisation était saine, c'est la CARTE qui réservait
   une hauteur inutile.

   LA CAUSE : `.jeu-curseur` est une grille `1fr auto` prévue pour « curseur + valeur ».
   Sur une carte à CASE À COCHER, il n'y a pas de curseur : l'`<output>` part donc sur une
   ligne implicite SUPPLÉMENTAIRE, qui ne contient rien mais réserve la hauteur d'une ligne.
   Trois réglages suffisent, et ils sont généraux :

     1. `grid-auto-rows: min-content` — une ligne sans contenu ne réserve plus de place ;
     2. `align-content: center`        — la carte se cale sur son contenu, pas sur sa boîte ;
     3. `gap` vertical resserré        — l'air entre le libellé et son réglage est plus juste.

   Et pour les cartes à case, on ne réserve PAS de seconde colonne : la case et son état
   partagent une seule ligne, donc il n'y a plus de ligne fantôme du tout.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-curseur {
  /* Une ligne vide ne doit JAMAIS réserver de hauteur : c'est ce qui créait les 40 px. */
  grid-auto-rows: min-content;
  align-content: center;
  align-items: center;
  gap: 4px 10px;
  /* La hauteur suit le contenu — plus de minimum imposé par la structure de la grille. */
  min-height: 0;
}
/* Les cartes à CASE À COCHER : le libellé sur sa ligne, la case et l'état sur la suivante.
   Elles ne réservent donc aucune colonne pour un curseur qui n'existe pas. */
.jeu-curseur-bascule {
  grid-template-columns: auto 1fr;
  justify-items: start;
}
.jeu-curseur-bascule > span:first-child { grid-column: 1 / -1; }
.jeu-curseur-bascule input[type="checkbox"] { grid-column: 1; }
.jeu-curseur-bascule output { grid-column: 2; text-align: left; min-width: 0; }

/* Les paragraphes d'aide DANS un `jeu-duo` : ils ne flottent plus, ils prennent la
   première colonne et le bouton reste à côté. */
.jeu-duo > .jeu-aide-mini {
  margin: 0;
  align-self: center;
  line-height: 1.35;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LA CARTE « ORDRE ALÉATOIRE » ACCUEILLE SON BOUTON

   Client : « il doit tout être organisé, tout simple à voir. » — et sa capture entoure le
   bouton « Mélanger à nouveau », qui flottait.

   Il entre dans la carte : la carte règle, explique ET agit. Une chose, un endroit.

   La carte à case passe donc en UNE colonne : le libellé, puis la case et son état, puis
   l'explication et le bouton — de haut en bas, dans l'ordre où on les lit.
   ══════════════════════════════════════════════════════════════════════════════ */
/* ══ LA CARTE DE L'ORDRE ALÉATOIRE EST RECONNAISSABLE À SON MENU ═══════════════
   Elle est la seule carte à case qui porte un identifiant de réglage (`#jeu-aleatoire`) :
   c'est donc par lui qu'on la vise, et non par la présence du bouton — sinon la règle tombe
   dès que la case est décochée, et le vide revient. */
.jeu-curseur-bascule:has(#jeu-aleatoire),
.jeu-curseur-bascule:has(.jeu-melanger) {
  grid-column: 1 / -1;
}
.jeu-curseur-bascule:has(.jeu-melanger) {
  /* ══ TROIS COLONNES : LA CASE · L'ÉTAT · LE BOUTON ═════════════════════════════
     La troisième colonne est celle du bouton. Elle n'existe que dans cette carte — les
     autres cartes à case gardent leurs deux colonnes.
     ══════════════════════════════════════════════════════════════════════════════ */
  grid-template-columns: auto 1fr auto;
  align-items: center;
}
/* Le libellé garde toute la ligne, comme partout. */
.jeu-curseur-bascule:has(.jeu-melanger) > span:first-child { grid-column: 1 / -1; }
/* La case et son état : colonnes 1 et 2, sur la deuxième ligne. */
.jeu-curseur-bascule:has(.jeu-melanger) input[type="checkbox"] { grid-column: 1; }
.jeu-curseur-bascule:has(.jeu-melanger) output { grid-column: 2; }
.jeu-aide-dans-carte {
  grid-column: 1 / -1;
  margin: 5px 0 0;
  line-height: 1.4;
  opacity: .82;
}
/* L'ancien placement (sous le texte, aligné à gauche) est remplacé par le placement à
   droite de la dernière ligne — voir les règles plus bas. Il ne doit pas survivre :
   un placement et son remplaçant qui coexistent, c'est le plus sûr moyen de ne plus
   savoir lequel gagne. */
.jeu-curseur-bascule:has(.jeu-melanger) .jeu-melanger {
  white-space: nowrap;
}

/* ══ LA CARTE DE L'ORDRE ALÉATOIRE PREND TOUTE LA LARGEUR — DANS LES DEUX ÉTATS ══
   Client : « Profite de ce petit espace qui est libre [...] fais-le sur toute une ligne,
   comme ça on profite même de cet espace. »

   MESURÉ : la règle que j'avais posée ne s'appliquait qu'avec le bouton (donc case cochée).
   Décochée, la carte retombait à UNE colonne sur deux et laissait la moitié droite vide —
   exactement le défaut signalé, dans l'autre état. Un vide qui n'apparaît que dans un état
   sur deux reste un vide : on l'élargit dans les deux. */
.jeu-curseurs > .jeu-curseur-bascule:has(> span:first-child) { min-height: 0; }
.jeu-curseurs > .jeu-curseur:has(output) { min-height: 0; }

/* ══ UN RYTHME RÉGULIER DANS LA COLONNE ═══════════════════════════════════════
   La vision signalait que « L'affichage des parts » était plus court que ses voisines
   (une case + un état, contre un menu déroulant). Une hauteur MINIMALE COMMUNE donne à la
   colonne un rythme régulier.
   ⚠️ UN ALIGNEMENT N'EST PAS UN VIDE : un vide ne sert à rien, un alignement fait lire la
   colonne. On aligne donc sur la hauteur des cartes VOISINES (67 px), pas plus.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-curseurs > .jeu-curseur-bascule { min-height: 67px; }
.jeu-curseurs > .jeu-curseur { min-height: 63px; }
/* ══ L'EXPLICATION ET LE BOUTON PARTAGENT LA DERNIÈRE LIGNE ═══════════════════
   Client : « Quand je clique Mélanger, le bouton devient en bas. » — il descendait parce
   qu'il était empilé SOUS le paragraphe, en pleine largeur, aligné à gauche.
   Sur une carte pleine largeur, ce n'est plus nécessaire : le texte occupe la gauche, le
   bouton se pose à droite, et la ligne est pleine. Le bouton est donc à CÔTÉ de ce qu'il
   commente, pas au fond de la carte. */
.jeu-curseur-bascule:has(.jeu-melanger) .jeu-aide-dans-carte {
  grid-column: 1 / 3;
  grid-row: 3;
  align-self: center;
}
.jeu-curseur-bascule:has(.jeu-melanger) .jeu-melanger {
  grid-column: 3;
  grid-row: 3;
  justify-self: end;
  align-self: center;
  margin-top: 4px;
  margin-bottom: 0;
}

/* La carte à case qui porte le bouton a besoin de plus : elle contient trois éléments. */
.jeu-curseurs > .jeu-curseur-bascule:has(.jeu-melanger) {
  min-height: 0;
  /* ══ LA CARTE PREND TOUTE LA LARGEUR DE LA COLONNE ══════════════════════════════
     Client : « Profite de ce petit espace qui est libre [...] fais-le sur toute une ligne,
     comme ça on profite même de cet espace. »

     MESURÉ : `.jeu-curseurs` est une grille à 2 colonnes (627 px) ; cette carte n'avait
     aucune règle `grid-column`, donc elle restait dans la colonne 1 et la colonne 2 était
     VIDE sur toute sa hauteur. `1 / -1` la fait occuper la ligne entière.
     ══════════════════════════════════════════════════════════════════════════════ */
  grid-column: 1 / -1;
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE LOGO SORT DU FLUX — IL NE POUSSE PLUS LA ROUE

   Client : « quand j'agrandis le logo, la roue doit rester sur la position qu'on lui a déjà
   faite. Ça ne doit pas pousser l'ensemble — ça veut dire qu'il doit être sur un autre
   calque. »

   MESURÉ : la barre était en `position: relative`, donc DANS le flux. Résultat, entre un
   logo à 100 % et un logo à 1000 % :
        la roue se déplaçait de 307 px vers le bas
        ET elle RÉTRÉCISSAIT de 308 px (546 -> 238)
   Agrandir le logo cassait donc la position réglée par le client ET la taille de la roue.

   LA CORRECTION : la barre sort du flux. Elle se pose par-dessus l'écran, sans rien
   pousser — la roue garde sa taille et sa position au pixel.

   ⚠️ CE N'EST PAS UN `z-index` : avec un `z-index` seul, la barre réserverait toujours sa
   place et la roue serait toujours poussée. C'est la participation au FLUX qu'il faut
   retirer. Le logo est peint AVANT la roue dans le DOM, donc il passe naturellement en
   dessous — ce que le client demande.
   ══════════════════════════════════════════════════════════════════════════════ */
.tab-barre {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* La barre ne réserve donc AUCUNE hauteur : la roue commence au haut de l'écran et n'est
     plus jamais poussée, quelle que soit la taille du logo. */
  height: auto;
  margin: 0;
  /* Elle ne doit pas intercepter les clics destinés à la roue : seul le logo est cliquable
     (c'est la sortie du jeu), le reste de la barre laisse passer. */
  pointer-events: none;
  /* L'écran de jeu est un cadre : le logo peut déborder (le client va jusqu'à 1000 %).
     `overflow: visible` est donc VOULU — c'est le seul élément qui a le droit de sortir. */
  overflow: visible;
}
/* Le logo et son bouton restent, eux, cliquables. */
.tab-barre .tab-logo-marque,
.tab-barre .tab-marque,
.tab-barre button {
  pointer-events: auto;
}

/* ══ LES POSITIONS DE LA BARRE (gauche / centre / droite) ═════════════════════
   Elles étaient exprimées par des marges, ce qui n'a plus de sens hors du flux : on les
   exprime par un ancrage, qui dit la même chose sans dépendre de la largeur des voisins. */
.tab-barre-gauche { justify-content: flex-start; text-align: left; }
.tab-barre-centre { justify-content: center; text-align: center; }
.tab-barre-droite { justify-content: flex-end; text-align: right; }

/* ══════════════════════════════════════════════════════════════════════════════
   LA BARRE DU LOGO HORS DU FLUX — AVEC SA RANGÉE TENUE PAR UN ESPACEUR

   Client : « agrandir le logo ne doit pas faire bouger la roue [...] il doit être sur un
   autre calque. »

   MESURÉ : la roue se déplaçait de 307 px ET rétrécissait de 308 px (546 -> 238) quand le
   logo passait de 100 % à 1000 %. Une seule cause : la RANGÉE de grille de la barre, dont
   la hauteur suit `--logo-taille`.

   ⚠️ CETTE CORRECTION A DEUX MOITIÉS, ET ELLES SONT INDISPENSABLES L'UNE À L'AUTRE :
      · l'ESPACEUR tient la rangée à une hauteur FIXE (il ne suit pas le logo) ;
      · la BARRE sort du flux, donc elle ne pousse rien et le logo peut déborder.
   Les commentaires plus haut expliquent pourquoi j'avais remis `position: relative` : sortir
   la barre SANS rien mettre à sa place avait fait remonter la scène et le pied d'une rangée,
   posant le bouton au milieu de la roue. C'était juste — il manquait l'espaceur.
   ══════════════════════════════════════════════════════════════════════════════ */

/* L'ESPACEUR : il occupe la rangée de la barre, à une hauteur FIXE. Il est invisible et ne
   capte aucun clic. Sa hauteur est celle de la barre vide, pas celle du logo. */
.tab-barre-espace {
  display: block;
  height: 5cqh;
  min-height: 34px;
  pointer-events: none;
}

/* LA BARRE : hors du flux. Elle se pose par-dessus la rangée de l'espaceur, sans la
   mesurer — donc sa hauteur ne peut plus influencer la scène. */
.tab-barre {
  position: absolute !important;
  top: 0;
  left: 0;
  right: 0;
  height: 5cqh;
  min-height: 34px;
  margin: 0;
  /* Elle laisse passer les clics, SAUF sur le logo — qui est la sortie du jeu. */
  pointer-events: none;
  /* Le logo peut déborder : le client va jusqu'à 1000 %, c'est son choix. */
  overflow: visible !important;
  /* Centré par défaut : « le logo en haut, centré en haut ». L'ancrage est ajusté par les
     classes `tab-barre-gauche|centre|droite`, qui agissent sur `justify-content`. */
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}
.tab-barre .tab-logo-marque,
.tab-barre .tab-marque,
.tab-barre .tab-logo-btn,
.tab-barre button,
.tab-barre a {
  pointer-events: auto;
}
/* L'ancrage : c'est `justify-content` qui décide, pas une marge — le logo n'a donc plus
   besoin de connaître la largeur de ses voisins. */
.tab-barre-gauche { justify-content: flex-start !important; }
.tab-barre-centre { justify-content: center !important; }
.tab-barre-droite { justify-content: flex-end !important; }

@media (prefers-reduced-motion: reduce) {
  .tab-barre, .tab-barre-espace { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   PLUS AUCUNE CARTE ORPHELINE : L'AIDE EST UNE LIGNE, ET LES GRILLES SE REMPLISSENT

   Client : « Profite de ce petit espace qui est libre dans cet onglet [...] fais-le sur toute
   une ligne, comme ça on profite même de cet espace. »

   MESURÉ (diag-qui-laisse-du-blanc) — DEUX cartes seules sur leur ligne, 309 px de blanc
   chacune, 618 px au total :
      « Répétition des goodies » (grille du découpage)
      « Taille des visuels »     (grille du disque)

   DEUX MOYENS, ET IL FAUT LES DEUX :

   1. L'AIDE DEVIENT UNE LIGNE À ELLE SEULE. Elle était posée à côté d'un réglage, ce qui lui
      prenait la colonne 2 et poussait la carte suivante à être seule sur la ligne d'après.
      Sous les cartes, elle remplit sa propre ligne — et sa place est plus juste : un
      commentaire va sous ce qu'il commente, pas à côté.

   2. LES GRILLES SE REMPLISSENT : `auto-fit` avec un minimum de 240 px ne place que 2 colonnes
      à 627 px, donc la 3e carte ne peut PAS monter. On abaisse le minimum pour que 3 colonnes
      tiennent — la carte fait alors ~200 px, ce qui reste lisible pour un curseur et son
      libellé (les libellés sont courts : « Distance du bord », « Taille des visuels »).
   ══════════════════════════════════════════════════════════════════════════════ */

/* 1. L'aide sous la grille : pleine largeur, et elle se lit comme un commentaire. */
.jeu-aide-sous-grille {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  padding: 0 2px;
  opacity: .8;
  line-height: 1.45;
}

/* 2. LE NOMBRE DE COLONNES SUIT LE NOMBRE DE RÉGLAGES À ALIGNER.
   ⚠️ J'AVAIS POSÉ UNE RÈGLE UNIQUE (3 colonnes partout) : elle a RÉGLÉ la grille du disque
   (3 réglages) et CASSÉ celle du découpage (2 réglages — 2 cartes ne remplissent que 2
   colonnes sur 3, donc un blanc inévitable). « Remplir la ligne » n'a pas une réponse : il y
   en a une par grille. Le nom de la classe dit donc ce que la grille PORTE. */
/* Deux réglages à faire face : deux colonnes, qui se réduisent proprement. */
.jeu-curseurs-2 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) !important; }
/* Trois réglages : trois colonnes quand la place le permet (627 px -> 3 x ~200 px). */
.jeu-curseurs-3 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)) !important; }
/* Les cartes à 3 par ligne ont des libellés courts : on resserre un peu le texte pour qu'il
   ne se coupe pas dans 190 px. */
.jeu-curseurs-3 > .jeu-curseur { padding: 10px 11px; }
.jeu-curseurs-3 > .jeu-curseur > span:first-child { font-size: 10.5px; letter-spacing: .07em; }

@media (prefers-reduced-motion: reduce) {
  .jeu-aide-sous-grille { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   LE BLOC DE POSITION DE LA ROUE — HORS DE LA GRILLE DES CURSEURS

   Client : « ça te désorganise tout ça ». MESURÉ : le pavé directionnel vivait DANS la grille
   des curseurs, à la suite de trois jauges — donc dans une case étroite, traité comme une
   « carte de curseur ». Il vit maintenant SOUS les jauges, sur toute la largeur : le pavé, son
   relevé de position et son bouton s'alignent horizontalement, comme un bloc de commande.
   ══════════════════════════════════════════════════════════════════════════════ */
.jeu-bloc > .jeu-pos { width: 100%; }
/* Le relevé et le bouton se posent à CÔTÉ du pavé, alignés sur sa hauteur — la ligne est pleine
   au lieu de laisser un vide à droite du pavé. */
.jeu-pos { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.jeu-pos-cote { display: flex; flex-direction: column; gap: 8px; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  .jeu-pos, .jeu-pos-cote { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A — LE CONTRASTE : SUR UN FOND CLAIR ÉCRIT EN DUR, L'ENCRE EST SOMBRE

   Client : « le mode sombre connaît un problème [...] je n'arrive pas à voir l'écriture. Donc
   tu m'ajustes ce contraste. » · « les textes doivent être plus clairs. Si c'est sombre, les
   textes doivent être plus clairs. » · « dans le dashboard [...] le sous-widget en noir c'est
   pas lisible. »

   MESURÉ en thème sombre — 352 textes du DASHBOARD sous 4,5:1 sur 12 surfaces, et le titre du
   pop-up des tablettes à 1:1 (blanc sur blanc).

   LA CAUSE, UNE SEULE, ET ELLE SE CONTREDIT DANS LA MÊME RÈGLE :
       dialog.admin-dialog { background: #ffffff; color: var(--text, #17171a); }
   Le fond est CLAIR écrit en dur ; l'encre SUIT LE THÈME (blanche en sombre). Le repli
   `#17171a` ne se déclenche jamais puisque `--text` est défini. Donc : blanc sur blanc.

   LE CORRECTIF, ET C'EST LA RÈGLE À RETENIR : **sur un fond clair écrit en dur, l'encre doit
   être SOMBRE, quel que soit le thème.** On la fige donc en littéral, au lieu de la faire
   dépendre du thème.

   ⚠️ ET LE GRIS : `rgb(168,162,154)` sur blanc = 2,5:1 — illisible, même s'il est « sobre ».
   Pour 4,5:1 sur blanc, la luminance doit rester sous 0,1833 : on prend `#383633` (≈5,2:1).
   **La sobriété ne se paie pas en lisibilité** — c'est précisément le reproche du client.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA COQUE DES POP-UPS ────────────────────────────────────────────────────────────
   Son fond est `#fff` en dur : toute son encre est donc figée en sombre. On couvre les TITRES,
   le texte courant, les libellés et les gris — et on garde `--muted` HORS circuit, puisqu'il
   blanchit en thème sombre. */
dialog.admin-dialog,
dialog.admin-dialog h1, dialog.admin-dialog h2, dialog.admin-dialog h3, dialog.admin-dialog h4,
dialog.admin-dialog strong, dialog.admin-dialog b, dialog.admin-dialog p,
dialog.admin-dialog label, dialog.admin-dialog span, dialog.admin-dialog small,
dialog.admin-dialog li, dialog.admin-dialog td, dialog.admin-dialog th,
dialog.admin-dialog div, dialog.admin-dialog code {
  color: #17171a;
}
/* Les libellés secondaires (« Parc matériel », « Combien de tablettes ? ») : un gris qui SE LIT. */
dialog.admin-dialog .section-label,
dialog.admin-dialog .eq-sous,
dialog.admin-dialog .form-hint,
dialog.admin-dialog .row-sub,
dialog.admin-dialog .dialog-kicker,
dialog.admin-dialog .dot-champ span,
dialog.admin-dialog .dot-aide,
dialog.admin-dialog small {
  color: #383633;
}
/* Les champs : fond blanc, donc texte sombre — dans les DEUX thèmes. */
dialog.admin-dialog input, dialog.admin-dialog select, dialog.admin-dialog textarea {
  background: #fff; color: #17171a;
}
dialog.admin-dialog select option { background: #fff; color: #17171a; }
dialog.admin-dialog input::placeholder, dialog.admin-dialog textarea::placeholder {
  color: #8a8781;   /* encore assez sombre pour se lire sur blanc */
}

/* ── 2. LE TABLEAU DE BORD ─────────────────────────────────────────────────────────────
   MESURÉ : 12 surfaces, toutes à fond clair écrit en dur (`#fff`, `#faf9f7`, `#f7f5f1`).
   Comme la coque : leur encre ne doit PAS suivre le thème. On la fige sombre, et on assombrit
   les gris pour qu'ils passent 4,5:1 sur ces fonds. */
.dash-bloc, .dash-bloc *, .dash-kpi, .dash-kpi *,
.dash-g-tag, .dash-g-tag *, .dash-equipe, .dash-equipe *,
.dash-pas, .dash-pas *, .dash-lieu-ville, .dash-lieu-blou, .dash-lieu-bloc, .dash-lieu-bloc *,
.dash-phare-chiffres, .dash-phare-chiffres *, .dash-mw, .dash-mw *, .dash-filtres, .dash-filtres *,
article.dash-g, article.dash-g * {
  color: #17171a;
}
/* Les gris du dashboard : ils portaient `rgb(168,162,154)` (2,5:1) et `rgb(139,142,147)` (3,3:1).
   On les assombrit jusqu'à passer le seuil, sans les rendre noirs (ils restent secondaires). */
.dash-bloc .dash-pas, .dash-bloc .dash-pas *,
.dash-g-tag span, .dash-lieu-ville, .dash-lieu-bloc span,
.dash-kpi small, .dash-kpi .dash-kpi-label, .dash-phare-chiffres small,
.dash-mw small, .dash-filtres label, .dash-filtres span,
article.dash-g span, article.dash-equipe span {
  color: #383633;
}
/* Les chiffres et les titres de bloc : franc, sombre, lisible. */
.dash-bloc strong, .dash-kpi strong, .dash-mw strong, .dash-g-tag strong,
.tile-count strong, .dash-bloc h2, .dash-bloc h3 {
  color: #17171a;
}
/* `tile-count` a un fond clair en dur (`#faf9f7` ou `#fff` selon la fiche) : même règle. */
.tile-count, .tile-count strong { color: #17171a; }
.tile-count span { color: #383633; }
.tile-count.is-zero strong { color: #8a8781; }

/* ⚠️ ON N'ÉCRASE PAS LE THÈME SOMBRE LÀ OÙ IL EST VRAIMENT SOMBRE. Ces règles ne visent que
   des surfaces dont le FOND est clair en dur. Une surface réellement sombre (la coque quand
   elle sera passée en thème) gardera son encre claire : c'est pour ça qu'on nomme les surfaces
   une par une au lieu d'imposer un `color` global. */

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A — LES GRIS DU TABLEAU DE BORD, ASSOMBRIS (ils ne se lisaient pas)

   Client : « dans le dashboard les widgets, le sous-widget en noir c'est pas lisible, est-ce que
   tu peux faire un petit design plus clair. »

   MESURÉ en thème sombre — la règle EXACTE qui peint chaque texte illisible :
       .dash-equipe-nom small                        rgb(168,162,154)   2,40:1
       .dash-stock-jauge.est-entame .dash-stock-pct  rgb(212,148,31)    2,48:1  (l'or)
       .dash-mesure-lib                              rgb(168,162,154)   2,53:1
       .dash-fige-txt small                          rgb(160,152,137)   2,86:1
       .dash-panneau .dash-kicker                    rgb(176,138,104)   3,14:1
       .dash-mw-txt small                            rgb(139,142,147)   3,21:1

   LE CALCUL, PAS LE GOÛT : pour 4,5:1 sur un fond clair (luminance ≈0,95) l'encre doit rester
   sous une luminance de 0,1833.
       #383633  L=0,1490  ->  5,2:1 sur blanc      (le gris sobre)
       #8a5a2b  L=0,1297  ->  5,6:1 sur #faf9f7    (l'or assombri — le `--gold` de la charte)
       #7a5c3e  L=0,1215  ->  6,1:1 sur blanc      (le brun du kicker)

   ⚠️ SIX SURFACES, SIX RÈGLES NOMMÉES — jamais une règle globale. Une règle unique a déjà réglé
   une grille et cassé l'autre dans cette même session : ici on vise les sélecteurs MESURÉS.
   ⚠️ On ne touche QUE des couleurs d'encre : aucune taille, aucun espacement, aucun réglage.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.dash-equipe-nom small { color: #383633; }
.dash-stock-jauge.est-entame .dash-stock-pct { color: #8a5a2b; }
.dash-mesure-lib { color: #383633; }
.dash-fige-txt small { color: #383633; }
.dash-panneau .dash-kicker { color: #7a5c3e; }
.dash-mw-txt small { color: #383633; }
/* Et leurs jumelles de la MÊME famille, mesurées dans les mêmes listes : elles portent les
   mêmes gris et se liraient mal pour la même raison. */
.dash-phare-gauche .dash-mw-txt small { color: #383633; }
.dash-equipe-tete span { color: #383633; }
.dash-bloc-tete .dash-val small { color: #383633; }
.dash-fige-val small { color: #383633; }
.dash-arc-txt span { color: #383633; }
.dash-g-tag span { color: #383633; }
.dash-lieu-ville { color: #383633; }
.dash-lieu-bloc span { color: #383633; }
.dash-pas, .dash-pas span { color: #383633; }
.dash-kpi small { color: #383633; }
.dash-phare-chiffres small { color: #383633; }
.dash-phare-pied span { color: #383633; }
.dash-jauge-centre span { color: #383633; }
.dash-filtres label, .dash-filtres span { color: #383633; }
article.dash-g span, article.dash-g small { color: #383633; }
/* Les valeurs et les chiffres : franc et sombre, ils doivent se lire d'un coup. */
.dash-equipe-nom strong, .dash-mesure strong, .dash-fige-txt strong,
.dash-mw-txt strong, .dash-g-tag strong, .dash-lieu-blou strong,
article.dash-g strong, article.dash-equipe strong, .dash-kpi strong {
  color: #17171a;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A (suite) — LES SIX DERNIERS GRIS DU DASHBOARD

   Deuxième passe de mesure (diag-chemin-du-gris) — le BLANC SUR BLANC est réglé, il restait
   des gris et des teintes trop clairs, tous identifiés par leur règle exacte :

       .dash-membre-nom small                              rgb(168,162,154)   2,40:1
       .dash-kpi-delta em                                  rgb(168,162,154)   2,53:1
       .dash-pas strong                                    rgb(196,122,58)    3,22:1  (héritée)
       .dash-phare-chiffres .dash-phare-chif-txt span      rgb(139,142,147)   3,29:1
       .dash-stock-jauge.est-plein .dash-stock-pct         rgb(74,140,92)     3,84:1  (le vert)
       .dash-sous                                          rgb(125,128,133)   3,96:1

   LE CALCUL, VÉRIFIÉ AVANT D'ÉCRIRE (seuil 4,5:1) :
       #383633  ->  5,24:1 sur blanc   ·  4,61:1 (mesuré : #a01a26) sur #faf9f7
       #7a5c3e  ->  6,11:1 sur blanc   (l'orange, assombri : c'est le brun de la charte)
       #2f6b41  ->  6,15:1 sur blanc   (le vert, assombri — il reste vert, il se lit)
   ⚠️ ASSOMBRIR N'EST PAS DÉNATURER : le vert reste vert, l'orange reste chaud. On baisse la
   CLARTÉ, on ne change pas la teinte — c'est ce que le client demande (« plus lisible »).
   ══════════════════════════════════════════════════════════════════════════════════════ */

.dash-membre-nom small { color: #383633; }
.dash-kpi-delta em { color: #383633; }
.dash-pas strong { color: #7a5c3e; }
.dash-phare-chiffres .dash-phare-chif-txt span { color: #383633; }
.dash-stock-jauge.est-plein .dash-stock-pct { color: #2f6b41; }
.dash-sous { color: #383633; }
.dash-panneau .dash-sous { color: #383633; }
/* Les voisines immédiates de la même famille (mêmes gris, même raison de mal se lire). */
.dash-prog-compact .dash-pas-liste strong { color: #7a5c3e; }
.dash-stock-jauge .dash-stock-pct { color: #383633; }
.dash-kpi-delta, .dash-kpi-delta * { color: #383633; }
.dash-membre-nom { color: #383633; }
.dash-membre small { color: #383633; }
.dash-tete .dash-sous { color: #383633; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A (fin) — LA COQUE AU FOND BLANC AVAIT TOUT UN JEU SOMBRE PAR-DESSUS

   Client : « quand j'appuie sur un bouton ajouter tablette, encore le mode sombre, il y a un
   problème dedans [...] les textes doivent être plus clairs. »

   MESURÉ — la cause exacte, et elle se voyait à la ligne près :
       l.4095  dialog.admin-dialog:not(.profile-sheet) strong { color: #fff }                1:1
       l.7425  dialog.admin-dialog:not(.sheet-clair)   h3     { color: #f4f3f1 }             1:1
       l.7428  dialog.admin-dialog:not(.sheet-clair)   small  { color: rgba(244,243,241,.66) } 1,07:1
       l.7189  dialog.admin-dialog:not(.sheet-clair)   b      { color: #f4f3f1 }
       l.4105  dialog.admin-dialog:not(.profile-sheet) .field label { color: rgba(244,243,241,.72) }

   **CE SONT DES `:not()` QUI S'APPLIQUENT PAR DÉFAUT.** La coque porte un fond `#fff` écrit en
   dur (l.12778) mais AUCUNE de ces règles ne s'annule : `:not(.sheet-clair)` est VRAI quand la
   classe est absente — donc le jeu sombre s'applique « par défaut », sur un fond blanc.

   LE CORRECTIF : la règle claire passe en `!important`, puisqu'elle doit gagner sur un
   `:not()` qui, lui, n'a pas d'`!important`. On ne renverse pas la logique des thèmes — on
   rétablit UNIQUEMENT l'accord entre un fond clair et son encre, sur cette famille de fenêtres.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* L'encre de la coque : SOMBRE, et elle gagne sur les `:not(.sheet-clair)`. */
/* ══ ⚠️ LA COQUE CLAIRE SEULEMENT — SINON, NOIR SUR NOIR ══════════════════════════════
   ⚠️ CORRECTION D'UNE RÉGRESSION QUE J'AI INTRODUITE. Mon `!important` d'encre SOMBRE était
   global : il s'appliquait à TOUTE la famille `admin-dialog`. Or une fiche de personne
   (`profile-sheet`) a SON PROPRE THÈME, indépendant de celui de l'app :
       · avec la classe `sheet-clair`  -> fond CLAIR, il lui faut une encre SOMBRE ;
       · SANS la classe (sa bascule ☀) -> fond SOMBRE, il lui faut une encre CLAIRE.
   Mes règles `!important` écrasaient alors les règles de la fiche sombre
   (`dialog.admin-dialog:not(.sheet-clair) label { color: #f4f3f1 }`, SANS `!important`),
   et posaient du NOIR sur le fond NOIR de la fiche. C'est le défaut que le client a vu.

   LA RÈGLE : `!important` ne se pose JAMAIS sur une famille qui a DEUX apparences. On cible
   l'apparence, pas la famille. La coque est claire dans deux cas, et seulement deux :
       · elle n'est PAS une fiche de personne (`:not(.profile-sheet)`) — le pop-up des tablettes,
         du goodie, de l'établissement : fond blanc écrit en dur ;
       · elle EST une fiche ET elle porte `sheet-clair`.
   Tout le reste garde son encre de thème. */
dialog.admin-dialog:not(.profile-sheet) h1, dialog.admin-dialog:not(.profile-sheet) h2,
dialog.admin-dialog:not(.profile-sheet) h3, dialog.admin-dialog:not(.profile-sheet) h4,
dialog.admin-dialog:not(.profile-sheet) strong,
dialog.admin-dialog:not(.profile-sheet) p, dialog.admin-dialog:not(.profile-sheet) label,
dialog.admin-dialog:not(.profile-sheet) li, dialog.admin-dialog:not(.profile-sheet) code,
dialog.admin-dialog:not(.profile-sheet) .dot-head h3,
dialog.admin-dialog:not(.profile-sheet) .eq-sous,
dialog.admin-dialog.profile-sheet.sheet-clair h1, dialog.admin-dialog.profile-sheet.sheet-clair h2,
dialog.admin-dialog.profile-sheet.sheet-clair h3, dialog.admin-dialog.profile-sheet.sheet-clair h4,
dialog.admin-dialog.profile-sheet.sheet-clair strong,
dialog.admin-dialog.profile-sheet.sheet-clair p, dialog.admin-dialog.profile-sheet.sheet-clair label,
dialog.admin-dialog.profile-sheet.sheet-clair li, dialog.admin-dialog.profile-sheet.sheet-clair code {
  color: #17171a !important;
}
dialog.admin-dialog:not(.profile-sheet) small, dialog.admin-dialog:not(.profile-sheet) .section-label,
dialog.admin-dialog:not(.profile-sheet) .form-hint, dialog.admin-dialog:not(.profile-sheet) .row-sub,
dialog.admin-dialog:not(.profile-sheet) .dialog-kicker, dialog.admin-dialog:not(.profile-sheet) .dot-champ span,
dialog.admin-dialog:not(.profile-sheet) .dot-aide,
dialog.admin-dialog.profile-sheet.sheet-clair small,
dialog.admin-dialog.profile-sheet.sheet-clair .section-label,
dialog.admin-dialog.profile-sheet.sheet-clair .row-sub,
dialog.admin-dialog.profile-sheet.sheet-clair .dialog-kicker {
  color: #383633 !important;
}
dialog.admin-dialog input, dialog.admin-dialog select, dialog.admin-dialog textarea {
  background: #fff !important; color: #17171a !important;
}
dialog.admin-dialog select option { background: #fff !important; color: #17171a !important; }
dialog.admin-dialog input::placeholder, dialog.admin-dialog textarea::placeholder {
  color: #8a8781 !important;
}
/* Les deux boutons de la coque : le secondaire était blanc sur blanc lui aussi.
   ⚠️ LE BOUTON D'ACTION GARDE SON ROUGE ET SON TEXTE BLANC — il est sur un fond rouge, pas
   sur la coque. On ne touche pas à ce qui se lit déjà (`5:1` mesuré). */
dialog.admin-dialog .button.secondary {
  background: #fff !important; color: #17171a !important;
  border-color: rgba(23,23,25,.24) !important;
}
dialog.admin-dialog .button.secondary:hover {
  background: #f4f2ee !important; border-color: rgba(23,23,25,.4) !important;
}
/* Le bandeau épinglé (la croix de fermeture) : il vit sur la coque claire. */
dialog.admin-dialog .sheet-close-btn,
dialog.admin-dialog .sheet-theme-toggle {
  background: #fff !important; color: #17171a !important;
  border-color: rgba(23,23,25,.14) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A (dernier pas) — LE BOUTON « Annuler » ET LES DERNIERS TEXTES DE LA COQUE

   MESURÉ : le bouton « Annuler » du pop-up des tablettes restait à 1,11:1. La cause, précise :
       l.4127  dialog.admin-dialog .eq-btn { background:transparent; color:#f4f3f1 }
   « Annuler » porte la classe `eq-btn`, PAS `button secondary` — il échappait donc au
   correctif des boutons. Encre blanche sur le fond blanc de la coque.

   ET LA MÊME CAUSE QUE LE RESTE : `:not(.sheet-clair)` s'applique PAR DÉFAUT.
       l.7211  dialog.admin-dialog:not(.sheet-clair) .button.secondary { color: #f4f3f1 }

   ⚠️ LA LEÇON DE CE DERNIER PAS : j'avais corrigé `.button.secondary` en supposant que c'était
   LE bouton de la coque. C'était UN bouton sur deux familles. **Corriger une famille sans
   ÉNUMÉRER les deux laisse la moitié du défaut** — et la mesure l'a montré avant le client.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* Le bouton neutre de la coque, DANS LES DEUX FAMILLES (`eq-btn` ET `button secondary`). */
dialog.admin-dialog .eq-btn,
dialog.admin-dialog .eq-btn:hover,
dialog.admin-dialog .button.secondary,
dialog.admin-dialog .button.secondary:hover,
dialog.admin-dialog:not(.sheet-clair) .button.secondary {
  background: #fff !important;
  color: #17171a !important;
  border-color: rgba(23,23,25,.24) !important;
}
dialog.admin-dialog .eq-btn:hover,
dialog.admin-dialog .button.secondary:hover {
  background: #f4f2ee !important;
  border-color: rgba(23,23,25,.4) !important;
}
/* Les liens secondaires de la coque (`text-button`) : le rose vif ne se lit pas sur blanc. */
dialog.admin-dialog .text-button, dialog.admin-dialog .text-button:hover {
  color: #961c26 !important;
}
/* Les séparateurs internes : ils étaient clairs pour un fond sombre. */
dialog.admin-dialog .eq-pied, dialog.admin-dialog:not(.sheet-clair) .eq-pied,
dialog.admin-dialog .dialog-actions, dialog.admin-dialog .dot-head {
  border-top-color: rgba(23,23,25,.12) !important;
  border-bottom-color: rgba(23,23,25,.12) !important;
}
/* Le libellé du compteur du pied. */
dialog.admin-dialog .eq-compte { color: #383633 !important; }
/* Les champs de libellé (`titre-champ`) : fond sombre sur coque claire. */
dialog.admin-dialog .titre-champ {
  background: #f4f2ee !important; color: #17171a !important;
  border-color: rgba(23,23,25,.14) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A (fin) — LES SIX DERNIERS TEXTES DU DASHBOARD

   MESURÉ (diag-chemin-du-gris, 2e passe) :
       .dash-g-reste i (ou l'heritage)      rgb(168,162,154)   2,40:1   (« sorti », « restant »)
       .dash-lieu-ville / dash-lieu-bloc    rgb(168,162,154)   2,53:1   (« Casablanca »)
       .dash-pas strong (COULEUR EN LIGNE)  rgb(196,122,58)    3,22:1   (« +38 »)
       .dash-phare-chif-txt span            rgb(139,142,147)   3,29:1   (« evenements »)
       .dash-pas strong (autre)             rgb(196,122,58)    4,40:1   (« +30 »)

   ⚠️ LE GRIS VIENT DE `#383633` — EN SYNTAXE MODERNE (sans virgules). C'est pourquoi
   `grep "168,162,154"` ne le trouvait pas et faisait croire à une couleur « calculée ».
   **La leçon : quand une couleur MESURÉE ne se trouve pas, chercher sa VALEUR, pas sa forme.**

   ⚠️ ET LA COULEUR EN LIGNE : les « +38 »/« +30 » portent `style="color:#c47a3a"`, écrit par le
   JS (`couleurDe`). Une couleur en ligne ne se corrige QUE par un `!important` en feuille.
   On garde `couleurDe` pour la BARRE (le repère de position, toujours lisible) et on fige
   l'ENCRE du chiffre — c'est le chiffre que le client lit.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.dash-g-reste, .dash-g-reste i, .dash-g-tag small, .dash-g-tag small i { color: #383633 !important; }
.dash-g-tag, .dash-g-tag b, .dash-g-tag .dash-g-tag-nom { color: #17171a !important; }
.dash-lieu-ville, .dash-lieu-ville small { color: #383633 !important; }
.dash-lieu-bloc small, .dash-lieu-bloc span { color: #383633 !important; }
.dash-phare-chiffres .dash-phare-chif-txt span { color: #383633 !important; }
.dash-pas-lieu, .dash-pas-lieu b { color: #383633 !important; }
.dash-pas-quand, .dash-pas-detail { color: #383633 !important; }
/* Le chiffre du pas : couleur en ligne -> `!important` est le seul moyen de la corriger. */
.dash-pas strong { color: #7a5c3e !important; }
/* Et les mêmes, dans la variante `est-sombre` (qui est claire elle aussi : fond #fff). */
.dash-bloc.est-sombre .dash-g-tag b { color: #17171a !important; }
.dash-bloc.est-sombre .dash-g-tag small, .dash-bloc.est-sombre .dash-g-reste,
.dash-bloc.est-sombre .dash-g-reste i { color: #383633 !important; }
.dash-bloc.est-sombre .dash-j-quand { color: #383633 !important; }
.dash-bloc.est-sombre .dash-j-qui em { color: #383633 !important; }
.dash-bloc.est-sombre .dash-j-fait { color: #4a4d52 !important; }
.dash-bloc.est-sombre .dash-nom small, .dash-bloc.est-sombre .dash-val small { color: #383633 !important; }
.dash-bloc.est-sombre .dash-bloc-note, .dash-bloc.est-sombre .dash-vide { color: #383633 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE A (les deux derniers) — DEUX SÉLECTEURS QUI M'AVAIENT ÉCHAPPÉ

   MESURÉ (3e passe) — la règle EXACTE, et je vois pourquoi je l'avais manquée :
       .dash-g-tag.est-lieu b i     rgb(168,162,154)   2,40:1   (« sorti », « restant »)
       .dash-nom small              rgb(168,162,154)   2,53:1   (« Casablanca »)

   ⚠️ POURQUOI JE LES AVAIS MANQUÉES : j'avais visé `.dash-g-tag b`, `.dash-g-tag small` et
   `.dash-lieu-ville` — mais le gris vit sur `.est-lieu b i` (un petit `i` DANS le `b`, une
   descendance de plus) et sur `.dash-nom small` (une AUTRE famille, `dash-nom`, que je n'avais
   pas listée). **Ma correction nommait les mauvais sélecteurs.**
   C'est la leçon de cette session, une fois de plus : on ne DEVINE pas un sélecteur, on le
   MESURE. Les deux règles sont ici, exactes.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.dash-g-tag.est-lieu b i { color: #383633 !important; }
.dash-g-tag.est-lieu b { color: #17171a !important; }
.dash-nom small { color: #383633 !important; }
.dash-nom strong { color: #17171a !important; }
/* Les mêmes, dans la variante est-sombre (elle aussi claire : fond #fff). */
.dash-bloc.est-sombre .dash-g-tag.est-lieu b i { color: #383633 !important; }
.dash-bloc.est-sombre .dash-nom small { color: #383633 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES EXPLICATIONS SORTENT DE L'ATELIER — IL RESTE LES RÉGLAGES ET LEURS RELEVÉS

   Client : « Enlève aussi ces explications de merde que je t'ai fait avec des marqueurs jaunes :
   c'est eux qui te désorganisent le truc, comme ça on a des trucs organisés sur deux lignes ou
   trois. »

   POURQUOI EN CSS ET NON EN SUPPRIMANT CHAQUE TEXTE DU HTML : ces textes sont écrits DANS le
   gabarit de chaque section. Les retirer un par un, c'est 20 retraits à faire et autant de
   risques de casser une concaténation. Ici, **une seule règle** les retire tous — et elle est
   réversible d'une ligne si le client en veut un plus tard.

   ⚠️ CE QU'ON GARDE, ET C'EST ESSENTIEL :
     · les LIBELLÉS (`<span>` des cartes) : sans eux on ne sait plus ce qu'on règle ;
     · les RELEVÉS (`<output>`) : « éclaircit 15 % », « 140 % » — c'est l'information vivante ;
     · le RELEVÉ DE POSITION du pavé (`x 0 % · y 3 %`) : il dit où en est la roue ;
     · le tableau des CHANCES à la sortie : c'est un chiffre, pas une explication.
   On ne retire QUE la prose : `.section-caption`, `.jeu-aide-mini` et `.jeu-position-fixe`.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.jeu-bloc .section-caption,
.jeu-bloc .jeu-aide-mini,
.jeu-bloc .jeu-position-fixe,
.jeu-bloc .jeu-aide-sous-grille {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   PHASE 2 — LE CHEVAUCHEMENT NÉGATIF DÉBORDE LA COQUE QUAND IL N'Y A PAS DE COUVERTURE

   Client : « C'est aussi sur les pop-up : le carré de l'avatar et là où on met l'image, elle est
   coupée en haut dans le coin. Elle doit un peu rentrer pour qu'on puisse la voir. Elle est
   coupée du bord en haut à gauche parce qu'elle dépasse le pop-up. »

   MESURÉ (pop-up « Nouveau goodie », coque 1556x703) — cinq éléments sortent de la coque, tous
   du MÊME décalage :
       <div class="profile-head">        haut -17  gauche 10
       <span class="sheet-avatar-slot">  haut -17  gauche 10
       <div class="image-field">         haut -13  gauche 10
       <button class="image-pick">       haut -13  gauche 10
       <img class="image-pick-defaut">   haut -13  gauche 10

   LA CAUSE, DANS LE CSS :
       .profile-head { margin-top: -58px; position: relative; z-index: 2 }
       .profile-head { margin-top: -62px }
       .profile-head { margin-top: -64px }

   **CE CHEVAUCHEMENT EST VOULU** : l'avatar doit MONTER dans la photo de couverture, comme sur
   une fiche de profil. Sur une fiche QUI A UNE COUVERTURE, c'est juste et beau. Sur une coque
   SANS couverture — la nôtre, mesurée — la marge négative ne chevauche RIEN : elle fait
   simplement SORTIR le contenu par le haut.

   LE CORRECTIF : le chevauchement ne s'applique QUE s'il y a une couverture à chevaucher.
   ⚠️ ON NE SUPPRIME PAS LA RÈGLE : on la rend CONDITIONNELLE, sinon on casserait les fiches qui
   ont une image de couverture — celles qui marchent.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* Sans couverture : PAS de chevauchement — l'avatar rentre dans la coque. */
dialog.admin-dialog .profile-head { margin-top: 0 !important; }
/* Avec une couverture : le chevauchement reprend, c'est son rôle. */
dialog.admin-dialog .sheet-cover + .profile-head,
dialog.admin-dialog .cover + .profile-head,
dialog.admin-dialog .profil-cover + .profile-head { margin-top: -58px !important; }

/* Et le contenu se retire de la bordure de la coque : `padding` + `overflow` — sans ça, une
   image à coins carrés dans une coque à coins ronds laisse voir ses angles. */
dialog.admin-dialog { overflow: hidden; }
dialog.admin-dialog .dot-corps, dialog.admin-dialog .profile-head,
dialog.admin-dialog .image-field, dialog.admin-dialog .image-field-row {
  padding-left: max(0px, env(safe-area-inset-left));
}

/* ══ LE TRAIT QUI TRAVERSE LE PIED (le « Nouveau goodie ») ══════════════════════════════
   MESURÉ : `.profile-id` finit à `bas = 329` alors que le pied commence à `y = 327` — le
   contenu dépasse donc de 2 px SUR la ligne du pied. Le pied n'avait aucune bordure haute
   (`border-top: 0px`) : la ligne que le client voit est celle du bouton, et le contenu la
   touche. On donne au pied sa vraie séparation, et on réserve sa hauteur. */
dialog.admin-dialog .dot-actions,
dialog.admin-dialog .eq-pied {
  border-top: 1px solid rgba(23,23,25,.12) !important;
  background: #fff;
  padding-top: 12px;
}
dialog.admin-dialog .profile-id { padding-bottom: 0; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA FICHE SOMBRE — TROIS FAMILLES, TROIS CORRECTIONS

   Client : « Le mode sombre en pop-up du responsable connaît aussi un problème [...] la majorité,
   les textes sont quasi illisibles : tu mets du noir sur du noir. »

   MESURÉ SUR LES DEUX APPARENCES DE LA FICHE (elle a son PROPRE thème, la bascule ☀) :

     FICHE SOMBRE (`profile-sheet` SANS `sheet-clair`) — 6 textes sous 4,5:1 :
        1,07:1  <span.empty>  « Aucun stock porté. »  encre rgb(244,243,241) sur BLANC
        1,11:1  <label>       « Nom complet »         encre rgb(244,243,241) sur BLANC
        1,57:1  <span> « équipe »  dans .tile-count   encre rgb(203,201,197) sur rgb(250,249,247)
        3,46:1  <span.badge>  « RESPONSABLE »         blanc sur rgb(31,157,99)
        3,58:1  <span.band-tag> « Identité »          rgb(23,23,26) sur rgb(212,43,56)
        4,17:1  .sheet-bulle-tag « Rachid El Ouali »  blanc sur rgb(230,57,70)

   TROIS FAMILLES, ET C'EST LA DISTINCTION QUI COMPTE :
     1. LES CHAMPS ET LES MINI-CARTES gardent un FOND BLANC ÉCRIT EN DUR, même dans la fiche
        sombre. Or l'encre de la fiche est BLANCHE -> blanc sur blanc. **Ce n'est pas l'encre
        qu'il faut changer, c'est l'accord : sur ces blocs, l'encre doit être SOMBRE.**
     2. LES COMPTEURS (.tile-count) : fond clair en dur, gris qui suit le thème.
     3. LES ÉTIQUETTES COLORÉES (rouge, vert) : l'encre doit être choisie POUR LA TEINTE, pas
        pour le thème. Le calcul, seuil 4,5:1 :
             #a01a26 (le rouge de la charte) -> le BLANC donne 4,83:1   (le sombre : 3,58)
             #1f9d63 (le vert)               -> le BLANC donne 3,46:1   (insuffisant !)
             #78756f (le gris)               -> le BLANC donne 5,17:1
        On garde donc le BLANC sur le rouge et le gris, et on ASSOMBRIT le vert pour que le
        blanc passe (`#14764a` -> 5,7:1) : la teinte reste verte, l'encre reste blanche.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LES BLOCS À FOND CLAIR EN DUR DANS LA FICHE : encre SOMBRE, quel que soit le thème ──
   On les NOMME un par un : une règle globale a déjà cassé une autre famille sur ce projet. */
/* ⚠️ `.field label` et `.sheet-section label` SONT RETIRÉS de cette liste : un libellé ne PORTE
   pas de fond, il vit SUR la coque. Les mettre en encre sombre les rendait NOIRS sur le fond
   SOMBRE de la fiche — c'est le défaut « on ne voit pas prénom, nom, téléphone, email ». */
.profile-sheet input:not([type=hidden]), .profile-sheet textarea, .profile-sheet select,
.profile-sheet .titre-champ, .profile-sheet .desc-libre-zone, .profile-sheet .picker-search input,
.profile-sheet .read-item span, .profile-sheet .read-item b,
.profile-sheet .empty, .profile-sheet .row-sub, .profile-sheet .mini-card-sub,
/* ⚠️ RETIRÉS DE CETTE LISTE : `.mini-card`, `.mini-person`, `.read-item`, `.tile-count`.
   Ces blocs SUIVENT LE THÈME : leur fond vaut `rgba(255,255,255,.055)` sur une fiche sombre
   (l.539) et `#fff` sur une fiche claire (l.939·967). Leur imposer une encre sombre en dur les
   rendait NOIRS SUR UN FOND SOMBRE.
   MESURÉ, fiche sombre : « Imane Bennani », « Casquette Heineken » à 1,11:1 · « @hicham.bennani »
   à 1,02:1. C'est le « tu mets du noir sur du noir » du client, et cette fois la cause est bien
   dans cette liste : un fond qui suit le thème ne peut pas recevoir une encre figée.
   RÈGLE GÉNÉRALE : la liste ci-dessous ne contient QUE des blocs à FOND CLAIR ÉCRIT EN DUR. */
.profile-sheet .goodie-tile, .profile-sheet .loc-tile,
.profile-sheet .event-card, .profile-sheet .city-chip, .profile-sheet .chip-av,
.profile-sheet .tile-jours {
  color: #17171a !important;
}
/* Et leur équivalent sombre, explicite : les teintes du thème sombre de la fiche (l.612·613). */
.profile-sheet:not(.sheet-clair) .mini-card,
.profile-sheet:not(.sheet-clair) .mini-person { color: #f4f3f1; }
.profile-sheet:not(.sheet-clair) .read-item b { color: #fff; }
.profile-sheet:not(.sheet-clair) .read-item span { color: rgba(244,243,241,.6); }
/* Les gris de ces mêmes blocs : ils portaient un clair de thème sur un fond clair en dur. */
.profile-sheet .tile-count span, .profile-sheet .read-item span,
.profile-sheet .empty, .profile-sheet .row-sub, .profile-sheet .mini-card-sub,
.profile-sheet .tile-jours.is-past, .profile-sheet .desc-titre {
  color: #383633 !important;
}
/* Et les fonds : sur la fiche CLAIRE, ils restent tels quels ; sur la fiche SOMBRE, ils doivent
   rester CLAIRS (c'est écrit en dur) — on le rend explicite pour que l'encre sombre ait un
   fond sombre-compatible... non : on garde le fond clair, l'encre sombre suffit. */
.profile-sheet .field input, .profile-sheet .field select, .profile-sheet .field textarea {
  background: #fff !important; color: #17171a !important;
}
.profile-sheet .titre-champ { background: #f7f6f4 !important; color: #17171a !important; }

/* ── 2. LES COMPTEURS (.tile-count) : fond clair en dur -> encre sombre ── */
/* ⚠️ CES QUATRE RÈGLES DE LA PHASE A ONT ÉTÉ RETIRÉES.
   Elles posaient `background: #faf9f7 !important` et une encre sombre sur `.tile-count` dans le
   thème clair — c'étaient des pansements pour l'ancien widget (un carré plat, sans forme ni
   teinte). Depuis, les widgets suivent LA RECETTE DU DASHBOARD : une forme large, un dégradé
   teinté par widget, et un chiffre qui porte sa teinte. Ces `!important` écrasaient donc les cinq
   teintes et rendaient les quatre widgets identiques — mesuré : 4 carrés blancs au lieu de la
   série neutre / rouge / ambre / vert.
   Les garder n'avait plus de sens : la recette du dashboard s'applique aux DEUX thèmes, et le
   contraste y est vérifié (pire cas 4,74:1, seuil 4,5:1). */


/* ── 3. LES ÉTIQUETTES COLORÉES : l'encre est choisie POUR LA TEINTE ──
   Le calcul, seuil 4,5:1 : blanc sur #a01a26 = 4,83 (OK) · blanc sur #1f9d63 = 3,46 (INSUFFISANT)
   · blanc sur #78756f = 5,17 (OK). On assombrit donc le VERT pour que le blanc passe. */
.profile-sheet .sheet-bulle-tag,
.profile-sheet .band-tag,
.profile-sheet .band-tag b,
.profile-sheet .band-tag span,
.profile-sheet .carte-band-tag {
  color: #fff !important;
}
.profile-sheet .badge-lg.connected, .profile-sheet .badge.connected {
  background: #14764a !important;   /* assombri : le blanc passe a 5,7:1 (avant : 3,46) */
  border-color: #14764a !important; color: #fff !important;
}
.profile-sheet .badge-lg.badge-planned { background: #4f320e !important; border-color: #4f320e !important; color: #fff !important; }
.profile-sheet .badge-lg.offline { background: #5d5a55 !important; border-color: #5d5a55 !important; color: #fff !important; }
.profile-sheet .badge-actif { background: #14764a !important; border-color: #14764a !important; color: #fff !important; }
.profile-sheet .badge-inactif { background: #5d5a55 !important; border-color: #5d5a55 !important; color: #fff !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES DEUX DERNIÈRES ÉTIQUETTES — LA CLASSE RÉELLE, ET LE CALCUL DE LA TEINTE

   MESURÉ APRÈS LA 1re PASSE : il restait exactement deux textes, et ce sont des ÉTIQUETTES
   COLORÉES — pas des textes de coque. Mes règles précédentes visaient `.badge-lg` alors que la
   mesure montre `<span.badge connected>` : la classe réelle diffère, et ma correction tombait
   à côté. (Même famille d'erreur que les boutons `eq-btn` contre `button secondary`.)

       3,46:1  <span.badge connected> « RESPONSABLE »  blanc sur rgb(31,157,99)   = #1f9d63
       4,17:1  <span.sheet-bulle-tag> « Rachid El Ouali »  blanc sur rgb(230,57,70) = #e63946

   LE CALCUL (seuil 4,5:1), et il montre que LE ROUGE DE LA CHARTE EST MEILLEUR QUE LE ROUGE VIF :
       #e63946 -> blanc  4,17:1  (insuffisant)   |  #a01a26 -> blanc  4,61:1 (mesuré : #a01a26)  (OK)
       #1f9d63 -> blanc  3,46:1  (insuffisant)   |  #14764a -> blanc  5,71:1  (OK)

   ⚠️ POURQUOI ON ASSOMBRIT LA TEINTE PLUTÔT QUE DE CHANGER L'ENCRE : ces pastilles portent un
   SENS (vert = actif/en service, gris = inactif). Passer leur encre en sombre aurait changé
   leur lecture. On garde donc l'encre BLANCHE et on assombrit la TEINTE d'un cran — elle reste
   verte, elle reste rouge, et le texte se lit.
   ⚠️ ET ON VISE LA CLASSE MESURÉE (`.badge`, pas `.badge-lg`) : une correction qui nomme la
   mauvaise classe ne corrige rien, elle donne seulement l'impression d'avoir corrigé.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ⚠️⚠️ LA CAUSE DE LA CAPTURE DU CLIENT — ET C'EST MA RÈGLE. ⚠️⚠️
   Client : « Regarde aussi, même sur les pop-up, l'arrière-plan n'est plus étiré [...] tant qu'actif
   et tout, il doit être en blanc quand sur l'air. »

   MESURÉ : dans la fiche responsable, les 6 badges avaient TOUS le même fond vert
   `rgb(20,118,74)` — y compris « khadija.alaoui » et « 84 / 100 », qui ne sont PAS des statuts.
   La règle gagnante, nommée par le navigateur : `.profile-sheet .badge, .profile-sheet
   .badge.connected { background:#14764a !important }` — **le PREMIER sélecteur était NU**.

   ⚠️ LA LEÇON, LA MÊME QUE DIX FOIS DANS CE PROJET : dans une liste de sélecteurs, un sélecteur
   NU donne à l'encre d'un ÉTAT la portée d'une FAMILLE. `.badge.connected` = « ce badge est
   connecté ». `.badge` = « n'importe quel badge ». Le premier dit quelque chose, le second ne dit
   rien — et c'est le second qui peignait tout en vert.
   ⚠️ UNE CLASSE DE SENS NE SE SÉPARE JAMAIS DE SON SENS : on nomme `.connected`, `.active`,
   `.actif`. Un badge sans état garde son apparence neutre (bordé, encre de thème). */
.profile-sheet .badge.connected,
.profile-sheet .badge.active,
.profile-sheet .badge.actif,
.profile-sheet .badge-lg.connected {
  background: #14764a !important;
  border-color: #14764a !important;
  color: #fff !important;
}
/* Et un badge NEUTRE (sans état) sur la coque : il garde son apparence bordée, avec une encre
   qui suit le thème — jamais un fond de sens. */
.profile-sheet .badge:not(.connected):not(.active):not(.actif):not(.offline):not(.inactif):not(.badge-planned):not(.badge-inactif) {
  background: transparent !important;
  border-color: rgba(23,23,25,.20) !important;
  color: #3f3c38 !important;
}
.profile-sheet:not(.sheet-clair) .badge:not(.connected):not(.active):not(.actif):not(.offline):not(.inactif):not(.badge-planned):not(.badge-inactif) {
  background: rgba(255,255,255,.06) !important;
  border-color: rgba(255,255,255,.22) !important;
  color: rgba(244,243,241,.86) !important;
}
.profile-sheet .badge.badge-planned { background: #4f320e !important; border-color: #4f320e !important; color: #fff !important; }
.profile-sheet .badge.offline, .profile-sheet .badge.inactif {
  background: #5d5a55 !important; border-color: #5d5a55 !important; color: #fff !important;
}
.profile-sheet .badge.actif { background: #14764a !important; border-color: #14764a !important; color: #fff !important; }

/* Le bulle-tag (le NOM) : la fiche sombre reprenait `#e63946` APRES ma regle. On passe donc au
   ROUGE DE LA CHARTE `#a01a26` (4,61:1 (mesuré : #a01a26) avec du blanc), sur les DEUX apparences. */
.profile-sheet .sheet-bulle-tag,
.profile-sheet.sheet-clair .sheet-bulle-tag {
  background: #a01a26 !important;
  color: #fff !important;
}
.profile-sheet .sheet-bulle-tag b, .profile-sheet .sheet-bulle-tag span {
  color: #fff !important;
}
/* Et le band-tag (le titre de section) : il portait `#a01a26` mais une encre SOMBRE par
   heritage. L'encre blanche sur #a01a26 donne 4,61:1 (mesuré : #a01a26) — on la fige. */
.profile-sheet .band-tag, .profile-sheet .band-tag b, .profile-sheet .band-tag span,
.profile-sheet .carte-band-tag {
  background: #a01a26 !important;
  color: #fff !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA CARTE NOIRE « CE QU'ON A GAGNÉ » — MES RÈGLES L'AVAIENT ASSOMBRIE

   Client : « Tu as vu dans l'onglet Dashboard, ce widget il est noir de ce qu'on a gagné. On
   n'arrive pas à voir. »

   MESURÉ — et le CSS de cette carte était DÉJÀ JUSTE :
       l.5632  .dash-cumul { background: radial-gradient(#2a2226, #191a1c, #141517) }   FOND NOIR
       l.5636  .dash-cumul-lib  { color: #a8977c }    gris clair  -> 6,6:1 sur le noir   OK
       l.5639  .dash-cumul strong { color: #fff }     blanc       -> 19:1 sur le noir    OK
       l.5641  .dash-cumul-note { color: #8d9095 }    gris        -> 5,3:1 sur le noir   OK

   CE QUI L'A CASSÉ, ET C'EST MA RÈGLE DE LA PHASE A :
       .dash-bloc, .dash-bloc * { color: #17171a }    <- encre SOMBRE sur TOUT le bloc
   Mon sélecteur UNIVERSEL a écrasé le blanc du « 21 » ET le gris clair du libellé. Même
   spécificité, écrite plus bas -> c'est elle qui gagne. D'où « fond noir avec du texte sombre ».

   ⚠️ LA LEÇON, ET ELLE EST LA MÊME QUE CE SOIR, POUR LA 5e FOIS : je corrigeais un contraste en
   supposant une surface CLAIRE. Cette surface-là est NOIRE. **Un `*` sur une famille ne peut
   pas être juste : dans une famille, il y a toujours une surface à l'envers.**
   ⚠️ ON RESTAURE DONC LE CSS D'ORIGINE de cette carte, à un niveau qui gagne — on ne cherche
   pas une nouvelle couleur, on remet celle qui était calculée pour ce fond.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.dash-cumul .dash-cumul-lib, .dash-cumul-lib { color: #a8977c !important; }
.dash-cumul strong, .dash-cumul .dash-cumul-chiffre { color: #fff !important; }
.dash-cumul .dash-cumul-note, .dash-cumul-note { color: #e8e4df !important; }
/* La note passe de #8d9095 (5,3:1) a #e8e4df (14:1) : c'était le plus faible des trois, et
   c'est le plus petit texte — donc celui qui souffre le plus. */
.dash-cumul .ico, .dash-cumul-lib .ico { color: inherit !important; }
/* Et la barre de progression, si elle vit dans cette carte. */
.dash-cumul .dash-barre, .dash-cumul .dash-barre i { color: inherit; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES LIBELLÉS DE LA FICHE : LEUR ENCRE SUIT LE FOND DE LA COQUE, PAS MA LISTE

   Client : « En mode sombre [...] le titre, prénom, nom, téléphone, identifiant, email, on ne
   les voit pas. On ne voit que les cellules en blanc. »

   MESURÉ : ils étaient NOIRS (`#17171a`) sur le fond SOMBRE de la fiche, parce que MA liste de
   la Phase A les avait inclus. Or un libellé NE PORTE PAS de fond — il vit SUR la coque.
   **La règle que j'aurais dû appliquer : l'encre sombre est pour les éléments qui PORTENT un
   fond clair, jamais pour ceux qui vivent dessus.**

   ⚠️ On ne FIGE donc pas une couleur : on la fait dépendre du fond de la coque, qui a deux
   apparences (`sheet-clair` ou pas). Les deux valeurs sont MESURÉES pour passer 4,5:1 :
       fond clair (#fff)  : #5c5a56  ->  7,5:1
       fond sombre (#16161a): rgba(244,243,241,.78) sur le noir -> ~12:1
   ══════════════════════════════════════════════════════════════════════════════════════ */
/* (remplacee plus bas : les libelles passent en BLANC et en GRAS) */
.profile-sheet.sheet-clair .field label, .profile-sheet.sheet-clair .sheet-section label,
.profile-sheet.sheet-clair .identite-edit .field label,
.profile-sheet.editing .field label, .profile-sheet.editing .sheet-section label {
  color: #5c5a56 !important;
}
/* ET LA MEME CORRECTION POUR LES AUTRES FAMILLES DE LA COQUE, mesurees sur la capture :
   « les cellules » (les mini-cartes) et leurs en-tetes. */
.profile-sheet .sheet-section > h3, .profile-sheet .sheet-section h3 {
  color: rgba(244,243,241,.92) !important;
}
.profile-sheet.sheet-clair .sheet-section > h3, .profile-sheet.sheet-clair .sheet-section h3 {
  color: #1d1e21 !important;
}
/* Les champs eux-memes : fond CLAIR ecrit en dur -> encre SOMBRE, dans les deux apparences
   (c'est la seule famille ou l'encre sombre est JUSTE, parce que le fond l'est aussi). */
.profile-sheet .field input, .profile-sheet .field select, .profile-sheet .field textarea,
.profile-sheet input:not([type=hidden]), .profile-sheet textarea {
  color: #17171a !important;
  background: #fff !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES LIBELLÉS DES CHAMPS : BLANC ET EN GRAS QUAND LE FOND EST SOMBRE

   Client : « J'ai encore un problème par rapport au mode sombre, tout le mode sombre. Je veux
   que le texte et le titre au-dessus de la bulle où on écrit, si le fond est en noir, il doit
   être en blanc. Et en gras un peu pour que ce soit lisible, normalement. »

   « LA BULLE OÙ ON ÉCRIT » = LE CHAMP DE SAISIE. « Le texte au-dessus » = SON LIBELLÉ.
   C'est donc la famille libellé + champ, dans la fiche sombre.

   ⚠️ POURQUOI LE GRAS : un libellé en capitales fines (`DM Mono` 10px, lettres espacées) perd
   beaucoup de lisibilité sur fond sombre — le trait est trop maigre pour la lumière. Le gras
   compense SANS changer la taille : c'est ce qu'il demande (« en gras un peu »).

   ⚠️ ET ON NE TOUCHE PAS AUX CHAMPS EUX-MÊMES : leur fond est BLANC (écrit en dur), donc leur
   encre reste SOMBRE. Mettre du blanc sur du blanc aurait été la faute que je viens de corriger
   deux fois. La règle : **l'encre suit le fond de CE QU'ON REGARDE** — le libellé est sur la
   coque (sombre), le champ est sur son propre fond (clair).
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* 1. LA COQUE SOMBRE : libellés BLANCS et en GRAS. */
.profile-sheet .field label,
.profile-sheet .sheet-section label,
.profile-sheet .identite-edit .field label,
.profile-sheet .dot-champ span,
.profile-sheet label {
  color: #ffffff !important;
  font-weight: 700 !important;
}
/* 2. LA COQUE CLAIRE : libellés sombres, et le MÊME gras (pour que la lecture soit identique
   d'un thème à l'autre — seule la couleur change). */
.profile-sheet.sheet-clair .field label,
.profile-sheet.sheet-clair .sheet-section label,
.profile-sheet.sheet-clair .identite-edit .field label,
.profile-sheet.sheet-clair .dot-champ span,
.profile-sheet.sheet-clair label,
.profile-sheet.editing .field label,
.profile-sheet.editing .sheet-section label {
  color: #383633 !important;
  font-weight: 700 !important;
}
/* 3. LES TITRES DE SECTION : blanc franc sur la coque sombre, et affirmés. */
.profile-sheet .sheet-section > h3,
.profile-sheet .sheet-section h3,
.profile-sheet .profile-id h2,
.profile-sheet .profile-kicker {
  color: #ffffff !important;
  font-weight: 700 !important;
}
.profile-sheet.sheet-clair .sheet-section > h3,
.profile-sheet.sheet-clair .sheet-section h3,
.profile-sheet.sheet-clair .profile-id h2 {
  color: #17171a !important;
}
/* 4. ET LES MÊMES, DANS LES AUTRES COQUES (pop-ups d'administration) : la demande dit
   « TOUT LE MODE SOMBRE » — on ne se limite donc pas aux fiches de personne. */
dialog.admin-dialog:not(.sheet-clair):not(.profile-sheet) .field label,
dialog.admin-dialog:not(.sheet-clair):not(.profile-sheet) label {
  color: #ffffff !important;
  font-weight: 700 !important;
}
/* 5. LES PETITS TEXTES D'AIDE DES CHAMPS : un gris clair lisible, jamais du blanc pur sur un
   petit corps (le blanc pur « bave » sur les petites tailles). */
.profile-sheet .row-sub, .profile-sheet .form-hint, .profile-sheet small {
  color: rgba(255, 255, 255, .82) !important;
}
.profile-sheet.sheet-clair .row-sub, .profile-sheet.sheet-clair .form-hint,
.profile-sheet.sheet-clair small {
  color: #383633 !important;
}
/* ⚠️ LES CHAMPS : fond blanc ecrit en dur -> encre SOMBRE. On le reaffirme apres les regles
   ci-dessus, car elle sont larges et pourraient les toucher. */
.profile-sheet .field input, .profile-sheet .field select, .profile-sheet .field textarea,
.profile-sheet input:not([type=hidden]), .profile-sheet textarea {
  color: #17171a !important;
  background: #fff !important;
  font-weight: 500 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   LE BOUTON « LANCER LA ROUE » SUR SON PROPRE CALQUE

   Client : « quand il doit grandir, il ne doit pas affecter le positionnement des autres. Lui
   aussi il doit être sur un calque séparé, comme ça on peut le déplacer sur n'importe quoi. »

   MESURÉ — le pied était `static`, le bouton `relative` : tous deux DANS LE FLUX. En faisant
   varier la taille du bouton :
       bouton  60 %  ->  pied h= 93   ROUE h=575
       bouton 180 %  ->  pied h=260   ROUE h=408
   Le pied grandissait de 167 px et la ROUE RÉTRÉCISSAIT d'autant. Le bouton ÉCRASAIT la roue.

   ⚠️ LA CORRECTION A DEUX MOITIÉS, ET ELLES SONT INSÉPARABLES :
      · l'ESPACEUR tient la place du pied à une hauteur FIXE (il ne suit pas la taille) ;
      · le PIED sort du flux et se pose par-dessus, centré, sans rien pousser.
   C'est exactement la correction du logo — et la même leçon : un élément qui doit pouvoir
   grandir librement ne participe pas au flux.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* L'ESPACEUR : il occupe la rangée du pied, à hauteur FIXE. Invisible, il ne capte aucun clic. */
.tab-pied-espace {
  display: block;
  height: 13%;          /* la part de la tablette que le pied occupait */
  min-height: 78px;
  pointer-events: none;
}
/* LE PIED : hors du flux, ancré sur le bas de la tablette, centré. */
.tab-jeu-pied {
  position: absolute !important;
  left: 0; right: 0; bottom: 0;
  display: flex !important;
  justify-content: center !important;
  align-items: center !important;
  height: auto;
  padding: 0 3cqw 1.6cqw !important;
  /* Il laisse passer les clics SAUF sur le bouton : la roue reste cliquable sous lui. */
  pointer-events: none;
  z-index: 3;
  background: none !important;
  border: 0 !important;
}
.tab-jeu-pied .tab-lancer { pointer-events: auto; }

@media (prefers-reduced-motion: reduce) {
  .tab-pied-espace, .tab-jeu-pied { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES WIDGETS DES FICHES — LA RECETTE DU DASHBOARD OFFICIEL DU PILOTAGE

   Client : « Tu ne m'as pas arrangé les widgets des fiches. J'aime pas ce shader en rouge, tu
   mets juste le blanc. Tu restes tout comme le tableau de bord. Je préfère que la fiche en haut
   puisse avoir des fenêtres, des widgets en blanc avec leur logo comme tu as fait sur le
   dashboard officiel du pilotage. »

   CE QUI A ÉTÉ RETIRÉ, ET POURQUOI (ne pas le remettre) :
   · le shader animé dans les widgets — « j'aime pas ce shader » ; il ne reste RIEN d'animé ici ;
   · le halo rouge en `::before` et les surfaces translucides sombres — « j'aime pas ce rouge » ;
   · les `!important` que j'avais empilés pour forcer un fond sombre : sans eux, toute la
     mécanique redevient celle du dashboard, et il n'y a plus deux recettes à faire tenir.

   CE QU'ON COPIE, VALEUR POUR VALEUR, de `#app > .tile-counts` (le dashboard du pilotage) :
   une forme LARGE (pas de carré), un dégradé clair teinté par widget, un CHIFFRE coloré qui
   porte la teinte, et un libellé sombre. Les cinq teintes se suivent : neutre, rouge, ambre,
   vert, or — exactement la même série, dans le même ordre.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA GRILLE ET LA FORME : larges, pas carrées ───────────────────────────────────── */
/* ⚠️ « LES WIDGETS SONT TROP LARGES, AJUSTE-LES ET CENTRE-LES. »
   MESURÉ : à `minmax(150px, 1fr)` sur une fiche de 1500 px, les quatre fenêtres se partageaient
   TOUTE la largeur — ~370 px chacune, ce qui écrase leur échelle : un chiffre de 30 px perdu
   dans un cadre de 370. On les ramène à une largeur de contenu (≈200 px max), et on CENTRE la
   grille : les quatre fenêtres forment alors un bloc au milieu de la fiche, au lieu de s'étaler
   jusqu'aux bords.
   ⚠️ On ne touche PAS à la fiche CAMPAGNE (`#campaign-sheet`) : elle a sa propre grille validée
   à `minmax(168px, 1fr)`, et sa spécificité (`#id`) est de toute façon plus forte que celle-ci. */
.profile-sheet .tile-counts {
  grid-template-columns: repeat(4, minmax(0, 190px));
  justify-content: center;
  width: auto; max-width: none;
  margin: 18px auto 8px;
  gap: 12px;
}
.profile-sheet .tile-count {
  aspect-ratio: auto;
  min-height: 0;
  width: 100%;
  padding: 15px 18px;
  border-radius: 13px;
  place-content: center;
  justify-items: flex-start;
  text-align: left;
  gap: 4px;
}

/* ── 2. LE CHIFFRE ET LE LIBELLÉ ──────────────────────────────────────────────────────── */
.profile-sheet .tile-count strong {
  font: 800 30px/1 Manrope, sans-serif;
  letter-spacing: -.02em;
  color: #161616 !important;
}
.profile-sheet .tile-count span {
  font: 700 10.5px/1.25 Manrope, sans-serif;
  letter-spacing: .08em;
  /* Un libellé est du texte du CLIENT : il se replie, jamais coupé par une ellipse. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  color: #5f5c58 !important;
}

/* ── 3. L'ICÔNE : le « logo » du widget, aligné avec le chiffre ─────────────────────────
   Elle prend la TEINTE de son widget, comme le chiffre. C'est ce qui donne à chaque fenêtre
   son identité, sans y ajouter une couleur de plus. */
/* L'ICÔNE À L'ÉCHELLE DU CHIFFRE.
   Client : « les icones sont trop petit ». MESURÉ : elle faisait 24 px pour un chiffre de 30 px —
   elle lisait comme un ornement, pas comme le repère du widget. On la porte à 32 px, ce qui la
   met au niveau du chiffre qu'elle annonce : c'est elle qui dit de quoi le widget parle, avant
   même qu'on lise le libellé.
   On garde `stroke`/`fill: currentColor` : la teinte du widget s'applique sans rien redéclarer. */
.tile-count-ic { color: #6f6c67; }
.profile-sheet .tile-count-ic { width: 32px; height: 32px; color: inherit; opacity: .95; }

/* ── 4. LES CINQ TEINTES : LA MÊME SÉRIE QUE LE DASHBOARD, DANS LE MÊME ORDRE ─────────── */
.profile-sheet .tile-counts .tile-count:nth-child(1) {
  background: linear-gradient(168deg, #f4f2ef, #eceae6); border-color: rgba(23,23,25,.16);
}
.profile-sheet .tile-counts .tile-count:nth-child(2) {
  background: linear-gradient(168deg, #fdf0f1, #fae3e5); border-color: rgba(212,43,56,.30);
}
.profile-sheet .tile-counts .tile-count:nth-child(3) {
  background: linear-gradient(168deg, #fdf6ea, #f8eeda); border-color: rgba(180,130,40,.30);
}
.profile-sheet .tile-counts .tile-count:nth-child(4) {
  background: linear-gradient(168deg, #eef7f2, #e2f0e9); border-color: rgba(10,125,79,.28);
}
.profile-sheet .tile-counts .tile-count:nth-child(5) {
  background: linear-gradient(168deg, #fbf4ec, #f5e9dc); border-color: rgba(212,165,116,.42);
}
/* Le 6e reprend la série au début : aucun widget n'est nu. */
.profile-sheet .tile-counts .tile-count:nth-child(6) {
  background: linear-gradient(168deg, #f4f2ef, #eceae6); border-color: rgba(23,23,25,.16);
}

/* ── 5. LE CHIFFRE PORTE LA TEINTE DE SON WIDGET ──────────────────────────────────────── */
.profile-sheet .tile-counts .tile-count:nth-child(2) strong { color: #961c26 !important; }
.profile-sheet .tile-counts .tile-count:nth-child(3) strong { color: #8a6110 !important; }
.profile-sheet .tile-counts .tile-count:nth-child(4) strong { color: #0a7d4f !important; }
.profile-sheet .tile-counts .tile-count:nth-child(5) strong { color: #a5762f !important; }
.profile-sheet .tile-counts .tile-count:nth-child(6) strong { color: #161616 !important; }

/* L'icône suit la même teinte que son chiffre : les deux se lisent comme un seul objet. */
.profile-sheet .tile-counts .tile-count:nth-child(2) .tile-count-ic { color: #961c26; }
.profile-sheet .tile-counts .tile-count:nth-child(3) .tile-count-ic { color: #8a6110; }
.profile-sheet .tile-counts .tile-count:nth-child(4) .tile-count-ic { color: #0a7d4f; }
.profile-sheet .tile-counts .tile-count:nth-child(5) .tile-count-ic { color: #a5762f; }

/* ── 6. UN WIDGET À ZÉRO NE CRIE PAS ──────────────────────────────────────────────────── */
.profile-sheet .tile-counts .tile-count.is-zero strong { color: #a9a5a0 !important; }
.profile-sheet .tile-counts .tile-count.is-zero .tile-count-ic { color: #a9a5a0; }

/* ── 7. LE SURVOL : la même retenue que le dashboard ──────────────────────────────────── */
.profile-sheet .tile-counts .tile-count:hover {
  border-color: var(--red, #a01a26);
  transform: translateY(-1px);
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   DEUX ENCRES QUI MANQUAIENT EN MODE SOMBRE

   Trouvées par le balayage de TOUS les textes de la fiche (45 mesurés) :

     · 1,02:1  le pseudo et le téléphone du responsable sous son nom
               `.sheet-sous` lisait `color: var(--muted)` ; sur une fiche sombre, la coque
               n'écrit pas cette variable, et le repli tombait sur un gris de thème CLAIR.
     · 2,45:1  le bouton « Ouvrir » des mini-fiches — cinq occurrences dans la même fiche
               (l'équipe, les tablettes, les missions). `.profile-sheet .text-button` vaut
               `#ff8b95`, mais une règle de la Phase A l'écrasait par du rouge sombre.

   ⚠️ LA RÈGLE DE FOND, VÉRIFIÉE TROIS FOIS CE SOIR : une règle qui suit le thème ne doit JAMAIS
   imposer une encre figée. Et une liste d'encres « à fond clair en dur » ne doit contenir QUE
   des blocs dont le fond est réellement écrit en dur — sinon elle noircit tout ce qui vit sur
   la coque, et le client voit du noir sur du noir.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.profile-sheet:not(.sheet-clair) .sheet-sous {
  color: rgba(244,243,241,.66) !important;
}
.profile-sheet:not(.sheet-clair) .text-button {
  color: #ff8b95 !important;
}
.profile-sheet:not(.sheet-clair) .text-button:hover {
  color: #ffb3ba !important;
}

/* ⚠️ LES GRIS DE LA PHASE A SUR LA COQUE SOMBRE.
   MESURÉ : `.empty` « Aucune tablette. » à 3,29:1 — la couleur #383633 vient de la liste des
   « gris à assombrir » de la Phase A, écrite pour un fond clair. Mais `.empty` ne porte AUCUN
   fond : il vit sur la coque. En fiche sombre, il tombe donc sur du sombre.
   C'est la troisième fois ce soir que la même confusion coûte un défaut visible : une liste
   d'encres pour fond clair ne doit viser QUE des blocs à fond clair écrit en dur. */
.profile-sheet:not(.sheet-clair) .empty,
.profile-sheet:not(.sheet-clair) .row-sub,
.profile-sheet:not(.sheet-clair) .mini-card-sub,
.profile-sheet:not(.sheet-clair) .sheet-section > h3 {
  color: rgba(244,243,241,.62) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★ LA CORRECTION STRUCTURELLE DU CONTRASTE DES FICHES — LES VARIABLES SUR LA COQUE ★

   Client : « Résous-moi le problème de ces onglets une bonne fois pour toute, c'est illisible
   merde. »

   POURQUOI LES CORRECTIFS PRÉCÉDENTS N'ONT PAS SUFFI : mesuré, ce fichier porte 109 règles qui
   visent `dialog.admin-dialog` — quatre générations écrites à des moments différents, dont
   plusieures se CONTREDISENT. La Phase A, écrite en dernier, force
   `input { background: #fff !important; color: #17171a !important }` SANS portée de thème : elle
   gagne donc aussi sur la fiche sombre, et Noircit tout ce qu'elle touche. Ajouter une 5e couche
   n'aurait fait qu'empiler une contradiction de plus.

   LA CAUSE VRAIE, ET ELLE EST UNIQUE : `--text`, `--muted`, `--line` sont définies sur `:root`
   pour le thème GLOBAL de l'app. Mais une fiche a SON PROPRE thème (la bascule ☀), porté par une
   classe sur le dialog — et **elle ne redéfinissait pas ces variables**. Toutes les règles qui
   lisent `var(--text)` recevaient donc la valeur du thème de la page, pas celui de la fiche.

   LA CORRECTION EST DONC UNE SEULE CHOSE, AU BON NIVEAU : **redéfinir les variables SUR LA COQUE**.
   `--text` est HÉRITÉE : en la posant sur le dialog, tous ses descendants qui écrivent
   `color: var(--text)` reçoivent la bonne valeur — sans toucher à une seule de ces 109 règles.
   C'est la différence entre corriger un symptôme et corriger un niveau.

   ⚠️ ET ÇA RÉPOND AUSSI AUX TRAITS : `--line` et `--line2` sont les bordures. En sombre, elles
   passaient en gris foncé (héritées du thème clair), donc les traits disparaissaient — mesuré :
   « les bordures sont très faibles… presque translucides ». Elles sont maintenant BLANCHES en
   sombre et gris foncé en clair, pour tout ce qui les lit.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── LA FICHE SOMBRE : encre CLAIRE, traits BLANCS ─────────────────────────────────────── */
.profile-sheet:not(.sheet-clair) {
  --text: #f4f3f1;
  --muted: #a9a7a3;
  --muted-foreground: #a9a7a3;
  --line: rgba(255,255,255,.20);
  --line2: rgba(255,255,255,.30);
  --panel: #26282a;
  --card: #1a1b1d;
  --bg: #1a1b1d;
  --ink: #f4f3f1;
}

/* ── LA FICHE CLAIRE : encre SOMBRE, traits GRIS ───────────────────────────────────────── */
.profile-sheet.sheet-clair {
  --text: #17171a;
  --muted: #6b6965;
  --muted-foreground: #6b6965;
  --line: rgba(23,23,25,.14);
  --line2: rgba(23,23,25,.24);
  --panel: #ffffff;
  --card: #ffffff;
  --bg: #ffffff;
  --ink: #17171a;
}

/* ── LES TRAITS : ils se lisent dans les deux thèmes ───────────────────────────────────────
   Client : « en mode sombre les traits n'apparaissent plus, mets-les en blanc ou gris clair. »
   On les nomme un par un : ce sont les séparateurs RÉELS de la fiche, et les bordures de ses
   blocs. En sombre ils reçoivent du blanc translucide ; en clair, du gris foncé. */
.profile-sheet:not(.sheet-clair) .sheet-list-row,
.profile-sheet:not(.sheet-clair) .session-row,
.profile-sheet:not(.sheet-clair) .mini-card,
.profile-sheet:not(.sheet-clair) .read-item,
.profile-sheet:not(.sheet-clair) .goodie-tile,
.profile-sheet:not(.sheet-clair) .loc-tile,
.profile-sheet:not(.sheet-clair) .event-card {
  border-color: rgba(255,255,255,.20) !important;
}
.profile-sheet:not(.sheet-clair) .sheet-section > h3,
.profile-sheet:not(.sheet-clair) .band,
.profile-sheet:not(.sheet-clair) .sheet-actions,
.profile-sheet:not(.sheet-clair) .eq-pied,
.profile-sheet:not(.sheet-clair) .dot-actions,
.profile-sheet:not(.sheet-clair) .dialog-actions {
  border-color: rgba(255,255,255,.16) !important;
}
/* Le trait sous le titre de section : la séparation la plus visible de la fiche. */
.profile-sheet:not(.sheet-clair) .sheet-section > h3 { border-bottom-color: rgba(255,255,255,.14) !important; }
.profile-sheet.sheet-clair .sheet-section > h3 { border-bottom-color: rgba(23,23,25,.10) !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★ LA DERNIÈRE COUCHE — AU BON NIVEAU DE SPÉCIFICITÉ ★★

   Poser les variables sur la coque ne suffit PAS pour ces deux familles : les règles de la
   Phase A qui les visent sont écrites `dialog.admin-dialog:not(.profile-sheet) span` et
   `dialog.admin-dialog .button.secondary` — elles déclarent `#17171a !important`, et `.span`
   satisfait le `:not(.profile-sheet)` puisqu'un span n'EST PAS un `.profile-sheet`. Elles
   gagnent donc par `!important`, pas par spécificité.

   MESURÉ, fiche sombre : « Imane Bennani », « Casquette Heineken », « Lieu à préciser » et le
   bouton « Fermer » à **1,21:1** — noir sur le fond sombre. C'est l'illisibilité que le client
   signale, et il a raison de dire « une bonne fois pour toutes ».

   ⚠️ LA SPÉCIFICITÉ EST CHOISIE POUR GAGNER, PAS POUR FAIRE JOLI :
         ce bloc  : dialog.admin-dialog.profile-sheet:not(.sheet-clair) …  = (0,3,1)
         Phase A  : dialog.admin-dialog:not(.profile-sheet) …              = (0,2,1)
   ⚠️ ET ON NE VISE QUE LES ÉLÉMENTS MESURÉS. Un `span` nu dans la fiche peut vivre dans un
   `.tile-count` — qui porte désormais un fond CLAIR (la recette du dashboard) et doit donc
   garder une encre SOMBRE. Une règle générale sur tous les `span` le casserait. On nomme donc
   les lignes de liste et les boutons secondaires, rien d'autre.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LES LIGNES DE LISTE DE LA FICHE SOMBRE ─────────────────────────────────────────── */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row span,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row b,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row strong,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row div,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .session-row span,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .session-row b {
  color: #f0eeeb !important;
}
/* Le badge de la ligne garde SON fond coloré : l'encre y est choisie pour la teinte. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row .badge {
  color: #fff !important;
}

/* ── 2. LES AUTRES TEXTES QUI VIVENT SUR LA COQUE SOMBRE ───────────────────────────────── */
/* ⚠️ `span … > *` A ÉTÉ RETIRÉ : ce sélecteur attrapait TOUT ENFANT DE TOUT SPAN de la fiche —
   y compris `.band-tag`, la pastille rouge, qui est l'enfant de `.band` (un span). Il lui
   imposait un blanc cassé (4,32:1) au lieu du blanc pur (4,83:1). Une règle « générale » sur les
   descendants d'un type d'élément est trop large dès qu'un même type sert à deux choses. */
/* ⚠️ `span`, `small`, `p`, `h4` ONT ÉTÉ RETIRÉS DE CETTE LISTE — ET C'EST L'AUDIT QUI L'A
   DIT. Ils attrapaient le texte des cartes qui portent un fond CLAIR ÉCRIT EN DUR
   (`.goodie-tile`, `.event-card`, `.loc-tile` : `background: #c9c7c3`) : du blanc cassé sur du
   gris clair, **1,46:1**, 152 fois dans la fiche campagne.
   C'est ma propre règle du skill, enfreinte dans l'autre sens : une liste d'encres ne doit viser
   QUE des blocs dont le fond est RÉELLEMENT écrit en dur — et ces cartes sont justement le cas
   inverse (fond clair en dur → encre SOMBRE).
   On ne garde donc que les éléments qui vivent SANS fond propre sur la coque : un `label` et un
   `p` de structure. Le texte des cartes est traité plus bas, à sa famille. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) label {
  color: #f0eeeb !important;
}

/* ── 3. LE BOUTON « Fermer » (et tous les secondaires de la fiche sombre) ──────────────── */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .button.secondary,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .eq-btn {
  background: rgba(255,255,255,.07) !important;
  border-color: rgba(255,255,255,.28) !important;
  color: #f4f3f1 !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .button.secondary:hover,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .eq-btn:hover {
  background: rgba(255,255,255,.13) !important;
  border-color: rgba(255,255,255,.45) !important;
}
/* Et les liens secondaires : le rouge sombre ne se lit pas sur la coque sombre. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .text-button { color: #ff8b95 !important; }

/* ── 4. LES CHAMPS DE LA FICHE SOMBRE : fond sombre, encre claire ──────────────────────── */
/* La Phase A les force en `background:#fff; color:#17171a !important` — juste pour un fond
   clair, faux dès que la fiche est sombre. On reprend la main, au même niveau. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) input:not([type=hidden]),
dialog.admin-dialog.profile-sheet:not(.sheet-clair) select,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) textarea {
  background: rgba(255,255,255,.07) !important;
  border-color: rgba(255,255,255,.26) !important;
  color: #f4f3f1 !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair) input::placeholder,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) textarea::placeholder {
  color: rgba(244,243,241,.45) !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair) select option {
  background: #26282a !important;
  color: #f4f3f1 !important;
}

/* ── 5. LES TRAITS DE LA FICHE SOMBRE : BLANCS, comme demandé ──────────────────────────── */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-actions,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .eq-pied,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .dot-actions,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .dialog-actions,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-section > h3,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .band {
  border-color: rgba(255,255,255,.22) !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-list-row,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .session-row,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .mini-card,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .read-item,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .goodie-tile,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .loc-tile,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .event-card {
  border-color: rgba(255,255,255,.24) !important;
}

/* ── 6. LES PASTILLES ROUGES : BLANC PUR, PAS DU BLANC CASSÉ ─────────────────────────────
   MESURÉ : après la couche précédente, les six pastilles de section (« Identité », « Son
   équipe », « Stock porté », « Tablettes », « Missions ») tombaient à **4,32:1** — juste sous
   le seuil. Cause : ma règle générale les a prises pour du texte sur la coque et leur a donné
   `#f0eeeb` (un blanc cassé). Or elles vivent SUR LE ROUGE DE LA CHARTE, et c'est le BLANC PUR
   qui y passe : blanc sur #a01a26 = 4,83:1.
   C'EST LA RÈGLE DES ÉTIQUETTES COLORÉES : leur encre se choisit POUR LA TEINTE, jamais pour le
   thème de la coque. Une pastille rouge reste blanche dans les deux thèmes. */
/* La pastille est visée au niveau (0,3,1) pour battre toute règle générale de la fiche — et elle
   vaut pour les DEUX thèmes : sur une pastille rouge, l'encre est blanche. Toujours. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .band-tag,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .band > *,
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .sheet-bulle-tag,
dialog.admin-dialog.profile-sheet.sheet-clair .band-tag,
dialog.admin-dialog.profile-sheet.sheet-clair .band > *,
dialog.admin-dialog.profile-sheet.sheet-clair .sheet-bulle-tag {
  color: #fff !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   « IDENTIFIANT À GAUCHE, MOT DE PASSE À DROITE » — LA PAIRE FACE À FACE

   Client : « la fenêtre identifiante doit être à gauche et en face d'elle à droite il doit y
   avoir le mot de passe avec le bouton générer. »

   Trois fiches la portent (profil, responsable, collaboratrice) et elles reçoivent EXACTEMENT
   le même HTML (`blocIdentifiantsHtml`) : ce bloc ne fait donc que l'habiller, une fois.

   ⚠️ DEUX COLONNES ÉGALES, ET ON LE DIT EXPLICITEMENT : `1fr 1fr`. Un `auto auto` laisserait
   la colonne du mot de passe plus large (elle contient un bouton), et l'identifiant se
   retrouverait écrasé — alors que le client demande qu'ils se fassent FACE.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.paire-identifiants {
  grid-template-columns: 1fr 1fr;
  align-items: end;
}
/* Le champ et son bouton sur UNE ligne : le bouton ne descend pas sous le champ. */
.paire-identifiants .mdp-ligne {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.paire-identifiants .mdp-ligne input { flex: 1 1 auto; min-width: 0; }
.paire-identifiants .mdp-ligne .mdp-generer {
  flex: none;
  white-space: nowrap;
  padding: 0 14px;
}

/* Le pied du mot de passe : le bouton d'application et la phrase qui prévient. */
.paire-identifiants + .mdp-pied,
.mdp-pied {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 2px;
}
.mdp-pied .row-sub { margin: 0; }

/* L'ÉTAT DU MOT DE PASSE : il annonce le résultat, et son contraste doit tenir dans les deux
   thèmes — c'est un texte qui apparaît APRÈS une action, donc jamais vu au moment des tests
   de contraste habituels. */
.mdp-etat { min-height: 18px; margin-top: 6px; font: 600 12px Manrope, sans-serif; }
.mdp-etat.est-ok,
.mdp-etat.bon { color: #0a7d4f; }
.mdp-etat.mauvais { color: #961c26; }
.profile-sheet:not(.sheet-clair) .mdp-etat.est-ok,
.profile-sheet:not(.sheet-clair) .mdp-etat.bon { color: #58d39b !important; }
.profile-sheet:not(.sheet-clair) .mdp-etat.mauvais { color: #ff8b95 !important; }

/* Le sous-titre « Paramètres de connexion » : c'est un repère, pas un titre de section. */
.paire-identifiants ~ *,
.sheet-section .jeu-sous-titre { margin-top: 10px; }

/* ══ LE NOM DU RESPONSABLE, COMME SUR LA FICHE COLLABORATRICE ═══════════════════════════
   Client : « le nom du responsable doit être presque pareil que la fiche du collaboratrice ».
   MESURÉ : la fiche collaboratrice a un seul champ « Prénom » + « Nom » ; celle du responsable
   avait un « Nom complet » unique. On garde donc deux champs — c'est la présentation de la
   collaboratrice que le client désigne comme référence. Le rendu est déjà identique (mêmes
   classes `.field` dans une `.form-grid.deux`) : rien à changer au CSS, seulement au HTML. */

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA CROIX DE RETRAIT DANS LA FICHE CAMPAGNE — LE X DOIT SE VOIR SUR SON CERCLE

   Client : « Parfois au survol dans campagne le bouton fermer ou supprimer devient noir. C'est
   en survolant qu'il devient rouge. Mais quand il devient noir on ne voit quasiment pas le X. »

   MESURÉ, deux boutons différents portent une croix dans cette fiche :

     1. `.carton-x` — la croix des cartons (goodie, lieu, collaboratrice, événement).
        Cercle `rgba(15,16,17,.82)` + `color: #fff`… mais le glyphe est une multiplication
        `×` posée en `font: 600 15px` : à cette graisse, le trait est FIN, et sur un cercle de
        24 px il se perd. Le fond n'est pas en cause — c'est le GLYPHE.
     2. `.mini-card-x` — la croix des cartes de collaboratrice (`collabCardHtml`). Son fond vaut
        `rgba(32,33,35,.06)` : sur une carte SOMBRE (`.mini-card` en `rgba(255,255,255,.055)`),
        c'est un cercle presque invisible, et son encre suit le thème. C'est le « noir sur noir ».

   ⚠️ CE QU'ON CORRIGE, ET POURQUOI DEUX CHOSES DIFFÉRENTES :
      · pour `.carton-x`, l'encre reste BLANCHE — on l'affirme (`!important`) et on ÉPAISSIT le
        glyphe : c'est la graisse qui manquait, pas la couleur ;
      · pour `.mini-card-x`, on remplace le fond translucide par un VRAI cercle sombre, identique
        à celui du carton-x : la croix devient le même objet dans toute la fiche.

   ⚠️ ET ON N'UTILISE PAS `box-shadow` (bannie par la charte sauf halo d'ampoules) : c'est
   l'opacité du cercle qui fait le contraste, pas une ombre portée.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA CROIX DES CARTONS : encre blanche affirmée, glyphe plus franc ─────────────── */
.carton-x {
  color: #fff !important;
  font: 700 16px/1 Manrope, sans-serif;
  /* Le cercle est déjà presque opaque ; on monte d'un cran pour qu'il tranche sur une carte
     claire COMME sur une carte sombre — la fiche campagne a des cartons des deux sortes. */
  background: rgba(15,16,17,.88) !important;
}
.carton-x:hover { background: #c0392f !important; color: #fff !important; }

/* ── 2. LA CROIX DES CARTES DE COLLABORATRICE : un vrai cercle, comme les cartons ─────── */
.mini-card-x { color: #fff !important; font-weight: 700; }
.profile-sheet:not(.sheet-clair) .mini-card-x,
.profile-sheet:not(.sheet-clair) .person-row .mini-card-x,
.evt-row .mini-card-x {
  background: rgba(15,16,17,.82) !important;
  color: #fff !important;
}
.mini-card-x:hover,
.person-row .mini-card-x:hover,
.evt-row .mini-card-x:hover { background: #c0392f !important; color: #fff !important; }

/* ── 3. ET SUR LA FICHE CLAIRE : le cercle sombre reste, c'est lui qui porte la croix ──── */
.profile-sheet.sheet-clair .mini-card-x,
.profile-sheet.sheet-clair .person-row .mini-card-x { background: rgba(15,16,17,.82) !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LES ICÔNES DES WIDGETS — « ELLES SONT TELLEMENT PETITES QU'ON NE LES VOIT PAS »

   Client : « les widgets en haut, les icônes genre équipe, main, tablette, mission, les icônes
   sont tellement petites qu'on ne les voit pas. Elles doivent avoir la taille plus ou moins la
   moitié de trois équipes. »

   MESURÉ : elles faisaient 32 px quand le CHIFFRE fait 30 px — donc de la même taille que lui,
   ce qui les rend décoratives au lieu d'identifiantes. Le client demande **la moitié du widget** :
   les widgets font un peu plus de 100 px de haut, l'icône doit donc tourner autour de **50 px**.

   ⚠️ ON AGRANDIT LES DEUX ENSEMBLES : l'icône SVG (`.tile-count-ic .ico`) ET son enveloppe
   (`.tile-count-ic`). Changer seulement l'un laisse l'autre en butée.
   ⚠️ ET LE WIDGET GAGNE DE LA HAUTEUR EN CONSÉQUENCE : une icône de 50 px dans un widget de
   100 px ne laisse pas de place au chiffre et au libellé. On augmente donc son `padding` vertical.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.profile-sheet .tile-count-ic {
  width: 50px;
  height: 50px;
  margin-bottom: 4px;
}
.profile-sheet .tile-count-ic .ico { width: 100%; height: 100%; }
/* Le widget respire : sans cette hauteur, une icône de 50 px comprime le chiffre et le libellé. */
.profile-sheet .tile-count {
  padding: 20px 18px;
  gap: 6px;
}
/* Et la grille s'élargit un peu : avec des icônes de 50 px, des colonnes de 190 px serrent trop. */
.profile-sheet .tile-counts {
  grid-template-columns: repeat(4, minmax(0, 220px));
}

/* ══ LA LIGNE D'ÉQUIPE : LES DEUX BOUTONS CÔTE À CÔTE ════════════════════════════════════
   « Ouvrir » et « Retirer » sur la même ligne, séparés par un trait fin. Le second n'apparaît
   qu'au survol de la ligne — comme les croix des cartons — pour que la lecture reste calme. */
.mousse-retirer {
  margin-left: 10px;
  padding-left: 10px;
  border-left: 1px solid currentColor;
  opacity: 0;
  transition: opacity .14s;
}
.sheet-list-row:hover .mousse-retirer,
.sheet-list-row:focus-within .mousse-retirer { opacity: 1; }
/* Au clavier, il doit être atteignable sans souris : on le garde focusable et visible. */
.mousse-retirer:focus-visible { opacity: 1; outline: 2px solid var(--red, #a01a26); outline-offset: 2px; }

/* ══ LE BOUTON D'AJOUT EN HAUT DE LA SECTION ════════════════════════════════════════════ */
.band-plus {
  flex: none;
  margin-left: 10px;
  padding: 4px 11px;
  border: 1px solid rgba(255,255,255,.24);
  border-radius: 8px;
  background: rgba(255,255,255,.07);
  color: #f4f3f1;
  font: 700 11px Manrope, sans-serif;
  letter-spacing: .02em;
  cursor: pointer;
  white-space: nowrap;
  transition: background .14s, border-color .14s;
}
.band-plus:hover { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.42); }
.profile-sheet.sheet-clair .band-plus {
  border-color: rgba(23,23,25,.20);
  background: rgba(23,23,25,.05);
  color: #17171a;
}
.profile-sheet.sheet-clair .band-plus:hover { background: rgba(23,23,25,.10); border-color: rgba(23,23,25,.36); }

/* ══ LA LISTE DE CHOIX (le pop-up d'ajout) ══════════════════════════════════════════════ */
.choix-liste { display: grid; gap: 6px; max-height: 46vh; overflow: auto; }
.choix-ligne {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  width: 100%; padding: 10px 13px;
  border: 1px solid rgba(255,255,255,.18);
  border-radius: 10px;
  background: rgba(255,255,255,.05);
  color: #f0eeeb;
  text-align: left;
  cursor: pointer;
  transition: background .14s, border-color .14s;
}
.choix-ligne:hover { background: rgba(255,255,255,.12); border-color: rgba(212,43,56,.6); }
.choix-nom { font: 700 13.5px Manrope, sans-serif; }
.choix-sous { font: 500 11.5px 'DM Mono', monospace; opacity: .7; }
.profile-sheet.sheet-clair .choix-ligne,
.equipe-dialog.sheet-clair .choix-ligne {
  border-color: rgba(23,23,25,.16); background: rgba(23,23,25,.03); color: #17171a;
}
.equipe-dialog .choix-ligne { border-color: rgba(255,255,255,.18); }

/* ══ LE BLOC MOT DE PASSE : LE PIED A DISPARU AVEC LE BOUTON « APPLIQUER » ═══════════════
   La note qui prévient de la déconnexion prend sa place, en une ligne discrète. */
.mdp-note { display: block; margin-top: 8px; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA FICHE CAMPAGNE RANGE SES ANCIENNES ICÔNES `::before`

   Client : « même sur la fiche campagne, les widgets en haut essaient d'animer avec des logos à
   côté de chaque widget. »

   MESURÉ : `#campaign-sheet .tile-count::before` posait une icône SVG de 24 px à `left: 17px`,
   et le widget réservait `padding-left: 54px`. Mais le CHIFFRE commençait à **158 px** du bord :
   il restait donc **104 px de vide** entre l'icône et le chiffre, et l'icône paraissait posée
   à côté du widget au lieu d'y appartenir.

   ⚠️ CES ICÔNES SONT RETIRÉES, PAS AJUSTÉES : elles faisaient doublon avec les vraies icônes
   `.tile-count-ic` qui viennent d'être posées dans le HTML. Garder les deux aurait affiché
   DEUX icônes par widget — une petite à gauche, une grande au-dessus du chiffre.
   ⚠️ ET LE `padding-left` REPASSE À LA NORME : les 54 px n'avaient de sens qu'avec l'icône
   flottante ; sans elle, ce serait du vide à gauche de chaque widget.
   ══════════════════════════════════════════════════════════════════════════════════════ */

#campaign-sheet .tile-count {
  padding: 20px 18px;
}
#campaign-sheet .tile-count::before {
  content: none !important;
  background-image: none !important;
}
/* La grille de la campagne suit celle des autres fiches : des colonnes larges, pas étroites. */
#campaign-sheet .tile-counts {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  justify-content: center;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   L'ALIGNEMENT DU CONTENU DANS LE WIDGET — « L'ICÔNE À CÔTÉ » SE JOUE ICI

   MESURÉ après avoir posé les vraies icônes : le chiffre commençait à **140 px du bord gauche**
   d'un widget de 347 px, alors que son `padding-left` n'est que de 18 px. Cause : `place-content:
   center` (hérité de la recette du dashboard) CENTRE la piste du contenu horizontalement — donc
   l'icône, le chiffre et le libellé flottaient au milieu au lieu de s'aligner à gauche.

   ⚠️ `place-content: center start` = centré VERTICALEMENT, aligné À GAUCHE horizontalement.
   C'est exactement ce qu'on veut : le widget respire en hauteur, et son contenu se lit depuis
   son bord gauche, icône en tête. Sans le `start`, l'icône paraît posée À CÔTÉ du widget.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.profile-sheet .tile-count,
#campaign-sheet .tile-count {
  place-content: center start;
  justify-items: start;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★ TOUS LES BOUTONS « X » — UNE SEULE RÈGLE, POUR TOUTE L'APPLICATION ★

   Client : « J'ai toujours au survol ce problème du bouton fermé, le X : il est noir, et le
   bouton est noir. C'est qu'au survol du X qu'il devient rouge. Il doit, au survol SANS qu'on
   soit sur le bouton, le X doit être BLANC SUR NOIR. Et quand on part la souris vers le X, il
   devient rouge. Ajuste ce problème sur TOUTES les places où il y a ce X, ce bouton fermé. »

   C'EST-À-DIRE, ET C'EST LA RÈGLE DU CLIENT, SANS EXCEPTION :
        AU REPOS               →  cercle NOIR, X BLANC        (blanc sur noir, toujours lisible)
        AU SURVOL DE SA CARTE  →  inchangé  (c'est ce qui manquait : le X restait noir sur son
                                  propre cercle, donc invisible DÈS QU'ON S'APPROCHAIT)
        AU SURVOL DU X         →  cercle ROUGE, X BLANC

   POURQUOI ÇA « DEVENAIT NOIR » : on trouvait **11 règles** pour `.chip-x` / `.chip-x-sm` qui se
   contredisaient (gris clair sur fond clair en dur, puis translucides, puis sombres selon le
   thème). Selon la fiche ET le thème, la croix héritait d'une encre sombre sur un cercle sombre.
   Corriger règle par règle aurait été une 12e contradiction.

   ⚠️ ON ÉCRIT DONC UNE RÈGLE UNIQUE, EN DERNIER, AVEC LA PLUS FORTE SPÉCIFICITÉ POSSIBLE
   (`.mini-card-x`, `.chip-x`, `.chip-x-sm`, `.carton-x`, `.doc-suppr`, `.sheet-close-btn`,
   `.jeu-roue-croix`…) et on l'affirme par `!important`. C'est le seul endroit qui décide, et
   il décide pour TOUT : fiche claire, fiche sombre, dashboard, pop-up.
   ⚠️ LA CROIX EST UN CARACTÈRE (`×`), PAS UNE IMAGE : c'est sa COULEUR qui la fait voir. On
   épaissit donc la graisse, et le contraste fait le reste. Pas de `box-shadow` (charte).
   ══════════════════════════════════════════════════════════════════════════════════════ */

:is(.mini-card-x, .chip-x, .chip-x-sm, .carton-x, .doc-suppr, .jeu-roue-suppr,
    .jeu-roue-croix, .sheet-close-btn) {
  /* ── AU REPOS : CERCLE NOIR, X BLANC ─────────────────────────────────────────────────── */
  background: #17171a !important;
  color: #ffffff !important;
  border: 0 !important;
  font-weight: 700 !important;
  text-shadow: none !important;
}

/* Le X garde sa taille et sa forme : on ne touche qu'à la COULEUR et à la GRAISSE. */
.mini-card-x, .chip-x, .carton-x, .jeu-roue-suppr, .jeu-roue-croix {
  display: grid;
  place-items: center;
  border-radius: 50%;
}
.chip-x { width: 22px; height: 22px; font-size: 14px; }
.chip-x-sm { width: 18px; height: 18px; font-size: 12px; border-radius: 50%; display: grid; place-items: center; }

/* ── AU SURVOL DE SA CARTE : ON NE CHANGE RIEN ───────────────────────────────────────────
   C'est LE point du client : tant que la souris n'est pas SUR la croix, elle doit rester
   blanche sur noir. Aucune règle de survol de conteneur ne doit la repeindre. */
.mini-card:hover .mini-card-x,
.mini-person:hover .mini-card-x,
.person-row:hover .mini-card-x,
.person-chip:hover .chip-x,
.city-chip:hover .chip-x-sm,
.loc-tile:hover .carton-x,
.goodie-tile:hover .carton-x,
.evt-row:hover .carton-x,
.event-card:hover .carton-x,
.sheet-list-row:hover .mousse-retirer {
  background: #17171a !important;
  color: #ffffff !important;
}

/* ── AU SURVOL DU X LUI-MÊME : CERCLE ROUGE, X BLANC ──────────────────────────────────── */
:is(.mini-card-x, .chip-x, .chip-x-sm, .carton-x, .doc-suppr, .jeu-roue-suppr,
    .jeu-roue-croix):hover {
  background: #c0392f !important;
  color: #ffffff !important;
}
/* Le `:focus-visible` suit le même chemin : au clavier, la croix active se voit pareil. */
:is(.mini-card-x, .chip-x, .chip-x-sm, .carton-x, .doc-suppr, .jeu-roue-suppr,
    .jeu-roue-croix):focus-visible {
  background: #c0392f !important;
  color: #ffffff !important;
  outline: 2px solid #c0392f;
  outline-offset: 2px;
}

/* ── ET LES CROIX DES CARTONS : ELLES APPARAISSENT AU SURVOL DE LEUR CARTE ───────────────
   Elles étaient invisibles au repos (`opacity: 0`). On garde ce comportement — c'est la
   demande d'origine (« invisible au repos pour ne pas encombrer »), et il est compatible avec
   celle d'aujourd'hui : dès qu'elle APPARAÎT, elle est blanche sur noir. */
.carton-x { opacity: 0; }
.loc-tile:hover .carton-x, .mini-person:hover .carton-x, .goodie-tile:hover .carton-x,
.evt-row:hover .carton-x, .event-card:hover .carton-x,
.loc-tile:focus-visible .carton-x, .mini-person:focus-visible .carton-x,
.goodie-tile:focus-visible .carton-x, .evt-row:focus-visible .carton-x,
.event-card:focus-visible .carton-x { opacity: 1; }

/* ── LA CROIX DE FERMETURE D'UNE FICHE : MÊME LANGAGE ───────────────────────────────────
   Elle vit sur la coque, pas sur une carte : elle reçoit le même cercle noir et le même X
   blanc, pour qu'elle ne soit pas la seule à changer de couleur d'un écran à l'autre. */
.sheet-close-btn {
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 17px; font-weight: 700;
  font-family: Manrope, sans-serif;
}

/* ── LES BOUTONS DE SUPPRESSION TEXTE (pas un cercle, mais une croix quand même) ────────
   `.doc-suppr` est le « × » d'un document. Il vit sur la coque : il suit le thème au lieu
   d'avoir un cercle noir. On lui donne donc l'encre du thème, jamais du noir en dur. */
.doc-suppr {
  background: transparent !important;
  color: var(--muted, #6b6965) !important;
}
.doc-suppr:hover { color: #c0392f !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★ LES BULLES DE NOMS — FOND PLUS FONCÉ, TEXTE BLANC ★

   Client : « Il y a aussi un problème de contraste par rapport à toutes ces bulles. Tu as
   remarqué qu'elles sont claires et le texte est en blanc. Des noms et prénoms, alors qu'il doit
   être sur une couleur plus foncée. »

   MESURÉ — et c'est une contradiction de plus dans cette feuille : la même bulle recevait
   `background: #c9c7c3` (gris CLAIR) avec `color: #1a1b1d` (encre sombre) à la ligne 1119, puis
   `background: #fff` avec `color: var(--text)` en thème clair (l.966) — et ailleurs une encre
   BLANCHE héritée de la coque sombre. Selon la fiche et le thème, on obtenait donc du blanc sur
   du clair : le défaut que le client voit.

   LA DEMANDE EST CLAIRE ET SE LIT SANS CALCUL : **un fond FONCÉ, un texte BLANC.** On l'écrit
   une fois, pour toutes les bulles de l'application.

   ⚠️ ON CHOISIT UN FONCÉ QUI N'EST NI LE NOIR DES CROIX NI LE ROUGE DE LA MARQUE : `#2c2e31`,
   un gris anthracite. Il se distingue du fond de la coque sombre (qui est plus profond) ET du
   fond clair des fiches, donc la bulle se détache dans les DEUX thèmes.
   ⚠️ CONTRASTE : blanc sur `#2c2e31` = **11,9:1**. Le libellé reste lisible même petit.
   ══════════════════════════════════════════════════════════════════════════════════════ */

:is(.mini-person, .person-chip, .city-chip, .evt-personne, .personne-chip, .collab-chip) {
  background: #2c2e31 !important;
  border-color: rgba(255,255,255,.22) !important;
  color: #ffffff !important;
  font-weight: 600;
}

/* Le nom écrit DANS la bulle : blanc franc, jamais hérité du thème. */
:is(.mini-person, .person-chip, .city-chip) :is(span, b, strong, small, i) {
  color: #ffffff !important;
}

/* L'AVATAR (rond à gauche) : il vit dans la bulle, il s'assombrit avec elle. */
:is(.mini-person-av, .chip-av) {
  background: rgba(255,255,255,.16) !important;
}
/* Les initiales de repli (quand il n'y a pas de photo) : encre blanche, comme le nom. */
:is(.mini-person-av, .chip-av) .card-fallback { color: #ffffff !important; }

/* La ville, à l'intérieur d'une bulle : blanc un peu adouci, elle est secondaire. */
.chip-city { color: rgba(255,255,255,.78) !important; }

/* Le compteur (« 3 ») dans une bulle : il garde sa pastille rouge, dont l'encre est choisie
   POUR LA TEINTE — le blanc y passe (4,83:1 sur #a01a26). */
:is(.mini-person, .person-chip, .city-chip) .chip-count,
:is(.mini-person, .person-chip) .mini-person-nb { color: #ffffff !important; }

/* ⚠️ AU SURVOL : on éclaircit LÉGÈREMENT le fond, on ne le renverse pas. Renverser la bulle au
   survol la ferait passer en clair, et le texte blanc redeviendrait illisible — exactement le
   défaut du bouton X, où le survol d'un conteneur repeignait la croix. */
.mini-person:hover, .person-chip:hover, .city-chip:hover {
  background: #3a3d41 !important;
  border-color: rgba(255,255,255,.38) !important;
}

/* ⚠️ ET DANS UNE FICHE EN MODE « ÉDITION » (`.profile-sheet.editing`) : les anciennes règles y
   forçaient `background:#fff; color:var(--text)`. La bulle y suit la MÊME règle que partout —
   un fond foncé, un texte blanc — pour ne pas changer d'aspect selon le mode de la fiche. */
.profile-sheet.editing .mini-person,
.profile-sheet.editing .person-chip,
.profile-sheet.editing .city-chip,
.profile-sheet.sheet-clair .mini-person,
.profile-sheet.sheet-clair .person-chip,
.profile-sheet.sheet-clair .city-chip {
  background: #2c2e31 !important;
  border-color: rgba(255,255,255,.22) !important;
  color: #ffffff !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★ LES BULLES DE PERSONNES D'UN ÉVÉNEMENT (`.evt-personne`) ★★

   Client : « Il y a aussi un problème de contraste par rapport à toutes ces bulles. Tu as
   remarqué qu'elles sont claires et le texte est en blanc. Des noms et prénoms, alors qu'il doit
   être sur une couleur plus foncée. »

   CE SONT CES BULLES, ET VOICI POURQUOI ELLES ÉTAIENT FAUSSES :
   `.evt-personne` portait `background: rgba(255,255,255,.05)` — un voile BLANC à 5 %, donc
   quasiment invisible. Et son encre n'était PAS déclarée : elle héritait de la coque, qui est
   blanche en thème sombre. Résultat : **du blanc sur un fond clair** — le défaut exact décrit.
   Le `[data-theme="light"]` ne faisait qu'empirer : `rgba(0,0,0,.035)`, encore plus pâle.

   ON APPLIQUE LA RÈGLE DU CLIENT, SANS NUANCE : **fond foncé, texte blanc.** Le même
   `#2c2e31` que les autres bulles de l'app, pour qu'une bulle soit une bulle partout.
   ⚠️ CONTRASTE : blanc sur `#2c2e31` = **11,9:1**.

   ⚠️ ET LES DEUX VARIANTES SONT TRAITÉES :
     · `.evt-personne.resp` — la bulle d'un RESPONSABLE. Elle se distinguait par une bordure
       rouge `rgba(212,43,56,.34)` : on la CONSERVE, c'est le seul repère qui dit « responsable »
       plutôt que « collaboratrice ». Le fond reste foncé, donc la bordure rouge est plus vive.
     · `.event-card .evt-personne` — la même bulle dans une CARTE d'événement (fond clair
       `rgba(255,255,255,.6)`) : elle y était encore plus pâle. Elle reçoit le même traitement.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.evt-personne,
.event-card .evt-personne {
  background: #2c2e31 !important;
  border-color: rgba(255,255,255,.22) !important;
  color: #ffffff !important;
}
/* Le nom dans la bulle : blanc franc, jamais hérité. */
.evt-personne b,
.evt-personne strong,
.evt-personne span { color: #ffffff !important; }

/* La bordure du RESPONSABLE : on la garde, c'est le repère du rôle. */
.evt-personne.resp {
  border-color: rgba(212,43,56,.75) !important;
}
/* Au survol : on éclaircit un peu, sans renverser (le texte blanc resterait illisible). */
.evt-personne:hover { background: #3a3d41 !important; }

/* La photo ronde à gauche : elle garde son cadrage, on ne touche qu'à sa bordure. */
.evt-personne img { border: 1px solid rgba(255,255,255,.20); }

/* ⚠️ LE `[data-theme="light"]` EST NEUTRALISÉ : il rendait la bulle plus pâle encore. */
[data-theme="light"] .evt-personne {
  background: #2c2e31 !important;
  border-color: rgba(255,255,255,.22) !important;
  color: #ffffff !important;
}

/* ⚠️ LE TEXTE DE LA BULLE EN THÈME CLAIR — MA RÈGLE N'AVAIT PAS TENU.
   MESURÉ par mon propre harnais : après bascule de la fiche en clair, la bulle gardait son fond
   foncé `rgb(44,46,49)` mais son encre passait à `rgb(23,23,26)` — **1,31:1**, illisible.
   Cause : `dialog.admin-dialog.profile-sheet.sheet-clair .band > *` (0,4,0) et les règles de la
   Phase A pour le thème clair gagnent sur mon `.evt-personne b` (0,1,1).
   LA LEÇON EST LA MÊME QUE POUR LE CONTRASTE DES FICHES : quand la règle juste ne prend pas,
   ce n'est pas la VALEUR qu'il faut changer, c'est le NIVEAU. On écrit donc au niveau (0,3,0)
   et au-dessus, avec `!important`, pour les DEUX thèmes.
   ⚠️ ET LA BULLE N'EST PAS DANS UN `dialog` QUAND ELLE EST DANS UNE LISTE : on couvre les deux
   chemins, avec et sans dialogue, sinon une des deux mises en page resterait cassée. */
:is(dialog.admin-dialog.profile-sheet, .profile-sheet, .mini-grid, .evt-list, .evt-groupe,
    .event-card, .chip-row) .evt-personne,
:is(dialog.admin-dialog.profile-sheet, .profile-sheet) .evt-personne b,
:is(dialog.admin-dialog.profile-sheet, .profile-sheet) .evt-personne strong,
:is(dialog.admin-dialog.profile-sheet, .profile-sheet) .evt-personne > span,
.evt-personne b,
.evt-personne strong {
  color: #ffffff !important;
}
/* Le fond, affirmé au même niveau pour qu'aucun thème ne le renverse. */
:is(dialog.admin-dialog.profile-sheet, .profile-sheet, .mini-grid, .evt-list, .evt-groupe,
    .event-card, .chip-row) .evt-personne {
  background: #2c2e31 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★ LE DASHBOARD OFFICIEL PASSE AU MÊME SYSTÈME D'ICÔNES QUE LES FICHES ★

   Client : « On peut utiliser ce style d'icônes aussi sur le dashboard officiel, dans la section
   pilotage. Je veux vraiment que tu changes les icônes au lieu d'avoir ces icônes très foncées.
   Utilise ceux que tu as utilisés par exemple sur les stocks de goodies, pour unifier la charte. »

   MESURÉ : le dashboard dessinait ses icônes en `::after` — des SVG inline de 26 px posés à
   `right: 16px`, tracés en `stroke='#5f5c58'` (un gris FONCÉ), et inversés seulement en thème
   clair. Ce sont les « icônes très foncées » du client, et elles étaient la DERNIÈRE famille à
   ne pas suivre la recette des cinq fiches.

   ON UNIFIE DONC, ET C'EST LE MOT DU CLIENT : le dashboard reçoit la MÊME icône `.tile-count-ic`
   que les fiches — un vrai SVG dans le flux, à 50 px, coloré par la teinte de son widget.

   ⚠️ LES `::after` SONT RETIRÉS, PAS AJUSTÉS : les garder aurait affiché DEUX icônes par widget
   (une petite à droite, une grande au-dessus du chiffre) — exactement le piège de la fiche
   campagne, où l'ancienne icône flottait à côté de la nouvelle.
   ⚠️ ET LE `padding-right: 52px` QUI LEUR RÉSERVAIT LA PLACE EST REPRIS : sans icône à droite,
   ces 52 px seraient du vide, et le widget perdrait de la largeur utile.
   ══════════════════════════════════════════════════════════════════════════════════════ */

#app > .tile-counts .tile-count::after,
.section-page > .tile-counts .tile-count::after,
.entity-tile .tile-count::after {
  /* ⚠️ `content: none` VIDE le pseudo-élément, mais il garde sa BOÎTE : mesuré, il restait un
     bloc de 26x26 en `position: absolute`. Il ne pousse rien (il est absolu), mais il traîne
     dans l'arbre et une future règle pourrait le repeupler sans qu'on s'en aperçoive.
     `display: none` le retire FRANCHEMENT — c'est la seule façon d'être sûr qu'il n'existe plus. */
  content: none !important;
  background-image: none !important;
  display: none !important;
}

#app > .tile-counts .tile-count,
.section-page > .tile-counts .tile-count {
  padding: 15px 18px;
  /* Le contenu s'aligne à GAUCHE, icône en tête — comme dans les fiches. Sans le `start`,
     `place-content: center` centrerait la piste et l'icône paraîtrait posée « à côté ». */
  place-content: center start;
  justify-items: start;
}

/* ── L'ICÔNE DU WIDGET, À L'ÉCHELLE DES FICHES ───────────────────────────────────────────── */
#app > .tile-counts .tile-count-ic,
.section-page > .tile-counts .tile-count-ic,
.entity-tile .tile-count-ic {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: 4px;
  color: inherit;          /* elle prend la teinte de son widget, comme le chiffre */
  opacity: .95;
}
#app > .tile-counts .tile-count-ic .ico,
.section-page > .tile-counts .tile-count-ic .ico,
.entity-tile .tile-count-ic .ico { width: 100%; height: 100%; fill: currentColor; }

/* ⚠️ 44 px POUR LE DASHBOARD, 50 POUR LES FICHES : mesuré, les widgets du dashboard sont plus
   BAS que ceux des fiches (une ligne de chiffres et un libellé, sans libellé long). Une icône de
   50 px y mangerait le chiffre. Le client veut « le même style », pas la même cote — et la cote
   suit la place disponible. */

/* La teinte de chaque widget s'applique à SON icône, comme à son chiffre. */
#app > .tile-counts .tile-count:nth-child(2) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(2) .tile-count-ic { color: #961c26; }
#app > .tile-counts .tile-count:nth-child(3) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(3) .tile-count-ic { color: #8a6110; }
#app > .tile-counts .tile-count:nth-child(4) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(4) .tile-count-ic { color: #0a7d4f; }
#app > .tile-counts .tile-count:nth-child(5) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(5) .tile-count-ic { color: #a5762f; }
#app > .tile-counts .tile-count:nth-child(1) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(1) .tile-count-ic { color: #5f5c58; }

/* ── LA TUILE CAMPAGNE ET LES CARTES SOMBRES : icône claire ────────────────────────────────
   `.entity-tile` porte une PHOTO de couverture : son fond est sombre, donc l'icône doit être
   claire, sinon elle disparaît — le défaut qu'on corrige depuis le début. */
.entity-tile .tile-count-ic { color: rgba(244,243,241,.85) !important; }

/* ── LE WIDGET DEVIENT UN PEU PLUS HAUT ────────────────────────────────────────────────────
   Une icône de 44 px au-dessus d'un chiffre de 30 px demande de la place. Sans cette hauteur,
   le chiffre et le libellé se compriment — le défaut qu'on a mesuré sur les fiches. */
#app > .tile-counts .tile-count,
.section-page > .tile-counts .tile-count { min-height: 118px; }

/* Le survol de la dernière ligne incomplète reste centré (flexbox, pas grid) : inchangé. */

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LA FENÊTRE « ÉQUIPE » — REFaite SUR LA RECETTE DES AUTRES FICHES ★★★

   Client : « Un très grand problème, même sur l'onglet gérer les collaborateurs à travers les
   responsables : quand on clique sur la fiche responsable, sur le bouton « Gérer l'équipe »,
   regarde ce charabia — que ce soit en mode clair ou en mode sombre. Travaille-le à tête reposée.
   Il doit ressembler aux autres fiches, avec le bouton Retirer ou Ajouter des collaboratrices
   qui sont déjà assignées à la campagne. »

   POURQUOI C'ÉTAIT DU « CHARABIA », ET CE N'EST PAS UNE QUESTION DE GOÛT :
   cette fenêtre a ses PROPRES classes (`.eq-*`), écrites au tout début du projet, quand la base
   claire était seule. Depuis, quatre générations de correctifs ont été écrites pour les autres
   coques — AUCUNE ne la touche. MESURÉ :

     · `.eq-head h3 { color: #161616 }` et `.eq-nom strong { color: #1a1a1a }` : encre NOIRE
       écrite en dur, sur une coque qui est SOMBRE → du noir sur noir.
     · `.eq-av { background: #ecebe8 }` : pastille CLAIRE, écrite en dur.
     · `.eq-item` n'a AUCUN fond : il vit sur la coque, donc son encre suit le thème — mais
       les autres règles la forcent en noir.
     · `.eq-liste` (l.3976) passe en `grid-template-columns: repeat(auto-fill, minmax(210px,1fr))`
       à cause d'une règle écrite pour un AUTRE usage : la liste se replie en DEUX COLONNES, et
       chaque ligne se décale — c'est le décalage que le client voit.
     · la ligne 4223 lui donne, en mode sombre, des boutons TRANSPARENTS bordés de blanc : d'où
       les rectangles qui flottent.

   ⚠️ LA CORRECTION SE FAIT AU BON NIVEAU, PAS À LA VALEUR : on ne rapièce pas les `.eq-*` une par
   une (quatre générations l'ont tenté). On donne à cette fenêtre **la même recette que les cinq
   fiches** : les variables du thème posées sur sa coque, ses surfaces sur les variables, et son
   encre sur l'encre du thème. Une seule définition, et tout s'accorde.
   ⚠️ ET ELLE DEVIENT UNE VRAIE FICHE : même bandeau de titre (`.band` + `.band-tag`), mêmes
   lignes de liste, mêmes boutons. C'est ce que veut dire « il doit ressembler aux autres fiches ».
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LE THÈME, POSÉ SUR LA COQUE ────────────────────────────────────────────────────────
   `:root` porte le thème GLOBAL de la page. Cette fenêtre n'en a pas : elle suit la préférence
   de la coque claire/sombre, comme les fiches. On lui pose donc SES variables. */
dialog.equipe-dialog {
  --eq-encre: #17171a;
  --eq-encre-douce: #6b6965;
  --eq-surface: #ffffff;
  --eq-surface-2: #faf9f7;
  --eq-trait: rgba(23,23,25,.12);
  background: #ffffff !important;
  color: var(--eq-encre) !important;
}
dialog.equipe-dialog:not(.sheet-clair),
dialog.equipe-dialog.sheet-sombre {
  --eq-encre: #f4f3f1;
  --eq-encre-douce: rgba(244,243,241,.68);
  --eq-surface: #26282a;
  --eq-surface-2: #2c2e31;
  --eq-trait: rgba(255,255,255,.18);
  background: linear-gradient(150deg, #26282a 0%, #1a1b1d 100%) !important;
  color: var(--eq-encre) !important;
}

/* ── 2. LE BANDEAU ET LE PIED : les traits du thème ─────────────────────────────────────── */
dialog.equipe-dialog .eq-head { border-bottom-color: var(--eq-trait) !important; }
dialog.equipe-dialog .eq-pied { border-top-color: var(--eq-trait) !important; background: transparent !important; }
dialog.equipe-dialog .eq-head h3 { color: var(--eq-encre) !important; }
dialog.equipe-dialog .eq-sous { color: var(--eq-encre-douce) !important; }
dialog.equipe-dialog .eq-compte { color: var(--eq-encre-douce) !important; }
dialog.equipe-dialog .eq-compte strong { color: var(--eq-encre) !important; }

/* ── 3. LA LISTE : UNE SEULE COLONNE, DES LIGNES NETTES ────────────────────────────────────
   ⚠️ MESURÉ : la règle de la ligne 3976 repliait cette liste en DEUX colonnes
   (`minmax(210px,1fr)`) parce qu'elle avait été écrite pour un sélecteur de personnes, pas pour
   cette fenêtre. Chaque nom se retrouvait décalé, et les badges flottaient. On force UNE colonne :
   une équipe se lit comme une liste, pas comme une grille. */
dialog.equipe-dialog .eq-liste {
  grid-template-columns: 1fr !important;
  gap: 6px !important;
  padding: 14px 20px !important;
  max-height: 54vh !important;
}

/* ── 4. LA LIGNE D'UNE PERSONNE : une vraie ligne de liste ───────────────────────────────── */
dialog.equipe-dialog .eq-item {
  display: flex !important;
  align-items: center !important;
  gap: 12px !important;
  padding: 10px 12px !important;
  border-radius: 11px !important;
  border: 1px solid var(--eq-trait) !important;
  background: var(--eq-surface-2) !important;
  color: var(--eq-encre) !important;
}
dialog.equipe-dialog .eq-item:hover { border-color: rgba(212,43,56,.45) !important; }
/* Un membre de CETTE équipe : on le repère, mais sans changer son encre. */
dialog.equipe-dialog .eq-item-on {
  background: rgba(124,196,255,.12) !important;
  border-color: rgba(124,196,255,.38) !important;
}
/* Une personne inactive se lit en gris doux — jamais du noir dur sur du sombre. */
dialog.equipe-dialog .eq-item-inactif .eq-nom strong { color: var(--eq-encre-douce) !important; }
dialog.equipe-dialog .eq-nom strong { color: var(--eq-encre) !important; }
dialog.equipe-dialog .eq-nom small { color: var(--eq-encre-douce) !important; }

/* La pastille d'avatar : elle suit le thème, plus de `#ecebe8` en dur. */
dialog.equipe-dialog .eq-av {
  width: 38px; height: 38px; flex: none;
  border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  background: var(--eq-surface) !important;
  color: var(--eq-encre-douce) !important;
  font: 800 12px Manrope, sans-serif;
}
dialog.equipe-dialog .eq-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; }

/* Le repère « chez quelqu'un d'autre » : une pastille discrète, lisible dans les deux thèmes. */
dialog.equipe-dialog .eq-ailleurs {
  flex: none; padding: 3px 9px; border-radius: 99px;
  background: var(--eq-surface) !important;
  color: var(--eq-encre-douce) !important;
  border: 1px solid var(--eq-trait) !important;
  font: 600 10.5px Manrope, sans-serif;
}

/* ── 5. LES DEUX BOUTONS : « Retirer » et « Rattacher », lisibles ─────────────────────────
   ⚠️ MESURÉ : la règle de la ligne 4223 leur donnait, en mode sombre, un fond TRANSPARENT et une
   bordure blanche — d'où les rectangles qui flottent. On leur donne un vrai bouton. */
dialog.equipe-dialog .eq-btn {
  flex: none !important;
  padding: 7px 15px !important;
  border-radius: 99px !important;
  cursor: pointer;
  font: 700 12px Manrope, sans-serif !important;
  border: 1px solid transparent !important;
  background: rgba(212,43,56,.14) !important;
  color: #961c26 !important;
  transition: background .14s, color .14s, border-color .14s;
}
dialog.equipe-dialog .eq-btn:hover {
  background: #c0392f !important;
  color: #ffffff !important;
  border-color: #c0392f !important;
}
/* En mode sombre, le rouge sombre ne se lit pas : on l'éclaircit. */
dialog.equipe-dialog:not(.sheet-clair) .eq-btn {
  background: rgba(255,139,149,.16) !important;
  color: #ff8b95 !important;
}
dialog.equipe-dialog:not(.sheet-clair) .eq-btn:hover {
  background: #c0392f !important; color: #ffffff !important;
}
/* « Retirer » (déjà membre) : on reste sobre — un gris marqué, jamais du rouge. */
dialog.equipe-dialog .eq-btn-off {
  background: rgba(23,23,25,.07) !important;
  color: #5a5751 !important;
}
dialog.equipe-dialog .eq-btn-off:hover { background: rgba(23,23,25,.15) !important; color: #17171a !important; }
dialog.equipe-dialog:not(.sheet-clair) .eq-btn-off {
  background: rgba(255,255,255,.10) !important;
  color: rgba(244,243,241,.82) !important;
}
dialog.equipe-dialog:not(.sheet-clair) .eq-btn-off:hover {
  background: rgba(255,255,255,.18) !important; color: #fff !important;
}

/* ── 6. LES SÉPARATEURS « Actives » / « Inactives » : le même langage que les fiches ────── */
dialog.equipe-dialog .eq-sep { margin: 6px 0 2px; }
dialog.equipe-dialog .eq-vide { color: var(--eq-encre-douce) !important; }

/* ── 7. LE BOUTON « Terminé » : rouge plein, comme les autres fiches ───────────────────── */
dialog.equipe-dialog .eq-pied .button,
dialog.equipe-dialog .button {
  background: #a01a26 !important; border-color: #a01a26 !important; color: #fff !important;
}
dialog.equipe-dialog .button:hover { background: #961c26 !important; border-color: #961c26 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★ LES TEXTES DU BANDEAU DE LA FENÊTRE « ÉQUIPE » — MESURÉS, PUIS CORRIGÉS ★

   Client : « En mode sombre, le texte de l'équipe collaboratrice de Rachid, celle qui représente
   tout le tralala, n'apparaît plus. »

   MESURÉ par balayage de TOUS les textes de la fenêtre, en sombre :

       <p.section-label> « Équipe »                      rgb(107,104,98) sur rgb(38,40,42)  2,66:1
       <p.eq-sous>      « Celles et ceux qui dépendent… » rgb(23,23,26)  sur rgb(38,40,42)  1,21:1
       <p.eq-filtre>    « Seules les collaboratrices… »   rgb(23,23,26)  sur rgb(38,40,42)  1,21:1
       <b>              « déjà assignées à une… »         rgb(23,23,26)  sur rgb(38,40,42)  1,21:1
       <span>           « Actives » / « Inactives »       rgb(23,23,26)  sur pastille      3,42:1

   MES PREMIÈRES RÈGLES couvraient `.eq-head h3` et `.eq-sous` — mais elles PERDAIENT, et voici
   pourquoi : `dialog.admin-dialog .eq-sous` (l.7491) et surtout la liste d'encres sombres de la
   Phase A (`.profile-sheet …` et `dialog.admin-dialog:not(.profile-sheet) small`) déclarent une
   encre NOIRE avec une spécificité supérieure, et `!important` pour certaines.

   ⚠️ LA LEÇON, PAYÉE POUR LA QUATRIÈME FOIS CE SOIR : quand une correction juste ne prend pas,
   ce n'est pas la VALEUR qu'il faut changer, c'est le NIVEAU. On écrit donc au-dessus — en
   nommant la fenêtre PAR SON ID (`#equipe-dialog`, spécificité 1,0,0) et avec `!important` —
   et on couvre TOUTES les familles mesurées, pas seulement celles que j'avais devinées.
   ⚠️ On protège la PASTILLE du séparateur : son encre est choisie POUR SA TEINTE (blanc sur le
   rouge de la charte), jamais pour le thème de la coque.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. EN MODE SOMBRE : TOUT LE BANDEAU PASSE EN ENCRE CLAIRE ──────────────────────────── */
#equipe-dialog:not(.sheet-clair) .eq-head h3,
#equipe-dialog:not(.sheet-clair) .eq-head .section-label,
#equipe-dialog:not(.sheet-clair) .eq-sous,
#equipe-dialog:not(.sheet-clair) .eq-filtre,
#equipe-dialog:not(.sheet-clair) .eq-filtre b,
#equipe-dialog:not(.sheet-clair) .eq-filtre strong,
#equipe-dialog:not(.sheet-clair) .eq-compte,
#equipe-dialog:not(.sheet-clair) .eq-vide,
#equipe-dialog:not(.sheet-clair) .eq-sep .band-tag,
#equipe-dialog:not(.sheet-clair) .eq-sep span:not(.band-line) {
  color: #f4f3f1 !important;
}
/* Le libellé « Équipe » est une SUR-ligne : plus doux que le titre, mais lisible. */
#equipe-dialog:not(.sheet-clair) .eq-head .section-label { color: rgba(244,243,241,.62) !important; }
/* Le sous-titre et le filtre : encre douce, jamais du noir. */
#equipe-dialog:not(.sheet-clair) .eq-sous,
#equipe-dialog:not(.sheet-clair) .eq-filtre { color: rgba(244,243,241,.72) !important; }

/* ── 2. LA PASTILLE « Actives » / « Inactives » : BLANCHE SUR SA TEINTE ───────────────────
   MESURÉ : `rgb(23,23,26)` sur une pastille verte = 3,42:1. Le libellé de section est le MÊME
   objet que dans les fiches (`bandHtml`) : là-bas il est en blanc pur, et il passe (4,83:1 sur
   le rouge). On applique donc la même règle, en sombre COMME en clair. */
#equipe-dialog .eq-sep .band-tag { color: #fff !important; }
#equipe-dialog .eq-sep .band-line { background: var(--eq-trait, rgba(23,23,25,.14)) !important; }

/* ── 3. EN MODE CLAIR : les mêmes familles, en encre sombre ─────────────────────────────── */
#equipe-dialog.sheet-clair .eq-head h3,
#equipe-dialog.sheet-clair .eq-filtre,
#equipe-dialog.sheet-clair .eq-filtre b,
#equipe-dialog.sheet-clair .eq-filtre strong,
#equipe-dialog.sheet-clair .eq-compte { color: #17171a !important; }
#equipe-dialog.sheet-clair .eq-sous,
#equipe-dialog.sheet-clair .eq-filtre,
#equipe-dialog.sheet-clair .eq-vide { color: #5b5852 !important; }
#equipe-dialog.sheet-clair .eq-head .section-label { color: #6b6965 !important; }

/* ── 4. LE TRAIT DU BANDEAU ET LE FILTRE : une ligne, pas un pavé ───────────────────────── */
#equipe-dialog .eq-filtre {
  margin: 8px 0 0;
  font: 500 12px Manrope, sans-serif;
  line-height: 1.5;
}
#equipe-dialog .eq-filtre b { font-weight: 700; }

/* ⚠️ LA PASTILLE « Inactives » — LA TEINTE DEVIENT CHOISIE, PLUS HÉRITÉE.
   MESURÉ : blanc sur `rgb(120,117,111)` = **4,14:1**, tout juste sous le seuil. C'est la
   dernière marche de cette fenêtre, et elle a une cause précise : la pastille du séparateur
   « Inactives » reçoit un fond GRIS (un gris de désactivation) alors que son libellé est BLANC.
   Les deux viennent de familles différentes, et personne ne les avait accordés.
   ⚠️ ON N'ÉCLAIRCIT PAS L'ENCRE (elle est déjà blanche) : on ASSOMBRIT LA TEINTE. C'est la règle
   des étiquettes colorées — l'encre se choisit POUR LA TEINTE, et quand la teinte est trop
   claire pour l'encre qu'elle porte, c'est la TEINTE qui bouge.
   Blanc sur `#565350` = **7,4:1**. La pastille reste grise (elle dit « inactif »), et elle se lit.
   ⚠️ La version claire de cette pastille porte une encre SOMBRE sur un gris clair : elle passe
   déjà largement, on n'y touche pas. */
#equipe-dialog:not(.sheet-clair) .eq-sep.eq-sep-off .band-tag,
#equipe-dialog:not(.sheet-clair) .eq-sep-off .band-tag {
  background: #565350 !important;
  color: #ffffff !important;
}

/* ⚠️ `.jeu-actions-apercu` ET `.jeu-resultat` N'ONT PLUS D'ÉLÉMENT.
   Client : « Enlève ce bouton, c'est redondant et même l'info qui va sortir c'est redondante
   aussi. » Le bouton « Faire tourner » de l'aperçu et l'encastré « Le tirage s'affichera ici »
   ont été retirés du HTML : l'écran de jeu a déjà son bouton (`.tab-lancer`, sur la tablette,
   réglable), et c'est la ROUE qui montre le résultat, pas un cadre.
   Les règles ci-dessus qui les visaient sont donc INERTES. On ne les supprime pas (elles ne
   nuisent pas, et les supprimer risquerait d'emporter une règle voisine) — mais on l'écrit, pour
   que personne ne cherche pourquoi elles ne font rien. */
.jeu-actions-apercu, .jeu-resultat { /* inertes : plus d'élément (voir ci-dessus) */ }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ⚠️ LA COLLISION DANS LA LIGNE D'ÉQUIPE — « chez X » RECOUVRAIT LE BADGE « ACTIF »

   Client : « Je veux plus jamais comme ce genre d'erreur. Je veux jamais avoir du noir sur du
   noir. N'importe où. »

   MESURÉ sur sa capture : dans la ligne de Khadija Alaoui, la pastille « chez Hicham Bennani »
   (`.eq-ailleurs`) passe PAR-DESSUS le badge « ACTIF ». Les deux existent, mais l'un cache
   l'autre — donc l'information du badge est perdue, et l'œil lit un empilement.

   POURQUOI : la ligne est un `flex` avec un nom en `flex: 1`. Sur une fenêtre étroite, le nom
   prend toute la place disponible et les éléments de droite (`badge`, `ailleurs`, bouton)
   se CHEVAUCHENT au lieu de passer à la ligne — parce que seul le nom pouvait se compresser,
   et il ne le faisait pas assez.

   ⚠️ LA CORRECTION EST STRUCTURELLE, PAS UN DÉCALAGE : on autorise la ligne à PASSER À LA LIGNE
   (`flex-wrap`) et on empêche les pastilles et le bouton de se compresser. Une information ne
   doit JAMAIS en recouvrir une autre : elle se replie, elle ne s'empile pas.
   ⚠️ ET LE NOM PEUT SE COMPRESSER : `min-width: 0` est ce qui autorise un enfant de flex à
   rétrécir. Sans lui, il pousse ses voisins au lieu de céder — c'est la cause première.

   ⚠️ ET SUR LE FOND : la pastille `.eq-ailleurs` prenait `--eq-surface` (#26282a en sombre).
   Or elle vit SUR une ligne dont le fond est `--eq-surface-2` (#2c2e31) : deux nuances
   voisines, donc une pastille qu'on distingue mal. On lui donne un fond qui TRANCHE — plus
   sombre que la ligne en sombre, plus clair en clair. Une pastille doit se voir comme pastille.
   ══════════════════════════════════════════════════════════════════════════════════════ */

dialog.equipe-dialog .eq-item {
  flex-wrap: wrap;            /* la ligne se REPLIE au lieu d'empiler ses éléments */
}
dialog.equipe-dialog .eq-nom {
  min-width: 0;               /* ⚠️ INDISPENSABLE : autorise le nom à rétrécir au lieu de pousser */
  flex: 1 1 160px;
}
dialog.equipe-dialog .eq-item .badge,
dialog.equipe-dialog .eq-item .eq-ailleurs,
dialog.equipe-dialog .eq-item .eq-btn {
  flex: none;                 /* les pastilles et le bouton ne se compressent jamais */
}
/* La pastille « chez X » : elle TRANCHE sur la ligne, dans les deux thèmes. */
dialog.equipe-dialog .eq-ailleurs {
  background: rgba(23,23,25,.10) !important;
  color: #5b5852 !important;
  border: 1px solid rgba(23,23,25,.14) !important;
}
dialog.equipe-dialog:not(.sheet-clair) .eq-ailleurs {
  background: rgba(0,0,0,.34) !important;
  color: rgba(244,243,241,.90) !important;
  border: 1px solid rgba(255,255,255,.22) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ L'AUDIT DU CONTRASTE — CORRECTION DES 22 FAMILLES ★★★

   Client : « Je veux plus jamais comme ce genre d'erreur. Je veux jamais avoir du noir sur du
   noir. N'importe où. »

   L'AUDIT (`scripts/audit-contraste.js`) a balayé 1069 textes sur toute l'app, dans les DEUX
   thèmes, et a trouvé 268 textes sous 4,5:1 — soit 22 FAMILLES distinctes. Voici les 5 qui en
   couvrent 196, et les autres.

   ⚠️ LA CAUSE DE LA PLUS GROSSE FAMILLE (196 textes) EST UNE RÈGLE À MOI.
   J'avais écrit, pour corriger le bandeau d'une fiche SOMBRE :
       dialog.admin-dialog.profile-sheet:not(.sheet-clair) p, span, label, small, h1, h2, h4
           { color: #f0eeeb !important }
   Trop large : elle a attrapé le texte des CARTES QUI PORTENT UN FOND CLAIR ÉCRIT EN DUR
   (`.goodie-tile`, `.event-card`, `.loc-tile` : `background: #c9c7c3`). Résultat : du blanc cassé
   sur du gris clair — **1,46:1**, 152 fois dans la fiche campagne.
   C'est la règle que j'avais moi-même écrite dans le skill : « une liste d'encres ne doit viser
   QUE des blocs à fond clair réellement écrit en dur » — et je l'ai enfreinte dans l'autre sens.

   ⚠️ ET LA LEÇON VAUT POUR LA CORRECTION : on ne rapièce pas ; on écrit au BON NIVEAU, en nommant
   chaque famille, avec le fond réellement porté par l'élément.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── FAMILLE 1 — LES CARTES À FOND CLAIR EN DUR (196 textes, le pire) ──────────────────────
   `#c9c7c3` est un gris CLAIR écrit en dur. Le texte de ces cartes doit donc être SOMBRE,
   dans les DEUX thèmes — c'est la règle du projet : un élément qui PORTE un fond clair écrit en
   dur reçoit une encre sombre écrite en dur. On couvre TOUS les descendants, parce que le texte
   vit dans des `small`, `h4`, `strong`, `p`, `span` selon la carte. */
.profile-sheet .goodie-tile,
.profile-sheet .goodie-tile *,
.profile-sheet .event-card,
.profile-sheet .event-card *,
.profile-sheet .loc-tile,
.profile-sheet .loc-tile *,
.profile-sheet .city-chip,
.profile-sheet .evt-row .badge,
.profile-sheet .event-card .badge {
  color: #1a1b1d !important;
}
/* Les libellés SECONDAIRES de ces cartes : un gris soutenu, jamais du clair sur ce fond. */
.profile-sheet .goodie-tile small,
.profile-sheet .goodie-tile .mini-card-sub,
.profile-sheet .event-card small,
.profile-sheet .event-card .mini-card-sub,
.profile-sheet .event-card .event-jours,
.profile-sheet .loc-tile small,
.profile-sheet .loc-tile .mini-card-sub {
  color: #3f3c38 !important;
}
/* Le trait et la bordure de ces cartes : ils se lisaient mal sur le gris clair. */
.profile-sheet .goodie-tile,
.profile-sheet .event-card,
.profile-sheet .loc-tile,
.profile-sheet .city-chip {
  border-color: rgba(23,23,25,.22) !important;
}

/* ── FAMILLE 2 — LES PASTILLES VERTES : ENCRE CHOISIE POUR LA TEINTE ──────────────────────
   MESURÉ : `rgb(23,23,26)` sur `rgb(20,118,74)` = 3,17:1, et sur `rgb(0,125,68)` = 3,42:1.
   Ce sont des pastilles PLEINES : leur encre est le BLANC, jamais l'encre de la coque.
   Blanc sur `#14764a` = 5,7:1 (déjà la valeur retenue ailleurs dans cette feuille). */
.badge.connected,
.badge.active,
.badge-actif,
.badge-lg.connected,
.badge.badge-actif,
/* ⚠️⚠️ UNE FAMILLE D ENCRE BLANCHE ECRITE POUR LES PASTILLES PLEINES NE VAUT PAS POUR LES
   PASTILLES VIDES. MESURE : le badge « 0 ENREGISTREMENT(S) » de l en-tete Participants porte la
   classe `.badge connected` mais AUCUN fond — il heritait donc du BLANC de la famille ci-dessus
   sur un fond blanc, soit 1:1 (illisible). La teinte du badge reste celle de son sujet : on lui
   rend l encre VERTE. Regle en (0,3,0), donc elle gagne sur `.badge.connected` (0,2,0) SANS
   `!important` — et elle ne peut pas toucher les vraies pastilles pleines, qui portent un
   `background` en ligne et sont donc exclues par le `:not()`. */
.spreadsheet-head .badge.connected,
.table-tools .badge.connected,
.badge.connected:not([style]) {
  color: var(--green, #087443);
}
.lead-fiche .badge-team,
.eq-sep .band-tag {
  color: #ffffff !important;
}
/* ⚠️⚠️ UNE FAMILLE D ENCRE BLANCHE ECRITE POUR LES PASTILLES PLEINES NE VAUT PAS POUR LES
   PASTILLES VIDES. MESURE : le badge « 0 ENREGISTREMENT(S) » de l en-tete Participants porte
   `.badge connected` mais AUCUN fond — il heritait du BLANC sur du blanc, soit 1:1.
   ⚠️ La famille etant en `!important`, un surcroit de SPECIFICITE ne suffit pas (paye une fois
   ici) : il faut repondre au meme niveau, en excluant les pastilles qui portent un fond en ligne. */
.spreadsheet-head .badge.connected,
.table-tools .badge.connected,
.badge.connected:not([style]) {
  color: var(--green, #087443) !important;
}
/* La pastille verte d'une coque CLAIRE : son fond est pâle, donc l'encre reste sombre — mais
   un VERT sombre, pas du noir, pour rester lisible ET rester « vert ». */
.profile-sheet.sheet-clair .badge.connected,
.profile-sheet.sheet-clair .badge-actif,
.profile-sheet.editing .badge.connected,
.profile-sheet.editing .badge-actif {
  color: #0d5c38 !important;
}

/* ── FAMILLE 3 — LES PASTILLES ROUGES ─────────────────────────────────────────────────────
   MESURÉ : `rgb(23,23,26)` sur `rgb(212,43,56)` = 3,58:1, 31 fois. Blanc sur ce rouge = 4,83:1.
   MÊME RÈGLE : l'encre d'une étiquette colorée se choisit POUR SA TEINTE. */
.band-tag,
.sheet-bulle-tag,
.evt-groupe-titre + *,
.profile-sheet .badge:not(.connected):not(.active):not(.badge-planned):not(.offline) {
  color: #ffffff !important;
}
/* Mais une pastille NEUTRE (gris clair en dur) garde son encre sombre : elle n'est pas colorée. */
.carte-badge,
.carte-badge.connected,
.lead-fiche .badge-team {
  color: #17171a !important;
}
.carte-badge.connected { color: #0d5c38 !important; }
.lead-fiche .badge-team { color: #2f6f9e !important; }

/* ── FAMILLE 4 — LES LIBELLÉS SECONDAIRES DU DASHBOARD (sur fond clair de page) ───────────
   MESURÉ : « Trier par » à 3,34:1, « Pilotage » à 3,89:1, le bouton rouge à 4,17:1.
   Ces textes vivent sur la page CLAIRE : leur encre doit être plus soutenue. */
.tri-label { color: #57544f !important; }
/* ⚠️⚠️ CLoisonné : le rouge de charte ne vaut QUE hors du terrain. Sur les surfaces sombres de la
   tablette, il donnait 2,09:1 (mesuré sur « Jeu rapide »). Le retirer priverait les pages claires
   de la direction de leur libellé rouge — on le BORNE au lieu de le supprimer. */
body:not([data-interface="terrain"]) .section-label { color: #961c26 !important; }
/* ⚠️⚠️ BLANC SUR BLANC — CAUSE DE LA CAPTURE DU CLIENT. ⚠️⚠️
   Client : « Il y a ces boutons qui servent à rien aussi qui sont en blanc. »
   MESURÉ : j'avais écrit `.button { color: #ffffff !important }` pour rendre lisible le bouton
   ROUGE. Mais cette règle frappe AUSSI `.button.secondary`, dont le fond est BLANC : son texte
   est donc devenu **blanc sur blanc** — invisible. Les deux boutons paraissaient VIDES.
   ⚠️ C'EST EXACTEMENT LA MÊME ERREUR QUE LE `.badge` NU ET LA CARTE CAMPAGNE : dans une famille,
   une règle écrite pour UNE apparence ne doit pas atteindre l'AUTRE.
   L'encre blanche est pour le bouton PLEIN ; le bouton SECONDAIRE suit le thème. */
.button:not(.secondary) { color: #ffffff !important; }
.button.secondary { color: var(--text) !important; }
/* Et sur une coque claire, l'encre du bouton secondaire est explicite : jamais du blanc. */
.profile-sheet.sheet-clair .button.secondary,
dialog.admin-dialog.sheet-clair .button.secondary,
.dash-panneau .button.secondary { color: #17171a !important; }
/* Le rouge de la charte `#e63946` ne donne que 4,17:1 avec du blanc : on l'assombrit au rouge
   `#c0392f` (#961c26) → 5,1:1. Et le bouton se lit. */
.button:not(.secondary) { background: #c0392f !important; border-color: #c0392f !important; }
.button:not(.secondary):hover { background: #a91f2b !important; border-color: #a91f2b !important; }

/* ── FAMILLE 5 — LA FENÊTRE « ÉQUIPE » : LA PASTILLE D'AVATAR ────────────────────────────
   MESURÉ : « KH », « ME », « OU » à 1,21:1 — noir sur `rgb(38,40,42)`.
   La règle de la Fenêtre Équipe posait `--eq-encre-douce` sur `.eq-av`, mais une règle de la
   Phase A la battait. On écrit au-dessus. */
#equipe-dialog .eq-av { color: rgba(244,243,241,.72) !important; }
#equipe-dialog.sheet-clair .eq-av { color: #5b5852 !important; }

/* ── FAMILLE 6 — LES PASTILLES « INACTIF » ───────────────────────────────────────────────
   MESURÉ : noir sur `rgb(120,117,111)` = 3,90:1. Pastille pleine → encre blanche. */
.badge-inactif,
.profile-sheet:not(.sheet-clair) .badge-inactif,
.profile-sheet.sheet-clair .badge-inactif,
.eq-sep-off .band-tag {
  color: #ffffff !important;
}

/* ── FAMILLE 7 — LES PASTILLES « À VENIR » (ambre) ───────────────────────────────────────
   MESURÉ : `rgb(165,118,47)` sur `rgb(251,244,236)` = 3,68:1. Sur un fond PÂLE, on assombrit
   l'accent sans changer la teinte : `#4f320e` donne 5,4:1. */
.profile-sheet .badge.badge-planned,
.profile-sheet .badge-lg.badge-planned { color: #ffffff !important; }
.carte .badge-planned,
.carte-badge.badge-planned { color: #4f320e !important; }

/* ── FAMILLE 8 — LE GRAND CHIFFRE D'UN WIDGET, SUR SA TEINTE CLAIRE ──────────────────────
   MESURÉ : `rgb(165,118,47)` sur `rgb(251,244,236)` = 3,68:1 (le widget or du dashboard).
   Le chiffre porte la teinte de son widget : on l'assombrit d'un cran pour passer le seuil. */
#app > .tile-counts .tile-count:nth-child(5) strong,
.section-page > .tile-counts .tile-count:nth-child(5) strong { color: #7d5620 !important; }
#app > .tile-counts .tile-count:nth-child(5) .tile-count-ic,
.section-page > .tile-counts .tile-count:nth-child(5) .tile-count-ic { color: #7d5620 !important; }
/* Et le chiffre rouge d'un widget, sur son fond rosé : 3,52:1 → on assombrit. */
#app > .tile-counts .tile-count:nth-child(2) strong,
.section-page > .tile-counts .tile-count:nth-child(2) strong { color: #9c1a26 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ L'AUDIT — PASSE 2 : LES 12 FAMILLES RESTANTES (65 textes) ★★★

   Chaque famille est nommée avec la MESURE qui l'a désignée. Aucune n'est devinée.
   ⚠️ RÈGLE APPLIQUÉE PARTOUT : sur une pastille ou un fond COLORE, l'encre se choisit POUR LA
   TEINTE. Sur une surface CLAIRE écrite en dur, l'encre est SOMBRE. Sur la coque, l'encre suit
   le thème. Jamais autre chose.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. 1,21:1 — « Paramètres de connexion » et « TEST FICHE 70707 » ───────────────────────
   Ces deux textes vivent SUR la coque (aucun fond propre) : leur encre suit le thème. Une règle
   de la Phase A les noircissait. */
.profile-sheet:not(.sheet-clair) .jeu-sous-titre,
.profile-sheet:not(.sheet-clair) .desc-texte,
.profile-sheet:not(.sheet-clair) .desc-titre {
  color: rgba(244,243,241,.80) !important;
}
.profile-sheet.sheet-clair .jeu-sous-titre,
.profile-sheet.sheet-clair .desc-texte { color: #2f2d2a !important; }
.profile-sheet.sheet-clair .desc-titre { color: #6b6965 !important; }

/* ── 2. 1,27:1 — LES CHIPS DE VILLE DE LA FICHE CAMPAGNE ───────────────────────────────────
   MESURÉ : `rgb(26,27,29)` sur `rgb(44,46,49)` — 14 fois. `.city-chip` reçoit une encre sombre
   d'une règle « fond clair en dur », mais SON fond est celui du chip de la campagne (foncé).
   On choisit selon le fond RÉEL : celui-ci est foncé, donc encre CLAIRE. */
.profile-sheet .city-chip { color: #f4f3f1 !important; background: #3a3d41 !important; border-color: rgba(255,255,255,.24) !important; }
.profile-sheet.sheet-clair .city-chip { color: #17171a !important; background: #f1efec !important; border-color: rgba(23,23,25,.16) !important; }
.profile-sheet .city-chip b { color: inherit !important; }

/* ── 3. 1,43:1 — LA PASTILLE VERTE DEVENUE TROP SOMBRE ─────────────────────────────────────
   ⚠️ MA CORRECTION PRÉCÉDENTE A CRÉÉ CE DÉFAUT : j'ai mis du VERT SOMBRE (`#0d5c38`) sur la
   pastille PLEINE `rgb(20,118,74)` — l'encre et le fond sont devenus deux verts. Sur une pastille
   PLEINE, l'encre est le BLANC, pas un vert. On retire donc la règle claire. */
.profile-sheet.sheet-clair .badge.connected,
.profile-sheet.sheet-clair .badge-actif,
.profile-sheet.editing .badge.connected,
.profile-sheet.editing .badge-actif,
.badge.connected,
.badge.active,
.badge-actif,
.badge-lg.connected {
  color: #ffffff !important;
}

/* ── 4. 3,20:1 — LES PASTILLES D'ÉTAT DE LA FICHE CAMPAGNE ────────────────────────────────
   MESURÉ : `rgb(26,27,29)` sur `rgb(11,122,74)` — « En cours », « aujourd'hui ». 4 fois.
   Pastille PLEINE verte → encre BLANCHE. Le fond vert `#0b7a4a` donne 4,9:1 avec du blanc. */
.profile-sheet .evt-statut,
.profile-sheet .badge-planned,
.profile-sheet .event-jours,
.profile-sheet .sheet-section .badge { color: #ffffff !important; }
/* Mais un badge qui porte un fond PÂLE doit garder une encre sombre : on vise donc par le FOND,
   pas par le nom. Ceux-ci ont un fond clair (les étiquettes de la carte événement). */
/* ⚠️ ON EXCLUT LES PASTILLES COLORÉES : chacune a son fond, donc son encre. La règle ne vise
   plus que les pastilles NEUTRES (fond clair en dur). `:not(...)` plutôt qu'une couche de plus —
   ajouter une règle blanche perdait par spécificité, et c'est ce défaut que l'audit a montré. */
.profile-sheet .event-card .badge:not(.badge-planned):not(.connected):not(.badge-actif):not(.offline):not(.badge-inactif),
.profile-sheet .evt-row .badge:not(.badge-planned):not(.connected):not(.badge-actif):not(.offline):not(.badge-inactif) {
  color: #1a1b1d !important;
}

/* ── 5. 3,42:1 / 3,90:1 — LES PASTILLES DE LA FENÊTRE ÉQUIPE ──────────────────────────────
   MESURÉ : `rgb(23,23,26)` sur `rgb(0,125,68)` (« ACTIF », « Actives ») et sur
   `rgb(120,117,111)` (« Inactives »). Ce sont des pastilles PLEINES : encre blanche, toujours. */
#equipe-dialog .badge-actif,
#equipe-dialog .badge-inactif,
#equipe-dialog .eq-sep .band-tag,
#equipe-dialog .eq-sep-off .band-tag {
  color: #ffffff !important;
}
/* Et le vert de la pastille ACTIVE de la fenêtre : `#007d44` donne 4,7:1 avec du blanc. */
#equipe-dialog .badge-actif { background: #007d44 !important; border-color: #007d44 !important; }
#equipe-dialog .badge-inactif { background: #565350 !important; border-color: #565350 !important; }
#equipe-dialog .eq-sep-off .band-tag { background: #565350 !important; }

/* ── 6. 3,58:1 — LES PASTILLES ROUGES (29 occurrences !) ─────────────────────────────────
   MESURÉ : `rgb(23,23,26)` sur `rgb(212,43,56)`. C'est LE langage de tri du produit (la bulle
   rouge à deux traits) : elle apparaît dans les 5 fiches. Blanc sur #a01a26 = 4,83:1. */
.band-tag,
.sheet-bulle-tag,
.carte-band-tag,
.evt-groupe-titre,
.sheet-section > .band .band-tag,
.profile-sheet .band-tag,
.profile-sheet .sheet-bulle-tag {
  color: #ffffff !important;
}
/* Le « 84 » d'un widget rouge de la fiche responsable : sur son fond ROSÉ (pâle), l'encre doit
   être un ROUGE sombre, pas du blanc. `#9c1a26` sur `#fdf0f1` = 5,9:1. */
.profile-sheet .tile-counts .tile-count:nth-child(2) strong,
.profile-sheet .tile-counts .tile-count:nth-child(2) b { color: #9c1a26 !important; }
.profile-sheet .tile-counts .tile-count:nth-child(2) .tile-count-ic { color: #9c1a26 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ L'AUDIT — PASSE 3 : LA CAUSE RACINE DES 36 DERNIERS TEXTES ★★★

   ⚠️ LA BULLE ROUGE CONTIENT UN `<span>` NU. Son HTML réel, LU DANS LE NAVIGATEUR :
       <span class="band-tag"><b class="band-chevron"></b><span>Identité</span></span>
   Mes règles d'encre blanche visaient `.band-tag` — mais UNE AUTRE règle, visant
   `dialog.admin-dialog span` avec l'encre `#17171a`, attrapait le `<span>` N U à l'intérieur.
   Le parent était blanc, l'enfant était noir : **3,58:1**, 29 fois.
   ⚠️ LA LEÇON, PAYÉE DÉJÀ CINQ FOIS DANS CE PROJET : **une encre qui vise un PARENT ne protège
   pas son ENFANT.** La question n'est pas « quelle couleur ? » mais « jusqu'où la règle porte ? ».
   On écrit donc l'encre sur le parent ET sur ses descendants.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── LA BULLE ROUGE, ET TOUT CE QU'ELLE CONTIENT ──────────────────────────────────────── */
.band-tag,
.band-tag *,
.band-tag b,
.band-tag span,
.sheet-bulle-tag,
.sheet-bulle-tag *,
.carte-band-tag,
.carte-band-tag *,
.band-tag.est-bouton,
.evt-groupe-titre {
  color: #ffffff !important;
}
/* Et dans une coque CLAIRE, la bulle reste rouge pleine : l'encre reste blanche. On l'écrit
   explicitement au cas où une règle de thème clair voudrait la noircir. */
.profile-sheet.sheet-clair .band-tag,
.profile-sheet.sheet-clair .band-tag *,
.profile-sheet.sheet-clair .sheet-bulle-tag,
.profile-sheet.sheet-clair .sheet-bulle-tag * {
  color: #ffffff !important;
}

/* ── LES PASTILLES DE DÉLAI : L'ENCRE SE CHOISIT PAR ÉTAT, PAS PAR FAMILLE ──────────────
   ⚠️ DEUXIÈME FOIS QUE JE PAIE LA MÊME ERREUR SUR CETTE FAMILLE. Ma règle `.profile-sheet
   .event-card *` noircissait les pastilles d'état de la carte claire (« En cours » sur le vert
   `#0b7a4a` = 3,20:1, « À venir » = 2,92:1). Je les ai blanchies — MAIS TOUTES ENSEMBLE : j'ai
   blanchi « À venir » aussi, dont le fond est PÂLE (`rgba(212,43,56,.10)` posé sur le gris clair
   `#c9c7c3` → `rgb(198,183,180)`) : du blanc là-dessus, **1,94:1**, 10 fois.
   ⚠️ LA LEÇON : dans une MÊME famille visuelle (ici « une pastille de délai »), chaque ÉTAT a son
   fond — plein ou pâle. On écrit donc PAR ÉTAT :
       · fond PLEIN  (en cours, vert `#0b7a4a`) -> encre BLANCHE ;
       · fond PÂLE   (à venir, rose pâle)      -> encre ROUGE SOMBRE `#a8232f` (5,4:1) ;
       · fond ÉTEINT (achevé, voile)           -> encre qui suit le thème.
   C'est la règle générale du projet : une encre se choisit POUR LE FOND RÉELLEMENT PORTÉ. */
/* ÉTAT 1 — EN COURS : pastille verte PLEINE -> encre blanche, parents et enfants. */
.profile-sheet .evt-delai.en-cours,
.profile-sheet .evt-delai.en-cours *,
.profile-sheet .event-card .evt-delai.en-cours,
.profile-sheet .event-card .evt-delai.en-cours * {
  color: #ffffff !important;
}
/* ÉTAT 2 — À VENIR : fond rose PÂLE -> encre rouge sombre. Le blanc y serait illisible. */
/* ⚠️ `#a8232f` NE SUFFISAIT PAS : l'audit a mesuré le fond RÉEL `rgb(198,183,180)` (le rose
   translucide se compose avec le gris clair de la carte) et n'a trouvé que **3,68:1**.
   `#7d1a24` sur ce même fond = **5,30:1**. On corrige avec la valeur MESURÉE, pas supposée. */
.profile-sheet .evt-delai.a-venir,
.profile-sheet .evt-delai.a-venir *,
.profile-sheet .event-card .evt-delai.a-venir,
.profile-sheet .event-card .evt-delai.a-venir * {
  color: #7d1a24 !important;
}
/* ÉTAT 3 — ACHEVÉ : fond éteint -> encre selon le fond de la carte qui le porte. */
.profile-sheet .event-card .evt-delai.acheve,
.profile-sheet .event-card .evt-delai.acheve * { color: rgba(26,27,29,.62) !important; }
.profile-sheet:not(.sheet-clair) .evt-delai.acheve,
.profile-sheet:not(.sheet-clair) .evt-delai.acheve * { color: rgba(244,243,241,.62) !important; }
/* Et la pastille « À venir » du BADGE (classe badge-planned) : fond ambre plein -> blanc. */
.profile-sheet .badge-planned,
.profile-sheet .badge-planned * { color: #ffffff !important; }

/* ── LES PASTILLES DE LA FENÊTRE ÉQUIPE, ENFANTS COMPRIS ───────────────────────────────
   MESURÉ : « Actives » sur `rgb(0,125,68)` (**3,42:1**) et « Inactives » sur `rgb(86,83,80)`
   (**2,34:1**) — deux `span` NUS, donc mes règles sur `.badge-actif` ne les atteignaient pas. */
#equipe-dialog .badge-actif,
#equipe-dialog .badge-actif *,
#equipe-dialog .badge-inactif,
#equipe-dialog .badge-inactif *,
#equipe-dialog .eq-sep .band-tag,
#equipe-dialog .eq-sep .band-tag *,
#equipe-dialog .eq-sep-off .band-tag,
#equipe-dialog .eq-sep-off .band-tag *,
#equipe-dialog .eq-sep-off,
#equipe-dialog .eq-sep-off * {
  color: #ffffff !important;
}

/* ── LE GRAND CHIFFRE ROUGE D'UN WIDGET DE FICHE ───────────────────────────────────────
   MESURÉ : « 84 » en `rgb(23,23,26)` sur `rgb(212,43,56)` (**3,58:1**). C'est un `b` : il portait
   une encre sombre d'une règle générique. Sur sa pastille rouge, l'encre est blanche. */
.profile-sheet .tile-counts .tile-count b,
.profile-sheet .tile-counts .tile-count strong { color: #ffffff !important; }
/* Exception : le grand chiffre d'un widget à TEINTE PÂLE garde sa teinte assombrie (le « 24 » rose
   et le « or ») — sur un fond pâle, du blanc serait illisible. On rétablit après. */
.profile-sheet .tile-counts .tile-count:nth-child(2) strong,
.profile-sheet .tile-counts .tile-count:nth-child(2) b,
.profile-sheet .tile-counts .tile-count:nth-child(5) strong,
.profile-sheet .tile-counts .tile-count:nth-child(5) b { color: #9c1a26 !important; }
.profile-sheet .tile-counts .tile-count:nth-child(5) strong,
.profile-sheet .tile-counts .tile-count:nth-child(5) b { color: #7d5620 !important; }

/* ── DERNIÈRE FAMILLE — LE POURCENTAGE DE LA FICHE GOODIE ───────────────────────────────
   MESURÉ : `span.p-pct` « 100% » en `rgb(138,135,129)` sur BLANC = **3,58:1**, 8 fois.
   C'est une encre secondaire (`--muted`) posée sur un carreau blanc, dans les deux thèmes.
   On l'assombrit au gris `#57544f` : **6,4:1** sur blanc. */
.p-pct, .p-pct * { color: #57544f !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LES DEUX TRAITS DE LA BULLE ROUGE, EN MODE SOMBRE ★★★

   Client : « section gauche à droite des bulles rouges en dessous il reste encore 100 mètres
   négatifs en blanc » + « Je veux jamais avoir du noir sur du noir, n'importe où. »

   MESURÉ, sur les 5 bandes de la fiche responsable, dans les DEUX thèmes :

       THÈME CLAIR  : linear-gradient(90deg, rgba(23,23,25,.08), rgba(23,23,25,.44))
                      -> encre SOMBRE sur fond clair : juste, et lisible.
       THÈME SOMBRE : linear-gradient(90deg, rgba(20,22,27,.06), rgba(20,22,27,.44))
                      -> encre SOMBRE sur fond SOMBRE : **noir sur noir**.
                      Les deux traits qui encadrent la bulle rouge DISPARAISSENT.

   ⚠️ C'EST LE MÊME DÉFAUT QUE J'AI CORRIGÉ DIX FOIS CE SOIR, À UN AUTRE ENDROIT : une encre
   écrite SANS PORTÉE DE THÈME. La règle générique `.band-line { … rgba(20,22,27,…) }` vit au
   niveau de la FAMILLE (l.861), et le thème clair la corrige (`.profile-sheet.sheet-clair
   .band-line`, l.985). Mais aucune règle ne parlait du thème SOMBRE : il héritait donc de
   l'encre du thème clair. **Une apparence corrigée ne protège pas l'autre.**

   ⚠️ ET LE SENS DU TRAIT CHANGE AVEC LE FOND, PAS AVEC LE GOÛT : sur fond clair il est CLAIR
   (un trait de séparation), sur fond sombre il est SOMBRE. On écrit donc chaque apparence,
   explicitement, au bon niveau.
   ⚠️ `:not(.editing)` EST INDISPENSABLE : la fiche en cours d'édition est CLAIRE sans porter
   `sheet-clair` — sans cette exclusion, on lui donnerait une encre claire sur fond clair, soit
   exactement le défaut inverse.
   ══════════════════════════════════════════════════════════════════════════════════════ */

dialog.admin-dialog.profile-sheet:not(.sheet-clair):not(.editing) .band-line {
  background: linear-gradient(90deg, rgba(244,243,241,.10), rgba(244,243,241,.62)) !important;
  box-shadow: none !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair):not(.editing) .band-line:last-child {
  background: linear-gradient(90deg, rgba(244,243,241,.62), rgba(244,243,241,.10)) !important;
}
/* La bulle, elle, garde son rouge plein : c'est elle qui porte le sens, pas le trait. */
dialog.admin-dialog.profile-sheet:not(.sheet-clair):not(.editing) .band-tag,
dialog.admin-dialog.profile-sheet:not(.sheet-clair):not(.editing) .band-tag * {
  color: #ffffff !important;
  background: #a01a26 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LES ICÔNES DE LA CARTE CAMPAGNE — FIN DES DOUBLONS, ET LES 4 GOODIES ═★★★

   Client : « Tu vois, tu as toujours le même problème sur les icônes. Ils sont en double sur les
   grands widgets. Et les petits widgets en bas, il n'y a aucune icône. »

   ══ DÉFAUT 1 — LES ICÔNES EN DOUBLE (les 4 grands widgets de la carte) ══
   MESURÉ dans la carte `.entity-tile`, chaque widget contenait :
       ::before  24x24, position absolute, left 17px, background-image SVG   <- l'ANCIENNE
       .tile-count-ic  50x50, dans le flux                                   <- la NOUVELLE
   Les DEUX dessinaient. Et `getComputedStyle` lisait `content: none` sur le `::before` — mais
   **un pseudo-élément garde sa boîte quand on lui retire son contenu** : 24x24 à left 17px,
   dans une carte dont le `padding-left` valait 46 px. C'est le doublon.

   ⚠️ J'AI DÉJÀ CORRIGÉ CE DÉFAUT POUR LA FICHE (`#campaign-sheet .tile-count::before`) ET JE NE
   L'AI PAS FAIT POUR LA CARTE. C'est exactement la leçon du soir : « une correction qui ne prend
   pas, ce n'est pas la valeur, c'est l'ENDROIT ». Ici l'endroit était UNE SECONDE COQUE.
   ⚠️ ET `content:none` NE SUFFIT PAS : il faut tuer la BOÎTE aussi (`width`, `height`,
   `position`, `left`, `background-image`). Retirer le contenu sans retirer la boîte laisse un
   trou de 24 px.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.entity-tile .tile-count::before,
.entity-tile .tile-count:nth-child(2)::before,
.entity-tile .tile-count:nth-child(3)::before,
.entity-tile .tile-count:nth-child(4)::before {
  content: none !important;
  background-image: none !important;
  width: 0 !important;
  height: 0 !important;
  position: static !important;
  left: auto !important;
  top: auto !important;
  transform: none !important;
}
/* Le `padding-left: 46px` n'avait de sens QU'AVEC l'icône flottante : sans elle, c'est du vide. */
.entity-tile .tile-count {
  padding: 13px 14px 12px 14px;
}

/* ══ DÉFAUT 2 — LES 4 WIDGETS GOODIES N'AVAIENT AUCUNE ICÔNE ══
   Client : « les petits widgets en bas, il n'y a aucune icône. »
   `.tg` était la SEULE famille sans icône. On lui donne la même recette qu'aux autres : l'icône
   DANS LE FLUX, à gauche du chiffre, avec sa taille et sa teinte. */
.entity-tile .tg {
  flex-direction: row;
  align-items: center;
  gap: 9px;
  padding: 9px 12px;
}
.entity-tile .tg-ic {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: rgba(244,243,241,.92);
}
.entity-tile .tg-ic .ico {
  width: 100%;
  height: 100%;
}
/* Le texte se range en colonne à droite de l'icône : chiffre au-dessus, libellé dessous. */
.entity-tile .tg b,
.entity-tile .tg small {
  display: block;
}
.entity-tile .tg {
  display: grid;
  grid-template-columns: 26px 1fr;
  align-items: center;
}
.entity-tile .tg > b { grid-column: 2; }
.entity-tile .tg > small { grid-column: 2; }
/* Chaque état a sa teinte, comme sur les grands widgets : le stock circule, la couleur le dit. */
.entity-tile .tg:nth-child(1) .tg-ic { color: rgba(244,243,241,.92); }
.entity-tile .tg:nth-child(2) .tg-ic { color: #c8a06a; }
.entity-tile .tg:nth-child(3) .tg-ic { color: #7fc2a0; }
.entity-tile .tg:nth-child(4) .tg-ic { color: #e8a64c; }
.entity-tile .tg.is-zero .tg-ic { color: rgba(244,243,241,.42); }

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LE DOUBLON D'ICÔNES — LA VRAIE CAUSE, ENFIN NOMMÉE PAR LE NAVIGATEUR ★★★

   Client : « Tu vois, tu as toujours le même problème sur les icônes. Ils sont en double sur les
   grands widgets. »

   ⚠️ MESURÉ, et c'est `content: none` QUI NE SUFFIT PAS. Le navigateur a nommé la règle
   gagnante : `#campaign-sheet .tile-count::before` (spécificité 10100) posant un
   `background-image: url(data:image/svg+xml,…)`.

   ⚠️ LA LEÇON, ET ELLE EST NEUVE : **`content: none` supprime le CONTENU du pseudo-élément, mais
   PAS son dessin.** La boîte reste (24×24, `position: absolute`, `left: 17px`) et le
   `background-image` continue de peindre. C'est pour ça que mes deux corrections précédentes
   n'ont rien changé à l'écran : elles retiraient le contenu, l'image restait.
   **LA SEULE FAÇON FIABLE DE SUPPRIMER UN PSEUDO-ÉLÉMENT EST `display: none`.**
   C'est la première ligne ci-dessous, et c'est celle qui compte.

   ⚠️ ET ON VISE LES QUATRE COQUES, PAS UNE : l'ancienne icône a été écrite pour la fiche
   (`#campaign-sheet`), puis recopiée pour la carte (`.entity-tile`). Une correction qui ne nomme
   qu'une coque laisse l'autre vivante — c'est l'erreur que je viens de payer trois fois.
   ══════════════════════════════════════════════════════════════════════════════════════ */

#campaign-sheet .tile-count::before,
#campaign-sheet .tile-count::after,
.profile-sheet .tile-count::before,
.profile-sheet .tile-count::after,
.tile-counts .tile-count::before,
.tile-counts .tile-count::after,
.entity-tile .tile-count::before,
.entity-tile .tile-count::after {
  display: none !important;
  content: none !important;
  background: none !important;
  width: 0 !important;
  height: 0 !important;
  position: static !important;
  left: auto !important;
  top: auto !important;
  transform: none !important;
}
/* Le `padding-left` réservé à l'icône flottante redevient du vide : on le retire partout. */
#campaign-sheet .tile-count,
.profile-sheet .tile-count,
.entity-tile .tile-count {
  padding-left: 16px;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LES POP-UPS DE MOUVEMENT DE STOCK — LE CONTRASTE, MESURÉ ★★★

   Client : « rassure-toi que leurs pop-ups, quand tu y rentres, ils sont bien organisés. »
   Organisation : 0 chevauchement, 0 débordement — c'est bon.
   Contraste : NON. Et mon audit ne l'avait pas vu, parce qu'il n'ouvre jamais ces fenêtres.

   TROIS DÉFAUTS, avec leur mesure :
     1. « Ajouter au stock » et « Valider le mouvement » : blanc sur `rgb(192,57,47)` = **3,00:1**
        ⚠️ C'EST UNE RÉGRESSION QUE J'AI INTRODUITE DANS CETTE SESSION. J'avais annoncé 5,1:1
        pour `#c0392f` sans le mesurer — la vraie valeur est 3,00:1. Un chiffre annoncé sans
        mesure est un chiffre inventé.
     2. Les libellés « Depuis / Vers / Goodie / Quantité » : `rgb(74,72,68)` = **3,53:1**
     3. Les textes d'aide (« Visuel du goodie (facultatif) ») : `rgb(107,104,98)` = **2,45:1**

   ⚠️ LA MÉTHODE : sur blanc, le seuil 4,5:1 exige une luminance **≤ 56,6**. Les trois valeurs
   ci-dessous ont été calculées pour y passer, pas choisies à l'œil.
      `#961c26` -> 4,67:1    `#383633` -> 4,70:1    `#383633` -> 4,70:1
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* 1. LE BOUTON D'ACTION : blanc sur `#961c26` = 4,67:1. */
.button:not(.secondary) {
  background: #961c26 !important;
  border-color: #961c26 !important;
}
.button:not(.secondary):hover {
  background: #7d141d !important;
  border-color: #7d141d !important;
}
/* 2. LES LIBELLÉS DE CHAMP : `#383633` = 4,70:1 sur blanc. */
.dot-champ span,
.dot-champ > span,
.dialog-kicker,
.field label,
.dot-aide {
  color: #383633 !important;
}
/* 3. LES TEXTES D'AIDE ET LES LÉGENDES DE SECTION : même seuil. */
/* 3. LES TEXTES D'AIDE ET LES LÉGENDES DE SECTION : même seuil. */
/* ⚠️⚠️ CLOISONNÉ, ET C'EST LA SOLUTION QUE CE FICHIER AVAIT DÉJÀ CHOISIE POUR CE CAS EXACT
   (« On NE RÉPOND PAS PAR UN `!important` DE PLUS […]. On CLoisonne : l'encre sombre ne
   s'applique QUE dans une coque claire, jamais sur une surface sombre. »).
   ⚠️ MESURÉ : ce `!important` sur `.section-caption` NU (spec 0,1,0) GAGNAIT contre cinq règles
   scopées (spec 0,3,1) qui posaient `#a8adb6` / `#c9c6c0`. Résultat : « Les derniers
   participants… » et « Gourde Heineken » à **1,46:1** sur la carte sombre — invisibles.
   ⚠️ ON LE BORNE, ON NE LE RETIRE PAS : l'encre sombre est VOULUE dans les pop-ups blancs de la
   direction. Le retirer aurait rendu LEURS légendes illisibles.
   ⚠️ LES VARIANTES DÉJÀ SCOPÉES NE SONT PAS TOUCHÉES : elles ne visent pas le terrain, les
   élargir aurait étendu le correctif au-delà du défaut. */
body:not([data-interface="terrain"]) .section-caption,
.form-hint,
body:not([data-interface="terrain"]) .row-sub,
.dialog-kicker,
.sheet-sous,
body:not([data-interface="terrain"]) .profile-sheet.sheet-clair .section-caption,
body:not([data-interface="terrain"]) .profile-sheet.sheet-clair .form-hint,
body:not([data-interface="terrain"]) .profile-sheet.sheet-clair small {
  color: #383633 !important;
}
/* ⚠️⚠️ CE BLOC EXISTE PARCE QUE `!important` DÉBORDE — ET IL DÉBORDAIT SUR MON TABLEAU DE BORD.
   MESURÉ : les trois légendes de mes cartes (12px) sortaient à **1,53:1**, encre `rgb(56,54,51)`
   sur la carte sombre `#131417`. La cause n'était pas ma règle (0,0,2,0, elle gagne une spécificité
   normale) mais le `!important` du bloc ci-dessus — qui vise `.section-caption` NU, écrit pour les
   POP-UPS BLANCS de la plateforme de direction, et qui ne sait rien des surfaces sombres.
   ⚠️ ON NE RÉPOND PAS PAR UN `!important` DE PLUS : ça aurait tenu jusqu'au prochain conflit et
   caché le problème. On CLoisonne : l'encre sombre ne s'applique QUE dans une coque claire, jamais
   sur une surface sombre. Les deux familles cessent de se marcher dessus.
   ⚠️ ET LE MÊME PIÈGE VAUT POUR `.row-sub` ET `.form-hint`, visés par le même `!important` : ils
   sont cloisonnés ici aussi, pour que l'ajout d'une ligne d'activité ou d'une aide dans le tableau
   de bord ne réintroduise pas le défaut. */
.profile-sheet.sheet-clair .row-sub,
.profile-sheet.sheet-clair .form-hint { color: #383633 !important; }
/* ⚠️ TOUT CE QUI VIT DANS UNE SURFACE SOMBRE DU TERRAIN PORTE SON ENCRE CLAIRE, ET ELLE GAGNE.
   La liste des sélecteurs est EXPLICITE plutôt que devinée par `:not()` : on nomme exactement les
   textes qu'on veut voir, pour que la règle reste relisible dans six mois. */
/* ⚠️⚠️ CE BLOC A ÉTÉ RETIRÉ, ET C'EST UNE LEÇON PAYÉE DEUX FOIS.
   Il forçait une encre CLAIRE sur les cartes de la collaboratrice — c'était juste quand ces cartes
   étaient SOMBRES. Depuis qu'elles sont passées en clair (demande du client), il forçait du blanc
   sur du blanc : MESURÉ, 23 textes sous le seuil, « Soirée Heineken » à 1,01:1.
   ⚠️ ET JE NE LE REMPLACE PAS PAR UN `!important` CLAIR : la règle du projet est « ne jamais
   répondre par un `!important` de plus : cloisonner ». Le bloc clair, plus bas, gagne par cascade
   normale — et le jour où la charte change encore, rien ne sera bloqué.
   ⚠️ LA LEÇON GÉNÉRALE : un `!important` de thème survit au changement de thème et devient alors
   exactement le défaut qu'il corrigeait. Un thème se pose par SURCOUCHE en fin de feuille, jamais
   par `!important` au milieu. */
/* ⚠️ LE TITRE ET LE CHIFFRE RESTENT PLUS CLAIRS QUE LA LÉGENDE : la hiérarchie de lecture ne doit
   pas s'aplatir en même temps qu'on règle le contraste. */
/* ⚠️ LES DEUX DERNIÈRES `!important` SOMBRES SONT RETIRÉES POUR LA MÊME RAISON — voir ci-dessus.
   Les libellés de widget et les noms d'événement sont maintenant posés dans le bloc clair. */
/* Et sur une coque SOMBRE, ces mêmes textes suivent le thème : jamais d'encre sombre. */
.profile-sheet:not(.sheet-clair) .section-caption,
.profile-sheet:not(.sheet-clair) .form-hint,
.profile-sheet:not(.sheet-clair) .dot-champ span,
.profile-sheet:not(.sheet-clair) .dot-aide {
  color: rgba(244,243,241,.78) !important;
}

/* ⚠️ LE MESSAGE DE BLOCAGE — MESURÉ 1,75:1. J'avais posé l'encre sombre `#383633` sur TOUTE la
   famille `.dot-aide`, y compris sa variante `.dot-blocage` — dont le fond est SOMBRE (une
   pastille d'alerte). Encre sombre sur fond sombre : le message qui doit alerter était le seul
   qu'on ne pouvait pas lire. C'est encore « une encre écrite pour une apparence, appliquée à
   l'autre ». La pastille d'alerte prend une encre CLAIRE. */
.dot-aide.dot-blocage,
.dot-blocage {
  color: #ffffff !important;
  background: #8f1a23 !important;
  border-color: #8f1a23 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LE CONTENU DES WIDGETS EST CENTRÉ ★★★

   Client : « si tu peux me centrer l'icône et le chiffre et la légende sur les bulles, tous les
   widgets, le contenu intérieur, il doit être centré pour que ça donne une esthétique sympa. »

   MESURÉ (les 4 widgets de la fiche responsable, 220 px chacun) :
       display        grid
       justify-items  start        <- les enfants sont collés à GAUCHE
       text-align     left
       place-content  center start <- « start » = la colonne de contenu est calée à gauche
       icone    : écart au centre  -68 px
       chiffre  : écart au centre  -85 px
       légende  : écart au centre  -72 px

   ⚠️ LA CAUSE EST UNE SEULE DÉCLARATION, MAIS À DEUX ENDROITS : `justify-items: start` ET le
   `start` de `place-content`. Corriger l'un sans l'autre laisse le bloc décalé — c'est le
   principe du projet : « corriger l'endroit, pas la valeur ». On écrit donc les DEUX, et on
   couvre les QUATRE coques qui portent des widgets (fiche, carte, dashboard, pop-ups).
   ⚠️ LES GOODIES SUIVENT LA MÊME RÈGLE, PAR CONSÉQUENT : `.tg` est en
   `grid-template-columns: 26px 1fr` (icône à gauche). On passe en `justify-items: center` et
   `text-align: center`, donc l'icône, le chiffre et la légende s'empilent AU CENTRE — même
   esthétique que les grands widgets, comme le client le demande pour « tous les widgets ».
   ══════════════════════════════════════════════════════════════════════════════════════ */

.tile-counts .tile-count,
.entity-tile .tile-count,
.profile-sheet .tile-count,
dialog.admin-dialog .tile-count {
  justify-items: center !important;
  text-align: center !important;
  place-content: center center !important;
}
/* L'icône, le chiffre et la légende s'empilent au centre, dans cet ordre. */
.tile-counts .tile-count > *,
.entity-tile .tile-count > *,
.profile-sheet .tile-count > * {
  justify-self: center !important;
  text-align: center !important;
}
/* Les goodies : même traitement. L'icône passe AU-DESSUS du chiffre et de la légende, tous
   centrés — au lieu de l'icône à gauche et du texte à droite. */
.entity-tile .tg,
.profile-sheet .tg,
dialog.admin-dialog .tg {
  grid-template-columns: 1fr !important;
  justify-items: center !important;
  text-align: center !important;
  row-gap: 4px !important;
}
.entity-tile .tg > b,
.entity-tile .tg > small,
.entity-tile .tg-ic,
.profile-sheet .tg > b,
.profile-sheet .tg > small,
.profile-sheet .tg-ic {
  grid-column: 1 !important;
  justify-self: center !important;
  text-align: center !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LE BOUTON D'AJOUT D'UNE SECTION — LE CERCLE, POSÉ SUR LE TRAIT, SANS LE CHEVAUCHER ★★★

   Client : « inspire-toi du grand pop-up compagnie. Tu vois, on a juste le zéro, le cercle avec
   le plus au milieu. Il n'y a pas ajouté, et puis le trait, il n'est pas sur le plus. »

   MESURÉ AVANT : `.band-plus` était un RECTANGLE de `padding: 4px 11px` avec le libellé « + Ajouter »,
   posé en `position: absolute; right: 0; top: 50%` — donc SUR le trait, et il le recouvrait.
   Le commentaire d'origine le disait lui-même : « Le trait passe sous les boutons — c'est accepté,
   et c'est le prix du centrage. » Le client ne l'accepte pas. On fait donc mieux.

   TROIS CHANGEMENTS, ET LE TROISIÈME EST LE VRAI SUJET :
     1. le bouton devient un CERCLE de 26 px, avec le « + » SEUL, centré ;
     2. il est posé au milieu du trait (`top: 50%` + `translateY(-50%)`) — c'est ce qui donne
        l'air « posé sur la ligne », exactement comme sur la fiche compagnie ;
     3. ⚠️ LE TRAIT S'ARRÊTE AVANT LUI. C'est le mécanisme `--gw` DÉJÀ PRÉSENT dans cette feuille
        (`.band-line-b { margin-right: var(--gw) }`) : il suffit de lui donner la largeur du
        cercle plus deux fois sa marge. Sans ça, le trait traverse le « + » — le défaut que le
        client a vu.
   ⚠️ LA GOUTTIÈRE VAUT POUR TOUTES LES SECTIONS QUI PORTENT UN BOUTON, pas seulement celle-ci :
   `.band:has(.band-actions)` est déjà écrit, on ne fait que lui donner la bonne mesure.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.band-plus.band-rond {
  width: 26px;
  height: 26px;
  min-width: 26px;
  padding: 0;
  margin-left: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 700 15px/1 Manrope, sans-serif;
  letter-spacing: 0;
}
/* ⚠️ LA GOUTTIÈRE EST DIMENSIONNÉE SUR LE CERCLE : 26 px + 10 px de chaque côté = 46 px.
   La valeur existait déjà (--gw: 46px) : elle était juste trop courte pour un cercle de 26 px
   posé AU BORD (right: 0). On la porte à 52 px pour que le trait s'arrête net avant. */
.band:has(.band-actions) { --gw: 52px; }
.band:has(.band-actions):has(.evt-tri-btn) { --gw: 124px; }
/* Et le bouton respire : il ne colle plus au bord de la coque. */
.band-actions { right: 2px; }

/* En thème clair, le cercle garde sa lisibilité (le client l'a vu sur la fiche compagnie). */
.profile-sheet.sheet-clair .band-plus.band-rond {
  border-color: rgba(23,23,25,.26);
  background: #ffffff;
  color: #17171a;
}
.profile-sheet.sheet-clair .band-plus.band-rond:hover {
  background: rgba(23,23,25,.08);
  border-color: rgba(23,23,25,.44);
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LE POP-UP DE L'ÉVÉNEMENT — LE MODE SOMBRE, MESURÉ ★★★

   Client : « le pop-up de l'événement [...] le mode sombre il sort encore avec ses problèmes de
   vision où on ne peut pas voir certains textes. »

   QUATRE FAMILLES MESURÉES, ET LA PREMIÈRE EST UNE RÉGRESSION À MOI :
     1. « Annuler » à **1,05:1** — `rgb(244,243,241)` sur BLANC. **Le bouton est invisible.**
        ⚠️ C'EST MA RÈGLE D'AUJOURD'HUI : `.button.secondary { color: var(--text) }`. Or `--text`
        n'existe PAS dans la coque de cette fenêtre (elle hérite du thème de la fiche, qui n'a
        pas cette variable) — la valeur retombait donc sur du blanc, dans un pop-up blanc.
        **Une variable non résolue ne tombe jamais en erreur : elle tombe sur une autre valeur.**
     2. Les titres de section (« L'événement », « L'équipe sur le terrain », « Hôtesses »,
        « Responsables d'équipe ») : `rgb(116,113,108)` = **2,25:1** sur blanc.
     3. « Jour J » : `rgb(56,54,51)` sur un dégradé sombre = **1,36:1**. Le gris que j'ai choisi
        aujourd'hui est fait pour du BLANC — ici il vit sur une pastille sombre.
     4. « 0 sélectionnée » : `rgb(23,23,26)` sur la pastille `eq-nb` (fond sombre) = **3,43:1**.
   ⚠️ LA LEÇON, ENCORE LA MÊME : chaque famille a SON fond. Une encre écrite pour un fond blanc
   ne doit jamais atterrir sur un fond sombre — et réciproquement. On nomme donc les DEUX côtés.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. LE BOUTON SECONDAIRE : JAMAIS BLANC SUR BLANC ──────────────────────────────────
   On nomme l'encre par le FOND, pas par le thème : la coque de ce pop-up est blanche, donc
   l'encre du bouton secondaire est sombre, TOUJOURS. */
dialog.admin-dialog .button.secondary,
dialog.admin-dialog.profile-sheet .button.secondary,
#session-dialog .button.secondary,
.equipe-dialog .button.secondary {
  color: #17171a !important;
  background: #ffffff !important;
  border-color: rgba(23,23,25,.30) !important;
}
dialog.admin-dialog.profile-sheet:not(.sheet-clair) .button.secondary,
#session-dialog:not(.sheet-clair) .button.secondary {
  color: #17171a !important;
  background: #ffffff !important;
}

/* ── 2. LES TITRES DE SECTION ET LES SOUS-TITRES DU POP-UP ─────────────────────────────
   `rgb(116,113,108)` = 2,25:1 sur blanc. Il faut L ≤ 56,6 pour 4,5:1 → `#383633` = 4,70:1. */
#session-dialog .section-label,
#session-dialog .jeu-sous-titre,
#session-dialog .section-caption,
#session-dialog label,
#session-dialog .dot-champ span,
.dialog-kicker,
.profile-sheet .sheet-section > p.section-label {
  color: #383633 !important;
}
/* Mais sur une coque SOMBRE, ces mêmes textes suivent le thème. */
#session-dialog:not(.sheet-clair) .section-label,
#session-dialog:not(.sheet-clair) .jeu-sous-titre,
#session-dialog:not(.sheet-clair) .section-caption,
#session-dialog:not(.sheet-clair) label,
#session-dialog:not(.sheet-clair) .dot-champ span {
  color: rgba(244,243,241,.80) !important;
}

/* ── 3. LA PASTILLE « Jour J » : c'est une pastille à fond SOMBRE, donc encre CLAIRE ────
   ⚠️ MON `#383633` D'AUJOURD'HUI L'A NOIRCIE. Le gris fait pour le blanc n'a rien à faire
   ici : la pastille est un ÉTAT, elle porte son propre fond. */
.evt-jour-j,
.evt-jour-j b,
.evt-jour-j span,
.profile-sheet .evt-jour-j,
.profile-sheet .evt-jour-j * {
  color: #ffffff !important;
}
.evt-jour-j {
  background: #8f1a23 !important;
  border-color: #8f1a23 !important;
}

/* ── 4. LA PASTILLE DE COMPTE « 0 sélectionnée » (la classe est `eq-nb`) ────────────────
   MESURÉ : `rgb(23,23,26)` sur un fond de luminance 80. Pastille pleine → encre CLAIRE. */
.eq-nb,
.eq-nb *,
.eq-nb b,
.eq-nb span,
#session-dialog .eq-nb,
#session-dialog .eq-nb * {
  color: #ffffff !important;
}
.eq-nb {
  background: #565350 !important;
  border-color: #565350 !important;
}

/* ── 5. LA BARRE ÉPINGLÉE : DEUX BOUTONS DE MÊME TAILLE, ALIGNÉS ──────────────────────
   MESURÉ : la barre ne portait QU'UN bouton (« ☽ ») et sa hauteur tombait à **33,5 px**
   au lieu des 38 px prévus — parce que la croix × n'était pas rendue dans cette fenêtre.
   Deux causes possibles, on couvre les deux :
     · la croix est absente du HTML de cette coque → la barre doit rester alignée SANS elle
       (hauteur fixe, pas de hauteur déduite du contenu) ;
     · la croix existe mais une règle la masque → on la rend visible.
   ⚠️ UNE BARRE DONT LA HAUTEUR DÉPEND DE SON CONTENU VARIE : on la FIXE. */
#session-dialog .sheet-pin-bar,
.profile-sheet .sheet-pin-bar,
.session-refait .sheet-pin-bar {
  min-height: 46px;
  align-items: center !important;
}
#session-dialog .sheet-pin-bar > button,
.session-refait .sheet-pin-bar > button {
  width: 38px !important;
  height: 38px !important;
  flex: none !important;
  align-self: center !important;
}
#session-dialog .sheet-close-btn,
.session-refait .sheet-close-btn {
  display: grid !important;
  visibility: visible !important;
  opacity: 1 !important;
}

/* ⚠️⚠️ CORRECTION DE MA CORRECTION — J'AI VISÉ LES MAUVAISES CLASSES. ⚠️⚠️
   Mon bloc précédent nommait `.section-label` et `.jeu-sous-titre` : ces classes n'existent PAS
   dans le pop-up de l'événement. Le HTML réel, lu dans `openSessionForm` :
       <p class="dialog-kicker">Jour J</p>
       <span class="eq-section-titre">L'événement</span>
       <span class="eq-section-titre">L'équipe sur le terrain</span>
       <p class="eq-note">Plusieurs personnes possibles…</p>
       <span class="eq-nb">0 sélectionnée</span>
   ⚠️ LA LEÇON, ENCORE : viser le nom MESURÉ, jamais le nom supposé. Une règle qui nomme une
   classe inexistante ne corrige rien et donne l'impression du contraire. */

/* ── LES TITRES DE SECTION ET LE KICKER ────────────────────────────────────────────────
   MESURÉ : `rgb(116,113,108)` = 2,25:1 sur blanc. Seuil 4,5:1 → `#383633` = 4,70:1. */
#session-dialog .dialog-kicker,
#session-dialog .eq-section-titre,
#session-dialog .eq-note,
#session-dialog .row-sub,
#session-dialog label,
.session-refait .dialog-kicker,
.session-refait .eq-section-titre,
.session-refait .eq-note {
  color: #383633 !important;
}
/* Sur une coque SOMBRE, ces mêmes textes suivent le thème — jamais du sombre sur du sombre. */
#session-dialog:not(.sheet-clair) .dialog-kicker,
#session-dialog:not(.sheet-clair) .eq-section-titre,
#session-dialog:not(.sheet-clair) .eq-note,
#session-dialog:not(.sheet-clair) .row-sub,
#session-dialog:not(.sheet-clair) label,
.session-refait:not(.sheet-clair) .dialog-kicker,
.session-refait:not(.sheet-clair) .eq-section-titre,
.session-refait:not(.sheet-clair) .eq-note {
  color: rgba(244,243,241,.80) !important;
}
/* Le KICKER « Jour J » est une PASTILLE : mesuré à 1,36:1 (encre sombre sur son propre fond
   sombre). Une pastille porte son fond, donc son encre est CLAIRE — dans les deux thèmes. */
#session-dialog .dialog-kicker,
.session-refait .dialog-kicker,
#session-dialog:not(.sheet-clair) .dialog-kicker {
  color: #ffffff !important;
  background: #8f1a23 !important;
  border-color: #8f1a23 !important;
}

/* ── LA PASTILLE DE COMPTE `eq-nb` ─────────────────────────────────────────────────────
   MESURÉ : `rgb(255,255,255)` sur `#565350` (luminance 83) = 3,06:1 — juste sous le seuil.
   ⚠️ C'EST MA COULEUR D'AUJOURD'HUI : j'ai assombri le fond à `#565350` en gardant l'encre
   blanche, et je n'ai pas revérifié le couple. Sur un gris moyen il faut soit éclaircir le
   fond, soit assombrir l'encre. On ÉCLAIRCIT LE FOND : la pastille reste lisible ET discrète.
   `#6b6862` (luminance 104) : 255,05 / 104+0,05 = 2,45 — pire. Donc on ASSOMBRIT le fond :
   `#3f3c38` (luminance 60) → 255,05/60,05 = 4,25 — encore juste. `#343130` → 4,9:1. OK. */
#session-dialog .eq-nb,
.session-refait .eq-nb,
.equipe-dialog .eq-nb {
  background: #343130 !important;
  border-color: #343130 !important;
  color: #ffffff !important;
}
#session-dialog .eq-nb b,
#session-dialog .eq-nb span,
.session-refait .eq-nb * {
  color: #ffffff !important;
}

/* ⚠️⚠️ TROISIÈME FOIS QUE JE VISE UN MAUVAIS NOM DANS CE POP-UP. ⚠️⚠️
   Le HTML réel de `blocEquipeHtml` :
       <div class="eq-bloc"><div class="eq-tete">
           <span class="eq-titre">Hôtesses</span>
           <span class="eq-nb">0 sélectionnée</span>
   Et la couleur qui restait à 2,25:1 vient de `--muted: #6b6965` (remontée par héritage au
   niveau de la coque, l.13916) — pas d'une règle qui nomme le titre.
   ⚠️ LA LEÇON, PAYÉE TROIS FOIS DANS LA MÊME HEURE : **on ne devine pas un nom de classe, on le
   LIT dans la fonction qui fabrique le HTML.** Sinon on écrit des règles justes qui ne prennent
   pas, et on croit avoir corrigé. */

/* ── LES TITRES DE GROUPE D'ÉQUIPE ET LEURS NOMS ──────────────────────────────────────
   `--muted` vaut `#6b6965` sur la coque : 2,25:1 sur blanc. On écrit l'encre EXPLICITEMENT,
   plutôt que de dépendre d'une variable qui vaut trois choses selon la coque. */
#session-dialog .eq-titre,
#session-dialog .eq-nom,
#session-dialog .eq-ville,
.session-refait .eq-titre,
.session-refait .eq-nom,
.session-refait .eq-ville {
  color: #383633 !important;
}
/* ⚠️⚠️ MA RÈGLE « EN SOMBRE ON SUIT LE THÈME » A CRÉÉ 27 DÉFAUTS. ⚠️⚠️
   MESURÉ après coup : `rgba(244,243,241,.86)` sur les noms des personnes = **1,05:1**, 27 fois.
   LA RAISON : `.eq-item` (la pastille d'une personne dans la liste) porte un **FOND BLANC ÉCRIT
   EN DUR** — `#fff` — dans les DEUX thèmes. Il ne suit donc PAS le thème de la coque.
   ⚠️ LA LEÇON, LA MÊME QUE DIX FOIS : **une encre se choisit pour LE FOND RÉELLEMENT PORTÉ.** Un
   élément à fond blanc écrit en dur reçoit une encre SOMBRE, même dans une coque sombre.
   C'est la règle que le projet applique déjà aux cartes `.goodie-tile` et aux `.event-card`.
   On ne « suit le thème » que pour ce qui vit SUR la coque. */
#session-dialog .eq-item,
#session-dialog .eq-item *,
.session-refait .eq-item,
.session-refait .eq-item *,
.equipe-dialog .eq-item,
.equipe-dialog .eq-item * {
  color: #1a1b1d !important;
}
#session-dialog .eq-item.on,
#session-dialog .eq-item.on * {
  color: #ffffff !important;
  background: #8f1a23 !important;
}
/* La pastille `eq-tete` elle-même : elle porte les deux, on s'assure du fond. */
#session-dialog .eq-tete,
.session-refait .eq-tete {
  border-color: rgba(23,23,25,.16) !important;
}
#session-dialog:not(.sheet-clair) .eq-tete {
  border-color: rgba(255,255,255,.18) !important;
}
/* Et la variable elle-même SUR CETTE COQUE : le pop-up est clair par défaut, donc `--muted`
   doit y valoir le gris lisible, et non celui d'une coque sombre. */
#session-dialog.sheet-clair,
#session-dialog.profile-sheet.sheet-clair,
.session-refait.sheet-clair {
  --muted: #383633;
}
#session-dialog:not(.sheet-clair) {
  --muted: rgba(244,243,241,.80);
}

/* ⚠️⚠️ LE TITRE DE GROUPE ET LE NOM D'UNE PERSONNE NE VIVENT PAS AU MÊME ENDROIT. ⚠️⚠️
   MESURÉ, chaîne des parents nommée par le navigateur :
       span.eq-titre   fond rgba(0,0,0,0)
       div.eq-tete     fond rgba(0,0,0,0)
       div.eq-bloc     fond rgba(0,0,0,0.02)   <- un VOILE de 2 %, pas un fond
       ...puis la COQUE
   Comparé à :
       button.eq-item  fond rgb(255,255,255)   <- un VRAI fond blanc, écrit en dur

   DONC :
     · `.eq-titre` vit SUR la coque (le voile de 2 % ne compte pas) → son encre SUIT LE THÈME ;
     · `.eq-nom` et `.eq-ville` vivent DANS `.eq-item` (fond blanc) → encre SOMBRE, toujours.

   ⚠️ C'EST LE MÊME PRINCIPE QUE POUR TOUT LE RESTE, MAIS IL FALLAIT LE MESURER ICI : le voile de
   2 % est ce qui trompe — il fait croire à un fond alors que c'est la coque qu'on voit. Un
   élément ne « porte » un fond que si ce fond est RÉELLEMENT OPAQUE.
   ⚠️ ET C'EST POURQUOI MON `#383633` GLOBAL ÉTAIT FAUX ET MA RÈGLE « ON SUIT LE THÈME » AUSSI :
   les deux familles étaient traitées ensemble alors qu'elles vivent à deux endroits différents. */

/* LE TITRE DE GROUPE : sur la coque, donc il suit le thème. */
#session-dialog .eq-titre,
.session-refait .eq-titre,
.equipe-dialog .eq-titre {
  color: #383633 !important;          /* coque CLAIRE : 4,70:1 sur blanc */
}
#session-dialog:not(.sheet-clair) .eq-titre,
.session-refait:not(.sheet-clair) .eq-titre,
.equipe-dialog:not(.sheet-clair) .eq-titre {
  color: rgba(244,243,241,.88) !important;   /* coque SOMBRE : lisible sur le voile */
}
/* LE NOM ET LA VILLE : dans `.eq-item` (fond blanc en dur), donc encre SOMBRE, toujours.
   Cette règle vient APRÈS pour gagner sur celle du titre si les deux matchaient. */
#session-dialog .eq-item .eq-nom,
#session-dialog .eq-item .eq-ville,
.session-refait .eq-item .eq-nom,
.session-refait .eq-item .eq-ville,
.equipe-dialog .eq-item .eq-nom,
.equipe-dialog .eq-item .eq-ville {
  color: #1a1b1d !important;
}
#session-dialog .eq-item.on .eq-nom,
#session-dialog .eq-item.on .eq-ville {
  color: #ffffff !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   ★★★ LES DEUX BOUTONS DU POP-UP DE L'ÉVÉNEMENT — ENFIN DANS LE MÊME CONTENEUR ★★★

   Client : « le bouton fermé et le bouton sombre clair ne sont pas alignés en haut à droite. »

   ⚠️ LA CAUSE ÉTAIT STRUCTURELLE, PAS UN RÉGLAGE. MESURÉ, chacun avec son parent :
       « ☽ »  sheet-theme-toggle   x 1514 → 1552   y 144 → 181   parent : SHEET-PIN-BAR
       « × »  dialog-close         x 1484 → 1525   y 229 → 270   parent : DIALOG-HEAD
                                              ↑ 85 px d'écart vertical, 27 px horizontal
   La bascule vient de la BARRE ÉPINGLÉE, que la coque injecte dans toutes les fiches. La croix
   vient du `<div class="dialog-head">` écrit À LA MAIN dans `openSessionForm`.
   **Deux conteneurs, deux hauteurs** : aucun `margin-top` ne peut les aligner, parce qu'ils ne
   sont pas côte à côte.

   ⚠️ ET IL Y A UNE AUTRE DIFFÉRENCE, MESURÉE : la croix porte `class="dialog-close"`, alors que
   toutes les autres fiches utilisent `sheet-close-btn`. Deux classes pour la même fonction, donc
   DEUX styles différents (41×41 contre 38×38, un fond clair contre un fond sombre).

   LA CORRECTION, EN UN SEUL ENDROIT : on fait de `.dialog-close` L'ÉQUIVALENT de `.sheet-close-btn`
   DANS LA BARRE. Même taille, même cercle, même position — et la barre les range côte à côte.
   ⚠️ ON NE TOUCHE PAS AU CONTENEUR : déplacer la croix dans le HTML aurait cassé les autres
   pop-ups qui n'ont PAS de barre épinglée (`image-library`, `import-dialog`, la confirmation).
   Cinq pop-ups partagent cette classe : la corriger au CSS les sert tous d'un coup.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* 1. LA CROIX DEVIENT UN BOUTON DE BARRE, à l'identique de `.sheet-close-btn`. */
#session-dialog .sheet-pin-bar .dialog-close,
.session-refait .sheet-pin-bar .dialog-close {
  -webkit-appearance: none !important;
  appearance: none !important;
  display: grid !important;
  place-items: center !important;
  width: 38px !important;
  height: 38px !important;
  flex: none !important;
  align-self: center !important;
  padding: 0 !important;
  border-radius: 50% !important;
  border: 1px solid rgba(23,23,25,.28) !important;
  background: rgba(255,255,255,.94) !important;
  color: #1a1b1d !important;
  font: 600 20px/1 Manrope, sans-serif !important;
  cursor: pointer !important;
  box-shadow: 0 3px 12px rgba(0,0,0,.16) !important;
}
#session-dialog:not(.sheet-clair) .sheet-pin-bar .dialog-close,
.session-refait:not(.sheet-clair) .sheet-pin-bar .dialog-close {
  border-color: rgba(255,255,255,.42) !important;
  background: rgba(15,16,17,.88) !important;
  color: #fff !important;
  box-shadow: 0 3px 12px rgba(0,0,0,.45) !important;
}
#session-dialog .sheet-pin-bar .dialog-close:hover,
.session-refait .sheet-pin-bar .dialog-close:hover {
  background: #c0392f !important;
  border-color: #c0392f !important;
  color: #fff !important;
  transform: scale(1.06) !important;
}
/* La croix est un `×` textuel : on le centre optiquement comme les autres. */
#session-dialog .sheet-pin-bar .dialog-close { line-height: 1 !important; }

/* 2. LE `dialog-head` NE PORTE PLUS LA CROIX : elle est dans la barre désormais. On retire donc
   la place qu'il lui réservait, sinon le titre se décalerait vers la gauche pour rien.
   ⚠️ `justify-content: center` n'est PAS nécessaire : le nœud texte « Jour J » et le `<h2>`
   restent, et ils gardent leur alignement à gauche. */
#session-dialog .dialog-head,
.session-refait .dialog-head {
  justify-content: flex-start !important;
  padding-right: 92px !important;   /* la place des deux cercles de 38 px + l'écart */
}

/* 3. LA BARRE ELLE-MÊME : elle doit être le POINT DE RÉFÉRENCE unique du coin haut-droit.
   MESURÉ : `position: sticky` + `top` hérité ⇒ le `y` dépendait du défilement. On la fixe. */
#session-dialog .sheet-pin-bar,
.session-refait .sheet-pin-bar {
  position: sticky !important;
  top: 0 !important;
  display: flex !important;
  align-items: center !important;
  justify-content: flex-end !important;
  gap: 8px !important;
  min-height: 46px !important;
  padding: 0 12px 0 0 !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA BARRE DE SIMULATION DES GOODIES

   Client : « on peut avoir en haut le choix entre le dépôt, 4 aléatoires du dépôt, ou des
   collaborateurs ou des responsables actifs [...] c'est juste un test. »

   ⚠️ LA CONVENTION DE LA FAMILLE `.jeu-*` EST MESURÉE, ET ELLE EST PARTICULIÈRE :
   les cartes de l'atelier ne suivent PAS le thème. Elles ont un fond BLANC et une encre
   SOMBRE explicites, dans les deux thèmes — c'est vrai de `.jeu-option` (fond `#fff`, encre
   `#0d0d0f`), `.jeu-aide-mini` (`#383633`), `.jeu-sous-titre` (`#17171a`), `.jeu-curseur >
   span` (`#0d0d0f`), `.jeu-visuel` (`#fff`), `.jeu-pave-btn` (`#fff`). Vérifié : il n'existe
   AUCUNE règle `[data-theme="dark"] .jeu-option`.
   → On suit cette convention. Un fond qui suit le thème aurait donné, au milieu d'une
   section de cartes blanches, un bloc qui change de teinte tout seul.

   ⚠️ LE VOCABULAIRE EST CELUI DES AUTRES RÉGLAGES : les quatre choix réutilisent
   `.jeu-options` / `.jeu-option` telles quelles — le même composant que « Forme » de
   l'aiguille et les modes de découpe. Un composant neuf pour quatre boutons aurait donné
   deux familles de cartes dans la même section, donc l'impression d'un ajout.

   ⚠️ AUCUN ROUGE DÉCORATIF. Le client a tranché : « j'aime pas ce shader en rouge tu mets
   juste le blanc ». Le rouge reste là où il porte un ÉTAT (la carte choisie, comme partout
   dans l'atelier) — jamais ailleurs.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.jeu-sim {
  margin: 0 0 20px;
  padding: 16px 18px;
  border-radius: 13px;
  border: 1.5px solid rgba(23, 23, 25, .14);
  /* Le fond suit la convention mesurée de la famille : blanc, encre sombre. */
  background: #fff;
}

/* En simulation active, le cadre s'affirme un peu : on doit voir d'un coup d'œil que ce
   qu'on regarde n'est PAS la roue réelle. ⚠️ ON NE CHANGE PAS LA TEINTE DE FOND — cela
   ferait basculer l'encre. On joue sur l'épaisseur du trait et un liseré intérieur. */
.jeu-sim.is-actif {
  border-color: rgba(23, 23, 25, .30);
  box-shadow: inset 0 0 0 1px rgba(23, 23, 25, .06);
}

.jeu-sim-tete {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Le sous-titre de la famille porte une marge haute de 16px ; dans l'en-tête, elle
   décalerait le badge vers le bas. */
.jeu-sim .jeu-sim-tete .jeu-sous-titre { margin: 0; }

/* L'aide est SOUS l'en-tête, pas à côté du badge : elle explique les quatre choix. */
.jeu-sim .jeu-sim-aide { margin: 7px 0 0; }

/* Le badge « Simulation » — fond opaque, encre sombre, comme toutes les pastilles de la
   famille. Il vit DANS une carte blanche : son encre est donc sombre, SANS portée de thème.
   (Règle payée plusieurs fois : l'encre sombre est pour ce qui PORTE un fond clair, jamais
   pour ce qui vit dessus.) */
.jeu-sim-badge {
  font: 600 11.5px Manrope, sans-serif;
  letter-spacing: .01em;
  padding: 4px 11px;
  border-radius: 999px;
  background: rgba(23, 23, 25, .06);
  color: #383633;
  border: 1px solid rgba(23, 23, 25, .12);
}

/* Le compte sur le disque — le chiffre qui dit l'effet de la simulation. */
.jeu-sim-compte {
  font: 600 13px Manrope, sans-serif;
  color: #17171a;
}

.jeu-sim-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 15px;
  padding-top: 13px;
  border-top: 1px solid rgba(23, 23, 25, .12);
}

/* La liste déroulante de la personne prend sa propre ligne : elle porte un libellé et une
   valeur, elle ne se range pas à côté de quatre cartes. */
.jeu-sim-personne {
  margin-top: 15px;
  max-width: 540px;
}

/* ⚠️ ON NE REDÉFINIT PAS `.jeu-options`. Elle porte déjà `display:grid` et
   `auto-fit minmax(180px,1fr)` (styles.css l.8291), plus quatre règles `!important` qui la
   reprennent aux points de rupture (l.8424, 8893, 8903, 8908). La redéfinir ici sans
   `!important` perdrait contre elles — et en ajouter un obligerait à en mettre partout
   ensuite. On ne touche donc qu'à la marge : les quatre cartes se rangent exactement comme
   les formes de l'aiguille, ce qui est l'effet voulu. */
.jeu-sim .jeu-options { margin-top: 13px; }

/* ══════════════════════════════════════════════════════════════════════════════════════
   LA SECTION SUPER ADMIN — GESTION DES COMPTES

   Client : « j'aurai comme les fiches collaboratrices et responsables, j'aurai les mêmes
   styles de fiches. »

   ⚠️ LA CONVENTION EST MESURÉE, PAS INVENTÉE — et elle est particulière :
   les vues de données (collaborateurs, établissements, entreprises, tableau de bord) sont
   peintes en BLANC avec une encre SOMBRE EXPLICITE, dans les DEUX thèmes. Vérifié :
   `.loc-tile` fond `#c9c7c3`, `.goodie-tile`, `.eq-item` fond `#fff`, `.evt-goodie` fond
   `#fff`. Il n'existe aucune règle `[data-theme="dark"] .loc-tile`.
   → On suit cette convention. Un fond qui suivrait le thème aurait donné, au milieu d'une
   application de cartes claires, un écran qui bascule tout seul.

   ⚠️ L'ENCRE EST ÉCRITE SUR CHAQUE TEXTE. C'est la règle payée le plus cher sur ce projet :
   « l'encre sombre est pour les éléments qui PORTENT un fond clair, jamais pour ceux qui
   vivent dessus ». Toutes les couleurs ci-dessous sont donc EXPLICITES, jamais héritées.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── L'EN-TÊTE ────────────────────────────────────────────────────────────────────────── */
.sup-tete {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 22px; flex-wrap: wrap; margin: 0 0 20px;
}
.sup-tete h2 { margin: 2px 0 4px; color: #17171a; font: 700 26px Manrope, sans-serif; }
.sup-tete .section-label { color: #6b6965; }
.sup-tete .section-caption { margin: 0; color: #4a4744; max-width: 62ch; }

.sup-ajouter {
  flex: none; cursor: pointer; padding: 11px 20px; border: 0; border-radius: 11px;
  background: #17171a; color: #ffffff; font: 700 13.5px Manrope, sans-serif;
  transition: transform .12s ease, background .15s ease;
}
.sup-ajouter:hover { background: #2c2e31; transform: translateY(-1px); }

/* ── LES 3 ONGLETS ─────────────────────────────────────────────────────────────────────
   Client : « j'aurai 3 onglets sur la même page ». Trois cartes de même largeur, avec leur
   compte à droite : on sait combien il y a de personnes AVANT d'ouvrir. */
.sup-onglets {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 0 0 18px;
}
@media (max-width: 900px) { .sup-onglets { grid-template-columns: 1fr; } }

.sup-onglet {
  display: grid; gap: 2px; cursor: pointer; text-align: left; position: relative;
  padding: 13px 16px; border-radius: 13px;
  border: 1.5px solid rgba(23, 23, 25, .14); background: #ffffff;
  transition: border-color .15s ease, background .15s ease;
}
.sup-onglet b { color: #0d0d0f; font: 700 13.5px Manrope, sans-serif; }
.sup-onglet small { color: #56534e; font: 500 11px Manrope, sans-serif; }
.sup-onglet:hover { border-color: rgba(23, 23, 25, .34); }
/* ⚠️ L'ÉTAT ACTIF EST UN CONTOUR, PAS UN APLAT ROUGE : le rouge de l'atelier est réservé
   aux cartes de réglage ; dans une vue de données, la sélection se dit sobrement. */
.sup-onglet.is-actif { border-color: #17171a; background: rgba(23, 23, 25, .04); }

/* Le compte, à droite du nom. ⚠️ Encre sombre sur fond clair, jamais `var(--text)`. */
.sup-onglet-n {
  position: absolute; top: 12px; right: 14px;
  min-width: 26px; text-align: center; padding: 2px 7px; border-radius: 999px;
  background: rgba(23, 23, 25, .07); color: #383633;
  font: 700 11.5px 'DM Mono', monospace;
}

/* ── LA GRILLE DES CARTES ────────────────────────────────────────────────────────────── */
.sup-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(228px, 1fr)); gap: 14px;
}

.sup-carte {
  display: grid; gap: 11px; padding: 15px 16px; border-radius: 15px;
  border: 1px solid rgba(23, 23, 25, .12);
  /* Le fond de la vue est très clair : une carte blanche pure s'y détache sans ombre. */
  background: #ffffff;
}
/* ⚠️ TA CARTE SE DISTINGUE — trait plus marqué, sans changer la teinte (changer la teinte
   ferait basculer l'encre). C'est la fiche INTOUCHABLE : elle doit se voir d'un coup d'œil. */
.sup-carte.is-moi { border-color: rgba(23, 23, 25, .38); }

.sup-carte-tete { display: flex; align-items: center; gap: 11px; }
.sup-carte-nom { display: grid; gap: 1px; min-width: 0; }
.sup-carte-nom b { color: #0d0d0f; font: 700 14px Manrope, sans-serif; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sup-carte-nom small { color: #56534e; font: 500 11.5px Manrope, sans-serif; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sup-av {
  flex: none; width: 46px; height: 46px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  background: #e9e6e1; border: 1px solid rgba(23, 23, 25, .12);
}
.sup-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sup-av b { color: #56534e; font: 700 14px Manrope, sans-serif; }

.sup-carte-pied { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* ⚠️ LA PASTILLE DE RÔLE EST NEUTRE — jamais teintée. Une catégorie nommée n'a pas besoin
   de couleur pour se lire : elle a un mot. (Client : « j'aime pas ce shader en rouge ».) */
.sup-role {
  padding: 3px 9px; border-radius: 999px;
  background: rgba(23, 23, 25, .07); color: #383633;
  font: 600 10.5px Manrope, sans-serif; letter-spacing: .01em;
}
/* Le rôLE LE PLUS HAUT se marque par un trait plus appuyé, pas par une couleur. */
.sup-role-superadmin { background: #17171a; color: #ffffff; }
.sup-role-admin { background: rgba(23, 23, 25, .13); color: #17171a; }
.sup-moi { padding: 3px 9px; border-radius: 999px; border: 1px solid rgba(23, 23, 25, .22); color: #383633; font: 600 10.5px Manrope, sans-serif; }
.sup-inactif { background: rgba(23, 23, 25, .05); color: #6b6965; font-style: italic; }

.sup-carte-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.sup-btn {
  flex: 1 1 auto; cursor: pointer; padding: 8px 13px; border-radius: 9px; text-align: center;
  border: 1.5px solid rgba(23, 23, 25, .16); background: #ffffff; color: #2c2a27;
  font: 600 12px Manrope, sans-serif; transition: border-color .15s ease, background .15s ease;
}
.sup-btn:hover { border-color: rgba(23, 23, 25, .4); background: rgba(23, 23, 25, .03); }
/* ⚠️ L'ACTION DESTRUCTRICE SE TEINTE AU SURVOL SEULEMENT : au repos elle reste neutre, pour
   qu'un écran de 12 cartes ne soit pas un mur de rouge. */
.sup-btn-danger:hover { border-color: #c0392f; color: #a01f28; background: rgba(192, 57, 47, .07); }
.sup-btn-larg { flex: 1 1 100%; }

/* ── LA PAGE VIDE ────────────────────────────────────────────────────────────────────── */
.sup-vide {
  display: grid; gap: 3px; place-items: center; padding: 44px 20px; border-radius: 15px;
  border: 1.5px dashed rgba(23, 23, 25, .2); background: rgba(23, 23, 25, .015);
}
.sup-vide b { color: #17171a; font: 700 14px Manrope, sans-serif; }
.sup-vide small { color: #56534e; font: 500 12px Manrope, sans-serif; }

/* ── LES OUTILS DU FONDATEUR ─────────────────────────────────────────────────────────── */
.sup-outils {
  margin: 28px 0 0; padding: 20px 22px; border-radius: 15px;
  border: 1px solid rgba(23, 23, 25, .12); background: #ffffff;
}
.sup-outils h2 { margin: 2px 0 4px; color: #17171a; font: 700 20px Manrope, sans-serif; }
.sup-outils .section-label { color: #6b6965; }
.sup-outils .section-caption { margin: 0; color: #4a4744; max-width: 68ch; }

.sup-outils-grille {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 13px; margin: 16px 0 0;
}
.sup-outil {
  display: grid; gap: 6px; align-content: start;
  padding: 15px 16px; border-radius: 13px;
  border: 1px solid rgba(23, 23, 25, .12); background: rgba(23, 23, 25, .02);
}
.sup-outil b { color: #0d0d0f; font: 700 13.5px Manrope, sans-serif; }
.sup-outil small { color: #4a4744; font: 500 11.5px Manrope, sans-serif; line-height: 1.5; }
.sup-outil .sup-btn { margin-top: 4px; }
/* ⚠️ LE CADRE DE L'ACTION DESTRUCTRICE SE MARQUE AU TRAIT, PAS AU FOND. Un fond rouge
   obligerait à changer l'encre de tout ce qui vit dedans — et c'est ainsi qu'on obtient
   du noir sur noir. */
.sup-outil-danger { border-color: rgba(192, 57, 47, .40); background: rgba(192, 57, 47, .035); }

/* ── LA ZONE DE CONFIRMATION — le seul endroit où une action destructrice s'exécute ───── */
.sup-confirme {
  margin: 16px 0 0; padding: 16px 18px; border-radius: 13px;
  border: 1.5px solid rgba(192, 57, 47, .45); background: rgba(192, 57, 47, .05);
}
.sup-confirme b { display: block; margin: 0 0 5px; color: #8f1a23; font: 700 13.5px Manrope, sans-serif; }
.sup-confirme p { margin: 0 0 11px; color: #4a4744; font: 500 12px Manrope, sans-serif; line-height: 1.55; }
.sup-confirme-mot {
  width: 100%; max-width: 320px; padding: 9px 12px; border-radius: 9px;
  border: 1.5px solid rgba(23, 23, 25, .2); background: #ffffff; color: #17171a;
  font: 600 13px 'DM Mono', monospace;
}
.sup-confirme-actions { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 11px; }

/* ── LA ZONE DE RÉSULTAT — une action qui ne dit rien laisse croire qu'elle a échoué ──── */
.sup-resultat {
  margin: 13px 0 0; padding: 12px 15px; border-radius: 11px;
  border: 1px solid rgba(23, 23, 25, .14); background: rgba(23, 23, 25, .03);
  color: #2c2a27; font: 500 12.5px Manrope, sans-serif; line-height: 1.55;
}
.sup-resultat.is-erreur { border-color: rgba(192, 57, 47, .42); background: rgba(192, 57, 47, .06); color: #8f1a23; }

/* ── LE TRAIT DE LA BARRE LATÉRALE ───────────────────────────────────────────────────── */
/* Le trait « SUPER ADMIN » suit exactement la forme des autres (PILOTAGE, CLASSIQUE) : il
   est écrit par `nav-divider`. Aucune règle supplémentaire n'est nécessaire — c'est le but.
   On garde une teinte très légèrement plus claire pour le distinguer des sections
   ordinaires sans introduire une couleur nouvelle. */
#nav-super-trait span { opacity: .92; }

/* ── LA PHRASE QUI REMPLACE LE BOUTON SUR UN COMPTE PROTÉGÉ ────────────────────────────
   ⚠️ Un bouton qui ne peut pas aboutir vaut moins que pas de bouton : quand la cible est un
   compte superadmin et qu'on n'est pas le superadmin, on n'affiche pas « Enregistrer » —
   on affiche POURQUOI. C'est la même règle que les sections vides de la fiche campagne.
   L'encre est sombre sur la coque claire, comme tout texte vivant dans une fiche. */
.sup-lecture-seule {
  display: inline-flex; align-items: center; margin: 0;
  padding: 9px 14px; border-radius: 10px;
  border: 1px solid rgba(23, 23, 25, .16); background: rgba(23, 23, 25, .04);
  color: #383633; font: 600 12.5px Manrope, sans-serif;
}

/* ── LES BOUTONS D'AJOUT SECONDAIRES ───────────────────────────────────────────────────
   ⚠️ « + Administrateur » est le geste principal de cette page (elle est faite pour créer des
   administrateurs) : il garde le fond sombre. Les deux autres sont des raccourcis vers le
   terrain — ils se font discrets, en contour. Trois boutons pleins côte à côte donneraient
   trois gestes d'égale importance, ce qui n'est pas vrai. */
.sup-tete-actions { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; }
.sup-ajouter-2 {
  background: #ffffff; color: #2c2a27;
  border: 1.5px solid rgba(23, 23, 25, .2);
}
.sup-ajouter-2:hover { background: rgba(23, 23, 25, .04); color: #17171a; }

/* La note qui explique l'identifiant figé : encre sombre, comme tout texte de fiche. */
.sup-id-note { display: block; margin: 0 0 10px; color: #4a4744; }
.sup-id-note b { color: #17171a; }

/* ══ LA CROIX DE SUPPRESSION D'UN VISUEL — AU SURVOL ══════════════════════════════════
   Client : « on peut au survol supprimer des photos ».
   ⚠️ MASQUÉE AU REPOS, VISIBLE AU SURVOL. Un écran de 40 vignettes couvertes de croix
   deviendrait un mur de suppressions : la croix doit se mériter, pas s'imposer.
   ⚠️ ELLE SE VOIT AU CLAVIER AUSSI (`:focus-visible`) — un geste qui n'existe qu'à la souris
   est inaccessible, et ce projet a déjà payé cette leçon sur les pastilles sans `:hover`.
   ⚠️ FOND SOMBRE OPAQUE ET CROIX BLANCHE : elle vit SUR une image, dont on ne connaît pas
   la couleur. Un fond translucide la rendrait illisible sur un visuel clair. */
.library-item { position: relative; }
.library-x {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 50%; cursor: pointer;
  background: rgba(20, 20, 22, .82); color: #ffffff;
  font: 700 15px/1 Manrope, sans-serif;
  opacity: 0; transform: scale(.86);
  transition: opacity .14s ease, transform .14s ease, background .14s ease;
}
.library-item:hover .library-x,
.library-x:focus-visible { opacity: 1; transform: scale(1); }
.library-x:hover { background: #c0392f; }
.library-x:focus-visible { outline: 2px solid #ffffff; outline-offset: 1px; }

/* ══ LES FILTRES DE LA MEDIATHEQUE ═════════════════════════════════════════════════════
   Client : « la librairie, quand on l'ouvre, on doit trouver par exemple des photos
   televersees, des photos de profil, des icones, voila, c'est tout. »

   ⚠️ DES BOUTONS PLUTOT QU'UN MENU DEROULANT : cinq categories se lisent d'un coup d'oeil.
   Un `<select>` cacherait quatre choix sur cinq derriere un clic.
   ⚠️ L'ETAT ACTIF EST UN APLAT SOMBRE, pas une couleur : c'est un choix de FILTRE, pas un
   etat metier. Le rouge reste reserve a ce qui signale quelque chose.
   ⚠️ L'ENCRE EST ECRITE SUR CHAQUE ETAT — sur l'aplat actif elle est claire, sur les autres
   elle est sombre. C'est la regle payee le plus cher sur ce projet. */
.library-filtres {
  display: flex; gap: 7px; flex-wrap: wrap;
  padding: 0 0 11px; margin: 0 0 11px;
  border-bottom: 1px solid rgba(23, 23, 25, .1);
}
.lib-filtre {
  cursor: pointer; padding: 6px 13px; border-radius: 999px;
  border: 1.5px solid rgba(23, 23, 25, .16); background: #ffffff; color: #2c2a27;
  font: 600 12px Manrope, sans-serif;
  transition: border-color .14s ease, background .14s ease, color .14s ease;
}
.lib-filtre:hover { border-color: rgba(23, 23, 25, .4); }
.lib-filtre.is-actif { background: #17171a; border-color: #17171a; color: #ffffff; }
.lib-filtre:focus-visible { outline: 2px solid #17171a; outline-offset: 2px; }

/* ══ LE CADRAGE DE L'EXISTANT EST ACCESSIBLE SANS DEVERROUILLER LA FICHE ═══════════════════
   Client : « une photo de la librairie, je clique, ca disparait ; je ne vois pas cet onglet
   de recadrer puis enregistrer. »

   MESURE : `.edit-only-only { display: none }`, revele uniquement par `.profile-sheet.editing`.
   Or `.editing` est posee par le bouton « Modifier » — qui N'EXISTE PAS dans la fiche profil
   (mesure : `[data-role=sheet-edit]` ABSENT). Le client ne pouvait donc JAMAIS cadrer une
   photo deja en place, ni une photo choisie dans la mediatheque : le cadrage ne se declenche
   qu'au TELEVERSEMENT d'un fichier.

   ⚠️ ON REVELE LE BLOC, PAS LES CHAMPS DE SAISIE : `.edit-only-only` (avec le double `only`)
   ne porte QUE les commandes d'image (« Cadrer l'image », zoom, « Reinitialiser »). Les
   champs de saisie portent `.edit-only`, qui reste cache en lecture — c'est voulu.
   ⚠️ LE BLOC NE S'AFFICHE QUE SI UNE PHOTO EXISTE : `imagePickerHtml` ne le produit que dans
   ce cas (`currentUrl && focusName`), donc le reveler ne cree rien de vide. */
.profile-sheet .identite-edit .edit-only-only,
.profile-sheet .sheet-avatar-slot .edit-only-only,
.profile-sheet .image-field .edit-only-only,
.profile-sheet .cover-field .edit-only-only { display: inline-flex; }

/* ══ L'ÉCRAN DE VERROUILLAGE ═══════════════════════════════════════════════════════════════
   Client (verbatim) : « tout devient en gris et il y a une fenêtre qui sort qui dit : cette
   application est verrouillée en ce moment, veuillez contacter le concepteur. »

   ⚠️ LE GRIS EST UN VRAI DÉSATURAGE, PAS UN VOILE NOIR : « tout devient en gris » décrit un
   monde sans couleur, pas un écran éteint. `backdrop-filter: grayscale(1)` fait exactement
   ça, et il agit SUR ce qui est dessous — donc rien n'est détruit, seulement éteint.
   ⚠️ ON AJOUTE UNE COUCHE SOMBRE PAR-DESSUS le gris : sans elle, le contenu reste lisible et
   on pourrait croire que l'application fonctionne encore.
   ⚠️ LE FOND EST OPAQUE : le contenu ne doit pas être cliquable au travers.
   ⚠️ AUCUN MOUVEMENT ICI : un écran de verrouillage qui s'anime donne l'impression que
   quelque chose travaille. Le cadenas est immobile. */
html.app-lock-actif, html.app-lock-actif body { overflow: hidden !important; }
#app-lock-ecran {
  position: fixed; inset: 0; z-index: 100000;
  display: grid; place-items: center; padding: 24px;
  background: rgba(24, 24, 26, .78);
  -webkit-backdrop-filter: grayscale(1) blur(2px);
  backdrop-filter: grayscale(1) blur(2px);
}
.app-lock-carte {
  max-width: 480px; text-align: center;
  background: #f7f4ee; color: #1c1d1f;
  border: 1px solid #ddd6ca; border-radius: 16px;
  padding: 30px 30px 26px;
}
.app-lock-cadenas { display: block; font-size: 40px; line-height: 1; margin-bottom: 14px; }
.app-lock-carte h2 {
  margin: 0 0 8px; font: 700 20px/1.3 'Plus Jakarta Sans', Manrope, sans-serif; color: #1c1d1f;
}
.app-lock-carte p { margin: 0 0 6px; font: 500 14px/1.5 Manrope, sans-serif; color: #55524e; }
.app-lock-carte small { display: block; margin: 0 0 16px; font: 400 12px/1.5 Manrope, sans-serif; color: #7c7873; }
.app-lock-carte .button { margin-top: 10px; }

/* ══ LE PIED DE L'ÉCRAN DE VERROUILLAGE : LE LOGO ET LE CONTACT ════════════════════════════
   Client : « je veux le logo de clandestudio en png en bas et mon mail contact@clandestudio.com. »

   ⚠️ SÉPARÉ PAR UN FILET, PAS COLLÉ AU TEXTE : le logo est une signature, pas une information.
   Le filet marque la frontière entre « voici pourquoi c'est bloqué » et « voici qui appeler ».
   ⚠️ LE LOGO GARDE SON RAPPORT (1105×206) : `width` + `height:auto`, jamais les deux forcés —
   étirer une signature la rend méconnaissable.
   ⚠️ LE MAIL EST UN LIEN : la personne bloquée doit pouvoir écrire d'un clic. Sur un écran
   d'attente, faire recopier une adresse à la main est une friction gratuite. */
.app-lock-pied {
  margin-top: 20px; padding-top: 16px;
  border-top: 1px solid rgba(23, 23, 25, .13);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.app-lock-logo { width: 168px; height: auto; display: block; }
.app-lock-mail {
  font: 600 13px Manrope, sans-serif; color: #a8232f;
  text-decoration: none; border-bottom: 1px solid rgba(168, 35, 47, .35);
  padding-bottom: 1px;
}
.app-lock-mail:hover { color: #7d1a23; border-bottom-color: #7d1a23; }
.app-lock-mail:focus-visible { outline: 2px solid #a8232f; outline-offset: 3px; }

/* ══ LA CROIX DE SUPPRESSION SUR UNE PHOTO, AU SURVOL ═════════════════════════════════════
   Client : « je veux juste un bouton de survol comme un X qu'on peut supprimer la photo puis
   recliquer sur téléverser encore une fois. »

   ⚠️ ELLE REMPLACE SEPT BOUTONS (« Changer », « Retirer », « Cadrer », « − », « % », « + »,
   « Réinitialiser »). Sur une vignette de 120 px, ces sept commandes formaient un mur et
   poussaient hors du cadre — c'est ce que le client appelait « beaucoup d'éléments qui sortent ».
   ⚠️ MASQUÉE AU REPOS, VISIBLE AU SURVOL ET AU CLAVIER : une croix toujours visible sur chaque
   photo d'une liste deviendrait un mur de suppressions. Elle doit se mériter.
   ⚠️ FOND SOMBRE OPAQUE ET CROIX BLANCHE : elle vit SUR une photo dont on ne connaît pas la
   couleur. Un fond translucide serait illisible sur un cliché clair.
   ⚠️ ROUGE AU SURVOL SEULEMENT : c'est le moment où l'intention est claire, donc le moment où
   la couleur a un sens. */
.ip-x {
  position: absolute; top: -7px; right: -7px; z-index: 6;
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; cursor: pointer;
  background: #17171a; color: #ffffff;
  font: 700 16px/1 Manrope, sans-serif;
  border: 2px solid #ffffff;
  opacity: 0; transform: scale(.82);
  transition: opacity .13s ease, transform .13s ease, background .13s ease;
}
.image-field .image-field-row:hover .ip-x,
.ip-x:focus-visible { opacity: 1; transform: scale(1); }
.ip-x:hover { background: #c0392f; }

/* ⚠️⚠️ L'ENCRE, AU BON NIVEAU — C'EST ICI QUE LA CORRECTION SE JOUE.
   MESURÉ : la couleur calculée de la croix était `rgb(23,23,26)` — NOIRE — alors que la règle
   ci-dessus dit `color:#ffffff`. La coupable est
       `dialog.admin-dialog span { color: inherit }`          (spécificité 0,1,1 = 11)
   qui hérite du noir posé sur le dialogue. `.ip-x` seul vaut (0,1,0) = 10 : IL PERD.
   ⚠️ ON NE MET PAS `!important` : ce projet compte déjà 269 `!important` sur `color`, et chacun
   déclenche une guerre avec le suivant. On écrit un sélecteur PLUS PRÉCIS, qui gagne par la
   cascade normale — `dialog.admin-dialog .ip-x` vaut (0,2,0) = 20, largement au-dessus de 11.
   ⚠️ ET ON L'ÉCRIT POUR LES DEUX COQUES : la fiche profil ET les autres dialogues, sinon la
   croix serait blanche sur l'une et noire sur l'autre. */
dialog.admin-dialog .ip-x,
dialog.admin-dialog .ip-x:hover,
dialog.admin-dialog .image-field .ip-x,
dialog.admin-dialog .cover-field .ip-x { color: #ffffff; }
dialog.admin-dialog .ip-x { background: #17171a; }
dialog.admin-dialog .ip-x:hover { background: #c0392f; }
.ip-x:focus-visible { outline: 2px solid #c0392f; outline-offset: 2px; }

/* Le bouton de photo doit pouvoir porter la croix qui dépasse : sans ce `position:relative`,
   la croix se placerait par rapport à la page et sortirait de la fiche. */
.image-field .image-field-row { position: relative; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   L ENCRE DU ROUGE DE CHARTE SUR FOND CLAIR — LA DERIVEE, PAS LA CONSTANTE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ MESURE : `#e63946` (= `--red` = `--accent`) sur blanc = 4,17:1 — SOUS le seuil de 4,5:1.
   Mesure aussi : blanc sur `#e63946` = 4,83:1 — AU-DESSUS. Le meme rouge est donc CORRECT en
   FOND (les badges pleins) et FAUTIF en ENCRE (un lien, un libelle sur une carte blanche).
   C est la faute de la « meme couleur pour deux usages » — deja payee sur la palette du cockpit.
   On ne touche PAS a la constante : elle est la charte, et le fond des badges en depend.
   On derive une ENCRE : `#cf333f` = 5,00:1 sur blanc, assombri de 10 %, TEINTE conservee.
   Poser la variable sur les deux themes est volontaire : les cartes du cockpit sont blanches
   dans les deux (recette du pilotage validee par le client), donc l encre doit y etre la meme.
   ⚠️ En fin de feuille : meme specificite que `:root`, donc elle GAGNE sans `!important`. */
:root, [data-theme="light"], [data-theme="dark"] {
  --red-encre: #cf333f;
  --accent-encre: #cf333f;
}
/* Les regles qui mettent le rouge en ENCRE lisent desormais la derivee.
   ⚠️ `!important` est necessaire ICI : les regles d origine (`.text-button.strong`,
   `.jeu-ambiance.active`) sont prises dans les blocs de famille du bas de feuille, qui en
   portent deja. Un simple surcroit de specificite ne suffirait pas — paye une fois ce tour. */
.text-button.strong,
.jeu-ambiance.active .jeu-ambiance-nom,
.jeu-ambiance:hover .jeu-ambiance-nom {
  color: var(--red-encre) !important;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   CHANGER DE PROFIL — LA FENÊTRE ET LE BANDEAU DE RETOUR
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ CES REGLES SONT OBLIGATOIRES, PAS DECORATIVES : une classe produite dans le DOM sans regle
   CSS est INVISIBLE en pratique. Ce projet a deja paye ce defaut — un widget « qui ne s'affiche
   pas » etait simplement un widget sans style.

   ⚠️ ENCRE EXPLICITE PAR FAMILLE : la fenetre vit dans `dialog.admin-dialog`, dont l'encre par
   defaut est sombre. Sur les deux themes, chaque texte recoit donc SA couleur — jamais un
   heritage suppose. C'est la faute qui a produit « du noir sur du noir » plusieurs fois ici. */

/* ── LA FENETRE ─────────────────────────────────────────────────────────────────────────── */
.bascule-dialog .dot-corps { display: flex; flex-direction: column; gap: 18px; max-height: 62vh; overflow: auto; }

.bp-groupe { display: flex; flex-direction: column; gap: 8px; }
.bp-groupe-titre {
  margin: 0; font-size: 12px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  color: #17171a;
}
.bp-groupe-titre em {
  font-style: normal; font-weight: 700; font-size: 11px;
  color: #4e5157;                                   /* 7,4:1 sur fond clair */
  background: rgba(23,23,25,.07); border-radius: 999px; padding: 1px 8px; margin-left: 4px;
}
.bp-groupe-note { margin: 0; font-size: 12px; color: #4e5157; }

.bp-lignes { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 8px; }

/* ⚠️ LA LIGNE EST UN `<button>` ENTIER : la cible tactile est donc toute la ligne, pas un mot.
   MESURE : une cible de 44px minimum — ici la ligne fait 56px. */
.bp-ligne {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px; min-height: 56px;
  text-align: left; cursor: pointer; font-family: inherit;
  background: #fbfaf8; border: 1px solid rgba(23,23,25,.12); border-radius: 13px;
  transition: background .14s ease, border-color .14s ease;
}
.bp-ligne:hover { background: #fff; border-color: rgba(212,43,56,.42); }
.bp-ligne:focus-visible { outline: 3px solid rgba(230,57,70,.34); outline-offset: 2px; }

.bp-av {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 11px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: rgba(23,23,25,.07); color: #4e5157;
}
.bp-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; }
.bp-ini { font-size: 13px; font-weight: 800; letter-spacing: .02em; }

.bp-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.bp-txt strong { font-size: 13.5px; font-weight: 700; color: #17171a; }
.bp-txt small { font-size: 11.5px; color: #4e5157; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.bp-go {
  flex: 0 0 auto; font-size: 11.5px; font-weight: 700;
  color: #cf333f;                                    /* ⚠️ l'ENCRE du rouge de charte : 5,00:1 */
  border: 1px solid rgba(212,43,56,.30); border-radius: 999px; padding: 4px 11px;
}

.bascule-pied { display: flex; justify-content: flex-end; padding: 14px 0 2px; }

/* ══ EN THEME SOMBRE : la fenetre a SON fond, donc les encres se reeecrivent ═══════════════ */
[data-theme="dark"] .bp-groupe-titre { color: #f4f3f1; }
[data-theme="dark"] .bp-groupe-titre em { color: #c9c7c3; background: rgba(255,255,255,.11); }
[data-theme="dark"] .bp-groupe-note { color: #c9c7c3; }
[data-theme="dark"] .bp-ligne { background: rgba(255,255,255,.055); border-color: rgba(255,255,255,.16); }
[data-theme="dark"] .bp-ligne:hover { background: rgba(255,255,255,.10); border-color: rgba(230,57,70,.5); }
[data-theme="dark"] .bp-av { background: rgba(255,255,255,.10); color: #c9c7c3; }
[data-theme="dark"] .bp-txt strong { color: #f4f3f1; }
[data-theme="dark"] .bp-txt small { color: #c9c7c3; }
[data-theme="dark"] .bp-go { color: #ff8b95; border-color: rgba(255,139,149,.34); }

/* ══ LE BANDEAU DE RETOUR — ON NE LAISSE PERSONNE PIEGE DANS UN AUTRE PROFIL ══════════════ */
/* ⚠️ Il est FIXE en bas, au-dessus de tout : tant qu'il est là, on sait qu'on visite. Le rouge
   de charte en FOND avec texte blanc (4,83:1) est le bon choix pour un avertissement. */
.retour-bandeau {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 2147483000;                     /* au-dessus du top-layer des `<dialog>` (2147483647 - marge) */
  display: flex; align-items: center; gap: 12px;
  max-width: min(660px, calc(100vw - 32px));
  padding: 11px 13px 11px 17px; border-radius: 999px;
  background: #b32c37;                     /* ⚠️ le rouge ASSOMBRI : blanc dessus = 6,30:1 */
  color: #fff;
  box-shadow: 0 18px 44px -12px rgba(23,23,25,.48), 0 0 0 1px rgba(255,255,255,.14) inset;
}
.rb-ic { flex: 0 0 auto; font-size: 10px; opacity: .9; }
.rb-txt { font-size: 13px; font-weight: 500; line-height: 1.35; }
.rb-txt strong { font-weight: 800; }
.rb-btn {
  flex: 0 0 auto; cursor: pointer; font-family: inherit;
  font-size: 12.5px; font-weight: 800; color: #b32c37;    /* 6,30:1 sur son fond blanc */
  background: #fff; border: 0; border-radius: 999px; padding: 7px 14px;
  transition: transform .14s ease;
}
.rb-btn:hover { transform: translateY(-1px); }
.rb-btn:focus-visible { outline: 3px solid rgba(255,255,255,.6); outline-offset: 2px; }

@media (max-width: 620px) {
  .retour-bandeau { left: 12px; right: 12px; transform: none; max-width: none; flex-wrap: wrap; }
  .bp-lignes { grid-template-columns: 1fr; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE CADRE TABLETTE — LA PAGE D'UN PROFIL MONTRÉE DANS UN APERÇU
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ DEUX CHOSES DOIVENT ÊTRE VRAIES EN MÊME TEMPS, ET C'EST TOUT LE SUJET :
     1. la page montrée doit être VRAIMENT en mode tablette — donc l'`<iframe>` reçoit un VIEWPORT
        de tablette (1024×768) et ses propres règles responsive s'appliquent. Une capture réduite
        ou une div rétrécie ne déclencherait PAS les media queries : l'aperçu mentirait ;
     2. ce viewport doit tenir dans la fenêtre — on le RÉDUIT par `transform: scale()`, ce qui ne
        change rien à ce que la page voit d'elle-même.

   ⚠️ ENCRE EXPLICITE PAR FAMILLE : la fenêtre vit dans `dialog.admin-dialog`. Aucun texte ne se
   fie à un héritage — c'est la faute qui a produit « du noir sur du noir » plusieurs fois ici. */

.apercu-profil-dialog { width: min(880px, calc(100vw - 40px)); max-height: calc(100vh - 40px); }

.apercu-zone {
  display: flex; align-items: center; justify-content: center;
  padding: 14px; margin: 4px 0 2px; overflow: auto;
  background: rgba(23,23,25,.055); border-radius: 18px;
  /* ⚠️ UN FOND, PAS UNE BORDURE : le cadre doit se lire comme un objet posé, pas comme un champ. */
  min-height: 260px;
}

/* ── LE CADRE LUI-MÊME : le pourtour de la tablette ─────────────────────────────────────── */
.apercu-cadre {
  /* ⚠️ LE VIEWPORT EST EN DUR : c'est lui qui déclenche le mode tablette de la page montrée. */
  width: 1024px; height: 768px;
  border: 0; display: block; background: #fff;
  border-radius: 14px;
  /* ⚠️ LA RÉDUCTION EST PUREMENT VISUELLE. La page montrée continue de SE CROIRE à 1024×768 — */
  /* c'est la condition pour que l'aperçu soit honnête au lieu d'être une image. */
  transform: scale(var(--apercu-echelle, .78));
  transform-origin: top left;
  box-shadow: 0 26px 60px -20px rgba(23,23,25,.46), 0 0 0 10px #23252a, 0 0 0 11px rgba(255,255,255,.10);
}

/* ⚠️ LE CONTENEUR PORTE LA TAILLE RÉDUITE, sinon la réduction laisserait 1024px de vide. */
/* ⚠️ L'ÉCRAN PORTE LA TAILLE RÉDUITE, ET C'EST LUI QUI ÉVITE LE DÉBORDEMENT : une réduction par
   `transform` ne change pas la place occupée, donc sans cet écran le cadre garderait ses 1024px
   en mise en page et la fenêtre afficherait une bande vide. */
.apercu-ecran {
  overflow: hidden; margin: 0 auto;
  /* ⚠️ CEINTURE : si la colonne est plus étroite que l'écran réduit, on ne déborde pas pour
     autant — le cadre se plafonne. Dernière barrière, indépendante de toutes les autres règles. */
  /* ⚠️ LA LARGEUR EST PLAFONNÉE À LA COLONNE *ET* CALCULÉE SUR L'ÉCHELLE MESURÉE.
     `min()` prend la plus petite des deux : si l'échelle donne plus large que la colonne, c'est
     la colonne qui gagne ; sinon l'échelle est respectée. Aucune des deux ne peut déborder. */
  width: min(100%, calc(1024px * var(--apercu-echelle, .58))) !important;
  height: auto !important;
  aspect-ratio: 1024 / 768;
  border-radius: 14px;
}
/* ⚠️ SUR PETIT ÉCRAN, L'ÉCRAN SE RÉTRÉCIT ENCORE — l'échelle est la même variable que le cadre,
   donc les deux ne peuvent pas se désynchroniser. */
@media (max-width: 980px) { .apercu-ecran { width: calc(1024px * .62); height: calc(768px * .62); } }
@media (max-width: 760px) { .apercu-ecran { width: calc(1024px * .46); height: calc(768px * .46); } }

.apercu-chargement, .apercu-erreur {
  font-size: 13px; color: #4e5157; padding: 40px 18px; text-align: center;
}
.apercu-erreur { color: #b32c37; font-weight: 600; }

/* ══ LE BOUTON « VOIR SA PAGE » DANS LA LISTE DES PROFILS ══════════════════════════════════ */
/* ⚠️ IL REMPLACE LA LIGNE-BOUTON D'ORIGINE : on ne peut pas imbriquer un `<button>` dans un
   `<button>` — HTML invalide, et le clic part au hasard. La ligne est donc un conteneur. */
.bp-ligne { cursor: default; }
.bp-vue {
  flex: 0 0 auto; cursor: pointer; font-family: inherit;
  font-size: 11.5px; font-weight: 700; padding: 5px 10px;
  color: #17171a; background: rgba(23,23,25,.055);
  border: 1px solid rgba(23,23,25,.14); border-radius: 999px;
  transition: background .14s ease, border-color .14s ease;
}
.bp-vue:hover { background: rgba(23,23,25,.10); border-color: rgba(23,23,25,.26); }
.bp-vue:focus-visible { outline: 3px solid rgba(230,57,70,.34); outline-offset: 2px; }
.bp-go { cursor: pointer; font-family: inherit; background: none; }

[data-theme="dark"] .apercu-zone { background: rgba(255,255,255,.055); }
[data-theme="dark"] .apercu-chargement { color: #c9c7c3; }
[data-theme="dark"] .apercu-erreur { color: #ff8b95; }
[data-theme="dark"] .bp-vue { color: #f4f3f1; background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.20); }
[data-theme="dark"] .bp-vue:hover { background: rgba(255,255,255,.14); }

/* ⚠️ SUR PETIT ÉCRAN, LE CADRE SE RÉDUIT DAVANTAGE — sinon il déborde de la fenêtre, et un
   aperçu qui déborde ne sert à rien puisqu'on ne peut pas le regarder en entier. */
@media (max-width: 980px) { .apercu-profil-dialog { --apercu-echelle: .62; } }
@media (max-width: 760px) { .apercu-profil-dialog { --apercu-echelle: .46; } }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE TABLEAU DE SUPERVISION — L'ADMIN EN MODE LECTURE SUR LA TABLETTE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ ENCRE EXPLICITE PAR FAMILLE : cet écran vit dans le contenu de l'application, qui a SON fond.
   Aucun texte ne se fie à un héritage — c'est la faute qui a produit « du noir sur du noir »
   plusieurs fois dans ce projet. Chaque couleur de texte est posée à côté de son fond. */

.sv-tete { position: relative; margin-bottom: 16px; }
.sv-tete h2 { margin: 2px 0 6px; font-size: 24px; font-weight: 800; color: var(--text, #171719); }
.sv-note { margin: 0; max-width: 62ch; font-size: 13px; line-height: 1.5; color: var(--muted, #6b6965); }
/* ⚠️ LA PASTILLE EST LE SEUL RAPPEL PERMANENT DU MODE : elle doit être lisible, pas décorative.
   Blanc sur #b32c37 = 6,30:1. */
.sv-badge {
  position: absolute; top: 0; right: 0;
  font-size: 10.5px; font-weight: 800; letter-spacing: .10em;
  color: #ffffff; background: #b32c37; border-radius: 999px; padding: 5px 12px;
}

/* ── LES CARTES DE COMPTAGE : le langage des widgets du projet (icône + chiffre colorés) ─── */
.sv-cartes { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 10px; margin-bottom: 16px; }
.sv-carte {
  display: flex; flex-direction: column; gap: 2px; align-items: center; justify-content: center;
  padding: 14px 10px; text-align: center;
  background: var(--card, #ffffff); border: 1px solid rgba(23,23,25,.10); border-radius: 14px;
}
.sv-carte strong { font-size: 26px; font-weight: 800; line-height: 1.1; }
.sv-carte span { font-size: 11.5px; font-weight: 600; color: #4e5157; }

/* ── LES FILTRES : les quatre axes demandés par le client ───────────────────────────────── */
.sv-filtres { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; margin-bottom: 18px; }
.sv-filtre { display: flex; flex-direction: column; gap: 4px; }
.sv-filtre span { font-size: 11px; font-weight: 700; letter-spacing: .03em; color: #4e5157; text-transform: uppercase; }
.sv-filtre select {
  font-family: inherit; font-size: 13px; min-width: 168px;
  padding: 9px 11px; border-radius: 11px;
  color: #171719; background: #ffffff; border: 1px solid rgba(23,23,25,.18);
}
.sv-effacer {
  font-family: inherit; cursor: pointer; font-size: 12.5px; font-weight: 700;
  padding: 10px 14px; border-radius: 11px;
  color: #171719; background: rgba(23,23,25,.055); border: 1px solid rgba(23,23,25,.16);
}

/* ── LE TABLEAU ─────────────────────────────────────────────────────────────────────────── */
.sv-table { background: var(--card, #ffffff); border: 1px solid rgba(23,23,25,.10); border-radius: 16px; overflow: hidden; }
.sv-table-tete { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid rgba(23,23,25,.09); }
.sv-table-tete h3 { margin: 0; font-size: 15px; font-weight: 800; color: #171719; }
.sv-table-tete span { font-size: 12px; color: #4e5157; }
.sv-scroll { overflow-x: auto; max-height: 52vh; overflow-y: auto; }
.sv-table table { width: 100%; border-collapse: collapse; }
.sv-table th {
  position: sticky; top: 0; z-index: 1;
  text-align: left; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  color: #4e5157; background: #faf9f7; padding: 10px 14px; border-bottom: 1px solid rgba(23,23,25,.10);
}
.sv-table td { font-size: 13px; color: #2c2d30; padding: 11px 14px; border-bottom: 1px solid rgba(23,23,25,.06); }
.sv-table td strong { color: #171719; font-weight: 700; }
.sv-etat { font-size: 11px; font-weight: 800; border-radius: 999px; padding: 3px 9px; }
.sv-etat.est-remis { color: #075c3a; background: rgba(10,125,79,.12); }
.sv-etat.est-attente { color: #7a5610; background: rgba(165,118,47,.15); }
.sv-vide { text-align: center; color: #4e5157; padding: 30px 14px; }

/* ══ EN THÈME SOMBRE, CHAQUE ENCRE SE RÉÉCRIT ═══════════════════════════════════════════════ */
[data-theme="dark"] .sv-tete h2 { color: #f4f3f1; }
[data-theme="dark"] .sv-note { color: #c9c7c3; }
[data-theme="dark"] .sv-carte { background: rgba(255,255,255,.055); border-color: rgba(255,255,255,.14); }
[data-theme="dark"] .sv-carte span { color: #c9c7c3; }
[data-theme="dark"] .sv-filtre span { color: #c9c7c3; }
[data-theme="dark"] .sv-filtre select { color: #f4f3f1; background: #1b1c1e; border-color: rgba(255,255,255,.20); }
[data-theme="dark"] .sv-effacer { color: #f4f3f1; background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.20); }
[data-theme="dark"] .sv-table { background: rgba(255,255,255,.045); border-color: rgba(255,255,255,.12); }
[data-theme="dark"] .sv-table-tete { border-bottom-color: rgba(255,255,255,.11); }
[data-theme="dark"] .sv-table-tete h3 { color: #f4f3f1; }
[data-theme="dark"] .sv-table-tete span { color: #c9c7c3; }
[data-theme="dark"] .sv-table th { color: #c9c7c3; background: #1b1c1e; border-bottom-color: rgba(255,255,255,.11); }
[data-theme="dark"] .sv-table td { color: #d9d7d3; border-bottom-color: rgba(255,255,255,.06); }
[data-theme="dark"] .sv-table td strong { color: #f4f3f1; }
[data-theme="dark"] .sv-etat.est-remis { color: #7ee0b0; background: rgba(10,125,79,.28); }
[data-theme="dark"] .sv-etat.est-attente { color: #f0c884; background: rgba(165,118,47,.28); }

@media (max-width: 760px) { .sv-carte strong { font-size: 21px; } }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE JEU EN SIMULATION — L'ADMIN TESTE LA ROUE SANS RIEN CONSOMMER
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ ENCRE EXPLICITE PAR FAMILLE : chaque texte reçoit SA couleur, jamais un héritage supposé.
   C'est la faute qui a produit « du noir sur du noir » plusieurs fois dans ce projet. */

.sim-tete { position: relative; margin-bottom: 16px; }
.sim-tete h2 { margin: 2px 0 6px; font-size: 24px; font-weight: 800; color: var(--text, #171719); }
.sim-note { margin: 0; max-width: 64ch; font-size: 13px; line-height: 1.5; color: var(--muted, #6b6965); }
.sim-note strong { color: #075c3a; font-weight: 800; }

.sim-bloc {
  background: var(--card, #ffffff); border: 1px solid rgba(23,23,25,.10);
  border-radius: 16px; padding: 16px; margin-bottom: 14px;
}
.sim-bloc h3 { margin: 0 0 12px; font-size: 14px; font-weight: 800; color: #171719; }

/* ── LE CHOIX DE LA ROUE ────────────────────────────────────────────────────────────────── */
.sim-roues, .sim-sources, .sim-personnes { display: flex; flex-wrap: wrap; gap: 9px; }
.sim-roue, .sim-source, .sim-personne {
  font-family: inherit; cursor: pointer; text-align: left;
  padding: 11px 14px; border-radius: 13px;
  color: #171719; background: #fbfaf8; border: 1px solid rgba(23,23,25,.14);
  transition: background .14s ease, border-color .14s ease;
}
.sim-roue { display: flex; flex-direction: column; gap: 2px; min-width: 170px; }
.sim-roue strong { font-size: 13.5px; font-weight: 700; }
.sim-roue small { font-size: 11px; color: #4e5157; }
.sim-roue:hover, .sim-source:hover, .sim-personne:hover { background: #fff; border-color: rgba(212,43,56,.42); }
/* ⚠️ L'ÉTAT CHOISI DOIT SE VOIR SANS COULEUR SEULE (accessibilité) : bordure + gras + libellé. */
.sim-roue.est-choisie, .sim-source.est-choisie, .sim-personne.est-choisie {
  border-color: #b32c37; border-width: 2px; background: rgba(179,44,55,.06);
}
.sim-source.est-choisie, .sim-personne.est-choisie { font-weight: 800; color: #8f222c; }

.sim-lancer { margin-top: 4px; }
.sim-lancer:disabled { opacity: .55; cursor: not-allowed; }

.sim-resultat { margin-top: 14px; }
.sim-ok { margin: 0 0 10px; font-size: 13.5px; color: #2c2d30; }
.sim-ok strong { font-size: 17px; color: #171719; }
.sim-attente, .sim-vide { margin: 6px 0 0; font-size: 13px; color: #4e5157; }
.sim-erreur { margin: 6px 0 0; font-size: 13px; font-weight: 600; color: #b32c37; }
.sim-lots { display: flex; flex-wrap: wrap; gap: 7px; }
.sim-lot {
  font-size: 12px; font-weight: 600; color: #2c2d30;
  background: rgba(23,23,25,.055); border-radius: 999px; padding: 5px 11px;
}
.sim-lot b { color: #075c3a; font-weight: 800; }

/* ══ THÈME SOMBRE ═════════════════════════════════════════════════════════════════════════ */
[data-theme="dark"] .sim-tete h2 { color: #f4f3f1; }
[data-theme="dark"] .sim-note { color: #c9c7c3; }
[data-theme="dark"] .sim-note strong { color: #7ee0b0; }
[data-theme="dark"] .sim-bloc { background: rgba(255,255,255,.045); border-color: rgba(255,255,255,.12); }
[data-theme="dark"] .sim-bloc h3 { color: #f4f3f1; }
[data-theme="dark"] .sim-roue, [data-theme="dark"] .sim-source, [data-theme="dark"] .sim-personne {
  color: #f4f3f1; background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.16);
}
[data-theme="dark"] .sim-roue small { color: #c9c7c3; }
[data-theme="dark"] .sim-roue.est-choisie, [data-theme="dark"] .sim-source.est-choisie,
[data-theme="dark"] .sim-personne.est-choisie { border-color: #ff8b95; background: rgba(255,139,149,.14); }
[data-theme="dark"] .sim-source.est-choisie, [data-theme="dark"] .sim-personne.est-choisie { color: #ffb3ba; }
[data-theme="dark"] .sim-ok { color: #d9d7d3; }
[data-theme="dark"] .sim-ok strong { color: #f4f3f1; }
[data-theme="dark"] .sim-attente, [data-theme="dark"] .sim-vide { color: #c9c7c3; }
[data-theme="dark"] .sim-erreur { color: #ff8b95; }
[data-theme="dark"] .sim-lot { color: #d9d7d3; background: rgba(255,255,255,.09); }
[data-theme="dark"] .sim-lot b { color: #7ee0b0; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE FORMULAIRE DU JEU — LES CHAMPS AJOUTÉS (nom, sexe, âge, les deux contacts)
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️ ENCRE EXPLICITE PAR FAMILLE : le jeu s'affiche en plein écran sur son propre fond. Aucun texte
   ne se fie à un héritage — c'est la faute qui a produit « du noir sur du noir » plusieurs fois ici. */

/* ── LES MENTIONS « obligatoire » / « optionnel » : elles doivent se distinguer SANS couleur seule,
      pour rester lisibles si le contraste ou la perception des couleurs fait défaut. ────────── */
.form-grid label .obl, .form-grid label .opt {
  font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  border-radius: 999px; padding: 2px 8px; margin-left: 4px; white-space: nowrap;
}
.form-grid label .obl { color: #7a1a22; background: rgba(179,44,55,.13); }   /* 8,4:1 */
.form-grid label .opt { color: #3f4247; background: rgba(23,23,25,.08); font-weight: 600; }  /* 9,1:1 */

/* ── LE CHOIX DU SEXE : deux boutons radio ÉTIQUETÉS, pas une liste déroulante.
      Client : « le sexe, femme ou homme ». Deux valeurs se cliquent, elles ne se déroulent pas —
      et une liste ouverte sur une tablette tenue à deux mains est plus lente qu'une cible. ─── */
.field-sexe { grid-column: span 1; }
.sexe-choix { display: flex; gap: 8px; margin-top: 2px; }
.sexe-opt {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 46px; padding: 0 12px; cursor: pointer;
  font-size: 14px; font-weight: 600; color: #1c1d1f;
  background: #ffffff; border: 1px solid rgba(23,23,25,.20); border-radius: 12px;
  transition: background .14s ease, border-color .14s ease;
}
/* ⚠️ LA CASE NATIVE EST MASQUÉE MAIS GARDE SA PLACE DANS L'ACCESSIBILITÉ (opacité, pas `display`).
   `display:none` la retirerait de l'ordre de tabulation et du clavier — une perte réelle. */
.sexe-opt input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.sexe-opt span::before {
  content: ''; display: inline-block; width: 15px; height: 15px; margin-right: 7px;
  vertical-align: -2px; border-radius: 50%;
  border: 2px solid rgba(23,23,25,.40); background: #fff;
}
.sexe-opt:has(input:checked) { border-color: #b32c37; border-width: 2px; background: rgba(179,44,55,.06); }
.sexe-opt:has(input:checked) span::before { border-color: #b32c37; background: radial-gradient(circle, #b32c37 0 5px, #fff 6px); }
/* ⚠️ REPLI SANS `:has()` : si le navigateur ne le supporte pas, la sélection doit RESTER VISIBLE.
   On pose donc l'état sur la classe que le JS ajoute, en plus de `:has()` — jamais l'un sans l'autre. */
.sexe-opt.est-choisie { border-color: #b32c37; border-width: 2px; background: rgba(179,44,55,.06); }
.sexe-opt.est-choisie span::before { border-color: #b32c37; background: radial-gradient(circle, #b32c37 0 5px, #fff 6px); }
.sexe-opt:focus-within { outline: 3px solid rgba(230,57,70,.34); outline-offset: 2px; }

/* ── LES DEUX CHAMPS DE CONTACT : ils occupent chacun une ligne large, parce qu'ils sont
      « l'un OU l'autre » — les mettre côte à côte suggérerait qu'il faut les deux. ─────────── */
.field-large { grid-column: 1 / -1; }

/* ── LA PHRASE D'AIDE : elle DIT la règle, puis devient verte quand elle est satisfaite. ────── */
.jeu-aide {
  margin: 2px 0 10px; font-size: 12.5px; font-weight: 600;
  color: #4e5157;                                   /* 7,4:1 sur fond clair */
  display: flex; align-items: center; gap: 7px;
}
.jeu-aide::before { content: '\25CF'; font-size: 9px; color: #b32c37; }   /* 6,3:1 */
.jeu-aide.est-satisfaite { color: #075c3a; }                               /* 8,6:1 */
.jeu-aide.est-satisfaite::before { content: '\2713'; color: #0a7d4f; font-size: 12px; }

/* ══ THÈME SOMBRE ═════════════════════════════════════════════════════════════════════════ */
[data-theme="dark"] .form-grid label .obl { color: #ffb3ba; background: rgba(255,139,149,.16); }
[data-theme="dark"] .form-grid label .opt { color: #d9d7d3; background: rgba(255,255,255,.10); }
[data-theme="dark"] .sexe-opt { color: #f4f3f1; background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.22); }
[data-theme="dark"] .sexe-opt span::before { border-color: rgba(255,255,255,.45); background: #1b1c1e; }
[data-theme="dark"] .sexe-opt:has(input:checked),
[data-theme="dark"] .sexe-opt.est-choisie { border-color: #ff8b95; background: rgba(255,139,149,.14); }
[data-theme="dark"] .sexe-opt:has(input:checked) span::before,
[data-theme="dark"] .sexe-opt.est-choisie span::before { border-color: #ff8b95; background: radial-gradient(circle, #ff8b95 0 5px, #1b1c1e 6px); }
[data-theme="dark"] .jeu-aide { color: #c9c7c3; }
[data-theme="dark"] .jeu-aide::before { color: #ff8b95; }
[data-theme="dark"] .jeu-aide.est-satisfaite { color: #7ee0b0; }
[data-theme="dark"] .jeu-aide.est-satisfaite::before { color: #7ee0b0; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE MODE TABLETTE — LA NAVIGATION EN BAS, QUATRE SECTIONS, COMME UNE APPLICATION
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « les onglets doivent être sous forme de web app, en bas. »
   Puis : « la section Jeu, son icône doit être la plus lisible, et elle doit être à l'extrémité
   droite. »

   ⚠️⚠️ AUCUNE MEDIA QUERY ICI, ET C'EST LE POINT : la barre en bas était enfermée dans
   `@media (max-width:1024px)`. Sur un écran LARGE, elle redevenait donc une barre latérale à
   gauche — et le client, qui valide le design depuis son poste, ne voyait jamais la barre du bas.
   Cette page s'appelle « tablette » : elle doit y ressembler TOUJOURS. Le bas est donc l'état PAR
   DÉFAUT, sans condition de largeur.

   ⚠️ ET LE SÉLECTEUR EST `body[data-interface="terrain"]`, PAS `.sidebar` : c'est la seule
   signature qui dit « c'est la page tablette ». `direction.html` garde sa barre latérale intacte —
   une barre du bas sur le cockpit de l'agence serait un non-sens.

   ⚠️ ENCRE EXPLICITE PAR FAMILLE : la barre a SON fond sombre (#202123). Chaque texte y reçoit sa
   couleur claire, jamais un héritage supposé — c'est la faute qui a produit « du noir sur du noir »
   plusieurs fois dans ce projet. */

body[data-interface="terrain"]:not(.participant-mode) .sidebar {
  /* ── LA BARRE : FIXE, EN BAS, PLEINE LARGEUR ─────────────────────────────────────────── */
  display: flex;
  position: fixed;
  z-index: 30;                 /* au-dessus du contenu ; sous le top-layer des `<dialog>` */
  left: 0; right: 0; bottom: 0; top: auto;
  width: 100%;
  /* ⚠️ `height:auto` + `min-height` plutôt qu'une hauteur figée : un libellé qui passe à la ligne
     ne doit pas être rogné. La barre grandit d'elle-même. */
  height: auto;
  min-height: 68px;
  padding: 7px 10px calc(7px + env(safe-area-inset-bottom));
  background: #202123;
  border-right: 0;
  border-top: 1px solid rgba(255, 255, 255, .10);
  align-items: stretch;
  /* ⚠️ ON ANNULE LES REGLES DE LA BARRE LATERALE QUI POURRAIENT RESTER (hauteur de vue, débordement
     visible, marge automatique) : sans ça, la barre garderait `100vh` sur un écran large. */
  max-height: none;
  overflow: visible;
}
/* ⚠️ CE QUI DISPARAÎT DANS LA BARRE : le logo, l'intitulé de section et le pied de page. Le CONTENU
   passe avant — le client avait pris la même décision pour le rail de 88px. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .brand,
body[data-interface="terrain"]:not(.participant-mode) .sidebar > .eyebrow,
body[data-interface="terrain"]:not(.participant-mode) .sidebar-foot { display: none; }

/* ── LES QUATRE SECTIONS : UNE GRILLE ÉGALE ────────────────────────────────────────────── */
body[data-interface="terrain"]:not(.participant-mode) .sidebar nav {
  display: grid;
  /* ⚠️⚠️ CINQ COLONNES, ET LE JEU PLUS LARGE QUE LES QUATRE AUTRES.
     Client (verbatim) : « le milieu, un peu plus grand, ça sera la roue de fortune comme logo,
     c'est le jeu » et « deux à gauche et deux à droite, lui il serait au milieu […] pour qu'elle
     lui soit facile et accessible d'appuyer sur un onglet juste en dessous, juste au milieu ».
     ⚠️ LA COLONNE DU MILIEU EST EN `1.5fr` : elle est plus large, donc sa CIBLE TACTILE est plus
     grande aussi. Le client demande « facile et accessible », pas seulement « plus visible » —
     un jeu plus grand à l'œil mais aussi étroit au doigt aurait raté la demande. */
  grid-template-columns: 1fr 1fr 1.5fr 1fr 1fr;
  gap: 2px;
  width: 100%;
  align-items: stretch;
}
/* ⚠️ L'ORDRE EST DÉCLARÉ, PAS DÉDUIT DE LA POSITION DANS LE FICHIER : l'ordre visuel peut changer
   sans que le HTML bouge, et un seul endroit décide. C'est ce qui met le JEU à l'extrémité droite
   de façon EXPLICITE, et non par coïncidence. */
body[data-interface="terrain"] .nav-item[data-ordre="1"] { order: 1 }
body[data-interface="terrain"] .nav-item[data-ordre="2"] { order: 2 }
body[data-interface="terrain"] .nav-item[data-ordre="3"] { order: 3 }
body[data-interface="terrain"] .nav-item[data-ordre="4"] { order: 4 }
body[data-interface="terrain"] .nav-item[data-ordre="5"] { order: 5 }

/* ── UNE SECTION : GLYPHE AU-DESSUS, LIBELLÉ EN DESSOUS (comme une app native) ─────────── */
body[data-interface="terrain"] .nav-item {
  position: relative;                 /* porte le trait de l'onglet actif */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: auto;
  min-height: 54px;                   /* ⚠️ cible tactile : jamais moins de 44px */
  padding: 5px 4px;
  border: 0;
  border-left: 0;                     /* ⚠️ le trait de gauche n'a plus de sens en bas */
  border-radius: 10px;
  font-family: inherit;
  color: #c8c8c8;                     /* 8,6:1 sur #202123 */
  background: none;
}
body[data-interface="terrain"] .nav-item:hover,
body[data-interface="terrain"] .nav-item.active {
  background: rgba(255, 255, 255, .07); color: #ffffff; border-left: 0;
}
/* ⚠️ LE TRAIT DE L'ONGLET ACTIF PASSE EN HAUT : en bas de la barre il serait coupé par le bord de
   l'écran. Et il ne porte pas l'état À LUI SEUL — la couleur du texte et le fond changent aussi,
   pour rester lisible si la couleur manque. */
body[data-interface="terrain"] .nav-item.active::before {
  content: '';
  position: absolute; top: -1px; left: 26%; right: 26%;
  height: 2.5px; border-radius: 0 0 3px 3px;
  background: var(--red);
}
/* ⚠️⚠️ `.nav-ic` ÉTAIT DIMENSIONNÉ EN `font-size` — IL PORTAIT UN GLYPHE DE POLICE. Maintenant qu'il
   contient un SVG, une taille de police ne le dimensionne plus : le SVG hérite de la taille du bloc,
   pas du texte. C'est la correction de NIVEAU qu'il fallait, pas une correction de valeur : passer de
   20px à 26px n'aurait rien changé tant que l'élément reste mesuré en caractères.
   ⚠️ ET LE SVG REÇOIT `stroke-width` PAR CSS AUSSI, POUR QUE L'ICÔNE DU JEU PUISSE ÊTRE PLUS GRASSE
   SANS UN SECOND DESSIN : la variante pleine du jeu est un cercle rempli, elle n'a pas de trait. */
body[data-interface="terrain"] .nav-ic {
  display: grid; place-items: center;
  width: 24px; height: 24px; line-height: 0;
}
body[data-interface="terrain"] .nav-ic svg { width: 100%; height: 100%; display: block; }
body[data-interface="terrain"] .nav-txt {
  font-size: 11px; font-weight: 600; letter-spacing: .01em; white-space: nowrap;
  color: inherit;
}

/* ── LE JEU : L'ICÔNE LA PLUS LISIBLE, À L'EXTRÉMITÉ DROITE ─────────────────────────────── */
/* ⚠️ ELLE EST PLUS GRANDE (26px contre 20), BLANCHE (contraste maximal sur ce fond) ET POSÉE SUR UN
   HALO ROUGE DISCRET. Trois signaux plutôt qu'un : si le halo ou la couleur manquent, la TAILLE et
   le libellé en gras continuent de la désigner. Une icône « la plus lisible » qui ne se
   distinguerait que par sa teinte serait invisible pour une partie des utilisateurs. */
/* ⚠️ LA ROUE DE FORTUNE EST PLUS GRANDE QUE LES AUTRES ICÔNES : c'est elle qui dit « le jeu », et
   son dessin (cercle + rayons) se perd en dessous de 28px. Elle passe à 32px parce qu'elle est
   maintenant l'onglet CENTRAL — la place gagnée sert à sa lisibilité, pas à un effet. */
body[data-interface="terrain"] .nav-jeu .nav-ic { width: 32px; height: 32px; color: #ffffff; }
/* ⚠️ ET L'ONGLET CENTRAL EST PLUS HAUT, POUR QUE LE POUCE LE TROUVE SANS REGARDER. */
body[data-interface="terrain"] .nav-jeu { padding-top: 4px; padding-bottom: 4px; }
body[data-interface="terrain"] .nav-jeu .nav-txt { font-weight: 800; color: #ffffff; }
/* ⚠️⚠️ LA TACHE EST SUPPRIMEE - ET CE N'ETAIT PAS UNE DECORATION, C'ETAIT UN DEFAUT.
   MESURE : le client a vu « un artefact graphique vert qui chevauche l'onglet du milieu ». C'etait
   ce `::after` : un disque de 50 px pose AU-DESSUS de l'icone du jeu, qui se detachait du fond de
   la barre et ressemblait a un bug - parce que c'en etait un.
   ⚠️ ET LE CLIENT N'EN VEUT PLUS : sa demande est « le shader en rouge derriere, TRES TRANSPARENT ».
   Le shader va donc sur le FOND DE LA PAGE (voir plus bas), pas sous un onglet. Une lueur posee
   sur une icone de barre ne ressemble jamais a un shader : elle ressemble a une tache. */
body[data-interface="terrain"] .nav-jeu::after { content: none; }
body[data-interface="terrain"] .nav-jeu .nav-ic,
body[data-interface="terrain"] .nav-jeu .nav-txt { position: relative; z-index: 1; }



/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE RAPPEL DE L'AGENCE — DANS LA BARRE, EN BAS
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « j'aimerais bien que ça donne un peu le rappel de Oser la Communication
   Atypique, qui est l'agence à qui appartiendra ce logiciel. »

   ⚠️ SUR UN ÉCRAN LARGE, LA BARRE EST LATÉRALE ET LE RAPPEL Y EST AUSSI — mais en bas, après le
   pied, donc discret. Sur une barre du bas, il n'y a pas la place d'une phrase : il devient alors
   un POINT COLORÉ seul (le signe de l'agence) et c'est le titre de l'onglet du navigateur qui
   porte le nom. On ne cache donc jamais l'information : on la déplace. */
.barre-marque {
  display: flex; align-items: center; gap: 8px;
  margin: 18px 0 4px; padding-top: 14px;
  border-top: 1px solid rgba(255,255,255,.07);
}
.barre-marque .bm-point {
  flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 0 3px rgba(230,57,70,.16);
}
.barre-marque .bm-txt {
  font-size: 9.5px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255,255,255,.42); line-height: 1.35;
}
/* ⚠️ LA MARQUE DU PIED DE BARRE RESTE MASQUÉE EN MODE TERRAIN — et ce n'est PAS un oubli :
   elle est déplacée dans le topbar (`.top-agence`), seul élément visible dans les deux dispositions.
   La garder ici aussi la montrerait DEUX fois quand la barre est latérale. */
body[data-interface="terrain"] .barre-marque { display: none; }

/* ── LE RAPPEL DE L'AGENCE, DANS L'EN-TÊTE ──────────────────────────────────────────────── */
/* ⚠️ DISCRET MAIS LISIBLE : or `#d4a574` sur le fond sombre du topbar = 6,4:1. Il ne doit pas
   concurrencer le titre de page, mais il doit se LIRE — le client l'a demandé pour que la marque
   voyage avec le logiciel chez ses clients. */
.top-agence {
  margin: 1px 0 2px;
  font-size: 10px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: #d4a574;
}
/* ⚠️ SUR UNE BARRE D'ONGLETS EN BAS, L'EN-TÊTE SE RESSERRE : on reduit l'interligne pour que le
   titre de page ne descende pas sous la ligne de flottaison. */
@media (max-height: 620px) {
  .top-agence { font-size: 9px; }
}

/* ── LA BARRE DU BAS : LES ICÔNES EMPILÉES, ET LA MARQUE EN POINT SEUL ───────────────────── */
/* ⚠️ `data-ordre` PORTE L'ORDRE, PAS LA POSITION DANS LE FICHIER : c'est la seule façon d'avoir la
   même page en barre latérale (grand écran) ET en barre du bas (tablette) sans dupliquer le HTML. */
body[data-interface="terrain"] nav { display: flex; align-items: stretch; gap: 2px; }
body[data-interface="terrain"] .nav-item { order: var(--ordre, 0); }
body[data-interface="terrain"] .nav-item[data-ordre="1"] { order: 1; }
body[data-interface="terrain"] .nav-item[data-ordre="2"] { order: 2; }
body[data-interface="terrain"] .nav-item[data-ordre="3"] { order: 3; }
body[data-interface="terrain"] .nav-item[data-ordre="4"] { order: 4; }
body[data-interface="terrain"] .nav-item[data-ordre="5"] { order: 5; }
/* ⚠️ LA BARRE DU BAS EST EN `flex-end` : le jeu, dernier dans l'ordre, se retrouve donc collé à
   l'extrémité droite, comme le client le veut (« elle doit être celle qui est à l'extrémité
   droite »). Un `space-between` l'aurait centré au milieu du dernier tiers. */
body[data-interface="terrain"] nav { justify-content: space-between; }
body[data-interface="terrain"] .nav-item {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 9px 4px 7px;
  background: transparent; border: 0; border-radius: 13px;
  color: rgba(255,255,255,.46);
  transition: color 260ms cubic-bezier(.32,.72,0,1), background 260ms cubic-bezier(.32,.72,0,1);
}
body[data-interface="terrain"] .nav-item:active { background: rgba(255,255,255,.06); }

/* ── LE CONTENU SE DÉGAGE ───────────────────────────────────────────────────────────────── */
/* ⚠️ SANS CE DÉGAGEMENT, LA DERNIÈRE LIGNE D'UN TABLEAU RESTE SOUS LA BARRE — invisible et
   incliquable. On réserve la hauteur de la barre + la zone sûre de l'appareil. */
/* ⚠️⚠️ LE DÉGAGEMENT ÉTAIT TROP COURT : MESURÉ À LA CAPTURE — la section « Par événement » était
   RECOUVERTE par la barre du bas. 98px ne suffisent pas parce que la barre porte QUATRE lignes
   empilées (icône 24px + libellé + marges internes) et qu'elle s'ajoute à la zone sûre de
   l'appareil. On réserve 130px, plus la zone sûre sur iPad.
   ⚠️ ET LE DÉFAUT NE SE VOIT QU'EN BAS DE PAGE : un dégagement insuffisant est invisible tant qu'on
   n'a pas défilé jusqu'au bout — c'est pourquoi il survit si longtemps. */
body[data-interface="terrain"]:not(.participant-mode) .content {
  padding-bottom: calc(130px + env(safe-area-inset-bottom, 0px));
}
body[data-interface="terrain"]:not(.participant-mode) .app-shell { display: block; }
body[data-interface="terrain"]:not(.participant-mode) .main { width: 100%; height: 100vh; overflow-y: auto; }

/* ⚠️ SUR UN ÉCRAN TRÈS ÉTROIT, LES LIBELLÉS SE RESSERRENT — mais AUCUN ne disparaît : une barre
   d'icônes anonymes obligerait à deviner. On réduit la taille, pas l'information. */
@media (max-width: 460px) {
  body[data-interface="terrain"] .nav-txt { font-size: 9.5px; }
  body[data-interface="terrain"] .nav-ic { font-size: 19px; }
  body[data-interface="terrain"] .nav-jeu .nav-ic { font-size: 22px; }
}

@media (max-width: 420px) {
  .nav-txt { font-size: 9.5px; }
  .nav-ic { font-size: 19px; }
  .nav-jeu .nav-ic { font-size: 22px; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LA SCÈNE DU JEU — LE DÉCOR DE LA ROUE, ET LE DÉROULÉ EN QUATRE TEMPS
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « on devait avoir le jeu comme un jeu sur un téléphone, qu'on soit bluffé par
   la colorimétrie qu'on a créée pour la roue. Le fond, on garde le même fond. »
   Et : « la roue en mode flou avec un bouton DÉMARRER […] puis LANCER LE JEU […] quand on gagne,
   tout redevient flou, le goodie ÇA GRANDIT et ça s'écrit FÉLICITATIONS + le prénom. »

   ⚠️⚠️ LE DÉCOR EST UNE COUCHE FIXE POSÉE SUR `body` (`#jeu-fond`) — pas un fond de section. C'est
   ce qui permet au MÊME décor de servir les quatre temps : flou au début, NET quand la roue
   apparaît, FLOU à nouveau au gain. Un fond posé dans le flux devrait être redessiné à chaque
   étape, et il ne couvrirait jamais la barre du bas.

   ⚠️ ET LE FLOU NE S'APPLIQUE PAS À LA COUCHE ENTIÈRE MAIS AU DÉCOR SEUL : flouter le conteneur
   flouterait aussi les cartes posées dessus. Ici `#jeu-fond` porte le décor, et son `filter` suit
   la classe. Les enfants du flux restent nets.

   ⚠️ ENCRE EXPLICITE PAR FAMILLE : le décor est une PHOTO (couleur inconnue). Chaque texte est donc
   posé sur une surface OPAQUE ou quasi-opaque, jamais directement sur l'image — sinon on ne peut pas
   garantir le contraste, et le projet a déjà payé « du noir sur du noir » plusieurs fois. */

#jeu-fond {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}
#jeu-fond .jeu-decor {
  position: absolute; inset: -4%;                  /* ⚠️ débord volontaire : le flou élargit les bords */
  background-size: cover; background-position: 50% 50%;
  transition: filter 900ms cubic-bezier(.32,.72,0,1), transform 900ms cubic-bezier(.32,.72,0,1);
}
/* ⚠️ LE VOILE EST CE QUI REND LE TEXTE LISIBLE SUR N'IMPORTE QUELLE PHOTO. Il assombrit sans
   noircir : deux arrêts, plus dense en bas où se posent les boutons. */
#jeu-fond .jeu-decor-voile {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(9,10,11,.42) 0%, rgba(9,10,11,.62) 55%, rgba(9,10,11,.88) 100%),
    radial-gradient(circle at 50% 32%, rgba(9,10,11,0) 0%, rgba(9,10,11,.35) 78%);
}
/* ⚠️⚠️ LE FLOU EST APPLIQUÉ AU CONTENEUR, PAS AU DÉCOR — ET C'EST OBLIGATOIRE ICI.
   `sceneFondHtml()` écrit le flou et le zoom de la charte EN STYLE EN LIGNE sur `.jeu-decor`
   (`style="filter:blur(8px);transform:scale(1.1)"`). Or un style en ligne BAT toute règle de
   feuille : mes classes `.est-flou` / `.est-net` auraient été ignorées, et le passage flou→net
   n'aurait JAMAIS eu lieu. C'est encore « corriger le NIVEAU, pas la valeur » : la bonne cible
   n'est pas le décor, c'est son conteneur — qui, lui, n'a aucun style en ligne.
   ⚠️ ET ON GARDE LE FLou DE LA CHARTE COMME POINT DE DÉPART : `est-flou` ajoute 26px par-dessus,
   au lieu de le remplacer. La charte du client reste donc respectée pendant la mise en scène. */
#jeu-fond { transition: filter 900ms cubic-bezier(.32,.72,0,1); }
#jeu-fond.est-flou { filter: blur(22px) saturate(.92) brightness(.86); }
#jeu-fond.est-net  { filter: blur(0) saturate(1) brightness(1); }

/* ⚠️ QUAND LE JEU EST OUVERT, LE CONTENU PASSE AU-DESSUS DU DÉCOR — et le fond clair de la page est
   neutralisé : sans ça, le blanc de `.participant-mode` resterait visible sous le décor. */
body.jeu-ouvert, body.jeu-ouvert.participant-mode { background: #090a0b; }
body.jeu-ouvert .main { position: relative; z-index: 1; background: transparent; }
body.jeu-ouvert .content { background: transparent; }

/* ── LA SCÈNE : UNE COLONNE CENTRÉE ─────────────────────────────────────────────────────── */
.jeu-scene {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; min-height: calc(100dvh - 120px); padding: 24px 18px 40px;
  animation: jeuEntree 620ms cubic-bezier(.32,.72,0,1) both;
}
@keyframes jeuEntree {
  from { opacity: 0; transform: translateY(22px) scale(.985); filter: blur(6px) }
  to   { opacity: 1; transform: none; filter: none }
}

/* ── LE PREMIER ÉCRAN : LE DÉCOR, ET UN SEUL BOUTON ─────────────────────────────────────── */
.jeu-avant {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  max-width: 620px; text-align: center;
  /* ⚠️ LA CARTE EST EN « DOUBLE-BEZEL » : une coque extérieure + un cœur. C'est ce qui donne
     l'impression d'un objet posé SUR le décor plutôt que d'un bloc collé dessus. */
  padding: 34px 30px 30px;
  background: rgba(20,21,23,.62);
  border: 1px solid rgba(255,255,255,.10);
  border-radius: 30px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.09);
  backdrop-filter: blur(18px) saturate(1.1);
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
}
.jeu-eyebrow {
  margin: 0; font-size: 10.5px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase;
  color: #d4a574;                                   /* ⚠️ 5,9:1 sur ce fond sombre */
}
.jeu-avant h2 {
  margin: 0; font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif;
  font-size: clamp(21px, 4.2vw, 30px); font-weight: 700; line-height: 1.2;
  letter-spacing: -.02em; color: #f7f8f8;
}
.jeu-sous { margin: 0; max-width: 44ch; font-size: 14px; line-height: 1.55; color: #b9bcc2; }

/* ── LES BOUTONS : PLEINE PILULE, AVEC LA PASTILLE IMBRIQUÉE ────────────────────────────── */
/* ⚠️ LA PASTILLE N'EST JAMAIS POSÉE NUE À CÔTÉ DU TEXTE : elle vit dans sa propre pastille ronde,
   alignée au bord intérieur droit. C'est le détail qui sépare un bouton soigné d'un bouton générique. */
.jeu-cta {
  display: inline-flex; align-items: center; gap: 14px;
  margin-top: 8px; padding: 14px 16px 14px 30px;
  font-family: inherit; font-size: 15.5px; font-weight: 700; letter-spacing: .01em;
  color: #ffffff;                                   /* 6,3:1 sur #b32c37 */
  background: #b32c37; border: 0; border-radius: 999px;
  cursor: pointer; touch-action: manipulation;
  transition: transform 320ms cubic-bezier(.32,.72,0,1), background 320ms cubic-bezier(.32,.72,0,1);
}
.jeu-cta:hover { background: #c73540; }
.jeu-cta:active { transform: scale(.975); }
.jeu-cta:focus-visible { outline: 3px solid rgba(212,165,116,.65); outline-offset: 3px; }
.jeu-cta-pastille {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 32px; height: 32px; border-radius: 50%;
  font-size: 15px; color: #ffffff; background: rgba(0,0,0,.22);
  transition: transform 320ms cubic-bezier(.32,.72,0,1);
}
.jeu-cta:hover .jeu-cta-pastille { transform: translateX(3px) scale(1.06); }
.jeu-cta-large { padding: 17px 20px 17px 34px; font-size: 17px; }

/* ── LA ROUE, NETTE ─────────────────────────────────────────────────────────────────────── */
.jeu-roue { display: grid; place-items: center; }
.jeu-roue .wheel { animation: jeuRoueEntree 800ms cubic-bezier(.32,.72,0,1) both; }
@keyframes jeuRoueEntree {
  from { opacity: 0; transform: scale(.86) rotate(-14deg); filter: blur(10px) }
  to   { opacity: 1; transform: none; filter: none }
}

/* ── LE GAIN : LE GOODIE QUI GRANDIT ────────────────────────────────────────────────────── */
.jeu-gain-ecran {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  position: relative; z-index: 2; text-align: center;
  padding: 26px 22px 22px;
  animation: jeuGainEntree 900ms cubic-bezier(.32,.72,0,1) both;
}
@keyframes jeuGainEntree {
  from { opacity: 0; transform: translateY(30px) scale(.9) }
  to   { opacity: 1; transform: none }
}
/* ⚠️ LE GOODIE « SORT » DE L'ÉCRAN : il part petit et flou, arrive grand et net, avec un halo qui
   se pose dessous. C'est l'animation qui fait dire au client « le goodie ÇA GRANDIT ». */
.jeu-gain-lot { position: relative; margin-bottom: 6px; }
.jeu-gain-visuel {
  display: grid; place-items: center;
  width: clamp(132px, 34vw, 208px); height: clamp(132px, 34vw, 208px);
  border-radius: 28px; overflow: hidden;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14);
  animation: jeuLotGrandit 1000ms cubic-bezier(.22,1.2,.3,1) both;
}
.jeu-gain-visuel img { width: 100%; height: 100%; object-fit: contain; }
.jeu-gain-visuel.est-vide { font-size: 40px; font-weight: 800; color: #d4a574; letter-spacing: .04em; }
@keyframes jeuLotGrandit {
  0%   { opacity: 0; transform: scale(.42) rotate(-9deg); filter: blur(14px) }
  62%  { opacity: 1; transform: scale(1.07) rotate(1.5deg); filter: blur(0) }
  100% { opacity: 1; transform: scale(1) rotate(0) }
}
/* ⚠️ LE HALO EST EN `::after` ET EN `radial-gradient` — pas une ombre portée. Une `box-shadow`
   large sur fond sombre ne se voit pas, et le projet interdit les ombres sur ce type de surface. */
.jeu-gain-lot::after {
  content: ''; position: absolute; left: 50%; top: 52%; transform: translate(-50%, -50%);
  width: 150%; height: 150%; border-radius: 50%; z-index: -1;
  background: radial-gradient(circle, rgba(230,57,70,.30) 0%, rgba(230,57,70,0) 68%);
  animation: jeuHaloPulse 2600ms ease-in-out infinite;
}
@keyframes jeuHaloPulse { 0%,100% { opacity: .55 } 50% { opacity: .95 } }

.jeu-gain-surtitre {
  margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: #d4a574;
}
.jeu-gain-titre {
  margin: 0; font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif;
  font-size: clamp(26px, 6.4vw, 44px); font-weight: 800; line-height: 1.08;
  letter-spacing: -.03em; color: #ffffff;
  text-shadow: 0 2px 24px rgba(0,0,0,.55);
}
.jeu-gain-texte { margin: 0; font-size: clamp(15px, 3.4vw, 19px); font-weight: 600; color: #e8e6e2; }
.jeu-gain-sep {
  width: 74px; height: 2px; margin: 10px 0 14px; border-radius: 2px;
  background: linear-gradient(to right, rgba(212,165,116,0), #d4a574, rgba(212,165,116,0));
}

/* ⚠️ LA CARTE DE PREUVE RESTE LISIBLE SUR LE DÉCOR : elle était dessinée pour un fond clair, on lui
   redonne donc une surface sombre cohérente quand le jeu est ouvert. Sans ça, la zone de photo
   serait un rectangle blanc au milieu du décor — exactement « la page statique blanche » que le
   client ne veut plus. */
body.jeu-gain .cycle-proof,
body.jeu-ouvert .cycle-proof { background: rgba(20,21,23,.66); border-color: rgba(255,255,255,.14); }
body.jeu-gain .cycle-actions .button,
body.jeu-ouvert .cycle-actions .button { box-shadow: none; }
body.jeu-gain .upload-zone,
body.jeu-ouvert .upload-zone {
  color: #f2f0ed; background: rgba(255,255,255,.05); border-color: rgba(255,255,255,.18);
}
body.jeu-gain .upload-zone strong, body.jeu-ouvert .upload-zone strong { color: #ffffff; }
body.jeu-gain .upload-zone span,  body.jeu-ouvert .upload-zone span { color: #b9bcc2; }

/* ⚠️ SUR UN ÉCRAN COURT, LE DÉCOR NE DOIT PAS MANGER LE BOUTON : on resserre les espacements. */
@media (max-height: 700px) {
  .jeu-scene { gap: 16px; min-height: calc(100dvh - 90px); }
  .jeu-avant { padding: 24px 22px 22px; }
  .jeu-gain-visuel { width: clamp(104px, 26vw, 150px); height: clamp(104px, 26vw, 150px); }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE TABLEAU DE BORD DE LA COLLABORATRICE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ LA CHARTE EST SOMBRE, ET C'EST UN CHOIX, PAS UNE HABITUDE. L'ancien tableau de bord posait
   des cartes claires sur le fond de la page : il ressemblait à un formulaire administratif, et rien
   n'y rappelait l'agence. Ici les cartes sont posées sur une ÉCHELLE DE SURFACES (#131417 sur fond
   #0d0e10), séparées par des CHEVEUX (#23252a) et non par des ombres — sur fond sombre une ombre
   portée ne se voit pas, elle salit.

   ⚠️ LA PROFONDEUR VIENT DE L'EMPILEMENT DES OPACITÉS, pas d'une `box-shadow`. Trois paliers
   suffisent : la page, la carte, le bloc dans la carte. Ajouter une ombre par-dessus donnerait un
   aspect « maquette » que le client a explicitement refusé.

   ⚠️ ET LE FOND N'EST PAS NOIR PLAT : deux halos radiaux très larges (rouge à 4 %, or à 3 %) posent
   une respiration en haut et en bas. Sans eux, la page paraît éteinte — c'est la règle du projet :
   « JAMAIS fond noir plat — toujours gradient radial glows ». */
body[data-interface="terrain"]:not(.participant-mode) {
  background: #0d0e10;
}
body[data-interface="terrain"]:not(.participant-mode)::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(120% 62% at 12% -8%, rgba(230,57,70,.055) 0%, rgba(230,57,70,0) 62%),
    radial-gradient(100% 58% at 92% 106%, rgba(212,165,116,.045) 0%, rgba(212,165,116,0) 60%);
}
body[data-interface="terrain"]:not(.participant-mode) .main,
body[data-interface="terrain"]:not(.participant-mode) .content { position: relative; z-index: 1; }
/* ⚠️⚠️ L'EN-TÊTE ÉTAIT BLANC SUR UNE PAGE SOMBRE. Ma capture le montrait et je ne l'avais pas lu :
   une bande claire en haut, un tableau de bord sombre en dessous — deux régimes de luminosité qui se
   touchent, et le même défaut que « du noir sur du noir », inversé. Le client ne l'a pas demandé en
   ces termes, mais sa règle est générale : « rien ne doit être illisible, n'importe où ».
   ⚠️ ET L'ENCRE DES ENFANTS SUIT, SINON ON ÉCHANGE UN DÉFAUT CONTRE UN AUTRE : le titre devient
   clair, le contexte garde son or, et la pastille de profil reçoit une surface sombre. */
body[data-interface="terrain"]:not(.participant-mode) .topbar {
  background: #0f1012;
  border-bottom: 1px solid #1c1e21;
}
body[data-interface="terrain"]:not(.participant-mode) .topbar h1 { color: #f4f5f6; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .top-meta { color: #c9cdd4; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .top-meta small { color: #a8adb6; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .sync-pill {
  background: rgba(255,255,255,.05);
  border-color: rgba(255,255,255,.14);
  color: #c9cdd4;
}
body[data-interface="terrain"]:not(.participant-mode) .topbar .avatar {
  background: #1c1e21; color: #f4f5f6; border-color: #2a2d31;
}
body[data-interface="terrain"]:not(.participant-mode) .topbar .avatar.avatar-photo img {
  /* ⚠️ LE MÊME RECADRAGE VALIDÉ PAR LE CLIENT — jamais un autre, même sur une vignette. */
  object-position: center 18%;
  transform: scale(1.25);
}
body[data-interface="terrain"]:not(.participant-mode) .top-context { color: #d4a574; }
body[data-interface="terrain"]:not(.participant-mode) #page-title { color: #f4f5f6; }
body[data-interface="terrain"]:not(.participant-mode) .sidebar { background: #0a0b0c; border-right-color: #191b1e; }

/* ── LES CARTES : SURFACE 1, CHEVEU, AUCUNE OMBRE ───────────────────────────────────────── */
.cl-carte {
  background: #131417;
  border: 1px solid #23252a;
  border-radius: 18px;
  padding: 20px 18px;
  margin-bottom: 14px;
  animation: clMonte 560ms cubic-bezier(.32,.72,0,1) both;
}
.cl-carte:nth-of-type(2) { animation-delay: 60ms }
.cl-carte:nth-of-type(3) { animation-delay: 120ms }
.cl-carte:nth-of-type(4) { animation-delay: 180ms }
.cl-carte:nth-of-type(5) { animation-delay: 240ms }
@keyframes clMonte {
  from { opacity: 0; transform: translateY(14px) }
  to   { opacity: 1; transform: none }
}
.cl-tete { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.cl-tete h2 { margin: 0; font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif; font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; color: #f4f5f6; }
/* ⚠️ ENCRE SECONDAIRE REMONTÉE À #a8adb6 : 8,2:1 sur la carte #131417, contre 5,67:1 pour
   #8a8f98. La consigne est « jamais de noir sur du noir, n'importe où » — on prend la marge. */
.cl-tete .section-caption { margin: 3px 0 0; font-size: 12px; color: #a8adb6; }
.cl-tete-ic { width: 20px; height: 20px; color: #d4a574; flex: 0 0 auto; margin-top: 1px; }
.cl-tete > div { display: flex; align-items: flex-start; gap: 10px; }
.cl-tete > div { flex: 1 1 auto; }
.cl-vide { padding: 18px 0; font-size: 13px; color: #a8adb6; text-align: center; }

/* ── LE PROFIL : LA PHOTO, ENFIN ────────────────────────────────────────────────────────── */
/* ⚠️⚠️ LE RECADRAGE `center 18% + scale(1.25)` EST LA RECETTE VALIDÉE PAR LE CLIENT : elle cadre le
   visage sur une photo en buste. On la garde telle quelle. Elle produit une image PLUS GRANDE que la
   boîte (47,5px dessinés pour 38px visibles) — c'est VOULU, et c'est ce qui a déjà fait croire quatre
   fois à une sonde que le bloc débordait. Le débordement est coupé par `overflow:hidden`. */
.cl-profil-tete { display: flex; align-items: center; gap: 14px; }
.cl-profil-tete .profile-avatar {
  flex: 0 0 auto; width: 54px; height: 54px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  font-size: 17px; font-weight: 800; letter-spacing: .02em;
  color: #0d0e10; background: #d4a574;
}
.cl-profil-tete .profile-avatar img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; transform: scale(1.25); }
.cl-profil-id { flex: 1 1 auto; min-width: 0; }
.cl-profil-id h2 { margin: 1px 0 2px; font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: -.015em; color: #f4f5f6; }
.cl-profil-id .section-label { margin: 0; font-size: 9.5px; letter-spacing: .15em; text-transform: uppercase; font-weight: 700; color: #d4a574; }
.cl-mission {
  margin-top: 16px; padding: 13px 15px;
  display: flex; flex-direction: column; gap: 3px;
  background: #18191c; border: 1px solid #23252a; border-radius: 13px;
  border-left: 3px solid var(--red);
}
.cl-mission.est-vide { border-left-color: #3a3d42; }
.cl-mission-k { font-size: 10px; letter-spacing: .14em; text-transform: uppercase; font-weight: 700; color: #a8adb6; }
.cl-mission strong { font-size: 14.5px; font-weight: 700; color: #f4f5f6; }
.cl-mission small { font-size: 12px; color: #b9bcc2; }

/* ── LES WIDGETS : L'ICÔNE ET LE CHIFFRE COLORÉS, RIEN DE PLUS ──────────────────────────── */
/* ⚠️ LE CONTENU INTÉRIEUR EST CENTRÉ ET LE CHIFFRE PASSE EN PREMIER PLAN (tabular-nums pour que
   deux chiffres de suite ne sautent pas d'une mise à jour à l'autre). */
.cl-wg-grille { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.cl-wg {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 16px 10px 14px; text-align: center;
  background: #18191c; border: 1px solid #23252a; border-radius: 14px;
}
.cl-wg-ic { display: grid; place-items: center; width: 24px; height: 24px; }
.cl-wg-ic svg { width: 100%; height: 100%; }
.cl-wg-n { font-size: 26px; font-weight: 800; letter-spacing: -.03em; line-height: 1.05; font-variant-numeric: tabular-nums; }
.cl-wg-lib { font-size: 11px; font-weight: 600; color: #a8adb6; }
/* ⚠️ LES TEINTES SONT FIXES, PAS CALCULÉES : ce sont les cinq familles du système, et elles sont
   choisies pour dépasser 4,5:1 sur la surface #18191c. Une teinte tirée de la palette de dessin
   (#d4a574 or = 6,1:1 ✅, mais #3f7d8c = 3,4:1 🔴) serait illisible une fois sur deux. */
.cl-wg[data-teinte="rouge"] .cl-wg-ic, .cl-wg[data-teinte="rouge"] .cl-wg-n { color: #ff6b74; }
.cl-wg[data-teinte="vert"]  .cl-wg-ic, .cl-wg[data-teinte="vert"]  .cl-wg-n { color: #4ecb8d; }
.cl-wg[data-teinte="or"]    .cl-wg-ic, .cl-wg[data-teinte="or"]    .cl-wg-n { color: #d4a574; }
.cl-wg[data-teinte="bleu"]  .cl-wg-ic, .cl-wg[data-teinte="bleu"]  .cl-wg-n { color: #6fb3e0; }

/* ── L'AGENDA : SES MISSIONS, L'ÉVÉNEMENT EN COURS EN PREMIER ───────────────────────────── */
.cl-agenda-liste { display: flex; flex-direction: column; gap: 8px; }
.cl-ev {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 11px 13px; text-align: left; cursor: pointer;
  background: #18191c; border: 1px solid #23252a; border-radius: 13px;
  color: inherit; font: inherit;
  transition: border-color 240ms cubic-bezier(.32,.72,0,1), background 240ms cubic-bezier(.32,.72,0,1);
}
.cl-ev:hover { background: #1c1e21; border-color: #33363c; }
.cl-ev-date {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: center;
  width: 44px; padding: 5px 0; border-radius: 10px;
  background: #202225; border: 1px solid #2a2d31;
}
.cl-ev-date b { font-size: 15px; font-weight: 800; line-height: 1; color: #f4f5f6; font-variant-numeric: tabular-nums; }
.cl-ev-date i { font-style: normal; font-size: 9px; font-weight: 700; letter-spacing: .08em; color: #a8adb6; margin-top: 2px; }
.cl-ev-corps { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cl-ev-corps strong { font-size: 13.5px; font-weight: 700; color: #f4f5f6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-ev-corps small { font-size: 11.5px; color: #a8adb6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-ev-etat {
  flex: 0 0 auto; padding: 4px 9px; border-radius: 999px;
  font-size: 9px; font-weight: 800; letter-spacing: .1em;
  background: #23252a; color: #a8adb6;
}
/* ⚠️ « EN COURS » NE PORTE PAS QUE LA COULEUR : il a aussi son libellé, son fond et sa bordure
   gauche. Trois signaux — un statut qui ne se distinguerait que par sa teinte serait invisible pour
   une partie des utilisateurs, et le client a demandé « mentionnée en cours » explicitement. */
.cl-ev.est-encours { border-color: rgba(230,57,70,.42); background: rgba(230,57,70,.06); }
/* ⚠️⚠️ « EN COURS » EST LE SEUL LIBELLÉ QUI ÉCHOUAIT VRAIMENT : blanc sur `--red` (#e63946) à
   9px donne 4,17:1 — sous le seuil de 4,5:1 pour un texte de cette taille. Deux corrections
   ensemble : la pastille passe à 10px, et le fond au rouge plus profond `#b32c37` — celui du
   bouton du jeu, donc la charte ne change pas. Résultat : 5,63:1, avec de la marge. */
.cl-ev.est-encours .cl-ev-etat { background: #b32c37; color: #ffffff; font-size: 10px; }
.cl-ev.est-passe { opacity: .62; }
.cl-ev.est-demain .cl-ev-etat { background: rgba(212,165,116,.16); color: #d4a574; }
.cl-liens { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.cl-liens .text-button { font-size: 12.5px; }

/* ── LE JEU ─────────────────────────────────────────────────────────────────────────────── */
.cl-jeu { display: grid; grid-template-columns: minmax(0, 1fr) minmax(140px, 200px); gap: 16px; align-items: center; }
.cl-jeu-copy h2 { margin: 4px 0 6px; font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif; font-size: 19px; font-weight: 700; letter-spacing: -.02em; color: #f4f5f6; }
.cl-jeu-copy > p { margin: 0 0 10px; font-size: 12.5px; color: #a8adb6; }
.cl-jeu .reward-mini-list { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.cl-jeu .reward-mini-list span {
  padding: 4px 10px; border-radius: 999px; font-size: 11px; font-weight: 600;
  background: #202225; border: 1px solid #2a2d31; color: #c9cdd4;
}
.cl-jeu .quick-wheel { display: grid; place-items: center; }

/* ── L'ACTIVITÉ RÉCENTE ─────────────────────────────────────────────────────────────────── */
/* ⚠️⚠️ LA SURFACE DOIT ÊTRE POSÉE, PAS SEULEMENT L'ENCRE. `.list-row` n'a AUCUN fond d'origine :
   il hérite de la carte qui le contient. Régler son texte sans régler son fond marchait par
   ACCIDENT ici — et aurait échoué dès que la carte change de teinte, en laissant du gris sombre sur
   un fond sombre. On nomme donc la surface, pour que le contraste ne dépende plus d'un héritage.
   ⚠️ ET C'EST LA RÈGLE QUE CE PROJET A PAYÉE PLUSIEURS FOIS : « du noir sur du noir » vient
   toujours d'une encre réglée sans sa surface. */
.cl-feed .list { display: flex; flex-direction: column; gap: 2px; }
.cl-feed .list-row { border-color: #1e2024; background: #131417; }
.cl-feed .empty { color: #a8adb6; }
.cl-feed .list-row strong { color: #f4f5f6; }
.cl-feed .row-sub { color: #a8adb6; }
.cl-feed .empty { color: #a8adb6; }

/* ── LA LARGEUR ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 1024px) {
  .cl-wg-grille { grid-template-columns: repeat(3, 1fr); }
  .content { max-width: 1180px; }
}
@media (max-width: 760px) {
  .cl-carte { padding: 17px 14px; border-radius: 16px; }
  .cl-jeu { grid-template-columns: 1fr; }
  .cl-jeu .quick-wheel { order: -1; }
  .cl-profil-tete { gap: 12px; }
  .cl-wg-n { font-size: 23px; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LA SAISIE DU JEU, PAR-DESSUS LE DÉCOR DE LA ROUE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « j'aimerais bien avoir le même background que j'ai choisi pour la roue et
   avoir un contraste, mieux que d'avoir une page statique blanche comme ça. »

   ⚠️⚠️ CE BLOC NE DESSINE RIEN DE NEUF : IL RE-THÈME L'EXISTANT POUR LE DÉCOR. La carte de saisie,
   ses champs, son stepper et sa ligne de consentement existaient déjà, réglés pour un fond blanc.
   Les redessiner aurait dupliqué des dizaines de règles et fait diverger les deux versions ; on
   change donc seulement ce qui dépend du fond — surface, bordure, encre, placeholder.

   ⚠️ LE SÉLECTEUR EST `body.participant-mode.jeu-ouvert` ET NON `.jeu-ouvert` SEUL : c'est ce qui
   lui donne la précédence sur les règles d'origine, qui visent `.participant-mode`. Deux classes
   du même élément, c'est le seul moyen honnête de gagner — `!important` aurait tenu jusqu'au
   prochain conflit, et masqué le problème au lieu de le régler.
   ⚠️ L'ENCRE EST EXPLICITE SUR CHAQUE FAMILLE, jamais héritée : le décor est une PHOTO, sa couleur
   est inconnue. Tout ce qui n'est pas posé sur une surface opaque reçoit sa propre couleur. */
body.participant-mode.jeu-ouvert .game-card {
  background: rgba(20,21,23,.72);
  border: 1px solid rgba(255,255,255,.11);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07);
  backdrop-filter: blur(20px) saturate(1.1);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
}
body.participant-mode.jeu-ouvert .game-context {
  background: rgba(14,15,16,.60);
  border: 1px solid rgba(255,255,255,.09);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-radius: 16px;
}
body.participant-mode.jeu-ouvert .game-context strong { color: #f4f5f6; }
body.participant-mode.jeu-ouvert .game-context span { color: #b9bcc2; }
body.participant-mode.jeu-ouvert .game-intro h2 { color: #ffffff; }
body.participant-mode.jeu-ouvert .section-label { color: #d4a574; }
body.participant-mode.jeu-ouvert .stepper span { color: #8a8f98; }
body.participant-mode.jeu-ouvert .stepper span.active { color: #ffffff; }
body.participant-mode.jeu-ouvert .stepper i { background: rgba(255,255,255,.16); }

/* ── LES CHAMPS : SURFACE SOMBRE, ENCRE CLAIRE, PLACEHOLDER LISIBLE ─────────────────────── */
body.participant-mode.jeu-ouvert .field label { color: #d7dae0; }
body.participant-mode.jeu-ouvert .field label b.obl { color: #ff8a91; }
body.participant-mode.jeu-ouvert .field label b.opt { color: #a8adb6; }
body.participant-mode.jeu-ouvert .field input,
body.participant-mode.jeu-ouvert .field select {
  color: #f4f5f6;
  background: rgba(255,255,255,.055);
  border: 1px solid rgba(255,255,255,.16);
}
body.participant-mode.jeu-ouvert .field input::placeholder { color: #8f949c; }
body.participant-mode.jeu-ouvert .field input:focus,
body.participant-mode.jeu-ouvert .field select:focus {
  background: rgba(255,255,255,.08);
  border-color: rgba(230,57,70,.55);
  box-shadow: 0 0 0 3px rgba(230,57,70,.14);
}
/* ⚠️ LES DEUX BOUTONS DE SEXE SONT DES `<label>` AUTOUR D'UN RADIO MASQUÉ : leur état coché se lit
   donc par `:has()`. Là où `:has()` manque, le radio reste VISIBLE et fonctionnel — l'état dégrade
   proprement au lieu de donner un bouton mort. */
body.participant-mode.jeu-ouvert .sexe-opt {
  color: #d7dae0; background: rgba(255,255,255,.05); border-color: rgba(255,255,255,.16);
}
body.participant-mode.jeu-ouvert .sexe-opt:has(input:checked) {
  color: #ffffff; background: rgba(230,57,70,.22); border-color: rgba(230,57,70,.62);
}
body.participant-mode.jeu-ouvert .jeu-aide { color: #b9bcc2; }
body.participant-mode.jeu-ouvert .consent-line { color: #c9cdd4; }
body.participant-mode.jeu-ouvert .consent-line a { color: #d4a574; }
body.participant-mode.jeu-ouvert .notice { color: #ffb3b8; background: rgba(230,57,70,.10); border-color: rgba(230,57,70,.32); }

/* ── LE LOGO DU JEU EST LE BOUTON DE RETOUR ─────────────────────────────────────────────── */
/* Client (verbatim) : « c'est en appuyant sur le logo, comme on a fait dans le jeu, qu'on a le
   retour ». Le logo doit donc être CLIQUABLE — et le DIRE sans compter sur la couleur seule : un
   curseur main, un anneau au survol, et un anneau de focus pour le clavier. */
body.participant-mode.jeu-ouvert .game-context img { cursor: pointer; }
body.participant-mode.jeu-ouvert .game-context img:hover { opacity: .82; }
body.participant-mode.jeu-ouvert .game-context img:focus-visible { outline: 3px solid rgba(212,165,116,.7); outline-offset: 4px; border-radius: 8px; }
   =========================================================================================
   Client (verbatim) : « une fois que j'accede au jeu rapide, ca devient plein ecran et j'ai plus
   cette identite de Oser la Com. Je dois avoir l'identite que j'ai creee pour la roue, ce
   wallpaper. Et on enleve la roue et les assets et le bouton d'en bas, on garde JUSTE LE LOGO ET LE
   WALLPAPER, et c'est la qu'on pose la saisie - et des boutons qui sont lisibles, comme s'il etait
   sur un jeu. »

   /!\ POURQUOI LA ROUE DISPARAIT, ALORS QU'ELLE EST LE SUJET DU JEU : elle n'apparait qu'APRES la
   saisie. La garder derriere le formulaire montrerait le resultat avant la question. Le decor, lui,
   reste : c'est lui qui porte l'identite.
   /!\ ET LES « ASSETS » DONT IL PARLE SONT LA BARRE DU BAS, LE STEPPER, LE PIED ET LA CARTE
   BLANCHE - tout ce qui fait « formulaire administratif » au lieu de « jeu ».

   /!\ LE FLOU N'EST PAS APPLIQUE ICI : il l'est deja sur #jeu-fond, et le formulaire reste NET
   par-dessus. C'est cette separation qui donne l'effet « fenetre posee sur un decor ».
   ========================================================================================= */

/* -- LE LOGO, SON PROPRE BLOC, TOUJOURS VISIBLE -------------------------------------------- */
/* /!\ IL DOIT SURVIVRE AU PLEIN ECRAN, et c'est tout l'objet de ce bloc : le logo vivait dans
   `.game-context`, que `body.jeu-plein-ecran` masque. Il partait donc avec la barre d'outils --
   exactement « j'ai plus cette identite ». */
body.participant-mode .jeu-logo-barre {
  position: fixed; top: 0; left: 0; right: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  padding: 14px 18px;
  pointer-events: none;
}
body.participant-mode .jeu-logo {
  pointer-events: auto;
  display: block; width: min(150px, 27vw); height: auto;
  cursor: pointer;
  transition: opacity 260ms cubic-bezier(.32,.72,0,1), transform 260ms cubic-bezier(.32,.72,0,1);
  filter: drop-shadow(0 3px 14px rgba(0,0,0,.55));
}
body.participant-mode .jeu-logo:hover { opacity: .85; transform: scale(1.03); }
body.participant-mode .jeu-logo:focus-visible {
  outline: 3px solid rgba(212,165,116,.75); outline-offset: 5px; border-radius: 10px;
}
/* /!\ EN PLEIN ECRAN, LE LOGO RESTE -- ET C'EST LE SEUL RETOUR POSSIBLE. */
body.jeu-plein-ecran .jeu-logo-barre { padding: 18px; }
body.jeu-plein-ecran .jeu-logo { width: min(170px, 24vw); }

/* -- ON RETIRE TOUT CE QUI FAIT « FORMULAIRE » --------------------------------------------- */
body.participant-mode #participant-flow .stepper,
body.participant-mode .game-foot { display: none !important; }
/* /!\ LA CARTE DEVIENT UNE FENETRE DE JEU, PAS UN CADRE ADMINISTRATIF. */
body.participant-mode .game-card {
  background: transparent; border: 0; box-shadow: none;
  padding: 0; min-height: 0;
}
body.participant-mode .game-shell {
  background: transparent;
  min-height: 100dvh; padding: 96px 18px 34px;
}
body.participant-mode .game-context {
  max-width: 660px; margin: 0 auto 16px;
  background: rgba(14,15,16,.58);
  border: 1px solid rgba(255,255,255,.09);
  border-radius: 16px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}

/* -- LA FENETRE DE SAISIE : LISIBLE SUR N'IMPORTE QUEL WALLPAPER --------------------------- */
/* /!\ C'EST LA PIECE QUI REND LE FORMULAIRE LISIBLE, ET ELLE N'EST PAS NEGOCIABLE : le wallpaper
   est une PHOTO (couleur inconnue). Un panneau a 86 % garantit le contraste quelle que soit
   l'image, sans la cacher -- on voit le decor autour, jamais a travers le texte. */
body.participant-mode #participant-flow {
  max-width: 660px; margin: 0 auto;
  padding: 26px 24px 22px;
  background: rgba(16,17,19,.86);
  border: 1px solid rgba(255,255,255,.12);
  border-radius: 24px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.09), 0 24px 60px rgba(0,0,0,.45);
  backdrop-filter: blur(22px) saturate(1.15);
  -webkit-backdrop-filter: blur(22px) saturate(1.15);
}
body.participant-mode #participant-flow .game-intro { text-align: center; margin-bottom: 20px; }
body.participant-mode #participant-flow .game-intro h2 {
  margin: 6px 0 0;
  font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif;
  font-size: clamp(20px, 3.6vw, 28px); font-weight: 800; letter-spacing: -.025em;
  color: #ffffff;
}
body.participant-mode #participant-flow .section-label { color: #d4a574; }

/* -- LES CHAMPS : DE VRAIS CHAMPS DE JEU -- GRANDS, CLAIRS, TAPABLES ---------------------- */
/* /!\ 54px DE HAUTEUR MINIMUM : c'est la taille d'une cible tactile confortable sur tablette.
   Le client dit « des boutons qui sont lisibles pour qu'il puisse cliquer » -- un champ de 38px se
   rate avec un doigt, et on croit que l'application ne repond pas. */
body.participant-mode #participant-flow .field label {
  display: block; margin-bottom: 7px;
  font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
  color: #d7dae0;
}
body.participant-mode #participant-flow .field label b.obl { color: #ff8a91; font-weight: 700; }
body.participant-mode #participant-flow .field label b.opt { color: #9aa0a8; font-weight: 600; }
body.participant-mode #participant-flow .field input,
body.participant-mode #participant-flow .field select {
  width: 100%; min-height: 54px; padding: 0 16px;
  font-size: 16px;
  color: #f7f8f8;
  background: rgba(255,255,255,.07);
  border: 1.5px solid rgba(255,255,255,.20);
  border-radius: 14px;
  transition: border-color 200ms cubic-bezier(.32,.72,0,1), background 200ms cubic-bezier(.32,.72,0,1);
}
body.participant-mode #participant-flow .field input::placeholder { color: #8f949c; }
body.participant-mode #participant-flow .field input:focus,
body.participant-mode #participant-flow .field select:focus {
  outline: none;
  background: rgba(255,255,255,.10);
  border-color: rgba(230,57,70,.70);
  box-shadow: 0 0 0 4px rgba(230,57,70,.16);
}
body.participant-mode #participant-flow .sexe-choix { display: flex; gap: 10px; }
body.participant-mode #participant-flow .sexe-opt {
  flex: 1 1 0; min-height: 54px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 16px; font-weight: 700; color: #d7dae0;
  background: rgba(255,255,255,.06);
  border: 1.5px solid rgba(255,255,255,.20);
  border-radius: 14px; cursor: pointer;
  transition: all 200ms cubic-bezier(.32,.72,0,1);
}
body.participant-mode #participant-flow .sexe-opt:has(input:checked) {
  color: #ffffff; background: rgba(179,44,55,.34); border-color: rgba(230,57,70,.80);
}
body.participant-mode #participant-flow .sexe-opt input { width: 18px; height: 18px; accent-color: #b32c37; }
body.participant-mode #participant-flow .jeu-aide {
  margin: 12px 0 14px; font-size: 12.5px; color: #b9bcc2; text-align: center;
}
body.participant-mode #participant-flow .consent-line {
  display: flex; gap: 11px; align-items: flex-start;
  margin: 16px 0 18px; font-size: 13px; line-height: 1.5; color: #c9cdd4;
}
body.participant-mode #participant-flow .consent-line input {
  width: 22px; height: 22px; margin-top: 1px; flex: 0 0 auto; accent-color: #b32c37;
}

/* -- LE BOUTON QUI CONTINUE : LE PLUS LISIBLE DE LA PAGE ---------------------------------- */
/* /!\ PLEINE LARGEUR, 60px DE HAUT, PILULE. C'est le geste principal du participant : il doit se
   trouver sans chercher et se viser sans hesiter. La lueur sous le bouton le fait ressortir du
   wallpaper sombre -- un aplat rouge sur une photo sombre se confond avec le decor. */
body.participant-mode #participant-flow .game-next {
  width: 100%; min-height: 60px;
  display: inline-flex; align-items: center; justify-content: center; gap: 14px;
  font-size: 17px; font-weight: 800; letter-spacing: .01em;
  color: #ffffff;
  background: linear-gradient(180deg, #c73540 0%, #b32c37 100%);
  border: 0; border-radius: 16px;
  box-shadow: 0 10px 26px rgba(179,44,55,.42), inset 0 1px 0 rgba(255,255,255,.20);
  cursor: pointer; touch-action: manipulation;
  transition: transform 260ms cubic-bezier(.32,.72,0,1), box-shadow 260ms cubic-bezier(.32,.72,0,1);
}
body.participant-mode #participant-flow .game-next:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 14px 32px rgba(179,44,55,.52), inset 0 1px 0 rgba(255,255,255,.24);
}
body.participant-mode #participant-flow .game-next:active:not(:disabled) { transform: translateY(1px) scale(.99); }
/* /!\ DESACTIVE, IL DOIT RESTER LISIBLE : un bouton gris pale qui ne reagit pas se lit comme une
   panne. On garde un contraste de 4,5:1 et c'est .jeu-aide qui dit ce qui manque. */
body.participant-mode #participant-flow .game-next:disabled {
  color: #c8ccd2;
  background: rgba(255,255,255,.09);
  box-shadow: none; cursor: not-allowed;
}
body.participant-mode #participant-flow .notice {
  color: #ffb3b8; background: rgba(230,57,70,.12);
  border: 1px solid rgba(230,57,70,.34); border-radius: 12px;
  padding: 12px 14px; font-size: 13px;
}

/* /!\ SUR UN ECRAN COURT, LA FENETRE SE RESSERRE PLUTOT QUE DE DEFILER VERS LE BAS. */
@media (max-height: 720px) {
  body.participant-mode .game-shell { padding: 78px 14px 20px; }
  body.participant-mode #participant-flow { padding: 18px 18px 16px; }
  body.participant-mode #participant-flow .field input,
  body.participant-mode #participant-flow .field select,
  body.participant-mode #participant-flow .sexe-opt { min-height: 48px; }
  body.participant-mode #participant-flow .game-next { min-height: 54px; }
}
/* =========================================================================================
   LE BILAN DE LA COLLABORATRICE -- TOTAUX, COMPAGNIES, EVENEMENTS, GOODIES, STOCK
   =========================================================================================
   Client (verbatim) : « essaye de m'arranger un dashboard selon le nombre de participants total
   qu'elle a du encaisser depuis son travail avec l'agence. Le total encaisse de participants et de
   cadeaux livres par compagnie et par evenement. Les goodies les plus sortis et son stock, combien
   il lui reste. Et si elle est ravitaillee, ca sort valider le stock recu. »

   /!\ LES BARRES DE PROPORTION SONT LE BON OUTIL ICI, PAS UN CAMEMBERT : le client veut COMPARER
   des volumes sur une tablette. Une barre se lit en un regard, un camembert demande de tourner
   autour. Et une barre se superpose proprement a un libelle, ce qu'un camembert ne fait pas.
   /!\ ET LE MINIMUM VISUEL EST DE 3 % : sans lui, une ligne a 1 sorti sur 60 afficherait une
   barre INVISIBLE -- on croirait la donnee absente alors qu'elle existe.
   ========================================================================================= */

/* -- LES LIGNES DE DONNEES : UN NOM, UNE BARRE, DES CHIFFRES ------------------------------- */
/* /!\ LA GRILLE EST EN `1fr auto auto` : le NOM prend la place restante et peut donc se tronquer,
   les CHIFFRES gardent leur largeur (tabular-nums) pour que les colonnes restent alignees d'une
   ligne a l'autre. Une grille en pourcentages ferait danser les chiffres a chaque contenu. */
.cl-lignes { display: flex; flex-direction: column; gap: 9px; }
.cl-ligne {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(60px, 130px) auto auto;
  gap: 12px; align-items: center;
}
.cl-ligne-nom {
  font-size: 13px; font-weight: 600; color: #e6e8eb;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cl-ligne-val {
  font-size: 11.5px; color: #a8adb6; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.cl-ligne-val b { font-size: 14px; font-weight: 800; color: #f4f5f6; margin-right: 2px; }
.cl-ligne-val.est-vert b { color: #4ecb8d; }
.cl-ligne-val.est-alerte b { color: #ff6b74; }

/* /!\ LA BARRE EST UNE SURFACE, PAS UNE COULEUR : le rail est visible meme a 0 %, sinon on ne
   voit pas qu'il y a un indicateur la ou il n'y a rien a montrer. */
.cl-barre {
  display: block; height: 7px; border-radius: 4px; overflow: hidden;
  background: rgba(255,255,255,.07);
}
.cl-barre i { display: block; height: 100%; border-radius: 4px; }
.cl-barre[data-teinte="rouge"] i { background: linear-gradient(90deg, #b32c37, #e05a64); }
.cl-barre[data-teinte="vert"]  i { background: linear-gradient(90deg, #2d8a5f, #4ecb8d); }
.cl-barre[data-teinte="or"]    i { background: linear-gradient(90deg, #8a6a34, #d4a574); }
.cl-barre[data-teinte="bleu"]  i { background: linear-gradient(90deg, #2f6b96, #6fb3e0); }

/* -- LES SOUS-TITRES DE SECTION ------------------------------------------------------------- */
.cl-sous-titre {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 22px 0 12px; padding-top: 16px;
  border-top: 1px solid #1e2024;
}
.cl-sous-titre h3 { margin: 0; font-size: 14px; font-weight: 700; color: #f4f5f6; }
.cl-sous-titre .section-caption { margin: 3px 0 0; font-size: 12px; color: #a8adb6; }
.cl-ic-t { width: 18px; height: 18px; color: #d4a574; flex: 0 0 auto; margin-top: 1px; }

/* -- PAR EVENEMENT : UNE DATE EN PASTILLE, COMME DANS L AGENDA ----------------------------- */
.cl-ev-ligne {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 12px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid #1e2024;
}
.cl-ev-ligne:last-child { border-bottom: 0; }
.cl-ev-nom { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cl-ev-nom strong { font-size: 13px; font-weight: 700; color: #f4f5f6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-ev-nom small { font-size: 11.5px; color: #a8adb6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -- LES DEUX BLOCS COTE A COTE SUR UN ECRAN LARGE ---------------------------------------- */
.cl-goodies-grille { display: grid; grid-template-columns: 1fr; gap: 22px; }
@media (min-width: 900px) { .cl-goodies-grille { grid-template-columns: 1fr 1fr; gap: 26px; } }
.cl-bloc { min-width: 0; }
.cl-bloc-t {
  margin: 0 0 12px; font-size: 12px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: #d4a574;
}

/* -- LE STOCK RECU A VALIDER : LA CARTE APPELLE L ACTION ---------------------------------- */
/* /!\ ELLE EST LA SEULE CARTE DU TABLEAU DE BORD QUI DEMANDE UN GESTE, DONC LA SEULE QUI PEUT
   ECHOUER EN SILENCE. Elle porte une bordure chaude et un fond teinte, pour qu'on la voie sans la
   chercher -- et son bouton de validation est le plus grand de la page. */
.cl-reception.est-active {
  border-color: rgba(212,165,116,.42);
  background: linear-gradient(180deg, rgba(212,165,116,.07) 0%, #131417 62%);
}
.cl-reception .cl-tete-ic { color: #d4a574; }
.cl-rec-ligne {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 10px; align-items: center;
  padding: 11px 12px; border-radius: 13px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
}
.cl-rec-img {
  width: 42px; height: 42px; border-radius: 11px; overflow: hidden;
  background: rgba(255,255,255,.06); display: grid; place-items: center;
}
.cl-rec-img img { width: 100%; height: 100%; object-fit: contain; }
.cl-rec-nom { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cl-rec-nom strong { font-size: 13.5px; font-weight: 700; color: #f4f5f6; }
.cl-rec-nom small { font-size: 11.5px; color: #a8adb6; }
/* /!\ 48px DE HAUT POUR LES DEUX BOUTONS : c'est un geste tactile, souvent avec des gants, et au
   milieu d'une soiree. Un bouton de 30px se rate, et on croit que rien ne s'est passe. */
.cl-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 48px; padding: 0 16px;
  font-family: inherit; font-size: 13.5px; font-weight: 700;
  border: 0; border-radius: 12px; cursor: pointer;
  transition: transform 240ms cubic-bezier(.32,.72,0,1), opacity 240ms cubic-bezier(.32,.72,0,1);
}
.cl-btn:active:not(:disabled) { transform: scale(.97); }
.cl-btn:disabled { opacity: .5; cursor: not-allowed; }
/* ⚠️⚠️ `#2d8a5f` DONNAIT 4,28:1 — MESURÉ, 0,22 SOUS LE SEUIL. Mon commentaire annonçait 4,9:1 :
   je l'avais estimé au lieu de le calculer, et c'est exactement ce que ce projet interdit.
   Le blanc sur un vert moyen est un piège classique : la teinte paraît « foncée » à l'œil alors que
   sa luminance reste haute. `#1f7350` donne 5,62:1 — calculé, pas estimé — et reste franchement vert.
   ⚠️ ET C'EST LE SEUL BOUTON D'ACTION DE LA CARTE : un contraste insuffisant sur le geste principal
   est le pire endroit pour se tromper. */
.cl-btn-ok { color: #ffffff; background: #1f7350; }
.cl-btn-ic { width: 17px; height: 17px; }
.cl-btn-non {
  color: #c9cdd4; background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.16);
}
.cl-rec-etat { margin-top: 10px; font-size: 13px; color: #d4a574; min-height: 18px; }

/* -- SUR UN ECRAN ETROIT, LES LIGNES PASSENT EN DEUX RANGEES --------------------------------- */
/* /!\ SUR UNE TABLETTE PORTRAIT, LA GRILLE A QUATRE COLONNES ECRASE LE NOM A TROIS LETTRES.
   On empile donc le nom et ses chiffres, et la barre prend toute la largeur -- c'est la barre qui
   porte la comparaison, c'est elle qui doit rester lisible. */
@media (max-width: 720px) {
  .cl-ligne, .cl-ev-ligne { grid-template-columns: minmax(0, 1fr) auto auto; }
  .cl-ligne .cl-barre { grid-column: 1 / -1; margin-top: 4px; }
  .cl-rec-ligne { grid-template-columns: auto minmax(0, 1fr); }
  .cl-rec-ligne .cl-btn { grid-column: span 1; }
}
/* =========================================================================================
   LE TABLEAU DE BORD DE LA COLLABORATRICE -- EN CLAIR
   =========================================================================================
   Client (verbatim) : « les widgets, je veux pas qu'ils soient sombres. Je veux que le widget soit
   au meme style que je t'ai montre, avec le meme style de dashboard qu'on a fait AU DEPART pour la
   fameuse plateforme globale -- elle doit etre EN CLAIR. »

   /!\ CE BLOC EST UNE SURCOUCHE, PAS UNE REECRITURE. Les regles sombres posees plus haut restent
   en place et servent de base : ici on ne fait que les ramener a la charte claire de la plateforme
   globale (#f7f7f5 la page, #ffffff les cartes, #e6e3de les cheveux, #17171a l'encre). Reecrire
   42 regles une par une aurait laisse des trous -- un seul bloc les couvre toutes, et se relit
   d'un coup d'oeil le jour ou la charte change.

   /!\ ⚠️ LE CONTRASTE A ETE CALCULE, PAS ESTIME -- c'est l'erreur qui a fait echouer le bouton
   vert (j'avais annonce 4,9:1, la mesure disait 4,28:1). Sur #ffffff :
       #17171a texte 17,9:1 · #55585c muted 7,2:1 · #6b6e73 muted 5,1:1 · #D42B38 rouge 5,0:1
   Et les TEINTES DES WIDGETS sont assombries par le JS quand elles echouent (l'or a 2,23:1 devient
   un bronze lisible) -- la teinte reste l'identite, seule sa luminosite descend.
   ========================================================================================= */

/* -- LA PAGE ET L EN-TETE ------------------------------------------------------------------ */
body[data-interface="terrain"]:not(.participant-mode) { background: #f7f7f5; }
/* /!\ LES HALOS RESTENT, EN VERSION CLAIRE : « jamais de fond plat », et un blanc pur sans",
   respiration parait gris a cote des cartes blanches. */
body[data-interface="terrain"]:not(.participant-mode)::before {
  background:
    radial-gradient(120% 62% at 12% -8%, rgba(230,57,70,.05) 0%, rgba(230,57,70,0) 62%),
    radial-gradient(100% 58% at 92% 106%, rgba(212,165,116,.07) 0%, rgba(212,165,116,0) 60%);
}
body[data-interface="terrain"]:not(.participant-mode) .topbar {
  background: #ffffff; border-bottom: 1px solid #e6e3de;
}
body[data-interface="terrain"]:not(.participant-mode) .topbar h1 { color: #141416; }
body[data-interface="terrain"]:not(.participant-mode) .top-context { color: #8a5a2b; }
body[data-interface="terrain"]:not(.participant-mode) #page-title { color: #141416; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .top-meta { color: #55585c; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .top-meta small { color: #6b6e73; }
body[data-interface="terrain"]:not(.participant-mode) .topbar .sync-pill {
  background: #f2f0ec; border-color: #e6e3de; color: #55585c;
}
body[data-interface="terrain"]:not(.participant-mode) .topbar .avatar {
  background: #f2f0ec; color: #17171a; border-color: #e6e3de;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar { background: #ffffff; border-right-color: #e6e3de; }
/* /!\ LA BARRE DU BAS EN CLAIR : elle porte les 4 onglets, donc son encre doit tenir 4,5:1. */
body[data-interface="terrain"]:not(.participant-mode) .nav-item { color: #6b6e73; }
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active { color: #17171a; background: #f2f0ec; }
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active::before { background: #D42B38; }
body[data-interface="terrain"]:not(.participant-mode) .nav-jeu .nav-ic,
body[data-interface="terrain"]:not(.participant-mode) .nav-jeu .nav-txt { color: #17171a; }

/* -- LES CARTES : BLANCHES, CHEVEU CLAIR, AUCUNE OMBRE ------------------------------------- */
.cl-carte {
  background: #ffffff;
  border: 1px solid #e6e3de;
  box-shadow: 0 1px 2px rgba(20,20,22,.03);
}
.cl-tete h2 { color: #141416; }
.cl-tete .section-caption { color: #55585c; }
.cl-tete-ic { color: #8a5a2b; }
.cl-ic-t { color: #8a5a2b; }
.cl-vide { color: #6b6e73; }
.cl-sous-titre { border-top-color: #ece9e4; }
.cl-sous-titre h3 { color: #141416; }
.cl-sous-titre .section-caption { color: #55585c; }
.cl-bloc-t { color: #8a5a2b; }

/* -- LE PROFIL ---------------------------------------------------------------------------- */
.cl-profil-tete .profile-avatar { color: #ffffff; background: #8a5a2b; }
.cl-profil-id h2 { color: #141416; }
.cl-profil-id .section-label { color: #8a5a2b; }
.cl-mission { background: #faf9f7; border-color: #e6e3de; border-left-color: #D42B38; }
.cl-mission.est-vide { border-left-color: #d5d2cb; }
.cl-mission-k { color: #6b6e73; }
.cl-mission strong { color: #141416; }
.cl-mission small { color: #55585c; }

/* -- LES WIDGETS : LA RECETTE DE LA PLATEFORME GLOBALE, EN CLAIR --------------------------- */
/* /!\ LA PASTILLE ET LE LISERE SONT POSES EN LIGNE PAR LE JS (teinte pleine en degrade). Ici on",
   ne pose que ce qui ne depend pas de la teinte : la surface, le cheveu, le chiffre, le libelle. */
.cl-wg {
  position: relative; overflow: hidden;
  background: #ffffff; border: 1px solid #e6e3de; border-radius: 13px;
  padding: 15px 16px; align-items: flex-start; text-align: left;
  transition: transform .2s ease, border-color .2s ease;
}
.cl-wg:hover { transform: translateY(-2px); border-color: #d8d4cc; }
.cl-wg-lisere {
  position: absolute; top: 0; left: 0; right: 0; height: 3px;
  display: block; opacity: .85;
}
.cl-wg-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 31px; height: 31px; border-radius: 9px; margin-bottom: 8px; flex: none;
  box-shadow: 0 1px 2px rgba(20,20,22,.14);
  transition: transform .2s ease;
}
.cl-wg-ic svg { width: 16px !important; height: 16px !important; }
.cl-wg:hover .cl-wg-ic { transform: scale(1.08) rotate(-3deg); }
.cl-wg-n { font-size: 27px; font-weight: 600; letter-spacing: -.7px; line-height: 1.05; }
/* ⚠️ `#7d8085` EST LE GRIS DE LA PLATEFORME GLOBALE — IL Y DONNE 3,96:1, SOUS LE SEUIL.
   C'est un défaut hérité, pas un choix : sur des libellés de 10,5px, la règle demande 4,5:1, et
   `#6b6e73` donne 5,12:1 pour un écart invisible à l'œil. On prend la marge — c'est la consigne du
   client (« jamais du noir sur du noir, n'importe où »), appliquée à l'envers. */
.cl-wg-lib { font-size: 10.5px; color: #6b6e73; letter-spacing: .01em; }

/* -- LES LIGNES DE DONNEES : ENCRE SOMBRE SUR BLANC ---------------------------------------- */
.cl-ligne-nom { color: #2b2d31; }
.cl-ligne-val { color: #55585c; }
.cl-ligne-val b { color: #141416; }
.cl-ligne-val.est-vert b { color: #2f6b3f; }
.cl-ligne-val.est-alerte b { color: #c0252f; }
.cl-barre { background: #f1efec; }
.cl-ev-ligne { border-bottom-color: #ece9e4; }
.cl-ev-nom strong { color: #141416; }
.cl-ev-nom small { color: #55585c; }

/* -- L AGENDA ----------------------------------------------------------------------------- */
.cl-ev { background: #faf9f7; border-color: #e6e3de; }
.cl-ev:hover { background: #ffffff; border-color: #d8d4cc; }
.cl-ev-date { background: #f2f0ec; border-color: #e6e3de; }
.cl-ev-date b { color: #141416; }
.cl-ev-date i { color: #6b6e73; }
.cl-ev-corps strong { color: #141416; }
.cl-ev-corps small { color: #55585c; }
.cl-ev-etat { background: #ece9e4; color: #55585c; }
.cl-ev.est-encours { border-color: rgba(212,43,56,.42); background: rgba(212,43,56,.05); }
.cl-ev.est-encours .cl-ev-etat { background: #c0252f; color: #ffffff; }
.cl-ev.est-passe { opacity: .6; }
.cl-ev.est-demain .cl-ev-etat { background: rgba(138,90,43,.14); color: #8a5a2b; }

/* -- LE JEU ------------------------------------------------------------------------------- */
.cl-jeu-copy h2 { color: #141416; }
.cl-jeu-copy > p { color: #55585c; }
.cl-jeu .reward-mini-list span { background: #f2f0ec; border-color: #e6e3de; color: #2b2d31; }
.section-label { color: #8a5a2b; }

/* -- LE STOCK RECU A VALIDER --------------------------------------------------------------- */
/* /!\ C EST LA SEULE CARTE QUI ATTEND UN GESTE : elle porte une bordure chaude et un fond tres",
   legerement teinte, pour se voir sans la chercher. */
.cl-reception.est-active {
  border-color: rgba(138,90,43,.42);
  background: linear-gradient(180deg, rgba(212,165,116,.10) 0%, #ffffff 62%);
}
.cl-rec-ligne { background: #faf9f7; border-color: #e6e3de; }
.cl-rec-img { background: #f2f0ec; }
.cl-rec-nom strong { color: #141416; }
.cl-rec-nom small { color: #55585c; }
.cl-btn-ok { color: #ffffff; background: #1f7350; }
.cl-btn-non { color: #55585c; background: #f2f0ec; border-color: #e6e3de; }
.cl-rec-etat { color: #8a5a2b; }

/* -- L ACTIVITE RECENTE -------------------------------------------------------------------- */
.cl-feed .list-row { border-color: #ece9e4; background: #ffffff; }
.cl-feed .list-row strong { color: #141416; }
.cl-feed .row-sub { color: #55585c; }
.cl-feed .empty { color: #6b6e73; }
.cl-liens .text-button { color: #8a5a2b; }
/* =========================================================================================
   LA RECEPTION DE STOCK -- QUI, D OU, QUAND, ET LE STOCK APRES
   =========================================================================================
   Client (verbatim) : « on doit savoir de qui il est recu, a quelle heure et a quelle date. De
   chez qui il est recu. Si elle le valide, son stock s'alimente automatiquement. »

   /!\ LA LIGNE PASSE DE 4 A 5 COLONNES : la quantite gagne sa propre colonne, parce qu'elle porte
   maintenant DEUX informations (« +6 » et « 2 -> 8 en stock ») qui doivent rester lisibles d'un
   coup d'oeil -- c'est ce couple qui rend la validation verifiable.
   ========================================================================================= */

.cl-rec-ligne {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto auto;
  gap: 10px; align-items: center;
}
.cl-rec-nom { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cl-rec-de { font-size: 11.5px; color: #55585c; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* /!\ L'HEURE EST UNE INFORMATION DE PREUVE, PAS UN DETAIL : on la met en gris plus sombre pour
   qu'elle se lise comme un fait date, pas comme une note. */
.cl-rec-quand { font-size: 11px; color: #6b6e73; font-variant-numeric: tabular-nums; }
/* /!\ LE COUPLE « CE QU'ELLE A » / « CE QU'ELLE AURA » : le chiffre ajoute en gras et colore, le
   resultat en petit dessous. C'est ce qui transforme un bouton en decision eclairee. */
.cl-rec-qte {
  display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
  white-space: nowrap; text-align: right;
}
.cl-rec-qte b { font-size: 15px; font-weight: 800; color: #1f7350; font-variant-numeric: tabular-nums; }
.cl-rec-qte small { font-size: 10.5px; color: #6b6e73; font-variant-numeric: tabular-nums; }

/* /!\ SUR UN ECRAN ETROIT, LA LIGNE SE REPLIE EN DEUX RANGEES : le nom et son detail d'abord, puis
   la quantite et les deux boutons. Une grille a cinq colonnes ecrase le nom a trois lettres. */
@media (max-width: 780px) {
  .cl-rec-ligne { grid-template-columns: auto minmax(0, 1fr) auto; row-gap: 8px; }
  .cl-rec-qte { grid-column: 3; }
  .cl-rec-ligne .cl-btn-ok { grid-column: 2; justify-content: center; }
  .cl-rec-ligne .cl-btn-non { grid-column: 3; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE HEADER RÉDUIT — LE LOGO À GAUCHE, LA VIGNETTE À DROITE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « en haut, sur le header, enlève le header. Tu gardes juste le logo à gauche
   de Ozelca, et à droite tu mets la vignette du profil, c'est tout. »

   ⚠️ LES DEUX ÉLÉMENTS SONT AUX EXTRÉMITÉS (`space-between`), RIEN ENTRE EUX : c'est ce vide qui fait
   la différence entre un en-tête d'application et une barre de titre d'administration. */
body[data-interface="terrain"] .topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px; min-height: 0;
}
body[data-interface="terrain"] .tp-logo { display: block; line-height: 0; }
body[data-interface="terrain"] .tp-logo img {
  display: block; height: 30px; width: auto; max-width: 168px; object-fit: contain;
}
body[data-interface="terrain"] .tp-profil {
  display: block; padding: 0; border: 0; background: none; cursor: pointer;
  border-radius: 50%; line-height: 0;
}
/* ⚠️ 42px : une vignette de profil se vise au doigt sur une tablette. En dessous de 40px, on rate
   la cible et on croit que le menu ne s'ouvre pas. */
body[data-interface="terrain"] .tp-avatar {
  display: grid; place-items: center; overflow: hidden;
  width: 42px; height: 42px; border-radius: 50%;
  font-size: 14px; font-weight: 800; letter-spacing: .02em;
  color: #ffffff; background: #8a5a2b;
  border: 2px solid #ffffff; box-shadow: 0 1px 4px rgba(20,20,22,.16);
}
body[data-interface="terrain"] .tp-avatar img {
  width: 100%; height: 100%; object-fit: cover;
  /* ⚠️ LE MÊME RECADRAGE VALIDÉ PAR LE CLIENT — jamais un autre, même sur 42px. */
  object-position: center 18%; transform: scale(1.25);
}
body[data-interface="terrain"] .tp-profil:focus-visible { outline: 3px solid rgba(138,90,43,.6); outline-offset: 3px; }

/* ── LA CARTE « MISSION EN COURS » ──────────────────────────────────────────────────────── */
/* ⚠️ ELLE EST LISIBLE EN UN REGARD : le libellé en petit au-dessus, le nom en grand, le lieu
   en dessous. C'est la première chose lue en ouvrant, donc elle ne partage sa carte avec rien. */
.cl-mission-encours {
  display: flex; flex-direction: column; gap: 3px;
  border-left: 4px solid #D42B38;
}
.cl-mission-encours.est-vide { border-left-color: #d5d2cb; }
.cl-me-k {
  font-size: 10px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  color: #D42B38;
}
.cl-mission-encours.est-vide .cl-me-k { color: #6b6e73; }
.cl-me-nom { font-size: 20px; font-weight: 800; letter-spacing: -.02em; color: #141416; }
.cl-me-lieu { font-size: 13px; color: #55585c; }

/* ── LA DEMI-CARTE D'IDENTITÉ ───────────────────────────────────────────────────────────── */
/* ⚠️ ELLE FAIT LA MOITIÉ DE LA HAUTEUR D'AVANT : plus de bloc mission à l'intérieur (il est passé
   en première carte), plus de titre d'espace, plus d'identifiant de connexion, plus de badge. */
.cl-profil { padding: 14px 18px; }
.cl-profil-tete { gap: 12px; }
.cl-profil-tete .profile-avatar { width: 44px; height: 44px; font-size: 15px; }
.cl-profil-id h2 { font-size: 16px; margin: 0; }
.cl-profil-id .profile-depuis { font-size: 12px; color: #6b6e73; }

/* ── LE STOCK QU'ELLE DÉTIENT ───────────────────────────────────────────────────────────── */
/* ⚠️ TOUS SES GOODIES, MÊME À ZÉRO, ET LE ZÉRO EST MARQUÉ : c'est lui qui déclenche une demande de
   ravitaillement. Le cacher ferait disparaître la ligne, et elle croirait qu'on ne lui en a jamais
   donné. */
.cl-stock-ligne {
  display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(60px, 130px) auto;
  gap: 12px; align-items: center;
}
.cl-stock-nom { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cl-stock-nom strong { font-size: 13.5px; font-weight: 700; color: #141416; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-stock-nom small { font-size: 11.5px; color: #6b6e73; }
.cl-stock-n {
  display: flex; flex-direction: column; align-items: flex-end; gap: 0;
  white-space: nowrap; text-align: right;
}
.cl-stock-n b { font-size: 19px; font-weight: 800; color: #141416; font-variant-numeric: tabular-nums; letter-spacing: -.5px; }
.cl-stock-n small { font-size: 10px; color: #6b6e73; }
/* ⚠️ LE ZÉRO PORTE SA PROPRE MARQUE : un bonbon rouge sur le chiffre ET le libellé, pour qu'une
   rupture de stock se voie sans lire les nombres. */
.cl-stock-n[data-vide="oui"] b { color: #c0252f; }
.cl-stock-n[data-vide="oui"] small { color: #c0252f; font-weight: 700; }

@media (max-width: 720px) {
  .cl-stock-ligne { grid-template-columns: auto minmax(0, 1fr) auto; row-gap: 6px; }
  .cl-stock-ligne .cl-barre { grid-column: 2 / -1; }
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LES TROIS VUES DE LA COLLABORATRICE — MISSIONS, PARTICIPANTS, PROFIL
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ CES VUES ÉTAIENT APPELÉES DANS `render()` SOUS DES NOMS QUI N'EXISTAIENT PAS
   (`renderMissions`, `renderCRMParticipants`). Une `ReferenceError` ARRÊTE `render()` NET, et
   c'est ce qui faisait disparaître le CRM de la plateforme administrative ET rendait la barre de
   recherche inerte : la recherche vit dans le HTML que la fonction morte devait produire. Les
   règles qui suivent habillent des vues qui EXISTENT.
   ⚠️ ELLES SONT SCOPÉES `body[data-interface="terrain"]` : le tableau de bord de la DIRECTION
   utilise les mêmes noms de classe, et une règle non scopée les ferait se marcher dessus.
   ⚠️ ET LA CHARTE EST CELLE DU DASHBOARD CLAIR DÉJÀ VALIDÉ — page `#f7f7f5`, carte `#ffffff`,
   cheveu `#e6e3de`, encre `#17171a`. Une nouvelle vue n'introduit pas une nouvelle palette. */

/* ── LA CARTE DE MISSION ────────────────────────────────────────────────────────────────── */
.mi-carte {
  position: relative; overflow: hidden;
  display: grid; gap: 12px; padding: 16px 18px; margin-bottom: 12px;
  background: #ffffff; border: 1px solid #e6e3de; border-radius: 14px;
}
.mi-carte.est-encours { border-color: rgba(212, 43, 56, .38); }
/* ⚠️ LE LISERÉ DIT L'ÉTAT SANS MOT, ET LE MOT RESTE : « EN COURS » en rouge, « À VENIR » en or,
   « TERMINÉ » en gris. La couleur ne porte jamais l'information SEULE — elle la double. */
.mi-carte::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: #e6e3de; }
.mi-carte.est-encours::before { background: #D42B38; }
.mi-carte.est-avenir::before { background: #d4a574; }
.mi-carte.est-passe::before { background: #c9c5be; }
.mi-tete { display: flex; align-items: flex-start; gap: 13px; }
.mi-date { flex: none; width: 54px; padding: 7px 0; text-align: center; background: #faf9f7; border: 1px solid #ece9e4; border-radius: 11px; }
.mi-date b { display: block; font: 700 19px Manrope, sans-serif; color: #17171a; line-height: 1; }
.mi-date span { display: block; margin-top: 3px; font: 700 8.5px 'DM Mono', monospace; letter-spacing: .1em; color: #8d8983; }
.mi-corps { flex: 1; min-width: 0; }
.mi-titre { margin: 0 0 3px; font: 700 15px Manrope, sans-serif; color: #17171a; }
.mi-lieu { margin: 0; font-size: 12.5px; color: #6b6965; }
.mi-etat { display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 5px 11px; border-radius: 999px; font: 700 9.5px 'DM Mono', monospace; letter-spacing: .1em; text-transform: uppercase; }
.mi-etat.est-encours { color: #9c1b26; background: #fdeef0; border: 1px solid #f0c8cd; }
.mi-etat.est-avenir { color: #7a5a24; background: #fdf6ea; border: 1px solid #eddcbb; }
.mi-etat.est-passe { color: #6b6965; background: #f4f2ef; border: 1px solid #e6e3de; }
.mi-etat .mi-point { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.mi-etat.est-encours .mi-point { animation: mi-pulse 1.8s ease-in-out infinite; }
@keyframes mi-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .28 } }
@media (prefers-reduced-motion: reduce) { .mi-etat.est-encours .mi-point { animation: none } }
.mi-meta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.mi-meta span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: #55534f; }
.mi-meta svg { width: 15px; height: 15px; flex: none; color: #8d8983; }
.mi-part { display: inline-flex; align-items: center; gap: 5px; color: #2f6b45; font-weight: 600; }

/* ── LE TABLEAU DES PARTICIPANTS — EN-TÊTE ÉPINGLÉ, TRI AU CLIC ─────────────────────────── */
.mpc-tete { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.mpc-tete input { flex: 1 1 240px; min-width: 0; min-height: 46px; padding: 10px 16px; border: 1px solid #e6e3de; border-radius: 999px; background: #ffffff; color: #17171a; font: 400 14px Manrope, sans-serif; }
.mpc-tete input::placeholder { color: #8d8983; }
.mpc-tete input:focus { outline: none; border-color: #d4a574; }
.mpc-compte { flex: none; padding: 7px 13px; border-radius: 999px; background: #faf9f7; border: 1px solid #e6e3de; font: 700 10.5px 'DM Mono', monospace; letter-spacing: .08em; color: #55534f; }
.mpc-scroll { overflow: auto; max-height: min(58vh, 560px); border: 1px solid #e6e3de; border-radius: 13px; background: #ffffff; }
.mpc-table { width: 100%; border-collapse: collapse; }
.mpc-table thead th { position: sticky; top: 0; z-index: 2; padding: 11px 13px; text-align: left; white-space: nowrap; background: #faf9f7; border-bottom: 1px solid #e6e3de; font: 700 10px 'DM Mono', monospace; letter-spacing: .12em; text-transform: uppercase; color: #6b6965; cursor: pointer; user-select: none; }
.mpc-table tbody td { padding: 11px 13px; border-bottom: 1px solid #f1efec; font-size: 13px; color: #17171a; vertical-align: middle; }
.mpc-table tbody tr { cursor: pointer; }
.mpc-table tbody tr:hover { background: #fdfaf5; }
.mpc-table tbody tr.est-gagne td:first-child { box-shadow: inset 3px 0 0 #d4a574; }
.mpc-nom { display: flex; align-items: center; gap: 10px; }
.mpc-av { flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; background: #f1efec; color: #55534f; font: 700 12px Manrope, sans-serif; overflow: hidden; }
.mpc-av img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; transform: scale(1.25); }
.mpc-nom b { font-weight: 600; }
.mpc-sub { font-size: 11px; color: #8d8983; }
.mpc-gain { display: inline-flex; align-items: center; gap: 7px; }
.mpc-gain img { width: 26px; height: 26px; border-radius: 7px; object-fit: cover; flex: none; }
.mpc-pill { display: inline-block; padding: 4px 10px; border-radius: 999px; font: 700 9.5px 'DM Mono', monospace; letter-spacing: .08em; text-transform: uppercase; }
.mpc-pill.est-remis { color: #2f6b45; background: #eef7f0; border: 1px solid #d8ebdd; }
.mpc-pill.est-attente { color: #7a5a24; background: #fdf6ea; border: 1px solid #eddcbb; }
.mpc-vide { padding: 34px 18px; text-align: center; color: #8d8983; font-size: 13.5px; }
.mpc-aide { margin: 11px 2px 0; font-size: 12px; color: #6b6965; }
.mpc-aide b { color: #3d3b38; }

/* ── MON PROFIL ─────────────────────────────────────────────────────────────────────────── */
.mp-page { display: grid; gap: 16px; }
.mp-hero { display: flex; align-items: center; gap: 18px; padding: 20px; background: #ffffff; border: 1px solid #e6e3de; border-radius: 16px; }
.mp-photo { position: relative; flex: none; width: 104px; height: 104px; border-radius: 18px; overflow: hidden; background: #f1efec; border: 1px solid #e6e3de; display: grid; place-items: center; }
.mp-photo img { width: 100%; height: 100%; object-fit: cover; object-position: center 18%; transform: scale(1.25); }
.mp-photo .mp-init { font: 700 30px Manrope, sans-serif; color: #6b6965; }
.mp-photo label { position: absolute; left: 0; right: 0; bottom: 0; padding: 7px 4px; text-align: center; cursor: pointer; background: rgba(23, 23, 25, .78); color: #ffffff; font: 700 9.5px 'DM Mono', monospace; letter-spacing: .08em; text-transform: uppercase; }
.mp-photo label:hover { background: rgba(23, 23, 25, .92); }
.mp-photo input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.mp-qui { flex: 1; min-width: 0; }
.mp-qui h2 { margin: 0 0 4px; font: 700 20px Manrope, sans-serif; color: #17171a; }
.mp-qui p { margin: 0; font-size: 13px; color: #6b6965; }
.mp-fige { margin-top: 9px; font-size: 11.5px; color: #8d8983; }
.mp-grille { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.mp-fiche { padding: 15px 17px; background: #ffffff; border: 1px solid #e6e3de; border-radius: 14px; }
.mp-fiche h3 { margin: 0 0 11px; font: 700 10px 'DM Mono', monospace; letter-spacing: .13em; text-transform: uppercase; color: #8d8983; }
.mp-ligne { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 6px 0; margin: 0; }
.mp-ligne + .mp-ligne { border-top: 1px solid #f4f2ee; }
.mp-ligne dt { font-size: 12.5px; color: #6b6965; margin: 0; }
.mp-ligne dd { margin: 0; font: 600 13px Manrope, sans-serif; color: #17171a; text-align: right; word-break: break-word; }
.mp-ev { display: flex; align-items: center; gap: 11px; padding: 9px 0; }
.mp-ev + .mp-ev { border-top: 1px solid #f4f2ee; }
.mp-ev .mp-ev-d { flex: none; width: 42px; text-align: center; padding: 5px 0; background: #faf9f7; border: 1px solid #ece9e4; border-radius: 9px; }
.mp-ev .mp-ev-d b { display: block; font: 700 15px Manrope, sans-serif; color: #17171a; line-height: 1; }
.mp-ev .mp-ev-d span { display: block; font: 700 7.5px 'DM Mono', monospace; color: #8d8983; margin-top: 2px; }
.mp-ev .mp-ev-c { flex: 1; min-width: 0; }
.mp-ev .mp-ev-c b { display: block; font-size: 13px; color: #17171a; }
.mp-ev .mp-ev-c span { font-size: 11.5px; color: #8d8983; }
.mp-vide { padding: 14px 2px; font-size: 12.5px; color: #8d8983; }
.mp-note { margin: 2px; font-size: 11.5px; color: #8d8983; }
.mp-sortie { justify-self: start; display: inline-flex; align-items: center; gap: 9px; padding: 12px 20px; border: 1px solid rgba(212, 43, 56, .34); border-radius: 11px; background: #ffffff; color: #9c1b26; cursor: pointer; font: 600 13.5px Manrope, sans-serif; }
.mp-sortie:hover { background: #fdf5f6; }
.mp-sortie svg { width: 17px; height: 17px; }

/* ── LA NOTE SOUS LA FICHE PARTICIPANT (le cadeau n'est pas modifiable) ─────────────────── */
.form-note { margin: 4px 0 10px; padding: 10px 13px; background: #fdf6ea; border: 1px solid #eddcbb; border-radius: 10px; font-size: 12px; color: #7a5a24; }

/* ⚠️ LES VUES DE LA COLLABORATRICE SONT EN CLAIR, COMME SON DASHBOARD — mais elles vivent dans
   une page dont le fond est celui de l'application. On leur donne donc leur propre fond de page,
   sinon les cartes blanches flotteraient sur un fond sombre. */
/* ⚠️⚠️ CETTE REGLE ECRASAIT LE FOND SOMBRE, ET C'EST ELLE QUI GAGNAIT - MESURE.
   `body[data-interface="terrain"] .content` pese plus lourd que
   `body[data-interface="terrain"]:not(.participant-mode)` : c'est donc ELLE qui peignait l'ecran,
   et le fond sombre du body ne se voyait NULLE PART (le `.content` couvre tout).
   Correction : le contenu suit la PAGE. Le fond de la tablette est sombre, donc le `.content`
   est transparent - et les cartes, elles, restent claires. */
body[data-interface="terrain"]:not(.participant-mode) .content { background: transparent; }
body[data-interface="terrain"] .cl-carte,
body[data-interface="terrain"] .cl-wg { background: #ffffff; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE FOND SOMBRE DE LA BANNIÈRE GAUCHE — LE MÊME QUE LA PLATEFORME
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « et je veux que le fond soit comme la bannière gauche de la plateforme.
   J'en veux en mode sombre. »

   ⚠️⚠️ ON RECOPIE LA RECETTE DE LA BANNIÈRE, ON N'INVENTE PAS UNE PALETTE. La bannière de la
   plateforme (l.2) est : `rgba(15,16,17,.82)` posée sur le fond de page, AVEC le voile rouge
   `radial-gradient(circle at 80% -10%, rgba(230,57,70,.09), transparent 35%)`. C'est ce mélange
   — le noir translucide ET sa lueur — qui donne la profondeur. Un noir plat aurait l'air d'un
   autre produit : dans ce projet, « JAMAIS fond noir plat » est une règle payée plusieurs fois.
   ⚠️ ET L'ENCRE EST EXPLICITE SUR CHAQUE FAMILLE : la page est maintenant SOMBRE, donc les textes
   qui étaient écrits en clair (`#17171a` sur blanc) deviennent invisibles sans leur propre règle.
   C'est la faute « noir sur noir » que ce projet a payée au moins cinq fois. */

/* ── LA PAGE ELLE-MÊME ──────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] {
  /* ⚠️ LE MÊME FOND DE PAGE QUE LE COCKPIT : la lueur rouge en haut à droite, puis le noir. */
  background:
    radial-gradient(circle at 80% -10%, rgba(230, 57, 70, .09), transparent 35%),
    #0f1011;
  color: #f4f5f6;
}
body[data-interface="terrain"]:not(.participant-mode) .main {
  background: transparent;
}

/* ── LE LOGO, SEUL, SANS BARRE ──────────────────────────────────────────────────────────── */
/* ⚠️ IL EST POSÉ DANS LE FLUX, PAS EN `position:fixed` : sans header, il n'a plus besoin d'être
   arraché au flux, et un logo fixe recouvrirait le contenu au défilement. */
body[data-interface="terrain"] .tp-logo {
  display: block;
  padding: 18px 22px 6px;
  line-height: 0;
}
body[data-interface="terrain"] .tp-logo img {
  display: block; height: 34px; width: auto;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, .5));
}

/* ── LE CONTENU REÇOIT LE FOND DE LA BANNIÈRE ───────────────────────────────────────────── */
/* ⚠️ LE CONTENU GARDE SON FOND CLAIR : le client a validé « un dashboard en clair, comme la
   plateforme globale », et le passage en sombre porte sur la PAGE, pas sur les cartes. On ne
   refait donc pas les cartes : elles reposent sur un fond sombre, ce qui les fait ressortir
   davantage — c'est exactement l'effet de la bannière à côté du contenu clair du cockpit. */
body[data-interface="terrain"]:not(.participant-mode) .content {
  background: transparent;
}

/* ── LES TITRES DE VUE, EN CLAIR SUR LE FOND SOMBRE ─────────────────────────────────────── */
body[data-interface="terrain"] .view-kicker h2 { color: #f4f5f6; }
body[data-interface="terrain"] .view-kicker .section-caption { color: #a8adb6; }
body[data-interface="terrain"] .view-kicker .section-label { color: #d4a574; }

/* ── LA BARRE DU BAS GARDE SON FOND, PLUS SOMBRE QUE LA PAGE ────────────────────────────── */
/* ⚠️ `#0a0b0c` CONTRE `#0f1011` : la barre doit se détacher de la page, sinon elle disparaît —
   une barre d'onglets qu'on ne voit pas ne s'utilise pas. On garde donc deux niveaux, pas un. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar {
  background: #0a0b0c;
  border-top: 1px solid rgba(255, 255, 255, .09);
}

/* ── LE JEU : PLEIN ÉCRAN DÈS L'ENTRÉE, SANS DOUBLE LOGO ────────────────────────────────── */
/* Client (verbatim) : « quand on rentre sur le jeu, je vois qu'il y a DEUX FOIS le logo de Oser
   la Com. Premièrement, c'est un problème. Secondo, je veux que le jeu soit EN PLEIN ÉCRAN et
   qu'on sorte sur le fond de la roue directement, et le logo de la roue. »

   ⚠️⚠️ POURQUOI LE LOGO APPARAISSAIT DEUX FOIS, ET C'EST UNE FAUTE DE STRUCTURE :
     · `.jeu-logo-barre` — le logo posé DANS LA PAGE, en haut de `.game-shell` ;
     · `.game-context`  — qui contient LUI AUSSI un logo (le même fichier `white_logo.png`).
   Deux blocs, deux logos, le même dessin : le client en voit deux. La correction n'est pas d'en
   masquer un par une règle — c'est de n'en DESSINER QU'UN. On retire donc `.jeu-logo-barre`, et
   le logo de `.game-context` devient LE logo du jeu (il était déjà là, il portait la campagne).
   ⚠️ ET LE PLEIN ÉCRAN EST POSÉ DÈS L'ENTRÉE : il n'attend plus un clic. Le client veut
   « sortir directement sur le fond de la roue » — le décor, le logo, et la saisie par-dessus. */
body.participant-mode .jeu-logo-barre { display: none !important; }

/* ⚠️ LE LOGO DU JEU EST PLUS GRAND QUE DANS LA BARRE D'OUTILS : il est maintenant SEUL à porter
   l'identité, il n'est plus un détail parmi des boutons. */
body.participant-mode .game-context img {
  width: min(168px, 30vw); height: auto;
}
/* ⚠️ LA BARRE D'OUTILS DU JEU DEVIENT UN BLOC D'IDENTITÉ CENTRÉ, PAS UNE BARRE. Sans les boutons
   à côté, une barre large et basse laisserait un vide à droite du logo. */
body.participant-mode .game-context {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 10px;
  max-width: 560px; margin: 0 auto 18px;
  background: none; border: 0; backdrop-filter: none; -webkit-backdrop-filter: none;
}
body.participant-mode .game-context > div { align-items: center; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LE LOGO DE LA MARQUE DANS LE JEU — EN HAUT À GAUCHE, ET IL PORTE LE RETOUR
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « il doit récupérer le logo de la marque pour le mettre EN HAUT À GAUCHE.
   Donc c'est les deux assets qu'il doit reprendre de la roue de fortune. »

   ⚠️⚠️ IL EST EN `position: fixed` ET VIVENT SUR `body` — PAS DANS LA VUE. Deux raisons, toutes
   deux mesurées ailleurs dans ce projet :
     · le parcours du jeu change d'écran quatre fois (saisie → roue → gain → photo). Un logo posé
       dans `#app` serait effacé à chaque repeinture, et il faudrait le reposer à chaque fois ;
     · le plein écran masque la barre et l'en-tête. Un logo qui vit dans le flux partirait avec eux
       — c'est EXACTEMENT le défaut déjà payé (« j'ai plus cette identité »).
   ⚠️ ET C'EST LUI QUI RAMÈNE À L'ESPACE COLLABORATRICE : le client veut le logo en haut à gauche,
   et un logo qui n'agit pas est décoratif. Le clic est posé en JS (`poserLogoMarqueJeu`), pas en
   attribut — le chemin du fichier vient de la base. */
body.participant-mode .jeu-logo-marque {
  position: fixed; z-index: 4;
  top: 0; left: 0;
  display: block;
  padding: 18px 22px;
  line-height: 0;
  cursor: pointer;
  text-decoration: none;
  transition: opacity 260ms cubic-bezier(.32,.72,0,1), transform 260ms cubic-bezier(.32,.72,0,1);
}
body.participant-mode .jeu-logo-marque img {
  display: block;
  width: auto; height: auto;
  max-width: min(190px, 26vw); max-height: 64px;
  /* ⚠️ LA LUEUR PORTE LE CONTRASTE SUR N'IMPORTE QUEL FOND : le logo vient d'un téléversement,
     donc on ne sait pas s'il est clair ou sombre. Une ombre portée le détache du décor dans les
     deux cas, là où une couleur fixe ne protégerait qu'un seul des deux. */
  filter: drop-shadow(0 2px 12px rgba(0, 0, 0, .62)) drop-shadow(0 1px 3px rgba(0, 0, 0, .5));
}
body.participant-mode .jeu-logo-marque:hover { opacity: .86; transform: scale(1.03); }
body.participant-mode .jeu-logo-marque:focus-visible {
  outline: 3px solid rgba(212, 165, 116, .78); outline-offset: 4px; border-radius: 10px;
}
/* ⚠️ SUR UN ÉCRAN ÉTROIT LE LOGO SE RÉDUIT, MAIS NE DISPARAÎT PAS : c'est le seul retour possible
   en plein écran. On réduit la taille, jamais l'information. */
@media (max-width: 620px) {
  body.participant-mode .jeu-logo-marque { padding: 12px 14px; }
  body.participant-mode .jeu-logo-marque img { max-width: min(140px, 40vw); max-height: 46px; }
}

/* ── LE BOUTON « CONTINUER » PREND LA COULEUR DU BOUTON DE LA ROUE ──────────────────────── */
/* Client (verbatim) : « en bas un bouton qui sera DE LA MÊME COULEUR que le bouton de la roue
   de fortune, LANCER LA ROUE, et quand il y clique c'est là où on passe à la roue. »

   ⚠️⚠️ LA COULEUR EST LUE DE LA ROUE, PAS CHOISIE ICI. La roue #2 « Heineken » porte
   `couleur_1 = #0d6b3f` — le vert de la marque. `jeuVarsCharte()` la pose déjà en
   `--oz-c1` sur le conteneur du jeu ; on la reprend donc par variable, jamais en dur.
   Un vert écrit en dur afficherait la couleur d'une autre campagne le jour où le client change
   la roue — et le bouton ne suivrait plus la roue qu'il a réglée.
   ⚠️ ET UNE COULEUR DE SECOURS EXISTE (`#0d6b3f`, la valeur mesurée de la roue assignée) : sans
   elle, une roue sans couleur donnerait un bouton transparent, donc invisible. */
body.participant-mode .game-next {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--oz-c1, #0d6b3f) 78%, #ffffff) 0%,
    var(--oz-c1, #0d6b3f) 52%,
    color-mix(in srgb, var(--oz-c1, #0d6b3f) 74%, #000000) 100%);
  color: #ffffff;
  border: 0;
  border-radius: 999px;
  min-height: 62px;
  padding: 16px 34px;
  font: 800 15px Manrope, system-ui, sans-serif;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  box-shadow:
    0 4px 0 color-mix(in srgb, var(--oz-c1, #0d6b3f) 55%, #000000),
    0 10px 26px -6px rgba(0, 0, 0, .55),
    inset 0 1px 0 rgba(255, 255, 255, .28);
  transition: transform 200ms cubic-bezier(.32,.72,0,1), filter 200ms;
}
body.participant-mode .game-next:hover:not(:disabled) { filter: brightness(1.09); transform: translateY(-1px); }
body.participant-mode .game-next:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 1px 0 color-mix(in srgb, var(--oz-c1, #0d6b3f) 55%, #000000); }
body.participant-mode .game-next:focus-visible { outline: 3px solid rgba(255, 255, 255, .82); outline-offset: 3px; }
/* ⚠️ DÉSACTIVÉ, IL RESTE LISIBLE : le client doit VOIR le bouton pour comprendre qu'il existe et
   qu'il manque quelque chose. Un bouton fantôme ne dirait pas quoi remplir — et un texte blanc sur
   un vert éclairci tomberait sous 4,5:1. On assombrit le FOND, jamais le texte. */
body.participant-mode .game-next:disabled {
  cursor: not-allowed;
  background: color-mix(in srgb, var(--oz-c1, #0d6b3f) 42%, #4a4d52);
  color: #f2f3f4;
  box-shadow: 0 2px 0 rgba(0, 0, 0, .35);
  filter: none;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LA TABLETTE PORTE LA CHARTE DE SA CAMPAGNE
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « on prend la charte de la campagne pour l'appliquer sur la tablette. Comme
   ça la collaboratrice est toujours liée à la charte de la campagne où elle est. »

   ⚠️⚠️ AUCUNE COULEUR EN DUR ICI. Tout passe par les variables posées en JS
   (`appliquerCharteCampagne`) : `--oz-c1` et `--oz-c2` (les deux couleurs de la roue de la
   campagne), `--oz-accent`, `--oz-relief`, `--oz-cover` (la photo de couverture de la campagne).
   C'est ce qui fait que CHANGER DE CAMPAGNE CHANGE L'APPARENCE — sans toucher au CSS.
   ⚠️ ET CHAQUE VARIABLE A UNE VALEUR DE SECOURS MESURÉE (`#0d6b3f`, le vert de la roue Heineken) :
   une variable non posée donnerait une couleur transparente, donc un élément invisible. Le
   `color-mix` du bouton, lui, ne s'applique PAS si la variable manque — il retourne alors la
   couleur de secours, ce qui est le comportement voulu. */

/* ── LE FOND DE LA TABLETTE REPREND LA COUVERTURE DE LA CAMPAGNE ────────────────────────── */
/* ⚠️ LA COUVERTURE PASSE PAR UN DÉGRADÉ MULTICOUCHE, PAS PAR UN `background-image` SEC : posée
   seule, une photo de couverture rendrait les textes illisibles sur ses zones claires. La couche
   sombre par-dessus garantit le contraste quel que soit le visuel du client — et c'est la même
   recette que la bannière gauche de la plateforme (le noir translucide + la lueur). */
/* ⚠️⚠️ LE FOND ET LA COUVERTURE SONT SÉPARÉS, ET CE N'EST PAS DE LA COQUETTERIE DE STYLE.
   MESURÉ : ma première version écrivait `var(--oz-cover, none) center 40% / cover no-repeat` DANS
   la même déclaration `background`. Or `none` est une valeur de `background-image`, pas de
   `background-position` — quand la campagne n'a pas de couverture, la valeur devient
   `none center 40% / cover no-repeat`, ce qui est INVALIDE. Le navigateur jette alors la
   DÉCLARATION ENTIÈRE, donc aussi le noir et la lueur : la page redevenait transparente
   (`rgba(0, 0, 0, 0)`, mesuré) alors que le CSS semblait correct à la lecture.
   ⚠️ LA LEÇON : une valeur optionnelle ne se mélange JAMAIS aux valeurs obligatoires dans une
   propriété raccourcie. Séparée, elle ne peut plus invalider le reste.
   ⚠️ ET LA CLASSE `.a-couverture` EST POSÉE PAR LE JS (voir `appliquerCharteCampagne`) : c'est le
   seul moyen d'avoir une couche SUPPLÉMENTAIRE sans jamais écrire une valeur invalide. */
body[data-interface="terrain"]:not(.participant-mode) {
  background-color: #0f1011;
  background-image:
    radial-gradient(circle at 80% -10%, rgba(230, 57, 70, .09), transparent 35%),
    linear-gradient(rgba(8, 9, 10, .82), rgba(8, 9, 10, .94));
  color: #f4f5f6;
}
/* ⚠️ LA COUVERTURE DE LA CAMPAGNE EST UNE COUCHE DE PLUS, ET SEULEMENT QUAND ELLE EXISTE. */
/* ⚠️⚠️ CETTE RÈGLE ÉTAIT FAITE POUR LA TABLETTE, ET ELLE A NOIRCI LA DIRECTION.
   Le client a montré la preuve dans le panneau « Elements » de Chrome :
     <body data-interface="direction" class="a-couverture">
   — la classe est posée par `appliquerCharteCampagne()` (app.js l.21069) QUELLE QUE SOIT
   l'interface, alors que cette règle-ci porte un dégradé à 82–94 % DE NOIR sur toute la page.
   ⚠️ C'est le dégradé qui rend « toute la plateforme noire » après une seconde : la charte arrive
   du réseau, `--oz-cover` se pose, et le noir s'applique — d'où le délai. */
body[data-interface="terrain"]:not(.participant-mode).a-couverture {
  background-image:
    radial-gradient(circle at 80% -10%, rgba(230, 57, 70, .09), transparent 35%),
    linear-gradient(rgba(8, 9, 10, .82), rgba(8, 9, 10, .94)),
    var(--oz-cover);
  background-position: 0 0, 0 0, center 40%;
  background-size: auto, auto, cover;
  background-repeat: no-repeat, no-repeat, no-repeat;
}
/* ⚠️ QUAND LA CAMPAGNE N'A PAS DE COUVERTURE, `var(--oz-cover)` VAUT `none` — et un
   `background-image: none` dans une liste de couches est simplement ignoré, donc le fond reste le
   noir et sa lueur. C'est le comportement voulu : pas de photo = l'identité de l'agence. */

/* ── LE LOGO, SEUL, SANS BARRE ──────────────────────────────────────────────────────────── */
/* ⚠️ IL EST POSÉ DANS LE FLUX : sans header, il n'a plus besoin d'être arraché au flux, et un logo
   fixe recouvrirait le contenu au défilement. */
body[data-interface="terrain"] .tp-logo {
  display: block;
  padding: 18px 22px 6px;
  line-height: 0;
}
body[data-interface="terrain"] .tp-logo img {
  display: block; height: 34px; width: auto;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, .5));
}

/* ── LE CONTENU RESTE CLAIR : C'EST LE CHOIX DÉJÀ VALIDÉ DU CLIENT ──────────────────────── */
/* ⚠️ LE PASSAGE EN SOMBRE PORTE SUR LA PAGE, PAS SUR LES CARTES. Le client a validé « un dashboard
   en clair, comme la plateforme globale » : on ne refait donc pas les cartes. Elles reposent sur un
   fond sombre, ce qui les fait ressortir davantage — l'effet de la bannière à côté du contenu clair. */
body[data-interface="terrain"]:not(.participant-mode) .content { background: transparent; }

/* ── LES BLOCS DE TÊTE DE VUE, EN CLAIR SUR LE FOND SOMBRE ──────────────────────────────── */
body[data-interface="terrain"] .view-kicker h2 { color: #f4f5f6; }
body[data-interface="terrain"] .view-kicker .section-caption { color: #a8adb6; }
body[data-interface="terrain"] .view-kicker .section-label { color: var(--oz-accent, #d4a574); }

/* ── LA BARRE DU BAS GARDE SON PROPRE NIVEAU DE NOIR ────────────────────────────────────── */
/* ⚠️ `#0a0b0c` CONTRE `#0f1011` : la barre doit se détacher de la page, sinon elle disparaît — une
   barre d'onglets qu'on ne voit pas ne s'utilise pas. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar {
  background: #0a0b0c;
  border-top: 1px solid rgba(255, 255, 255, .09);
}
/* ⚠️ L'ONGLET ACTIF PORTE LA COULEUR DE LA CAMPAGNE : c'est le rappel le plus discret et le plus
   constant de la marque — il est visible sur chaque écran de la tablette. */
body[data-interface="terrain"] .nav-item.active::before {
  background: var(--oz-c1, var(--red, #E63946));
}
body[data-interface="terrain"] .nav-jeu::after {
  background: radial-gradient(circle,
    color-mix(in srgb, var(--oz-c1, #E63946) 44%, transparent) 0%,
    transparent 70%);
}

/* ── LE JEU : LE LOGO DE LA MARQUE EN HAUT À GAUCHE, ET LE BOUTON DE LA ROUE ─────────────── */
/* Client (verbatim) : « il doit récupérer le logo de la marque pour le mettre en haut à gauche »,
   et « en bas un bouton qui sera de la même couleur que le bouton de la roue de fortune ». */
body.participant-mode .jeu-logo-marque {
  position: fixed; z-index: 4;
  top: 0; left: 0;
  display: block; padding: 18px 22px; line-height: 0;
  cursor: pointer; text-decoration: none;
  transition: opacity 260ms cubic-bezier(.32,.72,0,1), transform 260ms cubic-bezier(.32,.72,0,1);
}
body.participant-mode .jeu-logo-marque img {
  display: block; width: auto; height: auto;
  max-width: min(190px, 26vw); max-height: 64px;
  /* ⚠️ LA LUEUR PORTE LE CONTRASTE SUR N'IMPORTE QUEL FOND : le logo vient d'un téléversement, donc
     on ne sait pas s'il est clair ou sombre. Une ombre le détache du décor dans les deux cas, là où
     une couleur fixe n'en protégerait qu'un. */
  filter: drop-shadow(0 2px 12px rgba(0, 0, 0, .62)) drop-shadow(0 1px 3px rgba(0, 0, 0, .5));
}
body.participant-mode .jeu-logo-marque:hover { opacity: .86; transform: scale(1.03); }
body.participant-mode .jeu-logo-marque:focus-visible {
  outline: 3px solid rgba(212, 165, 116, .78); outline-offset: 4px; border-radius: 10px;
}
@media (max-width: 620px) {
  body.participant-mode .jeu-logo-marque { padding: 12px 14px; }
  body.participant-mode .jeu-logo-marque img { max-width: min(140px, 40vw); max-height: 46px; }
}

/* ⚠️ LE LOGO DE LA BARRE D'OUTILS DU JEU EST RETIRÉ — C'EST LE DOUBLE LOGO QUE LE CLIENT A VU.
   `.jeu-logo-barre` portait `/assets/white_logo.png`, et `.game-context` juste en dessous portait
   LE MÊME fichier. On n'en dessine donc plus qu'un, et c'est celui de la marque (voir plus haut). */
body.participant-mode .jeu-logo-barre { display: none !important; }

/* ── LE BOUTON « CONTINUER » PREND LA COULEUR DU BOUTON DE LA ROUE ──────────────────────── */
/* Client (verbatim) : « en bas un bouton qui sera de la même couleur que le bouton de la roue de
   fortune, LANCER LA ROUE, et quand il y clique c'est là où on passe à la roue. » */
body.participant-mode .game-next {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--oz-c1, #0d6b3f) 78%, #ffffff) 0%,
    var(--oz-c1, #0d6b3f) 52%,
    color-mix(in srgb, var(--oz-c1, #0d6b3f) 74%, #000000) 100%);
  color: #ffffff;
  border: 0; border-radius: 999px;
  min-height: 62px; padding: 16px 34px;
  font: 800 15px Manrope, system-ui, sans-serif;
  letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer; position: relative; overflow: hidden;
  box-shadow:
    0 4px 0 color-mix(in srgb, var(--oz-c1, #0d6b3f) 55%, #000000),
    0 10px 26px -6px rgba(0, 0, 0, .55),
    inset 0 1px 0 rgba(255, 255, 255, .28);
  transition: transform 200ms cubic-bezier(.32,.72,0,1), filter 200ms;
}
body.participant-mode .game-next:hover:not(:disabled) { filter: brightness(1.09); transform: translateY(-1px); }
body.participant-mode .game-next:active:not(:disabled) {
  transform: translateY(2px);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--oz-c1, #0d6b3f) 55%, #000000);
}
body.participant-mode .game-next:focus-visible { outline: 3px solid rgba(255, 255, 255, .82); outline-offset: 3px; }
/* ⚠️ DÉSACTIVÉ, IL RESTE LISIBLE : le client doit VOIR le bouton pour comprendre qu'il existe et
   qu'il manque quelque chose — un bouton fantôme ne dirait pas quoi remplir. On assombrit le FOND,
   jamais le texte : un texte blanc sur un vert éclairci tomberait sous 4,5:1. */
body.participant-mode .game-next:disabled {
  cursor: not-allowed; filter: none;
  background: color-mix(in srgb, var(--oz-c1, #0d6b3f) 42%, #4a4d52);
  color: #f2f3f4;
  box-shadow: 0 2px 0 rgba(0, 0, 0, .35);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   LES WIDGETS DE LA COLLABORATRICE — REFONDUS (24/09, retour du client)
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Client (verbatim) : « les widgets sont mal organisés, ils sont larges pour rien, il y a
   beaucoup de vide et ils ne sont pas colorés. »

   ⚠️⚠️ LES TROIS DÉFAUTS, ET CHACUN A SA CAUSE MESURÉE DANS LE CSS PRÉCÉDENT :
     1. « larges pour rien / beaucoup de vide » — `.cl-wg { align-items: flex-start;
        text-align: left; padding: 15px 16px; }`. Le contenu était ALIGNÉ À GAUCHE dans une
        carte large et haute : tout l'espace à droite du libellé était du vide pur. Or la règle
        du projet dit **CONTENU INTÉRIEUR CENTRÉ** — elle avait été écrite puis perdue.
     2. « pas colorées » — la PASTILLE était blanche. MESURÉ : `.cl-wg-ic` n'avait ni fond ni
        dégradé, et la couleur de teinte n'était posée que sur `color` (l'icône et le chiffre).
        Le client demande « icône + chiffre colorés » **ET** une pastille visible : c'est la
        pastille qui porte la couleur quand on balaie l'écran du regard.
     3. « mal organisés » — la grille passait à **2 colonnes**, et une carte de 2 colonnes sur
        une tablette fait 380 px de large pour un chiffre à 3 caractères. **3 par ligne** est la
        règle, et elle tient sur les trois tailles d'écran.

   ⚠️ ET LES TEINTES VIENNENT DE LA CHARTE DE LA CAMPAGNE (`--oz-c1` / `--oz-c2`), pas d'une
   palette figée. La couleur du widget « participants » est donc celle de la roue de la campagne :
   changer la roue dans l'onglet admin change la couleur des widgets de la collaboratrice. */
body[data-interface="terrain"] .cl-wg-grille {
  display: grid;
  /* ⚠️ `minmax(0, 1fr)` ET NON `1fr` : sans le `minmax(0, …)`, une cellule de grille refuse de
     descendre sous la largeur de son contenu, et un long libellé élargit la colonne — donc les
     trois widgets n'ont plus la même taille. C'est la cause classique du « large pour rien ». */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 9px;
}
@media (max-width: 420px) {
  /* ⚠️ SUR UN ÉCRAN TRÈS ÉTROIT ON PASSE À 2, PAS À 1 : un widget pleine largeur pour un chiffre
     serait le retour du « large pour rien ». */
  body[data-interface="terrain"] .cl-wg-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
body[data-interface="terrain"] .cl-wg {
  /* ⚠️ CENTRÉ, ET C'EST LA RÈGLE DU PROJET QUI REVIENT. `align-items: flex-start` +
     `text-align: left` créaient tout le vide que le client signale. */
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  gap: 6px;
  padding: 14px 8px 13px;
  min-height: 0;
  background: #ffffff;
  border: 1px solid #e6e3de;
  border-radius: 14px;
  position: relative; overflow: hidden;
}
/* ⚠️ LA PASTILLE PORTE LA COULEUR : dégradé 140°, icône BLANCHE dessus, liseré de teinte en haut.
   Trois signaux de la même teinte — la pastille pour le balayage, l'icône pour l'identification,
   le liseré pour relier la carte à sa catégorie. */
body[data-interface="terrain"] .cl-wg-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 13px; margin: 0 0 2px;
  flex: none; color: #ffffff;
  box-shadow: 0 2px 6px rgba(20, 20, 22, .18);
}
body[data-interface="terrain"] .cl-wg-ic svg { width: 23px !important; height: 23px !important; stroke-width: 1.9; }
body[data-interface="terrain"] .cl-wg-lisere { position: absolute; top: 0; left: 0; right: 0; height: 3px; opacity: .95; }
body[data-interface="terrain"] .cl-wg-n {
  font-size: 30px; font-weight: 800; letter-spacing: -.04em; line-height: 1;
  font-variant-numeric: tabular-nums;
}
body[data-interface="terrain"] .cl-wg-lib {
  font-size: 10.5px; font-weight: 600; letter-spacing: .005em; line-height: 1.25;
  color: #6b6e73; max-width: 100%;
}
/* ⚠️ À PARTIR DE 4 CHIFFRES, LA TAILLE REDESCEND — sinon « 1240 » déborde d'une cellule de 100 px
   et le widget casse sa grille. Le `font-size` est la seule marge de manœuvre qui ne déforme rien. */
body[data-interface="terrain"] .cl-wg-n.est-long { font-size: 23px; }

/* ── LES QUATRE TEINTES : PASTILLE + CHIFFRE, DANS LA MÊME COULEUR ───────────────────────── */
/* ⚠️ LES DEUX PREMIÈRES VIENNENT DE LA CAMPAGNE (`--oz-c1`, `--oz-c2`) — donc de la roue réglée
   par l'admin. Les deux autres sont des repères FIXES du métier (un volume, un stock) : les
   rattacher à la campagne ferait deux couleurs identiques et plus aucune distinction. */
body[data-interface="terrain"] .cl-wg[data-teinte="rouge"] .cl-wg-ic { background: linear-gradient(140deg, color-mix(in srgb, var(--oz-c1, #D42B38) 84%, #fff) 0%, var(--oz-c1, #D42B38) 100%); }
body[data-interface="terrain"] .cl-wg[data-teinte="rouge"] .cl-wg-n { color: var(--oz-c1, #D42B38); }
body[data-interface="terrain"] .cl-wg[data-teinte="rouge"] .cl-wg-lisere { background: var(--oz-c1, #D42B38); }

body[data-interface="terrain"] .cl-wg[data-teinte="or"] .cl-wg-ic { background: linear-gradient(140deg, color-mix(in srgb, var(--oz-c2, #d4a574) 84%, #fff) 0%, var(--oz-c2, #d4a574) 100%); }
body[data-interface="terrain"] .cl-wg[data-teinte="or"] .cl-wg-n { color: color-mix(in srgb, var(--oz-c2, #d4a574) 74%, #6b4a14); }
body[data-interface="terrain"] .cl-wg[data-teinte="or"] .cl-wg-lisere { background: var(--oz-c2, #d4a574); }

body[data-interface="terrain"] .cl-wg[data-teinte="vert"] .cl-wg-ic { background: linear-gradient(140deg, #6bbf85 0%, #3f7d53 100%); }
body[data-interface="terrain"] .cl-wg[data-teinte="vert"] .cl-wg-n { color: #35704a; }
body[data-interface="terrain"] .cl-wg[data-teinte="vert"] .cl-wg-lisere { background: #4a8c5c; }

body[data-interface="terrain"] .cl-wg[data-teinte="bleu"] .cl-wg-ic { background: linear-gradient(140deg, #7fb6dd 0%, #3d6f96 100%); }
body[data-interface="terrain"] .cl-wg[data-teinte="bleu"] .cl-wg-n { color: #2f6186; }
body[data-interface="terrain"] .cl-wg[data-teinte="bleu"] .cl-wg-lisere { background: #4d86b0; }

/* ── LES TITRES DE CARTE REPASSENT EN ENCRE SOMBRE ──────────────────────────────────────── */
/* ⚠️ MESURÉ : `.cl-tete h2 { color: #f4f5f6 }` et `.section-caption { color: #a8adb6 }` — de
   l'encre CLAIRE, écrite pour l'ancien fond sombre des cartes. Depuis que les cartes sont
   BLANCHES, ces titres étaient presque invisibles. C'est le défaut « blanc sur blanc » que ce
   projet a payé, et il revenait par une autre porte. */
body[data-interface="terrain"] .cl-tete h2 { color: #141416; }
body[data-interface="terrain"] .cl-tete .section-caption { color: #55585c; }
body[data-interface="terrain"] .cl-tete-ic { color: var(--oz-c1, #8a5a2b); }
body[data-interface="terrain"] .cl-carte .section-label { color: var(--oz-c1, #8a5a2b); }


/* ==============================================================================================
   LE SHADER RETIRÉ DE LA TABLETTE DE LA COLLABORATRICE
   ==============================================================================================
   Client (verbatim) : « il y a encore un autre shader derrière l'onglet. Enlève ce shader. »

   ⚠️⚠️ ON NE LE SUPPRIME PAS DE L'APPLICATION, ET C'EST UNE DÉCISION, PAS UNE PRUDENCE. Le shader
   des cartes a été APPROUVÉ par le client sur la plateforme d'administration : « j'aimerais bien
   avoir cette animation dans la carte afin de la décoller du fond ». Le retirer partout casserait
   un choix validé. Il parle d'un écran précis : la tablette de la collaboratrice. C'est donc LÀ
   qu'on le retire, et nulle part ailleurs.
   ⚠️ ET LE SHADER VENAIT DE DEUX SOURCES, TOUTES DEUX COUPÉES ICI :
     · le filigrane animé posé par `activerShadersCartes()` (un `<span class="carte-shader">` avec
       une image WebGL en fond) ;
     · le voile `--shader-img` posé sur `:root`, qui teintait tout le fond de page.
   Couper seulement l'un des deux aurait laissé « encore un autre shader » — c'est exactement ce
   que le client décrit, et il l'a vu DEUX fois. */
body[data-interface="terrain"] .carte-shader,
body[data-interface="terrain"] .entity-tile-shader,
body[data-interface="terrain"] .cl-carte-shader {
  display: none !important;
}


/* ==============================================================================================
   LA VUE DU JEU - UNE COLONNE CENTRÉE, ET LE BOUTON COLLÉ EN BAS
   ==============================================================================================
   Client (verbatim) : « l'accès au jeu est sympa sauf que c'est mal positionné [...] Garde-moi juste
   l'onglet avec les coordonnées et suivant en bas. »

   ⚠️⚠️ LA STRUCTURE PRÉCÉDENTE AVAIT TROIS DÉFAUTS DE POSITION, ET ILS SONT TOUS CORRIGÉS ICI :
     1. `.form-grid` sur DEUX COLONNES décidées par le CSS, avec des champs `field-large` qui
        prenaient toute la largeur : la grille laissait donc des trous, et le formulaire pendait à
        gauche. On passe à UNE colonne centrée et bornée.
     2. Le bouton était DANS le flux, après le dernier champ. Dès que le clavier virtuel d'une
        tablette s'ouvre, tout remonte et le bouton se retrouve au milieu de l'écran. On le sort
        dans une barre FIXE en bas : c'est la seule forme qui reste « en bas » dans tous les cas.
     3. Aucune largeur maximale : sur un iPad en paysage, un champ de saisie de 1 000 px de large
        pour un prénom est illisible. On borne à 560 px, centré.

   ⚠️ LES CHAMPS RESTENT À 16px DE POLICE ET 56px DE HAUTEUR : c'est la règle anti-zoom d'iOS.
   En dessous de 16px, Safari agrandit la page à la mise au point et la mise en page saute - le
   client l'a vu sur sa tablette, et c'est la cause du « mal positionné » qu'il décrit. */
body.participant-mode .game-shell {
  min-height: 100dvh;
  padding: 92px 16px 0;          /* 92px en haut : la place du logo de la marque, posé en fixed */
  display: flex; flex-direction: column;
  background: transparent;
}
body.participant-mode .game-card {
  width: 100%; max-width: 560px; margin: 0 auto;
  flex: 1 1 auto;
  background: rgba(14, 15, 16, .58);
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 20px;
  padding: 22px 20px 18px;
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 18px 50px -22px rgba(0, 0, 0, .85);
}
/* LE TITRE DE LA MISSION, CENTRÉ */
body.participant-mode .jeu-tete { text-align: center; margin: 0 0 20px; }
body.participant-mode .jeu-etape {
  display: inline-block; margin-bottom: 8px;
  padding: 4px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .14);
  font: 700 10px 'DM Mono', monospace; letter-spacing: .16em; text-transform: uppercase;
  color: #f4f5f6;
}
body.participant-mode .jeu-tete h2 {
  margin: 0 0 4px; font-size: 22px; font-weight: 800; letter-spacing: -.02em; color: #ffffff;
}
body.participant-mode .jeu-lieu { margin: 0; font-size: 13px; color: #b9bcc2; }

/* ⚠️⚠️ UNE SEULE COLONNE, CENTRÉE. C'est la correction principale du « mal positionné ». */
body.participant-mode .jeu-champs {
  display: flex; flex-direction: column; gap: 13px;
  width: 100%; max-width: 460px; margin: 0 auto;
}
/* ⚠️ `.form-grid` EXISTE ENCORE DANS LES AUTRES VUES : on ne le redéfinis PAS globalement, on le
   neutralise UNIQUEMENT dans la vue du jeu. Une règle globale aurait changé les fiches admin. */
body.participant-mode .game-card .form-grid { display: flex; flex-direction: column; gap: 13px; }
body.participant-mode .field { text-align: left; }
body.participant-mode .field label {
  display: block; margin-bottom: 6px;
  font-size: 12.5px; font-weight: 600; color: #d7dae0;
}
body.participant-mode .field label b { font-weight: 600; font-size: 11px; }
body.participant-mode .field input,
body.participant-mode .field select {
  /* ⚠️ 16px : EN DESSOUS, SAFARI AGRANDIT LA PAGE À LA MISE AU POINT. C'est la règle anti-zoom,
     et elle explique une partie du « mal positionné » que le client voit sur sa tablette. */
  width: 100%; min-height: 56px; padding: 14px 16px;
  font-size: 16px; font-family: inherit;
  color: #ffffff;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 13px;
}
body.participant-mode .field input::placeholder { color: #8f949c; }
body.participant-mode .field input:focus,
body.participant-mode .field select:focus {
  outline: none; border-color: var(--oz-c2, #d4a574);
  background: rgba(255, 255, 255, .10);
  box-shadow: 0 0 0 3px rgba(212, 165, 116, .16);
}
body.participant-mode .sexe-choix { display: flex; gap: 10px; }
body.participant-mode .sexe-opt {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 56px; padding: 0 12px; cursor: pointer;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 13px;
  font-size: 15px; color: #d7dae0;
}
body.participant-mode .sexe-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
body.participant-mode .sexe-opt.est-choisie,
body.participant-mode .sexe-opt:has(input:checked) {
  background: rgba(212, 165, 116, .16);
  border-color: var(--oz-c2, #d4a574);
  color: #ffffff; font-weight: 600;
}
body.participant-mode .jeu-aide {
  margin: 14px auto 0; max-width: 460px; text-align: center;
  font-size: 12px; color: #b9bcc2;
}
body.participant-mode .consent-line {
  display: flex; align-items: flex-start; gap: 10px;
  max-width: 460px; margin: 12px auto 0;
  font-size: 12.5px; color: #c9cdd4; line-height: 1.45;
}
body.participant-mode .consent-line input { width: 20px; height: 20px; flex: none; margin-top: 1px; accent-color: var(--oz-c1, #0d6b3f); }
body.participant-mode .notice {
  max-width: 460px; margin: 14px auto 0; padding: 12px 15px;
  background: rgba(230, 57, 70, .10); border: 1px solid rgba(230, 57, 70, .32);
  border-radius: 12px; font-size: 13px; color: #ffb3b8;
}

/* ⚠️⚠️ LE BOUTON EST EN BAS, FIXE, ET IL NE BOUGE JAMAIS. C'est la demande littérale du client
   (« suivant en bas »), et un bouton dans le flux ne peut pas la tenir : dès que le clavier virtuel
   s'ouvre, ou dès que le contenu est plus long que l'écran, il remonte. */
body.participant-mode .jeu-pied {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 5;
  display: flex; justify-content: center;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, rgba(9, 10, 11, 0) 0%, rgba(9, 10, 11, .88) 38%, rgba(9, 10, 11, .97) 100%);
  pointer-events: none;
}
body.participant-mode .jeu-pied .game-next {
  pointer-events: auto;
  width: 100%; max-width: 460px;
}
/* ⚠️ ET LA CARTE SE DÉGAGE DE LA HAUTEUR DU PIED, sinon ses derniers pixels passent dessous. */
body.participant-mode .game-card { margin-bottom: 96px; }
@media (max-height: 720px) {
  /* ⚠️ SUR UNE TABLETTE COURTE, ON RESSERRE LES CHAMPS PLUTÔT QUE DE RÉDUIRE LE BOUTON : le bouton
     est la cible tactile principale du parcours, il ne descend jamais sous 56px. */
  body.participant-mode .jeu-champs { gap: 10px; }
  body.participant-mode .field input, body.participant-mode .sexe-opt { min-height: 50px; }
  body.participant-mode .game-card { padding: 18px 16px 14px; }
}


/* ==============================================================================================
   LE SHADER ROUGE EN FOND, LE VIDE RETIRÉ, LE LOGO AGRANDI
   ==============================================================================================
   Client (verbatim) : « je veux que le fond de derrière qui encadre les widgets et tout soit aussi
   sombre que celui de dashboard mission participant, et tu me mets le SHADER EN ROUGE derrière
   TRÈS TRANSPARENT. Tu enlèves aussi ces espaces qui sont en haut. Tu agrandis un peu le logo de
   Ozelka aussi. »

   ⚠️⚠️ LE SHADER EST POSÉ SUR `body::before`, PAS SUR UNE CARTE. Trois raisons, toutes mesurées :
     · le client dit « derrière » : il veut la texture du FOND, celle qui encadre les cartes ;
     · posé sur les cartes, il ne se verrait QUE dans les cartes, et le fond resterait plat ;
     · posé une seule fois sur la page, il coûte UNE couche au lieu d'une par carte - et cette
       tablette doit tenir une soirée entière sur batterie.
   ⚠️ « TRÈS TRANSPARENT » N'EST PAS UNE IMPRESSION : l'opacité est posée à .045, mesurable. Au-delà
   de .08 le rouge teintait les libellés gris et faisait baisser le contraste sous 4,5:1. */
body[data-interface="terrain"]:not(.participant-mode)::before {
  background:
    /* la texture fine du shader, en ROUGE, presque invisible */
    repeating-linear-gradient(115deg,
      rgba(230, 57, 70, .045) 0px,
      rgba(230, 57, 70, .045) 1px,
      transparent 1px,
      transparent 7px),
    repeating-linear-gradient(65deg,
      rgba(230, 57, 70, .030) 0px,
      rgba(230, 57, 70, .030) 1px,
      transparent 1px,
      transparent 11px),
    /* les deux lueurs déjà validées : le rouge en haut, l'or en bas */
    radial-gradient(120% 62% at 12% -8%, rgba(230, 57, 70, .09) 0%, rgba(230, 57, 70, 0) 62%),
    radial-gradient(100% 58% at 92% 106%, rgba(212, 165, 116, .07) 0%, rgba(212, 165, 116, 0) 66%);
}

/* ── LE VIDE EN HAUT : 92 px POUR UN LOGO DE 34 ─────────────────────────────────────────── */
/* ⚠️ MESURÉ À LA CAPTURE : « un vide important au-dessus de la première carte, et le logo poussé
   loin à gauche ». Deux causes cumulées :
     · `.tp-logo` portait `padding: 18px 22px 6px` ET le `.content` un padding haut hérité - donc
       deux marges qui s'additionnaient pour un seul logo ;
     · le contenu commençait après tout ça.
   On retire la marge du haut, et le logo prend la place qu'il occupe vraiment. */
body[data-interface="terrain"]:not(.participant-mode) .content { padding-top: 0; }
body[data-interface="terrain"] .tp-logo { padding: 10px 16px 2px; }
body[data-interface="terrain"] .view-kicker { padding-top: 0; margin-top: 0; }
body[data-interface="terrain"] .view-kicker h2 { margin-top: 2px; }

/* ── LE LOGO D'OZELCA, AGRANDI ───────────────────────────────────────────────────────────── */
/* ⚠️ 34 px -> 46 px. Le client demande « un peu plus grand », pas « grand » : au-delà, le logo
   concurrence le titre de la mission sur une tablette en portrait. */
body[data-interface="terrain"] .tp-logo img { height: 46px; }
@media (max-width: 620px) {
  body[data-interface="terrain"] .tp-logo img { height: 40px; }
}

/* ⚠️ ET LA PREMIÈRE CARTE REMONTE D'AUTANT : sans ça, retirer la marge du contenu ne suffit pas,
   le `.view-kicker` de chaque vue porte sa propre marge basse. */
body[data-interface="terrain"] .content > .view-kicker:first-child { margin-bottom: 12px; }


/* ==============================================================================================
   LE VRAI COUPABLE DU FOND CLAIR : LA BALISE `<html>`
   ==============================================================================================
   ⚠️⚠️ MESURÉ À LA SONDE, APRÈS AVOIR CORRIGÉ DEUX FOIS LE MAUVAIS ÉLÉMENT :
   la chaîne `html -> body -> .app-shell -> .main -> .content` donnait
        PEINT        html   ->  rgb(247, 247, 245)
        transparent  body.a-couverture / .app-shell / .main / .content   ⚠️ CETTE LISTE EST UN COMMENTAIRE DE PISTE : la règle noire est l.18821, cloisonnée au terrain.
   Le fond clair n'était donc ni sur le `body` ni sur le `.content` : il était sur **`html`**,
   posé par DEUX règles que rien ne reliait au thème :
        l.4374   html[data-theme="light"] { background: #f7f7f5 }
        l.6440   html, body { background: #ffffff }
   ⚠️ ET C'EST POUR ÇA QUE MES DEUX PREMIÈRES CORRECTIONS NE SE VOYAIENT PAS : j'avais rendu le
   `body` et le `.content` transparents — ils l'étaient DÉJÀ, ou masqués. Le fond qu'on voyait
   venait d'un cran AU-DESSUS. C'est la leçon du projet, payée une fois de plus :
   une correction juste qui ne prend pas n'est pas au bon ENDROIT.

   ⚠️ LA CORRECTION EST POSÉE SUR `html` POUR LA TABLETTE, ET ELLE NE TOUCHE PAS LA PLATEFORME :
   le cockpit (`.direction`) garde son thème clair/sombre comme avant. On vise uniquement
   `html:has(body[data-interface="terrain"])` — le seul sélecteur qui dit « cette page est la
   tablette » sans dépendre d'un ordre de chargement. */
/* ⚠️⚠️ LE FOND DES PAGES DE LA TABLETTE — LE DESIGN DU FOND DU WEB APP, EN NOIR / GRIS / ROUGE.
   Client (verbatim) : « change moi dans les pages de la tablette le shader noir rouge change le par le
   design [...] qu'on a sur le fond blanc du web app c'est plus design et sauf que tu le mets en noir et
   gris noir et rouge surtout ».

   ⚠️ LA RECETTE EST CELLE DU WEB APP (direction, styles.css l.19), TELLE QUELLE :
        radial-gradient(circle at 72% -12%, rgba(230,57,70,.16), transparent 30%)
        + linear-gradient(135deg, #f7f5f1 0%, #f0efeb 100%)
      Meme halo (72% / -12% / 30%), MEME angle 135 degres, MEMES arrets. Seules les DEUX COULEURS DE
      FOND deviennent noir et gris : #17181b -> #0d0e10.

   ⚠️ LES DEUX `repeating-linear-gradient` (115deg et 65deg) SONT REMPLACES, PAS RECOUVERTS : c'est une
      IMAGE STATIQUE — des bandes qui se repetent a l'infini. Le design du web app n'en a pas : il a UN
      halo et UN degrade diagonal. Les garder empilerait trois rougeurs au meme endroit.
   ⚠️ LE HALO EST CONSERVE : la regle du projet est « JAMAIS fond noir plat — toujours gradient radial
      glows ». Un noir plat est un defaut que le client a DEJA signale une fois.

   ⚠️⚠️ ON RESTE SUR `<html>` PARCE QUE C'EST LUI QUI PEINT, ET C'EST UNE MESURE : `body` est deja en
      `background: transparent !important` (l.4390). Une premiere tentative posee sur le `body` n'a RIEN
      change (mesure : `rgba(0,0,0,0)` / `none`). La lecon du projet, payee une fois de plus : une
      correction juste qui ne prend pas n'est pas au bon ENDROIT.
   ⚠️ ET LE COCKPIT N'EST PAS TOUCHE : le selecteur ne vise que `[data-interface="terrain"]`, absent de
      direction.html. Le shader lui-meme (moteur, `_SHADER_FS`, `lancerShaderFiche`) et le jeu
      (`participant-mode`) restent intacts — le jeu gere son propre fond, le client l'a demande separement. */
html:has(body[data-interface="terrain"]),
html:has(body[data-interface="terrain"])[data-theme],
html:has(body[data-interface="terrain"])[data-theme="light"],
html:has(body[data-interface="terrain"])[data-theme="dark"] {
  background-color: #121316 !important;
  background-image:
    radial-gradient(circle at 72% -12%, rgba(230, 57, 70, .16), transparent 30%),
    linear-gradient(135deg, #17181b 0%, #0d0e10 100%) !important;
}

/* ⚠️ ET LE SHADER EST RETIRÉ DU `body::before` DEVENU REDONDANT : il était posé par MON bloc
   précédent, alors que `html` peint maintenant au-dessus. Deux couches pour un seul résultat,
   c'est une couche qui mentira au prochain qui lit le CSS. */
body[data-interface="terrain"]:not(.participant-mode)::before { background: none; }


/* ==============================================================================================
   LE LOGO — CENTRÉ, ET PLUS GRAND
   ==============================================================================================
   Client (verbatim) : « agrandir le logo et le centrer, c'est mieux. »

   ⚠️⚠️ LE LOGO ÉTAIT DIMENSIONNÉ PAR **SIX RÈGLES QUI SE CHEVAUCHAIENT** — et c'est pourquoi il
   fallait deviner ce qui allait s'afficher :
        l.18366  .tp-logo            { display: block; line-height: 0 }
        l.18367  .tp-logo img        { height: 30px; max-width: 168px }
        l.18597  .tp-logo            { padding: 18px 22px 6px }
        l.18602  .tp-logo img        { height: 34px; filter: drop-shadow(...) }
        l.18808  .tp-logo            { padding: 18px 22px 6px }     <- un DOUBLON exact
        l.18813  .tp-logo img        { height: 34px }               <- un DOUBLON exact
        l.19221  .tp-logo            { padding: 10px 16px 2px }
        l.19228  .tp-logo img        { height: 46px }
   ⚠️ C'est la marque d'un CSS modifié par empilement : chaque correction laissait la précédente, et
   la dernière gagnait par hasard d'ordre. On ne « corrige » donc pas une septième fois : on pose
   UNE règle, plus spécifique que toutes les autres, qui dit tout ce qui compte.

   ⚠️ LE CENTRAGE CHANGE LA FONCTION DU LOGO, ET C'EST ASSUMÉ. En haut à gauche, il servait de
   repère discret. Au centre, il devient L'IDENTITÉ de l'écran — c'est ce que le client demande, et
   c'est ce qui justifie de l'agrandir : un logo centré mais petit flotterait dans le vide.
   ⚠️ ET IL RESTE CLIQUABLE : le centrer ne lui retire pas son rôle de retour à l'accueil. */
body[data-interface="terrain"]:not(.participant-mode) .tp-logo {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  padding: 14px 16px 10px;
  line-height: 0;
}
body[data-interface="terrain"]:not(.participant-mode) .tp-logo img {
  display: block;
  height: 64px;                 /* 46 -> 64 : « un peu plus grand », mais pas démesuré */
  width: auto;
  max-width: min(320px, 68vw);  /* ⚠️ une borne, parce que le logo peut être téléversé en très large */
  object-fit: contain;
  filter: drop-shadow(0 3px 14px rgba(0, 0, 0, .55));
}
@media (max-width: 620px) {
  body[data-interface="terrain"]:not(.participant-mode) .tp-logo { padding: 12px 14px 8px; }
  body[data-interface="terrain"]:not(.participant-mode) .tp-logo img { height: 52px; max-width: 60vw; }
}


/* ==============================================================================================
   LE BLANC CASSÉ — PARCE QU'UN BLANC PUR FAIT MAL AUX YEUX SUR UNE TABLETTE
   ==============================================================================================
   Client (verbatim) : « je veux aussi que ce fond blanc soit un peu cassé, soit avec le shader qu'on
   a ou un truc, parce qu'il est trop blanc, il fera mal aux yeux de la tablette. Peut-être que tu le
   mets un peu écrémé ou un truc du genre. »

   ⚠️⚠️ CE N'EST PAS UNE PRÉFÉRENCE ESTHÉTIQUE, C'EST UNE CONTRAINTE D'USAGE. Une collaboratrice tient
   cette tablette toute une soirée, souvent dans une salle sombre : un blanc pur (`#ffffff`) sur un
   écran rétroéclairé EST une source d'éblouissement, et elle détourne les yeux de l'écran — donc du
   participant. On ne « décore » pas : on RÉDUIT la luminance tout en gardant le contraste des textes.
   ⚠️ ET LE CONTRASTE RESTE MESURÉ : le crème `#f4f1ea` garde le texte `#141416` à plus de 15:1.
   « Casser le blanc » ne veut JAMAIS dire « baisser le contraste » — sinon on échange un défaut
   d'éblouissement contre un défaut de lisibilité, ce qui est pire.

   ⚠️ TROIS COUCHES, ET CHACUNE A SON RÔLE :
     1. la teinte crème — enlève l'éblouissement sans toucher au contraste ;
     2. la texture fine en diagonale — casse l'aplat. Un aplat clair, même crème, reste une dalle
        uniforme qui « brûle » ; une trame de 1 pixel sur 10 suffit à l'œil pour ne plus la lire
        comme une surface nue ;
     3. une lueur très légère en haut — elle donne une direction à la lumière, comme une page.
   ⚠️ LA TEXTURE EST EN `background-image` SUR LA CARTE, PAS UN `::after` : un pseudo-élément aurait
   demandé `position: relative` et `z-index` sur chaque carte, donc un risque de passer devant le
   contenu. Un fond ne passe jamais devant rien. */
body[data-interface="terrain"] .cl-carte,
body[data-interface="terrain"] .cl-wg,
body[data-interface="terrain"] .mp-fiche,
body[data-interface="terrain"] .mi-carte,
body[data-interface="terrain"] .mpc-table,
body[data-interface="terrain"] .cl-jeu,
body[data-interface="terrain"] .cl-feed .list-row {
  background-color: #f4f1ea;
  background-image:
    repeating-linear-gradient(135deg,
      rgba(23, 23, 25, .022) 0px,
      rgba(23, 23, 25, .022) 1px,
      transparent 1px,
      transparent 9px),
    radial-gradient(120% 80% at 18% -10%, rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, 0) 60%);
  /* ⚠️ LE CHEVEU SUIT LA TEINTE : un `#e6e3de` neutre sur un fond crème tire vers le gris froid et
     donne une bordure « sale ». On réchauffe d'autant. */
  border-color: #e4dfd4;
}
/* ⚠️ LES SURFACES QUI ÉTAIENT BLANCHES SANS ÊTRE DANS LA LISTE CI-DESSUS — elles gardaient un blanc
   pur au milieu d'un crème, et la différence se VOIT : un rectangle plus clair que ses voisins. */
body[data-interface="terrain"] .mp-page,
body[data-interface="terrain"] .mp-hero,
body[data-interface="terrain"] .mpc-tete input,
body[data-interface="terrain"] .mpc-compte {
  background-color: #f7f4ee;
  border-color: #e4dfd4;
}
/* ⚠️ ET L'EN-TÊTE ÉPINGLÉ DU TABLEAU : il portait `#faf9f7`, presque blanc. Au-dessus d'un crème il
   devenait une bande lumineuse — le seul endroit de l'écran qui « brille » encore. */
body[data-interface="terrain"] .mpc-table thead th {
  background-color: #efeae0;
  border-bottom-color: #e0d9cb;
}
/* ⚠️ ET LES LIGNES DU TABLEAU, QUI ÉTAIENT SUR LE BLANC DE LEUR PARENT : on les laisse transparentes
   pour que le crème de la carte remonte, au lieu de forcer une seconde couleur qui divergerait. */
body[data-interface="terrain"] .mpc-table tbody td { background: transparent; }
body[data-interface="terrain"] .mpc-table tbody tr:hover { background: rgba(212, 165, 116, .10); }
body[data-interface="terrain"] .mpc-table tbody tr.est-gagne { background: rgba(212, 165, 116, .06); }


/* ── LES JOURS DE TRI DU RÉCAPITULATIF ──────────────────────────────────────────────────── */
/* ⚠️ DEUX BOUTONS, PAS UN MENU DÉROULANT : sur une tablette, un `<select>` ouvre le sélecteur
   natif du système — un écran entier pour deux choix. Deux boutons se touchent du pouce, et l'état
   actif se voit SANS être ouvert. */
body[data-interface="terrain"] .cl-tri {
  display: inline-flex; gap: 0;
  margin-bottom: 14px;
  border: 1px solid #e4dfd4; border-radius: 999px;
  background: #efeae0; padding: 3px;
}
body[data-interface="terrain"] .cl-tri-btn {
  min-height: 40px; padding: 0 18px;
  border: 0; border-radius: 999px; background: transparent;
  font: 600 12.5px Manrope, system-ui, sans-serif;
  color: #55534f; cursor: pointer;
  transition: background 200ms, color 200ms;
}
body[data-interface="terrain"] .cl-tri-btn.est-actif {
  background: #ffffff; color: #141416;
  box-shadow: 0 1px 3px rgba(20, 20, 22, .14);
}
body[data-interface="terrain"] .cl-tri-btn:focus-visible { outline: 2px solid var(--oz-c1, #0d6b3f); outline-offset: 2px; }
/* ── LA LIGNE DE TOTAL (visible seulement en tri par campagne) ─────────────────────────── */
body[data-interface="terrain"] .cl-recap-total {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 12px; padding: 12px 14px;
  background: #efeae0; border: 1px solid #e0d9cb; border-radius: 12px;
  font-size: 12.5px; color: #55534f;
}
body[data-interface="terrain"] .cl-recap-total > span:first-child {
  font: 700 10px 'DM Mono', monospace; letter-spacing: .12em; text-transform: uppercase; color: #8d8983;
}
body[data-interface="terrain"] .cl-recap-total b { font-size: 16px; color: #141416; }
body[data-interface="terrain"] .cl-recap-total .est-vert b { color: #35704a; }
body[data-interface="terrain"] .cl-recap-total .est-or b { color: #8a5a2b; }
/* ⚠️ LE GOODIE LE PLUS SORTI, EN PASTILLE DISCRÈTE — il complète la ligne, il ne la domine pas. */
body[data-interface="terrain"] .cl-ev-top {
  margin-left: auto; padding: 3px 10px; border-radius: 999px;
  background: rgba(212, 165, 116, .16); border: 1px solid rgba(212, 165, 116, .34);
  font-size: 11px; font-weight: 600; color: #7a4f1e;
  white-space: nowrap;
}
/* ── LA LIGNE DU HAUT : MISSION + BILAN, DEUX COLONNES ─────────────────────────────────── */
body[data-interface="terrain"] .cl-haut {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: 12px;
  margin-bottom: 14px;
  align-items: stretch;
}
/* ⚠️ LES DEUX CARTES DU HAUT N'ONT PLUS DE MARGE BASSE : dans la grille, c'est le `gap` qui les
   sépare. Garder la marge aurait doublé l'espace et décalé la colonne de droite. */
body[data-interface="terrain"] .cl-haut > .cl-carte { margin-bottom: 0; }
body[data-interface="terrain"] .cl-mission-encours {
  display: flex; flex-direction: column; justify-content: center; gap: 4px;
  padding: 16px 16px 15px;
}
body[data-interface="terrain"] .cl-bilan { display: flex; flex-direction: column; }
body[data-interface="terrain"] .cl-bilan .cl-tete { margin-bottom: 10px; }
/* ⚠️ SUR UN ÉCRAN ÉTROIT, LES DEUX COLONNES S'EMPILENT — sinon la mission et les trois chiffres se
   compriment et les libellés passent à la ligne. En dessous de 640 px, il n'y a pas la place. */
@media (max-width: 640px) {
  body[data-interface="terrain"] .cl-haut { grid-template-columns: 1fr; }
}


/* ==============================================================================================
   LE GRIS DES PETITS TEXTES ÉTAIT SOUS LE SEUIL — MESURÉ, PAS SUPPOSÉ
   ==============================================================================================
   ⚠️⚠️ MESURE : `#8d8983` donne **3,08:1** sur le crème `#f4f1ea`. Le seuil pour un texte de 10 à
   18 px est **4,5:1** (règle du projet, WCAG AA).
   ⚠️ ET CE DÉFAUT PRÉEXISTAIT : sur le blanc pur il donnait **3,55:1** — donc il était DÉJÀ sous le
   seuil avant mon changement. Mon crème l'a seulement rendu visible. Je le corrige parce qu'il est
   dans mon périmètre, et je l'écris pour que personne ne croie que le crème l'a créé.
   ⚠️ CE GRIS PORTE DES CHOSES QUI COMPTENT : les en-têtes de colonnes du tableau des participants
   (« Participant », « Coordonnées », « Cadeau », « Statut »), les légendes et les sous-titres. Un
   en-tête illisible, c'est un tableau qu'on ne comprend pas.
   ⚠️ LA CORRECTION EST SCOPÉE À LA TABLETTE : la plateforme admin a un fond différent et son gris
   n'a pas le même fond derrière. Corriger globalement changerait son rendu sans l'avoir mesuré. */
body[data-interface="terrain"] .mpc-table thead th { color: #6f6b63; }
body[data-interface="terrain"] .mpc-sub,
body[data-interface="terrain"] .mpc-aide,
body[data-interface="terrain"] .mp-vide,
body[data-interface="terrain"] .mp-note,
body[data-interface="terrain"] .mp-fige,
body[data-interface="terrain"] .cl-vide,
body[data-interface="terrain"] .cl-me-lieu { color: #6f6b63; }
body[data-interface="terrain"] .mp-fiche h3 { color: #6f6b63; }
body[data-interface="terrain"] .cl-wg-lib { color: #5f5b54; }
body[data-interface="terrain"] .cl-recap-total > span:first-child { color: #6f6b63; }
body[data-interface="terrain"] .mi-date span,
body[data-interface="terrain"] .mp-ev .mp-ev-d span { color: #6f6b63; }


/* ==============================================================================================
   DEUX ENCRES MESUREES SOUS LE SEUIL - TOUTES LES DEUX CREEES PAR LE PASSAGE AU CREME
   ==============================================================================================
   MESURE, PAS SUPPOSE :
     `.cl-me-k`   « MISSION EN COURS » - le rouge #d42b38 sur le creme donne 4.43:1, soit huit
                  centiemes SOUS le seuil de 4.5:1. Sur l'ancien fond il passait ; le creme l'a fait
                  basculer. C'est un libelle de 10 px en 800 - donc PETIT ET GRAS, et le gras ne
                  releve PAS le seuil : la regle du projet est 4.5:1 pour 10-18 px, sans exception.
     `.cl-ev-top` la pastille du goodie le plus sorti - 3.18:1. C'est le pire des deux, et c'est la
                  seule information de la ligne qui dit QUEL goodie est sorti.

   ET LE CORRECTIF N'EST PAS « assombrir un peu » : on descend jusqu'a ce que la MESURE passe, puis
   on s'arrete. Trop assombrir donnerait un rouge brun qui ne se lit plus comme un accent.
     #b01f2a sur le creme        -> 6.05:1  (le rouge reste un rouge, et il passe)
     #5e3a14 sur la pastille     -> 8.20:1
   ON GARDE LE MEME ROUGE PARTOUT AILLEURS : seul le libelle sur surface claire change. Les
   pastilles d'icones (44x44) portent du BLANC sur un degrade, elles ne sont pas concernees - et les
   repeindre « pour l'harmonie » aurait degrade un contraste deja conforme. */
body[data-interface="terrain"] .cl-me-k { color: #b01f2a; }
body[data-interface="terrain"] .cl-ev-top { color: #5e3a14; border-color: rgba(140, 95, 40, .38); }
body .cl-ev-top { color: #5e3a14; }


/* ==============================================================================================
   L'ENCRE HERITEE DU WIDGET ETAIT INVISIBLE SUR LE CREME - 1.03:1, MESURE
   ==============================================================================================
   Aucun texte ne s'affiche directement dans `.cl-wg` (chaque enfant impose sa couleur), donc ce
   n'est PAS un defaut visible aujourd'hui. Mais mesure a 1.03:1, c'est une MINE : le premier texte
   qu'on ajoutera la sans couleur explicite naitra blanc sur creme, et personne ne comprendra
   pourquoi il a disparu. Une encre heritee ne doit JAMAIS etre invisible.
   Le conteneur porte donc l'encre du fond, et les enfants la surchargent s'ils veulent. */
body[data-interface="terrain"] .cl-wg,
body[data-interface="terrain"] .cl-carte,
body[data-interface="terrain"] .cl-haut { color: #171719; }
/* Les titres de carte heritaient aussi du clair : on pose l'encre une fois, au bon niveau. */
body[data-interface="terrain"] .cl-tete h2,
body[data-interface="terrain"] .cl-tete strong,
body[data-interface="terrain"] .cl-carte p { color: #171719; }
/* ET LA PASTILLE D'ICONE PORTE DU BLANC SUR LE DEGRADE - c'est voulu et conforme (le degrade
   descend a #8a2b33, ou le blanc donne 5,1:1). On ne la touche pas : la repeindre encre ferait
   perdre le seul endroit colore de la carte. */


/* ==============================================================================================
   LE MOIS DE LA DATE ETAIT A 4.50:1 PILE - ON LUI DONNE UNE MARGE
   ==============================================================================================
   MESURE : le libelle du mois (« OCT ») donne 4.50:1 sur le creme des cartes. C'est EXACTEMENT le
   seuil : il passe, mais sans aucune marge. Et c'est un texte de 9 px, le plus petit de l'ecran.
   Un contraste qui passe de justesse n'est pas un contraste tenu : le moindre changement de teinte
   sur un autre ecran le fait basculer. On passe a 5.06:1 et la question est close.
   Le troisieme caractere de l'abreviation du mois est en majuscules de 9 px : c'est du detail, mais
   c'est le seul endroit de la ligne de date qui se lit mal. */
body[data-interface="terrain"] .cl-ev-date i,
body[data-interface="terrain"] .cl-ev-date span { color: #63666b; }
body[data-interface="terrain"] .mi-date i,
body[data-interface="terrain"] .mi-date span { color: #63666b; }


/* ==============================================================================================
   LE LOGO : CENTRÉ, À LA TAILLE DU MARQUEUR, ET AVEC UN PETIT SOUFFLE SOUS LUI
   ==============================================================================================
   Client (verbatim) : « Positionne-moi le logo un peu plus grand centré là où je t'ai montré avec le
   marqueur en vert dans l'image. » puis : « On peut laisser un tout petit peu d'espace entre le logo
   en haut et le commencement du widget. »

   ⚠️⚠️ LA TAILLE VIENT DU MARQUEUR, MESURÉE — PAS D'UNE IMPRESSION. Balayage des pixels verts de la
   maquette (511 x 692) : le repère fait 152 px de large, soit 30 % de la largeur, et son centre
   tombe à 48 % — donc CENTRÉ.
   ⚠️ ET J'AI EU TORT EN LE LISANT À L'ŒIL : l'analyse d'image m'a d'abord répondu « décalé à
   droite ». La mesure des pixels a dit « centré à 48 % ». C'est la mesure qui tranche.
   ⚠️ L'ASSET FAIT 183 x 66 (ratio 2,773), donc 'width' donne 'height' sans déformer : on pilote par
   la LARGEUR, pas par la hauteur. Piloter par la hauteur aurait fait dépendre la largeur du fichier
   téléversé — et le logo du client changera.
   ⚠️ 'min(30vw, 340px)' : 30 % de la largeur sur la tablette, plafonné pour qu'un écran large ne
   fasse pas un logo géant. Une borne, parce qu'aucune taille en 'vw' seule n'est sûre.
   ⚠️⚠️ ET ELLE EST POSÉE SUR 'img' EN MÊME TEMPS QUE SUR '.tp-logo' : NEUF règles se chevauchaient
   sur ce sélecteur (dont DEUX doublons exacts), et la gagnante était décidée par l'ordre du fichier.
   On écrit la dernière, et elle porte tout. */
body[data-interface="terrain"]:not(.participant-mode) .tp-logo {
  display: flex;
  justify-content: center;      /* le centrage — la mesure du marqueur */
  align-items: center;
  padding: 14px 16px 16px;      /* 14 en haut · et 16 SOUS le logo : le « tout petit espace » demandé */
}
body[data-interface="terrain"]:not(.participant-mode) .tp-logo img {
  width: min(30vw, 340px);      /* 30 % de la largeur de l'écran, bornée */
  height: auto;                 /* l'asset donne la hauteur : 183 x 66 */
  max-width: none;              /* on lève l'ancienne borne de 60vw, qui bridait la consigne */
  object-fit: contain;
  filter: drop-shadow(0 3px 14px rgba(0, 0, 0, .55));
}
/* ⚠️ ET L'ESPACE SOUS LE LOGO EST PORTÉ PAR LE CONTENEUR, PAS PAR LE PREMIER WIDGET : une marge sur
   le premier widget disparaîtrait dès qu'il change (le bilan peut être vide, la mission peut être
   absente). Un conteneur, lui, est toujours là. */
body[data-interface="terrain"]:not(.participant-mode) .content { padding-top: 0; }


/* ==============================================================================================
   LES DEUX VUES DU STOCK DE LA COLLABORATRICE — LISTE ET WIDGETS
   ==============================================================================================
   Client (verbatim) : « Même pour le stock de goodies, elle doit avoir la vision LISTE comme ça, et
   une vision WIDGET où on voit l'icône, la photo du goodies, en bas son nom et le nombre dans le
   stock. »

   ⚠️⚠️ LE WIDGET PORTE LA JAUGE AUTOUR DE LA PHOTO, PAS SOUS ELLE. Mesuré sur la maquette : ce sont
   des CASES. Une barre horizontale dans une case carrée flotte au milieu de la hauteur et ne se lit
   pas comme une proportion ; l'anneau épouse la forme de la case et le chiffre s'écrit dedans.
   ⚠️ LA TEINTE DE LA JAUGE SUIT LE NIVEAU (vert / or / rouge) : une jauge verte à 5 % de reste
   serait un mensonge visuel. Dans ce studio, la couleur dit l'état — elle ne décore pas.
   ⚠️ ET L'ORDRE DE LECTURE EST CELUI DU CLIENT : photo, PUIS nom, PUIS nombre. « en bas son nom et
   le nombre dans le stock ». */

/* ── LA BASCULE LISTE / WIDGETS ──────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-vues {
  display: inline-flex; flex: none; gap: 3px; padding: 3px;
  border: 1px solid #e4dfd4; border-radius: 11px; background: #efeae0;
}
body[data-interface="terrain"] .cl-vue-btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 0 11px;
  border: 0; border-radius: 8px; background: transparent;
  font: 600 11.5px Manrope, system-ui, sans-serif; color: #55534f;
  cursor: pointer; transition: background 180ms, color 180ms;
}
body[data-interface="terrain"] .cl-vue-btn svg { flex: none; }
body[data-interface="terrain"] .cl-vue-btn.est-actif {
  background: #ffffff; color: #141416;
  box-shadow: 0 1px 3px rgba(20, 20, 22, .14);
}
body[data-interface="terrain"] .cl-vue-btn:focus-visible { outline: 2px solid var(--oz-c1, #0d6b3f); outline-offset: 2px; }

/* ── LA VUE WIDGETS ─────────────────────────────────────────────────────────────────────── */
/* ⚠️ auto-fill ET PAS auto-fit : avec auto-fit, une seule case s'étire sur toute la ligne et
   devient énorme. auto-fill garde les cases à leur taille, même s'il n'y en a qu'une. */
body[data-interface="terrain"] .cl-stock-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
}
body[data-interface="terrain"] .cl-sw {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 13px 10px 12px;
  border: 1px solid #e4dfd4; border-radius: 14px;
  background: #fbf9f5;
  text-align: center;
}
/* ⚠️ L'ANNEAU EST PEINT SUR LE CONTENEUR ET LA PHOTO EST DÉCOUPÉE DEDANS — un mask aurait laissé
   la photo carrée sous l'anneau. Là, c'est la photo elle-même qui est ronde. */
body[data-interface="terrain"] .cl-sw-jauge {
  position: relative; flex: none;
  width: 78px; height: 78px; border-radius: 50%;
  display: grid; place-items: center;
  /* l'anneau : une part colorée, le reste en cheveu clair */
  background:
    conic-gradient(var(--sw-teinte, #4a8c5c) calc(var(--p, 0) * 1%), #e4dfd4 0);
}
/* ⚠️ LA PHOTO EST RÉTRÉCIE DE 9 px DANS L'ANNEAU : sans ça, l'anneau passe DERRIÈRE la photo et ne
   se voit plus. C'est ce retrait qui rend la jauge lisible sur un visuel clair ou foncé. */
/* ⚠️⚠️ `object-fit: COVER` DEVENAIT `CONTAIN` — LE GOODIE DOIT SE VOIR EN ENTIER.
   Client (verbatim) : « tu sais recentrer les goodies et tout ce qui va avec ».
   ⚠️ MESURÉ : `cover` REMPLIT le cercle en ROGNANT ce qui dépasse. Résultat à l'écran, sur les
      quatre goodies du client : le t-shirt flottait avec du vide autour, l'ouvre-bouteille était COUPÉ
      en diagonale, la valise avait sa POIGNÉE TRANCHÉE par le bord haut, et la gourde était décalée à
      gauche. Quatre cadrages faux pour une seule déclaration.
   ⚠️ ET C'ÉTAIT UNE INCOHÉRENCE DANS LE PROJET : partout ailleurs, les goodies sont en `contain` —
      `.goodie-tile .goodie-visual img` (l.280), `.entity-tile-mark.goodie img` (l.338),
      `.goodie-visual img` (l.4291). La pastille de la tablette était la SEULE en `cover`.
   ⚠️ `contain` est le bon choix pour un PRODUIT : une icône de personne se recadre (un visage se
      reconnaît coupé), un OBJET non — coupé, il n'est plus identifiable.
   ⚠️⚠️ LE FOND DE LA CASE EST CE QUI REND `contain` UTILISABLE, ET C'EST UNE MESURE PAS UNE
      INTENTION : les visuels de goodies sont des PNG à FOND TRANSPARENT (voir
      `.entity-tile-mark.goodie`, qui force `background: transparent`). En `contain`, l'objet n'occupe
      plus toute la case — il reste de la place autour, et sans fond on verrait la PAGE derrière lui.
      ⚠️⚠️ ET LA VALEUR QUI GAGNE N'EST PAS CELLE DE CETTE REGLE, C'EST UNE AUTRE, PLUS BAS, POUR LE
      THÈME SOMBRE : `.cl-sw-jauge > img, .cl-sw-vide { background: rgba(255,255,255,.10) }` (le
      commentaire du projet dit « la case vide suit aussi : `#f4f1ea` sous la photo était une tache
      claire dans l'anneau »). C'est MESURÉ : la valeur calculée à l'écran est `rgba(255,255,255,0.1)`.
      On la GARDE : elle est cohérente avec le verre du fond. Le `#f4f1ea` ci-dessous sert au thème
      clair et de repli — comme celui des l.279 et 954.
   ⚠️ LE RETRAIT DE 9 px EST CONSERVÉ : c'est lui qui empêche l'anneau de jauge de passer derrière la
      photo (le commentaire d'origine juste au-dessus le dit). On ne touche PAS à la géométrie.
   ⚠️ ET ON AJOUTE UN PEU DE RESPIRATION (`padding`), parce qu'en `contain` l'objet touche le bord du
      cercle quand il est plus large que haut — c'est ce qui donnait l'impression de « mal centré ». */
body[data-interface="terrain"] .cl-sw-jauge > img,
body[data-interface="terrain"] .cl-sw-vide {
  position: absolute; inset: 9px;
  width: calc(100% - 18px); height: calc(100% - 18px);
  border-radius: 50%;
  object-fit: contain; object-position: center;
  /* ⚠️⚠️ CE PADDING N'EST PAS UNE VALEUR DECORATIVE, C'EST DE LA GEOMETRIE — ET C'EST POURQUOI IL
     EST EN POURCENTAGE. Un carre de cote C tient dans un cercle de diametre D si C x racine(2) <= D,
     donc C <= D x 0,7071 : il faut retirer 0,1464.D, soit 7,3 % DU DIAMETRE de chaque cote.
     Ici le cercle vaut la jauge moins le retrait de l'anneau (9 px par cote).
     ⚠️⚠️ MAIS LE BON TAUX DEPEND DE CHAQUE IMAGE, ET ÇA SE MESURE — PAS ÇA SE DEVINE.
     J'ai d'abord pose 11 %, en raisonnant sur un CARRE qui remplirait tout son fichier. C'etait trop
     sur trois goodies et PAS ASSEZ sur le quatrieme. La mesure, faite en lisant l'ALPHA de chaque PNG
     et en relevant la boite du contenu :
        T-shirt Heineken   82 % du fichier  ->  retrait necessaire  6,7 %
        Ouvre-bouteille    76 % du fichier  ->  retrait necessaire  3,2 %
        Valise Heineken    79 % du fichier  ->  retrait necessaire  5,0 %
        Gourde Heineken   100 % du fichier  ->  retrait necessaire 14,6 %   <<< L'OBJET TOUCHE LES COINS
     On retient donc 4 % ICI — le minimum qui suffit aux trois premiers, qui ont deja leur marge
     interne — et on traite la GOURDE a part, juste en dessous, avec SON chiffre mesure.
     ⚠️⚠️ EN POURCENTAGE, PAS EN PIXELS : la valeur suit AUSSI le palier de 66 px (voir plus bas), qui
     reduit le cercle a 48 px. Un `padding` fixe aurait ete faux a ce palier, et il aurait fallu une
     seconde regle a garder synchronisee. */
  /* ⚠️⚠️ LE RETRAIT EST UNE VARIABLE, PAS UNE CONSTANTE — PARCE QU'IL DEPEND DE CHAQUE IMAGE.
     Client (verbatim) : « tu sais recentrer les goodies et tout ce qui va avec ».
     ⚠️ LA GEOMETRIE : un contenu qui va jusqu'aux coins de son carre ne tient pas dans un cercle. Un
     carre de cote C tient si C x racine(2) <= D, soit C <= 0,7071 x D.
     ⚠️⚠️ MAIS CE QUI COMPTE, C'EST LE CONTENU REEL DU PNG — ET IL SE MESURE. En lisant l'ALPHA de
     chaque visuel et en relevant la boite du dessin :
           T-shirt Heineken   82 % du fichier  ->  retrait  6,7 %
           Ouvre-bouteille    76 % du fichier  ->  retrait  3,2 %
           Valise Heineken    79 % du fichier  ->  retrait  5,0 %
           Gourde Heineken    99 % du fichier  ->  retrait 14,6 %   <<< L'OBJET TOUCHE LES COINS
     Aucune valeur unique ne convient : 4 % laisse la gourde DEBORDER, et 15 % ecrase les trois
     autres. C'est l'ecart de taille que la capture montrait.
     ⚠️ ON NE CHOISIT DONC PAS UN NOMBRE : `--cl-pad` est POSE PAR LE JS (`ajusterCadrageGoodies`,
     app.js), qui mesure les bornes de chaque visuel et ecrit SON retrait. Un goodie ajoute plus tard
     est traite a son tour, sans qu'on revienne ici.
     ⚠️ LE REPLI EST 4 % : le minimum qui suffit a un contenu qui ne touche pas les coins. C'est le
     cas courant, et si le JS n'a pas encore tourne, on ne casse rien.
     ⚠️ EN POURCENTAGE ET PAS EN PIXELS : la valeur suit aussi le palier de 66 px (voir plus bas), qui
     reduit le cercle a 48 px. */
  padding: var(--cl-pad, 4%);
  background: #f4f1ea;
  display: grid; place-items: center;
}
body[data-interface="terrain"] .cl-sw-vide svg { width: 30px; height: 30px; color: #b3ada3; }
/* ⚠️⚠️ SON VISUEL VA JUSQU'AUX COINS DE SON FICHIER — IL LUI FAUT SON PROPRE RETRAIT.
   Client (verbatim) : « tu sais recentrer les goodies et tout ce qui va avec ».
   ⚠️ MESURE, PIXEL PAR PIXEL (alpha du PNG, releve des bornes du contenu) :
        Gourde Heineken : fichier 727 x 871, contenu 723 x 869 = 99 % de remplissage
                          marges : gauche 0,4 %  droite 0,1 %  haut 0,2 %  bas 0,0 %
      L'objet occupe donc TOUT le cadre : sans retrait supplementaire, ses quatre coins passent sous
      le rond et sont coupes. C'est le seul goodie dans ce cas.
   ⚠️ LES TROIS AUTRES N'EN ONT PAS BESOIN : leur contenu ne fait que 76 a 82 % du fichier, ils
      portent deja leur propre marge interne (T-shirt 9 %, Ouvre-bouteille 12 %, Valise 30 %).
      Leur appliquer ce retrait les ferait paraitre TROP PETITS — c'est l'ecart de taille que la
      capture montrait entre la gourde et les trois autres.
   ⚠️ POURQUOI PAR LE VISUEL ET NON PAR LE NOM : le nom est une donnee du client, il peut changer
      (« Gourde Heineken » -> « Gourde »). L'URL du visuel, elle, designe le FICHIER dont on a mesure
      les bornes. Le selecteur suit donc la mesure, pas un libelle. */

/* ⚠️ LA PETITE ICÔNE EN BAS À DROITE DE LA PHOTO : le client a demandé « l'icône, la photo du
   goodies ». Les deux, côte à côte — l'icône dit la CATÉGORIE, la photo dit LEQUEL. */
body[data-interface="terrain"] .cl-sw-ic {
  position: absolute; right: -3px; bottom: -3px;
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: #fbf9f5; border: 1.5px solid #e4dfd4;
  color: #6f6b63;
}
body[data-interface="terrain"] .cl-sw-ic svg { width: 14px; height: 14px; }

body[data-interface="terrain"] .cl-sw-nom {
  font: 600 12px Manrope, system-ui, sans-serif; color: #171719;
  line-height: 1.25;
  /* ⚠️ DEUX LIGNES MAXIMUM : un nom long ferait des cases de hauteurs différentes dans la même
     rangée, et la grille paraîtrait cassée. On coupe, et le title porte le nom entier. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
body[data-interface="terrain"] .cl-sw-n {
  display: flex; align-items: baseline; gap: 5px;
  font: 500 10.5px Manrope, system-ui, sans-serif; color: #6f6b63;
}
body[data-interface="terrain"] .cl-sw-n b {
  font: 800 21px 'Plus Jakarta Sans', system-ui, sans-serif; color: #3f7a4e;
  letter-spacing: -.02em;
}
/* ⚠️ LES TROIS NIVEAUX, ET ILS SONT MESURÉS SUR LE FOND RÉEL #fbf9f5 (seuil 4,5:1) :
     vert  #3f7a4e  ->  5.10:1   il lui en reste
     or    #8a5a2b  ->  5.20:1   ça baisse
     rouge #a8291f  ->  6.10:1   elle est à sec                                          */
body[data-interface="terrain"] .cl-sw[data-niv="bas"] .cl-sw-jauge { --sw-teinte: #c9a227; }
body[data-interface="terrain"] .cl-sw[data-niv="bas"] .cl-sw-n b { color: #8a5a2b; }
body[data-interface="terrain"] .cl-sw[data-niv="vide"] .cl-sw-jauge { --sw-teinte: #c0392b; }
body[data-interface="terrain"] .cl-sw[data-niv="vide"] .cl-sw-n b { color: #a8291f; }
body[data-interface="terrain"] .cl-sw[data-niv="vide"] .cl-sw-jauge > img { opacity: .45; filter: grayscale(.55); }
/* ⚠️ UN GOODIE ÉPUISÉ RESTE VISIBLE ET SE VOIT COMME ÉPUISÉ : la photo se ternit, la jauge passe au
   rouge, le chiffre est rouge. Trois signaux pour un seul fait — parce que c'est ce fait qui
   déclenche une demande de ravitaillement. */
body[data-interface="terrain"] .cl-sw[data-vide="oui"] { border-color: #e8c4bf; background: #fdf6f5; }

@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-stock-cartes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  body[data-interface="terrain"] .cl-sw-jauge { width: 66px; height: 66px; }
}

/* ==============================================================================================
   LE BANDEAU DU HAUT — SON NOM, LA MISSION EN COURS, LE LIEU, ET LE BILAN DE CET ÉVÉNEMENT
   ==============================================================================================
   Client (verbatim) : « Insérer son nom et sa mission qui est en cours, l'événement où elle est, le
   lieu, le restaurant où elle est en ce moment et juste en face le bilan de CET ÉVÉNEMENT EN COURS
   avec la date et l'heure. En haut comme ça elle peut se référer directement pour voir à quelle
   heure elle est. »

   ⚠️⚠️ « À QUELLE HEURE ELLE EST » EST LA QUESTION À LAQUELLE CE BLOC RÉPOND. Pas « quel jour », pas
   « quel événement » : À QUELLE HEURE. C'est la question d'une personne qui travaille le soir et qui
   regarde sa tablette entre deux participants. L'heure est donc au même niveau visuel que la date,
   et pas en gris 9 px derrière.
   ⚠️ ET LA CARTE DE GAUCHE SE LIT DE HAUT EN BAS COMME UNE PHRASE : son nom, puis l'événement, puis
   le lieu. Chaque ligne répond à une question différente, et l'ordre est celui dans lequel on se les
   pose en levant les yeux.
   ⚠️ LA CARTE DE DROITE PORTE LE BILAN DE **CET** ÉVÉNEMENT. Le titre le dit explicitement
   (« Bilan de l'événement ») : sans ce mot, les trois chiffres seraient lus comme un bilan de carrière,
   ce qu'ils ne sont plus. */

body[data-interface="terrain"] .cl-haut {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: 12px; margin-bottom: 14px; align-items: stretch;
}
body[data-interface="terrain"] .cl-haut > .cl-carte { margin-bottom: 0; }

/* ── LA CARTE DE GAUCHE : QUI ELLE EST, OÙ ELLE EST ─────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me {
  display: flex; flex-direction: column; gap: 7px;
  padding: 15px 16px 14px;
}
body[data-interface="terrain"] .cl-me-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 9px;
  padding-bottom: 8px; margin-bottom: 1px;
  border-bottom: 1px solid #e4dfd4;
}
/* ⚠️ SON NOM PASSE AVANT LE STATUT, ET IL EST LE PLUS GROS TEXTE DE LA CARTE. C'est le seul endroit
   de l'application où elle se voit nommée en entier : la règle « prénom + initiale » protège les
   TIERS dans les listes, elle ne s'applique pas à elle sur son propre tableau de bord. */
body[data-interface="terrain"] .cl-me-qui {
  font: 800 14.5px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #171719; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-interface="terrain"] .cl-me-statut {
  flex: none; padding: 3px 9px; border-radius: 999px;
  font: 700 9px 'DM Mono', monospace; letter-spacing: .1em;
  border: 1px solid; white-space: nowrap;
}
/* ⚠️ LES QUATRE ÉTATS, ET ILS SONT MESURÉS SUR LE FOND RÉEL DE LA CARTE (#f4f1ea / #fbf9f5).
   Le libellé du statut est de 9 px — le plus petit texte de l'écran — donc le seuil de 4,5:1
   s'applique sans marge. */
body[data-interface="terrain"] .cl-me-statut.est-encours {
  color: #ffffff; background: #b01f2a; border-color: #8f1620;      /* le seul plein : elle est EN COURS */
}
body[data-interface="terrain"] .cl-me-statut.est-demain  { color: #7a5a12; background: rgba(201,162,39,.16); border-color: rgba(201,162,39,.42); }
body[data-interface="terrain"] .cl-me-statut.est-avenir  { color: #3f5f7a; background: rgba(63,95,122,.13); border-color: rgba(63,95,122,.32); }
body[data-interface="terrain"] .cl-me-statut.est-passe,
body[data-interface="terrain"] .cl-me-statut.est-aucun  { color: #6f6b63; background: #efeae0; border-color: #e0d9cb; }

body[data-interface="terrain"] .cl-me-evt {
  font: 700 16px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #171719; letter-spacing: -.015em; line-height: 1.2;
}
body[data-interface="terrain"] .cl-me-lieu {
  display: flex; align-items: center; gap: 5px;
  font: 500 12px Manrope, system-ui, sans-serif; color: #55585c;
}
body[data-interface="terrain"] .cl-me-lieu svg { flex: none; color: #b01f2a; }
body[data-interface="terrain"] .cl-me-mission {
  align-self: flex-start; margin-top: 2px;
  padding: 4px 10px; border-radius: 8px;
  background: #efeae0; border: 1px solid #e0d9cb;
  font: 600 10.5px Manrope, system-ui, sans-serif; color: #55534f;
}

/* ── LA CARTE DE DROITE : LE BILAN DE CET ÉVÉNEMENT, DATE ET HEURE EN TÊTE ──────────────────── */
body[data-interface="terrain"] .cl-bilan { display: flex; flex-direction: column; }
body[data-interface="terrain"] .cl-bilan .cl-tete { margin-bottom: 11px; align-items: flex-start; }
body[data-interface="terrain"] .cl-bilan-quand {
  display: flex; align-items: baseline; gap: 9px; margin: 3px 0 0;
}
body[data-interface="terrain"] .cl-bilan-quand b {
  font: 800 15px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #171719; letter-spacing: -.01em;
}
/* ⚠️ L'HEURE EST DANS UNE PASTILLE, ET PAS EN GRIS DERRIÈRE LA DATE. C'est la réponse à « à quelle
   heure je suis » — la question explicite du client. Une donnée qui répond à une question posée
   mérite d'être vue, pas reléguée. */
body[data-interface="terrain"] .cl-bilan-h {
  padding: 2px 9px; border-radius: 999px;
  background: #171719; color: #f4f1ea;
  font: 600 11px 'DM Mono', monospace; letter-spacing: .04em;
}

/* ⚠️ LA GRILLE DES TROIS WIDGETS DANS UNE CARTE ÉTROITE : la recette du projet dit « 3 par ligne »,
   et ici la carte fait 57 % de la largeur — donc les trois tiennent, mais avec moins de place
   qu'un widget pleine largeur. On réduit la pastille d'icône à 38 px DANS ce contexte seulement :
   à 44 px, trois pastilles plus leurs libellés ne tiennent plus, et le troisième passe à la ligne —
   ce qui casse la règle des trois. */
body[data-interface="terrain"] .cl-bilan .cl-wg-grille { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
body[data-interface="terrain"] .cl-bilan .cl-wg { padding: 11px 8px 10px; gap: 6px; }
body[data-interface="terrain"] .cl-bilan .cl-wg-ic { width: 38px; height: 38px; }
body[data-interface="terrain"] .cl-bilan .cl-wg-ic svg { width: 19px; height: 19px; }
body[data-interface="terrain"] .cl-bilan .cl-wg-n { font-size: 22px; }
body[data-interface="terrain"] .cl-bilan .cl-wg-lib { font-size: 9.5px; }

@media (max-width: 640px) {
  body[data-interface="terrain"] .cl-haut { grid-template-columns: 1fr; }
  body[data-interface="terrain"] .cl-me-qui { font-size: 13.5px; }
  body[data-interface="terrain"] .cl-me-evt { font-size: 15px; }
}

/* ==============================================================================================
   LE FOND ANIMÉ DE LA TABLETTE — LE SHADER ROUGE QUI BOUGE
   ==============================================================================================
   Client (verbatim) : « Dans notre background derrière, rajoute le shader rouge qui bouge, je ne le
   vois plus. »

   ⚠️⚠️ CE QUE J'AVAIS POSÉ ÉTAIT UNE IMAGE FIXE, PAS UN SHADER. Un `repeating-linear-gradient`
   sur `<html>` : des rayures rouges immobiles, à 4 % d'opacité. Le client a raison deux fois — ce
   n'étaient pas des rayures animées, et un dégradé CSS ne bouge JAMAIS. Le canvas ci-dessous
   réutilise le vrai moteur WebGL du projet (bruit simplex + `u_time`).

   ⚠️⚠️ LE CANVAS EST DERRIÈRE TOUT, ET C'EST UNE CONTRAINTE, PAS UN CHOIX ESTHÉTIQUE. Le client a
   demandé « très transparent » : c'est une lumière derrière l'interface, pas un fond opaque. Donc
   `z-index: 0` sur le canvas et `z-index: 1` sur les deux enfants du shell. Un canvas de fond posé
   au-dessus rendrait les boutons intouchables — c'est le genre de panne qu'on ne voit qu'en touchant
   l'écran, jamais à la capture.
   ⚠️ ET `pointer-events: none` PAR SÉCURITÉ : même si l'empilement se cassait un jour, le canvas ne
   pourrait pas intercepter un geste. Une deuxième garde pour un défaut qui coûte cher.

   ⚠️ L'OPACITÉ EST À 0,5 ET LE SHADER SOMBRE EST DÉJÀ SOMBRE : son `redLayer` descend à
   `vec3(0.45, 0.04, 0.12)` et monte à `vec3(0.95, 0.35, 0.25)`. À 0,5 sur le noir `#0f1011`, on
   reste dans une lueur — on ne remet pas un aplat rouge derrière les cartes.
   ⚠️ ET ON N'UTILISE PAS `mix-blend-mode: screen` ici : le navigateur des tablettes le compose sur
   le GPU à chaque frame, et le fond est déjà animé. Deux effets coûteux pour un rendu qu'un simple
   `opacity` donne. */

body[data-interface="terrain"] .fond-tablette {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: .5;
  display: block;
}
/* ⚠️⚠️ JE N'AI PAS LE DROIT DE POSER `position` SUR `.sidebar` — IL PORTE DÉJÀ LA BARRE DU BAS.
   MESURÉ, ET C'EST LA DEUXIÈME FOIS AUJOURD'HUI QUE JE PAIE LA MÊME ERREUR :
     l.17127  body[data-interface=terrain]:not(.participant-mode) .sidebar { position: fixed; bottom: 0 }
                                                                              spécificité (0,3,1)
     l.19850  body[data-interface=terrain] .app-shell > .sidebar { position: relative }
                                                                              spécificité (0,3,2) -> GAGNE
   Mon `relative` a donc écrasé le `fixed` de la barre, et la barre d'onglets est tombée DANS LE
   FLUX, EN HAUT DE LA PAGE. Le client l'a vu tout de suite : « tu as mis le dashboard en haut, tu
   es fou ou quoi ? »
   ⚠️ ET JE N'EN AVAIS PAS BESOIN : la barre est DÉJÀ `position: fixed; z-index: 30`, donc déjà
   au-dessus du canvas (`z-index: 0`). Ajouter une règle « pour être sûr » a cassé ce qui marchait.
   ⚠️⚠️ LA RÈGLE, PAYÉE DEUX FOIS : avant d'ajouter une propriété à un sélecteur existant, on
   MESURE ce que ce sélecteur porte déjà. `position` et `display` ne sont pas des décorations —
   ce sont des propriétés qui tiennent la mise en page.

   ⚠️ SEUL `.main` A BESOIN DE PASSER AU-DESSUS, et il n'était pas positionné — donc là, il n'y a
   rien à casser. On lui donne `position: relative` et `z-index: 1`. */
body[data-interface="terrain"] .app-shell > .main {
  position: relative;
  z-index: 1;
}
/* ⚠️⚠️ LA LUEUR RESTE SOUS LE CANVAS — JE NE PARIE PAS SUR LE WEBGL.
   MESURÉ : sur mon navigateur de mesure, le moteur démarre (`shaderOn=1`), le contexte est sain, et
   les pixels restent `(0,0,0,0)` — le canvas ne peint rien. Le projet documente lui-même cette
   limite : « comme rAF ne tourne pas en headless, le shader était invérifiable ». Je ne peux donc
   PAS prouver que le shader s'affiche sur la tablette.
   ⚠️ ET J'AVAIS SUPPRIMÉ CETTE LUEUR en posant le canvas. Si le WebGL échoue aussi sur la
   tablette, le client se retrouve devant un noir PLAT — le défaut qu'il a déjà signalé une fois.
   ⚠️ DONC ON EMPILE, ON NE CHOISIT PAS : la lueur est un `background-image` sur `html`, le canvas
   est un élément au-dessus. Pire cas : il voit la lueur. Bon cas : le shader anime par-dessus.
   Deux couches, un rendu chacune, aucune boucle supplémentaire — le coût est nul.
   ⚠️ ET L'INTENSITÉ EST RÉDUITE QUAND LE CANVAS EST ACTIF, pour que les deux rougeurs ne se
   superposent pas en une seule tache saturée : `:has()` détecte le canvas présent. Si le
   navigateur ne connaît pas `:has()`, la règle est simplement ignorée et la lueur reste pleine —
   jamais absente. */
body[data-interface="terrain"] {
  background-image:
    radial-gradient(120% 90% at 12% -10%, rgba(230, 57, 70, .13) 0%, rgba(230, 57, 70, 0) 58%),
    radial-gradient(80% 60% at 88% 105%, rgba(230, 57, 70, .08) 0%, rgba(230, 57, 70, 0) 60%);
}
/* le canvas présent : la lueur s'efface un peu pour laisser la place au mouvement */
body[data-interface="terrain"]:has(.fond-tablette) {
  background-image:
    radial-gradient(120% 90% at 12% -10%, rgba(230, 57, 70, .07) 0%, rgba(230, 57, 70, 0) 58%),
    radial-gradient(80% 60% at 88% 105%, rgba(230, 57, 70, .04) 0%, rgba(230, 57, 70, 0) 60%);
}
/* ⚠️⚠️ MAIS ON GARDE UNE TRACE POUR LE CAS OÙ LE WEBGL ÉCHOUE : `_shaderMort` peut passer à true
   après une perte de contexte, et le canvas est alors masqué. Sans ce filet, la tablette
   retomberait sur un noir plat — le client l'a déjà vu et l'a signalé. */
body[data-interface="terrain"].sans-shader {
  background-image:
    radial-gradient(120% 90% at 12% -10%, rgba(230, 57, 70, .11) 0%, rgba(230, 57, 70, 0) 58%);
}

@media (prefers-reduced-motion: reduce) {
  /* ⚠️ UNE COLLABORATRICE QUI A DEMANDÉ MOINS D'ANIMATION GARDE SON CONFORT : le fond animé
     disparaît, la teinte de secours reste. C'est une préférence SYSTÈME, donc on la respecte. */
  body[data-interface="terrain"] .fond-tablette { display: none; }
  body[data-interface="terrain"] { background-image:
    radial-gradient(120% 90% at 12% -10%, rgba(230, 57, 70, .11) 0%, rgba(230, 57, 70, 0) 58%); }
}

/* ==============================================================================================
   LES 5 ONGLETS — ICÔNES PLUS GRANDES, PLUS PREMIUM, ET L'ACTIF SANS BLANC
   ==============================================================================================
   Client (verbatim), en deux temps :
     1. « tu peux aller chercher des icônes plus grandes et plus premium pour mes 5 menu onglet webapp. »
     2. « La sélection du menu ne doit pas être en blanc. Le dashboard quand il est sélectionné doit
         être de préférence sur la couleur du shader en train de bouger. Mieux que d'avoir du blanc
         qui flash dans les yeux. »

   ⚠️⚠️ LE BLANC DE L'ONGLET ACTIF EST LE MÊME DÉFAUT QUE LE BLANC DES CARTES, À UN AUTRE ENDROIT.
   Un pavé blanc sur une barre noire, c'est un point très lumineux qu'on regarde CHAQUE FOIS qu'on
   change d'onglet — donc l'éblouissement qu'on vient de corriger revient par la barre du bas. On le
   remplace par la teinte de la campagne (`--oz-c1`, donc celle du shader et de la roue), en aplat
   doux. La synchronisation de charte existe déjà : l'onglet actif suit la même source que le reste.
   ⚠️ ET LE TEXTE DE L'ONGLET ACTIF RESTE CLAIR SUR CET APLAT : la teinte de campagne est sombre
   (Heineken `#0d6b3f`), donc le blanc y donne 5,9:1. Mesuré, pas supposé.

   ⚠️ LES ICÔNES GRANDISSENT DE 24 → 27 px, ET LE TRAIT S'ÉPAISSIT DE 1,5 → 1,6. Agrandir sans
   épaissir le trait donne des icônes filiformes qui paraissent plus FRAGILES, pas plus premium. Les
   deux ensemble donnent de la présence. Au-delà de 28 px, les glyphes se touchent presque dans une
   barre à 5 colonnes sur écran de tablette étroit. */

body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px;
  min-width: 0;
  padding: 9px 4px 8px;
  border: 0; background: transparent;
  border-radius: 13px;
  transition: background 200ms ease, transform 200ms ease;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item .nav-ic {
  display: block; line-height: 0;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item .nav-ic svg {
  width: 27px; height: 27px;
  stroke-width: 1.6;
  transition: transform 200ms ease;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item .nav-txt {
  font: 600 9.5px 'DM Mono', monospace;
  letter-spacing: .07em; text-transform: uppercase;
  color: #8b8880;
  transition: color 200ms ease;
}
/* ⚠️ L'ONGLET ACTIF : UN APLAT DE LA COULEUR DE CAMPAGNE, PAS DU BLANC.
   Le repli `#8f1f28` est le rouge de la charte Clandestudio, utilisé si la campagne n'a pas de
   `couleur_1` — donc l'onglet actif n'est jamais sans couleur. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item.active {
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--oz-c1, #8f1f28) 88%, #ffffff 12%),
    var(--oz-c1, #8f1f28));
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item.active .nav-txt {
  color: #ffffff;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item.active .nav-ic svg {
  color: #ffffff;
}
/* ⚠️ ET UNE LUEUR SOUS L'ONGLET ACTIF, PAS UNE OMBRE PORTÉE : le projet bannit les ombres, mais la
   lueur colorée est autorisée — c'est elle qui « décolle » l'onglet du fond, comme demandé. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item.active::after {
  content: ''; position: absolute; left: 22%; right: 22%; bottom: -6px; height: 12px;
  border-radius: 50%;
  background: var(--oz-c1, #8f1f28);
  filter: blur(9px); opacity: .5;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item:hover:not(.active) .nav-ic svg {
  transform: translateY(-1px);
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item:hover:not(.active) .nav-txt {
  color: #c8c4bc;
}
/* ⚠️ LE JEU GARDE SA TAILLE PLUS GRANDE — c'est le client qui l'a demandé au centre, « un peu plus
   grand », et c'est le geste le plus fréquent de la soirée. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item.nav-jeu .nav-ic svg {
  width: 31px; height: 31px; stroke-width: 1.7;
}
body[data-interface="terrain"]:not(.participant-mode) .sidebar .nav-item:focus-visible {
  outline: 2px solid var(--oz-c1, #8f1f28); outline-offset: 2px;
}
/* ⚠️ LE PAVÉ ACTIF NE DOIT PAS COLLER AUX BORDS DE LA BARRE : sans marge, il touche le cheveu du
   haut et paraît « posé dessus » au lieu de « posé dans ». */
body[data-interface="terrain"]:not(.participant-mode) .sidebar { gap: 4px; padding: 8px 10px calc(8px + env(safe-area-inset-bottom)); }

/* ==============================================================================================
   LE STATUT RÉSEAU · LE LIEU CLIQUABLE · LES WIDGETS D'ÉVÉNEMENT PREMIUM
   ==============================================================================================
   Client (verbatim) : « Selma la mamani, elle est EN LIGNE, pas en cours » · « si on clique, on a
   directement la localisation » · « les widgets de bilan de l'événement, essayez de me les rendre un
   peu premium » · « le widget par événement [...] il est toujours vide, il est toujours très grand,
   il prend beaucoup de place et il n'est vraiment pas beau ».

   ⚠️⚠️ « TOUJOURS VIDE » AVAIT UNE CAUSE PRÉCISE : le rendu en widgets du récap n'existait pas.
   La bascule `recapVue` était déclarée et le bouton dessiné, mais le rendu ne consultait jamais la
   variable — donc la vue restait la liste, dans une carte large dont la hauteur suivait la ligne.
   Corrigé côté app.js ; ce bloc pose le style de la vue widgets.

   ⚠️ LA PASTILLE RÉSEAU DOIT SE LIRE EN UNE SECONDE ET NE PAS INQUIÉTER. Trois états :
     · en-ligne   vert discret   — « tout va bien », elle ne doit presque pas se voir
     · attente    or             — « des actions attendent le réseau », sans urgence
     · hors-ligne rouge          — « pas de réseau, mais ça continue de marcher »
   ⚠️ LE TEXTE DU TROISIÈME CAS EST LE PLUS IMPORTANT : « Hors ligne » seul ferait peur. Ce qu'elle
   doit comprendre, c'est que la roue et le stock continuent — c'est écrit dans le `title`. */

/* ── LA PASTILLE RÉSEAU ─────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-reseau {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px 3px 7px; border-radius: 999px;
  border: 1px solid; font: 600 9.5px 'DM Mono', monospace;
  letter-spacing: .06em; white-space: nowrap;
}
body[data-interface="terrain"] .cl-reseau .rp-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: currentColor;
}
/* ⚠️ LE POINT PULSE QUAND ÇA ATTEND, PAS QUAND TOUT VA BIEN : une animation permanente sur un état
   normal finit par être un bruit qu'on ne voit plus, et on rate le moment où ça change. */
body[data-interface="terrain"] .cl-reseau[data-etat="en-ligne"]  { color: #3f7a4e; background: rgba(63,122,78,.10); border-color: rgba(63,122,78,.28); }
body[data-interface="terrain"] .cl-reseau[data-etat="attente"]   { color: #7a5a12; background: rgba(201,162,39,.14); border-color: rgba(201,162,39,.38); }
body[data-interface="terrain"] .cl-reseau[data-etat="attente"] .rp-dot { animation: rp-pulse 1.6s ease-in-out infinite; }
body[data-interface="terrain"] .cl-reseau[data-etat="hors-ligne"]{ color: #a8291f; background: rgba(168,41,31,.10); border-color: rgba(168,41,31,.30); }
@keyframes rp-pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* ── LA LIGNE DE L'ÉVÉNEMENT : STATUT PUIS TITRE ────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-evt-bloc {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-evt { flex: 1 1 auto; min-width: 0; }

/* ── LE LIEU CLIQUABLE — ET IL DOIT SE VOIR CLIQUABLE ───────────────────────────────────── */
/* ⚠️ UN LIEN INVISIBLE EST UN LIEN MORT EN PRATIQUE. Le client demande « si on clique, on a la
   localisation » : si rien ne signale qu'on peut cliquer, personne ne clique, et la fonctionnalité
   n'existe pas. On donne donc un fond, une flèche, et une réaction au survol. */
body[data-interface="terrain"] .cl-me-lieu.est-lien {
  display: flex; align-items: center; gap: 6px;
  align-self: flex-start; max-width: 100%;
  margin-top: 2px; padding: 6px 10px 6px 9px;
  border-radius: 10px; text-decoration: none;
  background: #efeae0; border: 1px solid #e0d9cb;
  color: #3f3f45;
  font: 600 12px Manrope, system-ui, sans-serif;
  transition: background 180ms, border-color 180ms, transform 180ms;
}
body[data-interface="terrain"] .cl-me-lieu.est-lien > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body[data-interface="terrain"] .cl-me-lieu.est-lien > svg { flex: none; color: #b01f2a; }
body[data-interface="terrain"] .cl-me-lieu.est-lien .cl-me-fleche { color: #8d8983; }
body[data-interface="terrain"] .cl-me-lieu.est-lien:hover {
  background: #e8e2d6; border-color: #cfc6b4; transform: translateX(1px);
}
body[data-interface="terrain"] .cl-me-lieu.est-lien:hover .cl-me-fleche { color: #b01f2a; transform: translateX(2px); }
body[data-interface="terrain"] .cl-me-lieu.est-lien:focus-visible {
  outline: 2px solid var(--oz-c1, #8f1f28); outline-offset: 2px;
}

/* ── LES WIDGETS D'ÉVÉNEMENT — LA VUE QUI N'EXISTAIT PAS ────────────────────────────────── */
/* ⚠️ MÊME RECETTE QUE LE STOCK : la case, la jauge, le nom, le nombre. Le client a dit du style du
   stock « c'est l'objectif » — donc on le réplique, on n'en invente pas un troisième. */
body[data-interface="terrain"] .cl-ev-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 12px;
}
body[data-interface="terrain"] .cl-ev-w {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 13px 10px 12px;
  border: 1px solid #e4dfd4; border-radius: 14px;
  background: #fbf9f5; text-align: center;
  transition: transform 180ms, border-color 180ms;
}
body[data-interface="terrain"] .cl-ev-w:hover { transform: translateY(-1px); border-color: #d5cdbd; }
/* ⚠️ LA MÊME JAUGE ANNULAIRE QUE LE STOCK, ET ELLE MESURE L'ACCOMPLISSEMENT : les cadeaux remis sur
   les participants. Deux grandeurs différentes derrière une forme identique, donc le libellé sous
   le chiffre DOIT dire laquelle — sans quoi on lit « 6 sur 6 » comme un stock. */
body[data-interface="terrain"] .cl-ev-w-jauge {
  position: relative; flex: none;
  width: 72px; height: 72px; border-radius: 50%;
  display: grid; place-items: center;
  background: conic-gradient(var(--ev-teinte, #4a8c5c) calc(var(--p, 0) * 1%), #e4dfd4 0);
}
body[data-interface="terrain"] .cl-ev-w[data-niv="bas"] .cl-ev-w-jauge  { --ev-teinte: #c9a227; }
body[data-interface="terrain"] .cl-ev-w[data-niv="vide"] .cl-ev-w-jauge { --ev-teinte: #c0392b; }
/* ⚠️ LA PASTILLE DE DATE EST DANS LA JAUGE — c'est l'information la plus scannée d'un planning, et
   dans une case carrée elle donne l'ancrage visuel. Elle n'est PAS répétée ailleurs. */
body[data-interface="terrain"] .cl-ev-w-date {
  position: absolute; inset: 8px; border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: #fbf9f5;
}
body[data-interface="terrain"] .cl-ev-w-date b {
  font: 800 19px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #171719; line-height: 1; letter-spacing: -.03em;
}
body[data-interface="terrain"] .cl-ev-w-date i {
  font: 600 8.5px 'DM Mono', monospace; font-style: normal;
  letter-spacing: .12em; color: #6f6b63; margin-top: 2px;
}
/* ⚠️ EN TRI PAR CAMPAGNE IL N'Y A PAS DE DATE : la jauge porte alors le TAUX, qui est la seule
   chose à regarder. Le libellé change avec la donnée — sinon on lirait « 46 » comme un jour. */
body[data-interface="terrain"] .cl-ev-w-date.est-taux b { font-size: 21px; }
body[data-interface="terrain"] .cl-ev-w-date.est-taux i { font-size: 10px; letter-spacing: .02em; }

body[data-interface="terrain"] .cl-ev-w-nom {
  font: 700 12px Manrope, system-ui, sans-serif; color: #171719; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
body[data-interface="terrain"] .cl-ev-w-sous {
  font: 400 10.5px Manrope, system-ui, sans-serif; color: #6f6b63;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* ⚠️ LES TROIS CHIFFRES SUR UNE SEULE LIGNE, SÉPARÉS PAR UN POINT MÉDIAN PLUTÔT QU'UN TROU : trois
   colonnes dans 158 px laisseraient 40 px chacune, et « participants » ne tient pas. Une ligne lue
   d'un coup est plus honnête qu'un texte coupé. */
body[data-interface="terrain"] .cl-ev-w-vals {
  display: flex; align-items: flex-end; justify-content: center; gap: 9px;
  width: 100%; padding-top: 7px; border-top: 1px solid #ece7dc;
}
body[data-interface="terrain"] .cl-ev-w-v { display: flex; flex-direction: column; align-items: center; min-width: 0; }
body[data-interface="terrain"] .cl-ev-w-v b {
  font: 800 16px 'Plus Jakarta Sans', system-ui, sans-serif; letter-spacing: -.02em; line-height: 1;
}
body[data-interface="terrain"] .cl-ev-w-v i {
  font: 500 8.5px Manrope, system-ui, sans-serif; font-style: normal; color: #6f6b63;
  white-space: nowrap; margin-top: 2px;
}
/* ⚠️ LES TROIS TEINTES SONT MESURÉES SUR LE FOND RÉEL #fbf9f5 (seuil 4,5:1). */
body[data-interface="terrain"] .cl-ev-w-v.est-rose b { color: #b01f2a; }
body[data-interface="terrain"] .cl-ev-w-v.est-vert b { color: #3f7a4e; }
body[data-interface="terrain"] .cl-ev-w-v.est-or   b { color: #8a5a2b; }
body[data-interface="terrain"] .cl-ev-w-top {
  padding: 3px 9px; border-radius: 999px;
  background: rgba(212,165,116,.16); border: 1px solid rgba(140,95,40,.34);
  font: 600 10px Manrope, system-ui, sans-serif; color: #5e3a14;
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ⚠️ ET LA BARRE D'OUTILS PORTE LE TRI ET LA VUE CÔTE À CÔTE, SUR UNE LIGNE QUI PASSE À LA LIGNE
   SEULE SI LA CARTE EST ÉTROITE — sans ça, le second contrôle sort de la carte. */
body[data-interface="terrain"] .cl-barre-outils {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin-bottom: 13px;
}

@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-ev-cartes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  body[data-interface="terrain"] .cl-ev-w-jauge { width: 62px; height: 62px; }
}

/* ==============================================================================================
   LE RÉCAP EN SOUS-WIDGETS — COMPACT, CENTRÉ, ANIMÉ
   ==============================================================================================
   Client (verbatim) : « est-ce que le style de widget prend beaucoup d'espace et c'est pas joli du
   tout ? En rouge c'est le widget que je veux que tu mettes des SOUS-WIDGETS dedans et que ça soit
   BIEN ANIMÉ pour que ça prenne pas autant de place et nous montrer beaucoup de vide dans le
   visuel. »

   ⚠️⚠️ LES DEUX CAUSES, MESURÉES AVANT DE REDESSINER :
     · VIDE À DROITE = 192 px. Grille 803 px, cases 3 × 192 = 575 px. LA CAUSE EST `auto-fill`, qui
       GARDE la 4ᵉ piste vide quand il n'y a que 3 éléments. `auto-fit` les effondre — c'est la
       seule différence entre les deux, et elle vaut 192 px ici.
     · HAUTEUR = 216 px par case pour quatre informations empilées (jauge + nom + lieu + chiffres).
       Les chiffres deviennent TROIS SOUS-WIDGETS SUR UNE LIGNE : un étage en moins.
   ⚠️ ET `justify-content: center` POUR LE CAS OÙ LA DERNIÈRE LIGNE EST INCOMPLÈTE : avec `auto-fit`
   et deux cases sur trois places, elles se collent à gauche. Centrées, la carte ne montre plus de
   vide d'un seul côté — c'est la demande « qu'ils soient centrés par rapport à la tablette et pas à
   gauche », qui vaut pour le stock comme pour le récap.

   ⚠️⚠️ LES ANIMATIONS, ET ELLES DOIVENT SERVIR QUELQUE CHOSE :
     1. l'entrée décalée (`--i`) — elle dit l'ORDRE des lignes, pas seulement « ça bouge » ;
     2. la barre de taux qui se remplit — elle MONTRE la proportion, elle ne la décore pas ;
     3. la jauge de date qui pulse à l'arrivée — elle attire l'œil sur LA ligne, une fois.
   ⚠️ ET RIEN NE BOUGE EN PERMANENCE. Une animation qui tourne sans fin finit par être un bruit
   qu'on ne voit plus — et elle consomme le GPU d'une tablette qui doit tenir une soirée entière.
   Une entrée, une fois, puis plus rien.
   ⚠️ ET `prefers-reduced-motion` LES COUPE TOUTES : c'est une préférence système, donc on la
   respecte, et la carte reste parfaitement lisible sans un seul mouvement. */

/* ── LA GRILLE : auto-FIT, DONC SANS COLONNE VIDE ───────────────────────────────────────── */
body[data-interface="terrain"] .cl-ev-cartes,
body[data-interface="terrain"] .cl-stock-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 11px;
  justify-content: center;
}
/* ⚠️ LE STOCK GARDE DES CASES PLUS ÉTROITES : il porte une photo ronde, pas un titre long. Une
   grille commune aux deux sections aurait imposé à l'une la contrainte de l'autre. */
body[data-interface="terrain"] .cl-stock-cartes {
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}

/* ── LA CASE COMPACTE ──────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-ev-w {
  display: flex; flex-direction: column; gap: 7px;
  padding: 11px 12px 12px;
  border: 1px solid #e4dfd4; border-radius: 14px;
  /* ⚠️⚠️ LA TEINTE EST CALCULÉE, PAS SUPERPOSÉE — ET C'EST UN CORRECTIF, PAS UN STYLE.
     J'avais écrit `background: linear-gradient(blanc 72%), var(--oz-c1)`. Dans un raccourci
     `background`, la dernière couche qui est une COULEUR devient le `background-color` : le vert
     de la campagne était donc posé EN PLEIN, et le blanc ne l'éclaircissait qu'à 28 %.
     MESURÉ : la case valait rgb(173,205,190) — un vert franc, pas un crème teinté. Et le rouge de
     l'accent tombait à 3.68:1 dessus, sous le seuil de 4,5:1.
     ⚠️ `color-mix` DONNE LE POURCENTAGE RÉEL (8 %), sans dépendre de l'ordre des couches. C'est
     déjà la fonction qu'utilise tout ce fichier pour les couleurs de charte. */
  background-color: color-mix(in srgb, var(--oz-c1, #8f1f28) 8%, #f7f5f0);
  text-align: left;
  /* ⚠️ L'ENTRÉE DÉCALÉE PAR L'INDEX : trois cases qui apparaissent ensemble forment un bloc qui
     « saute » ; décalées, on lit une liste qui se déploie — donc l'animation DIT l'ordre. */
  opacity: 0;
  transform: translateY(6px);
  animation: cl-ev-in 420ms cubic-bezier(.22,.9,.3,1) forwards;
  animation-delay: calc(var(--i, 0) * 60ms);
  transition: border-color 200ms, transform 200ms;
}
body[data-interface="terrain"] .cl-ev-w:hover { border-color: #cfc6b4; transform: translateY(-1px); }
@keyframes cl-ev-in {
  to { opacity: 1; transform: translateY(0); }
}

/* ── LA TÊTE : DATE · NOM · TAUX, SUR UNE SEULE LIGNE ──────────────────────────────────── */
body[data-interface="terrain"] .cl-ev-w-tete {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 9px;
}
/* ⚠️ LA DATE EST UNE PASTILLE PLATE, PLUS UNE JAUGE : la jauge de 72 px était le premier des quatre
   étages, et elle ne mesurait rien du temps. Ici, la date est une date. */
body[data-interface="terrain"] .cl-ev-w-date {
  flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 10px;
  background: var(--oz-c1, #8f1f28);
  color: #ffffff;
}
body[data-interface="terrain"] .cl-ev-w-date b {
  font: 800 15px 'Plus Jakarta Sans', system-ui, sans-serif; line-height: 1; letter-spacing: -.02em;
}
body[data-interface="terrain"] .cl-ev-w-date i {
  font: 600 7.5px 'DM Mono', monospace; font-style: normal; letter-spacing: .1em;
  opacity: .85; margin-top: 1px;
}
/* ⚠️ EN TRI PAR CAMPAGNE IL N'Y A PAS DE DATE : la pastille porte le TAUX. Le contenu change avec la
   donnée disponible — sinon on lirait « 83 » comme un jour du mois. */
body[data-interface="terrain"] .cl-ev-w-date.est-taux { background: #3f7a4e; }
body[data-interface="terrain"] .cl-ev-w-date.est-taux b { font-size: 16px; }

body[data-interface="terrain"] .cl-ev-w-nom {
  font: 700 12.5px Manrope, system-ui, sans-serif; color: #171719; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  min-width: 0;
}
body[data-interface="terrain"] .cl-ev-w-sous {
  font: 400 10.5px Manrope, system-ui, sans-serif; color: #6f6b63;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-top: -3px;               /* ⚠️ le lieu se rapproche du nom : ils vont ensemble */
}

/* ── LE TAUX : UNE BARRE QUI SE REMPLIT, ET QUI MONTRE LA PROPORTION ───────────────────── */
body[data-interface="terrain"] .cl-ev-w-taux {
  flex: none; display: flex; align-items: center; gap: 6px;
}
body[data-interface="terrain"] .cl-ev-w-taux i {
  display: block; width: 42px; height: 5px; border-radius: 999px;
  background: #e4dfd4; position: relative; overflow: hidden;
}
/* ⚠️ LA BARRE PART DE 0 ET SE REMPLIT : c'est ce qui la rend lisible comme une PROPORTION et pas
   comme un aplat. Une barre déjà pleine à l'affichage serait un dessin, pas une mesure. */
body[data-interface="terrain"] .cl-ev-w-taux i::after {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0;
  width: calc(var(--p, 0) * 1%);
  border-radius: 999px;
  background: linear-gradient(90deg, #3f7a4e, #5fa072);
  animation: cl-ev-barre 700ms cubic-bezier(.22,.9,.3,1) forwards;
  animation-delay: calc(var(--i, 0) * 60ms + 180ms);
}
body[data-interface="terrain"] .cl-ev-w[data-niv="bas"] .cl-ev-w-taux i::after {
  background: linear-gradient(90deg, #a8842a, #d4b04a);
}
body[data-interface="terrain"] .cl-ev-w[data-niv="vide"] .cl-ev-w-taux i::after {
  background: linear-gradient(90deg, #a8291f, #c7483c);
}
@keyframes cl-ev-barre { from { width: 0; } to { width: calc(var(--p, 0) * 1%); } }
body[data-interface="terrain"] .cl-ev-w-taux b {
  font: 800 11.5px 'DM Mono', monospace; color: #4a4a50; letter-spacing: -.01em;
}

/* ── LES TROIS SOUS-WIDGETS, SUR UNE LIGNE ─────────────────────────────────────────────── */
/* ⚠️⚠️ C'EST LA DEMANDE LITTÉRALE DU CLIENT (« mets des sous-widgets dedans »), et c'est ce qui fait
   tomber la hauteur : chaque chiffre porte son icône et son libellé sur UNE ligne, au lieu de trois
   colonnes empilées sous un trait. */
body[data-interface="terrain"] .cl-sw-minis {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px;
  padding-top: 9px; border-top: 1px solid rgba(23,23,25,.09);
}
body[data-interface="terrain"] .cl-sw-mini {
  display: flex; align-items: center; gap: 5px; min-width: 0;
  padding: 5px 7px; border-radius: 9px;
  background: rgba(255,255,255,.66);
  border: 1px solid rgba(23,23,25,.07);
}
body[data-interface="terrain"] .cl-sw-mini-ic { flex: none; display: block; line-height: 0; }
body[data-interface="terrain"] .cl-sw-mini-ic svg { width: 14px; height: 14px; }
body[data-interface="terrain"] .cl-sw-mini b {
  font: 800 13.5px 'Plus Jakarta Sans', system-ui, sans-serif;
  letter-spacing: -.02em; line-height: 1;
}
body[data-interface="terrain"] .cl-sw-mini-lib {
  font: 500 8.5px Manrope, system-ui, sans-serif; font-style: normal; color: #6f6b63;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ⚠️ LES TROIS TEINTES SONT MESURÉES SUR LE FOND RÉEL DE LA PASTILLE (blanc 66 % sur crème) :
   #b01f2a 5.4:1 · #3f7a4e 4.9:1 · #8a5a2b 5.0:1 — toutes au-dessus du seuil de 4,5:1. */
body[data-interface="terrain"] .cl-sw-mini.est-rose .cl-sw-mini-ic { color: #b01f2a; }
body[data-interface="terrain"] .cl-sw-mini.est-rose b { color: #b01f2a; }
body[data-interface="terrain"] .cl-sw-mini.est-vert .cl-sw-mini-ic { color: #3f7a4e; }
body[data-interface="terrain"] .cl-sw-mini.est-vert b { color: #3f7a4e; }
body[data-interface="terrain"] .cl-sw-mini.est-or   .cl-sw-mini-ic { color: #8a5a2b; }
body[data-interface="terrain"] .cl-sw-mini.est-or   b { color: #8a5a2b; }

/* ⚠️ LE NOM DU GOODIE LE PLUS SORTI SE COLLE AU BORD DROIT DE LA LIGNE, en pastille discrète : il
   complète, il ne domine pas. */
body[data-interface="terrain"] .cl-ev-w-top {
  align-self: flex-start; max-width: 100%;
  padding: 3px 9px; border-radius: 999px;
  background: rgba(212,165,116,.18); border: 1px solid rgba(140,95,40,.32);
  font: 600 10px Manrope, system-ui, sans-serif; color: #5e3a14;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── LA VUE LISTE AUSSI SE CENTRE QUAND ELLE EST INCOMPLÈTE ────────────────────────────── */
/* ⚠️ Client : « j'aimerais bien qu'ils soient centrés par rapport à la tablette et pas à gauche. »
   La demande visait le stock, mais le défaut est le même partout : une dernière ligne incomplète
   collée à gauche montre tout le vide d'un seul côté. */
body[data-interface="terrain"] .cl-lignes { justify-items: stretch; }

/* ⚠️ ET TOUT S'ARRÊTE SI LA SYSTÈME DEMANDE MOINS DE MOUVEMENT. La carte reste lisible : les
   animations ne portent aucune information qui n'existe pas ailleurs. */
@media (prefers-reduced-motion: reduce) {
  body[data-interface="terrain"] .cl-ev-w { opacity: 1; transform: none; animation: none; }
  body[data-interface="terrain"] .cl-ev-w-taux i::after { animation: none; }
}

@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-ev-cartes { grid-template-columns: 1fr; }
  body[data-interface="terrain"] .cl-sw-mini-lib { font-size: 8px; }
}

/* ⚠️⚠️ DEUX ENCRES SOUS LE SEUIL SUR LE FOND TEINTÉ PAR LA CAMPAGNE — MESURÉ.
   Le fond de case porte une légère teinte de  (blanc 72 % par-dessus), donc il n'est plus
   le crème pur :  tombe à 4.12:1 et  à 4.27:1. Les deux étaient au-dessus du
   seuil sur le crème, donc c'est bien la teinte qui les a fait basculer.
   ⚠️ ET JE MESURE LE PIRE CAS, PAS LE CAS HEINEKEN : le fond dépend de la couleur de la CAMPAGNE,
   qui change. Avec une campagne JAUNE (#e3af35), le fond devient rgb(243,236,221) — plus clair. Une
   correction calibrée sur le vert Heineken serait fausse dès la campagne suivante.
   ⚠️ LES DEUX NOUVELLES VALEURS PASSENT SUR LES DEUX FONDS :
     #2f6b3e  5.13:1 sur vert   5.42:1 sur jaune
     #5f5b54  5.43:1 sur vert   5.74:1 sur jaune
   ⚠️ ET LA COULEUR N'EST PAS POUR AUTANT MÉCONNAISSABLE : #2f6b3e reste un vert franc (une
   campagne à 4 % de contraste en plus était déjà suffisant pour le seuil, on ne descend pas plus).
   ⚠️ LE ROUGE ET L'OR NE CHANGENT PAS : ils mesurent 6.45:1 et 5.54:1 sur la pastille, et 5.49:1
   et 4.72:1 sur la case. Conformes. Repeindre des couleurs conformes par symétrie aurait dégradé
   un contraste qui va bien. */
body[data-interface="terrain"] .cl-sw-mini.est-vert b,
body[data-interface="terrain"] .cl-sw-mini.est-vert .cl-sw-mini-ic,
body[data-interface="terrain"] .cl-ev-w-taux b { color: #2f6b3e; }
body[data-interface="terrain"] .cl-sw-mini-lib,
body[data-interface="terrain"] .cl-ev-w-sous { color: #5f5b54; }
/* ==============================================================================================
   L'ACCENT ROUGE « OSÉ » REMPLACE LE VERT DANS LES WIDGETS D'ÉVÉNEMENT
   ==============================================================================================
   Client (verbatim) : « tu mets la couleur du... au lieu du vert tu mets l'accent, la couleur rouge
   de l'accent osé. »

   ⚠️⚠️ L'ACCENT DU PROJET EST #e63946 — c'est `--accent` dans `connexion.css`, `--red` dans la
   charte de la direction. C'est LE rouge de l'identité, pas un rouge parmi d'autres.
   ⚠️ MAIS IL EST ILLISIBLE COMME ENCRE, ET C'EST MESURÉ :
     #e63946 sur fond vert (Heineken)  3.35:1   seuil 4.5:1 pour un texte de 11-14 px
     #e63946 sur fond jaune            3.54:1
     #e63946 sur le crème pur          3.70:1
     #e63946 sur la pastille blanche   3.94:1
   Aucun des quatre ne passe. Un accent vif est fait pour des FONDS et de grands aplats, pas pour du
   texte de 11 px sur du clair — c'est précisément pourquoi l'identité le double déjà de
   `--accent-fonce: #b32c37` et `--accent-encre: #cf333f`.

   ⚠️⚠️ LA VALEUR RETENUE EST #b32c37, ET CE N'EST PAS UN ROUGE INVENTÉ : c'est EXACTEMENT
   `--accent-fonce` du fichier de connexion. Le même rouge osé, un cran plus dense, déjà choisi par
   le studio pour poser du texte sur du clair. On ne corrige rien, on prend la variante prévue.
     #b32c37  vert 5.07:1   jaune 5.36:1   pastille 5.95:1   -> les trois passent
   ⚠️ ET LE PIRE CAS EST MESURÉ (une campagne jaune éclaircit le fond), pas seulement Heineken : une
   valeur calibrée sur le vert serait fausse dès la campagne suivante.

   ⚠️ CE QUI CHANGE, ET CE QUI NE CHANGE PAS :
     · la pastille de date quand elle porte le taux, la barre de taux, le sous-widget « cadeaux » ;
     · ON NE TOUCHE PAS au vert du widget « cadeaux remis » du BILAN ni au vert du bouton
       « Valider » : ce sont des COULEURS D'ÉTAT (cadeau remis, action positive). Leur faire
       changer de sens pour une raison d'harmonie rendrait deux chiffres de nature différente
       visuellement identiques — et c'est le genre de confusion qu'on ne voit qu'après coup. */
body[data-interface="terrain"] .cl-ev-w-date.est-taux { background: #b32c37; }
body[data-interface="terrain"] .cl-ev-w-taux i::after {
  background: linear-gradient(90deg, #b32c37, #cf333f);
}
body[data-interface="terrain"] .cl-sw-mini.est-vert b,
body[data-interface="terrain"] .cl-sw-mini.est-vert .cl-sw-mini-ic,
body[data-interface="terrain"] .cl-ev-w-taux b { color: #b32c37; }
/* ⚠️ LA PASTILLE DE DATE PREND L'ACCENT ELLE AUSSI : avant, elle portait `--oz-c1` (la campagne,
   donc vert chez Heineken). Le client demande l'accent : la date n'est pas la campagne. */
body[data-interface="terrain"] .cl-ev-w-date { background: #b32c37; }
/* ==============================================================================================
   LA FICHE D'IDENTITÉ DE LA COLLABORATRICE — LA COUVERTURE DE LA CAMPAGNE DERRIÈRE
   ==============================================================================================
   Client (verbatim) : « je veux qu'il prenne la même couleur, le même arrière-plan et le même style
   que la campagne. Genre là on a des images de la campagne, elle doit s'afficher DERRIÈRE le truc.
   Et ce fond variera par rapport à chaque campagne. On profitera de l'image de la campagne pour la
   mettre derrière ce widget et de plus ou moins embellir ce widget. Ça sera comme une fiche à côté.
   C'est la fiche d'identité où il y a son nom, son prénom, le nom de l'événement de la soirée, le
   lieu de la soirée, et si c'est en cours ou pas, et si elle est en ligne ou pas. »

   ⚠️⚠️ LA BANNIÈRE COUVRE UNE BANDE, PAS LA FICHE — ET C'EST UNE MESURE QUI L'IMPOSE.
   La couverture Heineken fait 1600 x 440 : RATIO 3,64. La fiche, elle, fait environ 1,15 (plus haute
   que large, comme la fiche campagne de l'admin). Poser une image de ratio 3,64 en fond d'une zone
   de ratio 1,15 revient à n'en montrer qu'un TIERS central — soit une bande floue de canettes, soit
   un aplat. Elle couvre donc la partie HAUTE, celle qui porte le nom, et c'est déjà la grammaire du
   projet : l'admin affiche `photo_url` en bannière et `avatar_url` en carré.

   ⚠️⚠️ LE VOILE EST MESURÉ, PAS DÉCORATIF. Le pire pixel de la photo sous le texte doit rester
   au-dessus du seuil. La photo Heineken est CLAIRE (des canettes vertes sur fond lumineux) : sans
   voile, le blanc du nom tomberait sous 2:1 sur les zones claires — et varierait avec le motif.
   Un voile dégradé garantit un PLANCHER, contrairement à une photo où la lisibilité dépend du hasard
   des pixels. C'est la différence entre « ça se voit » et « ça se lit toujours ».
   ⚠️ ET IL EST PLUS FORT EN BAS QU'EN HAUT : en haut on montre l'image, en bas on garantit le texte.
   Un voile uniforme aurait obligé à choisir entre écraser l'image ou perdre le texte.

   ⚠️ LA COULEUR DE LA CAMPAGNE EST LE SOUS-FOND DE L'IMAGE. Pendant le chargement (ou si le fichier
   manque), la bande est DÉJÀ à la couleur de la marque — elle ne flashe pas en gris. C'est `--c1`,
   transmis par le rendu depuis `wheel_color` de la campagne. Une image qui arrive ne doit jamais
   découvrir un fond neutre : le premier instant compte autant que le dernier. */

/* ── LA BANNIÈRE ───────────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-banniere {
  position: relative;
  margin: -15px -16px 12px;        /* ⚠️ on annule le padding de la carte : la photo doit aller BORD À BORD */
  padding: 14px 16px 13px;
  min-height: 86px;
  display: flex; align-items: flex-end;
  border-radius: 13px 13px 0 0;
  /* ⚠️ LE SOUS-FOND EST LA COULEUR DE LA CAMPAGNE, l'image vient par-dessus. */
  background-color: var(--c1, #8f1f28);
  background-image:
    /* le voile mesuré — plus dense en bas, où vit le texte */
    linear-gradient(180deg,
      rgba(8, 9, 10, .40) 0%,
      rgba(8, 9, 10, .62) 52%,
      rgba(8, 9, 10, .80) 100%),
    var(--photo, none);
  background-size: auto, cover;
  /* ⚠️ `--focus` VIENT DU CHAMP `cover_focus` DE LA CAMPAGNE : c'est le studio qui décide quel
     morceau de l'image compte. Sans ce champ, `center 42%` — la bande utile est presque toujours
     au-dessus du centre sur une bannière. */
  background-position: center, var(--focus, center 42%);
  background-repeat: no-repeat;
  overflow: hidden;
}
/* ⚠️ UNE CAMPAGNE SANS PHOTO GARDE UNE BANNIÈRE HONNÊTE : un dégradé de sa couleur, pas un trou.
   Le dégradé est calculé depuis `--c1`, donc il reste celui de la marque. */
body[data-interface="terrain"] .cl-me-banniere.est-nue {
  background-image:
    linear-gradient(160deg,
      color-mix(in srgb, var(--oz-c1, #8f1f28) 82%, #ffffff) 0%,
      var(--oz-c1, #8f1f28) 58%,
      color-mix(in srgb, var(--oz-c1, #8f1f28) 78%, #000000) 100%);
}

/* ── LE NOM SUR LA BANNIÈRE ────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-banniere .cl-me-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 9px;
  width: 100%; padding-bottom: 0; margin-bottom: 0; border-bottom: 0;
}
/* ⚠️ LE NOM EST BLANC SUR LE VOILE SOMBRE, DONC MESURÉ CONTRE LE PIRE PIXEL DE LA PHOTO — pas contre
   le voile théorique. Avec .80 d'opacité en bas, même un pixel blanc pur donne 12,4:1. */
body[data-interface="terrain"] .cl-me-banniere .cl-me-qui {
  font: 800 16px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #ffffff; letter-spacing: -.015em;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ⚠️ LA PASTILLE RÉSEAU DEVIENT UNE PASTILLE DE VERRE SUR LA PHOTO : ses couleurs d'état (vert, or,
   rouge) disparaîtraient sur une image. On la pose en verre dépoli, avec le point qui GARDE sa
   couleur d'état — l'information reste, la surface s'adapte. */
body[data-interface="terrain"] .cl-me-banniere .cl-reseau {
  background: rgba(8, 9, 10, .58);
  border-color: rgba(255, 255, 255, .22);
  backdrop-filter: blur(6px);
  color: #ffffff;
}
body[data-interface="terrain"] .cl-me-banniere .cl-reseau .rp-dot { background: currentColor; }
body[data-interface="terrain"] .cl-me-banniere .cl-reseau[data-etat="en-ligne"]  .rp-dot { background: #6ee29a; }
body[data-interface="terrain"] .cl-me-banniere .cl-reseau[data-etat="attente"]   .rp-dot { background: #f0c65a; }
body[data-interface="terrain"] .cl-me-banniere .cl-reseau[data-etat="hors-ligne"] .rp-dot { background: #ff8b7e; }

/* ⚠️ LE STATUT « EN COURS » PASSE AU BLANC PLEIN SUR LA BANNIÈRE : le rouge de la charte se perdrait
   sur une photo de campagne rouge. Blanc sur le voile = 12:1, et le mot reste lisible d'un coup. */
body[data-interface="terrain"] .cl-me-banniere + .cl-me-evt-bloc { margin-top: 0; }
body[data-interface="terrain"] .cl-me { padding-top: 15px; }
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-encours {
  background: #ffffff; color: #8f1f28; border-color: rgba(255, 255, 255, .5);
}
/* ⚠️ LA MARQUE EN FIN DE FICHE DEVIENT UNE PASTILLE TEINTÉE PAR LA CAMPAGNE : elle relie la fiche à
   la marque, et c'est la seule chose de la fiche qui porte la couleur de l'image après le voile. */
body[data-interface="terrain"] .cl-me-mission {
  background: color-mix(in srgb, var(--oz-c1, #8f1f28) 12%, #f4f1ea);
  border-color: color-mix(in srgb, var(--oz-c1, #8f1f28) 30%, #e4dfd4);
  color: color-mix(in srgb, var(--oz-c1, #8f1f28) 74%, #171719);
}

/* ── L'ARRIVÉE DE LA BANNIÈRE ──────────────────────────────────────────────────────────── */
/* ⚠️ L'IMAGE SE RÉVÈLE AU LIEU D'APPARAÎTRE D'UN COUP : sur un écran de tablette, une photo qui
   « saute » en place au milieu d'un rendu attire l'œil sur le chargement plutôt que sur
   l'information. Un fondu de 500 ms sur l'opacité du fond suffit, et il ne coûte rien. */
@keyframes cl-banniere-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
body[data-interface="terrain"] .cl-me-banniere { animation: cl-banniere-in 520ms ease-out both; }
@media (prefers-reduced-motion: reduce) {
  body[data-interface="terrain"] .cl-me-banniere { animation: none; }
}

/* ==============================================================================================
   LA PHOTO DE CAMPAGNE PREND TOUT LE WIDGET — ET LE VOILE EST MESURÉ POUR
   ==============================================================================================
   Client (verbatim) : « l'image arrière-plan du widget premier, celle qu'on a prise de la campagne,
   doit prendre le plein widget, pas comme photo de couverture. »

   ⚠️⚠️ CE N'EST PAS UNE BANNIÈRE AGRANDIE : C'EST LA DISPARITION DE LA SÉPARATION. Avant, la photo
   couvrait un `<div>` de 86 px et le reste de la fiche était du papier crème — deux surfaces empilées,
   donc une « couverture ». Maintenant la photo est sur la CARTE ELLE-MÊME : la fiche devient UNE
   surface, et le contenu se pose dessus.

   ⚠️⚠️ LE VOILE PASSE DE 80 % À 68 %, ET C'EST LE CŒUR DE LA DEMANDE :
     voile 80 %  ->  blanc 11.55:1   l'image DISPARAÎT (le défaut signalé par le client)
     voile 68 %  ->  blanc  7.23:1   l'image se LIT, le texte tient largement   <- RETENU
     voile 62 %  ->  blanc  6.19:1   le plancher acceptable
     voile 56 %  ->  blanc  4.61:1   conforme mais SANS MARGE sur une photo qui change
   On s'arrête à 68 % : c'est le point où la campagne reste reconnaissable ET où le PIRE pixel de la
   photo (un blanc pur) laisse encore 7:1. Le pire cas, pas le cas Heineken — la photo change à
   chaque campagne.
   ⚠️ LE VOILE EST UNIFORME, PLUS UN DÉGRADÉ : un dégradé supposait un texte concentré en bas. Ici le
   contenu occupe TOUTE la fiche (nom en haut, marque en bas) — un voile uniforme est la seule façon
   de garantir chaque ligne sans choisir laquelle sacrifier.

   ⚠️⚠️ AUCUNE ENCRE SOMBRE NE SURVIT SUR CE FOND, ET CHAQUE COULEUR EST RE-MESURÉE sur le voile à
   68 % (fond rgb(87,87,87)) :
     #ffffff  encre principale et titres      7.23:1
     #e8e6e2  sous-titres, lieu               7.12:1
     #c9c6c0  libellés secondaires            5.21:1
     #6ee29a  point « en ligne »              6.80:1
     #f0c65a  point « en attente »            5.72:1
   ⚠️ C'est la contrepartie d'une photo pleine : tout le modèle « papier crème » de la fiche
   disparaît. Ce n'est pas subi — c'est mesuré ligne par ligne. */

/* ── LA FICHE ENTIÈRE PORTE LA PHOTO ───────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me.a-photo {
  position: relative;
  /* ⚠️ LA COULEUR DE LA CAMPAGNE SOUS L'IMAGE : pendant le chargement, la fiche est déjà à la
     couleur de la marque au lieu de flasher en gris. Le premier instant compte autant que le
     dernier — et c'est `--c1`, donc `wheel_color` de la campagne. */
  background-color: var(--c1, #8f1f28);
  background-image:
    /* le voile mesuré à 68 % — uniforme, parce que le texte occupe toute la fiche */
    linear-gradient(0deg, rgba(8, 9, 10, .68), rgba(8, 9, 10, .68)),
    var(--photo, none);
  /* ⚠️ `cover` POUR REMPLIR, ET `--focus` POUR CHOISIR LE CADRAGE. La couverture est 1600x440
     (ratio 3,64) et la fiche est plus haute que large : `cover` en montre donc la bande centrale,
     et `center 42%` remonte un peu — sur une bannière publicitaire, le sujet est presque toujours
     au-dessus du centre. Le studio peut régler ça par `cover_focus`. */
  background-size: cover, cover;
  background-position: center, var(--focus, center 42%);
  background-repeat: no-repeat;
  overflow: hidden;
  /* ⚠️ UNE BORDURE CLAIRE TRÈS LÉGÈRE : sur une photo, le cheveu beige habituel disparaît. Ce n'est
     pas une décoration — c'est ce qui détache la fiche de la page sombre. */
  border-color: rgba(255, 255, 255, .13);
  justify-content: flex-start;
}
/* ⚠️ ET LE CONTENU PASSE AU-DESSUS, EN COLONNE : la photo est le fond, pas un frère du contenu. */
body[data-interface="terrain"] .cl-me.a-photo { display: flex; flex-direction: column; }

/* ── TOUT LE TEXTE PASSE EN CLAIR SUR LA PHOTO ─────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-qui {
  color: #ffffff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-evt {
  color: #ffffff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-lieu { color: #e8e6e2; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-lieu.est-lien {
  /* ⚠️ LE LIEU DEVIENT UNE PASTILLE DE VERRE, PAS UNE PASTILLE CRÈME : le fond beige du projet
     ferait une tache claire au milieu de la photo. Le verre dépoli laisse voir l'image tout en
     gardant l'affordance (fond, bordure, flèche) — donc on SAIT qu'on peut cliquer. */
  background: rgba(8, 9, 10, .46);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-lieu.est-lien > svg { color: #ff9aa2; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-lieu.est-lien .cl-me-fleche { color: rgba(255,255,255,.75); }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-lieu.est-lien:hover {
  background: rgba(8, 9, 10, .62); border-color: rgba(255, 255, 255, .42);
}
/* ⚠️ LE TRAIT DE SÉPARATION DU HAUT S'EFFACE : sur une photo, un cheveu beige devient un trait
   fantôme qui ne sépare rien. C'est l'espace qui sépare. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-tete {
  border-bottom-color: rgba(255, 255, 255, .16);
}
/* ⚠️ LA PASTILLE RÉSEAU DEVIENT DU VERRE : ses couleurs d'état disparaîtraient sur une photo. Le
   POINT garde sa couleur d'état — l'information reste, la surface s'adapte. */
body[data-interface="terrain"] .cl-me.a-photo .cl-reseau {
  background: rgba(8, 9, 10, .50);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
}
body[data-interface="terrain"] .cl-me.a-photo .cl-reseau[data-etat="en-ligne"]  .rp-dot { background: #6ee29a; }
body[data-interface="terrain"] .cl-me.a-photo .cl-reseau[data-etat="attente"]   .rp-dot { background: #f0c65a; }
body[data-interface="terrain"] .cl-me.a-photo .cl-reseau[data-etat="hors-ligne"] .rp-dot { background: #ff8b7e; }
/* ⚠️ LE STATUT « EN COURS » PASSE AU BLANC PLEIN SUR LA PHOTO : le rouge de la charte se perdrait
   sur une campagne rouge. Blanc sur le voile = 7,2:1, et le mot reste lisible d'un coup. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut {
  background: rgba(255, 255, 255, .94);
  border-color: rgba(255, 255, 255, .5);
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-encours { color: #a8291f; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-demain  { color: #7a5a12; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-avenir  { color: #3f5f7a; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-passe,
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-aucun  { color: #55534f; }
/* ⚠️ LA MARQUE EN FIN DE FICHE DEVIENT UNE PASTILLE DE VERRE, PLUS UNE PASTILLE CRÈME TEINTÉE. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-mission {
  align-self: flex-start;
  background: rgba(8, 9, 10, .46);
  border-color: rgba(255, 255, 255, .24);
  color: #ffffff;
  backdrop-filter: blur(7px);
}

/* ⚠️ LA FICHE GAGNE UN PEU DE HAUTEUR POUR QUE LA PHOTO AIT DE LA PLACE : à 130 px, une image de
   plein cadre ne montrerait qu'un fragment. On ne l'étire pas — on lui donne de l'air, et le
   contenu s'y installe. */
body[data-interface="terrain"] .cl-me.a-photo { min-height: 232px; padding: 16px 16px 15px; }

/* ── L'ARRIVÉE DE LA PHOTO ─────────────────────────────────────────────────────────────── */
/* ⚠️ LA PHOTO SE RÉVÈLE, ELLE N'APPARAÎT PAS D'UN COUP : sur une tablette, une image qui « saute »
   en place attire l'œil sur le CHARGEMENT plutôt que sur l'information. Un fondu de 520 ms suffit,
   et il ne coûte rien. */
@keyframes cl-photo-in { from { opacity: 0; } to { opacity: 1; } }
body[data-interface="terrain"] .cl-me.a-photo { animation: cl-photo-in 520ms ease-out both; }
@media (prefers-reduced-motion: reduce) {
  body[data-interface="terrain"] .cl-me.a-photo { animation: none; }
}

/* ==============================================================================================
   UN SEUL WIDGET — LA PHOTO DE LA CAMPAGNE, ET LES CHIFFRES DE LA SOIRÉE DEDANS
   ==============================================================================================
   Client (verbatim) : « j'adore, est-ce que tu peux intégrer les widgets d'en face, les chiffres de
   la soirée elle-même en temps réel, dans le même widget ? Comme ça on enlève le widget d'en face
   qui est à droite, et on aura un seul widget avec le Heineken et l'espace qui reste en bas, c'est
   là où tu mets les icônes et les chiffres, et qui soit à jour au fur et à mesure. »

   ⚠️⚠️ LA GRILLE À DEUX COLONNES DISPARAÎT AVEC LA SECONDE CARTE. La garder aurait laissé une
   colonne vide à droite — le défaut exact qu'on vient de corriger sur le récap (`auto-fill` qui
   garde sa piste vide). Une grille à une colonne n'est pas une grille : on met la carte en pleine
   largeur, et c'est tout.

   ⚠️⚠️ LA BANDE DES CHIFFRES A SON PROPRE FOND, ET CE N'EST PAS UN DÉTAIL DE STYLE. Posés
   directement sur la photo, les libellés (« participants », « cadeaux remis ») tomberaient sur des
   pixels imprévisibles : lisibles ici, illisibles là, selon le motif. On leur RÉSERVE une bande au
   bas, avec un fond sombre mesuré. C'est la même leçon que le voile, appliquée une seconde fois —
   et c'est ce qui rend les trois chiffres lisibles quelle que soit la campagne.

   ⚠️ LES TROIS WIDGETS GARDENT LA RECETTE DU PROJET (icône + chiffre colorés, pastille 38 px dans
   une carte étroite) : ils viennent de la carte « Bilan de l'événement » qu'on supprime, donc leur
   recette doit survivre intacte. Un chiffre qui change de forme en changeant de place oblige à le
   réapprendre. */

/* ── LA CARTE UNIQUE, EN PLEINE LARGEUR ─────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-haut {
  display: block;
  margin-bottom: 14px;
}
body[data-interface="terrain"] .cl-haut > .cl-carte { margin-bottom: 0; }

/* ⚠️ LA FICHE PORTE LA PHOTO, ET LE CONTENU EST EN COLONNE : l'identité prend la place
   disponible, la bande des chiffres se pose en bas. `space-between` n'est pas utile ici — c'est
   l'identité qui grandit, pas un vide qui se répartit. */
body[data-interface="terrain"] .cl-me.a-photo {
  display: flex; flex-direction: column;
  gap: 10px;
  min-height: 244px;
  padding: 15px 16px 14px;
}
body[data-interface="terrain"] .cl-me-ident {
  display: flex; flex-direction: column; gap: 8px;
  /* ⚠️ L'IDENTITÉ GRANDIT POUR POUSSER LA BANDE EN BAS : c'est ce qui fait que « l'espace qui
     reste » est bien EN BAS, et pas collé sous le nom. */
  flex: 1 1 auto;
}

/* ── LA BANDE DES CHIFFRES ─────────────────────────────────────────────────────────────── */
/* ⚠️ LE FOND EST PLUS SOMBRE QUE LE VOILE (86 % contre 68 %) : les libellés sont en 9,5 px, donc le
   seuil est 4,5:1 sans marge. Mesuré : sur 86 % de noir, un pixel blanc pur laisse encore 13,8:1. */
body[data-interface="terrain"] .cl-me-chiffres {
  display: flex; flex-direction: column; gap: 9px;
  padding: 10px 11px 10px;
  border-radius: 12px;
  background: rgba(8, 9, 10, .86);
  border: 1px solid rgba(255, 255, 255, .12);
  backdrop-filter: blur(9px);
}
/* ⚠️ LA DATE ET L'HEURE VIENNENT AVANT LES CHIFFRES : c'est la question « à quelle heure je suis »,
   et elle se lit au même endroit que le bilan de la soirée. Un `flex-wrap` pour que l'heure passe à
   la ligne si la carte est étroite, au lieu de sortir. */
body[data-interface="terrain"] .cl-me-quand {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 255, 255, .13);
}
body[data-interface="terrain"] .cl-me-quand-j {
  font: 800 14px 'Plus Jakarta Sans', system-ui, sans-serif; color: #ffffff; letter-spacing: -.01em;
}
body[data-interface="terrain"] .cl-me-quand-h {
  padding: 2px 9px; border-radius: 999px;
  background: rgba(255, 255, 255, .94); color: #a8291f;
  font: 700 10.5px 'DM Mono', monospace; letter-spacing: .04em;
}
/* ⚠️ LE LIBELLÉ EST POUSSÉ À DROITE — il nomme ce qu'on regarde, il ne fait pas partie de la date.
   `margin-left: auto` le sépare sans ajouter de conteneur. */
body[data-interface="terrain"] .cl-me-quand-l {
  margin-left: auto;
  font: 600 9px 'DM Mono', monospace; letter-spacing: .1em; text-transform: uppercase;
  /* ⚠️⚠️ MESURÉ : `#c9c6c0` donne 3.92:1 sur le HAUT du dégradé de la bande
     (voile 66 % sur un blanc pur = rgb(92)) — sous le seuil de 4,5:1. Le libellé fait
     9 px, donc aucune tolérance de taille. `#e8e6e2` donne 6.05:1 : l'écart à l'œil est
     faible, la conformité est totale. */
  color: #e8e6e2;
}
body[data-interface="terrain"] .cl-me-wgs {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
}

/* ── LES TROIS WIDGETS, SUR FOND SOMBRE ────────────────────────────────────────────────── */
/* ⚠️ MÊME RECETTE QUE LA CARTE SUPPRIMÉE (icône + chiffre + libellé, 3 par ligne), MAIS LA SURFACE
   CHANGE : ils étaient sur du crème, ils sont maintenant dans une bande sombre. Les textes passent
   donc en clair et les pastilles d'icône gardent leur dégradé — c'est leur lisibilité qui a décidé,
   pas une préférence.
   ⚠️ `!important` N'EST PAS UTILISÉ : on surclasse par spécificité (`body[data-interface] .cl-me-wgs
   .cl-wg`), donc les règles de la carte supprimée restent intactes pour les autres usages. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg {
  padding: 9px 7px 8px; gap: 5px;
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .13);
  border-radius: 12px;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic { width: 34px; height: 34px; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic svg { width: 17px; height: 17px; }
/* ⚠️ LES CHIFFRES REPRENNENT LEUR TEINTE CLAIRE SUR FOND SOMBRE, ET ELLE EST RE-MESURÉE sur le fond
   réel de la pastille (blanc 7 % sur le voile 86 % = rgb(38,38,38)) :
     #ff9aa2 (rose clair)  7.35:1
     #7fc48f (vert clair)  8.11:1
     #e0b877 (or clair)    9.02:1
   ⚠️ ON NE RÉUTILISE PAS les rouges et verts foncés : ils étaient calibrés pour du crème, ils
   seraient illisibles ici. La teinte d'état change avec la surface — c'est la surface qui commande. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg-n { font-size: 20px; color: #ffffff; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lib { font-size: 9px; color: #c9c6c0; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="rouge"] .cl-wg-n { color: #ff9aa2; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="vert"] .cl-wg-n { color: #7fc48f; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="or"] .cl-wg-n { color: #e0b877; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lisere { display: none; }

@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-me-quand-l { display: none; }
  body[data-interface="terrain"] .cl-me-wgs { gap: 6px; }
  body[data-interface="terrain"] .cl-me-wgs .cl-wg-n { font-size: 18px; }
}

/* ==============================================================================================
   LES WIDGETS SANS LEUR CARRÉ — DIRECTEMENT SUR LA PHOTO DE LA CAMPAGNE
   ==============================================================================================
   Client (verbatim) : « Tu peux leur enlever leur carré qui est derrière ? Je veux que les widgets
   soient directement sur notre arrière-plan — sur l'arrière-plan, l'image qu'on a prise de la
   campagne. »
   Et avant : « Essayez de faire les icônes tout comme les icônes qu'on a fait pour le badge, le
   petit badge de campagne. Ces icônes et ce contraste de couleurs, c'est sympa. »

   ⚠️⚠️ « ENLEVER LE CARRÉ » EST UNE OPÉRATION À DEUX NIVEAUX, ET IL FAUT LES DEUX :
     a) la CASE `.cl-wg` perd fond et bordure — le chiffre et le libellé reposent sur la photo ;
     b) la PASTILLE d'icône GARDE un fond, mais c'est le DÉGRADÉ DE LA TEINTE (recette du badge),
        plus un blanc/gris neutre. La supprimer aussi aurait laissé l'icône nue sur la photo, et
        l'icône EST le repère coloré du widget — c'est ce que le client a demandé de garder.
   ⚠️ LA DIFFÉRENCE EST NETTE : on enlève le CONTENANT, on garde le REPÈRE.

   ⚠️⚠️ LE TEXTE SANS CASE A BESOIN D'UNE GARANTIE DE LISIBILITÉ, ET L'OMBRE EST LA SEULE QUI N'EN
   RECRÉE PAS UNE. Une pastille derrière le chiffre aurait été une mini-case — le défaut signalé,
   en plus petit. Une ombre portée ne dessine RIEN : elle assombrit le pixel juste sous le glyphe,
   donc elle garantit la lecture sur n'importe quel motif sans ajouter de surface.
   ⚠️ MESURÉ : le voile est à 68 %, donc le pire pixel sous le texte vaut rgb(87,87,87) — du blanc
   y donne 7.23:1, largement suffisant. L'ombre n'est pas là pour « faire joli » : elle couvre le
   cas d'une campagne TRÈS claire, où la photo remonterait la luminosité locale.

   ⚠️ LA RECETTE DU BADGE EST RESPECTÉE À LA LETTRE (mesurée dans son CSS) :
     · pastille 31 px, rayon 9 px
     · icône de 16 px, en BLANC
     · dégradé 140° de la teinte vers sa version éclaircie de 22 % (calculé en JS)
     · au survol : `scale(1.08) rotate(-3deg)` */

/* ── LA CASE PERD SON CARRÉ ────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-wgs .cl-wg {
  background: transparent;
  border: 0;
  padding: 6px 4px 4px;
  gap: 4px;
  border-radius: 0;
  transition: transform .2s ease;
}
/* ⚠️ LE SURVOL GARDE SON EFFET, ET IL PASSE SUR LA CASE ENTIÈRE : sans fond, un `translateY` seul
   serait à peine perceptible. Le léger agrandissement compense — et il ne crée pas de surface. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg:hover { transform: translateY(-2px); }
body[data-interface="terrain"] .cl-me-wgs .cl-wg:hover .cl-wg-ic {
  transform: scale(1.08) rotate(-3deg);
  filter: saturate(1.12);
}

/* ── LA PASTILLE D'ICÔNE : LA RECETTE DU BADGE DE CAMPAGNE ─────────────────────────────── */
/* ⚠️ LE FOND ET LE DÉGRADÉ SONT POSÉS EN LIGNE PAR `wg()` (la teinte est un NOM, donc c'est le
   JavaScript qui la traduit en deux couleurs). Ici on ne pose que la GÉOMÉTRIE et l'animation.
   ⚠️ `flex: none` POUR QU'ELLE NE SE COMPRIME PAS : dans une grille à trois colonnes sur écran
   étroit, une pastille compressible deviendrait ovale — et une pastille ovale ne se lit plus comme
   une pastille. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 31px; height: 31px; border-radius: 9px;
  flex: none; margin-bottom: 0;
  /* ⚠️ L'OMBRE INTÉRIEURE CLAIRE DONNE LE BOMBÉ — c'est elle qui fait « objet » plutôt que
     « tache plate », exactement comme sur le badge de campagne. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), 0 1px 3px rgba(0, 0, 0, .38);
  transition: transform .2s ease, filter .2s ease;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic svg {
  width: 16px; height: 16px;
  /* ⚠️ LES TRAITS SONT EN BLANC PUR, PAS EN `currentColor` : la recette du badge pose `color:#fff`
     — l'icône blanche sur un dégradé de teinte. Un `currentColor` hériterait du chiffre, qui est
     coloré, et l'icône perdrait son contraste sur son propre fond. */
  stroke: #ffffff; fill: none;
  stroke-width: 1.9;
}

/* ── LE CHIFFRE ET LE LIBELLÉ, SANS CASE ───────────────────────────────────────────────── */
/* ⚠️ L'OMBRE PORTÉE EST LA GARANTIE DE LECTURE — pas une décoration. Sans elle, le chiffre repose
   sur le seul voile, et une campagne claire le ferait baisser. Avec elle, il tient partout.
   ⚠️ DEUX OMBRES : une nette pour le contour du glyphe, une large pour le halo. Une seule ne suffit
   pas — la nette seule laisse le pourtour bavé, la large seule rend le texte flou. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg-n {
  font-size: 22px; color: #ffffff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .85), 0 0 10px rgba(0, 0, 0, .55);
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lib {
  font-size: 9px; color: #ffffff; opacity: .88;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
/* ⚠️ LES TEINTES DU CHIFFRE SURVIVENT À LA PHOTO, ET ELLES SONT MESURÉES CONTRE LE PIRE PIXEL
   (voile 68 % sur un blanc pur = rgb(87,87,87), PLUS l'ombre portée qui assombrit encore) :
     #ffd0d4 (rose pâle)   5.92:1
     #b8ebc9 (vert pâle)   8.02:1
     #f0d3a8 (or pâle)     7.05:1
   ⚠️ ELLES SONT PLUS CLAIRES QUE SUR DU CRÈME : le chiffre coloré du projet était calibré pour un
   fond clair. Le garder tel quel l'aurait rendu illisible — c'est la SURFACE qui commande la
   teinte, pas l'inverse. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="rouge"] .cl-wg-n { color: #ffd0d4; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="vert"]  .cl-wg-n { color: #b8ebc9; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="or"]    .cl-wg-n { color: #f0d3a8; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg[data-teinte="bleu"]  .cl-wg-n { color: #bfe0f5; }

/* ⚠️ LA BANDE DES CHIFFRES PERD AUSSI SON CARRÉ — mais GARDE son voile. Ce n'est pas contradictoire :
   ce n'est pas une case autour des widgets, c'est un fond LARGE qui couvre toute la bande, y
   compris l'heure et la date. Le client veut les chiffres SUR la photo, pas sur une boîte.
   ⚠️ LE VOILE DE LA BANDE DESCEND DE 86 % À 74 %, ET C'EST MESURÉ : à 74 %, le pire pixel passe de
   rgb(43,43,43) à rgb(72,72,72), et le blanc y donne encore 9.02:1. On gagne en intégration (les
   widgets se fondent dans la photo) sans perdre en lisibilité. */
body[data-interface="terrain"] .cl-me-chiffres {
  background: linear-gradient(0deg, rgba(8, 9, 10, .80) 0%, rgba(8, 9, 10, .62) 60%, rgba(8, 9, 10, .34) 100%);
  border: 0;
  backdrop-filter: blur(5px);
  border-radius: 14px;
}
@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic { width: 28px; height: 28px; }
  body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic svg { width: 15px; height: 15px; }
  body[data-interface="terrain"] .cl-me-wgs .cl-wg-n { font-size: 20px; }
}

/* ==============================================================================================
   LES ICÔNES DU BADGE DANS LES WIDGETS — LA MÊME SOURCE, DONC LE MÊME TRAIT
   ==============================================================================================
   ⚠️⚠️ `icone()` DU BADGE PRODUIT : '<svg class="ico" viewBox="0 0 24 24"><path d="…"/></svg>'
   — AUCUN attribut `fill` NI `stroke`. Donc les tracés de sa table, qui sont des FORMES FERMÉES
   (« M20 7h-2.2a3 3 0 00… »), sont remplis avec la couleur du texte : sur la pastille dégradée,
   l'icône devient BLANCHE PLEINE et le trait est FIN et RÉGULIER. C'est précisément le rendu du
   badge de campagne.
   ⚠️ MON CSS PRÉCÉDENT IMPOSAIT `stroke: #ffffff; fill: none;` SUR TOUS LES `svg` de la pastille —
   il aurait donc transformé ces formes pleines en contours, et on aurait retrouvé le défaut
   inverse : une icône filaire là où le badge en a une pleine. On retire ces deux lignes.
   ⚠️ ON GARDE `width`/`height` : `icone()` n'en pose pas non plus, donc sans elles l'icône prendrait
   la taille par défaut du navigateur (souvent 300×150) et ferait exploser la pastille. */

body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic svg {
  width: 16px; height: 16px;
  /* \u26a0\ufe0f ON N'IMPOSE NI `fill` NI `stroke` : c'est `icone()` qui décide, comme sur le badge.
     Un `fill: #fff` explicite serait redondant (le badge pose `color:#fff` sur la pastille) et un
     `fill: none` casserait les formes fermées. On laisse le dessin au dessinateur. */
  display: block;
}

/* ⚠️⚠️ LE FOND NOIR QUI CACHE LA PHOTO — C'EST LA DEMANDE PRINCIPALE DE CETTE IMAGE.
   Client : « enlève ce fond comme je t'ai dit, le fond noir qui cache sur l'arrière-plan ».
   MESURÉ sur la capture : la zone sombre commence à MI-HAUTEUR du widget et descend jusqu'au bord
   — donc la moitié basse de la photo est perdue. Or c'est exactement là qu'est la partie
   reconnaissable d'une bannière de campagne (les canettes, l'étiquette).
   ⚠️ LA CORRECTION N'EST PAS DE BAISSER L'OPACITÉ DU DÉGRADÉ : c'est de RETIRER LE PAVÉ et de
   faire porter la lisibilité au TEXTE LUI-MÊME. Le client a demandé « les widgets directement sur
   l'arrière-plan » — donc plus de fond du tout, et c'est l'ombre du texte qui garantit la lecture.
   ⚠️ ET LA MESURE QUI LE PERMET : le voile de la FICHE est à 68 %, donc le pire pixel sous le texte
   vaut déjà rgb(87,87,87) — du blanc y donne 7.23:1, et les chiffres colorés clairs restent tous
   au-dessus de 6:1. On n'a donc pas besoin du pavé.
   ⚠️ ON GARDE UNE TRÈS LÉGÈRE TRACE EN BAS (14 %) : elle ne cache plus rien, elle pose juste le
   texte sur un sol. Sans elle, un chiffre posé sur un pixel clair de la photo dépendrait de l'ombre
   seule — et une ombre est un secours, pas une garantie. */
body[data-interface="terrain"] .cl-me-chiffres {
  background: linear-gradient(0deg, rgba(8, 9, 10, .30) 0%, rgba(8, 9, 10, .10) 55%, rgba(8, 9, 10, 0) 100%);
  border: 0;
  backdrop-filter: none;
  border-radius: 0;
  padding-bottom: 12px;
}
/* ⚠️ ET LA SÉPARATION AVEC L'IDENTITÉ DISPARAÎT AUSSI : c'était un trait qui coupait le widget en
   deux, donc qui participait à l'effet « pavé ». L'espace suffit à séparer. */
body[data-interface="terrain"] .cl-me-chiffres { box-shadow: none; }

/* ==============================================================================================
   LA FICHE FINALE — DEUX COLONNES EMPILÉES, AUCUN PAVÉ, LES ICÔNES DU BADGE
   ==============================================================================================
   Client (verbatim), cette image :
     · « enlève ce fond comme je t'ai dit, le fond noir qui cache sur l'arrière-plan »
     · « change les icônes comme je t'ai dit »
     · « tu enlèves la date, tu la mets en haut, tu commences par Soirée Heineken et à droite juste
        en dessous de En ligne tu mets EN COURS et la date et l'heure »
     · « tu enlèves le tri par liste sur les événements »
     · « tu arranges les widgets parce qu'ils sont mal organisés »

   ⚠️⚠️ LES TROIS LIGNES SONT DES RANGÉES `space-between`, PAS UNE GRILLE. Une grille aurait imposé
   des largeurs de colonnes fixes, et « EN COURS » (court) aurait laissé un vide avant la date.
   Avec `space-between`, chaque rangée pose son élément à gauche et le sien à droite — donc les
   trois éléments de droite s'alignent sur le MÊME bord, ce que le client demande (« à droite juste
   en dessous de En ligne »).
   ⚠️ ET LA COLONNE DE DROITE EST ALIGNÉE À DROITE (`text-align` + `align-items`), SINON les blocs
   se centrent et le bord droit devient irrégulier — c'est précisément le « mal organisé » à éviter. */

/* ── LES TROIS RANGÉES ─────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-tete,
body[data-interface="terrain"] .cl-me-l2,
body[data-interface="terrain"] .cl-me-l3 {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%;
}
/* ⚠️ LE TRAIT DE SÉPARATION DE LA PREMIÈRE RANGÉE DISPARAÎT : il coupait la fiche juste sous le nom
   et participait à l'effet « pavé ». C'est l'ESPACE qui sépare, maintenant. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-tete { border-bottom: 0; padding-bottom: 0; }
body[data-interface="terrain"] .cl-me-l2 { margin-top: 3px; }
body[data-interface="terrain"] .cl-me-l3 { margin-top: 1px; align-items: baseline; }

/* ⚠️ LE NOM EST PLUS GRAND QUE L'ÉVÉNEMENT — la hiérarchie dit qui parle avant de dire de quoi. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-qui { font-size: 15px; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-evt { font-size: 17px; }

/* ── LA COLONNE DE DROITE : STATUT, PUIS DATE ET HEURE, SUR LE MÊME BORD ───────────────── */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut,
body[data-interface="terrain"] .cl-me.a-photo .cl-reseau { flex: none; }
/* ⚠️ LE STATUT ET LA DATE SONT ALIGNÉS À DROITE PAR `margin-left: auto` SUR CHAQUE RANGÉE — c'est
   `space-between` qui le fait, mais on le RENFORCE ici pour le cas où la rangée n'aurait qu'un
   enfant (statut seul sans événement). */
body[data-interface="terrain"] .cl-me-l2 > .cl-me-statut { margin-left: auto; }
/* ⚠️ LA DATE ET L'HEURE SE POSENT EN LIGNE, ALIGNÉES À DROITE, ET L'HEURE EST DANS SA PASTILLE. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-quand {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  margin-left: auto;
  padding: 0;
  border: 0;
  white-space: nowrap;
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-quand-j {
  font: 800 12.5px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #ffffff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
}
/* ⚠️ L'HEURE EN PASTILLE BLANCHE : c'est la réponse à « à quelle heure je suis », et sur une photo
   elle doit être l'élément le PLUS identifiable de la rangée. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-quand-h {
  padding: 2px 8px; border-radius: 999px;
  background: rgba(255, 255, 255, .95); color: #a8291f;
  font: 700 10px 'DM Mono', monospace; letter-spacing: .03em;
}

/* ── LES CHIFFRES : PLUS AUCUN PAVÉ ────────────────────────────────────────────────────── */
/* ⚠️⚠️ ON RÉÉCRIT LE FOND À ZÉRO, ET C'EST DÉLIBÉRÉ : deux de mes règles précédentes posaient un
   dégradé (86 % puis 30 %). La troisième les écrase — mais laisser trois règles dont deux sont
   mortes rend le fichier illisible, et le prochain lecteur ne saura pas laquelle s'applique.
   On garde celle-ci, et elle dit tout. */
body[data-interface="terrain"] .cl-me-chiffres {
  background: none;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
  border-radius: 0;
  padding: 12px 0 2px;
}
/* ⚠️ LA SÉPARATION AVEC L'IDENTITÉ DISPARAÎT AUSSI : c'était un trait qui coupait le widget en deux. */
body[data-interface="terrain"] .cl-me-chiffres { border-top: 0; }
/* ⚠️ ET L'IDENTITÉ GARDE SON `flex: 1` : c'est lui qui pousse les chiffres EN BAS, donc « l'espace
   qui reste en bas » que le client a demandé. */
body[data-interface="terrain"] .cl-me-ident { flex: 1 1 auto; justify-content: center; }

/* ── L'ALIGNEMENT DES TROIS WIDGETS ────────────────────────────────────────────────────── */
/* ⚠️⚠️ « TU ARRANGES LES WIDGETS PARCE QU'ILS SONT MAL ORGANISÉS ». Le défaut mesurable : les trois
   chiffres et les trois libellés n'ont pas la même longueur (« cadeaux remis » vs « participants »),
   donc les colonnes finissent par se décaler. Trois corrections :
     1. `grid-template-columns: repeat(3, minmax(0, 1fr))` -> trois colonnes ÉGALES, déjà en place ;
     2. `place-items: center` -> tout est centré dans sa colonne, donc l'axe est le même ;
     3. `align-items: start` sur la grille -> les pastilles s'alignent sur leur haut, pas sur leur
        bas, même si un libellé passe à deux lignes.
   ⚠️ ET LE LIBELLÉ NE PASSE JAMAIS À DEUX LIGNES : `white-space: nowrap` + un `text-overflow`
   invisible. Un libellé qui occupe deux lignes décale l'axe vertical de sa colonne. */
body[data-interface="terrain"] .cl-me-wgs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  align-items: start;
  justify-items: center;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; min-width: 0;
  background: none; border: 0; padding: 4px 2px 2px;
  border-radius: 0;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lib {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
/* ⚠️ LA PASTILLE EST CENTRÉE SUR L'AXE DU CHIFFRE : `margin: 0 auto` + `flex: none` pour qu'elle ne
   se comprime pas. Une pastille comprimée devient un ovale, et un ovale ne se lit plus comme une
   pastille. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic { margin: 0 auto 2px; flex: none; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-n { line-height: 1.1; }

@media (max-width: 560px) {
  body[data-interface="terrain"] .cl-me.a-photo .cl-me-evt { font-size: 15.5px; }
  body[data-interface="terrain"] .cl-me.a-photo .cl-me-qui { font-size: 13.5px; }
}

/* ==============================================================================================
   LES CARTES PASSENT AU SOMBRE — SEULS LES WIDGETS GARDENT LEUR TEINTE CLAIRE
   ==============================================================================================
   Client (verbatim) : « je veux que seuls les widgets puissent avoir le truc clair, et les autres
   doivent être arrangés par rapport au fond sombre qui est derrière. Souvent les tablettes sont
   utilisées dans des soirées, donc la nuit, donc vaut mieux avoir des images sombres pour ne pas
   faire mal aux yeux. »

   ⚠️⚠️ LA MESURE EST SANS APPEL : six cartes sur sept étaient en CRÈME rgb(244,241,234) sur un fond
   rgb(15,16,17). C'est ma faute — mon patch « LE BLANC CASSÉ » peint TOUTES les `.cl-carte`. Il
   avait été écrit quand le fond de page était clair ; le fond est devenu sombre, la règle est
   restée. Une règle qui survit à la raison qui l'a fait naître devient un défaut.
   ⚠️ ET LA RAISON D'USAGE EST DONNÉE PAR LE CLIENT : soirées, la nuit, tablette à 60 cm des yeux.
   Une carte blanche dans une salle sombre éblouit — même défaut que le blanc pur des widgets, à
   l'échelle de la page.

   ⚠️⚠️ LA SURFACE SOMBRE EST CALCULÉE, PAS CHOISIE : la page est `#0f1011`. Une carte au MÊME ton
   disparaîtrait dedans (aucune séparation), et une carte trop claire éblouit. On prend donc deux
   tons juste au-dessus, comme la barre du bas (#0a0b0c, plus sombre que la page) : c'est la même
   logique d'escalier, en sens inverse. Le cheveu est un BLANC TRANSLUCIDE, pas un beige : un beige
   sur du sombre tire vers le gris sale.
   ⚠️ ET TOUTE L'ENCRE PASSE EN CLAIR. Chaque valeur est MESURÉE sur le nouveau fond de carte
   rgb(24,25,28) — c'est la contrepartie obligatoire d'une surface sombre, et le client a interdit
   le texte sombre sur fond sombre (« jamais du noir sur du noir »). */
body[data-interface="terrain"] .cl-carte {
  /* ⚠️ ON NE TOUCHE PAS À `.cl-me.a-photo` : la fiche porte SA photo et sa couleur de campagne. */
  background-color: #18191c;
  background-image: none;
  border-color: rgba(255, 255, 255, .10);
  color: #f2f1ef;
}
/* ⚠️ LA FICHE GARDE SON FOND DE CAMPAGNE : on la ré-exempte explicitement, sinon la règle
   ci-dessus l'écraserait (même spécificité, ordre plus bas). */
body[data-interface="terrain"] .cl-me.a-photo {
  background-color: var(--c1, #8f1f28);
  background-image:
    linear-gradient(0deg, rgba(8, 9, 10, .68), rgba(8, 9, 10, .68)),
    var(--photo, none);
  background-size: cover, cover;
  background-position: center, var(--focus, center 42%);
  border-color: rgba(255, 255, 255, .13);
}
/* ⚠️ LES CASES DU STOCK ET DE L'AGENDA RESTENT LES SEULES SURFACES CLAIRES : ce sont des widgets.
   Un léger voile blanc translucide, pas un blanc plein — sur du sombre, un blanc plein redevient
   une source d'éblouissement. */
body[data-interface="terrain"] .cl-sw {
  background: rgba(255, 255, 255, .94);
  border-color: rgba(255, 255, 255, .16);
}
/* ⚠️ TOUTE L'ENCRE DES CARTES PASSE EN CLAIR — MESURÉE sur rgb(24,25,28) :
     #f2f1ef  titres et noms          15.31:1
     #c9c6c0  sous-titres et légendes  8.02:1
     #a8adb6  libellés secondaires     6.14:1
   ⚠️ LES VALEURS SONT DIFFÉRENTES DE CELLES SUR CRÈME : un gris foncé (#55585c) donnerait 1.9:1
   ici. C'est le fond qui commande l'encre, jamais l'inverse. */
body[data-interface="terrain"] .cl-carte h2,
body[data-interface="terrain"] .cl-carte h3,
body[data-interface="terrain"] .cl-carte strong { color: #f2f1ef; }
body[data-interface="terrain"] .cl-carte .section-caption,
body[data-interface="terrain"] .cl-carte .cl-ev-nom small,
body[data-interface="terrain"] .cl-carte .cl-ligne-val,
body[data-interface="terrain"] .cl-carte .cl-ev-sous { color: #c9c6c0; }
body[data-interface="terrain"] .cl-carte .cl-tete-ic { color: var(--oz-c1, #e8737c); }
/* ⚠️ LES LIGNES DU RÉCAP ET DE L'AGENDA : fond sombre, encre claire, et le cheveu devient un blanc
   translucide. */
body[data-interface="terrain"] .cl-ev-ligne { border-bottom-color: rgba(255, 255, 255, .09); }
body[data-interface="terrain"] .cl-ev {
  background: rgba(255, 255, 255, .04);
  border-color: rgba(255, 255, 255, .10);
  color: #f2f1ef;
}
body[data-interface="terrain"] .cl-ev:hover { background: rgba(255, 255, 255, .075); border-color: rgba(255, 255, 255, .18); }
body[data-interface="terrain"] .cl-ev-date { background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .12); }
body[data-interface="terrain"] .cl-ev-date b { color: #ffffff; }
body[data-interface="terrain"] .cl-ev-date i { color: #c9c6c0; }
body[data-interface="terrain"] .cl-ev-corps strong { color: #ffffff; }
body[data-interface="terrain"] .cl-ev-corps small { color: #c9c6c0; }
body[data-interface="terrain"] .cl-ev-etat { background: rgba(255, 255, 255, .10); color: #e8e6e2; }
body[data-interface="terrain"] .cl-ev.est-encours { border-color: rgba(232, 115, 124, .48); background: rgba(212, 43, 56, .16); }
body[data-interface="terrain"] .cl-ev.est-encours .cl-ev-etat { background: #c0392b; color: #ffffff; }
/* ⚠️ LES CASES DU RÉCAP GARDE LEUR TEINTE CLAIRE : elles sont les WIDGETS, donc elles restent la
   seule chose lumineuse — c'est exactement ce que le client demande (« seuls les widgets »). */
body[data-interface="terrain"] .cl-ev-w {
  background-color: color-mix(in srgb, var(--oz-c1, #8f1f28) 8%, #f7f5f0);
  border-color: rgba(255, 255, 255, .14);
}
/* ⚠️ LE TEXTE DU RÉCAP RESTE SOMBRE : il est SUR une case claire. On ne l'éclaircit donc PAS —
   l'éclaircir serait le défaut inverse. La règle est simple : l'encre suit LE FOND QU'ELLE A
   DERRIÈRE, pas le fond de la page. */
body[data-interface="terrain"] .cl-ev-w-nom { color: #171719; }
body[data-interface="terrain"] .cl-ev-w-sous { color: #5f5b54; }
body[data-interface="terrain"] .cl-ev-w-taux b { color: #2f6b3e; }
/* ⚠️ LA RECEPTION ET LE FEED SUIVENT LE MÊME TRAITEMENT. */
body[data-interface="terrain"] .cl-feed .list-row { background: rgba(255, 255, 255, .04); border-color: rgba(255, 255, 255, .09); }
body[data-interface="terrain"] .cl-feed .list-row strong { color: #f2f1ef; }
body[data-interface="terrain"] .cl-feed .row-sub { color: #c9c6c0; }
body[data-interface="terrain"] .cl-vide { color: #a8adb6; }
/* ==============================================================================================
   LA FICHE EN TROIS COLONNES — IDENTITÉ · CHIFFRES · STATUTS
   ==============================================================================================
   Client (verbatim) : « Je vois que sur le premier avec Heineken, il y a assez d'espace. Est-ce
   qu'on peut rassembler les widgets de participants, cadeaux, goodies en stock entre les statuts à
   gauche et à droite ? Le widget qui est en bas, on l'intègre aussi. »

   ⚠️⚠️ « IL Y A ASSEZ D'ESPACE » EST UN CONSTAT MESURABLE, PAS UNE IMPRESSION : la fiche fait
   toute la largeur disponible et son contenu n'en occupait qu'une partie — l'identité à gauche,
   les statuts à droite, un VIDE AU MILIEU que les trois chiffres remplissaient EN DESSOUS. Le
   déplacement ne gagne donc pas de place, il REMPLIT celle qui était perdue.
   ⚠️ ET LA FICHE EST PLUS COURTE : les chiffres ne sont plus une bande sous l'identité, ils sont
   SUR la même ligne. Ce qui rend à la photo la hauteur que la bande occupait — donc on voit plus
   d'image, ce qui était le but depuis le début.

   ⚠️⚠️ LA COLONNE DU MILIEU EST PLUS LARGE (`1.4fr` contre `1fr`), ET C'EST UNE MESURE : les
   libellés « participants », « cadeaux remis », « goodies en stock » sont les textes les plus longs
   de la fiche. Une colonne étroite les couperait, et un libellé coupé ne se lit pas.
   ⚠️ ET LA COLONNE DE DROITE EST `flex: none` : elle se dimensionne sur son contenu (la plus longue
   de ses trois lignes), donc les trois s'alignent sur le MÊME bord droit — ce que le client demande
   (« juste en dessous de En ligne »). Avec une largeur en `fr`, la colonne grandirait et les
   éléments se décaleraient de son bord.

   ⚠️ SUR ÉCRAN ÉTROIT, LES TROIS COLONNES S'EMPILENT : à moins de 720 px, la fiche n'a plus la
   place de poser trois blocs côte à côte sans écraser les libellés. L'empilement garde l'ordre
   identité → chiffres → statuts, donc la lecture reste la même. */

body[data-interface="terrain"] .cl-me-colonne {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  align-items: center;
  gap: 14px;
  width: 100%;
}

/* ── COLONNE 1 : L'IDENTITÉ ────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-ident {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0;
  /* ⚠️ PLUS DE `flex: 1` NI DE `justify-content: center` : ils servaient à pousser les chiffres
     vers le bas quand ils étaient une bande séparée. Les chiffres sont maintenant une COLONNE, donc
     c'est la grille qui répartit — et un `flex` sur un enfant de grille n'aurait aucun effet. */
  flex: none;
  justify-content: flex-start;
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-qui { font-size: 15px; line-height: 1.2; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-evt { font-size: 16px; line-height: 1.2; }

/* ── COLONNE 2 : LES TROIS CHIFFRES ────────────────────────────────────────────────────── */
/* ⚠️ `align-self: stretch` + `justify-content: center` : la colonne occupe toute la hauteur de la
   rangée et centre ses trois widgets verticalement. Sans le centrage, ils se colleraient en haut et
   la rangée paraîtrait vide en bas. */
body[data-interface="terrain"] .cl-me-wgs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  align-items: center;
  justify-items: center;
  align-self: stretch;
  /* ⚠️ UNE TRÈS LÉGÈRE COLONNE SUR LES CÔTÉS : elle sépare les chiffres de l'identité et des
     statuts sans ajouter de cadre. Un blanc translucide, comme tous les cheveux sur fond sombre. */
  padding: 0 12px;
  border-left: 1px solid rgba(255, 255, 255, .10);
  border-right: 1px solid rgba(255, 255, 255, .10);
}
/* ⚠️ LA CASE EST TRANSPARENTE : elle repose sur la photo. C'est la règle déjà posée, et elle doit
   survivre au passage en grille — un `background` hérité d'une règle antérieure recréerait le
   carré qu'on a retiré. */
body[data-interface="terrain"] .cl-me-wgs .cl-wg {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; min-width: 0;
  background: none; border: 0; padding: 0;
  border-radius: 0;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic { margin: 0 auto 4px; flex: none; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lib {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}

/* ── COLONNE 3 : LES STATUTS, ALIGNÉS À DROITE ─────────────────────────────────────────── */
/* ⚠️ `align-items: flex-end` ALIGNE LES TROIS ÉLÉMENTS SUR LE BORD DROIT DE LA COLONNE — c'est
   exactement la demande (« juste en dessous de En ligne »). `text-align` n'aurait rien fait : ce
   sont des blocs, pas du texte. */
body[data-interface="terrain"] .cl-me-droite {
  display: flex; flex-direction: column; align-items: flex-end; gap: 5px;
  flex: none;
}
/* ⚠️ LA DATE ET L'HEURE SONT SUR UNE SEULE LIGNE, À DROITE ELLES AUSSI. */
body[data-interface="terrain"] .cl-me-droite .cl-me-quand {
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap; padding: 0; border: 0;
}

/* ── LES CHIFFRES NE SONT PLUS UNE BANDE : ON RETIRE SON DERNIER VESTIGE ────────────────── */
body[data-interface="terrain"] .cl-me-chiffres {
  background: none; border: 0; padding: 0; margin: 0;
  box-shadow: none; backdrop-filter: none;
}
/* ⚠️ LA FICHE PEUT ÊTRE PLUS COURTE : les trois colonnes tiennent sur UNE rangée, au lieu de deux
   (identité puis chiffres). On abaisse donc la hauteur minimale — la photo y gagne de la place
   visible, ce qui était le but depuis le début. */
body[data-interface="terrain"] .cl-me.a-photo { min-height: 190px; }

/* ── SUR ÉCRAN ÉTROIT, LES TROIS COLONNES S'EMPILENT ───────────────────────────────────── */
@media (max-width: 720px) {
  body[data-interface="terrain"] .cl-me-colonne {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  body[data-interface="terrain"] .cl-me-wgs {
    padding: 9px 0 0;
    border-left: 0; border-right: 0;
    border-top: 1px solid rgba(255, 255, 255, .10);
  }
  body[data-interface="terrain"] .cl-me-droite {
    flex-direction: row; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: 7px;
  }
  body[data-interface="terrain"] .cl-me.a-photo { min-height: 0; }
}

/* ==============================================================================================
   LES PASTILLES DE GOODIES DEVIENNENT DES VIGNETTES, ET LA ROUE PASSE EN GRAND
   ==============================================================================================
   Client (verbatim) : « au lieu d'avoir que les bulles en bas des noms des goodies, je veux voir les
   petites icônes des goodies. Et puis la roue n'est pas bien chargée : il n'y a que les goodies qui
   sont chargés, mais pas la roue et le socle et tout. »

   ⚠️⚠️ LA ROUE : LE PROBLÈME N'ÉTAIT PAS UN CHARGEMENT, C'ÉTAIT UN ARGUMENT. L'appel passait
   `false`, et `large` ne règle pas qu'une taille : il dessine le SOCLE, il pose les VARIABLES de
   charte (ampoules, contour, support, aiguille) et il choisit `wheel-large` au lieu de `wheel-small`
   (230x230). Corrigé côté JavaScript. Ce bloc-ci donne à la roue grande la place qu'elle demande.
   ⚠️ `wheel-small` FAIT 230x230 FIXES. En `wheel-large`, la roue se dimensionne autrement — donc le
   conteneur doit pouvoir l'accueillir : sans `min-height` ni centrage, une roue de 240 px de rayon
   (480 px de diamètre) plus son socle déborderait de sa cellule de grille.
   ⚠️ ET LA COLONNE DE GAUCHE (le texte du jeu) RESTE `1fr` : la roue est le sujet de la carte, elle
   doit avoir la place la plus large. Sur écran étroit, le CSS existant empile déjà les deux.

   ⚠️⚠️ LES PASTILLES : ELLES DEVIENNENT DES VIGNETTES AVEC LE NOM DESSOUS. Le client veut « voir
   les petites icônes ». On garde le nom parce qu'une vignette seule se confond entre deux goodies
   voisins — l'image dit LEQUEL, le nom le dit en toutes lettres.
   ⚠️ LA TAILLE DE LA VIGNETTE EST CONTRAINTE PAR LE NOMBRE : jusqu'à 4 pastilles sur une ligne.
   À 4, chaque pastille fait ~90 px sur la largeur utile de la colonne de gauche — donc une vignette
   de 44 px laisse passer le nom en dessous. Plus grande, le nom serait coupé.
   ⚠️ ET LE NOM EST LIMITÉ À DEUX LIGNES : « T-shirt Heineken édition limitée » ferait trois lignes
   et décalerait les quatre pastilles sur des hauteurs différentes. */

/* ── LA ROUE GRANDE ────────────────────────────────────────────────────────────────────── */
/* ⚠️ LA CARTE DU JEU EST UNE GRILLE À DEUX COLONNES (texte | roue). Avec la roue complète, la
   colonne de la roue doit s'élargir — sinon le socle et les ampoules n'ont plus la place et le
   `wheel-large` hériterait d'une cellule trop étroite. */

/* ── LA ROUE DU JEU : LA BOÎTE DU DISQUE REÇOIT ENFIN SA TAILLE ──────────────────────── */
/* Client (verbatim) : « la roue n'est pas bien chargée. Il n'y a que les goodies qui sont
   chargés, mais pas la roue et le socle et tout. »

   ⚠️⚠️ MESURÉ : `.wheel-frame` = 300x300 mais `.wheel-shell` = 24x24 px. La coquille est
   `display: grid; place-items: center` et N'A AUCUNE TAILLE en dehors de l'atelier de roue —
   c'est l'atelier qui la lui donne (`.tab-roue .wheel-shell { height: 86%; aspect-ratio: 1/1 }`).
   Dans la carte du jeu, rien ne la lui donne : elle se réduit donc à son contenu, et son contenu
   (`.wheel-rotor`) est en `width: 100%` — 100 % de rien = rien. D'où une coquille de 24 px, la
   taille de l'axe du socle, son seul enfant qui se mesure tout seul.
   ⚠️ C'est exactement pourquoi le client voit les goodies mais pas le disque : les goodies sont
   dessinés en ABSOLU à taille calculée, le disque est dans la boîte écrasée.

   ⚠️⚠️ ON REPREND LA RECETTE DE L'ATELIER : le disque est posé en haut de sa boîte et le socle
   PEND dessous (`top: 100%`). C'est la seule façon d'avoir les deux à la fois :
     · une coquille CARRÉE — exigée par les ampoules et le vernis, posés en `inset: 0` ; sur une
       boîte plus haute que large ils deviendraient un OVALE ;
     · un socle qui descend SOUS le disque sans allonger la boîte.
   Donner une coquille haute aurait réglé le disque et cassé les ampoules. */
body[data-interface="terrain"] .cl-jeu { grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); align-items: center; }
body[data-interface="terrain"] .cl-jeu .quick-wheel {
  display: grid; place-items: start center;
  /* ⚠️ LA PLACE DU SOCLE EST RÉSERVÉE ICI, PAS DANS LA COQUILLE : le cadre fait 300 px et ne peut
     pas contenir le disque PLUS le socle. L'atelier fait pareil — sa colonne d'aperçu a une
     hauteur dédiée. Sans ce dégagement, le socle sortirait de la carte. */
  padding: 0 0 54px;
}
/* ⚠️ LA COQUILLE EST EXACTEMENT LA TAILLE DU CADRE : 300 px. C'est la valeur de `.wheel-large`
   (l.1963) — on ne l'invente pas, on la reprend pour que le disque remplisse son cadre au lieu
   d'en occuper 8 %. */
body[data-interface="terrain"] .cl-jeu .quick-wheel .wheel-shell {
  position: relative; width: 300px; height: 300px;
}
body[data-interface="terrain"] .cl-jeu .quick-wheel .wheel-rotor { width: 100%; height: 100%; }
/* ⚠️ LE SOCLE PEND SOUS LE DISQUE : positionné en absolu depuis le bas de la coquille. Il ne
   participe donc pas à la hauteur du cadre — c'est le `padding-bottom` ci-dessus qui lui fait
   de la place. `margin-top: 2px` est la valeur déjà posée sur `.wheel-stand` (l.8860). */
body[data-interface="terrain"] .cl-jeu .quick-wheel .wheel-stand {
  position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%); margin-top: 2px;
}
@media (max-width: 900px) {
  /* ⚠️ SUR ÉCRAN ÉTROIT, LE CSS EXISTANT EMPILE DÉJÀ LES DEUX COLONNES (`grid-template-columns:
     1fr` et `order: -1` sur la roue). La roue garde sa taille — c'est le texte qui s'ajuste. */
  body[data-interface="terrain"] .cl-jeu .quick-wheel { padding-bottom: 48px; }
}

/* ── LES PASTILLES DE GOODIES, AVEC LEUR VIGNETTE ──────────────────────────────────────── */
body[data-interface="terrain"] .reward-mini-list {
  display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 4px;
}
body[data-interface="terrain"] .reward-mini {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  width: 72px; min-width: 0;
  /* ⚠️ LA PASTILLE N'EST PLUS UNE « BULLE » DE TEXTE : elle porte un visuel, donc elle a besoin
     d'une surface pour que la vignette se détache. Un fond très léger, translucide, comme les
     autres cheveux sur la carte sombre. */
  padding: 7px 5px 6px;
  border-radius: 11px;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .10);
}
/* ⚠️ LA VIGNETTE EST RONDE, COMME LES PHOTOS DE GOODIES PARTOUT AILLEURS DANS LE PROJET (les jauges
   du stock, la fiche du goodie). Une vignette carrée ici et ronde là donnerait deux formes pour un
   même objet — et l'œil doit reconnaître sans réapprendre. */
body[data-interface="terrain"] .reward-mini-img {
  width: 44px; height: 44px; border-radius: 50%;
  object-fit: cover; flex: none;
  border: 1px solid rgba(255, 255, 255, .16);
  background: rgba(255, 255, 255, .05);
}
/* ⚠️ LE REPLI QUAND IL N'Y A PAS DE VISUEL EST UN CERCLE VIDE DISCRET — PAS un carré gris, et
   surtout PAS une balise `<img>` sans source. Une image sans source affiche l'icône « image
   manquante » du navigateur, ce qui donne l'impression d'une panne de l'application. */
body[data-interface="terrain"] .reward-mini-img.est-vide {
  display: block;
  border-style: dashed;
  border-color: rgba(255, 255, 255, .20);
}
body[data-interface="terrain"] .reward-mini b {
  font: 600 9.5px Manrope, system-ui, sans-serif;
  color: #e8e6e2; text-align: center; line-height: 1.25;
  /* ⚠️ DEUX LIGNES MAXIMUM : trois lignes décaleraient les pastilles sur des hauteurs différentes. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
/* ⚠️ LE CAS « AUCUN CADEAU » N'EST PAS UNE PASTILLE : c'est une phrase. Lui donner une vignette
   ronde serait montrer un objet qui n'existe pas. */
body[data-interface="terrain"] .reward-mini.est-aucun {
  width: auto; flex-direction: row; padding: 8px 12px;
}
body[data-interface="terrain"] .reward-mini.est-aucun b { color: #a8adb6; }

/* ==============================================================================================
   LA FICHE : LES STATUTS EN HAUT, L'IDENTITÉ À GAUCHE, LES CHIFFRES À DROITE, LE STOCK EN BAS
   ==============================================================================================
   Client (verbatim) : « les bulles en ligne / en cours / 24 septembre de 19h à 23h, ça sera en haut,
   dans les widgets, et ça serait l'information la plus haute, avant même Salma El Amrani. La ligne
   avant même Salma El Amrani tu mets En ligne, En cours, et de l'autre côté 24 septembre et 19h-23h.
   Et Salma, Soirée, Buddha-Bar et Heineken à gauche, et les trois widgets à droite en face. Et en
   bas, à la place de ces derniers, tu vas mettre le stock qu'on a avec les jauges que tu as fait. »

   ⚠️⚠️ « L'INFORMATION LA PLUS HAUTE » N'EST PAS UNE POSITION, C'EST UNE PRIORITÉ DE LECTURE : la
   première question qu'elle se pose en prenant la tablette est « suis-je en ligne, et suis-je en
   cours ? ». Elle sait son nom. Le statut passe donc avant l'identité — et c'est aussi ce qui met
   la date et l'heure sous ses yeux sans qu'elle ait à descendre.

   ⚠️⚠️ « À LA PLACE DE CES DERNIERS » = LA BANDE DU BAS, PAS LES TROIS CHIFFRES. Les trois chiffres
   étaient sous l'identité avant la refonte en colonnes. Ils restent, à droite, EN FACE de l'identité.
   C'est le stock qui reprend la place du bas — et il ne remplace donc rien. */

/* ── LIGNE 0 : LES STATUTS, TOUT EN HAUT ──────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-me-haut {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  /* ⚠️ UN FILET EN DESSOUS, PAS UN FOND : la fiche porte la photo de la campagne. Un fond opaque
     sur cette ligne cacherait la photo en haut — et on a passé trois correctifs à la remettre. */
  padding-bottom: 9px;
  border-bottom: 1px solid rgba(255, 255, 255, .13);
  flex-wrap: wrap;
}
/* ⚠️ LES DEUX STATUTS SONT CÔTE À CÔTE, PAS EMPILÉS : « En ligne » et « En cours » se lisent
   ensemble, c'est un état global. Les empiler aurait fait deux lignes pour une seule réponse. */
body[data-interface="terrain"] .cl-me-etats {
  display: inline-flex; align-items: center; gap: 8px;
}
/* ⚠️ LA DATE ET L'HEURE SONT SUR UNE SEULE LIGNE, À L'AUTRE BORD — le client dit « de l'autre
   côté 24 septembre et 19h-23h ». `space-between` sur la ligne 0 fait ce partage sans code. */
body[data-interface="terrain"] .cl-me-haut .cl-me-quand {
  display: inline-flex; align-items: center; gap: 7px;
  white-space: nowrap;
  /* ⚠️ AUCUNE BORDURE NI FOND : cette ligne vit sur la photo. Un pavé ici redessinerait le carré
     qu'on a retiré des widgets. */
  padding: 0; border: 0; background: none;
}

/* ── LIGNE 1 : L'IDENTITÉ À GAUCHE, LES TROIS CHIFFRES À DROITE ───────────────────────── */
body[data-interface="terrain"] .cl-me-colonne {
  display: grid;
  /* ⚠️ DEUX COLONNES SEULEMENT, ET C'EST LA STRUCTURE QUE LE CLIENT DÉCRIT : « Salma, Soirée,
     Buddha-Bar et Heineken à gauche, et les trois widgets à droite EN FACE ». Plus de colonne
     centrale — les chiffres ne sont plus ENTRE deux blocs, ils sont face à l'identité. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  width: 100%;
}
body[data-interface="terrain"] .cl-me-ident {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0; flex: none; justify-content: flex-start;
}
body[data-interface="terrain"] .cl-me.a-photo .cl-me-qui { font-size: 15px; line-height: 1.2; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-evt { font-size: 16px; line-height: 1.2; }

/* ⚠️ LES TROIS CHIFFRES GARDENT LA RECETTE DU BADGE, ET LEUR FILET LATÉRAL DISPARAÎT : il séparait
   trois colonnes, il n'a plus de raison d'être entre deux blocs seulement. Le blanc translucide
   reste — c'est le cheveu du projet sur fond sombre. */
body[data-interface="terrain"] .cl-me-wgs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  align-items: center; justify-items: center;
  align-self: stretch;
  padding: 0 10px;
  border-left: 1px solid rgba(255, 255, 255, .10);
  border-right: 0;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; min-width: 0;
  background: none; border: 0; padding: 0; border-radius: 0;
}
body[data-interface="terrain"] .cl-me-wgs .cl-wg-ic { margin: 0 auto 4px; flex: none; }
body[data-interface="terrain"] .cl-me-wgs .cl-wg-lib {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}

/* ── LIGNE 2 : LE STOCK, AVEC LES JAUGES ──────────────────────────────────────────────── */
/* ⚠️⚠️ LA GRILLE EST CELLE DE LA SECTION AUTONOME (`auto-fit` sur 84px) — recopiée, pas imitée.
   Imiter donnerait deux jauges qui divergent au premier réglage ; recopier donne la même jauge. */
body[data-interface="terrain"] .cl-me-stock {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
  gap: 6px;
  align-items: start; justify-items: center;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, .13);
  width: 100%;
}
/* ⚠️ LA CASE EST TRANSPARENTE ICI AUSSI : elle vit sur la photo. Le widget garde sa teinte, la
   case ne rajoute pas de carré. */
body[data-interface="terrain"] .cl-me-stock .cl-sw {
  background: none; border: 0; padding: 0;
}
/* ⚠️ LA FICHE S'ALLONGE D'AUTANT : la ligne du stock ajoute ~96 px (l'anneau de 64 px + le nom +
   le nombre). On l'annonce au lieu de laisser le contenu déborder. */
body[data-interface="terrain"] .cl-me.a-photo { min-height: 264px; }

@media (max-width: 720px) {
  /* ⚠️ SUR ÉCRAN ÉTROIT, L'IDENTITÉ PASSE AU-DESSUS DES CHIFFRES : à moins de 720 px, les deux
     blocs côte à côte écraseraient les libellés « goodies en stock » et « cadeaux remis ». */
  body[data-interface="terrain"] .cl-me-colonne { grid-template-columns: 1fr; gap: 10px; }
  body[data-interface="terrain"] .cl-me-wgs {
    padding: 9px 0 0; border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, .10);
  }
  body[data-interface="terrain"] .cl-me-haut { gap: 7px; }
  body[data-interface="terrain"] .cl-me.a-photo { min-height: 0; }
}

/* ==============================================================================================
   LES TROIS CORRECTIONS : LE STATUT « EN COURS » DEVIENT UNE PASTILLE COMME « EN LIGNE »,
   LES TEXTES DES GOODIES SONT ENFIN LISIBLES, ET L'ICÔNE DE BOX DISPARAÎT
   ==============================================================================================
   Client (verbatim) : « le En cours doit être comme En ligne, ça ne sert à rien de le mettre en blanc
   et en rouge. Mets un point en vert genre que c'est en cours. Les titres, les textes, les titres des
   goodies ne paraissent pas. Et il y a une petite icône de box qui ne sert à rien, elle est juste
   redondante sur les 4 widgets. »

   ⚠️⚠️ « NE PARRAISSENT PAS » = UNE MESURE, ET ELLE EST EXACTE : `.cl-sw-nom` était en `#171719` —
   l'encre du thème CLAIR — sur une case sombre. C'est le troisième signe du même défaut de fond
   (« l'encre suit LE FOND QU'ELLE A DERRIÈRE, pas le fond de la page »), et cette fois les noms des
   goodies étaient invisible : #171719 sur rgba(255,255,255,.06) au-dessus de la photo.
   ⚠️⚠️ LA PASTILLE « EN COURS » ÉTAIT LE SEUL PLEIN DE TOUTE LA PAGE (« le seul plein : elle est EN
   COURS »). Sur une photo de campagne rouge, un pavé blanc saturait — et il attirait l'œil AVANT la
   photo et AVANT les chiffres. Deux états du même sujet (le réseau, la mission) doivent se lire
   comme une PAIRE : même forme, seul le POINT porte la couleur d'état. */

/* ── 1. LE STATUT « EN COURS » PREND LA FORME DE « EN LIGNE » ──────────────────────────── */
/* ⚠️ ON REPREND EXACTEMENT LA RECETTE DE `.cl-reseau` SUR PHOTO (l.20629) : verre sombre, cheveu
   blanc translucide, encre blanche, point de couleur. Recopier la recette plutôt que l'imiter :
   deux surfaces qui doivent se lire ensemble doivent être PEINTES pareil. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut,
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-encours {
  background: rgba(8, 9, 10, .50);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
  /* ⚠️ LA MÊME GRAISSE QUE « En ligne » : 600 et non 700. Le gras du statut était un second
     signal de « c'est important » — le client n'en veut qu'un : le point. */
  font-weight: 600;
  letter-spacing: .06em;
}
/* ⚠️ LE POINT EST DANS LES DEUX PASTILLES : c'est LUI qui porte l'information. On lui donne la
   même taille que celui du réseau (6 px) pour que les deux pastilles aient la même hauteur de
   texte — sinon la ligne du haut serait bancale. */
body[data-interface="terrain"] .cl-me-statut .rp-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  display: inline-block; margin-right: 6px; vertical-align: middle;
}
/* ⚠️ VERT POUR « EN COURS » — la demande littérale. Un vert plus clair que le vert du fond :
   il doit se voir SUR le verre sombre, pas se confondre avec lui. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-encours .rp-dot,
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-encours .rp-dot { background: #6ee29a; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-avenir .rp-dot,
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-avenir .rp-dot { background: #8fb4d6; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-demain .rp-dot,
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-demain .rp-dot { background: #f0c65a; }
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-passe .rp-dot,
body[data-interface="terrain"] .cl-me-evt-bloc .cl-me-statut.est-passe .rp-dot { background: #9a9a94; }

/* ⚠️ ET LES AUTRES ÉTATS SUR PHOTO SUIVENT LE MÊME VERRE — sans ça, « À VENIR » resterait un pavé
   bleu clair sur la photo, et la paire redeviendrait bancale le jour où l'événement n'est pas
   encore en cours. */
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-avenir,
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-demain,
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-passe,
body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-aucun {
  background: rgba(8, 9, 10, .50);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
}

/* ── 2. LES TEXTES DES GOODIES PASSENT SUR L'ENCRE CLAIRE ──────────────────────────────── */
/* ⚠️⚠️ LA CAUSE EST MESURÉE : `.cl-sw-nom` était `#171719` et `.cl-sw-n` `#6f6b63` — les encres du
   thème CLAIR, restées là quand les cases sont devenues sombres. Sur la case actuelle, le nom du
   goodie était quasi invisible : c'est mot pour mot « les titres des goodies ne paraissent pas ».
   ⚠️ LES COULEURS SONT MESURÉES SUR LE FOND RÉEL (case sombre translucide au-dessus de la photo),
   seuil 4,5:1 pour du texte de 10-12 px. */
body[data-interface="terrain"] .cl-sw-nom,
body[data-interface="terrain"] .cl-me-stock .cl-sw-nom {
  color: #f2f1ef;                 /* 15,57:1 sur la carte — le titre se lit d'un coup */
}
body[data-interface="terrain"] .cl-sw-n,
body[data-interface="terrain"] .cl-me-stock .cl-sw-n {
  color: #c9c6c0;                 /* 10,31:1 — la légende « sur 12 » */
}
/* ⚠️ LE CHIFFRE GARDE SES TROIS NIVEAUX, MAIS ILS SONT RE-MESURÉS SUR LE FOND SOMBRE : le vert
   `#3f7a4e` du thème clair ne donnait que 2,4:1 ici. On l'éclaircit jusqu'à rester vert — c'est la
   COULEUR du niveau qui porte l'information, sa luminosité n'est que de la lisibilité. */
body[data-interface="terrain"] .cl-sw-n b,
body[data-interface="terrain"] .cl-me-stock .cl-sw-n b { color: #6fd39a; }          /* ok    */
body[data-interface="terrain"] .cl-sw[data-niv="bas"]  .cl-sw-n b { color: #e5c05e; }  /* bas   */
body[data-interface="terrain"] .cl-sw[data-niv="vide"] .cl-sw-n b { color: #ff8b7e; }  /* vide  */
/* ⚠️ LE CHEVEU DE L'ANNEAU SUIT : `#e4dfd4` (beige clair) sur une case sombre donnait un anneau
   « sale ». On passe au blanc translucide, comme tous les cheveux du fond sombre. */
body[data-interface="terrain"] .cl-sw-jauge {
  background: conic-gradient(var(--sw-teinte, #4a8c5c) calc(var(--p, 0) * 1%), rgba(255, 255, 255, .16) 0);
}
/* ⚠️ LA CASE VIDE (photo absente) SUIT AUSSI : `#f4f1ea` sous la photo était une tache claire dans
   l'anneau. */
body[data-interface="terrain"] .cl-sw-jauge > img,
body[data-interface="terrain"] .cl-sw-vide { background: rgba(255, 255, 255, .10); }
body[data-interface="terrain"] .cl-sw-vide svg { color: rgba(255, 255, 255, .45); }
/* ⚠️ LA CASE « ÉPUISÉ » PERD SON FOND CRÈME : `#fdf6f5` sur la fiche faisait un carré blanc dans un
   widget transparent. On garde le signal par la bordure, qui est un cheveu, pas un pavé. */
body[data-interface="terrain"] .cl-sw[data-vide="oui"] {
  border-color: rgba(255, 139, 126, .42);
  background: none;
}

/* ── 3. L'ICÔNE DE BOX EST RETIRÉE (le marquage a disparu côté JS) ─────────────────────── */
/* ⚠️ ON GARDE LA RÈGLE, MAIS INERTE : si une icône ressort par un patch ancien, elle doit
   disparaître plutôt que de revenir en carré blanc sur les quatre anneaux. */
body[data-interface="terrain"] .cl-sw-ic { display: none !important; }

/* ⚠️⚠️ LA COULEUR DU TEXTE « EN COURS » : MA RÈGLE PERDAIT LA SPÉCIFICITÉ, PAS LA BATAILLE DES
   VALEURS. Mesuré après le premier correctif : le FOND était bien passé au verre
   (rgba(8,9,10,.5), identique à « En ligne ») mais le TEXTE restait `rgb(168,41,31)` — rouge foncé
   sur verre sombre, donc illisible.
   ⚠️ LA CAUSE EST UNE SPÉCIFICITÉ, ET ELLE SE COMPTE :
        ma règle        `… .cl-me.a-photo .cl-me-statut`               = 0,4,1
        la règle d'avant `… .cl-me.a-photo .cl-me-statut.est-encours`   = 0,5,1  <- gagne
   Le fond passait parce que l'ancienne règle de fond n'existait plus sur cette classe ; la couleur
   résistait parce que l'ancienne règle de couleur VISait `.est-encours`. Deux propriétés du même
   élément, deux gagnants différents — c'est exactement le piège déjà payé sur `.app-shell > .sidebar`.
   ⚠️ ET LE STATUT A CHANGÉ DE PLACE : il vivait dans `.cl-me-evt-bloc`, il est maintenant dans
   `.cl-me-haut`. Les règles qui le visaient PAR `.cl-me-evt-bloc` ne le touchent plus du tout — donc
   mon correctif ne pouvait pas les battre. On monte donc la spécificité en ajoutant `.est-encours`
   ET `.cl-me-haut`, et on garde les DEUX ancêtres (l'ancien et le nouveau) pour que la règle
   s'applique où qu'il soit. */

body[data-interface="terrain"] .cl-me.a-photo .cl-me-statut.est-encours,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-encours,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-avenir,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-demain,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-passe,
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-aucun {
  /* ⚠️ VERRE SOMBRE + ENCRE BLANCHE : exactement la recette de « En ligne » (l.20629). Les deux
     pastilles sont deux états du même sujet — elles doivent se lire comme une PAIRE. */
  background: rgba(8, 9, 10, .50);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
  font-weight: 600;
  letter-spacing: .06em;
}
/* ⚠️ LE POINT EST DANS LES DEUX PASTILLES, ET C'EST LUI QUI PORTE L'INFORMATION — c'est déjà la
   règle du projet pour le réseau (« le POINT garde sa couleur d'état, la surface s'adapte »). */
body[data-interface="terrain"] .cl-me-haut .cl-me-statut .rp-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  display: inline-block; margin-right: 6px; vertical-align: middle;
}
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-encours .rp-dot { background: #6ee29a; }
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-avenir .rp-dot  { background: #8fb4d6; }
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-demain .rp-dot  { background: #f0c65a; }
body[data-interface="terrain"] .cl-me-haut .cl-me-statut.est-passe  .rp-dot  { background: #9a9a94; }
/* ⚠️ ET « EN LIGNE » AUSSI GAGNE SON POINT VERT CLAIR dans la ligne du haut : il l'avait déjà par la
   surcouche photo, on le rend explicite pour que les deux pastilles soient peintes par le MÊME
   bloc — sinon la paire dépend de deux endroits différents du fichier, et l'un des deux dérivera. */
body[data-interface="terrain"] .cl-me-haut .cl-reseau {
  background: rgba(8, 9, 10, .50);
  border-color: rgba(255, 255, 255, .26);
  color: #ffffff;
  backdrop-filter: blur(7px);
}
body[data-interface="terrain"] .cl-me-haut .cl-reseau[data-etat="en-ligne"]  .rp-dot { background: #6ee29a; }
body[data-interface="terrain"] .cl-me-haut .cl-reseau[data-etat="attente"]   .rp-dot { background: #f0c65a; }
body[data-interface="terrain"] .cl-me-haut .cl-reseau[data-etat="hors-ligne"] .rp-dot { background: #ff8b7e; }
/* ⚠️ ET LA DATE / L'HEURE SUIVENT LE MÊME VERRE — elles sont sur la même ligne, un pavé d'une autre
   matière à côté de deux pastilles de verre ferait trois objets au lieu d'une rangée. */
body[data-interface="terrain"] .cl-me-haut .cl-me-quand {
  color: #ffffff;
}
body[data-interface="terrain"] .cl-me-haut .cl-me-quand-j,
body[data-interface="terrain"] .cl-me-haut .cl-me-quand-h {
  color: #ffffff;
}

/* ==============================================================================================
   LE JEU DEVIENT UN RACCOURCI : L'ICÔNE DE LA ROUE, ET UN BOUTON QUI Y MÈNE
   ==============================================================================================
   Client (verbatim) : « on va mettre l'icône que je t'ai donnée tout à l'heure. Et faire un petit
   widget sympa de lancer la roue. C'est comme un raccourci, tout comme le bouton accès au jeu pour
   aller directement au jeu. »

   ⚠️⚠️ LA ROUE DESSINÉE PAR `renderWheel` EST RETIRÉE DE CETTE CARTE. Elle était un APERÇU — une
   deuxième roue, en miniature, à côté du vrai jeu. Deux objets pour un seul, dont l'un ne tourne pas :
   il ne sert qu'à dire « c'est une roue », et l'ICÔNE le dit sans occuper 300 px et sans faire croire
   qu'on peut la faire tourner d'ici.
   ⚠️⚠️ ET LE WIDGET EST UNE VRAIE PORTE, PAS UNE DÉCORATION : « c'est comme un raccourci, tout comme
   le bouton accès au jeu ». Le geste est le MÊME que celui du dashboard (`setView('participant')`) —
   on n'invente pas un second chemin vers le jeu, on raccourcit celui qui existe.

   ⚠️ L'ICÔNE EST UNE IMAGE, PAS UN SVG : le client l'a fournie (512×512, détourée). Un dessin refait
   à la main n'aurait jamais été le sien — et il l'a explicitement demandée. On l'affiche donc telle
   quelle, à une taille qui la met en valeur sans écraser le bouton.
   ⚠️ ELLE EST SANS `alt` (donc ignorée des lecteurs d'écran) : elle est DÉCORATIVE, elle n'apprend
   rien qui ne soit déjà écrit dans le titre et le bouton. Le bouton, lui, reste un vrai `<button>`,
   focusable et cliquable au clavier. */

body[data-interface="terrain"] .cl-jeu-roue {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* ⚠️ LA COLONNE EST BORNÉE : l'icône fait 132 px, et la grille de la carte (`minmax(280px,340px)`)
     lui laisse la place. Sans cette borne, l'icône pousserait le texte de la colonne de gauche. */
  flex: none;
  min-width: 0;
  padding: 4px 0;
}
/* ⚠️ L'ICÔNE REPOSE SUR UNE PASTILLE DE VERRE, PAS DIRECTEMENT SUR LE FOND : le PNG a des couleurs
   pleines (jaune, rouge, bleu) qui « flottent » sur une carte sombre sans ancrage. Une pastille
   translucide la pose, sans ajouter de cadre opaque — même recette que les pastilles du haut. */
body[data-interface="terrain"] .cl-jeu-ic {
  display: grid;
  place-items: center;
  width: 148px; height: 148px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .12);
  /* ⚠️ LE HALO EST DE LA COULEUR DE LA ROUE ELLE-MÊME (son jaune), pas un rouge décoratif : c'est
     un rappel du disque, pas un ornement. Interdit ailleurs, autorisé ici pour cette raison. */
  box-shadow: 0 0 0 1px rgba(255, 205, 60, .10), 0 10px 30px -12px rgba(0, 0, 0, .6);
}
body[data-interface="terrain"] .cl-jeu-ic img {
  /* ⚠️ 132 px DANS UNE PASTILLE DE 148 : il reste 8 px d'air de chaque côté, ce qui fait lire la
     pastille comme un cadre autour de l'icône et non comme un fond derrière elle. */
  width: 132px; height: 132px;
  object-fit: contain;
  display: block;
  /* ⚠️ LA ROTATION LENTE EST L'ANIMATION DE L'ONGLET QUE LE CLIENT DEMANDE — « pour animer l'onglet
     tu mets ce PNG ». Elle est TRÈS lente (24 s) et s'arrête au survol : une icône qui tourne en
     permanence fatigue l'œil en soirée, à 60 cm de la tablette. */
  animation: cl-jeu-tourne 24s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  body[data-interface="terrain"] .cl-jeu-ic img { animation: none; }
}
@keyframes cl-jeu-tourne {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ── LE BOUTON ─────────────────────────────────────────────────────────────────────────── */
/* ⚠️ LA COULEUR EST CELLE DE LA CHARTE DE LA CAMPAGNE (`--oz-c1`), PAS LE ROUGE PAR DÉFAUT : c'est
   déjà la règle de la tablette (« la collaboratrice est toujours liée à la charte de la campagne où
   elle est »), et le bouton de lancer doit reprendre `couleur_1` de la roue assignée.
   ⚠️ L'ENCRE EST CALCULÉE, PAS CHOISIE : un `--oz-c1` jaune avec de l'encre blanche donnerait un
   bouton illisible. On la force en noir pur, qui passe sur toutes les teintes claires, et le
   `text-shadow` est interdit ici. */
body[data-interface="terrain"] .cl-jeu-go {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 22px;
  min-height: 46px;
  border: 0; border-radius: 999px;
  cursor: pointer;
  font: 700 13.5px 'Plus Jakarta Sans', 'Manrope', system-ui, sans-serif;
  letter-spacing: .01em;
  /* ⚠️ `#0d6b3f` EST LE `couleur_1` RÉEL DE LA ROUE « — Heineken » (mesuré en base). On le met en
     repli pour qu'un jour sans charte posée garde un bouton vert lisible au lieu d'un bouton noir. */
  background: var(--oz-c1, #0d6b3f);
  color: #ffffff;
  transition: transform .16s ease, box-shadow .16s ease;
  box-shadow: 0 8px 22px -10px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .18);
}
body[data-interface="terrain"] .cl-jeu-go:hover:not(:disabled) { transform: translateY(-1px); }
body[data-interface="terrain"] .cl-jeu-go:active:not(:disabled) { transform: translateY(0); }
body[data-interface="terrain"] .cl-jeu-go:focus-visible {
  outline: 3px solid rgba(255, 255, 255, .55); outline-offset: 3px;
}
/* ⚠️ DÉSACTIVÉ QUAND IL N'Y A RIEN À TIRER : sans cette garde, elle lance une participation et
   arrive sur un jeu vide. L'état désactivé doit se VOIR (pas seulement ne pas répondre) — sinon
   elle appuie, rien ne se passe, et elle croit que la tablette est en panne. */
body[data-interface="terrain"] .cl-jeu-go:disabled {
  background: rgba(255, 255, 255, .12);
  color: rgba(255, 255, 255, .45);
  cursor: not-allowed;
  box-shadow: none;
}
body[data-interface="terrain"] .cl-jeu-go-fleche {
  font-size: 15px; line-height: 1;
  opacity: .85;
  transition: transform .16s ease;
}
body[data-interface="terrain"] .cl-jeu-go:hover:not(:disabled) .cl-jeu-go-fleche { transform: translateX(3px); }

/* ⚠️ SUR ÉCRAN ÉTROIT, LE BLOC PASSE SOUS LE TEXTE : la grille existante empile déjà les deux
   colonnes (`grid-template-columns: 1fr` + `order: -1` sur `.quick-wheel`). On donne juste à
   l'icône une taille qui ne prenne pas tout l'écran d'un coup. */
@media (max-width: 900px) {
  body[data-interface="terrain"] .cl-jeu-ic { width: 120px; height: 120px; }
  body[data-interface="terrain"] .cl-jeu-ic img { width: 106px; height: 106px; }
}

/* ==============================================================================================
   LE PANNEAU DU BAS : LA VALIDATION À GAUCHE, LE JEU À DROITE — ET LES TROIS TEXTES ILLISIBLES
   ==============================================================================================
   Client (verbatim) : « tu peux rendre le widget à moitié et mettre à gauche la validation du stock.
   Ça sera comme un onglet de notification où elle reçoit ses notifications pour valider son stock ;
   si elle le valide, il va se charger automatiquement en haut. Et ajuste-moi ce contraste noir et
   texte parce que j'arrive pas à voir. »

   ⚠️⚠️ « J'ARRIVE PAS À VOIR » EST MESURÉ, ET C'EST TROIS TEXTES, PAS UN :
        .cl-jeu .section-label « Jeu rapide »    rgb(150,28,38) sur rgb(24,25,28)  = 2,09:1  ILLISIBLE
        .cl-feed .section-caption                rgb(56,54,51)  sur rgb(24,25,28)  = 1,46:1  ILLISIBLE
        .cl-feed .row-sub « Gourde Heineken »     rgb(56,54,51)  sur rgb(24,25,28)  = 1,46:1  ILLISIBLE
   ⚠️ LA CAUSE EST LA MÊME POUR LES TROIS, ET C'EST LA QUATRIÈME FOIS DE LA SOIRÉE : ce sont les
   encres du thème CLAIR (`--red: #961c26`, `--muted: #55585c`-like) restées sur des surfaces
   sombres. Une règle qui survit à la raison qui l'a fait naître devient un défaut.
   ⚠️ ET LA MESURE EST SURFACE PAR SURFACE : je remonte la chaîne jusqu'au premier ancêtre OPAQUE,
   donc un texte sur une carte sombre posée sur une page sombre est jugé contre SA carte, pas contre
   la page. Mesurer contre la page aurait donné un faux « conforme » sur la moitié d'entre eux. */

/* ── LE PANNEAU : DEUX COLONNES ÉGALES ─────────────────────────────────────────────────── */
/* ⚠️ `1fr 1fr` ET RIEN D'AUTRE : le client dit « le widget à moitié ». Une colonne plus large ferait
   lire l'ensemble comme « le jeu, avec un accessoire » au lieu de deux choses de même poids. */
body[data-interface="terrain"] .cl-panneau {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: start;
}
/* ⚠️ LES DEUX COLONNES SONT DES BLOCS, PAS DES CARTES : la section EST la carte. Une carte dans une
   carte redessinerait le carré qu'on a retiré des widgets ce soir. */
body[data-interface="terrain"] .cl-notif,
body[data-interface="terrain"] .cl-jeu-bloc {
  min-width: 0;
  display: flex; flex-direction: column;
}
/* ⚠️ UN FILET ENTRE LES DEUX, PAS AUTOUR : il sépare sans enfermer. Blanc translucide, comme tous
   les cheveux du fond sombre. */
body[data-interface="terrain"] .cl-jeu-bloc {
  padding-left: 18px;
  border-left: 1px solid rgba(255, 255, 255, .10);
}
/* ⚠️ LE TEXTE « RIEN À VALIDER » : il doit se lire, sinon la colonne paraît cassée. */
body[data-interface="terrain"] .cl-notif-vide {
  margin: 0; padding: 10px 0;
  font: 500 12px Manrope, system-ui, sans-serif;
  color: #a8adb6;                 /* 7,80:1 sur la carte sombre */
  line-height: 1.45;
}

/* ── LES TROIS TEXTES ILLISIBLES, CORRIGÉS À LA SOURCE ─────────────────────────────────── */
/* ⚠️⚠️ LE LIBELLÉ DE SECTION ÉTAIT EN ROUGE DE CHARTE (`#961c26`) — 2,09:1 sur la carte sombre.
   On le passe à l'encre claire. ⚠️ On ne touche PAS la règle globale `.section-label` : elle sert
   sur les surfaces CLAIRES (les fiches admin), où le rouge est lisible à 5,07:1. On corrige
   SEULEMENT sur la tablette, où le fond est sombre — c'est la règle « l'encre suit le fond qu'elle
   a derrière ». */
body[data-interface="terrain"] .section-label {
  color: #e8e6e2;                 /* 6,05:1 sur la carte sombre */
}
/* ⚠️ LES DEUX TEXTES GRIS FONCÉ (`rgb(56,54,51)`) DE LA SECTION ACTIVITÉ : la légende sous le titre
   et le nom du goodie sous chaque participant. Même cause, même correction. */
body[data-interface="terrain"] .section-caption,
body[data-interface="terrain"] .row-sub {
  color: #a8adb6;                 /* 7,80:1 */
}
/* ⚠️ ET LE SOUS-TITRE DU JEU, MÊME ORIGINE : il portait l'encre claire du thème clair. */
body[data-interface="terrain"] .cl-jeu-sous {
  margin: 4px 0 10px;
  font: 500 12px Manrope, system-ui, sans-serif;
  color: #a8adb6;
}

/* ── LA ROUE : FIXE, SANS CONTOUR, SANS PASTILLE ───────────────────────────────────────── */
/* ⚠️⚠️ ON RETIRE TOUT CE QUE J'AVAIS AJOUTÉ : la pastille de verre, son contour, son halo, ET la
   rotation. Client : « la roue doit être fixe, ça sert à rien qu'elle tourne, et ça sert à rien que
   tu mettes un contour sur elle — elle est en png. »
   ⚠️ LE PNG EST DÉTOURÉ (512x512, alpha) : il n'a pas besoin d'un fond pour tenir. Un cercle
   derrière lui redessine une boîte autour d'un objet qui n'en a pas.
   ⚠️ ET LA ROTATION ÉTAIT UN AJOUT DE MA PART, PAS UNE DEMANDE : une icône qui tourne en permanence
   fatigue l'œil en soirée, à 60 cm de la tablette, et elle n'apprend rien. */
body[data-interface="terrain"] .cl-jeu-ic {
  display: block;
  width: 132px; height: 132px;
  margin: 0 auto 14px;
  /* ⚠️ AUCUN fond, AUCUNE bordure, AUCUNE ombre — l'image se pose directement sur la carte. */
  background: none;
  border: 0;
  box-shadow: none;
  border-radius: 0;
}
body[data-interface="terrain"] .cl-jeu-ic img {
  width: 132px; height: 132px;
  object-fit: contain;
  display: block;
  animation: none;                /* ⚠️ FIXE */
}

/* ⚠️ LE BOUTON EST CENTRÉ SOUS LA ROUE : il appartient à l'icône, il mène à son écran. */
body[data-interface="terrain"] .cl-jeu-bloc .cl-jeu-go { align-self: center; }
/* ⚠️ LES PASTILLES DE GOODIES SONT CENTRÉES AUSSI : dans une colonne à moitié, alignées à gauche
   elles flotteraient dans un vide à droite. */
body[data-interface="terrain"] .cl-jeu-bloc .reward-mini-list { justify-content: center; }

/* ⚠️ SUR ÉCRAN ÉTROIT, LES DEUX COLONNES S'EMPILENT — LA VALIDATION D'ABORD, LE JEU ENSUITE : c'est
   l'ordre de priorité. Valider son stock est un geste de service ; lancer la roue est un plaisir.
   Si le panneau déborde, c'est le geste qui doit rester visible. */
@media (max-width: 860px) {
  body[data-interface="terrain"] .cl-panneau { grid-template-columns: 1fr; gap: 16px; }
  body[data-interface="terrain"] .cl-jeu-bloc {
    padding-left: 0; padding-top: 16px;
    border-left: 0; border-top: 1px solid rgba(255, 255, 255, .10);
  }
  body[data-interface="terrain"] .cl-jeu-ic,
  body[data-interface="terrain"] .cl-jeu-ic img { width: 108px; height: 108px; }
}

/* ==============================================================================================
   LE POP-UP DE DÉTAIL D'UNE RÉCEPTION, ET LES DEUX CONTRASTES ENFIN CORRIGÉS
   ==============================================================================================
   ⚠️⚠️ LES DEUX CONTRASTES N'AVAIENT PAS PRIS, ET LA SONDE A NOMMÉ LE COUPABLE :
        #5156  body[data-interface="terrain"] .cl-carte .section-label  -> var(--oz-c1, #8a5a2b)  0,3,1  GAGNE
        #5591  body[data-interface="terrain"] .section-label            -> rgb(232,230,226)       0,2,1  perd
   Ma règle perdait d'UN CRAN de spécificité (le `.cl-carte` en plus). Et `--oz-c1` porte la TEINTE
   DE LA CAMPAGNE — un rouge foncé sur une carte sombre, donc illisible.
   ⚠️ C'est la même leçon que le statut « EN COURS », payée DEUX FOIS DANS LA MÊME HEURE : quand une
   correction juste ne prend pas, ce n'est pas la valeur qu'il faut changer, c'est le NIVEAU
   (spécificité / ordre). Ici on monte en ancrant sur `.cl-panneau`, qui est plus spécifique que
   `.cl-carte` pour ces éléments-là.
   ⚠️ ET LES DEUX GRIS FONCÉS (`rgb(56,54,51)`) VIENNENT DE LA MÊME RÈGLE `#4311`, écrite à l'époque
   où `app.js` était encore servi en thème clair. Elle n'a jamais été re-scopée. */

/* ── LE LIBELLÉ DE SECTION DANS LE PANNEAU ─────────────────────────────────────────────── */
/* ⚠️ ANCré SUR `.cl-panneau .cl-jeu-bloc` (0,3,1 avec le `body`, égal à `#5156`) ET placé APRÈS :
   à spécificité égale, c'est l'ORDRE qui tranche, et le mien est plus bas dans le fichier. */
body[data-interface="terrain"] .cl-panneau .cl-jeu-bloc .section-label,
body[data-interface="terrain"] .cl-panneau .cl-notif .section-label {
  color: #e8e6e2;                 /* 6,05:1 sur la carte sombre */
}
/* ⚠️ ET LE TITRE « Jeu rapide » GARDE SA MAJUSCULE ESPACÉE mais perd le rouge. On ne touche PAS à
   `--oz-c1` : il sert à d'autres endroits où la teinte de campagne est voulue (la marque en fin de
   fiche). C'est l'ENCRE qui suit le fond, pas la charte qu'on efface. */

/* ── LES DEUX GRIS FONCÉS DE LA SECTION ACTIVITÉ ───────────────────────────────────────── */
/* ⚠️ MÊME CAUSE : la règle `#4311` (`.section-caption, .form-hint, .row-sub, …`) pose `rgb(56,54,51)`
   — l'encre du thème clair. Mes deux correctifs précédents visaient `.section-caption` et `.row-sub`
   nus, donc ils perdaient contre les règles scopées (`.cl-feed .row-sub` = 0,2,0). On ancre sur
   `.cl-feed` + `body`, ce qui passe devant. */
body[data-interface="terrain"] .cl-feed .section-caption,
body[data-interface="terrain"] .cl-feed .row-sub {
  color: #a8adb6;                 /* 7,80:1 */
}
/* ⚠️ ET LE CAS GÉNÉRAL, POUR QUE ÇA NE REVIENNE PAS AILLEURS : tout `.section-caption` et `.row-sub`
   posé sur une CARTE du terrain passe à l'encre claire. `#4311` avait une portée globale — la
   contrepartie doit l'être aussi, sinon le défaut réapparaît sur la prochaine carte qu'on ajoute. */
body[data-interface="terrain"] .cl-carte .section-caption,
body[data-interface="terrain"] .cl-carte .row-sub,
body[data-interface="terrain"] .cl-panneau .section-caption {
  color: #a8adb6;
}
/* ⚠️ ET LE SOUS-TITRE DU JEU, MÊME ORIGINE. */
body[data-interface="terrain"] .cl-panneau .cl-jeu-sous {
  color: #a8adb6;
}

/* ── LA LIGNE DEVENUE CLIQUABLE ────────────────────────────────────────────────────────── */
/* ⚠️ LE CURSEUR ET LE SURVOL SONT NÉCESSAIRES, PAS DÉCORATIFS : sans eux, rien ne dit que la ligne
   s'ouvre. Le client veut qu'elle clique « pour avoir plus de détails » — encore faut-il qu'elle le
   devine. On ajoute aussi un chevron discret pour que ce soit visible AVANT le survol. */
body[data-interface="terrain"] .cl-panneau .cl-rec-ligne.est-cliquable {
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease;
}
body[data-interface="terrain"] .cl-panneau .cl-rec-ligne.est-cliquable:hover {
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .20);
}
body[data-interface="terrain"] .cl-panneau .cl-rec-ligne.est-cliquable:focus-visible {
  outline: 3px solid rgba(255, 255, 255, .45); outline-offset: 2px;
}
/* ⚠️ LE CHEVRON SE DESSINE EN `::after`, DONC IL NE FAIT PAS PARTIE DU CONTENU : il ne sera jamais
   lu par un lecteur d'écran ni copié dans un export. C'est un repère visuel, pas une donnée. */
body[data-interface="terrain"] .cl-panneau .cl-rec-ligne.est-cliquable::after {
  content: '\203A';
  font-size: 20px; line-height: 1;
  color: rgba(255, 255, 255, .38);
  margin-left: 2px;
  align-self: center;
}

/* ── LE POP-UP DE DÉTAIL ───────────────────────────────────────────────────────────────── */
/* ⚠️ ON PART DE LA COQUE EXISTANTE (`dialog.admin-dialog`, l.4183) : fond dégradé sombre, encre
   #f4f3f1, `position: relative` pour l'ancrage de la croix. On ne réinvente pas une coque — deux
   coques dans la même application finiraient par diverger (le défaut déjà payé sur les jauges). */
dialog.rec-detail {
  width: min(94vw, 460px);
  padding: 0;
  border-radius: 20px;
  border: 1px solid rgba(255, 255, 255, .14);
  max-height: 92vh;
  overflow: auto;
}
/* ⚠️ LA CROIX EST EN ABSOLU EN HAUT À DROITE — c'est le geste que le client cite en premier. Elle
   est SEULE (« 1 seule croix » est la règle du projet) : pas de « Annuler » en plus du pied, les
   deux boutons du pied agissent. */
dialog.rec-detail .rec-x {
  position: absolute; top: 10px; right: 10px;
  width: 40px; height: 40px;              /* 40 px : la cible tactile mini sur tablette */
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .10);
  color: #f4f3f1;                         /* 12,6:1 sur le fond du pop-up */
  font-size: 20px; line-height: 1;
  cursor: pointer;
  z-index: 2;
}
dialog.rec-detail .rec-x:hover { background: rgba(255, 255, 255, .18); }
dialog.rec-detail .rec-x:focus-visible { outline: 3px solid rgba(255, 255, 255, .5); outline-offset: 2px; }

dialog.rec-detail .rec-d-tete {
  display: flex; align-items: center; gap: 14px;
  padding: 22px 58px 16px 20px;           /* ⚠️ 58 px à droite : la place de la croix, sinon le titre passe dessous */
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
/* ⚠️ LA PHOTO EST GRANDE : c'est ce qu'elle doit reconnaître. 76 px, largement au-dessus des 42 px
   de la ligne — le pop-up sert justement à VOIR MIEUX. */
dialog.rec-detail .rec-d-img {
  flex: none; width: 76px; height: 76px;
  border-radius: 18px; overflow: hidden;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .10);
}
dialog.rec-detail .rec-d-img img { width: 100%; height: 100%; object-fit: contain; }
dialog.rec-detail .rec-d-img.est-vide { border-style: dashed; border-color: rgba(255, 255, 255, .20); }
dialog.rec-detail .rec-d-qui { min-width: 0; }
dialog.rec-detail .rec-d-qui h2 {
  margin: 0 0 4px;
  font: 700 19px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #ffffff;                          /* 15,6:1 */
  line-height: 1.2;
}
dialog.rec-detail .rec-d-qui p {
  margin: 0; font: 500 12.5px Manrope, system-ui, sans-serif;
  color: #a8adb6;                          /* 7,80:1 */
  line-height: 1.45;
}
dialog.rec-detail .rec-d-qui p b { color: #e8e6e2; }

/* ⚠️ LES CHIFFRES SONT EN GRAND ET ALIGNÉS À DROITE : « sur le stock actuellement, et combien une
   fois qu'elle va recevoir » est LA question du pop-up. Le `tabular-nums` aligne les colonnes de
   chiffres, sinon 8 et 12 dansent d'une ligne à l'autre. */
dialog.rec-detail .rec-d-lignes { margin: 0; padding: 14px 20px 4px; }
dialog.rec-detail .rec-d-lignes > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 7px 0;
}
dialog.rec-detail .rec-d-lignes dt {
  margin: 0; font: 500 12.5px Manrope, system-ui, sans-serif;
  color: #a8adb6;
}
dialog.rec-detail .rec-d-lignes dd {
  margin: 0; font: 700 15px 'Plus Jakarta Sans', system-ui, sans-serif;
  color: #f4f3f1; font-variant-numeric: tabular-nums;
}
/* ⚠️ LES DEUX CHIFFRES QUI COMPTENT SONT MIS EN AVANT, CHACUN PAR SA COULEUR, ET ELLES SONT
   MESURÉES SUR CE FOND : le rouge de charte (#b01f2a) ne donnait que 3,4:1 ici — on prend le vert
   et l'or du projet, qui sont les couleurs de « ça entre en stock ». */
dialog.rec-detail .rec-d-lignes .est-plus dd { color: #6fd39a; }      /* 9,7:1 */
dialog.rec-detail .rec-d-lignes .est-final {
  border-top: 1px solid rgba(255, 255, 255, .12);
  margin-top: 4px; padding-top: 10px;
}
dialog.rec-detail .rec-d-lignes .est-final dt { color: #e8e6e2; font-weight: 700; }
dialog.rec-detail .rec-d-lignes .est-final dd { color: #6fd39a; font-size: 21px; }   /* 9,7:1 */

dialog.rec-detail .rec-d-note {
  margin: 6px 20px 0; padding: 0;
  font: 500 11.5px Manrope, system-ui, sans-serif;
  color: #a8adb6; line-height: 1.5;
}
/* ⚠️ LE PIED SÉPARE LES DEUX GESTES, ET « VALIDER » EST À DROITE : c'est le geste qu'on veut, et le
   pouce droit y tombe naturellement. « Refuser » reste à gauche, visible et sans accentuation. */
dialog.rec-detail .rec-d-pied {
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 16px 20px 20px;
  margin-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, .10);
}
dialog.rec-detail .rec-d-pied .cl-btn { min-height: 44px; }

/* ==============================================================================================
   LE MENU DU BAS : L'ONGLET ACTIF EN ROUGE OZé, ET LE SURVOL QUI DIFFÈRE DE L'ACTIF
   ==============================================================================================
   Client (verbatim) : « la couleur du menu sélectionné doit être en rouge comme la charte de OZé la
   Communication Atypique. » et « chaque chose qu'on touche au survol, il ne doit pas être la même
   couleur. »

   ⚠️⚠️ L'ÉTAT ACTUEL NE LES DISTINGUE PAS : `:hover` ET `.active` sont dans LA MÊME RÈGLE
   (l.17210-17213, `rgba(255,255,255,.07)` + `#ffffff` pour les deux). Donc survoler un onglet donne
   exactement l'apparence de l'avoir sélectionné — on ne peut pas savoir si on a cliqué.
   ⚠️ C'est mot pour mot la demande du client, et c'est aussi une règle d'usage : le survol dit
   « tu es dessus », l'actif dit « tu es ici ». Deux messages, deux apparences.

   ⚠️⚠️ ET LE ROUGE DE CHARTE EST MESURÉ AVANT D'ÊTRE POSÉ :
        #E63946 (le rouge OZé, `--red`) sur le fond de la barre (proche de #0f1011)  = 2,97:1
        #ef4b54 (même teinte, éclaircie)                                            = 3,75:1
   L'indicateur graphique demande 3:1, le texte de 11px demande 4,5:1.
   ⚠️ ON NE MET DONC PAS LE TEXTE EN ROUGE — il deviendrait MOINS lisible qu'aujourd'hui, ce qui est
   exactement le défaut qu'on vient de corriger trois fois ce soir. Le rouge porte l'ICÔNE et le
   TRAIT (qui sont des indicateurs graphiques, seuil 3:1), et le libellé reste BLANC.
   ⚠️ L'onglet se lit ROUGE — la charte est respectée — et rien n'est illisible. */

/* ── LE SURVOL : DIFFÉRENT DE L'ACTIF ───────────────────────────────────────────────────── */
/* ⚠️ LE SURVOL EST PLUS DISCRET QUE L'ACTIF : un fond à peine plus clair, l'encre qui monte au
   blanc, et AUCUN trait. Le trait est réservé à l'actif — c'est lui qui dit « tu es ici ». */
body[data-interface="terrain"] .nav-item:hover:not(.active) {
  background: rgba(255, 255, 255, .04);
  color: #e4e6e9;
}
/* ⚠️ ET L'ICÔNE AU SURVOL PREND LA TEINTE DE LA CHARTE, SANS LE TRAIT : c'est la différence la plus
   lisible entre « je regarde » et « je suis dessus », et elle ne coûte aucune surface. */
body[data-interface="terrain"] .nav-item:hover:not(.active) .nav-ic { color: #ef4b54; }

/* ── L'ACTIF : LE ROUGE OZé ─────────────────────────────────────────────────────────────── */
/* ⚠️ LE TRAIT PASSE DU ROUGE SOMBRE AU ROUGE ÉCLAIRCI — il est l'indicateur le plus visible, il
   mérite le contraste le plus haut. */
body[data-interface="terrain"] .nav-item.active::before {
  background: #ef4b54;                     /* 3,75:1 — au-dessus du seuil des indicateurs */
}
/* ⚠️ L'ICÔNE DE L'ACTIF EST ROUGE OZé — c'est LA demande du client, et c'est un indicateur
   graphique, donc le seuil est 3:1 : #ef4b54 le passe. */
body[data-interface="terrain"] .nav-item.active .nav-ic { color: #ef4b54; }
/* ⚠️ LE FOND DE L'ACTIF EST UN PEU PLUS SOUTENU QUE LE SURVOL, ET TEINTÉ DE ROUGE : trois signaux
   pour un seul état (fond + icône + trait), pour que « tu es ici » se voie même si la couleur se
   perd sur un écran mal réglé. */
body[data-interface="terrain"] .nav-item.active {
  background: rgba(230, 57, 70, .13);
  color: #ffffff;
}
/* ⚠️ LE LIBELLÉ DE L'ACTIF RESTE BLANC ET PASSE EN GRAS : le gras est un second signal, indépendant
   de la couleur. Un onglet actif qui ne se distinguerait que par sa teinte serait invisible pour une
   partie des utilisateurs. */
body[data-interface="terrain"] .nav-item.active .nav-txt { color: #ffffff; font-weight: 800; }
body[data-interface="terrain"] .nav-item:hover:not(.active) .nav-txt { font-weight: 600; }

/* ⚠️ LE JEU GARDE SA PRÉÉMINENCE SUR LES AUTRES ONGLETS, ACTIF OU NON : il est le centre de la
   barre et le client l'a voulu « la plus lisible ». Sa règle passe donc APRÈS celle de l'actif —
   sinon il perdrait sa taille de 32 px et son libellé en gras en devenant actif. */
body[data-interface="terrain"] .nav-jeu .nav-ic { width: 32px; height: 32px; }
body[data-interface="terrain"] .nav-jeu .nav-txt { font-weight: 800; }
/* ⚠️ SA COULEUR SUIT LA MÊME RÈGLE QUE LES AUTRES : rouge OZé s'il est actif, blanc sinon. Lui
   donner un traitement à part ferait quatre apparences au lieu de trois (repos / survol / actif). */
body[data-interface="terrain"] .nav-jeu:not(.active) .nav-ic { color: #ffffff; }
body[data-interface="terrain"] .nav-jeu.active .nav-ic { color: #ef4b54; }

/* ==============================================================================================
   L'ONGLET ACTIF : DU ROUGE OZé PLEIN, PARCE QU'UN VOILE NE TIENT PAS SUR LA CHARTE DE CAMPAGNE
   ==============================================================================================
   ⚠️⚠️ MESURÉ AU PIXEL, ET C'EST LA VRAIE CAUSE — pas une histoire de spécificité :
        onglet actif : 919 pixels VERT (41,115,77) + un texte vert pâle (150,190,171)
   Or mon CSS posait bien `#ef4b54` sur l'icône — mais un fond à **13 % d'opacité** posé sur du VERT
   donne du vert légèrement rougi : (44,118,80). Le rouge n'a jamais été visible.
   ⚠️ LA BARRE ELLE-MÊME EST PEINTE PAR LA CHARTE DE CAMPAGNE : la sonde a montré que `.sidebar`
   porte `linear-gradient(color-mix(in srgb, var(--oz-c1) 88%, #ffffff 12%), var(--oz-c1))`, et
   `--oz-c1` vaut le VERT Heineken. C'est voulu ailleurs (« la collaboratrice est toujours liée à la
   charte de la campagne ») — on n'y touche pas.
   ⚠️ MAIS L'ONGLET ACTIF NE PEUT PAS ÊTRE UNE TEINTE PAR-DESSUS LA CAMPAGNE : il doit dire « tu es
   ici » QUELLE QUE SOIT la couleur de la campagne. Un voile translucide dépend du fond ; une
   surface PLEINE, non.
   ⚠️⚠️ DONC : FOND PLEIN `#e63946` — LE ROUGE OZé EXACT, celui qui est dans toute l'interface. Le
   client l'a demandé nommément (« en rouge comme la charte de OZé la Communication Atypique »).
   ⚠️ ET LES ENCRES SONT RECALCULÉES SUR CE FOND, PAS REPRISES : blanc sur #e63946 = **3,76:1**.
   C'est au-dessus du seuil des indicateurs graphiques (3:1) pour l'ICÔNE et le TRAIT — mais PAS du
   seuil de 4,5:1 pour un libellé de 11 px. Le libellé passe donc en **blanc pur ET en gras** : le
   gras est un signal indépendant de la couleur, et le client l'a voulu « la plus lisible ».
   ⚠️ C'EST UN CHOIX ASSUMÉ ET ANNONCÉ : le rouge de charte coûte 0,7 point de contraste sur le
   libellé. Le client l'a demandé explicitement, deux fois. On le fait, et on le dit. */

/* ── L'ONGLET ACTIF : ROUGE PLEIN ──────────────────────────────────────────────────────── */
body[data-interface="terrain"] .nav-item.active {
  /* ⚠️ `#e63946` EST LE ROUGE DE LA CHARTE, PAS UN ROUGE APPROCHANT : c'est celui du `--red` d'origine
     et celui de toutes les surfaces rouges de l'application. Prendre « un rouge lisible » aurait
     donné un onglet qui ne correspond à rien d'autre à l'écran. */
  background: #e63946;
  color: #ffffff;
}
/* ⚠️ L'ICÔNE ET LE LIBELLÉ PASSENT AU BLANC : sur un fond rouge plein, ils doivent quitter le rouge
   (rouge sur rouge = invisible). Blanc pur = 3,76:1, au-dessus du seuil graphique. */
body[data-interface="terrain"] .nav-item.active .nav-ic { color: #ffffff; }
body[data-interface="terrain"] .nav-item.active .nav-txt { color: #ffffff; font-weight: 800; }
/* ⚠️ LE TRAIT PASSE AU BLANC : il est l'indicateur le plus visible, et le rouge sur rouge ne se
   verrait pas. Le blanc sur le rouge de la barre donne un repère net. */
body[data-interface="terrain"] .nav-item.active::before {
  background: #ffffff;
}

/* ── LE SURVOL : DIFFÉRENT DE L'ACTIF, ET VISIBLE SUR LA CAMPAGNE ─────────────────────── */
/* ⚠️⚠️ LE SURVOL NE PEUT PAS ÊTRE UNE TEINTE LÉGÈRE — c'est justement ce qui ne se voyait pas. Il
   est un VOILE BLANC (donc il éclaircit quelle que soit la couleur de dessous) et l'icône prend le
   rouge OZé : deux signaux, dont un qui ne dépend pas du fond. */
body[data-interface="terrain"] .nav-item:hover:not(.active) {
  background: rgba(255, 255, 255, .10);
  color: #ffffff;
}
body[data-interface="terrain"] .nav-item:hover:not(.active) .nav-ic { color: #ff8b95; }
/* ⚠️ `#ff8b95` ET NON `#e63946` POUR LE SURVOL : le rouge de charte sur un voile blanc au-dessus de
   la campagne verte tombait sous le seuil. Cette teinte est le rouge OZé ÉCLAIRCI — même famille,
   lisible sur les deux fonds possibles (campagne verte ou barre sombre). */
body[data-interface="terrain"] .nav-item:hover:not(.active) .nav-txt { color: #ffffff; font-weight: 600; }

/* ── LES ONGLETS AU REPOS : LISIBLES SUR LA CAMPAGNE AUSSI ────────────────────────────── */
/* ⚠️⚠️ MESURÉ : les onglets 2, 4 et 5 étaient INVISIBLES dans la capture (« RIEN de lumineux »),
   et l'onglet 3 sortait à (134,135,135) — un gris moyen. Leur encre `#c8c8c8` (pensée pour la barre
   SOMBRE #202123) tombe sur le dégradé VERT de la campagne, où elle perd son contraste.
   ⚠️ UNE ENCRE NE PEUT PAS ÊTRE FIXE ICI : le fond de la barre change avec la campagne. On prend
   donc une encre CLAIRE et légèrement teintée, qui tient sur les deux (vert sombre de campagne ET
   barre sombre par défaut). */
body[data-interface="terrain"] .nav-item { color: #e8eaec; }
body[data-interface="terrain"] .nav-item .nav-ic { color: #c9ccd1; }
/* ⚠️ LE JEU GARDE SON ICÔNE BLANCHE — c'est l'onglet central, et le client l'a voulu « la plus
   lisible ». Elle passe à l'écart des autres, actif ou non. */
body[data-interface="terrain"] .nav-jeu:not(.active) .nav-ic { color: #ffffff; }
body[data-interface="terrain"] .nav-jeu.active .nav-ic { color: #ffffff; }
body[data-interface="terrain"] .nav-jeu.active { background: #e63946; }

/* ==============================================================================================
   L'ONGLET ACTIF : ROUGE OZé — LA RÈGLE QUI GAGNE ENFIN
   ==============================================================================================
   ⚠️⚠️ LA CAUSE, NOMMÉE APRÈS QUATRE TENTATIVES — styles.css l.18217 :
        body[data-interface="terrain"]:not(.participant-mode) .nav-item.active {
          color: #17171a; background: #f2f0ec;      <- CRÈME. Spécificité 0,4,1
        }
   Toutes mes règles (`background: #e63946`, `rgba(230,57,70,.13)`, `#ef4b54`) étaient en 0,2,1 ou
   0,3,1 : elles PERDAIENT. L'onglet actif restait crème, et c'est CE carré crème que le client
   décrivait comme « la barre blanche » — un onglet de 159x66 px en clair au milieu d'une barre
   sombre, avec l'icône et le libellé dessus.
   ⚠️ ET LE TRAIT ÉTAIT DÉJÀ ROUGE (l.18218, `#D42B38`) : quelqu'un avait commencé la demande et
   s'était arrêté au trait. C'est pour ça que le client voyait « un peu de rouge » mais pas l'onglet.

   ⚠️⚠️ ON GAGNE PAR LA MÊME ANCRE, PLUS BAS, AVEC `!important` — ET C'EST JUSTIFIÉ ICI :
   l'onglet actif doit être rouge QUELLE QUE SOIT la règle rencontrée ensuite. Ce n'est pas une
   préférence de style, c'est l'état du menu : il ne peut pas dépendre d'un ordre de fichier.
   ⚠️ ET ON ANNULE LE `background-image` EXPLICITEMENT : la mesure au DOM a montré sur `.nav-item`
   un `linear-gradient(165deg, color(srgb 0.1649 0.4893 0.3374), …)` — du VERT. Une `background`
   en couleur ne l'efface pas : c'est le `background-image` qu'il faut mettre à `none`. Sans ça,
   l'onglet resterait teinté par la charte de la campagne. */

body[data-interface="terrain"]:not(.participant-mode) .nav-item.active {
  background-color: #e63946 !important;
  /* ⚠️ LE DÉGRADÉ DE CAMPAGNE EST ANNULÉ ICI, ET SEULEMENT ICI : il reste voulu partout ailleurs
     (« la collaboratrice est toujours liée à la charte de la campagne »). Sur l'onglet ACTIF, il
     ferait dépendre « tu es ici » de la couleur de la marque — donc du hasard. */
  background-image: none !important;
  color: #ffffff !important;
}
/* ⚠️ L'ICÔNE ET LE LIBELLÉ PASSENT AU BLANC : sur un fond rouge plein, du rouge serait invisible
   (rouge sur rouge). Blanc sur #e63946 = 3,76:1, au-dessus du seuil des indicateurs graphiques. */
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active .nav-ic,
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active .nav-txt {
  color: #ffffff !important;
}
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active .nav-txt { font-weight: 800; }
/* ⚠️ LE TRAIT PASSE AU BLANC : il est l'indicateur le plus visible, et le rouge de charte sur le
   rouge de la barre ne se distinguerait pas. */
body[data-interface="terrain"]:not(.participant-mode) .nav-item.active::before {
  background: #ffffff !important;
}

/* ── LE SURVOL : DIFFÉRENT DE L'ACTIF ─────────────────────────────────────────────────── */
/* ⚠️ Client : « chaque chose qu'on touche au survol, il ne doit pas être la même couleur. »
   Aujourd'hui `:hover` et `.active` sont dans LA MÊME RÈGLE (l.8 et l.17210) : survoler donne
   exactement l'apparence d'avoir sélectionné. On les sépare.
   ⚠️ LE SURVOL EST UN VOILE BLANC (donc il éclaircit sur n'importe quel fond, y compris la charte
   de campagne) et NON une teinte : une teinte dépendrait de ce qu'il y a dessous. */
body[data-interface="terrain"]:not(.participant-mode) .nav-item:hover:not(.active) {
  background-color: rgba(255, 255, 255, .12) !important;
  background-image: none !important;
  color: #ffffff !important;
}
body[data-interface="terrain"]:not(.participant-mode) .nav-item:hover:not(.active) .nav-ic {
  color: #ff8b95 !important;
}
body[data-interface="terrain"]:not(.participant-mode) .nav-item:hover:not(.active) .nav-txt {
  color: #ffffff !important; font-weight: 600;
}

/* ── LES ONGLETS AU REPOS : LISIBLES SUR LA CAMPAGNE AUSSI ───────────────────────────── */
/* ⚠️ MESURÉ AU PIXEL : les onglets 2, 4 et 5 étaient INVISIBLES (« RIEN de lumineux ») et le 3e
   sortait à (134,135,135). Leur encre `#c8c8c8`, pensée pour la barre SOMBRE, tombe sur le dégradé
   VERT de la campagne où elle perd son contraste.
   ⚠️ ON PREND DONC UNE ENCRE CLAIRE ET LÉGÈREMENT TEINTÉE, qui tient sur les deux fonds possibles
   (dégradé vert de campagne ET barre sombre par défaut) — le fond de la barre change avec la
   campagne, une encre fixe ne peut pas convenir. */
body[data-interface="terrain"]:not(.participant-mode) .nav-item:not(.active) { color: #e8eaec !important; }
body[data-interface="terrain"]:not(.participant-mode) .nav-item:not(.active) .nav-ic { color: #ccd0d6 !important; }
/* ⚠️ LE JEU GARDE SON ICÔNE BLANCHE ET SA TAILLE : c'est l'onglet central, le client l'a voulu
   « la plus lisible des cinq ». Il ne suit donc pas la couleur des autres. */
body[data-interface="terrain"] .nav-jeu:not(.active) .nav-ic { color: #ffffff !important; }
body[data-interface="terrain"] .nav-jeu.active .nav-ic { color: #ffffff !important; }

/* ── LA BARRE BLANCHE : LE FOND DU SIDEBAR EN THÈME CLAIR ─────────────────────────────── */
/* ⚠️⚠️ DEUXIÈME COUCHE DU MÊME DÉFAUT : quand `<html data-theme="light">`, la barre elle-même est
   peinte CLAIRE. Le client la décrit comme « la barre blanche en haut ». Sur la tablette, le fond
   reste sombre quoi qu'il arrive — c'est l'exigence du client depuis le début. */
html[data-theme="light"]:has(body[data-interface="terrain"]) .sidebar {
  background: #0a0b0c !important;
}
html[data-theme="light"]:has(body[data-interface="terrain"]) .brand strong,
html[data-theme="light"]:has(body[data-interface="terrain"]) .brand small {
  color: #f4f5f6 !important;
}
html[data-theme="light"]:has(body[data-interface="terrain"]) .eyebrow {
  color: #a8adb6 !important;
}

/* ==============================================================================================
   LA VIGNETTE ET LE TITRE SONT RETIRÉS — LE LOGO CENTRÉ LES REMPLACE
   ==============================================================================================
   Client (verbatim) : « la barre blanche en haut qui doit s'enlever avec la vignette et le titre, et
   se faire remplacer par le logo OZélacom, centré. »

   ⚠️⚠️ LA MESURE A NOMMÉ LES DEUX COUPABLES, ET ILS SONT DEUX :
   1. `.brand` = la VIGNETTE (`brand-logo-wrap` + `img.brand-logo`) ET LE TITRE
      (`<strong>OZELCA</strong>` + `<small>LIVE COLLABORATION</small>`). À 900 px il est
      `display: none` — donc le client regarde une largeur où le sidebar est LATÉRAL, et là il
      s'affiche EN HAUT, dans un bloc clair quand `<html data-theme="light">`.
   2. `.eyebrow` = « Espace collaboration », le même bloc.
   ⚠️ ET LE LOGO CENTRÉ EXISTE DÉJÀ : `.tp-logo` dans `.main`, mesuré à 270x97 px, centré à 49 %.
      Le client ne demande donc pas de le créer — il demande de RETIRER ce qui le double.

   ⚠️⚠️ POURQUOI CES DEUX-LÀ ET PAS UNE BARRE IMAGINAIRE : la sonde a mesuré la bande y=0..170 sur
   toute la largeur, et le PREMIER élément opaque est la fiche à y=160. Tout ce qui est au-dessus est
   transparent — sauf ce bloc `.brand`, qui n'apparaît qu'à partir d'une certaine largeur. C'est lui
   le « blanc en haut », et il disparaît avec la vignette et le titre. */

/* ⚠️ ON LES RETIRE POUR TOUTE LA TABLETTE, À TOUTES LES LARGEURS. Le client ne les veut plus, et
   une règle qui ne vaut qu'à une largeur laisserait le défaut revenir dès qu'on tourne l'iPad. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .brand,
body[data-interface="terrain"]:not(.participant-mode) .sidebar .eyebrow {
  display: none !important;
}

/* ⚠️ ET LE PIED DE LA BARRE AUSSI : « Espace collaboration / Tablette / Terrain » répète exactement
   ce que le titre disait. Client : « le rappel de Oser la Communication Atypique » suffit. On garde
   `barre-marque` (le nom de l'agence, qu'il a demandé explicitement) et on retire le reste. */
body[data-interface="terrain"]:not(.participant-mode) .sidebar .sidebar-foot {
  display: none !important;
}

/* ⚠️ LE LOGO CENTRÉ GAGNE LA PLACE LIBÉRÉE : la barre latérale n'a plus d'en-tête, donc la barre du
   BAS (sur tablette) ne remonte pas — mais sur grand écran, le logo du `.main` reste le seul
   élément de tête, et il est déjà centré. On lui donne juste un peu plus d'air au-dessus, parce
   qu'il est maintenant la seule chose avant le contenu. */
body[data-interface="terrain"]:not(.participant-mode) .tp-logo {
  padding-top: 18px;
}

/* ==============================================================================================
   LE JOURNAL DES ACTIVITÉS — DES LIGNES DE LOG SERRÉES
   ==============================================================================================
   Client (verbatim) : « des lignes qui sont un peu plus proches l'une de l'autre, ça ne sert à rien
   d'avoir tous ces espaces pour rien [...] je ne veux pas avoir des espaces qui vont me bouffer de
   l'espace dans le visuel. »

   ⚠️⚠️ LA DIFFÉRENCE AVEC L'ANCIEN FEED EST DANS LA DENSITÉ, PAS SEULEMENT DANS LA SOURCE :
   l'ancienne ligne faisait ~64 px (nom en 14px + sous-titre en 12px + un badge arrondi, le tout
   espacé). Six lignes occupaient donc ~400 px pour 6 informations. Une ligne de log en fait 26 et
   porte la même chose — donc on passe de 6 à 15 lignes visibles dans la même surface.
   ⚠️ ET LA FORME CHANGE : une GRILLE à trois colonnes (heure · action · détail) au lieu d'un
   empilement. Un log se lit par COLONNES — les heures s'alignent, les libellés s'alignent, et l'œil
   descend la colonne du milieu sans être arrêté par la mise en forme.
   ⚠️ L'HEURE EST EN CHASSE FIXE (`DM Mono`) : c'est ce qui aligne les colonnes. En police
   proportionnelle, les chiffres ont des largeurs différentes et la colonne danse. */

body[data-interface="terrain"] .cl-journal {
  /* ⚠️ LE JOURNAL EST PLAFONNÉ ET DÉFILE SEUL : 120 lignes sur une page qui défile déjà obligeraient
     à descendre tout le journal pour retrouver la barre du bas. On lui donne sa propre hauteur. */
  max-height: 62vh;
  overflow-y: auto;
  /* ⚠️ `overscroll-behavior: contain` EMPÊCHE LE DÉFILEMENT DE SE PROPAGER À LA PAGE : sans lui, le
     pouce qui continue en bas du journal fait défiler la page derrière, et on perd sa position. */
  overscroll-behavior: contain;
  padding: 10px 12px;
}
/* ⚠️ LA GRILLE : l'heure est dimensionnée sur son contenu (`auto`), l'action aussi, et le détail
   prend ce qui reste. C'est ce qui fait que les colonnes s'alignent d'une ligne à l'autre — un
   `flex` avec des largeurs libres ne les alignerait pas. */
body[data-interface="terrain"] .cl-jl {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: baseline;
  gap: 10px;
  /* ⚠️ 4 px ENTRE LES LIGNES, ET RIEN DE PLUS : c'est la demande littérale. Chaque pixel d'écart
     multiplié par 120 lignes retire une ligne entière à l'écran. */
  padding: 4px 2px;
  /* ⚠️ UN FILET TRÈS FAIBLE PLUTÔT QU'UN ESPACE : il sépare les lignes sans les écarter. La `border`
     coûte 1 px, la marge en coûterait 6. */
  border-bottom: 1px solid rgba(255, 255, 255, .05);
  font: 500 12px Manrope, system-ui, sans-serif;
  line-height: 1.35;
}
body[data-interface="terrain"] .cl-jl:last-child { border-bottom: 0; }
/* ⚠️ LE SURVOL RESTE DISCRET : le journal n'est pas cliquable, mais survoler une ligne aide l'œil à
   ne pas perdre sa place sur une surface dense. */
body[data-interface="terrain"] .cl-jl:hover { background: rgba(255, 255, 255, .04); }

/* ⚠️ L'HEURE : chasse fixe, teinte atténuée. C'est une colonne de repère, pas une information
   principale — elle doit se lire sans attirer l'œil. */
body[data-interface="terrain"] .cl-jl-h {
  font: 500 11px 'DM Mono', ui-monospace, monospace;
  color: #8b8880;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* ⚠️ L'ACTION EST LE SEUL TEXTE FORT DE LA LIGNE — c'est ce qu'on cherche en descendant le journal.
   Blanc, poids 700. */
body[data-interface="terrain"] .cl-jl-a {
  font-weight: 700;
  color: #f2f1ef;                    /* 15,57:1 sur la carte sombre */
  white-space: nowrap;
}
/* ⚠️ LE DÉTAIL EST SECONDAIRE ET COUPÉ S'IL DÉBORDE : il complète l'action, il ne la remplace pas.
   `minmax(0,1fr)` + `text-overflow` évitent qu'un nom long pousse la ligne sur deux hauteurs — ce
   qui remettrait l'espace qu'on vient de retirer. */
body[data-interface="terrain"] .cl-jl-d {
  color: #a8adb6;                    /* 7,80:1 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
body[data-interface="terrain"] .cl-journal-vide {
  margin: 0; padding: 10px 2px;
  font: 500 12px Manrope, system-ui, sans-serif;
  color: #a8adb6;
}

/* ⚠️ SUR ÉCRAN ÉTROIT, LE DÉTAIL PASSE SOUS L'ACTION : trois colonnes n'y tiennent plus, et un
   libellé coupé (« Cadeau re… ») vaut moins qu'un détail sur la ligne suivante. */
@media (max-width: 620px) {
  body[data-interface="terrain"] .cl-jl {
    grid-template-columns: auto minmax(0, 1fr);
  }
  body[data-interface="terrain"] .cl-jl-d { grid-column: 2; color: #8b8880; }
}

/* ⚠️ LE COMPTEUR DE RÉPÉTITION : discret, aligné à droite, en chasse fixe pour ne pas danser. */
body[data-interface="terrain"] .cl-jl-n {
  font: 600 10.5px 'DM Mono', ui-monospace, monospace;
  color: #8b8880; white-space: nowrap;
}
/* ⚠️ LA GRILLE PASSE À QUATRE COLONNES (heure · action · détail · compte) : le compte doit rester
   sur la même ligne, mais il ne prend de place que quand il existe. */
body[data-interface="terrain"] .cl-jl { grid-template-columns: auto auto minmax(0, 1fr) auto; }
@media (max-width: 620px) { body[data-interface="terrain"] .cl-jl { grid-template-columns: auto minmax(0, 1fr); } }

/* ==============================================================================================
   LE JEU SE RÉORGANISE : LA ROUE À CÔTÉ DU TITRE, LES GOODIES ET LE BOUTON DESSOUS
   ==============================================================================================
   Client (verbatim) : « le positionnement de la roue est juste horrible en bas, c'est un truc
   classique. Soit tu le mets à côté du titre, soit tu le mets en haut, à droite, en plus petit. Tu
   mets les goodies qu'on a et en dessous le bouton de lancer le jeu. Pour montrer qu'on a des
   goodies à offrir aux gens. »

   ⚠️⚠️ LA GÉOMÉTRIE MESURÉE, AVANT — et elle explique le « horrible » :
        label    top=539   h=14
        h2       top=570   h=28
        sous     top=619   h=17
        goodies  top=658   h=88
        roue     top=750   h=132     <- un bloc de 132 px posé APRÈS les goodies, centré
        bouton   top=896   h=46      <- repoussé tout en bas par la roue
   La roue prenait à elle seule un tiers de la colonne (132 sur 404) et séparait les goodies du
   bouton — exactement l'inverse de la lecture voulue (« ces goodies, puis le geste »).

   ⚠️⚠️ POURQUOI CE CHOIX : le client donne DEUX pistes (« à côté du titre » OU « en haut à droite en
   plus petit »), et ce sont les MÊMES. À côté du titre = en haut ; le côté naturel = à droite. On
   les fait toutes les deux, on ne choisit pas.
   ⚠️ ET ÇA RÉSOUT LA HAUTEUR : la roue partage la rangée du titre (59 px au lieu de 132), donc on
   gagne ~100 px de carte et le bouton remonte juste sous les goodies. */

/* ── LA RANGÉE DU HAUT : LE TEXTE À GAUCHE, LA ROUE À DROITE ───────────────────────────── */
body[data-interface="terrain"] .cl-jeu-haut {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  /* ⚠️ UN FILET EN DESSOUS, PAS UN ESPACE : il sépare la rangée du titre des goodies sans coûter
     8 px de vide. La même recette que la ligne des statuts de la fiche. */
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
/* ⚠️ LE TEXTE PREND LA PLACE LIBRE (`1fr`), LA ROUE GARDE LA SIENNE (`flex: none`) : c'est le texte
   qui doit se replier sur un écran étroit, pas la roue qui doit rétrécir. Une roue écrasée devient
   un point coloré ; un titre replié reste un titre. */
body[data-interface="terrain"] .cl-jeu-txt { min-width: 0; flex: 1 1 auto; }
body[data-interface="terrain"] .cl-jeu-txt h2 { margin: 4px 0 0; }
body[data-interface="terrain"] .cl-jeu-sous { margin: 6px 0 0; }

/* ── LA ROUE : À DROITE DU TITRE, PLUS PETITE, SANS CONTOUR ────────────────────────────── */
body[data-interface="terrain"] .cl-jeu-ic {
  flex: none;
  display: block;
  /* ⚠️ 76 px AU LIEU DE 132 : le client la veut « en plus petit ». Une roue reste reconnaissable à
     40 px ; en dessous de 60 son dessin (cercle + rayons + aiguille) se confond. 76 est le plus
     petit qui reste net tout en libérant 56 px de hauteur. */
  width: 76px; height: 76px;
  margin: 0;
  /* ⚠️ AUCUN fond, AUCUNE bordure, AUCUNE ombre : le PNG est DÉTOURÉ (512x512, alpha). Un contour
     derrière lui redessine une boîte autour d'un objet qui n'en a pas — le client l'a dit :
     « ça sert à rien que tu mettes un contour sur elle, elle est en png ». */
  background: none;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  /* ⚠️ ET FIXE : « la roue doit être fixe, ça sert à rien qu'elle tourne ». */
  animation: none;
}
body[data-interface="terrain"] .cl-jeu-ic img {
  width: 76px; height: 76px;
  object-fit: contain;
  display: block;
  animation: none;
}

/* ── LES GOODIES, PUIS LE BOUTON JUSTE DESSOUS ─────────────────────────────────────────── */
/* ⚠️ LES PASTILLES RESTENT CENTRÉES : dans une colonne à moitié de largeur, alignées à gauche elles
   laisseraient un vide à droite. */
body[data-interface="terrain"] .cl-jeu-bloc .reward-mini-list { justify-content: center; margin: 0 0 12px; }
/* ⚠️ LE BOUTON EST CENTRÉ SOUS LES GOODIES, ET C'EST L'ORDRE DU CLIENT : « tu mets les goodies qu'on
   a et en dessous le bouton de lancer le jeu ». Il n'est plus repoussé par la roue — il est juste
   après ce qu'on peut gagner. */
body[data-interface="terrain"] .cl-jeu-bloc .cl-jeu-go { align-self: center; }

/* ⚠️ SUR ÉCRAN ÉTROIT, LA ROUE PASSE SOUS LE TITRE PLUTÔT QUE DE LE COMPRIMER : à moins de 520 px,
   deux colonnes ne tiennent plus et le titre se replierait sur trois lignes pour rien. */
@media (max-width: 520px) {
  body[data-interface="terrain"] .cl-jeu-haut { flex-direction: column; align-items: center; }
  body[data-interface="terrain"] .cl-jeu-txt { text-align: center; }
  body[data-interface="terrain"] .cl-jeu-ic,
  body[data-interface="terrain"] .cl-jeu-ic img { width: 64px; height: 64px; }
}


/* ==============================================================================================
   UN PEU PLUS D'AIR ENTRE LE LOGO ET LE PREMIER WIDGET
   ==============================================================================================
   Client (verbatim) : « séparer un tout petit peu entre le logo en haut et le premier widget. »

   ⚠️⚠️ MESURÉ AVANT DE TOUCHER, PARCE QUE « UN TOUT PETIT PEU » EST UNE MESURE, PAS UNE IMPRESSION :
     · logo (image)      : top=40   bas=163   h=123
     · .tp-logo          : top=22   bas=179   (padding 18px / 16px / 16px)
     · la fiche          : top=179
     · ÉCART logo->fiche : 16 px   <-- CES 16 px SONT LE padding-bottom DE `.tp-logo`
   Les 16 px ne viennent donc NI d'une marge du widget NI du `.content` : ils sont le souffle du
   conteneur du logo. C'est le seul endroit où grandir sans rien déplacer d'autre.

   ⚠️ ON NE TOUCHE PAS AU padding-top (18px) : le client demande d'écarter le logo DU WIDGET, pas de
   descendre le logo. Ce sont deux gestes différents, et seul le premier a été demandé.

   ⚠️ POURQUOI 30 ET PAS 48 : sous un logo de 123 px de haut, 16 px collent et 48 px ouvrent un
   VIDE. 30 px donne une respiration visible. +14 px = « un tout petit peu », littéralement.

   ⚠️ ELLE EST ÉCRITE EN DERNIÈRE POSITION DU FICHIER, ET C'EST SA SEULE RAISON DE GAGNER :
   NEUF règles se chevauchaient déjà sur ce sélecteur (dont DEUX doublons exacts), et la gagnante
   était décidée par l'ordre du fichier. Ajouter un `!important` ici relancerait la guerre ; poser
   la dernière la termine. Le projet a déjà tranché : « ON NE RÉPOND PAS PAR UN !important DE PLUS ».
   ============================================================================================== */
body[data-interface="terrain"]:not(.participant-mode) .tp-logo {
  padding-bottom: 30px;
}




/* ==============================================================================================
   L'ONGLET « STOCK ET MISSION » — LE GLOBAL, LE PAR ÉVÉNEMENT, ET LE JOURNAL
   ==============================================================================================
   Client (verbatim) : « passe au menu stock émission il est vide apparemment il faut que tu mettes
   son stock général ainsi que son stock par événement et combien elle a utilisé et combien il n'est
   pas sorti tant que le stock est à venir ça sert à rien de lui dire combien est sorti c'est parce
   que l'événement il est toujours pas passé »

   ⚠️⚠️ LA CARTE EST SOMBRE, ET ELLE RECOPIE LA RECETTE DÉJÀ MESURÉE — elle ne l'imite pas :
     fond `rgb(24, 25, 28)` · titre `#f2f1ef` (15,57:1) · sous-titre `#c9c6c0` (10,31:1)
     · secondaire `#a8adb6` (7,80:1).
   ⚠️ L'encre suit LE FOND QU'ELLE A DERRIÈRE, jamais le fond de la page. C'est la leçon payée sur
   les six cartes crème : un titre blanc sur une carte claire donne 1,03:1 — illisible.
   ⚠️ AUCUNE BORDURE BLANCHE, aucun `box-shadow`. Le cheveu est un blanc à 11 %.
*/

/* ── L'EN-TÊTE D'UNE CARTE DE CET ONGLET ────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-global .cl-tete,
body[data-interface="terrain"] .cl-evts .cl-tete,
body[data-interface="terrain"] .cl-journal .cl-tete {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  margin-bottom: 14px;
}
body[data-interface="terrain"] .cl-global .cl-tete h3,
body[data-interface="terrain"] .cl-evts .cl-tete h3,
body[data-interface="terrain"] .cl-journal .cl-tete h3 {
  margin: 0; font: 700 11px 'DM Mono', monospace; letter-spacing: .13em; text-transform: uppercase;
  color: #c9c6c0;
}
/* ⚠️ LE TOTAL DU GLOBAL : le chiffre est le plus gros de la carte, comme sur les widgets du
   tableau de bord — c'est la même information, elle doit se lire de la même façon. */
body[data-interface="terrain"] .cl-global-n { display: inline-flex; align-items: baseline; gap: 5px; }
body[data-interface="terrain"] .cl-global-n b { font: 800 22px Manrope, sans-serif; color: #f2f1ef; line-height: 1; }
body[data-interface="terrain"] .cl-global-n small { font-size: 12px; color: #a8adb6; }

/* ── LES QUATRE JAUGES DU GLOBAL ────────────────────────────────────────────────────────────── */
/* ⚠️ 4 PAR LIGNE, PAS 3 : ce sont des jauges, pas des widgets de pilotage. Trois par ligne les
   ferait passer à la ligne sur une tablette étroite et le quatrième tomberait seul. */
body[data-interface="terrain"] .cl-global {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
}
body[data-interface="terrain"] .cl-global .cl-tete { grid-column: 1 / -1; }
@media (max-width: 720px) { body[data-interface="terrain"] .cl-global { grid-template-columns: repeat(2, 1fr); } }

/* ── UN ÉVÉNEMENT ───────────────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-ev {
  padding: 14px 16px 15px; border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, .11);
  background: rgba(255, 255, 255, .028);
}
body[data-interface="terrain"] .cl-ev + .cl-ev { margin-top: 10px; }
/* ⚠️ L'ÉVÉNEMENT DU JOUR SE DISTINGUE, ET PAS PAR DE LA COULEUR PLEINE : le client a refusé
   « blanc et rouge » pour « EN COURS » (le rouge plein est réservé aux alertes). On marque donc
   d'un LISERÉ à gauche, qui dit « c'est maintenant » sans crier. */
body[data-interface="terrain"] .cl-ev.est-encours { border-left: 3px solid #e63946; }

body[data-interface="terrain"] .cl-ev-tete {
  display: flex; align-items: center; gap: 12px;
}
body[data-interface="terrain"] .cl-ev-d {
  flex: none; width: 42px; text-align: center; padding: 5px 0; border-radius: 10px;
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .10);
}
body[data-interface="terrain"] .cl-ev-d b { display: block; font: 800 16px Manrope, sans-serif; color: #f2f1ef; line-height: 1; }
body[data-interface="terrain"] .cl-ev-d span { display: block; font: 700 7.5px 'DM Mono', monospace; color: #c9c6c0; margin-top: 2px; }
body[data-interface="terrain"] .cl-ev-c { flex: 1; min-width: 0; }
body[data-interface="terrain"] .cl-ev-c b { display: block; font: 700 14.5px Manrope, sans-serif; color: #f2f1ef; }
body[data-interface="terrain"] .cl-ev-c span { font-size: 12px; color: #a8adb6; }

/* ── L'ÉTAT D'UN ÉVÉNEMENT ──────────────────────────────────────────────────────────────────── */
/* ⚠️ « EN COURS » PORTE UN POINT VERT, PAS UN FOND ROUGE. Client (verbatim) : « Le encours doit
   être comme en ligne, ça ne sert à rien de le mettre en blanc et en rouge. Mets un point en vert
   genre que c'est encours. » — la même règle qu'en haut de la fiche, appliquée ici. */
body[data-interface="terrain"] .cl-ev-et {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px;
  font: 700 9.5px 'DM Mono', monospace; letter-spacing: .07em;
  background: rgba(255, 255, 255, .06); color: #c9c6c0;
}
body[data-interface="terrain"] .cl-ev-et i { width: 6px; height: 6px; border-radius: 50%; background: #6b7280; }
body[data-interface="terrain"] .cl-ev-et.est-encours { background: rgba(74, 140, 92, .16); color: #8fd6a3; }
body[data-interface="terrain"] .cl-ev-et.est-encours i { background: #4a8c5c; box-shadow: 0 0 7px rgba(74, 140, 92, .85); }
body[data-interface="terrain"] .cl-ev-et.est-passe { color: #8b9099; }
body[data-interface="terrain"] .cl-ev-et.est-avenir { color: #c9c6c0; }
body[data-interface="terrain"] .cl-ev-et.est-avenir i { background: #d4a574; }

/* ── LES CHIFFRES D'UN ÉVÉNEMENT ────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .cl-ev-kpis {
  display: flex; flex-wrap: wrap; align-items: center; gap: 18px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
body[data-interface="terrain"] .cl-ev-k { display: inline-flex; align-items: center; gap: 8px; }
body[data-interface="terrain"] .cl-ev-k b { font: 800 17px Manrope, sans-serif; color: #f2f1ef; line-height: 1; }
body[data-interface="terrain"] .cl-ev-k small { font-size: 11.5px; color: #a8adb6; }
/* ⚠️ LA NOTE PREND TOUTE LA LARGEUR : elle explique la règle, elle ne doit pas être serrée entre
   deux chiffres. C'est elle qui dit POURQUOI il n'y a pas de « sorti » sur un événement à venir —
   sans elle, l'absence de chiffre ressemblerait à un oubli. */
body[data-interface="terrain"] .cl-ev-note {
  flex-basis: 100%; font-size: 11.5px; color: #c9c6c0; font-style: italic;
}


/* ==============================================================================================
   L'ONGLET PROFIL — LES CARTES PASSENT EN SOMBRE
   ==============================================================================================
   Client (verbatim) : « ajuste moi le menu profil, ajuste moi l'organisation du profil, il est
   blanc, les onglets ils sont pas bien [...] arrange son profil avec le pouvoir d'uploader sa photo
   ou de la changer ».

   ⚠️⚠️ MESURÉ, ET C'EST LE « IL EST BLANC » :
     .mp-page / .mp-hero / .mp-fiche = rgb(247, 244, 238)   <- CRÈME
     encre posée dessus              = rgb(244, 245, 246)   <- BLANCHE
     CONTRASTE = 1,03:1   (seuil 4,5:1 pour 10-18 px)  ❌ ILLISIBLE
   ⚠️ LA CAUSE EST UN DÉFAUT DE NIVEAU, PAS DE VALEUR : ces règles ont été écrites quand la
   TABLETTE AVAIT UN FOND CLAIR. Le fond est passé au sombre, les cartes sont restées claires — et
   l'encre blanche qui allait avec le fond sombre s'est retrouvée posée sur du crème.

   ⚠️ ON RECOPIE LA RECETTE DÉJÀ MESURÉE SUR LES AUTRES CARTES DE LA TABLETTE :
     fond rgb(24, 25, 28) · titre #f2f1ef (15,57:1) · sous-titre #c9c6c0 (10,31:1)
     · secondaire #a8adb6 (7,80:1) · cheveu blanc à 8 %.
   ⚠️ AUCUNE BORDURE BLANCHE, aucun box-shadow : le cheveu est un blanc translucide.
   ⚠️ LA DIRECTION GARDE SON THÈME CLAIR — ces règles sont cloisonnées à
   `body[data-interface="terrain"]`, exactement comme le projet l'avait déjà tranché
   (« ON NE RÉPOND PAS PAR UN !important DE PLUS, on CLoisonne »).
   ============================================================================================== */

body[data-interface="terrain"] .mp-hero,
body[data-interface="terrain"] .mp-fiche {
  background: rgb(24, 25, 28);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 14px;
  box-shadow: none;
}

/* ── LES ENCRES, RECALCULÉES SUR CE FOND ────────────────────────────────────────────────────── */
body[data-interface="terrain"] .mp-qui h2 { color: #f2f1ef; }
body[data-interface="terrain"] .mp-qui p { color: #a8adb6; }
body[data-interface="terrain"] .mp-fige { color: #a8adb6; }
body[data-interface="terrain"] .mp-fiche h3 { color: #c9c6c0; }
body[data-interface="terrain"] .mp-ligne dt { color: #c9c6c0; }
body[data-interface="terrain"] .mp-ligne dd { color: #f2f1ef; }
body[data-interface="terrain"] .mp-ligne + .mp-ligne { border-top-color: rgba(255, 255, 255, .07); }
body[data-interface="terrain"] .mp-ev + .mp-ev { border-top-color: rgba(255, 255, 255, .07); }
body[data-interface="terrain"] .mp-ev .mp-ev-c b { color: #f2f1ef; }
body[data-interface="terrain"] .mp-ev .mp-ev-c span { color: #a8adb6; }
body[data-interface="terrain"] .mp-ev .mp-ev-d {
  background: rgba(255, 255, 255, .05); border-color: rgba(255, 255, 255, .10);
}
body[data-interface="terrain"] .mp-ev .mp-ev-d b { color: #f2f1ef; }
body[data-interface="terrain"] .mp-ev .mp-ev-d span { color: #c9c6c0; }
body[data-interface="terrain"] .mp-vide { color: #a8adb6; }

/* ── LA PHOTO : LE GESTE DE CHANGEMENT DOIT SE VOIR ─────────────────────────────────────────── */
/* ⚠️ AVANT : un bandeau sombre de 7 px avec « Changer la photo » écrit en 9 px PAR-DESSUS les
   initiales — le texte se superposait au « SE » et donnait un pâté illisible. Le client a demandé
   « le pouvoir d'uploader sa photo ou de la changer » : le geste doit être VISIBLE, pas deviné.
   ⚠️ ON MET DONC UNE PASTILLE ronde (un appareil photo) dans le coin bas-droit quand une photo
   existe déjà — elle ne mange rien de l'image. Et quand il n'y en a PAS, le libellé s'affiche en
   clair SOUS les initiales : là, c'est la première action à faire, elle mérite d'être nommée. */
body[data-interface="terrain"] .mp-photo {
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .10);
}
body[data-interface="terrain"] .mp-photo .mp-init { color: #c9c6c0; }
body[data-interface="terrain"] .mp-photo img { object-position: center 18%; transform: scale(1.25); }

/* la pastille, quand une photo existe déjà */
body[data-interface="terrain"] .mp-photo label {
  position: absolute; left: auto; right: 6px; bottom: 6px;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 9px; border-radius: 999px;
  background: rgba(12, 13, 15, .82); color: #f2f1ef;
  font: 700 10px Manrope, sans-serif; letter-spacing: .02em;
  cursor: pointer; transition: background .15s ease;
}
body[data-interface="terrain"] .mp-photo label svg { width: 14px; height: 14px; flex: none; }
body[data-interface="terrain"] .mp-photo label:hover { background: rgba(230, 57, 70, .92); }
/* ⚠️ SANS PHOTO : le libellé passe SOUS les initiales dans une colonne centrée, sinon les deux se
   superposent à nouveau — le défaut qu'on vient de corriger. */
body[data-interface="terrain"] .mp-photo:not(.a-photo) {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
body[data-interface="terrain"] .mp-photo:not(.a-photo) label {
  position: static; left: 0; right: 0; bottom: 0; margin-top: 7px;
  border-radius: 999px; padding: 5px 10px;
}

/* ── L'ÉTAT D'UNE MISSION, DANS LE PROFIL ──────────────────────────────────────────────────── */
/* ⚠️ MÊME LANGAGE QUE L'ONGLET STOCK : point coloré + libellé. Vert pour « en cours », or pour
   « à venir ». On ne met PAS de rouge : il est réservé aux alertes. */
body[data-interface="terrain"] .mp-ev-et {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px; border-radius: 999px;
  font: 700 9px 'DM Mono', monospace; letter-spacing: .07em;
  background: rgba(255, 255, 255, .06); color: #c9c6c0;
}
body[data-interface="terrain"] .mp-ev-et i { width: 5px; height: 5px; border-radius: 50%; background: #d4a574; }
body[data-interface="terrain"] .mp-ev-et.est-encours { background: rgba(74, 140, 92, .16); color: #8fd6a3; }
body[data-interface="terrain"] .mp-ev-et.est-encours i { background: #4a8c5c; box-shadow: 0 0 7px rgba(74, 140, 92, .85); }

/* ── LE BOUTON DE SORTIE ────────────────────────────────────────────────────────────────────── */
body[data-interface="terrain"] .mp-sortie {
  color: #f2f1ef; border-color: rgba(230, 57, 70, .45); background: rgba(230, 57, 70, .10);
}
body[data-interface="terrain"] .mp-sortie:hover { background: rgba(230, 57, 70, .22); }


/* ==============================================================================================
   LE FOND DU CONTENEUR DU PROFIL — LA CAUSE DU « IL Y A TOUJOURS DU BLANC PASSÉ »
   ==============================================================================================
   Client (verbatim, DEUXIÈME signalement) : « dans l'onglet profil il y a toujours du blanc passé
   et le contraste ne va pas trop bien j'arrive pas à voir le profil ».

   ⚠️⚠️ MESURÉ, ET C'EST CE QUE J'AVAIS MANQUÉ : le balayage complet de l'onglet ne trouve qu'UN
   SEUL fond clair, et ce n'est aucune des cartes :
       <div class="mp-page">  bg = rgb(247, 244, 238)  lum = 0.91   <- CRÈME
   ⚠️ C'EST LE PARENT DES CARTES, PAS UNE CARTE. J'avais corrigé `.mp-hero` et `.mp-fiche` — donc
   les cartes étaient sombres — mais leur conteneur restait crème, et `.mp-page` porte
   `gap: 16px` : CES 16 PX DE GOUTTIÈRES LAISSENT VOIR LE CRÈME ENTRE CHAQUE BLOC.
   Le contour de chaque carte restait donc cerné de clair. Corriger les enfants en laissant le
   parent est exactement le défaut de niveau que le projet a déjà payé quinze fois.
   ⚠️ LA RÈGLE COUPABLE EST NOMMÉE : `styles.css l.19387`
       body[data-interface="terrain"] .mp-page, .mp-hero, .mpc-tete input, .mpc-compte { background-color: #f7f4ee; }
   Elle a été écrite quand la tablette avait un fond clair (« LES SURFACES QUI ÉTAIENT BLANCHES SANS
   ÊTRE DANS LA LISTE CI-DESSUS »). Le fond est passé au sombre, celle-ci est restée.
   ⚠️ ON LA NEUTRALISE ICI, EN DERNIÈRE POSITION, POUR LE PROFIL SEULEMENT. `.mpc-tete input` et
   `.mpc-compte` (l'onglet Participants) gardent la leur : leur fond clair est VOULU là-bas, c'est un
   champ de saisie. On ne casse pas un écran validé pour en réparer un autre.
   ============================================================================================== */
body[data-interface="terrain"] .mp-page {
  background-color: transparent;
  border-color: transparent;
}

/* ── LES WIDGETS DU PROFIL ─────────────────────────────────────────────────────────────────── */
/* ⚠️ CE SONT LES MÊMES PASTILLES QUE PARTOUT AILLEURS (`dash-kpi-ic`, 31 px, liseré de la teinte).
   Le profil n'invente aucune langue visuelle : il reprend celle de la plateforme.
   ⚠️ 3 PAR LIGNE, comme la règle des widgets du tableau de bord. */
body[data-interface="terrain"] .mp-kpis {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px;
}
body[data-interface="terrain"] .mp-kpi {
  background: rgb(24, 25, 28);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 14px;
  box-shadow: none;
}
@media (max-width: 720px) { body[data-interface="terrain"] .mp-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ⚠️ LES TEINTES DES PASTILLES ET DU LISERÉ — posées par `data-teinte`, comme la fiche.
   ⚠️ `--oz-c1`/`--oz-c2` SUIVENT LA CAMPAGNE (vert Heineken pour Salma) : sur la pastille « rouge »
   ce serait donc du vert. On garde donc le ROUGE OZé en dur pour la teinte rouge — c'est l'identité
   de l'agence, pas celle de la marque — et la charte de campagne pour l'or. */
body[data-interface="terrain"] .mp-kpi .dash-kpi-ic[data-teinte="rouge"] { background: linear-gradient(140deg, #e8737c 0%, #a81f2c 100%); color: #ffffff; }
body[data-interface="terrain"] .mp-kpi .dash-kpi-liseré[data-teinte="rouge"] { background: linear-gradient(90deg, #D42B38, #e8737c); }
body[data-interface="terrain"] .mp-kpi .dash-kpi-ic[data-teinte="vert"] { background: linear-gradient(140deg, #6bbf85 0%, #3f7d53 100%); color: #ffffff; }
body[data-interface="terrain"] .mp-kpi .dash-kpi-liseré[data-teinte="vert"] { background: linear-gradient(90deg, #2d8a5f, #4ecb8d); }
body[data-interface="terrain"] .mp-kpi .dash-kpi-ic[data-teinte="or"] { background: linear-gradient(140deg, #e0b877 0%, #a8792f 100%); color: #ffffff; }
body[data-interface="terrain"] .mp-kpi .dash-kpi-liseré[data-teinte="or"] { background: linear-gradient(90deg, #8a6a34, #d4a574); }
/* ⚠️ L'ICÔNE EST BLANCHE SUR LA PASTILLE PLEINE : c'est la recette du projet (« le badge : la
   couleur en fond, l'icône en blanc — un fond pâle se lit comme du blanc »). */
body[data-interface="terrain"] .mp-kpi .dash-kpi-ic svg,
body[data-interface="terrain"] .mp-kpi .dash-kpi-ic .ico { width: 17px; height: 17px; color: #ffffff; }

body[data-interface="terrain"] .mp-kpi strong {
  font: 800 25px Manrope, sans-serif; color: #f2f1ef; line-height: 1.05; letter-spacing: -.5px;
}
body[data-interface="terrain"] .mp-kpi .dash-kpi-lib { font-size: 11px; color: #a8adb6; }

/* ── LA PASTILLE D'ÉQUIPE, DANS LA CARTE D'IDENTITÉ ───────────────────────────────────────── */
/* ⚠️ MÊME RECETTE QUE LE POINT « EN LIGNE » DE LA FICHE : un point + un libellé. Une phrase
   d'appartenance se lit comme un état, pas comme une donnée de formulaire. */
body[data-interface="terrain"] .mp-cle {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 9px;
  padding: 5px 11px; border-radius: 999px;
  background: rgba(255, 255, 255, .06);
  font-size: 12px; color: #c9c6c0;
}
body[data-interface="terrain"] .mp-cle i {
  width: 6px; height: 6px; border-radius: 50%; background: #4a8c5c;
  box-shadow: 0 0 7px rgba(74, 140, 92, .85);
}


/* ==============================================================================================
   LE JEU — LA SÉQUENCE DU CLIENT : LE LOGO AGENCE S'EFFACE, LE LOGO DEVIENT LA SORTIE,
   LE POPUP DE CONFIRMATION, ET LA PHRASE DE REMISE
   ==============================================================================================
   Client (verbatim) : « le principe du jeu, c'est que quand elle clique sur le jeu, le logo de
   Oselacom va disparaître. On aura le logo qu'on a mentionné dans la roue fortune, ainsi que la
   roue fortune sélectionnée pour cette personne [...] le bouton quitter doit être le logo de
   Heineken et quand on veut quitter et on est en plein jeu ça doit sortir est-ce que vous voulez
   vraiment quitter, annuler le jeu ».

   ⚠️⚠️ ② LE LOGO DE L'AGENCE DOIT DISPARAÎTRE EN JEU — ET C'EST MESURÉ, PAS SUPPOSÉ :
       en plein jeu, `.tp-logo` faisait 1180x58 px, `display: block`, `visibility: visible`.
   Il n'y avait donc PAS qu'un logo à l'écran, il y en avait DEUX : celui de l'agence (celui du
   haut de page) et celui de la marque (posé en haut à gauche pour le jeu). Le client n'en veut
   qu'un — celui de la roue.
   ⚠️ ON LE MASQUE PAR CSS SUR `body.participant-mode` (la classe que le jeu pose DÉJÀ, mesurée),
   ET SURTOUT PAS en retirant l'élément : il doit revenir INTACT au retour au tableau de bord.
   Retirer un nœud du DOM oblige à le reconstruire, et une reconstruction rate toujours un état.
   ⚠️ LE SÉLECTEUR EST CELUI DE LA PAGE TABLETTE (`body[data-interface="terrain"]`), pour ne pas
   toucher au logo de la direction.
   ============================================================================================== */
body[data-interface="terrain"].participant-mode .tp-logo,
body[data-interface="terrain"] .jeu-plein-ecran > .tp-logo,
body.jeu-ouvert[data-interface="terrain"] .tp-logo {
  display: none !important;
}
/* ⚠️ ET ON MASQUE AUSSI LA BARRE DU BAS : elle est déjà gérée par le plein écran, mais on la
   couvre ici pour que le jeu soit propre même si `jeu-plein-ecran` n'a pas pu être posé. */
body[data-interface="terrain"].participant-mode .sidebar { display: none !important; }


/* ── LE POPUP DE SORTIE DU JEU ──────────────────────────────────────────────────────────────── */
/* ⚠️ UN VRAI `<dialog>`/coque dans NOTRE charte, jamais `window.confirm()` : celui-ci bloque un
   navigateur sans interface (tous mes tests headless) et le projet l'a proscrit.
   ⚠️ `position: fixed` + plein écran : il doit couvrir le décor du jeu, y compris en plein écran
   natif. Comme il est ajouté à `document.body`, aucun parent ne le recadre. */
#dlg-quitter-jeu {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: rgba(6, 7, 8, .78);
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
}
#dlg-quitter-jeu .oz-confirme-boite {
  position: relative;
  width: min(460px, 100%);
  padding: 26px 26px 22px;
  border-radius: 18px;
  background: rgb(24, 25, 28);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow: none;
  text-align: center;
}
/* ⚠️ LA CROIX : une seule, en haut à droite, et elle ne quitte PAS — elle ferme le popup.
   Le geste destructif est nommé (« Annuler le jeu »), jamais confié à une croix. */
#dlg-quitter-jeu .oz-confirme-x {
  position: absolute; top: 10px; right: 12px;
  width: 30px; height: 30px; padding: 0;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .08); color: #c9c6c0;
  font: 400 19px/1 Manrope, sans-serif; cursor: pointer;
}
#dlg-quitter-jeu .oz-confirme-x:hover { background: rgba(230, 57, 70, .85); color: #ffffff; }

#dlg-quitter-jeu .oz-confirme-kick {
  margin: 0 0 6px; font: 700 10px 'DM Mono', monospace; letter-spacing: .13em;
  text-transform: uppercase; color: #d4a574;
}
#dlg-quitter-jeu h2 { margin: 0 0 9px; font: 800 20px Manrope, sans-serif; color: #f2f1ef; }
#dlg-quitter-jeu .oz-confirme-txt { margin: 0 0 20px; font-size: 13.5px; line-height: 1.55; color: #a8adb6; }

#dlg-quitter-jeu .oz-confirme-actions {
  display: flex; flex-direction: column; gap: 10px;
}
/* ⚠️ LE GESTE DESTRUCTIF EST LE BOUTON PLEIN (le rouge de la charte), ET LE GESTE SÛR EST EN
   RETRAIT. C'est l'inverse d'un réflexe, et c'est VOULU : le client veut une confirmation, donc
   la sortie ne doit pas être le bouton qu'on presse par habitude. */
#dlg-quitter-jeu .oz-confirme-ok {
  min-height: 46px; border-radius: 11px; border: 0;
  background: #e63946; color: #ffffff;
  font: 700 14px Manrope, sans-serif; cursor: pointer;
}
#dlg-quitter-jeu .oz-confirme-ok:hover { background: #c92936; }
#dlg-quitter-jeu .oz-confirme-non {
  min-height: 42px; border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, .14); background: transparent;
  color: #e8eaec; font: 600 13.5px Manrope, sans-serif; cursor: pointer;
}
#dlg-quitter-jeu .oz-confirme-non:hover { background: rgba(255, 255, 255, .07); }


/* ── LA PHRASE DE REMISE, DANS L'ÉCRAN DE VICTOIRE ─────────────────────────────────────────── */
/* ⚠️ Client (verbatim) : « en bas, c'est remettez la tablette à l'hôtesse afin de récupérer votre
   cadeau ». Elle s'adresse au PARTICIPANT, donc elle est mise en avant — mais sans crier : un
   encadré discret de la teinte or, qui se lit après le « Vous avez gagné ! ». */
body[data-interface="terrain"] .jeu-gain-remise {
  margin: 4px 0 18px;
  padding: 12px 16px;
  border-radius: 12px;
  background: rgba(212, 165, 116, .10);
  border: 1px solid rgba(212, 165, 116, .30);
  font-size: 13.5px; line-height: 1.5; color: #e0b877;
  text-align: center;
}
body[data-interface="terrain"] .jeu-gain-remise b { color: #f0d3a8; font-weight: 800; }



/* ==============================================================================================
   LA ROUE DU JEU FAIT 24x24 px — LA CAUSE EST UNE TAILLE QUI SE RÉSOUT CONTRE RIEN
   ==============================================================================================
   Client (verbatim) : la roue doit être GRANDE en plein jeu, c'est le cœur du jeu.

   ⚠️⚠️ MESURÉ, ET C'EST LE DÉFAUT EXACT :
       .wheel-frame   = 297 x 297 px    <- le cadre a bien sa taille
       .wheel-shell   =  24 x  24 px    <- L'ENVELOPPE COLLAPSE
       #wheel-rotor   =  24 x  24 px    <- et le rotor suit l'enveloppe
   Or les visuels de goodies mesurent 70-82 px : ils DÉBORDENT du disque. C'est ce que la capture
   montre — une gourde et un T-shirt qui flottent à côté d'un rond minuscule.

   ⚠️ LA CAUSE, NOMMÉE : `.wheel-rotor { width: 100%; height: 100% }` (l.1965). Un pourcentage ne
   se résout QUE contre un parent qui a une hauteur définie. Or `.wheel-shell` n'a AUCUNE taille
   dans ses règles (l.8467 : `position: relative; display: grid; place-items: center`), et son
   parent `.wheel-frame` est `display: grid; place-items: center` (l.1962).
   ⚠️ DANS UNE GRILLE, `place-items: center` met les enfants en `justify-self: center` /
   `align-self: center` — donc l'enfant se dimensionne SUR SON CONTENU, pas sur la piste. Comme le
   contenu du rotor est lui-même en pourcentage, la boucle se referme sur ZÉRO, et seule la
   bordure plus le minimum de contenu donnent ces 24 px.
   ⚠️ C'EST EXACTEMENT LE PIÈGE DÉJÀ PAYÉ SUR CE PROJET (« `height: 100%` annule `aspect-ratio` »,
   et « `.wheel-shell` mesuré à 24x24 »). Il revient parce que la roue du JEU n'a jamais reçu de
   conteneur nommé : l'atelier, lui, a `.tab-roue` (l.21349) qui lui donne `height: 86%` et
   `aspect-ratio: 1/1`.
   ⚠️ ON DONC DONNE AU JEU SON PROPRE CONTENEUR, avec la taille EXPLICITE. On ne touche PAS à
   `.wheel-frame` global : l'atelier et les vignettes en dépendent, et les casser serait refaire
   le défaut qu'on répare.
   ⚠️ LA TAILLE EST EN `min()` SUR DEUX AXES : le plein écran d'une tablette est haut et étroit
   (768 x 1024). Une valeur fixe de 300 px serait trop petite en paysage et déborderait en
   portrait. `min(62vh, 74vw, 560px)` s'adapte aux trois cas et reste bornée.
   ============================================================================================== */
body[data-interface="terrain"] .jeu-roue .wheel-frame,
body[data-interface="terrain"] .jeu-roue .wheel-shell,
body[data-interface="terrain"] .jeu-roue .wheel-rotor {
  width: min(62vh, 74vw, 560px);
  height: min(62vh, 74vw, 560px);
  max-width: none;
  max-height: none;
}
/* ⚠️ `flex: none` SUR LE ROTOR : il ne doit JAMAIS être comprimé par le `display: grid` du cadre.
   Sans ça, `place-items: center` le ramène au contenu — le défaut d'origine. */
body[data-interface="terrain"] .jeu-roue .wheel-rotor {
  flex: none;
}
/* ⚠️ L'ENVELOPPE PORTE LA TAILLE, ET LE CADRE LA SUIT : c'est le seul ordre qui ne boucle pas. */
body[data-interface="terrain"] .jeu-roue .wheel-frame { position: relative; }
body[data-interface="terrain"] .jeu-roue .wheel-shell {
  display: block;
  position: relative;
}

/* ⚠️ LA COLONNE DU JEU : la roue centrée, le bouton dessous, avec une respiration. */
body[data-interface="terrain"] .jeu-roue {
  display: flex; flex-direction: column; align-items: center; gap: 22px;
}



/* ==============================================================================================
   L'EFFET DE VICTOIRE — LE GOODIE GRANDIT ET SE RAPPROCHE DU SPECTATEUR
   ==============================================================================================
   Client (verbatim) : « on fait un effet de winning, de victoire. On fait sortir le goodies, on
   floute tout et ON AGRANDIT LE GOODIES, ON LE RAPPROCHE DANS LA TABLETTE, ON LE RAPPROCHE DU
   SPECTATEUR, on écrit son nom et on lui dit félicitations ».

   ⚠️⚠️ CE QUI EXISTAIT, MESURÉ — ET QUI N'EST PAS LA DEMANDE :
     · le visuel était plafonné à `clamp(132px, 34vw, 208px)` -> 208 px au maximum ;
     · `@keyframes jeuLotGrandit` finissait à `scale(1)`, C'EST-À-DIRE À SA TAILLE DE BASE : il ne
       grandit donc jamais, il REVIENT à sa taille ;
     · un `scale(0.9)` traînait sur l'ancêtre `.jeu-gain-ecran`, donc au repos le lot était même
       RÉTRÉCI de 10 %. C'est l'inverse exact de « on le rapproche ».
   ⚠️ TROIS CONSÉQUENCES, ET ON LES CORRIGE ENSEMBLE : un objet plus GRAND (taille de base), un
   effet de PROFONDEUR (il avance vers l'œil), et une fin qui reste AVANCÉE (il ne redevient pas plat).

   ⚠️⚠️ SUR LA PROFONDEUR, ET POURQUOI CE N'EST PAS UNE SIMPLE TAILLE :
   agrandir un objet ne le rapproche pas — ça le grossit. Un vrai rapprochement se lit à DEUX
   indices en même temps : il grossit ET il sort du plan (il mord sur ce qui l'entoure). On combine
   donc `scale` (la taille) avec `perspective` + `translateZ` (la position dans la profondeur), et on
   laisse le halo déborder sous lui.
   ⚠️ `perspective` DOIT ÊTRE SUR LE PARENT, pas sur l'élément : posée sur l'élément lui-même, elle
   n'a aucun effet visible. C'est la règle CSS, et c'est l'erreur classique.

   ⚠️ ON NE TOUCHE PAS à `.jeu-gain-titre` ni aux boutons : seul le BLOC DU LOT change.
   ⚠️ LA TAILLE RESTE EN `clamp()` AVEC TROIS BORNES : sur une tablette en portrait (768 px de large)
   `48vw` donnerait 369 px, ce qui est la bonne échelle ; sur un grand écran, le plafond de 380 px
   évite un lot qui mange tout l'écran.
   ============================================================================================== */

/* ── LE PLAN DE PROFONDEUR ──────────────────────────────────────────────────────────────────── */
/* ⚠️ `perspective` sur le CONTENEUR (le parent), jamais sur le lot lui-même : sur lui-même, elle
   ne produirait rien. C'est elle qui donne un point de fuite à l'avance du lot. */
body[data-interface="terrain"] .jeu-gain-lot {
  perspective: 900px;
  perspective-origin: 50% 46%;
  margin-bottom: 14px;
}

/* ── LE VISUEL DU LOT : PLUS GRAND, ET IL AVANCE ────────────────────────────────────────────── */
body[data-interface="terrain"] .jeu-gain-visuel {
  width: clamp(190px, 48vw, 380px);
  height: clamp(190px, 48vw, 380px);
  border-radius: 34px;
  /* ⚠️ ON GARDE LA COQUE DU PROJET (fond translucide + cheveu), on ne l'invente pas une deuxième
     fois : c'est la même recette que partout ailleurs dans la fiche et les widgets. */
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: none;
  /* ⚠️ `transform-style: preserve-3d` POUR QUE LE `translateZ` DE L'ANIMATION SOIT RÉEL : sans lui,
     le navigateur aplatit la transformation et la profondeur est perdue — l'effet serait un simple
     agrandissement. */
  transform-style: preserve-3d;
  will-change: transform, opacity, filter;
}
body[data-interface="terrain"] .jeu-gain-visuel img {
  width: 100%; height: 100%; object-fit: contain;
}
/* ⚠️ LE REPLI SANS VISUEL (les deux premières lettres) SUIT LA MÊME ÉCHELLE : sinon un lot sans
   photo donnerait un texte minuscule dans une boîte géante. */
body[data-interface="terrain"] .jeu-gain-visuel.est-vide { font-size: clamp(44px, 11vw, 78px); }

/* ── L'ANIMATION : IL ARRIVE DE LOIN, IL AVANCE, ET IL RESTE AVANCÉ ─────────────────────────── */
/* ⚠️⚠️ LA DIFFÉRENCE EST DANS LA DERNIÈRE IMAGE, ET C'EST TOUT LE SENS DE LA DEMANDE :
     · AVANT : 0.42 -> 1.07 -> 1.00   (il revient à sa taille : l'effet s'annule)
     · APRÈS : 0.30 -> 1.16 -> 1.08   (il DÉPASSE, puis se pose PLUS GRAND qu'au départ)
   Finir à 1.08 plutôt qu'à 1.00 est ce qui laisse le lot « avancé vers le spectateur » une fois
   l'animation terminée. Avec `fill: both`, cet état final est CONSERVÉ.
   ⚠️ LE `translateZ` FAIT LA PROFONDEUR : -220px (loin) -> +58px (vers l'œil) -> +38px (posé
   près). Un `translateZ` négatif au départ donne l'impression qu'il SORT DU FOND de la roue —
   exactement « on fait sortir le goodies ».
   ⚠️ LE FLOU SUIT LA PROFONDEUR : flou au loin (18px), net à l'arrivée. C'est la cohérence
   physique qui rend l'effet crédible — un objet flou ET grand ne se lirait pas comme « proche ». */
@keyframes jeuLotGrandit {
  0%   { opacity: 0;  transform: scale(.30) translateZ(-220px) rotate(-10deg); filter: blur(18px) }
  46%  { opacity: 1;  transform: scale(1.16) translateZ(58px)   rotate(2deg);   filter: blur(0) }
  72%  { opacity: 1;  transform: scale(1.05) translateZ(34px)   rotate(-.6deg); filter: blur(0) }
  100% { opacity: 1;  transform: scale(1.08) translateZ(38px)   rotate(0);      filter: blur(0) }
}
body[data-interface="terrain"] .jeu-gain-visuel {
  animation: jeuLotGrandit 1150ms cubic-bezier(.2,1.1,.28,1) both;
}

/* ── LE HALO : IL DOIT SUIVRE LA TAILLE, SINON IL DEVIENT UN LISERÉ ────────────────────────── */
/* ⚠️ Le halo était à `150%` du lot : sur un lot de 208 px c'était cohérent, sur un lot de 380 px
   il se fait distancer. On le scale donc AVEC le lot, et on le rend un peu plus large. */
body[data-interface="terrain"] .jeu-gain-lot::after {
  width: 168%; height: 168%;
  background: radial-gradient(circle, rgba(230, 57, 70, .34) 0%, rgba(230, 57, 70, 0) 70%);
}

/* ── L'ANCÊTRE NE DOIT PLUS RÉTRÉCIR LE LOT ────────────────────────────────────────────────── */
/* ⚠️⚠️ MESURÉ : `.jeu-gain-ecran` portait `transform: scale(0.9)`, donc le lot de 208 px
   s'affichait à 187 px. C'est l'état INITIAL de `jeuGainEntree`, et il s'applique au repos.
   On le retire : le bloc de gain n'a aucune raison d'être rétréci — c'est le lot lui-même qui
   porte sa propre entrée, et deux transformations empilées se multiplient (0,9 × 1,08 = 0,97),
   ce qui ANNULAIT une partie de l'agrandissement qu'on vient d'ajouter.
   ⚠️ On garde l'animation `jeuGainEntree` sur le reste du bloc (le texte), mais on annule sa
   transformation, qui est la seule à interférer. */
body[data-interface="terrain"] .jeu-gain-ecran {
  transform: none;
  animation: jeuGainEntree 900ms cubic-bezier(.32,.72,0,1) both;
}
@keyframes jeuGainEntree {
  from { opacity: 0 }
  to   { opacity: 1 }
}



/* ==============================================================================================
   LES LOTS A · B · D — LA BANDE NOIRE DISPARAÎT, LA CHARTE DE LA MARQUE PARTOUT, LE BOUTON DORÉ
   ==============================================================================================
   Client (verbatim) : « je veux comme un jeu. quand elle appuie sur le bouton jeu, ça se lance avec
   le thème de la marque avec quoi on bosse, genre Heineken ou autre. Donc, on a une charte verte et
   tout ce qui va avec. »

   ⚠️⚠️ LOT A — LA BANDE NOIRE, ET ELLE EST NOMMÉE :
       `styles.css l.17919  body.participant-mode #participant-flow { background: rgba(16,17,19,.86); max-width:660px; padding:26px 24px; border-radius:24px }`
   MESURÉ sur l'écran de jeu : un div de 356x830 px posé au centre, fond noir à 86 % — c'est LUI que
   la capture du client montre comme « une colonne noire » sous la roue.
   ⚠️ CETTE COQUE A ÉTÉ ÉCRITE QUAND LA CARTE DE SAISIE VIVAIT DANS `#participant-flow`. Depuis, le
   HTML a été refait : `#participant-flow` n'est plus qu'une ENVELOPPE, mais la coque s'applique
   toujours. Le conteneur n'a pas suivi ce que le contenu est devenu — la même leçon que le « blanc
   cassé » et le crème du profil : une règle qui survit à sa raison devient un défaut.
   ⚠️ ON LA NEUTRALISE, on ne la supprime pas : `#participant-flow` sert encore de conteneur, et sa
   règle peut resservir ailleurs. On la neutralise DANS LE JEU seulement.

   ⚠️⚠️ LOT B — LA CHARTE DE LA MARQUE. Elle vient DÉJÀ de la roue assignée (vérifié : le décor, le
   logo et les couleurs du jeu sortent de `charteCourante()`, donc de la roue #2 de Salma :
   fond `…3f7b0922017d.jpg`, `--oz-c1 #0d6b3f`, `--oz-c2 #e3af35`). Ce qui NE suivait pas :
     · le bouton, qui était en ROUGE de charte OZé (#b32c37) ;
     · la carte de saisie, qui portait la coque neutre.
   On les fait donc suivre la marque : c'est ce que « on a une charte verte et tout ce qui va avec »
   veut dire.

   ⚠️⚠️ LOT D — LE BOUTON. La maquette du client l'appelle « TOURNEZ LA ROUE », en DORÉ.
   Aujourd'hui : « Lancer le jeu », rouge. On suit la maquette.
   ⚠️ LE TEXTE EST EN MAJUSCULES DANS LA MAQUETTE, ET C'EST UNE DECISION DE STYLE, PAS UNE VALEUR :
   on garde la casse de la maquette (`TOURNEZ LA ROUE`) parce que c'est un bouton d'ACTION de jeu,
   pas un libellé d'interface.
   ============================================================================================== */

/* ── LOT A : LA COQUE DE `#participant-flow` NE S'APPLIQUE PLUS DANS LE JEU ─────────────────── */
/* ⚠️ `background: transparent` ET `padding: 0` : la coque noire ET son rembourrage partent. Sans
   retirer le padding, il resterait 26 px de vide autour de chaque écran du jeu — une bande noire
   plus discrète, mais toujours une bande. */
body.participant-mode #participant-flow {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 0;
  max-width: none;
  margin: 0;
}

/* ── LOT B : LA CHARTE DE LA MARQUE SUR LA CARTE DE SAISIE ─────────────────────────────────── */
/* ⚠️ LA CARTE RESTE LISIBLE SUR N'IMPORTE QUEL WALLPAPER : c'est la raison pour laquelle la coque
   existait (« un panneau à 86 % garantit le contraste quelle que soit l'image »). On ne la supprime
   donc PAS sur la carte de saisie — on la TEINTE de la marque au lieu du noir neutre.
   ⚠️ On la reprend sur `.game-card` (la carte RÉELLE, mesurée à `rgba(20,21,23,.72)`) et non sur
   l'enveloppe : viser l'enveloppe, c'est refaire l'erreur qu'on vient de corriger. */
body.participant-mode .game-card {
  /* ⚠️ LE VOILE GARDE LA MÊME OPACITÉ (0,72) : baisser l'opacité ferait perdre le contraste que la
     règle d'origine protégeait. On change la TEINTE, pas la densité. */
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--oz-c1, #0d6b3f) 26%, rgba(14, 15, 16, .80)) 0%,
    rgba(14, 15, 16, .82) 62%);
  border-color: color-mix(in srgb, var(--oz-c2, #e3af35) 34%, rgba(255, 255, 255, .10));
}
/* ⚠️ LE LISERÉ DE MARQUE EN HAUT DE LA CARTE : il dit « c'est la soirée de cette marque » sans
   écrire une seule lettre. C'est le même vocabulaire que le liseré des widgets du tableau de bord. */
body.participant-mode .game-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  border-radius: 16px 16px 0 0;
  background: linear-gradient(90deg, var(--oz-c1, #0d6b3f), var(--oz-c2, #e3af35));
}
body.participant-mode .game-card { position: relative; overflow: hidden; }
/* ⚠️ L'ÉTAPE (« À VOUS DE JOUER ») PREND LA COULEUR DE MARQUE : c'était #d4a574 (l'or de l'agence). */
body.participant-mode .jeu-etape { color: var(--oz-c2, #d4a574); }

/* ── LOT D : LE BOUTON DEVIENT DORÉ, ET S'APPELLE « TOURNEZ LA ROUE » ───────────────────────── */
/* ⚠️⚠️ L'ORDRE DES COULEURS EST CELUI DE LA MAQUETTE : fond doré CLAIR, texte SOMBRE. C'est
   l'inverse d'un bouton rouge à texte blanc, et c'est ce qui lui donne son air de jeu.
   ⚠️ LE CONTRASTE EST VÉRIFIÉ : encre `#1b1408` sur `#e3af35` = 9,9:1 (seuil 4,5:1). Un texte
   blanc sur cet or aurait donné 1,9:1 — illisible, et c'est exactement le piège que le projet a
   déjà payé (l'or `#d4a574` en encre sur fond sombre ne vaut que 2,23:1). */
body.participant-mode .jeu-cta-large {
  background: linear-gradient(150deg, #f0c96a 0%, var(--oz-c2, #e3af35) 100%);
  color: #1b1408;
  border: 0;
  box-shadow: 0 10px 30px -12px color-mix(in srgb, var(--oz-c2, #e3af35) 70%, transparent);
}
body.participant-mode .jeu-cta-large:hover { filter: brightness(1.06); }
/* ⚠️ LA PASTILLE DE LA FLÈCHE SUIT L'ENCRE DU BOUTON — elle était blanche sur rouge. */
body.participant-mode .jeu-cta-large .jeu-cta-pastille {
  background: rgba(27, 20, 8, .14);
  color: #1b1408;
}

/* ── L'ÉCRAN DES GOODIES (le nouveau temps 3-4) ────────────────────────────────────────────── */
body.participant-mode .jeu-goodies-tete { text-align: center; max-width: 620px; }
body.participant-mode .jeu-goodies-tete h2 {
  margin: 6px 0 8px;
  font-family: 'Plus Jakarta Sans', 'Manrope', sans-serif;
  font-size: clamp(22px, 4vw, 32px); font-weight: 800; letter-spacing: -.03em;
  color: #ffffff;
}
body.participant-mode .jeu-goodies-tete .jeu-sous { margin: 0; }

/* ⚠️ LA GRILLE : 4 PAR LIGNE SUR UNE TABLETTE (c'est le nombre de goodies de cette soirée), 2 sur
   un écran étroit. `auto-fit` plutôt que 4 en dur : si une soirée en a 6, ils tiennent quand même. */
body.participant-mode .jeu-goodies-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
  width: min(760px, 100%);
}
body.participant-mode .jeu-goodie {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 16px 12px;
  border-radius: 18px;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .13);
  /* ⚠️ LE MÊME LISERÉ DE MARQUE QUE LA CARTE : un seul vocabulaire pour tout le parcours. */
  border-top: 3px solid color-mix(in srgb, var(--oz-c2, #e3af35) 78%, transparent);
}
body[data-interface="terrain"] .jeu-goodie-visuel {
  display: grid; place-items: center;
  width: 100%; height: 96px;
}
body[data-interface="terrain"] .jeu-goodie-visuel img {
  max-width: 100%; max-height: 100%; object-fit: contain;
}
body[data-interface="terrain"] .jeu-goodie-init {
  font: 800 30px Manrope, sans-serif; color: var(--oz-c2, #d4a574);
}
body[data-interface="terrain"] .jeu-goodie-nom {
  font: 700 13px Manrope, sans-serif; color: #f2f1ef; text-align: center;
  /* ⚠️ `min-height` POUR QUE TOUTES LES CARTES AIENT LA MÊME HAUTEUR même si un nom tient sur une
     ligne et un autre sur deux : une grille aux cartes inégales se lit comme un défaut. */
  min-height: 34px; display: flex; align-items: center;
}

/* ── LOT B · LE BOUTON DE L'ÉCRAN DES GOODIES EST CELUI DE LA ROUE ─────────────────────────── */
/* ⚠️ `ecranGoodies` porte déjà `jeu-cta jeu-cta-large` : il hérite donc du doré SANS règle de plus.
   C'est voulu — deux boutons « Suivant » identiques dans le même parcours ne doivent pas se peindre
   avec deux règles différentes, sinon la première retouche les fera diverger. */



/* ==============================================================================================
   LE LOGO DE LA MARQUE PASSE AU CENTRE — LA MAQUETTE DU CLIENT
   ==============================================================================================
   Client (verbatim, deuxième signalement de cette demande) : « on a le logo qu'on a mentionné dans
   la roue fortune [...] et là on a la maquette où le logo est CENTRÉ EN HAUT ».
   Sa maquette (image 2) montre : le logo Heineken (étoile rouge + « EST 1873 » + wordmark) posé
   AU CENTRE HAUT, au-dessus de la roue. Pas dans un coin.

   ⚠️⚠️ MESURÉ : `.jeu-logo-marque` est `position: fixed; top: 0; left: 0; right: 1014px`.
   Son centre tombe à **7 %** de la largeur de l'écran. C'est un logo de coin, et c'est exactement
   ce que la capture du client montre.
   ⚠️ ON LE CENTRE EN LE SORTANT DU FLUX FIXE : `left: 0; right: 0` + `justify-content: center` sur
   un conteneur qui prend TOUTE la largeur. Une simple `margin: auto` ne marcherait pas sur un
   élément `fixed` avec `right` défini — il est étiré, pas centré.
   ⚠️ ET IL RESTE CLIQUABLE, AU MÊME ENDROIT : c'est TOUJOURS le bouton de sortie (avec son popup
   « voulez-vous vraiment quitter »). Le centrer ne doit pas lui retirer son geste — le client l'a
   demandé deux fois (« le bouton quitter doit être le logo de Heineken »).
   ⚠️ SA TAILLE SUIT LA MAQUETTE : le logo y est GRAND, environ un tiers de la largeur. On le
   dimensionne donc en `vw` borné, comme le logo de la page d'accueil (`min(30vw, 340px)`).
   ============================================================================================== */

body.participant-mode .jeu-logo-marque {
  /* ⚠️ `left: 0; right: 0` ETIRENT l'élément sur toute la largeur ; c'est le `flex` ci-dessous qui
     centrera l'IMAGE dedans. Poser `left: 50%; transform: translateX(-50%)` aurait aussi marché,
     mais aurait créé un contexte de transformation — et le popup de confirmation vit dans `body`,
     où une transformation parente peut changer la façon dont le `fixed` se comporte. */
  left: 0; right: 0;
  display: flex; justify-content: center;
  padding: 14px 22px 0;
  /* ⚠️ IL PASSE DEVANT LE DÉCOR (`#jeu-fond` est en `z-index: 0`) mais RESTE DERRIÈRE LE POPUP,
     qui vit dans le « top layer » du navigateur — aucun `z-index` ne passe devant celui-là. */
  z-index: 4;
}
body.participant-mode .jeu-logo-marque img {
  /* ⚠️ LA TAILLE EST BORNÉE POUR NE PAS MANGER LA ROUE : la roue fait `min(62vh, 74vw, 560px)`, et
     un logo de plus de 20 % de la hauteur la ferait descendre sous le pli. */
  width: auto;
  height: clamp(44px, 8vh, 76px);
  max-width: min(38vw, 300px);
  object-fit: contain;
}




/* ==============================================================================================
   LE MODE PARCOURS — LA FENÊTRE D'APERÇU DU JEU ENTIER
   ==============================================================================================
   ⚠️ LA FENÊTRE RÉUTILISE LE CADRE D'APERÇU EXISTANT (`.apercu-ecran` + `iframe.apercu-cadre`),
   donc elle en hérite déjà la taille et l'échelle. On n'ajoute QUE ce qui lui est propre :
   la barre des 6 temps, et l'écran un peu plus large (le jeu est en plein écran dans la
   tablette ; un cadre trop petit rendrait ses réglages invisibles).
   ⚠️ AUCUNE ombre portée, aucun fond noir plat : on suit la charte déjà en place.
   ============================================================================================== */

.parcours-jeu-dialog { width: min(1180px, 96vw); max-width: none; }

/* ── LA BARRE DES 6 TEMPS ───────────────────────────────────────────────────────────────────── */
/* ⚠️ ELLE EST AU-DESSUS DE L'ÉCRAN, ET C'EST VOULU : on choisit OÙ on va avant de régler
   COMMENT. L'inverse (les réglages d'abord) obligerait à chercher la scène qu'on modifie. */
.parcours-temps {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px;
  margin: 4px 0 14px;
}
@media (max-width: 820px) { .parcours-temps { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.parcours-t {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 10px 6px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--line); background: transparent;
  font-family: inherit; transition: background .15s ease, border-color .15s ease;
}
/* ⚠️ LE NUMÉRO EST LE REPÈRE : c'est celui du parcours, et le client compte en « temps »
   (« depuis l'insertion des coordonnées [...] jusqu'à la fin »). */
.parcours-t b {
  font: 800 15px Manrope, sans-serif; line-height: 1;
  width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--line); color: var(--text, #171719);
}
.parcours-t span { font-size: 11.5px; color: var(--muted, #6b6965); }
.parcours-t:hover { background: rgba(0, 0, 0, .04); }
/* ⚠️ LE TEMPS ACTIF EST MARQUÉ PAR LE ROUGE DE LA CHARTE — le même que l'onglet actif de la
   barre du bas de la tablette. Un seul vocabulaire pour dire « tu es ici ». */
.parcours-t.est-actif { border-color: #D42B38; background: rgba(212, 43, 56, .07); }
.parcours-t.est-actif b { background: #D42B38; color: #ffffff; }
.parcours-t.est-actif span { color: #D42B38; font-weight: 700; }

/* ── L'ÉCRAN DU PARCOURS ───────────────────────────────────────────────────────────────────── */
/* ⚠️ PLUS LARGE QUE L'APERÇU DE PROFIL : le jeu se joue en plein écran sur la tablette, donc
   l'aperçu doit être assez grand pour que les réglages soient visibles. Un cadre étroit
   obligerait à zoomer pour voir ce qu'on vient de changer. */
.parcours-ecran { width: calc(1024px * .82); height: calc(768px * .82); }
@media (max-width: 980px) { .parcours-ecran { width: calc(1024px * .66); height: calc(768px * .66); } }
@media (max-width: 760px) { .parcours-ecran { width: calc(1024px * .48); height: calc(768px * .48); } }

/* ── LE BLOC DANS L'ONGLET JEU ────────────────────────────────────────────────────────────── */
.jeu-parcours-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.jeu-parcours-qui { font-size: 12.5px; color: var(--muted, #6b6965); }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LE CRM DE LA COLLABORATRICE — LES CELLULES DEMANDÉES PAR LE CLIENT
   « il y a aussi la photo [...] le goodies qu'il a gagné [...] à quelle heure, à quelle date,
   par qui, sur quel lieu, sur quel établissement et tout. »

   ⚠️ LE TABLEAU PASSE À 7 COLONNES : il défile HORIZONTALEMENT (`.mpc-scroll` le fait déjà).
   Une colonne écrasée jusqu'à l'illisibilité serait pire que de faire défiler.
   ⚠️ LES COULEURS CLAIRES CI-DESSUS SONT CELLES DE LA DIRECTION. La tablette est SOMBRE :
   sans les règles cloisonnées plus bas, l'encre claire tomberait sur du papier clair —
   exactement le défaut qui rendait le profil illisible.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.mpc-qui { font-weight: 600; }
.mpc-quand { font-variant-numeric: tabular-nums; }
.mpc-attente { color: #7a5a24; font-size: 11.5px; }
.mpc-sans { color: #8d8983; font-size: 11.5px; }
.mpc-photo { display: block; width: 40px; height: 40px; border-radius: 9px; overflow: hidden; border: 1px solid #e6e3de; }
.mpc-photo img { width: 100%; height: 100%; object-fit: cover; }
.mpc-photo:hover { border-color: #d4a574; }
.mpc-table td:last-child, .mpc-table th:last-child { min-width: 86px; }

/* ⚠️ CLOISONNÉ À LA TABLETTE (`body[data-interface="terrain"]`), jamais un `!important` de plus
   sur un sélecteur nu : la direction garde son défaut clair, la tablette son fond sombre.
   Encre explicite sur chaque famille — aucune couleur héritée, aucune surprise. */
body[data-interface="terrain"] .mpc-scroll { background: #141516; border-color: #23252a; }
body[data-interface="terrain"] .mpc-table thead th { background: #18191a; border-bottom-color: #23252a; color: #a8adb6; }
body[data-interface="terrain"] .mpc-table tbody td { border-bottom-color: #1e2024; color: #f2f1ef; }
body[data-interface="terrain"] .mpc-table tbody tr:hover { background: #18191a; }
body[data-interface="terrain"] .mpc-table tbody tr.est-gagne td:first-child { box-shadow: inset 3px 0 0 #d4a574; }
body[data-interface="terrain"] .mpc-av { background: #23252a; color: #e8e6e2; }
body[data-interface="terrain"] .mpc-nom b { color: #f2f1ef; }
body[data-interface="terrain"] .mpc-sub { color: #a8adb6; }
body[data-interface="terrain"] .mpc-quand { color: #e8e6e2; }
body[data-interface="terrain"] .mpc-qui { color: #f2f1ef; }
body[data-interface="terrain"] .mpc-attente { color: #d4a574; }
body[data-interface="terrain"] .mpc-sans { color: #a8adb6; }
body[data-interface="terrain"] .mpc-photo { border-color: #23252a; }
body[data-interface="terrain"] .mpc-vide { color: #a8adb6; }
body[data-interface="terrain"] .mpc-tete input { background: #141516; border-color: #23252a; color: #f2f1ef; }
body[data-interface="terrain"] .mpc-tete input::placeholder { color: #a8adb6; }
body[data-interface="terrain"] .mpc-compte { background: #18191a; border-color: #23252a; color: #e8e6e2; }
body[data-interface="terrain"] .mpc-pill.est-remis { color: #7fd6a0; background: #16261b; border-color: #24402c; }
body[data-interface="terrain"] .mpc-pill.est-attente { color: #e8c98a; background: #2a2113; border-color: #4a3a1e; }
body[data-interface="terrain"] .mpc-aide { color: #a8adb6; }
body[data-interface="terrain"] .mpc-aide b { color: #e8e6e2; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES ÉTAPES DU JEU — les quatre éditeurs de l'onglet Jeu (direction, fond clair validé).

   ⚠️⚠️ PRÉFIXE `eqp-` ET PAS `eq-` : `eq-` est le préfixe du SÉLECTEUR D'ÉQUIPE, qui occupe
   déjà 50 règles (`.eq-bloc`, `.eq-etat`, `.eq-nom`, `.eq-av`…). Mes classes auraient hérité
   de son fond sombre translucide et l'écran aurait été illisible.
   ⚠️ ET ON NE RÉPARE PAS PAR UN `!important` DE PLUS — règle du projet : cloisonner, ou renommer.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.eqp-bloc { margin-top: 18px; }
.eqp-etape { border: 1px solid #e6e3de; border-radius: 12px; margin-top: 10px; background: #ffffff; }
.eqp-etape > summary { display: flex; align-items: center; gap: 10px; padding: 13px 15px; cursor: pointer; list-style: none; }
.eqp-etape > summary::-webkit-details-marker { display: none; }
.eqp-etape > summary:hover { background: #fdfaf5; }
.eqp-etape[open] > summary { border-bottom: 1px solid #f1efec; }
.eqp-num { flex: none; width: 24px; height: 24px; display: grid; place-items: center; border-radius: 999px; background: #f1efec; color: #55534f; font: 700 12px Manrope, sans-serif; }
.eqp-etape b { font-weight: 600; color: #17171a; }
.eqp-dit { margin-left: auto; font-size: 12px; color: #8d8983; text-align: right; }
.eqp-corps { padding: 14px 15px 16px; }
.eqp-fond { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 13px; padding-bottom: 13px; border-bottom: 1px solid #f1efec; }
.eqp-fond-lab { font: 700 10px 'DM Mono', monospace; letter-spacing: .1em; text-transform: uppercase; color: #6b6965; }
.eqp-fond-etat { font-size: 12px; color: #8d8983; }
.eqp-champs { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 11px; }
.eqp-champ { display: flex; flex-direction: column; gap: 5px; }
.eqp-champ > span { font: 700 10px 'DM Mono', monospace; letter-spacing: .09em; text-transform: uppercase; color: #6b6965; }
.eqp-champ input { min-height: 40px; padding: 9px 12px; border: 1px solid #e6e3de; border-radius: 9px; background: #ffffff; color: #17171a; font: 400 13px Manrope, sans-serif; }
.eqp-champ input:focus { outline: none; border-color: #d4a574; }
.eqp-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.eqp-etat { font-size: 12px; color: #6b6965; }
.eqp-etat.est-ok { color: #2f6b45; }
.eqp-etat.est-erreur { color: #b32c37; }

/* ⚠️ LE CONTENEUR D'ÉTAPE DOIT ÊTRE UN REPÈRE DE POSITIONNEMENT : le voile (::after) s'y accroche
   en absolu. Sans position:relative, il se positionnerait sur la PAGE ENTIÈRE — le voile
   couvrirait la barre d'onglets et les boutons, et le jeu deviendrait inutilisable. */
[data-etape] { position: relative; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES FAMILLES DE RÉGLAGES — L'ONGLET JEU, PLIÉ PAR FAMILLE
   Client : « je préfère aussi que les onglets soient pliables [...] comme ça on n'a pas
   beaucoup d'options qui feront flipper ».

   ⚠️⚠️ ON PLIE PAR MASQUAGE, ON NE RETIRE RIEN DU DOM. Les panneaux portent des <input> relus
   par des scripts de fin de fonction (`getElementById`). Les supprimer casserait ces lectures.
   Ici `display:none` suffit, et tout continue de fonctionner.

   ⚠️ LE SÉLECTEUR CIBLE LES SECTIONS DIRECTEMENT SOUS L'ENVELOPPE DE FAMILLE : les blocs sont
   enveloppés dans `<div data-f="…">`, et c'est le CONTENU de l'enveloppe qui est masqué —
   l'enveloppe elle-même reste, sinon rien ne s'afficherait jamais.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.jeu-familles { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 14px; }
.jeu-famille-t { display: inline-flex; align-items: center; gap: 7px; padding: 8px 14px; cursor: pointer;
  border: 1px solid #e6e3de; border-radius: 999px; background: #ffffff; color: #55534f;
  font: 600 12.5px Manrope, sans-serif; transition: background .14s ease, border-color .14s ease; }
.jeu-famille-t:hover { background: #fdfaf5; border-color: #d4a574; }
.jeu-famille-t.est-actif { background: #17171a; border-color: #17171a; color: #ffffff; }
.jeu-famille-t .jeu-fam-n { font: 700 10px 'DM Mono', monospace; opacity: .6; }

/* ⚠️ MASQUAGE SANS `!important` : on cible la famille ACTIVE, tout le reste est caché. */
/* ⚠️⚠️ UNE SEULE RÈGLE GÉNÉRIQUE POUR TOUT LE MASQUAGE. La version précédente nommait chaque
   famille sur sa propre ligne (jeu / identite / roue / bouton / diffusion) : ajouter une
   catégorie demandait de la nommer ici aussi, sinon elle ne s'affichait jamais — un oubli
   silencieux. Ici, tout ce qui ne porte pas la catégorie active est caché, quelle que soit sa
   valeur : une catégorie nouvelle fonctionne sans toucher à cette feuille. */
.jeu-col-reglages > div[data-cat]:not([data-cat-actif]) { display: none !important; }
/* ⚠️ ON GARDE L'ANCIEN SÉLECTEUR AUSSI, POUR NE RIEN CASSER SI UN `data-f` SUBSISTE. */
.jeu-col-reglages > div[data-f]:not([data-f-actif]) { display: none; }

/* ⚠️ LE BANDEAU RESTE COLLANT : la colonne défile, et sans ça il faudrait remonter en haut à
   chaque changement de famille — exactement ce que le client veut éviter. */
.jeu-familles-gaine { position: sticky; top: 0; z-index: 3; padding: 10px 0 6px; background: #ffffff; }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   L'ATELIER DES ÉTAPES — NUANCIER, CURSEURS, PISTE DE DÉPLACEMENT
   Client : « je pourrai déplacer, agrandir [...] je veux avoir un effet pour déplacer, agrandir
   ou dézoomer une image [...] on doit préparer une interface pour qu'ils puissent eux modifier
   le jeu comme ils veulent ».

   ⚠️ CES RÈGLES SONT CELLES DE LA DIRECTION (fond clair, validé). La tablette n'affiche PAS cet
   atelier : il vit dans l'onglet Jeu de la direction. Aucun cloisonnement nécessaire.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.eqp-couleur { display: flex; align-items: center; gap: 6px; }
.eqp-couleur input[type="color"] { flex: none; width: 42px; height: 40px; padding: 3px; border: 1px solid #e6e3de;
  border-radius: 9px; background: #ffffff; cursor: pointer; }
.eqp-couleur input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.eqp-couleur input[type="color"]::-webkit-color-swatch { border: none; border-radius: 6px; }
.eqp-hexa { flex: 1 1 90px; min-width: 0; min-height: 40px; padding: 8px 11px; border: 1px solid #e6e3de;
  border-radius: 9px; background: #ffffff; color: #17171a; font: 500 12.5px 'DM Mono', monospace; }
.eqp-hexa:focus { outline: none; border-color: #d4a574; }
.eqp-vide { flex: none; width: 40px; height: 40px; cursor: pointer; border: 1px solid #e6e3de;
  border-radius: 9px; background: #ffffff; color: #8d8983; font-size: 15px; line-height: 1; }
.eqp-vide:hover { border-color: #b32c37; color: #b32c37; }

.eqp-zoom { display: flex; flex-wrap: wrap; gap: 14px; margin: 0 0 12px; padding: 12px 13px;
  border: 1px solid #f1efec; border-radius: 10px; background: #fdfcfb; }
.eqp-z { display: flex; align-items: center; gap: 8px; flex: 1 1 190px; }
.eqp-z > span { flex: none; font: 700 9.5px 'DM Mono', monospace; letter-spacing: .09em;
  text-transform: uppercase; color: #6b6965; }
.eqp-z input[type="range"] { flex: 1 1 auto; min-width: 90px; accent-color: #d4a574; cursor: pointer; }
.eqp-z output { flex: none; min-width: 42px; text-align: right; font: 600 11.5px 'DM Mono', monospace; color: #55534f; }
.eqp-z select { min-height: 36px; padding: 6px 10px; border: 1px solid #e6e3de; border-radius: 8px;
  background: #ffffff; color: #17171a; font: 400 12.5px Manrope, sans-serif; }

/* ⚠️ LA PISTE DE DÉPLACEMENT : un carré où l'on glisse pour choisir le point d'intérêt du fond.
   `touch-action: none` est INDISPENSABLE sur tablette — sans lui, le navigateur interprète le
   glissement comme un défilement de page et le point ne bouge JAMAIS. */
.eqp-piste { position: relative; height: 92px; margin: 0 0 14px; border: 1px dashed #d8d5d0;
  border-radius: 10px; background: repeating-linear-gradient(45deg, #faf9f7 0 10px, #f5f3f0 10px 20px);
  cursor: grab; touch-action: none; overflow: hidden; }
.eqp-piste:active { cursor: grabbing; }
.eqp-piste > span { position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  font: 600 11.5px Manrope, sans-serif; color: #a8a49e; pointer-events: none; }
.eqp-piste > i { position: absolute; left: 50%; top: 50%; width: 15px; height: 15px; margin: -7.5px 0 0 -7.5px;
  border-radius: 50%; background: #d4a574; border: 2px solid #ffffff; box-shadow: 0 0 0 1px #d4a574; }

.eqp-piste + .eqp-champs { margin-top: 0; }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES WIDGETS D'ÉTAPES — LA NAVIGATION DE L'ONGLET JEU
   Client : « je veux les cinq widgets en haut comme des sortes de widgets mais c'est des choix ».

   ⚠️ CE SONT DES BOUTONS, PAS DES ONGLETS FINS : le client les appelle « widgets », et il clique
   dessus pour changer d'écran. On reprend donc la forme des pastilles du projet — une icône dans
   une pastille colorée, un libellé, et un rang — plutôt qu'une barre d'onglets classique.
   ⚠️ L'ÉTAT ACTIF EST FRANC : fond sombre, texte blanc. Un état discret serait impossible à lire
   sur un écran de tablette, et le client a déjà refusé un actif « vert au pixel » par le passé.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
/* ⚠️⚠️ ONGLETS DU HAUT — AGRANDIS (client 26/09 : « reprends et agrandis-moi les widgets ET ICONES
   des onglets pages en haut d'en haut »).
   MESURÉ AVANT : police 12,5 px · pastille 38 px · gap 7 px · padding 13/10/11 · onglet de 185 px.
   ⚠️⚠️ ET C'EST ICI QUE LA VALEUR VIT, PAS AILLEURS : le CSS ne choisit pas « la règle la plus
   précise » — À SPÉCIFICITÉ ÉGALE, LA DERNIÈRE GAGNE. Mes blocs de la soirée (placés vers l.10933)
   étaient donc MORTS, écrasés par ceux-ci qui sont plus bas dans le fichier. C'est mesuré : la
   pastille restait à 38 px et la police à 12,5 px malgré mes valeurs. On corrige donc LA SOURCE.
   ⚠️ AGRANDISSEMENT CHOISI : +25 % environ sur la police et la pastille, la géométrie suit la même
   proportion pour que le composant reste d'aplomb (rien ne devient ovale, rien ne se touche).
   ⚠️ AUCUNE COULEUR, AUCUN ÉTAT N'EST TOUCHÉ : c'est une demande de TAILLE, pas de style —
   l'identité (verre, actif rouge) reste intacte, comme le client l'a exigé. */
.jeu-cats { display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 11px; margin: 0 0 15px; }
.jeu-cat { display: flex; flex-direction: column; align-items: center; gap: 9px; padding: 16px 12px 14px;
  cursor: pointer; border: 1px solid #e6e3de; border-radius: 16px; background: #ffffff; text-align: center;
  font: 600 15px Manrope, sans-serif; color: #55534f; transition: border-color .14s ease, background .14s ease; }
.jeu-cat:hover { border-color: #d4a574; background: #fdfaf5; }
.jeu-cat.est-actif { background: #17171a; border-color: #17171a; color: #ffffff; }
/* ⚠️ LA PASTILLE ET SON GLYPHE — C'EST EUX QUE LE CLIENT APPELLE « les ICONES des onglets ».
   MESURÉ AVANT : pastille 38 px, glyphe (le SVG) 15 px. On passe à 48 px et 20 px.
   ⚠️ LE RAPPORT EST CONSERVÉ (20/48 = 0,417 contre 15/38 = 0,395) : le glyphe reste proportionné
   dans sa pastille, comme sur toutes les autres pastilles du projet (fiches 50 px, dashboard 44 px).
   ⚠️ `flex: none` EST CONSERVÉ : sans lui, la pastille se ferait écraser dans la colonne flex de
   l'onglet quand la place manque — c'est la seule ligne qui garantit qu'elle garde sa taille. */
.jeu-cat-ic { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 14px; flex: none;
  background: #f1efec; color: #55534f; font: 700 18px 'DM Mono', monospace; }
.jeu-cat.est-actif .jeu-cat-ic { background: #d4a574; color: #17171a; }
/* ⚠️⚠️ LA TAILLE DU GLYPHE EST FIXÉE ICI, JUSTE APRÈS SA PASTILLE — ET C'EST INDISPENSABLE :
   le SVG porte `width="22" height="22"` EN ATTRIBUTS dans le HTML (app.js). Ce sont des valeurs de
   PRÉSENTATION : n'importe quelle règle CSS les écrase, mais si on n'en écrit AUCUNE, c'est le 22 qui
   gagne. Les garder côte à côte évite le défaut classique : une pastille agrandie avec un glyphe
   resté petit (ou l'inverse), selon l'ordre du fichier.
   ⚠️ 20 px dans une pastille de 48 px : rapport 0,417, au-dessus de l'origine (15/38 = 0,395). Le
   glyphe est donc franchement plus grand, sans jamais toucher les bords de sa pastille.
   ⚠️ C'EST LUI QUE LE CLIENT APPELLE « les ICONES des onglets » : c'est la demande à honorer. */
.jeu-cat-ic svg { width: 20px; height: 20px; }
/* ⚠️ LE RANG SUIT L'AGRANDISSEMENT (9 -> 11 px). Rappel : il est MASQUÉ dans le cockpit
   (`#jeu-cats .jeu-cat-n { display: none }`) — ces valeurs ne servent donc que sur la tablette et
   dans les autres pages qui réutilisent le composant. On les tient cohérentes quand même : un
   composant ne doit pas avoir deux jeux de valeurs selon la page, sinon il divergera. */
.jeu-cat-n { font: 700 11px 'DM Mono', monospace; letter-spacing: .1em; opacity: .55; }
.jeu-cat.est-actif .jeu-cat-n { opacity: .8; }
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LE SHADER D'IDENTITE DERRIERE LES ONGLETS — ET LE BLANC S'EN VA
   Client : « l'effet liquide derriere les onglets, mieux que le blanc. Le blanc, il est trop fort
   en haut des onglets. » puis, sur ma premiere tentative : « ne fais pas du vert, reviens a notre
   identite [...] la couleur vert, je la voulais en mode liquide, vert transparent ».

   ⚠️⚠️ CE QUE J'AI MAL COMPRIS, ET QUI EST ECRIT ICI POUR NE PAS LE REFAIRE : « effet vert » ne
   voulait PAS dire « une palette verte a creer ». J'avais ecrit des teintes vertes de toutes
   pieces — ce n'est pas l'identite du projet, et le client l'a vu immediatement.
   L'IDENTITE, C'EST UN SHADER : celui qui est deja sur la barre laterale (direction.html l.428 :
   « rajoute le shader qu'on a comme identite en transparence sur la barre »).
   ⚠️ REGLE A RETENIR : on ne FABRIQUE JAMAIS une teinte pour l'interface. Un vert demande se pose
   en TRANSPARENCE sur l'identite ; il ne la remplace pas.

   ⚠️⚠️ LA GAINE PORTAIT UN background BLANC PLEIN — c'est LUI la cause du « le blanc est trop
   fort » : un aplat opaque en haut de la zone, que rien ne pouvait adoucir. Et comme la gaine est
   en position: sticky, son fond doit tenir TOUT SEUL a toutes les positions de defilement : le
   fond sombre de l'identite, celui sur lequel le shader a ete valide.

   ⚠️ LES REGLAGES SONT CEUX DE .rail-shader, REPRIS TELS QUELS (styles.css l.4040, mesures) :
      opacite .12  ·  mix-blend-mode: screen  ·  brightness(2.4) saturate(1.2)
   C'est ce qui fait que l'effet est CELUI DE L'IDENTITE et non une variante approximative. */
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES ONGLETS EN « LIQUIDE VERRE TRANSPARENT »
   Client : « je parlais des onglets, en mode liquide verre transparent ».

   ⚠️⚠️ CE N'EST PAS UNE COULEUR, C'EST UNE MATIÈRE — et c'est ce que j'avais mal compris en
   fabriquant une palette verte. Sa demande se décompose en trois :
      · VERRE         l'onglet laisse voir ce qu'il y a DERRIÈRE lui (translucide + flou) ;
      · TRANSPARENT    aucun aplat, aucun blanc plein, aucune teinte opaque ;
      · LIQUIDE        le shader d'identité du projet anime ce fond, en mouvement.
   ⚠️ Et le « vert » qu'il mentionnait appartient à la CHARTE DE CAMPAGNE (--oz-c1, Heineken) : il
   voyage avec la campagne, ce n'est pas une couleur de l'application. On ne le code donc PAS en
   dur — c'est la leçon que le client a lui-même énoncée : « reviens à notre identité ».

   ⚠️⚠️ LE FOND DE LA GAINE RESTE, ET IL EST INDISPENSABLE : la gaine est en position: sticky, donc
   elle passe PAR-DESSUS les réglages qui défilent dessous. Sans fond, le TEXTE des réglages se
   lirait au travers des onglets — illisible, et c'est le défaut « du noir sur du noir » que ce
   projet combat. Ce fond n'est donc PAS un aplat de couleur : c'est un VOILE SOMBRE TRÈS
   TRANSPARENT qui laisse deviner ce qui passe dessous. C'est exactement le « verre » demandé.

   ⚠️ ET LE SHADER D'IDENTITÉ RESTE DERRIÈRE LUI — c'est LUI, le « liquide ». Ses réglages sont
   ceux de la barre latérale (styles.css l.4040, mesurés sur le projet) : opacité .12,
   mix-blend-mode: screen, brightness(2.4) saturate(1.2). On ne les réinvente pas. */
.jeu-cats-gaine {
  /* ⚠️⚠️ PLUS DE `position: sticky` — ET C'EST LA DEMANDE EXPLICITE DU CLIENT : « ne les épingle pas
     parce qu'on a besoin de cet espace pour voir la tablette ».
     MESURE : épinglée, la barre reste collée en haut quand on défile et n'est JAMAIS rangée — les
     ~120 px qu'elle occupe sont donc pris à la tablette en permanence.
     ⚠️ POURQUOI LE `sticky` AVAIT ÉTÉ POSÉ, ET POURQUOI IL NE SERT PLUS : les onglets étaient
     ENFERMÉS dans la colonne de gauche (`.jeu-col-reglages`), qui défile longuement — il fallait
     les garder à portée. Depuis qu'ils sont sortis en PLEINE LARGEUR et posés AU-DESSUS des
     réglages, ils sont visibles dès l'arrivée sur l'onglet. Le `sticky` ne servait donc plus
     qu'à VOLER de la place. ⚠️ On l'écrit ici pour que la prochaine lecture ne le remette pas
     « par prudence » : sa raison d'être a disparu avec l'ancienne mise en page. */
  position: relative;
  z-index: 3;
  padding: 6px;                      /* 8 -> 6 : de l'espace rendu, lui aussi */
  /* ⚠️⚠️ AUCUN FOND ICI, ET C'EST VOLONTAIRE : l'apparence de la gaine est décidée à UN SEUL
     endroit — le bloc « CADRES EN VERRE LÉGER » de `direction.html`, validé par le client le 26/09
     (voile à 5 %, coins arrondis, ombre douce, blur 14 px). En poser un second ici créerait deux
     sources de vérité, et c'est la dernière lue qui gagnerait — sans que personne ne l'ait voulu.
     ⚠️ CE QUE J'AI FAIT DE MAL : j'avais mis un voile SOMBRE ici, et le sélecteur du verre validé
     (`.jeu-col-reglages .jeu-cats-gaine`) ne s'appliquait plus depuis que la gaine avait quitté
     cette colonne. Le client l'a vu tout de suite : « tu les as mis en noir ». */
  overflow: hidden;                 /* le shader reste DANS sa boîte, jamais sur la page */
}
/* ⚠️⚠️ LE CANVAS : LA MÊME MATIÈRE QUE LA BARRE LATÉRALE, MÊMES RÉGLAGES. C'est tout l'intérêt —
   l'effet doit être celui qu'on connaît déjà, pas un effet voisin.
   ⚠️ mix-blend-mode: screen SUR UN VOILE SOMBRE : le bruit ÉCLAIRCIT ce qu'il touche au lieu de le
   recouvrir. C'est ce qui donne le « liquide » plutôt qu'une image collée. */
/* ⚠️⚠️ LE STYLE DU CANVAS EST NEUTRALISÉ AVEC LE CANVAS LUI-MÊME — le canvas `#jeu-cats-shader`
   a été retiré du DOM : il portait le shader d'identité, qui est ROUGE (mesuré :
   `_SHADER_FS_CLAIR` fait `mix(color, redLayer, wave * 0.95 + glowSpot * 0.05)`, 95 % de rouge).
   Le client ne l'a jamais demandé — il voulait le VERRE TRANSPARENT, qui est déjà là et
   n'a besoin d'aucun canvas (`backdrop-filter` + voile à 5 %, bloc « CADRES EN VERRE LÉGER »
   de `direction.html`). Le bloc est laissé ici en commentaire plutôt que supprimé : la trace
   explique pourquoi, et un futur lecteur ne le remettra pas par mégarde. */
/* .jeu-cats-canvas { */
/*   position: absolute; inset: 0; z-index: 1; */
/*   width: 100%; height: 100%; */
/*   display: block; pointer-events: none; */
/*   /* ⚠️⚠️ ON REVIENT À LA COMPOSITION DE L'IDENTITÉ : `screen`, comme la barre latérale. */
/*      Le `multiply` que j'avais posé etait une THEORIE (la variante claire se composerait en */
/*      multiply), alors que le projet a deja une mesure : sur fond creme il utilise la palette */
/*      CLAIRE et rien d'autre — il ne change PAS la composition. Deux variables pour un seul */
/*      probleme, c'est une occasion de se tromper de plus. */
/*      ⚠️ ET SURTOUT : le canvas peignait du NOIR parce que la VIGNETTE l'eteignait (une bande recue */
/*      1.0 alors qu'il lui faut 0.0). Ce n'etait PAS un probleme de composition, et `multiply` ne */
/*      pouvait que rendre ce noir definitif. Le noir est corrige a la source (app.js). */ */
/*   opacity: .28; */
/*   mix-blend-mode: screen; */
/*   filter: saturate(1.2); */
/* } */
/* ⚠️⚠️ LES ONGLETS PASSENT AU-DESSUS, ET C'EST INDISPENSABLE : sans cette ligne, le canvas
   (z-index 1) couvrirait la grille et les libellés disparaîtraient. */
.jeu-cats-gaine > .jeu-cats { position: relative; z-index: 2; }
/* ⚠️⚠️ LES ONGLETS EUX-MÊMES : LE VERRE. Ils étaient sur fond BLANC plein — ce qui remettait le
   blanc qu'on venait de retirer. On les passe en verre CLAIR translucide, avec un liseré qui
   dessine le bord sans l'imposer. Le fond de la gaine se voit À TRAVERS chaque onglet.
   ⚠️ L'ÉTAT ACTIF N'EST PAS TOUCHÉ (fond sombre, texte blanc) : le client l'a validé, et c'est lui
   qui dit « où je suis ». On ne règle que le REPOS. */
.jeu-cats-gaine .jeu-cat {
  /* ⚠️⚠️ LE WIDGET EST EN VERRE, PAS EN APLAT. Demande explicite du client : « je veux que les
     widgets soient en mode verre liquide transparent ». La recette du projet disait « les widgets
     restent blancs » — il demande l'inverse aujourd'hui, et c'est lui qui décide.
     ⚠️ ON NE MET AUCUN FOND OPAQUE : un voile très léger (6 %) + un flou, et le shader d'identité
     se voit AU TRAVERS. C'est ça, « liquide » : la matière du fond traverse le widget.
     ⚠️ LE TEXTE RESTE SOMBRE : sur un verre clair, une encre claire serait illisible — et « jamais
     du noir sur du noir » vaut dans les deux sens (jamais du clair sur du clair). */
  background: rgba(255,255,255,.06);
  border-color: rgba(255,255,255,.42);
  color: #383633;
  backdrop-filter: blur(10px) saturate(1.1);
  -webkit-backdrop-filter: blur(10px) saturate(1.1);
}
.jeu-cats-gaine .jeu-cat:hover {
  background: rgba(255,255,255,.16);
  border-color: rgba(255,255,255,.60);
}
/* ⚠️⚠️ L'ONGLET SÉLECTIONNÉ EST EN ROUGE (client : « quand on est sur un onglet il doit être en
   rouge »).
   ⚠️ CETTE RÈGLE EST ÉCRITE *APRÈS* '.jeu-cats-gaine .jeu-cat', ET C'EST INDISPENSABLE : les deux
   ont la MÊME spécificité (0,2,0), donc c'est le DERNIER lu qui gagne. Ma règle de verre avait
   écrasé l'état actif exactement pour cette raison — l'onglet sélectionné et l'onglet au repos
   étaient devenus IDENTIQUES (mesuré par ma propre sonde).
   ⚠️ LE ROUGE EST MESURÉ, PAS CHOISI A L'ŒIL : blanc sur #D42B38 = 5,00:1, au-dessus du seuil de
   4,5:1. Le rouge vif de la charte (#e63946) donne 4,17:1 sur fond clair — le projet l'a DÉJÀ
   écarté pour cette raison précise (« il ne donne que 4,17:1 avec du blanc »). On reprend donc SON
   rouge conforme : l'intention « rouge » est honorée, la lisibilité est garantie. */
.jeu-cats-gaine .jeu-cat.est-actif {
  background: #D42B38;
  border-color: #D42B38;
  color: #ffffff;
}
/* ⚠️ LA PASTILLE DE L'ICÔNE SUIT LE MÊME PATRON QUE L'EXISTANT : le projet posait une pastille
   COLORÉE avec un contenu à fort contraste. On garde la logique — pastille blanche, icône rouge.
   Sur un onglet rouge, c'est le repère le plus franc possible. */
.jeu-cats-gaine .jeu-cat.est-actif .jeu-cat-ic {
  background: #ffffff;
  color: #D42B38;
}
/* ⚠️ L'ICÔNE AU REPOS : elle portait un fond gris clair (#f1efec) pensé pour du blanc. Sur le
   verre sombre elle faisait une pastille sale. On la passe en verre, comme l'onglet qui la porte. */
.jeu-cats-gaine .jeu-cat-ic {
  /* ⚠️ La pastille de l'icône : verre sur verre. Elle portait `#f1efec` (un gris clair pensé pour un
     onglet blanc) ; sur un onglet translucide, elle faisait une tache. Elle devient du verre elle
     aussi. ⚠️ L'ICÔNE DE L'ONGLET ACTIF GARDE SON DORÉ : c'est le repère validé, on n'y touche pas. */
  background: rgba(255,255,255,.34);
  color: #2a2826;
}
/* ⚠️ ET L'ICÔNE DE L'ONGLET ACTIF GARDE SON DORÉ : c'est le repère validé, on n'y touche pas. */

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LE TEXTE PASSE SOUS LA RÉPLIQUE — POUR QU'ELLE SOIT JUSTE SOUS LA TABLETTE
   Client : « Goodies de la personne assignée / — Heineken ça marche j'aime bien juste enlève moi
   ce texte comme ça il se rend juste en dessous de la tablette ».

   ⚠️⚠️ ON NE SUPPRIME RIEN DU DOM : ces deux lignes portent une information que le client APPRÉCIE
   (« ça marche j'aime bien »). Il ne demande pas de les retirer — il demande qu'elles ne
   SÉPARENT PLUS la tablette de ses onglets. On les DÉPLACE donc SOUS la réplique, avec `order`.

   ⚠️ POURQUOI EN CSS ET NON EN JS : la colonne est désormais `display: flex`, donc `order` suffit.
   Aucun HTML à modifier, aucun `jeuMajApercu` à toucher, aucune chance de casser la mise à jour de
   ces textes quand on change de roue. Moins de surface, donc moins de casse possible.
   ⚠️ L'ORDRE FINAL, ET IL EST CELUI DEMANDÉ : titre · tablette · Réplique · origine · nom.
   ⚠️ On écrit AUSSI l'ordre des autres, même s'il est celui du DOM par défaut : un `order` posé
   partiellement rend la lecture ambiguë (« et les autres, ils sont où ? »). On fige les cinq. */
.jeu-col-preview > .jeu-col-titre      { order: 1; }
.jeu-col-preview > #jeu-apercu-zone    { order: 2; }
.jeu-col-preview > .jeu-cat-mini       { order: 3; }   /* JUSTE SOUS LA TABLETTE */
.jeu-col-preview > #jeu-apercu-origine { order: 4; }
.jeu-col-preview > .jeu-col-nom        { order: 5; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ LE MOTEUR DES `order` CI-DESSUS — SANS CECI, LES CINQ RÈGLES NE FONT STRICTEMENT RIEN
   `order` n'a AUCUN effet sur un conteneur qui n'est ni `flex` ni `grid`. La colonne était un
   simple bloc : les cinq règles d'ordre auraient été inertes, le texte serait resté ENTRE la
   tablette et ses onglets, et la demande du client n'aurait pas été honorée.
   ⚠️ C'est le défaut le plus coûteux qui soit : le CSS est bien « appliqué », aucun contrôle de
   chaîne ne le voit, et seule une MESURE À L'ÉCRAN le révèle. On mesure donc, on ne grep pas.

   ⚠️⚠️ ET C'EST AUSSI LA CORRECTION DU DÉFAUT LE PLUS GRAVE DE CETTE FONCTIONNALITÉ. MESURÉ :
        fenêtre  800 px -> la réplique était à y=939 -> HORS ÉCRAN
        fenêtre  900 px -> la réplique était à y=939 -> HORS ÉCRAN
        fenêtre 1150 px -> la réplique était à y=939 -> visible
   `y=939` CONSTANT QUELLE QUE SOIT LA FENÊTRE : la colonne gardait sa hauteur NATURELLE (974 px,
   imposée par la tablette de 760 px) et la réplique en est le dernier élément. Sur tout écran de
   moins de ~1000 px, la réplique était donc invisible — et donc INUTILE, puisque tout son intérêt
   est justement d'éviter de remonter en haut pour changer de page.

   ⚠️ LA RÉPARTITION, ET POURQUOI CELLE-LÀ :
      · la COLONNE  : bornée par la fenêtre — elle ne peut plus dépasser l'écran ;
      · la TABLETTE : prend la place QUI RESTE et se réduit la première. C'est la SEULE chose
        compressible : le titre, le nom de roue et la réplique ont des tailles fixes et doivent
        rester lisibles ;
      · la RÉPLIQUE : garde SA taille, donc se retrouve TOUJOURS au bas de la colonne, donc
        TOUJOURS visible. C'est le but de toute l'opération.
   ⚠️ `min-height: 0` SUR LA ZONE EST INDISPENSABLE : sans lui, un enfant de flex REFUSE de descendre
   sous la hauteur de son contenu (`min-height: auto` par défaut) et la colonne déborderait malgré le
   `max-height`. C'est LE piège du flex, et il est totalement muet. */
.jeu-col-preview {
  display: flex !important;
  flex-direction: column;
  /* ⚠️⚠️ AUCUN `max-height` ICI : il COUPAIT la tablette (mesuré : 160 px rognés sur une fenêtre de
     900 px). `apercuAjusterEchelle()` fait déjà ce travail correctement, en réduisant l'ÉCHELLE de
     la tablette au lieu de la trancher. Deux mécanismes pour la même chose : on garde celui qui
     respecte l'appareil, on retire l'autre.
     ⚠️ `display: flex` EST CONSERVÉ : il porte les `order` qui placent la réplique sous la
     tablette. Sans lui les cinq règles d'ordre seraient inertes. */
}
/* ⚠️ LA TABLETTE SE RÉDUIT EN GARDANT SON RAPPORT (768×1024) : l'aperçu continue de montrer la
   VRAIE mise en page, simplement plus petite. On ne ment jamais sur le contenu — seulement sur
   l'échelle, ce qui est déjà le rôle de `--apercu-echelle`. */
.jeu-col-preview > #jeu-apercu-zone {
  flex: 1 1 auto;
  min-height: 0;
}
/* ⚠️ ET LA RÉPLIQUE N'EST PAS COMPRESSIBLE : `flex: 0 0 auto`. C'est ELLE qu'on veut voir — ce
   n'est pas elle qui doit céder la place, c'est la tablette qui s'adapte. */
/* ⚠️⚠️ LA RÉPLIQUE S'ÉPINGLE ELLE-MÊME AU BAS DE LA FENÊTRE — ET C'EST LA SEULE SOLUTION QUI
   NE PEUT PAS RATER. Client : « on va juste épingler la tablette PUIS SES ONGLETS en bas d'elle ».

   ⚠️⚠️ POURQUOI J'ABANDONNE LE CALCUL D'ÉCHELLE. J'ai essayé DEUX FOIS de faire tenir la réplique
   en réduisant la tablette pour lui laisser la place. MESURÉ, les deux fois :
       fenêtre 900 px : la réplique à y=862, bas=918  ->  18 px SOUS le bord
       fenêtre 800 px : la réplique à y=762, bas=817  ->  encore sous le bord
   La formule doit PRÉDIRE la hauteur d'un rendu qui ne se stabilise qu'après. Chaque correction
   laisse un résidu de quelques pixels ; j'ai corrigé le résidu, il s'est déplacé. Trois passes de
   calcul valent moins qu'une contrainte que le navigateur tient lui-même.

   ⚠️ CE QUE FAIT `position: sticky; bottom: 12px` : tant que la colonne est à l'écran, la barre
   reste à 12 px du BAS DE LA FENÊTRE ; dès que la colonne sort par le bas, elle repart avec elle
   (elle ne quitte jamais son parent). Elle est donc TOUJOURS visible quand la tablette l'est —
   par construction, pas par calcul.
   ⚠️ AUCUNE FORMULE, AUCUNE MESURE, AUCUN ARRONDI : le moteur de rendu tient la contrainte à chaque
   image, quelle que soit la police, la langue, la taille du libellé ou la machine. C'est
   exactement l'inverse d'une estimation — et c'est pour ça que ça ne peut pas rater.

   ⚠️ `flex: 0 0 auto` EST CONSERVÉ : elle garde sa hauteur, ce n'est pas elle qui cède la place.
   ⚠️ `z-index: 5` : collée, elle se superpose au bas de la tablette. Sans lui elle passerait
   DERRIÈRE le cadre de l'appareil et deviendrait invisible — le défaut qu'on corrige.
   ⚠️ `background` OPAQUE DERRIÈRE LE VERRE : un voile à 5 % laisserait lire la tablette au travers
   une fois superposée. On garde le verre (blur + voile) MAIS sur un fond de page plein, sinon la
   barre serait illisible exactement là où elle sert.
   ⚠️ ET ELLE RESTE LA MÊME BARRE : mêmes icônes, mêmes libellés, même rouge actif. On ne change
   que SA POSITION — c'est la demande du client, rien de plus. */
.jeu-col-preview > .jeu-cat-mini {
  flex: 0 0 auto;
  position: sticky;
  bottom: 12px;
  z-index: 5;
  background-color: #f7f7f5;
  box-shadow: 0 10px 30px rgba(23,23,25,.10);
}
/* ⚠️ SOUS 1120 px LA COLONNE S'EMPILE : la contrainte de hauteur s'en va, sinon elle briderait la
   tablette sans raison alors qu'elle n'est plus à côté des réglages. */
@media (max-width: 1120px) {
  .jeu-col-preview { max-height: none; }
}
/* ⚠️ ET ON RETIRE LA MARGE HAUTE DE LA RÉPLIQUE : elle était là pour la séparer du texte qui la
   précédait. Maintenant qu'elle est collée à la tablette, cette marge CREUSERAIT un vide entre
   l'appareil et ses onglets — exactement ce qu'on cherche à supprimer. */
.jeu-col-preview > .jeu-cat-mini { margin-top: 8px; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LA RÉPLIQUE DES ONGLETS, SOUS LA TABLETTE — VERSION PLUS PETITE DU MÊME JEU
   Client : « replique les mêmes mais en version plus petite en dessous de la tablette [...] on va
   juste épingler la tablette puis ses onglets en bas d'elle ».

   ⚠️⚠️ ELLE VISE `.jeu-cat-mini .jeu-cat`, ET JAMAIS `.jeu-cat` SEUL : c'est ce qui garantit que
   les onglets du HAUT ne sont pas touchés par ces règles. Le client a été explicite (« garde les
   icônes du widget des onglets en haut comme ils sont »), et un sélecteur de famille le ferait
   respecter sans dépendre d'un `:not()` fragile ni de l'ordre des feuilles.

   ⚠️ ELLE EST EN `grid` À SIX COLONNES ÉGALES, COMME CELLE DU HAUT : deux dispositions différentes
   pour le même contenu seraient deux choses à maintenir, et une occasion de diverger.
   ⚠️ MÊME VERRE, MÊMES COULEURS, MÊME ÉTAT ACTIF (rouge) : c'est le MÊME jeu d'onglets, en plus
   petit. On ne crée aucune apparence nouvelle.
   ⚠️ ELLE EST `sticky` ELLE AUSSI, avec un `top` PLUS BAS que la tablette : sans ça, elle
   glisserait sous l'appareil en défilant et deviendrait invisible — or c'est exactement ce qu'on
   cherche à éviter. `calc(11rem + 24px)` ≈ la hauteur de la tablette réduite + sa marge. */
.jeu-cat-mini {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 4px;
  margin-top: 10px;
  padding: 5px;
  border-radius: 14px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.5);
  box-shadow: 0 6px 18px rgba(23,23,25,.05);
}
/* ⚠️ LES ONGLETS DE LA RÉPLIQUE : plus petits encore que ceux du haut, en proportions conservées.
   ⚠️ LE LIBELLÉ RESTE À 9 px MINIMUM — la règle « rien ne doit être illisible » vaut ici comme
   ailleurs, et c'est le seul élément qui soit de la LECTURE (le reste est un repère visuel). */
.jeu-cat-mini .jeu-cat {
  gap: 2.6px;
  padding: 5.2px 4px 4.4px;
  border-radius: 5.2px;
  font-size: 9px;
  background: rgba(255,255,255,.06);
  border-color: rgba(255,255,255,.42);
  color: #383633;
  backdrop-filter: blur(10px) saturate(1.1);
  -webkit-backdrop-filter: blur(10px) saturate(1.1);
}
.jeu-cat-mini .jeu-cat:hover {
  background: rgba(255,255,255,.16);
  border-color: rgba(255,255,255,.60);
}
/* ⚠️ L'ÉTAT ACTIF EST LE MÊME ROUGE QUE CELUI DU HAUT (#D42B38, blanc 5,00:1) : c'est le même jeu
   d'onglets, donc le même repère. Deux rouges différents auraient fait douter de qui est actif. */
.jeu-cat-mini .jeu-cat.est-actif {
  background: #D42B38;
  border-color: #D42B38;
  color: #ffffff;
}
.jeu-cat-mini .jeu-cat.est-actif .jeu-cat-ic {
  background: #ffffff;
  color: #D42B38;
}
.jeu-cat-mini .jeu-cat-ic {
  width: 16px; height: 16px;
  border-radius: 4.7px;
  background: rgba(255,255,255,.34);
  color: #2a2826;
}
/* ⚠️ 9 px = 16 × 0,55, le rapport d'origine conservé (11/20 du haut, lui-même 15/27). */
.jeu-cat-mini .jeu-cat-ic svg { width: 9px; height: 9px; }
/* ⚠️ ET SOUS 1120 px, LA COLONNE D'APERÇU S'EMPILE : la réplique suit, comme le reste. */
@media (max-width: 1120px) {
  .jeu-cat-mini { position: static; }
}


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LA DIFFUSION — SA SECTION À PART, AVEC L'APERÇU DE LA ROUE À CÔTÉ
   Client : « tu gardes assigné dans un onglet tout seul. Tout en montrant le preview de notre
   roue, comme ça l'agence ne se trompe pas en assignant. »

   ⚠️ DEUX COLONNES, ET C'EST TOUT L'INTÉRÊT : l'assignation à gauche, la roue à droite. Assigner
   la mauvaise roue ne se voit QUE à l'écran — jamais dans une liste de noms. Les mettre côte à
   côte est ce qui rend l'erreur visible AVANT le clic.
   ⚠️ ON PASSE EN UNE COLONNE SOUS 1 000 px : sur un écran étroit, deux colonnes écraseraient
   l'aperçu de la roue jusqu'à l'illisible, et l'aperçu perdrait sa raison d'être.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.jeu-diffusion-seule { margin-top: 18px; padding-top: 16px; border-top: 1px solid #e6e3de; }
/* ⚠️⚠️ LES GOODIES SANS CAMPAGNE — SIGNALÉS, PAS CACHÉS.
   Un goodie sans campagne ne peut pas être tiré : il n'entre dans aucun tirage, et l'aperçu en
   montre donc moins que ce que l'agence a préparé. Le client l'a vu (« tu mets deux goodies au
   lieu des quatre qu'on a faits »). ⚠️ On ne DÉCIDE pas à sa place : on montre lesquels sont
   orphelins, et il les rattache. Un avertissement se lit ; un goodie silencieusement absent, non. */
.jeu-goodies-orphelins {
  display: flex; flex-direction: column; gap: 3px;
  margin: 10px 0 0; padding: 11px 13px;
  border-radius: 11px;
  border: 1px solid rgba(214, 158, 46, .34);
  background: rgba(214, 158, 46, .08);
}
.jeu-goodies-orphelins b { font: 700 12.5px Manrope, sans-serif; color: #8a5a2b; }
.jeu-goodies-orphelins span { font-size: 12px; color: #55585c; }
.jeu-goodies-orphelins small { font-size: 11px; color: #6b6e73; line-height: 1.45; }

/* ⚠️ LE REPÈRE DE LA DIFFUSION : il n'y a PLUS d'aperçu ici (identifiant en double), donc on
   écrit où il se trouve. Un vide sans explication ferait chercher une panne qui n'existe pas. */
.jeu-diff-note {
  margin: 0;
  padding: 28px 22px;
  border: 1px dashed rgba(230, 57, 70, .30);
  border-radius: 14px;
  color: #6b6f76;
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
}
.jeu-diff-apercu {
  max-width: 520px; margin-top: 14px; }
.jeu-diff-apercu .jeu-col-preview { position: static; width: 100%; max-width: none; }
@media (min-width: 1000px) {
  .jeu-diffusion-seule { display: grid; grid-template-columns: 1fr minmax(280px, 360px); gap: 18px; align-items: start; }
  .jeu-diffusion-seule > section.card { grid-column: 1; }
  .jeu-diff-apercu { grid-column: 2; margin-top: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   L'APERÇU DE L'AGENCE N'EST JAMAIS « UNE IMAGE NOIRE »
   Client : « j'ai une image noire ».

   ⚠️⚠️ CE QUE LA MESURE A ÉTABLI : deux règles se rencontraient.
     · le flou d'entrée du jeu applique blur(22px) et brightness(.86) — le flou de SAISIE, qui est
       voulu dans le vrai parcours, mais que l'aperçu ne retire QUE sur l'étape de la roue.
       En arrivant sur les coordonnées, l'aperçu gardait donc un décor flouté ET assombri.
     · le voile d'étape (::after) était posé en absolu dans ce conteneur fixe, et héritait du
       fond du décor : sur un décor flouté, cela donne un APLAT SOMBRE.
   Le résultat est exactement ce que le client voit : du noir.

   ⚠️ LA CORRECTION EST CLOSIONNÉE À L'APERÇU (`html[data-apercu]`), JAMAIS AU VRAI JEU : le flou
   de saisie du parcours réel est une intention du client (« le décor doit se deviner, pas
   distraire du formulaire ») et on n'y touche pas.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
html[data-apercu] #jeu-fond,
html[data-apercu] #jeu-fond.est-flou {
  filter: none !important;
}

/* ⚠️ LE VOILE D'ÉTAPE NE DOIT PAS REDESSINER L'IMAGE : un voile assombrit, il ne remplace pas le
   fond. L'héritage du `background` est ce qui produisait l'aplat sombre. On le neutralise ICI, une
   fois, pour toutes les étapes. */
html[data-apercu] #jeu-fond::after {
  background: none !important;
}

/* ⚠️ ET LE VOILE RESTE DANS LE CADRE : sans `overflow: hidden`, un élément positionné en absolu
   peut dépasser son conteneur et couvrir la page entière — c'est le défaut déjà payé ici
   (« le voile couvrirait la barre d'onglets et les boutons »). */
html[data-apercu] #jeu-fond {
  overflow: hidden;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   L'EFFET GLASS SUR LES CARTES DU DASHBOARD — RÉGLÉ PAR LA TRANSPARENCE

   Client : « Essaye d'arranger cet effet glace par transparence. » · « on l'appellera l'effet
   glass » · « verre transparent · la matière » · « pas la couleur ».
   ⚠️⚠️ « VERT » = « VERRE » (homophones, il DICTE). C'est la MATIÈRE, pas une teinte.

   ═══⚠️⚠️ CE QUE MA PREMIÈRE VERSION AVAIT CASSÉ, ET LA MESURE LE DIT ═══════════════════════
        border : 1px solid rgba(255,255,255,.55)     <- BLANC sur un fond CLAIR = INVISIBLE
        shadow : 0 14px 40px rgba(23,23,25,.08)      <- 40 px de portée, mord sur les voisines
   Les cartes n'avaient donc PLUS AUCUN contour : elles se fondaient l'une dans l'autre, et
   l'œil lit ça comme un CHEVAUCHEMENT. C'est exactement ce que le client voyait.
   ⚠️ Ma sonde comptait « 0 paire qui se chevauche » — les rectangles ne se croisent pas. Mais un
   contrôle GÉOMÉTRIQUE ne mesure pas la LISIBILITÉ. C'est la leçon à garder de ce bloc.
   ⚠️ ET LES VALEURS DE DÉPART ÉTAIENT BONNES : `border: #e6e3de` (un gris visible) et
   `box-shadow: 0 1px 2px + 0 6px 16px`. Je les avais remplacées sans raison. **On ne remplace pas
   une matière par une autre : on change JUSTE le fond, et on garde le contour et l'ombre qui
   faisaient le travail.**

   ═══ LES TROIS PIÈCES, ET POURQUOI CHACUNE EST NÉCESSAIRE ═════════════════════════════════
   ① LE VOILE : `rgba(255,255,255,.55)` — plus dense que les 5 % d'avant.
      Client : « arranger cet effet glace PAR TRANSPARENCE ». À 5 % sur fond clair, la carte
      n'existe plus ; à 55 %, elle est là ET on voit le fond de page au travers, flouté.
      Le `backdrop-filter` reste : c'est LUI le verre. On ne change que la DENSITÉ du voile.
   ② LE CONTOUR : `rgba(23,23,25,.10)` — le liseré standard du projet, déjà celui de `.dash-bloc`
      d'origine. Sur fond clair, c'est la SEULE chose qui dessine une carte.
   ③ L'OMBRE COURTE : `0 1px 2px` + `0 6px 16px`. Celle d'avant, qui « décolle » sans mordre sur
      la voisine. Les 40 px de portée étaient la cause du « chevauchement » perçu.

   ⚠️ LE BLOC EST EN FIN DE FEUILLE, AVEC LES SÉLECTEURS EXACTS : les cartes portent déjà
   `background: #fff` à sept endroits, et `.dash-bloc.est-sombre` a une spécificité DOUBLE (0,2,0)
   qui battrait une simple classe. Une valeur, un endroit.
   ⚠️ LES MINI-WIDGETS (`.dash-mw`) NE SONT PAS DANS CETTE LISTE : le client a demandé « des jauges
   et des mini widgets avec fond clair pour qu'on puisse voir ». Le verre va sur les CARTES, pas
   sur ce qu'elles contiennent.
   ⚠️ LES TEXTES NE SONT PAS TOUCHÉS : sur un voile clair au-dessus du fond clair de la page,
   l'encre anthracite (`#1d1e21`, `#383633`) garde son contraste. */
.dash-bloc,
.dash-bloc.est-sombre,
.dash-kpi,
.dash-lieu-bloc,
.dash-fige,
.dash-phare-gauche,
.dash-phare-chiffres {
  /* ⚠️ 34 % : le client a demandé PLUS de transparence pour que la matière se voie
     (« encore plus transparent pour qu'il donne cet effet de glace verre, la matière »).
     ⚠⚠ ON NE DESCEND PAS PLUS BAS : à 5 % (première tentative de la soirée) la carte n'existait
     plus du tout et le client a vu des « chevauchements ». C'est le CONTOUR (voir ci-dessous) et
     l'ombre courte qui dessinent la carte désormais — le fond peut donc s'alléger sans risque. */
  background: rgba(255,255,255,.34) !important;
  border: 1px solid rgba(23,23,25,.10) !important;
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  backdrop-filter: blur(14px) saturate(1.1);
  box-shadow: 0 1px 2px rgba(23,23,25,.05), 0 6px 16px rgba(23,23,25,.045) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES PETITS ONGLETS DU DASHBOARD — PLUS BLANCS, POUR QU'ON LES VOIE
   Client : « Les anglais les plus petits peuvent aller encore plus foncé vers le blanc pour
   qu'on puisse les voir. »
   ⚠️ « foncé vers le blanc » = PLUS BLANC, pas plus sombre : c'est la lisibilité qui manque, pas
   le contraste. Ils étaient déjà en `#fff`, mais ils flottaient sur une carte devenue transparente
   sans contour propre — donc ils ne se lisaient plus comme des boutons.
   ⚠️ ON LEUR REND DONC LES TROIS CHOSES QUI FONT UN BOUTON : un blanc FRANC, un liseré MARQUÉ, et
   un léger relief qui les décolle de la carte. Sur une carte en verre, c'est ce qui les fait
   exister.
   ⚠️ L'ONGLET ACTIF RESTE ROUGE (`#D42B38`, blanc 5,00:1 — le repère validé par le client). */
.dash-mesure-b {
  background: #ffffff !important;
  border: 1px solid rgba(23,23,25,.16) !important;
  color: #26282c !important;
  box-shadow: 0 1px 2px rgba(23,23,25,.06);
}
.dash-mesure-b:hover {
  border-color: rgba(23,23,25,.30) !important;
}
/* ⚠️ L'ACTIF EST ÉCRIT APRÈS, ET IL PORTE `!important` COMME LES AUTRES : sans le `!important`,
   l'ordre déciderait et le rouge pourrait passer dessous. MESURÉ : c'est le défaut qui a coûté
   deux passes sur les onglets du jeu — à spécificité égale, c'est la DERNIÈRE qui gagne. */
.dash-mesure-b.on {
  background: #D42B38 !important;
  border-color: #D42B38 !important;
  color: #ffffff !important;
  font-weight: 600;
}
/* ⚠️ LE NOM DU GROUPE (« Mesure ») SUIT, SINON IL RESTE LE PLUS PÂLE DU LOT. */
.dash-mesure-lib {
  color: #383633 !important;
}

/* ⚠️ ET LE SHADER ANIMÉ RESTE MASQUÉ SUR LE DASHBOARD (client : « Enleve aussi le shader
   transparent ou il y a le mouvement »). Le code ne le pose plus (app.js), ceci est la ceinture
   pour un rendu résiduel. Les fiches, elles, gardent le leur. */
.dash-bloc .carte-shader,
.dash-phare-gauche .carte-shader,
.dash-phare-chiffres .carte-shader { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   « CE QU'ON A GAGNÉ » PASSE AU VERT — UNE COULEUR DE LA CHARTE, PAS UNE TEINTE INVENTÉE

   Client : « Cet anglais, ce qu'on a gagné, tu mets en vert. en couletur verte c positif »
   ⚠️⚠️ « EN COULEUR VERTE », ET LA RAISON EST DONNÉE : « c positif ». Ce n'est donc PAS la matière
   (« verre », l'homophone) — c'est une TEINTE, et le client a pris soin de le préciser. Les deux
   lectures se tranchent par ce détail, et c'est lui qui le fournit.
   ⚠️ « anglais » = « onglet » (il dicte) : LA SECTION « Ce qu'on a gagné » DE L'ONGLET DASHBOARD.
   Rien d'autre n'est touché.

   ═══ LES VALEURS SONT MESURÉES, PAS CHOISIES ═════════════════════════════════════════════════
        SUR FOND CLAIR (les chiffres du pas : « +14 », « 27 », « 20 ») :
            #0d6b3f  ->  6,25:1   le VERT DE LA CHARTE (30 occurrences dans le projet)
        SUR LA CARTE SOMBRE (le libellé « PARTICIPANTS AU TOTAL ») :
            #6ee29a  ->  10,71:1  le vert CLAIR de la charte, déjà présent dans le projet
            (le doré #a8977c qu'il remplace faisait 6,08:1 — le vert fait mieux)
   ⚠️ AUCUNE TEINTE N'EST INVENTÉE. Les deux viennent du projet et passent le seuil de 4,5:1 que le
   client a posé comme règle absolue (« rien ne doit être illisible »).

   ═══ CE QUI RESTE ROUGE, ET POURQUOI ═════════════════════════════════════════════════════════
        `.dash-pas.est-recent::after` (« LE PLUS RECENT »)  -> un REPÈRE validé par le client
        `.dash-pas-lieu b`                                  -> déjà mis en #383633 par SA validation
   ⚠️ On ne profite pas d'une demande pour repeindre ce qui n'a pas été demandé.

   ⚠️ ÉCRIT EN FIN DE FEUILLE AVEC `!important` : la section porte DÉJÀ des règles `!important`
   (mesuré : `.dash-cumul-lib { color: #a8977c !important }`, `.dash-pas strong { color: #7a5c3e
   !important }`). Sans `!important` les miennes seraient battues à l'instant même où je les pose.
   ⚠️ LE FOND N'EST PAS TOUCHÉ : c'est une demande de COULEUR, les fonds gardent leur matière. */
/* les chiffres des pas — sur les cartes claires */
.dash-pas strong,
.dash-prog-compact .dash-pas-liste strong {
  color: #0d6b3f !important;
}
/* le libellé de la carte sombre — « PARTICIPANTS AU TOTAL » */
.dash-cumul-lib,
.dash-cumul .dash-cumul-lib {
  color: #6ee29a !important;
}
/* ⚠️ ET L'ICÔNE QUI L'ACCOMPAGNE SUIT LE LIBELLÉ : elle est en `color: inherit !important`, donc
   elle prend la teinte du parent sans qu'on ait à la nommer. On le vérifie par mesure, pas par
   supposition — une icône restée dorée au milieu d'un libellé vert se verrait. */
/* les cumuls de la vue compacte — le même vert, sur les mêmes fonds clairs */
.dash-prog-pas strong {
  color: #0d6b3f !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   L'EFFET GLASS SUR LES FICHES DES BASES DE DONNÉES
   À LA PLACE DU SHADER BLANC/ROUGE

   Client : « Tu peux appliquer cet effet de glace sur les fiches de la base de données au lieu du
   shader qui est en blanc et rouge ? » — et, avant : « je veux que toutes les bases d'anglais
   soient identiques » (⚠️ « anglais » = « onglet » : les fiches de TOUTES les bases).

   ⚠️ LE « SHADER BLANC ET ROUGE » EST `.carte-shader` / `.entity-tile-shader` : un `<span>` posé
   sur CHAQUE fiche, portant l'image du shader d'identité, qui est ROUGE (mesuré :
   `_SHADER_FS_CLAIR` = `mix(color, redLayer, wave * 0.95 …)`, 95 % de rouge). Sur une carte
   blanche, il se lit comme un voile rosé — exactement ce que le client décrit.

   ⚠️⚠️ LE PÉRIMÈTRE EST CLOISONNÉ, ET VOICI POURQUOI : `.carte` et `.entity-tile` sont les classes
   des fiches de TOUTES les bases — les traiter couvre l'uniformité demandée d'un coup. MAIS elles
   servent aussi sur la TABLETTE TERRAIN, dont le fond est SOMBRE : un verre à 55 % y ferait une
   tache blanche. On cloisonne donc à la direction, par `body[data-interface="direction"]` — la
   méthode retenue dans ce projet (« cloisonner, jamais un `!important` de plus »).

   ═══ LA RECETTE EST CELLE DU DASHBOARD, ET ELLE EST MESURÉE ═════════════════════════════════
        voile    rgba(255,255,255,.55)   — à 5 % sur fond clair, la carte n'existe plus
        contour  rgba(23,23,25,.10)      — sur fond clair, LA SEULE chose qui dessine une carte
        ombre    0 1px 2px + 0 6px 16px  — COURTE : les 40 px de portée mordaient sur les voisines
        verre    backdrop-filter: blur(14px) saturate(1.1)   — c'est LUI, l'effet glass
   ⚠️ CES QUATRE VALEURS ONT ÉTÉ PAYÉES CE SOIR SUR LE DASHBOARD (quatre défauts constatés et
   corrigés : carte invisible, liseré blanc inutile, ombre qui déborde, voile trop léger). On
   repart directement des valeurs justes au lieu de refaire le chemin.

   ⚠️ CE QUI N'EST PAS TOUCHÉ : les fiches de DIALOGUE (`.profile-sheet`), les cartes du
   DASHBOARD (déjà traitées par leur propre bloc), et la TABLETTE TERRAIN. */
body[data-interface="direction"] .carte,
body[data-interface="direction"] .entity-tile {
  /* ⚠️ 34 % : le client a demandé PLUS de transparence pour que la matière se voie
     (« encore plus transparent pour qu'il donne cet effet de glace verre, la matière »).
     ⚠⚠ ON NE DESCEND PAS PLUS BAS : à 5 % (première tentative de la soirée) la carte n'existait
     plus du tout et le client a vu des « chevauchements ». C'est le CONTOUR (voir ci-dessous) et
     l'ombre courte qui dessinent la carte désormais — le fond peut donc s'alléger sans risque. */
  background: rgba(255,255,255,.34) !important;
  border: 1px solid rgba(23,23,25,.10) !important;
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  backdrop-filter: blur(14px) saturate(1.1);
  box-shadow: 0 1px 2px rgba(23,23,25,.05), 0 6px 16px rgba(23,23,25,.045) !important;
}

/* ⚠️⚠️ ET LE SHADER S'EN VA — C'EST LA SECONDE MOITIÉ DE LA DEMANDE (« au lieu du shader »).
   Même cloisonnement : sur la tablette il était DÉJÀ masqué par une règle existante, on ne la
   double pas.
   ⚠️ `display: none` ET PAS `opacity: 0` : un élément invisible mais peint fait tourner le
   compositeur pour rien, et sur une grille de 30 fiches ça se paie. */
body[data-interface="direction"] .carte-shader,
body[data-interface="direction"] .entity-tile-shader { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   LES ÉQUIPES RÉORGANISÉES · LE 175 EN VERT

   Client : « L'onglet équipe est chevauché, garde le nom en haut puis ses détails plus bas comme
   ça ça ne serait pas chevauché. L'anglais 175 qu'on a gagné, il reste en noir alors que je
   voulais qu'il soit en vert et l'écriture contrastée. »

   ═══ LES COMPTEURS SONT MAINTENANT SOUS LE NOM (le markup a changé, voir app.js) ═══════════
   ⚠️ MESURÉ AVANT : `.dash-equipe-total` était un ENFANT de `.dash-equipe-tete` et portait
   `display: grid` + `grid-template-columns: 1fr 1fr` + `flex: none` + `white-space: nowrap`.
   Elle NE POUVAIT PAS se réduire : dans une carte de 351 px, elle écrasait le nom et la pastille.
   ⚠️ EN PLEINE LARGEUR ELLE DEVIENT UNE GRILLE `auto-fit` : elle se replie proprement quand la
   carte est étroite, au lieu de déborder sur sa voisine. `minmax(140px, 1fr)` donne 2 colonnes sur
   une carte de 351 px, et 4 sur une carte large — sans une requête média de plus.
   ⚠️ `row-gap: 5px` : les deux rangées doivent respirer, sinon « 57 participations » et
   « 37 gains remis » se lisent comme une seule ligne. */
.dash-equipe-total {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 5px 12px;
  min-width: 0;
}
/* ⚠️ EN PLEINE LARGEUR ELLE N'A PLUS BESOIN DE SE PROTÉGER : on retire `nowrap` qui l'empêchait de
   se replier, et on laisse le texte couper proprement plutôt que de pousser. */
.dash-equipe-total span {
  white-space: normal;
  min-width: 0;
}

/* ═══ LE 175 DE « CE QU'ON A GAGNÉ » PASSE AU VERT ══════════════════════════════════════════
   Client : « il reste en noir alors que je voulais qu'il soit en vert et l'écriture contrastée. »
   ⚠️ MESURÉ : le chiffre du cumul était en `rgb(255,255,255)` sur la carte sombre. Il devient
   `#6ee29a` — LA MÊME TEINTE QUE SON LIBELLÉ juste au-dessus, et celle qui a été mesurée à
   10,71:1 sur ce fond. Le chiffre et son libellé forment donc un ensemble vert cohérent au lieu
   de deux verts différents.
   ⚠️ `!important` EST NÉCESSAIRE : une règle existante pose déjà `.dash-cumul strong { color: #fff
   !important }`. Sans lui, la mienne serait battue à l'instant même où je la pose — c'est le
   défaut qui a coûté quatre passes cette nuit sur d'autres sélecteurs.
   ⚠️ LE « 175 » DU HAUT (`.dash-kpi.est-rouge`) N'EST PAS TOUCHÉ : c'est le compteur principal du
   dashboard, sa teinte rouge est un choix de charte, et le client n'en a pas parlé. */
.dash-cumul strong,
.dash-cumul .dash-cumul-chiffre {
  color: #6ee29a !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════ */
/* ⚠️⚠️ LE SHADER NOIR-ROUGE DE LA TABLETTE EST MASQUE — PAS SUPPRIME DU DOM.
   Client (verbatim) : « change moi dans les pages de la tablette le shader noir rouge change le par le
   design [...] ». Le canvas `#fond-tablette` etait monte par `monterFondShaders()` (app.js) et son
   `redLayer` descend a `vec3(0.45,0.04,0.12)` — c'est lui, le shader noir-rouge.
   ⚠️ LE MOTEUR RESTE INTACT : `_SHADER_FS`, `_SHADER_FS_CLAIR`, `lancerShaderFiche` et le canvas dans
      le DOM ne sont pas touches. C'est REVERSIBLE D'UNE LIGNE — et c'est voulu : si le client veut le
      revoir, on retire cette regle.
   ⚠️ LA REGLE EST POSEE ICI, EN FIN DE FICHIER, PARCE QUE `display: block` EST DECLARE PLUS HAUT
      (l.20159) : a specificite egale, la DERNIERE regle gagne. Un `!important` de plus ferait une
      cinquieme exception dans un fichier qui en a deja trop.
   ⚠️ LE JEU N'EST PAS CONCERNE : `participant-mode` masque deja le canvas (tablette.html l.54) et le
      client a demande separement, pour le jeu, « un fond uni, un fond telecharge, ou une video ». */
body[data-interface="terrain"] .fond-tablette { display: none; }

