/* ============================================================
   _mobile-touch.css — Touch-first overrides per mobile (<1400px)
   ============================================================
   Scopo: regole globali tap target + gesture conventions per
   touch device. NON sostituisce regole dei singoli widget — è
   il "safety net" per garantire usabilità touch minima su
   tutti i controlli che ereditano dimensioni desktop.

   Wrap: applica solo a viewport ds-vp-app (<1400px) E
   pointer:coarse (touch). Su tablet con Pencil/mouse (pointer:fine)
   le regole desktop restano valide.

   Riferimento: brief_mobile_touch_widgets.md (sessione 22)
   ============================================================ */

/* ──────────────────────────────────────────────────────────
   TAP TARGET MINIMI — De Sisti custom 40px (sotto HIG 44 per estetica
   più compatta, sopra legacy 32). Icon-button square restano 44×44.
   sess.33 Step 1b — scelta design utente.
   sess.39: esteso a 1399.98 per coprire compact-narrow touch (iPad landscape).
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) and (pointer: coarse) {

  /* Pillole/chip universali — height 40px (De Sisti custom, sotto HIG) */
  body.ds-vp-app .units-chip,
  body.ds-vp-app .pill,
  body.ds-vp-app .acc-filter-pill,
  body.ds-vp-app .dl-filter-btn,
  body.ds-vp-app .dmx-strip-btn,
  body.ds-vp-app .dmx-modal-ranges button,
  body.ds-vp-app .working-modes button,
  body.ds-vp-app .internal-modes button,
  body.ds-vp-app .pm-mode-chip,
  body.ds-vp-app .pm-preset-btn,
  body.ds-vp-app .pm-cct-btn {
    min-height: 40px;
    padding-inline: 16px;
  }

  /* Bottoni icon-only generici (close, prev/next, ecc.) */
  body.ds-vp-app .ds-mobile-tabs button,
  body.ds-vp-app .dspmx-close,
  body.ds-vp-app .dsmx-close,
  body.ds-vp-app .dmx-modal-close,
  body.ds-vp-app .kfm-close,
  body.ds-vp-app .rfq-close,
  body.ds-vp-app .ph-modal-close,
  body.ds-vp-app .search-close {
    min-width: 44px;
    min-height: 44px;
  }

  /* Hero remote: prev/next/auto/tray-toggle erano 30-32px */
  body.ds-vp-app .hero-remote button {
    min-width: 44px;
    min-height: 44px;
  }

  /* Carousel rail buttons: erano 28×28 — 44 minimo o nascondere su touch */
  body.ds-vp-app .kf-rail-btn,
  body.ds-vp-app .ph-nav-btn {
    min-width: 44px;
    min-height: 44px;
  }
}

/* ──────────────────────────────────────────────────────────
   GESTURE CONVENTIONS — touch-action + scroll behavior
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {

  /* Carouselli orizzontali: solo pan-x interno, blocca pan-y per evitare
     conflitti con scroll verticale della pagina */
  body.ds-vp-app .kf-list,
  body.ds-vp-app .ph-strip,
  body.ds-vp-app .acc-filter {
    touch-action: pan-x;
  }

  /* Cono SVG photometrics: NO touch-action (no pinch), solo tap su marker
     intercettato in JS futuro */
  body.ds-vp-app .pm-cone-host svg,
  body.ds-vp-app .dspmx-cone-svg {
    touch-action: none;
  }

  /* Body principale: lascia pan-y per scroll verticale + pan-x consentito
     per swipe-tab globale (gestito da mobile-app-shell JS) */
  body.ds-vp-app .page-shell {
    touch-action: pan-y;
  }

  /* Modali/sheet aperti: blocca scroll body (gestito già dal modal CSS) */
}

/* ──────────────────────────────────────────────────────────
   COMFORT — preserva selectability su valori utili
   (long-press copy nativo browser per specs values)
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {
  body.ds-vp-app .specs-table td,
  body.ds-vp-app .specs-table th,
  body.ds-vp-app .pm-card-value,
  body.ds-vp-app .pm-kpi-value {
    -webkit-user-select: text;
    user-select: text;
  }
}

/* ──────────────────────────────────────────────────────────
   sess.24 SAFETY — overflow-x hidden a livello page-shell mobile
   ──────────────────────────────────────────────────────────
   Previene bleed orizzontale del regime mobile su desktop browser
   ridotto sotto 1400 (es. ~970px). Su iPhone reale (390px viewport
   CSS) tutto fittiles per design; su desktop ridotto alcuni componenti
   tarati per ~390px appaiono fuori scala e potrebbero overfloware il
   viewport orizzontalmente. Hidden safety net. Decisione strategica
   "regime mobile a <900px invece di <1400" sospesa in attesa feedback
   utente post-lettura audit grafico sess.24.
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {
  body.ds-vp-app,
  body.ds-vp-app .page-shell{
    overflow-x: hidden;
  }
}

/* ──────────────────────────────────────────────────────────
   SESS.24 CHIUSURA — iPad portrait + foldables: TRUE BURGER STATE
   estesa a TUTTO regime app (era solo max-width:780 in header.css L1606)
   ──────────────────────────────────────────────────────────
   Bug latente da sess.22: regole TRUE BURGER (nav nascosta + hamburger
   visibile + util compatti) applicate solo a `@media (max-width: 780px)`.
   Su iPad portrait regular (820), Pro 13" portrait (1024), foldables
   unfolded portrait (~673-689), iPhone landscape (852-956) erano sopra
   780 → nav LIGHTING/RIGGING/PROGETTI/etc visibile mentre body è ds-vp-app.

   Fix sess.24.5 aveva fixato iPhone landscape via media query landscape
   phone (max-h:500). Mancava iPad portrait + foldables portrait.

   Soluzione: scope esteso a `body.ds-vp-app #site-header` (tutto regime
   app sotto 1100), replica regole TRUE BURGER essenziali. Le regole
   header.css originali a max-width:780 restano valid per casi limite.
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {

  /* HEADER NAV nascosta in regime app — replica TRUE BURGER STATE */
  body.ds-vp-app #site-header .menu{
    display: none !important;
  }
  body.ds-vp-app #site-header .menu-toggle{
    display: inline-grid !important;
    place-items: center !important;
    flex: 0 0 auto !important;
    order: 2 !important;
    width: 38px !important;
    min-width: 38px !important;
    height: 38px !important;
    min-height: 38px !important;
    margin-left: 8px !important;
    border-radius: 999px !important;
    border: 1px solid rgba(0,0,0,.10) !important;
    background:
      linear-gradient(180deg, rgba(255,255,255,.70), rgba(255,255,255,.42)) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.80),
      0 3px 10px rgba(0,0,0,.10) !important;
  }
  body.ds-vp-app.ds-dark #site-header .menu-toggle{
    background:
      linear-gradient(180deg, rgba(50,48,44,.62), rgba(28,26,22,.42)) !important;
    border-color: rgba(255,255,255,.10) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.06),
      0 3px 10px rgba(0,0,0,.32) !important;
  }
  body.ds-vp-app.ds-dark #site-header .menu-toggle__lines span{
    background: rgba(218,214,206,.92);
  }
  /* Header layout flex space-between per ordinare logo + burger + util */
  body.ds-vp-app #site-header .wrap{
    justify-content: space-between !important;
    align-items: center !important;
    gap: 10px !important;
    padding-inline: 12px !important;
  }
  body.ds-vp-app #site-header .brand{
    order: 1 !important;
    flex: 0 0 auto !important;
  }
  body.ds-vp-app #site-header .util{
    order: 3 !important;
    margin-left: auto !important;
    gap: 6px !important;
  }
  body.ds-vp-app #site-header .util::before{
    display: none !important;
  }
}

/* ──────────────────────────────────────────────────────────
   STEP 3 sess.24 — TAP SCALE(0.96) :active universale
   ──────────────────────────────────────────────────────────
   Pattern iOS-native: ogni touch target risponde con un piccolo
   "press" visivo (scale 0.96) + transition 80ms. Eleva immediatamente
   il "tap feel" mobile rispetto al desktop hover. Convergenza
   Agent UX + utente sess.23 audit.

   Il chip F7 (.t-code) ha già la regola sopra in Step 1. Qui copre
   gli altri target: pill range, KF pill, taxo-btn, tab item, ph-item.

   Reduced-motion: la regola @media più sotto azzera transition-duration,
   il tap diventa istantaneo (no scale visibile) — comportamento corretto.

   sess.24 fix coerenza viewport: rimossa condizione `(hover: none) and
   (pointer: coarse)` per allineare con tutte le altre regole mobile
   (viewport-only). Lo scale active appare anche su desktop ridotto con
   mouse a viewport <1400 — è un benefit, non un problema (feedback
   visivo press anche su mouse click).
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {
  body.ds-vp-app .ds-mobile-titlebar__range,
  body.ds-vp-app .feature-pill,
  body.ds-vp-app .ds-taxo-btn,
  body.ds-vp-app .ds-mobile-tab,
  body.ds-vp-app .ph-item{
    transition: transform 80ms ease, background-color 180ms ease,
                box-shadow 180ms ease, color 180ms ease;
  }
  body.ds-vp-app .ds-mobile-titlebar__range:active,
  body.ds-vp-app .feature-pill:active,
  body.ds-vp-app .ds-taxo-btn:active,
  body.ds-vp-app .ds-mobile-tab:active,
  body.ds-vp-app .ph-item:active{
    transform: scale(.96) !important;   /* override translateY desktop active */
  }
}

/* ──────────────────────────────────────────────────────────
   STEP 4 sess.24 — BACKDROP-FILTER BLUR OFF Portfolio Hero
   ──────────────────────────────────────────────────────────
   Pattern macOS/Windows desktop, NON iOS native (Agent UX) +
   backdrop-filter contemporanei in dark mode → scroll jank Android
   mid-range Snapdragon 6xx (Agent Tech, 8-15ms/layer).

   Mantieni blur solo su titlebar + tabbar + lightbox + KF modal close
   (queste sono "chrome di sistema", on-brand iOS).

   Learn More già fatto sess.23 STEP 3.5. Qui solo Portfolio Hero
   (in tab Overview). RFQ + Specs scope-out post-feedback utente sess.24
   (lavorati nei rispettivi step dedicati: Step 6 RFQ wizard, Step 8/9
   accessories+downloads, Step 7 photometrics — ognuno definirà il blur
   come parte del proprio redesign).
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) {
  body.ds-vp-app #portfolio-hero,
  body.ds-vp-app .portfolio-hero{
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}

/* ──────────────────────────────────────────────────────────
   PREFERS-REDUCED-MOTION — accessibility fallback
   ────────────────────────────────────────────────────────── */
@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) {
  body.ds-vp-app *,
  body.ds-vp-app *::before,
  body.ds-vp-app *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================
   STEP 3 v2 (sess.23) — HERO MOBILE "ALBUM COVER" FULL-BLEED
   ============================================================
   Pattern raccomandato dall'agent UI/UX dedicato (sess.23 round 2):
   estetica nativamente mobile (Apple Music / Apple Books album).
   - Frame edge-to-edge con radius 18px, niente glass card desktop
   - Aspect-ratio ADATTIVO per tipo media (image native via --media-ratio,
     video 16:9 con sfondo #0a0a0a cinematografico, 3D 1:1, CAD 4:3 wb)
   - object-fit: cover su image con object-position: center 35%
     (la testa apparecchio raramente è esattamente centrata in foto)
   - object-fit: contain su video poster (sfondo nero per banding zero)
   - Thumbs: 6-tile grid full-width 56×56 scroll-snap orizzontale
   - Chip count tipologie media (es. "FOTO 8 · 3D 1 · VIDEO 2") sotto
     i thumbs — JS-injected da renderTypeCounts() in hero-gallery.js
   - Tap-to-lightbox (gestito JS), hint ⛶ SVG inline (non unicode)
   - Autoplay OFF, video controls/loop/autoplay OFF (da JS isMobileApp)
   - Risolve esplicitamente i 3 casi "tagliata": no letterbox grigio,
     no crop testa apparecchio, no banding video
   ============================================================ */
@media (max-width: 1399.98px) {

  /* ── Frame: full-bleed edge-to-edge, niente glass card desktop ── */
  body.ds-vp-app .hero{
    --hero-w: 100%;
    width: 100%;
    margin-inline: 0;
  }
  body.ds-vp-app .hero .hero-card{
    width: 100%;
    max-width: 100%;
    background: transparent !important;
    box-shadow: none !important;
    border-radius: 18px;
    overflow: hidden;
    padding: 0 !important;
  }

  /* ── Stage aspect-ratio FISSO 1:1 (sess.23 v3 — decisione utente
        post-test): widget altezza STABILE, no layout shift tra slide.
        Sfondi DIVERSIFICATI per tipo media controllano l'estetica del
        letterbox quando il media nativo non è 1:1 (es. video 16:9 con
        bande nere cinematografiche, no grigio default).
        Razionale utente: "preferirei restasse sempre alto uguale con
        bande quando scrolli e il viewer cambia dimensione secondo me
        è un po' disturbante". ── */
  body.ds-vp-app .hero .hero-stage{
    aspect-ratio: 1 / 1 !important;
    block-size: auto !important;
    min-block-size: 0 !important;
    max-block-size: none !important;
    width: 100%;
    border-top-left-radius: 18px;
    border-top-right-radius: 18px;
    background: #f6f6f6;          /* fallback chiaro */
  }
  body.ds-vp-app .hero.is-image .hero-stage{
    background: rgba(255,255,255,.82);
  }
  body.ds-vp-app .hero.is-video .hero-stage{
    background: #0a0a0a;          /* cinematografico, bande nere su video 16:9 */
  }
  body.ds-vp-app .hero.is-model .hero-stage{
    background: linear-gradient(180deg, rgba(255,255,255,.96), rgba(245,245,245,.92));
  }

  /* Dark mode: stage di base con sfondo studio scuro */
  body.ds-vp-app.ds-dark .hero .hero-stage{ background: rgba(28,26,22,.62); }
  body.ds-vp-app.ds-dark .hero.is-image .hero-stage{ background: rgba(20,18,14,.86); }
  body.ds-vp-app.ds-dark .hero.is-model .hero-stage{
    background: linear-gradient(180deg, rgba(40,36,30,.88), rgba(24,22,18,.94));
  }

  /* ── Object-fit: con widget 1:1 fisso, image cover ha senso solo se foto
        è anch'essa 1:1 (rischio crop testa apparecchio). Compromesso:
        IMAGE = contain (nessun crop, eventuale spazio laterale gestito
        dal background diverso); VIDEO = contain (bande nere bg dark);
        3D = transparent default. Risolve il caso "preview video tagliata"
        del feedback: ora è in contain con bg cinema, no crop. ── */
  body.ds-vp-app .hero.is-image #hero-canvas img{
    object-fit: contain;
  }
  body.ds-vp-app .hero.is-video #hero-canvas video{
    object-fit: contain;
  }
  body.ds-vp-app .hero.is-model #hero-canvas model-viewer{
    background: transparent;
  }

  /* ── #hero-canvas: cursor pointer (tap = lightbox) ── */
  body.ds-vp-app .hero #hero-canvas{ cursor: pointer; }

  /* ── Hint ⛶ "tap to expand" SVG inline (rendering predicibile vs unicode):
        top-right su image, bottom-left su 3D/video (non copre controls). ── */
  body.ds-vp-app .hero:not(.is-model):not(.is-video) #hero-canvas::after{
    content: "";
    position: absolute;
    top: 12px; right: 12px;
    width: 36px; height: 36px;
    border-radius: 10px;
    background:
      rgba(0,0,0,.42)
      url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 3 21 3 21 9'/><polyline points='9 21 3 21 3 15'/><line x1='21' y1='3' x2='14' y2='10'/><line x1='3' y1='21' x2='10' y2='14'/></svg>")
      center / 18px no-repeat;
    pointer-events: none;
    opacity: .85;
    z-index: 2;
  }
  body.ds-vp-app .hero.is-model #hero-canvas::after{
    content: "";
    position: absolute;
    bottom: 12px; left: 12px;
    width: 32px; height: 32px;
    border-radius: 8px;
    background:
      rgba(0,0,0,.55)
      url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 3 21 3 21 9'/><polyline points='9 21 3 21 3 15'/><line x1='21' y1='3' x2='14' y2='10'/><line x1='3' y1='21' x2='10' y2='14'/></svg>")
      center / 16px no-repeat;
    pointer-events: none;
    opacity: .80;
    z-index: 2;
  }
  /* sess.47b — VIDEO: su mobile autoplay è OFF e si vede solo il poster statico.
     Serve un ▶ CENTRALE esplicito (non l'⛶ angolare) per far capire che è un
     video tappabile (→ lightbox). Non dipende dal ▶ nativo iOS (incostante).
     pointer-events:none → il tap passa al gestore lightbox del canvas. */
  body.ds-vp-app .hero.is-video #hero-canvas::after{
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 58px; height: 58px;
    border-radius: 50%;
    background:
      rgba(0,0,0,.50)
      url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'><path d='M9 7.5v9l7.5-4.5z'/></svg>")
      center / 26px no-repeat;
    box-shadow: 0 4px 16px rgba(0,0,0,.30), inset 0 0 0 1px rgba(255,255,255,.28);
    pointer-events: none;
    opacity: .92;
    z-index: 4;
  }

  /* ── Dock minimo: nascondi prev/next/auto/tray buttons.
        Lo swipe orizzontale + tap thumbs sono le gesture primarie.
        L'utente che proprio non sa swipare può aprire il lightbox col
        tap che dà accesso al pannello completo con prev/next 44×44. ── */
  body.ds-vp-app .hero-remote [data-prev],
  body.ds-vp-app .hero-remote [data-next],
  body.ds-vp-app .hero-remote [data-auto],
  body.ds-vp-app .hero-remote [data-tray-toggle]{
    display: none !important;
  }
  /* CRITICO: rimuovere altezza fissa desktop dock (64/104px) + max-width
     che clippano il grid thumbs full-width. Senza questi override il grid
     6-tile si schiaccia in 240px (max-width clamp del .hero-dots desktop)
     e si vedono solo le strisce di badge. */
  body.ds-vp-app .hero-dock{
    height: auto !important;
    min-height: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    padding: 0 !important;
    border: none !important;
    border-top: none !important;
    border-radius: 0 !important;
    place-items: stretch !important;       /* niente center vertical che comprime */
    display: block !important;             /* annulla `display: grid` desktop */
    transition: none !important;
  }
  body.ds-vp-app .hero-dock::before{
    display: none !important;              /* gradiente glassy desktop OFF */
  }
  body.ds-vp-app .hero-remote{
    width: 100% !important;
    max-width: none !important;
    background: transparent !important;
    box-shadow: none !important;
    border: none !important;
    padding: 0 !important;
    display: block !important;             /* annulla `display: grid` desktop con cols */
    transform: none !important;
  }

  /* ── Pannellino grafico thumbs (sess.23 v3 — feedback utente):
        "thumbs sembrano buttate li, manca un effetto pannellino minimo
        come nel hero in desktop". Aggiungo border-top sottile + bg
        leggermente diverso + padding inset per "containment grafico".
        v3.1: display flex + center per centrare verticalmente i thumbs. ── */
  body.ds-vp-app .hero-dock{
    border-top: 1px solid rgba(0,0,0,.08) !important;
    background:
      linear-gradient(180deg, rgba(255,255,255,.55), rgba(248,248,248,.78)) !important;
    border-bottom-left-radius: 18px !important;
    border-bottom-right-radius: 18px !important;
    display: flex !important;
    align-items: center !important;
    justify-content: stretch !important;
    padding: 6px 0 !important;
  }
  body.ds-vp-app.ds-dark .hero-dock{
    border-top: 1px solid rgba(255,255,255,.08) !important;
    background:
      linear-gradient(180deg, rgba(28,26,22,.32), rgba(20,18,14,.55)) !important;
  }
  /* .hero-remote ora è il flex child del dock e contiene i hero-dots.
     Forza width 100% per riempire orizzontalmente. */
  body.ds-vp-app .hero-remote{
    width: 100% !important;
    flex: 1 1 100% !important;
  }

  /* ── Thumbs grid full-width 5-tile scroll-snap (Album Cover pattern).
        updateTrayUI() forza data-tray="open" su mobile → le regole base
        di hero-gallery.css renderizzano thumbs. Qui sostituiamo flex con
        grid auto-flow column, full-width responsive.
        v2.1 sess.23: 5 tile invece di 6 + size 64×64 (era 56) per
        più presenza grafica come da feedback utente "un pelo più grafico
        il container dei thumbs, ingrandirli un peletto". ── */
  body.ds-vp-app .hero[data-tray="open"] .hero-dock .hero-dots button{
    --thumb-w: 100% !important;
    --thumb-h: 64px !important;
    aspect-ratio: 1 !important;
    inline-size: auto !important;
    block-size: auto !important;
    border-radius: 12px !important;       /* leggermente più morbido a 64 */
  }
  body.ds-vp-app .hero-dots{
    display: grid !important;
    grid-auto-flow: column !important;
    grid-auto-columns: calc((100% - 24px - 4 * 8px) / 5) !important;  /* 5 tile + 4 gap, sottratto padding 24 (v2.1 era 6 tile/56) */
    gap: 8px !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    scroll-snap-type: x mandatory !important;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding: 12px !important;
    flex-wrap: nowrap !important;
    /* CRITICO override regole desktop: max-width clamp(240px,52vw,560px),
       min-width: 0, max-width: 0, opacity: 0 (closed-state). data-tray=open
       attivato da updateTrayUI() su mobile, ma il max-width clamp resta. */
    max-width: 100% !important;
    min-width: 0 !important;
    width: 100% !important;
    opacity: 1 !important;
    pointer-events: auto !important;
    transform: none !important;
    transition: none !important;
  }
  body.ds-vp-app .hero-dots::-webkit-scrollbar{ display: none; }
  body.ds-vp-app .hero-dots > button{
    scroll-snap-align: start;
    min-width: 0;
  }

  /* Badge data-media-type leggibile su thumb 56-square */
  body.ds-vp-app .hero[data-tray="open"] .hero-dock .hero-dots button[data-media-type]::after{
    bottom: 4px !important;
    right: 4px !important;
    font-size: 8px !important;
    padding: 2px 4px !important;
    letter-spacing: .04em !important;
  }
  /* Color override per badge "DIM" (drawing dimensionali, sigla in arrivo) */
  body.ds-vp-app .hero[data-tray="open"] .hero-dock .hero-dots button[data-media-type="dim"]::after,
  body.ds-vp-app .hero[data-tray="open"] .hero-dock .hero-dots button[data-media-type="cad"]::after{
    background: rgba(232,115,44,.78) !important;  /* arancio neutro datasheet */
  }

  /* ── Chip count tipologie media (JS-injected da renderTypeCounts).
        Markup: <div class="hero-type-counts" role="status">
                  <span class="ht-chip"><b>8</b> FOTO</span> · ...
                </div>  ── */
  body.ds-vp-app .hero-type-counts{
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 4px 10px;
    padding: 0 12px 14px;
    font: 700 11px/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgba(0,0,0,.62);
  }
  body.ds-vp-app .hero-type-counts .ht-chip{
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    white-space: nowrap;
  }
  body.ds-vp-app .hero-type-counts .ht-chip b{
    font-weight: 700;
    color: var(--ds-c-accent, #F4B532);
    font-size: 13px;
  }
  body.ds-vp-app .hero-type-counts .ht-sep{
    color: rgba(0,0,0,.20);
    font-weight: 400;
  }
  body.ds-vp-app.ds-dark .hero-type-counts{ color: rgba(218,214,206,.62); }
  body.ds-vp-app.ds-dark .hero-type-counts .ht-sep{ color: rgba(218,214,206,.22); }

  /* Single item: niente thumbs, niente count chip (1 solo media) */
  body.ds-vp-app .hero[data-has-single="true"] .hero-dots,
  body.ds-vp-app .hero[data-has-single="true"] .hero-type-counts{
    display: none !important;
  }

  /* Step 3 v2.1 sess.23 — Chip count nascosto (decisione utente post-test):
     ridondante con badge VIDEO/3D/CAD già visibili sui thumbs. Il counter
     resta solo nel lightbox top-left dove serve come navigation indicator.
     Se in futuro l'utente vuole riattivarlo: rimuovere questa regola. */
  body.ds-vp-app .hero-type-counts{
    display: none !important;
  }
}

/* ============================================================
   STEP 5 v2 (sess.23) — TITLE BAR MOBILE "Hero Plate" + DARK FIX
   ============================================================
   Fix v2 dopo feedback test utente:
   - Eyebrow tagliato sotto header → più gap top + padding-top interno
   - Chip F7 + pill range disallineati → align baseline (chip+pill stesso fondo)
   - Dark mode titlebar resta bianca → override bg dark
   ============================================================ */
@media (max-width: 1399.98px) {

  /* Token altezza titlebar — alzata a 72 per dare respiro interno e
     permettere all'eyebrow di non sembrare schiacciato sotto header */
  body.ds-vp-app{
    --ds-mobile-titlebar-h: 72px !important;
  }

  /* Layout titlebar: align-items END per allineare chip+pill al fondo,
     eyebrow flotta sopra. Top gap aumentato a 18px (era 8) per non
     finire sotto la curvatura bottom dell'header rounded. */
  body.ds-vp-app .ds-mobile-titlebar{
    padding: 8px 14px !important;
    gap: 12px !important;
    align-items: flex-end !important;        /* baseline allineata chip+pill */
    top: calc(var(--ds-header-h, 66px) + 18px) !important;
  }

  /* Dark mode titlebar bg (era bianca-ish anche in dark) */
  body.ds-vp-app.ds-dark .ds-mobile-titlebar{
    background: rgba(20,18,14,.88) !important;
    border-bottom: 1px solid rgba(255,255,255,.06) !important;
  }

  /* Dark mode pill range (era bianca anche in dark). Replica pattern
     desktop body.ds-dark #pb.product-bar .rhs .range-pills .pill (riga 1454
     product-bar.css): bg gradient rgba bianco trasparente, color light. */
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__range{
    background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.10)) !important;
    color: rgba(236,232,224,.95) !important;
    border-color: rgba(255,209,0,.85) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.10),
      0 2px 6px rgba(0,0,0,.32) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__range:hover,
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__range[aria-expanded="true"]{
    background: linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,.14)) !important;
  }
  /* Onda tricolore resta visibile su pill dark — la regola desktop ::after
     (gradient verde-bianco-rosso) è già visibile, niente override richiesto */

  /* Title sx: stack verticale eyebrow (top) + chip plate (bottom).
     align-items flex-start per eyebrow allineato al chip a sinistra. */
  body.ds-vp-app .ds-mobile-titlebar__title{
    display: flex !important;
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 4px !important;
    line-height: 1;
  }

  /* Eyebrow "FRESNEL" sess.24 STEP 2 — 11px Oswald medium + contrast WCAG AA.
     Era 8.5px (.55rem) leggero opacity .42 → contrast ratio ~3.2:1 light
     e ~2.4:1 dark, sotto 4.5:1 minima AA. Letto come "didascalia
     microstampata catalogo cartaceo" (Agent UX) invece che credit nativo.
     Fix: 11px medium (500) + opacity .62 light / .72 dark = leggibile,
     credit famiglia presente, non più "pesante" come 700. */
  body.ds-vp-app .ds-mobile-titlebar__title .t-eyebrow{
    display: block;
    font: 500 11px/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgba(0,0,0,.62);          /* WCAG AA su titlebar bg bianco */
    padding-left: 2px;
    margin-bottom: 3px;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__title .t-eyebrow{
    color: rgba(218,214,206,.72);    /* WCAG AA su titlebar bg dark */
  }

  /* Chip F[N] sess.24 STEP 1 — HERO PLATE solid black + DOPPIA LINEA brand
     ::after sotto, NO bordo amber perimetrale. Replica fedele label fisica
     F6 reference (placca laterale nera ConthraxSB + riga gialla sotto).
     Convergenza Agent UX (Agent 1: "warning sticker, raccomanda solid black
     plate matte") + Agent Brand (Agent 4: "TRADISCE label fisica F6,
     raccomanda doppia linea brand orizzontale") + utente confermato.
     Doppia linea brand (linguaggio visivo De Sisti, ds-tokens.css L19-20):
     - Linea spessa #F4B532 (--ds-brand-yellow-dark) — 2px superiore
     - Linea fine #F3E955 (--ds-brand-yellow-light) — 1px inferiore
     Bg gradient nero (product.css L555 `linear-gradient(180deg, #1f1f1f, #0a0a0a)`)
     preservato. Tap feedback iOS-native scale(0.96) on :active (Step 3). */
  body.ds-vp-app .ds-mobile-titlebar__title .t-code{
    height: 40px !important;
    width: auto !important;
    min-width: 56px !important;
    padding: 0 12px !important;
    border-radius: 9px !important;
    font-size: 1.3rem !important;
    border: none !important;             /* RIMUOVE bordo amber perimetrale */
    position: relative;                  /* anchor doppia linea ::after */
    transition: transform 80ms ease;
  }
  /* sess.24.6 v9 post-feedback: DOPPIA LINEA sotto chip F7 RIMOSSA.
     Insight brand utente: "storicamente la doppia/tripla linea brand va
     a FIANCO DEL LOGO per lunghezza fiancata proiettore, pertanto avrebbe
     più senso richiamo in header (vicino logo DeSisti) piuttosto che
     nella titlebar che senza doppia linea sta benissimo lo stesso".
     Pattern brand corretto: doppia linea vicino al LOGO header.
     Tracciato in polish_backlog per sess.25 P0 (refactor header). */
  body.ds-vp-app .ds-mobile-titlebar__title .t-code:active{
    transform: scale(.96);
  }

  /* Pill range: 32h compatto. Allineato al fondo del chip (entrambi su baseline).
     sess.24 STEP 0c: font-family ConthraxSB esplicito (era ereditato dalla
     regola desktop, ma ricerca utente "il font sembra Oswald, non Conthrax
     come desktop e label fisica del prodotto F6"). Forziamo per certezza. */
  body.ds-vp-app .ds-mobile-titlebar__range{
    height: 32px !important;
    padding: 0 12px !important;
    font-family: "ConthraxSB", "Oswald", system-ui, sans-serif !important;
    font-size: .68rem !important;
    font-weight: 700 !important;
    letter-spacing: .04em !important;
    padding-block: 6px !important;
    margin-block: -6px !important;
    align-self: flex-end !important;    /* allineato al fondo come chip */
    margin-bottom: 4px !important;       /* fine tuning baseline visiva */
  }
  /* Forza font ConthraxSB anche sulla label interna (nel caso di nesting) */
  body.ds-vp-app .ds-mobile-titlebar__range .ds-mt-range-label,
  body.ds-vp-app .ds-mobile-titlebar__range #ds-mt-range-label{
    font-family: "ConthraxSB", "Oswald", system-ui, sans-serif !important;
    font-weight: 700 !important;
    letter-spacing: .04em !important;
  }

  /* Onda tricolore "Made in Italy" su pill 32h */
  body.ds-vp-app .ds-mobile-titlebar__range::after{
    bottom: -2px !important;
  }

  /* Aggiorna offset top del page-shell per la nuova altezza titlebar 72.
     sess.24.6 v3: aggiunto --ds-header-gap per coerenza con titlebar top calc. */
  body.ds-vp-app .page-shell{
    --ds-mobile-app-top: calc(var(--ds-header-gap, 14px) + var(--ds-header-h, 66px) + 72px + 24px) !important;
  }

  /* ── Tab bar mobile bottom: DARK MODE FIX (era bianca anche in dark).
        Pattern coerente con titlebar dark bg. ── */
  body.ds-vp-app.ds-dark .ds-mobile-tabs{
    background: rgba(20,18,14,.92) !important;
    border-top: 1px solid rgba(255,255,255,.06) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-tab__label{
    color: rgba(218,214,206,.72) !important;
  }
  /* sess.24 fix: tab attiva ha bg yellow (product.css L392, light+dark).
     In dark mode label/icon erano settati a yellow → yellow-su-yellow illeggibile.
     Fix: nero #111 su bg yellow, coerente con light mode che già lo applica. */
  body.ds-vp-app.ds-dark .ds-mobile-tab.is-active .ds-mobile-tab__label,
  body.ds-vp-app.ds-dark .ds-mobile-tab[aria-pressed="true"] .ds-mobile-tab__label{
    color: #111 !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-tab__icon svg{
    color: rgba(218,214,206,.62) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-tab.is-active .ds-mobile-tab__icon svg,
  body.ds-vp-app.ds-dark .ds-mobile-tab[aria-pressed="true"] .ds-mobile-tab__icon svg{
    color: #111 !important;
  }
}

/* Hover-fine override (iPad con Pencil/mouse mantiene UI desktop sotto 1400)
   — RIMOSSO sess.24 per coerenza viewport-only.
   Era pensato per iPad+Pencil + desktop browser ridotto: annullava le
   regole mobile e ripristinava UI desktop. Ma le altre regole CSS mobile
   in `_mobile-touch.css` non avevano override speculari, quindi il
   risultato era un ibrido incoerente (alcuni widget mobile, altri
   desktop). Decisione sess.24: viewport <1400 = mobile UI sempre,
   indipendentemente da pointer device. iPad+Pencil avrà mobile UI come
   smartphone (accettabile: il sito mobile è progettato bene anche per
   stylus). Se in futuro serve diversificare iPad-Pencil ricreare con
   override completi (NON parziali). */

/* ============================================================
   STEP 4 (sess.23) — KF "LEDWALL TICKER" MOBILE
   ============================================================
   Pattern raccomandato dall'agent UI/UX KF (sess.23 round 3) +
   feedback utente (label COMPLETA, no truncation, meno pill visibili
   contemporaneamente). Decisioni utente registrate:
   - Pill: icon-circle SX + label completa DX max-2-line, 180×64h
   - Visibili: 2 + peek 30% del 3° (no 3-4 dense agent default)
   - Cycle: variabile JS dinamico (pixelsPerSecond=50, "ledwall slow")
   - Pausa: IntersectionObserver out-of-viewport + pause PERMANENTE
     dopo primo touch (utente "prende controllo" della sessione)
   - Reduced-motion: static + scroll-snap + swipe hint "→" 0-3s
   - Indicator: dots progress passive (no counter "12")
   - shortLabel JSON: NO, label originale completa

   sess.24 fix coerenza viewport: rimossa condizione `(hover: none) and
   (pointer: coarse)` per allineare con tutte le altre regole mobile
   (viewport-only). Era causa dell'ibrido: desktop browser ridotto sotto
   1400 con mouse aveva CSS mobile su hero/learnmore ma KF restava
   desktop perché `_kfIsMobileApp()` (e questa media query) richiedevano
   touch. Ora coerente: viewport <1400 = mobile UI completa.
   ============================================================ */
@media (max-width: 1399.98px) {

  /* ── Frecce KF gialle desktop (.kf-rail-btn) NASCOSTE su mobile.
        Lo swipe orizzontale o il ledwall ticker sostituiscono prev/next. ── */
  body.ds-vp-app #key-features .kf-rail-btn,
  body.ds-vp-app .key-features .kf-rail-btn{
    display: none !important;
  }

  /* ── sess.24 STEP 0b — Portfolio Hero mobile polish:
        Frecce .ph-nav-btn nascoste (coprono troppo spazio thumbnail su
        portrait stretto, lo swipe orizzontale è gesture sufficiente).
        data-swipe-lock già aggiunto a .ph-strip via JS portfolio-hero.js
        per evitare che lo swipe sui project thumbnail cambi tab. ── */
  body.ds-vp-app .portfolio-hero .ph-nav-btn,
  body.ds-vp-app #portfolio-hero .ph-nav-btn{
    display: none !important;
  }

  /* ── Container .kf-list mobile: 2 modalità DOM ─────────────────────
     [kf-ticker]  → overflow:hidden, .kf-track interno con animation
     [kf-static]  → overflow-x:auto + scroll-snap, niente .kf-track
     Switch da JS: post-touch sposta pill fuori dal track, .kf-static.
     CRITICO override desktop: .kf-list.as-carousel2 ha grid-template-columns
     con --kf-col-px che fa collassare le pill in colonna su mobile. La
     classe as-carousel2 è ora rimossa in setupKfMobileTicker(), ma metto
     anche fallback CSS per safety. ── */
  body.ds-vp-app .kf-list.kf-mobile,
  body.ds-vp-app .kf-list.kf-mobile.as-carousel2{
    display: block !important;
    overflow: hidden;
    position: relative;
    padding: 4px 12px 0;
    margin: 0;
    height: auto !important;
    max-height: none !important;
    min-height: 0 !important;
    /* Annulla CSS desktop carousel layout (grid-auto-columns kf-col-px) */
    grid-template-columns: none !important;
    grid-auto-columns: none !important;
    grid-auto-flow: row !important;
    /* Annulla flex desktop column flow */
    flex-direction: row !important;
    flex-wrap: nowrap !important;
  }

  /* Track wrapper: contiene tutte le pill durante ticker.
     Step 4 v2 sess.23: layout 2-RIGHE GRID (decisione utente "1 riga non
     basta, le pill devono dare idea di QUANTE features esclusive sono
     disponibili"). Le pill scorrono in 2 righe orizzontali. CSS Grid
     con grid-auto-flow: column dense distribuisce le pill 1,3,5...
     in row 1 e 2,4,6... in row 2. width: max-content fa fit al contenuto.
     Effetto wow "ledwall stadium" come da metafora utente. */
  body.ds-vp-app .kf-list.kf-mobile .kf-track{
    display: grid !important;
    grid-template-rows: 1fr 1fr;
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    gap: 8px 10px;
    width: max-content;          /* fit al contenuto, NON al parent */
    align-items: stretch;
    will-change: transform;
    padding-bottom: 4px;
  }

  /* Modalità ticker: animation perenne ledwall ping-pong.
     --kf-overflow e --kf-cycle settati da JS in setupKfMobileTicker().
     sess.26 Tier 1.4 fix: regola sotto a riga ~1101 vince per !important
     + cascade order. Definizione effettiva del ticker animation e' nella
     regola override (con keyframe 3-stop + normal direction per iOS Safari
     bullet-proof — vedi commento storico sess.23 sul fix pause/resume). */
  body.ds-vp-app .kf-list.kf-mobile.kf-ticker[data-paused="true"] .kf-track,
  body.ds-vp-app .kf-list.kf-mobile.kf-ticker:focus-within .kf-track{
    animation-play-state: paused;
  }

  /* Modalità static (post-touch, reduced-motion, o overflow≤0):
     2-row grid scrollable orizzontalmente, niente translate animation.
     v2 sess.23: stesso layout 2-row del ticker (decisione utente "due
     righe sempre, anche in static"). */
  body.ds-vp-app .kf-list.kf-mobile.kf-static,
  body.ds-vp-app .kf-list.kf-mobile.kf-static.as-carousel2{
    display: grid !important;
    grid-template-rows: 1fr 1fr !important;
    grid-auto-flow: column !important;
    grid-auto-columns: max-content !important;
    grid-template-columns: none !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    gap: 8px 10px !important;
    padding: 6px 12px !important;
    scroll-padding-inline: 12px;
    flex-direction: unset !important;
    flex-wrap: unset !important;
  }
  body.ds-vp-app .kf-list.kf-mobile.kf-static::-webkit-scrollbar{ display: none; }

  /* ── Pill mobile sess.26 Tier 1: SURFACE TIER-2 + DOPPIA LINEA BRAND.
        Aggiornamento sess.26 Tier 1 (post-feedback utente "squilibrio
        estetico KF↔container"): pills ora hanno rilievo coerente al
        sistema surface tier (tier-2 = "nested card / sub-section",
        ds-tokens.css:561-572).
        Sostituisce flat sess.24 STEP 5 (background transparent + no
        border + no shadow). Mantiene accent bar amber `::before` +
        linea fine gold `::after` (doppia linea brand identitaria
        non-negoziabile). Pattern reference: Spotify "Made for You"
        cards, Apple Music recommendations, Linear carousel pulsanti.
        Risultato: mini-card scrollabili con rilievo subtle, armonia
        cross-elemento con container surface-1 della pagina. ── */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill{
    flex: 0 0 auto !important;
    width: 150px !important;
    min-width: 150px !important;
    max-width: 150px !important;
    min-height: 52px !important;
    height: 52px !important;
    display: grid !important;
    grid-template-columns: 32px 1fr !important;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 12px !important;        /* 12 left per accent bar */
    border-radius: 12px !important;               /* sess.26 Tier 1: 10→12 coerenza scale tier-2 */
    scroll-snap-align: start;
    scroll-snap-stop: always;
    /* sess.24 STEP 5: rimosso `transform: none !important` per non bloccare
       lo scale(.96) :active dello Step 3. Le regole hover/focus più sotto
       continuano a fare reset solo per quegli stati. */
    /* sess.26 Tier 1: surface tier-2 (nested card subtle relief) */
    background: var(--ds-surface-2-bg) !important;
    border: 1px solid var(--ds-surface-2-border) !important;
    box-shadow: var(--ds-surface-2-shadow) !important;
    position: relative;
    overflow: hidden;
  }

  /* ── V-57 · la card senza icona metteva il testo NELLA COLONNA DELL'ICONA ──
     La griglia qui sopra dichiara `32px 1fr`: 32px per l'icona, il resto per il
     testo. Ma `features-render.js` (riga ~345), quando la feature non ha icona,
     NON emette il nodo icona e marca la card `.no-icon`. Restando un figlio
     solo, l'auto-placement lo mette nella PRIMA colonna: il testo finisce nei
     32px dell'icona e gli 85px suoi restano vuoti. Misurato su F7 a 390px:
     card con icona -> testo 85px; card senza icona -> testo 32px, 14 card su 40.
     Non era un problema di sillabazione ne' di spazio: era il testo nella
     casella sbagliata. Quindi NON si allarga la card (strada bocciata da David
     in V-04): si toglie la colonna che non ha piu' inquilino.
     ⚠️ Questa regola SPARISCE da sola il giorno che le 7 feature senza icona
     avranno la loro icona: non e' un cerotto permanente, e' il caso degenere
     gestito. */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill.no-icon{
    grid-template-columns: 1fr !important;
  }
  /* Accent bar amber 3px sx (pattern .ds-taxo-btn::before learn-more) */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill::before{
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--ds-c-accent, #F4B532);
    border-radius: 10px 0 0 10px;
    opacity: .60;
  }
  /* Sottile linea secondaria gold-light (richiamo doppia linea brand) */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill::after{
    content: "";
    position: absolute;
    left: 4px; top: 0; bottom: 0;
    width: 1.5px;
    background: var(--ds-brand-yellow-light, #F3E955);
    opacity: .35;
  }
  /* Pill stato active/selected: accent bar piena */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill.is-active::before,
  body.ds-vp-app .kf-list.kf-mobile .feature-pill[aria-selected="true"]::before{
    opacity: 1;
  }

  /* Dark mode pill sess.26 Tier 1: surface tier-2 token auto-reattivo.
     Sostituisce il fix sess.24 STEP 5 (rgba(50,48,44,.55) raw).
     Il token --ds-surface-2-bg in dark è gia gradient overlay + neutral-900
     opaco → presenza visiva garantita su body bg dot pattern. */
  body.ds-vp-app.ds-dark .kf-list.kf-mobile .feature-pill{
    background: var(--ds-surface-2-bg) !important;
    border: 1px solid var(--ds-surface-2-border) !important;
    box-shadow: var(--ds-surface-2-shadow) !important;
  }
  body.ds-vp-app.ds-dark .kf-list.kf-mobile .feature-pill::before{
    background: var(--ds-c-accent, #F4B532) !important;
    opacity: .75;
  }

  /* Icon-circle 28×28 (più compatto del 32 v1) */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill .pill-icon-circle{
    width: 28px !important;
    height: 28px !important;
    flex-shrink: 0;
  }
  body.ds-vp-app .kf-list.kf-mobile .feature-pill .pill-icon-circle .feature-icon{
    width: 16px !important;
    height: 16px !important;
  }

  /* Label compatta 1-line con ellipsis safety. Su pill 150 e 1-line a
     11px Oswald entrano label tipo "PRECISION GLASS LENS", "HIGH CRI/TLCI",
     "CCT RANGE". Per label molto lunghe (es. "Eccellente Controllo del
     fascio luminoso") l'ellipsis taglia, e il dettaglio si vede aprendo
     il modal — pattern coerente con la decisione "label completa nel modal,
     compact nel carousel" (sacrificiamo lunghezza in carousel per densità). */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill .feature-text{
    min-width: 0;
    overflow: hidden;
  }
  body.ds-vp-app .kf-list.kf-mobile .feature-pill .feature-title{
    font: 600 11px/1.2 "Oswald", system-ui, sans-serif;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: rgba(0,0,0,.84);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: normal !important;
  }
  body.ds-vp-app.ds-dark .kf-list.kf-mobile .feature-pill .feature-title{
    color: rgba(218,214,206,.92);
  }

  /* ── Dots indicator passive (modalità static, post-touch o reduced).
        Markup creato da JS: <div class="kf-progress"><span></span>...</div>
        Position: subito sotto la lista. v3.2: dark mode bg dots più visibile
        (era nero su nero), e is-current ha bg amber sempre. ── */
  body.ds-vp-app .kf-progress{
    display: flex;
    justify-content: center;
    gap: 5px;
    padding: 8px 0 6px;
    list-style: none;
    margin: 0;
  }
  body.ds-vp-app .kf-progress span{
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(0,0,0,.22);
    transition: width 180ms ease, background 180ms ease;
  }
  body.ds-vp-app .kf-progress span.is-current{
    width: 18px;
    border-radius: 3px;
    background: var(--kf-accent, #F4B532) !important;
  }
  /* Dark mode: dots inattivi visibili su sfondo scuro (era invisible nero su nero) */
  body.ds-vp-app.ds-dark .kf-progress span{
    background: rgba(218,214,206,.32) !important;
  }
  body.ds-vp-app.ds-dark .kf-progress span.is-current{
    background: var(--kf-accent, #F4B532) !important;
    box-shadow: 0 0 6px rgba(255,209,0,.45);
  }

  /* In modalità ticker i dots sono nascosti (l'animation è l'indicator) */
  body.ds-vp-app .kf-list.kf-mobile.kf-ticker ~ .kf-progress,
  body.ds-vp-app .key-features:has(.kf-list.kf-ticker) .kf-progress{
    display: none;
  }

  /* ── Swipe hint "→" SVG (modalità static).
        Markup creato da JS: <div class="kf-swipe-hint" aria-hidden="true">
        Fade-in 0-3s, fade-out via class .is-fading. ── */
  body.ds-vp-app .kf-swipe-hint{
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(0,0,0,.55);
    -webkit-backdrop-filter: blur(6px);
            backdrop-filter: blur(6px);
    pointer-events: none;
    opacity: 0;
    z-index: 3;
    animation: kf-swipe-hint-pulse 2.4s ease-in-out 1 forwards;
    animation-delay: 600ms;
  }
  body.ds-vp-app .kf-swipe-hint::before{
    content: "";
    position: absolute;
    inset: 0;
    background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 6 15 12 9 18'/></svg>")
      center / 14px no-repeat;
  }
  @keyframes kf-swipe-hint-pulse{
    0%   { opacity: 0; transform: translate(0, -50%); }
    20%  { opacity: 1; transform: translate(0, -50%); }
    50%  { opacity: 1; transform: translate(6px, -50%); }
    80%  { opacity: 1; transform: translate(0, -50%); }
    100% { opacity: 0; transform: translate(0, -50%); }
  }
  body.ds-vp-app .kf-swipe-hint.is-fading{ opacity: 0 !important; transition: opacity 280ms ease; }

  /* Reduced-motion: niente animation ticker + niente swipe hint pulse. */
  @media (prefers-reduced-motion: reduce){
    body.ds-vp-app .kf-list.kf-mobile.kf-ticker .kf-track{
      animation: none !important;
      transform: none !important;
    }
    body.ds-vp-app .kf-swipe-hint{
      animation: none !important;
      opacity: .85;
    }
  }

  /* ── Keyframes MARQUEE SEAMLESS (sess.26 Tier 1.5 refactor) ──
        Pattern ticker news/CNN/Bloomberg: track contiene 2 copie
        identiche delle pill (originali + cloni aria-hidden, gestiti da
        JS setupKfMobileTicker). Translate da 0 a -50% = esattamente 1
        lista intera. Al loop 100→0 la posizione visuale è IDENTICA
        (cloni a 100% = originali a 0%) → loop seamless invisibile.
        No ping-pong = no turning points = no pause percepite = no scatti.

        Storia decisione (per riferimento):
        - sess.23 BUG FIX: keyframe 3-stop (0/50/100) + normal direction
          = bullet-proof iOS Safari pause/resume (alternate perdeva state).
        - sess.26 Tier 1.4: linear → cubic-bezier per smussare turning.
        - sess.26 Tier 1.5 FINAL: refactor a marquee seamless (no turning
          points proprio) → linear è perfetto, no scatti.
        ── */
  /* Keyframe marquee seamless — sess.27 polish fix v2.
     Usa --kf-translate-end (distanza ESATTA inizio set1 → inizio set2,
     calcolata JS-side come Ncols*(colWidth+colGap) e settata GIA NEGATIVA
     come "-NNNpx"). Pattern noto-safe iOS Safari: translateX(var(...))
     diretto, senza calc() intorno (calc(-1 * var(...)) ha avuto bug
     intermittenti in iOS Safari 14-17).
     Fallback -50% per safety (browser senza var support / pre-JS-calc).
     Nota: var --kf-single-px (sess.27 p1) DEPRECATA, mantenuta solo come
     no-op legacy nel JS — il keyframe usa solo --kf-translate-end. */
  @keyframes kf-ticker-translate{
    from { transform: translateX(0); }
    to   { transform: translateX(var(--kf-translate-end, -50%)); }
  }
  /* Regola UNICA ticker animation (sess.26 Tier 1.5).
     Linear è ottimale per marquee seamless (velocità costante perfetta,
     no curva necessaria perché non ci sono turning points).
     Normal direction (no alternate) per iOS Safari bullet-proof. */
  body.ds-vp-app .kf-list.kf-mobile.kf-ticker .kf-track{
    animation: kf-ticker-translate var(--kf-cycle, 30s) linear infinite normal !important;
  }

  /* ── Hide hover/focus glow desktop su mobile (pattern coarse) ── */
  body.ds-vp-app .kf-list.kf-mobile .feature-pill:hover,
  body.ds-vp-app .kf-list.kf-mobile .feature-pill:focus{
    transform: none !important;
  }

  /* ── BUG FIX KF #4 sess.23: KF Modal v2 mobile fullscreen + sopra header.
        Status quo: #kf-modal-host z-index:1500, ma su mobile l'header (z 1000)
        appare SOPRA perché il modal non fa veramente fullscreen (limitato in
        altezza dal padding desktop). Fix: edge-to-edge + z 9000 + safe-area. ── */
  body.ds-vp-app #kf-modal-host{
    z-index: 9000 !important;       /* sopra header (1000), drawer (8990), tabs (1100) */
    padding: 0 !important;          /* annulla clamp(16px,4vw,40px) desktop */
    /* sess.24 STEP 0a: assicura inset 0 + 100dvh per fullscreen "in screen" */
    inset: 0 !important;
    width: 100vw !important;
    height: 100vh !important;       /* fallback */
    height: 100dvh !important;      /* iOS 15.4+ esclude UI chrome */
  }
  body.ds-vp-app #kf-modal-host .kfm-backdrop{
    background: rgba(8,6,4,.78) !important;
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
  }
  body.ds-vp-app #kf-modal-host .kfm-dialog{
    position: relative !important;        /* anchor per .kfm-close absolute */
    width: 100vw !important;
    /* sess.24 STEP 0a fix: 100vh → 100dvh (dynamic viewport height,
       iOS 15.4+). 100vh include UI chrome Safari (Dynamic Island top
       + url bar bottom) → modal strabordava sopra/sotto. dvh esclude
       UI chrome → fullscreen "in screen" perfetto. Fallback 100vh per
       browser vecchi (gracefully degrades). */
    height: 100vh !important;             /* fallback */
    height: 100dvh !important;            /* moderno, vince */
    max-width: 100vw !important;
    max-height: 100vh !important;
    max-height: 100dvh !important;
    min-height: 100vh !important;
    min-height: 100dvh !important;
    border-radius: 0 !important;
    margin: 0 !important;
    padding: 0 !important;                /* padding gestito dai children */
    padding-bottom: env(safe-area-inset-bottom, 0) !important;
    overflow-y: auto;
  }

  /* Close button v3.1 sess.23: position ABSOLUTE invece di FIXED.
     Bug iOS Safari noto: position:fixed dentro un parent con transform
     (animation scale-in) crea un nuovo containing block, il close diventa
     "fuori schermo". Position absolute relativo al .kfm-dialog è bullet-proof.
     Design: pill grigio scuro semi-trasparente, hover amber, no bordo. */
  body.ds-vp-app #kf-modal-host .kfm-close{
    position: absolute !important;
    top: calc(env(safe-area-inset-top, 0) + 14px) !important;
    right: 14px !important;
    width: 40px !important;
    height: 40px !important;
    border-radius: 50% !important;
    background: rgba(40,38,34,.72) !important;
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
    border: none !important;
    color: rgba(255,255,255,.92) !important;
    z-index: 10 !important;
    box-shadow: 0 2px 8px rgba(0,0,0,.18) !important;
    transition: background 180ms ease, transform 120ms ease, color 180ms ease;
  }
  body.ds-vp-app #kf-modal-host .kfm-close:hover{
    background: rgba(20,18,14,.88) !important;
    color: var(--kf-accent, #F4B532) !important;
  }
  body.ds-vp-app #kf-modal-host .kfm-close:active{
    transform: scale(.92);
  }
  /* SVG dimensione icona ridotta per match 40px button */
  body.ds-vp-app #kf-modal-host .kfm-close svg{
    width: 18px !important;
    height: 18px !important;
  }

  /* Header del modal: top padding generoso per non sembrare "tagliato".
     v3.2 sess.23 fix definitivo: padding-top 70px + safe-area, eyebrow visibile.
     Padding-right 60 per close button. Padding-bottom 14. */
  body.ds-vp-app #kf-modal-host .kfm-head{
    padding-top: calc(env(safe-area-inset-top, 0) + 70px) !important;
    padding-right: 60px !important;
    padding-left: 18px !important;
    padding-bottom: 14px !important;
  }
  /* Titolo feature più in evidenza — Oswald grande, no clipping */
  body.ds-vp-app #kf-modal-host .kfm-title{
    font: 700 1.05rem/1.25 "Oswald", system-ui, sans-serif !important;
    letter-spacing: .02em;
    text-transform: uppercase;
    overflow-wrap: break-word;
    word-wrap: break-word;
    hyphens: auto;
  }

  /* v3.2 sess.23 — Modal aperto NASCONDE la tab bar bottom (era visibile
     dietro a z 1100 ma rimaneva sempre visibile). Soluzione: aggiungo
     classe body.kf-modal-open quando modal aperto (gestita da JS).
     Inoltre il footer del modal (PREV/NEXT) ha padding-bottom safe-area. */
  body.ds-vp-app.kf-modal-open .ds-mobile-tabs,
  body.kf-modal-open .ds-mobile-tabs{
    display: none !important;
  }
  body.ds-vp-app #kf-modal-host .kfm-foot{
    padding-bottom: calc(env(safe-area-inset-bottom, 0) + 16px) !important;
    padding-top: 12px !important;
    background: rgba(20,18,14,.55) !important;
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
    position: sticky;
    bottom: 0;
  }
}

/* ============================================================
   STEP 3.5 (sess.23) — LEARN MORE TAXO MOBILE
   ============================================================
   Razionale (decisione utente sess.23): "testo descrizione e KF sono
   strettamente connessi concettualmente". Su mobile i 2 widget sono in
   flow naturale uno dopo l'altro (Hero → LearnMore → KF). Ottimizzazioni:
   - Padding compatto (portrait stretto: ogni px conta)
   - Bottoni Family/Range stack VERTICALE forzato (flex-wrap meno predicibile)
   - Tap target 56h (≥44 + comfort)
   - Backdrop-filter rimosso (perf mobile, niente sfondi complessi sotto)
   - Brand line gialla ::after sotto Learn More → connessione visiva con
     KF (replica del pattern desktop top-right-col card unificata)
   ============================================================ */
@media (max-width: 1399.98px) {

  /* Card padding compatto + niente blur (perf) */
  body.ds-vp-app #ds-learnmore-taxo{
    padding: 16px 18px !important;
    border-radius: 16px;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    box-shadow: 0 6px 16px rgba(0,0,0,.06) !important;
    margin-bottom: 0 !important;
    position: relative;       /* per ::after brand line */
  }

  /* Body dark mode: surface-1 tokens auto-reattivi (sess.25 task #3 step8).
     bg warm rgba(28,26,22,.62) rimosso → coerente con altri widget surface tier. */
  body.ds-vp-app.ds-dark #ds-learnmore-taxo{
    box-shadow: var(--ds-surface-1-shadow) !important;
  }

  /* Copy text leggibile portrait */
  body.ds-vp-app #ds-learnmore-taxo .ds-learnmore-copytext{
    font-size: 15px;
    line-height: 1.55;
  }

  /* Title "APPROFONDISCI" più discreto */
  body.ds-vp-app #ds-learnmore-taxo .ds-learnmore-title{
    font-size: 12px;
    margin-bottom: 6px;
  }

  /* Actions: stack VERTICALE forzato (più predicibile dello flex-wrap) */
  body.ds-vp-app #ds-learnmore-taxo .ds-learnmore-actions{
    flex-direction: column;
    gap: 8px;
  }

  /* Bottoni Family/Range full-width + tap target 56h */
  body.ds-vp-app #ds-learnmore-taxo .ds-taxo-btn{
    flex: 1 1 auto;
    width: 100%;
    min-height: 56px;
    padding: 10px 14px 10px 18px;
  }

  /* Brand line ::after (replica pattern desktop top-right-col) — conferma
     connessione concettuale: dopo "approfondisci" → "key features" sotto.
     Nota: learnmore è in #product-top-grid > .top-right-col, KF è in
     .product-main-grid (rami DOM diversi) → niente sibling combinator
     possibile. Il gap del main-grid sotto rimane, la brand line fa da
     hand-off visivo con direzione (gradient → fade right) */
  body.ds-vp-app #ds-learnmore-taxo::after{
    content: '';
    display: block;
    height: 2px;
    margin: 14px -2px -4px;
    background: linear-gradient(to right,
      var(--kf-accent, #F4B532) 45%,
      rgba(255,209,0,.18) 100%);
    border-radius: 2px;
  }
}

/* ============================================================
   SESS.24.6 — TAB BAR BOTTOM DISABLED (replaced by chip-nav top)
   ============================================================
   Markup #mobile-product-tabs è iniettato comunque (back-compat JS)
   ma forzato hidden. Le ex-classi setActiveTab continuano a girare
   (history pushState, swipe handler) ma senza effetto visivo.
   Da cleanup futuro: rimuovere markup + JS IIFE mobile tabs.
   ============================================================ */
@media (max-width: 1399.98px) {
  body.ds-vp-app .ds-mobile-tabs,
  body.ds-vp-app #mobile-product-tabs{
    display: none !important;
    visibility: hidden;
  }

  /* Page-shell: niente padding-bottom riservato per tab bar (ora assente),
     solo safe-area per home indicator iOS. */
  body.ds-vp-app .page-shell{
    padding-bottom: calc(env(safe-area-inset-bottom, 0) + 24px) !important;
  }

  /* FAB RFQ sticky: prima era offset sopra tab bar. Ora tab bar non c'è,
     FAB più in basso (resta sopra home indicator iOS). */
  body.ds-vp-app .ds-fab-rfq{
    bottom: calc(env(safe-area-inset-bottom, 0) + 16px) !important;
  }
}

/* ============================================================
   SESS.24.6 — MOBILE CHIP NAV (sticky top scroll-spy)
   ============================================================
   Sostituisce tab bar bottom mobile (sess.22 → deprecato sess.24.6).
   Pattern Polestar 4 / Apple Pro Display XDR / Linear: sticky chip-nav
   horizontal scrollable + scroll-spy IntersectionObserver. Pagina è
   superficie scrollabile continua, chip nav sticky top orienta.

   Razionale (audit_tabs_vs_scroll_mobile_s24.md):
   7/10 reference moderni 2024-2026 single-scroll page-level. Tab bar
   bottom B2B web = anti-pattern crescente (no app session, browser chrome
   bottom compete spazio, landscape rotture, deep link rotto, print rotto).

   Chip nav è iniettato via JS (main.js IIFE MOBILE CHIP NAV) in
   `<body>`. Selettore primary: `.ds-mobile-chipnav`.
   ============================================================ */
.ds-mobile-chipnav{
  display: none;  /* Default hidden, mostrato solo in regime app */
}

@media (max-width: 1399.98px) {

  /* sess.24.6 v3 post-feedback utente: chipnav ELIMINATA del tutto.
     Pattern Apple iPhone 16 Pro specs (single-scroll puro, no sub-nav).
     L'utente ha trovato l'estetica "disturbante" anche con reveal-after-hero.
     Pattern semplificato: single-scroll continuo, header + titlebar sticky,
     bottombar reveal-after-hero per conversion.

     Il markup #ds-mobile-chipnav resta iniettato JS in body (back-compat)
     ma forzato hidden via CSS. Cleanup futuro: rimuovere IIFE MOBILE CHIP NAV
     da main.js se confermato no rollback. */
  body.ds-vp-app .ds-mobile-chipnav{
    display: none !important;
  }

  /* Page-shell: ricalcola top padding senza chipnav (header + titlebar + buffer) */
  body.ds-vp-app .page-shell{
    --ds-mobile-app-top: calc(
      var(--ds-header-gap, 14px) +
      var(--ds-header-h, 66px) +
      var(--ds-mobile-titlebar-h, 72px) +
      24px
    ) !important;
  }

  /* sess.24.6 v3: HEADER SCROLL-AWARE HIDE.
     Su scroll-down il body riceve .is-tabs-hidden (script main.js linea 2003+).
     Sfrutto quella classe per nascondere header (slide up). Titlebar resta
     visible (è la personalità del prodotto: F7 + range attivo).
     Pattern Apple Safari iOS, Linear, Twitter X. */
  /* sess.24.6 v4 fix: transform su #site-header NON funziona (il .nav interno
     è position:fixed e scappa dal containing block). Regola obsoleta lasciata
     inerte. Vera regola transform su .nav è più sotto (v4 fix bug). */
  body.ds-vp-app #site-header{
    /* transition: transform .22s ease;  — obsoleto v4 */
  }
  /* Quando header sparisce, titlebar slitta up per rimanere sticky top
     senza buco vuoto. */
  body.ds-vp-app .ds-mobile-titlebar{
    transition: top .22s ease;
  }
  /* sess.24.6 v7 post-feedback: rimosso slittamento titlebar in is-tabs-hidden.
     Utente: "titlebar si nasconde ancora si vede una striscetta — o sparisce
     o resta". Soluzione A scelta: titlebar RESTA fissa (preserva CTA RFQ +
     range cambio durante scroll). Header sparisce sopra come prima.
     Spazio vuoto tra status bar e titlebar accettato come "header scomparso"
     pattern Polestar/Apple Pro. */
  /* body.ds-vp-app.is-tabs-hidden .ds-mobile-titlebar — slittamento rimosso */

  /* sess.24.6 v5 fix: FAB RFQ vecchio doppione → hidden in mobile.
     Era pattern sess.22 quando tab bar bottom esisteva e RFQ in basso aveva
     senso come "primary action". Ora CTA RFQ è dentro titlebar (sess.24.6 v4)
     → FAB ridondante, occupa spazio bottom screen inutilmente. */
  body.ds-vp-app .ds-fab-rfq{
    display: none !important;
  }
  /* Page-shell padding-top: senza header, ricalcola */
  body.ds-vp-app.is-tabs-hidden .page-shell{
    --ds-mobile-app-top: calc(
      var(--ds-header-gap, 14px) +
      var(--ds-mobile-titlebar-h, 72px) +
      24px
    ) !important;
  }

  /* sess.24.6 v3: FIX BUG titlebar sotto header.
     Il vecchio calc `var(--ds-header-h) + 18px` ignorava --ds-header-gap (14px)
     che è la distanza top a cui parte l'header (.nav position fixed top
     var(--ds-header-gap)). Header reale bottom = gap + altezza. Titlebar top
     deve partire da quel bordo. */
  body.ds-vp-app .ds-mobile-titlebar{
    top: calc(var(--ds-header-gap, 14px) + var(--ds-header-h, 66px) + 8px) !important;
  }

  /* sess.24.6 v4: CTA RFQ DENTRO TITLEBAR (sostituisce bottombar).
     Pattern Apple iPhone 16 Pro specs / Stripe. Posizione: dopo pill range.
     Pillola compatta yellow brand, icon + label "RFQ" (3 char compatti). */
  body.ds-vp-app .ds-mobile-titlebar__cta{
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    height: 32px;
    padding: 0 12px;
    border-radius: 999px;
    background: linear-gradient(180deg,
      var(--kf-accent, #F4B532),
      #F0BF00);
    color: #111;
    border: none;
    font: 700 .68rem/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow:
      inset 0 1px rgba(255,255,255,.62),
      0 2px 6px rgba(255,209,0,.32);
    transition: transform 80ms ease, background-color 180ms ease, box-shadow 180ms ease;
    -webkit-tap-highlight-color: transparent;
    align-self: flex-end;
    margin-bottom: 4px;
    margin-left: -2px;     /* leggero stretto vicino al pill range */
    flex: 0 0 auto;
  }
  body.ds-vp-app .ds-mobile-titlebar__cta:active{
    transform: scale(.96);
  }
  body.ds-vp-app .ds-mobile-titlebar__cta:hover{
    background: linear-gradient(180deg, #FFDB2E, #F4C400);
    box-shadow:
      inset 0 1px rgba(255,255,255,.72),
      0 4px 14px rgba(255,209,0,.42);
  }
  /* Dark mode: yellow resta ma adatta shadow */
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__cta{
    box-shadow:
      inset 0 1px rgba(255,255,255,.50),
      0 2px 8px rgba(255,209,0,.22);
  }
  /* SVG icon size */
  body.ds-vp-app .ds-mobile-titlebar__cta svg{
    width: 12px;
    height: 12px;
    flex-shrink: 0;
  }
  /* sess.33 Step 4c — Badge counter cart RFQ mobile. Bg nero contrast su pill gold,
     replica pattern .header-rfq__count desktop ma compatto. */

  /* ═══ LA PILL DIVISA — RFQ | CARRELLO (12/09/2026) ═══════════════════════
     David, dalla fiera: «il numero sarà toccabile ma non si riesce a
     prenderlo». Era uno span dentro il bottone. E un secondo pulsante a fianco
     non ci stava: a 390 px la titlebar e' gia' in overflow (486 px di
     contenuto, misurati).
     ⇒ Una pill sola divisa da un filo: a sinistra si configura, a destra si va
     al carrello. Il fondo dorato passa dal bottone al CONTENITORE, cosi' le due
     meta' sembrano un oggetto solo e si toccano come due bersagli distinti. */
  body.ds-vp-app .ds-mt-quote{
    display: inline-flex;
    align-items: stretch;
    height: 32px;
    border-radius: 999px;
    overflow: hidden;
    background: linear-gradient(180deg, var(--kf-accent, #F4B532), #F0BF00);
    box-shadow:
      inset 0 1px rgba(255,255,255,.62),
      0 2px 6px rgba(255,209,0,.32);
    align-self: flex-end;
    margin-bottom: 4px;
    flex-shrink: 0;
  }
  /* il bottone non e' piu' la pill: e' la sua meta' sinistra */
  body.ds-vp-app .ds-mt-quote .ds-mobile-titlebar__cta{
    height: 100%;
    margin: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    align-self: auto;
  }
  /* la meta' destra: il carrello. 44 px di larghezza minima — e' il bersaglio,
     non una decorazione. Il filo che le separa e' l'unico segno che servono. */
  body.ds-vp-app .ds-mt-cart{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-width: 46px;
    padding: 0 11px 0 10px;
    border-left: 1px solid rgba(0,0,0,.28);
    color: #111;
    text-decoration: none;
    font: 800 .70rem/1 "Oswald", system-ui, sans-serif;
    font-variant-numeric: tabular-nums;
    -webkit-tap-highlight-color: transparent;
  }
  body.ds-vp-app .ds-mt-cart[hidden]{ display: none !important; }
  body.ds-vp-app .ds-mt-cart:active{ background: rgba(0,0,0,.10); }
  body.ds-vp-app .ds-mt-cart svg{ flex: 0 0 auto; }
  body.ds-vp-app .ds-mt-cart .ds-mt-cta-count{
    /* niente piu' pastiglia nera: dentro la pill il numero e' gia' leggibile */
    min-width: 0;
    height: auto;
    padding: 0;
    margin: 0;
    background: none;
    box-shadow: none;
    color: #111;
    border-radius: 0;
    font: inherit;
    display: inline;
  }
  body.ds-vp-app.ds-dark .ds-mt-cart .ds-mt-cta-count{
    background: none;
    color: #111;
    box-shadow: none;
  }

  body.ds-vp-app .ds-mt-cta-count{
    /* 12/09/2026: da 16 a 26 px. Il badge non e' piu' solo un contatore —
       toccarlo porta al carrello (main.js, handler di #ds-mt-cta), e 16 px
       non e' un bersaglio: e' una speranza. */
    min-width: 26px;
    height: 26px;
    padding: 0 7px;
    cursor: pointer;
    margin-left: 2px;
    border-radius: 999px;
    display: inline-grid;
    place-items: center;
    background: #111;
    color: #F4B532;
    font: 800 10px/1 "Oswald", system-ui, sans-serif;
    letter-spacing: 0;
    box-shadow: inset 0 0 0 1px rgba(255,209,0,.55);
    flex-shrink: 0;
  }
  body.ds-vp-app .ds-mt-cta-count[hidden]{
    display: none !important;
  }
  /* Dark mode: invert pattern — bg gialla scura + text nero per contrasto su CTA */
  body.ds-vp-app.ds-dark .ds-mt-cta-count{
    background: #1A1410;
    color: #F4B532;
    box-shadow: inset 0 0 0 1px rgba(255,209,0,.45);
  }

  /* sess.24.6 v4: FIX BUG header che non sparisce.
     `#site-header` contiene `.nav position:fixed` che SCAPPA dal containing
     block. Transform su #site-header NON sposta .nav. Soluzione: transform
     direttamente su .nav. */
  body.ds-vp-app .nav{
    transition: transform .22s ease;
  }
  body.ds-vp-app.is-tabs-hidden .nav{
    transform: translateY(calc(-100% - var(--ds-header-gap, 14px) - 4px));
  }

  /* Regole originali chipnav lasciate sotto (inerti per display:none
     vincente più sopra). NO cleanup ora per non frammentare in caso
     rollback. Cleanup definitivo: vedi polish_backlog cleanup tab bar
     mobile DOM/JS. */
  body.ds-vp-app .ds-mobile-chipnav{
    /* le regole sotto sono lasciate per back-compat, vinte da display:none sopra */
    position: fixed;
    /* Sotto header (44 in landscape, 66 portrait) + titlebar
       (48 landscape, 72 portrait) + 8px gap. Calcolato via CSS var. */
    top: calc(var(--ds-header-h, 66px) + var(--ds-mobile-titlebar-h, 72px) + 18px);
    left: 10px;
    right: 10px;
    z-index: 989;   /* Sotto titlebar 990, sopra contenuto */
    gap: 6px;
    padding: 5px 8px;
    border-radius: 999px;
    background: rgba(255,255,255,.92);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
            backdrop-filter: blur(12px) saturate(140%);
    border: 1px solid rgba(0,0,0,.06);
    box-shadow: 0 4px 16px rgba(0,0,0,.06);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* sess.24.6 v2 post-feedback: chipnav nascosta finché hero in
       viewport (occupava spazio inutile sull'hero). Reveal via JS
       IntersectionObserver quando hero scrolled past (.is-revealed). */
    transform: translateY(calc(-100% - 20px));
    opacity: 0;
    pointer-events: none;
    transition: transform .28s cubic-bezier(.34, 1.56, .64, 1),
                opacity .2s ease;
  }
  body.ds-vp-app .ds-mobile-chipnav.is-revealed{
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }
  body.ds-vp-app .ds-mobile-chipnav::-webkit-scrollbar{ display: none; }

  /* Chip singolo */
  body.ds-vp-app .ds-mc-chip{
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 36px;
    padding: 6px 14px;
    border-radius: 999px;
    font: 600 .72rem/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .04em;
    text-transform: uppercase;
    text-decoration: none;
    color: rgba(18,18,18,.70);
    background: transparent;
    border: none;
    cursor: pointer;
    scroll-snap-align: center;
    transition: background 180ms ease, color 180ms ease, transform 80ms ease;
    -webkit-tap-highlight-color: transparent;
  }
  body.ds-vp-app .ds-mc-chip:active{ transform: scale(.96); }

  /* Stato attivo (scroll-spy) — yellow brand */
  body.ds-vp-app .ds-mc-chip.is-active{
    background: linear-gradient(180deg,
      var(--kf-accent, #F4B532),
      #F0BF00);
    color: #111;
    box-shadow:
      inset 0 1px rgba(255,255,255,.62),
      0 2px 8px rgba(0,0,0,.08);
  }

  /* Dark mode chip-nav */
  body.ds-vp-app.ds-dark .ds-mobile-chipnav{
    background: rgba(20,18,14,.88);
    border-color: rgba(255,255,255,.06);
    box-shadow: 0 4px 16px rgba(0,0,0,.32);
  }
  body.ds-vp-app.ds-dark .ds-mc-chip{
    color: rgba(218,214,206,.72);
  }
  body.ds-vp-app.ds-dark .ds-mc-chip.is-active{
    color: #111;
    /* Bg yellow resta (è il segnale interattivo brand) */
  }

  /* sess.24.6 v2: regola scroll-aware (is-tabs-hidden) rimossa per chipnav.
     Pattern semplificato: reveal-after-hero gestisce show/hide tramite
     IntersectionObserver. Aggiungere scroll-aware è ridondante e può
     confondere (chipnav che sparisce al scroll-down anche sotto hero). */

  /* Page-shell: aggiungi top padding per chipnav (era senza chipnav). */
  body.ds-vp-app .page-shell{
    --ds-mobile-app-top: calc(
      var(--ds-header-h, 66px) +
      var(--ds-mobile-titlebar-h, 72px) +
      48px +    /* chipnav height */
      24px      /* buffer */
    ) !important;
  }
}

/* Landscape phone override: chipnav comprimi altezza + top offset */
@media (max-width: 1099.98px) and (orientation: landscape) and (max-height: 500px) {
  body.ds-vp-app .ds-mobile-chipnav{
    top: calc(var(--ds-header-h, 44px) + var(--ds-mobile-titlebar-h, 48px) + 10px);
    padding: 4px 6px;
    gap: 5px;
  }
  body.ds-vp-app .ds-mc-chip{
    min-height: 30px;
    padding: 4px 10px;
    font-size: .65rem;
  }
  body.ds-vp-app .page-shell{
    --ds-mobile-app-top: calc(
      var(--ds-header-h, 44px) +
      var(--ds-mobile-titlebar-h, 48px) +
      38px +
      16px
    ) !important;
  }
}

/* ============================================================
   SESS.24.6 — MOBILE BOTTOM CTA BAR (sticky bottom, reveal post-hero)
   ============================================================
   Pattern Polestar 4 / Tesla Model S: sticky bottom CTA bar con
   "Datasheet PDF" (secondary) + "Request Quote" (primary). Reveal
   dopo hero scroll. Hide quando RFQ form è in viewport.
   ============================================================ */
.ds-mobile-bottombar{
  display: none;  /* Default hidden, mostrato solo in regime app */
}

@media (max-width: 1399.98px) {

  /* sess.24.6 v4 post-feedback utente: bottombar ELIMINATA.
     CTA RFQ spostata DENTRO titlebar (a destra dopo pill range).
     Pattern Apple iPhone 16 Pro specs / Stripe / Linear: nav top
     ospita CTA primaria, single-scroll puro, no chrome bottom.
     Risolve: bottombar occupava spazio landscape, datasheet rimandava
     a section download inconsistente.
     Markup #ds-mobile-bottombar resta iniettato JS (back-compat),
     hidden via CSS. JS IIFE da rimuovere in cleanup polish_backlog. */
  body.ds-vp-app .ds-mobile-bottombar,
  body.ds-vp-app #ds-mobile-bottombar{
    display: none !important;
  }

  /* Regole originali bottombar lasciate sotto (inerti, vinte da display:none): */
  body.ds-vp-app .ds-mobile-bottombar.sess24-6-v3-legacy-keep{
    display: flex;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1080;
    padding: 12px 14px calc(env(safe-area-inset-bottom, 0) + 12px);
    gap: 10px;
    align-items: center;
    background: rgba(255,255,255,.94);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
            backdrop-filter: blur(14px) saturate(140%);
    border-top: 1px solid rgba(0,0,0,.08);
    box-shadow: 0 -4px 20px rgba(0,0,0,.08);
    transform: translateY(110%);
    opacity: 0;
    pointer-events: none;
    transition: transform 280ms cubic-bezier(.34, 1.56, .64, 1),
                opacity 200ms ease;
  }
  body.ds-vp-app .ds-mobile-bottombar.is-visible{
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }

  /* Dark mode bottombar */
  body.ds-vp-app.ds-dark .ds-mobile-bottombar{
    background: rgba(20,18,14,.92);
    border-top-color: rgba(255,255,255,.06);
    box-shadow: 0 -4px 20px rgba(0,0,0,.32);
  }

  /* CTA buttons */
  body.ds-vp-app .ds-mb-cta{
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 48px;
    padding: 10px 16px;
    border-radius: 999px;
    font: 600 .82rem/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    border: none;
    transition: transform 80ms ease, background-color 180ms ease,
                box-shadow 180ms ease;
    -webkit-tap-highlight-color: transparent;
  }
  body.ds-vp-app .ds-mb-cta:active{ transform: scale(.96); }

  /* Primary: yellow brand */
  body.ds-vp-app .ds-mb-cta--primary{
    flex: 1.5 1 auto;
    background: linear-gradient(180deg,
      var(--kf-accent, #F4B532),
      #F0BF00);
    color: #111;
    box-shadow:
      inset 0 1px rgba(255,255,255,.62),
      0 4px 12px rgba(255,209,0,.32);
  }
  body.ds-vp-app .ds-mb-cta--primary:hover{
    background: linear-gradient(180deg, #FFDB2E, #F4C400);
  }

  /* Secondary: outline neutro */
  body.ds-vp-app .ds-mb-cta--secondary{
    background: transparent;
    color: rgba(18,18,18,.84);
    border: 1px solid rgba(0,0,0,.12);
  }
  body.ds-vp-app .ds-mb-cta--secondary:hover{
    background: rgba(0,0,0,.04);
  }
  body.ds-vp-app.ds-dark .ds-mb-cta--secondary{
    color: rgba(218,214,206,.86);
    border-color: rgba(255,255,255,.14);
  }
  body.ds-vp-app.ds-dark .ds-mb-cta--secondary:hover{
    background: rgba(255,255,255,.06);
  }

  /* Padding-bottom page-shell: aggiungi spazio per bottombar (~64h + safe area) */
  body.ds-vp-app .page-shell{
    /* sess.24.6 v4: bottombar OUT, padding-bottom solo per safe area iOS
       home indicator (era 96px reservato per bottombar height + safe). */
    padding-bottom: calc(env(safe-area-inset-bottom, 0) + 24px) !important;
  }
}

/* Landscape phone bottombar legacy (display:none vince, regole inerti) */
@media (max-width: 1099.98px) and (orientation: landscape) and (max-height: 500px) {
  body.ds-vp-app .page-shell{
    padding-bottom: calc(env(safe-area-inset-bottom, 0) + 16px) !important;
  }
}

/* ============================================================
   SESS.24.5 — LANDSCAPE PHONE OVERRIDE (Opzione Y)
   ============================================================
   Trigger: viewport <1100 + orientation landscape + max-height 500
   = iPhone landscape (SE 667×375, 14/15/16 852-874×393-402,
   16 Pro Max 956×440), foldables landscape (~841×673), Android
   flagship landscape (780-998×360-448).
   NON matcha: desktop browser landscape (h>500), iPad landscape
   (è >=1100 quindi compact-narrow), iPad portrait (orientation
   portrait), iPhone portrait (orientation portrait).

   Razionale: il regime `app` di default è progettato per portrait
   stretto (~390 wide × ~840 tall). Su landscape phone (874×402,
   956×440) le stesse regole producono "ibrido illeggibile":
   - tab bar 64h occupa 16% verticale (Apple HIG vieta in landscape)
   - hero `aspect-ratio: 1/1` produce quadrato gigante overflow 2x
   - title bar 72h + header + tab bar = chrome >70% schermo
   - menu nav full appare (TRUE BURGER STATE attiva solo a max-width:780)

   Audit: vedi memory file `audit_responsive_2025_landscape_phone_s24.md`.
   Pattern Apple HIG iOS 18 + Material 3 + Tailwind 3.4 landscape variant.
   ============================================================ */
@media (max-width: 1099.98px) and (orientation: landscape) and (max-height: 500px) {

  /* ── 1. HEADER: replica TRUE BURGER STATE (è in header.css solo per
        max-width 780, qui forziamo per landscape phone fino 1099) ── */
  body.ds-vp-app #site-header .menu{
    display: none !important;
  }
  body.ds-vp-app #site-header .menu-toggle{
    display: inline-grid !important;
    place-items: center !important;
    flex: 0 0 auto !important;
    order: 2 !important;
    width: 38px !important;
    min-width: 38px !important;
    height: 38px !important;
    min-height: 38px !important;
    margin-left: 8px !important;
    border-radius: 999px !important;
    border: 1px solid rgba(0,0,0,.10) !important;
    background:
      linear-gradient(180deg, rgba(255,255,255,.70), rgba(255,255,255,.42)) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.80),
      0 3px 10px rgba(0,0,0,.10) !important;
  }
  /* Dark mode: hamburger bg coerente con header dark */
  body.ds-vp-app.ds-dark #site-header .menu-toggle{
    background:
      linear-gradient(180deg, rgba(50,48,44,.62), rgba(28,26,22,.42)) !important;
    border-color: rgba(255,255,255,.10) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.06),
      0 3px 10px rgba(0,0,0,.32) !important;
  }
  body.ds-vp-app.ds-dark #site-header .menu-toggle__lines span{
    background: rgba(218,214,206,.92);
  }
  /* Header layout flex space-between (replica TRUE BURGER L1622-1646) */
  body.ds-vp-app #site-header .wrap{
    justify-content: space-between !important;
    align-items: center !important;
    gap: 10px !important;
    padding-inline: 12px !important;
  }
  body.ds-vp-app #site-header .brand{
    order: 1 !important;
    flex: 0 0 auto !important;
  }
  body.ds-vp-app #site-header .util{
    order: 3 !important;
    margin-left: auto !important;
    gap: 6px !important;
  }
  body.ds-vp-app #site-header .util::before{
    display: none !important;
  }

  /* ── 2. TAB BAR BOTTOM: OFF (Apple HIG vieta tab bar in landscape
        compact-width environment). Lo swipe-tab globale gestito da
        main.js initSwipeTabs è il modo per cambiare tab in landscape.
        Pattern Safari/Apple Mail landscape. ── */
  body.ds-vp-app .ds-mobile-tabs{
    display: none !important;
  }
  /* Page-shell: niente padding-bottom riservato per tab bar (ora assente) */
  body.ds-vp-app .page-shell{
    padding-bottom: env(safe-area-inset-bottom, 0) !important;
  }
  /* FAB RFQ posizione: senza tab bar non serve offsetto, sticky più in alto */
  body.ds-vp-app .ds-fab-rfq{
    bottom: calc(env(safe-area-inset-bottom, 0) + 14px) !important;
  }

  /* ── 3. TITLE BAR: comprimi 72→48 (compact-height environment).
        Chip F7 + pill range + eyebrow restano allineati ma più densi. ── */
  body.ds-vp-app{
    --ds-mobile-titlebar-h: 48px !important;
  }
  body.ds-vp-app .ds-mobile-titlebar{
    padding: 4px 14px !important;
    gap: 10px !important;
    /* sess.24.6 v10 post-feedback: titlebar slitta UP a top:gap quando
       header sparisce → spazio vuoto = gap normale (14px) anziché 64-88px.
       Pattern Apple Mail iOS landscape. Risolve "spazio doppio landscape"
       segnalato dall'utente, mantiene "ci sta" sensation portrait. */
    top: calc(var(--ds-header-gap, 14px) + var(--ds-header-h, 44px) + 6px) !important;
    transition: top .22s ease;
  }
  body.ds-vp-app.is-tabs-hidden .ds-mobile-titlebar{
    /* When header sparisce, titlebar slitta UP a gap-equivalent position.
       Spazio vuoto sopra = 14px cross-orientation, uniforme e ragionevole.
       sess.24.6 v11 fix: ANNULLA transform legacy sess.22 in product.css:729
       che faceva `translateY(-titlebar-h - 4px)` → titlebar usciva dal viewport
       sommandosi al top:14 di v10 (= -76 portrait / -52 landscape). */
    top: var(--ds-header-gap, 14px) !important;
    transform: none !important;
  }

  /* ─────────────────────────────────────────────────────────
     sess.24.6 v12 — DROPDOWN RANGE con grafica brand
     ─────────────────────────────────────────────────────────
     Pattern: ogni voce del dropdown è una "pill range" con onda
     tricolore Made in Italy (replica grafica della pill attiva
     in titlebar). Sostituisce voci testuali plain. Convergenza
     visiva range pill = identità prodotto su tutte le interfacce.
     ───────────────────────────────────────────────────────── */
  body.ds-vp-app .ds-mobile-titlebar__menu{
    /* sess.24.6 v14 fix: posizione centrata via margin-left negativo
       (pattern classic, no transform conflict). Width fissa 200px,
       left:50%, margin-left:-100px → centro esatto schermo viewport. */
    left: 50% !important;
    right: auto !important;
    width: 200px !important;
    margin-left: -100px !important;
    transform: scale(.95) !important;
    transform-origin: top center !important;
    padding: 8px !important;
    background: rgba(255,255,255,.96) !important;
    -webkit-backdrop-filter: blur(14px) saturate(140%);
            backdrop-filter: blur(14px) saturate(140%);
  }
  body.ds-vp-app .ds-mobile-titlebar__menu.is-open{
    transform: scale(1) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu{
    background: rgba(28,26,22,.94) !important;
    border-color: rgba(255,255,255,.10) !important;
  }

  /* Mini pill brand con onda tricolore (replica .ds-mobile-titlebar__range).
     sess.24.6 v13: testo CENTRATO + size uguale a active range pill.
     v14 fix: overflow:hidden !important per garantire clip onda tricolore
     (legacy product.css L704 button rule mancava !important → vinceva).  */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill{
    appearance: none !important;
    position: relative !important;
    isolation: isolate;
    overflow: hidden !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    height: 32px !important;
    padding: 0 12px !important;
    margin: 0 !important;
    border-radius: 999px !important;
    background: linear-gradient(180deg, rgba(255,255,255,1), rgba(255,255,255,.92)) !important;
    color: #111 !important;
    border: 1.5px solid var(--kf-accent, #F4B532) !important;
    font-family: "ConthraxSB", "Oswald", system-ui, sans-serif !important;
    font-weight: 700 !important;
    font-size: .72rem !important;
    line-height: 1 !important;
    text-transform: uppercase !important;
    letter-spacing: .04em !important;
    text-align: center !important;
    cursor: pointer;
    box-shadow:
      inset 0 1px rgba(255,255,255,.92),
      0 2px 6px rgba(0,0,0,.08) !important;
    transition: transform 80ms ease, background .15s ease, box-shadow .15s ease;
  }
  body.ds-vp-app .ds-mobile-titlebar__menu-pill:active{
    transform: scale(.96) !important;
  }
  body.ds-vp-app .ds-mobile-titlebar__menu-pill:hover{
    background: linear-gradient(180deg, rgba(255,255,255,1), rgba(255,250,220,.95)) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,1),
      0 4px 10px rgba(0,0,0,.12) !important;
  }

  /* Onda tricolore Made in Italy (replica pattern .ds-mobile-titlebar__range::after) */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill::after{
    content: "" !important;
    position: absolute !important;
    left: 6px !important;
    right: 6px !important;
    bottom: -3px !important;
    height: 50% !important;
    background: linear-gradient(90deg,
      #009246 0 33.333%,
      #ffffff 33.333% 66.666%,
      #CE2B37 66.666%) !important;
    clip-path: ellipse(120% 70% at 50% 115%);
    opacity: .95;
    filter: saturate(1.15);
    z-index: 0 !important;
    pointer-events: none;
  }

  /* Label sopra l'onda */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill .ds-mm-pill-label{
    position: relative;
    z-index: 1;
  }

  /* Stato attivo: bg yellow brand */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill.is-active{
    background: linear-gradient(180deg,
      var(--kf-accent, #F4B532),
      #F0BF00) !important;
    border-color: #C9A800 !important;
  }

  /* Dark mode mini pill */
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu-pill{
    background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.10)) !important;
    color: rgba(236,232,224,.95) !important;
    border-color: rgba(255,209,0,.85) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu-pill.is-active{
    background: linear-gradient(180deg,
      var(--kf-accent, #F4B532),
      #F0BF00) !important;
    color: #111 !important;
  }
  /* Chip F7: 40→32 high in landscape */
  body.ds-vp-app .ds-mobile-titlebar__title .t-code{
    height: 32px !important;
    min-width: 48px !important;
    padding: 0 10px !important;
    font-size: 1.05rem !important;
  }
  /* Eyebrow FRESNEL: nascosto in landscape phone (gap stretto, ridondante con chip) */
  body.ds-vp-app .ds-mobile-titlebar__title .t-eyebrow{
    display: none !important;
  }
  /* Pill range: 32→28 high */
  body.ds-vp-app .ds-mobile-titlebar__range{
    height: 28px !important;
    padding: 0 10px !important;
    font-size: .62rem !important;
    margin-bottom: 2px !important;
  }
  /* sess.24.6 v9: doppia linea sotto chip rimossa (vedi sopra in v9 commento).
     Era qui regola landscape override per posizione/altezza. */
  /* Aggiorna offset top page-shell per nuova altezza titlebar 48 */
  body.ds-vp-app .page-shell{
    --ds-mobile-app-top: calc(var(--ds-header-h, 44px) + 48px + 12px) !important;
  }

  /* ── 4. HEADER altezza: 66→44 (compact-height) ── */
  body.ds-vp-app{
    --ds-header-h: 44px !important;
  }

  /* ── 5. HERO: aspect-ratio 1/1 → 16/9 cinematic, max-height bounded.
        100svh (small viewport height) per evitare bug iOS Safari URL bar
        che fa 100vh > actual viewport. ── */
  body.ds-vp-app .hero .hero-stage{
    /* Fix v2 sess.24.5: aspect-ratio + max-height non funzionava perché
       width:100% del parent forzava height calcolata da aspect-ratio
       ignorando max-height. Soluzione: annulla aspect-ratio (1/1 sess.23
       portrait), imposta height direttamente.
       Sottraggo header 44 + titlebar 48 + safe margin 24 = 116px chrome. */
    aspect-ratio: auto !important;
    block-size: calc(100svh - 116px) !important;
    height: 70vh;                                  /* fallback no-svh */
    height: calc(100svh - 116px) !important;       /* svh override moderno */
    max-height: calc(100svh - 116px) !important;
    min-height: 0 !important;
    width: 100%;
  }
  /* Immagine interna: contain per non tagliare il prodotto */
  body.ds-vp-app .hero.is-image #hero-canvas img,
  body.ds-vp-app .hero.is-video #hero-canvas video{
    object-fit: contain !important;
    object-position: center !important;
  }

  /* ── 6. PADDING widget compatti (compact-height) ── */
  body.ds-vp-app #ds-learnmore-taxo{
    padding: 12px 14px !important;
  }
  body.ds-vp-app #ds-learnmore-taxo .ds-learnmore-copytext{
    font-size: 14px !important;
    line-height: 1.45 !important;
  }
  /* Bottoni Family/Range stack: ridotto 56→44 in landscape (più verticale prezioso) */
  body.ds-vp-app #ds-learnmore-taxo .ds-taxo-btn{
    min-height: 44px !important;
    padding: 8px 12px 8px 16px !important;
  }

  /* ── 7. SAFE AREA: env() insets per notch laterale iPhone landscape
        (Dynamic Island ruotata sul lato). Padding orizzontali rispettano
        safe area sia top che left/right. ── */
  body.ds-vp-app #site-header,
  body.ds-vp-app .ds-mobile-titlebar{
    padding-left: max(12px, env(safe-area-inset-left)) !important;
    padding-right: max(12px, env(safe-area-inset-right)) !important;
  }

  /* ── KF list mobile: in landscape lascia ledwall ticker funzionante ma
        compatto (1 row invece di 2). Verticale prezioso. ── */
  body.ds-vp-app .kf-list.kf-mobile .kf-track,
  body.ds-vp-app .kf-list.kf-mobile.kf-static{
    grid-template-rows: 1fr !important;   /* 1 row invece di 2 */
  }
  body.ds-vp-app .kf-list.kf-mobile .feature-pill{
    min-height: 44px !important;
    height: 44px !important;
  }

  /* ── HERO THUMBS LANDSCAPE: il default mobile aveva aspect-ratio 1
        (quadrato 164×164 a 874 wide × 5 thumb), strip 180px tall =
        45% schermo landscape. Fix: aspect-ratio 16/9 + height 48px,
        6 thumb invece di 5, gap compatto. Strip diventa ~58px (48 thumb
        + 10 padding) = 14% verticale, accettabile. ── */
  /* sess.24.6 v10 post-feedback: thumbs page hero landscape ulteriormente
     ridotti (era 32h, ancora "grandicelli"). Strip 9% → 7% viewport
     landscape (~28px tall total). */
  body.ds-vp-app .hero[data-tray="open"] .hero-dock .hero-dots button{
    aspect-ratio: 16 / 9 !important;
    --thumb-w: 100% !important;
    --thumb-h: 26px !important;
    inline-size: auto !important;
    block-size: auto !important;
    border-radius: 4px !important;
  }
  body.ds-vp-app .hero-dots{
    padding: 2px 6px !important;
    gap: 4px !important;
  }
  body.ds-vp-app .hero-dock{
    padding: 1px 0 !important;
  }

  /* sess.24.6 v7 post-feedback: thumbs LIGHTBOX landscape ridotti ancora.
     Era 56×36, ulteriormente compatto a 48×30 (rapporto 8:5). Stage stage
     guadagna ~6-8px verticale. Refactor proper "thumbs sidebar verticale"
     (pattern Apple Photos landscape) tracciato in polish_backlog per
     sess. dedicata lightbox. */
  body.ds-vp-app .ds-hl-thumb{
    width: 48px !important;
    height: 30px !important;
    border-radius: 5px !important;
  }
  body.ds-vp-app .ds-hl-thumbs{
    padding: 4px 8px !important;
    gap: 4px !important;
  }
  body.ds-vp-app .ds-hl-foot{
    padding: 3px 0 calc(env(safe-area-inset-bottom, 0) + 4px) !important;
  }
  body.ds-vp-app .hero-dots{
    grid-auto-columns: calc((100% - 24px - 5 * 6px) / 6) !important;
    gap: 6px !important;
    padding: 6px 10px !important;
  }
  body.ds-vp-app .hero-dock{
    padding: 4px 0 !important;
  }

  /* ── Portfolio hero: card avevano min-height 220px (default) che su
        landscape phone (402h) occupavano >50% schermo + meta padding generosi
        = bloccano lo scroll. Fix: card max-height 50svh (metà viewport),
        media aspect-ratio 16:9 condensato, meta compatti. ── */
  body.ds-vp-app .portfolio-hero .ph-item,
  body.ds-vp-app #portfolio-hero .ph-item{
    min-height: 0 !important;
    max-height: 50svh !important;
  }
  body.ds-vp-app .portfolio-hero .ph-media,
  body.ds-vp-app #portfolio-hero .ph-media{
    aspect-ratio: 16/9 !important;
    max-height: 38svh !important;
  }
  body.ds-vp-app .portfolio-hero .ph-media img,
  body.ds-vp-app #portfolio-hero .ph-media img{
    height: 100% !important;
    width: 100% !important;
    object-fit: cover !important;
  }
  body.ds-vp-app .portfolio-hero .ph-meta,
  body.ds-vp-app #portfolio-hero .ph-meta{
    padding: 8px 12px 10px !important;
    gap: 2px !important;
  }
  body.ds-vp-app .portfolio-hero .ph-name,
  body.ds-vp-app #portfolio-hero .ph-name{
    font-size: .85rem !important;
    -webkit-line-clamp: 1 !important;
  }
  body.ds-vp-app .portfolio-hero .ph-line,
  body.ds-vp-app #portfolio-hero .ph-line{
    font-size: .72rem !important;
  }
}

/* ============================================================
   TIER 0 SESS.26 — LAYOUT MOBILE UNIFORME TOKEN-DRIVEN
   ============================================================
   Decisione strategica sess.26 (post-audit design-led):
   regola SCENE-vs-DEVICE per container mobile.
   - Dispositivi-di-lavoro (Specs/DMX/Accessories/Downloads/Portfolio/
     RFQ) → container surface-1 (bordo + microelevation).
   - Scene/navigazione (Hero/KF ticker/Title bar) → stampati su body
     bg (full-bleed, no container).
   Inversione ordine KF↔LM: KF ticker (scena Ledwall) viene PRIMA
   di Learn More (primo container tecnico) per continuità hero→KF→
   datasheet, non hero→LM→KF→tecnica.
   Token applicati:
     --ds-mobile-body-px (14)            margin laterale uniforme
     --ds-mobile-gap-section (28)        gap verticale tra widget
     --ds-mobile-gap-section-tight (20)  gap coppie semantiche legate
     --ds-mobile-card-radius (16)        radius container surface-1
   Sintomi sess.25 risolti:
     ✓ Downloads "più stretto" → width override component-specific neutralizzato
     ✓ DMX "incolla a Specs" → gap-section-tight 20px in .dmx-dl-col flex
     ✓ Disparità visiva → radius uniforme 16px cross-widget
   ============================================================ */
@media (max-width: 1399.98px) {

  /* ── Ordine widget mobile (sess.26 Tier 0.4 — DECISIONE FINALE post-test) ──
     Sequenza visiva: Hero → Title bar sticky → Learn More container →
     buttons FAMIGLIA + RANGE → KF pills riassunto.
     Razionale (context proprietario emerso dopo test reale iPhone):
     le KF pills sono il RIASSUNTO VISIVO delle features di family + range
     attivo combinate → cadono semanticamente DOPO i pulsanti famiglia/range
     del Learn More. Pattern: hero+thumbs (scena media) → testo descrittivo
     (LM) → ingressi navigazione (famiglia/range cards) → digest visivo
     (KF pills) = combinato armonioso.
     Storia: sess.26 inizio aveva invertito KF prima di LM su input
     agent design-led ("KF scena Ledwall continua hero"). Test visivo iPhone
     ha mostrato che lo squilibrio con thumbs era forte e la regola
     scene-vs-device pura ignorava il context "KF=digest famiglia+range".
     Decisione: KF resta "scena stampata" (no container) ma posizionato
     come digest sotto i pulsanti taxo (DOM order naturale, no CSS order). */
  body.ds-vp-app #product-top-grid .top-right-col{
    /* gap intra-fascia top-right-col mobile = gap-section per coerenza
       con il flow widget sottostante (Photometrics, Specs, ...) */
    gap: var(--ds-mobile-gap-section) !important;
  }
  /* NO `order:` swap — DOM order naturale (LM appendChild prima, KF dopo
     in mountFamilyRangeLearnMore() main.js:493+511) = ordine voluto. */

  /* ── Width container token-uniformi ──
     Neutralizza disparità da regole component-specifiche (downloads,
     accessories, dmx, rfq) che usano `--_x` locale + `calc(100% - 64px)`,
     causando il sintomo "Downloads / RFQ più stretto" cross-widget mobile.
     Riassegniamo width: 100% (il parent .product-main-grid ha già
     margin laterale = body-px).
     sess.26 Tier 0.2: RFQ specificity boost — rfq.css:154-156 ha
     selector (0,1,3,1) che batteva il mio (0,1,2,0). Aggiunto override
     con specificity (0,1,4,1) per garantire victory. */
  body.ds-vp-app .product-main-grid > #photometrics,
  body.ds-vp-app .product-main-grid > #specs-card,
  body.ds-vp-app .product-main-grid > #accessories-widget,
  body.ds-vp-app .product-main-grid > #portfolio-hero,
  body.ds-vp-app .product-main-grid > #rfq-cta,
  body.ds-vp-app section#rfq-cta.section.widget.widget--glass-pro,
  body.ds-vp-app .dmx-dl-col > #dmx-widget,
  body.ds-vp-app .dmx-dl-col > #downloads-widget,
  body.ds-vp-app .downloads-card.widget.widget--glass-pro{
    width: 100% !important;
    max-width: 100% !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }

  /* Reset RFQ inner card width — la struttura nidificata di RFQ
     (#rfq-cta > .widget-content.rfq-wrap > .rfq-card > .rfq-wrap) può
     introdurre disparità interna. Forziamo le wrapper interne a 100%. */
  body.ds-vp-app #rfq-cta .widget-content.rfq-wrap,
  body.ds-vp-app section#rfq-cta.section.widget .rfq-card,
  body.ds-vp-app section#rfq-cta.section.widget .rfq-card .rfq-wrap{
    max-width: 100% !important;
    width: 100% !important;
  }

  /* ── Border-radius uniforme container surface-1 mobile ──
     Sotto i 18px desktop, 16 token coerente. */
  body.ds-vp-app .product-main-grid > #photometrics,
  body.ds-vp-app .product-main-grid > #specs-card,
  body.ds-vp-app .product-main-grid > #accessories-widget,
  body.ds-vp-app .product-main-grid > #downloads-widget,
  body.ds-vp-app .dmx-dl-col > #downloads-widget,
  body.ds-vp-app .product-main-grid > #rfq-cta,
  body.ds-vp-app .downloads-card.widget.widget--glass-pro{
    border-radius: var(--ds-mobile-card-radius) !important;
  }

  /* ── Gap interno .dmx-dl-col: coppia semantica legata (DMX-Downloads)
     usa gap-section-tight (20px) per legatura visiva.
     Tra .dmx-dl-col e widget sopra/sotto usa gap-section (28px)
     che è già gestito dal margin-top globale (product.css:316-326). */
  body.ds-vp-app .dmx-dl-col{
    display: flex !important;
    flex-direction: column !important;
    gap: var(--ds-mobile-gap-section-tight) !important;
  }
  /* Reset margin-top tra figli di .dmx-dl-col (DMX strip + Downloads):
     il gap del flex gestisce la spaziatura, non il margin-top globale. */
  body.ds-vp-app .dmx-dl-col > #dmx-widget,
  body.ds-vp-app .dmx-dl-col > #downloads-widget{
    margin-top: 0 !important;
  }

  /* ── SPECS margin-top specificity boost (sess.26 Tier 0.3) ──
     product.css:1894 `.product-main-grid > section#specs-card.specs-card`
     ha `margin: 0 !important` con specificity (0,1,2,1) che batteva il
     mio override `body.ds-vp-app #specs-card` (0,1,1,0) → Specs restava
     incollata a Photometrics nonostante gap-section 38px.
     Selettore qui (0,1,3,2) vince. */
  body.ds-vp-app .product-main-grid > section#specs-card.specs-card{
    margin-top: var(--ds-mobile-gap-section) !important;
  }

  /* ── PHOTOMETRICS margin-top TIGHT (sess.26 Tier 1.5) ──
     Photometrics è il PRIMO widget di .product-main-grid (dopo
     #product-top-grid che contiene hero + top-right-col con KF/LM).
     Feedback utente: spazio tra fine KF (ultima zona di top-right-col)
     e inizio container Photometrics percepito "troppo grande".
     Riduco margin-top da gap-section (38) a gap-section-tight (26)
     SOLO per Photometrics. Razionale scene-vs-device: KF è scena
     stampata → Photometrics e' device container → la scena introduce
     il device, legatura più stretta giustificata (pattern Apple Pro). */
  body.ds-vp-app .product-main-grid > #photometrics{
    margin-top: var(--ds-mobile-gap-section-tight) !important;
  }


  /* ─────────────────────────────────────────────────────────
     RANGE PILL DROPDOWN — pattern estetica pill cross-orientation
     ─────────────────────────────────────────────────────────
     Decisione sess.26: pill centrale (range attivo) + dropdown
     estetica pill sotto, allineato a desktop sticky e mobile
     landscape. Sostituisce ipotesi sess.25 v3 stack verticale.

     Le regole sotto (originali sess.24.6 v12-v14, prima attive
     solo dentro `@media landscape and max-height:500`) ora attive
     cross-orientation. Le regole landscape compact-height
     residue restano valide per override specifici (vincono per
     specificity media in casi limite).
     ───────────────────────────────────────────────────────── */
  body.ds-vp-app .ds-mobile-titlebar__menu{
    /* Centrato in viewport, sostituisce default right:14px */
    left: 50% !important;
    right: auto !important;
    width: 200px !important;
    margin-left: -100px !important;
    transform: scale(.95) !important;
    transform-origin: top center !important;
    padding: 8px !important;
    background: rgba(255,255,255,.96) !important;
    -webkit-backdrop-filter: blur(14px) saturate(140%);
            backdrop-filter: blur(14px) saturate(140%);
  }
  body.ds-vp-app .ds-mobile-titlebar__menu.is-open{
    transform: scale(1) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu{
    background: rgba(28,26,22,.94) !important;
    border-color: rgba(255,255,255,.10) !important;
  }

  /* Pill estetica con onda tricolore Made in Italy (replica della
     .ds-mobile-titlebar__range attiva — convergenza visiva range
     pill = identità prodotto su tutte le interfacce). */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill{
    appearance: none !important;
    position: relative !important;
    isolation: isolate;
    overflow: hidden !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    height: 32px !important;
    padding: 0 12px !important;
    margin: 0 !important;
    border-radius: 999px !important;
    background: linear-gradient(180deg, rgba(255,255,255,1), rgba(255,255,255,.92)) !important;
    color: #111 !important;
    border: 1.5px solid var(--kf-accent, #F4B532) !important;
    font-family: "ConthraxSB", "Oswald", system-ui, sans-serif !important;
    font-weight: 700 !important;
    font-size: .72rem !important;
    line-height: 1 !important;
    text-transform: uppercase !important;
    letter-spacing: .04em !important;
    text-align: center !important;
    cursor: pointer;
    box-shadow:
      inset 0 1px rgba(255,255,255,.92),
      0 2px 6px rgba(0,0,0,.08) !important;
    transition: transform 80ms ease, background .15s ease, box-shadow .15s ease;
  }
  body.ds-vp-app .ds-mobile-titlebar__menu-pill:active{
    transform: scale(var(--ds-mobile-tap-scale, .96)) !important;
  }
  body.ds-vp-app .ds-mobile-titlebar__menu-pill:hover{
    background: linear-gradient(180deg, rgba(255,255,255,1), rgba(255,250,220,.95)) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,1),
      0 4px 10px rgba(0,0,0,.12) !important;
  }

  /* Onda tricolore Made in Italy (replica pattern .ds-mobile-titlebar__range::after) */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill::after{
    content: "" !important;
    position: absolute !important;
    left: 6px !important;
    right: 6px !important;
    bottom: -3px !important;
    height: 50% !important;
    background: linear-gradient(90deg,
      #009246 0 33.333%,
      #ffffff 33.333% 66.666%,
      #CE2B37 66.666%) !important;
    clip-path: ellipse(120% 70% at 50% 115%);
    opacity: .95;
    filter: saturate(1.15);
    z-index: 0 !important;
    pointer-events: none;
  }

  /* Label sopra l'onda */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill .ds-mm-pill-label{
    position: relative;
    z-index: 1;
  }

  /* Stato attivo (pill range selezionata): bg yellow brand */
  body.ds-vp-app .ds-mobile-titlebar__menu-pill.is-active{
    background: linear-gradient(180deg, #FFE070, #F4B532) !important;
    box-shadow:
      inset 0 1px rgba(255,255,255,.85),
      0 0 0 1px rgba(244,181,50,.55),
      0 4px 12px rgba(244,181,50,.32) !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu-pill{
    background: linear-gradient(180deg, rgba(50,48,44,.92), rgba(28,26,22,.85)) !important;
    color: #E8E6E0 !important;
  }
  body.ds-vp-app.ds-dark .ds-mobile-titlebar__menu-pill.is-active{
    background: linear-gradient(180deg, #FFE070, #F4B532) !important;
    color: #1A1714 !important;
  }


  /* ─────────────────────────────────────────────────────────
     sess.26 Tier 0.1 — POST-FEEDBACK TEST MOBILE
     ─────────────────────────────────────────────────────────
     Fix dopo verifica visiva utente:
     1. Title "Key Features" h2 nascosto su mobile (replica
        regola desktop ≥1400 product.css:1706 — su mobile non
        scattava perché dentro media query desktop).
     2. Accent border logical full-row per tutti i widget
        surface-1 mobile (mobile single-column = ogni widget
        è "riga intera" semantica → bordo sx + dx). Override
        regole accent bars product.css:2192-2217 che danno
        solo sx a #specs-card e solo dx a #dmx/#downloads.
     3. Learn More entra in lista accent border (era escluso
        da regole accent bars desktop — open polish per
        verificare se omissione storica o voluta).
     ───────────────────────────────────────────────────────── */

  /* Title KF h2 nascosto mobile (le pills parlano da sole, come desktop) */
  body.ds-vp-app #key-features > h2{
    display: none !important;
  }

  /* Accent border full-row mobile single-column: sx + dx per tutti i
     container surface-1. Mantiene microelevation + inset border subtile. */
  body.ds-vp-app #specs-card,
  body.ds-vp-app #dmx-widget,
  body.ds-vp-app #downloads-widget,
  body.ds-vp-app .dmx-dl-col > #dmx-widget,
  body.ds-vp-app .dmx-dl-col > #downloads-widget,
  body.ds-vp-app #ds-learnmore-taxo,
  body.ds-vp-app .downloads-card.widget.widget--glass-pro{
    box-shadow:
      inset  3px 0 0 rgba(244,181,50,.48),
      inset -3px 0 0 rgba(244,181,50,.48),
      inset 0 0 0 1px rgba(0,0,0,.06),
      var(--ds-surface-1-shadow) !important;
  }

  /* Dark mode coerenza accent alpha .32 + inset border bianco */
  body.ds-vp-app.ds-dark #specs-card,
  body.ds-vp-app.ds-dark #dmx-widget,
  body.ds-vp-app.ds-dark #downloads-widget,
  body.ds-vp-app.ds-dark .dmx-dl-col > #dmx-widget,
  body.ds-vp-app.ds-dark .dmx-dl-col > #downloads-widget,
  body.ds-vp-app.ds-dark #ds-learnmore-taxo,
  body.ds-vp-app.ds-dark .downloads-card.widget.widget--glass-pro{
    box-shadow:
      inset  3px 0 0 rgba(244,181,50,.32),
      inset -3px 0 0 rgba(244,181,50,.32),
      inset 0 0 0 1px rgba(255,255,255,.10),
      var(--ds-surface-1-shadow) !important;
  }
}


/* ============================================================
   SUB-TIER — iPhone Pro Max / Plus landscape (≥900 width)
   ============================================================
   Pattern Apple Mail landscape Pro Max: abilita split-view 60/40
   (lista + dettaglio side-by-side). Ha senso solo a w ≥ 900 dove
   c'è spazio per 2 colonne leggibili (~540 + 360).
   iPhone Pro Max 956, Plus 932, Pixel 9 Pro XL 998, foldables 1080.
   Placeholder per ora — il pattern split-view richiede markup
   `.ds-content-split` che non esiste ancora nel DOM. Sarà attivato
   quando un widget specifico (es. specs table o hero+description)
   userà la classe per layout side-by-side.
   ============================================================ */
@media (max-width: 1099.98px) and (orientation: landscape) and (min-width: 900px) {
  body.ds-vp-app .ds-content-split{
    display: grid;
    grid-template-columns: 60fr 40fr;
    gap: 20px;
  }
}

/* ============================================================
   STEP 7 — DESKTOP slider beam styling globale (sess.27 Batch 1e + 1h)
   ============================================================
   Utente feedback: il slider .pm-ang-range (beam angle) appare con
   styling default browser BLU su desktop (visibile in pannello maximize sx).
   Forziamo styling gold consistente con mobile + thumb 16px (più piccolo
   di mobile 28 perché desktop ha mouse-precision input).
   Selettore NO body.ds-vp-app prefix → applica a TUTTI i regimi.
   Batch 1h: gradient usa --pm-ang-pct dinamico (settato JS in onAngChange
   + render) — barra gold riflette posizione thumb in tempo reale. */
#photometrics .pm-ang-range{
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 999px;
  background: linear-gradient(90deg,
    #F4B532 0%,
    #F4B532 var(--pm-ang-pct, 50%),
    rgba(255,255,255,.16) var(--pm-ang-pct, 50%),
    rgba(255,255,255,.16) 100%);
  outline: none;
}
#photometrics .pm-ang-range::-webkit-slider-thumb{
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #F4B532;
  border: 2px solid #1a1a1a;
  box-shadow: 0 1px 3px rgba(0,0,0,.32);
  cursor: pointer;
}
#photometrics .pm-ang-range::-moz-range-thumb{
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #F4B532;
  border: 2px solid #1a1a1a;
  box-shadow: 0 1px 3px rgba(0,0,0,.32);
  cursor: pointer;
}

/* ============================================================
   STEP 7 — PHOTOMETRICS IN-PAGE GLANCE WIDGET MOBILE (sess.27/28)
   ============================================================
   Brief: brief_step7_photometrics_mobile.md decisioni A-N.
   Architettura C ibrida: in-page glance widget (questa sez) +
   modal fullscreen "Tool esteso" (sess.28+ Batch 2+).

   In-page glance mobile = SOLO:
   - Header con CTA "⤢ Apri tool esteso" (placeholder Batch 1, attivo Batch 2)
   - Context line: F7 · FIXED WHITE · 160W
   - CCT dropdown native (sostituisce chip pill - preserva scelta utente 3200/5600)
   - Cono SVG semplificato, no markers numerici overlay
   - Slider distance singolo full-width
   - Metric boxes 2x2 (Illuminance / F-Stop / Ø Beam / Ø Field)

   NASCOSTO in-page mobile (vive solo nel modal fullscreen Batch 2+):
   - Toolbar destra (Metric/Imperial/Markers/Reset)
   - Stepper distance ▲▼
   - Pulsanti SPOT/MID/FLOOD
   - Slider beam angle
   - Label "FULL SPOT"
   ============================================================ */

/* ── Default DESKTOP: CTA mobile pill hidden (sess.29 fix bug residuo).
   I 2 button .pm-extend-cta + .pm-export-cta sono iniettati nel
   pm-header-row del widget in-page MA su desktop devono restare hidden
   (il widget desktop ha la sua toolbar maximize + export distinta).
   Bug pre-Anchor: la regola hide era DENTRO @media max-width:1099.98 →
   inerte su desktop (>=1100). Spostata FUORI come default global. */
#photometrics .pm-extend-cta,
#photometrics .pm-export-cta{
  display: none;
}

@media (max-width: 1399.98px) {

  /* ── 1. Header row con CTA "Apri tool esteso" ───────────── */
  /* sess.34 Opzione B Step 2: flex-wrap su narrow viewport per evitare
     wrap della label interna ("TOOL ESTESO" su 2 righe). I CTA hanno
     white-space:nowrap e flex-shrink:0 sotto → se non ci stanno con il
     titolo, wrappano sotto come gruppo intero. */
  body.ds-vp-app #photometrics .pm-header-row{
    display: flex !important;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 12px;
  }
  /* CTA "Tool esteso" + "Esporta" (sess.29 Batch 3.6) — pill identici stile.
     Esporta è sibling del Tool esteso, click → DSPhotometricsPageExport.open
     (export cono apparecchio attivo, distinto dall'export del maximize che
     esporta confronti).
     Sess.34 Opzione B convergenza: pattern B famiglia 3 (action trigger)
     outline gold ambra .45 hardcoded (var --ds-surface-2-border risolveva
     a grey neutro non gold). Color hardcoded light .78 / dark .88 per parity
     con Downloads/Specs. Vedi memory/feedback_button_family3_convergence.md */
  body.ds-vp-app #photometrics .pm-extend-cta,
  body.ds-vp-app #photometrics .pm-export-cta{
    display: inline-flex !important;
    align-items: center;
    gap: 6px;
    /* ⭐ V-93 (04/09/2026) — 36 era sotto la soglia touch, e non di poco.
       Misurati a 390: «TOOL ESTESO» 108,5×36 e «ESPORTA» 84,4×36. La soglia
       canonica e' 44: sotto quella misura il dito prende il vicino, e questi
       due pulsanti sono adiacenti con 12 px di gap. Non e' una preferenza
       estetica — e' la stessa soglia che il soft applica gia' alle sue pill
       (`photometrics-soft.css`, blocco < 1400).
       📌 Il CSS e' quello dello STAMPO (F7) e il soft lo eredita: un punto
       solo, due famiglie servite. Verificato che la riga non si rompe — il
       `pm-header-row` e' `flex-wrap: wrap` con `gap: 8px 12px`, quindi
       l'altezza cresce di 8 px e l'a capo del gruppo CTA resta quello di
       prima. */
    min-height: 44px;
    padding: 0 12px;
    border: 1px solid rgba(244,181,50,.45);
    border-radius: 999px;
    background: transparent;
    color: rgba(0,0,0,.78);
    font: 600 12px/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
    transition: background 160ms ease, border-color 160ms ease, color 160ms ease, opacity 160ms ease;
  }
  /* Placeholder state (Batch 1): visibile ma disabled, opacity ridotta */
  body.ds-vp-app #photometrics .pm-extend-cta[disabled],
  body.ds-vp-app #photometrics .pm-export-cta[disabled]{
    opacity: .42;
    cursor: not-allowed;
  }
  body.ds-vp-app #photometrics .pm-extend-cta:not([disabled]):active,
  body.ds-vp-app #photometrics .pm-export-cta:not([disabled]):active{
    transform: scale(.96);
    background: rgba(244,181,50,.12);
  }
  /* Dark mode — pattern B action mantiene outline gold + color light */
  body.ds-dark.ds-vp-app #photometrics .pm-extend-cta,
  body.ds-dark.ds-vp-app #photometrics .pm-export-cta{
    border-color: rgba(244,181,50,.45);
    color: rgba(255,255,255,.88);
  }
  /* Desktop: CTA non si vedono mai */
  #photometrics .pm-extend-cta,
  #photometrics .pm-export-cta{
    display: none;
  }

  /* ── 2. Hide SOLO toolbar destra (markers/units/reset) + mode label + divider
     sess.27 Batch 1c: presets (SPOT/MID/FLOOD) e beam slider RIPRISTINATI
     (utente feedback: zoom essenziale in-page, NON solo nel fullscreen).
     Distance stepper +/- ripristinato come tap target 44×44 (vs slider drop). */
  body.ds-vp-app #photometrics .pm-head-right,
  body.ds-vp-app #photometrics .pm-mode-label,
  body.ds-vp-app #photometrics .pm-ctrl-divider{
    display: none !important;
  }

  /* ── 3. CCT chip pill hidden mobile, DSDropdown visible ─
     I bottoni pill CCT restano nel DOM (delegate click target) ma
     display:none. Il wrapper `.pm-cct-mobile-select` è iniettato JS-side
     (vedi photometrics.js / photometrics-modal.js) e contiene un singolo
     `<button.pm-cct-mobile-trigger>` su cui DSDropdown.mount() costruisce
     il custom dropdown (styling globale in ds-dropdown.css).
     Sess.29 Batch 3c Heritage: regole condivise tra widget in-page
     (#photometrics) e widget modal (.dspmx) — selectors estesi con
     comma per coprire entrambi i contesti.
     Sess.36 cleanup: rimosse regole label/select native (post-refactor
     sess.35 DSDropdown — il `<select>` HTML non esiste più nel wrap,
     il `<label>` non viene più injettato). Solo il container styling
     resta vivo qui; il trigger button è styled da ds-dropdown.css. */
  body.ds-vp-app #photometrics .pm-cct .pill,
  body.ds-vp-app .dspmx       .pm-cct .pill{
    display: none !important;
  }
  body.ds-vp-app #photometrics .pm-cct-mobile-select,
  body.ds-vp-app .dspmx       .pm-cct-mobile-select{
    display: inline-flex !important;
    align-items: center;
    gap: 6px;
  }

  /* Desktop: wrapper mobile non si vede mai */
  #photometrics .pm-cct-mobile-select,
  .dspmx       .pm-cct-mobile-select{
    display: none;
  }

  /* ── 4. pm-head context: full-width senza pm-head-right ──── */
  body.ds-vp-app #photometrics .pm-head{
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 14px;
  }
  body.ds-vp-app #photometrics .pm-context{
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 8px;
    max-width: 100%;
  }
  body.ds-vp-app #photometrics .pm-cct{
    flex-basis: 100%;
    margin-top: 0;
  }

  /* ── 5. pm-ctrl-row: 1 RIGA SOLA — 3 elementi (sess.27 Batch 1g).
     Grid: distance | zoom | angle (slider only, no .pm-ang-val mobile)
     EXP button spostato in pm-context (era qui sovrapposto al slider).
     .pm-ang-val mobile hidden — info beam disponibile in merge cell 2. */
  body.ds-vp-app #photometrics .pm-ctrl-row{
    display: grid !important;
    grid-template-columns: auto auto 1fr;
    grid-template-rows: auto;
    grid-template-areas: "distance zoom angle";
    gap: 8px;
    margin-bottom: 14px;
    align-items: center;
  }
  body.ds-vp-app #photometrics .pm-dist-ctrl   { grid-area: distance; }
  body.ds-vp-app #photometrics .pm-presets     { display: none !important; }  /* sostituito da pm-zoom-mobile-select */
  body.ds-vp-app #photometrics .pm-zoom-mobile-select{ grid-area: zoom; }
  body.ds-vp-app #photometrics .pm-angle       { grid-area: angle; min-width: 0; }
  body.ds-vp-app #photometrics .pm-ctrl-divider{ display: none !important; }  /* divider | legacy hidden */
  /* sess.27 Batch 1g: hide .pm-ang-val mobile (degree label inline al slider).
     Info beam accessibile via merge cell 2 BEAM ANGLE tap-cycle. */
  body.ds-vp-app #photometrics .pm-angle .pm-ang-val{
    display: none !important;
  }

  /* Bottone EXP mobile — sess.27 Batch 1h v2: dentro pm-header-row,
     sibling sx del CTA "Tool esteso". Layout: [titolo] · [⚙] [↗ Tool esteso]
     Pm-header-row con space-between → titolo sx, button group dx. */
  body.ds-vp-app #photometrics .pm-header-row{
    display: flex !important;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  /* sess.28 STEP 7 refactor — unified EXP button.
     Su mobile riusiamo `.pm-exposure-btn` esistente (relocato in pm-header-row
     via JS + marcato data-pm-mobile-relocated). Rimosso `.pm-exp-mobile-btn`
     parallelo di sess.27 1f-1j (sync bugs). Lo `aria-expanded` è gestito da
     openPanel() in photometrics.js → single source of truth.
     Sess.34 Opzione B convergenza: pattern semi-toggle famiglia 3 outline
     gold ambra .45 hardcoded (var --ds-surface-2-border risolveva a grey
     neutro). Aria-expanded=true → gold subtle (settings open). Vedi
     memory/feedback_button_family3_convergence.md +
     memory/feedback_css_cascade_order_trap.md (dark is-active esplicito). */
  body.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated]{
    /* Sess.35 Step 2: refactor DSDropdown CCT/ZOOM elimina <select> native iOS,
       nuovo trigger DSDropdown a 44px universal touch target. EXP button match
       44px per align definitivo riga CCT (fix residuo sess.34 punto 1). */
    position: static !important;
    box-sizing: border-box !important;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 44px !important;
    min-height: 44px !important;
    padding: 0 12px;
    border: 1px solid rgba(244,181,50,.45) !important;
    border-radius: 999px;
    background: transparent !important;
    color: rgba(0,0,0,.85) !important;
    font: 600 13px/1 "Oswald", system-ui, sans-serif !important;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    flex-shrink: 0;
    white-space: nowrap;
    box-shadow: none !important;
    transition: background 160ms ease, color 160ms ease, border-color 160ms ease;
  }
  /* Stato attivo (aria-expanded=true): pill gold subtle, semi-toggle pattern
     famiglia 3 (action con feedback "open"). */
  body.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated][aria-expanded="true"]{
    background: rgba(244,181,50,.10) !important;
    color: #B5841F !important;
    border-color: rgba(244,181,50,.65) !important;
  }
  /* Dark mode pattern — color light + state aria-expanded esplicito
     (cascade-order trap: senza .is-active/[aria-expanded] dark esplicito,
     la regola dark base sovrascriverebbe lo stato attivo light). */
  body.ds-dark.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated]{
    border-color: rgba(244,181,50,.45) !important;
    color: rgba(255,255,255,.88) !important;
  }
  body.ds-dark.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated][aria-expanded="true"]{
    background: rgba(244,181,50,.14) !important;
    color: #F4B532 !important;
    border-color: rgba(244,181,50,.65) !important;
  }
  /* Hover gated — pointer-fine only (sticky_hover_ios) */
  @media (hover: hover){
    body.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated]:hover{
      background: rgba(244,181,50,.10) !important;
      border-color: rgba(244,181,50,.62) !important;
    }
  }
  body.ds-vp-app #photometrics .pm-exposure-btn[data-pm-mobile-relocated]:active{
    transform: scale(var(--ds-mobile-tap-scale, 0.96));
    transition: transform 80ms ease;
  }

  /* sess.27 Batch 1j — Exposure panel mobile (post-relocation a document.body).
     Il panel viene moved fuori dal subtree #photometrics al primo click EXP
     (data-pm-mobile-relocated="1"). Selettore senza #photometrics ancestor
     così la regola si applica anche dopo relocation. body.ds-vp-app limita
     comunque scope al regime mobile. */
  body.ds-vp-app .pm-exp-panel[data-pm-mobile-relocated].is-open{
    position: fixed !important;
    left: 50% !important;
    top: 50% !important;
    right: auto !important;
    transform: translate(-50%, -50%) !important;
    max-width: 92vw;
    width: 92vw;
    max-height: 80vh;
    overflow-y: auto;
    z-index: 9999;
    background: var(--ds-surface-1-bg, #141414);
    border: 1px solid rgba(244,181,50,.42) !important;
    border-radius: 14px;
    padding: 16px !important;
    box-shadow: 0 12px 40px rgba(0,0,0,.62) !important;
    /* Force display:block on top of base photometrics.css (.is-open già fa block) */
    display: block !important;
  }

  /* ZOOM dropdown mobile (analogo CCT dropdown — DSDropdown post sess.35).
     Compatto, riempie poco spazio orizzontale (utente: chips occupavano troppo).
     Sess.36 cleanup: rimosse regole label/select native (DSDropdown
     sostituisce il <select> con button.pm-zoom-mobile-trigger; lo styling
     del trigger è in ds-dropdown.css). */
  body.ds-vp-app #photometrics .pm-zoom-mobile-select{
    display: inline-flex !important;
    align-items: center;
    gap: 6px;
  }
  /* Desktop: zoom wrapper non si vede mai */
  #photometrics .pm-zoom-mobile-select{
    display: none;
  }

  /* ZOOM compact: chips SPOT/MID/FLOOD a sx + slider beam fianco a dx.
     sess.27 Batch 1d (utente feedback): pulsanti full-width 44h erano
     "ciccioni". Ora chips 36h compatti + slider beam si espande accanto.
     Layout row, 1 sola riga zoom + ang val a destra. */
  /* sess.27 Batch 1f: rimosso il display:flex su .pm-presets (era Batch 1c).
     Ora ci sono SOLO `display: none !important` (sopra linea 2679) per
     nascondere i chip SPOT/MID/FLOOD originali, rimpiazzati da
     pm-zoom-mobile-select dropdown. Style chips inutile = rimosso. */

  /* ZOOM beam slider continuo - bookmarks via chips a sx in stessa riga */
  body.ds-vp-app #photometrics .pm-angle{
    display: flex !important;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
  }
  body.ds-vp-app #photometrics .pm-angle .pm-ang-range{
    flex: 1;
    -webkit-appearance: none;
    appearance: none;
    height: 8px;
    border-radius: 999px;
    /* sess.27 Batch 1h: gradient dinamico via --pm-ang-pct (JS-side) */
    background: linear-gradient(90deg,
      #F4B532 0%,
      #F4B532 var(--pm-ang-pct, 50%),
      rgba(255,255,255,.16) var(--pm-ang-pct, 50%),
      rgba(255,255,255,.16) 100%);
    outline: none;
    touch-action: pan-y;
    margin: 0;
  }
  body.ds-vp-app #photometrics .pm-angle .pm-ang-range::-webkit-slider-thumb{
    -webkit-appearance: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #F4B532;
    border: 2px solid #1a1a1a;
    box-shadow: 0 2px 6px rgba(0,0,0,.32);
    cursor: pointer;
  }
  body.ds-vp-app #photometrics .pm-angle .pm-ang-range::-moz-range-thumb{
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #F4B532;
    border: 2px solid #1a1a1a;
    box-shadow: 0 2px 6px rgba(0,0,0,.32);
    cursor: pointer;
  }
  body.ds-vp-app #photometrics .pm-angle .pm-ang-val{
    min-width: 42px;
    text-align: right;
    font: 700 14px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.95);
  }

  /* DISTANCE stepper COMPACT [-] [3 m] [+] (sess.27 Batch 1e).
     Utente feedback: occupava troppa larghezza. Drop label "DISTANCE"
     + drop ".pm-dist-unit" external (unit inline col valore).
     Layout: [-] [3 m] [+] super-compact, 1 chunk inline. */
  body.ds-vp-app #photometrics .pm-dist-ctrl{
    display: flex !important;
    align-items: center;
    gap: 4px;
    border: none !important;
    background: transparent !important;
    padding: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
  /* Hide solo label "DISTANCE" — unit "m" rimane inline col valore.
     Layout finale: [-] [3] m [+] super compatto. */
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-label-sm{
    display: none !important;
  }
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-unit{
    display: inline !important;
    font: 600 12px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.62);
    flex-shrink: 0;
    margin: 0 2px;
    text-transform: lowercase;
  }
  /* Stepper buttons compact [-]/[+] 40×40 (sess.27 Batch 1e: ridotti da 44
     per stare in 1 riga con zoom dropdown + slider beam). Tap target 40
     accettabile per stepper secondario (Apple HIG min 40 acceptable). */
  body.ds-vp-app #photometrics .pm-dist-spin{
    display: contents !important;  /* sciogli wrapper, figli direttamente in flex */
  }
  body.ds-vp-app #photometrics .pm-dist-spin .pm-spin-btn{
    width: 40px;
    height: 40px;
    border-radius: 10px;
    border: 1px solid rgba(244,181,50,.32);
    background: transparent;
    color: rgba(218,214,206,.92);
    font: 700 16px/1 "Oswald", system-ui, sans-serif;
    cursor: pointer;
    font-size: 0;
    position: relative;
    flex-shrink: 0;
  }
  body.ds-vp-app #photometrics .pm-dist-spin .pm-spin-up::before{
    content: "+";
    font: 700 20px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.92);
  }
  body.ds-vp-app #photometrics .pm-dist-spin .pm-spin-dn::before{
    content: "−";
    font: 700 20px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.92);
  }
  body.ds-vp-app #photometrics .pm-dist-spin .pm-spin-dn{ order: -1; }  /* [-] sx */
  body.ds-vp-app #photometrics .pm-dist-spin .pm-spin-up{ order: 2; }   /* [+] dx, dopo input+unit */
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-val{
    order: 1;
    width: 44px;
    min-height: 40px;
    text-align: center;
    font: 700 16px/1 "Oswald", system-ui, sans-serif;
    border: none;
    outline: none;
    box-shadow: none;
    background: transparent;
    color: rgba(218,214,206,.95);
    -moz-appearance: textfield;
    -webkit-appearance: none;
    flex-shrink: 0;
  }
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-val:focus{
    outline: 2px solid rgba(244,181,50,.42);
    border-radius: 8px;
  }
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-val::-webkit-outer-spin-button,
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-val::-webkit-inner-spin-button{
    -webkit-appearance: none;
    margin: 0;
  }
  /* pm-dist-unit "m" — order 2 ma prima del [+] visivamente:
     in flex con order, l'unit sta tra value e [+]. */
  body.ds-vp-app #photometrics .pm-dist-ctrl .pm-dist-unit{
    order: 1; /* dopo input number, prima di [+] */
  }

  /* Drop slider distance mobile sess.27 Batch 1b (sostituito da stepper) */
  body.ds-vp-app #photometrics .pm-dist-mobile-slider,
  #photometrics .pm-dist-mobile-slider{
    display: none !important;
  }

  /* ── 6. Cono SVG portrait: width 100% + max-height 380 (sess.27 Batch 1f).
     Widget ora corto in altezza (4→2 celle + 1 row controlli), libero spazio
     verticale per il cono → max-height 300 → 380. Il SVG si scala fino al
     limite più stretto (width 100% portrait limita ancora) ma respira meglio. */
  body.ds-vp-app #photometrics .pm-cone-host{
    width: 100%;
    max-width: 100%;
    max-height: 380px;
    margin: 6px 0 14px 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  body.ds-vp-app #photometrics .pm-cone-host svg{
    width: 100%;
    height: auto;
    max-height: 380px;
  }
  /* MARKERS sempre attivi mobile (sess.27 Batch 1c). I .mk pills cliccabili
     restano visibili e funzionali (preview behavior st.preview_m).
     I .mk-info TEXT overlay (lux/f-stop/ØB/ØF sotto i pills) sono nascosti
     SOLO in portrait — illeggibili a quella scala. Le 4 metric boxes sotto
     mostrano comunque i valori della distanza selezionata. */
  body.ds-vp-app #photometrics .pm-cone-host svg .mk-info{
    display: none;
  }
  /* HIDE preset badge SVG mobile (sess.27 Batch 1e — utente feedback).
     Il pannello "FULL FLOOD / Beam X° / Field Y°" top-right del cono ruba
     spazio significativo al rendering del cono. Le stesse info sono già
     accessibili in merge cell 2 (Beam Angle / Field Angle via tap-cycle).
     Hide g.panel = rimuove rect badge + tutti i text label inside.
     Cono ora protagonista visivo, full-width visible area. */
  body.ds-vp-app #photometrics .pm-cone-host svg g.panel{
    display: none;
  }

  /* ── 7. METRIC BOXES: hide grid originale mobile (sess.27 Batch 1d).
     Le 4 boxes (Illuminamento/F-STOP/Ø Beam/Ø Field) restano nel DOM —
     la logica render() le popola con i valori correnti, e .pm-mobile-merge
     le legge per il tap-cycle display. Solo hidden visivamente mobile.
     sess.30 Commit C: stesso pattern esteso al modal "Tool esteso" .dspmx
     — pre-compare 4 cards → 2 merge cells tap-cycle (richiesta utente). */
  body.ds-vp-app #photometrics .pm-grid,
  body.ds-vp-app .dspmx .pm-grid{
    display: none !important;
  }
  /* Style nuove 2 celle merge mobile — grid 2 colonne uniformi.
     Tap-cycle: cella 1 = Illuminamento/F-Stop/EV/Candela.
                cella 2 = Beam Angle/Ø Beam/Field Angle/Ø Field.
     Indicator dots ●○○○ top-right via ::after content + dataset.mode. */
  body.ds-vp-app #photometrics .pm-mobile-merge,
  body.ds-vp-app .dspmx .pm-mobile-merge{
    display: grid !important;
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: minmax(110px, auto);
    gap: 10px;
    margin-top: 4px;
  }
  body.ds-vp-app #photometrics .pm-merge-cell,
  body.ds-vp-app .dspmx .pm-merge-cell{
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 14px 16px;
    border-radius: 12px;
    background: rgba(255,255,255,.04);
    border: 1px solid rgba(244,181,50,.18);
    cursor: pointer;
    user-select: none;
  }
  body.ds-vp-app #photometrics .pm-merge-cell:active,
  body.ds-vp-app .dspmx .pm-merge-cell:active{
    transform: scale(var(--ds-mobile-tap-scale, 0.96));
    transition: transform 80ms ease;
  }
  body.ds-vp-app #photometrics .pm-merge-cell .pm-merge-h,
  body.ds-vp-app .dspmx .pm-merge-cell .pm-merge-h{
    font: 500 11px/1.2 "Oswald", system-ui, sans-serif;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgba(218,214,206,.62);
    flex-shrink: 0;
  }
  body.ds-vp-app #photometrics .pm-merge-cell .pm-merge-val,
  body.ds-vp-app .dspmx .pm-merge-cell .pm-merge-val{
    font: 700 22px/1.1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.95);
    margin-top: auto;
  }
  body.ds-vp-app #photometrics .pm-merge-cell .pm-merge-val .pm-val-unit,
  body.ds-vp-app .dspmx .pm-merge-cell .pm-merge-val .pm-val-unit{
    font: 500 12px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.62);
    margin-left: 3px;
    /* sess.27 Batch 1e fix: unit (° / m / lux / EV / cd) era allineata
       a metà cifra → top-align per leggibilità. */
    vertical-align: top;
    position: relative;
    top: 3px;
  }
  /* Indicator dots ●○○○ top-right (4 modes per cella, dot count = 4) */
  body.ds-vp-app #photometrics .pm-merge-cell::after,
  body.ds-vp-app .dspmx .pm-merge-cell::after{
    content: "● ○ ○ ○";
    position: absolute;
    top: 8px;
    right: 10px;
    font: 600 9px/1 system-ui, sans-serif;
    letter-spacing: .12em;
    color: rgba(244,181,50,.72);
    pointer-events: none;
  }
  /* Cella 1 — 4 modes: illuminamento (default) / fstop / ev / candela */
  body.ds-vp-app #photometrics .pm-merge-cell-1[data-mode="fstop"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-1[data-mode="fstop"]::after        { content: "○ ● ○ ○"; }
  body.ds-vp-app #photometrics .pm-merge-cell-1[data-mode="ev"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-1[data-mode="ev"]::after           { content: "○ ○ ● ○"; }
  body.ds-vp-app #photometrics .pm-merge-cell-1[data-mode="candela"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-1[data-mode="candela"]::after      { content: "○ ○ ○ ●"; }
  /* Cella 2 — 4 modes: beamAngle (default) / diamBeam / fieldAngle / diamField */
  body.ds-vp-app #photometrics .pm-merge-cell-2[data-mode="diamBeam"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-2[data-mode="diamBeam"]::after     { content: "○ ● ○ ○"; }
  body.ds-vp-app #photometrics .pm-merge-cell-2[data-mode="fieldAngle"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-2[data-mode="fieldAngle"]::after   { content: "○ ○ ● ○"; }
  body.ds-vp-app #photometrics .pm-merge-cell-2[data-mode="diamField"]::after,
  body.ds-vp-app .dspmx .pm-merge-cell-2[data-mode="diamField"]::after    { content: "○ ○ ○ ●"; }
  /* Desktop: merge cells non si vedono mai (sia in-page sia modal). */
  #photometrics .pm-mobile-merge,
  .dspmx .pm-mobile-merge{
    display: none;
  }
  /* sess.27 Batch 1d: regole .pm-grid .pm-card mobile RIMOSSE — la grid
     originale è ora display:none mobile (rimpiazzata da .pm-mobile-merge).
     Le 4 boxes restano nel DOM per logica render() (popolano i valori
     che le merge cells leggono) ma non sono visibili mobile.
     Regole tap-cycle F-STOP su .pm-card.pm-fstop anch'esse obsolete
     (sostituite dal tap-cycle delle merge cells). */

  /* ── 8. LANDSCAPE SUB-TIER (sess.27 fix post-test): controlli 1 riga ─
     In landscape phone (max-width:1099.98 + orientation:landscape) il
     viewport h è basso (~415px iPhone Pro Max landscape). Compressione
     altezza widget = priorità. Pm-head + pm-ctrl-row diventano flex row.
     Cono ridotto a 200px max-height. */
}

@media (max-width: 1099.98px) and (orientation: landscape) {
  /* Pm-head: F7·FIXED WHITE·160W + CCT dropdown in 1 riga */
  body.ds-vp-app #photometrics .pm-head{
    flex-direction: row !important;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px 16px;
    margin-bottom: 10px;
  }
  body.ds-vp-app #photometrics .pm-cct{
    flex-basis: auto !important;
    margin-top: 0;
  }
  /* Pm-ctrl-row landscape: zoom chips + slider beam + stepper distance
     in 1 riga sola (utente feedback: sfruttare orizzontale per ridurre
     altezza widget — vincolo h limited landscape ~415px iPhone). */
  body.ds-vp-app #photometrics .pm-ctrl-row{
    flex-direction: row !important;
    align-items: center;
    gap: 12px;
    margin-bottom: 10px;
    flex-wrap: wrap;
  }
  body.ds-vp-app #photometrics .pm-presets{
    width: auto !important;
    flex-shrink: 0;
  }
  body.ds-vp-app #photometrics .pm-angle{
    flex: 1;
    min-width: 140px;
  }
  body.ds-vp-app #photometrics .pm-dist-ctrl{
    width: auto !important;
    flex-shrink: 0;
  }
  /* Cono ridotto in landscape per non strabordare height + ripristina
     width 100% (in landscape c'è spazio orizzontale, niente trick 70%) */
  body.ds-vp-app #photometrics .pm-cone-host{
    width: 100% !important;
    max-width: 100% !important;
    max-height: 200px !important;
  }
  body.ds-vp-app #photometrics .pm-cone-host svg{
    max-height: 200px !important;
  }
  /* Landscape: ripristina .mk-info text overlay (cono è più grande, sono leggibili) */
  body.ds-vp-app #photometrics .pm-cone-host svg .mk-info{
    display: inline;
  }
  /* Metric boxes mantengono 2x2 ma altezza ridotta */
  body.ds-vp-app #photometrics .pm-grid{
    grid-auto-rows: minmax(82px, auto) !important;
  }
}

/* ============================================================
   STEP 7 BATCH 2 — PHOTOMETRICS MODAL FULLSCREEN MOBILE (sess.29)
   ============================================================
   Strategia architettura: riusiamo il modal desktop esistente
   (.dspmx + window.DSPhotometricsMaximize.open) e via CSS lo
   trasformiamo in fullscreen 100dvh touch-sized su mobile. Zero
   duplicazione codice — tutta la logica compare/export/polling
   PM_STATE/listener language e units eredita gratis dal desktop.

   Trigger: tap su .pm-extend-cta (in-page glance widget mobile)
   → photometrics.js click handler chiama
   window.DSPhotometricsMaximize.open(root).

   Brief: brief_step7_photometrics_mobile.md decisioni A-N.
   Override per:
   - Layout fullscreen: 100vw × 100dvh, no padding/border/radius
   - Backdrop nascosto (frame copre tutto schermo)
   - Header sticky top con close 44×44 touch
   - Toolbar Compare/Export touch-sized 44h
   - Body scrollable, stage padding ridotto
   ============================================================ */
@media (max-width: 1399.98px) {

  /* ── Root: fullscreen, no padding intorno ──────────────── */
  body.ds-vp-app .dspmx{
    padding: 0 !important;
    /* Mantenuto z-index 9000 dal default — copre header sito + bottombar */
  }

  /* ── Backdrop: nascosto (frame copre tutto, no overlay visibile) ── */
  body.ds-vp-app .dspmx-backdrop{
    display: none !important;
  }

  /* ── Frame: 100vw × 100dvh, no chrome esterno ──────────── */
  body.ds-vp-app .dspmx-frame{
    width: 100vw !important;
    height: 100dvh !important;
    max-width: none !important;
    max-height: none !important;
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    /* ⭐ V-97 (04/09/2026) — «IL FRAME COPRE TUTTO» COPRIVA LA GEOMETRIA,
       NON LA LUCE.
       Il commento qui sopra («Backdrop nascosto — frame copre tutto schermo»)
       era vero per i pixel e falso per l'opacita': `--ds-c-modal-frame` vale
       `rgba(28,25,20,0.94)` — quel 6% su desktop mostra il BACKDROP sfocato
       che sta dietro, e in fascia app il backdrop e' `display:none`, quindi
       mostra la PAGINA. A 390 si leggevano il titolo prodotto e le pill del
       widget sotto attraverso il tool esteso.
       🔑 Non e' un difetto del soft: e' dello STAMPO. Riprodotto identico su
       F7 e su S2, che condividono `.dspmx-frame`. Una riga, due famiglie.
       ⛔ Si sceglie l'opacita' piena, non il ritorno del backdrop: il velo
       sfocato serve quando il modale e' una finestra su una pagina che si
       vede intorno — qui il modale E' lo schermo, e un blur a tutto campo su
       telefono e' solo lavoro per la GPU.
       📌 Il token nuovo sta accanto al suo gemello in `ds-tokens.css`: due
       letterali uguali sono due letterali che prima o poi divergono.
       ⚠️ I DUE fallback servono, e il secondo non basta da solo. I token del
       telaio sono dichiarati SOLO su `body.ds-dark` / `:root.ds-dark`: in
       tema light non esistono, e in una shorthand basta UN `var()` vuoto per
       invalidare l'INTERA dichiarazione — il fallback sull'altro non salva
       niente e lo sfondo torna trasparente. Misurato: con `body.ds-light` a
       390 il frame dava `rgba(0,0,0,0)` anche con il solo fallback sul
       colore. Con `none` sullo sheen la dichiarazione resta valida e il
       telaio diventa `--ds-c-bg-elevated`, cioe' opaco in tutti e due i temi.
       ⭐ R-01 (05/09/2026) — IL FALLBACK CHIARO ERA IL COLORE SBAGLIATO.
       `--ds-c-bg-elevated` in light e' BIANCO, e il contenuto del modale e'
       disegnato per il dark: opaco si', ma illeggibile (F7 e S2, 390 · 1150).
       La domanda rinviata qui sopra («che colore ha un modale in tema light»)
       aveva gia' una risposta nel desktop: SCURO — li' il backdrop all'82% fa
       da fondo e il modale si legge scuro anche a tema chiaro.
       ⇒ La cura NON sta in questa riga: sta in `ds-tokens.css`, dove il
       blocco `body.ds-vp-app` dichiara i token del telaio per tutti e due i
       temi. Cosi' il `var()` qui sotto trova sempre il suo valore e il
       fallback chiaro non scatta piu' — resta scritto come rete, non come
       colore previsto.
       📌 Su DESKTOP light `.dspmx-frame` e `.pmsx__frame` restano trasparenti
       (V-100): li' il backdrop c'e' e maschera, e la scelta e' di David. */
    background:
      var(--ds-c-modal-sheen, none),
      var(--ds-c-modal-frame-solid, var(--ds-c-bg-elevated)) !important;
    /* Open animation scale → translateY più nativo iOS */
    transform: translateY(20px);
    transition: transform 320ms cubic-bezier(.22, 1, .36, 1);
  }
  body.ds-vp-app .dspmx.is-open .dspmx-frame{
    transform: translateY(0) !important;
  }
  body.ds-vp-app .dspmx.is-closing .dspmx-frame{
    transform: translateY(20px) !important;
    transition: transform 220ms cubic-bezier(.22, 1, .36, 1);
  }

  /* ── Head: sticky top, padding compatto + safe-area iOS ── */
  body.ds-vp-app .dspmx-head{
    padding: 10px 14px 10px;
    padding-top: calc(10px + env(safe-area-inset-top, 0px));
    gap: 10px;
    /* sticky non serve perché .dspmx-body è flex column con head flex-shrink:0 */
  }
  body.ds-vp-app .dspmx-eyebrow{
    font-size: .58rem;
    letter-spacing: .12em;
  }
  body.ds-vp-app .dspmx-title{
    font-size: 1rem;
    letter-spacing: .03em;
  }

  /* ── Toolbar Compare/Export: touch-sized 44h ────────────── */
  body.ds-vp-app .dspmx-toolbar{
    gap: 6px;
  }
  body.ds-vp-app .dspmx-tb-btn{
    height: 44px;
    min-width: 44px;
    padding: 0 14px;
    font-size: .76rem;
  }
  body.ds-vp-app .dspmx-tb-btn svg{
    width: 16px;
    height: 16px;
  }

  /* ── Close: 44×44 touch target ──────────────────────────── */
  body.ds-vp-app .dspmx-close{
    width: 44px;
    height: 44px;
  }
  body.ds-vp-app .dspmx-close svg{
    width: 22px;
    height: 22px;
  }

  /* ── Body: portrait stack column (compare panel sotto stage)
        Su landscape large rimane row come desktop (gestito sotto). */
  body.ds-vp-app .dspmx-body{
    flex-direction: column;
  }

  /* ── Main: padding minimo, scroll naturale + bottom safe-area ── */
  body.ds-vp-app .dspmx-main{
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* iOS Safari: prevent rubber-band exposing page below */
    overscroll-behavior: contain;
  }

  /* ── Stage: padding ridotto ─────────────────────────────── */
  body.ds-vp-app .dspmx-stage{
    padding: 12px 14px 8px;
  }

  /* ── Compare panel laterale → bottom-sheet style su portrait
        (Batch 4 evolverà a vero bottom-sheet con snap; per ora
        nasconde la transformazione laterale).
        NOTA: il widget interno è in evoluzione (sess.29 Batch 3+).
        Per ora il panel quando aperto occupa full-width stack sotto
        il main, scrollabile naturalmente. */
  body.ds-vp-app .dspmx-compare-panel{
    width: 100% !important;
    max-width: 100% !important;
    border-left: none !important;
    border-top: 1px solid rgba(255,255,255,.08);
  }

  /* ── Title wrap: gap ridotto per height compatta ─────────── */
  body.ds-vp-app .dspmx-title-wrap{
    gap: 2px;
  }
}

/* ── Landscape sub-tier (max-height 500px iPhone landscape) ── */
@media (max-width: 1099.98px) and (orientation: landscape) and (max-height: 500px) {
  /* Head ancora più compatto in landscape phone (height ~415px) */
  body.ds-vp-app .dspmx-head{
    padding: 6px 12px 6px;
    padding-top: calc(6px + env(safe-area-inset-top, 0px));
  }
  body.ds-vp-app .dspmx-eyebrow{
    display: none; /* nascondi eyebrow in landscape: solo titolo */
  }
  body.ds-vp-app .dspmx-title{
    font-size: .92rem;
  }
  body.ds-vp-app .dspmx-close,
  body.ds-vp-app .dspmx-tb-btn{
    height: 36px; /* sub-tier: ridotto a 36 per accomodare h limited */
    min-width: 36px;
  }
  body.ds-vp-app .dspmx-close{
    width: 36px;
  }
  /* Stage in landscape: padding compatto */
  body.ds-vp-app .dspmx-stage{
    padding: 8px 14px 4px;
  }
}

/* ============================================================
   STEP 7 BATCH 2c — MODAL-WIDGET INTERNAL CONTROLS TOUCH-SIZE
   ============================================================
   Il widget interno del modal (.dspmx .pm) è una replica del
   widget in-page ma con id container diverso (#ds-photometrics-
   modal-host) → le regole STEP 7 in-page (gated su #photometrics)
   NON si applicano qui. Scope dedicato body.ds-vp-app .dspmx .pm-*.

   Brief decisione K: stepper +/- 44 minimo, slider thumb 28-32,
   chip 44h, pill segmented 44h. Mantengo layout desktop "row"
   per .pm-ctrl-row, ma con touch-sizing.
   ============================================================ */
@media (max-width: 1399.98px) {

  /* ── Stepper distance: replica pattern in-page mobile sess.27 Batch 1e.
     Sess.29 Batch 3.6 (utente feedback): "controllo distanza in maximize è
     quello desktop mentre per mobile abbiamo adottato pulsanti differenti".
     Pattern finale: [-] [3] m [+] super-compatto in 1 chunk inline.
     - .pm-dist-ctrl: flex transparent (no border/bg/shadow desktop)
     - .pm-dist-label-sm: hidden (label "DISTANCE" testo)
     - .pm-dist-spin: display:contents (sciogli wrapper, figli direttamente)
     - .pm-spin-btn: 40×40 con content "+"/"−" via ::before
     - .pm-spin-dn order: -1 (sx) / .pm-spin-up order: 2 (dx)
     - .pm-dist-val: order:1, width 44, font 16 center
     - .pm-dist-unit: inline tra value e [+]
     Regole identiche al widget in-page (line 2873-2960) — selettore .dspmx. */
  body.ds-vp-app .dspmx .pm-dist-ctrl{
    display: flex !important;
    align-items: center;
    gap: 4px;
    border: none !important;
    background: transparent !important;
    padding: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-label-sm{
    display: none !important;
  }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-unit{
    display: inline !important;
    font: 600 12px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.62);
    flex-shrink: 0;
    margin: 0 2px;
    text-transform: lowercase;
  }
  body.ds-vp-app .dspmx .pm-dist-spin{
    display: contents !important;
  }
  body.ds-vp-app .dspmx .pm-dist-spin .pm-spin-btn{
    width: 40px;
    height: 40px;
    border-radius: 10px;
    border: 1px solid rgba(244,181,50,.32);
    background: transparent;
    color: rgba(218,214,206,.92);
    font-size: 0;
    position: relative;
    flex-shrink: 0;
    cursor: pointer;
  }
  body.ds-vp-app .dspmx .pm-dist-spin .pm-spin-up::before{
    content: "+";
    font: 700 20px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.92);
  }
  body.ds-vp-app .dspmx .pm-dist-spin .pm-spin-dn::before{
    content: "−";
    font: 700 20px/1 "Oswald", system-ui, sans-serif;
    color: rgba(218,214,206,.92);
  }
  body.ds-vp-app .dspmx .pm-dist-spin .pm-spin-dn{ order: -1; }
  body.ds-vp-app .dspmx .pm-dist-spin .pm-spin-up{ order: 2; }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-val{
    order: 1;
    width: 44px;
    min-height: 40px;
    text-align: center;
    font: 700 16px/1 "Oswald", system-ui, sans-serif;
    border: none;
    outline: none;
    box-shadow: none;
    background: transparent;
    color: rgba(218,214,206,.95);
    -moz-appearance: textfield;
    -webkit-appearance: none;
    flex-shrink: 0;
  }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-val:focus{
    outline: 2px solid rgba(244,181,50,.42);
    border-radius: 8px;
  }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-val::-webkit-outer-spin-button,
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-val::-webkit-inner-spin-button{
    -webkit-appearance: none;
    margin: 0;
  }
  body.ds-vp-app .dspmx .pm-dist-ctrl .pm-dist-unit{
    order: 1; /* dopo input number, prima di [+] */
  }

  /* ── Slider beam angle: thumb 28, track 7 (Apple HIG) ────── */
  body.ds-vp-app .dspmx .pm-angle{
    height: 44px;
    gap: 10px;
  }
  body.ds-vp-app .dspmx .pm-ang-range{
    height: 7px;
  }
  body.ds-vp-app .dspmx .pm-ang-range::-webkit-slider-thumb{
    width: 28px;
    height: 28px;
    border-width: 3px;
  }
  body.ds-vp-app .dspmx .pm-ang-range::-moz-range-thumb{
    width: 28px;
    height: 28px;
    border-width: 3px;
  }
  body.ds-vp-app .dspmx .pm-ang-val{
    font-size: 15px;
    min-width: 44px;
  }

  /* ── Pill buttons (.pm-units, .pm-presets, .pm-cct, .pm-toggle-btn) ── */
  body.ds-vp-app .dspmx .pm-units .pill,
  body.ds-vp-app .dspmx .pm-presets .pill,
  body.ds-vp-app .dspmx .pm-cct .pill,
  body.ds-vp-app .dspmx .pm-toggle-btn{
    min-height: 40px;
    padding: 0 14px;
    font-size: 13px;
  }
  body.ds-vp-app .dspmx .pm-units{
    flex-wrap: wrap;
    gap: 6px;
  }
  body.ds-vp-app .dspmx .pm-cct{
    flex-wrap: wrap;
    gap: 6px;
  }

  /* ── sess.35 Step 4: UNITS METRIC/IMPERIAL → dropdown unico mobile.
     I 2 button .pill[data-units] restano nel DOM per preservare listener
     esistenti, ma sono hidden via CSS — il dropdown .pm-units-mobile-select
     li sostituisce visivamente. */
  body.ds-vp-app .dspmx .pm-units .pill[data-units]{
    display: none !important;
  }
  body.ds-vp-app .dspmx .pm-units-mobile-select{
    display: inline-flex !important;
    align-items: center;
  }

  /* ── Sess.34 Opzione B Step 4: convergenza famiglia 3 modal photometrics ─
     Pattern A (toggle) per pm-units .pill (METRIC/IMPERIAL), pm-markers,
     pm-exposure-btn (semi-toggle aria-expanded) → outline gold .45 base +
     active fill #FFD100 (parity Downloads/Specs/in-page convergenza).
     Pattern B (action) per pm-reset-btn → outline gold .45, no active fill.
     Memory: feedback_button_family3_convergence.md +
     feedback_css_cascade_order_trap.md (dark .is-active esplicito). */
  body.ds-vp-app .dspmx .pm-units .pill,
  body.ds-vp-app .dspmx .pm .pill.pm-markers,
  body.ds-vp-app .dspmx .pm .pill.pm-reset-btn,
  body.ds-vp-app .dspmx .pm-exposure-btn{
    background: transparent !important;
    border: 1px solid rgba(244,181,50,.45) !important;
    color: rgba(0,0,0,.78) !important;
    box-shadow: none !important;
  }
  /* Active toggle (METRIC/IMPERIAL/MARKERS): fill gold brand #FFD100 */
  body.ds-vp-app .dspmx .pm-units .pill.active,
  body.ds-vp-app .dspmx .pm-units .pill[aria-pressed="true"],
  body.ds-vp-app .dspmx .pm .pill.pm-markers.active,
  body.ds-vp-app .dspmx .pm .pill.pm-markers[aria-pressed="true"]{
    background: #F4B532 !important;
    border-color: #C9A800 !important;
    color: #111 !important;
  }
  /* Semi-toggle EXPOSURE (aria-expanded=true): gold subtle no fill */
  body.ds-vp-app .dspmx .pm-exposure-btn[aria-expanded="true"]{
    background: rgba(244,181,50,.10) !important;
    color: #B5841F !important;
    border-color: rgba(244,181,50,.65) !important;
  }
  /* Action trigger pm-reset-btn — :active feedback minimo (no toggle fill) */
  body.ds-vp-app .dspmx .pm .pill.pm-reset-btn:active{
    transform: scale(.96);
    background: rgba(244,181,50,.12) !important;
  }
  /* Dark mode — pattern dark + state esplicito (cascade order trap) */
  body.ds-dark.ds-vp-app .dspmx .pm-units .pill,
  body.ds-dark.ds-vp-app .dspmx .pm .pill.pm-markers,
  body.ds-dark.ds-vp-app .dspmx .pm .pill.pm-reset-btn,
  body.ds-dark.ds-vp-app .dspmx .pm-exposure-btn{
    background: transparent !important;
    border-color: rgba(244,181,50,.45) !important;
    color: rgba(255,255,255,.88) !important;
  }
  body.ds-dark.ds-vp-app .dspmx .pm-units .pill.active,
  body.ds-dark.ds-vp-app .dspmx .pm-units .pill[aria-pressed="true"],
  body.ds-dark.ds-vp-app .dspmx .pm .pill.pm-markers.active,
  body.ds-dark.ds-vp-app .dspmx .pm .pill.pm-markers[aria-pressed="true"]{
    background: #F4B532 !important;
    border-color: #C9A800 !important;
    color: #111 !important;
  }
  body.ds-dark.ds-vp-app .dspmx .pm-exposure-btn[aria-expanded="true"]{
    background: rgba(244,181,50,.14) !important;
    color: #F4B532 !important;
    border-color: rgba(244,181,50,.65) !important;
  }
  /* Hover gated (sticky_hover_ios) */
  @media (hover: hover){
    body.ds-vp-app .dspmx .pm-units .pill:not(.active):not([aria-pressed="true"]):hover,
    body.ds-vp-app .dspmx .pm .pill.pm-markers:not(.active):not([aria-pressed="true"]):hover,
    body.ds-vp-app .dspmx .pm .pill.pm-reset-btn:hover,
    body.ds-vp-app .dspmx .pm-exposure-btn:not([aria-expanded="true"]):hover{
      background: rgba(244,181,50,.10) !important;
      border-color: rgba(244,181,50,.62) !important;
    }
  }

  /* ── Mode label "FULL SPOT": readable size ───────────────── */
  body.ds-vp-app .dspmx .pm-mode-label{
    font-size: 13px;
    min-height: 28px;
    padding: 0 10px;
  }

  /* ── Layout pm-head: stack column su portrait (full width) ─ */
  body.ds-vp-app .dspmx .pm-head{
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  body.ds-vp-app .dspmx .pm-head-right{
    width: 100%;
  }
  body.ds-vp-app .dspmx .pm-units{
    width: 100%;
    justify-content: flex-start;
  }

  /* ── Layout pm-ctrl-row: wrap su portrait, controlli stay on rows ── */
  body.ds-vp-app .dspmx .pm-ctrl-row{
    flex-wrap: wrap;
    gap: 12px;
    row-gap: 14px;
  }
  body.ds-vp-app .dspmx .pm-ctrl-divider{
    display: none; /* su mobile il wrap fa già la separazione visiva */
  }
  body.ds-vp-app .dspmx .pm-presets{
    flex-wrap: wrap;
    gap: 6px;
  }
  body.ds-vp-app .dspmx .pm-angle{
    flex: 1 1 200px;
    min-width: 0;
  }

  /* ── pm-grid (4 metric cards): 2×2 su portrait per touch ergonomy ── */
  body.ds-vp-app .dspmx .pm-grid{
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 10px;
  }
  body.ds-vp-app .dspmx .pm-card{
    padding: 12px;
    min-height: 78px;
  }
  body.ds-vp-app .dspmx .pm-value{
    font-size: 22px;
  }
  body.ds-vp-app .dspmx .pm-fstop-val{
    font-size: 22px;
  }

  /* ── Batch 3.5 sess.29 — F-Stop card tap-cycle (utente feedback):
     hide i 3 button toggle interni che creano disarmonia con le altre
     3 celle piatte. La cella diventa tap-target che cycle dataset.primary
     direttamente (handler JS in photometrics-modal.js mount). Le altre
     3 celle (Illuminance/Ø Beam/Ø Field) restano statiche. */
  body.ds-vp-app .dspmx .pm-card.pm-fstop .pm-fstop-toggles{
    display: none !important;
  }
  /* La cella F-Stop ha lo stesso layout interno delle altre 3 celle: */
  body.ds-vp-app .dspmx .pm-card.pm-fstop{
    display: flex !important;
    flex-direction: column;
    justify-content: center;
    cursor: pointer;
    position: relative; /* per l'::after dots indicator */
  }
  body.ds-vp-app .dspmx .pm-card.pm-fstop .pm-fstop-main{
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  /* Indicator visivo "tappabile" (dots ●○○ analogo merge cell in-page) */
  body.ds-vp-app .dspmx .pm-card.pm-fstop::after{
    content: "\25CF \25CB \25CB";
    position: absolute;
    top: 8px;
    right: 10px;
    font-size: 10px;
    letter-spacing: 2px;
    opacity: .55;
    color: var(--ds-c-text-soft, rgba(218,214,206,.62));
    pointer-events: none;
  }
  body.ds-vp-app .dspmx .pm-card.pm-fstop[data-primary="ev"]::after{
    content: "\25CB \25CF \25CB";
  }
  body.ds-vp-app .dspmx .pm-card.pm-fstop[data-primary="candela"]::after{
    content: "\25CB \25CB \25CF";
  }

  /* ── Batch 3.5 sess.29 — Zoom 3 chip → dropdown nel modal (.dspmx):
     replica selectors esistenti widget in-page (.pm-zoom-mobile-select).
     I 3 button pill SPOT/MID/FLOOD restano nel DOM (delegate click) ma
     display:none — DSDropdown li sostituisce visivamente. Mirror del
     pattern CCT dropdown sopra.
     Sess.36 cleanup: rimosse regole label/select native post-refactor
     DSDropdown sess.35 (.dspmx). Wrapper container resta vivo qui;
     trigger button styled da ds-dropdown.css. */
  body.ds-vp-app #photometrics .pm-presets,
  body.ds-vp-app .dspmx       .pm-presets{
    display: none !important;
  }
  body.ds-vp-app #photometrics .pm-zoom-mobile-select,
  body.ds-vp-app .dspmx       .pm-zoom-mobile-select{
    display: inline-flex !important;
    align-items: center;
    gap: 6px;
  }
  /* Desktop default: zoom wrapper non si vede */
  #photometrics .pm-zoom-mobile-select,
  .dspmx       .pm-zoom-mobile-select{
    display: none;
  }

  /* sess.35 Step 4: desktop default UNITS dropdown non si vede mai (modal
     desktop continua a usare le 2 pill METRIC/IMPERIAL standard). */
  .dspmx .pm-units-mobile-select{
    display: none;
  }

  /* ── Cone host: dimensione adattiva. Sess.29 Batch 3.7 (ROLLBACK Batch 3.6):
     il bump 240→360/420 portrait regrediva entrambi gli stati (cono SVG ha
     aspect-ratio fisso da viewBox, container più alto non lo stretcha
     verticalmente — anzi rompe layout landscape). Riprendo dimensioni Batch
     2c originali. La risoluzione corretta del problema "cono portrait tagliato"
     richiede refactor del SVG renderer (vedi polish backlog), non parametri
     CSS bidirezionali. */
  /* sess.29 fix bug "cono modal portrait allungato/1-4 del container":
     SVG viewBox 1000:300 = aspect 3.33:1. Container era 240/280 height
     (Anchor 3.7 rollback) → SVG si scala su width, height proporzionata
     ~108px, lasciando 130+px vuoti sopra+sotto. Cono renderizza piccolo
     dentro container grande.
     Fix: aspect-ratio CSS match SVG → container height = width/3.33.
     Cono riempie container al 100% senza vuoto. Supportato iOS Safari 15.4+. */
  body.ds-vp-app .dspmx .pm-cone-host{
    /* sess.30 redesign grafico (decisione utente): aspect-ratio 1000/300
       → 1000/400 per +33% altezza container portrait. Sync con cone-renderer
       LEFT compactLayout 80→40 (vertice cono dietro proiettore iconizzato).
       sess.30 fix E: display:flex centratura SVG nel container.
       sess.30 fix G: `aspect-ratio` MIGRATO al @media portrait only sotto.
       In landscape combinato con sub-tier max-height:220 forzava container
       width 550px (220×2.5) ≠ 100% viewport → spazio vuoto dx visibile
       (utente feedback "riquadro grafico landscape ancora sx, vuoto dx").
       Qui mantengo solo le proprietà mobile-generic (no aspect-ratio). */
    min-height: 0 !important;
    height: auto;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  body.ds-vp-app .dspmx .pm-cone-host svg{
    display: block;
  }
  /* sess.30 fix E: hide box "Beam/Field" SVG (g.panel) + overlay numerici
     mk-info in modal mobile portrait — sono tagliati (utente feedback) e
     ridondanti con le 2 merge cells sotto al cono. Decisione sess.29 Batch
     3.6 di revoca hide era pre-redesign; ora con merge cells + tabella
     compare HTML compatta l'info è disponibile altrove. */
  body.ds-vp-app .dspmx .pm-cone-host svg g.panel{
    display: none;
  }
  body.ds-vp-app .dspmx .pm-cone-host svg .mk-info{
    display: none;
  }
  /* sess.30 fix E: in compare attivo (.pm-grid.pm-grid--compare) le 2
     merge cells sono ridondanti (info nella tabella compatta sotto).
     Hide pm-mobile-merge quando compare attivo per ridurre rumore visivo. */
  body.ds-vp-app .pm-grid.pm-grid--compare ~ .pm-mobile-merge{
    display: none !important;
  }
  body.ds-vp-app .dspmx .pm-cone-host svg{
    min-height: 0 !important;
    max-height: none;
    width: 100%;
    height: 100%;
  }

  /* ── Batch 3.7 sess.29: hide .mk-info+g.panel portrait NON ripristinato
     (utente preferisce vederli per compare anche se cono è compresso).
     Le regole hide originali Batch 3a restano revocate sul .dspmx.
     Nel widget in-page (#photometrics) restano nascosti (vedi line 2982-2993). */
}

/* ── sess.30 fix #2ter + redesign + fix G: force aspect-ratio container modal
   mobile PORTRAIT ONLY. Aspect 1000/400 = 2.5 → height = (100vw-28px)/2.5.
   CSS aspect-ratio prop non sempre honored se parent flex stretchavano figli
   (osservato container ~220px anziché computed). Forza height esplicito.
   sess.30 fix G: aspect-ratio + height calc MOVED HERE from outer mobile
   media block. In landscape l'aspect-ratio combinava con sub-tier max-height
   forzando width=550px (asimmetria sx-aligned). Ora landscape lascia
   container width 100% naturale, sub-tier max-height 220 fa il resto. */
@media (max-width: 1099.98px) and (orientation: portrait) {
  body.ds-vp-app .dspmx .pm-cone-host{
    aspect-ratio: 1000 / 400;
    height: calc((100vw - 28px) / 2.5) !important;
    max-height: calc((100vw - 28px) / 2.5) !important;
  }

  /* ── sess.30 Commit D — Compare table 5 col → 3 col compact tap-cycle.
     Override grid columns + styling header cycle. Tap header → cicla
     intensità (lux/F/EV/cd) o geometria (BA/FA/ØB/ØF) tutta la colonna.
     Indicator dots ●○○○ sotto label. State persistente localStorage. ── */
  body.ds-vp-app .pm-cmp-table--compact{
    grid-template-columns: minmax(110px, 1.3fr) 1fr 1fr !important;
  }
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-cell--cycle{
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 4px;
  }
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-cell--cycle:active{
    transform: scale(var(--ds-mobile-tap-scale, 0.96));
    transition: transform 80ms ease;
  }
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-cycle-label{
    font: 600 10px/1 "Oswald", system-ui, sans-serif;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: rgba(218,214,206,.78);
  }
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-cycle-dots{
    font: 600 8px/1 system-ui, sans-serif;
    letter-spacing: .14em;
    color: rgba(244,181,50,.72);
  }
  /* Body cells compact: valore + delta stack verticale */
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-row:not(.pm-cmp-row--head) .pm-cmp-cell{
    align-items: flex-start;
    text-align: left;
  }
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-row:not(.pm-cmp-row--head) .pm-cmp-cell:nth-child(2),
  body.ds-vp-app .pm-cmp-table--compact .pm-cmp-row:not(.pm-cmp-row--head) .pm-cmp-cell:nth-child(3){
    align-items: flex-start;
  }
}

/* ── Landscape sub-tier modal-widget (max-h 500 phone) ──── */
@media (max-width: 1099.98px) and (orientation: landscape) and (max-height: 500px) {
  /* In landscape phone height limited → riprendere layout row come desktop
     ma con touch-sizing. Slider+presets+dist tutti su 1 riga, no stack. */
  body.ds-vp-app .dspmx .pm-head{
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
  body.ds-vp-app .dspmx .pm-head-right{
    width: auto;
  }
  body.ds-vp-app .dspmx .pm-ctrl-row{
    flex-wrap: nowrap;
    gap: 10px;
  }
  /* Cono ridotto in landscape phone (h ~415px) */
  body.ds-vp-app .dspmx .pm-cone-host,
  body.ds-vp-app .dspmx .pm-cone-host svg{
    min-height: 180px !important;
    max-height: 220px;
  }
  /* Grid 4 cards diventa 4×1 row su landscape (orizzontale ha spazio) */
  body.ds-vp-app .dspmx .pm-grid{
    grid-template-columns: repeat(4, 1fr) !important;
  }
  body.ds-vp-app .dspmx .pm-card{
    min-height: 64px;
    padding: 10px;
  }
  body.ds-vp-app .dspmx .pm-value{
    font-size: 18px;
  }
  /* sess.29 Batch 3.6: regole "ripristina .mk-info/g.panel" landscape sono
     ora ridondanti (revoca portrait-hide su modal nella sez sopra le rende
     sempre visibili). Rimosse per evitare confusione codice. */
}
