﻿/* ===================================================================   custom.css — personalizzazioni di prodotto
   · Caricato DOPO knm-theme.css (vedi App.razor): le regole qui vincono senza !important.
   · UNICO file di personalizzazione: niente fogli per-feature.
   · Regola d'oro: si aggiunge SOLO ciò che tema e utility Bootstrap non danno già;
     una regola che non serve si cancella.  (docs/ui-knm-adoption-log.md)
   ========================================================================== */

:root{
   --knm-progress-height: 0.4rem;
   /* The "off" track of every KNM switch/toggle. It USED to be the literal #facb83 — a pale orange, i.e. the
      ORANGE accent hard-coded — so a tenant on the green/blue/teal/violet accent got an orange switch on a
      green page (visible on the login "Stay signed in" toggle). Bound to the theme token instead: the wash
      follows whatever [data-knm-accent] is active AND flips with [data-bs-theme], because
      --knm-theme-accent-muted is redefined per surface polarity in knm-theme.css. Both attributes live on
      <html>, the same element this rule declares on, so the reference resolves against the active theme. */
   --knm-toggle-inactive: var(--knm-theme-accent-muted);

   /* The OPEN accordion header. KNM.RazorComponents feeds these two into Bootstrap's
      --bs-accordion-active-bg / --bs-accordion-active-color, and their package default is
      --bs-primary-bg-subtle — a wash so pale that an open panel and a closed one read the same at a glance.
      Bound to the accent tokens so the open header is a filled accent band (Berruto's model), and so it
      follows [data-knm-accent] and flips with [data-bs-theme] instead of pinning one colour.
      Applies to KNMAccordionSize.Small too: the package's .knm-accordion-sm rule reads the same variable. */
   --knm-accordion-active-bg: var(--knm-theme-accent);
   --knm-accordion-active-color: var(--knm-theme-accent-contrast);
}

/* Bootstrap draws the accordion chevron as a dark SVG data-URI, which disappears against the filled accent
   band above. Inverting it to white is the only way to recolour a background-image glyph. Scoped to the OPEN
   header (:not(.collapsed)) — the closed ones keep the normal surface and need the dark chevron. */
.accordion-button:not(.collapsed)::after {
   filter: brightness(0) invert(1);
}


/* --------------------------------------------------------------------------
   Shared components (Components/Shared) — standard chrome reused across pages.
   -------------------------------------------------------------------------- */
/* PageHeading subtitle — was an inline style="font-size:.82rem" repeated on every admin page. */
.knm-t-page-subtitle { font-size: .82rem; }

/* Grid export buttons — semantic filled colour matched to the format icon (CSV=info, Excel=success,
   PDF=danger), subtle at rest, solid on hover. KnmGridExportAction carries no colour, so it is applied by
   CSS on the toolbar button that contains the format icon (pattern lifted from Connect's knm-theme.css). */
.knm-grid-toolbar .btn:has(.ph-file-csv),
.knm-grid-toolbar .btn:has(.ph-microsoft-excel-logo),
.knm-grid-toolbar .btn:has(.ph-file-pdf) { border-color: transparent; color: #fff; }
.knm-grid-toolbar .btn:has(.ph-file-csv) { background-color: color-mix(in srgb, var(--bs-info) 80%, transparent); }
.knm-grid-toolbar .btn:has(.ph-file-csv):hover,
.knm-grid-toolbar .btn:has(.ph-file-csv):focus-visible { background-color: var(--bs-info); }
.knm-grid-toolbar .btn:has(.ph-microsoft-excel-logo) { background-color: color-mix(in srgb, var(--bs-success) 80%, transparent); }
.knm-grid-toolbar .btn:has(.ph-microsoft-excel-logo):hover,
.knm-grid-toolbar .btn:has(.ph-microsoft-excel-logo):focus-visible { background-color: var(--bs-success); }
.knm-grid-toolbar .btn:has(.ph-file-pdf) { background-color: color-mix(in srgb, var(--bs-danger) 80%, transparent); }
.knm-grid-toolbar .btn:has(.ph-file-pdf):hover,
.knm-grid-toolbar .btn:has(.ph-file-pdf):focus-visible { background-color: var(--bs-danger); }

/* Row-actions palette — CIRCLES, not pills (CTO 2026-09-08, item 9: "falli diventare dei cerchi").

   Why the rule exists: the installation runs WithGlobalLayout(LayoutType.Rounded), whose shape rule is
   `.knm-layout-rounded .btn { border-radius: 50rem }`. A radius that large only draws a circle on a SQUARE
   box; the pencil and the bin are icon buttons carrying btn-sm's horizontal padding, so they are wider than
   they are tall and the same radius draws an oval pill. The radius is not the problem — the box is, which is
   why this squares width/height and drops the padding instead of touching border-radius alone.

   The package itself already does exactly this for the grid's own pager under Rounded
   (`.knm-layout-rounded .knm-grid-footer .btn { width:2rem;height:2rem;…;border-radius:50% }`), so 2rem is
   not an invented measure: it makes the two button clusters of one grid the same size.

   ONE rule for every list page: all eleven grids with an actions cell render it through
   Shared/MasterDataRowActions (MasterDataGrid's .WithRowActions), and the per-page RowActionsExtra buttons
   (Restore, Unlock, …) sit in that same container — so they are circles too, with no page saying so. */
.knm-t-rowactions .btn {
   width: 2rem; height: 2rem; min-width: 2rem;
   padding: 0;
   display: inline-flex; align-items: center; justify-content: center;
   border-radius: 50%;
   line-height: 1;
}

/* --------------------------------------------------------------------------
   Login
   Il resto del layout Login vive in knm-theme.css (.knm-t-login/.knm-t-login-card);
   qui solo gli scostamenti che prima erano style="" inline.
   -------------------------------------------------------------------------- */
/* Quick actions pinned to the login's top-right corner (language · light-dark · appearance). Was
   .knm-t-login-themetoggle, a single-button box; renamed because it now holds a row. */
.knm-t-login-actions { position: fixed; top: 1rem; right: 1rem; z-index: 10; display: flex; align-items: center; gap: .25rem; }

/* The compact language switcher next to two 36px .knm-t-iconbtn siblings: give it the same footprint so the
   three read as one cluster instead of a flag wedged between two buttons. */
.knm-t-langsel { display: inline-flex; flex-shrink: 0; }
.knm-t-langsel-trigger {
   width: 36px; height: 36px;
   align-items: center; justify-content: center;
   border-radius: 8px;
}
.knm-t-langsel-trigger:hover { background: var(--knm-theme-surface-2); }

/* Logo Login: più grande di quello della sidebar (.knm-t-logo = 30px). */
.knm-t-login-logo { width: 48px; height: 48px; border-radius: 14px; margin-bottom: 1rem; }

.knm-t-login-subtitle { font-size: .875rem; }

/* .knm-t-grid usa gap var(--knm-theme-gap)=1.25rem; il form Login vuole 1rem.
   .knm-t-lock-form è lo stesso form — password + pulsante — sulla schermata di blocco (CTO 2026-09-08): stessa
   misura dichiarata QUI e non un'altra inventata lì, così le due schermate non possono divergere. */
.knm-t-login-form,
.knm-t-lock-form { gap: 1rem; }

.knm-t-login-footer { margin-top: 1.25rem; }

/* The organization's note at the foot of the portal and of the sign-in page (PortalDisclaimer.razor, half-features-fields
   point 3): plain text, the line breaks the administrator typed kept by pre-line rather than turned into markup. */
.knm-portal-disclaimer {
  margin-top: .5rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
  font-size: .75rem;
  color: var(--knm-theme-text-muted);
}

/* --------------------------------------------------------------------------
   Home / Dashboard — classi che sostituiscono gli style="" inline della pagina
   -------------------------------------------------------------------------- */
.knm-t-dash-updated { font-size: .82rem; }
.knm-t-dash-spinner { padding: 3rem; }
.knm-t-dash-kpi-head { margin-bottom: .85rem; }
.knm-t-dash-cardmeta { font-size: .78rem; }
.knm-t-dash-side { align-content: start; }
.knm-t-dash-list { gap: .9rem; }
.knm-t-dash-list-tight { gap: .8rem; }
.knm-t-dash-prio-row { margin-bottom: .4rem; font-size: .82rem; }
.knm-t-dash-agent { min-width: 0; flex: 1; }
.knm-t-dash-agent-name { font-size: .85rem; font-weight: 600; line-height: 1.25; }
.knm-t-dash-kpi-sub { margin-top: .35rem; }
/* Language flag inside KnmTranslationTabs — a small, crisp rectangle with a hairline edge. */

.knm-t-flag { height: 16px; width: auto; box-shadow: 0 0 0 1px rgb(0 0 0 / .08); }

/* Lookup chip (state/priority) — a neutral KNMChip whose leading icon (the row's own UiIcon) is tinted with the
   row's own UiColor via --lc-dot (a custom lookup shows its colour, black included). The text keeps the chip's
   default colour so it stays readable; only the icon carries the colour. */
.knm-t-lookupchip .knm-chip__icon { color: var(--lc-dot, var(--bs-secondary-color)); }

/* --------------------------------------------------------------------------
   Sidebar nested menu (3 livelli: gruppo → padre non-navigabile → voce).
   Il padre riusa .knm-t-navlink del tema (icona + label + accent attivo); qui
   solo il reset del <button>, il caret a destra che ruota, e l'indentazione
   dei figli con una guida verticale. (grafica "inventata", CTO 2026-08-26)
   -------------------------------------------------------------------------- */
.knm-t-navitem { position: relative; }
.knm-t-navparent { width: 100%; border: 0; background: transparent; text-align: left; cursor: pointer; }
.knm-t-navlabel { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The caret is scoped so the theme's ".knm-t-navlink i { font-size:1rem }" cannot inflate it; a hair smaller than
   the row icons and dimmer, it reads as an affordance, not a second icon. */
.knm-t-navparent .knm-t-navcaret { margin-left: auto; width: auto; font-size: .78rem; opacity: .5; transition: transform .15s ease; }
.knm-t-navcaret-open { transform: rotate(90deg); }
/* The lock of a menu entry whose module the organization does not hold (CUSTOM_BRANDING, 2026-10-02): the entry stays
   and its page explains. Scoped and sized like the caret, for the same reason; gone on the collapsed rail, where the
   label is gone too and the flyout carries the mark. */
.knm-t-navlink .knm-t-navlock,
.knm-t-navflyout-link .knm-t-navlock { margin-left: auto; width: auto; font-size: .78rem; opacity: .6; }
.knm-t-navchildren { display: flex; flex-direction: column; gap: .1rem; margin: .1rem 0 .1rem .95rem; padding-left: .55rem; border-left: 1px solid var(--knm-theme-sidebar-border, var(--bs-border-color)); }

/* A child's active state is LIGHTER than a top-level item's (CTO 2026-08-26): the indent + left guide already say
   "nested", so the heavy accent bar + solid fill are redundant here. Keep only a faint tint and the accent text. */
.knm-t-navchildren .knm-t-navlink.active { background: rgb(255 255 255 / .04); }
.knm-t-navchildren .knm-t-navlink.active::before { display: none; }

/* Collapsed icon rail (desktop): the label + inline children are hidden, so a caret would dead-end. Drop it and
   reveal the children in a hover flyout to the right instead — reachable and tidy. */
.knm-t-navflyout { display: none; }
@media (min-width: 992px) {
   [data-knm-sidebar="collapsed"] .knm-t-navcaret,
   [data-knm-sidebar="collapsed"] .knm-t-navlink > .knm-t-navlock,
   [data-knm-sidebar="collapsed"] .knm-t-navchildren { display: none !important; }
   /* Center the icon in the rail: the label span keeps `flex: 1 1 auto`, so even hidden by the theme's font-size:0
      it still claims flex space and shoves the icon left. Remove it entirely, and tighten the container's side
      padding so the icons sit centered in the full 78px rail (CTO 2026-08-26: "icone centrate, tanto padding"). */
   [data-knm-sidebar="collapsed"] .knm-t-navlabel { display: none; }
   [data-knm-sidebar="collapsed"] .knm-t-sidebar-nav { padding-left: .4rem; padding-right: .4rem; overflow: visible; }
   [data-knm-sidebar="collapsed"] .knm-t-navparent { justify-content: center; }
   /* Let the flyout escape the rail instead of being clipped by the nav's scroll box, AND lift the whole sidebar
      stacking context above the page content: the sidebar is position:sticky with no z-index, so its flyout paints
      UNDER the grid/search (CTO 2026-08-26). 1040 sits above the topbar (1020) and content, below modals (~1055). */
   [data-knm-sidebar="collapsed"] .knm-t-sidebar { overflow: visible; z-index: 1040; }
   [data-knm-sidebar="collapsed"] .knm-t-navitem:hover > .knm-t-navflyout,
   [data-knm-sidebar="collapsed"] .knm-t-navitem:focus-within > .knm-t-navflyout {
      display: block;
      position: absolute; left: 100%; top: 0; z-index: 1050;
      min-width: 220px; margin-left: .4rem; padding: .35rem;
      background: var(--bs-body-bg); color: var(--bs-body-color);
      border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius);
      box-shadow: var(--bs-box-shadow);
   }
}
.knm-t-navflyout-title { padding: .3rem .6rem; font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--bs-secondary-color); }
.knm-t-navflyout-link { display: flex; align-items: center; gap: .5rem; padding: .4rem .6rem; border-radius: 6px; font-size: .85rem; color: var(--bs-body-color); text-decoration: none; }
.knm-t-navflyout-link:hover { background: var(--bs-secondary-bg); }
.knm-t-navflyout-link.active { color: var(--bs-primary); font-weight: 600; }
.knm-t-navflyout-link i { width: 1.15rem; text-align: center; opacity: .9; }

/* --------------------------------------------------------------------------
   Top-bar breadcrumb — vertical alignment. KNMBreadcrumb renders a Bootstrap
   .breadcrumb, whose default margin-bottom:1rem makes the text ride high in the
   centered topbar row (CTO 2026-08-26: "troppo su"). Zero it and center the slot.
   -------------------------------------------------------------------------- */
.knm-t-breadcrumb { display: flex; align-items: center; min-width: 0; }
/* KNMBreadcrumb's own wrapper carries the offset (--knm-breadcrumb-page-header-margin-bottom: 1.25rem, meant for a
   page-header layout). In the topbar we want it flush — override the variable KNM exposes rather than the margin. */
.knm-t-breadcrumb .knm-breadcrumb-wrapper { --knm-breadcrumb-page-header-margin-bottom: 0; margin-bottom: 0; }
.knm-t-breadcrumb :where(nav, ol, .breadcrumb) { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Section hub (/hub/{code}) — the card landing for a container menu node (Connect
   model). Each tile is a NavLink wrapping a KNMCard; the whole tile is the click
   target, lifts on hover, and reveals the arrow. (grafica "inventata", CTO 2026-08-26)
   -------------------------------------------------------------------------- */
.knm-t-hub-tile { display: block; text-decoration: none; color: inherit; height: 100%; }
.knm-t-hub-tile .card { transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease; }
.knm-t-hub-tile:hover .card { transform: translateY(-2px); border-color: var(--bs-primary); box-shadow: var(--bs-box-shadow-sm); }
/* align-items: flex-start, not center: with a description the card is two lines tall and a vertically centred
   icon floats between them instead of marking where the entry starts. */
.knm-t-hub-tile-inner { display: flex; align-items: flex-start; gap: .75rem; }
.knm-t-hub-tile-icon { font-size: 1.5rem; color: var(--bs-primary); flex: 0 0 auto; line-height: 1.3; }
.knm-t-hub-tile-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.knm-t-hub-tile-text { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Two lines, then clipped. A description that grows without limit makes the cards in a row different heights,
   which is the one thing a catalogue must not do. */
.knm-t-hub-tile-desc { font-size: .8rem; opacity: .7; line-height: 1.35;
   display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.knm-t-hub-tile-go { align-self: center; }
.knm-t-hub-tile-go { opacity: 0; color: var(--bs-secondary-color); transition: opacity .12s ease; flex: 0 0 auto; }
.knm-t-hub-tile:hover .knm-t-hub-tile-go { opacity: 1; }
/* A tile whose module is locked shows the lock where the arrow would appear, always visible. */
.knm-t-hub-tile-lock { align-self: center; flex: 0 0 auto; color: var(--bs-secondary-color); }

/* --------------------------------------------------------------------------
   Top-bar user menu — the avatar opens a profile/sign-out dropdown. This is the
   user block relocated from the sidebar footer (CTO 2026-08-26); the Customizer
   is an icon button next to it, both live in MainLayout's topbar now.
   -------------------------------------------------------------------------- */
.knm-user-menu-btn { border: 0; padding: 0; background: none; border-radius: 50%; cursor: pointer; line-height: 0; }
.knm-user-menu-btn:focus-visible { outline: 2px solid var(--knm-theme-accent); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   PersonAvatar — the one avatar (person-avatar). The colour and the size are
   KNMAvatar's; this only lays out the container, the icon variant and the
   photo, drawn over the initials so a photo that fails to load leaves them.
   -------------------------------------------------------------------------- */
/* align-self: a flex item of a tall row (an article in the conversation) must not stretch to the row height: the
   photo and the icon below would follow it into a tall oval (measured 32x436 on a long article, 2026-10-16). */
.knm-person-avatar { position: relative; display: inline-flex; flex-shrink: 0; vertical-align: middle; align-self: flex-start; }
.knm-person-avatar__icon {
  position: absolute; inset: 0 auto auto 0;
  width: 100%; aspect-ratio: 1 / 1;
  display: flex; align-items: center; justify-content: center;
  color: var(--knm-avatar-color, var(--bs-white));
  pointer-events: none;
}
.knm-person-avatar__photo {
  /* Square to the avatar's width, anchored top-left: the height never comes from the wrapper. */
  position: absolute; inset: 0 auto auto 0;
  width: 100%; height: auto; aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 50%;
  /* No background, no alt text drawn: an image that fails before the circuit is up (a 404 during prerender, which
     @onerror never hears) leaves the initials under it visible instead of a broken-image box. */
  background: transparent;
  color: transparent;
  font-size: 0;
}
/* The presence dot stays above the photo. */
.knm-person-avatar .knm-avatar__status { z-index: 1; }

/* --------------------------------------------------------------------------
   Ticket presence — who else is on this ticket (ticket-presence). A stack of
   PersonAvatar in the title row, right-aligned; the writer's overlay sits where
   KNMAvatar draws its status dot. Colours are the theme's variables only.
   -------------------------------------------------------------------------- */
.knm-presence { display: flex; flex-direction: column; align-items: flex-end; gap: .25rem; min-width: 0; }
/* A button that looks like one without a hover: a pill with a border and a chevron after the avatars. */
.knm-presence__stack {
  display: inline-flex; align-items: center; cursor: pointer;
  padding: .2rem .55rem .2rem .25rem;
  border: 1px solid var(--knm-theme-border); border-radius: 999px;
  background: var(--knm-theme-surface); color: var(--knm-theme-text-muted);
}
.knm-presence__stack:hover { border-color: var(--knm-theme-border-strong); color: var(--knm-theme-text); }
.knm-presence__stack:focus-visible { outline: 2px solid var(--knm-theme-accent); outline-offset: 2px; }
.knm-presence__chevron { margin-left: .4rem; font-size: .8rem; }
.knm-presence__person { position: relative; display: inline-flex; }
.knm-presence__stack .knm-presence__person + .knm-presence__person { margin-left: var(--knm-avatar-group-overlap, -.5rem); }
.knm-presence__person--idle .knm-avatar { opacity: .55; }
.knm-presence__badge {
  position: absolute; right: -.15rem; bottom: -.15rem;
  display: flex; align-items: center; justify-content: center;
  width: 1rem; height: 1rem; border-radius: 50%; font-size: .65rem;
  background: var(--knm-theme-accent); color: var(--knm-theme-accent-contrast);
  border: var(--knm-avatar-border-width, 2px) solid var(--knm-theme-surface);
  box-sizing: content-box; pointer-events: none;
}
.knm-presence__line { font-size: .8rem; color: var(--knm-theme-text-muted); text-align: right; }
/* The full list and the take-over notice take the whole row under the title. */
.knm-presence__below { flex: 0 0 100%; display: grid; gap: var(--knm-theme-gap); min-width: 0; }
.knm-presence__list {
  border: 1px solid var(--knm-theme-border); border-radius: var(--knm-theme-radius);
  background: var(--knm-theme-surface); padding: var(--knm-theme-card-pad);
}
.knm-presence__list-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .5rem; }
.knm-presence__list-title { font-size: .85rem; font-weight: 600; margin: 0; color: var(--knm-theme-text-muted); }
.knm-presence__row { display: flex; align-items: center; gap: .6rem; padding: .25rem 0; }
.knm-presence__state { color: var(--knm-theme-text-muted); }
.knm-t-usermenu { min-width: 12rem; }
.knm-t-usermenu-head { padding-top: .5rem; padding-bottom: .5rem; }
.knm-t-usermenu-name { font-weight: 600; color: var(--bs-body-color); }
.knm-t-usermenu-login { font-size: .8rem; color: var(--bs-secondary-color); }
.knm-t-usermenu .dropdown-item { display: flex; align-items: center; }
.knm-t-usermenu form { margin: 0; }

/* --------------------------------------------------------------------------
   Tab strips — ONE style for the whole app (CTO 2026-09-04, item 12: "lo stile di tutte le tab deve essere
   come la tab presente in Tipi").

   The underline strip was introduced for KnmTranslationTabs (CTO 2026-08-26) and lived scoped to that one
   component, so every OTHER tab in the product — the user editor, the customer portal, the ticket view
   switcher, KNMCron's Guidata/Avanzata — kept Bootstrap's default folder-tab shape and the two did not look
   like the same product. The rules are unchanged; only the SELECTOR moved, from `.knm-t-translation .nav-tabs`
   to plain `.nav-tabs`, which is what KNMTabs (`nav nav-tabs knm-tabs__nav`), KNMCron (`nav nav-tabs
   knm-cron__tabs`) and the hand-rolled strip all render.

   Active tab = a 2px accent line under the label, nothing else: no border box, no filled background. Reads as
   sub-navigation rather than as a button group.
   -------------------------------------------------------------------------- */
.nav-tabs { border-bottom: 1px solid var(--bs-border-color); gap: .25rem; }
.nav-tabs .nav-link {
   border: 0;
   border-bottom: 2px solid transparent;
   border-radius: 0;
   background: transparent;
   color: var(--bs-secondary-color);
   padding: .35rem .75rem;
   margin-bottom: -1px;
}
.nav-tabs .nav-link:hover { color: var(--bs-body-color); border-bottom-color: var(--bs-border-color); }
.nav-tabs .nav-link.active {
   background: transparent;
   color: var(--bs-primary);
   border-bottom-color: var(--bs-primary);
   font-weight: 600;
}
.nav-tabs .nav-link:disabled,
.nav-tabs .nav-link.disabled { color: var(--bs-secondary-color); opacity: .5; }

/* --------------------------------------------------------------------------
   Modal widths (CTO 2026-09-04, item 16: "espandi su tutte le modali").

   Bootstrap's stock widths (500 / 800 / 1140) were drawn for a marketing dialog, not for an editor that
   carries a two-column form, a translation strip and an audit band — the fields ended up stacked in a column
   narrower than the page they were opened from. Raised one notch each, on the shared --bs-modal-width token
   so every KNMModal in the product moves together and no page has to pass a size to look normal.

   `min(…, 94vw)` is what keeps the wider values safe: .modal-dialog uses this as a MAX-width, so the dialog
   still shrinks on a narrow viewport, and Bootstrap's own media queries mean .modal-lg/.modal-xl do not even
   apply below 992/1200px. KNMAlert is NOT touched — it is not a Bootstrap modal (it renders .knm-alert, with
   its own --knm-alert-max-width), so a confirm dialog keeps its compact size. */
.modal-dialog { --bs-modal-width: min(41rem, 94vw); }
@media (min-width: 992px) {
   .modal-lg, .modal-xl { --bs-modal-width: min(60rem, 94vw); }
}
@media (min-width: 1200px) {
   .modal-xl { --bs-modal-width: min(78rem, 94vw); }
}

/* --------------------------------------------------------------------------
   Boot splash (CTO 2026-09-04, item 2 — Berruto's <KNMAssetLoader LoaderId="knm-loading" />).

   KNM.RazorComponents injects its stylesheet from JavaScript, at the <style data-knm="css"> anchor in
   App.razor's <head>. Until that runs the first paint is the page with the component CSS missing, which is a
   visible flash of unstyled content on every cold load. knm-loader.js exists for exactly this: it dismisses
   an overlay by id on window.load (adding .loaded, then removing the node 400ms later). The default id it
   looks for is "knm-loading" — the app already rendered <KNMAssetLoader /> but had no overlay for it to
   dismiss, so the mechanism was wired to nothing.

   These rules must be in a <link>ed stylesheet, not injected: they have to be in force before the splash div
   itself paints. custom.css is linked in <head>, so they are. */
#knm-loading {
   position: fixed;
   inset: 0;
   z-index: 20000;
   display: flex;
   align-items: center;
   justify-content: center;
   background: var(--knm-theme-bg, #fff);
   transition: opacity .3s ease;
}
#knm-loading.loaded { opacity: 0; pointer-events: none; }
.knm-loading-spinner {
   width: 40px;
   height: 40px;
   border: 3px solid var(--knm-theme-border, #e9ecef);
   border-top-color: var(--knm-theme-accent, #ea580c);
   border-radius: 50%;
   animation: knm-boot-spin .6s linear infinite;
}
@keyframes knm-boot-spin { to { transform: rotate(360deg); } }
/* A user who asked the OS for less motion gets a static ring instead of a spinner — the overlay is dismissed
   by window.load either way, so nothing depends on the animation running. */
@media (prefers-reduced-motion: reduce) {
   .knm-loading-spinner { animation: none; }
   #knm-loading { transition: none; }
}

.knm-t-dash-seeall { font-size: .8rem; font-weight: 600; text-decoration: none; }
.knm-t-dash-period { width: auto; }

/* Kanban toolbar (item 5: the search and the two filters are KNMInput). The components bring their own chrome, so
   what is left here is sizing — the same job .knm-t-dash-period does for the dashboard period selector.
   Sized on a WRAPPER, in the shape .knm-t-search-host already uses for the global search: KNMInput's CustomClass
   lands on the input element, which cannot size the wrapper around it.
   The margin reset is the load-bearing part: every KNM field wrapper carries .knm-field (`margin-bottom: 1rem`), and
   inside an align-items:center toolbar that margin lifts the control off the row's centre line. Scoped to
   .knm-t-toolbar so it holds whichever wrapper the delegated sub-component (KNMSelect) ends up rendering.
   KNM.RazorComponents 3.17 moved the margin onto .knm-field for the Rounded layout too (this host's global layout),
   and kept .form-label-group only as a deprecated alias on the label-inside layouts: the rule targets .knm-field. */
.knm-t-kanban-search { width: 260px; }
.knm-t-kanban-filter { width: auto; min-width: 12rem; }
.knm-t-kanban-search > *,
.knm-t-kanban-filter > * { width: 100%; }
/* Since KNM.RazorComponents 3.19 the field margin is the inherited --knm-field-spacing: set once on each toolbar. */
.knm-t-toolbar,
.knm-perm-toolbar { --knm-field-spacing: 0; }

/* A card whose body is FLUSH full-bleed content (a grid or a table with p-0): the card's rounded corners must crop
   it, or the inner square-cornered content bleeds past the round (CTO 2026-08-26). Clip the card, and drop the
   grid's own inner radius so it meets the card edge instead of leaving a mismatched arc. Safe with the data grid:
   its filter/column popovers are JS-positioned outside the card, so overflow:hidden here does not clip them. */
.knm-t-flushcard { overflow: hidden; }
.knm-t-flushcard .knm-grid { border-radius: 0; }

/* KNMCard reso identico al tema: il tema (.knm-t-card-head) controlla l'header,
   qui azzero lo sfondo di default del .card-header di Bootstrap che altrimenti trapela. */
.card-header.knm-t-card-head { background: transparent; }

/* Volume chart fills the card height: the card is stretched to the side column in the two-col row, so the chart
   should grow to fill it instead of leaving whitespace under a fixed 220px (CTO 2026-08-26). Card body → flex
   column; the chart grows and overrides KNMChart's inline fixed height (it already runs maintainAspectRatio:false,
   so it fills the box). A min-height keeps it sane when the row collapses to one column on mobile. */
.knm-t-volumecard .card-body { display: flex; flex-direction: column; }
.knm-t-fillchart { flex: 1 1 auto; height: 100% !important; min-height: 220px; }

/* Recent-tickets column priority (it is a plain .knm-t-table, not a KNMDataGrid, so this replaces the grid's native
   responsive columns): drop the lowest-priority columns as the viewport narrows so subject + state always stay
   without a horizontal scroll. ≤lg: Updated + Assignee; ≤sm: also Priority + ID. */
@media (max-width: 991.98px) {
   .knm-t-recent .knm-t-col-updated,
   .knm-t-recent .knm-t-col-assignee { display: none; }
}
@media (max-width: 575.98px) {
   .knm-t-recent .knm-t-col-priority,
   .knm-t-recent .knm-t-col-id { display: none; }
}

/* KNMProgressBar delle barre "Aperti per priorità": il default Bootstrap è alto ~1rem, qui la meter
   è sottile (6px). SOLO l'altezza: la radius la governa già il tema (--bs-border-radius =
   --knm-theme-radius-sm, con .progress overflow:hidden che ritaglia il fill) — niente magic-number. */
.knm-t-dash-meter { height: 6px; }

/* --------------------------------------------------------------------------
   App-level reset (consolidated from the retired app.css — CTO 2026-08-26:
   "un unico file di personalizzazione"). The design system lives in
   knm-theme.css; these are the two app-wide resets that are not part of it.
   -------------------------------------------------------------------------- */
* { box-sizing: border-box; }

/* The black rectangle around the login <h1> (CTO 2026-08-31) is the BROWSER's default focus ring, and the h1 is
   focused by us: <FocusOnNavigate Selector="h1"> in Routes.razor calls Blazor's focusBySelector, which reads
   `t.hasAttribute("tabindex") || (t.tabIndex = -1), t.focus(...)` — it STAMPS tabindex="-1" on the first heading
   and focuses it so a screen reader announces the new page. Chrome then paints `outline: auto`: an #101010 outer
   ring with a #ffffff inner one, the two colours sampled off the screenshot.

   Bootstrap 4's reboot suppressed exactly this with `[tabindex="-1"]:focus:not(:focus-visible){outline:0}`;
   v5 dropped the rule and 5.3.3 — the build vendored here — carries no `[tabindex` selector at all, so the
   "standard Bootstrap behaviour" has to be put back rather than merely relied on.

   Restored WITHOUT :not(:focus-visible), because Chrome is evidently matching :focus-visible here. That costs
   nothing a keyboard user needs: tabindex="-1" is programmatic-focus-only and never reachable with Tab, so the
   ring marks no position in the tab order. The focus MOVE — the accessible part — is untouched. */
[tabindex="-1"]:focus { outline: 0; }

/* Status tabs are declared as <button> (for Blazor @onclick) but .knm-t-tab is authored for <a>, which has no
   native chrome. Strip the button's default background + top/left/right borders so they don't render as boxes;
   .knm-t-tab keeps the bottom border it uses for the active underline. */
button.knm-t-tab {
   background: transparent;
   border-top: 0;
   border-left: 0;
   border-right: 0;
   font: inherit;
   cursor: pointer;
}

/* --------------------------------------------------------------------------
   Grid theme hook (consolidated from the retired knm-grid-tickets.css). Every
   grid carries .WithTableCssClass("knm-grid-tickets"), so the product-wide look
   is defined once — now here, in the single custom stylesheet, not a per-feature
   file (converting a page needs ZERO css).
   -------------------------------------------------------------------------- */
.knm-grid-tickets {
   --knm-grid-detail-bg: var(--knm-surface-2, #f8f9fa);
   --knm-grid-detail-accent: var(--knm-primary, #0d6efd);
}

/* Header labels that may wrap onto two lines (/admin/permissions, r.2338).
   A grid column is as wide as its header's content, not as the Width its row
   declares: label + sort icon + filter button on one unbreakable line. Measured
   on 2026-09-26 (Playwright, en-US), the five "Show …" axes each took 146-171px
   and the responsive engine hid three of the six boolean columns at 1400px and
   two at 1600px. Letting the label break between its words is what narrows
   them. Opt-in per grid (MasterDataGrid TableCssClass): on a grid of one-word
   headers a wrap only makes the header taller. */
.knm-grid-wrapheads thead th,
.knm-grid-wrapheads thead th .knm-grid-th__row,
.knm-grid-wrapheads thead th .knm-grid-th__label { white-space: normal; }

/* A soft-deleted row (MasterDataGrid emits "table-danger knm-row-deleted" from
   RowDeleted). Bootstrap's .table-danger pins light colours on the --bs-table-*
   variables and has no dark variant, so on the dark surfaces the row showed a
   pale pink band. Dark theme only: the light theme keeps Bootstrap's tint
   exactly, and .table-danger itself is left alone because it also marks other
   things (an inactive row on the dev gold grid). The wash is the danger colour
   mixed into the theme surface, deepening for striped / hover / active, and
   the text stays the theme's own light text colour. */
[data-bs-theme="dark"] .knm-row-deleted {
   --bs-table-color: var(--knm-theme-text);
   --bs-table-bg: color-mix(in oklab, var(--knm-danger, var(--bs-danger)) 22%, var(--knm-theme-surface));
   --bs-table-border-color: color-mix(in oklab, var(--knm-danger, var(--bs-danger)) 40%, var(--knm-theme-border));
   --bs-table-striped-bg: color-mix(in oklab, var(--knm-danger, var(--bs-danger)) 27%, var(--knm-theme-surface));
   --bs-table-striped-color: var(--knm-theme-text);
   --bs-table-active-bg: color-mix(in oklab, var(--knm-danger, var(--bs-danger)) 36%, var(--knm-theme-surface));
   --bs-table-active-color: var(--knm-theme-text);
   --bs-table-hover-bg: color-mix(in oklab, var(--knm-danger, var(--bs-danger)) 32%, var(--knm-theme-surface));
   --bs-table-hover-color: var(--knm-theme-text);
}



/* --------------------------------------------------------------------------
   Permission matrix (role -> permissions, user -> permission overrides)
   --------------------------------------------------------------------------
   Both panels show the same thing — one line per permission, five CRUDX axes —
   so they share one skin. The layout is a fixed-width table per menu section
   rather than one long table: the axis header then repeats inside every
   section, which is what lets a section be read on its own after scrolling
   past the first screen. Pattern lifted from Berruto's UserPermissionsPanel;
   the components underneath are ours. */

.knm-perm-toolbar {
   display: flex;
   align-items: center;
   gap: .75rem;
   flex-wrap: wrap;
   margin-bottom: .75rem;
}

.knm-perm-toolbar .knm-perm-filter { flex: 1 1 16rem; min-width: 12rem; }

/* The override panel's own save row. It lives INSIDE the editor's "Permessi"
   tab rather than in the modal footer, because the overrides are a second
   payload behind a second permission — see UserPermissionsPanel's header. The
   separating rule is what tells the eye that this button belongs to the matrix
   above it and not to the dialog around it. */
.knm-perm-actions {
   display: flex;
   align-items: center;
   justify-content: flex-end;
   gap: .75rem;
   flex-wrap: wrap;
   margin-top: 1rem;
   padding-top: .75rem;
   border-top: 1px solid var(--knm-theme-border);
}

/* The row of per-user commands at the foot of the editor's "Generale" tab —
   reset password, roles, disable the second factor, unlock. They were icon
   buttons on the grid row until 2026-09-03; in the modal they carry their
   label, so they wrap instead of shrinking. */
.knm-user-actions {
   display: flex;
   align-items: center;
   gap: .5rem;
   flex-wrap: wrap;
   margin-top: .5rem;
   padding-top: .75rem;
   border-top: 1px solid var(--knm-theme-border);
}

/* A section one of those buttons unfolds — the password reset, the role set.
   Boxed and tinted so it reads as a command operating on the account, not as
   three more fields of the profile form above it: they post separately, behind
   their own permission, and the frame is what says so before the Save button
   does. (2026-09-04: these two were modals stacked on the editor's modal.) */
.knm-user-panel {
   margin-top: .75rem;
   padding: .875rem 1rem;
   border: 1px solid var(--knm-theme-border);
   border-left: 3px solid var(--knm-theme-accent);
   border-radius: var(--knm-theme-radius, .5rem);
   background: var(--knm-theme-surface-muted, rgba(0, 0, 0, .02));
}

.knm-user-panel-title {
   display: flex;
   align-items: center;
   gap: .4rem;
   margin-bottom: .75rem;
   font-size: .82rem;
   font-weight: 600;
   text-transform: uppercase;
   letter-spacing: .03em;
   color: var(--knm-theme-accent);
}

.knm-user-panel-actions {
   display: flex;
   align-items: center;
   justify-content: flex-end;
   gap: .5rem;
   flex-wrap: wrap;
   margin-top: .875rem;
}

/* The accordion wrapping the menu sections, repainted in the app's accent.
   KNMAccordion's own defaults resolve the expanded header to Bootstrap's blue
   (--bs-primary-bg-subtle), which on an orange-accented app reads as a
   different product's chrome. Set as VARIABLES on a wrapper rather than as
   rules on .accordion-button: the component's markup stays its own business,
   and an accent preset (data-knm-accent) still flows through. */
.knm-perm-sections {
   --knm-accordion-active-bg: var(--knm-theme-accent);
   --knm-accordion-active-color: #fff;
   --knm-accordion-btn-focus-border: var(--knm-theme-accent);
}

/* Repeated under the dark-theme selector because KNM's own dark block targets
   `[data-bs-theme="dark"] .accordion` — two classes' worth of specificity
   against this rule's one, so without it the accent silently reverts to
   Bootstrap blue the moment the user switches theme. */
[data-bs-theme="dark"] .knm-perm-sections {
   --knm-accordion-active-bg: var(--knm-theme-accent);
   --knm-accordion-active-color: #fff;
}

/* The COLLAPSED headers are painted here rather than through
   --knm-accordion-btn-bg, and that is not a preference: setting the variable
   was tried first and, checked in the browser, the button still computed to
   `background-color: rgba(0,0,0,0)` — something downstream of the variable
   mapping paints .accordion-button transparent — while the white text the same
   variable set DID apply, leaving every collapsed section unreadable. Painting
   the property the component actually resolves is what works. The extra
   `.accordion` on the selector puts this above the component's own
   two-class rules whichever way the two stylesheets end up ordered. */
.knm-perm-sections.accordion .accordion-button {
   background-color: var(--knm-theme-accent);
   color: #fff;
}

/* The chevron is an inlined SVG in Bootstrap's own dark ink, invisible on the
   accent bar. Inverted rather than re-inlined: filter costs one line and
   survives whatever icon the component ships next. */
.knm-perm-sections.accordion .accordion-button::after { filter: brightness(0) invert(1); }

/* Section heading in the FILTERED view, where there is no accordion to carry
   the menu name. Typeset as a section label, not as a paragraph: uppercase and
   muted, so it separates the blocks without competing with the rows. */
.knm-perm-section-title {
   display: flex;
   align-items: center;
   gap: .375rem;
   text-transform: uppercase;
   font-size: .72rem;
   font-weight: 600;
   letter-spacing: .04em;
   color: var(--knm-theme-text-muted);
   margin-bottom: .35rem;
}

.knm-perm-section-title i { font-size: .9rem; opacity: .8; }

/* Bulk row sits above the section table, right-aligned so the destructive one
   is furthest from the checkboxes it would clear. */
.knm-perm-bulk {
   display: flex;
   justify-content: flex-end;
   gap: .4rem;
   margin-bottom: .5rem;
}

/* Fixed layout so the five axis columns line up across every section, and NO
   table-striped: the rows already carry two meanings of their own (a page-level
   accent on the left, an override tint across the line) and a third alternating
   background turns all three into decoration. Bootstrap's own row border is
   what separates the lines. */
.knm-perm-table { table-layout: fixed; width: 100%; }

.knm-perm-table thead th {
   font-size: .72rem;
   font-weight: 600;
   text-transform: uppercase;
   letter-spacing: .03em;
   color: var(--knm-theme-text-muted);
   border-bottom: 1px solid var(--knm-theme-border);
   padding-top: .25rem;
   padding-bottom: .25rem;
}

.knm-perm-table td { vertical-align: middle; }

/* Axis columns are narrow and centred; the name column takes what is left. A
   switch is wider than the checkbox it replaced (2026-09-04), hence 72px. */
.knm-perm-axis { width: 72px; text-align: center; }

/* The override switch on the user panel is a MODE, not a sixth axis: it decides
   whether the five boxes to its right mean anything at all. Two hairlines
   bracket it so the eye reads three groups — what the permission is, whether an
   override is in force, what the override says — instead of six identical
   boxes in a row. */
.knm-perm-toggle {
   width: 80px;
   text-align: center;
   border-left: 1px solid var(--knm-theme-border);
   border-right: 1px solid var(--knm-theme-border);
}

.knm-perm-name {
   display: flex;
   align-items: center;
   gap: .4rem;
   flex-wrap: wrap;
}

/* The raw permission code, kept visible but out of the way — it is what an
   administrator quotes in a ticket, and what the seeded RESX label hides. */
.knm-perm-code {
   font-family: var(--knm-theme-font-mono, monospace);
   font-size: .72rem;
   color: var(--knm-theme-text-muted);
}

/* A page-level (primary) permission opens the section it governs, so it reads
   first: a hairline accent on the left instead of a second chip column. */
.knm-perm-row-page > td:first-child { border-left: 2px solid var(--knm-theme-accent); padding-left: .5rem; }

/* An axis the permission does not declare. Not an unchecked box: "no such
   verb" and "not granted" are different statements. */
.knm-perm-none { color: var(--knm-theme-text-muted); }

/* A line whose override is in force: the five axes below are the user's real
   authority, not the role's, and the tint is what says so at a glance.
   Written as --bs-table-bg rather than as `background`, because Bootstrap
   paints a table cell through `background-color: var(--bs-table-bg)` plus an
   inset box-shadow — a plain `background` here would end up UNDER that shadow
   on any striped or hovered row, which is why the table below carries neither.
   Redefining the variable on the cell makes Bootstrap paint our colour. */
.knm-perm-row-override > td { --bs-table-bg: var(--knm-theme-accent-soft); }

.knm-perm-empty { padding: 1rem 0; color: var(--knm-theme-text-muted); }

/* Floor under the fixed columns. The role panel spends 360px on its five axes,
   the user panel 440px on axes + override; below that the name column is
   squeezed to nothing, so the table scrolls inside its own .table-responsive
   container rather than the dialog growing to fit. Not in a media query: what
   matters is the width of the MODAL, which a viewport breakpoint does not
   measure. */
.knm-perm-table { min-width: 34rem; }
.knm-perm-table:has(.knm-perm-toggle) { min-width: 40rem; }

/* Group-access matrices (role -> groups, group -> roles, user -> groups;
   decision G8 of docs/decisions/2026-09-29-appartenenza-gruppi.md). They wear
   the permission-matrix skin above; what is theirs is the (?) beside each
   letter, which carries what the letter lets a person do on that group's
   tickets, and the read-only "from the role" cell of the user panel. */
.knm-group-letter-head {
   display: inline-flex;
   align-items: center;
   gap: .25rem;
}

/* The letters are words here («Modificare», «Cancellare»), not the one-letter
   axes of the permission matrix: its 72px column made them collide with the (?)
   and clipped the last one (walk, 29/09). Wider, and never wrapped. */
.knm-perm-axis:has(.knm-group-letter-head) {
   width: auto;
   min-width: 7.5rem;
   white-space: nowrap;
}

.knm-group-letter-help {
   font-size: .85rem;
   color: var(--knm-theme-text-muted);
   cursor: help;
}

/* The user panel's two column groups: what the roles give, then what this
   person gets on top. The hairline between them is what says the first is not
   editable here. */
.knm-group-from-roles {
   width: 12rem;
   border-right: 1px solid var(--knm-theme-border);
}

.knm-group-from-roles .knm-group-chips {
   display: flex;
   flex-wrap: wrap;
   gap: .2rem;
}

.knm-group-role-names {
   font-size: .72rem;
   color: var(--knm-theme-text-muted);
   margin-top: .15rem;
}

.knm-perm-table:has(.knm-group-from-roles) { min-width: 46rem; }

/* The group editor's "Chi ci lavora" section: separated from the group's own
   form by a rule, because it is a second payload (saved by the footer's Save,
   after the group). */
.knm-group-members {
   margin-top: 1.25rem;
   padding-top: .75rem;
   border-top: 1px solid var(--knm-theme-border);
}

/* Valerio's walk of 03/10, item 4: in the group editor the five letter
   columns fit the Large modal from 1366 px up (narrower switch columns than
   the role and user panels'), and below that it is the modal body that
   scrolls, never the table inside it. Scoped here: the role and user panels
   keep their own widths. */
.knm-group-members .table-responsive { overflow-x: visible; }
.knm-group-members .knm-perm-table { min-width: 36rem; }
.knm-group-members .knm-perm-axis:has(.knm-group-letter-head) { min-width: 5.5rem; }
.modal-body:has(.knm-group-members) { overflow-x: auto; }

/* --------------------------------------------------------------------------
   Screen lock (Components/Shared/ScreenLock.razor — CTO 2026-09-04, item 3).

   This is NOT an overlay. ScreenLock renders the card INSTEAD of the shell, so there is nothing underneath to
   sit on top of: the rules below just centre a card on a full-height page, the same way .knm-t-login does for
   the sign-in screen — which is deliberate, because the two screens ask the same question.
   -------------------------------------------------------------------------- */
.knm-t-lock {
   min-height: 100vh;
   display: grid;
   place-items: center;
   padding: 2rem 1rem;
   background:
      radial-gradient(820px 420px at 50% -8%, var(--knm-theme-accent-soft), transparent 70%),
      var(--knm-theme-bg);
}
.knm-t-lock-card {
   width: 100%;
   max-width: 24rem;
   padding: 2rem;
   border: 1px solid var(--knm-theme-border);
   border-radius: var(--knm-theme-radius);
   background: var(--knm-theme-surface);
   box-shadow: var(--knm-theme-shadow);
}
.knm-t-lock-head { text-align: center; margin-bottom: 1.25rem; }
.knm-t-lock-login { font-size: .8rem; }
.knm-t-lock-hint { font-size: .82rem; text-align: center; margin-bottom: 1.25rem; }
.knm-t-lock-signout { margin: 1rem 0 0; text-align: center; font-size: .82rem; }

/* --------------------------------------------------------------------------
   Team messaging drawer (Components/Shared/Messaging — CTO 2026-09-05).

   KNMDrawer owns the panel itself: the slide, the backdrop, the fixed
   positioning and the Esc key all come from the package. What is here is the
   INSIDE — a head that stays put, a body that scrolls, and the two shapes a
   message can take. Colours are theme tokens only, so the drawer follows the
   accent and flips with [data-bs-theme] like everything else in the shell.
   -------------------------------------------------------------------------- */
.knm-chat-drawer {
   display: flex;
   flex-direction: column;
   /* The package sets overflow-y:auto on .knm-drawer, which would scroll the
      whole panel — head included — instead of the list inside it. */
   overflow: hidden;
   background: var(--knm-theme-bg);
   color: var(--knm-theme-text);
}
.knm-chat-head {
   flex: 0 0 auto;
   display: flex;
   align-items: center;
   gap: .5rem;
   padding: .8rem 1rem;
   background: var(--knm-theme-surface);
   border-bottom: 1px solid var(--knm-theme-border);
}
.knm-chat-head-icon { font-size: 1.1rem; color: var(--knm-theme-accent); }
.knm-chat-head-title { font-weight: 700; font-size: .95rem; }

.knm-chat-body {
   flex: 1 1 auto;
   min-height: 0;
   display: flex;
   flex-direction: column;
   gap: 1rem;
   padding: 1rem;
   overflow: hidden;
}
.knm-chat-section { display: flex; flex-direction: column; gap: .5rem; min-height: 0; }
.knm-chat-section-grow { flex: 1 1 auto; }
.knm-chat-section-title {
   display: flex;
   align-items: center;
   gap: .4rem;
   margin: 0;
   font-size: .74rem;
   font-weight: 700;
   text-transform: uppercase;
   letter-spacing: .04em;
   color: var(--knm-theme-text-muted);
}
.knm-chat-card {
   background: var(--knm-theme-surface);
   border: 1px solid var(--knm-theme-border);
   border-radius: var(--knm-theme-radius);
   overflow: hidden;
}
.knm-chat-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* Colleague picker (KNMSelect combobox) — the option row and its no-results line. */
.knm-chat-colleague { display: flex; align-items: center; gap: .5rem; width: 100%; text-align: left; color: inherit; }
.knm-chat-colleague-name { font-size: .875rem; font-weight: 600; }
.knm-chat-picker-empty { padding: .6rem; font-size: .82rem; color: var(--knm-theme-text-muted); }

/* Conversation rows (KNMListItem). A room with something unread carries the
   accent wash the notification list already uses for the same idea. */
.knm-chat-row-unread { background: var(--knm-theme-accent-soft); }
.knm-chat-row-meta { display: flex; flex-direction: column; align-items: flex-end; gap: .2rem; }
.knm-chat-row-time { font-size: .68rem; color: var(--knm-theme-text-muted); white-space: nowrap; }

/* Open conversation. */
.knm-chat-thread-head {
   flex: 0 0 auto;
   display: flex;
   align-items: center;
   gap: .5rem;
   padding-bottom: .5rem;
   border-bottom: 1px solid var(--knm-theme-border);
}
.knm-chat-thread-name { font-weight: 700; font-size: .9rem; }
/* column-reverse is what opens a conversation at its END. In a reversed column
   the browser's scroll origin IS the bottom, so the newest message is on screen
   without a line of JavaScript, and it stays there as messages arrive. It also
   packs a short thread against the bottom instead of stranding it at the top.
   ChatThread's markup enumerates messages newest-first for this reason — see the
   comment there before reordering either one. */
.knm-chat-thread {
   flex: 1 1 auto;
   min-height: 0;
   display: flex;
   flex-direction: column-reverse;
   justify-content: flex-start;
   gap: .5rem;
   overflow-y: auto;
   padding: .25rem 0;
}
.knm-chat-older { align-self: center; }
.knm-chat-msg {
   max-width: 85%;
   align-self: flex-start;
   padding: .45rem .65rem;
   border: 1px solid var(--knm-theme-border);
   border-radius: var(--knm-theme-radius-sm);
   background: var(--knm-theme-surface);
}
.knm-chat-msg-mine {
   align-self: flex-end;
   background: var(--knm-theme-accent-soft);
   border-color: var(--knm-theme-accent-border);
}
.knm-chat-msg-sender { font-size: .7rem; font-weight: 700; color: var(--knm-theme-text-muted); }
/* pre-wrap keeps the line breaks the sender typed WITHOUT any markup: the body
   is printed as an escaped Razor expression, so this is presentation of plain
   text, never a licence to render it as HTML. overflow-wrap breaks the pasted
   URL that would otherwise widen the whole panel. */
.knm-chat-msg-body { font-size: .86rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.knm-chat-msg-time { font-size: .66rem; color: var(--knm-theme-text-muted); text-align: right; }

.knm-chat-composer {
   flex: 0 0 auto;
   display: flex;
   align-items: flex-end;
   gap: .5rem;
   padding-top: .5rem;
   border-top: 1px solid var(--knm-theme-border);
}
.knm-chat-composer > *:first-child { flex: 1 1 auto; min-width: 0; }
=======
   Action button — a fixed 30x30 square for the icon buttons in a row's action
   column (CTO 2026-09-07). Applied by hand where it is wanted, not globally:
   a grid's actions and a toolbar's buttons are not the same thing, and one
   rule that caught both would resize the toolbar too.
   -------------------------------------------------------------------------- */
.knm-t-actionbtn {
   min-height: 30px;
   min-width: 30px;
   max-width: 30px;
   max-height: 30px;
   /* The four sizes above are the CTO's. These three are what makes them hold:
      a Bootstrap button carries its own padding, so without them the icon sits
      off-centre inside the box the size rules just fixed. */
   display: inline-flex;
   align-items: center;
   justify-content: center;
   padding: 0;
}

/* A mention inside a chat message. Tinted rather than boxed: it has to be findable while scanning a thread
   without turning every message into a row of badges. */
.knm-chat-mention {
   color: var(--knm-theme-accent, #ea580c);
   background: var(--knm-theme-accent-soft, rgb(234 88 12 / .1));
   border-radius: 4px;
   padding: 0 .15rem;
   font-weight: 600;
}


/* --------------------------------------------------------------------------
   Settings (/settings) — the sections are KNMTabs (KeepAlive keeps every one
   mounted, so typing and an MFA challenge survive a tab switch); what lives
   here is only the page's distance from the edges and the unsaved-work dot.
   -------------------------------------------------------------------------- */

/* 20px of air on every side (CTO 2026-09-10: "allontana tutto dai bordi di 20px"), measured to the CARD and
   not to the row. The two are not the same thing here: a Bootstrap row carries negative gutter margins and
   its columns pay them back as padding, so a flat `margin: 20px` would land the card at 20 plus half a gutter
   — 28 or 32px depending on the g-* class, and silently different again if somebody changes it. The margin is
   therefore the target MINUS what the column already adds, written as calc against Bootstrap's own variables
   so the number holds whatever the gutter becomes.

   The sides are 40 and the top/bottom 20, which is not an oversight: "allontana tutto dai bordi LATERALI
   (destra e sinistra) del doppio" named the two sides and only those. Vertically the page is already bounded
   by the topbar and the footer, so the same doubling there would buy nothing. */
.knm-set-shell {
    margin-top: calc(20px - var(--bs-gutter-y, 1rem));
    margin-bottom: 20px;
    margin-inline: calc(40px - .5 * var(--bs-gutter-x, 1rem));
}

/* Valerio, 10/10 night: "lo metterei più centrato, triplica gli spazi a destra e sinistra" — up to 120 a side.
   The first cut switched to 120 at the lg VIEWPORT breakpoint and broke the page when the window shrank (his
   screenshot, 10/10): the viewport still said "wide" while the sidebar ate 270px of it, so the cards were left
   ~300px each. The side space is therefore a share of the space the page actually has (a percentage margin is
   measured on the containing block, not on the viewport), never below the 40 above and never above 120. */
.knm-set-shell {
    margin-inline: calc(clamp(40px, 11%, 180px) - .5 * var(--bs-gutter-x, 1rem));
}

/* Valerio, 11/10: "un altro po' di spazio a destra e sinistra" — the share went from 8% to 11% and the ceiling from
   120 to 180, still measured on the page and not on the window, for the reason above. */

/* The trusted-devices card (Valerio, 11/10, reference layout): a soft icon tile above its title, and the device's
   two facts (last used, expiry) on two lines in the narrow column. */
.knm-set-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: var(--bs-border-radius-lg);
    background: var(--bs-primary-bg-subtle);
    color: var(--bs-primary);
    font-size: 1.5rem;
}

.knm-set-devices__detail { white-space: pre-line; }

/* The photo of «Impostazioni › Profilo» at 150px (Valerio, 12/10 item 4: the size he tried and preferred), its initials
   scaled with it; the card's wrapper drops its top padding in the markup (pb-2). Only here: every other avatar keeps the
   library's sizes. */
.knm-set-photo .knm-avatar.knm-avatar--xl {
    width: 150px;
    height: 150px;
    font-size: 3.25rem;
}

/* Side by side only when the PAGE is wide enough (Valerio, 11/10: on his smaller screen the paired cards were squeezed).
   A Bootstrap breakpoint reads the window, and on a laptop the window is "xxl" while the sidebar and the side margins
   leave the cards ~850px between them. The shell is a size container, so the columns read the width they actually get:
   below 860px of page every card takes the whole row (lowered from 1000 the same night: his smaller screen, scaled,
   gives the page ~940px and he wants the cards side by side there too). */
.knm-set-shell { container: knm-set / inline-size; }

@container knm-set (min-width: 860px) {
    .knm-set-shell .knm-set-col-half { width: 50%; }
    .knm-set-shell .knm-set-col-wide { width: 66.6667%; }
    .knm-set-shell .knm-set-col-narrow { width: 33.3333%; }
}

/* No min-height on the pane, and it is worth saying why rather than leaving the absence to be re-invented.
   Doubling the card's height did exactly what it says — and the extra height landed INSIDE the card, as white
   space under the save button, because a form does not grow to fill a box. The card ends where its content
   ends (CTO 2026-09-10: "tutto quel bianco sotto non va bene").

   Making the panel reach the footer instead was tried on 2026-09-10 and rejected on sight: the card grew, the
   save bar was anchored to its bottom edge, and the result was worse than the empty page below it. Recorded so
   the next reader does not spend the afternoon rediscovering it. */

}


/* Work pending in a section the reader may not be looking at. Pushed to the end
   of its entry so it reads as a state OF that section rather than an icon FOR
   it, and sized to be noticed without competing with the entry's own icon. */
.knm-set-dirty {
   margin-left: auto;
   flex-shrink: 0;
   width: 7px;
   height: 7px;
   border-radius: 99px;
   background: currentColor;
}


/* ── People directory (/admin/users) ─────────────────────────────────────────
   Three cells the grid draws rather than prints, plus the summary line under
   the tabs. Non-scoped and app-wide, like the rest of this file: the product
   has no .razor.css. */

/* UTENTE — avatar beside the name, on one line. min-width:0 on the name is what
   lets the ellipsis happen: without it the flex item refuses to shrink below its
   content and the cell pushes the whole row past its declared width. */
.knm-t-usercell { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.knm-t-usercell__name {
   min-width: 0;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   font-weight: 500;
}

/* RUOLO — the chips wrap instead of widening the column, so a user holding three
   roles makes their own row taller and leaves every other column where it was. */
.knm-t-chipcell { display: flex; flex-wrap: wrap; gap: .25rem; }

/* …each chip on ONE line (people-editor-tabs, Valerio 10/10, item 9: «Super amministratore» wrapped inside its own
   chip). The chips still wrap between each other; a name longer than the column is cut with «…» and read whole in the
   tooltip of its wrapper. min-width:0 lets the text shrink inside the chip's inline-flex. */
.knm-t-chipcell--ellipsis > * { max-width: 100%; min-width: 0; }
.knm-t-chipcell--ellipsis .knm-chip { max-width: 100%; }
.knm-t-chipcell--ellipsis .knm-chip__text {
   max-width: 100%;
   min-width: 0;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
}

/* The two numbers under the tab strip. Muted and small on purpose: it is a recap
   of the badges above it, not a heading competing with them. */
.knm-t-dirsummary {
   display: flex;
   align-items: center;
   gap: .4rem;
   padding: .5rem .25rem 0;
   font-size: .8125rem;
   color: var(--bs-secondary-color);
}

/* Organizations editor: attachment storage section (WP9). The hint under the
   password says a key is stored without showing it; the buttons sit on their own
   line so a narrow modal wraps them instead of squeezing the fields. */
.knm-tenant-storage__hint {
   margin-top: .25rem;
   font-size: .78rem;
   color: var(--knm-theme-text-muted);
}
.knm-tenant-storage__actions { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: flex-end; }

/* Work-session banner (WP8b Task 8): «Stai operando in …» with «Torna all'host», in the shell while the operator
   works inside another organization. Same family as the licence banner (an alert strip in the content column), with
   --knm-warning as the accent: a state to leave, not an error. No dismiss control, by design. */
.knm-ws-banner {
   display: flex;
   flex-wrap: wrap;
   align-items: center;
   gap: .5rem .75rem;
   padding: .6rem .9rem;
   border: 1px solid var(--knm-warning);
   border-left-width: 4px;
   border-radius: var(--bs-border-radius, .375rem);
   background: color-mix(in srgb, var(--knm-warning) 14%, var(--bs-body-bg));
   color: var(--bs-body-color);
}
.knm-ws-banner-icon { font-size: 1.25rem; color: var(--knm-warning); }
.knm-ws-banner-text { display: flex; flex-wrap: wrap; gap: .25rem 1rem; align-items: baseline; min-width: 0; }
.knm-ws-banner-text strong { overflow-wrap: anywhere; }
.knm-ws-banner-time { font-size: .8125rem; color: var(--bs-secondary-color); white-space: nowrap; }
.knm-ws-banner-form { margin: 0 0 0 auto; }
[data-bs-theme="dark"] .knm-ws-banner {
   background: color-mix(in srgb, var(--knm-warning) 18%, var(--bs-body-bg));
}

/* F2.8 - the ticket detail's history tab: under each timeline entry, the actor's initials beside "who · when" (the
   absolute time is the tooltip), and the "show more" button centred under the timeline. */
.knm-t-history-meta { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.knm-t-history-when { font-size: .8125rem; color: var(--knm-theme-text-muted); overflow-wrap: anywhere; }
.knm-t-history-more { display: flex; justify-content: center; margin-top: .75rem; }
/* The mark of a host operator acting in a work session: the size of a small KNMAvatar, a neutral icon instead of initials. */
.knm-t-history-operator { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; background: var(--knm-theme-surface-2, var(--bs-secondary-bg)); color: var(--knm-theme-text-muted); }

/* S1a (A7, MODELLO §3.2 «Modalità super-admin visibile»): super-admin mode wears a colour of its own, the danger hue,
   so a person never forgets they act on the installation. Same shape as .knm-ws-banner. */
.knm-t-superadmin-bar {
   display: flex;
   flex-wrap: wrap;
   align-items: center;
   gap: .5rem 1rem;
   margin-bottom: 1rem;
   padding: .625rem 1rem;
   border-radius: var(--bs-border-radius);
   border: 1px solid color-mix(in srgb, var(--knm-danger, var(--bs-danger)) 55%, transparent);
   background: color-mix(in srgb, var(--knm-danger, var(--bs-danger)) 12%, var(--bs-body-bg));
}
.knm-t-superadmin-label { font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--knm-danger, var(--bs-danger)); }
.knm-t-superadmin-text { font-size: .875rem; }
.knm-t-superadmin-leave { font-size: .875rem; }
[data-bs-theme="dark"] .knm-t-superadmin-bar {
   background: color-mix(in srgb, var(--knm-danger, var(--bs-danger)) 22%, var(--bs-body-bg));
}

/* Organizations editor: domains section (S3). One block per domain; the TXT record to publish is shown in
   monospace so it can be copied exactly, and long tokens wrap instead of widening the modal. */
.knm-tenant-domains__hint, .knm-tenant-domains__empty {
   margin: .25rem 0 .5rem;
   font-size: .78rem;
   color: var(--knm-theme-text-muted);
}
.knm-tenant-domains__row {
   padding: .6rem 0;
   border-top: 1px solid var(--knm-theme-border, var(--bs-border-color));
}
.knm-tenant-domains__head { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.knm-tenant-domains__host { font-weight: 600; }
.knm-tenant-domains__txt { margin: .35rem 0; font-size: .8rem; }
.knm-tenant-domains__txt code { word-break: break-all; }
.knm-tenant-domains__label { color: var(--knm-theme-text-muted); margin-right: .25rem; }
.knm-tenant-domains__actions { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: flex-end; margin-top: .35rem; }
.knm-tenant-domains__add { margin-top: .5rem; }

/* «Aspetto» (S3): the organization's appearance page. The preview box has a fixed height so a tall logo and an
   empty slot line up; images are scaled down, never up. */
.knm-tenant-appearance__hint { margin-top: .25rem; font-size: .78rem; color: var(--knm-theme-text-muted); }
.knm-tenant-appearance__label { font-weight: 600; margin-bottom: .35rem; }
.knm-tenant-appearance__preview {
   display: flex; align-items: center; justify-content: center;
   height: 96px; margin-bottom: .5rem; padding: .5rem;
   border: 1px dashed var(--knm-theme-border, var(--bs-border-color)); border-radius: .5rem;
}
.knm-tenant-appearance__preview img { max-height: 100%; max-width: 100%; object-fit: contain; }
.knm-tenant-appearance__actions { display: flex; justify-content: flex-end; }

/* S3 (D12/D13): the image inside the square brand mark (BrandLogo). The mark keeps its size and radius; the image fits
   inside it without distortion. */
.knm-t-logo-img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; }

/* ticket-split: the list of messages in the split dialog scrolls inside the dialog instead of growing it. */
.knm-split-articles { max-height: 16rem; }

/* explained-states: a technical detail behind «Dettagli tecnici» (the stopped organization erasure's job error) — smaller
   than the sentence it explains, which a person reads first. */
.knm-t-tech-detail { font-size: .74rem; }

/* Issue #162 (decision 8): the DNS records of a sender's domain (Components/Shared/SenderDomainDnsCard.razor), on the
   installation-mail page and in the e-mail channel editor. One block per record: name and state on a line, what the
   record does, what to do, and the zone-file line in a code block that wraps instead of widening the dialog. The
   state marks use Bootstrap's semantic tokens, as installation-mail.css does: knm-theme.css has no success/danger family. */
.knm-t-sender-dns__records { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .75rem; }
.knm-t-sender-dns__record {
   padding: .6rem .75rem;
   border: 1px solid var(--knm-theme-border);
   border-radius: var(--knm-theme-radius-sm);
   background: var(--knm-theme-surface);
   color: var(--knm-theme-text);
}
.knm-t-sender-dns__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem; }
.knm-t-sender-dns__name { font-weight: 600; }
.knm-t-sender-dns__state { display: inline-flex; align-items: center; gap: .35rem; font-size: .85rem; }
.knm-t-sender-dns__state--ok > i { color: var(--bs-success); }
.knm-t-sender-dns__state--ko > i { color: var(--bs-danger); }
.knm-t-sender-dns__state--unknown > i { color: var(--knm-theme-text-muted); }
.knm-t-sender-dns__what { margin: .15rem 0 .35rem; font-size: .8rem; color: var(--knm-theme-text-muted); }
.knm-t-sender-dns__say { margin: 0 0 .35rem; }
.knm-t-sender-dns__say--warn { display: flex; gap: .4rem; align-items: baseline; }
.knm-t-sender-dns__say--warn > i { color: var(--bs-warning); }
.knm-t-sender-dns__label { margin: .35rem 0 .2rem; font-size: .8rem; color: var(--knm-theme-text-muted); }
.knm-t-sender-dns__line { display: flex; gap: .5rem; align-items: flex-start; }
.knm-t-sender-dns__line > .knm-t-sender-dns__code { flex: 1 1 auto; min-width: 0; }
.knm-t-sender-dns__code {
   margin: 0;
   padding: .4rem .6rem;
   border: 1px solid var(--knm-theme-border);
   border-radius: var(--knm-theme-radius-sm);
   background: var(--knm-theme-bg);
   font-family: var(--knm-theme-font-mono);
   font-size: .78rem;
   white-space: pre-wrap;
   overflow-wrap: anywhere;
   user-select: all;
}
.knm-t-sender-dns__actions { display: flex; justify-content: flex-end; margin-top: .75rem; }

/* --------------------------------------------------------------------------
   Responsive shell (skin-layout, Corrado 09/10: «il sito deve essere responsive, una volta per tutte»). One place for
   the rules that keep every page inside the screen from 360 px up; the E2E guard ResponsiveShellTests holds them.
   -------------------------------------------------------------------------- */

/* A page's toolbar: fields and buttons on one row that wraps when the screen is narrow, all of the same height. A KNM
   field keeps a bottom margin for its validation message (--knm-field-spacing); in a toolbar there is none to show,
   and the margin lifts the field above the buttons beside it (GlobalSearch, the dashboard's period). */
.knm-toolbar {
   display: flex;
   flex-wrap: wrap;
   align-items: center;
   gap: .5rem;
   --knm-field-spacing: 0;
}

/* The page's vertical scrollbar always keeps its room (people-editor-tabs, Valerio 10/10, item 2): the top bar jumped
   by the scrollbar's width, 17 px, between a page with a vertical scrollbar (the Home) and one without (the ticket
   list), because the scrollbar appearing narrowed the whole page. On html, the element that scrolls: on body it has no
   effect. */
html { scrollbar-gutter: stable; }

/* A grid item's automatic minimum width is its content's: one long word or a row of chips widened /admin/license past
   a 360 px screen. Every child of a template grid shrinks with its track instead. */
.knm-t-grid > * { min-width: 0; }

/* The top bar's breadcrumb takes the room left between the menu button and the icons, and pushes the icons to the
   right even when the language picker (which carries ms-auto) is not drawn. */
.knm-t-topbar > .knm-t-breadcrumb { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.knm-t-topbar > .knm-t-breadcrumb :where(.knm-breadcrumb-wrapper, nav) { min-width: 0; }
.knm-t-topbar > .knm-t-breadcrumb .breadcrumb { flex-wrap: nowrap; min-width: 0; }
.knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item { white-space: nowrap; }

/* Below 1200 px the breadcrumb is the current page only, on one line, cut with «…». Corrado chose it for phones (09/10);
   the walk showed the full path does not fit beside the search and the icons up to 1200 px either («Home › G»), and
   from 992 px the open sidebar already shows where the page sits. */
@media (max-width: 1199.98px) {
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item:not(.active) { display: none; }
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item.active {
      padding-left: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      font-weight: 600;
      color: var(--knm-theme-text);
   }
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item.active::before { content: none; }
}

/* From 1200 px the whole path is shown; when it is still too long the levels above shrink with «…» first, and the
   current page keeps its full name. */
@media (min-width: 1200px) {
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item:not(.active) { flex: 0 1 auto; min-width: 2rem; overflow: hidden; }
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item:not(.active) > a {
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
   }
   .knm-t-topbar > .knm-t-breadcrumb .breadcrumb-item.active { flex: 0 0 auto; }
}

/* The global search gives room to the page name on mid-size screens instead of holding 280 px. */
.knm-t-topbar .knm-t-search-host { width: clamp(180px, 24vw, 280px); }

/* Below 576 px the language moves into the user menu (LanguageMenuItems): the top bar keeps the icons that are
   used every day. */
@media (max-width: 575.98px) {
   .knm-t-topbar > [data-testid="language-switcher"] { display: none !important; }
   .knm-t-topbar { gap: .25rem; }
}

/* The focus target a drawer's content gets on open (AppearanceCustomizer): it takes the focus for Escape and the
   keyboard, and draws no ring of its own; the controls inside keep theirs. */
.knm-drawer-focus:focus { outline: none; }

/* Density keeps reaching cards (skin-replacement §6.5): the template's card padding on Bootstrap's card body, so every
   KNMCard follows «Densità interfaccia» like the template cards did. Lost with the skin-layout shell revert, put back
   by skin-auth (Valerio on the bridge, 70792b05). */
.card-body { padding: var(--knm-theme-card-pad); }

/* Every KNMCard drawn as the template's .knm-t-card (Corrado 2026-10-10: «guarda la tua DASHBOARD: le card sono come
   le tue?»). KNMCard renders a Bootstrap .card, which has no theme of its own: Bootstrap's defaults left it on the page
   background with a black hairline and no shadow, and its header and footer on 3% black. The card's own variables are
   bound to the SAME tokens .knm-t-card uses (surface, border, radius, shadow, card padding), and the header title to
   .knm-t-card-title's type, so a KNMCard and a template card read the same and follow the customizer (accent,
   density, dark mode). Measured on /, «Volume ticket» against /settings, 2026-10-10. */
.card {
    --bs-card-bg: var(--knm-theme-surface);
    --bs-card-cap-bg: var(--knm-theme-surface);
    --bs-card-border-color: var(--knm-theme-border);
    --bs-card-border-radius: var(--knm-theme-radius);
    --bs-card-inner-border-radius: calc(var(--knm-theme-radius) - 1px);
    --bs-card-box-shadow: var(--knm-theme-shadow);
    --bs-card-cap-padding-y: calc(var(--knm-theme-card-pad) * .75);
    --bs-card-cap-padding-x: var(--knm-theme-card-pad);
    box-shadow: var(--knm-theme-shadow); /* Bootstrap declares --bs-card-box-shadow but never applies it to .card */
}
/* One card style for every card, KNMCard and the template's alike (Corrado 2026-10-10: «Header, nessun border bottom.
   Footer: nessun border TOP. Body: nessun border. Stile uguale per tutti»). */
.card-header, .knm-t-card-head { border-bottom: 0; }
.card-footer, .knm-t-card-foot { border-top: 0; }
.card-body, .knm-t-card-pad { border: 0; }
.card-header > .card-title {
    font-family: var(--knm-theme-font-display);
    font-size: .95rem;
    font-weight: 600;
    letter-spacing: -.01em;
    margin-bottom: 0;
}

/* The theme's border token (#e7e4df) is almost the page background (#f6f5f3), so a card's edge disappeared on the page
   and showed only on a card drawn inside white (Valerio, 10/10: «i bordi della card esterna sono invisibili»). Cards
   take a border a step darker than the token, mixed from it so dark mode and the customizer still drive it. */
.card, .knm-t-card { --bs-card-border-color: color-mix(in srgb, var(--knm-theme-border) 82%, var(--knm-theme-text, #000)); }
.knm-t-card { border-color: var(--bs-card-border-color); }

/* The rounded corners of a card were invisible (Valerio, 10/10). The package's global layout sets
   `.knm-layout-rounded .card { border-radius: 1rem }` (16px) straight on the card, and as a descendant rule it beats the
   variables above; the header and the footer still took the theme's inner radius (11px). A header with a background,
   less rounded than the border around it, paints over the border exactly on the curve. The card's own radius goes back
   to the theme token, the same 12px as the template's .knm-t-card, so the outer edge and the inner radius are again
   one pixel apart. */
.knm-layout-rounded .card { border-radius: var(--bs-card-border-radius); }

/* «I miei ticket» below 576 px (CustomerTickets.razor): one card per ticket, the whole card is the link. The subject
   wraps like normal text, the number and the date read like the table's. Theme tokens only, so the customizer's accent,
   density and dark mode reach the cards. */
.knm-portal-ticketcard { display: block; color: var(--knm-theme-text); text-decoration: none; border-radius: var(--knm-theme-radius); }
.knm-portal-ticketcard:hover { color: var(--knm-theme-text); }
.knm-portal-ticketcard:hover .card { border-color: var(--knm-theme-accent); }
.knm-portal-ticketcard:focus-visible { outline: 2px solid var(--knm-theme-accent); outline-offset: 2px; }
.knm-portal-ticketcard-title { font-weight: 600; overflow-wrap: anywhere; }
.knm-portal-ticketcard-number {
    font-family: var(--knm-theme-font-mono);
    font-size: .76rem;
    color: var(--knm-theme-text-muted);
    white-space: nowrap;
}

/* Global search results (search-results-look part 2): the Search type of KNMInput, dressed in the theme tokens. */
.knm-gsearch-panel {
  --knm-search-panel-bg: var(--knm-theme-bg);
  --knm-search-panel-border: var(--knm-theme-border);
  --knm-search-panel-radius: var(--knm-theme-radius);
  --knm-search-panel-shadow: var(--knm-theme-shadow);
  --knm-search-panel-gap: 8px;
  --knm-search-panel-padding: 8px;
  --knm-search-panel-min-width: min(480px, calc(100vw - 32px));
  --knm-search-panel-max-height: min(70vh, 520px);
  --knm-search-header-color: var(--knm-theme-text);
  --knm-search-header-font-size: .8rem;
  --knm-search-option-active-bg: var(--knm-theme-accent-soft);
  --knm-search-option-subtitle-color: var(--knm-theme-text-muted);
  --knm-search-match-bg: var(--knm-theme-accent-soft);
  --knm-search-view-all-color: var(--knm-theme-accent);
  max-width: calc(100vw - 32px);
  color: var(--knm-theme-text);
}
.knm-gsearch-panel .knm-search__card { background: var(--knm-theme-surface); border-color: var(--knm-theme-border); }
.knm-gsearch-panel .knm-search__group-header { text-transform: none; letter-spacing: normal; }
.knm-gsearch-panel .knm-search__group-icon { color: var(--knm-theme-accent); }
/* The count badge on the theme's own colours: Bootstrap's primary-subtle pair is dark blue on near-black in the dark theme. */
.knm-gsearch-panel .knm-search__group-count {
  --knm-badge-variant-bg: var(--knm-theme-accent-soft);
  --knm-badge-variant-color: var(--knm-theme-text);
  font-weight: 600;
}
.knm-gsearch-panel .knm-search__match { font-weight: 700; color: inherit; }
.knm-gsearch-panel .knm-search__option-meta { max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Ticket and message rows have nothing on the left: an empty slot must not indent them. */
.knm-gsearch-panel .knm-search__option-leading:not(:has(*)) { display: none; }
/* «No results»: one card with the term and a hint (Corrado 15/10). */
.knm-gsearch-panel .knm-gsearch-empty {
  background: var(--knm-theme-surface);
  border: 1px solid var(--knm-theme-border);
  border-radius: var(--knm-theme-radius);
  padding: 1rem .75rem;
  /* The status row is a flex box: without this the card shrinks to its text and sits centred. */
  flex: 1 1 auto;
}
.knm-gsearch-panel .knm-gsearch-empty .knm-empty-state-icon { font-size: 1.5rem; color: var(--knm-theme-text-muted); }
.knm-gsearch-panel .knm-gsearch-empty .knm-empty-state-description { color: var(--knm-theme-text-muted); }
