/*
 * Hand-written replacement for the MUI components this screen uses.
 *
 * MUI ships no stylesheet — every rule is generated at runtime by emotion, so
 * nothing could be extracted from the build. These rules reproduce the same
 * metrics from the MUI v6 defaults (spacing unit 8px, elevation shadows,
 * outlined-input notch geometry, small-size paddings) plus the app's own
 * theme overrides (textTransform: none on buttons, 0.75rem / 4px compact
 * inputs from utils/formStyles.fieldSx).
 *
 * Colours come from CSS custom properties resolved server-side by
 * resolve_colors(), the equivalent of theme/colors.ts + ColorProvider.
 */

/* Note on letter-spacing: MUI's default typography sets a letterSpacing per
 * variant, but this app's `typography: { fontFamily: ... }` override drops
 * them — every element measures as `normal` in the running React app, so that
 * is what these rules use. */

/* --- palette bits that depend on the mode rather than on the DB ---------- */

:root {
  --outline: rgba(0, 0, 0, 0.23);
  --action-hover: rgba(0, 0, 0, 0.04);
  /* `action.hoverOpacity`: 0.04 in light and *0.08* in dark, which every tint
     built on a palette colour has to follow — a hovered outlined button, a
     day in the calendar, an error-coloured icon. Hard-coding 4 % everywhere
     made all of them half-strength in dark mode. */
  --hover-opacity: 4%;
  --action-focus: rgba(0, 0, 0, 0.12);
  --warning-main: #ed6c02;
  --warning-dark: #e65100;
  /* MUI derives contrastText from the fill: #ed6c02 needs white, the dark
     mode's lighter #ffa726 needs the dark text instead. */
  --warning-contrast: #fff;
  --info-main: #0288d1;
  --text-disabled: rgba(0, 0, 0, 0.38);
  --action-disabled: rgba(0, 0, 0, 0.26);
  --action-disabled-bg: rgba(0, 0, 0, 0.12);
  --action-active: rgba(0, 0, 0, 0.54);
  --row-odd: rgba(0, 0, 0, 0.035);
  --row-odd-hover: rgba(0, 0, 0, 0.06);
  --row-hover: rgba(0, 0, 0, 0.04);
  --chip-default: rgba(0, 0, 0, 0.08);
  --chip-hover: rgba(0, 0, 0, 0.12);      /* action.selected + action.hover */
  --chip-outline: #bdbdbd;      /* MUI Chip outlined: grey[400] / grey[700] */
  --placeholder-opacity: 0.42;
  --paper-overlay: none;
  --paper-overlay-8: none;
  --drawer-overlay: none;
  --list-selected: color-mix(in srgb, var(--primary-main) 8%, transparent);
  --list-selected-hover: color-mix(in srgb, var(--primary-main) 12%, transparent);
  /* MuiSwitch disabled: grey[100] on a track at 12%, grey[600] at 20% in dark. */
  --switch-disabled-track: 0.12;
  --switch-track: #000;
  --elev1: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
  --elev2: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
  --elev3: 0px 3px 3px -2px rgba(0, 0, 0, 0.2), 0px 3px 4px 0px rgba(0, 0, 0, 0.14), 0px 1px 8px 0px rgba(0, 0, 0, 0.12);
  --elev4: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
  --elev8: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
  --elev16: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12);
  --elev24: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}

[data-mode="dark"] {
  --outline: rgba(255, 255, 255, 0.23);
  --action-hover: rgba(255, 255, 255, 0.08);
  --hover-opacity: 8%;
  --action-focus: rgba(255, 255, 255, 0.12);
  --warning-main: #ffa726;
  --warning-dark: #f57c00;
  --warning-contrast: rgba(0, 0, 0, 0.87);
  --info-main: #29b6f6;
  --text-disabled: rgba(255, 255, 255, 0.5);
  --action-disabled: rgba(255, 255, 255, 0.3);
  --action-disabled-bg: rgba(255, 255, 255, 0.12);
  --action-active: #fff;
  --row-odd: rgba(0, 0, 0, 0.035);
  --row-odd-hover: rgba(0, 0, 0, 0.06);
  --row-hover: rgba(255, 255, 255, 0.08);
  --chip-default: rgba(255, 255, 255, 0.16);
  --chip-hover: rgba(255, 255, 255, 0.24);
  --chip-outline: #616161;
  --switch-disabled-track: 0.2;
  --switch-track: #fff;
  --placeholder-opacity: 0.5;
  /* MUI tints dark Paper by elevation; the app only disables this for
     Dialog/Menu/Popover, so plain Paper keeps the overlay. */
  --paper-overlay: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05));
  /* Elevation 8's own tint: the pickers' Popper is a plain Paper, so unlike a
     Menu's it keeps the overlay MUI would apply. */
  --paper-overlay-8: linear-gradient(rgba(255, 255, 255, 0.118), rgba(255, 255, 255, 0.118));
  --drawer-overlay: linear-gradient(rgba(255, 255, 255, 0.145), rgba(255, 255, 255, 0.145));
  --list-selected: color-mix(in srgb, var(--primary-main) 16%, transparent);
  --list-selected-hover: color-mix(in srgb, var(--primary-main) 24%, transparent);
}

/* --- CssBaseline --------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
/* A class that sets `display` outranks the UA's `[hidden]` rule. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--text-primary);
  background-color: var(--bg-default);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* --- Typography ---------------------------------------------------------- */

.typo-subtitle1 {
  font-size: 1rem;
  line-height: 1.75;
  letter-spacing: normal;
  font-weight: 400;
}

/* --- AppBar / Toolbar ---------------------------------------------------- */

/* `color="primary"` only paints in light mode: MUI's AppBar ships
   `enableColorOnDark: false`, so in dark it drops back to the Paper it is —
   background.paper under the elevation-4 overlay (9% white). */
.appbar {
  background-color: var(--primary-main);
  color: #fff;
  box-shadow: var(--elev4);
  width: 100%;
  /* What the bar is actually painted, for the one thing that has to disappear
     into it: the bell's knockout, below. */
  --bar-bg: var(--primary-main);
}

[data-mode="dark"] .appbar {
  background-color: var(--bg-paper);
  background-image: linear-gradient(rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09));
  /* The same two layers the rule above composites, so the knockout matches
     the bar and not the colour underneath it. */
  --bar-bg: color-mix(in srgb, #fff 9%, var(--bg-paper));
}

.toolbar {
  display: flex;
  align-items: center;
  min-height: 64px;
  padding: 0 24px;
}

.toolbar-nav { display: flex; gap: 16px; flex-grow: 1; }
.toolbar-prefs { display: flex; align-items: center; gap: 8px; }

/* --- Container ----------------------------------------------------------- */

.container {
  width: 100%;
  margin: 0 auto;
  padding-left: 24px;
  padding-right: 24px;
}

.container-main { margin-top: 32px; }

@media (max-width: 599.95px) {
  .container { padding-left: 16px; padding-right: 16px; }
  .toolbar { padding: 0 16px; }
}

.page { max-width: 852px; margin: 0 auto; }

/* --- Tabs ---------------------------------------------------------------- */

/* App.tsx wraps the settings tabs in a Box with a 1px divider and the paper
   background — not part of MUI's Tabs itself. */
.tabs-bar {
  background-color: var(--bg-paper);
  border-bottom: 1px solid var(--border-cell);
}

.tabs { display: flex; min-height: 42px; overflow: hidden; position: relative; }

.tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  min-width: auto;
  padding: 12px 16px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-secondary);
  white-space: nowrap;
  text-decoration: none;
}

.tab-selected { color: var(--primary-main); }

/* `TabIndicator`: one line under the row, moved to the selected tab rather
   than drawn on it, so that changing tab slides it across. MUI's own
   transition is `theme.transitions.create()` — everything, 300ms, easeInOut —
   and its width and offset are set from the tab it belongs to. */
.tab-indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2px;
  background-color: var(--primary-main);
  transition: left 300ms var(--ease-in-out), width 300ms var(--ease-in-out);
}

@media (prefers-reduced-motion: reduce) {
  .tab-indicator { transition-duration: 1ms; }
}

/* --- Paper --------------------------------------------------------------- */

.paper {
  background-color: var(--bg-paper);
  background-image: var(--paper-overlay);
  color: var(--text-primary);
  border-radius: 4px;
  box-shadow: var(--elev1);
}

/* --- Button -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  box-sizing: border-box;
  margin: 0;
  border: 0;
  outline: 0;
  cursor: pointer;
  user-select: none;
  vertical-align: middle;
  text-decoration: none;
  font-family: inherit;
  font-weight: 500;
  font-size: 0.875rem;
  line-height: 1.75;
  letter-spacing: normal;
  text-transform: none;          /* theme override: MuiButton.textTransform */
  min-width: 64px;
  padding: 6px 8px;
  border-radius: 4px;
  background-color: transparent;
  color: var(--primary-main);
  transition: background-color 250ms var(--ease-out), box-shadow 250ms var(--ease-out),
              border-color 250ms var(--ease-out), color 250ms var(--ease-out);
}

.btn-small { padding: 4px 5px; font-size: 0.8125rem; }

.btn:hover { background-color: color-mix(in srgb, var(--primary-main) var(--hover-opacity), transparent); }

.btn-outlined {
  padding: 5px 15px;
  border: 1px solid color-mix(in srgb, var(--primary-main) 50%, transparent);
}
.btn-outlined.btn-small { padding: 3px 9px; }
.btn-outlined:hover { border-color: var(--primary-main); }

.btn-contained {
  padding: 6px 16px;
  color: #fff;
  background-color: var(--primary-main);
  box-shadow: var(--elev2);
}
.btn-contained.btn-small { padding: 4px 10px; }
.btn-contained:hover { background-color: var(--primary-dark); box-shadow: var(--elev4); }

.btn-warning { color: var(--warning-main); }
.btn-warning.btn-outlined { border-color: color-mix(in srgb, var(--warning-main) 50%, transparent); }
.btn-warning:hover {
  border-color: var(--warning-main);
  background-color: color-mix(in srgb, var(--warning-main) var(--hover-opacity), transparent);
}

.btn-inherit { color: inherit; }
.btn-inherit:hover { background-color: rgba(255, 255, 255, 0.08); }

/* --- IconButton ---------------------------------------------------------- */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  /* ButtonBase's own `position: relative`: without it the ripple this button
     starts is placed against whatever ancestor happens to be positioned. */
  position: relative;
  padding: 5px;
  margin: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  color: inherit;
  cursor: pointer;
  transition: background-color 150ms var(--ease-out);
}

.icon-btn-error { color: var(--error-main); }
.icon-btn-error:hover { background-color: color-mix(in srgb, var(--error-main) var(--hover-opacity), transparent); }
.icon-btn-inherit:hover { background-color: rgba(255, 255, 255, 0.08); }

.icon-btn-spacer { width: 30px; height: 30px; flex-shrink: 0; }

/* MUI SvgIcon, fontSize="small". Sized in em so it sits on a line box the
   same way MUI's does, which matters inside DataGrid cells. */
.mui-icon {
  display: inline-block;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  font-size: 1.25rem;
  fill: currentColor;
  user-select: none;
}

/* --- Checkbox ------------------------------------------------------------ */

.cb {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  padding: 4px;                  /* app override: sx={{ p: 0.5 }} */
  border-radius: 50%;
  transition: background-color 150ms var(--ease-out);
}

.cb:hover { background-color: var(--action-hover); }

.cb-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.cb-icon { width: 20px; height: 20px; fill: currentColor; display: block; }
.cb-blank { color: var(--text-secondary); }
.cb-checked { display: none; color: var(--primary-main); }
.cb-input:checked ~ .cb-blank { display: none; }
.cb-input:checked ~ .cb-checked { display: block; }

/* --- FormControlLabel ---------------------------------------------------- */

.form-control-label {
  display: inline-flex;
  align-items: center;
  margin-left: -11px;
  margin-right: 0;               /* app override: sx={{ mr: 0 }} */
  vertical-align: middle;
  cursor: pointer;
}

.fcl-label { font-size: 0.75rem; }

/* --- TextField (outlined, small) ----------------------------------------- */

.tf {
  position: relative;
  display: inline-flex;
  align-items: center;
  border-radius: 4px;
  font-size: 1rem;
  line-height: 1.4375em;
  color: var(--text-primary);
}

.tf-input {
  font: inherit;
  font-size: 0.75rem;            /* app override: fieldSx */
  letter-spacing: inherit;
  color: currentColor;
  padding: 4px 14px;             /* app override: py 4px, MUI small keeps 14px sides */
  border: 0;
  box-sizing: content-box;
  background: none;
  height: 1.4375em;
  margin: 0;
  display: block;
  min-width: 0;
  width: 100%;
  outline: 0;
}

.tf-input::placeholder { color: currentColor; opacity: var(--placeholder-opacity); }

.tf-input:disabled { color: var(--text-disabled); -webkit-text-fill-color: var(--text-disabled); }
.tf-input:disabled ~ .tf-outline { border-color: var(--action-disabled); }
.tf-input:disabled ~ .tf-label { color: var(--text-disabled); }
.tf-asterisk { color: inherit; }

.tf-label {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  transform: translate(14px, 9px) scale(1);
  transform-origin: top left;
  max-width: calc(100% - 24px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.4375em;
  pointer-events: none;
  transition: color 200ms var(--ease-out), transform 200ms var(--ease-out),
              max-width 200ms var(--ease-out);
}

.tf-outline {
  position: absolute;
  inset: -5px 0 0 0;
  margin: 0;
  padding: 0 8px;
  overflow: hidden;
  min-width: 0%;
  pointer-events: none;
  border: 1px solid var(--outline);
  border-radius: inherit;
}

.tf-outline legend {
  float: unset;
  width: auto;
  overflow: hidden;
  display: block;
  padding: 0;
  height: 11px;
  font-size: 0.75em;
  visibility: hidden;
  max-width: 0.01px;
  white-space: nowrap;
  transition: max-width 50ms var(--ease-out);
}

.tf-outline legend > span {
  padding-left: 5px;
  padding-right: 5px;
  display: inline-block;
  opacity: 0;
  visibility: visible;
}

.tf:hover .tf-outline { border-color: var(--text-primary); }

.tf-input:focus ~ .tf-outline {
  border-width: 2px;
  border-color: var(--primary-main);
}

/* Float the label once the field has a value or focus. `legend-empty` marks
   the unlabelled fields (code, hex), whose notch must stay closed. */
/* `input, textarea` on purpose: `:placeholder-shown` never matches a <div>,
   so the negation would match every hand-drawn Select value and float its
   label even when the field is empty. */
.tf-input:focus ~ .tf-label,
input.tf-input:not(:placeholder-shown) ~ .tf-label,
textarea.tf-input:not(:placeholder-shown) ~ .tf-label {
  transform: translate(14px, -9px) scale(0.75);
  max-width: calc(133% - 24px);
}

.tf-input:focus ~ .tf-outline legend:not(.legend-empty),
input.tf-input:not(:placeholder-shown) ~ .tf-outline legend:not(.legend-empty),
textarea.tf-input:not(:placeholder-shown) ~ .tf-outline legend:not(.legend-empty) {
  max-width: 100%;
  transition-duration: 100ms;
}

.tf-input:focus ~ .tf-label { color: var(--primary-main); }

.tf-error .tf-outline { border-color: var(--error-main); }
.tf-error:hover .tf-outline { border-color: var(--error-main); }
.tf-error .tf-input:focus ~ .tf-outline { border-color: var(--error-main); }
.tf-error .tf-label { color: var(--error-main); }

.code-input { text-transform: uppercase; }

/* --- Alert (standard) ----------------------------------------------------
   A standard Alert is built from the palette's *light* shade, not its main:
   the background is that shade lightened 0.9, the text the same shade
   darkened 0.6, and the two functions swap over in dark mode. Both shades
   are resolved in `core.alert_variables` — the light of the two severities
   the theme configures is `lighten(main, 0.2)`, and MUI truncates every
   channel on the way, which is why this is not a `color-mix`. */

.alert {
  display: flex;
  padding: 6px 16px;
  border-radius: 4px;
  font-size: 0.875rem;
  line-height: 1.43;
  letter-spacing: normal;
}

.alert-error   { background-color: var(--alert-error-bg);   color: var(--alert-error-color); }
.alert-warning { background-color: var(--alert-warning-bg); color: var(--alert-warning-color); }
.alert-info    { background-color: var(--alert-info-bg);    color: var(--alert-info-color); }
.alert-success { background-color: var(--alert-success-bg); color: var(--alert-success-color); }

/* The icon keeps the palette's `main`, whatever the two shades above did. */
.alert-icon {
  display: flex;
  margin-right: 12px;
  padding: 7px 0;
  opacity: 0.9;
  font-size: 22px;
  color: var(--error-main);
}

.alert-warning .alert-icon { color: var(--warning-main); }
.alert-info .alert-icon { color: var(--info-main); }
.alert-success .alert-icon { color: var(--success-main); }

.alert-icon svg { width: 22px; height: 22px; fill: currentColor; }
.alert-message { padding: 8px 0; min-width: 0; }

/* --- TouchRipple ---------------------------------------------------------
   `ButtonBase` puts one of these behind the label of every button, menu
   entry, tab, chip and day. The numbers are MUI's own: a 550ms enter from
   `scale(0)` at 10 % to `scale(1)` at 30 %, and a 550ms fade to nothing when
   the pointer comes back up, both on `easeInOut`. The size is the diagonal
   that reaches the furthest corner from where the click landed, which is why
   a ripple started in a corner still fills the whole control. */

.ripple-root {
  position: absolute;
  overflow: hidden;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 0;
}

/* A ripple whose control was swapped away finishes here: same box, same
   corner radius, same colour, drawn over the page and untouchable. */
.ripple-orphan {
  position: fixed;
  z-index: 1500;
  pointer-events: none;
}

.ripple {
  position: absolute;
  opacity: 0.3;
  transform: scale(1);
  animation: ripple-enter 550ms var(--ease-in-out);
}

.ripple-child {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 1;
}

.ripple-leaving {
  opacity: 0;
  animation: ripple-exit 550ms var(--ease-in-out);
}

@keyframes ripple-enter {
  0%   { transform: scale(0); opacity: 0.1; }
  100% { transform: scale(1); opacity: 0.3; }
}

@keyframes ripple-exit {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* --- Content that arrives ------------------------------------------------
   Two places where the page changes under the reader without moving: the
   screen itself, swapped by a navigation, and the two grids, replaced whole
   when the month or a team filter changes. Both come in on a short fade —
   the same trick as the collapse, a `@starting-style` the element settles
   away from on its first frame — so a change of month reads as a change
   rather than a flicker. */
main.container-main {
  animation: content-in 160ms var(--ease-out);
}

#calendar-body, #oncall-body {
  opacity: 1;
  transition: opacity 160ms var(--ease-out);
}

@starting-style {
  #calendar-body, #oncall-body { opacity: 0; }
}

@keyframes content-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Somebody who has asked their system for less movement asked for less
   *movement*: what goes is the growing and the folding, not the feedback.
   The ripple arrives at full size and fades in, and the swaps keep a short
   cross-fade — a control that answers to a press is not decoration, and
   cutting every duration to 1ms is indistinguishable from a screen that has
   stopped working. (MUI cuts nothing at all here.) */
@media (prefers-reduced-motion: reduce) {
  .ripple { animation: ripple-enter-still 200ms var(--ease-out); }
  .ripple-leaving { animation-duration: 200ms; }
  .collapse { transition-duration: 1ms; }
  main.container-main { animation-duration: 120ms; }
  #calendar-body, #oncall-body { transition-duration: 120ms; }
}

@keyframes ripple-enter-still {
  0%   { transform: scale(1); opacity: 0; }
  100% { transform: scale(1); opacity: 0.3; }
}

/* --- Collapse ------------------------------------------------------------
   The two timesheet panels open and close the way `Collapse` does: 300ms —
   `transitions.duration.standard` — on `easeInOut`, with the height going to
   and from zero. `0fr` to `1fr` is what lets CSS animate a height nobody
   knows in advance; htmx supplies the two states, `htmx-settling` on the way
   in and `htmx-swapping` on the way out. */

/* `minmax(0, ...)`, not a bare `1fr`: a bare `Nfr` row is `minmax(auto, Nfr)`,
   and that `auto` floor is the item's own minimum -- 40px here, the paper's
   padding and the first line of its title. The panel stopped there instead of
   closing, and opened by jumping to it before the transition began. Measured:
   `0fr` resolves to 40px, `minmax(0, 0fr)` to 0. */
.collapse {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  transition: grid-template-rows 300ms var(--ease-in-out);
}

.collapse > * { overflow: hidden; min-height: 0; }
/* `Collapse` ends `entered` with `overflow: visible`, and it matters: a
   Paper's shadow is drawn outside its box and would stay clipped for as long
   as the panel is open. */
.collapse-open > * { overflow: visible; }
.htmx-swapping .collapse-open > * { overflow: hidden; }

/* A newly inserted element has no previous value to transition *from*, which
   is what `@starting-style` is for: the panel arrives at `0fr` and settles to
   `1fr` on the next frame. Going out, htmx holds the swap for the length of
   the animation and marks the target while it runs. */
@starting-style {
  .collapse { grid-template-rows: minmax(0, 0fr); }
}

.htmx-swapping .collapse { grid-template-rows: minmax(0, 0fr); }

/* --- Tooltip (CSS-only stand-in for MUI's portal tooltip) ---------------- */

.shape-choice { position: relative; display: inline-flex; }

.shape-choice[title]::after {
  content: attr(title);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) scale(0.9);
  transform-origin: top center;
  background-color: rgba(97, 97, 97, 0.92);
  color: #fff;
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  z-index: 1500;
  transition: opacity 0ms 0ms, transform 0ms 0ms, visibility 0ms 0ms;
}

.shape-choice[title]:hover::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) scale(1);
  transition: opacity 200ms var(--ease-out) 100ms, transform 200ms var(--ease-out) 100ms, visibility 0ms 100ms;
}

/* --- Layout: sections, cards, rows --------------------------------------- */

.section { margin-bottom: 32px; }
.section-title { font-weight: 600; margin-bottom: 12px; }
.section .alert { margin-bottom: 12px; }

.status-grid {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  justify-content: start;
  align-items: start;
  gap: 12px;
}

@media (max-width: 599.95px) {
  .status-grid { grid-template-columns: max-content; }
}

.card { padding: 12px; }
.card-head { display: flex; align-items: center; gap: 8px; }
.card-variants { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.variant-row { display: flex; align-items: center; gap: 8px; min-height: 32px; }
.variant-label { width: 80px; font-size: 0.75rem; }

.section-actions {
  grid-column: 1;
  display: flex;
  gap: 12px;
  margin-top: 4px;
  position: relative;
}

.grow { flex-grow: 1; }

.teletravail-wrap { display: inline-block; }
.teletravail-actions { margin-top: 12px; }

.marker-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.marker-label { width: 194px; font-size: 0.75rem; white-space: nowrap; }

/* --- Colour swatch ------------------------------------------------------- */

.swatch {
  position: relative;
  display: inline-block;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  cursor: pointer;
  border: 1px solid var(--border-legend);
}

.swatch-picker {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* --- Remote-work marker shapes ------------------------------------------- */

/* Visually hidden but still focusable — MUI renders these as click-only
   <div>s, which no keyboard can reach. */
.shape-choice input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.shape-choice input:focus-visible + .shape-preview {
  outline: 2px solid var(--primary-main);
  outline-offset: 2px;
}

.shape-preview {
  position: relative;
  display: block;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  cursor: pointer;
  background-color: var(--bg-default);
  border: 1px solid var(--border-legend);
}

.shape-choice input:checked + .shape-preview {
  box-shadow: 0 0 0 2px var(--primary-main);
}

.shape-preview::before { content: ""; position: absolute; }

.shape-bar-top::before { top: 0; left: 0; right: 0; height: 4px; background-color: var(--marker-color); }
.shape-bar-bottom::before { bottom: 0; left: 0; right: 0; height: 4px; background-color: var(--marker-color); }
.shape-triangle-tl::before {
  top: 0; left: 0; width: 0; height: 0;
  border-style: solid; border-width: 8px 8px 0 0;
  border-color: var(--marker-color) transparent transparent transparent;
}
.shape-triangle-tr::before {
  top: 0; right: 0; width: 0; height: 0;
  border-style: solid; border-width: 8px 0 0 8px;
  border-color: var(--marker-color) transparent transparent transparent;
}

/* --- Saved badge --------------------------------------------------------- */

.saved-badge {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 12px;
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--success-main);
  font-size: 0.8rem;
  white-space: nowrap;
  /* React clears the flag with a 2s setTimeout; here the badge is rendered
     once by the server and hides itself. */
  animation: saved-hide 0s 2s forwards;
}

.saved-badge .mui-icon { width: 20px; height: 20px; }

@keyframes saved-hide { to { visibility: hidden; opacity: 0; } }

/* ==========================================================================
   Users screen
   ========================================================================== */

.users-page { max-width: 1272px; width: 100%; margin: 0 auto; }
.users-panel { padding: 16px; margin-bottom: 16px; overflow: hidden; }
.users-toolbar { display: flex; gap: 16px; margin-bottom: 24px; }

/* --- Search field (outlined small + start adornment) --------------------- */

.search-field { margin-left: auto; width: 240px; padding-left: 14px; }
.search-field .tf-input { padding-left: 0; }
.search-field, .search-field .tf-input { height: 30.75px; }
.search-field .tf-input { height: auto; }

.input-adornment {
  display: flex;
  align-items: center;
  margin-right: 8px;
  height: 0.01em;
  max-height: 2em;
  color: var(--action-active);
  flex-shrink: 0;
}

/* --- Chip ---------------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  max-width: 100%;
  font-size: 0.75rem;            /* theme override: MuiChip.fontSize */
  font-family: inherit;
  border-radius: 16px;
  white-space: nowrap;
  vertical-align: middle;
  box-sizing: border-box;
  border: 0;
  outline: 0;
  text-decoration: none;
  background-color: var(--chip-default);
  color: var(--text-primary);
  transition: background-color 300ms var(--ease-out), box-shadow 300ms var(--ease-out);
}

.chip-label { padding: 0 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Positioned like the ButtonBase a clickable Chip is built on, so the ripple
   lands inside the chip instead of the nearest positioned ancestor. */
.chip-clickable { cursor: pointer; user-select: none; position: relative; }
.chip-clickable.chip-filled-primary:hover { background-color: var(--primary-dark); }
/* `action.hover` for an outlined chip, and `action.selected` + `action.hover`
   opacities added for a filled one — both of them the action colours, not the
   text colour mixed down. */
.chip-clickable.chip-outlined:hover { background-color: var(--action-hover); }
.chip-clickable.chip-filled:hover { background-color: var(--chip-hover); }

.chip-filled-primary { background-color: var(--primary-main); color: #fff; }
.chip-outlined { background-color: transparent; border: 1px solid var(--chip-outline); }
.chip-outlined .chip-label { padding: 0 7px; }

.chip-delete {
  width: 16px; height: 16px;
  margin: 0 4px 0 -4px;
  fill: var(--chip-delete);
  cursor: pointer;
  flex-shrink: 0;
}
.chip-delete:hover { fill: var(--chip-delete-hover); }

/* Stack spacing={0.5} adds a 4px left margin between children and the sx sets
   gap: 0.5 on top of it — 8px between chips, 4px between wrapped rows. */
.team-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
  margin-bottom: 16px;
}
.team-chips > * + * { margin-left: 4px; }

/* --- DataGrid ------------------------------------------------------------ */

.grid-outer { max-width: 100%; margin: 0 auto; }

.datagrid {
  border: 1px solid var(--grid-border);
  border-radius: 4px;
  font-size: 0.75rem;            /* sx: '& .MuiDataGrid-root' */
  /* `auto`, like MuiDataGrid-virtualScroller, and not `hidden`: together with
     the identity transform on the rows below, it is what puts the grid on the
     same paint layer MUI's virtualiser puts it on. Both are needed, and
     neither is decoration — a circle or a rounded corner rasterised straight
     into the page comes out a level or two off the same shape rasterised into
     a composited scroller, which is every icon in this grid. Nothing here
     scrolls: every row is rendered, and the scrollbars are hidden the way
     MuiDataGrid-virtualScroller hides its own — it draws its own instead, and
     without this the last column's separator, which hangs twelve pixels past
     the grid, would raise a horizontal bar that React never shows. */
  overflow: auto;
  scrollbar-width: none;                  /* Firefox */
  /* `GridVirtualScroller` is `position: relative` and carries no transform of
     its own — the transform is the render zone's, below. */
  position: relative;
}
.datagrid::-webkit-scrollbar { display: none; }   /* Safari and Chrome */

/* The header container carries the divider, so it sits outside the 33px
   column-header height — same as MuiDataGrid-columnHeaders. The sx in
   UserManager asks for a transparent background, but DataGrid v7 paints it
   from --DataGrid-containerBackground instead, so the grey shows through. */
.dg-headers {
  border-bottom: 1px solid var(--border-table);
  background-color: var(--bg-default);
}
.dg-header-row, .dg-row { display: flex; }

.dg-header {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  box-sizing: border-box;
  padding: 4px 8px;              /* sx on the DataGrid itself */
  /* The header separator is MUI's own, in the lightened border colour — the
     sx that asks for borderColors.table lands on a v6 class name the current
     DataGrid no longer uses (same reason its background override is inert). */
  border-right: 1px solid var(--grid-border);
  /* The header cell carries its own bottom border on top of the container's,
     which is why the divider under the headers reads as 2px. It also shrinks
     the content box to the 24px the title container occupies. */
  border-bottom: 1px solid var(--border-cell);
  white-space: normal;
  line-height: 1.3;
  cursor: pointer;
  /* Not `hidden`: the separator icon hangs half a pixel past the cell's own
     border, and the title clips itself. */
  position: relative;
  overflow: visible;
}

/* The title container fills the header's content box; the collapsed sort slot
   still occupies 2px, which nudges the centred title 1px left. */
.dg-header-content {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  width: 100%;
  overflow: hidden;
}
/* The title keeps MUI's own nowrap+ellipsis; the sx `whiteSpace: normal`
   applies to the header cell around it, not to this element. */
.dg-header-title {
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* MUI keeps the sort slot collapsed until the header is hovered or sorted. */
.dg-sort-slot { display: flex; align-items: center; width: 2px; visibility: hidden; flex-shrink: 0; }
.dg-header:hover .dg-sort-slot, .dg-header-sorted .dg-sort-slot { visibility: visible; width: auto; }
.sort-icon { fill: var(--action-active); }
.sort-desc { transform: rotate(180deg); }

.dg-cell {
  display: block;
  flex-shrink: 0;
  box-sizing: border-box;
  padding: 8px;
  /* MUI draws the row separator on the cell's TOP edge, which also pushes the
     content down by 1px — copying that is what lines the text up. */
  border-top: 1px solid var(--border-cell);
  border-right: 1px solid var(--border-table);
  white-space: normal;
  line-height: 1.3;
  overflow: hidden;
  /* `MuiDataGrid-cell` truncates rather than clips. Invisible at 1240px,
     where nothing overflows, and the whole difference between the two grids
     at any width narrower than that. */
  text-overflow: ellipsis;
}

.dg-align-center { text-align: center; }
.dg-align-left { text-align: left; }

/* No separator above the first row — the header divider already draws one.
   MUI keeps the border and makes it transparent, and that is not the same as
   dropping it: a cell with a top border mitres it into the right one, so the
   column separator starts a quarter of a pixel lower and fades in over the
   first row of the grid. Dropping the border squares that corner off. */
.dg-body .dg-row:first-child .dg-cell { border-top-color: transparent; }

/* MuiDataGrid-virtualScrollerRenderZone's own transform. Identity, and it
   still matters — see `.datagrid`'s overflow. */
/* `GridVirtualScrollerRenderZone` writes `translate3d(0, offsetTop, 0)`, and
   with nothing scrolled that is `translate3d(0, 0, 0)` — which the CSSOM
   serialises back as a 2D `matrix(1, 0, 0, 1, 0, 0)`. Reading the computed
   value and writing *that* is not the same declaration: a 2D identity does
   not promote anything, a 3D one does. */
.dg-body { transform: translate3d(0, 0, 0); }

.dg-row { cursor: pointer; }
.dg-row:hover { background-color: var(--row-hover); }
.row-odd { background-color: var(--row-odd); }
.row-odd:hover { background-color: var(--row-odd-hover); }


.cell-upper { text-transform: uppercase; }
.cell-bold { font-weight: 600; }

.primary-team {
  display: flex; align-items: center; justify-content: center;
  gap: 4px; overflow: hidden; width: 100%;
}
.primary-team-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.share-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0;
  background-color: var(--primary-main);
}
/* 7.5, not the 9 the Material share sat at: Font Awesome fills its viewBox
   edge to edge, so at 9px it inked 8.4x9 where the icon it replaced inked
   6.75x7.5 — a third larger inside the same 14px disc. */
.share-badge svg { width: 7.5px; height: 7.5px; fill: #fff; }

/* A step up from the 1rem the cells' own text sits at: an emoji flag is a
   picture, not a glyph, and reads small at the size the words around it want. */
.flag-cell { font-size: 1.15rem; }

/* The two states of a boolean column, sized back to what the Material pair
   inked inside the same 20px box: the tick's disc filled 16.7 of it and the
   dash 11.7 across, where Font Awesome fills its viewBox edge to edge and so
   came out at 20 and 17.5. Two sizes, not one — the dash was the narrower of
   the two before, and reads wrong beside the disc if it is not. */
.icon-success { fill: var(--success-main); font-size: 16.7px; }
.icon-disabled { fill: var(--text-disabled); font-size: 13.3px; }

.toast { margin-bottom: 16px; }

/* ==========================================================================
   Dialog, Autocomplete, DatePicker — the Users edit form
   ========================================================================== */

.dialog-root { position: fixed; inset: 0; z-index: 1300; }

.dialog-backdrop {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);
  -webkit-tap-highlight-color: transparent;
}

.dialog-container {
  position: relative;
  height: 100%;
  outline: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.dialog-paper {
  position: relative;
  margin: 32px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  max-height: calc(100% - 64px);
  max-width: 600px;
  border-radius: 4px;
  box-shadow: var(--elev24);
  /* Theme override: MuiDialog paper takes background.popup and no elevation tint. */
  background-color: var(--bg-popup);
  background-image: none;
}

.dialog-sm-full { width: calc(100% - 64px); }

.dialog-title {
  flex: 0 0 auto;
  margin: 0;
  font-size: 1rem;               /* sx */
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: normal;
  padding: 12px 16px;            /* sx */
}

.dialog-title-default { font-size: 1.25rem; padding: 16px 24px; }

.dialog-content {
  flex: 1 1 auto;
  overflow-y: auto;
  /* MUI's `.MuiDialogTitle-root + &` rule zeroes the top padding and outranks
     the sx pt:1; the 8px gap comes from the form Box's own padding-top. */
  padding: 0 24px 20px;
}

.dialog-content-default { padding: 0 24px 20px; }

.dialog-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 8px 16px;             /* sx */
}

.dialog-actions-default { padding: 8px; }
.dialog-actions > :not(style) ~ :not(style) { margin-left: 8px; }
.dialog-delete { margin-right: auto; }

.btn-error { background-color: var(--error-main); }
.btn-error:hover { background-color: var(--error-dark); }

.btn-start-icon { display: inherit; margin-right: 8px; margin-left: -2px; }
.btn-start-icon .mui-icon { font-size: 1.125rem; }
/* Font Awesome's bin is a solid, wide thing where Material's was a slim one:
   at the 18px the other start icons get, it inked 15.75x18.6 against the
   10.5x13.5 of the icon it replaced. 13px puts it back beside its own label. */
.btn-start-icon .trash-icon { font-size: 13px; }

/* --- Form body ----------------------------------------------------------- */

/* variant="outlined" is elevation 0, so it gets no dark-mode elevation tint. */
.paper-outlined {
  border: 1px solid var(--border-cell);
  box-shadow: none;
  background-image: none;
}

.typo-subtitle2 { font-size: 0.875rem; line-height: 1.57; font-weight: 500; letter-spacing: normal; }
.section-heading { font-weight: 600; margin-bottom: 8px; }

.user-form { display: grid; gap: 12px; padding-top: 8px; }
.form-section { padding: 12px; }
.form-grid { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; }
/* MUI's FormControl is the grid item and stretches; the input inside keeps its
   own height and sits at the top of the row. */
.form-grid:not(.form-grid-centered) > .tf { align-self: start; }
.form-grid-centered { align-items: center; }
.form-flags { display: flex; gap: 16px; align-items: center; padding: 0 4px; }
.form-notif-teams { padding: 0 4px; }
.new-password { grid-column: 1 / -1; font-size: 0.8rem; }
.select-all { user-select: all; }

.delete-warning { margin-top: 12px; font-size: 0.85rem; }
.delete-body { padding-top: 16px; }

/* --- Autocomplete (multiple, small) -------------------------------------- */

.tf-autocomplete { display: inline-flex; width: 100%; }

.ac-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  width: 100%;
  padding: 3px 9px 3px 5px;
  position: relative;
}

.ac-field .chip { margin: 2px; max-width: calc(100% - 6px); }

.ac-input {
  flex-grow: 1;
  width: 0;
  min-width: 0;                  /* app override: multiFieldSx */
  padding: 2.5px 4px 2.5px 6px;
  height: auto;
}

/* freeSolo Autocompletes render no popup indicator; the Pays field puts a
   flag where it would have been. */
.ac-flag { font-size: 1.25rem; white-space: nowrap; padding-left: 8px; line-height: 1; margin-left: auto; }
.tf-autocomplete-flag .ac-field { padding-right: 8px; }

/* A shrunk label is the resting state for these fields: they always carry
   either chips or the outline notch.

   `top: 0` because the compact fields rest their label at `top: 50%` to centre
   it, and -9px counts from the field's top edge, not from its middle: without
   this the label sat in the middle of the box, over the chips it is supposed
   to be above. */
.tf-label-shrunk {
  top: 0;
  transform: translate(14px, -9px) scale(0.75);
  max-width: calc(133% - 24px);
}
/* An Autocomplete's notch is open because its label floats — which it only
   does once the field holds something. */
.tf-autocomplete .tf-outline legend:not(.legend-empty) { max-width: 100%; }

/* --- DatePicker (closed field) ------------------------------------------- */

.tf-datepicker { padding-right: 14px; }
.tf-datepicker .tf-input { cursor: default; }

.dp-button {
  position: relative;
  margin-right: -8px;
  padding: 8px;
  color: var(--action-active);
  flex-shrink: 0;
}
/* Below what the Material calendar inked (18x20 at a 24px box), and
   deliberately: the field's own text is 16px, and the button is a way into the
   picker rather than something to read. */
.dp-button .mui-icon { font-size: 18px; }

/* --------------------------------------------------------------------------
   UserManager passes utils/formStyles.fieldSx to every field: a 0.75rem input
   at 6px vertical padding, and a 0.75rem label.

   StatusManager's own fieldSx kept the default 1rem label, and this port
   followed it — but a 1rem label at rest is 23px tall inside a 25px field, and
   in a 130px card it filled the box edge to edge. The status cards join the
   others below; the input padding stays theirs.
   -------------------------------------------------------------------------- */

.users-page .tf-input,
.dayoff-page .tf-input,
.dialog-paper .tf-input { padding: 6px 14px; }

/* A 0.75rem label rests a pixel higher than MUI's own 1rem one: 8px, not 9.
   `.tf-set-number` is the same compact label, wherever it is used. */
.users-page .tf-label,
.dayoff-page .tf-label,
.dialog-paper .tf-label,
.card .tf-label,
.tf-set-number .tf-label {
  font-size: 0.75rem;
  /* Centred in whatever height the field turns out to have, rather than a
     fixed offset measured on one of them. These are 25px in a status card, 29
     in a dialog, 32 with a date picker's button beside them and 34 once an
     autocomplete holds chips — the single 8px they all used to share was right
     for the 34 and two pixels low everywhere else. */
  top: 50%;
  transform: translate(14px, -50%) scale(1);
}

.users-page .tf-input:focus ~ .tf-label,
.users-page .tf-input:not(:placeholder-shown) ~ .tf-label,
.dayoff-page .tf-input:focus ~ .tf-label,
.dayoff-page .tf-input:not(:placeholder-shown) ~ .tf-label,
.dialog-paper .tf-input:focus ~ .tf-label,
.dialog-paper .tf-input:not(:placeholder-shown) ~ .tf-label,
.card .tf-input:focus ~ .tf-label,
.card .tf-input:not(:placeholder-shown) ~ .tf-label,
/* The class, not just the `:not(:placeholder-shown)` above: an Autocomplete
   raises its label by hand, because chips are what fills it and no input is
   involved. Each scope has to be named, or the centring rule wins on
   specificity and the label sits over the chips it should be above. */
.users-page .tf-label-shrunk,
.dayoff-page .tf-label-shrunk,
.card .tf-label-shrunk,
.dialog-paper .tf-label-shrunk,
.tf-set-number .tf-label-shrunk,
.tf-set-number .tf-input:focus ~ .tf-label,
.tf-set-number .select-value:focus ~ .tf-label {
  /* Back to the top edge: -9px means nine above the field, not above its
     middle. */
  top: 0;
  transform: translate(14px, -9px) scale(0.75);
}

.search-field .tf-input { padding-left: 0; }

/* Checkboxes in the dialog keep MUI's default padding — the compact 4px is a
   StatusManager-only override. */
.dialog-paper .cb { padding: 9px; }

/* Autocomplete: `freeSolo` removes the popup indicator, but a field holding
   chips still gets a clear icon — and its 39px of reserve. Empty, there is
   nothing to clear and the padding is 9px; the Pays field's sx forces 8. */
.ac-field { padding: 6px 9px 6px 6px; }
.tf-autocomplete-filled .ac-field { padding-right: 39px; }
.tf-autocomplete-flag .ac-field { padding-right: 8px; }

.dialog-paper .ac-input {
  padding: 2.5px 4px 2.5px 8px;
  min-width: 0;
  width: 0;
  flex-grow: 1;
  height: 17.25px;
}

/* DatePicker: the root stays 32px tall (the adornment caps at max-height 2em)
   while the 40px icon button overflows it, exactly as MUI renders it. */
.tf-datepicker {
  height: 32px;
  padding: 0 14px 0 0;
  align-items: center;
}
.dialog-paper .tf-datepicker .tf-input { padding: 6px 0 6px 14px; }
.dp-button { padding: 8px; margin-right: -12px; }

/* ==========================================================================
   Team calendar
   ========================================================================== */

.calendar-page { width: 100%; overflow-x: auto; }

.cal-controls {
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.cal-chips { margin-bottom: 0; }

/* --- ButtonGroup --------------------------------------------------------- */

.btn-group { display: inline-flex; border-radius: 4px; }
.btn-group > .btn:not(:last-of-type) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.btn-group > .btn:not(:first-of-type) { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.btn-group > .btn-outlined:not(:last-of-type) { border-right-color: transparent; }
.btn-group > .btn-outlined:hover:not(:last-of-type) { border-right-color: var(--primary-main); }
.btn-group > .btn-contained { box-shadow: none; }

/* sx px:1 — needs to outrank `.btn-contained.btn-small`. */
.btn-group .cal-month, .btn-group .cal-year { padding-left: 8px; padding-right: 8px; }
.cal-month { min-width: 40px; font-size: 0.75rem; }
/* The sx asks for minWidth 80, but ButtonGroup's own `grouped` rule (40px)
   wins over it — the year button is only as wide as its label. */
.cal-year {
  min-width: 40px;
  font-size: 0.75rem;
  border-left: 2px solid var(--border-table) !important;
}

/* --- The grid ------------------------------------------------------------ */

.cal-container {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  overflow-x: auto;
}

.cal-table {
  width: max-content;
  /* stickyHeader switches MUI's Table to separate borders — collapsing them
     changes every row height by half a pixel. */
  border-collapse: separate;
  border-spacing: 0;
  display: table;
}

.cal-th, .cal-td {
  border-bottom: 1px solid var(--border-cell);
  border-right: 1px solid var(--border-table);
  text-align: left;
  vertical-align: inherit;
  font-size: 0.875rem;           /* MuiTableCell default */
  line-height: 1.43;
}
.cal-th:last-child, .cal-td:last-child { border-right: none; }

.cal-th {
  /* sx: '& th' — 2px padding, 0.75rem, and stickyHeader on top of it. */
  padding: 2px 2px;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.5rem;
  color: var(--text-primary);
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--bg-default);
}

.cal-th-name { width: 180px; position: sticky; left: 0; z-index: 3; background-color: var(--bg-paper); }
.cal-th-team { width: 90px; text-align: center; }
.cal-th-tla { width: 40px; text-align: center; }

/* The day headers override stickyHeader's background with `inherit`, so the
   Paper shows through; Team/TLA keep the sticky default. */
.cal-th-day {
  background-color: transparent;
  width: 30px;
  min-width: 30px;
  max-width: 30px;
  box-sizing: border-box;
  text-align: center;
}
.cal-th-day.is-shaded { background-color: var(--gray-800); }
.cal-th-day.is-today { background-color: var(--today); color: #fff; }

.cal-td { padding: 0; }
.cal-td-name { position: sticky; left: 0; z-index: 1; background-color: var(--bg-paper); }
/* The name is what decides how tall a row is: 6px of padding around a line
   of 12px text inherits `line-height: 1.43` and lands on 29.16px, which made
   every day of the grid a sixth of a pixel taller than it is wide. Pinned to
   17px, the row comes to exactly 30 and the painted square is 29 by 29. */
.cal-name { padding: 6px 6px; font-size: 0.75rem; line-height: 17px; font-weight: bold; }
.cal-td-team, .cal-td-tla { text-align: center; font-size: 0.75rem; }

.cal-cell {
  position: relative;
  height: 30px;
  font-size: 0.75rem;
  width: 30px;
  min-width: 30px;
  max-width: 30px;
  box-sizing: border-box;
  padding: 0;
  border-top: 0 solid var(--border-cell);
  border-left: 0 solid var(--border-cell);
  pointer-events: none;
}
.cal-cell-inner { height: 100%; width: 100%; }

.cal-cell.is-editable { pointer-events: auto; cursor: pointer; user-select: none; }
.cal-cell.is-locked { pointer-events: auto; cursor: not-allowed; user-select: none; }

/* Past days keep their colour and take a veil on top — an overlay rather than
   a background, so half-day gradients survive. */
.cal-cell.is-past::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--past-day);
  pointer-events: none;
}

/* Remote-work marker, shaped by the Statuses screen's setting. */
.cal-cell.is-remote::before, .cal-cell.is-remote-over::before { content: ""; position: absolute; }
.cal-cell.is-remote::before { --marker: var(--tt-color); }
.cal-cell.is-remote-over::before { --marker: var(--tt-over-color); }

[data-marker-shape="bar-top"] .cal-cell.is-remote::before,
[data-marker-shape="bar-top"] .cal-cell.is-remote-over::before {
  top: 0; left: 0; right: 0; height: 6px; background-color: var(--marker);
}
[data-marker-shape="bar-bottom"] .cal-cell.is-remote::before,
[data-marker-shape="bar-bottom"] .cal-cell.is-remote-over::before {
  bottom: 0; left: 0; right: 0; height: 6px; background-color: var(--marker);
}
[data-marker-shape="triangle-tl"] .cal-cell.is-remote::before,
[data-marker-shape="triangle-tl"] .cal-cell.is-remote-over::before {
  top: 0; left: 0; width: 0; height: 0; border-style: solid;
  border-width: 11px 11px 0 0; border-color: var(--marker) transparent transparent transparent;
}
[data-marker-shape="triangle-tr"] .cal-cell.is-remote::before,
[data-marker-shape="triangle-tr"] .cal-cell.is-remote-over::before {
  top: 0; right: 0; width: 0; height: 0; border-style: solid;
  border-width: 11px 0 0 11px; border-color: var(--marker) transparent transparent transparent;
}

/* Selection outline: the run's outer edges take the selected colour, the
   joins between two selected cells stay on the default one. The right-hand
   join reads `sel-end`, a class the selection sets on the last cell of each
   run: the equivalent `:has(+ .cal-cell.sel)` is stale in Firefox once the
   class comes off the sibling to its right. */
.cal-cell.sel {
  border-top: 1px solid var(--border-selected);
  border-bottom: 1px solid var(--border-selected);
  border-left: 1px solid var(--border-selected);
  border-right: 1px solid var(--border-selected) !important;
  /* And a wash of the same colour over the whole cell, not just its edge: an
     outline one pixel wide is a lot to ask of a 30px square. An inset shadow
     rather than a background, so the status colour and the half-day gradients
     under it survive — the two overlay slots are taken, `::before` by the
     remote-work marker and `::after` by the past-day veil. The spread is the
     cell's own size, which is what closes the hole an inset shadow leaves. */
  box-shadow: inset 0 0 0 30px color-mix(in srgb, var(--border-selected) 12%, transparent);
}
.cal-cell.sel + .cal-cell.sel { border-left-color: var(--border-cell); }
.cal-cell.sel:not(.sel-end) { border-right-color: var(--border-cell) !important; }

/* --- Legend -------------------------------------------------------------- */

.cal-legend {
  margin-top: 32px;
  margin-bottom: 32px;
  display: flex;
  gap: 64px;
  justify-content: center;
  flex-wrap: wrap;
}
.cal-legend-col { display: flex; flex-direction: column; gap: 8px; }
.cal-legend-row { display: flex; align-items: center; gap: 8px; }
.cal-legend-swatch {
  width: 20px; height: 20px; box-sizing: border-box; flex-shrink: 0;
  border: 1px solid var(--gray-400);
}
.typo-caption { font-size: 0.75rem; line-height: 1.66; letter-spacing: normal; }

/* --- Context menu -------------------------------------------------------- */

.menu-root, .select-menu { position: fixed; inset: 0; z-index: 1300; }
.menu-backdrop, .select-backdrop { position: fixed; inset: 0; }

/* Three four-digit entries, so none of the 400px floor below applies: that is
   the calendar's context menu, which carries a row of status buttons. Both
   classes named, or the rule loses to `.menu-paper` on order alone. */
.menu-paper.year-menu { min-width: 0; width: max-content; }

.menu-paper, .select-paper {
  position: absolute;
  min-width: 400px;
  max-height: calc(100% - 96px);
  overflow-y: auto;
  border-radius: 4px;
  box-shadow: var(--elev8);
  background-color: var(--bg-popup);
  background-image: none;
}

.menu-list { padding: 8px 0; }
.menu-group { padding: 4px 8px; display: flex; flex-direction: column; gap: 4px; }
.menu-halves { padding: 4px 8px; display: flex; gap: 16px; }
.menu-half { flex: 1; padding: 0; }

.divider { border: 0; border-top: 1px solid var(--border-cell); margin: 4px 0; }

/* sx px:2 — has to outrank `.btn-outlined.btn-small`. */
.menu-group .menu-btn, .menu-half .menu-btn {
  padding-left: 16px;
  padding-right: 16px;
}

.menu-btn {
  white-space: nowrap;
  width: 100%;
  border-color: var(--border-button);
  color: var(--border-button);
}
/* The sx that paints these borders outranks MUI's own hover rule, so a
   hovered menu button keeps its colour where a plain outlined one goes to the
   primary. The two are the same colour in light mode, which is why this only
   ever showed in dark — and only with the pointer left on a button, which one
   script does and the rest do not. */
.menu-btn:hover { border-color: var(--border-button); }
.menu-btn-centred { justify-content: center; align-items: center; }
.menu-btn-start { justify-content: flex-start; gap: 8px; }
/* MUI: a disabled Button takes action.disabled for text and
   action.disabledBackground for an outlined border — two different alphas. */
.menu-btn:disabled {
  color: var(--action-disabled);
  border-color: var(--action-disabled-bg);
  pointer-events: none;
}

.menu-btn-success { color: #fff; background-color: var(--success-main); }
.menu-btn-success:hover { background-color: var(--success-dark); }
.menu-btn-danger { color: #fff; background-color: var(--error-main); border-color: var(--border-button); }
.menu-btn-danger:hover { background-color: var(--error-dark); border-color: var(--border-button); }

.mi-content { display: flex; align-items: center; justify-content: flex-start; gap: 8px; }
.mi-swatch { width: 16px; height: 16px; flex-shrink: 0; border: 1px solid var(--border-legend); }
.mi-swatch-empty { background-color: transparent; }
/* Half-day codes: the square mirrors the segment the code applies to, filled
   left for morning and right for afternoon. React always fills the left half
   in this menu on the grounds that the column already says which it is; the
   swatch reading the same for both halves was reported as confusing, so the
   pilot deviates here (README, deliberate deviations). */
.mi-swatch-half { background: linear-gradient(to right, var(--half) 50%, transparent 50%); }
.mi-swatch-half-pm { background: linear-gradient(to right, transparent 50%, var(--half) 50%); }

.tt-chip { height: 18px; font-size: 0.7rem; font-weight: 600; color: #000; }
.tt-chip .chip-label { padding: 0 6px; }

.menu-list-plain { padding: 8px 0; }
.menu-item, .select-item {
  padding: 6px 16px;
  font-size: 0.75rem;
  min-height: auto;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.menu-item:hover, .select-item:hover { background-color: var(--action-hover); }
.menu-item-selected { background-color: color-mix(in srgb, var(--primary-main) 8%, transparent); }

.dialog-xs-full { width: calc(100% - 64px); max-width: 444px; }

/* --- Importing my data: a determinate LinearProgress and the log box ----- */

/* MUI paints the bar on top of a track it colours itself, and translates it
   left by `value - 100` percent of its own width — the bar is always the full
   width of the track. */
.progress {
  position: relative;
  overflow: hidden;
  display: block;
  height: 4px;
  z-index: 0;
  background-color: var(--progress-track);
}

.progress-bar {
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  transform-origin: left;
  transition: transform 0.4s linear;
  background-color: var(--primary-main);
}

/* DialogContentText — body1 in the secondary text colour. */
.dialog-text {
  margin: 0;
  font-size: 1rem;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--text-secondary);
}

.import-spaced { margin-bottom: 16px; }      /* sx={{ mb: 2 }} */

.import-log {
  margin: 0;
  padding: 8px;
  max-height: 260px;
  overflow-y: auto;
  font-size: 0.7rem;
  font-family: monospace;
  background-color: var(--action-hover);
  border-radius: 4px;
  white-space: pre-wrap;
}
.custom-code-field { width: 100%; margin-top: 8px; }

/* ==========================================================================
   Detailed timesheet
   ========================================================================== */

.ts-page { max-width: 100%; }

.ts-controls {
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.ts-toggles { display: flex; gap: 8px; }

/* --- Recap dashboard ----------------------------------------------------- */

/* The dashboard is as wide as the grid below it — React measures the table
   with a ResizeObserver, a shared shrink-to-fit wrapper does it in CSS. */
.ts-sized { width: fit-content; max-width: 100%; margin: 0 auto; }

.ts-dashboard {
  padding: 4px 12px;
  margin: 0 0 16px;
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  box-sizing: border-box;
}

.ts-dash-grid { display: grid; }
.ts-dash-rule { grid-column: 1 / -1; border-top: 1px solid var(--border-table); height: 1px; }

.ts-dash-title,
.ts-dash-label,
.ts-dash-value {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-top: 4px;
  padding-bottom: 4px;
}

.ts-dash-title {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding-right: 4px;
  border-right: 1px solid var(--border-table);
}

.ts-dash-label {
  font-size: 0.7rem;
  color: var(--text-secondary);
  text-align: right;
  padding-left: 4px;
  padding-right: 12px;
}

.ts-dash-value {
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
  padding-left: 4px;
  padding-right: 4px;
}
.ts-dash-sep { border-right: 1px solid var(--border-table); }

/* --------------------------------------------------------------------------
   The tooltip overlay (`static/tooltip.js`). MUI's own Tooltip, which is what
   the browser's `title` was standing in for: the same grey, size and padding
   in both themes, since it is a floating label rather than a surface.
   -------------------------------------------------------------------------- */
.tooltip-overlay {
  position: fixed;
  z-index: 1600;                      /* over the menus (1300) and ripples (1500) */
  max-width: 300px;
  padding: 4px 8px;
  border-radius: 4px;
  background: rgba(97, 97, 97, 0.92);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  /* The meetings button lists one line per meeting. */
  white-space: pre-line;
  /* It is placed against its host: catching the pointer would end the very
     hover it is explaining, and flicker. */
  pointer-events: none;
}

/* A clipped cell's full text is one unbroken run — a country, a filename, a
   comment — and wrapping it at 300px turns a one-line answer into four. */
.tooltip-overlay-value { max-width: 480px; }

.ts-dash-negative { color: var(--error-main); }
.ts-dash-negative-white { color: #fff; }

/* --- The grid ------------------------------------------------------------ */

.ts-container {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  overflow-x: auto;
}

.ts-table {
  border-collapse: separate;   /* stickyHeader, same as the calendar */
  border-spacing: 0;
  display: table;
}

.ts-th, .ts-td {
  font-size: 0.8rem;
  padding: 0 6px;
  height: 30px;
  box-sizing: border-box;
  text-align: center;
  border-right: 1px solid var(--border-cell);
  border-bottom: 1px solid var(--border-cell);
  line-height: 1.43;
}
.ts-th:last-child, .ts-td:last-child { border-right: 0; }

/* A head cell's line-height is a fixed 24px in MuiTableCell, not the body's
   1.43 — a sixth of a pixel on the text, which lands on a different device
   pixel as soon as anything above the table changes height. */
.ts-th {
  font-weight: 600;
  line-height: 24px;
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 2;
  background-color: var(--bg-default);
}
.ts-th-day { width: 30px; min-width: 30px; max-width: 30px; position: sticky; left: 0; z-index: 3; background-color: var(--bg-paper); }
.ts-th-totals { background-color: var(--bg-default); }

.ts-td-day {
  width: 30px; min-width: 30px; max-width: 30px;
  padding: 0;
  position: sticky; left: 0; z-index: 1;
  background-color: var(--bg-paper);
}
.ts-day-number { font-size: 0.8rem; font-weight: 700; }

/* Totals columns are tinted only on a normal row: on a coloured one the cell
   stays transparent so the row's tint shows through once, not twice. */
.ts-td-totals { background-color: var(--bg-default); }
.ts-td-clear { background-color: transparent; }

.ts-today { box-shadow: inset 3px 0 0 0 #1976d2; }

/* These are MUI Typographies: body1's line-height 1.5, not the cell's 1.43. */
.ts-value, .ts-day-number { line-height: 1.5; }
.ts-value { font-size: 0.8rem; }
.ts-plus { color: var(--success-dark); }
.ts-moins { color: var(--error-dark); }

.ts-td-comment { text-align: left; }

/* Excel-style fields: no underline, no spinner, no native time widget. */
.ts-input {
  width: 100%;
  min-width: 0;
  border: 0;
  outline: 0;
  background: none;
  font: inherit;
  font-size: 0.75rem;
  color: currentColor;
  padding: 6px 0;
  box-sizing: content-box;
  height: 1.4375em;
  /* MUI's FormControl is an inline-flex with `vertical-align: top`: it makes
     a line box like any inline, but hangs from its top rather than sitting on
     the baseline. Plain `display: block` would eat the line box's own height. */
  display: inline-block;
  vertical-align: top;
  /* MUI puts `1.4375em` on the InputBase *root*, so the input inherits 23px
     whatever its own font-size is — `em` here would resolve against 0.8rem
     and sit the glyphs a pixel and a half low. */
  line-height: 1.4375rem;
}
.ts-input::placeholder { color: currentColor; opacity: var(--placeholder-opacity); }
.ts-input:disabled { color: var(--text-disabled); -webkit-text-fill-color: var(--text-disabled); }

.ts-time { font-size: 0.8rem; text-align: center; padding: 2px 0; min-width: 30px; }

/* A time that runs backwards: 12:50 at Midi and 12:30 at A. Midi is not a day,
   and the cell that breaks the order says so. The disabled pair is repeated
   because the team view locks every field it shows, and a locked cell that is
   wrong is exactly the one a manager is reading. */
.ts-backwards,
.ts-input:disabled.ts-backwards {
  color: var(--error-main);
  -webkit-text-fill-color: var(--error-main);
}

.ts-meetings { display: flex; align-items: center; }
.ts-meetings-hours { flex: 1; min-width: 0; text-align: center; }
.ts-icon { flex-shrink: 0; color: var(--action-active); }
/* The meetings calendar is the one icon repeated on every row of the grid, so
   it reads as a texture rather than as a control: Font Awesome's outline
   calendar, and 1rem where every other icon gets 1.25. */
.ts-meet-icon { font-size: 1rem; }
.ts-icon-primary { color: var(--primary-main); }

/* --- Meetings dialog ----------------------------------------------------- */

/* `sx={{ pt: 1 }}` loses: MUI's own `.MuiDialogTitle-root + .MuiDialogContent-root
   { padding-top: 0 }` is two classes deep and the sx layer is one. */
.ts-meetings-content { padding-top: 0; }
.ts-meeting-list { display: grid; gap: 8px; }
.ts-meeting-list-filled { margin-bottom: 16px; }
.ts-meeting-empty { font-size: 0.8rem; color: var(--text-secondary); }

.ts-meeting-row { padding: 8px; display: flex; align-items: center; gap: 12px; }
.ts-meeting-time { font-size: 0.8rem; font-weight: 600; width: 92px; flex-shrink: 0; }
.ts-meeting-comment { font-size: 0.8rem; color: var(--text-secondary); flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-meeting-actions { display: flex; gap: 2px; }

.ts-meeting-form { padding: 8px; display: flex; align-items: center; gap: 12px; }
.ts-meeting-times { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.ts-meeting-field { width: 40px; }
.ts-meeting-dash { font-size: 0.8rem; }
.ts-meeting-input { flex-grow: 1; min-width: 0; }
.ts-meeting-input .tf-input { font-size: 0.8rem; padding: 4px 14px; }
.ts-meeting-buttons { display: flex; gap: 4px; flex-shrink: 0; }

/* --- On-call list -------------------------------------------------------- */

/* Unlike the calendar, the on-call day headers carry an explicit height. */
.oncall-grid .cal-th { height: 30px; }

/* The read-only calendar has no hover; the editable on-call cells do. */
.oncall-grid .cal-cell.is-editable:hover { opacity: 0.7; }
.oncall-grid .cal-cell.is-outside, .cal-th-day.is-outside { opacity: 0.6; }

.cal-th-phone { width: 130px; text-align: center; }
.cal-th-email { width: 180px; text-align: center; }
.cal-td-phone { text-align: center; font-size: 0.75rem; font-weight: bold; }
.cal-td-email { text-align: center; font-size: 0.75rem; }

.oncall-legend-row { display: flex; flex-direction: row; gap: 24px; flex-wrap: wrap; }

/* Unlike the calendar's, this Menu sets no minWidth on its paper — the 220px
   lives on the inner Box, so the paper shrink-wraps to the widest label. */
.oncall-menu { min-width: 0; }
.oncall-menu-group { min-width: 220px; }

.chip-filled-warning { background-color: var(--warning-main); color: var(--warning-contrast); }
/* A coloured Chip hovers to its palette's `dark`, not to a mix of its own. */
.chip-clickable.chip-filled-warning:hover { background-color: var(--warning-dark); }
.chip-outlined-warning {
  background-color: transparent;
  border: 1px solid color-mix(in srgb, var(--warning-main) 70%, transparent);
  color: var(--warning-main);
}
.chip-outlined-warning .chip-label { padding: 0 7px; }
.chip-clickable.chip-outlined-warning:hover { background-color: color-mix(in srgb, var(--warning-main) var(--hover-opacity), transparent); }

/* ==========================================================================
   Days off
   ========================================================================== */

.dayoff-page { max-width: 980px; width: 100%; margin: 0 auto; }
.dayoff-panel { padding: 16px; margin-bottom: 16px; overflow: hidden; }
.dayoff-toolbar { margin-bottom: 16px; display: flex; gap: 16px; align-items: center; }
.dayoff-year { width: 110px; }
.dayoff-scroll { overflow-x: auto; }

.dayoff-cal {
  display: grid;
  grid-template-columns: 36px repeat(12, minmax(56px, 1fr));
  width: fit-content;
  min-width: 100%;
  border-top: 1px solid var(--border-table);
  border-left: 1px solid var(--border-table);
}

.dayoff-corner, .dayoff-month, .dayoff-daynum, .dayoff-cell {
  border-right: 1px solid var(--border-table);
  border-bottom: 1px solid var(--border-table);
}
/* A day the month does not have: no top or bottom rule, but keep the right one
   so the next cell does not lose its left edge. */
.dayoff-void { border-right: 1px solid var(--border-table); }

.dayoff-month {
  font-size: 0.7rem;
  font-weight: 700;
  text-align: center;
  text-transform: capitalize;
  padding: 4px 0;
  line-height: 1.5;
}

.dayoff-daynum {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--text-secondary);
  text-align: center;
  line-height: 26px;
}

.dayoff-cell {
  position: relative;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.dayoff-cell:hover { opacity: 0.7; }
.dayoff-cell.is-holiday { color: #fff; }
.dayoff-flag { position: absolute; right: 3px; font-weight: 400; font-size: 0.95rem; }

.dayoff-form { display: grid; gap: 12px; padding-top: 8px; }
/* Only the year field carries a helper line, so the three must align on their
   tops rather than stretch to the tallest. */
.dayoff-date-row { display: flex; gap: 8px; align-items: flex-start; }
.dayoff-date-row > .tf, .dayoff-date-field { flex: 1; min-width: 0; }
.dayoff-date-field .tf { width: 100%; }
.dayoff-location, .dayoff-description { width: 100%; }

/* A multiline field is sized by its rows, not by the single-line height the
   shared input rule sets. MUI moves the block padding of a multiline field
   from the input to the root (8.5px), which is what makes two rows 75px tall
   instead of 58. */
.dayoff-description { padding: 8.5px 0; }
.dayoff-description .tf-input { height: auto; }

/* sx={{ p: 0.5 }} on the Checkbox, against the 9px the other dialogs use. */
.dayoff-form .cb { padding: 4px; }
/* An endAdornment moves the right padding from the input to the root, so the
   flag sits inside the 14px gutter rather than against the outline. */
.dayoff-location { padding-right: 14px; }
.dayoff-location .tf-input { padding-right: 0; }
/* 1.15rem, as in the Users table: a flag is a picture, and at the 0.75rem the
   helper text around it uses it read as a smudge. */
.dayoff-location-flag { font-size: 1.15rem; line-height: 1.5; color: var(--text-secondary);
  white-space: nowrap; padding-left: 8px; }
/* A multiline label rests at the top of the box rather than on a text line
   that starts 14.5px down, so the 2px the single-line fields just gained does
   not apply to it: kept where it was. */
.dayoff-description .tf-label { top: 0; transform: translate(14px, 8px) scale(1); }

/* MUI's textarea is not resizable; the grip would show as a corner glyph. */
.dayoff-description .tf-input { resize: none; }

/* MUI helperText: FormHelperText under an outlined field. The 4px top margin
   is the sizeSmall variant's — the default is 3px. */
.tf-helper {
  margin: 4px 14px 0;
  font-size: 0.75rem;
  line-height: 1.66;
  color: var(--text-secondary);
}

.btn-error-text { color: var(--error-main); }
.btn-error-text:hover { background-color: color-mix(in srgb, var(--error-main) var(--hover-opacity), transparent); }

/* --- Colours screen (ColorConfig.tsx) ------------------------------------ */

.colors-page { max-width: 1120px; margin: 0 auto; width: 100%; }
.colors-panel { padding: 16px; margin-bottom: 16px; overflow: hidden; }

.cc-toolbar { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.cc-actions, .cc-transfer { display: flex; gap: 16px; }
.cc-transfer { margin-left: auto; }
.cc-editing {
  display: flex;
  align-items: center;
  margin: 0 0 0 8px;
  font-size: 0.8rem;
  color: var(--text-secondary);
}
.cc-alert { margin: 16px 0; }

/* A disabled Button: action.disabled for the text, action.disabledBackground
   for a contained face — and no elevation. */
.btn:disabled { color: var(--action-disabled); pointer-events: none; }
.btn-contained:disabled { background-color: var(--action-disabled-bg); box-shadow: none; }

.btn-error-color { color: var(--error-main); }
.btn-error-color.btn-outlined { border-color: color-mix(in srgb, var(--error-main) 50%, transparent); }
.btn-error-color:hover {
  border-color: var(--error-main);
  background-color: color-mix(in srgb, var(--error-main) var(--hover-opacity), transparent);
}

/* The grid's own sx: 8px cells, line-height 1, and rows that are not clickable.
   This one keeps MUI's default 56px header, where UserManager forces 33. */
.colors-page .dg-cell, .colors-page .dg-header { padding: 8px; line-height: 1; }
/* MUI keeps the first row's separator and paints it transparent, rather than
   dropping it and making the padding up — which matters here because the
   value cell's padding is 2px, not 8. */
.colors-page .dg-body .dg-row:first-child .dg-cell { border-top: 1px solid transparent; padding-top: 8px; }
.colors-page .dg-body .dg-row:first-child .cc-value-cell { padding: 2px; }
.colors-page .dg-row { cursor: default; }
.dg-header-left { justify-content: flex-start; }

/* The title container overflows its header by 5px, because the (empty) menu
   icon slot carries margin-right: -5px. That is what puts the centred title
   2.5px right of where the padding box would. */
.colors-page .dg-header-content { flex: 0 0 auto; width: calc(100% + 5px); }
/* MUI's own header weight; UserManager is the screen that asks for 600. */
.colors-page .dg-header-title { font-weight: 500; }

/* No vertical rules anywhere: the DataGrid draws no cell borders, and the
   column lines in the header are the resize handles' own icon — a 1x24 bar
   straddling the boundary, in the grid's (lightened) border colour. The
   divider under the headers is the header cell's own bottom border, so the
   container adds none. */
.colors-page .dg-cell, .colors-page .dg-header { border-right: 0; }
.colors-page .dg-headers { border-bottom: 0; }
/* The column lines are not borders: they are the resize handles' own icon, a
   1x24 bar drawn inside a 24x24 SVG. Reproducing it as an SVG rather than a
   pseudo-element matters — a 1px box at a half-pixel offset gets snapped to
   whole pixels by the compositor, while the same bar inside an SVG does not. */
.dg-sep {
  position: absolute;
  right: -12px;
  /* Centred in the header's padding box, which is what MUI's flex separator
     lands on — 15.5px down a 55px box here, 4px down the Users grid's 32. */
  top: 0;
  bottom: 0;
  margin: auto 0;
  width: 24px;
  height: 24px;
  fill: var(--grid-border);
}

/* `padding: 2px` and a flex box, so the field is centred in the 35px row. */
.colors-page .cc-value-cell { padding: 2px; display: flex; align-items: center; overflow: hidden; }
.cc-value-slot { display: block; width: 100%; }
.cc-value { width: 100%; height: 28px; }
/* MUI keeps the input at its own 1.4375em and centres it in the 28px root,
   rather than stretching it. */
.cc-value .tf-input { height: 1.4375em; line-height: 1.4375; padding: 4px 8px; }
.tf-outline-error { border-color: var(--error-main); }

.cc-usage { display: flex; align-items: center; gap: 12px; width: 100%; }
.cc-usage-text { margin: 0; font-size: 0.75rem; line-height: 1.2; white-space: normal; }

.cc-swatch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 20px;
  cursor: pointer;
  border: 1px solid #ccc;        /* literal in the component, not a theme colour */
  border-radius: 4px;
}
.cc-swatch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* renderExample() — the contextual samples in the Description column. */
.cc-ex-cell {
  width: 22px; height: 22px; flex-shrink: 0;
  border: 1px solid var(--border-cell);
}
.cc-ex-bar {
  display: flex; align-items: center; flex-shrink: 0;
  width: 70px; height: 20px; padding: 0 4px; border-radius: 2px;
}
.cc-ex-bar-btn { width: 26px; height: 12px; border: 1px solid rgba(255,255,255,0.8); border-radius: 2px; }
.cc-ex-alert {
  display: flex; align-items: center; flex-shrink: 0;
  width: 70px; height: 20px; padding: 0 4px; border-left: 3px solid;
}
.cc-ex-alert-line { width: 34px; height: 4px; opacity: 0.6; border-radius: 8px; }
.cc-ex-grid {
  display: grid; flex-shrink: 0;
  grid-template-columns: 11px 11px; grid-template-rows: 11px 11px;
  width: 22px; height: 22px;
}
.cc-ex-grid > div { border: 1px solid var(--sample); }
.cc-ex-veil { position: relative; width: 22px; height: 22px; flex-shrink: 0; border: 1px solid var(--border-cell); }
.cc-ex-veil > div { position: absolute; inset: 0; }

/* --- Timesheet: annual settings and year summary ------------------------- */

.ts-panel-title {
  margin: 0 0 10px;             /* subtitle1 + sx mb: 1.25 */
  font-size: 1rem;
  line-height: 1.75;
  font-weight: 600;              /* subtitle1, overridden by sx */
}

.ts-settings-wrap { margin-bottom: 16px; }
.ts-settings { padding: 10px; width: 100%; }

.ts-field-caption { margin: 0 0 2px; font-size: 0.7rem; color: var(--text-secondary); }

.ts-pattern { margin-bottom: 24px; text-align: center; }
.ts-pattern-days { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; }
.ts-pattern-btn { min-width: 48px; padding-left: 4px; padding-right: 4px; font-size: 0.7rem; }
/* Same primary as a selected month button; a half day is that colour over half
   the button, which is what the calendar's morning/afternoon codes do too. */
.ts-pattern-F { background-color: var(--primary-main); color: #fff; }
.ts-pattern-F:hover { background-color: var(--primary-main); }
.ts-pattern-M,
.ts-pattern-M:hover { background: linear-gradient(to right, var(--primary-main) 50%, transparent 50%); color: #fff; }
.ts-pattern-A,
.ts-pattern-A:hover { background: linear-gradient(to left, var(--primary-main) 50%, transparent 50%); color: #fff; }

.ts-settings-grid {
  display: grid;
  grid-template-columns: repeat(6, 158px);
  gap: 40px;
  justify-content: center;
}
.ts-settings-grid-2 { margin-top: 10px; }

/* The settings fields: 158px wide, 0.75rem centred text, label always shrunk. */
.tf-settings { width: 158px; }
.tf-settings .tf-input { padding: 4px 14px; text-align: center; }
.tf-settings .tf-outline legend { max-width: 100%; }

.ts-extra-badge {
  color: #fff;
  background-color: var(--primary-main);
  border-radius: 10px;
  padding: 2px 9px;
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 20px;
  vertical-align: middle;
}

.ts-parttime { margin-top: 12px; width: 1148px; max-width: 100%; margin-left: auto; margin-right: auto; text-align: left; }
.ts-parttime-hint { margin-top: 4px; margin-bottom: 0; }
.ts-mode-btn { font-size: 0.7rem; }

.ts-settings-bools {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  width: 1148px;
  max-width: 100%;
  margin: 16px auto 0;
}
.ts-settings-bools-2 { margin-top: 4.8px; }
.ts-bool { margin-left: -11px; }
.ts-settings .ts-cb { padding: 0 4px; }
/* The panel is dense, so its checkboxes are 18px where the rest of the app
   draws them at 20. Only the size differs now: they are the same two icons. */
.ts-cb .cb-icon { width: 18px; height: 18px; }

.ts-settings-save { display: flex; justify-content: center; margin-top: 24px; }
.ts-settings-save-anchor { position: relative; }
.ts-saved-slot {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}
.ts-saved {
  display: flex; align-items: center; gap: 4px;
  color: var(--success-main); font-size: 0.8rem; white-space: nowrap;
}
.mui-icon-small { font-size: 1.25rem; }
.ts-settings-error { padding-top: 0; padding-bottom: 0; }

/* --- Year summary -------------------------------------------------------- */

/* Both panels are as wide as the table below them — React measures it with a
   ResizeObserver, `.ts-sized` shrink-wraps it in CSS and the panels fill it. */
/* The gap under the panel belongs to the wrapper, as it does for the settings
   above: `.ts-recap-wrap` is what the collapsing row sizes, and a margin left
   on the paper would be measured inside it. */
.ts-recap-wrap { margin-bottom: 16px; }
.ts-recap { padding: 12px; width: 100%; }
.ts-recap .ts-panel-title { margin-bottom: 12px; }   /* sx mb: 1.5 */

.ts-recap-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(90px, 1fr) minmax(55px, 0.6fr));
  margin-bottom: 16px;
}
.ts-recap-stat-label, .ts-recap-stat-value {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-bottom: 1px solid var(--border-table);
}
.ts-recap-stat-label {
  font-size: 0.7rem;
  color: var(--text-secondary);
  text-align: right;
  padding: 4px 12px 4px 4px;
}
.ts-recap-stat-value {
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
  padding: 4px;
  border-right: 1px solid var(--border-table);
}
.ts-recap-top { border-top: 1px solid var(--border-table); }
.ts-recap-left { border-left: 1px solid var(--border-table); }

.ts-recap-table-wrap { width: fit-content; max-width: 100%; margin: 0 auto; overflow-x: auto; }
.ts-recap-table { border-collapse: collapse; }
.ts-recap-th, .ts-recap-td {
  font-size: 0.75rem;
  padding: 2px 6px;
  height: 28px;
  box-sizing: border-box;
  text-align: center;
  border-right: 1px solid var(--border-table);
  border-bottom: 1px solid var(--border-table);
}
/* MuiTableCell head fixes its line-height at 24px; a body cell keeps 1.43. */
.ts-recap-th { font-weight: 600; white-space: nowrap; background-color: var(--bg-default); line-height: 24px; }
.ts-recap-td { line-height: 1.43; }
.ts-recap-th:last-child, .ts-recap-td:last-child { border-right: 0; }
.ts-recap-month { text-align: left; text-transform: capitalize; }
.ts-recap-total { font-weight: 700; }
.ts-negative { color: var(--error-main); }

/* --- Switch (Font Awesome's two toggles) --------------------------------- */

/* Was MUI's Switch, built here out of a track and a thumb. It is one icon per
   state now, the pair stacked so neither moves and the change is a cross-fade
   rather than a slide. The box keeps the 40x24 the MUI one had, which is what
   the label beside it is spaced for. */
.switch {
  display: inline-flex;
  position: relative;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 24px;
  vertical-align: middle;
}

/* 576x512 in Font Awesome, so a 30px-wide icon stands 27 tall — the largest
   that clears the 24px box once the pill's own margins inside the viewBox are
   taken off. */
.switch-icon {
  position: absolute;
  width: 30px;
  height: 30px;
  fill: currentColor;
  transition: opacity 150ms var(--ease-in-out), color 150ms var(--ease-in-out);
}
.switch-icon-off { color: var(--switch-track); opacity: 0.6; }
.switch-icon-on { color: var(--primary-main); opacity: 0; }
.switch:has(.switch-input:checked) .switch-icon-off { opacity: 0; }
.switch:has(.switch-input:checked) .switch-icon-on { opacity: 1; }

/* The hit area is the whole switch, not the icon: a person aims at the end of
   the track they want, and both ends have to answer. */
.switch-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.switch:has(.switch-input:disabled) .switch-icon-off { opacity: var(--switch-disabled-track); }
.switch:has(.switch-input:checked:disabled) .switch-icon-on { color: var(--switch-checked-disabled); }
.set-switch-label:has(.switch-input:disabled) .fcl-label { color: var(--text-disabled); }
.switch:has(.switch-input:disabled) .switch-input { cursor: default; }

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

/* The width of the Colours screen's block, so the two tabs line up:
   `colors.GRID_WIDTH` is 1088 inside a Paper that adds 16px each side. */
.settings-page { max-width: 1120px; margin: 0 auto; }

.set-panel { padding: 16px; }
.set-panel-spaced { margin-top: 16px; }
.set-panel-half { flex: 1; min-width: 0; }
.set-row { display: flex; gap: 16px; margin-top: 16px; }
/* The first row sits under the tabs, which already space it. The default
   `stretch` is what it wants: two cards of one height side by side, and two
   of one width once the row turns into a column. */
.set-row-top { margin-top: 0; }

/* Branch and commit, label above value, so a long subject wraps under its
   own label instead of pushing the panel wider than its half. */
.set-update-facts { margin: 0 0 12px; display: grid; gap: 2px 12px; font-size: 0.8rem; }
.set-update-facts dt { color: var(--text-secondary); }
.set-update-facts dd { margin: 0 0 6px; }
.set-update-commit { font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; overflow-wrap: anywhere; }
.set-update-blocked { margin: 0; font-size: 0.8rem; color: var(--text-secondary); font-style: italic; }

@media (max-width: 600px) {
  .set-row { flex-direction: column; }
}

.set-title {
  margin: 0 0 12px;
  font-size: 0.875rem;
  line-height: 1.57;
  font-weight: 600;              /* subtitle2 + sx */
}
.set-title-tight { margin-bottom: 8px; }
/* body2, so 1.43 — not the body's 1.5. */
.set-hint { margin: 0 0 12px; font-size: 0.8rem; line-height: 1.43; color: var(--text-secondary); }

/* No `sx={{ mr: 0 }}` here, so FormControlLabel keeps its 16px right margin. */
.set-switch-label { display: flex; margin-bottom: 8px; margin-left: -11px; margin-right: 16px; }
.set-switch-text { font-size: 0.85rem; }

.set-fields { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.tf-set-number { width: 160px; }
.tf-set-number .tf-input { padding: 6px 14px; }
.tf-set-number .tf-label { line-height: 1.4375; }
/* Open only when there is a label to make room for. */
.tf-set-number .tf-outline legend:not(.legend-empty) { max-width: 100%; }

/* Four fields in one half-panel: at the fixed 160px the fourth wrapped onto a
   line of its own. A grid instead, its tracks sharing whatever the panel has.
   105px is what the widest shrunk label needs -- "Failures per address", 82px
   at 9px, plus the outline's 14px offset and some air -- so a narrower panel
   drops a column rather than clipping the labels, and the columns keep one
   width whatever their count. No breakpoint to guess at. */
.set-fields-four {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(105px, 1fr));
  gap: 12px;
}
.set-fields-four .tf-set-number { width: auto; }
/* The spinner arrows need their own room once the box is this narrow. */
.set-fields-four .tf-input { padding-left: 10px; padding-right: 6px; }

.set-alert { margin-top: 12px; font-size: 0.85rem; }

/* --- Backups ------------------------------------------------------------- */

.set-title-spaced { margin-top: 24px; }

.set-backup-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  border-top: 1px solid var(--border-cell);
}
.set-backup-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-cell);
  font-size: 0.8rem;
}
.set-backup-name { color: var(--primary-main); text-decoration: none; }
.set-backup-name:hover { text-decoration: underline; }
.set-backup-meta { color: var(--text-secondary); white-space: nowrap; }

/* The rest of the list, folded. Same trick as the timesheet's panels: a grid
   row that goes from `0fr` to `1fr`, which is the only way to animate to a
   height nobody knows in advance. */
.set-backup-toggle {
  margin-top: 8px;
  padding: 2px 8px;
  font-size: 0.75rem;
  gap: 2px;
}
.set-backup-chevron {
  font-size: 1.25rem;
  transition: transform 200ms var(--ease-in-out);
}
.set-backup-toggle[aria-expanded="true"] .set-backup-chevron { transform: rotate(180deg); }

.set-backup-rest {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms var(--ease-in-out);
}
.set-backup-rest > * { overflow: hidden; min-height: 0; }
.set-backup-rest.is-open { grid-template-rows: 1fr; }
.set-backup-list-rest { border-top: 0; margin-top: 0; }

@media (prefers-reduced-motion: reduce) {
  .set-backup-rest { transition-duration: 1ms; }
  .set-backup-chevron { transition-duration: 1ms; }
}

.set-restore { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.set-restore-file { font-size: 0.8rem; color: var(--text-secondary); max-width: 100%; }
.set-restore-file::file-selector-button {
  margin-right: 12px;
  padding: 5px 15px;
  border: 1px solid var(--border-button);
  border-radius: 4px;
  background: transparent;
  color: var(--primary-main);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
}
.set-restore-file::file-selector-button:hover {
  background-color: var(--action-hover);
}
.set-version {
  display: block;
  text-align: right;
  margin: 8px 0 0;
  font-size: 0.7rem;
  line-height: 1.66;             /* caption */
  color: var(--text-secondary);
}

/* --- Logs ---------------------------------------------------------------- */

/* Tabs and controls share one row, clear of the box below: the row is a set
   of controls, not the box's own header. */
.log-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 10px;
}
/* Nothing to lift: the tabs are now the button's own height, so `flex-end`
   lands both on the rule. */
.log-controls { padding-bottom: 0; }
.log-controls form { display: flex; align-items: center; gap: 16px; }
/* The Select shrink-wraps its label, with the FormControl's 160px floor. */
/* The row sits on the box: a rule under it, and the box's own corners
   squared off where the two meet. The labels keep the size they had in the
   Select this replaced — `.tf-input`'s 0.75rem, not a tab's 0.875 — because
   a filename is not a section title. */
.log-tabs { min-height: 0; }
/* Sized on the Pause button at the other end of the row — 31px, the same
   `btn-small` — so the row reads as one thing rather than two of different
   heights. The selected file is a filled block in the colour the underline
   used to be: at this size a 2px line said too little. */
.log-tabs .tab {
  font-size: 0.75rem;
  min-height: 0;
  height: 31px;
  padding: 0 16px;
  border-radius: 4px;
  transition: background-color 300ms var(--ease-in-out),
              color 300ms var(--ease-in-out);
}
.log-tabs .tab-selected { background-color: var(--primary-main); color: #fff; }

@media (max-width: 600px) {
  /* Three filenames and the line count do not share a 358px row: the strip was
     squeezed to 146px against 518px of tabs, and `.tabs`' own `overflow:
     hidden` cut all three off — two of the sources could not be reached at
     all. The row becomes two, and the strip scrolls sideways instead of hiding
     what does not fit. */
  .log-head { flex-direction: column; align-items: stretch; gap: 10px; }
  .log-tabs { overflow-x: auto; }
  /* A tab in a scroller keeps its own width; flex would otherwise shrink the
     three of them back into the space there is. */
  .log-tabs .tab { flex-shrink: 0; }
  /* The two controls take the width the row gained. */
  .log-controls form { justify-content: space-between; }
}

/* Pause / Reprendre: the label that is not showing still takes its width, so
   the button keeps one size and the row does not shuffle when it flips. */
.log-pause-label { display: grid; justify-items: center; }
.log-pause-label > * { grid-area: 1 / 1; }
.log-pause-ghost { visibility: hidden; }

/* A destructive confirmation is not the app's primary blue. */
.btn-error-filled { background-color: var(--error-main); color: #fff; }
.btn-error-filled:hover { background-color: var(--error-dark); }
.log-tabs .tab:hover:not(.tab-selected) { background-color: var(--gray-800); }

@media (prefers-reduced-motion: reduce) {
  .log-tabs .tab { transition-duration: 1ms; }
}
.log-count { width: 100px; }
/* The value keeps the InputBase root's 23px line-height even though `fieldSx`
   drops its font to 0.75rem, which is what makes a Select 35px tall where a
   TextField is 29.25. */
.select-icon {
  position: absolute;
  right: 7px;
  top: calc(50% - 0.5em);
  font-size: 1.5rem;
  fill: var(--action-active);
  pointer-events: none;
}
.log-box {
  background-color: #1e1e1e;
  color: #d4d4d4;
  font-family: monospace;
  font-size: 0.75rem;
  padding: 12px;
  border-radius: 4px;
  height: 65vh;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* --- Login (Login.tsx) --------------------------------------------------- */

/* Container maxWidth="xs". */
.container-xs { max-width: 444px; }

.typo-h5 { margin: 0; font-size: 1.5rem; line-height: 1.334; font-weight: 400; letter-spacing: normal; }

.login-box { margin-top: 64px; display: flex; flex-direction: column; align-items: center; }
.login-paper {
  padding: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  box-shadow: var(--elev3);
}
.login-form { margin-top: 8px; width: 100%; }
.login-alert { margin-bottom: 16px; }
/* margin="normal" on a TextField: 16px above, 8px below. */
.login-field { margin-top: 16px; margin-bottom: 8px; width: 100%; }
.login-submit { margin-top: 24px; margin-bottom: 16px; width: 100%; }

/* The default (medium) density, which only these two screens use: a 56px-high
   field with 1rem text, against the 0.75rem/4px fields everywhere else. */
.tf-normal .tf-input { font-size: 1rem; padding: 16.5px 14px; }
.tf-normal .tf-label { font-size: 1rem; transform: translate(14px, 16px) scale(1); }
.tf-normal .tf-input:focus ~ .tf-label,
.tf-normal .tf-input:not(:placeholder-shown) ~ .tf-label {
  transform: translate(14px, -9px) scale(0.75);
}

/* --- Change password (ChangePassword.tsx) -------------------------------- */

/* The Dialog carries neither `fullWidth` nor a `maxWidth`, yet its paper fills
   the 600px `sm` cap all the same — the three `fullWidth` fields inside it are
   what stretch it, where the delete dialog (no such field) shrink-wraps. The
   fields keep MUI's own density, not the compact one the other dialogs ask
   for. */
.pwd-paper { width: 100%; }
.pwd-paper .tf-input { font-size: 1rem; padding: 16.5px 14px; }
.pwd-paper .tf-label { font-size: 1rem; transform: translate(14px, 16px) scale(1); }
.pwd-paper .tf-input:focus ~ .tf-label,
.pwd-paper .tf-input:not(:placeholder-shown) ~ .tf-label {
  transform: translate(14px, -9px) scale(0.75);
}
/* margin="dense": 8px above, 4px below. The form is a flex column so those do
   not collapse into each other — MUI's FormControls are inline-level, which
   keeps all twelve pixels between two fields. */
.pwd-form { display: flex; flex-direction: column; }
.pwd-field { display: flex; width: 100%; margin-top: 8px; margin-bottom: 4px; }
.pwd-alert { margin-bottom: 16px; }

/* --- AppBar right side (App.tsx) ----------------------------------------- */

.toolbar-right { display: flex; align-items: center; margin-left: auto; }
/* Default-size IconButtons: 8px around a 24px icon. */
.toolbar-right .icon-btn { padding: 8px; }
.toolbar-right .mui-icon { font-size: 1.5rem; }
.user-button { padding: 6px 8px; min-width: 64px; }
/* The settings routes light their own nav button. */
.nav-current { background-color: rgba(255, 255, 255, 0.12); }

/* MuiBadge: the bubble is anchored to the icon's top-right corner and hides
   itself at zero, which is why nothing is drawn when the counts are empty. */
.badge { position: relative; display: inline-flex; vertical-align: middle; flex-shrink: 0; }
/* The bug and the bell, the only two badged icons. Font Awesome fills its
   viewBox edge to edge where the Material set kept a margin inside the same
   24 units, so at the AppBar's 24px they came out a fifth larger than the
   icons they replaced. Measured: the Material pair inked 16x18 and 20x20,
   this brings them back to it. */
.appbar .badge .mui-icon { font-size: 1.25rem; }

/* The bell was `CircleNotifications`: a disc with the bell knocked out of it.
   Font Awesome Free has no bell in a circle, so the disc is drawn here — in
   `currentColor`, the white the bar's own text is — and the bell is filled
   with what the bar is painted, which is what makes it read as a hole rather
   than as a second shape. 12px of bell inside 4px of padding is the 20px disc
   the Material one inked. */
.appbar .badge .appbar-bell {
  font-size: 0.75rem;
  padding: 4px;
  box-sizing: content-box;
  background-color: currentColor;
  border-radius: 50%;
  fill: var(--bar-bg);
}
.badge-dot {
  position: absolute;
  top: 0;
  right: 0;
  transform: scale(1) translate(50%, -50%);
  transform-origin: 100% 0%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1;
  background-color: var(--error-main);
  color: #fff;
  z-index: 1;
}

/* --- The user menu ------------------------------------------------------- */

/* `slotProps.paper.sx = { width: 200 }` — and none of the 400px floor the
   calendar's context menu sets on the same class. */
.user-menu { width: 200px; min-width: 0; }
/* A divider between two MenuItems gets 8px of air; the one under the
   notifications header, which follows a plain Box, gets none — as does the
   calendar's, which is why the base rule cannot serve all three. */
.user-menu .divider { margin: 8px 0; }
.notif-menu .divider { margin: 0; }
/* A menu item that is a link must not look like one. */
.menu-item { color: inherit; text-decoration: none; }
/* Two MenuItems that are really little forms: a flex column with a 4px gap. */
.menu-item-stack { display: flex; flex-direction: column; gap: 4px; }
.menu-item-stretch { align-items: stretch; }
.user-menu-group { width: 100%; justify-content: center; }
.user-menu-btn { width: 50%; font-size: 0.7rem; }
.user-menu-caption { margin: 0; font-size: 0.7rem; color: var(--text-secondary); }
.user-menu-select { width: 100%; font-size: 0.7rem; }
.user-menu-select .select-value {
  width: 100%;
  font-size: 0.7rem;             /* sx on the Select, not the 0.75rem default */
  height: auto;
  min-height: 1.4375em;
  line-height: 1.4375;
  padding: 8.5px 32px 8.5px 14px;
  cursor: pointer;
}

/* --- The notifications menu ---------------------------------------------- */

.notif-menu { width: 408px; max-height: 420px; }
.notif-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 16px;
  gap: 8px;
}
.notif-header-left, .notif-header-right { display: flex; align-items: center; gap: 4px; }
.notif-title { margin: 0; font-weight: 600; font-size: 0.85rem; }
.menu-item-disabled { opacity: 0.38; pointer-events: none; }
.notif-item { white-space: normal; align-items: flex-start; gap: 8px; }
.notif-unread { background-color: var(--action-hover); }
.notif-body { flex: 1; min-width: 0; }
.notif-message { margin: 0; font-size: 0.75rem; }
.notif-time { margin: 0; font-size: 0.65rem; color: var(--text-secondary); }
.notif-close { padding: 2px; }
.notif-close .mui-icon { font-size: 16px; }
.icon-btn-error { color: var(--error-main); }

/* --- Team timesheets (TimesheetTeamView.tsx) ----------------------------- */

.ts-team-picker { min-width: 280px; width: auto; margin-bottom: 16px; }
.ts-team-picker .select-value {
  width: 100%;
  height: auto;
  min-height: 1.4375em;
  line-height: 23px;
  padding: 6px 32px 6px 14px;
  white-space: nowrap;
}

/* --- Problem reports (IssueReportButton.tsx) ----------------------------- */

/* Tabs inside a dialog: 36px tall against the settings bar's 42, and padded
   16px on the sides by the sx rather than by the tabs themselves. */
.issue-tabs { min-height: 36px; padding: 0 16px; }
/* MUI's Tab uppercases its label; the settings bar is the one that asks for
   `textTransform: none`, not this. */
.issue-tab { min-height: 36px; font-size: 0.8rem; text-transform: uppercase; }
/* A plain Divider has no margin; the 4px in the base rule is the calendar
   menu's own. */
.issue-tabs + .divider { margin: 0; }

.issue-content { padding-top: 16px; }
.issue-form { display: flex; flex-direction: column; gap: 12px; }

.tf-issue { width: 100%; }
.tf-issue .tf-input { font-size: 0.75rem; padding: 6px 14px; }
.tf-issue .tf-label { font-size: 0.75rem; }
.tf-issue .tf-outline legend { max-width: 100%; }
/* A multiline field carries its block padding on the root, not the input. */
.tf-issue-multiline { padding: 8.5px 0; }
.tf-issue-multiline .tf-input { height: auto; resize: none; }

.typo-caption { font-size: 0.75rem; line-height: 1.66; letter-spacing: normal; }
.typo-body2 { font-size: 0.875rem; line-height: 1.43; letter-spacing: normal; }
.issue-hint { margin: 0; color: var(--text-secondary); }
.issue-attach { align-self: flex-start; }
.issue-alert { font-size: 0.8rem; }
.issue-empty { margin: 0; color: var(--text-secondary); }

/* Pending attachments: 48px thumbs with the remove button hanging off the
   corner, over the paper so it reads as detached. */
/* Empty, they must not exist: as flex children they would each add the
   column's 12px gap, which React does not render at all. */
#issue-thumbs:empty, #issue-file-error:empty { display: none; }
.issue-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.issue-pending { position: relative; }
.issue-pending-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border: 1px solid var(--border-cell);
  border-radius: 4px;
}
.issue-pending-file { display: flex; align-items: center; justify-content: center; }
.issue-pending-remove {
  position: absolute;
  top: -10px;
  right: -10px;
  padding: 2px;
  background-color: var(--bg-paper);
  border: 1px solid var(--border-cell);
}
.issue-pending-remove .mui-icon { font-size: 14px; }

/* One report. */
.issue-card {
  border: 1px solid var(--border-cell);
  border-radius: 4px;
  padding: 12px;
}
.issue-card + .issue-card, .issue-empty + .issue-card { margin-top: 12px; }
.issue-card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.issue-card-text { min-width: 0; }
.issue-reporter { margin: 0; display: block; color: var(--text-secondary); }
.issue-time { color: var(--text-secondary); }
.issue-description { margin: 0; white-space: pre-wrap; word-break: break-word; }
.issue-attachments { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.issue-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border: 1px solid var(--border-cell);
  border-radius: 4px;
}
.issue-thumb-file { display: flex; align-items: center; justify-content: center; color: inherit; }
.issue-card-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
.issue-fixed { margin-left: -11px; }
.issue-fixed-label { font-size: 0.8rem; }
/* A `color="success"` Chip is filled, with white text from the sx. */
.chip-success { background-color: var(--success-main); color: #fff; }

/* --- Writing a notification ---------------------------------------------- */

.icon-btn-small { padding: 3px; }
.icon-btn-small .mui-icon { font-size: 1.25rem; }

/* The `title + content` rule zeroes the top padding and outranks the sx's
   `pt: 3`; the 12px of air comes from the Autocomplete's own margin. */
.compose-form { display: flex; flex-direction: column; gap: 16px; }
.compose-teams { margin-top: 12px; }          /* sx mt: 1.5 */
/* Not a freeSolo Autocomplete, so it keeps MUI's popup indicator. */
.ac-arrow {
  position: absolute;
  right: 9px;
  top: calc(50% - 14px);
  padding: 2px;
  margin-right: -2px;
  color: var(--action-active);
}
.ac-arrow .mui-icon { font-size: 1.5rem; }
.tf-compose-message { width: 100%; padding: 8.5px 0; }
.tf-compose-message .tf-input { height: auto; resize: none; font-size: 0.8rem; padding: 6px 14px; }
.tf-compose-message .tf-label { font-size: 0.75rem; }
.tf-compose-message .tf-outline legend { max-width: 100%; }

/* --- Mobile shell (App.tsx's isMobile branch) ---------------------------- */

/* The breakpoint is MUI's `sm`: 600px, the one `useIsMobile` uses. */
/* `edge="start"` pulls the button back into the toolbar's own 16px padding. */
.drawer-button { display: none; margin: 0 8px 0 -12px; padding: 8px; }
.drawer-button .mui-icon { font-size: 1.5rem; }

@media (max-width: 599.95px) {
  .drawer-button { display: inline-flex; }
  /* MuiToolbar's own `xs` variant: 56px below the sm breakpoint, not 64. */
  .toolbar { min-height: 56px; }
  .toolbar-nav { display: none; }
  /* The settings tabs move into the drawer. */
  .tabs-bar { display: none; }
}

.drawer-root { position: fixed; inset: 0; z-index: 1200; }
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);
  -webkit-tap-highlight-color: transparent;
}
.drawer-paper {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 240px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  border-radius: 0;
  box-shadow: var(--elev16);
  outline: 0;
  /* Elevation 16, so a heavier overlay than the 5% a plain Paper gets. */
  background-image: var(--drawer-overlay);
}

/* MuiList / MuiListItemButton / MuiListItemText. */
.list { list-style: none; margin: 0; padding: 8px 0; }
.list-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  position: relative;
  box-sizing: border-box;
  width: 100%;
  padding: 8px 16px;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1.5;
  transition: background-color 150ms var(--ease-out);
}
/* ListItemText's own 4px margins are what make a one-line row 48px tall. */
.list-item-text { margin: 4px 0; }
.list-item:hover { background-color: var(--action-hover); }
/* Selected is the primary hue, not a grey — and hovering a selected row adds
   the hover opacity on top: 8+4 in light, 16+8 in dark. */
.list-item-selected { background-color: var(--list-selected); }
.list-item-selected:hover { background-color: var(--list-selected-hover); }
.divider-plain { margin: 0; }

/* --- Colours on a phone -------------------------------------------------- */

.cc-cards { display: none; flex-direction: column; gap: 8px; }
.cc-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--border-table);
  border-radius: 4px;
}
.cc-card-text { flex: 1; min-width: 0; margin: 0; font-size: 0.8rem; line-height: 1.2; }
.cc-card-swatch { width: 40px; height: 28px; flex-shrink: 0; }

@media (max-width: 599.95px) {
  .cc-cards { display: flex; }
  .colors-page .grid-outer { display: none; }
  /* The toolbar stacks, and import/export drops off entirely. */
  .cc-toolbar { flex-direction: column; align-items: flex-start; gap: 8px; }
  /* `ml: isMobile ? 0 : 1` — stacked, the caption has nothing to sit beside. */
  .cc-editing { margin-left: 0; }
  .cc-transfer { display: none; }
}

/* --- Days off on a phone ------------------------------------------------- */

.dayoff-cards { display: none; flex-direction: column; gap: 8px; }
.dayoff-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--border-table);
  border-radius: 4px;
  cursor: pointer;
}
.dayoff-card-date { width: 64px; flex-shrink: 0; font-size: 0.85rem; font-weight: 700; text-transform: capitalize; }
.dayoff-card-letter {
  width: 26px;
  height: 26px;
  border-radius: 4px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
}
.dayoff-card-flag { font-size: 1.5rem; flex-shrink: 0; line-height: 1; }
.dayoff-card-text {
  flex: 1;
  min-width: 0;
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dayoff-add { display: none; }

@media (max-width: 599.95px) {
  .dayoff-cards, .dayoff-add { display: flex; }
  .dayoff-scroll { display: none; }
  /* The year field and the add button sit at either end. */
  .dayoff-toolbar { justify-content: space-between; }
}

/* --- The year summary on a phone ----------------------------------------- */

.ts-recap-mobile { display: none; padding: 12px; margin-bottom: 16px; }
/* Label / value, twice per line, the same shape as the monthly dashboard. */
.ts-recap-mobile-grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto;
  row-gap: 4px;
  column-gap: 8px;
}
.ts-recap-mkey {
  margin: 0;
  align-self: center;
  font-size: 0.75rem;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ts-recap-mval {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ts-recap-mval-tinted { padding: 0 6px; border-radius: 2px; }
.ts-recap-cards { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.ts-recap-card { padding: 12px; }
.ts-recap-card-title { margin: 0 0 8px; font-size: 0.8rem; font-weight: 700; }

@media (max-width: 599.95px) {
  .ts-recap-mobile { display: block; }
  .ts-recap-wide { display: none; }
}

/* --- The month picker the calendar and the timesheet share on a phone ---- */

.month-picker { display: none; align-items: center; gap: 8px; }
/* An IconButton with no colour prop draws in `action.active`; `size="small"`
   pads 5px around the 20px icon, for 30x30. */
.month-picker .icon-btn { color: var(--action-active); padding: 5px; font-size: 18px; }
/* 34.25px, not the 35 of the Logs source select: there `fieldSx` puts the
   0.75rem on the *input*, leaving the root at 16px and a 23px line; here the
   sx sets it on the root, so the line is 1.4375 x 12. */
.picker-field .select-value {
  width: 100%;
  height: auto;
  min-height: 1.4375em;
  line-height: 1.4375em;
  padding: 8.5px 32px 8.5px 14px;
  font-size: 0.75rem;
  cursor: pointer;
}
.picker-month { width: 90px; min-width: 90px; }
.picker-year { width: 80px; min-width: 80px; }

@media (max-width: 599.95px) {
  .month-picker { display: flex; }
  .month-buttons { display: none; }
}

/* --- The calendar on a phone (TeamCalendar's isMobile branch) ------------ */

.cal-mobile { display: none; flex-direction: column; align-items: center; gap: 16px; }
/* The 0.8rem sits on the Select root, so the fieldset and its notch inherit
   it; the floating label keeps MUI's own 1rem. */
.cal-person { min-width: 220px; width: auto; font-size: 0.8rem; }
.cal-person .tf-outline legend:not(.legend-empty) { max-width: 100%; }
/* 0.8rem on the Select root, so the line follows the root's font — see the
   month picker above for the other half of that rule. */
.cal-person .select-value {
  width: 100%;
  height: auto;
  min-height: 1.4375em;
  /* A length, not the unitless 1.4375: Chromium multiplies a unitless factor
     at LayoutUnit precision, and 12.8px x 1.4375 lands a 64th of a pixel
     short of what MUI's `1.4375em` gives. */
  line-height: 1.4375em;
  padding: 8.5px 32px 8.5px 14px;
  font-size: 0.8rem;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.cal-cards { width: 100%; max-width: 420px; display: flex; flex-direction: column; gap: 4px; }
.cal-card {
  display: flex;
  align-items: stretch;
  min-height: 44px;
  border: 1px solid var(--border-table);
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
}
.cal-card.is-locked { opacity: 0.6; cursor: default; }
.cal-card-day {
  width: 40px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  padding: 4px 0;
}
.cal-card-day.is-today { background-color: var(--today); color: #fff; }
/* A day the "today" filter pulled in from the next month is dimmed on its
   own, without dimming the colour beside it. */
.cal-card-day.is-outside { opacity: 0.6; }
.cal-card-day.is-shaded { background-color: var(--gray-800); }
.cal-card-weekday { font-size: 0.7rem; color: var(--text-secondary); }
.cal-card-day.is-today .cal-card-weekday { color: #fff; }
.cal-card-number { font-size: 0.9rem; font-weight: 700; }
/* The status colour spreads over the whole rest of the card; the remote
   marker keeps the literal 6px/11px it has in a 30px cell. */
.cal-card-color {
  flex: 1;
  width: auto;
  min-width: 0;
  max-width: none;
  height: auto;
  border: none;
}

.cal-legend-flat {
  display: none;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

@media (max-width: 599.95px) {
  .cal-mobile { display: flex; }
  .cal-wide, .cal-legend-wide { display: none; }
  .cal-legend-flat { display: grid; }
}

/* --- The timesheet on a phone (TimesheetGrid's isMobile branches) -------- */

/* The dashboard loses its section titles and runs flat, two pairs to a line. */
.ts-dash-mobile { display: none; padding: 8px 12px; margin: 0 0 16px; box-sizing: border-box; }
.ts-dash-flat {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto;
  row-gap: 4px;
  column-gap: 8px;
}
.ts-dash-mkey {
  margin: 0;
  align-self: center;
  font-size: 0.75rem;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ts-dash-mval {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
}
.ts-dash-mval-tinted { padding: 0 6px; border-radius: 2px; }

/* One card per day, the table row stacked. */
.ts-cards {
  display: none;
  flex-direction: column;
  gap: 12px;
  max-width: 480px;
  margin: 0 auto;
}
/* `backgroundColor: rowBg ?? 'transparent'` — an uncoloured card lets the
   page's own background show through, it is not paper white. */
.ts-card {
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background-color: transparent;
}
/* `boxShadow: inset` on a Paper, not a left border: the card keeps its width. */
.ts-card-today { box-shadow: inset 3px 0 0 0 var(--today); }
.ts-card-head { display: flex; justify-content: space-between; align-items: center; }
/* React capitalises the formatted date, which reaches the month too. */
.ts-card-date { margin: 0; font-size: 0.85rem; font-weight: 700; text-transform: capitalize; }
.ts-card-flag { margin: 0; font-size: 0.75rem; font-weight: 600; }
.ts-card-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.ts-card-key { margin: 0; font-size: 0.7rem; color: var(--text-secondary); }
.ts-card-val { margin: 0; font-size: 16px; }
/* 16px, not the table's 0.8rem: under 16 iOS zooms the page on focus. The
   line has to come from the field, not from the page's 24px body line, or
   every value sits half a pixel low. */
.ts-card .ts-input { font-size: 16px; }
.ts-card-report { text-align: center; min-width: 30px; }
.ts-card-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.ts-card-btn { font-size: 0.7rem; }
.ts-card-astreinte { font-size: 0.7rem; color: var(--text-secondary); align-self: center; }

@media (max-width: 599.95px) {
  /* `fit-content` shrink-wraps to the widest child, and below the breakpoint
     that child is the 480px column of day cards — which drags the dashboard
     in with it. React sizes the dashboard from the container and caps only
     the cards, so between 480 and 600 the two disagreed by the whole block.
     Never visible at 390: the container is narrower than the cap there. */
  .ts-sized { width: 100%; }
  .ts-dash-mobile { display: block; }
  .ts-dash-wide, .ts-grid-wide { display: none; }
  .ts-cards { display: flex; }

  /* The annual settings kept their desktop width on a phone: six 158px
     columns and five 40px gaps is 1148px whatever the screen, so the panel
     stood three times wider than the viewport and dragged the page's own
     horizontal scroll with it. The columns share what there is instead, and
     the field stops carrying a width of its own. */
  .ts-settings-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
  .tf-settings { width: auto; }
  /* Four checkboxes across 350px leave 87px each for labels that run to
     "Heure d'astreinte entamée = 1h". One per line.

     `width: auto` matters more than the column count: both of these carry a
     fixed 1148px, and their `max-width: 100%` cannot rein it in — the panel
     around them is sized by its own content, so the percentage resolves
     against the very width they are setting. */
  .ts-settings-bools { grid-template-columns: 1fr; width: auto; }
  .ts-parttime { width: auto; }

  /* The meetings form stacks: the two times centred on their own line, the
     comment full width, the buttons pushed right. Same markup, new axis. */
  .ts-meeting-form { flex-direction: column; align-items: stretch; gap: 8px; }
  .ts-meeting-times { justify-content: center; }
  .ts-meeting-input .tf-input { font-size: 16px; }
  /* The dialog's TimeCells take the same 16px as the cards', for the same
     reason: below it iOS zooms the page on focus. */
  .ts-meeting-form .ts-time { font-size: 16px; }
  .ts-meeting-buttons { justify-content: flex-end; }
}

/* --- Users on a phone (UserManager's isMobile branch) -------------------- */

/* No grid at all here: one person's form, picked from a Select. */
.users-mobile { display: none; padding: 16px; margin: 0 0 16px; box-sizing: border-box; }
.users-picker { width: 100%; margin-bottom: 16px; font-size: 0.9rem; }
.users-picker .tf-outline legend:not(.legend-empty) { max-width: 100%; }
.users-picker .select-value {
  width: 100%;
  height: auto;
  min-height: 1.4375em;
  line-height: 1.4375em;
  padding: 8.5px 32px 8.5px 14px;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.users-mobile-actions { display: flex; justify-content: center; gap: 8px; margin-top: 16px; }

@media (max-width: 599.95px) {
  .users-mobile { display: block; }
  .users-wide { display: none; }
  /* iOS zooms the page on a field under 16px. React writes this as
     `{...fieldSx, ...mobileInputSx}`, and since both objects carry a
     `& .MuiInputBase-input` key the second replaces the first whole — the
     0.75rem goes, and so does the 4px padding fieldSx had set with it. What
     comes back is MUI's own small padding. */
  .users-mobile .tf-input { font-size: 16px; padding: 8.5px 14px; }
  /* An Autocomplete's input is padded by the Autocomplete, not by the field:
     the box around it is what carries the 6px. */
  .users-mobile .ac-input { padding: 2.5px 4px 2.5px 8px; }
  .users-mobile .fcl-label { font-size: 0.95rem; }
  .users-mobile .form-grid { grid-template-columns: 1fr; }
  /* The picker is a Select, not one of the form's TextFields: it keeps the
     0.9rem its own sx gives it, and MUI's 1rem InputLabel. */
  .users-mobile .users-picker .select-value { font-size: 0.9rem; padding: 8.5px 32px 8.5px 14px; }
  .users-mobile .users-picker .tf-label { font-size: 1rem; line-height: 1.4375em; }

  /* `sx={{ py: 0, px: 0.5 }}` on the Checkbox, and a 19px icon. */
  .users-mobile .cb { padding: 0 4px; }
  .users-mobile .cb-icon { width: 19px; height: 19px; }

  /* The three flags stack, and Full remote loses the -11px pull that lines a
     Checkbox up with the text above it. */
  .users-mobile .form-flags { flex-direction: column; gap: 0; align-items: flex-start; }
  .users-mobile .form-grid-centered .form-control-label { margin-left: 0; }

  /* The date field is 40px like every other one here, not the 32 the desktop
     dialog packs it into. */
  .users-mobile .tf-datepicker { height: auto; }
  .users-mobile .tf-datepicker .tf-input { padding-right: 0; }
}

/* --- Select: the Menu it opens, in place of a native listbox ------------- */

/* The paper hangs from the field's bottom edge and takes its width, which is
   what MUI's Select anchors it to; `app.js` measures and places it. */
.select-field { position: relative; }
.select-paper { min-width: 0; position: absolute; }
/* A <ul> brings a 1em block margin and a 40px inline padding of its own. */
.select-list { margin: 0; padding: 8px 0; list-style: none; }
/* Each Select's items carry the font its own sx gives them, and the item's
   height follows: 0.75rem is 30px tall, 0.8rem is 31.2, 0.9rem is 33. */
/* A menu entry is a ButtonBase too, and rippling needs it positioned. */
.select-item { line-height: 1.5; position: relative; }
.cal-person .select-item { font-size: 0.8rem; }
.users-picker .select-item { font-size: 0.9rem; }
/* Every entry but the first carries the 0.9rem its own sx gives it; "add a
   user" is written without one, so it keeps the theme's 0.75rem. */
.users-picker .select-item:first-child { font-size: 0.75rem; }
.user-menu-select .select-item { font-size: 0.7rem; }
/* MUI turns the Select's arrow over while its menu is open. */
.select-field.is-open .select-icon { transform: rotate(180deg); }
/* The five states of a `MenuItem`, in MUI's own order and with MUI's own
   specificities — the ties are what decide the overlaps, so the class-count
   of each selector matters as much as its colour:

     hover                 action.hover
     selected              primary  8%
     selected + focus      primary 20%   (8 + 12, the opacities add)
     selected + hover      primary 12%   (8 + 4) — and it wins over the line
                                         above, which is why it comes after
     focus                 action.focus  — and this one wins over plain hover

   MUI opens the menu with the selected item focused, which is why the server
   renders both classes on it; the keyboard moves the second one, `app.js`. */
.select-item-selected {
  background-color: color-mix(in srgb, var(--primary-main) 8%, transparent);
}
.select-item.select-item-selected.select-item-focus {
  background-color: color-mix(in srgb, var(--primary-main) 20%, transparent);
}
.select-item.select-item-selected:hover {
  background-color: color-mix(in srgb, var(--primary-main) 12%, transparent);
}
.select-item.select-item-focus { background-color: var(--action-focus); }
/* A finger cannot hover, and MUI resets both hovers where it cannot. */
@media (hover: none) {
  .select-item:hover { background-color: transparent; }
  .select-item.select-item-selected:hover {
    background-color: color-mix(in srgb, var(--primary-main) 8%, transparent);
  }
}
/* Focusing an empty Select floats its label, so the notch has to open under
   it — even though nothing is chosen yet. A Select with no label at all has
   nothing to make room for, and keeps its outline unbroken. */
.select-field:has(.tf-label) .select-value:focus ~ .tf-outline legend.legend-empty {
  max-width: 100%;
}

/* --- Autocomplete: MUI's own suggestion paper ---------------------------- */

/* A Popper, not a Popover: no backdrop, elevation 1 rather than 8, and the
   list caps at 400px instead of the viewport. */
/* A fixed box of no size, not a full-screen sheet: MUI portals its popper to
   the body and paints nothing behind it, and an overlay the size of the
   viewport is one more composited layer over everything under it — which
   changes how what is under it is rasterised. */
.ac-menu { position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 1300; }
/* Everything else is `.paper`'s own: elevation 1, and in dark mode the 5%
   overlay that goes with it — a Menu's paper is the one MUI opts out of. */
/* The popper places the popup and owns the composited layer; the paper only
   paints. A layer that also carries the shadow and the radius does not
   rasterise what is under it the same way — see the README's finding 92. */
.ac-popper {
  position: absolute;
  inset: 0 auto auto 0;
  pointer-events: auto;
  will-change: transform;
}
.ac-paper { width: 100%; }
/* The app's theme puts 0.75rem on `MuiAutocomplete`'s listbox, so the paper
   itself is compact and not only the options inside it. */
.ac-list {
  margin: 0;
  padding: 8px 0;
  max-height: 400px;
  overflow-y: auto;
  list-style: none;
  font-size: 0.75rem;
  line-height: 1.5;
}
.ac-option {
  padding: 6px 16px;
  font-size: 0.75rem;
  line-height: 1.5;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.ac-option:hover { background-color: var(--action-hover); }
/* The list keeps what is already chosen, and opens on the first of them —
   selected *and* focused, which is the selected and hover opacities added:
   12% of the primary in light, 24% in dark. */
.ac-option-focused { background-color: var(--list-selected-hover); }
.ac-option[hidden] { display: none; }

/* The input sits inside the chip row, so the outline and the label answer to
   the row rather than to a sibling input. */
.ac-field:focus-within ~ .tf-outline {
  border-width: 2px;
  border-color: var(--primary-main);
}
.ac-field:focus-within ~ .tf-label { color: var(--primary-main); }

/* --- DatePicker: the calendar it opens ----------------------------------- */

.dp-menu { position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 1300; }
.dp-open .tf-outline { border-width: 2px; border-color: var(--primary-main); }
.dp-open .tf-label { color: var(--primary-main); }
.dp-popper { position: absolute; inset: 0 auto auto 0; pointer-events: auto; will-change: transform; }
.dp-paper {
  width: 320px;
  box-shadow: var(--elev8);
  background-color: var(--bg-paper);
  background-image: var(--paper-overlay-8);
}
.dp-calendar { height: 336px; overflow: hidden; }

/* Header: the month label on the left with its view switch, the two arrows
   pulled against the paper's right padding. */
.dp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 40px;
  margin: 12px 0 4px;
  padding: 0 12px 0 24px;
}
.dp-label-row { display: flex; align-items: center; overflow: hidden; }
.dp-label { font-size: 1rem; line-height: 1.5; font-weight: 500; margin-right: 6px; }
/* 34px: the default 24px icon in a 5px-padded button, whatever the 18px
   font-size on the button itself suggests. */
.dp-switch { padding: 5px; color: var(--action-active); }
.dp-switch .mui-icon { width: 24px; height: 24px; }
/* The switcher puts a 24px spacer between the arrows, which their -12px
   margins eat back into: 40 - 12 + 24 - 12 + 40 = 80. */
.dp-arrows { display: flex; }
.dp-spacer { width: 24px; }
.dp-arrow { padding: 8px; color: var(--action-active); }
.dp-arrow .mui-icon { width: 24px; height: 24px; }
.dp-prev { margin-right: -12px; }
.dp-next { margin-left: -12px; }

/* The year view: four to a row, scrolling, the whole 280px below the header. */
.dp-years {
  display: flex;
  flex-wrap: wrap;
  height: 280px;
  padding: 0 4px;
  overflow-y: auto;
}
.dp-year-cell { flex-basis: 25%; display: flex; align-items: center; justify-content: center; }
.dp-year {
  height: 36px;
  margin: 6px 0;
  padding: 1px 6px;
  min-width: 72px;
  border: 0;
  border-radius: 18px;
  background: none;
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--text-primary);
  cursor: pointer;
}
.dp-year:hover { background-color: var(--action-hover); }
.dp-year-selected, .dp-year-selected:hover {
  background-color: var(--primary-dark);
  color: #fff;
}
/* Open, the arrow turns over — and nothing else: the button carries no
   resting background once the pointer has left it. */
.dp-switch-open .mui-icon { transform: rotate(180deg); }

.dp-weekdays { display: flex; justify-content: center; align-items: center; height: 40px; }
.dp-weekday {
  width: 36px;
  margin: 0 2px;
  text-align: center;
  font-size: 0.75rem;
  line-height: 1.66;
  color: var(--text-secondary);
}

/* 240px whatever the month needs: MUI sizes the slide area for six weeks. */
/* `PickersSlideTransition` declares `overflow-x: hidden` alone, which makes
   the block axis compute to `auto` — a scrollable box, and Chromium
   rasterises what is inside one on its own layer. Finding 83 again, on the
   day circles this time: `overflow: hidden` on both axes is not the same
   paint. Its children hang from the top of it, absolutely positioned. */
.dp-slide {
  display: block;
  position: relative;
  min-height: 240px;
  height: 240px;
  overflow-x: hidden;
}
/* A block, not a flex column: the weeks' 2px margins have to collapse into
   one, or every row after the first drifts two pixels further down. */
.dp-month {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  overflow: hidden;
}
.dp-week { display: flex; justify-content: center; margin: 2px 0; }
/* A day is a `ButtonBase`, and its reset is not decoration: a `<button>` left
   at `appearance: auto` is painted by Chromium's form-control path, which
   rounds the edge of its own background differently. */
.dp-day {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  user-select: none;
  vertical-align: middle;
  text-decoration: none;
  width: 36px;
  height: 36px;
  margin: 0 2px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  font-family: inherit;
  font-size: 0.75rem;
  line-height: 1.66;
  color: var(--text-primary);
  cursor: pointer;
  outline: 0;
  transition: background-color 250ms var(--ease-in-out);
}
.dp-day-blank { cursor: default; }
/* `PickersDay` tints with the primary, not with the neutral `action.hover`
   the rest of the app uses — and only where there is a pointer to hover with. */
@media (pointer: fine) {
  .dp-day:hover:not(.dp-day-blank) {
    background-color: color-mix(in srgb, var(--primary-main) var(--hover-opacity), transparent);
  }
}
/* `&:focus`, and plain `:focus` at that: a day the keyboard is on is tinted
   whether or not the browser would have drawn a ring, and the ring itself is
   gone — `ButtonBase` sets `outline: 0`. */
.dp-day:focus:not(.dp-day-blank) {
  background-color: color-mix(in srgb, var(--primary-main) 12%, transparent);
}
/* Today is a ring; the day picked is filled — and MUI opens with it focused,
   which on a contained button is the darker primary. */
/* MUI writes this ring as `&:not(.selected)`, and the exception is not
   cosmetic: a border under a fill of the same shape is invisible, but it is
   still an edge, and it antialiases the circle differently. */
.dp-day-today:not(.dp-day-selected) { border: 1px solid var(--text-secondary); }
.dp-day-selected {
  background-color: var(--primary-main);
  color: #fff;
  font-weight: 500;
}
/* Selected *and* focused or hovered is `primary.dark`, and MUI declares
   `will-change` on both — which is what puts the circle on a layer of its
   own, and rasterises its edge differently. The calendar focuses its landing
   day the moment it opens, so this is the state the popup is normally seen
   in. */
.dp-day:focus.dp-day-selected,
.dp-day:hover.dp-day-selected {
  background-color: var(--primary-dark);
  will-change: background-color;
}
