/* DBControl -- Core CSS (layout, scrollbars, tooltips, avatar, page system) */

[x-cloak] { display: none !important; }

/* ============================================================
   Required field asterisk
   ============================================================ */
.required-asterisk::after {
    content: ' *';
    color: #ef4444;
    font-weight: 600;
}

/* Sidebar widths — single source of truth */
:root {
    --sidebar-width: 256px;
    --sidebar-width-collapsed: 56px;
    --mobile-header-height: 56px;
}

.app-sidebar {
    width: var(--sidebar-width);
    transition: width 300ms ease;
    overflow-x: hidden;
}
html.sidebar-collapsed .app-sidebar {
    width: var(--sidebar-width-collapsed);
}

/* Slim scrollbars -- Chrome / Safari / Edge */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #c5c5c5; border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }

/* Slim scrollbars -- Firefox */
* { scrollbar-width: thin; scrollbar-color: #c5c5c5 transparent; }

/* Collapsed sidebar: hide the nav scrollbar until the sidebar is hovered.
   The always-visible bar on the narrow icon-only rail looks noisy, so we fade
   the thumb to transparent while keeping the 6px gutter reserved — the icons
   never shift (no layout jump), only the bar appears/disappears on hover.
   The expanded sidebar keeps its scrollbar visible (wide enough not to bother).
   Gated on `(hover: hover)`: on touch devices there is no hover to reveal the
   bar, so there the scrollbar stays visible (the scroll affordance is kept). */
@media (hover: hover) {
    html.sidebar-collapsed .sidebar-nav { scrollbar-color: transparent transparent; }
    html.sidebar-collapsed .sidebar-nav::-webkit-scrollbar-thumb { background: transparent; }
    html.sidebar-collapsed .app-sidebar:hover .sidebar-nav { scrollbar-color: #c5c5c5 transparent; }
    html.sidebar-collapsed .app-sidebar:hover .sidebar-nav::-webkit-scrollbar-thumb { background: #c5c5c5; }
    html.sidebar-collapsed .app-sidebar:hover .sidebar-nav::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }
}

/* Smooth transitions */
.sidebar-transition { transition: width 300ms ease; }

/* Progress bar animation */
.progress-bar { transition: width 0.5s ease-in-out; }

/* Tooltip */
[data-tooltip] { position: relative; }
[data-tooltip]:hover::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 8px;
    background: #1f2937;
    color: white;
    font-size: 0.75rem;
    border-radius: 4px;
    white-space: nowrap;
    z-index: 50;
}

/* ============================================================ */
/* Standard page layout system                                  */
/* ============================================================ */

.page-layout {
    display: grid;
    grid-template-rows: auto 1fr auto;
    height: 100%;
    width: 100%;
    min-height: 0;
    /* Allow the grid to shrink below its content's min-content width.
       Without this the header's intrinsic min-content (≈900px on the
       crowded Attività toolbar) keeps the grid wider than the viewport;
       the surplus is then clipped by the `overflow-hidden` main wrapper,
       so the kanban board's own `overflow-x: auto` becomes unreachable. */
    min-width: 0;
    /* Query container for toolbar compaction. Tracks the width of the
       page content area (`main`), so the toolbar reacts to the space it
       actually has — collapsing the sidebar widens it and controls
       expand back — instead of the raw viewport width. */
    container-type: inline-size;
    container-name: pagemain;
}

.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1.25rem 12px 1.25rem;
    min-height: 48px;
    min-width: 0;
    /* Wrap onto a second row instead of forcing the page wider than the
       viewport when the toolbar can't fit (Attività is the densest one). */
    flex-wrap: wrap;
    gap: 8px;
}

.page-header h1 {
    font-size: 0.95rem;
    font-weight: 700;
    color: #111827;
    line-height: 1.2;
    margin: 0;
    display: flex;
    align-items: center;
}

.page-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    min-width: 0;
    flex-wrap: wrap;
}

.page-actions button,
.page-actions select,
.page-actions label {
    font-size: 0.8rem;
}

/* Board switcher — chips by default, a compact dropdown when narrow.
   Styled entirely here (NOT with Tailwind utilities) on purpose: Tailwind
   is the runtime CDN, which injects its stylesheet AFTER first paint and
   AFTER app.css. Utility-styled header chrome would (a) flicker on load
   and (b) lose display swaps, because Tailwind's later `.flex` would beat
   `.board-chips { display: none }`. Keeping it in app.css makes both
   instant and authoritative. */
.board-switcher {
    display: flex;
    align-items: center;
    min-width: 0;
}

/* Reserve top padding so the absolute badge never clips at the header top. */
.board-chips {
    display: flex;
    align-items: center;
    gap: 4px;
    padding-top: 4px;
}
.board-chip {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 9999px;
    font-size: 0.75rem;
    line-height: 1rem;
    white-space: nowrap;
    border: 1px solid #e5e7eb;
    background: #f9fafb;
    color: #4b5563;
    cursor: pointer;
    transition: background-color 150ms;
}
.board-chip:hover {
    background: #f3f4f6;
}
.board-chip-active {
    background: #eff6ff;
    border-color: #93c5fd;
    color: #1d4ed8;
}
.board-chip-dot {
    width: 8px;
    height: 8px;
    border-radius: 9999px;
    flex-shrink: 0;
}

/* Compact dropdown (shown via @container). Trigger and menu share a fixed
   width and right edge so the open menu lines up with the closed trigger. */
.board-select {
    position: relative;
    display: none;
}
.board-select-trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    width: 11rem;
    padding: 6px 10px;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    background: #ffffff;
    font-size: 0.8rem;
    color: #374151;
    cursor: pointer;
}
.board-select-trigger:hover {
    background: #f9fafb;
}
.board-select-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
}
.board-select-caret {
    width: 12px;
    height: 12px;
    color: #9ca3af;
    flex-shrink: 0;
}
.board-select-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    width: 11rem;
    max-width: 100%;
    padding: 4px;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.board-select-option {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 8px;
    border: none;
    border-radius: 6px;
    background: transparent;
    font-size: 0.8rem;
    color: #374151;
    text-align: left;
    cursor: pointer;
}
.board-select-option:hover {
    background: #f3f4f6;
}
.board-select-option-active {
    background: #eff6ff;
    color: #1d4ed8;
}
.board-select-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.board-select-count {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.3rem;
    font-size: 10px;
    font-weight: 500;
    line-height: 1;
    color: #ffffff;
    background: #ef4444;
    border-radius: 9999px;
}

/* Search box — sized here so it doesn't reflow when the runtime Tailwind
   CDN injects its utilities after first paint. */
.vendite-search {
    width: 12rem;
    padding: 0.375rem 0.75rem 0.375rem 2rem;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    font-size: 0.875rem;
}

/* ── Toolbar progressive compaction ──
   Keyed on the width of `main` (see `container-name: pagemain` on
   `.page-layout`), NOT the viewport: collapsing the sidebar gives the
   toolbar more room and the controls expand back. Collapse the widest
   consumer (board chips → dropdown) first, before the toolbar would wrap. */
@container pagemain (max-width: 1200px) {
    .board-chips { display: none; }
    .board-select { display: inline-flex; }
}
@container pagemain (max-width: 1100px) {
    .page-header-btn .btn-label { display: none; }
    .view-tabs { width: auto; }
    .view-tab-label { display: none; }
    .vendite-search { width: 9rem; }
}
@container pagemain (max-width: 820px) {
    .vendite-search { width: 7rem; }
}

/* ── Dense page header: match the standard header height ──
   Some toolbars carry controls taller than a plain `page-header-btn`
   (selects, inputs, search, segmented switchers). Without compensation the
   bar renders ~11px taller than every other page header. Used by Attività
   (board switcher, search, filters, view switcher, "Nuovo") and Interventi
   (per-tab filter selects + "Nuovo").

   Fix mirrors the `page-header-btn` trick — negative vertical margins that
   cancel a control's own padding so it doesn't inflate the flex line — but
   applies it to EVERY direct child of `.page-actions`, so no control (now or
   added later) is forgotten and silently re-inflates the bar. The header
   keeps `min-height` + `flex-wrap` (NO forced fixed height), so it still
   reflows onto multiple rows when narrow. */
.page-header-dense .page-actions > * {
    margin-top: -6px;
    margin-bottom: -6px;
}
/* The negative margins let each control bleed ~6px past its box; on a single
   row the header padding absorbs that, but when the toolbar WRAPS (mobile)
   the stacked rows would touch. A row-gap — which applies only BETWEEN
   wrapped rows, zero effect on the desktop single-row layout — keeps them
   clear without ever forcing a taller bar. */
.page-header-dense .page-actions {
    row-gap: 16px;
}
.page-header.page-header-dense {
    row-gap: 12px;
}

/* The segmented view switcher (Attività only) carries its own border +
   double padding, so the shared -6px alone can't pull it down to the button
   height — trim its padding so it lines up with the neighbouring buttons. */
.attivita-header .view-tabs {
    padding: 2px;
}
.attivita-header .view-tab {
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}

/* Per-board unread badge. ABSOLUTE so its presence/absence after the
   async fetch never changes chip width — no horizontal layout shift. */
.board-chip-badge {
    position: absolute;
    top: -6px;
    right: -6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.3rem;
    font-size: 10px;
    font-weight: 500;
    line-height: 1;
    color: #ffffff;
    background-color: #ef4444;
    border-radius: 9999px;
    pointer-events: none;
}

/* Standard button for page header */
.page-header-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.75rem;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.25rem;
    border-radius: 0.5rem;
    transition: all 150ms;
    border: none;
    cursor: pointer;
    margin-top: -6px;
    margin-bottom: -6px;
}

.page-header-btn-primary {
    background-color: #2563eb;
    color: #ffffff;
}

.page-header-btn-primary:hover {
    background-color: #1d4ed8;
}

.page-header-btn-primary:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}

.page-header-btn-secondary {
    background-color: #f3f4f6;
    color: #374151;
}

.page-header-btn-secondary:hover {
    background-color: #e5e7eb;
}

.page-header-btn-secondary:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
}

.page-header-btn svg {
    width: 1rem;
    height: 1rem;
}

/* Visibilità per-breakpoint dei bottoni header.
   ⚠ `.page-header-btn { display: inline-flex }` vive in app.css, che (dropped
   il CDN Tailwind play, ADR 2026-06-11) è caricata DOPO tailwind.css: a parità
   di specificità (singola classe) batte le utility `.hidden` / `.md:hidden`,
   quindi quelle utility da sole NON nascondono un `.page-header-btn`. Per una
   visibilità per-breakpoint affidabile servono regole a specificità maggiore
   (2 classi), autorevoli a prescindere dall'ordine di caricamento.
   - `.header-action-desktop`: visibile da ≥768px, nascosto su mobile.
   - `.header-action-mobile`: visibile su mobile, nascosto da ≥768px. */
@media (max-width: 767.98px) {
    .page-header-btn.header-action-desktop { display: none; }
}
@media (min-width: 768px) {
    .page-header-btn.header-action-mobile { display: none; }
}

/* Helpdesk detail header: su mobile la toolbar (timer + Info + ⋯) deve poter
   spingere il kebab ⋯ tutto a destra. Senza questo, `.page-actions` si dimensiona
   sul contenuto quando va a capo sotto il titolo, quindi `ml-auto` sul kebab non
   ha spazio libero su cui agire e il ⋯ resta accanto a Info (non a destra).
   Forzando flex:1 1 100% la toolbar occupa l'intera riga: `justify-content:
   flex-end` + `ml-auto` sul kebab tengono timer/Info a sinistra e il ⋯ a destra.
   Scoped alla sola pagina di dettaglio (classe dedicata) per non toccare le altre
   toolbar dell'app. */
@media (max-width: 767.98px) {
    .page-actions.page-actions-helpdesk-detail,
    .page-actions.page-actions-detail { flex: 1 1 100%; }
}

/* Open-record icon — small external-link icon next to a label that opens
   the selected record's edit form in a new tab. Visible only when a value
   is selected (controlled by `x-show` / inline `display:none`). */
.open-record-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #2563eb;
    transition: color 120ms;
    text-decoration: none;
    line-height: 1;
}
.open-record-icon:hover {
    color: #1d4ed8;
}
.open-record-icon:focus {
    outline: none;
    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.25);
    border-radius: 0.25rem;
}

.page-body {
    min-height: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    margin-bottom: 8px;
    padding: 0 1.25rem;
}

.page-body-scroll {
    min-height: 0;
    overflow-y: auto;
    margin-bottom: 8px;
    padding: 0 1.25rem;
}

/* Attività — Lista (Tabulator) view.
   The list wrapper must fill the `.page-body` flex column so the table area
   has a DEFINITE height: Tabulator is built with `height: '100%'`, and a
   percentage height only resolves against an ancestor whose height is
   resolved. Without this, the holder grew to its full content height, the
   internal scroll never engaged and the table overflowed the page. The
   table lives in its own flex:1 region so the optional pagination row below
   doesn't get overlapped by Tabulator's inline `height:100%`. */
.attivita-lista {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.attivita-lista-tablewrap {
    flex: 1 1 auto;
    min-height: 0;
}
.attivita-lista-pagination {
    flex-shrink: 0;
}

/* Dev Tracker — Lista fills the flex column so Tabulator's height:100% resolves
   (mirror of .attivita-lista); progress bar + drag ghost for the kanban. */
.dev-lista {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.dev-lista-tablewrap {
    flex: 1 1 auto;
    min-height: 0;
}
.dev-progress {
    height: 4px;
    background: #e5e7eb;
    border-radius: 9999px;
    overflow: hidden;
}
.dev-progress-bar {
    height: 100%;
    background: #22c55e;
    border-radius: 9999px;
}
.kanban-card-ghost {
    opacity: 0.4;
}

/* ============================================================
   Dev Tracker — Gantt view (Fase 5)
   Week-axis timeline. The label column and the canvas rows share a single
   row pitch (--gantt-row-h == GANTT_ROW_H in dev_tracker.js) so the two stay
   aligned. Bars are absolutely positioned by left%/width% (day-precise) over
   equal-width flex week columns; dependency arrows are an SVG overlay drawn in
   px against the measured canvas width.
   ============================================================ */
.dev-gantt-wrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding-bottom: 0.75rem;
}
.dev-gantt-filters {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.5rem 0 0.75rem;
}
.dev-gantt-filter-input {
    font-size: 0.8rem;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    padding: 0.375rem 0.5rem;
    background: #fff;
    outline: none;
}
.dev-gantt-filter-input:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.35);
}
.dev-gantt-filter-label {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    font-size: 0.75rem;
    color: #6b7280;
}
.dev-gantt-filter-clear {
    font-size: 0.75rem;
    color: #2563eb;
    padding: 0.375rem 0.5rem;
    border-radius: 0.5rem;
}
.dev-gantt-filter-clear:hover {
    background: #eff6ff;
}
.dev-gantt-empty {
    padding: 2.5rem 1rem;
    text-align: center;
    color: #9ca3af;
}
.dev-gantt {
    --gantt-label-w: 15rem;
    --gantt-row-h: 44px;

    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 0.6rem;
    overflow: hidden;
}
.dev-gantt-head {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 6;
    background: #f9fafb;
    border-bottom: 1px solid #e5e7eb;
}
.dev-gantt-head-task {
    width: var(--gantt-label-w);
    flex-shrink: 0;
    padding: 0.6rem 0.75rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: #6b7280;
    border-right: 1px solid #e5e7eb;
}
.dev-gantt-head-weeks {
    flex: 1 1 auto;
    display: flex;
    min-width: 0;
}
.dev-gantt-week {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
    font-size: 0.68rem;
    color: #6b7280;
    padding: 0.6rem 0.2rem;
    border-right: 1px solid #f1f5f9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dev-gantt-week:last-child {
    border-right: none;
}
.dev-gantt-body {
    display: flex;
}
.dev-gantt-labels {
    width: var(--gantt-label-w);
    flex-shrink: 0;
    border-right: 1px solid #e5e7eb;
}
.dev-gantt-label {
    height: var(--gantt-row-h);
    padding: 0.3rem 0.75rem;
    border-bottom: 1px solid #f1f5f9;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    cursor: pointer;
    overflow: hidden;
}
.dev-gantt-label:hover {
    background: #f9fafb;
}
.dev-gantt-label-title {
    font-size: 0.8rem;
    font-weight: 500;
    color: #1f2937;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dev-gantt-label-meta {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.dev-gantt-code {
    font-size: 0.66rem;
    color: #9ca3af;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* (the old .dev-gantt-avatar hand-rolled circle is gone — the gantt label now
   uses renderAvatar with avatar-circle-xs, see index.html) */
.dev-gantt-canvas {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    /* horizontal row separators every row (--gantt-row-h) */
    background-image: repeating-linear-gradient(
        to bottom,
        transparent 0,
        transparent calc(var(--gantt-row-h) - 1px),
        #f1f5f9 calc(var(--gantt-row-h) - 1px),
        #f1f5f9 var(--gantt-row-h));
}
.dev-gantt-grid {
    position: absolute;
    inset: 0;
    display: flex;
    z-index: 0;
    pointer-events: none;
}
.dev-gantt-gridcol {
    flex: 1 1 0;
    min-width: 0;
    border-right: 1px solid #f1f5f9;
}
.dev-gantt-gridcol:last-child {
    border-right: none;
}
.dev-gantt-arrows {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    overflow: visible;
}
.dev-gantt-bar {
    position: absolute;
    height: 26px;
    border-radius: 5px;
    z-index: 2;
    display: flex;
    align-items: center;
    padding: 0 6px;
    color: #fff;
    font-size: 0.66rem;
    font-weight: 600;
    cursor: pointer;
    overflow: hidden;
    white-space: nowrap;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    transition: filter 0.1s ease;
}
.dev-gantt-bar:hover {
    filter: brightness(1.06);
}
.dev-gantt-bar.is-terminal {
    opacity: 0.65;
}
.dev-gantt-bar-fill {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.18);
    z-index: 0;
}
.dev-gantt-bar-txt {
    position: relative;
    z-index: 1;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
}
.dev-gantt-today {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: #f87171;
    z-index: 3;
    pointer-events: none;
}
.dev-gantt-legend {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid #e5e7eb;
    background: #f9fafb;
    font-size: 0.68rem;
    color: #6b7280;
}
.dev-gantt-legend-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.dev-gantt-legend-swatch {
    width: 12px;
    height: 12px;
    border-radius: 3px;
    display: inline-block;
    flex-shrink: 0;
}
.dev-gantt-legend-today {
    background: #f87171;
}
@media (max-width: 640px) {
    .dev-gantt {
        --gantt-label-w: 8.5rem;
    }
    .dev-gantt-week {
        font-size: 0.6rem;
    }
}

/* Settings tab bar — horizontally scrollable, sticky between header and content.
   Thin visible scrollbar (matches global 6px style) so the scroll affordance is
   discoverable when tabs overflow the viewport. */
.settings-tabs-bar {
    padding: 0 1.25rem;
    flex-shrink: 0;
    max-width: 100%;
    scrollbar-width: thin;           /* Firefox */
    scrollbar-color: #c5c5c5 transparent;
}
.settings-tabs-bar > button {
    flex-shrink: 0;
}
.settings-tabs-bar::-webkit-scrollbar {
    height: 6px;
}
.settings-tabs-bar::-webkit-scrollbar-track {
    background: transparent;
}
.settings-tabs-bar::-webkit-scrollbar-thumb {
    background: #c5c5c5;
    border-radius: 999px;
}
.settings-tabs-bar::-webkit-scrollbar-thumb:hover {
    background: #a0a0a0;
}

/* Standardized page footer */
.page-footer {
    background: #f9fafb;
    border-top: 1px solid #e5e7eb;
    padding: 6px 1.25rem;
    font-size: 0.75rem;
    color: #6b7280;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    min-height: 36px;
}

.page-footer .footer-stat {
    display: flex;
    align-items: center;
    gap: 4px;
}

.page-footer .footer-stat strong {
    color: #1f2937;
    font-weight: 600;
}

.page-footer .footer-divider {
    width: 1px;
    height: 14px;
    background: #d1d5db;
}

/* ============================================================
   Avatar / Portrait
   ============================================================ */
.avatar-circle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-weight: 600;
    color: #fff;
    flex-shrink: 0;
    text-transform: uppercase;
    font-size: 0.6rem;
    line-height: 1;
    box-sizing: border-box;
}
.avatar-circle-xs { width: 18px; min-width: 18px; height: 18px; font-size: 0.5rem; }
.avatar-circle-sm { width: 28px; min-width: 28px; height: 28px; font-size: 0.6rem; }
.avatar-circle-md { width: 36px; min-width: 36px; height: 36px; font-size: 0.7rem; }
.avatar-circle-lg { width: 64px; min-width: 64px; height: 64px; font-size: 1.25rem; }

/* ============================================================
   Hierarchy tree (tab Contatti)
   ============================================================ */

/* Tab indicator — inset shadow instead of border to avoid layout shift */
.tab-indicator-active {
    box-shadow: inset 0 -2px 0 0 #2563eb;
}

.hierarchy-tree {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none; /* Firefox: keep horizontal scroll available but hidden */
}
.hierarchy-tree::-webkit-scrollbar {
    height: 0; /* Chromium/WebKit: hidden horizontal scrollbar */
}
.tree-connector { font-family: monospace; color: #d1d5db; white-space: pre; }
.tree-node-selected { background-color: #eff6ff; color: #1d4ed8; border-radius: 0.25rem; }

/* Avatar in table cells: negative margins so the circle doesn't expand row height */
.tabulator-cell .avatar-circle-sm,
td .avatar-circle-sm {
    margin-top: -4px;
    margin-bottom: -4px;
}

/* ============================================================
   Ticket presence / conflict indicator (ADR 2026-07-07)
   Striscia avatar (riga "Stato" del dettaglio ticket) degli agenti che stanno
   guardando lo stesso ticket ora. Larghezza massima ~4 avatar; oltre → scroll
   orizzontale con scrollbar nascosta + drag.
   ============================================================ */
.ticket-presence {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    max-width: 6.5rem;
    overflow-x: auto;
    overflow-y: hidden;
    /* padding per non tagliare anello+glow dal box di scroll, con margin
       negativo UGUALE: il padding non incide sul layout della riga, così la
       riga "Stato" mantiene la sua altezza originale (nessun ingrandimento). */
    padding: 8px;
    margin: -8px;
    scrollbar-width: none;        /* Firefox */
    -ms-overflow-style: none;     /* IE/legacy Edge */
    cursor: grab;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
}
.ticket-presence::-webkit-scrollbar { display: none; }  /* WebKit */
.ticket-presence.dragging { cursor: grabbing; scroll-behavior: auto; }
/* Avatar piccoli (avatar-circle-xs, 18px) per stare nella riga senza alzarla;
   anello rosso pulito (gap bianco + rosso) sopra il bordo colorato di
   renderAvatar. Il box-shadow non occupa spazio nel layout → non alza la riga.
   NB: non forzare display sull'.avatar-circle interno (è inline-flex e centra
   le iniziali); block romperebbe l'allineamento del testo. */
.ticket-presence-avatar {
    /* flex container: l'.avatar-circle standard resta un figlio centrato come
       ovunque, senza quirk di inline-layout. Solo il ring + il pulse sono
       aggiunti qui; nessuno stile che tocchi il centraggio delle iniziali. */
    display: flex;
    flex: 0 0 auto;
    border-radius: 9999px;
    box-shadow: 0 0 0 1.5px #fff, 0 0 0 3px #ef4444;
    animation: ticket-presence-pulse 1.4s ease-in-out infinite;
}
.ticket-presence.dragging .ticket-presence-avatar { pointer-events: none; user-select: none; }
/* Micro-correzione verticale SCOPED (solo avatar presence, mai il .avatar-circle
   condiviso): le iniziali maiuscole risultano otticamente un filo alte per via
   dello spazio vuoto del descender sotto la baseline; 1px di padding-top le
   ricentra. box-sizing è già border-box sul .avatar-circle → il cerchio resta 18px. */
.ticket-presence-avatar .avatar-circle { padding-top: 1px; }
/* Pulse: glow rosso marcato (opacità alta) e raggio ampio. Il fallback
   reduced-motion vive nel blocco @media condiviso (più in basso), non qui, per
   non anticipare il primo `prefers-reduced-motion` atteso dai test dello skeleton. */
@keyframes ticket-presence-pulse {
    0%, 100% { box-shadow: 0 0 0 1.5px #fff, 0 0 0 3px #ef4444, 0 0 0 0 rgba(239, 68, 68, 0.85); }
    50%      { box-shadow: 0 0 0 1.5px #fff, 0 0 0 3px #ef4444, 0 0 0 8px rgba(239, 68, 68, 0); }
}

/* Avatar column in Tabulator: compact padding for circle alignment */
.tabulator-cell-avatar { padding: 7px !important; }

/* ============================================================
   Tabulator overrides for DBControl theme
   ============================================================ */
.tabulator {
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    font-size: 0.875rem;
    background: #fff;
}

.tabulator .tabulator-header {
    background: #f9fafb;
    border-bottom: 1px solid #e5e7eb;
    font-weight: 600;
    color: #4b5563;
}

.tabulator .tabulator-header .tabulator-col {
    border-right: 1px solid #e5e7eb;
    padding: 8px 12px;
}

.tabulator .tabulator-header .tabulator-col .tabulator-col-content {
    padding: 0;
}

.tabulator .tabulator-header .tabulator-col .tabulator-col-title {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
}

.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input {
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    padding: 4px 8px;
    font-size: 0.75rem;
    outline: none;
    width: 100%;
    margin-top: 4px;
}

.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input[type="search"]::-webkit-search-decoration,
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}

.tabulator .tabulator-header .tabulator-col .tabulator-header-filter select {
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    padding: 4px 8px;
    font-size: 0.75rem;
    outline: none;
    width: 100%;
    margin-top: 4px;
    background: white;
}

.tabulator .tabulator-tableholder .tabulator-table .tabulator-row {
    border-bottom: 1px solid #f3f4f6;
}

.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover {
    background-color: #eff6ff !important;
    cursor: pointer;
}

.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell {
    padding: 10px 12px;
    border-right: none;
}

.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-row-even {
    background: #fafafa;
}

/* ── Inline cell editing (helpdesk list e future tabelle editabili) ──
   L'altezza riga è fissa (rowHeight): l'editor deve riempire la cella
   esistente senza alterarne il box — nessuno shift verticale quando
   l'editor appare/scompare. */
.tabulator .tabulator-cell.hd-inline-edit:not(.tabulator-editing):hover {
    box-shadow: inset 0 0 0 1px #bfdbfe;
    background-color: #eff6ff;
    cursor: pointer;
}

/* inline-flex, NON flex: le celle Tabulator sono inline-block dentro un
   contesto di formattazione inline — un display block-level (flex) qui
   forza un line-break prima/dopo la cella e spacca la riga su 3 righe.
   Evidenza come box-shadow INSET, mai border: il border 1px del theme
   (#1D68CD) si somma al border-right della cella, sposta il contenuto
   e sborda visivamente sulle celle adiacenti. */
.tabulator .tabulator-row .tabulator-cell.tabulator-editing {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    padding-top: 0;
    padding-bottom: 0;
    border: none !important;
    box-shadow: inset 0 0 0 2px #2563eb;
    background-color: #fff;
}

.tabulator .tabulator-row .tabulator-cell.tabulator-editing input,
.tabulator .tabulator-row .tabulator-cell.tabulator-editing select {
    width: 100%;
    height: 100% !important;
    max-height: 100%;
    box-sizing: border-box;
    margin: 0;
    /* Niente padding orizzontale proprio: la cella mantiene il suo
       (10px 12px dal tema) e il valore NON deve shiftare a destra
       quando l'editor si apre. */
    padding: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: inherit;
    /* Stesso corpo del testo badge della cella (text-xs): l'editor non
       deve "ingrandire" il valore rispetto al render normale. */
    font-size: 0.75rem;
    color: #374151;
}

.tabulator .tabulator-row .tabulator-cell.tabulator-editing input[type="datetime-local"] {
    /* Le celle datetime renderizzano text-sm. */
    font-size: 0.875rem;
}

/* Select2 (assegnatario) dentro la cella in editing: selection compatta
   con avatar che riempie la cella; il dropdown vive su body e non tocca
   il layout della tabella. */
.tabulator .tabulator-cell.tabulator-editing .select2-container {
    width: 100% !important;
    height: 100%;
}

.tabulator .tabulator-cell.tabulator-editing .select2-container--default .select2-selection--single {
    height: 100%;
    border: none;
    border-radius: 0;
    background: transparent;
    display: flex;
    align-items: center;
}

.tabulator .tabulator-cell.tabulator-editing .select2-container--default .select2-selection--single .select2-selection__rendered {
    display: flex;
    align-items: center;
    /* height piena: il rendered di select2 nasce alto quanto la
       line-height e l'avatar da 28px veniva clippato sopra/sotto; col box
       a tutta cella l'avatar ci sta e l'overflow hidden torna a fare il
       suo lavoro SOLO sull'orizzontale (nomi/etichette lunghi non devono
       dipingere sopra la freccia o le celle adiacenti). padding-left 0:
       il padding orizzontale lo dà già la cella, il valore non deve
       shiftare all'apertura. */
    height: 100%;
    overflow: hidden;
    line-height: normal;
    font-size: 0.875rem;
    padding-left: 0;
    padding-right: 24px;
}

.tabulator .tabulator-cell.tabulator-editing .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 100%;
    top: 0;
}

/* Niente selezione testo spuria sulle celle editabili: il flusso
   click→editor→pick può generare un dblclick sulla cella sottostante
   che selezionerebbe il testo della tabella. */
.tabulator .tabulator-cell.hd-inline-edit {
    user-select: none;
}

/* Dropdown Select2 degli editor inline (dropdownCssClass): largo quanto
   il contenuto (dropdownAutoWidth) con un minimo leggibile — senza, il
   dropdown eredita la larghezza della cella, i nomi vanno a capo e gli
   avatar si tagliano. !important: select2 scrive min-width INLINE sul
   dropdown quando dropdownAutoWidth è attivo. */
.hd-inline-edit-dropdown {
    min-width: 240px !important;
}

.hd-inline-edit-dropdown .select2-results__option {
    padding: 6px 12px;
    white-space: nowrap;
    line-height: normal;
}

.hd-inline-edit-dropdown .select2-results__option .avatar-circle-sm,
.hd-inline-edit-dropdown .select2-results__option img {
    flex-shrink: 0;
}

/* ============================================================
   Kanban styles
   ============================================================ */
.kanban-container {
    display: flex;
    gap: 1rem;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    padding-bottom: 0.5rem;
}

.kanban-column {
    flex-shrink: 0;
    width: 18rem;
    display: flex;
    flex-direction: column;
    background: #f3f4f6;
    border-radius: 0.75rem;
    height: 100%;
    overflow: hidden;
}

.kanban-column-header {
    padding: 0.75rem;
    flex-shrink: 0;
    border-radius: 0.75rem 0.75rem 0 0;
}

.kanban-column-header h3 {
    font-size: 0.875rem;
    font-weight: 600;
    color: #1f2937;
    margin: 0;
}

.kanban-column-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 9999px;
    background: #d1d5db;
    font-size: 0.75rem;
    font-weight: 700;
    color: #374151;
}

.kanban-column-cards {
    flex: 1;
    overflow-y: auto;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    min-height: 60px;
    cursor: pointer;
    transition: background-color 120ms ease;
}

/* Column-wide hover tint: makes it obvious the whole empty area is clickable.
   Suppressed while hovering a card (:has(.kanban-card:hover)) so the hint
   only appears over the actual empty/clickable space. */
.kanban-column-cards:hover:not(:has(.kanban-card:hover)) {
    background-color: rgba(59, 130, 246, 0.06);
}

/* "+ Aggiungi lead" affordance: rendered as a pseudo-element so SortableJS
   can't pick it up as draggable. Sits right after the last card (no
   margin-top:auto) so it stays visible near the content, not at the bottom
   of the scrollable area. Hidden during drag via body.kanban-dragging. */
.kanban-column-cards::after {
    content: "+ Aggiungi lead";
    display: flex;
    align-items: center;
    justify-content: center;
    color: #4b5563;
    font-size: 0.8125rem;
    font-weight: 500;
    padding: 0.5rem 0.75rem;
    border: 1px dashed #9ca3af;
    border-radius: 0.5rem;
    background-color: rgba(255, 255, 255, 0.9);
    opacity: 0;
    transition: opacity 120ms ease;
    pointer-events: none;
}
.kanban-column-cards:hover:not(:has(.kanban-card:hover))::after {
    opacity: 1;
}
body.kanban-dragging .kanban-column-cards:hover {
    background-color: transparent;
}
body.kanban-dragging .kanban-column-cards::after {
    opacity: 0 !important;
}

/* Dev Tracker kanban reuses .kanban-column-cards for drag&drop parity, but it
   has NO "add lead"/click-to-add affordance — issues are created from the
   header "Nuova Issue" button. Suppress the leads-board chrome that would
   otherwise leak in via the shared class: the "+ Aggiungi lead" pseudo-element
   (also non-interactive → "click does nothing"), the click-to-add hover tint,
   and the pointer cursor over empty column space. */
.dev-kanban-cards {
    cursor: default;
}
.dev-kanban-cards::after {
    content: none;
    display: none;
}
.dev-kanban-cards:hover:not(:has(.kanban-card:hover)) {
    background-color: transparent;
}

.kanban-card {
    position: relative;
    background: white;
    border-radius: 0.5rem;
    padding: 0.75rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    border: 1px solid #e5e7eb;
    cursor: grab;
    transition: box-shadow 150ms;
}

.kanban-card:hover {
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.kanban-card:active {
    cursor: grabbing;
}

/* Visual feedback while the card detail fetch is in flight: subtle
   pressed-down look + blue ring so the user sees the click landed
   even on slow networks. Border stays 1px (we widen via box-shadow,
   which doesn't affect layout) — no horizontal shift. */
.kanban-card-loading {
    box-shadow: 0 0 0 2px #2563eb, 0 1px 3px rgba(0, 0, 0, 0.1);
    opacity: 0.85;
    pointer-events: none;
}

.kanban-column-summary {
    font-size: 0.7rem;
    color: #6b7280;
    padding: 0.25rem 0.75rem;
    border-bottom: 1px solid #e5e7eb;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.kanban-column-summary .overdue-count {
    color: #ef4444;
    font-weight: 600;
}

.overdue-card {
    border-left: 4px solid #ef4444 !important;
}

.overdue-date {
    color: #ef4444;
    font-weight: 700;
}

.overdue-count {
    color: #ef4444;
    font-weight: 700;
}

/* ============================================================
   View tabs
   ============================================================ */
.view-tabs {
    display: flex;
    gap: 0.25rem;
    background: white;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    padding: 0.25rem;
    width: fit-content;
    flex-shrink: 0;
}

/* When view-tabs are inside page-actions (header), remove bottom margin and align vertically */
.page-actions .view-tabs {
    margin-top: -6px;
    margin-bottom: -6px;
}

.view-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.375rem 1rem;
    font-size: 0.875rem;
    font-weight: 500;
    border-radius: 0.375rem;
    transition: all 150ms;
    cursor: pointer;
    border: none;
}

.view-tab-active {
    background: #2563eb;
    color: white;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.view-tab-inactive {
    color: #6b7280;
    background: transparent;
}

.view-tab-inactive:hover {
    background: #f3f4f6;
}

/* ============================================================
   Stage breadcrumb
   ============================================================ */
.stage-breadcrumb {
    display: flex;
    gap: 2px;
    margin-bottom: 1rem;
    overflow-x: auto;
}

.stage-arrow {
    display: flex;
    align-items: center;
    padding: 0.5rem 1rem 0.5rem 1.75rem;
    font-size: 0.75rem;
    font-weight: 600;
    background: #f3f4f6;
    color: #6b7280;
    position: relative;
    cursor: pointer;
    clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 50%, calc(100% - 12px) 100%, 0 100%, 12px 50%);
    transition: all 0.15s;
    white-space: nowrap;
}

.stage-arrow:first-child {
    padding-left: 1rem;
    clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 50%, calc(100% - 12px) 100%, 0 100%);
    border-radius: 0.375rem 0 0 0.375rem;
}

.stage-arrow:last-child {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 12px 50%);
    border-radius: 0 0.375rem 0.375rem 0;
}

.stage-arrow.active {
    background: #2563eb;
    color: white;
}

.stage-arrow.completed {
    background: #dbeafe;
    color: #1d4ed8;
}

.stage-arrow:hover:not(.active) {
    background: #e5e7eb;
}

/* Star rating */
.star-rating { display: inline-flex; gap: 2px; }
.star-rating svg {
    transition: color 0.1s, fill 0.1s;
}
.star-rating svg:hover {
    transform: scale(1.15);
}

/* ============================================================
   Two-panel form layout
   ============================================================ */
.form-two-panel {
    display: flex;
    gap: 0;
    min-height: 450px;
}

.form-two-panel.grid-cols-1-only {
    display: grid;
    grid-template-columns: 1fr;
}

.form-left-panel {
    width: 66%;
    flex-shrink: 0;
    padding: 1.25rem;
    border-right: 1px solid #e5e7eb;
    overflow-y: auto;
}

.form-right-panel {
    width: 34%;
    flex-shrink: 0;
    padding: 1.25rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

/* ============================================================
   Dev Tracker — issue modal body + attribute sidebar
   ============================================================ */
/* Body form: Titolo pinned on top, Descrizione editor fills the rest so the
   rich-text area gets the full modal height (no cramped few-line box). */
.dev-issue-body-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    height: 100%;
}
.dev-desc-field {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#dev-descrizione-wrap {
    flex: 1 1 auto;
    min-height: 220px;
    display: flex;
    flex-direction: column;
}
/* Quill turns #dev-descrizione-editor into .ql-container; let it grow and
   scroll internally so the toolbar stays fixed and the field fills the panel. */
#dev-descrizione-wrap .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 0.875rem;
}
#dev-descrizione-wrap .ql-editor {
    min-height: 180px;
}

/* ============================================================
   Dev Tracker — Issue detail page (/dev/{numero}), tab Descrizione
   ------------------------------------------------------------
   Display: il contenuto rich-text usa la classe condivisa
   `.kb-preview-content` (headings/liste/tabelle/code), qui aggiungiamo solo
   lo scroll interno del riquadro. Edit: stesso pattern del modale — l'editor
   cresce e scrolla internamente così la toolbar resta ancorata in alto.
   ============================================================ */
.dev-detail-desc {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
/* Il breadcrumb delle fasi nasce nel modale, dove ha un margine sotto che lo
   separa dal form. Nell'header della pagina la spaziatura la dà la riga che lo
   contiene (`py-1.5`): il margine extra sbilancerebbe la banda. */
.detail-header .stage-breadcrumb {
    margin-bottom: 0;
}
#dev-detail-desc-wrap .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 0.875rem;
}
#dev-detail-desc-wrap .ql-editor {
    min-height: 180px;
}

/* Crea Issue crossover modal — Quill description editor (mirror of the issue
   detail #dev-descrizione-wrap): the editor GROWS to fill the tall modal and
   scrolls internally so the toolbar stays pinned. */
.create-issue-desc-wrap {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 220px;
}
.create-issue-desc-wrap .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 0.875rem;
}
.create-issue-desc-wrap .ql-editor {
    min-height: 200px;
}

/* ============================================================
   KB article modal — two-panel body (left: Titolo + Contenuto)
   Mirrors the dev-tracker issue modal: the Quill editor fills the
   left panel and scrolls INTERNALLY, so its toolbar stays pinned at
   the top of the field instead of scrolling out with the modal body.
   ============================================================ */
.kb-article-body {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    height: 100%;
}
.kb-desc-field {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#kb-article-editor-wrap {
    flex: 1 1 auto;
    min-height: 220px;
    display: flex;
    flex-direction: column;
}
/* Quill turns #kb-article-editor into .ql-container; let it grow and scroll
   internally so the toolbar stays fixed and the field fills the panel. */
#kb-article-editor-wrap .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 0.875rem;
}
#kb-article-editor-wrap .ql-editor {
    min-height: 180px;
}
/* FAQ modal: same internal-scroll editor as the article modal. */
#kb-faq-editor-wrap {
    flex: 1 1 auto;
    min-height: 220px;
    display: flex;
    flex-direction: column;
}
#kb-faq-editor-wrap .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 0.875rem;
}
#kb-faq-editor-wrap .ql-editor {
    min-height: 180px;
}
/* Widen the KB modal (same max width as the dev-tracker issue modal) but grow
   ONLY the writable left panel: the right attribute column keeps the width it
   had at the previous max-w-5xl layout. Desktop-only — on mobile the shared
   media query stacks the panels, so a fixed flex-basis here must not apply
   (in a column flex it would lock the panel HEIGHT instead of width). */
/* Let the KB two-panel body shrink to the modal's available height. The shared
   .form-two-panel sets min-height:450px and app.css loads after Tailwind, so the
   `min-h-0` utility on the element can't override it: on a short desktop viewport
   header + footer + 450px body would exceed the h-[90vh] card and clip the save
   footer. Zeroing it lets the flex body size to the card and the editor scroll
   internally instead (its own min-heights keep it usable). */
.kb-two-panel {
    min-height: 0;
}
@media (min-width: 769px) {
    .kb-two-panel .form-left-panel {
        width: auto;
        flex: 1 1 auto;
    }
    .kb-two-panel .form-right-panel {
        width: 360px;
        flex: 0 0 360px;
    }
}

/* Completamento %: a draggable graphical bar (range 0–100, step 5). The filled
   portion is driven by the --pct custom property set inline from form.percentuale.
   --pct-color (default blue) lets a caller recolour the fill/thumb per value —
   the list cell slider sets it to a red→green threshold colour; the issue-form
   slider omits it and stays blue. */
.dev-pct-slider {
    -webkit-appearance: none;
    appearance: none;
    height: 8px;
    border-radius: 9999px;
    background: linear-gradient(to right, var(--pct-color, #2563eb) var(--pct, 0%), #e5e7eb var(--pct, 0%));
    outline: none;
    cursor: pointer;
}
.dev-pct-slider:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}
.dev-pct-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #ffffff;
    border: 2px solid var(--pct-color, #2563eb);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    cursor: pointer;
}
.dev-pct-slider::-moz-range-thumb {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #ffffff;
    border: 2px solid var(--pct-color, #2563eb);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    cursor: pointer;
}
.dev-pct-slider::-moz-range-track {
    height: 8px;
    border-radius: 9999px;
    background: transparent;
}

/* Inline % cell (Lista): a compact draggable bar + label, edited in place.
   The slider is thinner than the form one so it fits the 42px row. */
.dev-pct-cell {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
}
.dev-pct-cell .dev-pct-slider {
    height: 6px;
    flex: 1 1 auto;
    min-width: 40px;
}
.dev-pct-cell .dev-pct-slider::-webkit-slider-thumb {
    width: 14px;
    height: 14px;
}
.dev-pct-cell .dev-pct-slider::-moz-range-thumb {
    width: 14px;
    height: 14px;
}
.dev-pct-cell-label {
    font-size: 11px;
    font-weight: 600;
    color: #6b7280;
    /* Fit "100%" (4 glyphs) without clipping — 30px cut the trailing "%". */
    width: 34px;
    text-align: right;
    flex-shrink: 0;
    white-space: nowrap;
}
.dev-pct-cell-label.is-derived {
    font-style: italic;
    color: #9ca3af;
}

/* Inline-edit affordance (Lista): faint hover highlight on editable cells,
   mirroring helpdesk's .hd-inline-edit. */
.dev-inline-edit {
    cursor: text;
}
.dev-inline-edit:hover {
    background: #f9fafb;
    box-shadow: inset 0 0 0 1px #e5e7eb;
}

/* Righe soft-deleted (visibili solo col toggle "Cancellate"): attenuate, così
   si distinguono a colpo d'occhio dalle issue vive senza nasconderne i dati.
   Il badge "Eliminata" nella colonna Issue resta a piena opacità. */
.tabulator-row.dev-row-deleted {
    opacity: 0.6;
    background: #fef2f2;
}
/* NB: questa lista non usa la row-selection di Tabulator (la selezione è la
   checkbox HTML della prima colonna + ``selectedIds``), quindi nessuna riga
   riceve mai .tabulator-selected e non serve una variante per le cancellate
   selezionate — verificato in browser, vedi test_dev_top_filters.mjs §3b. */
.tabulator-row.dev-row-deleted .dev-inline-edit:hover {
    background: transparent;
    box-shadow: none;
    cursor: default;
}

/* ── Lista Dev Tracker: vista ad albero (toggle "Gerarchia") ──────────────
   Tabulator disegna il controllo di espansione SOLO sulle righe che hanno
   figli (DataTree.generateControlElement), quindi due radici — una con figli e
   una senza — partirebbero sfasate della larghezza del controllo. Lo slot va
   quindi riservato anche sulle foglie, ma NON con padding sulla cella più
   margine negativo sul controllo: quella coppia dipende da come il motore
   compone un margine negativo con la fascia della colonna checkbox (che è
   ``position: sticky``) e con l'indentazione che Tabulator scrive inline sul
   padding della cella. Su Firefox il controllo risultava presente nel DOM ma
   non visibile, su Chrome sì. Lo spacer lo mette invece il formatter della
   colonna ISSUE (``dev-tree-spacer``, solo sulle righe senza figli): resta nel
   flusso, larghezza identica al controllo, nessun offset negativo. */
.dev-tree-spacer {
    display: inline-block;
    width: var(--dev-tree-control-size);
    margin-right: var(--dev-tree-control-gap);
    vertical-align: middle;
}
/* La misura vive in due custom property perché spacer e controllo DEVONO avere
   lo stesso INGOMBRO: è quello che tiene allineate le radici con e senza figli.
   ``--dev-tree-control-size`` è l'icona (SVG); ``--dev-tree-control-hit`` è il
   padding cliccabile attorno, che NON cambia l'ingombro (i margini negativi
   sotto lo annullano) e resta invisibile. */
.tabulator-row .tabulator-cell.dev-cell-tree {
    --dev-tree-control-size: 12px;
    --dev-tree-control-hit: 6px;
    --dev-tree-control-gap: 4px;
}
/* Il tema disegna il controllo come quadratino con bordo e un glifo fatto di
   pseudo-elementi; qui l'icona è un SVG inline passato a Tabulator, quindi il
   contenitore va spogliato di bordo e sfondo per non incorniciarla.
   Area cliccabile allargata SENZA cambiare la grafica: il padding
   (``box-sizing: content-box``) porta il box degli EVENTI a 18×24 (icona 12 +
   6 a destra, + 6 sopra e sotto), e margini negativi uguali al padding
   annullano quell'ingombro, così posizione dell'icona, gap col testo e
   allineamento con lo spacer restano identici (il margin-right sottrae il
   padding e ripristina il gap).
   Cruciale: il padding è SOLO a destra e in verticale, MAI a sinistra — così
   l'icona resta flush a sinistra senza bisogno di un margine negativo sinistro.
   È proprio il margine negativo verso sinistra che, combinato con la colonna
   checkbox ``position: sticky`` e l'indent inline scritto da Tabulator sul
   padding della cella, rendeva il controllo presente ma non dipinto su Firefox
   (nota sopra, righe dello spacer, e commit b2bdb35). Non toccando il lato
   verso la colonna sticky quello scenario non si ripresenta; l'espansione a
   destra e in verticale non attraversa alcun confine sticky.
   ``background-clip: content-box`` confina sfondo e :hover alla sola icona da
   12px: glifo ed evidenza restano identici, cresce solo l'area cliccabile. */
.tabulator-row .tabulator-cell.dev-cell-tree .tabulator-data-tree-control {
    box-sizing: content-box;
    width: var(--dev-tree-control-size);
    height: var(--dev-tree-control-size);
    /* T R B L */
    padding: var(--dev-tree-control-hit) var(--dev-tree-control-hit) var(--dev-tree-control-hit) 0;
    margin: calc(-1 * var(--dev-tree-control-hit))
            calc(var(--dev-tree-control-gap) - var(--dev-tree-control-hit))
            calc(-1 * var(--dev-tree-control-hit))
            0;
    border: 0;
    border-radius: 2px;
    background: transparent;
    background-clip: content-box;
    overflow: visible;
    color: #6b7280;
}
.tabulator-row .tabulator-cell.dev-cell-tree .tabulator-data-tree-control:hover {
    color: #111827;
    background: #f3f4f6;
    background-clip: content-box;
}

/* Righe tirate dentro come contesto dell'albero (un padre, un fratello o un
   figlio di una issue che soddisfa i filtri): attenuate, così si distingue
   quello che si stava cercando da ciò che lo accompagna. */
.tabulator-row.dev-row-unmatched {
    opacity: 0.45;
}

/* Toggle di testata della Lista (Chiuse / Da gestire / …), mirror dei toggle
   dell'header helpdesk. */
.dev-top-filters {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.dev-top-filter {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    font-size: 0.75rem;
    line-height: 1rem;
    color: #4b5563;
    cursor: pointer;
    white-space: nowrap;
}
.dev-top-filter:hover {
    color: #1f2937;
}

/* Anagrafica/Azienda open-modal link buttons inside a table cell. */
.dev-anag-open-btn {
    color: #2563eb;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-align: left;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dev-anag-open-btn:hover {
    text-decoration: underline;
}
.dev-anag-open-btn.is-self {
    color: #6b7280;
}

/* Ticket/Issue linked-record cell: closed/done → strike the number through
   (mirror of the chat-sessions .cs-ticket-closed). */
.dev-link-closed,
.hd-issue-done {
    text-decoration: line-through;
    color: #9ca3af !important;
    cursor: default;
}

/* ============================================================
   Activity log
   ============================================================ */
.activity-log {
    border-left: 2px solid #e5e7eb;
    padding-left: 1.5rem;
}

.activity-log-entry {
    position: relative;
    padding-bottom: 0.75rem;
}

.activity-log-entry::before {
    content: '';
    position: absolute;
    left: -1.8rem;
    top: 0.3rem;
    width: 8px;
    height: 8px;
    background: #3b82f6;
    border-radius: 50%;
}

.activity-log-entry.note-entry::before {
    background: #f59e0b;
}

/* ============================================================
   History / Cronologia timeline
   ============================================================ */
.history-timeline {
    border-left: 2px solid #e5e7eb;
    padding-left: 1.5rem;
    max-height: 300px;
    overflow-y: auto;
}
.history-timeline-entry {
    position: relative;
    padding-bottom: 0.625rem;
    font-size: 0.75rem;
}
.history-timeline-entry::before {
    content: '';
    position: absolute;
    left: -1.65rem;
    top: 0.25rem;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #9ca3af;
    z-index: 1;
}
.history-timeline-entry.ht-commit::before { background: #8b5cf6; }
.history-timeline-entry.ht-stage::before { background: #3b82f6; }
.history-timeline-entry.ht-message::before { background: #10b981; }
.history-timeline-entry.ht-phone::before { background: #f59e0b; }
.history-timeline-entry.ht-email::before { background: #ef4444; }

/* ============================================================
   Calendar styles
   ============================================================ */
.cal-cell {
    overflow-y: auto;
    min-width: 0;
}

.cal-today {
    background-color: #eff6ff;
}

.cal-day-cell {
    min-height: 60px;
    border-bottom: 1px solid #e5e7eb;
    min-width: 0;
    overflow: hidden;
}

.cal-day-cell:hover {
    background-color: #f0f9ff;
    cursor: pointer;
}

.cal-week-cell {
    min-height: 50px;
    min-width: 0;
    overflow: hidden;
}

.cal-week-cell:hover {
    background-color: #f0f9ff;
    cursor: pointer;
}

.cal-week-cell, .cal-day-cell {
    position: relative;
}

.cal-quarter-indicator {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background: #3b82f6;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.1s;
    z-index: 10;
}

.cal-week-cell:hover .cal-quarter-indicator,
.cal-day-cell:hover .cal-quarter-indicator {
    opacity: 1;
}

.fullday-bar {
    min-height: 26px;
}

/* Calendar appointment items — fixed width, no overflow */
.cal-appointment {
    display: block;
    max-width: 100%;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
}

/* ============================================================
   Quill Editor overrides
   ============================================================ */
.ql-container {
    font-size: 0.875rem;
    font-family: inherit;
}
.ql-editor {
    min-height: 120px;
    line-height: 1.6;
}
.ql-toolbar.ql-snow {
    border-color: #d1d5db;
    border-radius: 0.5rem 0.5rem 0 0;
}
.ql-container.ql-snow {
    border-color: #d1d5db;
    border-radius: 0 0 0.5rem 0.5rem;
}
.ql-editor.ql-blank::before {
    font-style: normal;
    color: #9ca3af;
}
.ql-snow .ql-editor h2 {
    font-size: 1.25rem;
    font-weight: 700;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    color: #111827;
}
.ql-snow .ql-editor h3 {
    font-size: 1.1rem;
    font-weight: 600;
    margin-top: 0.75rem;
    margin-bottom: 0.5rem;
    color: #1f2937;
}
.ql-snow .ql-editor p {
    margin-bottom: 0.5rem;
}
.ql-snow .ql-editor ul,
.ql-snow .ql-editor ol {
    padding-left: 1.5rem;
    margin-bottom: 0.5rem;
}
.ql-snow .ql-editor li {
    margin-bottom: 0.25rem;
}
.ql-snow .ql-editor blockquote {
    border-left: 4px solid #d1d5db;
    padding-left: 1rem;
    color: #6b7280;
    margin: 0.5rem 0;
}
.ql-snow .ql-editor strong {
    font-weight: 700;
}
.ql-snow .ql-editor em {
    font-style: italic;
}
.ql-snow .ql-editor a {
    color: #2563eb;
    text-decoration: underline;
}
/* KB article/FAQ content editors (kb/list.html modals) */
#kb-article-editor .ql-editor {
    min-height: 320px;
}
#kb-faq-editor .ql-editor {
    min-height: 180px;
}
.ql-snow .ql-editor pre.ql-syntax {
    background-color: #1f2937;
    color: #e5e7eb;
    padding: 0.75rem 1rem;
    border-radius: 0.375rem;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.8125rem;
    margin: 0.5rem 0;
}

/* ============================================================
   Toggle switch
   ============================================================ */
.toggle-switch { position: relative; display: inline-block; width: 36px; height: 20px; flex-shrink: 0; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #d1d5db; transition: 0.2s; border-radius: 20px; }
.toggle-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: 0.2s; border-radius: 50%; }
.toggle-switch input:checked + .toggle-slider { background-color: #3b82f6; }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(16px); }

/* ============================================================
   SLA scadenza change highlight
   ============================================================ */
@keyframes sla-highlight {
    0%, 100% { background-color: transparent; }
    25%, 75% { background-color: rgba(245, 158, 11, 0.3); }
}
.sla-scadenza-changed {
    animation: sla-highlight 0.6s ease 3;
    border-radius: 4px;
}

/* ============================================================
   Environment indicators (test / development)
   ============================================================ */

/* Sidebar env line — colored stripe on right edge via ::after, no layout impact */
.sidebar-env-glow-test,
.sidebar-env-glow-dev { position: relative; }
.sidebar-env-glow-test::after,
.sidebar-env-glow-dev::after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    width: 3px;
    height: 100%;
    z-index: 10;
}
.sidebar-env-glow-test::after { background: #ef4444; }
.sidebar-env-glow-dev::after  { background: #f59e0b; }

/* Sidebar header (hamburger + logo) */
.logo-header {
    position: relative;
    overflow: visible !important;
}

/* Env pill badge on header */
.logo-env-pill {
    position: absolute;
    bottom: -3px;
    right: 4px;
    z-index: 20;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    border-radius: 9999px;
    line-height: 1.4;
    letter-spacing: 0.5px;
    padding: 1px 5px;
    animation: env-badge-pulse 2s ease-in-out infinite;
}
.logo-env-pill-test { background: #dc2626; }
.logo-env-pill-dev  { background: #d97706; }

@keyframes env-badge-pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.2); }
}

/* ============================================================
   Skeleton loading — shimmer placeholder
   ============================================================
   Used while async content is fetched (helpdesk KB suggest panel
   and KB article preview). A highlight band sweeps across a
   neutral block so the wait reads clearly as "loading". The bare
   Tailwind `animate-pulse` (an opacity fade on a near-white block)
   was too faint on light backgrounds and looked static. */
.skeleton-block {
    position: relative;
    overflow: hidden;
    background-color: #e5e7eb; /* gray-200 base */
    border-radius: 0.375rem;   /* matches Tailwind `rounded` */
}
.skeleton-block::after {
    content: '';
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(
        90deg,
        rgba(255, 255, 255, 0) 0,
        rgba(255, 255, 255, 0.65) 50%,
        rgba(255, 255, 255, 0) 100%
    );
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
    100% { transform: translateX(100%); }
}
/* Respect reduced-motion: drop the sweep, keep the neutral block. */
@media (prefers-reduced-motion: reduce) {
    .skeleton-block::after { animation: none; }
    /* Presence: niente pulse per chi ha ridotto le animazioni (ring statico). */
    .ticket-presence-avatar { animation: none; box-shadow: 0 0 0 1.5px #fff, 0 0 0 3px #ef4444; }
}

/* Email — Gmail-like font for all email content */
.email-body-container,
.email-body-html,
.email-body-text,
.composer-main-editor .ql-editor,
.composer-quote-editor .ql-editor {
    font-family: 'Google Sans', Roboto, Arial, Helvetica, sans-serif;
}

/* Email composer — Quill editors. The composer body has ONE scroll region;
   the editor grows to fill it instead of scrolling internally (no max-height
   in vh that left dead white space, no inner overflow that produced a second
   scrollbar — the two layout horrors of the old forms). */
.composer-main-editor,
.composer-quote-editor {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
/* Quill turns the container element ITSELF into .ql-container (it adds the
   class to .composer-main-editor / .composer-quote-editor) — style it directly,
   NOT as a descendant. The .ql-editor is a child (descendant selector ok). */
.composer-main-editor.ql-container,
.composer-quote-editor.ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow: visible;
}
/* Main editor sizing. WITHOUT a quote (new compose) it grows to fill the
   composer so the writing area is large. WITH a quote (reply/forward) it must
   be CONTENT height instead: otherwise an empty writing area grows to fill the
   whole composer and pushes the "..." quote (and the expanded quoted text)
   below the fold, reachable only by scrolling (the reported bug). shrink:0 in
   both cases so a tall quote never squeezes the editor to zero — the scroll
   region scrolls instead; `.ql-editor { min-height }` keeps a writing floor. */
.composer-main-editor.ql-container {
    flex: 1 0 auto;
}
.composer-has-quote .composer-main-editor.ql-container {
    flex: 0 0 auto;
    /* Quill base sets .ql-container/.ql-editor { height: 100% }. The main editor
       is a direct child of the fixed-height .composer-scroll, so height:100% +
       flex-basis:auto makes it FILL the composer (grow:0 doesn't help — the
       basis already resolves to 100%) and pushes the quote below the fold.
       Force content height so the quote sits right under the writing area. */
    height: auto;
}
.composer-has-quote .composer-main-editor .ql-editor {
    height: auto;
    /* Hug the writing line: with a quote, the base 160px floor would leave a big
       empty gap before the "..." toggle (which reads as an arbitrary position).
       A one-line floor puts "..." right under "Scrivi una risposta"; the editor
       grows with typed content and the toggle moves down with it. */
    min-height: 2.5rem;
}
.composer-main-editor .ql-editor,
.composer-quote-editor .ql-editor {
    overflow: visible;
    font-size: 0.875rem;
    line-height: 1.625;
}
.composer-main-editor .ql-editor {
    min-height: 160px;
}
.composer-quote-editor .ql-editor {
    min-height: 80px;
}
.composer-main-editor .ql-editor.ql-blank::before,
.composer-quote-editor .ql-editor.ql-blank::before {
    font-style: normal;
    color: #9ca3af;
}
/* Quill embedded images */
.composer-main-editor .ql-editor img,
.composer-quote-editor .ql-editor img {
    max-width: 100%;
    height: auto;
    border-radius: 4px;
}
/* Seamless Gmail-like main editor: no box frame around the writing area. */
.composer-main-editor.ql-container.ql-snow {
    border: none;
}
/* Quill inserts .ql-toolbar as the previousElementSibling of the container,
   i.e. as a DIRECT child of the scroll region (.composer-scroll). Hide that
   main toolbar by default; the "Aa" toggle adds .composer-show-toolbar to the
   scroll region to reveal it. `> .ql-toolbar` keeps this from touching the
   quote editor's own (nested) toolbar. */
.composer-scroll > .ql-toolbar {
    display: none;
}
.composer-scroll.composer-show-toolbar > .ql-toolbar {
    display: block;
    border: none;
    border-bottom: 1px solid #e5e7eb;
}
/* Editable quote region: reads as an inline quoted block (the gmail_quote
   content carries its own left rule), not a second framed Quill editor. The
   toolbar module is kept (quill-table-better needs it) but its strip is hidden,
   and the container is borderless. */
.composer-quote-block .ql-toolbar {
    display: none;
}
.composer-quote-editor.ql-container.ql-snow {
    border: none;
}
/* The quote editor must be CONTENT-height (not flex-grow), otherwise the left
   rule extends past the quoted text down into the attach zone below. */
.composer-quote-editor.ql-container {
    flex: 0 0 auto;
}
/* ONE continuous, uniform quote rule: a single left border on the editor
   container, and EVERY inner left-border killed (Quill blockquote blots,
   mail-client divs/blockquotes with inline borders, nested quotes, …) so the
   line never fragments regardless of the source email markup. */
.composer-quote-editor .ql-editor {
    border-left: 2px solid #dadce0;
    padding-left: 14px;
    color: #5f6368;
}
.composer-quote-editor .ql-editor * {
    border-left: 0 !important;
}
.composer-quote-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 20px;
    border-radius: 10px;
    background: #e8eaed;
    color: #5f6368;
    transition: background 0.12s ease;
}
.composer-quote-toggle:hover {
    background: #dadce0;
}

/* Email — Gmail-compact thread list rows */
.email-row {
    height: 40px;
    min-height: 40px;
}

/* Email row action buttons (archive / trash / spam / …) reveal.
   Hover-capable devices (desktop): reveal on row hover / focus-within,
   as before. Touch devices: the buttons stay hidden AND inert until the
   right-hand action zone is tapped once (Alpine adds .row-actions-revealed
   to the row) — the first tap only reveals, a second tap fires the button.
   This avoids the mobile bug where tapping the right side of a row fired a
   still-hidden button by accident (emulated :hover made it interactive on
   the same tap). */
@media (hover: hover) {
    .email-row .email-row-actions {
        opacity: 0;
        pointer-events: none;
    }
    .email-row:hover .email-row-actions,
    .email-row:focus-within .email-row-actions {
        opacity: 1;
        pointer-events: auto;
    }
    .email-row:hover .email-row-meta,
    .email-row:focus-within .email-row-meta {
        visibility: hidden;
    }
}
@media (hover: none) {
    /* The zone stays hittable (opacity 0, pointer-events:auto) so it can catch
       the first reveal tap, but the buttons inside are visibility:hidden. That
       removes them from the accessibility tree AND the tab order (not just
       hit-testing, as pointer-events would), so neither a stray tap, a
       keyboard Tab, nor a VoiceOver swipe can reach a hidden Archive/Trash/Spam
       button before it is revealed. First tap reveals them; a second tap
       activates. */
    .email-row .email-row-actions {
        opacity: 0;
        pointer-events: auto;
    }
    .email-row .email-row-actions > * {
        visibility: hidden;
    }
    .email-row.row-actions-revealed .email-row-actions {
        opacity: 1;
    }
    .email-row.row-actions-revealed .email-row-actions > * {
        visibility: visible;
    }
    .email-row.row-actions-revealed .email-row-meta {
        visibility: hidden;
    }
}

/* Email/Helpdesk message bodies — layout containment. Message HTML is
   sender-controlled: without containment, inline CSS like
   position:absolute + huge z-index (Outlook/Word VML wrappers) paints
   over the app UI. The sanitizer strips those properties from new
   content; this block is the guarantee for content stored before that
   rule (helpdesk sanitizes at store time, not render time).
   - contain: paint → clips painting to the box and makes it the
     containing block for absolute/fixed descendants (also a stacking
     context, so z-index stays confined);
   - position: relative + isolation: isolate → same containing-block and
     stacking-context guarantees where `contain` is unsupported;
   - overflow-x: auto → content wider than the box (newsletter tables)
     stays reachable via scroll instead of being clipped by paint
     containment. */
.email-body-html,
.ticket-message-body,
.ticket-message-quoted {
    position: relative;
    isolation: isolate;
    contain: paint;
    overflow-x: auto;
}

/* Email — Body HTML styling */
.email-body-html {
    font-size: 0.875rem;
    line-height: 1.625;
}
/* Rendered email/message HTML — mirror Quill editor block styling so the
   stored body matches what the sender composed (paragraph spacing,
   blockquote indent, list markers, heading sizes, inline-code highlight).
   Scoped to ALL surfaces that render composed/received HTML: the email
   client (.email-body-html) AND the helpdesk thread message body + its
   collapsed quoted part (.ticket-message-body / .ticket-message-quoted).
   Without the ticket selectors, only Tailwind's preflight reset applies in
   the helpdesk thread — it strips <p> margins, list markers and blockquote
   indent, so an AI/operator draft that looked formatted in the composer
   renders as a flat wall of text once sent (the reported bug). */
.email-body-html img,
.ticket-message-body img,
.ticket-message-quoted img {
    max-width: 100%;
    height: auto;
}
.email-body-html h1,
.ticket-message-body h1,
.ticket-message-quoted h1 {
    font-size: 1.5rem;
    font-weight: 700;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    color: #111827;
}
.email-body-html h2,
.ticket-message-body h2,
.ticket-message-quoted h2 {
    font-size: 1.25rem;
    font-weight: 700;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    color: #111827;
}
.email-body-html h3,
.ticket-message-body h3,
.ticket-message-quoted h3 {
    font-size: 1.1rem;
    font-weight: 600;
    margin-top: 0.75rem;
    margin-bottom: 0.5rem;
    color: #1f2937;
}
.email-body-html p,
.ticket-message-body p,
.ticket-message-quoted p,
.dev-comment-body p {
    margin-bottom: 0.5rem;
}
.email-body-html ul,
.email-body-html ol,
.ticket-message-body ul,
.ticket-message-body ol,
.ticket-message-quoted ul,
.ticket-message-quoted ol {
    padding-left: 1.5rem;
    margin-bottom: 0.5rem;
}
.email-body-html ul,
.ticket-message-body ul,
.ticket-message-quoted ul { list-style: disc outside; }
.email-body-html ol,
.ticket-message-body ol,
.ticket-message-quoted ol { list-style: decimal outside; }
.email-body-html li,
.ticket-message-body li,
.ticket-message-quoted li {
    margin-bottom: 0.25rem;
}
.email-body-html blockquote,
.ticket-message-body blockquote,
.ticket-message-quoted blockquote {
    border-left: 4px solid #d1d5db;
    padding-left: 1rem;
    color: #6b7280;
    margin: 0.5rem 0;
}
.email-body-html strong,
.ticket-message-body strong,
.ticket-message-quoted strong,
.dev-comment-body strong { font-weight: 700; }
.email-body-html em,
.ticket-message-body em,
.ticket-message-quoted em,
.dev-comment-body em { font-style: italic; }
.email-body-html a,
.ticket-message-body a,
.ticket-message-quoted a,
.dev-comment-body a {
    color: #2563eb;
    text-decoration: underline;
}
/* Inline code (field names / identifiers) — light highlight matching the
   Quill composer so `patient.id`-style spans stay visually marked after
   send. `pre code` resets it so fenced code blocks aren't double-styled. */
.email-body-html code,
.ticket-message-body code,
.ticket-message-quoted code {
    background: rgba(0, 0, 0, 0.06);
    padding: 1px 4px;
    border-radius: 3px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.875em;
}
/* Rendered email/ticket <pre>: a LIGHT code block, not a dark one.
   Outlook/OWA wraps quoted prose in (nested) <pre> whose inner text carries an
   inline `color: rgb(0,0,0)`. A dark `background-color` here put that black
   text on a near-black box → unreadable (reported: email thread 69661 /
   ticket #4506 — the dark box was OUR <pre> style, not a sender background).
   A light surface with dark default text stays readable whether the content
   has an inline dark color (sender prose) or none (real code blocks). */
.email-body-html pre,
.ticket-message-body pre,
.ticket-message-quoted pre {
    background-color: #f6f8fa;
    color: #1f2937;
    border: 1px solid #e5e7eb;
    padding: 0.75rem 1rem;
    border-radius: 0.375rem;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.8125rem;
    margin: 0.5rem 0;
}
.email-body-html pre code,
.ticket-message-body pre code,
.ticket-message-quoted pre code {
    background: none;
    padding: 0;
    border-radius: 0;
}
.email-body-text {
    font-family: 'Google Sans', Roboto, Arial, Helvetica, sans-serif;
    font-size: 0.875rem;
    line-height: 1.625;
}

/* Knowledge Base rendered article/FAQ HTML — single shared rendering engine.
   Applied wherever stored KB HTML (contenuto_html / risposta_html) is shown:
   the KB page article view + FAQ list (.kb-preview-content) AND the helpdesk
   KB suggestion modal preview. Lives here (not in a page-local <style>) so the
   helpdesk preview renders identically to the KB page instead of falling back
   to Tailwind's preflight reset — without it, headings/lists/blockquotes/
   tables collapse into a flat wall of text. The previous helpdesk markup used
   `prose prose-sm` (Tailwind Typography, dropped with the play CDN — see
   docs/adr/2026-06-11-tailwind-precompiled-css-drop-play-cdn.md) and an
   undefined `kb-article-body`, so it had no effective styling at all. */
.kb-preview-content h1 { font-size: 1.5rem; font-weight: 700; margin: 1.25rem 0 0.75rem; color: #111827; }
.kb-preview-content h2 { font-size: 1.25rem; font-weight: 700; margin: 1.25rem 0 0.75rem; color: #111827; }
.kb-preview-content h3 { font-size: 1.1rem; font-weight: 600; margin: 1rem 0 0.5rem; color: #1f2937; }
.kb-preview-content h4 { font-size: 1rem; font-weight: 600; margin: 0.75rem 0 0.5rem; color: #374151; }
.kb-preview-content p { margin: 0.5rem 0; line-height: 1.7; color: #374151; }
.kb-preview-content ul { list-style-type: disc; padding-left: 1.5rem; margin: 0.5rem 0; }
.kb-preview-content ol { list-style-type: decimal; padding-left: 1.5rem; margin: 0.5rem 0; }
.kb-preview-content li { margin: 0.25rem 0; line-height: 1.6; color: #374151; }
.kb-preview-content blockquote { border-left: 4px solid #3b82f6; padding: 0.75rem 1rem; margin: 0.75rem 0; background: #eff6ff; border-radius: 0 0.375rem 0.375rem 0; color: #1e40af; }
.kb-preview-content a { color: #2563eb; text-decoration: underline; }
.kb-preview-content a:hover { color: #1d4ed8; }
.kb-preview-content img { max-width: 100%; border-radius: 0.5rem; margin: 0.75rem 0; }
.kb-preview-content hr { border: none; border-top: 2px solid #e5e7eb; margin: 1.5rem 0; }
.kb-preview-content table { width: 100%; border-collapse: collapse; margin: 0.75rem 0; font-size: 0.875rem; }
.kb-preview-content th { background: #f9fafb; font-weight: 600; text-align: left; padding: 0.5rem 0.75rem; border: 1px solid #e5e7eb; }
.kb-preview-content td { padding: 0.5rem 0.75rem; border: 1px solid #e5e7eb; }
.kb-preview-content code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
.kb-preview-content pre { background: #1f2937; color: #e5e7eb; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; margin: 0.75rem 0; font-size: 0.875rem; line-height: 1.5; }
.kb-preview-content pre code { background: none; padding: 0; color: inherit; font-size: inherit; }
.kb-preview-content del { text-decoration: line-through; color: #9ca3af; }
.kb-preview-content input[type="checkbox"] { margin: 0; pointer-events: none; }

/* Email thread list scrollbar */
.email-thread-list {
    scrollbar-width: thin;
}

/* ── Email thread reader — narrow-width compaction ──
   Keyed on the thread pane itself (`emailthread` container = the
   `max-w-4xl` message column), NOT `pagemain`: `.page-layout` also spans
   the persistent folder sidebar + the `px-4` wrapper, so a `pagemain`
   threshold would mis-fire (e.g. landscape phone, collapsed sidebar:
   `pagemain` > 640px while the actual card is ~583px and the labels still
   overflow). Measuring the pane gives the real available width regardless
   of sidebar/padding (rule: label compaction → @container, never @media).
   On a phone the message header (avatar + sender/badge + date + action
   buttons) and the blocked-external-images banner overflowed the card —
   which clips with `overflow-hidden` — so the date collided with the
   "Bozza non inviata" badge and the banner toggles were cut off. Below the
   threshold we drop the verbose text and keep the icons (detail still
   reachable via the title/aria-label tooltip), and the right-side action
   cluster stacks the date above the icons so it stops competing for width
   with the sender/badge column. */
.email-thread-pane {
    container-type: inline-size;
    container-name: emailthread;
}
/* Blocked-images banner: desktop shows the full sentence; the short label is
   the mobile-only collapsed form (swapped in by the @container rule below). */
.email-img-banner-short { display: none; }
@container emailthread (max-width: 640px) {
    .email-msg-action-label { display: none; }
    .email-msg-actions {
        flex-direction: column;
        align-items: flex-end;
        gap: 0.25rem;
    }
    /* Banner: collapse to "Immagini bloccate"; clicking the icon/text sets
       `.email-img-expanded` on the row → reveal the full sentence. */
    .email-img-banner-long { display: none; }
    .email-img-banner-short { display: inline; }
    .email-img-expanded .email-img-banner-long { display: inline; }
    .email-img-expanded .email-img-banner-short { display: none; }
}

/* Sticky reply footer — keyed on the content area (right of the folder
   sidebar) so the threshold tracks the real footer width, not the viewport.
   Gmail-mobile style: the PRIMARY action ("Rispondi a tutti") stays a labelled
   pill; the secondary ones (Rispondi / Inoltra) collapse to compact icon
   buttons (label hidden, square padding). Detail stays in title/aria-label. */
.email-content-pane {
    container-type: inline-size;
    container-name: emailmain;
}
@container emailmain (max-width: 640px) {
    .email-reply-footer { gap: 0.5rem; }
    .reply-footer-secondary .reply-footer-label { display: none; }
    .reply-footer-secondary { padding: 0.5rem; }
}

/* Broken / missing image placeholder */
img.img-broken {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    min-width: 48px;
    background: #f3f4f6;
    border: 1px dashed #d1d5db;
    border-radius: 0.375rem;
    position: relative;
    color: #6b7280;
    font-size: 0.75rem;
    text-align: center;
    padding: 24px 8px 8px;
    overflow: hidden;
}
img.img-broken::before {
    content: '';
    display: block;
    width: 24px;
    height: 24px;
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'/%3E%3Cpolyline points='21 15 16 10 5 21'/%3E%3Cline x1='2' y1='2' x2='22' y2='22'/%3E%3C/svg%3E");
    background-size: contain;
    background-repeat: no-repeat;
}
img.img-broken::after {
    content: attr(alt);
}

/* Quill drag-over feedback (inline images) */
.ql-editor.ql-drag-over {
    background-color: #eff6ff;
    outline: 2px dashed #3b82f6;
    outline-offset: -2px;
}

/* Compose attachment drop zone */
.compose-attach-dropzone {
    padding: 6px 0;
    border-radius: 0.5rem;
    border: 1px dashed transparent;
    transition: all 0.15s ease;
}
.compose-attach-dropzone-hover {
    border-color: #3b82f6;
    background-color: #eff6ff;
    padding: 6px 8px;
}

/* ============================================================
   Email tag-input (To/CC recipients)
   ============================================================ */
.email-tag-input {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    background: white;
    min-height: 38px;
    cursor: text;
}
.email-tag-input:focus-within {
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
.email-tag {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px 6px;
    background: #eff6ff;
    border: 1px solid #bfdbfe;
    border-radius: 4px;
    font-size: 0.75rem;
    color: #1e40af;
    white-space: nowrap;
    flex-shrink: 0;
}
.email-tag-remove {
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    color: #93c5fd;
    margin-left: 2px;
    background: none;
    border: none;
    padding: 0;
}
.email-tag-remove:hover {
    color: #ef4444;
}
.email-tag-input-field {
    border: none;
    outline: none;
    flex: 1;
    min-width: 80px;
    font-size: 0.875rem;
    padding: 4px 0;
    background: transparent;
}

/* Autocomplete dropdown */
.email-autocomplete-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 100;
    background: white;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    max-height: 200px;
    overflow-y: auto;
    margin-top: 4px;
}
.email-autocomplete-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    cursor: pointer;
    transition: background 0.1s;
}
.email-autocomplete-item:hover {
    background: #f3f4f6;
}

/* Email account connection test spinner */
@keyframes status-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}
.status-testing {
    animation: status-spin 1s linear infinite;
    color: #3b82f6;
}

/* ============================================================
   Select2 overrides (Tailwind-consistent styling)
   ============================================================ */

/* Il clone accessibile del <select> originale (`.select2-hidden-accessible`)
   è `position: absolute !important` senza `top`/`left`: resta quindi alla
   propria posizione statica, ma il suo containing block è il PRIMO antenato
   posizionato — e se nessun antenato lo è, diventa l'initial containing block
   (il documento). In quel caso il contenitore scrollabile che lo ospita NON lo
   clippa: il clone sporge sotto la viewport e allunga
   `documentElement.scrollHeight`, facendo comparire una scrollbar di PAGINA su
   un'app che invece è a schermo pieno (lo scroll deve vivere dentro le
   colonne). Succedeva sul dettaglio issue del Dev Tracker, dove i Select2
   (responsabile, co-assegnatari, etichette, clienti, ticket, relazioni) stanno
   inline nella colonna proprietà scrollabile e non più in un modale: l'ultimo
   clone finiva a y≈1400 su una viewport da 720px → 676px di scroll di
   documento (il resto della pagina restava clippato, quindi si scrollava su
   area bianca).

   Ancorare il clone a 0,0 lo rende inerte per il layout in ogni contesto,
   posizionato o non posizionato: resta 1×1 e clippato (`clip`/`clip-path` di
   Select2), quindi invisibile e ancora raggiungibile dagli screen reader, ma
   non può più estendere il documento. Fix globale e non per-pagina di
   proposito: qualsiasi nuova pagina che monti un Select2 dentro un pannello
   scrollabile ricadrebbe altrimenti nello stesso bug. */
.select2-hidden-accessible {
    top: 0;
    left: 0;
}

.select2-container--default .select2-selection--single {
    height: 38px; border: 1px solid #d1d5db; border-radius: 0.5rem; background-color: #ffffff;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 36px; padding-left: 12px; font-size: 0.875rem; color: #111827;
}
.select2-container--default .select2-selection--single .select2-selection__placeholder {
    color: #9ca3af;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 36px;
}
.select2-container--default.select2-container--focus .select2-selection--single {
    border-color: #3b82f6; outline: none; box-shadow: 0 0 0 2px rgba(59,130,246,0.3);
}
.select2-dropdown {
    border: 1px solid #d1d5db; border-radius: 0.5rem;
    box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
    background-color: #eff6ff; color: #1d4ed8;
}
.select2-search--dropdown .select2-search__field {
    border: 1px solid #d1d5db; border-radius: 0.375rem;
    padding: 4px 8px; font-size: 0.875rem;
}
.select2-container--default .select2-selection__clear {
    margin-right: 4px; font-size: 1.1rem;
}
/* Multi-select (partecipanti): allinea il tema Tailwind del single-select.
   Box con border/rounded/altezza coerenti, chip compatti on-brand. */
.select2-container--default .select2-selection--multiple {
    min-height: 38px; border: 1px solid #d1d5db; border-radius: 0.5rem;
    background-color: #ffffff; padding: 3px 6px;
}
.select2-container--default.select2-container--focus .select2-selection--multiple,
.select2-container--default.select2-container--open .select2-selection--multiple {
    border-color: #3b82f6; outline: none; box-shadow: 0 0 0 2px rgba(59,130,246,0.3);
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
    display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
    padding: 0; margin: 0;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
    display: inline-flex; align-items: center;
    background-color: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af;
    border-radius: 0.375rem; padding: 1px 8px; margin: 2px 0;
    font-size: 0.8125rem; line-height: 1.5;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
    /* Select2 4.1.x posiziona la × in absolute dentro il chip: con il chip in
       inline-flex va riportata in-flow, altrimenti si sovrappone alla label. */
    position: static; inset: auto; transform: none; width: auto; height: auto;
    color: #1e40af; background: transparent; border: none;
    margin-right: 6px; padding: 0; font-size: 1rem; line-height: 1; cursor: pointer;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
    color: #1e3a8a; background: transparent;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__display {
    padding: 0;
}
.select2-container--default .select2-selection--multiple .select2-search--inline .select2-search__field {
    /* height:auto + a line-height/height pair that leaves room for descenders:
       with a fixed ~20px height and line-height:1.5 the placeholder's g/p/q were
       clipped at the bottom edge of the box. */
    margin: 2px 0; font-size: 0.875rem; color: #111827;
    height: auto; line-height: 1.6; padding: 2px 0;
}
.select2-container--default .select2-selection--multiple .select2-search--inline .select2-search__field::placeholder {
    color: #9ca3af;
}
/* Tipo Select2: icon + colored text, colored background */
#ana-tipo-select + .select2-container .select2-selection--single .select2-selection__rendered {
    display: flex !important; align-items: center; height: 36px;
    overflow: visible; line-height: normal;
}
/* Dropdown option colors for Tipo select */
.select2-results .tipo-opt-persona { color: #166534; }
.select2-results .tipo-opt-azienda { color: #1e40af; }
/* On hover, brighten the colored text to keep the hue but show highlight */
.select2-container--default .select2-results__option--highlighted[aria-selected] .tipo-opt-persona,
.select2-container--default .select2-results__option--highlighted[aria-selected] .tipo-opt-azienda {
    filter: brightness(1.6);
}

/* ============================================================
   Email Composer — shared root + two layout modes (compose + reply)
   ============================================================ */
.email-composer-root {
    z-index: 60;
    background: white;
    display: flex;
    flex-direction: column;
    transition: width 0.2s ease, height 0.2s ease;
}

/* Docked: Gmail-style bottom-right, NON-blocking (no backdrop). Wide enough
   that the Quill formatting toolbar never wraps to a second line; capped to
   the viewport so it stays usable on small monitors. */
.email-composer-docked {
    position: fixed;
    bottom: 0;
    right: 24px;
    width: 600px;
    max-width: calc(100vw - 48px);
    height: min(660px, 90vh);
    max-height: 90vh;
    border-radius: 12px 12px 0 0;
    box-shadow: 0 -2px 24px rgba(0, 0, 0, 0.18);
}

/* Fullscreen: truly FULL — cover the whole viewport (incl. the app header),
   no centered card. Restore/close live in the composer's own header bar. */
.email-composer-fullscreen {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: 0;
}

/* ── Composer recipients: Gmail-compact thin rows ──
   No boxes, no labels-above. Each field is a thin placeholder row separated
   by a hairline, with a short gray prefix (Da / A / Cc / Ccn) on the left.
   New mail shows only A + Oggetto (Cc/Ccn behind links on the A row); reply
   opens collapsed to a single recipient line that expands on click. */
.composer-collapsed {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 0;
    border-bottom: 1px solid #f0f0f0;
    cursor: pointer;
    font-size: 0.875rem;
}
.composer-collapsed:hover {
    background: #fafafa;
}
.composer-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 34px;
    padding: 1px 0;
    border-bottom: 1px solid #f0f0f0;
}
.composer-row-label {
    flex-shrink: 0;
    width: 2rem;
    font-size: 0.875rem;
    color: #5f6368;
}
.composer-row-field {
    flex: 1 1 auto;
    min-width: 0;
}
.composer-row-actions {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 4px;
}
.composer-row-actions button {
    font-size: 0.875rem;
    color: #5f6368;
    padding: 2px 6px;
    border-radius: 4px;
    transition: background 0.1s, color 0.1s;
}
.composer-row-actions button:hover {
    background: #f3f4f6;
    color: #1f2937;
}
.composer-firmati {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.8125rem;
    color: #5f6368;
    cursor: pointer;
    white-space: nowrap;
}
.composer-subject-input {
    width: 100%;
    border: none;
    outline: none;
    background: transparent;
    font-size: 0.875rem;
    padding: 5px 0;
    color: #111827;
}
.composer-subject-input::placeholder {
    color: #9ca3af;
    font-weight: 400;
}

/* Borderless tag-input inside a composer row: drop the box, keep the chips. */
.composer-row .email-tag-input {
    border: none;
    border-radius: 0;
    padding: 1px 0;
    min-height: 30px;
    background: transparent;
}
.composer-row .email-tag-input:focus-within {
    border: none;
    box-shadow: none;
}

/* Borderless From (account) Select2 inside the Da row. */
.composer-da-select { width: 100%; }
.composer-row .select2-container { width: 100% !important; }
.composer-row .select2-container--default .select2-selection--single {
    border: none;
    border-radius: 0;
    height: 30px;
    background: transparent;
}
.composer-row .select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: 30px;
    padding-left: 0;
    font-size: 0.875rem;
    color: #1f2937;
}
.composer-row .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 28px;
}
.composer-row .select2-container--default.select2-container--focus .select2-selection--single {
    border: none;
    box-shadow: none;
}

/* ── CSV Import ── */

.csv-dropzone {
    border: 2px dashed #d1d5db;
    border-radius: 0.75rem;
    padding: 3rem 2rem;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s;
}
.csv-dropzone:hover {
    border-color: #93c5fd;
    background-color: #eff6ff;
}
.csv-dropzone-active {
    border-color: #3b82f6;
    background-color: #dbeafe;
}

.csv-preview-table {
    overflow-x: auto;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
}
.csv-preview-table.csv-mapping-table {
    overflow: visible;
}
.csv-preview-table table {
    border-collapse: collapse;
}
.csv-preview-table th,
.csv-preview-table td {
    border-bottom: 1px solid #e5e7eb;
    white-space: nowrap;
}

.csv-result-card {
    padding: 1rem;
    border-radius: 0.5rem;
    text-align: center;
}
.csv-result-card-created { background-color: #ecfdf5; }
.csv-result-card-created .csv-result-number { color: #059669; }
.csv-result-card-merged { background-color: #fffbeb; }
.csv-result-card-merged .csv-result-number { color: #d97706; }
.csv-result-card-unchanged { background-color: #f3f4f6; }
.csv-result-card-unchanged .csv-result-number { color: #6b7280; }
.csv-result-card-linked { background-color: #eff6ff; }
.csv-result-card-linked .csv-result-number { color: #2563eb; }
.csv-result-card-skipped { background-color: #fff7ed; }
.csv-result-card-skipped .csv-result-number { color: #ea580c; }
.csv-result-card-errors { background-color: #fef2f2; }
.csv-result-card-errors .csv-result-number { color: #dc2626; }

/* Mapping step – field selects (compact for table layout) */
.csv-mapping-select {
    width: 100%;
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    padding: 0.25rem 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.25rem;
    background-color: #fff;
    outline: none;
    appearance: auto;
}
.csv-mapping-select:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
.csv-mapping-select option,
.csv-mapping-select optgroup {
    font-size: 0.8125rem;
    line-height: 1.2;
    padding: 2px 6px;
}
.csv-mapping-select optgroup {
    font-weight: 600;
    font-style: normal;
    color: #374151;
}

/* Sidebar mobile-only elements — hidden on desktop */
.sidebar-mobile-backdrop {
    display: none;
}

/* ============================================================
   Email sidebar — collapsible like Gmail
   ============================================================ */
.email-sidebar {
    transition: width 200ms ease;
}
.email-sidebar-expanded {
    width: 14rem;
}
.email-sidebar-collapsed {
    width: 3.25rem;
}
.email-sidebar-item {
    padding-left: 1rem;
    padding-right: 1rem;
}
.email-sidebar-collapsed .email-sidebar-item {
    padding-left: 0;
    padding-right: 0;
    justify-content: center;
}

/* View tab icons — ALWAYS visible (icon + label when wide, icon only when
   the label hides on compaction), so the tabs can never collapse to empty
   pills. Sized here (not via Tailwind w-4/h-4) so the icon never renders at
   0/auto size before the runtime Tailwind CDN injects its utilities. */
.view-tab-icon {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
}

/* Calendar view dropdown — hidden on desktop, shown on mobile */
.cal-view-dropdown {
    display: none;
}

/* Email account filters — hide scrollbar */
.email-account-filters::-webkit-scrollbar {
    display: none;
}

/* Calendar header label — full on desktop, short on mobile */
.cal-header-short {
    display: none;
}

/* ============================================================
   Anagrafiche — tipo column: hide text label on mobile
   ============================================================ */
@media (max-width: 768px) {
    .col-tipo-anagrafica .tipo-label { display: none; }
    .col-tipo-anagrafica { min-width: 50px !important; width: 50px !important; max-width: 60px !important; }
}

/* ============================================================
   RESPONSIVE — Mobile layout (max-width: 768px)
   ============================================================ */
@media (max-width: 768px) {

    /* ── Sidebar: acts as mobile header when collapsed, fullscreen menu when open ── */
    .app-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        width: 100%;
        max-width: 100%;
        height: var(--mobile-header-height);
        overflow: hidden;
        z-index: 70;
        transition: height 300ms ease;
    }
    .app-sidebar.sidebar-mobile-open {
        height: 100vh;
        height: 100dvh;
    }

    /* Force labels visible on mobile (override x-show="sidebarOpen") */
    .app-sidebar [x-show="sidebarOpen"] {
        display: inline-flex !important;
    }

    /* Hamburger: tap-friendly on mobile */
    .app-sidebar .sidebar-hamburger {
        -webkit-tap-highlight-color: transparent;
    }

    /* Backdrop not needed for fullscreen */
    .sidebar-mobile-backdrop {
        display: none;
    }

    /* ── Main layout adjusts for sidebar header ── */
    #app-layout {
        flex-direction: column;
        padding-top: var(--mobile-header-height);
    }
    #app-layout > .flex-1 {
        width: 100%;
        min-width: 0;
    }

    /* ── Page layout responsive ── */
    .page-header {
        padding: 0.5rem 0.75rem;
        height: auto;
        min-height: 44px;
        flex-wrap: wrap;
        gap: 8px;
    }
    .page-header h1 {
        font-size: 0.875rem;
    }
    .page-actions {
        flex-wrap: wrap;
        gap: 6px;
    }
    .page-body,
    .page-body-scroll {
        padding: 0 0.75rem;
    }
    .page-footer {
        padding: 6px 0.75rem;
        font-size: 0.7rem;
        gap: 8px;
    }

    /* ── Page header buttons: smaller hit area on mobile ──
       (icon-only collapse is handled by the container query below, keyed
       on the width of `main` rather than the viewport.) */
    .page-header-btn {
        padding: 0.375rem;
        font-size: 0.8rem;
    }

    /* ── Settings tabs bar: smaller padding on mobile ── */
    .settings-tabs-bar {
        padding: 0 0.75rem;
    }

    /* ── View tabs compact ── */
    .view-tab {
        padding: 0.375rem 0.5rem;
        font-size: 0.75rem;
    }

    /* ── Two-panel form: stack vertically ── */
    .form-two-panel {
        flex-direction: column;
        min-height: auto;
        overflow-y: auto;
    }
    .form-left-panel,
    .form-right-panel {
        width: 100%;
        border-right: none;
        overflow-y: visible;
    }
    .form-right-panel {
        border-top: 1px solid #e5e7eb;
    }

    /* ── Kanban: full-width columns, horizontal scroll ── */
    .kanban-column {
        width: 85vw;
        min-width: 280px;
    }

    /* ── Email composer: TRUE full-screen on mobile — cover the app header too
       (top:0), so only the composer's own header bar shows, never a double bar. ── */
    .email-composer-docked,
    .email-composer-fullscreen {
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        right: 0 !important;
        /* Above the app's mobile header (z-index 70), so the composer covers it
           and only the composer's own header bar shows — not the app header. */
        z-index: 80 !important;
        bottom: 0 !important;
        transform: none !important;
        width: 100% !important;
        max-width: 100% !important;
        height: 100vh !important;
        height: 100dvh !important;
        max-height: 100vh !important;
        max-height: 100dvh !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }

    /* ── Tabulator: allow horizontal scroll ── */
    .tabulator {
        font-size: 0.8rem;
    }

    /* ── Stage breadcrumb: smaller on mobile ── */
    .stage-arrow {
        padding: 0.375rem 0.75rem 0.375rem 1.25rem;
        font-size: 0.65rem;
    }

    /* ── Calendar: compact cells ── */
    .cal-day-cell {
        min-height: 40px;
    }
    .cal-week-cell {
        min-height: 35px;
    }

    /* ── CSV dropzone ── */
    .csv-dropzone {
        padding: 1.5rem 1rem;
    }

    /* ── Quill editor ── */
    .composer-main-editor .ql-editor {
        min-height: 200px;
    }

    /* ── Email sidebar: collapsed by default on mobile ── */
    .email-sidebar-expanded {
        width: 12rem;
    }
    .email-sidebar-collapsed {
        width: 3.25rem;
    }
    .email-sidebar-toggle {
        padding: 4px 0;
    }

    /* ── Calendario: compact navbar ── */
    .cal-navbar {
        flex-wrap: nowrap;
        gap: 3px;
        padding: 0.25rem 0.375rem;
    }
    .cal-nav-left {
        flex-shrink: 0;
        gap: 0 !important;
    }
    .cal-nav-left button {
        padding: 0.25rem;
    }
    .cal-nav-left button svg {
        width: 1rem;
        height: 1rem;
    }
    .cal-nav-left h2 {
        white-space: nowrap;
        margin-left: 0.25rem;
    }
    /* Show abbreviated header on mobile */
    .cal-header-full {
        display: none;
    }
    .cal-header-short {
        display: inline !important;
        font-size: 0.75rem;
    }
    .cal-nav-right {
        gap: 2px;
        flex-wrap: nowrap;
        overflow: hidden;
    }
    /* Hide desktop view tabs on mobile */
    .cal-view-tabs {
        display: none !important;
    }
    /* Show mobile view dropdown */
    .cal-view-dropdown {
        display: block !important;
        flex-shrink: 0;
    }
    /* Oggi button: icon-only on mobile */
    .cal-oggi-label {
        display: none;
    }
    /* User filters: horizontal scroll */
    .cal-user-filters {
        overflow-x: auto;
        max-width: 35vw;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    .cal-user-filters::-webkit-scrollbar {
        display: none;
    }
    /* Hide disponibilita label on mobile */
    .cal-disp-toggle .btn-label {
        display: none;
    }
}

/* ── Chatbot markdown content (shared: transcript modal, portal chat) ── */
.chatbot-md img { max-width: 100%; border-radius: 8px; margin: 8px 0; }
.chatbot-md p { margin: 0 0 8px; }
.chatbot-md p:last-child { margin-bottom: 0; }
.chatbot-md ul, .chatbot-md ol { margin: 8px 0; padding-left: 22px; }
.chatbot-md ul { list-style-type: disc; }
.chatbot-md ol { list-style-type: decimal; }
.chatbot-md li { margin: 4px 0; line-height: 1.5; }
.chatbot-md li > ul, .chatbot-md li > ol { margin: 4px 0; }
.chatbot-md code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 3px; font-family: monospace; font-size: 13px; }
.chatbot-md pre { background: rgba(0,0,0,.06); padding: 10px 12px; border-radius: 6px; overflow-x: auto; margin: 8px 0; }
.chatbot-md pre code { background: none; padding: 0; }
.chatbot-md blockquote { border-left: 3px solid #d1d5db; padding-left: 12px; margin: 8px 0; color: #6b7280; }
.chatbot-md table { border-collapse: collapse; margin: 8px 0; font-size: 13px; }
.chatbot-md th, .chatbot-md td { border: 1px solid #e5e7eb; padding: 4px 8px; }
.chatbot-md th { background: rgba(0,0,0,.04); font-weight: 600; }
.chatbot-md h1, .chatbot-md h2, .chatbot-md h3, .chatbot-md h4, .chatbot-md h5, .chatbot-md h6 { font-weight: 600; margin: 12px 0 6px; }
.chatbot-md h1 { font-size: 1.25em; }
.chatbot-md h2 { font-size: 1.15em; }
.chatbot-md h3 { font-size: 1.05em; }
.chatbot-md a { color: #2563eb; text-decoration: underline; }
.chatbot-md hr { border: none; border-top: 1px solid #e5e7eb; margin: 12px 0; }

/* ── Chatbot copy buttons (transcript modal, portal chat) ── */
.chatbot-msg-wrap { position: relative; }
.chatbot-cpbtns { position: absolute; bottom: 4px; right: 4px; display: flex; gap: 2px; opacity: 0; pointer-events: none; transition: opacity .15s; }
.chatbot-msg-wrap:hover .chatbot-cpbtns, .chatbot-msg-wrap:focus-within .chatbot-cpbtns { opacity: 1; pointer-events: auto; }
.chatbot-cpbtn { background: rgba(0,0,0,.08); border: none; cursor: pointer; padding: 3px; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: inherit; opacity: .5; transition: opacity .15s, background .15s; }
.chatbot-cpbtn:hover { opacity: .9; background: rgba(0,0,0,.15); }
.chatbot-cpbtn:focus-visible { opacity: .9; outline: 2px solid currentcolor; outline-offset: 2px; }
.chatbot-cpbtn svg { width: 13px; height: 13px; }
.chatbot-cpbtn-ok { opacity: .9; color: #16a34a !important; }
.chatbot-msg-user .chatbot-cpbtn { background: rgba(255,255,255,.18); color: #fff; }
.chatbot-msg-user .chatbot-cpbtn:hover { background: rgba(255,255,255,.3); }
@media (hover: none) { .chatbot-cpbtns { opacity: 1; pointer-events: auto; } }

/* ── Chat session "Dati raw" box (transcript modal sidebar) ── */
.cs-raw-json { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: monospace; font-size: 11px; line-height: 1.4; color: #374151; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 4px; padding: 4px 6px; cursor: help; }
.cs-raw-copy { flex: 0 0 auto; background: rgba(0,0,0,.06); border: none; cursor: pointer; padding: 5px; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #6b7280; transition: background .15s, color .15s; }
.cs-raw-copy:hover { background: rgba(0,0,0,.12); color: #374151; }
.cs-raw-copy:focus-visible { outline: 2px solid currentcolor; outline-offset: 2px; }
.cs-raw-copy svg { width: 13px; height: 13px; }
.cs-raw-copy.chatbot-cpbtn-ok { color: #16a34a !important; }

/* ============================================================
   Anagrafica modal — mobile layout (max-width: 768px)
   Fixed sidebar header occupies top 56px (z-70); modal is z-60.
   On mobile the modal becomes a full-bleed sheet below the header
   and uses dvh to stay inside the visible viewport (Chrome URL bar).
   Preserves internal vertical scroll (tab body) and horizontal
   scroll (tab bar).
   ============================================================ */
@media (max-width: 768px) {
    .anagrafica-modal-container {
        align-items: flex-start !important;
        padding-top: var(--mobile-header-height);
    }
    .anagrafica-modal-card {
        height: calc(100vh - var(--mobile-header-height)) !important;
        height: calc(100dvh - var(--mobile-header-height)) !important;
        max-height: calc(100vh - var(--mobile-header-height)) !important;
        max-height: calc(100dvh - var(--mobile-header-height)) !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        max-width: 100% !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
}

/* ============================================================
   Lead modal — mobile layout (max-width: 768px)
   Same pattern as anagrafica modal: full-bleed sheet below the
   fixed sidebar header, dvh for Chrome URL bar, internal scroll
   preserved by flex-col + flex-1 min-h-0 on form-two-panel.
   ============================================================ */
@media (max-width: 768px) {
    .lead-modal-container {
        align-items: flex-start !important;
        padding-top: var(--mobile-header-height);
    }
    .lead-modal-card {
        height: calc(100vh - var(--mobile-header-height)) !important;
        height: calc(100dvh - var(--mobile-header-height)) !important;
        max-height: calc(100vh - var(--mobile-header-height)) !important;
        max-height: calc(100dvh - var(--mobile-header-height)) !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        max-width: 100% !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
}

/* ============================================================
   Appointment modal — mobile layout (max-width: 768px)
   Same pattern as anagrafica/lead modals: full-bleed sheet
   below the fixed sidebar header, dvh for Chrome URL bar,
   internal scroll preserved by flex-col + flex-1 min-h-0 on
   the body.
   ============================================================ */
@media (max-width: 768px) {
    .appointment-modal-container {
        align-items: flex-start !important;
        padding-top: var(--mobile-header-height);
    }
    .appointment-modal-card {
        height: calc(100vh - var(--mobile-header-height)) !important;
        height: calc(100dvh - var(--mobile-header-height)) !important;
        max-height: calc(100vh - var(--mobile-header-height)) !important;
        max-height: calc(100dvh - var(--mobile-header-height)) !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        max-width: 100% !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
}

/* ============================================================
   Ticket modal — mobile layout (max-width: 768px)
   Same pattern as anagrafica/lead/appointment modals.
   ============================================================ */
@media (max-width: 768px) {
    .ticket-modal-container {
        align-items: flex-start !important;
        padding-top: var(--mobile-header-height);
    }
    .ticket-modal-card {
        height: calc(100vh - var(--mobile-header-height)) !important;
        height: calc(100dvh - var(--mobile-header-height)) !important;
        max-height: calc(100vh - var(--mobile-header-height)) !important;
        max-height: calc(100dvh - var(--mobile-header-height)) !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        max-width: 100% !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
}

/* ============================================================
   Shared mobile modal sheet — mobile layout (max-width: 768px)
   Reusable equivalent of the per-entity blocks above (anagrafica/lead/
   appointment/ticket). Any centered `fixed inset-0 flex items-center
   justify-center` modal whose card is `flex flex-col` with a bounded
   height (h-[..] or max-h-[..]) and an internal `flex-1 min-h-0
   overflow-y-auto` body becomes a full-bleed sheet below the navbar just
   by adding `mobile-modal-container` to the overlay and `mobile-modal-card`
   to the card. Prefer these two classes for new/updated modals; the
   per-entity blocks above are kept as-is because existing browser tests
   target their class names.
   ============================================================ */
@media (max-width: 768px) {
    .mobile-modal-container {
        align-items: flex-start !important;
        padding-top: var(--mobile-header-height);
    }
    .mobile-modal-card {
        height: calc(100vh - var(--mobile-header-height)) !important;
        height: calc(100dvh - var(--mobile-header-height)) !important;
        max-height: calc(100vh - var(--mobile-header-height)) !important;
        max-height: calc(100dvh - var(--mobile-header-height)) !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        max-width: 100% !important;
        border-radius: 0 !important;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
}

/* ============================================================
   Full-screen sheet modals (import wizards) — mobile layout
   These are already `fixed inset-0 flex flex-col bg-white` full-screen
   sheets (not centered cards), so they don't need mobile-modal-card. At
   z-[60] (< navbar z-70) their top header/step-nav — including the close
   button — hides behind the 56px mobile navbar. Just pad the top so the
   whole sheet sits below the navbar; the internal flex-1 body still
   scrolls within the remaining height. Keeping z-[60] (not bumping above
   the navbar) preserves stacking for any sub-modal opened from the wizard.
   ============================================================ */
@media (max-width: 768px) {
    .mobile-modal-fullscreen {
        padding-top: var(--mobile-header-height) !important;
    }
}

/* ============================================================
   Long-press tooltip (touch devices)
   Native title attribute is unreliable on Android Chrome for
   non-link elements. Custom JS in app.js shows this tooltip
   on long-press of any [title] element that isn't a link/button.
   ============================================================ */
.longpress-tip {
    position: fixed;
    z-index: 10000;
    background: rgba(17, 24, 39, 0.95);
    color: white;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 12px;
    line-height: 1.3;
    max-width: 90vw;
    overflow-wrap: anywhere;
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
    animation: longpress-tip-fade 0.15s ease-out;
}
@keyframes longpress-tip-fade {
    from { opacity: 0; transform: translateY(2px); }
    to { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   Helpdesk Composer — F3 UX refinement
   ============================================================
   - Toolbar Quill visibile solo dopo click sul bottone "Formatta" (T)
     nel footer (composer-editor.composer-show-toolbar), NON al focus —
     parità col composer email. Resta fino a un nuovo click o alla
     chiusura del composer.
   - Editor auto-grow: cresce con il contenuto, niente scroll interno.
     Il pannello Col 3 (overflow-y-auto) gestisce lo scroll.
   - Sticky footer condizionale: position: sticky bottom: 0 implementa
     il pattern Zoho (footer agganciato finché composer in viewport,
     scrolla via insieme alle card del thread sotto).

   ⚠ DOM Quill: dopo `new Quill(container)` la struttura è
       <div class="composer-editor">
         <div class="ql-toolbar ql-snow">…</div>      ← sibling
         <div class="reply-editor ql-container ql-snow">  ← stesso div
           <div class="ql-editor">…</div>
         </div>
       </div>
   `.ql-toolbar` è SIBLING (previousElementSibling, vedi quill_loader
   linea 166), non figlio di `.reply-editor`. Selettori che vanno via
   da `.composer-editor` direttamente, NON via `.reply-editor`.
============================================================ */
.composer-editor .ql-toolbar {
    display: none;
}
.composer-editor.composer-show-toolbar .ql-toolbar {
    display: block;
}
/* F3 polish: composer outer container already provides the border + rounded
   corners. Strip Quill snow theme's own border on toolbar + editor so we
   don't get a nested box-in-box look (richiesta utente: "togliere bordo
   interno ridondante"). Keep the toolbar's bottom border as a separator
   between toolbar and editor when focused. */
.composer-editor .ql-toolbar.ql-snow {
    border-left: none;
    border-right: none;
    border-top: none;
    border-bottom: 1px solid #e5e7eb;
    border-radius: 0;
}
.composer-editor .reply-editor.ql-container.ql-snow {
    border: none;
    border-radius: 0;
}
.composer-editor.composer-show-toolbar .reply-editor.ql-container.ql-snow {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
.composer-editor .reply-editor .ql-editor {
    /* F3 polish: editor parte ad altezza generosa (pattern Zoho Desk:
       il composer occupa lo spazio principale, il thread vive sotto
       accessibile via scroll Col 3). 40vh dà ~400px su laptop standard
       senza schiacciare il thread sui display piccoli. Il max(200px,…)
       garantisce un minimo assoluto in landscape mobile (viewport
       height ~400px → 40vh = ~160px, troppo piccolo per comporre). */
    min-height: max(200px, 40vh);
    max-height: none;
}
.composer-editor .reply-editor.ql-container {
    height: auto;
}

/* Draft autosave indicator — galleggia nell'angolo in basso a destra del
   composer, appena sopra la action bar. È figlio assoluto del .composer-footer
   (position:sticky è un containing block valido per gli absolute), così resta
   ancorato all'angolo qualunque sia l'altezza di editor/upload. Resta comunque
   fuori dal flow flex del footer: il cambio di stato (Salvataggio…→Salvato ora)
   non rimisura la riga dei bottoni, niente flicker su mobile. pointer-events:none
   per non rubare i tap; il bottone "riprova" (errore) li riattiva localmente.

   bottom: ~15px di stacco dal footer (1px se ne mangia il border-top) per
   tenere la label appena sopra la action bar; la distanza non dipende
   dall'altezza del footer (1 o 2 righe su mobile). */
.composer-status-float {
    position: absolute;
    bottom: calc(100% + 15px);
    right: 12px;
    z-index: 5;
    pointer-events: none;
    background: rgba(255, 255, 255, 0.9);
    padding: 0 4px;
    border-radius: 6px;
    line-height: 1.4;
}
.composer-status-float button {
    pointer-events: auto;
}

.composer-collapsed {
    padding: 8px 0;
}

/* ============================================================
   Time Tracking — F2: timer widget + popup di submit (globale).
   Visual: pill compatto, monospace per il display hh:mm:ss, popup
   in basso-destra (z-index sopra modali tipici). Mobile: full-bleed
   pattern (vedi `.agents/rules/frontend-modal-mobile.md`) — il popup
   occupa la width meno padding e resta in fondo.
   ============================================================ */
.tt-timer-widget {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 8px;
    background-color: #f3f4f6;
    border: 1px solid #e5e7eb;
    font-size: 13px;
    /* Margini verticali negativi (come i bottoni del toolbar): il widget,
       più alto delle azioni accanto per via di padding + bordo, non deve
       stretchare l'header del detail ticket. Allineato a .page-header-btn. */
    margin-top: -4px;
    margin-bottom: -4px;
}
.tt-timer-widget.is-paused {
    background-color: #fef3c7;
    border-color: #fde68a;
}
.tt-timer-display {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    min-width: 64px;
    text-align: center;
}
.tt-timer-btn {
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    color: #6b7280;
    transition: background-color .15s ease, color .15s ease;
}
.tt-timer-btn:hover {
    background-color: #e5e7eb;
    color: #111827;
}

/* ── Popup stack (globale) ───────────────────────────────────── */
.tt-popup-stack {
    /* In alto a destra, SOTTO la toolbar di pagina. Il bottom-right è occupato dai
       toast (#app-toast-container, fixed bottom-4 right-4); il top-0 dai bottoni
       della toolbar (.page-actions: Rispondi a tutti, KB, Elimina / header lista).
       top:150px scende sotto la barra azioni + i tab e atterra nel whitespace
       iniziale della conversazione (dettaglio) / delle prime righe (lista). */
    position: fixed;
    right: 16px;
    top: 150px;
    z-index: 80;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: calc(100vw - 32px);
    width: 360px;
    pointer-events: none;
}
@media (max-width: 640px) {
    .tt-popup-stack {
        right: 8px;
        left: 8px;
        top: 120px;
        width: auto;
        max-width: none;
    }
}
.tt-popup {
    pointer-events: auto;
    background-color: #ffffff;
    border: 1px solid #d1d5db;
    border-radius: 12px;
    box-shadow: 0 10px 25px -5px rgba(0,0,0,0.10), 0 4px 6px -2px rgba(0,0,0,0.05);
    padding: 12px;
    font-size: 13px;
}
.tt-popup-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.tt-popup-title {
    font-weight: 600;
    color: #111827;
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.tt-popup-target {
    font-size: 11px;
    color: #6b7280;
    font-weight: 400;
}
.tt-popup-countdown {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    color: #6b7280;
    background-color: #f3f4f6;
    padding: 2px 6px;
    border-radius: 999px;
}
.tt-popup-warning {
    font-size: 11px;
    line-height: 1.3;
    color: #92400e;
    background-color: #fef3c7;
    border: 1px solid #fde68a;
    border-radius: 6px;
    padding: 6px 8px;
    margin-bottom: 8px;
}
.tt-popup-fields {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.tt-popup-input {
    width: 100%;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 13px;
}
.tt-popup-input:focus {
    outline: none;
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.tt-popup-input.is-duration {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums;
}
.tt-popup-actions {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
    margin-top: 8px;
}
.tt-popup-btn {
    padding: 4px 10px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 500;
    transition: background-color .15s ease;
}
.tt-popup-btn-primary {
    background-color: #2563eb;
    color: #ffffff;
}
.tt-popup-btn-primary:hover {
    background-color: #1d4ed8;
}
.tt-popup-btn-secondary {
    background-color: #f3f4f6;
    color: #374151;
}
.tt-popup-btn-secondary:hover {
    background-color: #e5e7eb;
}

/* ============================================================
   Detail 3-col (ticket helpdesk + Issue Dev Tracker) — mobile optimization
   ------------------------------------------------------------
   Su mobile (< md, 768px) la Col-2 proprietà è `hidden`. Il bottone
   "Info ticket" / "Info issue" nell'header setta `mobilePropsOpen` → la
   colonna diventa un drawer full-bleed sotto la navbar (stile modal
   anagrafica), così si possono vedere/modificare stato, assegnatario,
   categoria, priorità, scadenza e anagrafica anche da telefono.
   Le due pagine condividono la stessa regola (selettori affiancati, MAI una
   copia per pagina): la classe storica `helpdesk-col-properties` resta per il
   dettaglio ticket, `detail-col-properties` è il nome generico usato dalle
   pagine nuove.
   Breakpoint a 767.98px (non 768): a 768px esatti `md:flex` di Tailwind è
   già attivo (Col-2 fissa) — sovrapporre qui il fixed overlay la romperebbe.
   ============================================================ */
@media (max-width: 767.98px) {
    .helpdesk-col-properties.props-mobile-open,
    .detail-col-properties.props-mobile-open {
        display: flex !important;
        position: fixed;
        top: var(--mobile-header-height);
        left: 0;
        right: 0;
        bottom: 0;
        width: 100% !important;
        z-index: 60; /* sotto la navbar (z-70), sopra il resto */
        background-color: #ffffff;
        border-right: none;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }

    /* Composer footer: bottoni secondari icon-only (testo nascosto), così la
       riga azioni non trabocca fuori schermo. I bottoni principali
       (Invia / Salva nota) conservano il testo. L'indicatore di autosave
       NON è più nascosto: vive in posizione assoluta ancorato al footer
       (.composer-status-float), fuori dal flow flex, quindi non comprime la
       riga azioni e resta visibile (appena sopra di essa) anche su mobile. */
    .composer-btn-label { display: none; }

    /* Reply button icon-only su mobile: il testo della modalità si legge
       comunque nel sottomenu (caret). Toglie i 170px di min-width così la
       riga libera spazio per il titolo del ticket, troppo compresso. */
    .reply-btn-label { display: none; }
    .reply-mode-btn { min-width: 0 !important; }
    /* Il menu modalità resta leggibile: ancorato a destra (sotto il caret)
       con min-width fissa, così "Rispondi a tutti" non si tronca quando il
       bottone è ridotto alla sola icona. */
    .composer-mode-menu {
        left: auto;
        right: 0;
        min-width: 12rem;
    }
}

/* ============================================================
   TIME TRACKING — Tab «Report mensile» (griglia pivot)
   Plan: docs/plans/done/2026-07-25-time-tracking-report-mensile-fase-2-pagina.md
   ADR:  docs/adr/2026-07-25-monthly-pivot-grid-not-tabulator.md

   Matrice giorni × righe ad albero: <table> + Alpine (NON Tabulator, vedi ADR).
   Il tab «Voci» resta su Tabulator e non è toccato.
   ============================================================ */

/* ── Header: filtri contestuali del tab ──
   I controlli vivono nel page-header (pattern Interventi): niente toolbar sotto
   i tab, così la griglia guadagna l'altezza di una riga intera. L'header può
   scorrere in orizzontale come ultima risorsa, ma prima si comprime. */
.tt-hdr-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    flex-wrap: nowrap;
}
.tt-hdr-select { max-width: 150px; }
/* Wrapper del Select2 utenti: larghezza fissa (Select2 usa width:100%) e
   altezza allineata agli altri controlli dell'header. */
.tt-hdr-select2 { width: 176px; }
/* `display: block` sul container: da inline-block aggiungerebbe il gap di
   baseline e il gruppo diventerebbe più alto del bottone accanto, alzando la
   barra oltre i 48px standard nonostante `page-header-dense`. */
.tt-hdr-select2 .select2-container { line-height: 1.2; display: block; }
/* Il selettore porta `--default` come quelli di Select2: senza, la specificità
   è identica alla sua e l'altezza dipende dall'ordine di caricamento. */
.tt-hdr-select2 .select2-container--default .select2-selection--single {
    /* Select2 dichiara anche `min-height: 38px`: senza azzerarla la `height`
       viene clampata verso l'alto e la barra resta più alta dello standard. */
    height: 32px;
    min-height: 32px;
    border-color: #d1d5db;
    border-radius: 0.5rem;
    display: flex;
    align-items: center;
}
.tt-hdr-select2 .select2-selection__rendered {
    padding-left: 8px;
    padding-right: 28px;
    font-size: 0.875rem;
}
.tt-hdr-select2 .select2-selection__arrow { height: 30px; }
/* L'avatar nella selezione non deve alzare la riga. */
.tt-hdr-select2 .avatar-circle { width: 20px; height: 20px; font-size: 0.5rem; }

/* Bottone Filtri a riposo: nessun riempimento, si accende solo con filtri
   attivi. Il contorno è un `box-shadow: inset` e NON un `border`: la variante
   attiva (`page-header-btn-secondary`) è senza bordo, quindi con un border vero
   il bottone cambierebbe altezza di 2px accendendo un filtro — la stessa
   ragione per cui la piattaforma usa box-shadow per i contorni che compaiono
   solo in certi stati. */
.tt-hdr-btn-plain {
    background: #ffffff;
    color: #374151;
    box-shadow: inset 0 0 0 1px #d1d5db;
}
.tt-hdr-btn-plain:hover { background: #f9fafb; }

/* Compensazione densa applicata due volte: il wrapper del popover la prende
   come figlio di `.page-actions`, e il bottone dentro porta già i suoi -6px da
   `.page-header-btn`. Sommate, il bottone sborda sopra il proprio wrapper e
   finisce 2px più alto dei fratelli. Si azzerano quelli del bottone e si lascia
   la compensazione al wrapper, che è il flex item della barra. */
.tt-hdr-popover > .page-header-btn {
    margin-top: 0;
    margin-bottom: 0;
}

/* Pannello del popover Filtri: `position: fixed` (dal markup) perché
   `.tt-hdr-actions` è uno scroller e ritaglierebbe un discendente `absolute` —
   il pannello si vedeva solo per la striscia alta quanto la barra. Coordinate e
   cap in altezza li scrive `posizionaFiltri()` dal rect del bottone: lo spazio
   utile sotto il bottone è `innerHeight - top`, che un `max-height` in `vh`
   sovrastima sistematicamente (il footer Ripristina/Chiudi finirebbe sotto il
   bordo, irraggiungibile perché un `fixed` non scorre con la pagina).
   La larghezza sta QUI come unica fonte — la funzione la rilegge da
   `getComputedStyle` per l'allineamento a destra, così non esiste un secondo
   valore da tenere in sync col markup. */
.tt-hdr-filtri-panel {
    --tt-filtri-w: 300px;

    width: var(--tt-filtri-w);
    overflow-y: auto;
}

/* ── Griglia ──
   `table-layout: fixed` = 31 colonne giorno di larghezza costante. Le larghezze
   stanno in custom properties e gli offset sticky si derivano da quelle con
   `calc()`: stringere la colonna etichetta non può sfasare la fascia (era un
   vincolo replicato a mano su due blocchi). */
.tt-grid {
    --tt-w-label: 340px;
    --tt-w-tot: 70px;
    --tt-w-pack: 96px;
    --tt-w-day: 36px;

    border-collapse: separate;
    border-spacing: 0;
    table-layout: fixed;
    font-variant-numeric: tabular-nums;
}
.tt-grid th,
.tt-grid td {
    border-right: 1px solid #f1f5f9;
    border-bottom: 1px solid #f1f5f9;
}
.tt-grid thead th {
    position: sticky;
    top: 0;
    z-index: 20;
    background: #ffffff;
    border-bottom: 1px solid #e2e8f0;
}

.tt-grid col.tt-c-label { width: var(--tt-w-label); }
.tt-grid col.tt-c-tot   { width: var(--tt-w-tot); }
.tt-grid col.tt-c-pack  { width: var(--tt-w-pack); }
.tt-grid col.tt-c-day   { width: var(--tt-w-day); }

/* Prima fascia sticky: etichetta + totale + pacchetto restano visibili mentre
   scorrono i giorni del mese (in Redmine il totale scompare a destra). */
.tt-grid .tt-col-label { position: sticky; left: 0; z-index: 10; }
.tt-grid .tt-col-tot   { position: sticky; left: var(--tt-w-label); z-index: 10; }
.tt-grid .tt-col-pack {
    position: sticky;
    left: calc(var(--tt-w-label) + var(--tt-w-tot));
    z-index: 10;
    border-right: 2px solid #cbd5e1;
}
.tt-grid .tt-col-tot.is-last-sticky { border-right: 2px solid #cbd5e1; }
.tt-grid thead .tt-col-label,
.tt-grid thead .tt-col-tot,
.tt-grid thead .tt-col-pack { z-index: 30; }

/* Colonne weekend: tinta neutra (rosso/verde restano riservati agli stati). */
.tt-grid .is-weekend { background-color: #eef2f7; }
.tt-grid thead .is-weekend { background-color: #e2e8f0; color: #64748b; }

/* Colonna di oggi: box-shadow inset → zero impatto sul layout. */
.tt-grid .is-today { box-shadow: inset 2px 0 0 0 #2563eb, inset -2px 0 0 0 #2563eb; }
.tt-grid thead .is-today { color: #1d4ed8; }

/* ── Righe ──
   Livello 0 scuro sull'asse con pochi membri (persone, come Redmine). Sull'asse
   clienti (decine di righe) resta chiaro, altrimenti la heatmap — il segnale
   principale — sparirebbe su fondo scuro. */
.tt-row-person > td {
    background: #1e293b;
    color: #ffffff;
    font-weight: 600;
    border-bottom-color: #334155;
}
.tt-row-client > td {
    background: #e8edf3;
    font-weight: 700;
    border-bottom-color: #cbd5e1;
}
.tt-row-group > td { background: #f8fafc; }
.tt-row-leaf  > td { background: #ffffff; }
.tt-row-leaf:hover > td { background: #f9fbff; }

/* Riga totali ANCORATA in basso: scorrendo centinaia di righe il totale resta
   sempre a schermo, quindi non serve ripeterlo in cima. */
.tt-row-total > td {
    position: sticky;
    bottom: 0;
    z-index: 15;
    background: #fffbeb;
    font-weight: 600;
    border-top: 2px solid #e2e8f0;
}
/* Le celle della fascia sinistra sono sticky su DUE assi (left + bottom):
   devono stare sopra le celle giorno della stessa riga. */
.tt-row-total > td.tt-col-label,
.tt-row-total > td.tt-col-tot,
.tt-row-total > td.tt-col-pack { z-index: 25; }

/* Celle cliccabili: il target è un <button> a piena cella, così il drill-down
   è raggiungibile da tastiera come dal mouse. */
.tt-cell { padding: 0; }
.tt-cell-btn {
    display: block;
    width: 100%;
    height: 100%;
    padding: 4px 0;
    background: none;
    border: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.tt-cell-btn:hover { outline: 2px solid #2563eb; outline-offset: -2px; }
.tt-cell-btn:focus-visible { outline: 2px solid #1d4ed8; outline-offset: -2px; }

/* Segnaposto cella vuota: pallino disegnato in CSS, non un glyph Unicode. */
.tt-dot {
    display: inline-block;
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: #cbd5e1;
    vertical-align: middle;
}
.tt-row-person .tt-dot { background: #475569; }

/* Marker all-day: SVG nell'angolo cella. Segnala un appuntamento a giornata
   intera (flag `is_fullday`) da verificare — la voce resta nel totale, il
   marker non la esclude. */
.tt-allday {
    position: absolute;
    top: 0;
    right: 0;
    width: 9px;
    height: 9px;
    color: #b45309;
}

/* Nome cliente cliccabile → apre la modale anagrafica in-page. */
.tt-open-ana {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tt-open-ana:hover { color: #2563eb; text-decoration: underline; }

/* Meter capacità / consumo pacchetto. */
.tt-meter {
    height: 4px;
    border-radius: 999px;
    background: #e2e8f0;
    overflow: hidden;
}
.tt-meter > i {
    display: block;
    height: 100%;
    border-radius: 999px;
}

/* Chip sorgente nel popover filtri. */
.tt-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
    border: 1px solid transparent;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

.tt-truncate {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Skeleton della riga di terzo livello durante il fetch on-demand. */
.tt-skeleton-bar {
    height: 10px;
    border-radius: 999px;
    background: #e2e8f0;
}

/* ── Compattazione ──
   @container (larghezza del CONTENUTO), MAI @media: con la sidebar aperta il
   viewport è largo ma il contenuto no. Priority+: prima cadono le label dei
   controlli, poi i testi dei bottoni (`.btn-label`, già globale a 1100px), poi
   si stringe la colonna etichetta. Nessuna azione viene rimossa: la matrice
   resta la stessa vista, scorre in orizzontale con la fascia nome+totale
   ancorata. */
/* Tier 1 — cadono le label testuali dei controlli: `title`/`aria-label`
   restano, e il valore selezionato nella select dice già di cosa si tratta. */
@container pagemain (max-width: 1400px) {
    .tt-hdr-label { display: none; }
    .tt-hdr-select { max-width: 120px; }
}

/* Tier 2 — le select si stringono e cade «Oggi» (il periodo resta navigabile
   con ‹ ›). I testi dei bottoni li nasconde già la regola globale a 1100px. */
@container pagemain (max-width: 1150px) {
    .tt-hdr-select { max-width: 104px; }
    .tt-hdr-select2 { width: 130px; }
    .tt-hdr-today { display: none; }
}

/* Tier 3 — la colonna etichetta si restringe e la griglia garantisce
   un'altezza minima utile; nessuna azione è rimossa, solo compressa. */
@container pagemain (max-width: 780px) {
    /* Sotto questa soglia i controlli vanno A CAPO invece di vivere in uno
       scroller orizzontale: con `justify-content: flex-end` l'overflow verrebbe
       tagliato a SINISTRA, cioè si perderebbero i primi controlli senza che
       nulla lo segnali. È l'intento del wrap di `.page-header`.
       `overflow` e non `overflow-x`: un asse `visible` con l'altro non-visible
       computa ad `auto` per specifica, quindi la regola direbbe una cosa e il
       browser ne farebbe un'altra (uno scroller dentro una toolbar wrappata). */
    .tt-hdr-actions {
        flex-wrap: wrap;
        overflow: visible;
        justify-content: flex-start;
    }
    /* Solo variabili: gli offset sticky si ricalcolano da soli. La fascia
       sinistra si stringe tutta e non solo l'etichetta — a 390px i tre valori
       di default (340+70+96) ne mangerebbero 506, lasciando ai giorni meno di
       una colonna e mezzo. */
    .tt-grid {
        --tt-w-label: 150px;
        --tt-w-tot: 56px;
        --tt-w-pack: 72px;
    }
    /* Abbastanza per mostrare righe utili senza spingere legenda/footer fuori
       su un viewport corto (390×667). */
    .tt-scroll { min-height: 240px; }
    /* I conteggi secondari cedono il posto al dato principale. */
    .tt-row-badge { display: none; }
}
