/* MyTasKat — ported from the legacy stella33-tasks-standalone artifact stylesheet.
 *
 * Every colour/radius/font the legacy file hard-coded is now derived from the
 * ten branding tokens of the /branding.css contract (PLAN §4):
 *
 *   --brand-primary --brand-accent --sidebar-bg --sidebar-text
 *   --page-bg --card-bg --text-main --text-muted --radius --font-family
 *
 * The defaults below reproduce the legacy look exactly. /branding.css is loaded
 * AFTER this file and re-declares the same ten names on a bare `:root`, so an
 * admin's values win by cascade order without touching any rule here.
 *
 * Everything else (surfaces, borders, faint text, sidebar shades, priority
 * chips, avatars) is color-mix()'d off those ten, so a dark token set produces
 * a coherent dark UI and a light one the legacy palette — which is why there is
 * no prefers-color-scheme block any more (see the note in app.js' header).
 */

/* ---------------------------------------------------------------------------
 * Poppins, self-hosted (QUOTE-TOOL-MYTASKAT-ADDON-FEASIBILITY.md §4.18).
 *
 * These files used to come from fonts.googleapis.com via three <link> tags in
 * index.html. They are now the seven latin-subset .woff2 files under
 * public/fonts/ (~56 KB in total), for two reasons: the PDF sidecar's Chromium
 * may have no outbound internet and a quote's typography must not depend on
 * it, and dropping MyTasKat's only external asset is a small win for the
 * Portal's compliance story.
 *
 * 300..800 + italic 500 is the range the quote tool's paper layout uses; the
 * app chrome itself only needs 400..700. KEEP THIS LIST IN SYNC with
 * src/services/fonts.js, which emits the same faces with data: URL sources for
 * the self-contained HTML posted to the sidecar.
 * ------------------------------------------------------------------------ */
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 300; font-display: swap;
  src: url('/fonts/poppins-300.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/fonts/poppins-400.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/fonts/poppins-500.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/fonts/poppins-600.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/fonts/poppins-700.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 800; font-display: swap;
  src: url('/fonts/poppins-800.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: italic; font-weight: 500; font-display: swap;
  src: url('/fonts/poppins-500-italic.woff2') format('woff2'); }

:root {
  /* ---- the branding contract (defaults = the legacy look) ---- */
  --brand-primary: #94562B;
  --brand-accent: #7C4522;
  --sidebar-bg: #1B2C31;
  --sidebar-text: #D9DED3;
  --page-bg: #F5F4EE;
  --card-bg: #FFFFFF;
  --text-main: #1C2C31;
  --text-muted: #5B6B67;
  --radius: 10px;
  --font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* ---- fixed project palette (server: src/services/constants.js) ---- */
  --project-color-1: #94562B;
  --project-color-2: #4F7350;
  --project-color-3: #4A6FA0;
  --project-color-4: #8C7A3E;
  --project-color-5: #B5502F;
  --project-color-6: #5C7874;
  --project-color-7: #6B5B95;
  --project-color-8: #C77DA0;
  --project-color-9: #3E7C8C;
  --project-color-10: #A68A56;
}

:root {
  /* ---- legacy variables: direct aliases + literal fallbacks ----
     A custom property whose value fails to parse does NOT fall back to an
     earlier declaration (it goes invalid-at-computed-value-time at the point of
     use), so the literals below are the real safety net for browsers without
     color-mix(); the @supports block after this one re-derives them from the
     branding tokens wherever color-mix() is available. */
  --bg: var(--page-bg);
  --surface: var(--card-bg);
  --surface-raised: #FBFAF6;
  --surface-2: #ECEAE0;
  --border: #DAD8CB;
  --border-soft: #E7E5DA;
  --text: var(--text-main);
  --text-faint: #8A9490;

  --accent: var(--brand-primary);
  --accent-hover: var(--brand-accent);
  --accent-soft: #F3E4D6;
  --accent-contrast: #FFF9F3;

  --sidebar-bg-deep: #142327;
  --sidebar-text-muted: #93A29C;
  --sidebar-border: #2B3F44;

  /* Priority / status hues are semantic, not brand — they keep the legacy
     values; only their soft backgrounds follow the card colour. */
  --pri-low: #5C7874;
  --pri-low-bg: #E7ECE9;
  --pri-medium: #8C7A3E;
  --pri-medium-bg: #EFE9D3;
  --pri-high: #B5502F;
  --pri-high-bg: #F6E2D9;
  --status-done: #4F7350;
  --status-done-bg: #E1EADD;
  --status-watch: #4A6FA0;
  --status-watch-bg: #E1E7F0;

  /* Task board ink + card tags (0.78.4, contrast). Each tag is a FILLED tint
     of one hue with text that hue mixed towards --text-main, so the text
     stays dark on a light palette and light on a dark one (≥ 4.5:1 either
     way). --tag-building is a fixed slate-teal like the priority hues; the
     category tag follows the brand copper. Literals = the default palette. */
  --tag-building: #2F5D6A;
  --tag-building-fg: #25424B;
  --tag-building-bg: #DCE3E6;
  --tag-building-border: #C1CED2;
  --tag-category-fg: #5F3E27;
  --tag-category-bg: #F3E4D6;
  --tag-foreign-fg: #5F5B39;
  --kic-ink: #3A5879;

  --shadow-sm: 0 1px 2px rgba(20, 25, 20, 0.07);
  --shadow-md: 0 10px 28px rgba(20, 28, 25, 0.10);
  --focus-ring: #5C7874;

  /* 0.86.0 — the ONE motion scale. The "Motion" section at the end of this
     file is its only reader, plus app.js MOTION_MED_MS, which must equal
     --motion-med. fast = a control answering the pointer (tint, press); med =
     something that changes size or appears (a card unrolling, the editor
     window). The curve decelerates hard: things arrive quickly and settle,
     which is what reads as "fluid" rather than "mechanical". */
  --motion-fast: 140ms;
  --motion-med: 220ms;
  --ease-out: cubic-bezier(.2, .8, .2, 1);

  /* 10px → 6 / 10 / 18, the legacy triple, from a single token. */
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) * 1.8);

  --avt-1-bg: #F3E4D6; --avt-1-fg: #7C4522;
  --avt-2-bg: #DCE3E2; --avt-2-fg: #20343A;
  --avt-3-bg: #E7ECEA; --avt-3-fg: #4C5D5A;
  --avt-4-bg: #EDEAE0; --avt-4-fg: #6B664F;
  --avt-5-bg: #EFF1E8; --avt-5-fg: #5C6152;
}

/* Everything the legacy stylesheet hard-coded, re-derived from the ten tokens.
   Mixing towards --text-main / --card-bg rather than towards black or white is
   what lets a DARK token set produce a coherent dark UI from the same rules. */
@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    --surface-raised: color-mix(in srgb, var(--card-bg) 94%, var(--text-main));
    --surface-2: color-mix(in srgb, var(--page-bg) 92%, var(--text-main));
    --border: color-mix(in srgb, var(--page-bg) 82%, var(--text-main));
    --border-soft: color-mix(in srgb, var(--page-bg) 89%, var(--text-main));
    --text-faint: color-mix(in srgb, var(--text-muted) 74%, var(--page-bg));

    --accent-soft: color-mix(in srgb, var(--brand-primary) 15%, var(--card-bg));
    --accent-contrast: color-mix(in srgb, var(--card-bg) 92%, var(--brand-primary));

    --sidebar-bg-deep: color-mix(in srgb, var(--sidebar-bg) 84%, #000);
    --sidebar-text-muted: color-mix(in srgb, var(--sidebar-text) 60%, var(--sidebar-bg));
    --sidebar-border: color-mix(in srgb, var(--sidebar-bg) 82%, var(--sidebar-text));

    --pri-low-bg: color-mix(in srgb, var(--pri-low) 16%, var(--card-bg));
    --pri-medium-bg: color-mix(in srgb, var(--pri-medium) 18%, var(--card-bg));
    --pri-high-bg: color-mix(in srgb, var(--pri-high) 16%, var(--card-bg));
    --status-done-bg: color-mix(in srgb, var(--status-done) 16%, var(--card-bg));
    --status-watch-bg: color-mix(in srgb, var(--status-watch) 15%, var(--card-bg));

    --tag-building-fg: color-mix(in srgb, var(--tag-building) 45%, var(--text-main));
    --tag-building-bg: color-mix(in srgb, var(--tag-building) 17%, var(--card-bg));
    --tag-building-border: color-mix(in srgb, var(--tag-building) 30%, var(--card-bg));
    --tag-category-fg: color-mix(in srgb, var(--brand-accent) 70%, var(--text-main));
    --tag-category-bg: var(--accent-soft);
    --tag-foreign-fg: color-mix(in srgb, var(--pri-medium) 60%, var(--text-main));
    --kic-ink: color-mix(in srgb, var(--status-watch) 65%, var(--text-main));

    --avt-1-bg: color-mix(in srgb, #94562B 18%, var(--card-bg));
    --avt-1-fg: color-mix(in srgb, #7C4522 80%, var(--text-main));
    --avt-2-bg: color-mix(in srgb, #4A6FA0 16%, var(--card-bg));
    --avt-2-fg: color-mix(in srgb, #20343A 80%, var(--text-main));
    --avt-3-bg: color-mix(in srgb, #5C7874 16%, var(--card-bg));
    --avt-3-fg: color-mix(in srgb, #4C5D5A 80%, var(--text-main));
    --avt-4-bg: color-mix(in srgb, #8C7A3E 16%, var(--card-bg));
    --avt-4-fg: color-mix(in srgb, #6B664F 80%, var(--text-main));
    --avt-5-bg: color-mix(in srgb, #6B5B95 15%, var(--card-bg));
    --avt-5-fg: color-mix(in srgb, #5C6152 80%, var(--text-main));
  }
}

/* ------------------------------------------------------ responsive shell
   SHARED BREAKPOINTS — the main app and the Preventivi add-on (quotes.css)
   use exactly these four and no others. Custom properties cannot drive
   @media, so the literal px values in every media query below MUST match:
     --bp-phone:  640px   (one board column, stacked rows, calendar → week)
     --bp-tablet: 900px   (task editor becomes a full-screen sheet;
                           app.js EDITOR_SHEET_MAX)
     --bp-drawer: 1024px  (sidebar becomes an off-canvas drawer;
                           app.js SIDEBAR_DRAWER_MAX)
     --bp-desk:   1280px  (density tier: `compact` tokens at or above it —
                           the block right below this one)
     --bp-wide:   1920px  (content column capped and centred; --content-max)
   --sidebar-w is the ONE source of truth for the sidebar's width: the <body>
   gradient band and .sidebar both read it. ≥1281px 268px, 1025–1280px 232px,
   ≤1024px 0 (the drawer paints over the page, it takes no column). */
:root { --sidebar-w: 268px; }
@media (max-width: 1280px) { :root { --sidebar-w: 232px; } }
@media (max-width: 1024px) { :root { --sidebar-w: 0px; } }

/* ------------------------------------------------------------ density scale
   (2026-09-14) ONE set of spacing/size knobs, two tiers. The values on bare
   :root are the app's historical look and stay in force on tablets and
   phones (nothing under 900px gets denser: touch targets first). At or above
   --bp-desk 1280px — the office desktops and laptops — the `compact` tier
   tightens gutters, card/cell/control paddings, the stack gap and the two
   font bases, and the content column may grow to --content-max so a 1440 or
   1920 screen gets real columns instead of centred margins. Every rule that
   paints a gutter, a card padding, a table cell, a form gap or a header bar
   reads these tokens; public/density.css applies the same scale to the
   Preventivi add-on (its own stylesheet is owned by the add-on and is not
   edited for this) and to the assist widget.
     --rem-base   the <html> font-size: every `rem` in this file follows it
     --font-base  the <body> (and #quotes-root) inherited text size
     --content-max  the .main cap — min(100%, 1920px), was 1600px
   Custom properties cannot drive @media, so the tier switch is the literal
   1280px below and MUST match the `--bp-desk` note in the breakpoint list. */
:root {
  --density: comfortable;
  --rem-base: 16px;
  --font-base: 15px;
  --content-max: min(100%, 1920px);
  --gutter-x: clamp(14px, 4vw, 42px);
  --gutter-y: clamp(16px, 3vw, 34px);
  --gutter-b: 60px;
  --stack-gap: 22px;
  --sb-pad: 28px 22px 20px;
  --sb-gap: 28px;
  --card-pad-y: 16px;
  --card-pad-x: 18px;
  --tile-pad-y: 12px;
  --tile-pad-x: 13px;
  --row-pad-y: 11px;
  --row-pad-x: 14px;
  --row-gap: 8px;
  --lane-pad: 14px;
  --lane-gap: 18px;
  --cell-pad-y: 8px;
  --cell-pad-x: 10px;
  --form-gap: 18px;
  --field-gap: 12px;
  --control-pad-y: 9px;
  --control-pad-x: 11px;
  --control-h: 38px;
  --btn-pad-y: 7px;
  --btn-pad-x: 13px;
  --head-pad-y: 18px;
  --head-pad-x: 20px;
  --foot-pad-y: 14px;
  --win-margin: 16px;
}
@media (min-width: 1280px) {
  :root {
    --density: compact;
    --rem-base: 15px;
    --font-base: 14px;
    --gutter-x: clamp(16px, 2vw, 28px);
    --gutter-y: clamp(12px, 1.5vw, 20px);
    --gutter-b: 40px;
    --stack-gap: 16px;
    --sb-pad: 20px 18px 16px;
    --sb-gap: 20px;
    --card-pad-y: 12px;
    --card-pad-x: 14px;
    --tile-pad-y: 10px;
    --tile-pad-x: 12px;
    --row-pad-y: 8px;
    --row-pad-x: 12px;
    --row-gap: 6px;
    --lane-pad: 10px;
    --lane-gap: 14px;
    --cell-pad-y: 6px;
    --cell-pad-x: 10px;
    --form-gap: 14px;
    --field-gap: 10px;
    --control-pad-y: 7px;
    --control-pad-x: 10px;
    --control-h: 34px;
    --btn-pad-y: 6px;
    --btn-pad-x: 12px;
    --head-pad-y: 12px;
    --head-pad-x: 16px;
    --foot-pad-y: 10px;
  }
}

* { box-sizing: border-box; }
html, body { min-height: 100%; }
/* --rem-base is the one knob every `rem` in this file hangs off; 16px is the
   browser default, so nothing moves in the comfortable tier. */
html { font-size: var(--rem-base); }
body {
  margin: 0;
  /* ARCHITECTURE §5: the sidebar's dark band is painted on <body> as a hard
     gradient stop so it always reaches the bottom of the page, even when the
     flex stretch fails. With --sidebar-w at 0 (drawer mode) the gradient is
     plain var(--bg). */
  background: linear-gradient(to right, var(--sidebar-bg) var(--sidebar-w), var(--bg) var(--sidebar-w));
  color: var(--text);
  font-family: var(--font-family);
  font-size: var(--font-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  display: flex;
  min-height: 100vh;
}
h1, h2, h3, p { margin: 0; }
h1, h2, h3 { text-wrap: balance; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; }
::selection { background: var(--accent-soft); color: var(--accent-hover); }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.mono { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- sidebar */
#app { display: flex; width: 100%; min-height: 100vh; }
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  display: flex;
  flex-direction: column;
  padding: var(--sb-pad);
  gap: var(--sb-gap);
  border-right: 1px solid var(--sidebar-border);
  overflow-y: auto;
}
.brand { display: flex; flex-direction: column; gap: 10px; }
.brand .mark-img {
  width: 178px;
  max-width: 100%;
  height: auto;
  display: block;
}
.brand .wordmark {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--sidebar-text);
}
.brand .sub {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--sidebar-text-muted);
  font-weight: 600;
}

.sb-block { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.sb-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--sidebar-text-muted);
  font-weight: 600;
}
.sb-user-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.sb-user-name { font-size: 0.85rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-user-mail { font-size: 0.7rem; color: var(--sidebar-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.avatar {
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.72rem; font-weight: 700; flex-shrink: 0;
}
.avatar.sm { width: 22px; height: 22px; font-size: 0.6rem; }
.avt-1 { background: var(--avt-1-bg); color: var(--avt-1-fg); }
.avt-2 { background: var(--avt-2-bg); color: var(--avt-2-fg); }
.avt-3 { background: var(--avt-3-bg); color: var(--avt-3-fg); }
.avt-4 { background: var(--avt-4-bg); color: var(--avt-4-fg); }
.avt-5 { background: var(--avt-5-bg); color: var(--avt-5-fg); }

/* Written for the legacy "pick a name from a dropdown" user switcher, left
   unused when that became a real login — now the sidebar's "board di un
   collega" picker (app.js renderColleagueBlock, spec §3). Sidebar-toned, full
   width, and text-overflow:ellipsis because a long display name must not push
   the sidebar wider. */
select.user-select {
  flex: 1; min-width: 0; width: 100%; max-width: 100%;
  background: var(--sidebar-bg-deep); color: var(--sidebar-text);
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
  padding: 7px 8px; font-size: 0.85rem; font-weight: 600;
  text-overflow: ellipsis;
}
.sb-new-user { display: none; gap: 6px; min-width: 0; }
.sb-new-user.open { display: flex; }
.sb-new-user input {
  flex: 1; min-width: 0; background: var(--sidebar-bg-deep); color: var(--sidebar-text);
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
  padding: 7px 8px; font-size: 0.83rem;
}
.sb-new-user input::placeholder { color: var(--sidebar-text-muted); }

.nav-btn {
  display: flex; align-items: center; justify-content: space-between;
  background: none; border: none; color: var(--sidebar-text);
  padding: 8px 10px; border-radius: var(--radius-sm); font-size: 0.88rem; font-weight: 500;
  text-align: left; width: 100%;
}
.nav-btn:hover { background: rgba(255, 255, 255, 0.06); }
.nav-btn.active { background: var(--accent); color: var(--accent-contrast); font-weight: 700; }
.nav-btn .count { font-size: 0.76rem; opacity: 0.85; }
.nav-list { display: flex; flex-direction: column; gap: 3px; }
/* 0.66.0 — the "Amministrazione" group (app.js sidebarAdminGroup); 0.67.0 —
   the one collapsible group every titled sidebar area uses (sidebarGroup):
   the heading is a button with a chevron that turns when closed, the items
   hide with [hidden]. In the rail the admin heading has no room and becomes a
   thin rule above the glyphs (a collapsed state does not hide rail glyphs). */
.nav-group { display: flex; flex-direction: column; gap: 3px; margin-top: 10px; min-width: 0; }
.nav-group.sb-block { margin-top: 0; gap: 10px; }
.nav-group-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 10px 2px 0; }
.nav-group.sb-block .nav-group-head { padding: 0; }
.nav-group-toggle {
  display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0;
  background: none; border: none; padding: 2px 10px 2px 0; margin: 0; cursor: pointer;
  text-align: left; font-family: inherit;
}
.nav-group-toggle:hover { color: var(--sidebar-text); }
.nav-group-chevron { display: inline-block; font-size: 0.8rem; line-height: 1; }
.nav-group-toggle[aria-expanded="false"] .nav-group-chevron { transform: rotate(-90deg); }
@media (prefers-reduced-motion: no-preference) {
  .nav-group-chevron { transition: transform 0.15s ease; }
}
.nav-group-items { display: flex; flex-direction: column; gap: 3px; padding-left: 10px; min-width: 0; }
.nav-group-items--flush { padding-left: 0; gap: 10px; }
.nav-group-items[hidden] { display: none; }
.nav-group--rail { align-items: center; margin-top: 4px; }
.nav-group-sep { width: 24px; height: 0; border-top: 1px solid var(--sidebar-border); margin: 2px 0 4px; }

.chip-list { display: flex; flex-direction: column; gap: 3px; }
.cat-chip {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  background: none; border: none; color: var(--sidebar-text-muted);
  padding: 7px 10px; border-radius: var(--radius-sm); font-size: 0.83rem;
  text-align: left; width: 100%;
}
.cat-chip:hover { background: rgba(255, 255, 255, 0.06); color: var(--sidebar-text); }
.cat-chip.active { background: rgba(255, 255, 255, 0.1); color: var(--sidebar-text); font-weight: 600; }
.cat-chip .cat-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-chip-row { display: flex; align-items: center; gap: 2px; }
.cat-chip-row .cat-chip { flex: 1; min-width: 0; }
.cat-delete-btn {
  flex-shrink: 0; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--radius-sm); color: var(--sidebar-text-muted);
  font-size: 0.95rem; line-height: 1; opacity: 0.4; transition: opacity 0.12s;
}
.cat-chip-row:hover .cat-delete-btn, .cat-delete-btn:focus-visible { opacity: 1; }
.cat-delete-btn:hover { background: rgba(197, 90, 60, 0.18); color: var(--pri-high); }
.cat-delete-btn.confirm { opacity: 1; background: var(--pri-high); color: #fff; font-weight: 700; }
.cat-delete-btn.confirm:hover { background: var(--pri-high); color: #fff; }
.cat-add-toggle {
  background: none; border: 1px dashed var(--sidebar-border); color: var(--sidebar-text-muted);
  padding: 6px 10px; border-radius: var(--radius-sm); font-size: 0.8rem; text-align: left;
}
.cat-add-toggle:hover { color: var(--sidebar-text); border-color: var(--sidebar-text-muted); }
.cat-add-form { display: none; gap: 6px; min-width: 0; }
.cat-add-form.open { display: flex; }
.cat-add-form input {
  flex: 1; min-width: 0; background: var(--sidebar-bg-deep); color: var(--sidebar-text);
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
  padding: 6px 8px; font-size: 0.82rem;
}
.cat-add-form input::placeholder { color: var(--sidebar-text-muted); }
.project-add-form.open { flex-direction: column; align-items: stretch; }
.color-swatch-row { display: flex; gap: 7px; flex-wrap: wrap; padding: 2px 0; }
.color-swatch {
  width: 22px; height: 22px; border-radius: 50%; padding: 0; flex-shrink: 0;
  border: none; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
}
.color-swatch.selected { box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.95), 0 0 0 4px rgba(0, 0, 0, 0.6); }
.project-visibility-wrap { display: flex; flex-direction: column; gap: 6px; }
.project-visibility-select {
  border: 1px solid rgba(127, 127, 127, 0.4); border-radius: var(--radius-sm);
  background: rgba(127, 127, 127, 0.08); color: inherit; padding: 6px 8px; font-size: 0.82rem;
}
.project-share-checklist {
  display: flex; flex-direction: column; gap: 4px; max-height: 130px; overflow-y: auto;
  padding: 6px 8px; border: 1px solid rgba(127, 127, 127, 0.4); border-radius: var(--radius-sm);
  background: rgba(127, 127, 127, 0.05);
}
.project-share-checklist label { display: flex; align-items: center; gap: 7px; font-size: 0.8rem; cursor: pointer; }
.project-share-checklist-hidden { display: none; }
.picker-list { display: flex; flex-direction: column; gap: 6px; }
.picker-item {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--surface-2); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 9px 12px; font-size: 0.85rem; text-align: left; color: var(--text);
}
.picker-item:hover { border-color: var(--accent); background: var(--accent-soft); }

.sb-label-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.select-mode-toggle {
  background: none; border: none; color: var(--sidebar-text-muted); font-size: 0.74rem;
  font-weight: 600; text-decoration: underline; text-underline-offset: 2px; padding: 2px 0;
}
.select-mode-toggle:hover { color: var(--sidebar-text); }
/* Desideri §D-bis 6 — the board's own "Archiviati" toggle, which is a STATE
   and not just a link: while it is on, the board is showing the archive. */
.select-mode-toggle.active { color: var(--accent-hover); font-weight: 700; }
.select-checkbox-wrap { display: flex; align-items: center; flex-shrink: 0; padding: 0 2px; }
.select-checkbox-wrap input { width: 15px; height: 15px; cursor: pointer; }
.complete-btn:hover { background: rgba(79, 115, 80, 0.2); color: var(--status-done); }
.bulk-bar {
  display: flex; align-items: center; gap: 10px; background: var(--accent-soft);
  border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 8px 12px;
  margin: 0 0 14px 0;
}
.bulk-count { font-size: 0.82rem; font-weight: 600; color: var(--text); }
.sidebar .bulk-bar { margin: 0; }
.sidebar .bulk-count { color: var(--text); }
.bulk-bar-wrap { margin: 0 0 14px 0; }
.bulk-bar-wrap .bulk-bar { margin: 0 0 8px 0; }
.bulk-assign-panel {
  background: var(--surface); border: 1px solid var(--accent); border-radius: var(--radius-sm);
  padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;
}
.bulk-assign-actions { display: flex; gap: 8px; }
.selected-row { outline: 2px solid var(--accent); outline-offset: -2px; }
.archive-tabs { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.archive-tab-btn {
  background: var(--surface-2); border: 1px solid var(--border-soft); color: var(--text-faint);
  padding: 7px 14px; border-radius: 999px; font-size: 0.82rem; font-weight: 600;
}
.archive-tab-btn:hover { color: var(--text); }
.archive-tab-btn.active { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.project-done-row .card-title { flex: 1; }
.project-done-row .project-dot { flex-shrink: 0; }

.btn {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  padding: var(--btn-pad-y) var(--btn-pad-x); border-radius: var(--radius-sm); font-size: 0.85rem; font-weight: 600;
}
/* 0.86.0: + a light tint, so a hover is seen and not only a border nudge. Kept
   HERE, before the variants, on purpose: .btn.primary/.ghost/.danger come
   later at equal or higher specificity and keep their own backgrounds. */
.btn:hover { border-color: var(--text-faint); background: var(--surface-raised); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.ghost { background: none; border-color: transparent; }
.btn.ghost:hover { background: var(--surface-2); }
.btn.danger { color: var(--pri-high); border-color: var(--pri-high-bg); }
.btn.danger:hover { background: var(--pri-high-bg); }
.btn.danger.armed { background: var(--pri-high); border-color: var(--pri-high); color: #fff; }
.btn.small { padding: 5px 9px; font-size: 0.78rem; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.sidebar .btn.ghost { color: var(--sidebar-text-muted); }
.sidebar .btn.ghost:hover { background: rgba(255, 255, 255, 0.06); color: var(--sidebar-text); }
.sidebar .btn.danger { background: none; border-color: transparent; color: var(--pri-high); }
.sidebar .btn.danger:hover { background: rgba(197, 90, 60, 0.16); }

.sidebar-footer { margin-top: auto; display: flex; flex-direction: column; gap: 8px; }
.sidebar-footer .foot-note { font-size: 0.72rem; color: var(--sidebar-text-muted); }
.mail-toggle {
  display: flex; align-items: center; gap: 8px; font-size: 0.74rem;
  color: var(--sidebar-text-muted); cursor: pointer;
}
.mail-toggle input { flex-shrink: 0; }
.mail-toggle:hover { color: var(--sidebar-text); }

/* ------------------------------------------------------------------- main */
/* Gutters and the stack gap come from the density tokens; the cap is
   --content-max (min(100%, 1920px) — the old 1600px left a 1920 screen with
   two empty bands, and a 1440 one lost 84px to gutters alone). Text that IS
   line-length sensitive (.view-note, .m365-guide, the admin helper lines) is
   capped on its own in ch units instead. NOT a container-query root on
   purpose: .news-popover / .news-backdrop are position:fixed INSIDE the
   header, and an engine that still ties layout containment to
   container-type would re-root them here. The board gets its own
   .board-wrap container below. */
.main {
  flex: 1; min-width: 0; width: 100%; max-width: var(--content-max); margin-inline: auto;
  padding: var(--gutter-y) var(--gutter-x) var(--gutter-b);
  display: flex; flex-direction: column; gap: var(--stack-gap);
}
.readonly-banner {
  background: var(--pri-high-bg); color: var(--pri-high); border: 1px solid var(--pri-high-bg);
  border-radius: var(--radius-sm); padding: 10px 14px; font-size: 0.85rem; font-weight: 600;
}
.main-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px 20px; flex-wrap: wrap; }
.main-title { flex: 1 1 240px; min-width: 0; }
.main-header h1 { font-size: 1.5rem; font-weight: 700; min-width: 0; overflow-wrap: anywhere; }
/* The ☰ that opens the sidebar drawer (app.js menuButton, first child of
   every .main-header). display:none above the drawer breakpoint — see the
   responsive block at the end of this file. */
.menu-btn {
  display: none; flex-shrink: 0; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; font-size: 1.15rem; line-height: 1;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text);
}
.menu-btn:hover { border-color: var(--text-faint); }
/* Spec §3 — a colleague's board has to be unmistakable: their own avatar next
   to their name in the h1 (the way a project view gets its coloured dot), and
   one line saying plainly that these tasks are not the viewer's. Nothing is
   disabled — any logged-in user can already edit any task in this app. */
.title-avatar { display: inline-flex; vertical-align: middle; margin-right: 9px; }
.view-note { font-size: 0.8rem; color: var(--text-muted); margin-top: 5px; max-width: 62ch; }
/* 0.73.0 — the area a view belongs to, above its h1 (app.js viewKicker):
   "Amministrazione" over "Utenti", "Registro email", … Small caps, muted, so
   the h1 stays the thing the eye lands on. */
.view-kicker {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted); line-height: 1.2; margin-bottom: 2px;
}
.main-header .stats { display: flex; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
/* 0.78.1 (contrast): the chips used --text-muted on --surface-2, both derived
   from the branding — on the live palette the label came out a few shades off
   the page and read as disabled. They now carry the MAIN text colour on a card
   surface with a real border, so they read as content, not as background. */
.stat-chip {
  font-size: 0.78rem; font-weight: 700; color: var(--text-main);
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 100px; padding: 4px 11px;
  display: flex; align-items: center; gap: 5px;
}
.stat-dot { width: 8px; height: 8px; border-radius: 50%; }
.header-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; max-width: 100%; }
/* Edifici §G — the building filter beside the search box. It reuses the
   sidebar's .user-select rule (the colleague picker), but that rule forces
   width:100%/flex:1 and an ellipsis meant for a fixed-width sidebar column —
   here the select sits in a wrapping flex row, so it should size to its
   option text instead of being squeezed and truncated ("Tutti i miei
   edifici" was clipped). Size to content, capped so a long building name
   still can't push the bell off a narrow header. */
.building-select {
  width: auto; min-width: 0; max-width: min(100%, 320px); flex: 0 1 auto;
  text-overflow: clip;
}
.header-actions > * { max-width: 100%; }
.search-box {
  display: flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 100px; padding: 7px 12px; min-width: 0; width: min(220px, 100%); flex: 1 1 160px;
}
.search-icon { color: var(--text-faint); flex-shrink: 0; }
.search-input { border: none; background: none; outline: none; font-size: 0.85rem; width: 100%; min-width: 0; color: var(--text); }
.search-input::placeholder { color: var(--text-faint); }
.search-clear {
  flex-shrink: 0; background: none; border: none; color: var(--text-faint); font-size: 1rem;
  line-height: 1; padding: 0 0 0 2px; border-radius: 50%;
}
.search-clear:hover { color: var(--text); }
.saving-pill {
  font-size: 0.76rem; color: var(--text-muted); display: none; align-items: center; gap: 6px;
}
.saving-pill.visible { display: flex; }
.spin {
  width: 11px; height: 11px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.filter-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.filter-chip {
  background: var(--surface); border: 1px solid var(--border); color: var(--text-muted);
  padding: 5px 12px; border-radius: 100px; font-size: 0.8rem; font-weight: 600;
}
.filter-chip.active { background: var(--text); color: var(--surface); border-color: var(--text); }

/* The board's column count follows the CONTENT width (the sidebar column
   and the fluid gutters vary), not the viewport: .board-wrap is the query
   container (renderMain wraps renderBoard in it). The two viewport media
   queries survive only for an engine without container queries, fenced off
   with @supports so the two never fight. */
.board-wrap { container-type: inline-size; min-width: 0; }
/* THREE lanes since 0.37.0 — "Keep in check" left the board and became a flag
   (see .kic below). The two-column switch guards the same MINIMUM lane width
   as before (~260px, the point where a card stops being readable): four lanes
   hit it at 1100px of content, three lanes hit it at ~840px — so the
   breakpoint moves with the lane count while the lane width it protects does
   not. The one-column switch is unchanged. */
.board { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--lane-gap); align-items: start; min-width: 0; }
@container (max-width: 840px) { .board { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (max-width: 700px) { .board { grid-template-columns: 1fr; } }
@supports not (container-type: inline-size) {
  @media (max-width: 920px) { .board { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (max-width: 640px) { .board { grid-template-columns: 1fr; } }
}
.col { background: var(--surface-2); border-radius: var(--radius-lg); padding: var(--lane-pad); display: flex; flex-direction: column; gap: 10px; min-height: 120px; min-width: 0; }
.col-head { display: flex; align-items: center; justify-content: space-between; padding: 2px 4px 2px 6px; }
/* 0.78.1 (contrast): lane titles and counts on the main text colour — they were
   --text-muted / --text-faint, i.e. barely darker than the lane background. */
.col-head .title { font-size: 0.82rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-main); display: flex; align-items: center; gap: 7px; }
.col-head .count { font-size: 0.78rem; font-weight: 700; color: var(--text-main); }
.col-cards { display: flex; flex-direction: column; gap: calc(var(--row-gap) + 1px); }
.col-empty {
  color: var(--text-faint); font-size: 0.82rem; padding: 14px 6px; text-align: center;
  border: 1px dashed var(--border); border-radius: var(--radius-md);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
/* 0.78.2 (contrast, second pass): "+ Aggiungi" sits on the LANE, whose colour
   follows the branding's page colour — --text-faint and --border are derived
   from that same colour, so a darker page made the button vanish. Main text
   colour, and a dashed border mixed from the text colour, read on any lane. */
.col-add-btn {
  background: none; border: 1px dashed color-mix(in srgb, var(--text-main) 45%, transparent); color: var(--text-main);
  border-radius: var(--radius-md); padding: 9px 10px; font-size: 0.82rem; font-weight: 600; text-align: center; width: 100%;
}
.col-add-btn:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* ------------------------------------------ Keep in check (0.37.0)
   The strip between the stat chips and the board. It is NOT a lane: every card
   in it is also drawn in its own column below. Collapsed it is one row — a
   dot, the label, the count, a chevron and the 5/10/20/Tutti select; open it
   grows a list of ordinary .card nodes, so nothing in here needs card CSS of
   its own. The watch tokens throughout, which is where the flag's colour comes
   from and the only thing it still shares with the OSSERVATORI feature. */
.kic { margin: 10px 0 16px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface-2); }
.kic-head {
  display: flex; align-items: center; gap: 9px; padding: 9px 13px; cursor: pointer;
  font-size: 0.82rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  /* 0.78.4: --kic-ink (the watch blue pulled towards the main text colour) —
     plain --status-watch was 4.0:1 on the strip and 2.8:1 on a dark palette. */
  color: var(--kic-ink); border-radius: var(--radius-lg);
}
.kic-head:hover { background: var(--status-watch-bg); }
.kic-head:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.kic-head .stat-dot { flex-shrink: 0; }
.kic-title { white-space: nowrap; }
.kic-count { color: var(--text-main); font-weight: 700; }
/* Pushes the select to the far right, so the label group and the control never
   drift apart as the count changes width. */
.kic-chevron { margin-right: auto; color: var(--text-main); font-size: 0.75rem; }
.kic-limit {
  flex-shrink: 0; font-size: 0.76rem; font-weight: 600; text-transform: none; letter-spacing: normal;
  padding: 4px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface); color: var(--text-muted);
}
.kic-body { display: flex; flex-direction: column; gap: 9px; padding: 2px 13px 13px; }
/* One column, not the board's grid: the strip is a LIST — it is read top to
   bottom in due-date order, and a grid would scramble that on every resize. */
.kic-cards { display: flex; flex-direction: column; gap: 9px; }
.kic-empty { color: var(--text-faint); font-size: 0.82rem; padding: 10px 2px; }
.kic-more {
  background: none; border: none; color: var(--accent); font-size: 0.8rem; font-weight: 700;
  text-align: left; padding: 2px 0; align-self: flex-start;
}
.kic-more:hover { text-decoration: underline; }
.kic-add { background: var(--surface); }
@media (max-width: 640px) {
  .kic-head { flex-wrap: wrap; gap: 7px; }
  .kic-chevron { margin-right: 0; }
  .kic-limit { margin-left: auto; }
}

.card {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: var(--tile-pad-y) var(--tile-pad-x); display: flex; flex-direction: column; gap: 8px; box-shadow: var(--shadow-sm);
  text-align: left; width: 100%; cursor: pointer; min-width: 0; overflow-wrap: anywhere;
}
.card:hover { border-color: var(--text-faint); box-shadow: var(--shadow-md); }
.card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* draggable="true"/"false" reflects ui.selectMode (app.js renderCard) — only
   show the grab affordance when a drag can actually start. */
.card[draggable="true"] { cursor: grab; }
.card.dragging { opacity: 0.55; transform: rotate(-1.5deg); cursor: grabbing; }
/* Column drop target: brand-derived tint/outline, already token-based via
   --accent-soft / --accent (color-mix off --brand-primary, with the literal
   fallback declared alongside the other legacy variables above). */
.col.drop-target { background: var(--accent-soft); outline: 2px dashed var(--accent); outline-offset: -2px; }
/* Spec §2 — the insertion indicator for a drag WITHIN a column, matching the
   --accent of .col.drop-target's outline above so the two read as one gesture.
   Drawn as a pseudo-element on the card next to the drop point, sitting in the
   9px .col-cards gap: it is load-bearing that this costs NO layout, because a
   real node spliced into the flow would move the very card midpoints
   computeDropIndex() measures, and dragover fires continuously — the line
   would oscillate under the pointer. -6px centres the 3px bar in that gap;
   pointer-events:none keeps it from ever becoming a drop target of its own. */
.card.drop-before, .card.drop-after { position: relative; }
.card.drop-before::before,
.card.drop-after::after {
  content: ''; position: absolute; left: 0; right: 0; height: 3px;
  background: var(--accent); border-radius: 3px; pointer-events: none;
}
.card.drop-before::before { top: -6px; }
.card.drop-after::after { bottom: -6px; }
.card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.card-top-left { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* 0.78.1 (contrast): the category tag ("Sales", "Events") was grey-on-grey,
   the same --surface-2 as the lane behind the card. It now has a colour of its
   own — the brand copper, in the accent tokens, so a rebrand follows.
   0.78.4: --tag-category-fg/-bg — the copper pulled towards --text-main, so a
   pale brand accent (or a dark palette) cannot fade it again. */
.cat-tag { font-size: 0.7rem; font-weight: 700; color: var(--tag-category-fg); background: var(--tag-category-bg); border-radius: 100px; padding: 2px 8px; }
/* 0.78.4 (contrast): app.js passes the project's palette colour as
   --project-hue (and --project-tint, its 18% rgba, for the fallback). The raw
   hue as TEXT was 2.6–4.6:1 on its own tint (pink and sand the worst) and
   ~2.5:1 on a dark palette; mixed 55% towards --text-main it is ≥ 5:1 on both. */
.project-tag {
  font-size: 0.7rem; font-weight: 700; border-radius: 100px; padding: 2px 8px;
  color: var(--project-hue); background: var(--project-tint);
}
@supports (color: color-mix(in srgb, red 50%, blue)) {
  .project-tag {
    color: color-mix(in srgb, var(--project-hue) 55%, var(--text-main));
    background: color-mix(in srgb, var(--project-hue) 18%, var(--card-bg));
  }
}
.project-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
.title-dot { width: 12px; height: 12px; margin-right: 9px; vertical-align: middle; }
.watch-tag { font-size: 0.7rem; font-weight: 700; color: var(--status-watch); background: var(--status-watch-bg); border-radius: 100px; padding: 2px 8px; }
/* Spec §1 — the private-task lock chip. Same pill geometry, size and weight as
   the .cat-tag / .watch-tag chips it sits beside in .card-top-left; inline-flex
   only because it carries an icon as well as a word. The amber priority tokens
   rather than a new colour, so an admin's branding still drives it, and `color`
   is what the inline SVG's stroke="currentColor" picks up. */
.private-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.7rem; font-weight: 700; color: var(--pri-medium);
  background: var(--pri-medium-bg); border-radius: 100px; padding: 2px 8px;
}
.private-tag .lock-icon { flex-shrink: 0; }
/* Desideri (WISHES_PLAN.md §D-bis 8) — the wish chip, .private-tag's twin: same
   pill geometry, size and weight, the accent tokens instead of the amber ones
   so the two are never mistaken for each other, and no icon (the word is the
   whole signal). Branding-driven like everything else here. */
.wish-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.7rem; font-weight: 700; color: var(--accent-hover);
  background: var(--accent-soft); border-radius: 100px; padding: 2px 8px;
}
/* Edifici (BUILDINGS_PLAN.md §G) — the building chip, the third twin of
   .private-tag / .wish-tag: same pill geometry, size and weight, and the
   NEUTRAL surface tokens, because it is a fact about the row rather than a
   warning about it. Drawn only for a viewer with more than one building.
   `.foreign` is the §E creator override — I see this task because I made it,
   but it lives in a building I am not part of, so it is read-only: the amber
   priority tokens, the same ones .private-tag uses to mean "special rules". */
/* 0.78.1 (contrast): the building tag was --text-muted on --surface-2 with a
   faint border — it almost vanished on the card. It now has its own colour
   pair, a deep slate on a pale blue-grey, distinct from the category tag
   (copper) and the priority tags.
   0.78.4: the same slate-teal, now as the --tag-building-* tokens: a fixed hue
   (like the priority hues) whose fill is mixed with --card-bg and whose text
   is mixed with --text-main — not with the page colour, which is what made
   the old surface tokens pale — so it holds ≥ 4.5:1 on a dark palette too.
   `.foreign` keeps the amber fill but darker ink (#8C7A3E was 3.4:1) and a
   dashed amber outline: never mistaken for a normal building tag (teal, solid
   outline) nor for the MEDIA priority pill (uppercase, no outline). */
.building-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.7rem; font-weight: 700; color: var(--tag-building-fg);
  background: var(--tag-building-bg); border-radius: 100px; padding: 2px 8px;
  border: 1px solid var(--tag-building-border);
}
.building-tag.foreign { color: var(--tag-foreign-fg); background: var(--pri-medium-bg); border: 1px dashed var(--pri-medium); }
/* Keep in check (0.37.0) — the flag's card chip, the fourth twin of
   .private-tag / .wish-tag / .building-tag: same pill geometry, size and
   weight, the watch tokens the flag inherited from the status it replaced. It
   is the ONLY thing that still ties the two "watch" words together, and they
   are otherwise unrelated: .watch-tag above means "Osservato". */
.kic-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.7rem; font-weight: 700; color: var(--status-watch);
  background: var(--status-watch-bg); border-radius: 100px; padding: 2px 8px;
}
/* The one line that says why a card cannot be touched. Deliberately quiet:
   the chip above it already names the building. */
.card-readonly-note { font-size: 0.72rem; font-weight: 600; color: var(--text-faint); }
/* The editor's "Edificio" row when the viewer cannot move the task: a value,
   not a control — a select that always refuses is worse than a sentence. */
.readonly-value { font-size: 0.85rem; font-weight: 600; color: var(--text-main); padding: 2px 0; }
/* An assignee who is still ticked but no longer belongs to the task's building
   (the editor keeps them listed so they can be UNticked — see assignableUsers). */
.assignee-check-row.outside-building span { color: var(--pri-medium); }
.due-urgency-tag { font-size: 0.7rem; font-weight: 700; border-radius: 100px; padding: 2px 8px; }
.due-urgency-tag.soon { color: var(--pri-medium); background: var(--pri-medium-bg); }
.due-urgency-tag.overdue { color: var(--pri-high); background: var(--pri-high-bg); }
.pri-tag { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; border-radius: 100px; padding: 2px 8px; }
.pri-tag.low { background: var(--pri-low-bg); color: var(--pri-low); }
.pri-tag.medium { background: var(--pri-medium-bg); color: var(--pri-medium); }
.pri-tag.high { background: var(--pri-high-bg); color: var(--pri-high); }
/* KT-117: the board card's pill is a <button> when the viewer may change the
   priority. `font`/`color`/`cursor` already come from the global `button`
   reset above; `.pri-tag.low/.medium/.high` still wins the background (same
   two classes, just with `pri-tag-btn` added alongside `pri-tag`) — only the
   UA border and background BOX need clearing here so it still reads as the
   plain pill. The focus ring is the global `:focus-visible` rule above; no
   separate one needed. */
.pri-tag-btn { border: none; background: none; appearance: none; -webkit-appearance: none; }
.card-title { font-size: 0.92rem; font-weight: 600; line-height: 1.35; }
.card-title.done { text-decoration: line-through; color: var(--text-muted); }
.card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.card-meta-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* 0.78.2 (contrast, second pass): everything secondary INSIDE a card — the
   assignee name, the comment count, the due date, "n/m sottotask", the
   inactive status buttons — used --text-muted / --text-faint, which are
   derived from the branding's page colour and fade with it. --card-text-soft
   is mixed from the MAIN text colour and the card's own white instead, so it
   stays a readable dark grey whatever page colour is picked. */
.card { --card-text-soft: color-mix(in srgb, var(--text-main) 80%, var(--card-bg)); }
.card-assignee { display: flex; align-items: center; gap: 6px; font-size: 0.78rem; color: var(--card-text-soft); }
.comment-badge { display: flex; align-items: center; gap: 3px; font-size: 0.74rem; color: var(--card-text-soft); flex-shrink: 0; }
.card-due { font-size: 0.75rem; color: var(--card-text-soft); }
.card-due.overdue { color: var(--pri-high); font-weight: 700; }
.subtask-bar { height: 4px; background: var(--border-soft); border-radius: 4px; overflow: hidden; }
.subtask-bar .fill { height: 100%; background: var(--status-done); }
.subtask-count { font-size: 0.72rem; color: var(--card-text-soft, var(--text-muted)); }
.status-row { display: flex; gap: 5px; margin-top: 2px; flex-wrap: wrap; }
.status-btn {
  flex: 1 1 60px; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  /* 0.78.2: an INACTIVE choice is white with dark-grey text and a visible
     border (was --surface-2 fill + --text-faint, both page-derived, which
     melted into each other on a darker palette). The current one stays the
     solid dark block. */
  padding: 5px 4px; border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--text-main) 28%, var(--card-bg));
  background: var(--card-bg);
  color: var(--card-text-soft, var(--text-muted));
}
.status-btn:hover:not(.current):not([disabled]) { color: var(--text-main); border-color: var(--text-main); }
.status-btn.current { background: var(--text); color: var(--surface); border-color: var(--text); }
.status-btn.current.is-done { background: var(--status-done); border-color: var(--status-done); }
/* Desideri: on a wish, a non-admin's quick status buttons still SHOW the state
   (the current one stays highlighted) but cannot change it — the drag is off
   for the same reason, and the editor says who owns the field. */
.status-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
/* Keep in check (0.37.0) — the fourth button in .status-row, and deliberately
   NOT a .status-btn: the three on its left are mutually exclusive, this one
   toggles on top of whichever is current. The left border is the whole visual
   argument — it separates a toggle from a segmented control — and the watch
   tokens say which flag it is. */
.kic-btn {
  flex: 0 0 auto; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  /* 0.78.2: same treatment as an inactive .status-btn. */
  padding: 5px 9px; border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--text-main) 28%, var(--card-bg));
  background: var(--card-bg); color: var(--card-text-soft, var(--text-muted));
  margin-left: 5px; border-left: 3px solid color-mix(in srgb, var(--text-main) 45%, var(--card-bg));
}
.kic-btn:hover:not(.active):not([disabled]) { color: var(--status-watch); border-color: var(--status-watch); }
.kic-btn.active { background: var(--status-watch); border-color: var(--status-watch); color: var(--surface); }
.kic-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.card-watch-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
/* M9 §3.7 — the in-card "Archivia" action on a done, not-yet-archived card. */
.card-archive-row { display: flex; justify-content: flex-end; margin-top: 2px; }

/* ------------------------------------- spec A: the unrolled card preview
   A read-only block appended INSIDE the card, so the card grows downward and
   pushes the rest of the column down — never an overlay. cursor:default undoes
   the card's own `pointer`/`grab`: nothing in here toggles or drags. */
.card.expanded { border-color: var(--accent); box-shadow: var(--shadow-md); }
/* 0.86.0: two boxes (app.js renderCardPreview). The outer one is a one-row
   grid — the track the "Motion" section animates 0fr <-> 1fr — and the inner
   one carries the look and clips, so a 0fr track really is zero tall. */
.card-preview { display: grid; grid-template-rows: 1fr; cursor: default; }
.card-preview-body {
  /* min-height:0 lets the track shrink below the content; the clipping
     (overflow:hidden) is switched on only WHILE it animates (Motion section),
     so at rest a focus ring on an edge button is never cut off. */
  min-height: 0;
  margin-top: 4px; padding-top: 10px; border-top: 1px dashed var(--border);
  display: flex; flex-direction: column; gap: 9px;
}
.preview-head { display: flex; flex-direction: column; gap: 2px; }
.preview-ref { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em; color: var(--text-faint); }
.preview-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* A due date that is neither overdue nor close still needs the chip shape the
   urgent ones wear, just without the alarm colours. */
.due-urgency-tag.neutral { color: var(--text-muted); background: var(--surface-2); }
.preview-row { display: flex; gap: 8px; align-items: baseline; }
.preview-block { display: flex; flex-direction: column; gap: 5px; }
.preview-label {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-faint); flex: 0 0 88px;
}
.preview-value { flex: 1; min-width: 0; font-size: 0.78rem; color: var(--text-muted); word-break: break-word; }
.preview-subtasks { display: flex; flex-direction: column; gap: 3px; }
.preview-subtask { display: flex; align-items: baseline; gap: 7px; font-size: 0.78rem; color: var(--text-muted); }
.preview-subtask .box {
  width: 12px; height: 12px; flex-shrink: 0; display: inline-block; align-self: center;
  border: 1px solid var(--border); border-radius: 3px; background: var(--surface);
}
.preview-subtask.done .box { background: var(--status-done); border-color: var(--status-done); }
.preview-subtask.done .label { text-decoration: line-through; color: var(--text-faint); }
/* 0.91.0 — the row is a button when the viewer may tick it from the card. */
.preview-subtask-toggle {
  width: calc(100% + 8px); margin: 0 0 0 -4px; padding: 2px 4px;
  border: 0; border-radius: var(--radius-sm); background: transparent;
  font: inherit; font-size: 0.78rem; color: var(--text-muted); text-align: left; cursor: pointer;
}
.preview-subtask-toggle:hover { background: var(--surface-2); color: var(--text); }
.preview-subtask-toggle:hover .box { border-color: var(--status-done); }
.preview-subtask-toggle:focus-visible { outline: 2px solid var(--status-done); outline-offset: 1px; }
.preview-comments { display: flex; flex-direction: column; gap: 6px; }
.preview-comment { background: var(--surface-2); border-radius: var(--radius-sm); padding: 6px 8px; }
.preview-comment-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 0.68rem; color: var(--text-faint); }
.preview-comment-author { font-weight: 700; color: var(--text-muted); }
.preview-comment-text { font-size: 0.78rem; line-height: 1.45; word-break: break-word; margin-top: 2px; }
/* Task <-> Preventivo links. The collapsed badge shares .comment-badge's look
   and adds only what a clickable thing needs; the preview/editor rows share
   one shape (number in bold, title or client, status chip). */
.quote-badge { cursor: pointer; border-radius: var(--radius-sm); padding: 1px 3px; margin: -1px -3px; }
.quote-badge:hover, .quote-badge:focus-visible { color: var(--accent); background: var(--accent-soft); outline: none; }
.preview-quotes, .quote-link-list { display: flex; flex-direction: column; gap: 4px; }
.preview-quote {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; min-width: 0;
  font-size: 0.78rem; color: var(--text-muted); text-decoration: none;
}
.preview-quote-link { cursor: pointer; }
.preview-quote-link:hover .quote-number, .preview-quote-link:focus-visible .quote-number { text-decoration: underline; color: var(--accent); }
.preview-quote .quote-number { font-weight: 700; color: var(--text); }
.preview-quote .quote-label { flex: 1; min-width: 0; word-break: break-word; }
.preview-quote .quote-status {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-faint); background: var(--surface-2); border-radius: 100px; padding: 1px 7px;
}
.quote-link-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.quote-link-row .preview-quote { font-size: 0.85rem; }
.quote-unlink-btn { flex-shrink: 0; padding: 1px 8px; }
.quote-picker { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
/* KT-104: the "Crea preventivo associato" row above the picker. */
.quote-create-row { display: flex; margin-top: 6px; }
.quote-create-btn { flex-shrink: 0; }
.quote-link-input {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 7px 10px;
  font-size: 0.85rem; background: var(--surface);
}
.quote-picker-matches { display: flex; flex-direction: column; gap: 3px; }
.quote-pick-btn {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; width: 100%; text-align: left;
  font-size: 0.82rem; color: var(--text-muted); background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 6px 9px; cursor: pointer;
}
.quote-pick-btn:hover, .quote-pick-btn:focus-visible { border-color: var(--accent); background: var(--accent-soft); outline: none; }
.quote-pick-btn .quote-number { font-weight: 700; color: var(--text); }
.quote-pick-btn .quote-label { flex: 1; min-width: 0; }
.quote-pick-btn .quote-status {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-faint); background: var(--surface); border-radius: 100px; padding: 1px 7px;
}
.preview-private {
  display: flex; align-items: flex-start; gap: 6px; font-size: 0.74rem; line-height: 1.4;
  color: var(--pri-medium); background: var(--pri-medium-bg);
  border-radius: var(--radius-sm); padding: 7px 9px;
}
.preview-private .lock-icon { flex-shrink: 0; margin-top: 2px; }
.preview-link-row { display: flex; gap: 6px; align-items: center; }
.preview-link-row input {
  flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 5px 8px; font-size: 0.72rem; background: var(--surface-2); color: var(--text-muted);
}
.preview-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 2px; }
.preview-actions .preview-edit-btn { margin-right: auto; }

/* ------------------------------------------------------------- login gate */
.gate { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 50; }
.gate-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 36px 34px; max-width: 400px; width: 100%; box-shadow: var(--shadow-md); display: flex; flex-direction: column; gap: 18px; }
.gate-card .mark-img { width: 168px; height: auto; display: block; }
.gate-card .wordmark { font-size: 1.6rem; font-weight: 700; letter-spacing: 0.02em; }
.gate-card p { color: var(--text-muted); font-size: 0.9rem; }
.gate-users { display: flex; flex-direction: column; gap: 6px; }
.gate-user-btn { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-raised); font-weight: 600; font-size: 0.88rem; justify-content: center; }
.gate-user-btn:hover { border-color: var(--accent); background: var(--accent-soft); }
.gate-divider { display: flex; align-items: center; gap: 10px; color: var(--text-faint); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }
.gate-divider::before, .gate-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.gate-new { display: flex; gap: 8px; min-width: 0; }
.gate-new input { flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 11px; background: var(--surface); font-size: 0.88rem; }
.gate-form { display: none; flex-direction: column; gap: 8px; }
.gate-form.open { display: flex; }
.gate-form input {
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 11px;
  background: var(--surface); font-size: 0.88rem; width: 100%;
}
.gate-toggle { background: none; border: none; color: var(--text-muted); font-size: 0.78rem; text-decoration: underline; padding: 0; }
.gate-toggle:hover { color: var(--text); }
.gate-error { color: var(--pri-high); font-size: 0.82rem; font-weight: 600; }
/* The forgot-password confirmation. Neutral on purpose — it is shown whether
   or not the address matched an account (plan §11.2), so it must not read as
   success. */
.gate-note { color: var(--text-muted); font-size: 0.8rem; line-height: 1.45; }

/* ------------------------------------------------------- modals and panel */
.backdrop { position: fixed; inset: 0; background: rgba(17, 25, 23, 0.32); z-index: 40; }
.export-modal {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(720px, 96vw); max-height: calc(100vh - 2 * var(--win-margin)); max-height: calc(100dvh - 2 * var(--win-margin));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); z-index: 41; display: flex; flex-direction: column;
}
.export-body { padding: var(--head-pad-y) var(--head-pad-x); display: flex; flex-direction: column; gap: 12px; overflow-y: auto; }
.export-textarea {
  width: 100%; min-height: clamp(260px, 50vh, 600px); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px;
  font-family: 'Courier New', monospace; font-size: 0.76rem; background: var(--surface-2); color: var(--text);
  resize: vertical;
}
.footer-link-btn {
  background: none; border: none; color: var(--sidebar-text-muted); font-size: 0.72rem; text-decoration: underline;
  padding: 0; text-align: left;
}
.footer-link-btn:hover { color: var(--sidebar-text); }
/* ------------------------------------ spec B: the floating editor window
   Replaces the docked `.panel` (position:fixed; top/right/bottom:0) that used
   to hold the task form. The .panel-head / .panel-body / .panel-foot classes
   BELOW are kept exactly as they were: the export and picker modals share
   them, and the window reuses them too — only the outer container changed.

   Geometry (left/top/width/height) is written as an inline style by app.js
   from ui.editorWin, so nothing here may set those in windowed mode. */
.task-window {
  position: fixed; z-index: 41;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
  display: flex; flex-direction: column;
  min-width: min(360px, 100vw); min-height: min(320px, 100vh); max-width: 100vw; max-height: 100vh;
  /* app.js defaultEditorWin() opens it the FULL viewport height (minus
     --win-margin top and bottom, EDITOR_WIN_MARGIN) so the form reaches the
     bottom of the screen and scrolls inside .panel-body, never the page. */
  /* `resize` is ignored while `overflow` computes to visible, so the window
     clips deliberately: the ONE scroller inside it stays .panel-body. */
  overflow: hidden; resize: both;
}
/* No backdrop and no outside-click-to-close on purpose: the board behind stays
   live, because dragging the window aside to keep reading it is the point. */
/* 0.86.0: the entrance moved to the "Motion" section as `.task-window-enter`
   (one paint only). The old unconditional `.task-window { animation: winpop }`
   replayed on every repaint — the 7 s poll, every toast — while it was open. */
.task-window-head {
  cursor: grab; user-select: none;
  /* the drag is pointer-events based, so the browser must not claim the
     gesture for scrolling on a touch screen */
  touch-action: none;
  gap: 10px;
}
.task-window-head:active { cursor: grabbing; }
.task-window-head h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-window-head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
/* The armed half of the two-click "Chiudi senza salvare?" guard. */
.panel-close.armed {
  color: var(--pri-high); background: var(--pri-high-bg);
  font-size: 0.74rem; font-weight: 700; padding: 5px 9px; white-space: nowrap;
}
/* Narrow screens: a movable, resizable window is theatre on a phone, so it
   becomes a full-screen sheet with exactly the same content. app.js stops
   emitting the inline geometry below EDITOR_SHEET_MAX (.sheet); the media
   query is the belt-and-braces half, for a viewport that changed between
   renders. 900px is --bp-tablet and MUST equal app.js EDITOR_SHEET_MAX. */
.task-window.sheet { inset: 0; width: 100%; height: 100%; border-radius: 0; resize: none; }
@media (max-width: 900px) {
  .task-window {
    inset: 0; width: 100% !important; height: 100% !important;
    max-width: none; max-height: none; border-radius: 0; border: none; resize: none;
  }
  .task-window-head { cursor: default; }
}

.panel-head { display: flex; align-items: center; justify-content: space-between; padding: var(--head-pad-y) var(--head-pad-x); border-bottom: 1px solid var(--border-soft); }
.panel-head h2 { font-size: 1.05rem; font-weight: 700; }
.panel-close { background: none; border: none; color: var(--text-muted); font-size: 1.1rem; padding: 4px 8px; border-radius: var(--radius-sm); }
.panel-close:hover { background: var(--surface-2); }
.panel-body { flex: 1; overflow-y: auto; padding: var(--head-pad-y) var(--head-pad-x); display: flex; flex-direction: column; gap: var(--form-gap); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 0.76rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.field input[type="text"], .field input[type="date"], .field input[type="email"],
.field input[type="password"], .field input[type="color"], .field input[type="file"], .field select {
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--control-pad-y) var(--control-pad-x); background: var(--surface); font-size: 0.9rem;
}
.field input[type="color"] { padding: 2px; height: var(--control-h); }
.seg { display: flex; gap: 6px; flex-wrap: wrap; }
.seg-btn { flex: 1 1 84px; border: 1px solid var(--border); background: var(--surface); padding: 7px 6px; border-radius: var(--radius-sm); font-size: 0.8rem; font-weight: 600; color: var(--text-muted); }
/* KT-138: the same control inside the card's preview — content-sized, not
   stretched across the card, and small enough to sit beside its label. */
.preview-row-control { align-items: center; }
.seg-compact { gap: 4px; min-width: 0; }
.seg-compact .seg-btn { flex: 0 0 auto; padding: 3px 10px; font-size: 0.74rem; }
.seg-btn.active { background: var(--text); color: var(--surface); border-color: var(--text); }
.seg-btn.pri-active.low { background: var(--pri-low); border-color: var(--pri-low); color: #fff; }
.seg-btn.pri-active.medium { background: var(--pri-medium); border-color: var(--pri-medium); color: #fff; }
.seg-btn.pri-active.high { background: var(--pri-high); border-color: var(--pri-high); color: #fff; }
/* Desideri §D-bis 7: Stato and Assegnatari are DISABLED, not hidden, for a
   contributor — the value stays legible, the helper line under it says
   "Gestito dagli amministratori". */
.seg-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.field-row { display: flex; gap: var(--field-gap); flex-wrap: wrap; }
.field-row > * { min-width: 0; }
.field-row .field { flex: 1 1 160px; }
.task-window textarea, .task-window input, .export-modal textarea, .admin-card textarea, .admin-card input[type="text"],
.admin-card input[type="email"], .admin-card input[type="password"] { max-width: 100%; }
.assignee-checklist {
  display: flex; flex-direction: column; gap: 2px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px; max-height: clamp(168px, 28vh, 320px); overflow-y: auto;
}
.assignee-check-row {
  display: flex; align-items: center; gap: 9px; padding: 6px 7px; border-radius: var(--radius-sm);
  font-size: 0.87rem; cursor: pointer;
}
.assignee-check-row:hover { background: var(--surface-2); }
.assignee-check-row input[type="checkbox"] { flex-shrink: 0; }
.assignee-check-row.disabled { opacity: 0.6; cursor: not-allowed; }
.assignee-check-row.disabled:hover { background: none; }
.assignee-check-row.disabled input[type="checkbox"] { cursor: not-allowed; }
.watch-field { gap: 0; }
label.watch-toggle-row {
  display: flex; align-items: center; gap: 9px; padding: 8px 9px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-2); font-size: 0.83rem; font-weight: 600;
  text-transform: none; letter-spacing: normal; color: var(--text); cursor: pointer;
}
.watch-toggle-row input[type="checkbox"] { flex-shrink: 0; }
/* Spec §1: the privacy toggle a non-creator cannot use is GREYED, not hidden —
   it still shows the task's current state, and the .helper-text under it names
   the creator who can change it. */
label.watch-toggle-row.disabled { opacity: 0.6; cursor: not-allowed; }
label.watch-toggle-row.disabled input[type="checkbox"] { cursor: not-allowed; }
.cat-inline-add { display: none; gap: 6px; margin-top: 4px; }
.cat-inline-add.open { display: flex; }
.cat-inline-add input { flex: 1; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 7px 9px; font-size: 0.85rem; }
.project-inline-add.open { flex-direction: column; align-items: stretch; }
.subtask-list { display: flex; flex-direction: column; gap: 6px; }
/* flex-start, not center: the title is a textarea grown to as many lines as
   the title needs, and the checkbox and the × belong next to its FIRST line
   rather than floating in the middle of a tall block. */
.subtask-row { display: flex; align-items: flex-start; gap: 8px; }
.subtask-row input[type="checkbox"] { flex-shrink: 0; margin-top: 10px; }
/* resize:none + overflow:hidden are load-bearing, not cosmetic: the height is
   driven from scrollHeight by autoGrow() in app.js, so a hand-resized or
   internally scrolling box would be silently overwritten on the next render.
   min-width:0 keeps a long unbroken word from stretching the flex row. */
.subtask-row .subtask-title {
  flex: 1; min-width: 0; display: block; resize: none; overflow: hidden;
  border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 6px 9px; font-size: 0.85rem; line-height: 1.45; background: var(--surface);
  overflow-wrap: break-word;
}
.subtask-row .subtask-title.done-text { text-decoration: line-through; color: var(--text-faint); }
.subtask-remove { background: none; border: none; color: var(--text-faint); font-size: 0.95rem; padding: 4px 6px; border-radius: var(--radius-sm); margin-top: 2px; }
.subtask-remove:hover { background: var(--pri-high-bg); color: var(--pri-high); }
.subtask-add { display: flex; gap: 6px; margin-top: 2px; }
.subtask-add input { flex: 1; border: 1px dashed var(--border); border-radius: var(--radius-sm); padding: 6px 9px; font-size: 0.85rem; background: none; }
.panel-foot { border-top: 1px solid var(--border-soft); padding: var(--foot-pad-y) var(--head-pad-x); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.panel-foot .foot-actions { display: flex; align-items: center; gap: 8px; }
.helper-text { font-size: 0.78rem; color: var(--text-faint); }

.completed-note {
  display: flex; align-items: center; gap: 7px; font-size: 0.82rem; font-weight: 600;
  color: var(--status-done); background: var(--status-done-bg); border-radius: var(--radius-sm); padding: 8px 11px;
}
.join-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 0.82rem; font-weight: 600;
  color: var(--accent-hover); background: var(--accent-soft); border-radius: var(--radius-sm); padding: 8px 11px;
}
/* Spec §1 — "privato ma assegnato ad altri". .join-banner's band, retinted with
   the same amber the lock chip uses and switched to `display: block` because
   this one is a sentence that must wrap, not a row with a button on the right. */
.join-banner.private-warning {
  display: block; color: var(--pri-medium); background: var(--pri-medium-bg); line-height: 1.45;
}
/* The note that stands in for the public-link field while the task is private:
   a paragraph, so it needs the leading a one-line .helper-text does not. */
.private-link-note { line-height: 1.45; }
.comment-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
.comment-row { display: flex; gap: 9px; }
.comment-body { flex: 1; background: var(--surface-2); border-radius: var(--radius-md); padding: 8px 11px; }
.comment-head { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.comment-author { font-size: 0.8rem; font-weight: 700; }
.comment-time { font-size: 0.7rem; color: var(--text-faint); }
.comment-source { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--status-watch); background: var(--status-watch-bg); border-radius: 100px; padding: 1px 7px; }
/* Pushed to the end of the wrapping .comment-head row, clear of the author/
   time/source chips that precede it. */
.comment-del-btn { margin-left: auto; padding: 1px 8px; }
.comment-text { font-size: 0.85rem; color: var(--text); white-space: pre-wrap; word-break: break-word; margin-top: 2px; }
/* A MARKDOWN comment (comments.format = 'markdown') is rendered HTML, not raw
   text, so it must NOT keep `pre-wrap`: every <ul>/<div> QuoteEngine.mdToHTML
   emits is preceded by its own source newline, which pre-wrap would print as
   a blank line. Exactly the rule routes/public.js applies on the /t/ page, so
   the two readings of one comment cannot drift. The subset below is all
   mdToHTML can emit: .md-h1/2/3, ul/ol/li, strong/em, <br>. */
.comment-text.md, .preview-comment-text.md { white-space: normal; }
.comment-text.md ul, .comment-text.md ol,
.preview-comment-text.md ul, .preview-comment-text.md ol { margin: 4px 0; padding-left: 20px; }
.comment-text.md li, .preview-comment-text.md li { margin: 1px 0; }
.comment-text.md .md-h1, .comment-text.md .md-h2, .comment-text.md .md-h3,
.preview-comment-text.md .md-h1, .preview-comment-text.md .md-h2, .preview-comment-text.md .md-h3 {
  font-weight: 700; margin: 6px 0 2px;
}
.comment-text.md .md-h1 { font-size: 0.98rem; }
.comment-text.md .md-h2 { font-size: 0.92rem; }
.comment-text.md .md-h3 { font-size: 0.87rem; }
.comment-text.md strong, .preview-comment-text.md strong { font-weight: 700; }
.comment-text.md em, .preview-comment-text.md em { font-style: italic; }
.comment-add { display: flex; flex-direction: column; gap: 8px; align-items: flex-end; }
.comment-add textarea {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px;
  font-size: 0.85rem; resize: vertical; min-height: 54px; background: var(--surface);
}
.comment-add-wrap { position: relative; width: 100%; min-width: 0; }
/* The "Commenta" button is right-aligned by .comment-add; the note that stands
   in for it while creating a task reads as a caption, so it hugs the left. */
.comment-add .helper-text { align-self: flex-start; }
/* Attachments (TASK_COMMENTS_RTE_ATTACHMENTS_PLAN.md §A.3). One chip shape for
   all three surfaces: a comment's files, the task's "Allegati" block and the
   composer's not-yet-uploaded queue. */
.attach-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.attach-chip {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 3px 7px; font-size: 0.78rem;
}
.attach-queue .attach-chip { background: var(--accent-soft); border-color: transparent; }
.attach-icon { flex-shrink: 0; font-size: 0.9rem; line-height: 1; }
.attach-thumb {
  flex-shrink: 0; width: 28px; height: 28px; object-fit: cover;
  border-radius: var(--radius-sm); background: var(--surface);
}
.attach-name {
  flex: 1; min-width: 0; color: var(--text); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.attach-name:hover, a.attach-name:focus-visible { color: var(--accent); text-decoration: underline; }
.attach-size { flex-shrink: 0; font-size: 0.7rem; color: var(--text-faint); }
.attach-del-btn { flex-shrink: 0; padding: 0 6px; line-height: 1.5; }
/* The <input type=file> is driven by the button beside it and never shown. */
.attach-input { display: none; }
.attach-add { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
/* The drop zones: the "Allegati" field and the comment composer. The tint is
   applied by app.js's bindFileDrop() toggling .drop-on. */
.can-drop { border-radius: var(--radius-sm); transition: background 0.12s, box-shadow 0.12s; }
.can-drop.drop-on { background: var(--accent-soft); box-shadow: inset 0 0 0 2px var(--accent); }
.attach-badge-icon { font-size: 0.72rem; line-height: 1; }
.preview-attachments { display: flex; flex-direction: column; gap: 3px; }
.preview-attachment {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
  font-size: 0.78rem; color: var(--text-muted);
}
.preview-attachment-name { flex: 1; min-width: 0; word-break: break-word; }
/* The rich-text comment composer: the mount point MtkRte fills, and the row of
   its two side buttons ("@ Menziona…", "📎 Allega"). The vendored editor brings
   its own chrome (toastui-editor.css, loaded by index.html); only the outer box
   is ours. */
.comment-rte { width: 100%; min-width: 0; }
.comment-rte .toastui-editor-defaultUI { border-radius: var(--radius-sm); }
.comment-add .rte-fallback {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px;
  font-size: 0.85rem; resize: vertical; min-height: 54px; background: var(--surface);
}
.comment-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; align-self: flex-start; }
.mention { color: var(--accent); font-weight: 700; }
.mention-menu {
  display: none; flex-direction: column; position: absolute; left: 0; bottom: calc(100% + 6px); width: min(260px, 100%);
  max-height: 180px; overflow-y: auto; background: var(--surface-raised); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md); padding: 5px; gap: 2px; z-index: 20;
}
.mention-menu.open { display: flex; }
.mention-item {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius-sm);
  font-size: 0.83rem; text-align: left; width: 100%; background: none; border: none;
}
.mention-item:hover { background: var(--surface-2); }

/* ---------------------------------------------------------- notifications */
.notif-search { margin-bottom: 14px; max-width: 420px; }
.notif-list { display: flex; flex-direction: column; gap: var(--row-gap); }
.notif-row {
  display: flex; align-items: flex-start; gap: 11px; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-md); padding: calc(var(--row-pad-y) + 1px) var(--row-pad-x); box-shadow: var(--shadow-sm);
}
.notif-row.unread { border-color: var(--accent); background: var(--accent-soft); }
.notif-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 8px; flex-shrink: 0; background: transparent; }
.notif-dot.unread { background: var(--accent); }
.notif-body { flex: 1; min-width: 0; }
.notif-text { font-size: 0.86rem; color: var(--text); }
.notif-actor { font-weight: 700; }
.notif-task { font-weight: 600; color: var(--accent-hover); }
.notif-time { font-size: 0.72rem; color: var(--text-faint); margin-top: 3px; }
.notif-actions { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.badge-unread { background: var(--accent); color: var(--accent-contrast); border-radius: 100px; padding: 1px 7px; }
.nav-btn.active .badge-unread { background: rgba(255, 255, 255, 0.28); color: inherit; }

/* ------------------------------------------------------- "Novità" (bell) --
   A bell at the top-left of the sidebar and the popover it opens. Sibling of
   the notifications badge above — same .badge-unread pill, deliberately not
   the same feature: those are task notifications, these are release notes and
   admin avvisi.

   Z-INDEX 45. The ladder in this file is: .mention-menu 20, .backdrop 40,
   .task-window / .export-modal 41, .gate 50, .toast-wrap 60, quotes.css's
   modal backdrop 100, "Chiedi a Salem" 500. 45 puts the popover over the
   backdrop and the editor window — it is opened by a deliberate click on a
   control that is visible at all times, so it must never paint UNDER a window
   it has nothing to do with — while staying under the toasts, the login gate
   and the Preventivi modal, all of which have to win. app.js's Escape chain is
   ordered to match: the popover closes before the editor behind it.

   The bell is the rightmost item of .header-actions in the main header, next
   to the search box. The list itself is NOT anchored to it: it is a centered
   pop-up (position: fixed, the .export-modal pattern) over a dimming
   .news-backdrop, so the header's flex wrapping and the page's scroll can
   never push it somewhere unreadable — the first cut hung it off the bell
   and it landed at the bottom of the page. Backdrop 44 / box 45 sit above the
   editor layer (41) and below toasts, the login gate and the Preventivi
   modal, same reasoning as before. */
.news-bell-wrap { display: flex; }
.news-backdrop { position: fixed; inset: 0; background: rgba(17, 25, 23, 0.32); z-index: 44; }
/* Deliberately large: the bell is the one always-visible "something changed"
   signal, so it gets a 32px glyph in a 52px hit area rather than the 17px
   icon the nav uses. */
.news-bell {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; padding: 0;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--sidebar-text-muted);
}
.news-bell:hover { background: rgba(255, 255, 255, 0.06); color: var(--sidebar-text); }
.news-bell.open { background: rgba(255, 255, 255, 0.1); color: var(--sidebar-text); }
/* The count sits on the bell's shoulder and must stay SMALL relative to the
   glyph: font-size is 33% of the 32px icon (11px), so the number reads as a
   dot with a digit in it, never as a second icon. Overrides .badge-unread's
   nav-sized padding, which would otherwise make the pill as big as the bell.
   The ring is the page surface (the bell lives in the header), not the
   sidebar colour. */
.news-bell-badge {
  position: absolute; top: 4px; right: 4px;
  font-size: 11px; font-weight: 700; line-height: 14px;
  min-width: 14px; padding: 0 4px; text-align: center;
  border: 1px solid var(--surface);
}

.news-popover {
  position: fixed; z-index: 45;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(620px, 92vw);
  display: flex; flex-direction: column;
  max-height: 78vh;
  background: var(--surface-raised); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;                 /* the ONE scroller inside is .news-list */
}
.news-popover-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 8px 9px 13px; border-bottom: 1px solid var(--border-soft);
  flex-shrink: 0;
}
.news-popover-title {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--text-muted);
}
.news-popover-foot {
  padding: 9px 13px; border-top: 1px solid var(--border-soft); flex-shrink: 0;
}
/* .footer-link-btn is sidebar-toned; on the popover's light card it needs the
   page's own muted colour instead. */
.news-popover-foot .news-readall { color: var(--text-muted); font-size: 0.76rem; }
.news-popover-foot .news-readall:hover { color: var(--accent-hover); }
.news-empty { padding: 20px 13px; font-size: 0.82rem; color: var(--text-faint); text-align: center; }
.news-list { overflow-y: auto; display: flex; flex-direction: column; }

.news-row { border-bottom: 1px solid var(--border-soft); }
.news-row:last-child { border-bottom: none; }
.news-row-head {
  display: flex; align-items: flex-start; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; padding: 10px 13px; color: inherit;
}
.news-row-head:hover { background: var(--surface-2); }
.news-row-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.news-row-title { font-size: 0.84rem; font-weight: 500; color: var(--text); overflow-wrap: break-word; }
.news-row-date { font-size: 0.7rem; color: var(--text-faint); }
/* The whole feature in one rule: unread is bold until the row is opened. */
.news-row.unread .news-row-title { font-weight: 700; }
.news-row.unread .news-row-head { background: var(--accent-soft); }
.news-row.unread .news-row-head:hover { background: var(--accent-soft); filter: brightness(0.98); }
.news-row.expanded .news-row-head { background: var(--surface-2); }
.news-chip {
  flex-shrink: 0; font-size: 0.64rem; font-weight: 700; letter-spacing: 0.03em;
  border-radius: 100px; padding: 2px 8px; white-space: nowrap; margin-top: 1px;
}
.news-chip.release { background: var(--accent-soft); color: var(--accent-hover); }
.news-chip.post { background: var(--status-watch-bg); color: var(--status-watch); }
.news-row-body {
  padding: 0 13px 12px; display: flex; flex-direction: column; gap: 7px;
  font-size: 0.8rem; line-height: 1.5; color: var(--text-muted);
}
.news-row-body .news-para { overflow-wrap: break-word; }
.news-row-body strong { font-weight: 700; color: var(--text); }
.news-row-body em { font-style: italic; }
.news-row-body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.86em;
  background: var(--surface-2, rgba(0,0,0,.05)); border-radius: 4px; padding: 0 4px;
  overflow-wrap: anywhere;
}
.news-row-body .news-bullets { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.news-row-body .news-bullets li { overflow-wrap: break-word; }

/* --------------------------------------------------- archive / flat lists */
.done-list { display: flex; flex-direction: column; gap: var(--row-gap); }
.done-row {
  display: flex; align-items: center; gap: 8px 12px; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-md); padding: var(--row-pad-y) var(--row-pad-x); text-align: left; width: 100%; box-shadow: var(--shadow-sm);
  cursor: pointer; flex-wrap: wrap; min-width: 0;
}
.done-row:hover { border-color: var(--text-faint); }
.done-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.done-row .card-title { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
.done-row .cat-tag { flex-shrink: 0; }
.done-row .private-tag { flex-shrink: 0; }
.done-row .wish-tag { flex-shrink: 0; }
.done-row .project-tag { flex-shrink: 0; }
.done-row .due-urgency-tag { flex-shrink: 0; }
.done-row .done-when { flex-shrink: 0; font-size: 0.76rem; color: var(--text-faint); width: auto; min-width: 8ch; text-align: right; }
.archive-meta { display: flex; align-items: center; gap: 10px; flex-shrink: 0; flex-wrap: wrap; }
.status-pill {
  flex-shrink: 0; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  border-radius: 100px; padding: 4px 11px; width: 84px; text-align: center;
}
.status-pill-todo { background: var(--surface-2); color: var(--text-faint); }
.status-pill-doing { background: var(--accent-soft); color: var(--accent-hover); }
.status-pill-done { background: var(--status-done-bg); color: var(--status-done); }
.status-pill-watch { background: var(--status-watch-bg); color: var(--status-watch); }
/* Desideri — the five wish statuses reuse the task palette tokens: neutral
   while it is being weighed, the "done" green once approved, the accent
   (same as "doing") while it is being built, "done" green again once
   delivered (WISHES_LOOP_PLAN.md §D: delivered replaces approved as the
   "finished" look — approved is merely queued now), high-priority red once
   rejected. */
.status-pill-evaluation { background: var(--surface-2); color: var(--text-faint); }
.status-pill-approved { background: var(--status-done-bg); color: var(--status-done); }
.status-pill-development { background: var(--accent-soft); color: var(--accent-hover); }
.status-pill-delivered { background: var(--status-done-bg); color: var(--status-done); }
.status-pill-rejected { background: var(--pri-high-bg); color: var(--pri-high); }

/* ----------------------------------------------------------------- admin */
.admin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
/* 1400px, not the 860px it used to be: the Utenti / Edifici / Email-log
   tabs are LISTS and tables and were leaving half of a 1440 screen empty.
   The prose that needs a line length (helper lines, the M365 guide) is
   capped in ch below. */
.admin-section { display: flex; flex-direction: column; gap: calc(var(--field-gap) + 2px); max-width: min(1400px, 100%); min-width: 0; }
/* the Preventivi tab hosts the add-on's own settings editor (two-column
   layouts, side-by-side texts, previews): it gets the whole main area, only
   its flag card keeps the readable width of the other tabs */
.admin-section--wide { max-width: none; }
.admin-section--wide > .admin-card { max-width: min(1400px, 100%); }
.admin-card {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: var(--card-pad-y) var(--card-pad-x); display: flex; flex-direction: column; gap: calc(var(--field-gap) + 2px); box-shadow: var(--shadow-sm);
}
.admin-card > p, .admin-card > .helper-text, .admin-card .field > .helper-text { max-width: 90ch; }
.admin-card h2 { font-size: 1rem; font-weight: 700; }
.user-row {
  display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-md); padding: calc(var(--row-pad-y) - 1px) var(--row-pad-x); box-shadow: var(--shadow-sm);
}
.user-row .grow { flex: 1; min-width: 0; }
.user-row .user-name { font-size: 0.9rem; font-weight: 600; }
.user-row .user-mail { font-size: 0.76rem; color: var(--text-faint); }
.role-pill {
  flex-shrink: 0; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  border-radius: 100px; padding: 4px 11px; background: var(--surface-2); color: var(--text-faint);
}
.role-pill.admin { background: var(--accent-soft); color: var(--accent-hover); }
/* Desideri §B1 — a SECOND pill beside the role one, never a role value: the
   contributor flag is additive, so "sales" + "contributor" must be able to sit
   side by side on the same row. */
.role-pill.contributor { background: var(--pri-medium-bg); color: var(--pri-medium); }
/* Listini 0.34.0 (PRICELISTS_PLAN.md D1) — a THIRD pill: Building Manager is
   additive too, bound to sedi rather than a role value. */
.role-pill.bm { background: var(--accent-soft); color: var(--accent-hover); }
/* KT-142 (CONTRACT_APPROVAL_PLAN.md §5.2) — the AFC Auditor pill, additive on
   a sales row like contributor; an admin is one by role and gets no pill. */
.role-pill.afc { background: var(--status-done-bg); color: var(--status-done); }
/* Edifici §D — a FOURTH pill on the user row: where the user actually belongs.
   Neutral like the chip on a card, `main` for their main building, `none` for
   the broken case (no building at all: they can create nothing). */
.role-pill.building { background: var(--surface-2); color: var(--text-muted); text-transform: none; letter-spacing: 0; }
.role-pill.building.main { background: var(--status-done-bg); color: var(--status-done); }
.role-pill.building.none { background: var(--pri-high-bg); color: var(--pri-high); }
/* Amministrazione > Edifici: the per-building member editor, unrolled under
   its row. Same checklist look as "Building manager di:" / "Invitato in". */
.building-members { padding: 8px 14px 12px; display: flex; flex-direction: column; gap: 8px; }
.building-members .sedi-checklist { max-height: 220px; }
.building-error { color: var(--pri-high); padding: 0 14px 6px; }
/* The "Building manager di:" compact multi-select on the Utenti card. */
.sedi-checklist { display: flex; flex-direction: column; gap: 4px; max-height: 132px; overflow-y: auto; padding: 4px 0; }
.sedi-checklist label { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; cursor: pointer; }
.sedi-checklist input { margin: 0; }
.badge-active {
  flex-shrink: 0; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  border-radius: 100px; padding: 4px 11px; background: var(--status-done-bg); color: var(--status-done);
}
/* Admin "Utenti" invite flow — the row keeps its own card look; the status
   line / copy-link box sit underneath it, indented to align under the name
   column (avatar 30px + .user-row's 14px padding + 12px gap = 56px). */
.user-row-block { display: flex; flex-direction: column; gap: 6px; }
.invite-status { font-size: 0.76rem; color: var(--text-faint); padding-left: 56px; }
.invite-link-box { padding-left: 56px; display: flex; flex-direction: column; gap: 4px; max-width: min(480px, 100%); overflow-wrap: anywhere; }
.admin-card { min-width: 0; overflow-wrap: anywhere; }
.invite-link-box .subtask-add input { font-family: inherit; }
.logo-preview { max-width: 200px; max-height: 70px; display: block; }

/* Amministrazione > Avvisi. The compose textarea is .autogrow, so it must not
   also be user-resizable (the two fight); the list below reuses .user-row's
   card-row shape, with a clamped body preview so one long avviso cannot push
   the next twenty off the screen — same trick as .mail-log-error. */
.news-body-input {
  width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 11px; font-size: 0.88rem; line-height: 1.5; font-family: inherit;
  background: var(--surface); color: var(--text); resize: none; overflow: hidden;
  min-height: 92px;
}
.news-admin-list { display: flex; flex-direction: column; gap: var(--row-gap); }
.news-admin-row {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-md); padding: var(--row-pad-y) var(--row-pad-x); box-shadow: var(--shadow-sm);
}
.news-admin-row .grow { flex: 1; min-width: 0; }
.news-admin-title { font-size: 0.9rem; font-weight: 600; overflow-wrap: break-word; }
.news-admin-meta { font-size: 0.74rem; color: var(--text-faint); margin-top: 2px; }
.news-admin-preview {
  margin-top: 5px; font-size: 0.8rem; line-height: 1.45; color: var(--text-muted);
  white-space: pre-wrap; overflow-wrap: break-word;
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3;
}
.m365-guide {
  border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 10px 14px; background: var(--surface-2); max-width: 90ch;
}
.m365-guide summary { cursor: pointer; font-weight: 600; font-size: 0.85rem; }
.m365-guide ol { margin: 10px 0 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.m365-guide li { font-size: 0.82rem; color: var(--text-muted); }
.m365-guide code { font-size: 0.8em; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 4px; padding: 1px 5px; }

/* "Email logs" — the outbound send log and the inbound receive log (admin >
   Email logs), which share every rule below: same six columns' worth of width,
   same pills, same sortable headers.
   Six columns is more than an .admin-card is wide on a laptop, so the TABLE
   scrolls inside its own box (min-width) rather than the page scrolling
   sideways, the same trick .cal-month-scroll uses for the month grid.
   min(720px, 100%): a card wider than 720px must not be forced to scroll.
   The box is also bounded in HEIGHT (the log is a list: it fills the screen
   and scrolls inside, with the header row stuck to its top) — see the
   density block at the foot of this file for the desktop tier. */
.mail-log-scroll { overflow: auto; }
.mail-log { width: 100%; min-width: min(720px, 100%); border-collapse: collapse; font-size: 0.8rem; }
.mail-log th {
  text-align: left; font-size: 0.7rem; font-weight: 700; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: 0.04em; padding: var(--cell-pad-y) var(--cell-pad-x) var(--cell-pad-y) 0;
  border-bottom: 1px solid var(--border-soft); white-space: nowrap;
  position: sticky; top: 0; background: var(--surface); z-index: 1;
}
.mail-log td { padding: var(--cell-pad-y) var(--cell-pad-x) var(--cell-pad-y) 0; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
.mail-log tr:last-child td { border-bottom: none; }
.mail-log-when { white-space: nowrap; color: var(--text-faint); }
.mail-log-attempts { text-align: right; color: var(--text-faint); padding-right: 0; }
/* The address can be long and is not the thing being read: one line, clipped,
   with the full value on hover via title=. */
.mail-log-to { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mail-log-subject { font-weight: 600; }
/* last_error is the reason the log exists, so it is shown in full rather than
   summarised — just clamped to two lines so one stack trace cannot push the
   next twenty rows off the screen. The text stays selectable and copyable,
   and title= gives the whole thing on hover. */
.mail-log-error {
  margin-top: 3px; font-size: 0.74rem; color: var(--brand-accent);
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2;
}
/* Both pills are the app's existing ones, borrowed rather than reinvented —
   .status-pill straight off the board (same colours mean the same things) and
   .role-pill for the module. Two things have to give in a table cell: the
   status pill's fixed 84px width, and the role pill's UPPERCASE, which is
   right for "ADMIN" and shouty for "Reimposta password". */
.mail-log .status-pill { width: auto; display: inline-block; }
.mail-log .role-pill {
  display: inline-block; text-transform: none; letter-spacing: 0;
  font-weight: 600; font-size: 0.72rem; white-space: nowrap;
}
/* An inbound row's `reason` is usually routine ('no-task-ref') and only
   sometimes a fault, so it gets the error block's shape — clamped, full text
   on hover — without its alarm colour. .mail-log-error is used for the
   genuinely broken ones. */
.mail-log-reason {
  margin-top: 0; font-size: 0.74rem; color: var(--text-muted);
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2;
}
.mail-log-muted { color: var(--text-faint); }
.mail-log-task { white-space: nowrap; }

/* Sortable headers. The <th> keeps its own padding and the button carries the
   click target, so a header is clickable across its whole label instead of
   only on the text; a non-sortable column is a plain <th> with no button and
   therefore nothing to click. The arrow slot has a fixed width so the header
   row does not shift sideways as the active column changes. */
.mail-log th.mail-log-sortable { padding: 0 var(--cell-pad-x) 0 0; }
.mail-log-sort {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: var(--cell-pad-y) 0; margin: 0;
  font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit;
  cursor: pointer; white-space: nowrap;
}
.mail-log-sort:hover { color: var(--text-muted); }
.mail-log th.mail-log-sortable.active .mail-log-sort { color: var(--text); }
.mail-log-arrow { display: inline-block; min-width: 8px; font-size: 0.6rem; line-height: 1; }

/* -------------------------------------------------------------- calendar */
.cal-wrap { display: flex; flex-direction: column; gap: 14px; }
.cal-header {
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
  padding: 10px 14px; box-shadow: var(--shadow-sm);
}
.cal-nav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cal-range-label { font-size: 0.92rem; font-weight: 700; color: var(--text); margin-left: 4px; white-space: nowrap; }
.cal-mode-seg, .cal-scope-seg { flex: 0 0 auto; max-width: 260px; }
.cal-scope-seg { margin-left: auto; max-width: 160px; }
.cal-footer { font-size: 0.78rem; color: var(--text-faint); padding: 2px 4px; }

/* month grid: 6x7, stable height, horizontal scroll of the whole grid rather
   than page overflow on a narrow viewport. */
.cal-month-scroll { overflow-x: auto; }
.cal-month { min-width: min(700px, 100%); }
.cal-month-weekdays, .cal-month-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-month-weekdays {
  gap: 6px; margin-bottom: 6px; font-size: 0.72rem; font-weight: 700; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.cal-weekday { text-align: center; padding: 2px 0; }
.cal-month-grid { gap: 6px; grid-auto-rows: minmax(96px, 1fr); }
.cal-cell {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 6px; display: flex; flex-direction: column; gap: 4px; min-width: 0;
  position: relative; cursor: pointer;
}
.cal-cell:hover { border-color: var(--text-faint); }
.cal-cell.dimmed { background: var(--surface-2); opacity: 0.6; }
.cal-cell.today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* '+' affordance: a new task pre-filled with this cell's date. Hidden until
   hover/focus on pointer devices that have hover to offer (mouse/trackpad);
   permanently visible under `(hover: none)` (touch), since there is no hover
   to reveal it there. */
.cal-add {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 50%;
  color: var(--text-muted); width: 20px; height: 20px; line-height: 1; padding: 0;
  display: flex; align-items: center; justify-content: center; font-size: 0.85rem; font-weight: 700;
  cursor: pointer; opacity: 0; transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.cal-add:hover { background: var(--surface-2); color: var(--text); }
.cal-cell .cal-add { position: absolute; top: 4px; right: 4px; }
.cal-cell:hover .cal-add, .cal-cell:focus-within .cal-add, .cal-add:focus-visible { opacity: 1; }
@media (hover: none) {
  .cal-add { opacity: 1; }
}
.cal-daynum {
  align-self: flex-start; background: none; border: none; font-size: 0.78rem; font-weight: 700;
  color: var(--text-muted); border-radius: var(--radius-sm); padding: 1px 5px;
}
.cal-cell.today .cal-daynum { background: var(--accent); color: var(--accent-contrast); }
.cal-daynum:hover { background: var(--surface-2); }
.cal-cell.today .cal-daynum:hover { background: var(--accent-hover); }
.cal-chips { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.cal-chip {
  display: flex; align-items: center; gap: 4px; width: 100%; text-align: left;
  background: var(--surface-2); border: 1px solid var(--border-soft); border-left: 3px solid var(--border);
  border-radius: 5px; padding: 2px 6px; font-size: 0.72rem; color: var(--text); cursor: pointer;
}
.cal-chip-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-chip-pri { flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--pri-high); }
.cal-chip.soon { background: var(--pri-medium-bg); color: var(--pri-medium); }
.cal-chip.overdue { background: var(--pri-high-bg); color: var(--pri-high); font-weight: 700; }
.cal-chip.done { color: var(--text-faint); text-decoration: line-through; }
.cal-more {
  align-self: flex-start; background: none; border: none; font-size: 0.72rem; font-weight: 700;
  color: var(--accent); padding: 1px 4px;
}
.cal-more:hover { text-decoration: underline; }

/* week grid: horizontal scroll of the whole row rather than page overflow. */
.cal-week-scroll { overflow-x: auto; }
.cal-week { display: flex; gap: 8px; min-width: min(700px, 100%); }
.cal-week-col {
  flex: 1 1 0; min-width: 140px; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm); padding: 8px; display: flex; flex-direction: column; gap: 6px;
  cursor: pointer;
}
.cal-week-col:hover { border-color: var(--text-faint); }
.cal-week-col.today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.cal-week-col-head { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
.cal-week-head {
  align-self: flex-start; background: none; border: none; font-size: 0.78rem; font-weight: 700;
  color: var(--text-muted); text-transform: capitalize; padding: 1px 2px;
}
.cal-week-col.today .cal-week-head { color: var(--accent-hover); }
.cal-week-head:hover { text-decoration: underline; }
.cal-week-col .cal-add { opacity: 0; }
.cal-week-col:hover .cal-add, .cal-week-col:focus-within .cal-add, .cal-week-col .cal-add:focus-visible { opacity: 1; }
@media (hover: none) {
  .cal-week-col .cal-add { opacity: 1; }
}
.cal-week-list { display: flex; flex-direction: column; gap: 4px; }
.cal-empty-hint { color: var(--text-faint); font-size: 0.76rem; padding: 2px; }

/* day mode: richer single-column rows. */
.cal-day { display: flex; flex-direction: column; gap: 8px; }
.cal-day-add { align-self: flex-start; }
.cal-day-row {
  display: flex; flex-direction: column; gap: 6px; background: var(--surface); border: 1px solid var(--border-soft);
  border-left: 3px solid var(--border); border-radius: var(--radius-md); padding: calc(var(--row-pad-y) + 1px) var(--row-pad-x);
  cursor: pointer; box-shadow: var(--shadow-sm);
}
.cal-day-row:hover { border-color: var(--text-faint); }
.cal-day-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cal-day-row.soon { border-left-color: var(--pri-medium); }
.cal-day-row.overdue { border-left-color: var(--pri-high); }
.cal-day-row-title { display: flex; align-items: center; gap: 6px; font-size: 0.92rem; font-weight: 700; }
.cal-day-row-title.done { color: var(--text-faint); text-decoration: line-through; }
.cal-day-row-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cal-day-row-assignees { font-size: 0.8rem; color: var(--text-muted); }

/* ---------------------------------------------------------------- toasts */
.toast-wrap { position: fixed; bottom: 20px; right: 20px; z-index: 60; display: flex; flex-direction: column; gap: 8px; }
.toast {
  background: var(--text); color: var(--surface); padding: 10px 16px; border-radius: var(--radius-sm);
  font-size: 0.85rem; box-shadow: var(--shadow-md); max-width: min(320px, calc(100vw - 40px));
}
@media (prefers-reduced-motion: no-preference) { .toast { animation: rise 0.18s ease-out; } }
@keyframes rise { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

/* ------------------------------------------------------- "Chiedi a Salem"
   Positioned by app.js (left/top written as inline px, viewport-relative to
   match getBoundingClientRect()). z-index sits above .task-window (41) and
   .toast-wrap (60) — both boards and toasts stay reachable, but neither can
   cover this — and well below assist_widget.css's own panel/launcher
   (2147483000+), so opening Salem's panel always wins. */
/* "Cronologia di Salem" — the host-owned passphrase card
   (ASSIST_MEMORY_PLAN.md, public/assist-memory.js). Appended to
   document.body, OUTSIDE the widget's DOM (a standard module), anchored
   above the widget launcher (assist_widget.css: right 20px, bottom 20px,
   56px tall) and one z-index below it, so an opened panel simply covers it. */
.assist-memory-card {
  position: fixed; right: 20px; bottom: 88px; z-index: 2147482999;
  width: min(320px, calc(100vw - 40px));
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 8px;
  font-size: 0.85rem;
}
.assist-memory-title { font-weight: 700; font-size: 0.92rem; }
.assist-memory-text { margin: 0; color: var(--text-muted); line-height: 1.35; }
.assist-memory-input {
  width: 100%; padding: 7px 9px; font: inherit; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.assist-memory-remember { display: flex; align-items: center; gap: 6px; font-size: 0.8rem; color: var(--text-muted); }
.assist-memory-error { color: var(--pri-high); font-size: 0.8rem; font-weight: 600; }
.assist-memory-actions { display: flex; gap: 6px; justify-content: flex-end; }
@media (max-width: 640px) {
  .assist-memory-card { right: 16px; left: 16px; width: auto; bottom: 88px; }
}

.ask-salem-bubble {
  position: fixed; z-index: 500;
  background: var(--brand-primary); color: #fff; border: none; border-radius: 999px;
  padding: 7px 14px; font-size: 0.78rem; font-weight: 600; line-height: 1.2;
  box-shadow: var(--shadow-md); cursor: pointer;
}
.ask-salem-bubble[hidden] { display: none; }
.ask-salem-bubble:hover, .ask-salem-bubble:focus-visible { filter: brightness(1.08); outline: none; }

.ask-salem-menu {
  position: fixed; z-index: 500;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md); padding: 4px; min-width: 160px;
}
.ask-salem-menu[hidden] { display: none; }
.ask-salem-menu-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: 8px 10px; font-size: 0.82rem; color: var(--text); border-radius: var(--radius-sm);
  cursor: pointer;
}
.ask-salem-menu-item:hover, .ask-salem-menu-item:focus-visible {
  background: var(--accent-soft); outline: none;
}


/* ------------------------------------------------------------- icon rail
   The sidebar collapsed to a 64px column of glyphs — app.js
   setSidebarCollapsed(), `sidebar-collapsed` on <body>, the preference in
   localStorage ('mytaskat.sidebar').

   --sidebar-rail-w is --sidebar-w's collapsed twin, and the switch is ONE
   declaration: body.sidebar-collapsed re-points --sidebar-w at it, so the
   <body> gradient band, the column and everything else keep reading the one
   variable they always have. (It is declared on its own line rather than
   folded into the :root block at the top of this file, which holds exactly
   one declaration and is matched as such by test/layout-shell.test.js.)

   DESKTOP ONLY, in two halves. The width switch is undone under the drawer
   breakpoint at the end of this file — there --sidebar-w is already 0 and the
   sidebar is an overlay, so there is no column to narrow and a
   `sidebar-collapsed` left on <body> by a wider window must change nothing.
   The rail's GEOMETRY needs no fence at all: it hangs off `.sidebar-rail`,
   a class app.js adds only when sidebarIsCollapsed() is true, which is false
   under the breakpoint whatever localStorage says. Deliberately no new
   min-width media query: this file has exactly one (the density tier).

   Everything here sits AFTER .nav-btn, which is how .rail-btn out-cascades it
   at equal specificity. */
:root { --sidebar-rail-w: 64px; }

body.sidebar-collapsed { --sidebar-w: var(--sidebar-rail-w); }
/* The content-side twin of the chevron: with the rail on, the ☰ that is
   normally a drawer control becomes "give me the labels back". */
body.sidebar-collapsed .menu-btn { display: inline-flex; }
.sidebar { transition: width 160ms ease; }
/* `.sidebar.sidebar-rail`, not `.sidebar-rail`: the ≤1280px block at the end
   of this file sets `.sidebar { padding-left: 16px }` at equal specificity and
   later in source, which would leave 32px of a 64px column for a 40px item. */
.sidebar.sidebar-rail { padding: 14px 8px; gap: 10px; align-items: center; overflow-x: hidden; }

/* The chevron. Top-right of the column when there are labels, centred on the
   rail. Never rendered in the drawer (the ✕ is that mode's control). */
.sb-top { display: flex; justify-content: flex-end; margin-bottom: -14px; }
.sidebar-rail .sb-top { justify-content: center; margin-bottom: 0; }
.sb-collapse {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 32px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--sidebar-text-muted);
}
.sb-collapse:hover { background: rgba(255, 255, 255, 0.08); color: var(--sidebar-text); }

/* One rail row: a 40 × 44 hit area — 44px is the touch minimum and the rhythm
   the glyphs sit on — the glyph centred, the count floated over its corner. */
.nav-btn.rail-btn {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 40px; min-height: 44px; padding: 0;
}
.rail-icon { display: flex; align-items: center; justify-content: center; }
/* Sizes iconQuote() — a 12px card badge everywhere else — to the rail's 20px
   without a second copy of the glyph. */
.rail-icon svg, .rail-glyph { display: block; width: 20px; height: 20px; }
.rail-count {
  position: absolute; top: 4px; right: 0;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--sidebar-bg-deep); color: var(--sidebar-text);
  border: 1px solid var(--sidebar-border);
  font-size: 0.58rem; font-weight: 700; line-height: 1;
}
.rail-count.badge-unread { background: var(--accent); color: var(--accent-contrast); border-color: transparent; }
.nav-btn.rail-btn.active .rail-count { background: rgba(255, 255, 255, 0.28); color: inherit; border-color: transparent; }
.rail-sections, .rail-footer { border-top: 1px solid var(--sidebar-border); padding-top: 10px; }
.rail-footer { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.rail-brand {
  display: flex; align-items: center; justify-content: center;
  width: 40px; min-height: 40px; padding: 0;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--sidebar-text);
}
.rail-brand:hover { background: rgba(255, 255, 255, 0.06); }
.rail-brand .mark-img { width: 34px; max-width: 100%; height: auto; }
/* The fallback mark, and NAV_ICONS' fallback for a view key with no glyph. */
.rail-letter {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--sidebar-bg-deep); border: 1px solid var(--sidebar-border);
  color: var(--sidebar-text); font-size: 0.85rem; font-weight: 700; line-height: 1;
}

/* Labels fade in on the ONE render that follows a toggle: body.sidebar-shift
   is a 260 ms one-shot (app.js setSidebarCollapsed), so the 7 s poll's rebuild
   of the same tree never replays it. */
@keyframes sb-fade-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  body.sidebar-shift .sidebar .nav-btn > span,
  body.sidebar-shift .sidebar .rail-icon,
  body.sidebar-shift .sidebar .sb-label,
  body.sidebar-shift .sidebar .chip-list,
  body.sidebar-shift .sidebar .brand,
  body.sidebar-shift .sidebar .sidebar-footer { animation: sb-fade-in 160ms ease-out; }
}
/* The reduced-motion guard for the rail: no width transition, no fade. (The
   drawer's own `transition: none` is at the end of this file and says the
   same thing about the slide.) */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
  body.sidebar-shift .sidebar * { animation: none; }
}

/* ------------------------------------------------ responsive shell (cont.)
   Everything that changes WITH the viewport, in one place. Breakpoints are
   the four shared ones documented at the top of this file; nothing here
   touches a colour. */

/* --bp-drawer: the sidebar is an off-canvas drawer, opened by the ☰ in the
   header (body.sidebar-open, app.js setSidebarOpen). visibility rides along
   with the transform so a closed drawer is out of the tab order too. z-index:
   above the editor window (41) and the Novità popover (45), below the toasts
   (60) and Salem's bubble (500). */
.sidebar-close { display: none; }
.sidebar-backdrop { display: none; }
@media (max-width: 1024px) {
  .sidebar {
    position: fixed; inset: 0 auto 0 0; width: min(300px, 86vw); z-index: 55;
    transform: translateX(-100%); visibility: hidden;
    transition: transform 0.2s ease, visibility 0.2s;
    padding-top: 52px;
  }
  body.sidebar-open .sidebar { transform: none; visibility: visible; box-shadow: var(--shadow-md); }
  .sidebar-close {
    display: flex; align-items: center; justify-content: center;
    position: absolute; top: 12px; right: 12px; width: 36px; height: 36px; padding: 0;
    background: none; border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
    color: var(--sidebar-text); font-size: 1rem; line-height: 1;
  }
  .sidebar-close:hover { background: rgba(255, 255, 255, 0.06); }
  .sidebar-backdrop { position: fixed; inset: 0; background: rgba(17, 25, 23, 0.32); z-index: 54; }
  body.sidebar-open .sidebar-backdrop { display: block; }
  .menu-btn { display: inline-flex; }
  /* The icon rail is DESKTOP ONLY (see "icon rail" above): here the sidebar
     is an overlay and takes no column, so a `sidebar-collapsed` left on
     <body> by a wider window gives back the same 0 the :root step does. */
  body.sidebar-collapsed { --sidebar-w: 0px; }
}
@media (max-width: 1280px) {
  .sidebar { padding-left: 16px; padding-right: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* --bp-phone */
@media (max-width: 640px) {
  /* the page behind an open drawer must not scroll on a phone */
  body.sidebar-open { overflow: hidden; }
  .main-header { gap: 10px 12px; }
  .main-header h1 { font-size: 1.3rem; }
  .search-box { flex-basis: 100%; }
  .building-select { flex-basis: 100%; max-width: 100%; }
  /* archive / admin rows: one line each for name, pills and controls */
  .user-row { flex-wrap: wrap; }
  .user-row .grow { flex: 1 1 140px; }
  .invite-status, .invite-link-box { padding-left: 0; }
  .news-admin-row { flex-wrap: wrap; }
  .panel-head, .panel-body, .panel-foot { padding-left: 14px; padding-right: 14px; }
  .panel-foot { flex-wrap: wrap; }
  .panel-foot .foot-actions { flex-wrap: wrap; }
  /* calendar: the month grid stays scrollable but its cells no longer force
     700px; the week columns shrink a little. app.js paints week instead of
     month at this width anyway (effectiveCalMode). */
  .cal-month-weekdays, .cal-month-grid { grid-template-columns: repeat(7, minmax(56px, 1fr)); }
  .cal-month-grid { grid-auto-rows: minmax(72px, 1fr); }
  .cal-week-col { min-width: 120px; }
  .cal-scope-seg { margin-left: 0; }
  .toast-wrap { left: 16px; right: 16px; bottom: 16px; }
  .toast { max-width: none; }
}

/* --bp-desk: the compact tier's few STRUCTURAL rules (the tokens above do
   the rest). The email logs use the screen's height: the scroll box is
   bounded so a long log scrolls inside it under its sticky header row
   instead of pushing the page; 300px is the header + the card chrome above
   it on the admin tab. */
@media (min-width: 1280px) {
  .mail-log-scroll { max-height: max(320px, calc(100vh - 300px)); max-height: max(320px, calc(100dvh - 300px)); }
}

/* touch: every tappable control is at least 36px tall; the calendar '+' is
   already always visible here (see .cal-add above) and grows a little. */
@media (hover: none) {
  .btn, .seg-btn, .status-btn, .kic-btn, .nav-btn, .cat-chip, .filter-chip, .panel-close, .menu-btn,
  .archive-tab-btn, .select-mode-toggle, .col-add-btn, .kic-head, .cal-daynum, .cal-more,
  .cal-week-head { min-height: 36px; }
  .search-box { min-height: 38px; }
  .cal-add { width: 28px; height: 28px; }
}

/* Amministrazione > Edifici — the two "Planimetria IT/EN" controls per
   building (0.50.0, CONTRACT_ANNEX_A_PLAN.md §3): a small thumbnail of the
   uploaded image, the hidden file input behind Carica/Sostituisci, Rimuovi. */
.building-maps { display: flex; flex-wrap: wrap; gap: 16px; padding-left: 56px; }
.building-map { display: flex; flex-direction: column; gap: 6px; min-width: 180px; }
.building-map-label { font-size: 0.78rem; font-weight: 600; color: var(--text-faint); }
/* 0.82.0: the "Allegato D (sicurezza ed emergenza)" row under them — the
   same two controls (PDF IT / PDF EN), its title on a line of its own. */
.building-annex-d-title { flex-basis: 100%; font-size: 0.82rem; font-weight: 600; }
/* 0.83.0: the "Planimetria per il contratto (Allegato A)" row between them
   reuses that title class; its thumbnail is the landscape drawing, so it may
   be wider than the quote map's portrait one. */
.building-contract-map .building-map-thumb { max-width: 240px; }
.building-map-thumb { display: block; max-width: 180px; max-height: 120px; width: auto; height: auto; border: 1px solid var(--border, #E3E1D3); border-radius: 6px; background: #fff; object-fit: contain; }

/* ===================================================================== Motion
   0.86.0 (session 134) — "the board feels mechanical": cards that lift under
   the pointer, controls that answer a hover and a press, a card that rolls
   open downward and an editor window that rises in. Rules of the section:
     - every duration and curve is a token from :root (--motion-fast,
       --motion-med, --ease-out); nothing here invents its own timing;
     - every transition, transform and animation sits inside ONE
       `prefers-reduced-motion: no-preference` block, so with the OS "reduce
       motion" switch on the board is exactly as instant as before 0.86.0 (the
       colour-only hover tints just below are not motion and stay outside);
     - the card lift is `transform` only — no width/height/margin — so it
       never reflows the lane, and it is switched off while a drag is in
       flight (body.board-dragging, app.js handleCardDragStart): the drop
       indicator is computed from card midpoints that a scaled card would move;
     - the ENTRANCES (card-preview-enter, task-window-enter) are classes app.js
       puts on for ONE paint after the user's own gesture (motionPaint), never
       on a repaint — the 7 s poll rebuilds the whole tree, and a class keyed
       on "this node exists" would replay the animation every seven seconds. */

/* Colour-only hover answers for the two chip families that had none. */
.filter-chip:hover:not(.active) { border-color: var(--text-faint); color: var(--text); }
.seg-btn:hover:not(.active):not(.pri-active):not(:disabled) { border-color: var(--text-faint); color: var(--text); }
/* The editor's exit ghost (app.js ghostEditorExit) is a picture, not a window. */
.task-window-ghost { pointer-events: none; }

@media (prefers-reduced-motion: no-preference) {
  /* Controls in the task area — ONE list, one transition. `.btn` covers every
     header/card/editor/admin button (and, through the add-on's own `.btn`
     with its own `transition: all`, Preventivi keeps working the same way).
     .cal-add is left out on purpose: it owns an opacity transition. */
  .btn, .seg-btn, .status-btn, .kic-btn, .filter-chip, .col-add-btn, .pri-tag-btn,
  .panel-close, .archive-tab-btn, .select-mode-toggle, .menu-btn, .kic-head, .kic-more,
  .quote-badge, .search-clear {
    transition: background-color var(--motion-fast) ease, color var(--motion-fast) ease,
      border-color var(--motion-fast) ease, box-shadow var(--motion-fast) ease,
      transform var(--motion-fast) var(--ease-out);
  }
  /* The press: a 3 % give under the pointer, back on release. */
  .btn:active:not(:disabled), .seg-btn:active:not(:disabled), .status-btn:active:not(:disabled),
  .kic-btn:active:not(:disabled), .filter-chip:active, .col-add-btn:active, .pri-tag-btn:active,
  .panel-close:active, .archive-tab-btn:active, .select-mode-toggle:active, .menu-btn:active {
    transform: scale(0.97);
  }

  /* The card lift — board lanes, the Keep in check strip and the project
     boards all draw the same .card. 2px up, with the deeper --shadow-md the
     plain :hover rule above already sets. 0.86.1: NO scale and NO
     will-change — a scaled card (1.012) on its own compositor layer is
     rasterised once and stretched, which blurred the card text on hover
     (Simone). A whole-pixel translate keeps the text on the pixel grid and
     crisp; the shadow carries the "lifted" feel. */
  .card {
    transition: transform var(--motion-fast) var(--ease-out),
      box-shadow var(--motion-fast) var(--ease-out), border-color var(--motion-fast) ease;
  }
  .card:hover { transform: translateY(-2px); }
  /* An unrolled card is a surface you work IN (its buttons, the link field):
     it stays put under the pointer instead of floating. */
  .card.expanded:hover { transform: none; }
  /* Mid-drag: nothing lifts and nothing eases, so the midpoints that
     computeDropIndex() measures hold still; the dragged card keeps its tilt. */
  body.board-dragging .card { transform: none; transition: none; will-change: auto; }
  body.board-dragging .card.dragging { transform: rotate(-1.5deg); }

  /* The unroll: the preview's grid track goes 0fr -> 1fr (its real height,
     whatever it holds), the negative margin swallows the card's 8px flex gap
     so nothing jumps on the first frame, and the content fades in with it.
     The roll-up is the same film backwards (app.js collapseCardAnimated keeps
     the node for one --motion-med). Clipping only while it moves. */
  .card-preview-enter { animation: card-unroll var(--motion-med) var(--ease-out) both; }
  .card-preview-leave { animation: card-rollup var(--motion-med) var(--ease-out) both; pointer-events: none; }
  .card-preview-enter > .card-preview-body,
  .card-preview-leave > .card-preview-body { overflow: hidden; }

  /* The editor window rises in, and its ghost sinks out the same way. */
  .task-window-enter { animation: win-rise var(--motion-med) var(--ease-out) both; }
  .task-window-leave { animation: win-sink var(--motion-med) var(--ease-out) both; }
}
@keyframes card-unroll {
  from { grid-template-rows: 0fr; opacity: 0; margin-top: -8px; }
  to { grid-template-rows: 1fr; opacity: 1; margin-top: 0; }
}
@keyframes card-rollup {
  from { grid-template-rows: 1fr; opacity: 1; margin-top: 0; }
  to { grid-template-rows: 0fr; opacity: 0; margin-top: -8px; }
}
@keyframes win-rise {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
@keyframes win-sink {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateY(8px) scale(0.98); }
}
