/* dfr-site — one hand-written stylesheet. No framework, no CDN, no build step.
 *
 * Phone width first: these pages get filled in standing on an airfield, on a
 * phone, possibly in sunlight and possibly in gloves. So: one column by
 * default, large tap targets, high contrast, system fonts (nothing to fetch,
 * nothing to wait for). The wider layout is the exception, added at 44rem.
 *
 * Legible and usable is the bar. Visual design is cheap to change later;
 * structure is not, which is what Batch 2 is actually freezing.
 */

:root {
  --ink:        #14181d;
  --ink-soft:   #4a545f;
  --line:       #ccd3da;
  --line-soft:  #e4e9ee;
  --paper:      #ffffff;
  --ground:     #f3f5f7;
  --accent:     #14507d;
  --accent-ink: #ffffff;
  --warn-bg:    #fdf3e3;
  --warn-line:  #c98a1e;
  --tap:        2.75rem;   /* minimum comfortable target */
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 1.0625rem/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ------------------------------------------------------------- skip link */

.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  left: 0.5rem; top: 0.5rem;
  z-index: 10;
  padding: 0.5rem 0.75rem;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 4px;
}

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

.site-header {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding: 0.75rem 1rem;
}

.brand {
  display: block;
  text-decoration: none;
  color: inherit;
}
.brand-name { display: block; font-weight: 650; font-size: 1.0625rem; }
.brand-sub  { display: block; font-size: 0.8125rem; color: var(--ink-soft); }

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-top: 0.625rem;
}
.site-nav a,
.site-nav .linkish {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 0.125rem;
  color: var(--accent);
  text-decoration: none;
  font-weight: 550;
}
.site-nav a:hover, .site-nav a:focus,
.site-nav .linkish:hover, .site-nav .linkish:focus { text-decoration: underline; }

/* Batch 21, item 1: the Log out button was invisible on every page since
   Batch 2. It is a <button type="submit">, so `button[type="submit"]` below
   (specificity 0,1,1) gave it the accent background and border, beating
   `.linkish` (0,1,0) -- and `.site-nav .linkish` above (0,2,0) then set its
   text to the same accent. Accent on accent. Restated here at 0,2,0, which
   beats the button rule, so the nav button looks like the nav links beside it. */
.site-nav .linkish { background: none; border: 0; border-radius: 0; }

.nav-form { margin: 0; display: inline; }
.linkish {
  background: none; border: 0; padding: 0;
  font: inherit; font-weight: 550;
  color: var(--accent);
  cursor: pointer;
}

/* ------------------------------------------------------------------ page */

.page {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
}

h1 { font-size: 1.5rem; line-height: 1.25; margin: 0.5rem 0 0.75rem; }
h2 { font-size: 1.125rem; margin: 1.75rem 0 0.5rem; }

.lede  { color: var(--ink-soft); margin: 0 0 1.25rem; }
.note  { color: var(--ink-soft); font-size: 0.9375rem; margin-top: 1.5rem; }
.empty { color: var(--ink-soft); font-style: italic; }
.crumb { margin: 0 0 0.5rem; font-size: 0.9375rem; }
.crumb a, .note a, .empty a, .lede a { color: var(--accent); }

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

form { margin: 0 0 1rem; }
.narrow { max-width: 26rem; }

fieldset {
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--paper);
  padding: 0.75rem 1rem 0.25rem;
  margin: 0 0 1.25rem;
}
legend { font-weight: 650; padding: 0 0.375rem; }
.optional { font-weight: 400; color: var(--ink-soft); font-size: 0.875rem; }

.field { margin: 0 0 1rem; }
.field label {
  display: block;
  font-weight: 550;
  margin-bottom: 0.25rem;
}
.req { color: var(--warn-line); }
.format { font-weight: 400; color: var(--ink-soft); font-size: 0.875rem; }

/* Batch 10: takeoff and landing are text inputs carrying HH:MM, not the native
   time control, which renders in the browser's locale and shows AM/PM to an
   English-locale viewer. A five-character value does not need a full-width box;
   the width is capped rather than set, so a narrow screen still shrinks it. */
input.time-input { max-width: 8rem; }

/* Batch 11: the same for the date, and for the same measured reason -- the
   native date control renders mm/dd/yyyy to an English-locale viewer whatever
   the document says. Ten characters. */
input.date-input { max-width: 11rem; }

/* Batch 19: the date picker, dfr/static/date-picker.js. None of these classes
   exist with scripting off -- the script creates every element below -- so the
   field above is the whole of the no-script layout.

   The field and the Calendar button share a row. The grid opens in the flow
   underneath rather than floating: at phone width there is nowhere for a
   popover to go that does not cover the next field. Day cells keep --tap as
   their height and divide the width seven ways, which at 380px leaves each one
   about 40px wide -- still a fingertip, in a glove, outside. */
.date-row { display: flex; align-items: stretch; gap: 0.5rem; }
.date-row input.date-input { flex: 0 1 11rem; }
.date-toggle { flex: none; }

.date-picker {
  max-width: 22rem;
  margin: -0.5rem 0 1rem;
  padding: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.dp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}
.dp-title { margin: 0; font-weight: 650; }
.dp-nav, .dp-day {
  min-height: var(--tap);
  font: inherit;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid transparent;
  border-radius: 5px;
  cursor: pointer;
}
.dp-nav {
  min-width: var(--tap);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--accent);
  border-color: var(--line);
}
.dp-grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
.dp-grid th {
  padding: 0.25rem 0;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ink-soft);
  text-align: center;
}
.dp-grid td { padding: 1px; text-align: center; }
.dp-day { width: 100%; padding: 0; font-variant-numeric: tabular-nums; }
.dp-day:hover { background: var(--ground); }
.dp-nav:disabled, .dp-day:disabled {
  color: #a3acb5;
  background: transparent;
  border-color: transparent;
  cursor: default;
}
.dp-today { font-weight: 700; border-color: var(--accent); }
.dp-chosen, .dp-chosen:hover { color: var(--accent-ink); background: var(--accent); }
.dp-note { margin: 0.375rem 0 0; font-size: 0.8125rem; color: var(--ink-soft); }

/* Batch 14: takeoff and landing are two menus each, hour and minute. The rule
   below is the whole layout -- `select` is display:block, width:100% further
   down, which would stack them; these two sit on one line with a colon between
   and stay finger-sized on a phone. */
.time-field .time-pair {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
}
.time-field .time-part { flex: 0 1 7rem; }
.time-field .time-part label {
  font-weight: 400;
  font-size: 0.875rem;
  color: var(--ink-soft);
}
.time-field select.time-select { width: 100%; }
.time-field .time-colon {
  padding-bottom: 0.625rem;
  font-weight: 650;
  color: var(--ink-soft);
}

input[type="text"], input[type="email"], input[type="password"],
input[type="tel"], input[type="date"],
input[type="number"], select, textarea {
  display: block;
  width: 100%;
  min-height: var(--tap);
  padding: 0.5rem 0.625rem;
  font: inherit;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 5px;
}
textarea { min-height: 6rem; resize: vertical; }

input:focus, select:focus, textarea:focus, button:focus, a:focus {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
}

.hint {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* Contract 4.4: dropdown plus a free-text alternative. No JavaScript, so both
   controls are always visible and the second label explains the relationship
   instead of the first one appearing and disappearing. */
.managed label { margin-top: 0.75rem; font-weight: 500; font-size: 0.9375rem; }
.managed label:first-of-type { margin-top: 0; }
.managed select { margin-bottom: 0.25rem; }

/* --------------------------------------------------------------- buttons */

.actions { display: flex; flex-wrap: wrap; gap: 0.625rem; margin: 1.25rem 0; }

.button, button[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 0 1.125rem;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  color: var(--accent-ink);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 5px;
  cursor: pointer;
}
.button-secondary, .button-secondary:link {
  color: var(--accent);
  background: var(--paper);
}

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

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

.request-row {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-bottom: 0.625rem;
}

.row-link {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.125rem 0.75rem;
  padding: 0.75rem 0.875rem;
  text-decoration: none;
  color: inherit;
}
.row-link:hover { background: var(--ground); }

.row-date  { font-weight: 650; }
.row-state { grid-row: 1 / span 2; grid-column: 2; align-self: start; }
.row-times { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.row-location  { grid-column: 1 / -1; color: var(--ink-soft); font-size: 0.9375rem; }

.row-note {
  margin: 0;
  padding: 0 0.875rem 0.75rem;
  font-size: 0.9375rem;
  color: var(--ink-soft);
}
.row-note a { color: var(--accent); }

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

.badge {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  border: 1px solid var(--line);
  background: var(--ground);
  color: var(--ink-soft);
  white-space: nowrap;
}
.badge-pending   { background: #fdf3e3; border-color: #d9ad63; color: #6d4a06; }
.badge-confirmed { background: #e4f3e8; border-color: #6aa87c; color: #17552b; }
.badge-denied    { background: #fbe7e7; border-color: #c98383; color: #7c1d1d; }
.badge-withdrawn,
.badge-cancelled,
.badge-expired   { background: var(--line-soft); border-color: var(--line); color: var(--ink-soft); }

/* --------------------------------------------------------------- detail */

.state-line { margin: 0 0 1.25rem; }
.version { color: var(--ink-soft); font-size: 0.9375rem; margin-left: 0.375rem; }

.detail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 0 0 1.25rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.25rem 0.875rem 0.75rem;
}
.detail dt { font-size: 0.875rem; color: var(--ink-soft); margin-top: 0.625rem; }
.detail dd { margin: 0.0625rem 0 0; font-weight: 500; }

.account { margin: 0 0 1.5rem; }
.account dt { font-size: 0.875rem; color: var(--ink-soft); margin-top: 0.5rem; }
.account dd { margin: 0; font-weight: 500; }

.notes { white-space: pre-wrap; }
.decided { color: var(--ink-soft); font-size: 0.9375rem; }

/* Written by AELMIS, displayed here and nowhere edited. */
.manager-note {
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  border-left-width: 5px;
  border-radius: 5px;
  padding: 0.25rem 1rem 0.875rem;
  margin: 0 0 1.5rem;
}
.manager-note h2 { margin-top: 0.75rem; font-size: 1rem; }
.manager-note p  { margin: 0; }

/* Batch 21, item 6. The same six lines as the approval mail. Bordered like the
   note above but in the line colour: an instruction, not a warning. */
.safety-block {
  border: 1px solid var(--line);
  border-left-width: 5px;
  border-radius: 5px;
  padding: 0.25rem 1rem 0.5rem;
  margin: 0 0 1.5rem;
}
.safety-block h2 { margin-top: 0.75rem; font-size: 1rem; }
.safety-block ul { margin: 0.5rem 0; padding-left: 1.25rem; }

.request-actions {
  border-top: 1px solid var(--line);
  margin-top: 2rem;
  padding-top: 1rem;
}
.request-actions form { margin-bottom: 1rem; }
.request-actions .hint { margin-top: 0.375rem; }

/* Contract 4.1: the two account gates. Absent control, stated reason. */
.gate {
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  border-left-width: 5px;
  border-radius: 5px;
  padding: 0.25rem 1rem 0.875rem;
  margin: 0 0 1.5rem;
}
.gate h2 { margin-top: 0.75rem; font-size: 1rem; }
.gate p  { margin: 0; }
.gate a  { color: var(--accent); }

/* --------------------------------------------------------------- footer */

.site-footer {
  border-top: 1px solid var(--line);
  padding: 1rem;
  color: var(--ink-soft);
  font-size: 0.875rem;
  text-align: center;
}
.site-footer p { margin: 0; }

/* ------------------------------------------------- dev shells index only */

body.shells { background: var(--paper); }
body.shells ul { padding-left: 1.125rem; }
body.shells li { margin-bottom: 0.5rem; }
body.shells a  { color: var(--accent); }
body.shells code {
  background: var(--ground);
  padding: 0.0625rem 0.3125rem;
  border-radius: 3px;
  font-size: 0.9em;
}
body.shells .absent { color: var(--ink-soft); }

/* ------------------------------------------------------- wider than phone */

@media (min-width: 44rem) {
  .site-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 0.75rem 1.5rem;
  }
  .site-nav { margin-top: 0; }
  .page { padding: 1.75rem 1.5rem 4rem; }
  h1 { font-size: 1.75rem; }

  .detail {
    grid-template-columns: 11rem 1fr;
    padding: 0.875rem 1rem;
  }
  .detail dt { margin-top: 0.375rem; }
  .detail dd { margin-top: 0.375rem; }

  .account { display: grid; grid-template-columns: 11rem 1fr; }
  .account dt, .account dd { margin-top: 0.25rem; }

  .row-link { grid-template-columns: 9rem 9rem 1fr auto; align-items: baseline; }
  .row-location  { grid-column: 3; }
  .row-state { grid-row: 1; grid-column: 4; }
}


/* ------------------------------------------------------------ form messages
   Batch 3. One style for a refusal, one for a confirmation. Both are written
   by the view; neither ever carries a value the visitor supplied except the
   email address echoed back into the form, which Jinja escapes. */

.form-error,
.form-notice {
  margin: 0 0 1rem;
  padding: 0.625rem 0.875rem;
  border-left: 3px solid;
  border-radius: 2px;
  font-size: 0.9375rem;
}

.form-error {
  border-left-color: #a8321e;
  background: #fbecea;
  color: #6d2114;
}

.form-notice {
  border-left-color: #2f6b3a;
  background: #eaf4ec;
  color: #1f4a27;
}

/* Batch 4a. The request form reports every rule of contract 4.2 that a
   submission broke, not just the first, so the refusal carries a list. */

.form-error p  { margin: 0; font-weight: 600; }
.form-error ul { margin: 0.375rem 0 0; padding-left: 1.25rem; }
.form-error li { margin: 0.125rem 0; font-weight: 400; }

/* Batch 20b. The honeypot on /register and /external -- see form_guard in
   _macros.html. Off screen, not display:none, which some bots skip. */

.hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}


/* ------------------------------------------------- saved entries (Batch 11)

   Three sets on the profile page, and a picker per set on the request form.
   No JavaScript is involved in any of it: a picker is a select and a submit
   button, and the fields it fills are ordinary inputs. */

.set-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin: 2rem 0 0.5rem;
}
.set-heading h2 { margin: 0; }

.entry-list { list-style: none; padding: 0; margin: 0 0 0.5rem; }

.entry {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.625rem 0.875rem;
  margin: 0 0 0.5rem;
  background: var(--ground);
}
/* The default is the one AELMIS sees -- contract 4.1. Marked, not merely
   first. */
.entry-default { border-color: var(--accent); }

.entry-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.625rem;
  margin: 0;
}
.entry-name { font-weight: 650; }
.entry-detail { color: var(--ink-soft); font-size: 0.9375rem; }
.entry-ids { margin: 0.125rem 0 0; }

.badge-default { background: #e8eef8; border-color: var(--accent); color: #1c3d70; }

/* Edit is a link; Make default and Delete are one-button POST forms, because
   both change state and neither belongs on a GET. They read as one row. */
.entry-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 1rem;
  margin: 0.5rem 0 0;
}
.entry-actions form { display: inline; margin: 0; }
.entry-actions a, .entry-actions .linkish { font-size: 0.9375rem; }

/* The picker sits above the fields it fills, inside the same fieldset, so the
   relationship is visible without a line of prose saying so. */
.picker {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0 0 0.875rem;
  border-bottom: 1px dashed var(--line);
}
.picker label {
  display: block;
  width: 100%;
  font-weight: 550;
  margin-bottom: 0.25rem;
}
.picker select { flex: 1 1 12rem; width: auto; }
.picker .button { flex: 0 0 auto; }


/* --------------------------------------------------- contract 4.5, Batch 14
   The departure checklist, and the record of it afterwards.

   The boxes carry `required`, so the browser refuses an incomplete form and
   puts the cursor on the first unticked one. Nothing here disables the button:
   a control that greys itself out as boxes are ticked needs script, and the
   gate is in dfr/flight_requests.py where it cannot be styled away. */
.checklist { list-style: none; margin: 0 0 0.5rem; padding: 0; }
.checklist-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--tap);
  padding: 0.25rem 0;
}
.checklist-item input[type="checkbox"] {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
}
.checklist-item label { margin: 0; font-weight: 550; }
.checklist-done .tick { color: #17552b; font-weight: 700; }

.depart-flight { color: var(--ink-soft); margin: 0 0 1rem; }

/* Airborne and landed sit BESIDE the state badge, never instead of it --
   contract 4.3 keeps the six states for what the manager decided, and 4.5's
   condition is derived from two timestamps. Overdue is the loud one: it is
   either a forgotten button or a real overrun. */
.badge-airborne { background: #e8eef8; border-color: #5b83b8; color: #1c3d70; }
.badge-landed   { background: var(--line-soft); border-color: var(--line); color: var(--ink-soft); }

/* The one solid badge on the site, and the only one that inverts. Overdue means
   a drone is still up past the window the manager granted -- a forgotten button
   or a real overrun -- and it has to be the loudest thing on the page. The light
   triples above are all reserved for states somebody decided; this is not one
   of them. Solid #7c1d1d on white is about 9:1, well past AA. */
.badge-overdue  { background: #7c1d1d; border-color: #7c1d1d; color: #ffffff; }


/* ------------------------------------------- external requests, Batch 15
   Contract v1.11 4.6. The public form, the map, and the reference page. */

/* The landing-page entry. It has to read as a different door rather than a
   third button beside Log in and Register: the people it is for do not have an
   account and will not be getting one. */
.external-entry {
  margin: 2rem 0 0;
  padding: 1.25rem 1.25rem 0.5rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 6px;
  background: var(--paper);
}
.external-entry h2 { margin-top: 0; }

.lede { font-size: 1.0625rem; color: var(--ink-soft); }

/* A label for a group of fields that is not itself a control. */
.pseudo-label { display: block; font-weight: 550; margin-bottom: 0.25rem; }

/* The map. A fixed height rather than an aspect ratio, because Leaflet needs a
   laid-out container to size its tiles against and an empty box with no height
   renders nothing at all.

   WITH SCRIPTING OFF this stays an empty bordered box. That is deliberate and
   is not a broken state: 4.6 makes the three fields below the actual control,
   and the map an input method for them. */
.area-map {
  height: 22rem;
  margin: 0 0 1rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--ground);
}

/* Leaflet draws its own controls and attribution inside; keep the site's font
   rather than the library's default stack. */
.area-map, .area-map .leaflet-container { font: inherit; }

/* The three numbers the map fills in. Side by side on a wide screen, stacked
   on a narrow one, and always visible -- 4.6 requires that they "remain
   visible and editable". */
.area-fields { display: flex; flex-wrap: wrap; gap: 0 1rem; }
.area-fields .field { flex: 1 1 12rem; }

.radius-slider { width: 100%; }

/* The reference is the only handle an external requester has (4.6), and they
   will be reading it down a telephone. Large, spaced, and selectable. */
.reference-block {
  margin: 1.5rem 0;
  padding: 1rem 1.25rem;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: #e8eef8;
}
.reference-label {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.reference {
  margin: 0.25rem 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.75rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  color: var(--accent);
}
