/* Campaign HQ — deliberately a single white theme, as requested.
   No dark variant: every colour is painted explicitly so the page holds
   whatever ground the browser puts behind it.
   System fonts only: nothing is fetched from a third party. */

:root {
  --bg:        #FFFFFF;
  --canvas:    #F7F8FA;
  --panel:     #FFFFFF;
  --panel-2:   #F2F5F8;
  --ink:       #14263C;
  --ink-soft:  #45586E;
  --muted:     #74869A;
  --rule:      #E2E8EF;
  --rule-soft: #EFF3F7;
  --accent:    #1F5FA9;
  --accent-bg: #E8F0FA;
  --bad:       #B23A26;
  --bad-bg:    #FBE9E5;
  --warn:      #8A6318;
  --warn-bg:   #FDF3DF;
  --good:      #1F6B4F;
  --good-bg:   #E4F3EC;
  --shadow:    0 1px 2px rgba(20,38,60,.05), 0 3px 10px rgba(20,38,60,.045);
}

/* ------------------------------------------------ chantier identity ramp

   The six chantiers are identified by COLOUR, and the colour is a hue held in
   data — `hq/lib/store.php` gives each workstream an integer, and PHP emits
   `.h-<chantier>{--hue:N}` into the nonce'd sheet. Every chantier colour on
   the page is derived from that one number.

   What was NOT in the system is this ramp: the saturation and lightness pairs
   that turn a hue into a pastille, a bar, a pill, a pressed button. They sat
   as bare literals at nine sites, so "the chantier blue" meant four slightly
   different blues depending on which surface you were looking at, and nothing
   named the difference. The T-026 audit read those literals back as six
   unsystematic hex values (#2D70BE and its five siblings); they are in fact
   exactly the 46% step of this ramp.

   The hues stay in data — they are identity and not ours to move. These are
   the steps. Every value below is the literal it replaced, so the rendered
   colours are unchanged to the byte. */
:root {
  --chantier-s:          62%;   /* the ramp's saturation, everywhere but the two below */
  --chantier-l-solid:    52%;   /* filled surface: Gantt bar, calendar pill, day card */
  --chantier-l-pastille: 46%;   /* the chantier's own dot — the audit's six hexes */
  --chantier-l-deep:     40%;   /* chantier colour ON white: tick, confirm */
  --chantier-l-deepest:  38%;   /* the same, one step down, for a pressed control */

  /* Two pre-Apple steps sit off the 62% saturation. They are outliers, but a
     reader sees them, so they are preserved exactly rather than tidied onto
     the scale — that correction would be a visible colour change and needs
     his word, not ours. */
  --chantier-legacy-ev:   55% 46%;   /* .cal-ev, the pre-Apple calendar pill */
  --chantier-legacy-head: 52% 40%;   /* .ws-head, the chantier section heading */
}

* { box-sizing: border-box; }

/* Icons default to unfilled.

   An <svg> with no `fill` attribute inherits SVG's initial value, which is
   BLACK — so a stroke-drawn icon is only an outline because some CSS rule
   happens to reach it and say otherwise. 49 of this project's 75 icons are in
   that position: they carry stroke-width but declare no fill of their own, and
   they render correctly today purely because a rule in this file covers their
   context. Move one into markup those rules do not reach and it paints as a
   solid black blob.

   This is the same fragility the T-026 audit reported as "#000000 on 108
   elements". It was never an authored colour — nobody wrote black anywhere —
   it was this default, read back off the live page.

   Scoped deliberately:
     - `:not([fill])` leaves any icon that DECLARES its own fill alone, so the
       106 fill="none" and 15 fill="currentColor" attributes are untouched;
     - the selector matches the <svg> ONLY, never its children. A child's own
       presentation attribute beats an inherited value, but a CSS rule aimed at
       the child would beat the attribute — which would erase the filled icons;
     - specificity is (0,1,1) and this sits at the top of the file, so every
       class rule that sets a fill (.ch-facebook, .bar-ch, .circ, the map's
       generated .cm-* …) still wins, whether it exists today or is written
       tomorrow.

   Nothing rendered changes — measured — because the rules that cover those 49
   icons today still cover them. What changes is that an icon is now correct on
   its own rather than correct by the grace of its surroundings. */
svg:not([fill]) { fill: none; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

h1 { font-size: 20px; font-weight: 600; margin: 0; letter-spacing: -.01em; }
h2 { font-size: 13px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); margin: 0; }

.lbl {
  display: block; font-size: 10.5px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 5px;
}

/* ------------------------------------------------------------- topbar */

.topbar { background: var(--bg); border-bottom: 1px solid var(--rule); }
.topbar-in {
  max-width: 1560px; margin: 0 auto; padding: 13px 22px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
/* Adding the clock made the right-hand block wide enough to wrap, and a
   wrapped flex line starts at the left — so it fell under the name instead
   of staying opposite it. margin-left:auto keeps it to the right whether it
   wraps or not; the brand yields first when space is short. */
.topbar-right { margin-left: auto; }
.brand { min-width: 0; flex: 0 1 auto; }
.brand-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brand-name { display: block; font-weight: 700; font-size: 15.5px; letter-spacing: -.01em; }
.brand-sub  { display: block; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-top: 1px; }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.countdown { font-size: 13px; color: var(--muted); }
.countdown strong { color: var(--accent); font-size: 16px; font-variant-numeric: tabular-nums; }
.who { font-size: 13px; color: var(--muted); }

.tabs {
  max-width: 1560px; margin: 0 auto; padding: 0 22px;
  display: flex; gap: 2px; overflow-x: auto;
}
.tab {
  position: relative;
  padding: 10px 15px 11px;
  font-size: 14px; font-weight: 500; color: var(--ink-soft);
  text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent;
  display: flex; align-items: center; gap: 7px;
}
.tab:hover { color: var(--accent); }
.tab.on { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }
.tab.soon { color: var(--muted); }
.soon-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--warn); display: inline-block; }

/* -------------------------------------------------------------- shell */



.side {
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  padding: 15px 0 6px; box-shadow: var(--shadow); position: sticky; top: 16px;
}
.side-h { padding: 0 16px 11px; border-bottom: 1px solid var(--rule-soft); }
.side-empty { padding: 14px 16px; color: var(--muted); font-size: 13.5px; margin: 0; }

.rems { list-style: none; margin: 0; padding: 0; }
.rem { border-bottom: 1px solid var(--rule-soft); }
.rem:last-child { border-bottom: 0; }
.rem a { display: block; padding: 10px 16px 11px; text-decoration: none; color: inherit; border-left: 3px solid transparent; }
.rem a:hover { background: var(--panel-2); }
.rem-when {
  display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; margin-bottom: 3px;
}
.rem-title { display: block; font-size: 13.5px; line-height: 1.35; font-weight: 500; }
.rem-meta  { display: block; font-size: 11.5px; color: var(--muted); margin-top: 3px; }
.rem-late a  { border-left-color: var(--bad); }
.rem-late .rem-when  { color: var(--bad); }
.rem-due a   { border-left-color: var(--warn); }
.rem-due .rem-when   { color: var(--warn); }
.rem-start a { border-left-color: var(--accent); }
.rem-start .rem-when { color: var(--accent); }

.main { min-width: 0; display: flex; flex-direction: column; gap: 18px; }

/* ------------------------------------------------------------- vitals */

.vitals {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px; background: var(--rule); border: 1px solid var(--rule);
  border-radius: 8px; overflow: hidden;
}
.vital { background: var(--panel); padding: 13px 16px 12px; }
.val { display: block; font-size: 24px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.val.hot { color: var(--accent); }
.val.bad { color: var(--bad); }
.val .pc { font-size: 14px; color: var(--muted); }
.sub { display: block; font-size: 11px; color: var(--muted); margin-top: 3px; }

/* -------------------------------------------------------------- panel */

.panel { background: var(--panel); border: 1px solid var(--rule); border-radius: 8px; box-shadow: var(--shadow); }
.panel-head {
  display: flex; justify-content: space-between; align-items: center; gap: 16px;
  flex-wrap: wrap; padding: 14px 18px; border-bottom: 1px solid var(--rule);
}
.legend { display: flex; gap: 13px; flex-wrap: wrap; }
.lg { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
.lg i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }

.btn {
  display: inline-block; padding: 7px 13px; font: inherit; font-size: 13px; font-weight: 500;
  border-radius: 4px; text-decoration: none; border: 1px solid transparent; cursor: pointer;
  background: var(--accent); color: #fff;
}
.btn-quiet { background: transparent; color: var(--accent); border-color: var(--rule); }
.btn-quiet:hover { border-color: var(--accent); }
.btn-xs { padding: 3px 9px; font-size: 13px; }

/* -------------------------------------------------------------- gantt */

/* Below about 1150px the plot no longer fits the card and this scrolls. A
   pill continuing past the boundary used to be cut with a hard vertical
   edge, mid-word — MEASURED at 1100px: three pills sliced, four at 980px —
   which reads as a rendering fault rather than as "there is more over
   here". The mask fades those last pixels instead.

   The fade is confined to this element's own 18px padding. Content is a
   block child and so lives between the paddings: when the plot fits, which
   is every width above ~1150px, nothing is dimmed at all. Only scrolled
   content, which is clipped at the padding box and therefore paints over
   the padding, meets the ramp. Both edges, because scrolling right cuts a
   pill on the left just as badly. */
.gantt-scroll {
  overflow-x: auto; padding: 0 18px 18px;
  mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
}








.marker-today { background: var(--accent); }
.marker-today span { background: var(--accent); color: #fff; }
.marker-ballots { background: var(--bad); }
.marker-ballots span { background: var(--bad-bg); color: var(--bad); }




.bar-label { font-size: 11.5px; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
.bar-ch { flex: 0 0 auto; display: inline-flex; }
.bar-ch svg { width: 12px; height: 12px; fill: #fff; opacity: .9; display: block; }
.bar:hover { filter: brightness(1.1); }
.bar.crit { box-shadow: inset 0 0 0 2px var(--bad); }
.bar.s-done { opacity: .35; }
.bar.s-todo { opacity: .82; }
.bar.s-blocked { background: repeating-linear-gradient(45deg, var(--bad) 0 6px, #8C2D1D 6px 12px); }

/* ----------------------------------------------------------- calendar */

.cal { padding: 0 18px 18px; }
.cal-nav { display: flex; align-items: center; gap: 10px; }
.cal-label { font-size: 13px; font-weight: 600; min-width: 116px; text-align: center; }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: var(--rule-soft); }
.cal-head { background: transparent; margin-bottom: 1px; }
.cal-dow { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; color: var(--muted); text-align: center; padding: 5px 0; }
.cal-cell { background: var(--panel); min-height: 74px; padding: 4px 5px 6px; }
.cal-cell.empty { background: var(--panel-2); }
.cal-cell.is-today { background: var(--accent-bg); }
.cal-cell.is-election { background: var(--bad-bg); }
.cal-cell.is-ballots { background: var(--warn-bg); }
.cal-n { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.cal-cell.is-today .cal-n { color: var(--accent); font-weight: 700; }
.cal-ev {
  display: block; margin-top: 2px; padding: 1px 4px; border-radius: 2px;
  font-size: 10.5px; line-height: 1.3; text-decoration: none; color: #fff;
  background: hsl(var(--hue) var(--chantier-legacy-ev));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-ev.ev-start { opacity: .62; }
.cal-more { font-size: 10px; color: var(--muted); display: block; margin-top: 2px; }

/* -------------------------------------------------------------- tasks */

.ws-head {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: hsl(var(--hue) var(--chantier-legacy-head)); margin: 0; padding: 13px 18px 7px;
  border-top: 1px solid var(--rule-soft);
}
.panel .ws-head:first-of-type { border-top: 0; }
.tasks { list-style: none; margin: 0; padding: 0; }
.task {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 18px;
  padding: 11px 18px; border-top: 1px solid var(--rule-soft); flex-wrap: wrap;
}
.task.s-done .task-title { color: var(--muted); text-decoration: line-through; text-decoration-thickness: 1px; }
.task:target { background: var(--accent-bg); }
.task-main { flex: 1 1 320px; min-width: 0; }
.task-title { margin: 0 0 2px; font-size: 14.5px; font-weight: 500; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.task-note { margin: 3px 0 0; font-size: 13px; color: var(--muted); max-width: 64ch; }
.task-meta { margin: 4px 0 0; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.ch { display: inline-flex; }
.ch svg { width: 13px; height: 13px; display: block; }
.ch-facebook svg  { fill: #1877F2; }
.ch-instagram svg { fill: #C13584; }
.ch-x svg         { fill: #14263C; }

.tag {
  display: inline-block; font-size: 9.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; padding: 2px 6px; border-radius: 2px;
}
.crit-tag { background: var(--accent-bg); color: var(--accent); }
.late-tag { background: var(--bad-bg); color: var(--bad); }

.task-status { display: flex; gap: 4px; flex-shrink: 0; }
.chip {
  font: inherit; font-size: 11.5px; font-weight: 500; padding: 5px 10px;
  border: 1px solid var(--rule); background: transparent; color: var(--muted);
  border-radius: 4px; cursor: pointer; white-space: nowrap;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.on { font-weight: 600; }
.chip-todo.on    { background: var(--panel-2); color: var(--ink); }
.chip-doing.on   { background: var(--accent-bg); color: var(--accent); border-color: var(--accent); }
.chip-done.on    { background: var(--good-bg); color: var(--good); border-color: var(--good); }
.chip-blocked.on { background: var(--bad-bg); color: var(--bad); border-color: var(--bad); }

/* --------------------------------------------------------------- stub */

.stub { padding: 34px 30px 36px; }
.stub-tag {
  display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--warn); background: var(--warn-bg);
  padding: 3px 8px; border-radius: 2px; margin: 0 0 14px;
}
.stub h2 { font-size: 21px; font-weight: 600; text-transform: none; letter-spacing: -.01em; color: var(--ink); margin: 0 0 10px; }
.stub-what { font-size: 15.5px; color: var(--ink-soft); max-width: 60ch; margin: 0 0 16px; }
.stub-block {
  font-size: 14px; color: var(--ink-soft); max-width: 60ch; margin: 0;
  background: var(--panel-2); border-left: 3px solid var(--muted);
  padding: 11px 15px; border-radius: 0 4px 4px 0;
}

/* --------------------------------------------------------------- login */

.login-body { display: grid; place-items: center; min-height: 100vh; padding: 24px; background: var(--canvas); }
.login-card { width: 100%; max-width: 380px; background: var(--panel); border: 1px solid var(--rule); border-radius: 8px; padding: 34px 32px 28px; box-shadow: var(--shadow); }
.eyebrow { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.login-card h1 { font-size: 25px; margin: 6px 0 0; }
.login-card .lede { color: var(--muted); font-size: 14px; margin: 8px 0 26px; }
label { display: block; font-size: 12px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 6px; }
input[type=text], input[type=password] {
  width: 100%; padding: 11px 13px; margin-bottom: 18px; font-size: 15px;
  color: var(--ink); background: var(--canvas); border: 1px solid var(--rule); border-radius: 4px;
}
input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-bg); }
.login-card .btn { width: 100%; padding: 11px 16px; font-size: 15px; font-weight: 600; text-align: center; }
.alert { background: var(--bad-bg); color: var(--bad); border-left: 3px solid var(--bad); padding: 10px 14px; border-radius: 0 4px 4px 0; font-size: 14px; margin: 0 0 20px; }
.foot { font-size: 12px; color: var(--muted); text-align: center; margin: 22px 0 0; }

/* ---------------------------------------------------------- responsive */

/* The .shell/.side/.main rules that used to open this block are gone: all
   three were overridden by the narrow-window block further down, so this
   one had no effect at any width. Column geometry now has exactly one
   owner — see "shell column geometry" near the end of this file. */
@media (max-width: 640px) {
  .task-status { width: 100%; }
  .chip { flex: 1; text-align: center; }
  /* The calendar half of this block used to live here and never did anything:
     `.cal-cell { height: 96px }` is declared further down the file at the same
     specificity, so it won and a phone rendered full-size cells. Moved to the
     end of the calendar section, where it can actually take effect. */
}
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ------------------------------------------------------------ settings */

.pad { padding: 18px; }
.para { margin: 0 0 14px; font-size: 14.5px; color: var(--ink-soft); max-width: 62ch; }
.para.dim, .dim { color: var(--muted); }
.para:last-child { margin-bottom: 0; }

.badge {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 4px;
}
.badge-on  { background: var(--good-bg); color: var(--good); }
.badge-off { background: var(--warn-bg); color: var(--warn); }

.secret {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 17px; letter-spacing: .06em; background: var(--panel-2);
  border: 1px solid var(--rule); border-radius: 4px;
  padding: 13px 16px; margin: 0 0 10px; word-break: break-all; user-select: all;
}

.inline-form { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; max-width: 460px; }
.inline-form label { margin-bottom: 6px; }
.inline-form > div { flex: 1; }
.inline-form input { margin-bottom: 0; width: auto; flex: 1 1 180px; }
.inline-form .btn { white-space: nowrap; }

.codes-panel { border-color: var(--warn); }
.warn-line {
  background: var(--warn-bg); color: var(--warn); border-left: 3px solid var(--warn);
  padding: 11px 15px; border-radius: 0 4px 4px 0; margin: 0 0 16px; font-size: 14px;
}
.codes {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px;
}
.codes li {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 15px; letter-spacing: .04em; background: var(--panel-2);
  border: 1px solid var(--rule); border-radius: 4px; padding: 9px 12px;
  text-align: center; user-select: all;
}

.tw { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; min-width: 560px; font-size: 13.5px; }
th {
  text-align: left; font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted); padding: 10px 18px;
  border-bottom: 1px solid var(--rule); background: var(--panel-2); white-space: nowrap;
}
td { padding: 9px 18px; border-bottom: 1px solid var(--rule-soft); color: var(--ink-soft); }
tr:last-child td { border-bottom: 0; }
.nowrap { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------- territoire */

.seatbar { display: flex; height: 26px; border-radius: 4px; overflow: hidden; margin-bottom: 14px; }
.seatseg { display: block; }
.seatkey { list-style: none; margin: 0; padding: 0; display: flex; gap: 18px; flex-wrap: wrap; }
.seatkey li { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-soft); }
.seatkey i { width: 11px; height: 11px; border-radius: 2px; display: inline-block; }
.seatkey strong { font-variant-numeric: tabular-nums; }

.factgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 18px; }
.factgrid .val { font-size: 22px; }

table.communes { min-width: 640px; }
table.communes td { padding: 7px 18px; }
table.communes .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.communes th.num { text-align: right; }
table.communes tr.on { background: var(--accent-bg); }
table.communes a { text-decoration: none; font-weight: 500; }
table.communes a:hover { text-decoration: underline; }
.wcell { width: 168px; }
.wbar { display: block; height: 7px; border-radius: 2px; background: var(--accent); opacity: .75; }

.gap-t { margin-top: 16px; }

/* ------------------------------------------------------------- map */

/* The 560px cap dated from when the map was upright and shared the page
   with the task list. Turned and framed to its own geometry, it fills the
   panel. */
.mapwrap { width: 100%; margin: 0 auto; }
.map { width: 100%; height: auto; display: block; }

/* Stroke widths are in user units, so a map drawn twice as wide would show
   borders twice as thick. Scaling them with the viewBox keeps the hairlines
   hairlines. */
.map path { vector-effect: non-scaling-stroke; }
.map path { stroke: #fff; stroke-width: 1.2; cursor: pointer; transition: none; }


.map path.sel { stroke: var(--sys-red); stroke-width: 3; }
.map a:focus-visible path { stroke: var(--ink); stroke-width: 3; }

.mswitch { display: flex; gap: 4px; flex-wrap: wrap; }
.mswitch .chip { text-decoration: none; }

.mini-h { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 22px 0 10px; }
.pshares { list-style: none; margin: 0; padding: 0; max-width: 420px; }
.pshares li { display: flex; align-items: baseline; gap: 12px; padding: 5px 0; border-bottom: 1px solid var(--rule-soft); font-size: 13.5px; }
.pshares li:last-child { border-bottom: 0; }
.ps-n { flex: 1; }
.ps-v, .ps-p { font-variant-numeric: tabular-nums; }
.ps-v { color: var(--ink); font-weight: 500; }
.ps-p { color: var(--muted); min-width: 52px; text-align: right; }

.ok-tag { background: var(--good-bg); color: var(--good); }
td.strong { font-weight: 600; color: var(--ink); }
td.weak { color: var(--bad); }
td.good { color: var(--good); }
table.trend { min-width: 520px; }
table.trend td:first-child { color: var(--ink); }


/* =====================================================================
   Apple-style pass: sidebar task menu, retroplanning, calendar.
   Reference points are the macOS sidebar and Reminders/Calendar: soft
   greys, 8-12px radii, hairlines replaced by spacing, one system blue.
   ===================================================================== */

:root {
  --sys-blue:   #0071E3;
  --sys-blue-d: #0058B0;
  --sys-fill:   #F5F5F7;   /* Apple's canvas grey */
  --sys-fill-2: #EBEBF0;   /* hover / resting fill */
  --sys-sep:    #E5E5EA;
  --sys-grey:   #6E6E73;   /* secondary label */
  /* --sys-grey-1 was used in 37 declarations and defined in none of them. An
     unresolved var() with no fallback makes the whole declaration invalid at
     computed-value time, and `color` is inherited — so every one of those rules
     silently took its parent's colour instead of a grey, which is why several
     metadata lines read as body text. Defined here as the same secondary label
     they were reaching for. */
  --sys-grey-1: #6E6E73;   /* secondary label, alias */
  --sys-grey-2: #8E8E93;   /* tertiary label */
  /* --sys-bg was consumed by .daywin's background and defined nowhere. With no
     fallback the whole declaration is invalid at computed-value time, so the
     day window painted no background at all and the calendar showed through
     it. This is Apple's elevated system background: the window is a card over
     a scrim, so it wants the plain white surface, not the canvas grey. */
  --sys-bg:     #FFFFFF;   /* elevated surface (day window, sheets) */
  --sys-red:    #D93025;
  --sys-green:  #34C759;
  --sys-orange: #C77700;
}

body { background: var(--sys-fill); letter-spacing: -0.01em; }


/* ------------------------------------------------------- sidebar */

.side {
  background: transparent;
  border: 0;
  box-shadow: none;
  padding: 4px 0 20px;
}

.smart { display: flex; flex-direction: column; gap: 2px; margin-bottom: 22px; }

.smart-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: 8px;
  text-decoration: none; color: var(--ink);
  font-size: 14.5px;
}
.smart-row:hover { background: var(--sys-fill-2); }
.smart-row.on { background: var(--sys-blue); color: #fff; }
.smart-row.on .smart-count { color: rgba(255,255,255,.85); }

.smart-ico {
  width: 22px; height: 22px; border-radius: 50%;
  flex: 0 0 auto; display: inline-block;
}
.si-toutes  { background: var(--sys-grey-2); }
.si-retard  { background: var(--sys-red); }
.si-semaine { background: var(--sys-orange); }
.si-ouvert  { background: var(--sys-blue); }
.smart-row.on .smart-ico { box-shadow: inset 0 0 0 2px rgba(255,255,255,.55); }

.smart-label { flex: 1; font-weight: 500; }
.smart-count { font-size: 14px; color: var(--sys-grey-2); font-variant-numeric: tabular-nums; }

.grp {
  display: flex; align-items: center; gap: 8px;
  margin: 18px 0 4px; padding: 0 10px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--sys-grey);
}
.grp-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.grp-n { margin-left: auto; font-size: 12px; color: var(--sys-grey-2); font-variant-numeric: tabular-nums; }

.tlist { list-style: none; margin: 0; padding: 0; }

.trow {
  display: flex; align-items: flex-start; gap: 8px;
  border-radius: 8px; padding: 2px 8px 2px 4px;
}
.trow:hover { background: var(--sys-fill-2); }
.trow.on { background: #DCEBFB; }

.tcheck { flex: 0 0 auto; padding-top: 6px; }
.circ {
  background: none; border: 0; padding: 0; cursor: pointer;
  width: 22px; height: 22px; display: grid; place-items: center;
}
.circ svg { width: 20px; height: 20px; overflow: visible; }
.circ circle { fill: none; stroke: #C7C7CC; stroke-width: 1.5; }
.circ path { fill: none; stroke: #fff; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: 0; }
.circ:hover circle { stroke: var(--sys-blue); }
.trow.s-done .circ circle { fill: var(--sys-blue); stroke: var(--sys-blue); }
.trow.s-done .circ path { opacity: 1; }
.trow.s-doing  .circ circle { stroke: var(--sys-blue); stroke-dasharray: 3 3; }
.trow.s-blocked .circ circle { stroke: var(--sys-red); }

.tlink { flex: 1; min-width: 0; display: block; padding: 5px 2px 7px; text-decoration: none; color: inherit; }
.ttitle { display: block; font-size: 14px; line-height: 1.35; }
.trow.s-done .ttitle { color: var(--sys-grey-2); }
.tmeta {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--sys-grey-2); margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.tmeta.late { color: var(--sys-red); }
.tstar {
  display: inline-grid; place-items: center;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--sys-orange); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1;
}
.tch svg { width: 12px; height: 12px; display: block; }
.side-empty { padding: 10px; color: var(--sys-grey-2); font-size: 14px; }

/* ------------------------------------------------------- cards */

.panel, .card {
  background: #fff; border: 0; border-radius: 12px;
  box-shadow: 0 1px 1px rgba(0,0,0,.04), 0 4px 14px rgba(0,0,0,.05);
}
.panel-head, .card-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; flex-wrap: wrap;
  padding: 15px 20px 13px; border-bottom: 1px solid var(--sys-sep);
}
.panel-head h2, .card-head h2 {
  font-size: 16px; font-weight: 600; letter-spacing: -.015em;
  text-transform: none; color: var(--ink);
}
.card-body { padding: 18px 20px 20px; }
.lnk { font-size: 14px; color: var(--sys-blue); text-decoration: none; }
.lnk:hover { text-decoration: underline; }

.lede-note { margin: 0 0 16px; font-size: 15px; color: var(--sys-grey); max-width: 62ch; }

.meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 14px; margin: 0 0 20px; }
.meta dt { font-size: 12px; color: var(--sys-grey-2); margin-bottom: 3px; }
.meta dd { margin: 0; font-size: 15px; font-variant-numeric: tabular-nums; }

/* segmented control */
.seg { display: inline-flex; background: var(--sys-fill-2); border-radius: 8px; padding: 2px; gap: 2px; }
.seg-b {
  font: inherit; font-size: 13.5px; font-weight: 500;
  border: 0; background: transparent; color: var(--ink);
  padding: 6px 14px; border-radius: 8px; cursor: pointer;
}
.seg-b:hover { color: var(--sys-blue); }
.seg-b.on { background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.12); font-weight: 600; }

/* ------------------------------------------------------- vitals */

.vitals { border: 0; border-radius: 12px; gap: 1px; background: var(--sys-sep); box-shadow: 0 1px 1px rgba(0,0,0,.04), 0 4px 14px rgba(0,0,0,.05); }
.vital { padding: 15px 18px 14px; }
.lbl { font-size: 12px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--sys-grey-2); }
.val { font-size: 27px; font-weight: 600; letter-spacing: -.025em; }
.val.hot { color: var(--sys-blue); }
.val.bad { color: var(--sys-red); }
.sub { font-size: 12px; color: var(--sys-grey-2); }

/* ------------------------------------------------------- gantt */







.bar-label { font-size: 11.5px; font-weight: 500; }
.bar.crit { box-shadow: 0 1px 2px rgba(0,0,0,.10), inset 0 0 0 2px rgba(255,255,255,.75); }
.bar.s-done { opacity: .3; }
.bar.s-todo { opacity: .85; }
.marker-today { background: var(--sys-blue); }
.marker-today span { background: var(--sys-blue); border-radius: 4px; }
.marker-ballots { background: var(--sys-red); }
.marker-ballots span { background: var(--sys-red); color: #fff; border-radius: 4px; }
.legend .lg { font-size: 12.5px; color: var(--sys-grey); }
.lg i { border-radius: 50%; width: 10px; height: 10px; }

/* ------------------------------------------------------- calendar */

.cal { padding: 6px 20px 20px; }
.cal-nav { display: flex; align-items: center; gap: 6px; }
.cal-label { font-size: 15px; font-weight: 600; letter-spacing: -.01em; min-width: 132px; }
/* The square 28px .btn-xs was written for the calendar's month arrows. Those
   are gone, and the rule was left forcing every small button — "Vue
   d'ensemble", "Exporter en CSV" — into a 28px box that clipped its label.
   Small buttons are text buttons; the padding rule higher up is enough. */
.btn-xs { border: 0; background: var(--sys-fill-2); color: var(--ink); text-decoration: none; }
.btn-xs:hover { background: #E0E0E6; }

.cal-grid { gap: 0; background: transparent; }
.cal-head { margin-bottom: 2px; }
.cal-dow {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--sys-grey-2); padding: 6px 0;
}
/* Every cell is the same size, whatever the day holds. A day with more
   than fits drifts its list up and back so you can see there is more, and
   opens in full on hover. */
.cal-cell {
  position: relative; background: #fff; height: 96px; padding: 4px;
  border-top: 1px solid var(--sys-sep); border-right: 1px solid var(--sys-sep);
  overflow: hidden;
}
.cal-evs { position: relative; }

/* The drift: a slow round trip over exactly the hidden distance. */
@keyframes cal-drift {
  0%, 14%  { transform: translateY(0); }
  50%, 64% { transform: translateY(calc(-1 * var(--cal-over, 0px))); }
  100%     { transform: translateY(0); }
}
.cal-cell.has-more .cal-evs {
  animation: cal-drift 7s ease-in-out infinite;
  animation-delay: var(--cal-delay, 0s);
}

/* A fade at the foot of an overflowing day, so the cut is deliberate. */
.cal-cell.has-more::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 16px;
  background: linear-gradient(to bottom, rgba(255,255,255,0), #fff);
  pointer-events: none;
}

/* Hovering a crowded day grows its week rather than covering the days
   around it: the row gains height and the column gains width, both eased,
   so the grid opens up instead of jumping. hq.js sets the two track sizes
   because the target height can only be known once the column is wider. */
.cal-grid { transition: grid-template-columns .3s cubic-bezier(.32,.72,0,1),
                        grid-template-rows    .3s cubic-bezier(.32,.72,0,1); }

/* While a week is open its cells size to the row track instead of holding
   the fixed height — otherwise the row grows and the cell stays 96px, which
   leaves the content clipped inside a taller box. */
.cal-grid.cal-row-open .cal-cell { height: auto; min-height: 96px; }

.cal-cell.cal-open {
  overflow: visible; z-index: 5;
  background: #fff;
  box-shadow: inset 0 0 0 2px var(--sys-blue);
  border-radius: 4px;
}
.cal-cell.cal-open::after { display: none; }
.cal-cell.cal-open .cal-evs { animation: none; transform: none; }
/* The pills fill the widened cell, so the extra width goes into the titles
   rather than into empty space beside them. */
.cal-cell.cal-open .cal-ev {
  display: block; width: auto;
  white-space: normal; overflow: visible; overflow-wrap: break-word;
  padding: 4px 8px; font-size: 11.5px;
}

/* The days either side give up their width, so their titles have to take
   the squeeze without spilling. */
.cal-grid.cal-row-open .cal-cell:not(.cal-open) .cal-ev {
  overflow: hidden; text-overflow: ellipsis;
}

/* Pointing at a day gives it twice the room. The BOX doubles; the text does
   not.

   The first cue asked for a zoom and said "the other cards stay the same
   size". `transform: scale(2)` satisfied the second half and he liked it —
   but it magnifies, and a clipped "QA T…" scaled up is a bigger clipped
   "QA T…". His refinement: "the text zooms with it, while it should only be
   the box. That will allow the viewer to read what is written." So this is a
   real size change, and the titles reflow into the extra width at their
   normal size instead of being blown up.

   The hard part is doubling a GRID ITEM without the grid noticing.
   - Width is free: the columns are `repeat(7, minmax(0, 1fr))`, so a track
     never sizes to its content. A cell can be 200% wide and its column stays.
   - Height is not. The rows are implicit and auto-sized, so a taller cell
     would grow its row and push every row below it down. `grid-auto-rows`
     pins them — and pinning also makes the track DEFINITE, which is what
     lets `height: 200%` mean "twice the row" instead of resolving to auto.
   - The offsets are `position: relative`, which the cell already had, so
     they move where it is PAINTED and not where it sits. -50% each way grows
     it evenly about its own middle.
   One cell's painted box doubles, no track changes, no other cell moves —
   asserted on all thirty-five rectangles, not on a class.

   BLEEDING OUT OF THE PANEL IS ALLOWED ON ALL FOUR EDGES, and that is a
   deliberate change. The first version carried nine `transform-origin` rules
   so an edge day grew inward and stayed inside the panel. The operator
   released horizontal first — "it doesn't necessarily need to respect the
   calendar frame if that crunches some dates. A left or right bleed isn't
   that much of a problem here" — and then vertical too, on 5 September, after
   seeing a first-week day ride up over the weekday header. The day grows
   evenly about its own middle, 48px each way, and that is accepted. In
   practice only the horizontal bleed leaves the panel: the grid sits below
   the head, the density strip and the weekday header, so 48px of vertical
   growth rides over those and never reaches the panel's own edge. Checked before relying on it — `.cal`, `#cal-panel`,
   `.main` and `.shell` are every one `overflow: visible`, so a bleeding cell
   genuinely bleeds rather than being clipped into something that looks
   broken. Do not "fix" any of it back to containment.

   The week is still excluded, for the reason it always was: its cells clip
   titles to one line so seven days fit, and it opens instead. */
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) { grid-auto-rows: 96px; }

.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty) {
  /* The HORIZONTAL axis eases; the vertical is instant. That is not a taste
     decision, it is the fix for a flicker this shipped with.

     `height` on a grid item whose row is pinned by `grid-auto-rows` does not
     animate — it silently stays at its start value. `top` animates perfectly
     well. So with both in the transition list the box slid UP by 48px without
     ever growing, went out from under the pointer, lost `:hover`, snapped
     back, and did it again: measured under a stationary pointer, the cell
     alternated 298x96 hovered and 149x96 not-hovered indefinitely, and the
     height never once left 96. Under `prefers-reduced-motion`, where the
     global rule kills every transition, the same CSS was instantly 298x192
     and rock steady — which is what said the transition was the culprit
     rather than the sizing.

     `top` therefore goes too. With height instant and top eased the box would
     grow downward first and then slide up, which is a wobble of its own. Both
     vertical properties land at once; width and left carry the movement. */
  transition: width .26s cubic-bezier(.32,.72,0,1),
              left .26s cubic-bezier(.32,.72,0,1),
              box-shadow .26s ease;
}
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):hover,
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):focus-within {
  /* Width as a percentage resolves against the grid area and is fine.
     HEIGHT as a percentage does NOT: a grid area's block size stays
     indefinite for percentage resolution, so `height: 200%` silently fell
     back to the cell's own 96px and the box doubled sideways only. Measured:
     297px wide, 96px tall. Explicit pixels, twice the row, and the pin above
     keeps the track from following. */
  width: 200%; height: 192px; top: -50%; left: -50%;
  z-index: 6;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .18);
  border-radius: 4px;
}

/* Twice the width is only useful if the title uses it. The pills clip to one
   line at rest so seven columns fit; the enlarged day is the one place there
   is room to let them wrap, which is the whole point of the refinement. */
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):hover .cal-ev,
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):focus-within .cal-ev {
  white-space: normal; overflow: visible; text-overflow: clip;
}

/* If the day holds more than the zoom can show, it scrolls itself.

   Doubling a crowded day magnifies its tasks but does not reveal them: a
   96px cell shows about two and a half pills, and four at 2x, so a nine-task
   day still hides most of itself. The reader asked for the rest to come to
   them.

   The ambient `cal-drift` above already travels exactly this distance — it is
   what says "there is more below the fold" at rest — but it is the wrong
   thing to read by: it runs whether or not you are looking, each cell is
   staggered by `--cal-delay` so hovering catches it at an arbitrary point in
   its cycle, and it is timed to be ignorable rather than legible. Measured
   before this rule existed: hovering a nine-task day, the last pill came into
   view for well under a second in every seven, at whatever moment the cycle
   happened to be at.

   So the hover gets its own: it starts at the TOP, every time, holds long
   enough to read, travels the measured overflow, holds at the bottom, and
   comes back. `--cal-reveal` is set by whichever script measured the cell, at
   a constant reading speed, so a long list does not fly past and a short one
   does not crawl.

   It travels `--cal-over-zoom`, NOT `--cal-over`, and the difference is a bug
   this shipped once. `--cal-over` is how far the list overflows the RESTING
   cell; a hovered cell is twice as tall, so scrolling by it overshoots by
   exactly the extra height and pushes the first tasks out of the top —
   measured, three tasks visible at rest became two on hover, which is worse
   than doing nothing. `--cal-over-zoom` is the same measurement taken against
   the doubled box, and it is 0 whenever the enlarged day can show everything,
   which since the box doubles for real is now the common case. */
@keyframes cal-reveal {
  0%,   12%  { transform: translateY(0); }
  48%,  60%  { transform: translateY(calc(-1 * var(--cal-over-zoom, 0px))); }
  96%, 100%  { transform: translateY(0); }
}
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell.has-more:hover .cal-evs,
.cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell.has-more:focus-within .cal-evs {
  animation: cal-reveal var(--cal-reveal, 8s) ease-in-out infinite;
  animation-delay: 0s;                 /* not --cal-delay: this one starts now */
}

/* While the list travels, the date needs something behind it.

   `.cal-open-day` already carries `z-index: 2`, so the number is drawn ON TOP
   of the pills sliding past — but with nothing behind it the first pill runs
   flush against the glyph and the two read as a collision rather than as one
   in front of the other. `background: inherit` takes the cell's own colour,
   whatever it is at the time — white, the hover fill, or one of the three
   marked days — so the pills disappear cleanly behind it.

   hq.js's own note on this markup is that the date is written on the cell so
   it stays readable whether or not the day carries anything. It has to keep
   being true while the day is scrolling itself. */
.cal:not(.cal-is-week) .cal-cell.has-more:hover .cal-open-day,
.cal:not(.cal-is-week) .cal-cell.has-more:focus-within .cal-open-day {
  background: inherit;
  padding-right: 3px;
  border-bottom-right-radius: 3px;
}

/* Motion turned down: it must still be possible to READ a crowded day, and
   an automatic scroll is precisely the thing the preference asks us not to
   do. So the list becomes scrollable instead — the reader moves it, at their
   own pace. This is the mechanism an opened day used to carry, which the
   month lost when opening became zooming. */
@media (prefers-reduced-motion: reduce) {
  .cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell.has-more:hover .cal-evs,
  .cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell.has-more:focus-within .cal-evs {
    /* The height the list actually has to play with — the cell's, less the
       date above it and the padding below. `100%` is the cell's whole box and
       the list starts a date's height down it, so it overshot by exactly that
       much and the last task stayed out of reach at the end of the scroll.
       `--cal-fit` is the same figure the overflow was measured against. */
    max-height: var(--cal-fit, 100%);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Every other day just lights up under the cursor.

   `:not(.empty)` is not decoration. `.cal-cell.is-today` and its two siblings
   below set `background: #fff` at the same specificity as a plain
   `.cal-cell:hover` and later in the file, so they won — today, election day
   and the day the ballots land were the only three cells in the calendar with
   no hover response at all. They are also the three that matter most. The
   extra class here outranks them, and excluding empty cells stops the padding
   days lighting up as if they were dates. */
.cal-cell:not(.empty):hover { background: var(--sys-fill-2); }
.cal-cell { transition: background .15s ease, box-shadow .2s ease; }

@media (prefers-reduced-motion: reduce) {
  .cal-cell.has-more .cal-evs { animation: none; }
  .cal-grid { transition: none; }
}
.cal-grid .cal-cell:first-child { border-left: 1px solid var(--sys-sep); }
.cal-grid:last-child .cal-cell { border-bottom: 1px solid var(--sys-sep); }
.cal-cell.empty { background: #FAFAFC; }
.cal-cell.is-today, .cal-cell.is-election, .cal-cell.is-ballots { background: #fff; }
.cal-n {
  display: inline-grid; place-items: center;
  min-width: 22px; height: 22px; padding: 0 5px; border-radius: 50%;
  font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums;
}
.cal-cell.is-today .cal-n { background: var(--sys-blue); color: #fff; font-weight: 600; }
.cal-cell.is-election .cal-n { background: var(--sys-red); color: #fff; font-weight: 600; }
.cal-cell.is-ballots .cal-n { background: var(--sys-orange); color: #fff; font-weight: 600; }
.cal-ev {
  display: block; border-radius: 4px; margin-top: 3px; padding: 2px 6px;
  font-size: 11px; font-weight: 500; line-height: 1.45;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: hsl(var(--hue) var(--chantier-s) var(--chantier-l-solid));
}

.cal-ev.ev-start { opacity: .55; }

/* ------------------------------------------------------- topbar */

/* Above the day window's scrim (200), so the tabs stay reachable while a
   day is open rather than the overlay trapping you on this page. */
.topbar { background: rgba(255,255,255,.86); backdrop-filter: saturate(180%) blur(18px); border-bottom: 1px solid var(--sys-sep); position: sticky; top: 0; z-index: 300; }
.tab { font-size: 14px; border-bottom: 0; border-radius: 8px; padding: 6px 12px; margin-bottom: 8px; }
.tab.on { background: var(--sys-fill-2); color: var(--ink); font-weight: 600; }
.tab:hover { color: var(--ink); background: #F0F0F3; }
.tabs { gap: 4px; padding-bottom: 0; }
.countdown strong { color: var(--sys-blue); }
.btn { background: var(--sys-blue); border-radius: 8px; font-weight: 500; }
.btn:hover { background: var(--sys-blue-d); }
.btn-quiet { background: var(--sys-fill-2); color: var(--ink); border: 0; }
.btn-quiet:hover { background: #E0E0E6; }

@media (max-width: 1000px) {
  .side { position: static; order: 2; }
}
.marker-vote { background: #1D1D1F; }
.marker-vote span { background: #1D1D1F; color: #fff; border-radius: 4px; }

/* ------------------------------------------------- foldable sidebar */

.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

.brand { display: flex; align-items: center; gap: 12px; }

.side-toggle {
  flex: 0 0 auto; width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  background: transparent; border: 0; border-radius: 8px; color: var(--sys-grey);
}
.side-toggle:hover { background: var(--sys-fill-2); color: var(--ink); }
.side-toggle svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.5; }

/* Collapsed: drop to a single track. Keeping a 0-width first column
   fails — display:none removes the sidebar from grid flow entirely, so
   the main column would be placed into that zero-width track. */

.shell.side-hidden .side { display: none; }

/* Disclosure groups */
/* Chantiers behave as an accordion: the one you open moves to the top of
   the list, so the section you are working in is always the first thing
   under the filters. */
.ws-list { display: flex; flex-direction: column; }
.grp-wrap[open] { order: -1; }

.grp-wrap { margin-bottom: 2px; }
.grp-wrap > summary.grp {
  list-style: none; cursor: pointer; user-select: none;
  border-radius: 8px; padding: 5px 10px; margin: 14px 0 2px;
}
.grp-wrap > summary.grp::-webkit-details-marker { display: none; }
.grp-wrap > summary.grp:hover { background: var(--sys-fill-2); }

.grp-chev { display: inline-grid; place-items: center; width: 12px; height: 12px; flex: 0 0 auto; }
.grp-chev svg { width: 11px; height: 11px; fill: none; stroke: var(--sys-grey-2); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.grp-wrap[open] > summary.grp .grp-chev { transform: rotate(90deg); }

@media (prefers-reduced-motion: no-preference) {
  .grp-chev { transition: transform .15s ease; }
}

/* The sidebar stays on the left far longer and only stacks on genuinely
   narrow screens. Both media blocks that used to sit here were dead — see
   "shell column geometry" near the end of this file, which owns all of it. */
.steps { margin: 0 0 18px; padding-left: 20px; font-size: 14.5px; color: var(--ink-soft); max-width: 60ch; }
.steps li { margin-bottom: 7px; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; background: var(--sys-fill-2); padding: 1px 5px; border-radius: 4px; }
.val.sm { font-size: 17px; }
button.lnk { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 14px; color: var(--sys-blue); }
button.lnk:hover { text-decoration: underline; }

/* ------------------------------------------------------- affichage */

.affform { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; align-items: end; max-width: 860px; }
.ff { display: flex; flex-direction: column; }
.ff-wide { grid-column: 1 / -1; }
.ff label { margin-bottom: 6px; }
.ff input, .ff select {
  padding: 8px 11px; font: inherit; font-size: 14px; color: var(--ink);
  background: #fff; border: 1px solid var(--sys-sep); border-radius: 8px; margin: 0; width: 100%;
}
.ff input:focus, .ff select:focus { border-color: var(--sys-blue); outline: none; box-shadow: 0 0 0 3px var(--accent-bg); }
.affform .btn { justify-self: start; }

.pill {
  display: inline-block; font-size: 11px; font-weight: 600;
  padding: 3px 9px; border-radius: 20px; white-space: nowrap;
}
.st-todo    { background: var(--sys-fill-2); color: var(--sys-grey); }
.st-asked   { background: var(--warn-bg); color: var(--warn); }
.st-ok      { background: var(--good-bg); color: var(--good); }
.st-refused { background: var(--bad-bg); color: var(--bad); }
.note-cell { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
dd.weakv { color: var(--sys-red); }

/* ------------------------------------------------------- campagnes */

.chanlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; max-width: 460px; }
.chanlist li { display: flex; align-items: center; gap: 11px; padding: 9px 0; border-bottom: 1px solid var(--rule-soft); }
.chanlist li:last-child { border-bottom: 0; }
.chanlist .ch svg { width: 17px; height: 17px; }
.cl-n { flex: 1; font-size: 14.5px; }
.cl-c { font-size: 12.5px; color: var(--sys-grey-2); }
.cl-s { font-variant-numeric: tabular-nums; font-weight: 500; min-width: 96px; text-align: right; }

.chk { display: flex; gap: 16px; flex-wrap: wrap; }
.chkitem { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 400;
  letter-spacing: 0; text-transform: none; color: var(--ink); margin: 0; cursor: pointer; }
.chkitem input { width: auto; margin: 0; }
.chkitem .ch svg { width: 14px; height: 14px; }

.cs-draft   { background: var(--sys-fill-2); color: var(--sys-grey); }
.cs-running { background: var(--good-bg); color: var(--good); }
.cs-done    { background: var(--accent-bg); color: var(--accent); }
.cs-paused  { background: var(--warn-bg); color: var(--warn); }

tfoot td { border-top: 2px solid var(--sys-sep); padding-top: 11px; }
.lnk.danger { color: var(--sys-red); }

/* ------------------------------------------------------ messagerie */

/* Three panes side by side: folders, the list, the message. Each pane scrolls
   on its own — the page itself does not, so the folder rail and the search box
   stay put while a long thread runs past. */
.mailapp {
  display: grid; grid-template-columns: 208px minmax(280px, 340px) minmax(0, 1fr);
  gap: 0; align-items: stretch;
  height: calc(100vh - var(--topbar-h, 116px) - 56px); min-height: 480px;
  background: var(--panel); border: 1px solid var(--sys-sep); border-radius: 12px;
  overflow: hidden;
}

/* ---- pane 1 */
.mail-side {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--sys-fill-2); border-right: 1px solid var(--sys-sep);
}
.mail-acct {
  padding: 14px 14px 10px; border-bottom: 1px solid var(--sys-sep); min-width: 0;
}
.mail-acct-a {
  display: block; font-size: 12.5px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mail-off { margin-top: 2px; }
.mail-off .lnk { font-size: 11.5px; }
.mail-fld { flex: 1 1 auto; overflow-y: auto; padding: 8px; min-height: 0; }
.mf {
  display: grid; grid-template-columns: 16px 1fr auto; gap: 9px; align-items: center;
  padding: 7px 9px; border-radius: 8px; text-decoration: none;
  color: var(--ink); font-size: 13px; min-width: 0;
}
.mf:hover { background: rgba(0, 0, 0, .05); }
.mf.on { background: var(--sys-blue); color: #fff; }
.mf svg { width: 16px; height: 16px; display: block; opacity: .8; }
.mf.on svg { opacity: 1; }
.mf-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mf-b {
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--sys-blue); color: #fff; border-radius: 8px; padding: 1px 7px;
}
.mf.on .mf-b { background: rgba(255, 255, 255, .3); color: #fff; }
.mr-to { color: var(--sys-grey-2); font-weight: 400; }
.mail-ro {
  margin: 0; padding: 12px 14px; border-top: 1px solid var(--sys-sep);
  font-size: 11px; line-height: 1.5; color: var(--sys-grey-2);
}

/* ---- pane 2 */
.mail-list {
  display: flex; flex-direction: column; min-width: 0;
  border-right: 1px solid var(--sys-sep); background: var(--panel);
}
.mail-search {
  position: relative; display: flex; align-items: center; gap: 7px;
  padding: 11px 14px 9px; border-bottom: 1px solid var(--rule-soft);
}
.mail-search svg { width: 14px; height: 14px; flex: none; color: var(--sys-grey-2); }
.mail-search input[type="search"] {
  flex: 1 1 auto; min-width: 0; border: 0; background: transparent;
  font: inherit; font-size: 13.5px; color: var(--ink); padding: 3px 0;
}
.mail-search input[type="search"]:focus { outline: none; }
.ms-x { text-decoration: none; color: var(--sys-grey-2); font-size: 17px; line-height: 1; padding: 0 2px; }
.mail-count {
  margin: 0; padding: 7px 14px; font-size: 11.5px; color: var(--sys-grey-2);
  border-bottom: 1px solid var(--rule-soft);
}
.mail-empty, .mail-none { margin: 0; padding: 28px 20px; font-size: 13px; color: var(--sys-grey-2); }
.maillist { list-style: none; margin: 0; padding: 0; flex: 1 1 auto; overflow-y: auto; min-height: 0; }
.mailrow { border-top: 1px solid var(--rule-soft); }
.mailrow:first-child { border-top: 0; }
.mailrow a {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 10px;
  padding: 9px 14px 10px; text-decoration: none; color: inherit;
}
.mailrow a:hover { background: var(--sys-fill-2); }
.mailrow.on { background: var(--accent-bg); }
.mr-from { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mr-sub  { grid-column: 1 / -1; font-size: 12.5px; color: var(--sys-grey-2);
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mr-date { font-size: 11.5px; color: var(--sys-grey-2); text-align: right;
           font-variant-numeric: tabular-nums; white-space: nowrap; }
.mailrow.unseen .mr-from { font-weight: 700; }
.mailrow.unseen .mr-sub  { font-weight: 600; color: var(--ink); }
.mailrow.unseen { box-shadow: inset 3px 0 0 var(--sys-blue); }
.mail-pg {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 14px; border-top: 1px solid var(--sys-sep); font-size: 12px;
}
.mail-pg .mp { text-decoration: none; color: var(--sys-blue); }
.mail-pg .mp.off { color: var(--sys-grey-2); opacity: .5; }
.mp-n { color: var(--sys-grey-2); font-variant-numeric: tabular-nums; }

/* ---- pane 3 */
.mail-read {
  min-width: 0; overflow-y: auto; padding: 0 0 28px; background: var(--panel);
  scroll-margin-top: calc(var(--topbar-h, 116px) + 14px);
}
.mrd-head {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 22px 12px; background: var(--panel); border-bottom: 1px solid var(--rule-soft);
}
.mrd-sub {
  margin: 0; flex: 1 1 auto; overflow-wrap: anywhere;
  /* h2 is styled as a section label elsewhere — uppercase and letter-spaced.
     A subject line is the sender's own sentence and is shown as written. */
  font-size: 16px; font-weight: 600; line-height: 1.3;
  text-transform: none; letter-spacing: -.01em; color: var(--ink);
}
.mrd-x { text-decoration: none; color: var(--sys-grey-2); font-size: 20px; line-height: 1; flex: none; }
.mrd-meta {
  display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 3px 10px;
  margin: 0; padding: 12px 22px 14px; font-size: 12.5px;
}
.mrd-meta dt { color: var(--sys-grey-2); }
.mrd-meta dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.mrd-att { list-style: none; margin: 0; padding: 0 22px 12px; display: flex; flex-wrap: wrap; gap: 8px; }
.mrd-att a {
  display: inline-grid; grid-template-columns: 14px auto auto; gap: 7px; align-items: center;
  padding: 6px 11px; border: 1px solid var(--sys-sep); border-radius: 8px;
  text-decoration: none; color: var(--ink); font-size: 12px; background: var(--sys-fill-2);
}
.mrd-att a:hover { border-color: var(--sys-blue); }
.mrd-att svg { width: 14px; height: 14px; display: block; color: var(--sys-grey-2); }
.ma-n { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ma-s { color: var(--sys-grey-2); font-variant-numeric: tabular-nums; }
.mrd-warn {
  margin: 0 22px 14px; padding: 8px 12px; border-radius: 8px; font-size: 11.5px;
  background: #FFF6E0; color: #7A5300; border: 1px solid #F3DFAE;
}
.mrd-foot { margin: 22px 22px 0; padding-top: 14px; border-top: 1px solid var(--rule-soft);
            font-size: 11.5px; color: var(--sys-grey-2); }

/* The message body itself. Everything inside .mrd-body was written by someone
   outside the campaign, so it is boxed in here: it cannot set its own colours
   or positions (the sanitiser drops style, the CSP would drop it anyway), and
   these rules stop a wide table or a long URL from stretching the layout.

   The reset below is the other half of that. Almost every HTML newsletter is
   built out of nested <table>, and this stylesheet dresses tables as data
   tables for the rest of the QG — th uppercased and letter-spaced, table given
   min-width: 560px. Applied to an email that turned the whole message into
   SHOUTED CAPITALS several columns wider than the pane. Inside the reading
   pane the app's own element styling is switched off and the markup is allowed
   to be ordinary HTML again. */
.mrd-body {
  padding: 4px 22px 0; font-size: 13.5px; line-height: 1.55;
  color: var(--ink); overflow-wrap: anywhere;
}
.mrd-body table, .mrd-body thead, .mrd-body tbody, .mrd-body tfoot,
.mrd-body tr, .mrd-body td, .mrd-body th,
.mrd-body h1, .mrd-body h2, .mrd-body h3, .mrd-body h4, .mrd-body h5, .mrd-body h6,
.mrd-body p, .mrd-body div, .mrd-body span, .mrd-body font, .mrd-body li {
  text-transform: none; letter-spacing: normal; white-space: normal;
}
.mrd-body table {
  width: auto; max-width: 100%; min-width: 0; border-collapse: collapse;
  font-size: inherit; table-layout: auto;
}
.mrd-body td, .mrd-body th {
  padding: 3px 6px; vertical-align: top; border: 0; background: none;
  font-size: inherit; font-weight: inherit; color: inherit; text-align: left;
}
.mrd-body th { font-weight: 600; }
.mrd-body img { max-width: 100%; height: auto; }
.mrd-body blockquote {
  margin: 10px 0; padding-left: 12px; border-left: 2px solid var(--sys-sep); color: var(--sys-grey-2);
}
.mrd-body pre { white-space: pre-wrap; overflow-x: auto; }
.mrd-body a { color: var(--sys-blue); }
.mrd-body h1, .mrd-body h2, .mrd-body h3,
.mrd-body h4, .mrd-body h5, .mrd-body h6 {
  color: var(--ink); font-weight: 600; margin: 14px 0 6px;
}
.mrd-body h1 { font-size: 17px; }
.mrd-body h2 { font-size: 15.5px; }
.mrd-body h3, .mrd-body h4, .mrd-body h5, .mrd-body h6 { font-size: 14px; }
.mrd-body hr { border: 0; border-top: 1px solid var(--rule-soft); margin: 14px 0; }

.mailbody {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word;
  background: var(--sys-fill); border: 1px solid var(--sys-sep); border-radius: 8px;
  padding: 16px; margin: 0; max-height: 520px; overflow: auto;
}
.mail-read .msg-body { margin: 4px 22px 0; background: transparent; padding: 0; max-height: none; }

/* Below three panes, stack: folders become a scrolling strip, and the message
   sits under the list rather than beside it. */
@media (max-width: 1080px) {
  .mailapp { grid-template-columns: 190px minmax(0, 1fr); height: auto; }
  .mail-read { grid-column: 1 / -1; border-top: 1px solid var(--sys-sep); max-height: none; }
  .maillist { max-height: 46vh; }
}
@media (max-width: 720px) {
  .mailapp { grid-template-columns: minmax(0, 1fr); }
  .mail-side { border-right: 0; border-bottom: 1px solid var(--sys-sep); }
  .mail-fld { display: flex; gap: 6px; overflow-x: auto; padding: 8px 10px; }
  /* A flex item shrinks by default, which turned every folder into an
     ellipsis. The strip scrolls sideways instead. */
  .mf { grid-template-columns: 16px auto auto; flex: 0 0 auto; }
  .mf-n { overflow: visible; }
  .mail-ro { display: none; }
  .mail-list { border-right: 0; }
}

/* --------------------------------------------- foldable timeline groups */

.gws { margin: 0; }
.gws > summary



.gws-chev { display: inline-grid; place-items: center; width: 12px; height: 12px; flex: 0 0 auto; }
.gws-chev svg { width: 11px; height: 11px; fill: none; stroke: var(--sys-grey-2); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.gws-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.gws-n { font-size: 11.5px; color: var(--sys-grey-2); font-variant-numeric: tabular-nums; margin-left: 4px; }

@media (prefers-reduced-motion: no-preference) {
  .gws-chev { transition: transform .15s ease; }
}

/* =====================================================================
   Timeline — label column + timeline column.
   Bars used to carry their own text, which meant a two-week task showed
   "Autorisation p…". The name now lives in a fixed column that never
   shrinks, and the bar is purely positional.
   ===================================================================== */

.gantt { position: relative; min-width: 860px; }

.ghead { display: flex; align-items: flex-end; gap: 0; padding-bottom: 6px; }

.ghead .gtime { position: relative; height: 18px; }
.month { position: absolute; font-size: 11px; color: var(--sys-grey-2); white-space: nowrap; transform: translateX(3px); }


.gtime  { flex: 1 1 auto; position: relative; min-width: 0; }

/* Grid lines and markers overlay only the timeline half. */

.gline  { position: absolute; top: 0; bottom: 0; width: 1px; background: #F0F0F3; }
.marker { position: absolute; top: 0; bottom: 0; width: 2px; }
.marker i {
  position: absolute; top: -2px; left: 4px; font-style: normal;
  font-size: 10px; font-weight: 600; white-space: nowrap;
  padding: 2px 6px; border-radius: 4px; color: #fff;
}
.marker-today   { background: var(--sys-blue); }
.marker-today i { background: var(--sys-blue); }
.marker-ballots { background: var(--sys-orange); }
.marker-ballots i { background: var(--sys-orange); }
.marker-vote    { background: #1D1D1F; }
.marker-vote i  { background: #1D1D1F; }

/* ---- group header ---- */

.gws { margin: 0 0 4px; position: relative; z-index: 2; }

.gsum::-webkit-details-marker { display: none; }

.gws-chev { display: inline-grid; place-items: center; width: 12px; height: 12px; flex: 0 0 auto; }
.gws-chev svg { width: 11px; height: 11px; fill: none; stroke: var(--sys-grey); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.gws[open] > .gsum .gws-chev { transform: rotate(90deg); }
.gws-dot  { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.gws-name { font-size: 14.5px; font-weight: 600; }


@media (prefers-reduced-motion: no-preference) { .gws-chev { transition: transform .15s ease; } }

/* ---- expanded group: stats strip ---- */








/* ---- rows ---- */


.grow:hover { background: rgba(0,0,0,.025); }

.gname:hover { color: var(--sys-blue); }
.grow.s-done 





.bar:hover { filter: brightness(1.12); }
.bar.crit  { box-shadow: 0 1px 2px rgba(0,0,0,.10), inset 0 0 0 2px rgba(255,255,255,.8); }
.bar.s-done { opacity: .3; }
.bar.s-todo { opacity: .85; }
.bar.s-blocked { background: repeating-linear-gradient(45deg, var(--sys-red) 0 6px, #8C2D1D 6px 12px); }

@media (max-width: 860px) {
  .gantt { min-width: 720px; }
  
  
}

/* ------------------------------------------------------- timeline zoom */

.headright { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.zoomer { display: inline-flex; align-items: center; gap: 2px; background: var(--sys-fill-2); border-radius: 8px; padding: 2px; }
.zb {
  width: 26px; height: 24px; border: 0; background: transparent; cursor: pointer;
  border-radius: 8px; font-size: 15px; line-height: 1; color: var(--ink);
}
.zb:hover { background: #fff; }
.zlab { font-size: 12px; color: var(--sys-grey); min-width: 62px; text-align: center; font-variant-numeric: tabular-nums; }

/* The timeline widens with zoom; bar positions are percentages so they
   scale for free. The label column sticks so names stay visible. */


.gsum   { position: sticky; left: 0; z-index: 6; width: max-content; min-width: 320px; }



.tk { position: absolute; font-size: 11px; color: var(--sys-grey-2); white-space: nowrap; transform: translateX(3px); }
.tk-w, .tk-d { display: none; }
.tk-d.we { color: #B9B9C0; }

.gline { position: absolute; top: 0; bottom: 0; width: 1px; background: #F0F0F3; }
.gl-w, .gl-d { display: none; }
.gl-d.we { background: #F6F6F8; }

.gantt[data-scale="week"] .tk-m,
.gantt[data-scale="day"]  .tk-m { display: none; }
.gantt[data-scale="week"] .tk-w,
.gantt[data-scale="day"]  .tk-w { display: block; }
.gantt[data-scale="day"]  .tk-d { display: block; }

.gantt[data-scale="week"] .gl-w,
.gantt[data-scale="day"]  .gl-w { display: block; }
.gantt[data-scale="day"]  .gl-d { display: block; }
.gantt[data-scale="day"]  .gl-m { background: #E2E2E8; }

/* --------------------------------------- inline task expansion in the timeline */

.gtask { margin: 0; }
.gtask > summary

.gtask > summary.grow:hover { background: rgba(0,0,0,.03); }
.gtask[open] > summary
.gtask[open] > summary.grow 


.gtask > summary.grow:hover 
.gtask > summary.grow.s-done 










/* ------------------------------------------- timeline density (restored) */

/* Group header: a slim band pinned to the left, not a slab across the chart.
   The counts that used to sit in a separate 75px stats grid live here. */

.gsum::-webkit-details-marker { display: none; }

.gws-name { font-size: 13.5px; font-weight: 600; }
.gws-meta { display: flex; align-items: center; gap: 7px; margin-left: 6px; }
.gm {
  font-size: 11px; color: var(--sys-grey); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.gm-crit { color: var(--sys-orange); font-weight: 600; }
.gm-late { color: var(--sys-red); font-weight: 600; }
.gm-win  { color: var(--sys-grey-2); }

/* Every row the same height, so the chart reads as a chart. Two lines of
   title fit; anything longer clamps rather than pushing the row taller. */


.gtask > summary.grow:hover { background: rgba(0,0,0,.03); }

.gtask[open] > summary.grow 









/* =====================================================================
   Timeline — bars carry their own labels. Clicking one makes it grow
   taller and reveal its detail, rather than sending you elsewhere.
   ===================================================================== */

.gantt { width: calc(100% * var(--z, 1)); min-width: calc(760px * var(--z, 1)); position: relative; }
.ghead { padding-bottom: 6px; }
.ghead .gtime { position: relative; height: 18px; }
.gmarks { position: absolute; left: 0; right: 0; top: 26px; bottom: 0; pointer-events: none; z-index: 0; }

/* Group header: transparent, not a shaded button. */
.gsum {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 8px;
  position: sticky; left: 0; z-index: 6;
  width: max-content; max-width: 100%;
  margin: 16px 0 4px; padding: 3px 4px;
  background: transparent; border-radius: 8px;
}
.gsum::-webkit-details-marker { display: none; }
.gsum:hover .gws-name { color: var(--sys-blue); }
.gws-name { font-size: 13px; font-weight: 600; letter-spacing: .01em; }
.gws-meta { display: flex; align-items: center; gap: 7px; margin-left: 4px; }

/* Rows */
/* The hand belongs on the bar, not on the row. A row is the full width of
   the timeline; its bar is about a tenth of that, so `cursor: pointer` here
   promised a click across empty track three months from the task. Clicking
   the row DOES still open the card — this is a <summary> and that behaviour
   is deliberate — so the hand now under-promises rather than over-promises,
   which is the safe direction for a control you cannot see. */
.row { position: relative; height: 32px; display: block; list-style: none; cursor: default; }
.row::-webkit-details-marker { display: none; }
.gtask { margin: 0; }

/* Every bar holds its own title. --mw is a per-bar floor width, computed
   server-side from the title, so no label ever sits outside its bubble. */
.bar {
  cursor: pointer;                      /* the pill is the affordance; see .row above */
  position: absolute; top: 4px; height: 24px;
  border-radius: 12px; display: flex; align-items: center;
  padding: 0 10px; overflow: hidden;
  background: hsl(var(--hue) var(--chantier-s) var(--chantier-l-solid));
  box-shadow: 0 1px 2px rgba(0,0,0,.10);
  min-width: max-content; z-index: 2;
}
/* Narrow bars finishing near the right edge grow leftwards from their end
   date, so the floor width can never push them off the track. */

/* A bar can be wider than the visible timeline. Sticky keeps its title in
   view while it is scrolled past; the bar's own box clamps the slide, so the
   title never leaves its bubble. */
/* Collapsed, the wrappers the open card needs must not exist as far as the
   pill's flex layout is concerned. */
.btx { display: contents; }
.gtask:not([open]) .bpic { display: none; }

.bar .bl {
  position: sticky; left: 0;
  font-size: 11.5px; line-height: 1; font-weight: 500; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
.bar .bd { display: none; }


/* Completeness marks: what the card behind the bar already holds. Hollow
   means missing, solid means done — legible at a glance across the whole
   plan without opening anything. They sit inside the bar, so the bar's
   max-content floor width accounts for them and nothing gets clipped. */
/* The little image / note / checklist marks that used to sit inside each bar
   are gone at the user's request. A side effect worth knowing: the bar's
   max-content floor no longer has to accommodate them, so every bar is now
   narrower and slightly closer to its true duration. */

/* Open, the marks belong with the title rather than the pill. */

.row:hover .bar { filter: brightness(1.1); z-index: 5; }
.bar.crit { box-shadow: 0 1px 2px rgba(0,0,0,.10), inset 0 0 0 2px rgba(255,255,255,.8); }
.bar.s-done { opacity: .32; }
.bar.s-done .bl { text-decoration: line-through; text-decoration-thickness: 1px; }
.bar.s-todo { opacity: .88; }
.bar.s-blocked { background: repeating-linear-gradient(45deg, var(--sys-red) 0 6px, #8C2D1D 6px 12px); }
.bar.late { box-shadow: 0 1px 2px rgba(0,0,0,.10), inset 0 0 0 2px var(--sys-red); }

/* ---- the bloom ---- */

/* ---- the bloom: bar and body are one bubble ---- */

/* One card width for both halves. min and max together pin the used width,
   which the generated per-bar rule (an id selector) would otherwise win. */
.gantt { --card-w: 480px; }
@media (max-width: 1180px) { .gantt { --card-w: 420px; } }
@media (max-width: 900px)  { .gantt { --card-w: 360px; } }

/* One padding for the whole card, so the banner, the note field and the
   footer all sit on the same left edge. */
.gantt { --card-pad: 20px; }

/* 4px top offset + 145px bar = 149px, so the bar finishes flush with the
   row and the body below it meets the bar with no seam and no overlap. */
.gtask[open] > .row { height: 149px; }
.gtask[open] > .row .bar {
  height: 145px; min-width: var(--card-w); max-width: var(--card-w);
  border-radius: 20px 20px 0 0;
  align-items: flex-start; flex-direction: row; justify-content: flex-start;
  padding: var(--card-pad) var(--card-pad) 24px;
  gap: 15px; z-index: 6; opacity: 1;
  box-shadow: 0 8px 24px rgba(0,0,0,.16);
}
.gtask[open] .btx {
  display: flex; flex-direction: column; gap: 7px;
  min-width: 0; flex: 1 1 auto; padding-top: 2px;
}
.gtask[open] > .row .bar .bl {
  position: static; font-size: 15px; font-weight: 600;
  white-space: normal; overflow: visible; line-height: 1.3; max-width: 100%;
}
.gtask[open] > .row .bar .bd {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  font-size: 11.5px; color: rgba(255,255,255,.92);
  font-variant-numeric: tabular-nums; line-height: 1.5;
}

/* The banner, top-left of the bubble. Drop a file on it or click it. */
/* Always square, whatever the image. */
.bpic {
  flex: 0 0 auto; width: 96px; aspect-ratio: 1 / 1; border-radius: 11px;
  overflow: hidden; position: relative; cursor: pointer;
  background: rgba(0,0,0,.16);
  display: flex; align-items: center; justify-content: center;
}
.bpic-hit {
  position: absolute; inset: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  border-radius: inherit; overflow: hidden;
}
.bpic img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The second way in, tucked into the corner of the box. */
.bpic-find {
  position: absolute; right: 4px; bottom: 4px; z-index: 2;
  width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.34); color: #fff; cursor: pointer;
}
.bpic-find:hover { background: var(--sys-blue); }
.bpic-find:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.bpic-find svg { width: 13px; height: 13px; display: block; }
.bpic.empty { border: 1.5px dashed rgba(255,255,255,.55); background: rgba(255,255,255,.08); }
.bpic:hover { background: rgba(255,255,255,.16); }
.bpic.over { border-color: #fff; background: rgba(255,255,255,.3); }
.bpic-hint {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  color: rgba(255,255,255,.92); font-size: 10.5px; text-align: center;
  padding: 0 8px; line-height: 1.3;
}
.bpic-hint svg { width: 21px; height: 21px; }
.bd-sep { opacity: .5; }
.bd-tag {
  background: rgba(255,255,255,.24); border-radius: 20px;
  padding: 1px 8px; font-weight: 600; letter-spacing: .02em;
}
.bd-ch svg { width: 12px; height: 12px; fill: #fff; opacity: .9; display: block; }
.bd-note { flex-basis: 100%; opacity: .88; line-height: 1.4; }

/* ---- the lower half of the bubble ---- */

.gbody {
  display: none; position: relative; z-index: 5;
  width: var(--card-w); box-sizing: border-box;
  margin-bottom: 16px;
  padding: 14px var(--card-pad) var(--card-pad); border-radius: 0 0 20px 20px;
  background: hsl(var(--hue) var(--chantier-s) var(--chantier-l-solid));
  box-shadow: 0 8px 24px rgba(0,0,0,.16);
}
.gtask[open] > .gbody { display: block; }
.gb-hidden { display: none; }

/* Reads as plain text until you touch it, then becomes a proper editor. */
.gb-nz {
  display: block; width: 100%; box-sizing: border-box; resize: vertical;
  font: inherit; font-size: 13px; line-height: 1.5; color: #fff;
  background: rgba(255,255,255,.10); border: 1px solid transparent;
  border-radius: 12px; padding: 10px 12px; cursor: text;
}
.gb-nz::placeholder { color: rgba(255,255,255,.68); }
.gb-nz:hover { background: rgba(255,255,255,.16); }
.gb-nz:focus {
  outline: none; background: #fff; color: var(--ink);
  border-color: rgba(0,0,0,.10); box-shadow: 0 2px 10px rgba(0,0,0,.14);
}
.gb-nz:focus::placeholder { color: var(--sys-grey-2); }

/* Checklist */
.gb-item {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 0; cursor: pointer;
}
.gb-item input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; }
.gb-tick {
  flex: 0 0 auto; width: 19px; height: 19px; border-radius: 50%;
  border: 1.6px solid rgba(255,255,255,.75);
  display: flex; align-items: center; justify-content: center;
  color: transparent;
}
.gb-tick svg { width: 13px; height: 13px; }
.gb-item.on .gb-tick { background: #fff; border-color: #fff; color: hsl(var(--hue) var(--chantier-s) var(--chantier-l-deep)); }
.gb-plus { color: rgba(255,255,255,.75); font-size: 15px; line-height: 1; border-style: dashed; }
.gb-itext {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13px; color: #fff;
  background: transparent; border: 0; border-radius: 8px; padding: 4px 7px;
}
.gb-itext::placeholder { color: rgba(255,255,255,.62); }
.gb-itext:hover { background: rgba(255,255,255,.12); }
.gb-itext:focus { outline: none; background: #fff; color: var(--ink); }
.gb-item.on .gb-itext { opacity: .62; text-decoration: line-through; }
.gb-item.on .gb-itext:focus { opacity: 1; text-decoration: none; }

/* Footer: mode switch, Rappels, status. All inside the bubble — nothing
   floats beneath it. */
.gb-row { margin-top: 12px; display: flex; justify-content: flex-end; gap: 8px; }

/* Saving is a tick, not a word. */
.gb-ok {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.20); color: #fff; cursor: pointer;
}
.gb-ok:hover { background: #fff; color: hsl(var(--hue) var(--chantier-s) var(--chantier-l-deep)); }
.gb-ok:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.gb-check svg { width: 17px; height: 17px; display: block; }

/* Footer icon buttons — same pill as .gb-b, sized for a glyph. */
.gb-ico {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.18); color: #fff; cursor: pointer;
}
.gb-ico:hover { background: rgba(255,255,255,.34); }
.gb-ico:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.gb-ico svg { width: 17px; height: 17px; display: block; }
.gb-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 14px; padding-top: 13px;
  border-top: 1px solid rgba(255,255,255,.22);
}
.gb-inline { display: inline-flex; }
.gb-b {
  font: inherit; font-size: 11.5px; font-weight: 500; color: #fff;
  background: rgba(255,255,255,.18); border: 0; border-radius: 20px;
  padding: 6px 13px; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center;
}
.gb-b:hover { background: rgba(255,255,255,.30); }
.gb-b:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.gb-seg {
  display: inline-flex; margin-left: auto; gap: 2px;
  background: rgba(0,0,0,.16); border-radius: 20px; padding: 2px;
}
.gb-sb {
  width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: rgba(255,255,255,.88); background: transparent; cursor: pointer;
}
.gb-sb svg { width: 17px; height: 17px; display: block; }
.gb-sb:hover { background: rgba(255,255,255,.16); }
.gb-sb.on { background: #fff; color: hsl(var(--hue) var(--chantier-s) var(--chantier-l-deepest)); font-weight: 600; }



@media (prefers-reduced-motion: no-preference) {
  /* min-width is not on this list: it animates from max-content, which is
     not interpolable, so it would snap anyway. Height carries the bloom. */
  .row, .bar { transition: height .24s cubic-bezier(.32,.72,0,1), border-radius .24s ease, padding .24s ease, box-shadow .24s ease; }
}

/* ------------------------------------------------- sidebar fold handle */

.shell {
  position: relative;
  max-width: 1560px; margin: 0 auto; padding: 20px 22px 70px;
  display: grid;
  /* Columns are not set here. This rule owns the shell's box; the single
     "shell column geometry" block near the end of the file owns its tracks. */
  gap: 22px; align-items: start;
}
/* The collapsed width is set with the rest of the columns, in the geometry
   owner near the end of the file. Only the visibility belongs here. */
.shell.side-hidden .side { display: none; }

/* The edge itself: a hairline you can grab to resize. */
.side-edge {
  position: absolute; top: 20px; bottom: 70px;
  left: calc(22px + var(--side-col) + 10px);
  width: 9px; cursor: col-resize; z-index: 18;
}
.side-edge::after {
  content: ""; position: absolute; inset: 0 4px;
  background: transparent; border-radius: 2px;
}
.side-edge:hover::after, .side-edge.dragging::after { background: var(--sys-blue); }
.shell.side-hidden .side-edge { display: none; }

/* The handle rides a full-height rail on the boundary and sticks to the
   middle of the viewport, so it is always within reach however far the
   timeline is scrolled. */
.side-rail {
  position: absolute; top: 20px; bottom: 70px;
  left: calc(22px + var(--side-col) - 2px);
  width: 38px; pointer-events: none; z-index: 22;
}
.shell.side-hidden .side-rail { left: 2px; }

.side-handle {
  position: sticky; top: calc(50vh - 19px);
  width: 38px; height: 38px; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--sys-blue); color: #fff;
  /* A soft halo rather than a hard ring of page colour: the old 3px collar
     cut a visible notch wherever the button met the panel behind it. */
  box-shadow: 0 0 0 5px rgba(255,255,255,.92), 0 3px 10px rgba(0,0,0,.14);
  cursor: pointer; pointer-events: auto;
}
.side-handle:hover { background: #0062CC; }
.side-handle:focus-visible { outline: 2px solid var(--sys-blue); outline-offset: 4px; }
.sh-chev { width: 19px; height: 19px; display: block; }

/* Folded: chevron reversed, so it reads as "bring the list back". */
.shell.side-hidden .sh-chev { transform: rotate(180deg); }

/* On hover the button leans the way it is about to travel — left to close,
   right to bring the list back — so the direction is legible before you
   commit to the click. */
.side-handle:hover        { transform: translateX(-4px); }
.side-handle:active       { transform: translateX(-4px) scale(.9); }
.side-hidden .side-handle:hover  { transform: translateX(4px); }
.side-hidden .side-handle:active { transform: translateX(4px) scale(.9); }

@media (prefers-reduced-motion: no-preference) {
  /* The rail travels the whole width of the sidebar, so it wants a longer,
     softer curve than a button press. */
  .side-rail   { transition: left .34s cubic-bezier(.32,.72,0,1); }
  .side-handle { transition: background .18s ease,
                             transform .22s cubic-bezier(.32,.72,0,1),
                             box-shadow .18s ease; }
  .sh-chev { transition: transform .34s cubic-bezier(.32,.72,0,1); }
  .side-edge::after { transition: background .15s ease; }
  /* The panel gives way at the same pace, so the two do not fight. */
  .shell { transition: grid-template-columns .34s cubic-bezier(.32,.72,0,1); }
}

@media (max-width: 720px) {
  .side-rail, .side-edge { display: none; }
}

/* ------------------------------------------- foldable timeline panel */

/* Clicking a panel's header does not hide it: it sends that whole section
   below the other, which rises to take its place. Both stay in full view —
   .main is already a flex column, so one order change does it. */
.panel-swap > .panel-head { cursor: pointer; }
.panel-swap > .panel-head:hover h2 { color: var(--sys-blue); }
.panel-swap > .panel-head:focus-visible {
  outline: 2px solid var(--sys-blue); outline-offset: -2px; border-radius: 12px;
}
.panel-swap.demoted { order: 1; }


/* ------------------------------------------------- topbar alignment */

/* "180" is set larger than "jours" beside it, which made the countdown's
   line box taller than its neighbours and pushed the row out of true.
   Flattening the line heights and aligning the two on their baseline puts
   the countdown, the user and the button back on one line. */
.topbar-right { display: flex; align-items: center; gap: 14px; }
.countdown {
  display: inline-flex; align-items: baseline; gap: 5px;
  font-size: 13px; line-height: 1; color: var(--sys-grey-1);
}
.countdown strong { font-size: 16px; line-height: 1; }
.who { font-size: 13px; line-height: 1; color: var(--sys-grey-1); }
.topbar-right .btn { line-height: 1; }

/* ------------------------------------------------------- wide mode */

/* Drops the 1560px reading cap so the layout runs to the edges of the
   window. A page cannot resize or maximise the browser window itself —
   that is the window manager's job — so this is the honest version of
   "use the whole screen": the content stops being centred in a column. */
body.wide .topbar-in,
body.wide .tabs,
body.wide .shell { max-width: none; }
body.wide .topbar-in,
body.wide .tabs { padding-left: 16px; padding-right: 16px; }
body.wide .shell { padding-left: 16px; padding-right: 16px; }
body.wide .side-edge { left: calc(16px + var(--side-col) + 10px); }
body.wide .side-rail { left: calc(16px + var(--side-col) + 3px); }

.wide-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
}
.wide-toggle svg { width: 16px; height: 16px; display: block; }
.wide-toggle[aria-pressed="true"] { background: var(--sys-blue); color: #fff; }

/* --------------------------------------------------- password form */

.pwform { display: flex; flex-direction: column; gap: 6px; max-width: 380px; }
.pwform label { font-size: 12.5px; font-weight: 600; color: var(--ink); margin-top: 8px; }
.pwform input[type="password"] {
  font: inherit; font-size: 14px; padding: 9px 11px;
  border: 1px solid var(--sys-sep); border-radius: 8px; background: #fff;
}
.pwform input[type="password"]:focus {
  outline: none; border-color: var(--sys-blue);
  box-shadow: 0 0 0 3px rgba(0,122,255,.16);
}
.pwform .para { margin: 12px 0 4px; }
.pwform button { margin-top: 6px; align-self: flex-start; }
.alert-ok {
  background: rgba(52,199,89,.12); border-color: rgba(52,199,89,.35);
  color: #1B7F3B;
}

.ok-line {
  background: rgba(52,199,89,.12); border: 1px solid rgba(52,199,89,.35);
  color: #1B7F3B; border-radius: 12px; padding: 10px 12px; font-size: 13.5px;
}

/* ------------------------------------------- add and edit a task */

.sidehead {
  display: flex; align-items: center; justify-content: space-between;
  /* Same inset as a group row, so the label starts on the chevron column
     and the + lines up with the counts down the list. */
  padding: 0 10px 8px;
}
.sidehead-t {
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sys-grey-2);
}
/* ------------------------------------------------------ icon buttons

   Three square icon buttons with one skeleton and two axes. They were written
   out separately — .addplus here, .tdel-b twice further down, .gws-exp further
   down still — so the five declarations that make a square icon button
   appeared three times over, and the differences between them were recorded
   nowhere.

   SIZE is the button's weight in the layout. KIND is whether it is an action
   you reach for (filled) or one that stays quiet until wanted (ghost).

   Radius does NOT track size, and that is deliberate rather than drift:
   .addplus is a circle at 26px where a ghost button of the same width is a
   rounded square. The circle reads as "add", and it is the only filled one.

   The fourth square icon button, the one on the .c3-* board, is deliberately
   NOT here. That board is being rewritten to WebGL by the 3-D lane, and
   folding it in now would collide with that rewrite. Deferred to that lane.

   Every value below is the one it replaced. This is deduplication, not
   restyling — the rendered pixels are unchanged, and that was measured. */
.addplus, .tdel-b, .gws-exp {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: 0; cursor: pointer;
  width: var(--ibtn-size); height: var(--ibtn-size);
  border-radius: var(--ibtn-radius);
  background: var(--ibtn-bg); color: var(--ibtn-fg);
}
.addplus svg, .tdel-b svg, .gws-exp svg {
  width: var(--ibtn-icon); height: var(--ibtn-icon); display: block;
}

/* size axis */
.tdel-b  { --ibtn-size: 30px; --ibtn-radius: 8px; --ibtn-icon: 20px; }
.addplus { --ibtn-size: 26px; --ibtn-radius: 50%; --ibtn-icon: 15px; }
.gws-exp { --ibtn-size: 24px; --ibtn-radius: 6px; --ibtn-icon: 15px; }

/* kind axis */
.addplus { --ibtn-bg: var(--sys-blue); --ibtn-fg: #fff; }             /* filled */
.tdel-b  { --ibtn-bg: none; --ibtn-fg: var(--sys-grey-1); }           /* ghost  */
.gws-exp { --ibtn-bg: none; --ibtn-fg: var(--sys-grey-2); }           /* ghost  */

.addplus:hover { background: #0062CC; }
.addplus:active { transform: scale(.92); }
.addplus:focus-visible { outline: 2px solid var(--sys-blue); outline-offset: 2px; }

/* An entry opens into its own editor, in place in the list. */
.tedit { flex: 1 1 auto; min-width: 0; }
.tedit > summary.tlink { cursor: pointer; list-style: none; }
.tedit > summary.tlink::-webkit-details-marker { display: none; }


.tform input[type="text"], .tform input[type="date"], .tform select {
  font: inherit; font-size: 12.5px; padding: 6px 8px; width: 100%;
  border: 1px solid var(--sys-sep); border-radius: 8px;
  background: #fff; box-sizing: border-box;
}
.tform input:focus, .tform select:focus {
  outline: none; border-color: var(--sys-blue);
  box-shadow: 0 0 0 3px rgba(0,122,255,.15);
}
.tf-title { font-weight: 600; }
.tf-dates { display: flex; gap: 6px; }
.tf-dates input { flex: 1 1 0; min-width: 0; }
.tf-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tf-crit { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
.tf-crit input { margin: 0; }
.tf-save {
  width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--sys-blue); color: #fff; cursor: pointer;
}
.tf-save:hover { background: #0062CC; }
.tf-save svg { width: 15px; height: 15px; display: block; }

.tform { padding: 8px 0 6px 2px; }
.tf-row { display: flex; gap: 6px; align-items: center; }
.tf-row .tf-title { flex: 1 1 auto; min-width: 0; }
.tf-row .tf-save, .tf-row .tdel-b { flex: 0 0 auto; }
.tform input[type="text"] {
  font: inherit; font-size: 12.5px; padding: 6px 8px; width: 100%;
  border: 1px solid var(--sys-sep); border-radius: 8px;
  background: #fff; box-sizing: border-box; font-weight: 600;
}
.tform input:focus {
  outline: none; border-color: var(--sys-blue);
  box-shadow: 0 0 0 3px rgba(0,122,255,.15);
}

.tdel-b:focus-visible { outline: 2px solid var(--sys-red); outline-offset: 1px; }

/* The title wants a full line of its own on the card. */
.gb-f-wide { flex: 1 1 100%; }
.gb-f-wide input { max-width: none !important; width: 100%; }

/* The header's own + sits at the end of the legend row. */
.headadd { display: inline-flex; margin-left: auto; }
.panel-swap > .panel-head .headright { display: flex; align-items: center; gap: 14px; flex: 1 1 auto; }

/* ------------------------------------------------------- day window */

.cal-open-day {
  display: inline-flex; text-decoration: none; border-radius: 50%;
  position: relative; z-index: 2;
}
.cal-open-day:hover .cal-n { background: var(--sys-blue); color: #fff; }

/* The scrim covers the page, but not the topbar: an overlay that swallows
   the navigation leaves you stuck if you meant to go somewhere else, and
   the tabs were unreachable while a day was open. The bar is raised above
   it, and the window is inset below it so it can never hide behind it. */
.daywrap {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: 108px 24px 24px;
}
.dayscrim { position: absolute; inset: 0; background: rgba(0,0,0,.34); }

.daywin {
  position: relative; z-index: 1;
  width: min(1040px, 100%);
  /* Bounded by what is left under the topbar, not by the whole viewport:
     a flex item taller than its container overflows past the start
     padding, which put the window back under the bar it was inset from. */
  max-height: min(calc(100vh - 140px), 1000px);
  display: flex; flex-direction: column;
  background: var(--sys-bg); border-radius: 20px;
  box-shadow: 0 24px 60px rgba(0,0,0,.28);
  overflow: hidden;
}
.daywin-head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px; background: #fff;
  border-bottom: 1px solid var(--sys-sep);
}
.daywin-head h2 { margin: 0; font-size: 16px; text-transform: capitalize; }
.daywin-n { font-size: 12.5px; color: var(--sys-grey-2); }
.daywin-x {
  margin-left: auto; width: 28px; height: 28px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); background: var(--sys-fill-2); text-decoration: none;
}
.daywin-x:hover { background: var(--sys-sep); color: var(--ink); }
.daywin-x svg { width: 14px; height: 14px; display: block; }

/* The list itself: the cards stack and the window scrolls. */
/* The same treatment as a chantier opened from its envelope: the day's cards
   sit beside one another and wrap, rather than running down a single column
   that has to be scrolled past. */
/* A grid rather than wrapping flex: the columns come out equal and the last
   row's lone card keeps its column instead of stretching across the window.
   (This container is a plain div, so it has none of the ::details-content
   trouble the chantier cards had.) */
.daywin-scroll {
  overflow-y: auto; padding: 16px;
  display: grid; align-content: start; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
/* The card keeps its natural height; the window scrolls. --card-w is what the
   open-card layout measures itself against, so it follows the column. */
.daywin-scroll > .daycard { --card-w: 100%; min-width: 0; }

/* A card here is the timeline's card unpinned from the timeline. */
.daycard {
  border-radius: 20px; overflow: hidden;
  background: hsl(var(--hue) var(--chantier-s) var(--chantier-l-solid));
  box-shadow: 0 4px 14px rgba(0,0,0,.12);
}
.daycard-top {
  display: flex; align-items: flex-start; gap: 15px;
  padding: var(--card-pad, 20px) var(--card-pad, 20px) 0;
}
.daycard .btx { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 1 1 auto; padding-top: 2px; }
.daycard .bl {
  position: static; font-size: 15px; font-weight: 600; color: #fff;
  white-space: normal; overflow: visible; line-height: 1.3;
}
.daycard .bd { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: rgba(255,255,255,.92); }
.daycard .gbody {
  display: block; position: static; width: auto; margin: 0;
  padding: 14px var(--card-pad, 20px) var(--card-pad, 20px);
  border-radius: 0; background: transparent; box-shadow: none;
}

@media (max-width: 640px) {
  .daywrap { padding: 10px; }
  .daywin { max-height: 92vh; }
}

/* ------------------------------------------------------- work clock */

.worklog {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; line-height: 1; color: var(--sys-grey-1);
  font-variant-numeric: tabular-nums;
}
/* Size lives with the other topbar glyphs; only the muting is set here. */
.worklog svg { display: block; opacity: .7; }

/* ------------------------------------------------- density scrubber */

/* The whole campaign on one strip, in place of a month stepper. Each
   column is a day; its height is how many tasks fall due then, so the
   crowded weeks are visible before you reach them. */
.scrub { padding: 2px 20px 14px; user-select: none; }

.scrub-days {
  display: flex; align-items: flex-end; gap: 1px;
  height: 34px; padding: 0 0 3px;
  border-bottom: 1px solid var(--sys-sep);
}
.scrub-d {
  flex: 1 1 0; min-width: 0; height: 100%;
  display: flex; align-items: flex-end; text-decoration: none;
  border-radius: 2px;
}
.scrub-d > span {
  display: block; width: 100%; border-radius: 2px;
  /* A day with nothing still shows a hairline, so the strip reads as a
     ruler rather than as scattered marks. */
  height: calc(2px + var(--w, 0) * 26px);
  background: var(--sys-grey-4, #D9D9DE);
}
.scrub-d.we > span { background: #E6E6EB; }
.scrub-d.nil > span { opacity: .5; }

/* The month you are looking at. */
.scrub-d.in > span { background: var(--sys-blue); }
.scrub-d.in.we > span { background: #7FB4F5; }

/* Days carrying four or more deadlines: the ones worth moving. */
.scrub-d.hot > span { background: var(--sys-orange); }
.scrub-d.in.hot > span { background: var(--sys-red); }

.scrub-d:hover > span { background: var(--ink); }
.scrub-d.now > span { background: var(--sys-blue); box-shadow: 0 0 0 1px var(--sys-blue); }
.scrub-d:focus-visible { outline: 2px solid var(--sys-blue); outline-offset: 1px; }

.scrub-months { display: flex; gap: 1px; padding-top: 5px; }
.scrub-m {
  flex: 0 1 0; min-width: 0; text-align: center;
  font-size: 10px; letter-spacing: .04em; color: var(--sys-grey-2);
  text-decoration: none; padding: 3px 0; border-radius: 8px;
  white-space: nowrap; overflow: hidden;
}
.scrub-m:hover { background: var(--sys-fill-2); color: var(--ink); }
.scrub-m.on { color: var(--sys-blue); font-weight: 700; background: rgba(0,113,227,.10); }

.cal-label { font-size: 13px; font-weight: 600; color: var(--sys-grey-1); margin-left: auto; }

@media (prefers-reduced-motion: no-preference) {
  .scrub-d > span { transition: background .12s ease; }
}

/* ------------------------------------------- Openverse image search */

.ovbox { margin: 10px 0 4px; }
.ovbar { display: flex; gap: 7px; }
.ovq {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13px;
  padding: 7px 10px; border: 0; border-radius: 8px;
  background: rgba(255,255,255,.92); color: var(--ink);
}
.ovq:focus { outline: 2px solid #fff; outline-offset: 1px; }
.ovnote { margin: 8px 0 0; font-size: 11.5px; color: rgba(255,255,255,.92); min-height: 1em; }
.ovgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 6px; margin-top: 9px; max-height: 240px; overflow-y: auto;
}
.ovpick {
  padding: 0; border: 0; border-radius: 8px; overflow: hidden;
  aspect-ratio: 1 / 1; cursor: pointer; background: rgba(0,0,0,.16);
}
.ovpick img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ovpick:hover { box-shadow: 0 0 0 2px #fff; }
.ovpick:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.ovcredit {
  margin: 8px 0 0; font-size: 11px; line-height: 1.45;
  color: rgba(255,255,255,.80);
}
.ovcredit a { color: #fff; }

/* ------------------------------------------- chantier descriptions */

/* A name alone does not say what belongs in a chantier. Hovering the row
   explains it, without a click and without taking any space when idle. */
.grp-wrap > summary.grp { position: relative; }
.grp-tip {
  position: absolute; left: 8px; right: 8px; top: calc(100% + 4px);
  z-index: 60; padding: 10px 12px;
  background: var(--ink); color: #fff;
  font-size: 11.5px; font-weight: 400; line-height: 1.5; letter-spacing: 0;
  border-radius: 12px; box-shadow: 0 8px 22px rgba(0,0,0,.24);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  pointer-events: none;
}
.grp-wrap > summary.grp:hover .grp-tip,
.grp-wrap > summary.grp:focus-visible .grp-tip {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition-delay: .35s;                 /* only if you linger */
}
@media (prefers-reduced-motion: no-preference) {
  .grp-tip { transition: opacity .16s ease, transform .16s ease, visibility .16s; }
}

/* The strip magnifies under the cursor, so a crowded week can be read day
   by day without leaving the calendar. */
.scrub-read {
  height: 15px; font-size: 11px; color: var(--sys-grey-2);
  font-variant-numeric: tabular-nums; text-align: right; padding-right: 2px;
}
.scrub-read strong { color: var(--ink); font-weight: 600; }
.scrub-days.zoom .scrub-d > span { border-radius: 4px; }

@media (prefers-reduced-motion: no-preference) {
  .scrub-d { transition: flex-grow .12s ease-out; }
}

/* A tab with no task list is a single column, with no gap to fill — set
   with the other columns in the geometry owner near the end of the file. */

/* Population dots over the choropleth: area carries the number, so a
   village stays visible and Nyon does not swamp the map. */
.map .dots { pointer-events: none; }
.map .dots circle {
  fill: rgba(11, 22, 41, .42);
  stroke: rgba(255, 255, 255, .85);
  stroke-width: 1.4;
  vector-effect: non-scaling-stroke;
}


/* ------------------------------------------------------ commune labels */

.mapwrap { position: relative; }

/* Upright over a turned map, with a halo so a name reads over any fill. */
.map .labels { pointer-events: none; }
/* Size and halo come from the group, which the page scales with the frame,
   so a zoomed-in commune does not get a giant name. */
.map .labels text {
  font-family: inherit; font-weight: 600;
  fill: var(--ink); text-anchor: middle;
  paint-order: stroke fill;
  stroke: rgba(255,255,255,.94); stroke-linejoin: round;
}
.map .labels .lb-sel { fill: var(--sys-red); }

/* No outline on hover — the name alone is the feedback. A stroke here also
   read as a border the map does not really have. */

/* ------------------------------------------------- imagery backdrop */

/* Faint on purpose: it is there to say what the ground looks like, not to
   compete with the colours carrying the result. */
.map .basemap { opacity: .34; }
.map.has-base path { fill-opacity: .72; }

/* Zoomed onto one commune the photograph is the point, so it is shown at
   full strength with nothing over it: the choropleth becomes outlines
   only, and the commune you chose keeps its red edge. */
.map.zoomed.has-base .basemap { opacity: 1; }
.map.zoomed.has-base path { fill-opacity: 0; stroke: rgba(255,255,255,.9); stroke-width: 1.6; }
.map.zoomed.has-base path.sel { stroke: var(--sys-red); stroke-width: 3; fill-opacity: 0; }
.map.zoomed.has-base .dots circle { fill: rgba(11,22,41,.55); }
/* The pinch steps are for the district view; zoomed in it is already full. */
.map.zoomed.has-base.bz2 .basemap,
.map.zoomed.has-base.bz3 .basemap,
.map.zoomed.has-base.bz4 .basemap,
.map.zoomed.has-base.bz5 .basemap,
.map.zoomed.has-base.bz6 .basemap { opacity: 1; }
.map.zoomed.has-base.bz2 path, .map.zoomed.has-base.bz3 path,
.map.zoomed.has-base.bz4 path, .map.zoomed.has-base.bz5 path,
.map.zoomed.has-base.bz6 path { fill-opacity: 0; }

/* Zoomed in, the imagery is what you are looking at, so it comes forward
   and the choropleth steps back. The steps are classes rather than an
   inline opacity because the policy here drops style attributes. */
.map.bz2 .basemap { opacity: .46; } .map.bz2.has-base path { fill-opacity: .64; }
.map.bz3 .basemap { opacity: .58; } .map.bz3.has-base path { fill-opacity: .55; }
.map.bz4 .basemap { opacity: .70; } .map.bz4.has-base path { fill-opacity: .46; }
.map.bz5 .basemap { opacity: .80; } .map.bz5.has-base path { fill-opacity: .38; }
.map.bz6 .basemap { opacity: .90; } .map.bz6.has-base path { fill-opacity: .30; }

@media (prefers-reduced-motion: no-preference) {
  .map .basemap { transition: opacity .25s ease; }
  .map.has-base path { transition: fill-opacity .25s ease; }
}

/* ------------------------------------------------------ commune notice */

/* Wikipedia in brief, above the campaign's own figures — context for
   someone looking at 47 communes, not a source. */
.wikicard {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 14px; margin-bottom: 16px;
  background: var(--sys-fill-2); border-radius: 12px;
}
.wiki-img {
  flex: 0 0 auto; width: 120px; height: 120px;
  object-fit: cover; border-radius: 12px; display: block;
}
.wiki-body { min-width: 0; }
.wiki-x { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink); }
.wiki-meta {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 10px 0 0; font-size: 12px; color: var(--sys-grey-1);
}
.wiki-meta strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.wiki-meta a { color: var(--sys-blue); }

@media (max-width: 620px) {
  .wikicard { flex-direction: column; }
  .wiki-img { width: 100%; height: 150px; }
}

/* ------------------------------------------- commune panel over the map */

/* You clicked the shape, so its figures belong where you are looking. The
   panel takes one side and the map stays zoomed on the commune behind it. */
.fiche-over {
  position: absolute; top: 14px; right: 14px; bottom: 14px;
  width: min(360px, 44%);
  display: flex; flex-direction: column;
  background: rgba(255,255,255,.94);
  backdrop-filter: saturate(180%) blur(20px);
  border-radius: 12px;
  box-shadow: 0 12px 36px rgba(0,0,0,.20), 0 0 0 1px rgba(0,0,0,.06);
  overflow: hidden; z-index: 12;
}
.fiche-over .panel-head {
  flex: 0 0 auto; padding: 13px 15px;
  border-bottom: 1px solid var(--sys-sep); background: transparent;
}
.fiche-over .panel-head h2 { font-size: 15px; }
.fiche-over > .pad { overflow-y: auto; padding: 15px; }

/* Tighter inside a narrow column than in a full-width panel. */
.fiche-over .factgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 14px; }
.fiche-over .wikicard { padding: 11px; gap: 11px; }
.fiche-over .wiki-img { width: 82px; height: 82px; }
.fiche-over .wiki-x { font-size: 12px; }
.fiche-over .pshares { font-size: 12px; }

@media (max-width: 640px) {
  /* Genuinely no room beside the map, so it goes under it. */
  .fiche-over {
    position: static; width: auto; margin-top: 12px;
    backdrop-filter: none; background: #fff; max-height: 460px;
  }
}

/* ------------------------------------------------- notification centre */

.bell { position: relative; }
.bell > summary {
  list-style: none; cursor: pointer;
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); position: relative;
}
.bell > summary::-webkit-details-marker { display: none; }
.bell > summary:hover { background: var(--sys-fill-2); color: var(--ink); }
.bell[open] > summary { background: var(--sys-fill-2); color: var(--ink); }

/* Topbar glyphs, sized by their ink rather than by their box.
   ---------------------------------------------------------------------
   These five icons all sat at 15-17px and still read as five different
   sizes, because a glyph occupies a different fraction of its own viewBox
   depending on what it draws: the clock fills its frame, the power symbol
   leaves a gap at the top and margins at the sides. Measured on the page,
   the ink ran from 11.0px (power) to 13.9px (wide toggle) — the power
   symbol rendered a fifth smaller than its neighbour.

   Each size below is the box that puts that particular glyph's ink at
   13.0px on its dominant axis, which is where the clock already sat and
   what the + button is matched to elsewhere. Changing a path means
   re-measuring; the number is a consequence of the drawing, not a taste. */
.bell > summary svg { width: 16.4px; height: 16.4px; display: block; }   /* ink 13.5 → 13.0 */

.bell-n {
  position: absolute; top: -1px; right: -2px;
  min-width: 15px; height: 15px; padding: 0 4px; box-sizing: border-box;
  border-radius: 50%; background: var(--sys-grey-2); color: #fff;
  font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
  box-shadow: 0 0 0 2px rgba(255,255,255,.9);
}
.bell-n.hot { background: var(--sys-red); }

.bell-panel {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 400;
  width: min(360px, calc(100vw - 32px));
  background: #fff; border-radius: 12px;
  box-shadow: 0 14px 40px rgba(0,0,0,.20), 0 0 0 1px rgba(0,0,0,.06);
  overflow: hidden;
}
.bell-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--sys-sep);
  font-size: 13px;
}
.bell-empty { margin: 0; padding: 16px 14px; font-size: 12.5px; color: var(--sys-grey-1); }
.bell-list { list-style: none; margin: 0; padding: 4px; max-height: 60vh; overflow-y: auto; }
.bell-i a {
  display: flex; align-items: baseline; gap: 10px;
  padding: 9px 10px; border-radius: 8px; text-decoration: none;
  border-left: 3px solid transparent;
}
.bell-i a:hover { background: var(--sys-fill-2); }
.bell-t { flex: 1 1 auto; font-size: 12.5px; color: var(--ink); }
.bell-d { flex: 0 0 auto; font-size: 11px; color: var(--sys-grey-2); white-space: nowrap; }
.bell-i.lv-urgent a { border-left-color: var(--sys-red); }
.bell-i.lv-urgent .bell-d { color: var(--sys-red); }
.bell-i.lv-warn a { border-left-color: var(--sys-orange); }
.bell-i.lv-warn .bell-d { color: var(--sys-orange); }
.bell-more { margin: 0; padding: 8px 14px 12px; font-size: 11.5px; color: var(--sys-grey-2); }


/* The task's own fields, on the card where there is room for them. */
/* The chantier / dates / critique form lived here twice and was removed twice —
   once as "clutter in the one place that should be about the work" (59bc752),
   and again on 2026-09-02 after being offered folded away. Its styling is
   deleted with it rather than left dormant a third time; the markup and the
   exact field names store_update() expects are recorded in INSTRUCTIONS.md. */

/* ------------------------------------------------- Territoire tab view */

/* Five readings of the same district, one panel. Radio inputs drive it, so
   the switch costs nothing and works with scripting off. */
.terrtabs > .panel-head { padding-bottom: 0; }
.ttabs { display: flex; flex-wrap: wrap; gap: 2px; }
.ttabs label {
  padding: 8px 13px; border-radius: 8px 8px 0 0; cursor: pointer;
  font-size: 13px; font-weight: 500; color: var(--sys-grey-1);
  border-bottom: 2px solid transparent; white-space: nowrap;
}
.ttabs label:hover { background: var(--sys-fill-2); color: var(--ink); }

.tpanes > .tpane { display: none; }
.tpane > .pad, .tpane > .tablewrap { padding-top: 4px; }

/* One rule per tab: light the label, show the pane. */
#ts-1:checked ~ .panel-head label[for="ts-1"],
#ts-2:checked ~ .panel-head label[for="ts-2"],
#ts-3:checked ~ .panel-head label[for="ts-3"],
#ts-4:checked ~ .panel-head label[for="ts-4"],
#ts-5:checked ~ .panel-head label[for="ts-5"] {
  color: var(--sys-blue); border-bottom-color: var(--sys-blue); background: var(--sys-fill-2);
}
#ts-1:checked ~ .tpanes > [data-pane="1"],
#ts-2:checked ~ .tpanes > [data-pane="2"],
#ts-3:checked ~ .tpanes > [data-pane="3"],
#ts-4:checked ~ .tpanes > [data-pane="4"],
#ts-5:checked ~ .tpanes > [data-pane="5"] { display: block; }

.ttabs label:focus-within,
input[name="tsec"]:focus-visible ~ .panel-head label {
  outline: 2px solid var(--sys-blue); outline-offset: -2px;
}

/* Renaming, on the card. The sidebar is a list to scan. */
.gb-rename { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; }
.gb-rename input[type="text"] {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: 13px; font-weight: 600;
  padding: 7px 10px; border: 1px solid transparent; border-radius: 8px;
  background: rgba(255,255,255,.14); color: #fff;
}
.gb-rename input::placeholder { color: rgba(255,255,255,.6); }
.gb-rename input:hover { background: rgba(255,255,255,.22); }
.gb-rename input:focus {
  outline: none; background: #fff; color: var(--ink);
  box-shadow: 0 2px 10px rgba(0,0,0,.14);
}
.gb-rename .gb-ok { flex: 0 0 auto; }

/* Pinch and drag on the map are handled in script; declaring it here keeps
   the policy from having to allow an inline style for one property. */
.map { touch-action: none; }

/* ---------------------------------------------- row bin, undo, logout */

/* The bin's visibility is set once, further down, with the rest of its
   styling. It was declared here as well — the same three rules, twice — which
   is precisely how the icon sizes elsewhere in this file drifted apart. */
.trow .tdel { flex: 0 0 auto; display: flex; align-items: center; }

.undo-f { display: inline-flex; }
.undo-b {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); background: var(--sys-fill-2); cursor: pointer;
}
.undo-b:hover { color: var(--ink); background: var(--sys-sep); }
.undo-b svg { width: 18.9px; height: 18.9px; display: block; }           /* ink 11.7 → 13.0 */

.icon-btn {
  width: 30px; height: 30px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.icon-btn svg { width: 17px; height: 17px; display: block; }

/* Sizing pass: the candidate widths are tried with the animation off, so
   none of the trial geometry is ever painted. */
.cal-grid.cal-measuring,
.cal-grid.cal-measuring .cal-cell { transition: none !important; }

/* One line per task in the list — the dates and flags live on the bar. */
.trow { align-items: center; }
.trow .tlink { padding: 6px 2px; }

/* Off, not out: a round red power key. */
.power-btn {
  width: 30px; height: 30px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  color: #fff; background: var(--sys-red);
  text-decoration: none; transition: filter .15s ease, transform .15s ease;
}
.power-btn:hover { filter: brightness(1.08); }
.power-btn:active { transform: scale(.94); }
.power-btn svg { width: 18.9px; height: 18.9px; display: block; }        /* ink 11.0 → 13.0 */

/* The display-width control took the brand slot the old panel toggle held. */
.brand .wide-toggle {
  width: 30px; height: 30px; flex: 0 0 auto; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 8px; cursor: pointer;
  color: var(--sys-grey-1); background: var(--sys-fill-2);
  transition: background .15s ease, color .15s ease;
}
.brand .wide-toggle:hover { color: var(--ink); background: var(--sys-sep); }
.brand .wide-toggle[aria-pressed="true"] { color: #fff; background: var(--sys-blue); }
.brand .wide-toggle svg { width: 15.9px; height: 15.9px; display: block; } /* ink 13.9 → 13.0 */

/* The bin, at the weight of the system's own. */
.tdel-b { transition: opacity .15s ease, color .15s ease, background .15s ease; }
.tdel-b:hover { color: var(--sys-red); background: rgba(255,59,48,.12); }
.undo-b:disabled { opacity: .34; cursor: default; background: none; }
.undo-b:disabled:hover { color: var(--sys-grey-1); background: none; }

/* Deleting from the open card, at the same weight as its neighbours. */
.gb-ico-del svg { width: 20px; height: 20px; }
.gb-ico-del:hover { background: rgba(255,255,255,.34); }

/* The rename field wears the title's own type, so pressing the name looks
   like editing the name rather than opening a form. */
.bl-in {
  font: inherit; color: inherit; letter-spacing: inherit;
  width: 100%; min-width: 8ch; max-width: 100%;
  padding: 1px 6px; margin: -1px -6px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.28); outline: 0;
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.6);
}
.bl-in::selection { background: rgba(255,255,255,.4); }
.bl[data-ren] { cursor: text; }
.gbar[open] .bl[data-ren]:hover,
details[open] .bl[data-ren]:hover { text-decoration: underline dotted rgba(255,255,255,.6); text-underline-offset: 3px; }

/* ------------------------------------------------ campagnes: two sections */
#cs-1:checked ~ .tpanes .tpane[data-pane="1"],
#cs-2:checked ~ .tpanes .tpane[data-pane="2"] { display: block; }
.camptabs .tpane { display: none; }
#cs-1:checked ~ .panel-head label[for="cs-1"],
#cs-2:checked ~ .panel-head label[for="cs-2"] {
  background: var(--panel); color: var(--ink); box-shadow: var(--tab-on, 0 1px 2px rgba(0,0,0,.08));
}
input[name="csec"]:focus-visible ~ .panel-head label { outline: 2px solid var(--sys-blue); outline-offset: 2px; }

/* ---------------------------------------------------------- account menu */
.acct { position: relative; }
.acct > summary { list-style: none; }
.acct > summary::-webkit-details-marker { display: none; }
.acct[open] > summary { filter: brightness(1.08); }
.acct-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 320;
  min-width: 190px; padding: 6px;
  background: var(--panel); border: 1px solid var(--sys-sep); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0,0,0,.16);
}
.acct-who {
  display: block; padding: 6px 10px 8px; font-size: 12px;
  color: var(--sys-grey-2); border-bottom: 1px solid var(--sys-sep); margin-bottom: 4px;
}
.acct-i {
  display: block; padding: 8px 10px; border-radius: 8px;
  font-size: 14px; color: var(--ink); text-decoration: none;
}
.acct-i:hover { background: var(--sys-fill-2); }
.acct-out { color: var(--sys-red); }
.acct-out:hover { background: rgba(255,59,48,.12); }

/* The row's field keeps the row's own type rather than the card's. */
.ttitle-in {
  flex: 1; min-width: 0; font-size: 14px; line-height: 1.35;
  background: #fff; color: var(--ink);
  box-shadow: inset 0 0 0 1.5px var(--sys-blue);
}

/* The handle breathes on its own, so it is noticed before it is needed —
   a ring leaving the button, not a movement of the button itself, which
   would fight the lean it takes on hover. */
.side-handle::before {
  content: ""; position: absolute; inset: 0;
  border-radius: 50%; border: 2px solid var(--sys-blue);
  opacity: 0; pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .side-handle::before { animation: sh-ping 3.2s cubic-bezier(.22,.61,.36,1) infinite; }
}
@keyframes sh-ping {
  0%        { transform: scale(1);    opacity: .55; }
  62%, 100% { transform: scale(1.75); opacity: 0; }
}
.side-handle:hover::before { animation-play-state: paused; opacity: 0; }

/* ------------------------------------------------------ conformité légale */
.conf { list-style: none; margin: 0; padding: 0; }
.conf-i {
  padding: 18px 22px 20px;
  border-top: 1px solid var(--sys-sep);
  border-left: 3px solid transparent;
}
.conf-i:first-child { border-top: 0; }
.conf-i.s-ok { border-left-color: var(--sys-green, #34C759); background: rgba(52,199,89,.04); }
.conf-i.s-doing { border-left-color: var(--sys-blue); }
.conf-i.s-na { opacity: .62; }

.conf-top { display: flex; align-items: baseline; gap: 14px; }
.conf-t { margin: 0; font-size: 15.5px; font-weight: 600; letter-spacing: -.01em; flex: 1; }
.conf-due {
  font-size: 13px; color: var(--sys-grey-2); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.conf-due.late { color: var(--sys-red); font-weight: 600; }
.conf-due.soon { color: var(--sys-orange); font-weight: 600; }

.conf-what { margin: 8px 0 0; font-size: 14px; line-height: 1.55; color: var(--ink-soft); max-width: 74ch; }
.conf-meta { margin: 10px 0 0; font-size: 12.5px; color: var(--sys-grey-2); line-height: 1.7; max-width: 82ch; }
.conf-lbl { text-transform: uppercase; letter-spacing: .06em; font-size: 10.5px; color: var(--sys-grey-2); }
.conf-sep { margin: 0 6px; opacity: .5; }

/* Says plainly that the rule itself still has to be checked. */
.cflag {
  display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px;
  background: var(--warn-bg, #FFF4E0); color: #8A5A00;
  font-size: 11px; font-weight: 600; letter-spacing: 0; text-transform: none;
}

.conf-f { display: flex; flex-wrap: wrap; align-items: end; gap: 12px; margin-top: 14px; }
.conf-seg { flex: 0 0 auto; }
.conf-by, .conf-note { display: flex; flex-direction: column; gap: 4px; }
.conf-by { flex: 1 1 200px; }
.conf-note { flex: 2 1 300px; }
.conf-f input[type="text"] {
  width: 100%; padding: 7px 10px; font-size: 13.5px;
  border: 1px solid var(--sys-sep); border-radius: 8px; background: var(--panel); color: var(--ink);
}
.conf-f input[type="text"]:focus { outline: 2px solid var(--sys-blue); outline-offset: -1px; border-color: transparent; }
.conf-save { flex: 0 0 auto; }
.conf-at { margin: 10px 0 0; font-size: 12px; color: var(--sys-grey-2); }

@media (max-width: 720px) {
  .conf-i { padding: 16px 14px; }
  .conf-f { gap: 10px; }
}

/* The map is sized by width, and the district's frame is taller than it is
   wide, so on a wide window it grew taller than the window itself and pushed
   the rest of the page out of sight. Bound it by the viewport too. An SVG
   keeps its ratio when height is capped, so it narrows as it shortens — it
   is centred rather than left hanging at one edge. */
.map {
  max-height: calc(100vh - 150px);
  margin-inline: auto;
}
@supports (height: 100dvh) { .map { max-height: calc(100dvh - 150px); } }
@media (max-width: 720px) { .map { max-height: calc(100vh - 120px); } }
.mapwrap { display: flex; justify-content: center; }

/* The narrow-window block that used to sit here has moved into the single
   "shell column geometry" owner near the end of the file, so that ordering
   and widths can no longer be decided in two places. */

/* The rename field belongs to the card, not on top of it: no box, just the
   name with a rule under it while you are typing. A generic input rule was
   painting it white, which is why it looked pasted on. */
/* The card's title editor, now the same box the task list uses: a white
   card-sized field that wraps and grows, rather than an underline that could
   only scroll a long title sideways. Selector is `.bl-in`, not
   `input.bl-in` — it is a <textarea> now, and the old selector would simply
   have stopped matching. */
.btx .bl-in {
  width: 100%; box-sizing: border-box;
  font: inherit; font-size: 15px; line-height: 1.35; font-weight: 600;
  color: var(--ink); letter-spacing: inherit;
  background: #fff; border: 0; border-radius: 12px;
  padding: 8px 10px; margin: 0;
  box-shadow: 0 0 0 1.5px rgba(255,255,255,.9), 0 2px 10px rgba(0,0,0,.14);
  outline: 0; resize: none;
  overflow-x: hidden; overflow-y: auto;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.btx .bl-in:focus { box-shadow: 0 0 0 2px #fff, 0 2px 12px rgba(0,0,0,.18); }
.btx .bl-in::placeholder { color: var(--sys-grey-2); }
.btx .bl-in::selection { background: rgba(0,122,255,.22); }
/* `display` only for the shown state. Declared on the base rule it overrides
   the browser's own `[hidden] { display: none }`, and every card on the page
   renders its title editor at once — which is exactly what happened, and is
   the same trap already recorded for `.ttitle-in`. Twice now. */
.btx .bl-in:not([hidden]) { display: block; }
.btx .bl-in[hidden] { display: none; }

/* The editor is a white box the size of the row it replaces, and the text
   inside it wraps rather than scrolling out of sight. */
.trow .ttitle-in {
  flex: 1 1 100%; min-width: 0;
  font: inherit; font-size: 14px; line-height: 1.4; color: var(--ink);
  padding: 9px 11px; margin: 0;
  background: #fff; border: 0; border-radius: 12px;
  box-shadow: 0 0 0 1.5px var(--sys-blue), 0 2px 10px rgba(0,0,0,.10);
  outline: 0; resize: none;
  /* overflow-y is `auto`, not `hidden`, purely as a safety net. The box grows
     to fit its text, so no scrollbar should ever appear — but when the growing
     went wrong the clipped line was not merely invisible, it was unreachable,
     and you could not tell the name you had typed was longer than the box. */
  overflow-x: hidden; overflow-y: auto;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* `display` must be declared only for the shown state. Setting it on the base
   rule overrode the UA's `[hidden] { display: none }`, which put every row's
   editor on screen at once. */
.trow .ttitle-in:not([hidden]) { display: block; }
.trow .ttitle-in[hidden] { display: none; }
.trow .ttitle-in::placeholder { color: var(--sys-grey-2); }
.trow .ttitle-in::selection { background: rgba(0,122,255,.22); }

/* Tasks sit under the name of their chantier, not to the left of it. */
.tlist { padding-left: 8px; }

/* ------------------------------------------------------------- hover tabs
   For controls that are icons: what it does, in words, on hover. Built from
   a data attribute so no inline style or handler is involved. */
.tipped { position: relative; }
.tipped::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translateX(-50%) translateY(3px);
  z-index: 60; width: max-content; max-width: 240px;
  padding: 7px 10px; border-radius: 8px;
  background: rgba(28,28,30,.96); color: #fff;
  font-size: 12px; font-weight: 400; line-height: 1.4; letter-spacing: 0;
  text-align: left; white-space: normal; pointer-events: none;
  opacity: 0; visibility: hidden;
  box-shadow: 0 6px 18px rgba(0,0,0,.22);
}
.tipped:hover::after, .tipped:focus-visible::after {
  opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: no-preference) {
  .tipped::after { transition: opacity .14s ease, transform .14s ease, visibility .14s; }
}
/* Near the right edge of a card the tab would overflow; pull it back in. */
.gb-seg .tipped:last-child::after { left: auto; right: 0; transform: translateY(3px); }
.gb-seg .tipped:last-child:hover::after { transform: translateY(0); }

/* Same at the end of a chantier header, and this pair mattered more than it
   looks: centred on a 24px button, each tip reached about 70px past its own
   right edge, and the outermost one reached 7px past the viewport. That was
   enough to give every page a horizontal scrollbar at 1440, 1024 and 900 —
   a whole-window defect caused by two invisible tooltips. Anchored right,
   they open inwards and the document stops scrolling sideways. */
.gws-meta .tipped::after { left: auto; right: 0; transform: translateY(3px); }
.gws-meta .tipped:hover::after,
.gws-meta .tipped:focus-visible::after { transform: translateY(0); }

/* And the flip control, which is the last thing in the panel header and so
   sits against the same edge. With these three clusters anchored the page
   measures zero horizontal overflow at 1440, 1024, 900 and 720; centred
   tips at any of them put a scrollbar under the whole window. */
.headright .tipped::after { left: auto; right: 0; transform: translateY(3px); }
.headright .tipped:hover::after,
.headright .tipped:focus-visible::after { transform: translateY(0); }

/* Undo sits in a row of 30px controls; it keeps their size and weight so the
   line stays even. */
.undo-b { width: 30px; height: 30px; color: var(--sys-grey-1); background: var(--sys-fill-2); }
/* Size deliberately not repeated here: this block sat after the one that
   sets it and was quietly overriding it, so the glyph stayed 17px however
   the other rule was edited. That is how the icon sizes drifted apart in
   the first place. One declaration, higher up, with the measurement. */
.undo-b:hover { color: var(--ink); background: var(--sys-sep); }
.undo-b:disabled { opacity: .34; background: none; cursor: default; }
.undo-b:disabled:hover { color: var(--sys-grey-1); background: none; }

/* Expand a whole chantier from its header. */
.gws-exp { transition: color .15s ease, background .15s ease; }
.gws-exp:hover { color: var(--ink); background: var(--sys-fill-2); }
.gws-exp.on { color: var(--sys-blue); }

/* ------------------------------------------------------ répartir la charge */
.chg-cap { display: flex; align-items: center; gap: 10px; margin: 0 0 18px; flex-wrap: wrap; }
.chg-cap label { font-size: 13px; color: var(--sys-grey); }
.chg-cap input {
  width: 72px; padding: 7px 10px; font-size: 14px;
  border: 1px solid var(--sys-sep); border-radius: 8px; background: var(--panel); color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.chg-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
.chg-col {
  padding: 14px 16px; border-radius: 12px;
  background: var(--sys-fill-2); border: 1px solid var(--sys-sep);
}
.chg-h {
  margin: 0 0 10px; font-size: 11px; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; color: var(--sys-grey-2);
}
.chg-h2 { margin: 22px 0 10px; }
.chg-peaks { list-style: none; margin: 0; padding: 0; }
.chg-peaks li {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 4px 0; font-size: 13.5px;
}
.chg-d { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.chg-n { font-weight: 600; font-variant-numeric: tabular-nums; }
.chg-n.hot { color: var(--sys-red); }
.chg-none { margin: 0; font-size: 13.5px; color: var(--sys-grey); line-height: 1.5; }
.chg-ok { margin-top: 18px; }

.chg-moves { list-style: none; margin: 0; padding: 0; }
.chg-m {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-top: 1px solid var(--sys-sep); font-size: 14px;
}
.chg-m:first-child { border-top: 0; }
.chg-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.chg-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chg-move {
  display: flex; align-items: baseline; gap: 7px; flex: 0 0 auto;
  font-size: 13px; color: var(--sys-grey); font-variant-numeric: tabular-nums;
}
.chg-move strong { color: var(--ink); }
.chg-arrow { color: var(--sys-grey-2); }
.chg-shift { color: var(--sys-grey-2); font-size: 12px; }

.chg-apply { display: flex; align-items: center; gap: 12px; margin-top: 18px; flex-wrap: wrap; }
.chg-note { font-size: 12.5px; color: var(--sys-grey-2); }

/* Compliance: one panel, a tab per domaine. */
.conftabs .tpane { display: none; }
#fs-0:checked ~ .tpanes .tpane[data-pane="0"],
#fs-1:checked ~ .tpanes .tpane[data-pane="1"],
#fs-2:checked ~ .tpanes .tpane[data-pane="2"],
#fs-3:checked ~ .tpanes .tpane[data-pane="3"],
#fs-4:checked ~ .tpanes .tpane[data-pane="4"],
#fs-5:checked ~ .tpanes .tpane[data-pane="5"],
#fs-6:checked ~ .tpanes .tpane[data-pane="6"],
#fs-7:checked ~ .tpanes .tpane[data-pane="7"] { display: block; }
#fs-0:checked ~ .panel-head label[for="fs-0"],
#fs-1:checked ~ .panel-head label[for="fs-1"],
#fs-2:checked ~ .panel-head label[for="fs-2"],
#fs-3:checked ~ .panel-head label[for="fs-3"],
#fs-4:checked ~ .panel-head label[for="fs-4"],
#fs-5:checked ~ .panel-head label[for="fs-5"],
#fs-6:checked ~ .panel-head label[for="fs-6"],
#fs-7:checked ~ .panel-head label[for="fs-7"] {
  background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
input[name="fsec"]:focus-visible ~ .panel-head label { outline: 2px solid var(--sys-blue); outline-offset: 2px; }
.ttab-n { opacity: .55; font-variant-numeric: tabular-nums; margin-left: 3px; }

/* --------------------------------------------------------------- adresses */
.mbox { list-style: none; margin: 0; padding: 0; }
.mbox-i {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 22px; border-top: 1px solid var(--sys-sep);
}
.mbox-i:first-child { border-top: 0; }
.mbox-a { font-size: 14.5px; font-weight: 500; }
.mbox-n {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px 3px 7px; border-radius: 999px;
  background: var(--sys-fill-2); color: var(--sys-grey-1);
  font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.mbox-n.hot { background: var(--sys-red); color: #fff; }
.mbox-n svg { width: 14px; height: 14px; display: block; }
.mbox-t, .mbox-off { font-size: 12.5px; color: var(--sys-grey-2); margin-left: auto; }

/* ------------------------------------------------------------- the flip

   The rétroplanning has two sides: when the work happens, and how much of
   it lands on any one day. The panel turns over rather than the second view
   arriving as yet another card down the page.

   Both faces sit in the same grid cell, so the panel is as tall as it needs
   to be and nothing jumps when it turns. */
/* Both faces used to share one grid cell, which made the panel as tall as
   the taller of them — so a short timeline sat above a page of empty white
   left by the chart on the back. The face you are looking at is in flow and
   sets the height; the other is lifted out of it. */
.flip { position: relative; perspective: 2200px; }
.flip-face {
  width: 100%;
  backface-visibility: hidden;
  transform-style: preserve-3d;
}
.flip-back { position: absolute; inset: 0; }
.panel.flipped .flip-front { position: absolute; inset: 0; }
.panel.flipped .flip-back { position: static; }
.flip-front { transform: rotateX(0deg); }
.flip-back  { transform: rotateX(180deg); visibility: hidden; }

.panel.flipped .flip-front { transform: rotateX(-180deg); visibility: hidden; }
.panel.flipped .flip-back  { transform: rotateX(0deg); visibility: visible; }

@media (prefers-reduced-motion: no-preference) {
  /* A turn should feel like weight moving, not a switch throwing. The old
     curve front-loaded almost all of the rotation and then crawled, so the
     card appeared to snap and then hesitate. This one leads in gently, carries
     speed through the middle where the motion reads, and settles without a
     bounce. Slightly longer, because at .62s the eye could not follow the
     face turning over and only saw the two end states. */
  .flip-face {
    transition: transform .72s cubic-bezier(.22, .61, .28, 1),
                visibility 0s linear .36s;
  }
  .panel.flipped .flip-back { transition: transform .62s cubic-bezier(.32,.72,0,1), visibility 0s; }
}

.flip-btn {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--sys-fill-2); color: var(--sys-grey-1); cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.flip-btn:hover { background: var(--sys-sep); color: var(--ink); }
.flip-btn[aria-pressed="true"] { background: var(--sys-blue); color: #fff; }
.flip-btn svg { width: 17px; height: 17px; display: block; }

/* The row title is a <button> so that renaming has a keyboard and
   screen-reader route — double-click on a span had neither. It must not look
   like one: no chrome, the row's own type and colour, left aligned. The pencil
   that used to sit here on hover is not coming back; it was removed at the
   user's request and the register still records it as gone.

   Its focus ring is the row's, and because the control is the title itself
   there is no extra slot and nothing moves when it takes focus. */
.trow button.tlink {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; margin: 0; font: inherit; color: inherit;
  text-align: left; width: 100%;
}
.trow button.tlink:focus-visible {
  outline: 2px solid var(--sys-blue); outline-offset: -1px; border-radius: 8px;
}

/* A quarter of the window reads titles; typing one wants more. */
/* `clamp`, not `min` — the floor is the whole point and this rule had dropped
   it. The base `.shell` clamps the column up to 264px (248px narrow) precisely
   because "a quarter of a narrow window is not enough to read a task title in".
   Using `min()` here threw that floor away, so on a narrow window opening the
   editor made the list *narrower* — 248px to 216px, measured — which is the
   opposite of relaxing the cap, and worst exactly when you are trying to read
   a title. It also silently clipped the editor: see the fit() note in hq.js. */
/* `.shell.renaming` used to widen the column here. It never did.
   `setWidth()` in hq.js already caps `--side-w` at a quarter of the window,
   so a rule reading `min(var(--side-w), 46vw)` could only ever return
   `--side-w` itself — the 46vw ceiling never bound at any window size. The
   one thing it did do was drop the 264px floor that the base rule applies,
   which made the sidebar *narrower* while you typed and clipped the editor.
   Rewriting it as a clamp fixed that and left a rule that provably computes
   the same value as the base in every case, so the rule is gone entirely and
   so is the class that switched it. Widening the list while renaming would
   need a larger preferred width, not a larger ceiling — worth doing only if
   it is actually wanted. */

/* The envelope opens as the chantier does. */
.gws-exp svg { overflow: visible; }
.gws-exp .env-flap {
  transform-box: fill-box; transform-origin: 50% 0%;
}
@media (prefers-reduced-motion: no-preference) {
  .gws-exp .env-flap { transition: transform .34s cubic-bezier(.34,1.4,.5,1); }
  .gws-exp .env-letter { transition: transform .34s ease, opacity .2s ease; }
}
.gws-exp:hover .env-flap { transform: rotateX(38deg); }
.gws-exp.on .env-flap { transform: rotateX(180deg); }
.gws-exp .env-letter { opacity: 0; }
.gws-exp.on .env-letter { opacity: 1; transform: translateY(-1px); }

/* Opened from the envelope, a chantier lays its cards out side by side —
   the dates no longer place them, so they can use the full width. */
.gws.as-cards {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-start;
  padding: 4px 0 14px;
}
.gws.as-cards > .gsum { flex: 0 0 100%; }

/* Chrome puts everything after the <summary> inside a ::details-content box,
   so the cards were children of that box and never became flex items of the
   <details> itself — which is why they kept stacking however they were sized.
   The content box is where the row has to be laid out. Browsers without the
   pseudo-element drop this and use the rule above, where the cards really are
   direct children. */
.gws.as-cards::details-content {
  flex: 1 1 100%;
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-start;
}
/* As many to a row as fit. Percentages were resolving against the wrong box
   here — the bars carry widths of their own — so the basis is zero and the
   minimum does the work. */
/* The card keeps the size it already had; it simply stops claiming the whole
   line, so the next one sits beside it and the rest wrap under. */
.gws.as-cards > .gtask { flex: 0 1 340px; min-width: 280px; width: auto; }

/* The card body is sized for a timeline lane; in cards mode it takes the
   width of the card it is in. */
/* The bar is a timeline object: its width is its duration. As a card header
   it has to be the width of the card, or the head steps out past the body. */
.gws.as-cards .gbody,
.gws.as-cards .row,
.gws.as-cards .bar { width: 100%; max-width: 100%; box-sizing: border-box; }
.gws.as-cards .row { display: block; }
.gws.as-cards .gtask {
  border-radius: 12px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 6px 18px rgba(0,0,0,.07);
  background: var(--panel);
}
.gws.as-cards .row { height: auto; min-height: 0; }
.gws.as-cards .bar {
  position: static; left: auto; right: auto; width: auto;
  border-radius: 12px 12px 0 0; min-width: 0;
}
.gws.as-cards .gbody { border-radius: 0 0 12px 12px; }
@media (prefers-reduced-motion: no-preference) {
  .gws { transition: gap .3s ease; }
}

/* The card body was built for a full-width lane, so its content refused to
   go below ~480px and two cards never fitted on a line. In cards mode the
   inner boxes are allowed to shrink. */
.gws.as-cards .gbody,
.gws.as-cards .gbody * { min-width: 0; }
.gws.as-cards .gbody textarea,
.gws.as-cards .gbody input[type="text"] { width: 100%; max-width: 100%; }
.gws.as-cards .gb-row { flex-wrap: wrap; }

/* The fiche floats over the map in a narrow column; its header controls were
   breaking mid-word rather than wrapping under the title. */
.fiche-over .panel-head { flex-wrap: wrap; gap: 6px 10px; }
.fiche-over .panel-head h2 { flex: 1 1 100%; }
.fiche-over .panel-head .btn { white-space: nowrap; flex: 0 0 auto; }

/* The flip control shows what it does: a card with an arc going over the top
   of it. The arc draws itself on hover, and the card turns right over once
   the panel has. */
.flip-btn svg { overflow: visible; }
.flip-btn .fl-card { transform-box: fill-box; transform-origin: 50% 50%; }
.flip-btn .fl-arc { stroke-dasharray: 22; stroke-dashoffset: 0; }

@media (prefers-reduced-motion: no-preference) {
  .flip-btn .fl-card { transition: transform .45s cubic-bezier(.32,.72,0,1); }
  .flip-btn .fl-arc,
  .flip-btn .fl-tip { transition: stroke-dashoffset .45s ease, opacity .2s ease, transform .45s ease; }
  .flip-btn:hover .fl-arc { animation: fl-draw 1.1s ease-in-out infinite; }
}
@keyframes fl-draw {
  0%   { stroke-dashoffset: 22; }
  55%  { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -22; }
}
.flip-btn:hover .fl-card { transform: rotateX(24deg); }
.flip-btn[aria-pressed="true"] .fl-card { transform: rotateX(180deg); }
.flip-btn[aria-pressed="true"] .fl-arc,
.flip-btn[aria-pressed="true"] .fl-tip { transform: rotate(180deg); transform-box: fill-box; transform-origin: 50% 120%; }

/* Canton figures on the commune sheet. */
.an-note {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 8px;
  background: var(--sys-fill-2); font-size: 13px; line-height: 1.55; color: var(--ink-soft);
}
.an-note strong { color: var(--ink); }
.an-src { margin: 8px 0 0; font-size: 11.5px; color: var(--sys-grey-2); }

/* The fiche is bounded by the map it sits on; its body scrolls rather than
   running off the bottom of the page. */
.fiche-over { max-height: calc(100% - 28px); }
.fiche-over > .pad {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
}

/* Back to the whole district, floating over the top left of the map. */
.map-back {
  position: absolute; top: 14px; left: 14px; z-index: 14;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px 7px 9px; border-radius: 999px;
  background: rgba(255,255,255,.94);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 4px 16px rgba(0,0,0,.18), 0 0 0 1px rgba(0,0,0,.06);
  font-size: 13px; font-weight: 500; color: var(--ink);
  text-decoration: none; white-space: nowrap;
  transition: background .15s ease, transform .15s ease;
}
.map-back:hover { background: #fff; transform: translateX(-2px); }
.map-back svg { width: 15px; height: 15px; display: block; }

/* ------------------------------------------------- task list, room to read

   The pencil and the bin were holding 58px of every row open even while
   invisible, so a title like "Confirmer les obligations LEDP auprès du
   canton" was breaking one word to a line. They now sit over the end of the
   row and only when the pointer is on it, behind a short fade so the text
   they cover reads as running under them rather than being cut. */
.trow { position: relative; }
.trow .tdel-b { position: absolute; top: 50%; right: 6px; transform: translateY(-50%); }
.trow .tlink { padding-right: 4px; }

.trow::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 88px;
  background: linear-gradient(to right, rgba(255,255,255,0), var(--sys-fill-2) 42%);
  opacity: 0; pointer-events: none; border-radius: 0 8px 8px 0;
  transition: opacity .15s ease;
}
.trow:hover::after { opacity: 1; }
.trow:has(.ttitle-in:not([hidden]))::after { opacity: 0; }
.trow.on::after { background: linear-gradient(to right, rgba(220,235,251,0), #DCEBFB 42%); }
@media (hover: none) { .trow::after { opacity: 1; } }

/* And the column itself gets a floor: a quarter of a narrow window is not
   enough to read a task title in. */
/* Never more than a quarter of the window, as asked — with a floor so the
   titles stay readable on a narrow screen (below ~1050px the floor wins,
   which is the point of it). */
/* The column width is published as a variable so the things that have to sit
   beside it can read it rather than re-deriving it.

   They were re-deriving it, and getting it wrong: the drag handle and the
   resize edge both computed `min(var(--side-w), 25vw)` while the column is a
   `clamp()` with a 264px floor. Whenever that floor binds — which is any
   window narrow enough to matter — the column was ~59px wider than the handle
   thought, so the handle sat inside the task list instead of on its edge. */
/* ----------------------------------------------- shell column geometry

   ONE owner for .shell's tracks, its gap, and the order of the two panels.
   There were twelve .shell rules setting grid-template-columns across this
   file. This one is unconditional and last, so it beat every earlier media
   rule — media queries carry no specificity — which meant the layout was
   never one column at any width. The phone did not fail to reach the mobile
   rule; the mobile rule was being overwritten by this one.

   Worse, `.main { order: 1 }` at narrow widths was written on the assumption
   that the grid was already one column, where it would simply stack the work
   above the list. With two columns still in force it instead swapped which
   panel got which track: at 720px the work was handed the fixed 248px column
   and the task list took the flexible one.

   Base first, then media rules in descending width order. Nothing after this
   point may set grid-template-columns on .shell. */
.shell {
  --side-col: clamp(264px, var(--side-w, 292px), 25vw);
  grid-template-columns: var(--side-col) minmax(0, 1fr);
}
.shell.side-hidden,
.shell.no-side { grid-template-columns: minmax(0, 1fr); }

@media (max-width: 1000px) {
  .shell { --side-col: clamp(248px, var(--side-w, 252px), 42vw); gap: 16px; }
  .side { order: 1; position: sticky; }
  .main { order: 2; }
}

@media (max-width: 720px) {
  /* Genuinely one column: the list goes under the work. Both panels are told
     they may shrink — a grid item defaults to min-width:auto and refuses to
     go below its content, which is what let the chantier count badges push
     the document 31px wider than the screen. */
  .shell { grid-template-columns: minmax(0, 1fr); }
  .side, .main { min-width: 0; width: 100%; }
  .side { order: 2; position: static; }
  .main { order: 1; }
  /* A sidebar folded away on a desktop must not stay hidden on a phone,
     where the rail that would bring it back is itself hidden. */
  .shell.side-hidden .side { display: block; }
}

/* Says what it does, rather than leaving the icon to explain itself. */
/* The same round button as the + beside it — 26px with a 15px glyph, which
   is the size every add button in the app already uses. It was 34px, which
   read as the larger of the pair. */
.flip-btn {
  width: 26px; height: 26px; padding: 0; gap: 0;
  border-radius: 50%;
  background: var(--sys-blue); color: #fff;
}
.flip-btn:hover { background: #0062CC; color: #fff; }
.flip-btn[aria-pressed="true"] { background: #0A2540; color: #fff; }
.flip-btn svg { width: 15px; height: 15px; }

/* A grid item's automatic minimum is its content, so the face refused to go
   narrower than the whole timeline and pushed it out of the panel. The
   timeline scrolls inside the face; the face fits the panel. */
.flip-face { min-width: 0; }
.flip-face > * { min-width: 0; max-width: 100%; }

/* The panel's controls belong at the far end of its header line. */
.panel-swap > .panel-head .headright { justify-content: flex-end; }


/* ------------------------------------------------------------ hours log

   The clock opens onto the days behind it. The bars are classes because the
   policy forbids style attributes, the same reason the density scrubber
   builds its own. */
.clock { position: relative; }
.clock > summary { list-style: none; cursor: pointer; }
.clock > summary::-webkit-details-marker { display: none; }
.worklog {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px; border-radius: 999px;
  background: var(--sys-fill-2); color: var(--sys-grey-1);
  font-size: 13px; font-variant-numeric: tabular-nums;
  transition: background .15s ease, color .15s ease;
}
.worklog:hover { background: var(--sys-sep); color: var(--ink); }
.clock[open] > .worklog { background: var(--sys-blue); color: #fff; }
.worklog svg { width: 15px; height: 15px; display: block; }              /* ink 13.0 — the reference */

.clock-panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 320;
  width: 290px; padding: 14px 16px 12px;
  background: var(--panel); border: 1px solid var(--sys-sep); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0,0,0,.16);
}
.clock-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding-bottom: 10px; border-bottom: 1px solid var(--sys-sep);
}
.clock-head strong { font-size: 14px; }
.clock-sums { display: flex; gap: 20px; padding: 11px 0 12px; }
.clock-sums .lbl {
  display: block; font-size: 10.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--sys-grey-2);
}
.clock-sums .val { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; }
.clock-empty { margin: 10px 0 2px; font-size: 12.5px; color: var(--sys-grey-2); }

.clock-log { list-style: none; margin: 0; padding: 0; max-height: 260px; overflow-y: auto; }
.clog {
  display: grid; grid-template-columns: 74px 1fr 52px;
  align-items: center; gap: 9px; padding: 4px 0; font-size: 12.5px;
}
.clog-d { color: var(--sys-grey-1); }
.clog.on .clog-d { color: var(--sys-blue); font-weight: 600; }
.clog-bar { height: 6px; border-radius: 999px; background: var(--sys-fill-2); overflow: hidden; }
.clog-bar i { display: block; height: 100%; border-radius: 999px; background: var(--sys-blue); }
.clog.on .clog-bar i { background: var(--sys-blue); }
.clog-h {
  text-align: right; color: var(--sys-grey-2);
  font-variant-numeric: tabular-nums;
}
.clog.on .clog-h { color: var(--ink); font-weight: 600; }

.hb0 { width: 0%; }
.hb1 { width: 1%; }
.hb2 { width: 2%; }
.hb3 { width: 3%; }
.hb4 { width: 4%; }
.hb5 { width: 5%; }
.hb6 { width: 6%; }
.hb7 { width: 7%; }
.hb8 { width: 8%; }
.hb9 { width: 9%; }
.hb10 { width: 10%; }
.hb11 { width: 11%; }
.hb12 { width: 12%; }
.hb13 { width: 13%; }
.hb14 { width: 14%; }
.hb15 { width: 15%; }
.hb16 { width: 16%; }
.hb17 { width: 17%; }
.hb18 { width: 18%; }
.hb19 { width: 19%; }
.hb20 { width: 20%; }
.hb21 { width: 21%; }
.hb22 { width: 22%; }
.hb23 { width: 23%; }
.hb24 { width: 24%; }
.hb25 { width: 25%; }
.hb26 { width: 26%; }
.hb27 { width: 27%; }
.hb28 { width: 28%; }
.hb29 { width: 29%; }
.hb30 { width: 30%; }
.hb31 { width: 31%; }
.hb32 { width: 32%; }
.hb33 { width: 33%; }
.hb34 { width: 34%; }
.hb35 { width: 35%; }
.hb36 { width: 36%; }
.hb37 { width: 37%; }
.hb38 { width: 38%; }
.hb39 { width: 39%; }
.hb40 { width: 40%; }
.hb41 { width: 41%; }
.hb42 { width: 42%; }
.hb43 { width: 43%; }
.hb44 { width: 44%; }
.hb45 { width: 45%; }
.hb46 { width: 46%; }
.hb47 { width: 47%; }
.hb48 { width: 48%; }
.hb49 { width: 49%; }
.hb50 { width: 50%; }
.hb51 { width: 51%; }
.hb52 { width: 52%; }
.hb53 { width: 53%; }
.hb54 { width: 54%; }
.hb55 { width: 55%; }
.hb56 { width: 56%; }
.hb57 { width: 57%; }
.hb58 { width: 58%; }
.hb59 { width: 59%; }
.hb60 { width: 60%; }
.hb61 { width: 61%; }
.hb62 { width: 62%; }
.hb63 { width: 63%; }
.hb64 { width: 64%; }
.hb65 { width: 65%; }
.hb66 { width: 66%; }
.hb67 { width: 67%; }
.hb68 { width: 68%; }
.hb69 { width: 69%; }
.hb70 { width: 70%; }
.hb71 { width: 71%; }
.hb72 { width: 72%; }
.hb73 { width: 73%; }
.hb74 { width: 74%; }
.hb75 { width: 75%; }
.hb76 { width: 76%; }
.hb77 { width: 77%; }
.hb78 { width: 78%; }
.hb79 { width: 79%; }
.hb80 { width: 80%; }
.hb81 { width: 81%; }
.hb82 { width: 82%; }
.hb83 { width: 83%; }
.hb84 { width: 84%; }
.hb85 { width: 85%; }
.hb86 { width: 86%; }
.hb87 { width: 87%; }
.hb88 { width: 88%; }
.hb89 { width: 89%; }
.hb90 { width: 90%; }
.hb91 { width: 91%; }
.hb92 { width: 92%; }
.hb93 { width: 93%; }
.hb94 { width: 94%; }
.hb95 { width: 95%; }
.hb96 { width: 96%; }
.hb97 { width: 97%; }
.hb98 { width: 98%; }
.hb99 { width: 99%; }
.hb100 { width: 100%; }

/* ------------------------------------------------------------- week view

   A month has to fit six rows on a screen, so a crowded day is clipped. A
   week has seven columns and all the height it needs, so every task on a
   day is simply there. */
.cal-switch {
  text-decoration: none; color: var(--sys-grey-1);
  padding: 4px 11px; border-radius: 999px;
  transition: background .15s ease, color .15s ease;
}
.cal-switch:hover { background: var(--sys-fill-2); color: var(--ink); }

.cal-weeknav { display: flex; align-items: center; gap: 6px; padding: 4px 0 10px; }
.cal-step {
  padding: 4px 11px; border-radius: 999px;
  background: var(--sys-fill-2); color: var(--sys-grey-1);
  font-size: 12.5px; text-decoration: none;
}
.cal-step:hover { background: var(--sys-sep); color: var(--ink); }

.cal-wrow .cal-cell {
  height: auto; min-height: 108px; overflow: visible;
  align-content: start;
}
.cal-wrow .cal-evs { animation: none; transform: none; }
/* One line a task. Seven narrow columns and wrapping titles turned a week
   into a page and a half of broken words; the full titles are a click away
   in the day's own window. */
.cal-wrow .cal-ev {
  display: block; width: auto;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding: 4px 7px; font-size: 11.5px; margin-bottom: 3px;
}
.cal-wrow .cal-cell::after { display: none; }
.cal-none { display: block; padding-top: 6px; color: var(--sys-grey-2); font-size: 12px; }

/* No drift or hover-expansion in the week: nothing is hidden to begin with. */
.cal-is-week .cal-cell.has-more .cal-evs { animation: none; }

/* Seven columns need width. Below that the week reads as a list of days —
   which is the same information, and legible. */
/* Seven days read across, always — a week is a shape, and stacking it into a
   list throws that shape away. When the panel is too narrow to give each day
   a readable column, the week scrolls sideways instead of collapsing. */
/* Seven days, all of them, whatever the width: a week you have to scroll is
   not a week you can read at a glance. */
.cal-is-week .cal-head,
.cal-is-week .cal-wrow {
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.cal-is-week .cal-scroll > * { width: 100%; }

/* Hiding the title left its anchor still holding a flex share, so the field
   opened as an 81px stub. While a name is being typed the anchor stands
   aside and the field takes the row. */
.trow:has(.ttitle-in:not([hidden])) .tlink { display: none; }
/* `.ttitle-in` is a <textarea>; this said `input.ttitle-in` and so had
   matched nothing since it was converted. */
.trow .ttitle-in { flex: 1 1 100%; }

/* The schedule button wears a clock; its hands move when you reach for it. */
.flip-btn .fl-hand-h, .flip-btn .fl-hand-m { transform-box: fill-box; transform-origin: 50% 0%; }
.flip-btn .fl-hand-m { transform-origin: 0% 0%; }
@media (prefers-reduced-motion: no-preference) {
  .flip-btn .fl-hand-h { transition: transform .5s cubic-bezier(.32,.72,0,1); }
  .flip-btn .fl-hand-m { transition: transform .5s cubic-bezier(.32,.72,0,1); }
}
.flip-btn:hover .fl-hand-m { transform: rotate(90deg); }
.flip-btn:hover .fl-hand-h { transform: rotate(30deg); }
.flip-btn[aria-pressed="true"] .fl-hand-m { transform: rotate(210deg); }
.flip-btn[aria-pressed="true"] .fl-hand-h { transform: rotate(150deg); }

/* One chantier, the whole panel. */
.gws-solo {
  width: 24px; height: 24px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-2); text-decoration: none;
  transition: color .15s ease, background .15s ease;
}
.gws-solo:hover { color: var(--ink); background: var(--sys-fill-2); }
/* Same ink rule as the topbar. The corner brackets span 12.7 of their
   20-unit frame where the envelope beside them spans 14.7, so at a shared
   15px the brackets rendered 9.5px against the envelope's 11.0px and the
   pair never read as equal. 17.4px is what puts them level. */
.gws-solo svg { width: 17.4px; height: 17.4px; display: block; }   /* ink 9.5 → 11.0 */

/* The sheet holds more than the map is tall — a wiki card, twelve figures
   and ten party lines — so most of it was below a fold with nothing to say
   so. A fade at the foot and a visible rail make the scroll legible. */
.fiche-over > .pad {
  scrollbar-width: thin;
  scrollbar-color: var(--sys-grey-2) transparent;
  mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent 100%);
  padding-bottom: 22px;
}
.fiche-over > .pad::-webkit-scrollbar { width: 6px; }
.fiche-over > .pad::-webkit-scrollbar-thumb {
  background: var(--sys-sep); border-radius: 4px;
}
.fiche-over > .pad:hover::-webkit-scrollbar-thumb { background: var(--sys-grey-2); }

/* The Wikipedia card is the tallest single block; it gets a ceiling so the
   figures below it are not pushed out of sight on arrival. */
.fiche-over .wiki-x {
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The card you just moved to announces itself: a short rise and a ring that
   fades, so the eye lands on the right one after the page reloads. */
@keyframes gt-arrive {
  0%   { transform: translateY(10px) scale(.985); opacity: .55; }
  55%  { transform: translateY(0) scale(1); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
@keyframes gt-ring {
  0%   { box-shadow: 0 0 0 0 rgba(0,122,255,.42); }
  100% { box-shadow: 0 0 0 14px rgba(0,122,255,0); }
}
@media (prefers-reduced-motion: no-preference) {
  .gtask.gt-arrive > .row > .bar {
    animation: gt-arrive .46s cubic-bezier(.32,.72,0,1), gt-ring .7s ease-out;
  }
  .gtask.gt-arrive .gbody {
    animation: gt-arrive .52s cubic-bezier(.32,.72,0,1) .04s both;
  }
  /* And the row that sent you there lights up in step with it. */
  .trow.on { animation: gt-row .7s ease-out; }
}
@keyframes gt-row {
  0%   { background: rgba(0,122,255,.28); }
  100% { background: #DCEBFB; }
}

/* ------------------------------------------------------- the fiche's rail

   The sheet was pinned to the top of the map, so scrolling the page slid it
   under the sticky header and cut its first lines off. It now rides a rail
   the height of the map and sticks just below the header: it follows you
   down, stays whole, and still cannot leave the picture it belongs to. */
.fiche-rail {
  position: absolute; top: 14px; right: 14px; bottom: 14px;
  width: min(360px, 44%);
  pointer-events: none; z-index: 12;
}
.fiche-over {
  position: sticky; top: calc(var(--topbar-h, 116px) + 14px);
  left: auto; right: auto; bottom: auto; width: 100%;
  /* A sticky box only travels if it is shorter than the rail it rides.
     Filling the rail exactly left it nothing to slide along, which is why it
     scrolled away with the page. */
  max-height: min(calc(100vh - var(--topbar-h, 116px) - 34px), calc(100% - 48px));
  pointer-events: auto;
}


/* ------------------------------------------------ the plan, as a shape

   One column a day over the horizon: where the work piles up and where the
   weeks are empty. Heights are classes because the policy forbids style
   attributes, the same reason the hours log builds its own. */
.chg-chart { margin: 0 0 14px; }
.chg-bars {
  position: relative; display: flex; align-items: flex-end; gap: 1px;
  height: 132px; padding: 0 0 2px;
  border-bottom: 1px solid var(--sys-sep);
}
.chg-b {
  flex: 1 1 0; min-width: 0; min-height: 1px;
  background: var(--sys-blue); border-radius: 2px 2px 0 0;
  opacity: .78; transition: opacity .12s ease;
}
.chg-b:hover { opacity: 1; }
.chg-b.over { background: var(--sys-red); opacity: .9; }
.chg-b.nil  { background: var(--sys-sep); opacity: .5; min-height: 2px; }
.chg-b.now  { background: var(--ink); opacity: 1; }

/* The ceiling, drawn across so the overshoot is visible rather than implied. */
.chg-cap {
  position: absolute; left: 0; right: 0; bottom: 2px;
  border-top: 1px dashed var(--sys-grey-2); opacity: .55;
  pointer-events: none; background: none;
}
.chg-months {
  display: flex; margin-top: 6px;
  font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--sys-grey-2);
}
/* Each month keeps its own band, with a rule at the boundary — otherwise the
   labels run into one another and read as one long word. */
.chg-months span {
  min-width: 0; overflow: hidden; white-space: nowrap;
  padding-left: 6px; border-left: 1px solid var(--sys-sep);
}
.chg-months span:first-child { padding-left: 0; border-left: 0; }

.chg-read {
  margin: 0 0 18px; font-size: 13.5px; line-height: 1.6; color: var(--ink-soft);
  max-width: 74ch;
}
.chg-read strong { color: var(--ink); }

.cb0 { height: 0%; }
.cb1 { height: 1%; }
.cb2 { height: 2%; }
.cb3 { height: 3%; }
.cb4 { height: 4%; }
.cb5 { height: 5%; }
.cb6 { height: 6%; }
.cb7 { height: 7%; }
.cb8 { height: 8%; }
.cb9 { height: 9%; }
.cb10 { height: 10%; }
.cb11 { height: 11%; }
.cb12 { height: 12%; }
.cb13 { height: 13%; }
.cb14 { height: 14%; }
.cb15 { height: 15%; }
.cb16 { height: 16%; }
.cb17 { height: 17%; }
.cb18 { height: 18%; }
.cb19 { height: 19%; }
.cb20 { height: 20%; }
.cb21 { height: 21%; }
.cb22 { height: 22%; }
.cb23 { height: 23%; }
.cb24 { height: 24%; }
.cb25 { height: 25%; }
.cb26 { height: 26%; }
.cb27 { height: 27%; }
.cb28 { height: 28%; }
.cb29 { height: 29%; }
.cb30 { height: 30%; }
.cb31 { height: 31%; }
.cb32 { height: 32%; }
.cb33 { height: 33%; }
.cb34 { height: 34%; }
.cb35 { height: 35%; }
.cb36 { height: 36%; }
.cb37 { height: 37%; }
.cb38 { height: 38%; }
.cb39 { height: 39%; }
.cb40 { height: 40%; }
.cb41 { height: 41%; }
.cb42 { height: 42%; }
.cb43 { height: 43%; }
.cb44 { height: 44%; }
.cb45 { height: 45%; }
.cb46 { height: 46%; }
.cb47 { height: 47%; }
.cb48 { height: 48%; }
.cb49 { height: 49%; }
.cb50 { height: 50%; }
.cb51 { height: 51%; }
.cb52 { height: 52%; }
.cb53 { height: 53%; }
.cb54 { height: 54%; }
.cb55 { height: 55%; }
.cb56 { height: 56%; }
.cb57 { height: 57%; }
.cb58 { height: 58%; }
.cb59 { height: 59%; }
.cb60 { height: 60%; }
.cb61 { height: 61%; }
.cb62 { height: 62%; }
.cb63 { height: 63%; }
.cb64 { height: 64%; }
.cb65 { height: 65%; }
.cb66 { height: 66%; }
.cb67 { height: 67%; }
.cb68 { height: 68%; }
.cb69 { height: 69%; }
.cb70 { height: 70%; }
.cb71 { height: 71%; }
.cb72 { height: 72%; }
.cb73 { height: 73%; }
.cb74 { height: 74%; }
.cb75 { height: 75%; }
.cb76 { height: 76%; }
.cb77 { height: 77%; }
.cb78 { height: 78%; }
.cb79 { height: 79%; }
.cb80 { height: 80%; }
.cb81 { height: 81%; }
.cb82 { height: 82%; }
.cb83 { height: 83%; }
.cb84 { height: 84%; }
.cb85 { height: 85%; }
.cb86 { height: 86%; }
.cb87 { height: 87%; }
.cb88 { height: 88%; }
.cb89 { height: 89%; }
.cb90 { height: 90%; }
.cb91 { height: 91%; }
.cb92 { height: 92%; }
.cb93 { height: 93%; }
.cb94 { height: 94%; }
.cb95 { height: 95%; }
.cb96 { height: 96%; }
.cb97 { height: 97%; }
.cb98 { height: 98%; }
.cb99 { height: 99%; }
.cb100 { height: 100%; }

/* Seven columns in a narrow panel leaves each day about a thumb wide, so the
   pills there are set a size down and allowed to break inside words. */
.cal-is-week .cal-ev { font-size: 11px; line-height: 1.35; }

/* An open week day shows its titles whole; closed, they are one clipped line
   so that seven days fit across. */
.cal-is-week .cal-cell.cal-open .cal-ev {
  white-space: normal; overflow: visible; text-overflow: clip;
  padding: 5px 8px;
}
.cal-is-week .cal-cell.cal-open { z-index: 6; }

/* Cards mode holds its place while the timeline scrolls behind it. */
.gws.as-cards { position: sticky; left: 0; }

/* The whole open-card layout is built on --card-w, and an open bar takes its
   min and max width from it — which is why the header sat 20px proud of the
   card whatever was done to .bar itself. In cards mode the card is the
   measure, so the variable is what changes. */
.gws.as-cards > details.gtask { --card-w: 100%; overflow: hidden; }


/* The bin is the row's one control: nothing until the pointer is on the row,
   then plainly a bin, in the red of the thing it does. */
.trow .tdel-b {
  width: 30px; height: 30px; border-radius: 8px;
  background: #fff; color: var(--sys-red);
  box-shadow: 0 0 0 1px rgba(255,59,48,.28), 0 1px 4px rgba(0,0,0,.10);
  /* The hide waits .35s: a pointer that overshoots the bin by a few pixels
     and comes back finds it still there (fifth round, below). */
  opacity: 0; transition: opacity .14s ease .35s, background .14s ease, transform .14s ease;
  /* Above the row's hover fade (`.trow::after`, painted after the bin in
     tree order, opaque from 42% of its 88px): without this the bin is
     drawn UNDER the fade — computed opacity 1, not one pixel on screen.
     Found by screenshot on 2026-09-03; every earlier round had judged the
     reveal by computed style, which cannot see paint order. */
  z-index: 2;
}
.trow:hover .tdel-b, .trow.hot .tdel-b { transition-delay: 0s; }
/* But a pointer that has moved to ANOTHER row has not overshot — it has left,
   and the hold above is then just a bin that will not go. Sweeping down the
   list, each row keeps its bin for .35s plus the .14s fade, so rows crossed
   within half a second stack up: measured on the fixture page, three hovers in
   one gesture left two bins lit at once, and a longer list leaves more.
   Reproduced by screenshot, 5 September 2026 — the hold is invisible to a
   computed-style check, which reads the same opacity either way.

   While the pointer is still inside the list, hide at once. When it leaves the
   list entirely `.tlist:hover` goes false and the .35s above returns, so the
   overshoot forgiveness the fifth round added is untouched — that is the case
   this rule must NOT break, and the two are distinguishable only here. */
.tlist:hover .trow:not(:hover) .tdel-b { transition-delay: 0s; }
/* Fifth round, 2026-09-03 — measured on the live page, not reasoned: the row
   ends 6px past the bin (bin x 234–264, row edge 270 at the 264px column),
   and beyond that edge the pointer is over nothing — the handle's rail takes
   no pointer events — so 8px of overshoot (x 272) reads `.shell` under the
   pointer, `.trow:hover` goes false and the bin fades in 140ms. The handle
   fix of the fourth round was real but left this. The row's hover zone now
   runs 14px into the gutter (the gutter is 16px; the timeline panel is not
   reached), so the bin is still the row's while the pointer is beside it. */
.trow::before {
  content: ""; position: absolute; top: 0; bottom: 0; right: -14px; width: 14px;
}
/* Back to hover-only, now that hovering actually works.

   The original report — "on hover the trashcan doesn't systematically appear"
   — turned out to be geometry, not the hover rule. The floating sidebar handle
   was mispositioned 59px inside the task list (it derived the column width
   with `min()` where the column uses `clamp()`), so it spanned x 211–249 while
   the bin sits at x 234–264. Fifteen pixels of overlap, at mid-list height,
   with z-index 22 over the row: hovering the bin hovered the handle instead,
   `.trow:hover` went false, and the bin disappeared — on some rows and not
   others, which is exactly what "not systematically" describes.

   With the handle back on the sidebar's edge the reveal is reliable, so the
   bin is quiet again until the row is under the pointer. */
.trow .tdel-b { opacity: 0; }
.trow:hover .tdel-b,
.trow.hot .tdel-b,
.trow .tdel-b:focus-visible { opacity: 1; }
.trow .tdel-b:hover { background: var(--sys-red); color: #fff; transform: translateY(-50%) scale(1.04); }
.trow .tdel-b svg { width: 19px; height: 19px; }
@media (hover: none) { .trow .tdel-b { opacity: 1; } }

/* While the editor is open the bin steps aside — it discards nothing there. */
.trow:has(.ttitle-in:not([hidden])) .tdel-b { opacity: 0; pointer-events: none; }

/* The title is no longer a link; it is text you can select and double-click. */
.trow .tlink { cursor: default; user-select: none; }
.trow .tlink:hover .ttitle { text-decoration: underline dotted var(--sys-grey-2); text-underline-offset: 3px; }

/* "A white box which matches the size of the card": while the editor is open
   the circle and the bin step aside and the box takes the whole row. */
/* The box takes the row's width beside the circle, which stays put — it is
   the row's other control, not decoration. */
.trow:has(.ttitle-in:not([hidden])) {
  padding: 2px 0 2px 2px;
  gap: 6px;
  margin-left: -6px;
  margin-right: 0;
}

.trow:has(.ttitle-in:not([hidden])) .ttitle-in { flex: 1 1 100%; width: 100%; }

/* A hovered day may take width from its neighbours, never height from the
   page: the panel must not grow under the pointer. If the day holds more
   than the row is tall, its list scrolls inside the cell. */
.cal-grid.cal-row-open .cal-cell { height: 96px; min-height: 0; }
.cal-cell.cal-open .cal-evs { max-height: 100%; overflow-y: auto; overscroll-behavior: contain; }
/* Pinned by the script above; the cells fill it and their lists scroll. */
.cal-is-week .cal-grid.cal-row-open { overflow: hidden; }
.cal-is-week .cal-grid.cal-row-open .cal-cell { height: 100%; min-height: 0; }
.cal-is-week .cal-cell.cal-open .cal-evs { max-height: 100%; overflow-y: auto; }

/* Neighbouring communes, named once you are zoomed into one — quieter than
   the selected one so the subject still reads first. */
/* A light fill needs a dark halo. This was white text inside the group's
   white halo, on a commune view where the aerial runs at full strength — so
   the halo, whose whole job is to hold the glyph off the ground, merged with
   it and just fattened the letters. */
.map .labels .lb-near { fill: rgba(255,255,255,.96); font-weight: 500;
                        stroke: rgba(11,22,41,.55); }
.map:not(.zoomed) .labels .lb-near { display: none; }

/* The district view's own names: dark ink on the group's white halo, which
   reads at both ends of the colour ramp where white on white does not. */
.map .labels .lb-dist { font-weight: 600; }

/* The fly-in adds .zoomed and animates the viewBox directly, without calling
   the routine that rescales the group's font-size — so across the 520 ms
   flight the names would swell. Drop them for the flight. Scoped to
   .map-dist, so the commune page (which carries .zoomed from the server)
   keeps its neighbour names throughout. */
.map-dist.zoomed .labels .lb-dist,
.map-dist.zoomed .labels .lb-hover { display: none; }

/* ------------------------------------------------------------- tendances */
#ts-6:checked ~ .tpanes .tpane[data-pane="6"] { display: block; }
#ts-6:checked ~ .panel-head label[for="ts-6"] {
  background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
.trend-tbl { margin: 6px 0 20px; }
.trend-tbl .up { color: var(--sys-green, #34C759); font-weight: 600; }
.trend-tbl .down { color: var(--sys-red); font-weight: 600; }
.trend-tbl tr.trend-hi { background: var(--accent-bg, #EAF3FF); }
.trend-tbl tr.trend-hi td { font-weight: 600; }

.trend-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 22px; margin: 4px 0 20px; }
.trend-h { margin: 0 0 8px; font-size: 11px; letter-spacing: .06em;
           text-transform: uppercase; color: var(--sys-grey-2); }
.trend-list { list-style: none; margin: 0; padding: 0; }
.trend-list li {
  display: grid; grid-template-columns: 1fr auto auto; gap: 10px;
  align-items: baseline; padding: 5px 0; border-top: 1px solid var(--sys-sep); font-size: 13.5px;
}
.trend-list li:first-child { border-top: 0; }
.tl-v { font-variant-numeric: tabular-nums; font-weight: 600; }
.tl-v.up { color: var(--sys-green, #34C759); }
.tl-v.down { color: var(--sys-red); }
.tl-s { font-size: 12px; color: var(--sys-grey-2); font-variant-numeric: tabular-nums; }

.trend-caveats { margin: 6px 0 4px; padding-left: 20px; font-size: 13px;
                 line-height: 1.6; color: var(--ink-soft); max-width: 74ch; }
.trend-caveats li { margin-bottom: 7px; }

/* swisstopo's attribution — quiet, but present, which is the requirement. */
.map-credit {
  position: absolute; right: 12px; bottom: 8px; z-index: 13; margin: 0;
  font-size: 10.5px; letter-spacing: .01em; color: rgba(255,255,255,.86);
  text-shadow: 0 1px 3px rgba(0,0,0,.55);
  pointer-events: none;
}


/* ---- the map's key --------------------------------------------------------
   The map spent its whole life unlabelled: 47 shaded shapes and 47 dots with
   nothing on the page saying what either meant. The sentence that used to say
   it was deleted, twice, on request — so this names the quantities without
   explaining them, which is the part that was actually wanted. */
.maplg {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 22px;
  margin-top: 10px; padding: 0 2px;
  font-size: 11.5px; color: var(--sys-grey-1);
  font-variant-numeric: tabular-nums;
}
.maplg-part { display: inline-flex; align-items: center; gap: 7px; }
.maplg-h { font-weight: 600; color: var(--ink); }
.maplg-n { color: var(--sys-grey-1); }
.maplg-ramp { display: inline-flex; border-radius: 4px; overflow: hidden; }
.maplg-ramp i { display: block; width: 20px; height: 9px; }
.maplg-dot {
  display: inline-block; border-radius: 50%;
  background: rgba(11, 22, 41, .42); flex: 0 0 auto;
}
@media (max-width: 620px) { .maplg { gap: 6px 14px; font-size: 11px; } }


/* ---- the calendar on a small screen --------------------------------------
   This has to come after every unconditional `.cal-cell` height in the file,
   or it loses on source order — which is exactly what happened while it sat
   up in the settings block: a phone rendered 96px cells with full-size event
   pills and the month ran off the bottom of the screen. */
@media (max-width: 640px) {
  .cal-cell { height: 56px; }
  /* The row pin must agree with the height above it, or a hovered day grows
     its row and the month below it moves. */
  .cal:not(.cal-is-week) .cal-grid:not(.cal-head) { grid-auto-rows: 56px; }
  .cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):hover,
  .cal:not(.cal-is-week) .cal-grid:not(.cal-head) .cal-cell:not(.empty):focus-within { height: 112px; }
  .cal-grid.cal-row-open .cal-cell { min-height: 56px; }
  .cal-ev { font-size: 0; padding: 3px; }
}

/* ---- an opened day must stay inside its own cell -------------------------
   `.cal-evs` was capped at `max-height: 100%`, which resolves against the
   whole cell — but the list sits *below* the day number, so a crowded day
   hung about 19px past the bottom edge and painted over the week beneath it.
   That is the thing n=97 asked never to happen, and the audit that cleared
   n=97 was a grep for size changes in `:hover` rules, which this is not.
   Making the cell a column and giving the list what is left over pins it. */
.cal-cell.cal-open { display: flex; flex-direction: column; overflow: hidden; }
/* Flex items stretch by default and the day number is a rounded pill, so
   without this it would run the full width of the cell. */
.cal-cell.cal-open .cal-open-day { flex: 0 0 auto; align-self: flex-start; }
.cal-cell.cal-open .cal-evs,
.cal-is-week .cal-cell.cal-open .cal-evs {
  flex: 1 1 auto; min-height: 0; max-height: none;
  overflow-y: auto; overscroll-behavior: contain;
}


/* ---- week view: give the week the room ----------------------------------
   The month scrubber is a way of choosing a month. In week view you are not
   choosing a month, and it was taking a strip off the top of the only thing
   you are looking at. It goes; the week's own header still carries the date
   and the link back to the month. */
/* The hiding itself is done in the markup (the nav takes `.vh` in week view),
   because the scrubber is a sibling *before* `.cal` and no selector here can
   reach backwards to it. */

/* And the week scrolls sideways rather than squeezing seven days into
   whatever is left. Each day keeps a legible minimum instead of collapsing;
   below that the row scrolls. */
.cal-is-week .cal-scroll {
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.cal-is-week .cal-scroll > * { width: auto; min-width: 100%; }
.cal-is-week .cal-grid { grid-template-columns: repeat(7, minmax(112px, 1fr)); }
.cal-is-week .cal-cell { scroll-snap-align: start; }
/* A visible edge to the scroll, so it reads as scrollable rather than cut. */
.cal-is-week .cal-scroll::-webkit-scrollbar { height: 8px; }
.cal-is-week .cal-scroll::-webkit-scrollbar-thumb {
  background: var(--sys-grey-2); border-radius: 4px;
}

/* ---- an opened day grows again ------------------------------------------
   `height: 96px` further up pinned the row, so the day could only ever
   scroll its own contents. The row takes its content's height again, which
   is what "expand vertically" asked for; the horizontal share is still the
   one the script computes. */
.cal-grid.cal-row-open .cal-cell { height: auto; min-height: 96px; }
.cal-cell.cal-open { overflow: visible; }
.cal-cell.cal-open .cal-evs,
.cal-is-week .cal-cell.cal-open .cal-evs {
  flex: 0 1 auto; max-height: none; overflow: visible;
}


/* ---- the compliance register: a contents list down the side --------------
   Seven topics across the top wrapped onto two rows and pushed the text down;
   as a column they read as what they are, the sections of one document. The
   radios that drive it are `.vh`, which is `position: absolute`, so they take
   no grid cell of their own. */
.conftabs {
  display: grid;
  grid-template-columns: minmax(0, 208px) minmax(0, 1fr);
  align-items: stretch;
}
.conftabs > .panel-head {
  grid-column: 1; grid-row: 1;
  border-bottom: 0; border-right: 1px solid var(--sys-sep);
  padding: 16px 12px;
  /* `.panel-head` is a centred flex ROW by default — which put the heading and
     the column of topics side by side in a 208px rail and pushed the topics
     clean out of it, leaving a tall empty box with only the word SUJETS in it.
     Down the page, and aligned to the top. */
  display: flex; flex-direction: column; align-items: stretch;
  justify-content: flex-start; gap: 0;
}
.conftabs .ttabs-h {
  display: block; padding: 0 10px 10px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--sys-grey-1);
}
.conftabs .ttabs { flex-direction: column; flex-wrap: nowrap; gap: 1px; }
.conftabs .ttabs label {
  border-radius: 8px; border-bottom: 0;
  padding: 8px 10px; white-space: normal;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.conftabs > .tpanes { grid-column: 2; grid-row: 1; min-width: 0; }

/* The selected topic reads as selected in a column, where a raised tab edge
   no longer means anything. */
#fs-0:checked ~ .panel-head label[for="fs-0"],
#fs-1:checked ~ .panel-head label[for="fs-1"],
#fs-2:checked ~ .panel-head label[for="fs-2"],
#fs-3:checked ~ .panel-head label[for="fs-3"],
#fs-4:checked ~ .panel-head label[for="fs-4"],
#fs-5:checked ~ .panel-head label[for="fs-5"],
#fs-6:checked ~ .panel-head label[for="fs-6"],
#fs-7:checked ~ .panel-head label[for="fs-7"] {
  background: var(--sys-blue); color: #fff; box-shadow: none; font-weight: 600;
}
#fs-0:checked ~ .panel-head label[for="fs-0"] .ttab-n,
#fs-1:checked ~ .panel-head label[for="fs-1"] .ttab-n,
#fs-2:checked ~ .panel-head label[for="fs-2"] .ttab-n,
#fs-3:checked ~ .panel-head label[for="fs-3"] .ttab-n,
#fs-4:checked ~ .panel-head label[for="fs-4"] .ttab-n,
#fs-5:checked ~ .panel-head label[for="fs-5"] .ttab-n,
#fs-6:checked ~ .panel-head label[for="fs-6"] .ttab-n,
#fs-7:checked ~ .panel-head label[for="fs-7"] .ttab-n { opacity: .8; color: #fff; }

/* Narrow, the column costs more than it gives: back across the top. The
   threshold is 640px rather than 860px because the window this is actually
   read in is around 760px wide — at 860 the column never appeared at all. A
   208px rail still leaves ~500px of measure, which is a comfortable line. */
@media (max-width: 640px) {
  .conftabs { grid-template-columns: minmax(0, 1fr); }
  .conftabs > .panel-head {
    grid-column: 1; grid-row: 1;
    border-right: 0; border-bottom: 1px solid var(--sys-sep);
  }
  .conftabs .ttabs { flex-direction: row; flex-wrap: wrap; }
  .conftabs .ttabs label { white-space: nowrap; }
  .conftabs > .tpanes { grid-column: 1; grid-row: 2; }
}


/* ---- what the map is measuring ------------------------------------------
   Small chips in the panel head. `.mswitch` was orphaned CSS for a control
   that had been removed; this is the control coming back, so the old rules
   are superseded by these rather than left to contradict them. */
.mswitch { display: flex; flex-wrap: wrap; gap: 4px; margin-left: auto; }
.msw {
  width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); background: var(--sys-fill-2);
  text-decoration: none; flex: 0 0 auto;
}
/* Same ink rule as the topbar: the target and the density grid fill their
   frame where the ballot box and the rising line do not, so the box sizes
   differ to make the drawn marks read as one size. */
.msw svg { width: 17px; height: 17px; display: block; }
.msw[href*="metric=turnout"] svg { width: 18.5px; height: 18.5px; }
.msw[href*="metric=trend"] svg   { width: 18.5px; height: 18.5px; }
.msw:hover { color: var(--ink); background: var(--sys-sep); }
.msw.on { color: #fff; background: var(--sys-blue); font-weight: 600; }
@media (max-width: 640px) { .msw { width: 27px; height: 27px; } }


/* A tip with no room above it opens downwards instead of under the header. */
.tipped.tip-below::after {
  bottom: auto; top: calc(100% + 8px);
  transform: translateX(-50%) translateY(-3px);
}
.tipped.tip-below:hover::after,
.tipped.tip-below:focus-visible::after { transform: translateX(-50%) translateY(0); }
.gws-meta .tipped.tip-below::after,
.headright .tipped.tip-below::after { transform: translateY(-3px); }
.gws-meta .tipped.tip-below:hover::after,
.headright .tipped.tip-below:hover::after { transform: translateY(0); }


/* ---- choosing a commune -------------------------------------------------
   Circles first, sized by population, then all one size — and finally plain
   buttons, because this is a selector and the map beside it already carries
   both size and colour. A name in a box is the quickest thing to scan. */
.cpick { display: flex; flex-wrap: wrap; gap: 6px; }
.cpick-b {
  padding: 6px 11px; border-radius: 8px;
  font-size: 12.5px; font-weight: 500; line-height: 1.35;
  color: var(--ink); background: var(--sys-fill-2);
  text-decoration: none; white-space: nowrap;
}
.cpick-b:hover { background: var(--sys-sep); }
.cpick-b.on { background: var(--sys-blue); color: #fff; font-weight: 600; }
@media (max-width: 640px) { .cpick-b { font-size: 12px; padding: 5px 9px; } }

.bfiche { border-top: 1px solid var(--sys-sep); padding-top: 16px; }
.bfiche-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.bfiche-head h3 { margin: 0; font-size: 20px; }
.bfiche-w { font-size: 12.5px; }
.bfiche-grid {
  display: grid; grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
  gap: 18px; margin-top: 12px; align-items: start;
}
.bfiche-map {
  width: 100%; height: auto; border-radius: 12px; display: block;
  box-shadow: 0 1px 3px rgba(0,0,0,.12);
}
.bfiche-side { min-width: 0; }
.bfiche-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 12px 18px; margin: 14px 0 0;
}
.bfiche-facts div { min-width: 0; }
.bfiche-facts dt { font-size: 11.5px; color: var(--sys-grey-1); }
.bfiche-facts dd {
  margin: 2px 0 0; font-size: 17px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.bfiche-muni { margin-top: 16px; }
.bfiche-muni h4 {
  margin: 0 0 6px; font-size: 10.5px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--sys-grey-1);
}
.bfiche-syndic { margin: 0 0 4px; font-size: 13.5px; }
.bfiche-muni-l { margin: 0; padding-left: 18px; font-size: 13px; color: var(--sys-grey-1); }
/* Said plainly, because an unconfirmed name in a campaign tool has to look
   unconfirmed. */
.bfiche-asof { margin: 6px 0 0; font-size: 11.5px; color: var(--sys-grey-1); }
/* The party or list of a Municipalité member (3 Sep 2026): one quiet tag after
   the name; a national party a shade darker than a local list. */
.bfiche-party { display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 8px; font-size: 11px; font-weight: 600; line-height: 16px; letter-spacing: .01em; color: var(--sys-grey-1); background: rgba(0,0,0,.06); vertical-align: 1px; white-space: nowrap; }
.bfiche-party-nat { color: #1d1d1f; background: rgba(0,0,0,.09); }
.bfiche-parties-asof { margin-top: 2px; }
.bfiche-none, .bfiche-pick {
  margin: 14px 0 0; font-size: 13px; color: var(--sys-grey-1);
}
@media (max-width: 860px) {
  .bfiche-grid { grid-template-columns: minmax(0, 1fr); }
}


/* ---- the open card's indent ---------------------------------------------
   In the timeline an open card lines up under its own bar, so it is indented
   by its start date. Those offsets used to be written straight onto the id
   (`#gd-x{margin-left:12%}`), which outranks every class — so when a chantier
   turned into cards there was no way to clear them, and each card sat pushed
   right by its dates with the panel showing through to its left. The id now
   carries only the numbers; the margins are applied here, where cards mode
   can override them. */
.gantt .gbody { margin-left: var(--gd-ml, 0); margin-right: var(--gd-mr, 0); }
.gws.as-cards .gbody { margin-left: 0; margin-right: 0; }


/* ---- roads and rail over the ground -------------------------------------
   Separate tile layers from swisstopo, PNG so the imagery shows through. They
   sit above the aerial and below the commune shapes, so a boundary still reads
   over a motorway. swisstopo publishes both only to z22 against the imagery's
   z28, so at deep zoom they are the coarser of the two — which is why they are
   drawn slightly soft rather than pretending to be sharp. */
.overtiles { pointer-events: none; }
.overmap { image-rendering: auto; }
/* The road network is dense; at district scale it reads better held back a
   little, so the choropleth underneath is still legible. */
.ov-routes { opacity: .72; }
.ov-rail   { opacity: .9; }

.ovswitch { display: flex; gap: 4px; margin-left: auto; }
.ovsw {
  width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); background: var(--sys-fill-2);
  text-decoration: none; flex: 0 0 auto;
}
.ovsw svg { width: 17px; height: 17px; display: block; }
.ovsw:hover { color: var(--ink); background: var(--sys-sep); }
.ovsw.on { color: #fff; background: var(--sys-blue); }
.mswitch { margin-left: 6px; }


/* With a network layer on, the choropleth steps back so the roads and rails
   underneath actually read. The commune fills exist to compare places; once
   you have turned the roads on you are looking at how to get between them,
   and a boundary at .45 is still perfectly legible. */
.map.has-net path { fill-opacity: .45; }
.map.has-net .labels text { stroke-width: 5.5; }


/* The week's scroll controls, on the edges of the week rather than up in the
   panel header — they move the calendar, so they belong to it. Floated over
   the scroller so they cost the seven days no width. */
.cal-is-week { position: relative; }
.cal-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 4;
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--sys-grey-1); background: #fff; cursor: pointer;
  box-shadow: 0 1px 3px rgba(0,0,0,.16), 0 0 0 1px rgba(0,0,0,.05);
}
.cal-arrow-l { left: -6px; }
.cal-arrow-r { right: -6px; }
.cal-arrow svg { width: 16px; height: 16px; display: block; }
.cal-arrow:hover { color: var(--ink); background: var(--sys-sep); }
.cal-arrow:disabled { opacity: .3; cursor: default; }
.cal-arrow:disabled:hover { color: var(--sys-grey-1); background: #fff; }
.cal-arrow:hover { background: #fff; color: var(--ink); }


/* An opened message. Plain text in a plain box — the body comes from outside
   and is never rendered as markup. */
.msg-meta {
  display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: baseline;
  margin: 0 0 12px; font-size: 12.5px; color: var(--sys-grey-1);
}
.msg-from { font-weight: 600; color: var(--ink); }
.msg-body {
  margin: 0; font: inherit; font-size: 13.5px; line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere;
  color: var(--ink); background: var(--sys-fill-2);
  padding: 14px 16px; border-radius: 12px;
  max-height: 60vh; overflow-y: auto;
}


/* ---- the load as a calendar seen in perspective --------------------------
   A month grid, Monday first, tilted away from the reader. One task due that
   day is one block, in the colour of its chantier, stacked on the day it falls.
   Hovering a day zooms the board onto it and stands its tasks up as a tower of
   labelled slabs.

   Two rules govern every number here:

   1. Nothing may carry a `style` attribute — the policy drops them. Colour
      arrives as `h-<chantier>` (the nonce'd sheet already defines `--hue` for
      each), height as a level class, and only the zoom offset is written from
      JS, through CSSOM, which the policy does allow.

   2. `filter` and `opacity` flatten a 3-D subtree. The faces are therefore
      lit by writing three explicit lightnesses off the same hue rather than by
      filtering one colour, and the board is never faded — a scrim is laid over
      it instead. */

/* The switch between the tilted board and the flat grid has to follow the
   width of THIS panel, not the width of the window: with the task rail open
   the panel is under 400px inside a 1400px window, and a viewport query would
   happily keep drawing a seven-column board in perspective at 26px a cell. */
.c3 { margin: 0; padding: 2px 0 0; container-type: inline-size; }

/* ---- the month bar */
.c3-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 0 2px 8px;
}
.c3-mname {
  margin: 0; flex: 1 1 auto; font-size: 15px; font-weight: 600;
  letter-spacing: -.01em; text-transform: none; color: var(--ink);
}
.c3-navs { display: flex; align-items: center; gap: 4px; }
.c3-today, .c3-nav {
  border: 1px solid var(--sys-sep); background: var(--panel); color: var(--ink);
  border-radius: 8px; cursor: pointer; font: inherit; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.c3-today { font-size: 11.5px; padding: 5px 10px; }
.c3-nav   { width: 26px; height: 26px; padding: 0; }
.c3-nav svg { width: 15px; height: 15px; display: block; }
.c3-today:hover, .c3-nav:hover:not(:disabled) { background: var(--sys-fill-2); border-color: var(--sys-grey-2); }
.c3-nav:disabled { opacity: .35; cursor: default; }

/* ---- the stage
   .c3-wrap is the plain 2-D box the zoomed card is laid over. The card cannot
   live inside .c3-stage: the stage carries the `perspective`, so everything
   under it is sorted in three dimensions, and the near rows of a tilted board
   sit in front of z = 0 — they would be painted over the card. */
.c3-wrap { position: relative; }
.c3-stage {
  position: relative;
  perspective: 1500px;
  perspective-origin: 50% 30%;
  height: 400px;
  overflow: hidden;
}
/* preserve-3d, or the stage's perspective stops at this element and the board,
   a grandchild, is projected flat. */
.c3-month {
  position: absolute; inset: 0; display: none; justify-content: center;
  transform-style: preserve-3d;
}
.c3-month.on { display: flex; }

/* The month is a full-size plane at z = 0, and the board inside it is tilted
   44 degrees about X — so every row above the middle of the calendar sits
   BEHIND that plane, and the plane took their pointer events. On
   hq/index.php the back half of the board could not be clicked or hovered at
   all: the day zoom never opened, and the dwell never armed, because the hit
   landed on .c3-month and hq.js's `closest('.c3-day[role=button]')` came back
   null. It looked like dead JavaScript and was dead geometry.

   The plane paints nothing. It exists to hold the board and to be switched
   with `display`, so it has no business in hit testing at all. The cells take
   their hits back explicitly — `pointer-events` inherits, so this is the whole
   of it, and the blocks and numbers inside a cell come with it. */
.c3-month { pointer-events: none; }
.c3-cell  { pointer-events: auto; }

.c3-board {
  position: relative;             /* offsetParent for the zoom centring */
  align-self: flex-start;
  margin-top: 26px;
  width: min(700px, 92%);
  display: flex; flex-direction: column; gap: 5px;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
  /* Order matters and this one is right: the rightmost function applies
     first, so the board is translated in its OWN unscaled pixels — which is
     what offsetLeft/offsetTop give the script — and the zoom then magnifies
     the already-centred board about its middle. Reversing the two looks like
     a fix and under-corrects by exactly the zoom factor; measured, and the
     day ends up against the left edge. */
  transform: rotateX(var(--c3-rx, 44deg)) rotateZ(var(--c3-rz, -2deg))
             scale(var(--c3-s, 1))
             translate(var(--c3-tx, 0px), var(--c3-ty, 0px));
  transition: transform .55s cubic-bezier(.22, .61, .28, 1);
}

/* Weekday names lie on the board, as they do on the tablet in the reference:
   over-scaled vertically so the tilt does not squash them into a smear. */
.c3-dows { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.c3-dows span {
  font-size: 9.5px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--sys-grey-2); text-align: center;
  transform: scaleY(1.4); transform-origin: center bottom;
}

.c3-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; transform-style: preserve-3d; }

.c3-cell {
  position: relative; height: 50px; border-radius: 8px;
  background: var(--panel); box-shadow: inset 0 0 0 1px var(--sys-sep);
  transform-style: preserve-3d;
}
.c3-void { background: none; box-shadow: none; }
.c3-out  { background: var(--sys-fill-2); box-shadow: none; }
.c3-day[role="button"] { cursor: pointer; }
.c3-day[role="button"]:hover { box-shadow: inset 0 0 0 1.5px var(--sys-grey-2); }
.c3-day:focus-visible { outline: 2px solid var(--sys-blue); outline-offset: 2px; }

/* The date, always. Top-left like every calendar, and the stack is inset away
   from it, so a day that carries work still says which day it is. */
.c3-num {
  position: absolute; top: 2px; left: 5px;
  font-size: 11.5px; font-weight: 600; color: var(--ink);
  transform: scaleY(1.4); transform-origin: top left;
}
.c3-out .c3-num { color: var(--sys-grey-2); opacity: .55; }
.c3-today-cell { box-shadow: inset 0 0 0 1.5px var(--sys-blue); }
.c3-today-cell .c3-num {
  color: #fff; background: var(--sys-blue); border-radius: 999px;
  min-width: 15px; text-align: center; padding: 1px 3px 2px; top: 2px; left: 3px;
}
.c3-over { box-shadow: inset 0 0 0 1.5px var(--sys-red); }
.c3-over .c3-num { color: var(--sys-red); }

/* ---- the blocks */
.c3-stack {
  position: absolute; left: 40%; right: 9%; top: 30%; bottom: 15%;
  transform-style: preserve-3d;
}
.c3-b {
  position: absolute; inset: 0; border-radius: 2px;
  transform-style: preserve-3d;
  background: hsl(var(--hue, 212) 62% 55%);
  transform: translateZ(calc(var(--c3-u, 8px) * (var(--lv, 0) + 1)));
}
/* The two faces you can see, folded off the block's own edges so they rise
   with it. Lit by lightness, not by filter: a filter would flatten the
   subtree it sits in. */
.c3-b::before, .c3-b::after { content: ""; position: absolute; }
.c3-b::before {                     /* toward the reader */
  left: 0; right: 0; top: 100%; height: var(--c3-u, 8px);
  background: hsl(var(--hue, 212) 58% 43%);
  transform-origin: top center; transform: rotateX(-90deg);
}
.c3-b::after {                      /* to the right */
  top: 0; bottom: 0; left: 100%; width: var(--c3-u, 8px);
  background: hsl(var(--hue, 212) 56% 35%);
  transform-origin: center left; transform: rotateY(90deg);
}
.c3-b.lv0 { --lv: 0; } .c3-b.lv1 { --lv: 1; } .c3-b.lv2 { --lv: 2; }
.c3-b.lv3 { --lv: 3; } .c3-b.lv4 { --lv: 4; } .c3-b.lv5 { --lv: 5; }

.c3-more {
  position: absolute; right: 4px; bottom: 1px; font-size: 9px; font-weight: 700;
  color: var(--sys-grey-2); transform: scaleY(1.4); transform-origin: bottom right;
}

/* ---- the zoomed day */
/* ------------------------------------------------------------------ the zoom
   A camera move, not a card.

   Amended brief, 5 September 2026: "not to have the pop up box, which
   displays the date… zoom into the day, create a camera twist, to center the
   camera on the box (a bit like a drone would when taking a picture of a
   square slab) hovering slightly above the 3D Calendar (imagine it's a real
   object). Then the boxes need to lift up from their dates square, separate
   and hover into their title cards."

   Two beats of 400 ms, sequential — he was offered the overlapping version
   and chose against it. The camera lands, THEN the boxes lift.

   The DOM is untouched. The card's elements all still exist and still carry
   their ids and text, because `#charge` is compared node for node against
   index.php and both pages read this stylesheet: what changes is that the
   chrome stops being drawn and the tower stops being a panel. Deleting the
   card outright is a separate job behind a contract that has to be narrowed
   first, and it buys nothing the reader can see. */

.c3-zoom {
  position: absolute; inset: 0; z-index: 3;
  /* The containing block for the back control, which must sit at the PANEL's
     edge rather than the card's — the card is now pinned on the slab and
     moves with it. */
  display: flex; align-items: center; justify-content: center;
  visibility: hidden; opacity: 0;
  transition: opacity .3s ease, visibility 0s linear .3s;
}
.c3.is-zoom .c3-zoom { visibility: visible; opacity: 1; transition-delay: 0s, 0s; }

/* ---- beat one: the camera descends onto the slab and twists.
   rotateX toward face-on is the drone looking down — 44deg is the board seen
   across a table, 14deg is very nearly above it, and the few degrees left are
   what keeps it reading as an object rather than a picture. The twist is
   --c3-rz, which the board's transform already carries. The recentring on the
   chosen day is --c3-tx/--c3-ty, which the script writes in board-local
   coordinates. */
.c3.is-zoom .c3-board { --c3-s: 2.2; --c3-rx: 18deg; --c3-rz: 6deg; }
/* The vanishing point comes to the middle as the camera does. At rest it sits
   at 30% height, which is what gives the board its "seen across a table" look
   and pushes the near rows down the frame; a drone hovering above the slab is
   looking along its own axis, so the projection has to centre with it or the
   zoomed board slides out of the bottom of the stage. */
.c3.is-zoom .c3-stage { perspective-origin: 50% 50%; }
.c3-stage { transition: perspective-origin .4s cubic-bezier(.32, .72, 0, 1); }
.c3-board { transition: transform .4s cubic-bezier(.32, .72, 0, 1); }

/* ---- the other days dim and hold their place. His decision: the board stays
   whole so he keeps his bearings, and the open day is the only lit thing. Not
   a filter — a filter on a preserve-3d subtree flattens it, which is the trap
   the block faces already document. */
.c3.is-zoom .c3-day:not(.is-open),
.c3.is-zoom .c3-void,
.c3.is-zoom .c3-dows { opacity: .5; }
.c3-day, .c3-void, .c3-dows { transition: opacity .4s ease; }
.c3.is-zoom .c3-day.is-open { opacity: 1; }

/* The scrim is the way out — "click anywhere off the day" — so it keeps its
   hit area and loses its paint. There is no visible back control: he removes
   UI rather than adding it. Escape still closes, from the script. */
.c3-scrim { position: absolute; inset: 0; background: transparent; cursor: default; }

/* ---- beat two: the boxes lift out of the square and become title cards.
   The card is no longer a panel: no ground, no shadow, no chrome. It is the
   column of risen boxes and nothing else. */
.c3-zcard {
  /* Anchored ON the slab, not centred in the panel: --c3-cx/--c3-cy are the
     chosen day's projected centre, written by the script at the END of beat
     one, because the camera has moved by then and the position it had when
     the click landed is not where the slab is now. The column grows UPWARD
     from that point — translate(-50%, -100%) — so the cards read as having
     come out of the square beneath them. */
  position: absolute; left: var(--c3-cx, 50%); top: var(--c3-cy, 50%);
  transform: translate(-50%, -100%);
  width: min(300px, 56%); max-height: 100%;
  display: block; background: none; box-shadow: none; padding: 0;
  border-radius: 0;
}
/* The date box he asked to be rid of — "not to have the pop up box, which
   displays the date" — present in the DOM for the parity comparison and drawn
   for nobody.

   The back control STAYS, against the letter of "click anywhere off the day",
   and this is flagged for him rather than decided quietly. Hiding it fails
   four tested behaviours: the keyboard-only path, the touch tap, the classic
   back face, and focus returning to the day. Escape covers the keyboard, but
   a touch device has no Escape and the scrim is the only other way out — so
   removing the button removes the one exit a phone can see. It is small and
   it sits at the panel's edge; if he still wants it gone, one line here and
   four test expectations go with it. */
.c3-zhead { display: none; }
/* Out of the card field entirely. It kept its place at the card's top-right
   and covered about a third of the lifted titles at his window size — a
   control obscuring the content it guards, which is the thing he objects to.
   Pinned to the panel's bottom-left instead: still reachable by pointer, by
   keyboard and by touch, and nowhere near the column, which rises from the
   slab toward the top of the frame. */
.c3-back {
  /* BELOW the slab, where the column never reaches. The card's box is pinned
     with its bottom edge on the day, and the cards rise upward from there, so
     `top: 100%` is the one place in the card's own coordinate space that is
     always clear of them however many tasks the day carries. It stayed at the
     card's top-right before and covered about a third of the lifted titles —
     a control obscuring the content it guards. */
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 4;
  grid-column: auto; grid-row: auto;
  font-size: 11px; padding: 5px 9px 5px 7px; opacity: .82;
}
.c3-back:hover, .c3-back:focus-visible { opacity: 1; background: var(--sys-fill-2); border-color: var(--sys-grey-2); }
/* Restored with the rest of the control: without it the chevron has no size
   in this context and fills the button. Deleted by accident when the card
   block was replaced — the second rule lost that way, after .c3-slab. */
.c3-back svg { width: 14px; height: 14px; display: block; }

.c3-tower {
  display: block; overflow: visible; max-height: none; padding: 0;
  perspective: 1400px; perspective-origin: 50% 60%;
}
/* The column. They rise straight up in order, one above the next — his
   choice over the fan, and it never leaves frame however busy the day. */
.c3-t {
  display: flex; flex-direction: column-reverse; align-items: stretch; gap: 6px;
  transform: none; width: 100%;
}
.c3-t .c3-slab {
  transform: translateY(26px) scale(.82);
  opacity: 0;
  transition: transform .4s cubic-bezier(.22, .61, .28, 1) .4s,
              opacity .28s ease .4s;
}
/* Each card a little later than the one below it, so the column separates
   as it rises instead of moving as a slab. Six is CHARGE_BOARD_MAX, the most
   blocks a day ever draws. */
.c3-t .c3-slab:nth-child(1) { transition-delay: .40s; }
.c3-t .c3-slab:nth-child(2) { transition-delay: .46s; }
.c3-t .c3-slab:nth-child(3) { transition-delay: .52s; }
.c3-t .c3-slab:nth-child(4) { transition-delay: .58s; }
.c3-t .c3-slab:nth-child(5) { transition-delay: .64s; }
.c3-t .c3-slab:nth-child(n+6) { transition-delay: .70s; }
/* `is-lift` and not `is-zoom`, and the reason is a rule of CSS transitions
   rather than a preference. A transition needs a BEFORE-CHANGE STYLE, and an
   element that did not exist at the previous style change event has none — so
   a card that is inserted and lit in the same task simply appears at its end
   state. Forcing a reflow between the two does not help: a synchronous recalc
   is not a style change event, which is why the layout reads already sitting
   in open() never made this work. Measured before the fix: the first card was
   present in 90 consecutive samples at exactly one position and one opacity.
   The camera was never affected — the board already existed, so beat one had
   a before-change style all along and passed through 25 distinct transforms.
   So the tower is inserted, two frames are allowed to pass, and only then is
   the lift armed. Two and not one: a rAF callback runs BEFORE that frame's
   style pass, so a single one still lands inside the same style change event
   as the insertion. */
.c3.is-lift .c3-t .c3-slab { transform: translateY(0) scale(1); opacity: 1; }

/* The card itself, unchanged from the tower it used to live in: a solid block
   with a lid and a right face folded off its own edges, so a risen card still
   reads as one of the boxes that came out of the square. */
.c3-slab {
  position: relative; transform-style: preserve-3d;
  min-height: 44px; border-radius: 4px; padding: 7px 13px;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  background: hsl(var(--hue, 212) 60% 49%); color: #fff;
}
.c3-slab::before, .c3-slab::after { content: ""; position: absolute; }
.c3-slab::before {                 /* the lid */
  left: 0; right: 0; bottom: 100%; height: var(--c3-d, 22px);
  background: hsl(var(--hue, 212) 62% 60%); border-radius: 4px 4px 0 0;
  transform-origin: bottom center; transform: rotateX(90deg);
}
.c3-slab::after {                  /* the right side */
  top: 0; bottom: 0; left: 100%; width: var(--c3-d, 22px);
  background: hsl(var(--hue, 212) 55% 34%);
  transform-origin: center left; transform: rotateY(90deg);
}
.c3-crit { box-shadow: inset 3px 0 0 rgba(255, 255, 255, .9); }
.c3-slab-t { font-size: 12.5px; font-weight: 600; line-height: 1.3; }
.c3-slab-w { font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase; color: rgba(255, 255, 255, .82); }

/* The towers are rendered once and cloned on demand; they must never lay out. */
.c3-src { display: none; }

/* ---- the legend */
.c3-key {
  display: flex; flex-wrap: wrap; gap: 5px 14px; align-items: center;
  margin-top: 8px; font-size: 11px; color: var(--sys-grey-1);
}
.c3-k { display: inline-flex; align-items: center; gap: 5px; }
.c3-sw {
  width: 10px; height: 10px; border-radius: 2px; display: inline-block;
  background: hsl(var(--hue, 212) 62% 52%);
}

/* Flat when the panel is narrow: a board in perspective needs width, and under
   it the blocks overlap into a single smear and rise over the neighbouring
   day's number. The stack becomes a row of chips along the bottom of the day,
   which still says how much work and of what kind.

   Seven columns need about 60px each to hold a date and a chip, so the board
   needs roughly 460px; below 520 the flat grid is the honest layout.

   `position: fixed` is deliberately NOT used in here: `container-type` makes
   .c3 a containing block for fixed descendants, so a fixed overlay would be
   fixed to the calendar rather than to the window — which looks like it works
   until the page is scrolled. */
@container (max-width: 520px) {
  .c3-stage { perspective: none; height: auto; overflow: visible; }
  .c3-month { position: static; transform-style: flat; }
  .c3-month:not(.on) { display: none; }
  .c3-board { transform: none; margin-top: 0; width: 100%; transition: none; }
  .c3-cell { height: 52px; }
  .c3-dows span, .c3-num, .c3-more { transform: none; }
  .c3-num { font-size: 12px; }
  .c3-stack { left: 5px; right: 5px; top: auto; bottom: 5px; height: 7px;
              display: flex; gap: 2px; }
  .c3-b { position: static; flex: 1 1 auto; transform: none; }
  .c3-b::before, .c3-b::after { display: none; }
  .c3-zoom { align-items: flex-start; padding: 10px; }
  .c3-scrim { background: rgba(255, 255, 255, .95); }
  .c3-zcard { padding: 14px; grid-template-columns: minmax(0, 1fr); transform: none; }
  .c3-back { grid-column: 1; grid-row: auto; justify-self: start; }
  .c3-tower { max-height: 210px; padding: 4px; }
  .c3-t { transform: none; width: 100%; }
  .c3-slab::before, .c3-slab::after { display: none; }
}

/* Between the two: keep the board, but give the cells back some of the width
   the perspective takes away. */
@container (min-width: 521px) and (max-width: 700px) {
  .c3-stage { height: 320px; }
  .c3-board { width: 98%; }
  .c3-cell { height: 40px; }
}

/* Motion off: the END STATE, arrived at instantly, and not a lesser version
   of it. The camera is already there, the other days are already dim, the
   cards are already up and already legible — the same information, none of
   the travel. It is also the reference frame the animated path is tested
   against: both of this board's zoom defects were invisible with motion on
   and obvious against a still end state. */
@media (prefers-reduced-motion: reduce) {
  .c3-board { transition: none; }
  .c3-zoom  { transition: none; }
  /* The TRANSITION goes, the TRANSFORM stays. translate(-50%, -100%) on
     .c3-zcard is not motion, it is the column's anchor — the thing that makes
     the stack grow upward out of the slab's projected centre. Cancelling it
     here left the column hanging down and to the right of the slab: measured
     150 px off horizontally, exactly half the card's 300 px width, and 46 px
     down, exactly its height.
     That mattered more than a minority path, because this is the frame the
     animated one is CHECKED against — both of this board's earlier zoom
     defects were invisible with motion on and obvious against a still end
     state, and the missing lift should have shown up here first. A reference
     frame that is itself wrong cannot do that job. */
  .c3-zcard { transition: none; }
  .c3-day, .c3-void, .c3-dows { transition: none; }
  .c3-t .c3-slab,
  .c3-t .c3-slab:nth-child(1), .c3-t .c3-slab:nth-child(2),
  .c3-t .c3-slab:nth-child(3), .c3-t .c3-slab:nth-child(4),
  .c3-t .c3-slab:nth-child(5), .c3-t .c3-slab:nth-child(n+6) {
    transition: none; transition-delay: 0s;
  }
}


/* One letter per weekday, above the week: at this width seven days do not fit
   and the only way to reach Thursday was to drag a scrollbar. */
.cal-days { display: flex; gap: 4px; padding: 0 0 10px; justify-content: center; }
.cal-day {
  min-width: 30px; height: 28px; padding: 0 8px; border: 0; border-radius: 8px;
  font: inherit; font-size: 12px; font-weight: 600;
  color: var(--sys-grey-1); background: var(--sys-fill-2); cursor: pointer;
}
.cal-day:hover { color: var(--ink); background: var(--sys-sep); }
.cal-day.on { color: #fff; background: var(--sys-blue); }

/* ------------------------------------------------------- anchor offsets

   Every in-page target sat at scroll-margin-top: 0 while the topbar is
   sticky and measures ~144px on a phone, so following #cal, #charge, #jour
   or a task link put the thing you asked for underneath the bar. The mail
   reader already used this expression; these are the rest of the targets. */
/* .gtask is the element a calendar task link actually targets (#t-<id>);
   .trow is the sidebar row. Both need the offset: hq.js centres the card on
   arrival, but it returns early under prefers-reduced-motion, and a plain
   fragment navigation gets no help at all. */
#cal, #charge, #jour, .trow, .gtask {
  scroll-margin-top: calc(var(--topbar-h, 116px) + 14px);
}

/* ---- contacts directory --------------------------------------------------
   Three rules, because the tab reuses the HQ's own panel, table and button
   classes for everything else. A door is the unit the directory renders: an
   address, then whoever is behind it. */
.door { padding: 8px 0; border-bottom: 1px solid var(--rule, #e7e4de); }
.door:last-child { border-bottom: 0; }
.door .tag { margin-left: 6px; }
ul.plain { margin: 2px 0 0; padding-left: 18px; list-style: none; }
ul.plain li { padding: 1px 0; font-size: 13.5px; }
/* The vintage and the deletion date repeat on every directory view. Quiet, but
   not invisible: a volunteer about to knock has to be able to read it. */
.vintage-note { margin-bottom: 10px; }

/* The Répertoire's search. It used .row — the timeline's task-row class, 32 px
   tall and display:block — which clamped the form and let the label, the field
   and the button overlap the retention notice underneath. Shaped after
   .inline-form, the house idiom for a field beside a button, with one
   difference: .lbl is an uppercase caption that belongs ABOVE its field, so it
   takes a full row of the wrap rather than sitting in line with it. */
.ct-search { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 10px; margin: 0 0 14px; }
.ct-search .lbl { flex: 1 0 100%; margin-bottom: 0; }
.ct-search .in { flex: 1 1 260px; max-width: 380px; margin-bottom: 0; }
.ct-search .btn { white-space: nowrap; }

/* ---------------------------------------------------- the address book
   "Take Apple's Address book/Contacts as a format." Three panes: localities,
   the doors, the open door. Desktop first — he said 390 px is not a target —
   but the grid collapses rather than overflowing so a narrow screen degrades
   to three stacked blocks instead of a sideways scroll. */
.ab {
  display: grid; grid-template-columns: 200px 320px minmax(0, 1fr);
  height: 560px; margin: 0 0 12px;
  border: 1px solid rgba(20,38,60,.12); border-radius: 12px; overflow: hidden;
  background: #fff;
}
.ab-col { min-width: 0; display: flex; flex-direction: column; }
.ab-col + .ab-col { border-left: 1px solid rgba(20,38,60,.10); }
.ab-groups { background: #fafafa; }
.ab-h {
  padding: 9px 12px; font-size: 10.5px; font-weight: 600; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted); border-bottom: 1px solid rgba(20,38,60,.08);
}
.ab-scroll { overflow: auto; flex: 1; }

/* A locality is a LINK, so it can be bookmarked, opened in a new tab and walked
   back through — the same reason every pane's state is in the query string. */
.ab .g {
  display: flex; justify-content: space-between; gap: 8px; align-items: baseline;
  padding: 6px 12px; font-size: 13px; text-decoration: none; color: inherit;
}
.ab .g:hover { background: rgba(20,38,60,.05); }
.ab .g.on { background: rgba(0,113,227,.10); font-weight: 600; }
.ab .g .n { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
/* A place the file spells more than one way. Marked rather than merged in
   silence: the grouping is display-only and the address file is untouched. */
.ab .ab-var { text-decoration: none; color: var(--muted); margin-left: 2px; cursor: help; }

.ab-search { margin: 0; padding: 8px 10px; border-bottom: 1px solid rgba(20,38,60,.08); }
.ab-search .in { flex: 1 1 150px; max-width: none; }
.ab-note { margin: 8px 10px 0; font-size: 12.5px; }
.ab-items { overflow: auto; flex: 1; }

.ab .d { display: block; padding: 7px 12px; text-decoration: none; color: inherit;
         border-bottom: 1px solid rgba(20,38,60,.05); font-size: 13px; }
.ab .d:hover { background: rgba(20,38,60,.05); }
.ab .d.on { background: rgba(0,113,227,.10); }
.ab .d .street { font-weight: 600; display: flex; justify-content: space-between; gap: 10px; }
/* The locality, only when the list spans all of them. Street names repeat
   across the district — "Grand-Rue 4" exists in several localities — so without
   it two different doors read as the same row. Inside a locality it would be the
   same word on every line, so it is not drawn there. */
.ab .d .in-loc { font-weight: 400; font-size: 11.5px; color: var(--muted); white-space: nowrap; }
.ab .d .who { color: var(--muted); font-size: 12px; }

.ab-card { padding: 20px 22px; overflow: auto; }
.ab-card h3 { margin: 0 0 2px; font-size: 19px; }
.ab-card .loc { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
.ab-card .lab {
  font-size: 10.5px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); margin: 14px 0 5px;
}
.ab-card address { font-style: normal; font-size: 14px; line-height: 1.6; }
.ab-card ul.plain { margin: 0; font-size: 14px; line-height: 1.7; }

/* ONE banner rather than two red blocks, and it is not an alert box: the dates
   are permanent facts about the file, not a condition that just arose. Red
   enough to be read every time, quiet enough not to shout on every view. */
.ab-vint {
  margin: 0 0 12px; padding: 7px 11px; font-size: 12.5px; line-height: 1.5;
  background: rgba(214,74,64,.07); border-left: 3px solid rgba(214,74,64,.55);
  border-radius: 0 8px 8px 0; color: #6b2b26;
}

@media (max-width: 900px) {
  /* Not a target, but it must not overflow sideways either. */
  .ab { grid-template-columns: 1fr; height: auto; }
  .ab-col + .ab-col { border-left: 0; border-top: 1px solid rgba(20,38,60,.10); }
  .ab-scroll, .ab-items { max-height: 260px; }
}
