/* ProjectManager interface styles.
   Single stylesheet, no build step and no CDN request: the two typefaces are
   served from static/fonts, so the interface looks the same offline.
   Layout is fluid: tables scroll horizontally inside their wrapper and the
   navigation collapses behind a toggle on narrow screens.

   The palette and typography follow the Urban Water Management and
   Hydroinformatics Group site (uwmh.eu): sand #b09980 as the brand accent,
   navy #20295a, cyan #0e9cd1, warm grey #ecece7, with Dosis for headings and
   navigation and Source Sans for body text. Two deliberate departures, both
   for legibility at application densities: text is set in navy rather than
   sand, which fails contrast against white; and the cyan used for interactive
   text is darkened to #0a6f95 to clear WCAG AA. The undarkened cyan is kept
   for fills and rules where nothing has to be read off it. */

@font-face {
  font-family: "Dosis";
  src: url("../fonts/dosis-latin.87bc734caa77.woff2") format("woff2");
  font-weight: 200 800;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("../fonts/source-sans-3-latin.30164609c163.woff2") format("woff2");
  font-weight: 200 900;
  font-display: swap;
}

:root {
  --sand: #b09980;
  --sand-dark: #96805f;
  --sand-pale: #f3efe9;
  --navy: #20295a;
  --navy-dark: #161d40;
  --cyan: #0e9cd1;
  --cyan-text: #0a6f95;
  --mist: #ecece7;

  --bg: #f5f5f5;
  --surface: #ffffff;
  --border: #ddd9d2;
  --text: #21232b;
  --muted: #6a6459;
  --accent: var(--navy);
  --accent-dark: var(--navy-dark);
  --success: #1f7a4d;
  --warning: #9a6608;
  --danger: #a52a2a;
  --info: var(--cyan-text);
  --heading-font: "Dosis", "Trebuchet MS", system-ui, sans-serif;
  --body-font: "Source Sans 3", "Source Sans Pro", system-ui, -apple-system,
    "Segoe UI", Roboto, Arial, sans-serif;
  --radius: 6px;
  --gap: 1rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--body-font);
  font-size: 16px;
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
}

.container { width: min(1400px, 100% - 2rem); margin-inline: auto; }

a { color: var(--accent); }
a:hover { color: var(--cyan-text); }

h1, h2, h3, h4, .brand, .site-nav a, .stat__value, .button {
  font-family: var(--heading-font);
}
h1, h2, h3, h4 { color: var(--navy); letter-spacing: .01em; }
h1 { font-size: 1.75rem; font-weight: 700; margin: 0 0 .25rem; }
h2 { font-size: 1.3rem; font-weight: 600; margin: 0 0 .75rem; }
h3 { font-size: 1.1rem; font-weight: 600; margin: 1rem 0 .5rem; }

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

.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 100;
  background: var(--accent); color: #fff; padding: .5rem 1rem;
}
.skip-link:focus { left: 0; }

:focus-visible { outline: 3px solid var(--cyan); outline-offset: 2px; }

/* Keeps the sand footer band at the bottom of a short page. */
body { display: flex; flex-direction: column; min-height: 100vh; }

/* ----------------------------------------------------- impersonation bar

   Loud on purpose. The failure mode of "view as another user" is forgetting
   you are in it and acting under someone else's name, so the banner stays on
   every page and the whole viewport is outlined while it is active. */
.impersonation-bar {
  background: var(--danger); color: #fff; font-size: .9rem;
  padding: .5rem 0; position: sticky; top: 0; z-index: 50;
}
.impersonation-bar__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap); flex-wrap: wrap;
}
.impersonation-bar .button { padding: .2rem .7rem; font-size: .85rem; }
body.is-impersonating { box-shadow: inset 0 0 0 3px var(--danger); }

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

.site-header {
  background: var(--surface);
  border-bottom: 3px solid var(--sand);
  box-shadow: 0 1px 3px rgba(33, 35, 43, .07);
}
.site-header__inner {
  display: flex; align-items: center; gap: var(--gap);
  flex-wrap: wrap; padding: .55rem 0;
}
.brand {
  display: flex; align-items: center; gap: .7rem;
  text-decoration: none; color: var(--navy); flex: none;
}
.brand__logo { display: block; width: 172px; height: auto; }
.brand__name {
  font-size: 1.05rem; font-weight: 700; line-height: 1.1;
  text-transform: uppercase; letter-spacing: .06em;
  padding-left: .7rem; border-left: 2px solid var(--sand);
}
.site-nav { display: flex; gap: .85rem; flex: 1 1 auto; flex-wrap: wrap; justify-content: flex-end; }
.site-nav a {
  text-decoration: none; padding: .3rem 0; color: var(--navy);
  font-weight: 600; font-size: .98rem; border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--sand-dark); border-bottom-color: var(--sand); }
.site-header__user {
  display: flex; align-items: center; gap: .5rem;
  flex: none; padding-left: .9rem; border-left: 1px solid var(--border);
}
.nav-toggle {
  display: none; background: none; border: 1px solid var(--border);
  border-radius: var(--radius); font-size: 1.1rem; padding: .25rem .6rem;
  cursor: pointer; color: var(--navy);
}
.nav-toggle:hover { border-color: var(--sand); color: var(--sand-dark); }

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

   A <details> disclosure: the browser handles opening, closing, keyboard
   operation and the state it announces, so the menu works without JavaScript.
   The default triangle marker is removed in favour of the caret in the markup,
   which can then be rotated to show the open state. */
.user-menu { position: relative; }
.user-menu__trigger {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .3rem .6rem; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface);
  color: var(--navy); font-family: var(--heading-font); font-weight: 600;
  cursor: pointer; list-style: none; white-space: nowrap;
}
.user-menu__trigger::-webkit-details-marker { display: none; }
.user-menu__trigger::marker { content: ""; }
.user-menu__trigger:hover { border-color: var(--sand); background: var(--sand-pale); }
.user-menu[open] .user-menu__trigger { border-color: var(--sand); background: var(--sand-pale); }
.user-menu__caret { font-size: .7rem; transition: transform .12s ease; }
.user-menu[open] .user-menu__caret { transform: rotate(180deg); }

.user-menu__panel {
  position: absolute; right: 0; top: calc(100% + .35rem); z-index: 60;
  min-width: 13rem; padding: .3rem;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 6px 18px rgba(33, 35, 43, .16);
}
.user-menu__meta {
  margin: 0; padding: .35rem .6rem .5rem;
  border-bottom: 1px solid var(--border);
  font-size: .8rem; color: var(--muted); white-space: nowrap;
}
.user-menu__panel a,
.user-menu__signout {
  display: block; width: 100%; text-align: left;
  padding: .45rem .6rem; border: 0; border-radius: 3px;
  background: none; color: var(--navy);
  font: inherit; font-size: .92rem; text-decoration: none; cursor: pointer;
}
.user-menu__panel a:hover,
.user-menu__signout:hover { background: var(--sand-pale); color: var(--navy); }
.user-menu__signout { border-top: 1px solid var(--border); margin-top: .3rem; }

/* The panel would otherwise hang off the right edge of a narrow screen. */
@media (max-width: 1100px) {
  .user-menu__panel { right: auto; left: 0; }
}

@media (max-width: 1100px) {
  .nav-toggle { display: block; order: 2; margin-left: auto; }
  .site-nav {
    display: none; flex-basis: 100%; order: 4;
    flex-direction: column; gap: .2rem; justify-content: flex-start;
  }
  .site-nav.is-open { display: flex; }
  .site-header__user { order: 3; padding-left: 0; border-left: 0; }
}

.breadcrumbs { padding-top: .75rem; font-size: .875rem; }
.breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: .4rem; margin: 0; padding: 0; }
.breadcrumbs li + li::before { content: "/"; margin-right: .4rem; color: var(--muted); }

main { padding: 1.25rem 0 3rem; }

/* The sand band mirrors the footer of uwmh.eu. */
.site-footer {
  background: var(--sand); color: #2b2419;
  font-size: .85rem; padding: 1.15rem 0; margin-top: auto;
}
.site-footer a { color: #2b2419; }
.site-footer p { margin: .15rem 0; }
.site-footer__brand { font-family: var(--heading-font); font-weight: 700; letter-spacing: .04em; }

/* ------------------------------------------------------------ messages */

.messages { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: .5rem; }
.message { padding: .65rem .9rem; border-radius: var(--radius); border: 1px solid; }
.message--success, .message--info { background: #e4f3fa; border-color: #a5d6ea; }
.message--warning { background: #fdf5e5; border-color: #e6cf9a; }
.message--error, .message--danger { background: #fbeded; border-color: #e3b3b3; }

/* -------------------------------------------------------------- layout */

.page-header {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: var(--gap); flex-wrap: wrap; margin-bottom: 1rem;
}
.actions { display: flex; gap: .5rem; flex-wrap: wrap; }

.panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1rem; margin-bottom: 1rem;
}
.panel--accent { border-left: 4px solid var(--sand); }
.panel--danger { border-left: 4px solid var(--danger); }
.panel-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.muted, .help { color: var(--muted); font-size: .875rem; }
/* Descriptions run the full width of their panel. The usual ~75ch measure is
   easier on the eye, but these sit alone in a wide panel under a full-width
   definition list, and a narrow column of text beside all that empty space
   reads as a layout fault rather than as typography. */
.prose { white-space: normal; max-width: 100%; }
/* Notes are working commentary, not part of the record's own description --
   the sand rule keeps the two apart without a second panel. */
.prose--notes {
  border-left: 3px solid var(--sand); padding: .1rem 0 .1rem .75rem;
  background: var(--sand-pale); border-radius: 0 var(--radius) var(--radius) 0;
}

.empty-state {
  color: var(--muted); background: var(--sand-pale); border: 1px dashed var(--sand);
  border-radius: var(--radius); padding: 1rem; text-align: center;
}

/* --------------------------------------------------------------- stats */

.stats { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); margin-bottom: 1rem; }
.stat {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: .85rem; text-decoration: none; color: inherit; display: block;
}
.stat--alert { border-color: var(--danger); }
.stat__value { display: block; font-size: 1.6rem; font-weight: 700; }
.stat__label { display: block; color: var(--muted); font-size: .85rem; }

/* -------------------------------------------------------------- tables */

.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; background: var(--surface); }
.table th, .table td { padding: .55rem .6rem; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.table thead th {
  background: var(--mist); font-family: var(--heading-font); font-weight: 700;
  font-size: .9rem; white-space: nowrap; color: var(--navy);
}
.table thead th a { text-decoration: none; color: inherit; }
.table--compact th, .table--compact td { padding: .4rem .5rem; font-size: .9rem; }
.row--overdue td:first-child { border-left: 3px solid var(--danger); }
.row--error td { background: #fbeded; }
.cell--links { max-width: 20ch; }
.cell--months { white-space: nowrap; color: var(--muted); font-size: .85rem; }

/* -------------------------------------------------------------- badges */

.badge {
  display: inline-block; padding: .1rem .45rem; border-radius: 999px;
  font-size: .75rem; font-weight: 600; border: 1px solid; white-space: nowrap;
}
.badge--neutral { background: var(--mist); border-color: #d3d3cc; color: #45483f; }
.badge--info { background: #e4f3fa; border-color: #a5d6ea; color: var(--cyan-text); }
.badge--success { background: #e9f5ee; border-color: #a9d8bf; color: var(--success); }
.badge--warning { background: #fdf3e2; border-color: #e8cd93; color: var(--warning); }
.badge--danger { background: #fbeaea; border-color: #e5b0b0; color: var(--danger); }
.badge--muted { background: #f1f0ed; border-color: #dad7d1; color: var(--muted); }

/* Role chips. Solid fills, one colour per role, so an Administrator is never
   mistaken for a Viewer at a glance; every pairing clears WCAG AA. */
.badge--administrator { background: var(--navy); border-color: var(--navy); color: #fff; }
.badge--editor { background: var(--cyan-text); border-color: var(--cyan-text); color: #fff; }
.badge--viewer { background: var(--sand); border-color: var(--sand-dark); color: #23201a; }

/* ------------------------------------------------------------ progress */

.progress {
  position: relative; height: 1.1rem; min-width: 84px; background: var(--mist);
  border: 1px solid var(--border); border-radius: 999px; overflow: hidden;
}
.progress__bar { height: 100%; background: var(--cyan); }
.progress__label {
  position: absolute; inset: 0; font-size: .72rem; line-height: 1.1rem;
  text-align: center; color: #16202e;
}

/* --------------------------------------------------------------- forms */

.form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem; max-width: 900px; }
.field { margin-bottom: .9rem; }
.field label { display: block; font-weight: 600; margin-bottom: .25rem; font-size: .9rem; }
.form-control, input[type="text"], input[type="search"], input[type="date"],
input[type="number"], input[type="email"], input[type="password"], select, textarea {
  width: 100%; padding: .45rem .55rem; border: 1px solid var(--border);
  border-radius: var(--radius); font: inherit; background: #fff; color: inherit;
}
.form-check { width: auto; }

/* Date field with its calendar button. The wrapper and button are injected by
   static/js/dateinput.js, so none of this applies until the enhancement runs --
   without JavaScript the input is an ordinary full-width text field. */
.date-field { position: relative; display: block; }
.date-field .form-control { padding-right: 2.4rem; }
.date-field__button {
  position: absolute; right: .3rem; top: 50%; transform: translateY(-50%);
  width: 1.8rem; height: 1.8rem; padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid transparent; border-radius: 3px;
  font-size: 1rem; cursor: pointer;
}
.date-field__button:hover { background: var(--sand-pale); border-color: var(--sand); }
/* Rendered but invisible: showPicker() anchors its popup to this element and
   refuses to open on one that is display:none. */
.date-field__native {
  position: absolute; right: .8rem; bottom: .2rem;
  width: 1px; height: 1px; padding: 0; border: 0;
  opacity: 0; pointer-events: none;
}
textarea { resize: vertical; }
.field--error .form-control, .field--error input, .field--error select { border-color: var(--danger); }
.errors { list-style: none; margin: .25rem 0 0; padding: 0; color: var(--danger); font-size: .85rem; }
.required { color: var(--danger); }
.form__actions { display: flex; gap: .5rem; margin-top: 1rem; flex-wrap: wrap; }
.field-extracted { background: var(--sand-pale); border-color: var(--sand); }

.filters {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: .75rem; margin-bottom: 1rem;
}
.filters__row { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); align-items: end; }
.field--actions { display: flex; gap: .5rem; }

.button {
  display: inline-block; padding: .45rem .95rem; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface); color: var(--navy);
  text-decoration: none; font-size: 1rem; font-weight: 600; cursor: pointer;
  white-space: nowrap;
}
.button:hover { background: var(--sand-pale); border-color: var(--sand); }
.button--primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.button--primary:hover { background: var(--sand); border-color: var(--sand-dark); color: #23201a; }
.button--danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.button--danger:hover { background: #8b2020; color: #fff; }
.button--secondary { background: var(--mist); border-color: #d3d3cc; }
.button--link {
  background: none; border: none; color: var(--accent); padding: .2rem;
  text-decoration: underline; font-family: var(--body-font);
}
.button--link:hover { background: none; color: var(--cyan-text); }
.inline-form { display: inline-flex; gap: .5rem; align-items: center; }

/* ---------------------------------------------------- definitions/tree */

.definitions { display: grid; gap: .5rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); margin: 0; }
.definitions dt { font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
.definitions dd { margin: .1rem 0 0; }

.link-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.link-list li { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }

.tree { list-style: none; margin: 0; padding: 0; }
.tree--nested { margin-left: 1.5rem; border-left: 2px solid var(--border); padding-left: .75rem; }
.tree__node { margin: .35rem 0; }
.tree__row { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.tree__label { font-weight: 600; text-decoration: none; }
.tree__empty { margin: .2rem 0 .2rem 1.6rem; font-size: .85rem; }

.pagination { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin-top: .75rem; }
.pagination__status { color: var(--muted); font-size: .875rem; }

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

.gantt-legend { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .8rem; color: var(--muted); margin-bottom: .5rem; }
.swatch { display: inline-block; width: .8rem; height: .8rem; border-radius: 2px; vertical-align: -1px; }
.swatch--planned { background: #cfe7f3; }
.swatch--actual { background: var(--navy); }
.swatch--progress { background: var(--cyan); }
.swatch--milestone { background: var(--sand-dark); transform: rotate(45deg); }
.swatch--deliverable { background: #1f7a4d; transform: rotate(45deg); }
.swatch--overdue { background: var(--danger); }
.swatch--today { background: rgba(165, 42, 42, .6); width: 2px; }

.gantt {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: .75rem; overflow-x: auto;
}
.gantt__controls {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--gap); flex-wrap: wrap; margin-bottom: .5rem;
}
.gantt__summary { color: var(--muted); font-size: .85rem; margin: 0; }
.gantt__hidden-count { color: var(--muted); }
.gantt__toolbar { display: flex; gap: .4rem; flex-wrap: wrap; }
.gantt__toolbar .button { padding: .25rem .6rem; font-size: .8rem; }

/* One variable drives the label column so the rows, the header and the
   today overlay all line up; the media query below is the only place it
   changes. */
.gantt__chart { --label-width: 320px; min-width: 900px; position: relative; }
.gantt__body { position: relative; }

.gantt__row {
  display: grid; grid-template-columns: var(--label-width) 1fr;
  align-items: center; border-bottom: 1px solid #eef0f3;
}
/* The class selector would otherwise beat the browser's [hidden] rule. */
.gantt__row[hidden] { display: none; }
.gantt__row--header { border-bottom: 2px solid var(--border); font-weight: 600; height: 2rem; }
.gantt__row.is-structural { opacity: .55; }
.gantt__row--project .gantt__label { font-weight: 700; }

.gantt__label {
  display: flex; align-items: center; gap: .3rem;
  padding: .3rem .5rem .3rem calc(.5rem + var(--level, 0) * 1.1rem);
  font-size: .85rem; white-space: nowrap; overflow: hidden;
}
.gantt__link { text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
.gantt__type { color: var(--muted); font-size: .7rem; text-transform: capitalize; flex: none; }

/* Expand / collapse control on Project and Work Package rows.

   The two states carry different backgrounds so the shape of the tree is
   readable at a glance without tracing every glyph: an expanded row is filled
   with the sand accent, a collapsed one stays plain. The selector keys off
   aria-expanded, which the renderer already maintains, so the visual state and
   the state announced to a screen reader cannot drift apart. */
.gantt__toggle {
  flex: none; width: 1.15rem; height: 1.15rem; padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border); border-radius: 3px;
  font-size: .7rem; cursor: pointer;
}
.gantt__toggle[aria-expanded="true"] {
  background: var(--sand); border-color: var(--sand-dark); color: #23201a;
}
.gantt__toggle:hover { background: var(--sand-pale); color: var(--navy); }
.gantt__toggle[aria-expanded="true"]:hover {
  background: var(--sand-dark); border-color: var(--sand-dark); color: #fff;
}
.gantt__toggle--none { visibility: hidden; cursor: default; }

.gantt__track { position: relative; height: 1.7rem; }
.gantt__axis { position: absolute; inset: 0; }
.gantt__tick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--border); }
.gantt__tick-label { font-size: .7rem; color: var(--muted); padding-left: .2rem; }

/* Today marker.
   The line must be measured against the timeline column only, so the overlay
   repeats the row grid and puts an empty gutter under the label column.
   Positioning it against the whole chart would place it at the wrong date. */
.gantt__today-layer {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  display: grid; grid-template-columns: var(--label-width) 1fr;
}
.gantt__today-gutter { /* spacer beneath the label column */ }
.gantt__today-track { position: relative; }
.gantt__today {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: rgba(165, 42, 42, .6);
}
.gantt__today-label {
  position: absolute; top: 0; transform: translateX(-50%);
  background: var(--danger); color: #fff; font-size: .65rem; font-weight: 600;
  padding: 0 .3rem; border-radius: 0 0 3px 3px; white-space: nowrap;
}

.gantt__bar { position: absolute; top: .35rem; height: .7rem; border-radius: 3px; overflow: hidden; }
.gantt__bar--planned { background: #cfe7f3; border: 1px solid #96c6dd; }
.gantt__bar--planned.is-overdue { border-color: var(--danger); }
.gantt__bar--actual { top: 1.15rem; height: .35rem; background: var(--navy); border-radius: 2px; }
.gantt__progress { height: 100%; background: var(--cyan); }

.gantt__marker {
  position: absolute; top: .45rem; width: .65rem; height: .65rem;
  transform: translateX(-50%) rotate(45deg);
}
.gantt__marker--milestone { background: var(--sand-dark); }
.gantt__marker--deliverable { background: #1f7a4d; }
.gantt__marker.is-overdue { background: var(--danger); }
.gantt__nodate { font-size: .72rem; color: var(--muted); padding-left: .3rem; }

@media (max-width: 700px) {
  .gantt__chart { --label-width: 190px; min-width: 700px; }
}
