/* ==========================================================================
   KLC Rooster - design system
   --------------------------------------------------------------------------
   Layered on top of Bootstrap 4.5.3, which supplies the grid, the utility
   classes and the component markup contract. Everything visual below is ours:
   tokens first, then base, layout, and components in the order they nest.

   Conventions
     - Colour, spacing, radius, elevation and motion come from --klc-* tokens.
       A literal colour in a rule is a bug unless it is a third-party brand.
     - Every colour token is declared three times: light in :root, dark under
       prefers-color-scheme, dark again under [data-theme="dark"] so an explicit
       choice wins. The two dark blocks must stay identical.
     - Interactive targets reach 44px. Bootstrap's defaults do not.

   Stacking order, so additions land in the right place
     1020 header            1030 bottom bar        1032 push bell
     1021 refresh/offline   1035 TSAT widget       1036 save toast
     1037 nav scrim         1038 nav rail          1040 modal backdrop (BS)
     1050 modal (BS)        1055 push prompt       1060 photo lightbox

   Brand reference
     KLM BLUE        #00A1DE      KLM LIGHT BLUE 1  #5EB6E4
     KLM DARK BLUE 1 #005882      KLM LIGHT BLUE 2  #C2DEEA
     KLM DARK BLUE 2 #003145      KLM ORANGE        #E37222
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   Light is the default. Dark redefines the same names and nothing else, so a
   component is written once and themes itself.
   -------------------------------------------------------------------------- */

:root {
  /* Brand */
  --klc-brand: #00a1de;
  --klc-brand-strong: #0076a5;
  --klc-brand-deep: #005882;
  --klc-brand-soft: #e8f6fc;
  /* The day heading sticks and cards pass underneath it, so its fill has to be
     opaque. brand-soft is a translucent wash in dark mode, which would let the
     cards read through the date. This is the same tint, already composited. */
  --klc-day-band: #e8f6fc;
  --klc-accent: #e37222;
  /* The accent as a foreground: the plain one is a fill, and at 3.1 against
     white it is too light to carry small text or an icon. */
  --klc-accent-strong: #b05616;
  --klc-accent-soft: #fdf1e8;

  /* Which side of the door a colleague is on. Named separately from the text and
     brand colours they borrow here, because the flight deck cannot follow the
     text colour into dark mode: that flips it from the darkest of the three to
     the lightest. */
  --klc-crew-deck: var(--klc-text);
  --klc-crew-cabin: var(--klc-brand-strong);
  /* Not a fill: the deadhead marker is a ring, so it needs a line colour. */
  --klc-crew-dh: var(--klc-text-muted);

  /* Status */
  --klc-success: #16884b;
  /* Small type in green needs more weight than the fill does; matches how
     brand and accent already split. */
  --klc-success-strong: #147e46;
  --klc-success-soft: #e6f5ec;
  --klc-danger: #d13438;
  --klc-danger-strong: #cb3236;
  --klc-danger-soft: #fdecec;
  /* Between the full colour and the ordinary border: enough to carry a whole
     card without turning it into a warning panel. */
  --klc-danger-line: #eaa9ab;
  --klc-warning: #b26a00;
  --klc-warning-strong: #a26000;
  /* Schiphol signs a gate in yellow and the crew read those signs all day. Its
     own pair rather than the warning colour: nothing is wrong, it is where to go. */
  --klc-gate: #ffc72c;
  --klc-gate-ink: #1c1c1c;
  --klc-warning-soft: #fdf3e2;
  --klc-info: #0b6f96;
  --klc-info-soft: #e6f3f9;

  /* Surfaces, from furthest back to furthest forward */
  --klc-bg: #f4f7fa;
  --klc-surface: #ffffff;
  --klc-surface-2: #f8fafc;
  --klc-surface-3: #eef2f6;
  --klc-overlay: rgba(10, 22, 32, 0.55);

  /* Ink */
  --klc-text: #16202a;
  --klc-text-secondary: #55677a;
  --klc-text-muted: #5f7083;
  --klc-text-on-brand: #ffffff;

  /* Lines */
  --klc-border: #e2e8f0;
  --klc-border-strong: #cbd5e1;

  /* Shape */
  --klc-radius-sm: 8px;
  --klc-radius: 12px;
  --klc-radius-lg: 18px;
  --klc-radius-pill: 999px;

  /* Elevation. Deliberately soft - this is a utility app, not a landing page. */
  --klc-shadow-sm: 0 1px 2px rgba(16, 32, 48, 0.06), 0 1px 3px rgba(16, 32, 48, 0.05);
  --klc-shadow: 0 2px 4px rgba(16, 32, 48, 0.05), 0 6px 16px rgba(16, 32, 48, 0.07);
  --klc-shadow-lg: 0 8px 20px rgba(16, 32, 48, 0.09), 0 24px 48px rgba(16, 32, 48, 0.12);

  /* Motion */
  --klc-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --klc-fast: 120ms;
  --klc-normal: 200ms;

  /* Type */
  --klc-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --klc-font-mono: "SFMono-Regular", ui-monospace, "JetBrains Mono", Menlo,
    Consolas, "Liberation Mono", monospace;

  --klc-sidebar-width: 16rem;
  --klc-bottomnav-height: 3.25rem;
  /* The page gutter. Read both by the container that sets it and by anything
     that has to step out through it, so the two cannot drift apart. */
  --klc-gutter: 1rem;
  /* The full height of the app header, safe area included: a 2.75rem control
     between 0.5rem of padding. sidebar.js measures the header for real as soon as
     it runs and overwrites this, so the value here is only what the first paint
     uses - enough that the day headings do not start out too low and then jump. */
  --klc-header-height: 3.75rem;

  color-scheme: light;
}

/* The dark palette is declared once and referenced twice: once for people who
   have never touched the switch (OS preference), once for people who have
   (data-theme on <html>). The :not() guard lets an explicit light choice win
   on a device that is set to dark. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --klc-brand: #33b5e6;
    --klc-brand-strong: #5cc4ea;
    --klc-brand-deep: #9bd9f2;
    --klc-brand-soft: rgba(51, 181, 230, 0.14);
  --klc-day-band: #122b37;
    --klc-accent: #f0904a;
    --klc-accent-strong: #f0904a;
    --klc-accent-soft: rgba(240, 144, 74, 0.14);

    /* Darker than the cabin dot, as it is in light mode, and still 7.6:1 against
       the card - a shade off the 8.6:1 the cabin dot carries, so neither reads as
       the lesser of the two. */
    --klc-crew-deck: #9bb0c2;

    --klc-success: #4ac07d;
    --klc-success-strong: #4ac07d;
    --klc-success-soft: rgba(74, 192, 125, 0.14);
    --klc-danger: #f4777b;
    --klc-danger-strong: #f4777b;
    --klc-danger-soft: rgba(244, 119, 123, 0.14);
    --klc-danger-line: rgba(244, 119, 123, 0.45);
    --klc-warning: #e6a34a;
    --klc-warning-strong: #e6a34a;
    /* The sign stays a sign: yellow with dark type, the same as in the terminal. */
    --klc-gate: #f0bd2e;
    --klc-gate-ink: #1c1c1c;
    --klc-warning-soft: rgba(230, 163, 74, 0.14);
    --klc-info: #56b6d9;
    --klc-info-soft: rgba(86, 182, 217, 0.14);

    /* A deep, slightly blue night. Crew read this at 03:00 on a dark flight deck. */
    --klc-bg: #0d141a;
    --klc-surface: #141d25;
    --klc-surface-2: #1a242d;
    --klc-surface-3: #223039;
    --klc-overlay: rgba(0, 0, 0, 0.66);

    --klc-text: #e8eef4;
    --klc-text-secondary: #a3b4c4;
    --klc-text-muted: #8697a5;
    --klc-text-on-brand: #04212e;

    --klc-border: #26333f;
    --klc-border-strong: #35475a;

    --klc-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --klc-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.3);
    --klc-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5), 0 32px 64px rgba(0, 0, 0, 0.4);

    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --klc-brand: #33b5e6;
  --klc-brand-strong: #5cc4ea;
  --klc-brand-deep: #9bd9f2;
  --klc-brand-soft: rgba(51, 181, 230, 0.14);
  --klc-day-band: #122b37;
  --klc-accent: #f0904a;
  --klc-accent-strong: #f0904a;
  --klc-accent-soft: rgba(240, 144, 74, 0.14);

  /* Darker than the cabin dot, as it is in light mode, and still 7.6:1 against
     the card - a shade off the 8.6:1 the cabin dot carries, so neither reads as
     the lesser of the two. */
  --klc-crew-deck: #9bb0c2;

  --klc-success: #4ac07d;
  --klc-success-strong: #4ac07d;
  --klc-success-soft: rgba(74, 192, 125, 0.14);
  --klc-danger: #f4777b;
  --klc-danger-strong: #f4777b;
  --klc-danger-soft: rgba(244, 119, 123, 0.14);
  --klc-danger-line: rgba(244, 119, 123, 0.45);
  --klc-warning: #e6a34a;
  --klc-warning-strong: #e6a34a;
  /* The sign stays a sign: yellow with dark type, the same as in the terminal. */
  --klc-gate: #f0bd2e;
  --klc-gate-ink: #1c1c1c;
  --klc-warning-soft: rgba(230, 163, 74, 0.14);
  --klc-info: #56b6d9;
  --klc-info-soft: rgba(86, 182, 217, 0.14);

  --klc-bg: #0d141a;
  --klc-surface: #141d25;
  --klc-surface-2: #1a242d;
  --klc-surface-3: #223039;
  --klc-overlay: rgba(0, 0, 0, 0.66);

  --klc-text: #e8eef4;
  --klc-text-secondary: #a3b4c4;
  --klc-text-muted: #8697a5;
  --klc-text-on-brand: #04212e;

  --klc-border: #26333f;
  --klc-border-strong: #35475a;

  --klc-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --klc-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.3);
  --klc-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5), 0 32px 64px rgba(0, 0, 0, 0.4);

  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
  /* Also paints the area behind the iOS toolbars, which samples the canvas. */
  background-color: var(--klc-bg);
}

body {
  background-color: var(--klc-bg);
  color: var(--klc-text);
  font-family: var(--klc-font);
  font-size: 0.9375rem;
  line-height: 1.55;
  font-weight: 400;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "cv05" 1, "ss01" 1;
}

::selection {
  background: var(--klc-brand);
  color: var(--klc-text-on-brand);
}

/* In-page links land under the sticky header without this. Settings and Help
   are both built around <a name="..."> jump targets. */
html {
  scroll-behavior: smooth;
}

:root {
  --klc-anchor-offset: calc(6.5rem + env(safe-area-inset-top));
}

[id] {
  scroll-margin-top: var(--klc-anchor-offset);
}

@media (min-width: 768px) {
  :root {
    --klc-anchor-offset: calc(7rem + env(safe-area-inset-top));
  }
}

hr {
  border: 0;
  border-top: 1px solid var(--klc-border);
  margin: 1.25rem 0;
}

small,
.small {
  font-size: 0.8125rem;
}

code,
kbd,
pre,
samp {
  font-family: var(--klc-font-mono);
}

/* Focus: visible for keyboards, quiet for mice. */
:focus-visible {
  outline: 2px solid var(--klc-brand);
  outline-offset: 2px;
  border-radius: var(--klc-radius-sm);
}

.btn:focus,
.form-control:focus,
.list-group-item-action:focus {
  outline: none;
  box-shadow: 0 0 0 3px var(--klc-brand-soft), 0 0 0 1px var(--klc-brand);
}

@media (prefers-reduced-motion: reduce) {
  .colleague-card {
    animation: none;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   3. Typography
   A compact scale. Headings are tight and confident; body stays readable.
   -------------------------------------------------------------------------- */

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: var(--klc-font);
  color: var(--klc-text);
  font-weight: 650;
  letter-spacing: -0.021em;
  line-height: 1.25;
  margin-top: 0;
  margin-bottom: 0.5rem;
}

h1, .h1 { font-size: 1.75rem; letter-spacing: -0.028em; }
h2, .h2 { font-size: 1.4375rem; }
h3, .h3 { font-size: 1.1875rem; }
h4, .h4 { font-size: 1.0625rem; }
h5, .h5 { font-size: 0.9375rem; }
h6, .h6 {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--klc-text-muted);
}

p {
  margin-bottom: 0.75rem;
  font-weight: 400;
}

a {
  color: var(--klc-brand-strong);
  text-decoration: none !important;
  font-weight: 500;
  transition: color var(--klc-fast) var(--klc-ease);
}

a:hover {
  color: var(--klc-brand-deep);
}

.text-muted {
  color: var(--klc-text-muted) !important;
}

.text-secondary {
  color: var(--klc-text-secondary) !important;
}

.display-4 {
  font-size: 2.75rem;
  font-weight: 300;
  color: var(--klc-text-muted);
  letter-spacing: -0.03em;
}

/* --------------------------------------------------------------------------
   4. Application shell
   Desktop: a fixed navigation rail beside a centred content column.
   Phone: no rail, a bottom bar for the four screens crew actually use, and
   everything else behind a sheet that slides in from the left.
   -------------------------------------------------------------------------- */

.app {
  display: block;
  min-height: 100vh;
}

/* ---- Rail ---------------------------------------------------------------- */

.nav-rail {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--klc-sidebar-width);
  background-color: var(--klc-surface);
  border-right: 1px solid var(--klc-border);
  display: flex;
  flex-direction: column;
  padding: calc(0.875rem + env(safe-area-inset-top)) 0.75rem
    calc(1rem + env(safe-area-inset-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  /* Below the modal backdrop (1040): a dialog must dim and block the rail,
     which it could not do while the rail sat above it. */
  z-index: 1038;
  transform: translateX(-100%);
  transition: transform var(--klc-normal) var(--klc-ease);
}

body.nav-open .nav-rail {
  transform: translateX(0);
  box-shadow: var(--klc-shadow-lg);
}

@media (min-width: 768px) {
  .nav-rail {
    transform: none;
    box-shadow: none;
  }
}

/* The person heads the rail, under one rule that keeps them apart from the
   destinations below. */
.nav-head {
  padding-bottom: 1rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--klc-border);
}

/* Identity block */
.nav-identity {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--klc-radius);
  /* No frame of its own - it belongs to the header above it, and lights up only
     when you are about to click it. */
  background-color: transparent;
  border: 1px solid transparent;
  transition: background-color var(--klc-fast) var(--klc-ease);
}

.nav-identity:hover,
.nav-identity:focus {
  background-color: var(--klc-surface-2);
}

.nav-identity:hover {
  border-color: var(--klc-brand);
}

.nav-avatar {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  object-fit: cover;
  background-color: var(--klc-surface-3);
  flex: 0 0 auto;
}

.nav-identity-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
}

.nav-identity-hint {
  font-size: 0.6875rem;
  color: var(--klc-accent-strong);
  font-weight: 600;
}

.nav-identity-name {
  font-size: 0.875rem;
  font-weight: 650;
  color: var(--klc-text);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Rail links */
/* Two boxes rather than two entries in the list: neither is a screen you read.
   One asks for something, the other hands the app your roster, and both are
   reached for far more often than anything below them. */
.nav-actions {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  margin-bottom: 1rem;
}

.nav-action {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
  padding: 0.625rem 0.75rem;
  border-radius: var(--klc-radius);
  border: 1px solid var(--klc-border);
  background-color: var(--klc-surface-2);
  color: var(--klc-text);
  font-size: 0.8125rem;
  font-weight: 600;
  transition: border-color var(--klc-fast) var(--klc-ease),
    background-color var(--klc-fast) var(--klc-ease);
}

.nav-action:hover,
.nav-action:focus {
  border-color: var(--klc-brand-strong);
  color: var(--klc-text);
  text-decoration: none;
}

.nav-action > .bi {
  flex: 0 0 auto;
  font-size: 1rem;
  color: var(--klc-brand-strong);
}

.nav-action > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The one that asks carries the warm colour, in the box and on its edge. The
   brand orange itself for the icon: 3.14:1 on the rail, which is the threshold an
   icon answers to rather than the one text does. */
.nav-action.is-support > .bi {
  color: var(--klc-accent);
}

.nav-action.is-support:hover,
.nav-action.is-support:focus,
.nav-action.is-support.is-current {
  border-color: var(--klc-accent-strong);
  background-color: var(--klc-accent-soft);
}

/* That soft fill takes the icon down to 2.83, so on these states it steps back to
   the darkened one. */
.nav-action.is-support:hover > .bi,
.nav-action.is-support:focus > .bi,
.nav-action.is-support.is-current > .bi {
  color: var(--klc-accent-strong);
}

.nav-section {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.nav-section-quiet {
  margin-top: 1.25rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--klc-border);
}

.nav-section-title {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--klc-text-muted);
  padding: 0 0.75rem 0.375rem;
}

.nav-link-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5625rem 0.75rem;
  border-radius: var(--klc-radius-sm);
  color: var(--klc-text-secondary);
  font-size: 0.875rem;
  font-weight: 500;
  transition: background-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease);
}

.nav-link-row .bi {
  font-size: 1.0625rem;
  width: 1.25rem;
  flex: 0 0 1.25rem;
  text-align: center;
  color: var(--klc-text-muted);
  transition: color var(--klc-fast) var(--klc-ease);
}

.nav-link-row:hover {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
}

.nav-link-row:hover .bi {
  color: var(--klc-text-secondary);
}

/* The heart on the bottom bar keeps its own colour, until it is the page you
   are on and the current state takes over. */
.bottom-nav-item:not(.is-current) > .bi-heart-fill {
  /* The brand orange itself rather than the darkened one kept for text: this is an
     icon, and 3.14:1 on the bar clears the threshold that applies to one. */
  color: var(--klc-accent);
}

.nav-link-row.is-current {
  background-color: var(--klc-brand-soft);
  color: var(--klc-brand-deep);
  font-weight: 650;
}

.nav-link-row.is-current .bi {
  color: var(--klc-brand);
}

/* The third group: your own account and the way out. It holds several rows now,
   so it stacks like the sections above it and carries the same rule. */
.nav-footer {
  margin-top: auto;
  padding-top: 0.875rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  border-top: 1px solid var(--klc-border);
}

/* The roster upload row while its form is submitting. */
.nav-link-row.is-busy {
  opacity: 0.6;
  pointer-events: none;
}

.nav-link-signout {
  margin-top: 0.5rem;
}

.nav-link-signout:hover {
  background-color: var(--klc-danger-soft);
  color: var(--klc-danger);
}

.nav-link-signout:hover .bi {
  color: var(--klc-danger);
}

/* Scrim behind the sheet */
.nav-scrim {
  position: fixed;
  inset: 0;
  background: var(--klc-overlay);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--klc-normal) var(--klc-ease);
  z-index: 1037;
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

body.nav-open .nav-scrim {
  opacity: 1;
  pointer-events: auto;
}

@media (min-width: 768px) {
  .nav-scrim {
    display: none;
  }
}

/* ---- Content column ------------------------------------------------------ */

#page-content-wrapper {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* Room for the bottom bar and the home indicator. */
  padding-bottom: calc(var(--klc-bottomnav-height) + 1rem + env(safe-area-inset-bottom));
}

@media (min-width: 768px) {
  #page-content-wrapper {
    margin-left: var(--klc-sidebar-width);
    padding-bottom: 0;
  }
}

#page-content-wrapper > .container-fluid {
  max-width: 64rem;
  width: 100%;
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--klc-gutter);
  padding-right: var(--klc-gutter);
  flex: 1 0 auto;
}

@media (min-width: 768px) {
  :root {
    --klc-gutter: 2rem;
  }
}

/* --------------------------------------------------------------------------
   5. Header
   Page title, theme switch, and a strip that answers "what is next?" without
   making anyone read a list.
   -------------------------------------------------------------------------- */

.app-header {
  position: sticky;
  top: 0;
  z-index: 1020;
  background-color: var(--klc-surface);
  border-bottom: 1px solid var(--klc-border);
  /* Added to the home screen, iOS draws the page under a translucent status
     bar. The header's background fills that strip; its controls sit below it.
     In a browser tab the inset is 0, so this costs nothing there. */
  padding-top: env(safe-area-inset-top);
  /* No backdrop-filter here: it would create a stacking context that traps the
     header's own dropdowns below the bottom bar. z-index alone is enough. */
}

.app-header-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  max-width: 64rem;
  margin: 0 auto;
  width: 100%;
}

/* Page-level controls sit beside the theme switch. */
.app-header-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex: 0 0 auto;
}

.app-header-actions:empty {
  display: none;
}

@media (min-width: 768px) {
  .app-header-bar {
    padding: 0.875rem 2rem;
  }
}

.app-title {
  flex: 1;
  margin: 0;
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--klc-text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon-btn {
  border-radius: var(--klc-radius-sm);
  border: 1px solid var(--klc-border);
  background-color: var(--klc-surface);
  color: var(--klc-text-secondary);
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex: 0 0 auto;
  font-size: 1rem;
  line-height: 1;
  transition: background-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease),
    border-color var(--klc-fast) var(--klc-ease);
}

.icon-btn:hover {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
  border-color: var(--klc-border-strong);
}

/* A switch rather than a button. Held down it carries the brand, so which way
   it stands is readable without opening anything. */
.icon-btn[aria-pressed="true"] {
  background-color: var(--klc-brand-soft);
  border-color: var(--klc-brand);
  color: var(--klc-brand-strong);
}


/* --------------------------------------------------------------------------
   6. Bottom navigation (phone)
   -------------------------------------------------------------------------- */

.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Under the TSAT widget, which is transient and more urgent, and under the
     modal backdrop at 1040. */
  z-index: 1030;
  display: flex;
  background-color: var(--klc-surface);
  border-top: 1px solid var(--klc-border);
  padding-bottom: env(safe-area-inset-bottom);
}

@media (min-width: 768px) {
  .bottom-nav {
    display: none;
  }
}

.bottom-nav-item {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.1875rem;
  padding: 0.5rem 0.25rem 0.5625rem;
  border: 0;
  background: transparent;
  color: var(--klc-text-muted);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: -0.002em;
  min-height: 3.25rem;
  transition: color var(--klc-fast) var(--klc-ease);
}

.bottom-nav-item .bi {
  font-size: 1.1875rem;
  line-height: 1;
}

.bottom-nav-item:hover,
.bottom-nav-item:focus {
  color: var(--klc-text-secondary);
}

.bottom-nav-item.is-current {
  color: var(--klc-brand-strong);
}

/* The current tab is marked by more than its colour: a short rule on the top
   edge, the way a tab bar signs itself, so the state survives a screen that
   washes the hue out and a reader who cannot separate the two blues. */
.bottom-nav-item.is-current::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 1.75rem;
  height: 2px;
  border-radius: 0 0 2px 2px;
  background-color: currentColor;
}

.bottom-nav-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* Theme switch. Shows the state you are in, and cycles auto -> light -> dark.
   Auto is the absence of data-theme, so it is also what you get before anyone
   has ever touched the control. */
.theme-toggle .theme-icon-auto,
.theme-toggle .theme-icon-light,
.theme-toggle .theme-icon-dark {
  display: none;
}

:root:not([data-theme]) .theme-toggle .theme-icon-auto {
  display: inline-block;
}

:root[data-theme="light"] .theme-toggle .theme-icon-light {
  display: inline-block;
}

:root[data-theme="dark"] .theme-toggle .theme-icon-dark {
  display: inline-block;
}

/* Sizes that used to be inline: the settings avatar, the Jetlog QR, the wide
   METAR dialog, and the rotated plane between two airports. */
#settingsProfilePictureImg {
  width: 7.5rem;
  height: 7.5rem;
}

#METARTAFModal .modal-dialog {
  max-width: 50rem;
}

.flight-leg-icon {
  display: inline-block;
  transform: rotate(90deg);
  color: var(--klc-brand);
  /* On the icon rather than in the markup, so "SVG > AMS" cannot end up as
     "SVG>AMS" the next time the string is rebuilt. */
  margin-inline: 0.45em;
}

/* The flight list is the positioning context for its own overlays. */
#flight-list {
  position: relative;
  /* Nothing above the first day heading: at the top of the scroll it then sits
     exactly where it sticks, so there is no state to scroll back to in which it
     appears unstuck. */
  padding-top: 0;
}

/* Crew photos: the rail, the colleague lists and the settings cropper. */
.profilePicture {
  border-radius: 50%;
  border: 2px solid var(--klc-surface);
  box-shadow: 0 0 0 1px var(--klc-border);
  max-width: 100%;
  object-fit: cover;
  background-color: var(--klc-surface-3);
}

/* --------------------------------------------------------------------------
   7. Cards
   The workhorse: flight list, weather, allowances, settings all use these.
   -------------------------------------------------------------------------- */

.card {
  background-color: var(--klc-surface);
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  box-shadow: var(--klc-shadow-sm);
  transition: box-shadow var(--klc-normal) var(--klc-ease),
    border-color var(--klc-normal) var(--klc-ease),
    transform var(--klc-normal) var(--klc-ease);
}

/* Cancelled: the red runs through the card rather than sitting in one line of
   it. Border, the rule under the header and the route line all take the tint,
   which is enough to read at a glance without filling anything in. */
.flight-card.is-cancelled {
  border-color: var(--klc-danger-line);
}

.flight-card.is-cancelled .flight-body > .flight-row:first-child {
  border-bottom-color: var(--klc-danger-line);
}

.flight-card.is-cancelled .flight-leg,
.flight-card.is-cancelled .flight-leg::before,
.flight-card.is-cancelled .flight-leg::after {
  background: var(--klc-danger-line);
}

.flight-card.is-cancelled .flight-leg .bi {
  color: var(--klc-danger);
}

/* Elevation on hover is reserved for cards that represent something live -
   a flight, a nightstop - rather than every block of static text. */
#flight-list .card:hover,
#weatherInfo .card:hover {
  box-shadow: var(--klc-shadow);
  border-color: var(--klc-border-strong);
}

.card-header {
  background-color: transparent;
  border-bottom: 1px solid var(--klc-border);
  font-weight: 650;
  font-size: 0.9375rem;
  letter-spacing: -0.01em;
  padding: 0.875rem 1.125rem;
  border-radius: var(--klc-radius) var(--klc-radius) 0 0 !important;
  color: var(--klc-text);
}

.card-body {
  padding: 1.125rem;
}

.card-footer {
  background-color: var(--klc-surface-2);
  border-top: 1px solid var(--klc-border);
  padding: 0.875rem 1.125rem;
  border-radius: 0 0 var(--klc-radius) var(--klc-radius);
}

.card-title {
  font-weight: 650;
}

/* The flight list stacks many cards with mb-1; give them room to breathe. */
#flight-list .card {
  margin-bottom: 0.75rem !important;
}

/* Bare lists inside a card are used as tables of contents on Settings and
   Help. Bullets and browser indentation make them look like an afterthought. */
.card-body > ul {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
}

.card-body > ul > li {
  border-bottom: 1px solid var(--klc-border);
}

.card-body > ul > li:last-child {
  border-bottom: 0;
}

.card-body > ul > li > a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.625rem 0.125rem;
  color: var(--klc-text);
  font-weight: 500;
}

.card-body > ul > li > a::after {
  content: "\F285";
  font-family: "bootstrap-icons";
  font-size: 0.75rem;
  color: var(--klc-text-muted);
  transition: transform var(--klc-fast) var(--klc-ease);
}

.card-body > ul > li > a:hover {
  color: var(--klc-brand-strong);
}

.card-body > ul > li > a:hover::after {
  transform: translateX(2px);
  color: var(--klc-brand);
}

/* --------------------------------------------------------------------------
   8. Buttons
   -------------------------------------------------------------------------- */

.btn {
  border-radius: var(--klc-radius-sm);
  font-weight: 550;
  font-size: 0.875rem;
  letter-spacing: -0.006em;
  padding: 0.5rem 0.9375rem;
  /* 44px: the minimum a thumb can hit reliably. */
  min-height: 2.75rem;
  border-width: 1px;
  transition: background-color var(--klc-fast) var(--klc-ease),
    border-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease),
    box-shadow var(--klc-fast) var(--klc-ease),
    transform var(--klc-fast) var(--klc-ease);
}

/* A button tall enough to need it centres its own label. Inputs are centred by
   the browser, so they are left alone. Being a flex container also means a
   literal space between an icon and its label collapses away, so the gap has to
   come from the button rather than from the markup. */
a.btn,
button.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

a.btn.btn-block,
button.btn.btn-block {
  display: flex;
}

.btn:active {
  transform: translateY(1px);
}

.btn-sm {
  padding: 0.4375rem 0.8125rem;
  font-size: 0.8125rem;
  border-radius: var(--klc-radius-sm);
  min-height: 2.25rem;
  position: relative;
}

/* Keeps the chip small while the thumb still gets its 44px. */
.btn-sm::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2.75rem;
  transform: translateY(-50%);
}

.btn-lg {
  padding: 0.6875rem 1.25rem;
  font-size: 1rem;
  border-radius: var(--klc-radius);
}

.btn-block + .btn-block {
  margin-top: 0.625rem;
}

.btn-primary {
  background-color: var(--klc-brand);
  border-color: var(--klc-brand);
  color: var(--klc-text-on-brand);
  box-shadow: var(--klc-shadow-sm);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:not(:disabled):not(.disabled):active {
  background-color: var(--klc-brand-strong);
  border-color: var(--klc-brand-strong);
  color: var(--klc-text-on-brand);
}

.btn-success {
  background-color: var(--klc-success);
  border-color: var(--klc-success);
  color: var(--klc-text-on-brand);
}

.btn-success:hover,
.btn-success:not(:disabled):not(.disabled):active {
  filter: brightness(0.93);
  background-color: var(--klc-success);
  border-color: var(--klc-success);
  color: var(--klc-text-on-brand);
}

.btn-danger {
  background-color: var(--klc-danger);
  border-color: var(--klc-danger);
  color: var(--klc-text-on-brand);
}

.btn-danger:hover,
.btn-danger:not(:disabled):not(.disabled):active {
  filter: brightness(0.93);
  background-color: var(--klc-danger);
  border-color: var(--klc-danger);
  color: var(--klc-text-on-brand);
}

.btn-warning {
  background-color: var(--klc-warning);
  border-color: var(--klc-warning);
  color: var(--klc-text-on-brand);
}

.btn-info {
  background-color: var(--klc-info);
  border-color: var(--klc-info);
  color: var(--klc-text-on-brand);
}

.btn-secondary {
  background-color: var(--klc-surface-3);
  border-color: var(--klc-border-strong);
  color: var(--klc-text);
}

.btn-secondary:hover,
.btn-secondary:not(:disabled):not(.disabled):active {
  background-color: var(--klc-border);
  border-color: var(--klc-border-strong);
  color: var(--klc-text);
}

.btn-light {
  background-color: var(--klc-surface);
  border-color: var(--klc-border);
  color: var(--klc-text);
}

.btn-light:hover {
  background-color: var(--klc-surface-3);
  border-color: var(--klc-border-strong);
  color: var(--klc-text);
}

.btn-dark {
  background-color: var(--klc-text);
  border-color: var(--klc-text);
  color: var(--klc-surface);
}

.btn-outline-primary {
  color: var(--klc-brand-strong);
  border-color: var(--klc-brand);
  background-color: transparent;
}

.btn-outline-primary:hover {
  background-color: var(--klc-brand);
  border-color: var(--klc-brand);
  color: var(--klc-text-on-brand);
}

.btn-outline-secondary {
  color: var(--klc-text-secondary);
  border-color: var(--klc-border-strong);
}

.btn-outline-secondary:hover {
  background-color: var(--klc-surface-3);
  border-color: var(--klc-border-strong);
  color: var(--klc-text);
}

.btn-outline-danger {
  color: var(--klc-danger);
  border-color: var(--klc-danger);
}

.btn-outline-danger:hover {
  background-color: var(--klc-danger);
  border-color: var(--klc-danger);
  color: var(--klc-text-on-brand);
}

.btn:disabled,
.btn.disabled {
  opacity: 0.55;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   9. Badges
   Used heavily in the flight list for times, gates, registrations and ranks.
   -------------------------------------------------------------------------- */

.badge {
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0;
  padding: 0.3125rem 0.5625rem;
  border-radius: var(--klc-radius-sm);
  vertical-align: baseline;
  border: 1px solid transparent;
}

.badge-primary {
  background-color: var(--klc-brand-soft);
  color: var(--klc-brand-deep);
  border-color: transparent;
}

.badge-secondary {
  background-color: var(--klc-surface-3);
  color: var(--klc-text-secondary);
}


.badge-dark {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
  border-color: var(--klc-border-strong);
}

a.badge:hover {
  filter: brightness(0.96);
  color: inherit;
}

.badge s {
  opacity: 0.6;
  font-weight: 500;
}

/* --------------------------------------------------------------------------
   10. Forms
   -------------------------------------------------------------------------- */

label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--klc-text-secondary);
  margin-bottom: 0.375rem;
  letter-spacing: -0.004em;
}

.form-group {
  margin-bottom: 1.125rem;
}

/* Heading for a set of controls, where a <label> would have to point at one of
   them and so would be wrong. Reads identically to a label. */
.group-label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--klc-text-secondary);
  margin-bottom: 0.375rem;
  letter-spacing: -0.004em;
}

.form-control {
  background-color: var(--klc-surface);
  border: 1px solid var(--klc-border-strong);
  border-radius: var(--klc-radius-sm);
  color: var(--klc-text);
  font-size: 0.9375rem;
  padding: 0.5625rem 0.75rem;
  height: auto;
  transition: border-color var(--klc-fast) var(--klc-ease),
    box-shadow var(--klc-fast) var(--klc-ease),
    background-color var(--klc-fast) var(--klc-ease);
}

.form-control::placeholder {
  color: var(--klc-text-muted);
}

.form-control:focus {
  background-color: var(--klc-surface);
  border-color: var(--klc-brand);
  color: var(--klc-text);
}

.form-control:disabled,
.form-control[readonly] {
  background-color: var(--klc-surface-3);
  color: var(--klc-text-muted);
  cursor: not-allowed;
}

select.form-control {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23808f9e' stroke-width='2' stroke-linecap='round'%3e%3cpath d='M4 6l4 4 4-4'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.625rem center;
  background-size: 1rem;
  padding-right: 2.25rem;
}

.form-text {
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
  margin-top: 0.375rem;
}

.input-group-text {
  background-color: var(--klc-surface-3);
  border: 1px solid var(--klc-border-strong);
  color: var(--klc-text-secondary);
  border-radius: var(--klc-radius-sm);
  font-size: 0.875rem;
}

/* Checkboxes and switches */

.form-check-input {
  accent-color: var(--klc-brand);
}

.form-check-label {
  font-weight: 500;
  color: var(--klc-text);
}

/* File inputs. Bootstrap 4 leaves the native control inside a .form-control box,
   which looks nothing like the rest of the form. Used by the roster upload,
   support tickets, the profile photo and the LogTen monthly overview. */
input[type="file"].form-control,
input[type="file"] {
  padding: 0.4375rem 0.75rem;
  height: auto;
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
  cursor: pointer;
}

input[type="file"]::file-selector-button {
  appearance: none;
  border: 1px solid var(--klc-border-strong);
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
  border-radius: var(--klc-radius-sm);
  padding: 0.3125rem 0.75rem;
  margin-right: 0.75rem;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--klc-fast) var(--klc-ease);
}

input[type="file"]::file-selector-button:hover {
  background-color: var(--klc-border);
}

/* Textareas need room to be worth typing into. */
textarea.form-control {
  min-height: 6rem;
  line-height: 1.55;
  resize: vertical;
}

/* Date and time controls keep the theme's colour scheme for their pickers. */
input[type="date"].form-control,
input[type="time"].form-control,
input[type="datetime-local"].form-control {
  min-height: 2.625rem;
}

input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.7;
}

input[type="date"]:hover::-webkit-calendar-picker-indicator {
  opacity: 1;
}

/* Checkbox and switch rows. Subscriptions and Settings are largely lists of
   these, and Bootstrap 4 leaves them cramped and hard to hit on a phone. */
/* Two columns: the box, then the label with its description underneath. A grid
   keeps the box on the same line as the label whatever the label wraps to,
   which Bootstrap's absolutely-positioned input could not do once the label
   grew tall enough to be a comfortable tap target. */
.form-check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.75rem;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-2);
  margin-bottom: 0.5rem;
  transition: border-color var(--klc-fast) var(--klc-ease);
}

.form-check:hover {
  border-color: var(--klc-border-strong);
}

.form-check .form-check-input {
  position: static;
  grid-column: 1;
  grid-row: 1;
  /* Centred against the label's first line, not the whole row. */
  align-self: center;
  margin: 0;
  width: 1.125rem;
  height: 1.125rem;
}

.form-check .form-check-label {
  position: relative;
  grid-column: 2;
  grid-row: 1;
  display: flex;
  align-items: center;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--klc-text);
  cursor: pointer;
}

/* The label keeps its natural height, and reaches out to 44px for the thumb.
   Making the label itself 44px tall bought the same target but spent a whole
   line of blank space per row to do it - with sixteen of these in the app, on
   the recipients list most of the dialog. */
.form-check .form-check-label::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2.75rem;
  transform: translateY(-50%);
}

.form-check .form-text {
  grid-column: 2;
  grid-row: 2;
  margin-top: 0;
  margin-bottom: 0.125rem;
  line-height: 1.45;
}

/* Some options carry two things: what they do, and a caution about doing it.
   Running them together as one grey paragraph buries the half that matters. */
.form-note {
  grid-column: 2;
  grid-row: 3;
  display: flex;
  gap: 0.4375rem;
  margin-top: 0.5rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-warning-soft);
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--klc-text-secondary);
}

.form-note > .bi {
  flex: 0 0 auto;
  margin-top: 0.0625rem;
  color: var(--klc-warning);
}

/* A calendar URL is something you copy, so make it look copyable. */
code {
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
  padding: 0.3125rem 0.5rem;
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-3);
  border: 1px solid var(--klc-border);
  color: var(--klc-text-secondary);
  font-size: 0.75rem !important;
  line-height: 1.4;
}

/* Dividers inside a card should be lighter than the card edge. */
.card-body hr {
  margin: 1rem -1.125rem;
  border-top-color: var(--klc-border);
}

/* Legacy <center> elements still used on a few pages. */
center {
  display: block;
  text-align: center;
}

/* --------------------------------------------------------------------------
   11. Third-party widgets
   intl-tel-input and cropper.js load their own stylesheets after this one and
   only know about prefers-color-scheme, so an explicit theme choice desynced
   them: a white country list with light text on it, unreadable.
   -------------------------------------------------------------------------- */

/* intl-tel-input wraps the field in .iti, which ships as inline-block. Left
   alone the phone field sits beside its label at its own width, unlike every
   other control on the form. */
.iti {
  display: block;
  width: 100%;
}

.iti__country-list {
  background-color: var(--klc-surface) !important;
  border: 1px solid var(--klc-border) !important;
  border-radius: var(--klc-radius) !important;
  box-shadow: var(--klc-shadow-lg) !important;
  color: var(--klc-text) !important;
}

.iti__country {
  color: var(--klc-text) !important;
}

.iti__dial-code {
  color: var(--klc-text-muted) !important;
}

.iti__country.iti__highlight,
.iti__country:hover {
  background-color: var(--klc-surface-3) !important;
}

.iti__divider {
  border-bottom-color: var(--klc-border) !important;
}

.iti__selected-flag:hover,
.iti__selected-flag:focus {
  background-color: var(--klc-surface-3) !important;
}

/* Cropper: its checkerboard and view box assume a light page. */
/* Holds the crop preview; empty until a file is chosen. */
.crop-preview img {
  max-width: 100%;
}

.cropper-bg {
  background-image: none !important;
  background-color: var(--klc-surface-3) !important;
}

.cropper-view-box {
  outline-color: var(--klc-brand) !important;
}

.cropper-line,
.cropper-point {
  background-color: var(--klc-brand) !important;
}

/* Bootstrap's own switch, used in the mail dialog. The design system coloured
   the checked and focus states but left the unchecked track as a white pill
   with a grey knob, which is a bright spot on a dark modal. */

/* Switching web push on for this device, in the panel that also switches it off
   again - rather than a floating bell on another page. */
.push-device {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.875rem;
}

.push-device-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.push-device-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--klc-text);
}

.push-device-state {
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
}

.push-device .btn {
  flex: 0 0 auto;
}

/* The init asks for no bell, but the dashboard can put it back and does, so the
   app refuses it here too. Switching notifications on lives in Settings now, one
   tap from the switch that turns them off; a launcher floating over the flight
   list is a second control for the same thing, positioned and styled from a
   dashboard rather than from this stylesheet.
   
   To bring it back it takes two things: the dashboard, and removing this rule.
   And then it needs the two below it again, which are kept for that reason. The
   z-index has to go on the container rather than the launcher inside it, because
   OneSignal puts the container at 2147483000 and that makes it a stacking context
   of its own - ordering the launcher within it says nothing about where that
   context sits on the page, and setting the launcher alone left the bell over
   every dialog. */
#onesignal-bell-container {
  display: none !important;
}

#onesignal-bell-container.onesignal-reset {
  z-index: 1032 !important;
}

@media (max-width: 767.98px) {
  #onesignal-bell-container.onesignal-bell-container-bottom-left .onesignal-bell-launcher,
  #onesignal-bell-container.onesignal-bell-container-bottom-right .onesignal-bell-launcher {
    bottom: calc(var(--klc-bottomnav-height) + 1rem + env(safe-area-inset-bottom)) !important;
  }
}

/* --------------------------------------------------------------------------
/* --------------------------------------------------------------------------
   12. Alerts
   -------------------------------------------------------------------------- */

.alert {
  border-radius: var(--klc-radius);
  border: 1px solid transparent;
  padding: 0.875rem 1.125rem;
  font-size: 0.875rem;
  box-shadow: var(--klc-shadow-sm);
}

.alert strong,
.alert b {
  font-weight: 650;
}

.alert-info {
  background-color: var(--klc-info-soft);
  border-color: transparent;
  color: var(--klc-info);
}

.alert-success {
  background-color: var(--klc-success-soft);
  border-color: transparent;
  color: var(--klc-success);
}

.alert-warning {
  background-color: var(--klc-warning-soft);
  border-color: transparent;
  color: var(--klc-warning);
}

.alert-danger {
  background-color: var(--klc-danger-soft);
  border-color: transparent;
  color: var(--klc-danger);
}

/* Flash messages after a redirect. */
.flash {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
}

.flash-icon {
  flex: 0 0 auto;
  font-size: 1rem;
  margin-top: 0.0625rem;
}

.flash-body {
  flex: 1;
  min-width: 0;
}

.flash-dismiss {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: -0.5rem -0.75rem -0.5rem 0;
  font-size: 0.8125rem;
  line-height: 1;
  cursor: pointer;
  min-width: 2.25rem;
  min-height: 2.25rem;
}

.flash-dismiss:hover {
  opacity: 1;
}

.alert a {
  color: inherit;
  text-decoration: underline !important;
  font-weight: 650;
}

/* A callout carries a warning inside a dialog, where a full-width alert would
   fight the dialog's own padding. */
.callout {
  display: flex;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  border-radius: var(--klc-radius);
  margin-bottom: 1rem;
  font-size: 0.875rem;
}

.callout > .bi {
  font-size: 1.0625rem;
  flex: 0 0 auto;
  margin-top: 0.0625rem;
}

.callout p {
  margin: 0.25rem 0 0;
  color: inherit;
  opacity: 0.9;
}

.callout-warning {
  background-color: var(--klc-warning-soft);
  color: var(--klc-warning);
}

/* Numbered instructions, used where a paragraph of bolded fragments was. */
.steps {
  margin: 0 0 1rem;
  padding-left: 1.125rem;
  font-size: 0.875rem;
  color: var(--klc-text-secondary);
}

.steps li {
  margin-bottom: 0.4375rem;
}

.steps li::marker {
  color: var(--klc-text-muted);
  font-weight: 650;
}

/* A long procedure needs its step numbers to be findable, so you can put the
   page down and come back to step 14. */
.steps.steps-numbered {
  list-style: none;
  padding-left: 0;
  counter-reset: step;
}

.steps.steps-numbered li {
  position: relative;
  padding-left: 2.25rem;
  margin-bottom: 0.75rem;
  counter-increment: step;
}

.steps.steps-numbered li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: -0.0625rem;
  width: 1.625rem;
  height: 1.625rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: var(--klc-surface-3);
  color: var(--klc-text-secondary);
  font-size: 0.75rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* Stacked footer for dialogs where the actions are a choice of paths rather
   than confirm/cancel. */
.modal-footer-stacked {
  flex-direction: column;
  align-items: stretch;
}

.modal-footer-stacked .btn-block {
  margin: 0;
}

/* --------------------------------------------------------------------------
   13. Modals
   -------------------------------------------------------------------------- */

/* Added to the home screen the page runs under the status bar, and two of the
   dialogs are top-aligned rather than centred. */
.modal {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

.modal-content {
  background-color: var(--klc-surface);
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-lg);
  box-shadow: var(--klc-shadow-lg);
}

.modal-header {
  border-bottom: 1px solid var(--klc-border);
  padding: 1.125rem 1.25rem;
  border-radius: var(--klc-radius-lg) var(--klc-radius-lg) 0 0;
}

.modal-title {
  font-weight: 650;
  font-size: 1.0625rem;
}

/* Bootstrap pulls the close button out by its own -1rem, which no longer
   matches this header's padding and left the x sitting slightly high. */
.modal-header .close {
  padding: 1.125rem 1.25rem;
  margin: -1.125rem -1.25rem -1.125rem auto;
}

.modal-body {
  padding: 1.25rem;
}

.modal-footer {
  border-top: 1px solid var(--klc-border);
  padding: 0.875rem 1.25rem;
  gap: 0.5rem;
}

.modal-footer > * {
  margin: 0;
}

.modal-backdrop.show {
  opacity: 1;
  background-color: var(--klc-overlay);
}

.close {
  color: var(--klc-text-muted);
  opacity: 1;
  text-shadow: none;
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1;
  transition: color var(--klc-fast) var(--klc-ease);
}

.close:hover {
  color: var(--klc-text);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   14. Dropdowns
   -------------------------------------------------------------------------- */

.dropdown-menu {
  background-color: var(--klc-surface);
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  box-shadow: var(--klc-shadow-lg);
  padding: 0.375rem;
  font-size: 0.875rem;
  color: var(--klc-text);
}

.dropdown-item:hover,
.dropdown-item:focus {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
}

/* Header menus: theme and list layout. Both show which option is in force
   rather than making you cycle blind through the states. */
.header-menu {
  flex: 0 0 auto;
  line-height: 0;
}

.header-menu .dropdown-menu {
  min-width: 12rem;
  margin-top: 0.375rem;
}

.dropdown-header {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--klc-text-muted);
  padding: 0.375rem 0.75rem 0.25rem;
  margin: 0;
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.5rem 0.75rem;
  border: 0;
  border-radius: var(--klc-radius-sm);
  background: transparent;
  color: var(--klc-text);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}

.dropdown-item > .bi {
  font-size: 1rem;
  width: 1.125rem;
  flex: 0 0 1.125rem;
  color: var(--klc-text-muted);
}

.dropdown-item.is-selected {
  color: var(--klc-brand-deep);
  font-weight: 650;
  background-color: var(--klc-brand-soft);
}

.dropdown-item.is-selected > .bi {
  color: var(--klc-brand);
}

/* A tick on the right marks the active option without shifting the label. */
.dropdown-item.is-selected::after {
  content: "\F272";
  font-family: "bootstrap-icons";
  margin-left: auto;
  font-size: 0.8125rem;
  color: var(--klc-brand);
}

.dropdown-divider {
  border-top-color: var(--klc-border);
}

/* --------------------------------------------------------------------------
   15. App-specific
   -------------------------------------------------------------------------- */

.form_block {
  padding: 1.5rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-lg);
  background-color: var(--klc-surface);
  box-shadow: var(--klc-shadow);
}

/* TSAT widget.
   A card pinned to the foot of the page on duty days. The number that matters
   is how long you have, so that is the one set large; the TSAT and the clock
   are the supporting facts, and the track underneath shows where in the
   ten minute window you actually are. */
.tsat-widget {
  position: fixed;
  left: 0.75rem;
  right: 0.75rem;
  margin-inline: auto;
  max-width: 30rem;
  /* Stacks on top of the bottom navigation on a phone. */
  bottom: calc(var(--klc-bottomnav-height) + 0.75rem + env(safe-area-inset-bottom));
  display: grid;
  gap: 0.375rem;
  padding: 0.5rem 0.875rem 0.625rem;
  background-color: var(--klc-surface);
  border: 1px solid var(--klc-border);
  border-left: 4px solid var(--tsat-tone);
  border-radius: var(--klc-radius-lg);
  box-shadow: var(--klc-shadow-lg);
  font-variant-numeric: tabular-nums;
  /* Above the bottom bar so it is not hidden behind it, below the modal
     backdrop (1040) so a modal still covers it. */
  z-index: 1035;
  /* The script only toggles opacity; the curve belongs here. */
  transition: opacity var(--klc-normal) var(--klc-ease);

  --tsat-tone: var(--klc-brand-strong);
}

/* Waiting for the window, inside it, about to lose it, past it, and no longer
   being refreshed. */
.tsat-widget.blue { --tsat-tone: var(--klc-brand-strong); }
.tsat-widget.green { --tsat-tone: var(--klc-success); }
.tsat-widget.amber { --tsat-tone: var(--klc-warning); }
.tsat-widget.red { --tsat-tone: var(--klc-danger); }
.tsat-widget.is-stale { --tsat-tone: var(--klc-text-muted); }

@media (min-width: 768px) {
  .tsat-widget {
    /* The rail is fixed and paints over this (1038 against 1035), so a card
       spanning the whole viewport had its left third hidden behind it. Sit in
       the content column instead, where it lines up with the list above. */
    left: calc(var(--klc-sidebar-width) + 2rem);
    right: 2rem;
    bottom: 1rem;
  }
}

.tsat-widget-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.tsat-widget-state {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--tsat-tone);
}

.tsat-widget-header {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--klc-text);
  white-space: nowrap;
}

.tsat-widget-route {
  margin-left: 0.4375rem;
  font-weight: 600;
  color: var(--klc-text-muted);
}

.tsat-widget-body {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
}

.tsat-widget-count {
  flex: 0 0 auto;
  /* Three CDM times sit beside this now instead of two clocks, and each of them
     carries a Z: the figure gives up the width for them, and it can afford to.
     It is the only thing on the card set this large. */
  font-size: 1.5rem;
  line-height: 1;
  font-weight: 700;
  font-family: var(--klc-font-mono);
  letter-spacing: -0.02em;
  color: var(--klc-text);
}

.tsat-widget.red .tsat-widget-count,
.tsat-widget.is-stale .tsat-widget-count {
  color: var(--klc-text-muted);
}

.tsat-widget-left {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  min-width: 0;
  overflow: hidden;
}

.tsat-widget-caption {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--klc-text-muted);
  /* A second line here would take the whole card down with it, so this gives
     way rather than wrapping. */
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tsat-widget-facts {
  display: flex;
  gap: 0.5rem;
  flex: 0 0 auto;
}


/* On the narrowest phones the countdown, the caption and the two clocks cannot
   all share a line. The caption is the one that can go: the heading above it
   already says whether the window is starting, running or over. */
@media (max-width: 26rem) {
  .tsat-widget-caption {
    display: none;
  }
}

.tsat-widget-facts > div {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.tsat-widget-time-label {
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--klc-text-muted);
}

.tsat-widget-time {
  font-size: 0.875rem;
  font-weight: 700;
  font-family: var(--klc-font-mono);
  letter-spacing: -0.02em;
  color: var(--klc-text);
}

/* The ten minutes around the TSAT, and where you are in them. */
.tsat-widget-track {
  position: relative;
  height: 3px;
  border-radius: 2px;
  background-color: var(--klc-surface-3);
}

.tsat-widget-track > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--tsat-progress, 0) * 100%);
  border-radius: 2px;
  background-color: var(--tsat-tone);
}

.tsat-widget-track > em {
  position: absolute;
  top: 50%;
  left: calc(var(--tsat-progress, 0) * 100%);
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background-color: var(--tsat-tone);
  box-shadow: 0 0 0 2px var(--klc-surface);
}

.tsat-widget.is-stale .tsat-widget-track > em {
  display: none;
}

/* Before the window starts there is nothing to measure, and a marker parked at
   the left edge for an hour only repeats what the heading already says. The
   track breathes instead, so it reads as waiting rather than as broken. */
.tsat-widget.is-waiting .tsat-widget-track > i {
  width: 100%;
  animation: tsat-waiting 2.6s ease-in-out infinite;
}

.tsat-widget.is-waiting .tsat-widget-track > em {
  display: none;
}

/* The card is the tone itself for every state that is still about a window you
   can act on, so a page left open cannot be misread at a glance. Only a TSAT
   that is no longer being refreshed drops back to a plain card. */
.tsat-widget.is-loud {
  background-color: var(--tsat-tone);
  border-color: var(--tsat-tone);
  color: var(--klc-text-on-brand);
  box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.3);
}

.tsat-widget.is-loud .tsat-widget-state,
.tsat-widget.is-loud .tsat-widget-header,
.tsat-widget.is-loud .tsat-widget-count,
.tsat-widget.is-loud .tsat-widget-time {
  color: var(--klc-text-on-brand);
}

.tsat-widget.is-loud .tsat-widget-route,
.tsat-widget.is-loud .tsat-widget-caption,
.tsat-widget.is-loud .tsat-widget-time-label {
  color: var(--klc-text-on-brand);
  opacity: 0.72;
}

/* The track has to be drawn out of the text colour now: the tone is the
   background, and how light or dark the text sits on it flips with the theme. */
.tsat-widget.is-loud .tsat-widget-track {
  background-color: transparent;
}

.tsat-widget.is-loud .tsat-widget-track::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-color: currentColor;
  opacity: 0.3;
}

.tsat-widget.is-loud .tsat-widget-track > i,
.tsat-widget.is-loud .tsat-widget-track > em {
  background-color: currentColor;
}

.tsat-widget.is-loud .tsat-widget-track > em {
  box-shadow: 0 0 0 2px var(--tsat-tone);
}

/* The last two minutes are the ones worth interrupting for. */
.tsat-widget.amber::after {
  content: "";
  position: absolute;
  inset: -0.1875rem;
  border: 2px solid var(--tsat-tone);
  /* The card's radius plus the distance it sits outside it. Inheriting the
     radius put the same curve on a larger box, so the ring kept its distance
     along the sides but closed in on the card at every corner. */
  border-radius: calc(var(--klc-radius-lg) + 0.1875rem);
  pointer-events: none;
  animation: tsat-attention 1.8s ease-in-out infinite;
}

@keyframes tsat-attention {
  0%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 0.7;
  }
}

@keyframes tsat-waiting {
  0%,
  100% {
    opacity: 0.12;
  }

  50% {
    opacity: 0.4;
  }
}

/* Only a visible widget claims extra room at the foot of the page. */
@media (max-width: 767.98px) {
  body:has(.tsat-widget:not([style*="display: none"])) #page-content-wrapper {
    padding-bottom: calc(var(--klc-bottomnav-height) + 9rem + env(safe-area-inset-bottom));
  }
}

@media (min-width: 768px) {
  body:has(.tsat-widget:not([style*="display: none"])) #page-content-wrapper {
    padding-bottom: 9.5rem;
  }
}
/* A status line: who it is about, the status chip, and the planned time.
   Previously one 41-character sentence crammed into a single badge. */
/* --------------------------------------------------------------------------
   Flight status, laid out like a boarding pass.

   A coloured edge carries the state. The time and the gate are set large
   because they are the two things you actually look for; everything else is
   one quiet line beneath, so the block stays short enough that the crew list
   below it is still on screen. A departure and an arrival share the shape.
   -------------------------------------------------------------------------- */
.fstatus {
  border-left: 3px solid var(--klc-border);
  padding-left: 0.8125rem;
}

/* Only when the block is about a different flight than the card it sits in. A
   departure describes itself, so it stays plain card content; an inbound is the
   Amsterdam leg of another aeroplane, and gets a ground of its own to say so.
   Placed before the tone rules, which set the left border colour after it. */
.fstatus.is-inbound {
  border: 1px solid var(--klc-border);
  border-left-width: 3px;
  border-radius: var(--klc-radius);
  background-color: var(--klc-surface-2);
  padding: 0.75rem 0.875rem;
}

.fstatus.is-ontime {
  border-left-color: var(--klc-success);
}

.fstatus.is-delayed,
.fstatus.is-cancelled {
  border-left-color: var(--klc-danger);
}

.fstatus-subject {
  display: block;
  margin-bottom: 0.3125rem;
  font-size: 0.6875rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text-muted);
}

/* Names the flight the panel is about, with its own rule under it: everything
   below belongs to that flight, not to the card around it. */
.fstatus.is-inbound .fstatus-subject {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  margin: -0.125rem 0 0.5rem;
  padding-bottom: 0.4375rem;
  border-bottom: 1px solid var(--klc-border);
}

.fstatus-subject-what {
  flex: 0 0 auto;
}

.fstatus-subject strong {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  color: var(--klc-text);
}

.fstatus-subject-route {
  margin-left: auto;
  letter-spacing: 0.02em;
  color: var(--klc-text-secondary);
}

/* Two rows across three columns: every label on one line, every value on the
   next. Placed explicitly, so a flight with no gate or no walking time leaves an
   empty cell rather than sliding the remaining values sideways. */
.fstatus-main {
  display: grid;
  /* Status, walking time, then the pair on the right: whichever of gate and
     stand is secondary, and the one you are actually heading for at the edge. */
  grid-template-columns: auto auto 1fr auto auto;
  column-gap: 1.25rem;
  row-gap: 0.0625rem;
  /* On the baseline, not the bottom edge: these are four different type sizes
     and one of them is a padded sign, so aligning boxes leaves the figures
     sitting at four slightly different heights. */
  align-items: baseline;
}

.fstatus-word {
  grid-area: 1 / 1;
}

.fstatus-walk-label {
  grid-area: 1 / 2;
}

.fstatus-time {
  grid-area: 2 / 1;
}

.fstatus-walk-time {
  grid-area: 2 / 2;
}

.fstatus-second-label {
  grid-area: 1 / 4;
  justify-self: end;
}

.fstatus-second-value {
  grid-area: 2 / 4;
  justify-self: end;
  /* Named and legible, but plainly the one you are not walking to. */
  color: var(--klc-text-muted);
}

.fstatus-gate-label {
  grid-area: 1 / 5;
  justify-self: end;
}

.gate-sign {
  grid-area: 2 / 5;
  justify-self: end;
}

.fstatus-was {
  grid-area: 3 / 1;
}

/* Four values on one line is comfortable on a modern phone and tight on a small
   one, so the gutter closes up first. */
@media (max-width: 575.98px) {
  .fstatus-main {
    column-gap: 0.75rem;
  }
}

/* Below this they genuinely cannot share a line. Rather than squeeze the type
   until it is unreadable, or let it run past the card and be clipped, the pair
   moves under the times and keeps its own labels. */
@media (max-width: 359.98px) {
  .fstatus-main {
    grid-template-columns: auto auto 1fr;
    row-gap: 0.375rem;
  }

  .fstatus-second-label {
    grid-area: 3 / 1;
    justify-self: start;
  }

  .fstatus-second-value {
    grid-area: 4 / 1;
    justify-self: start;
  }

  .fstatus-gate-label {
    grid-area: 3 / 2;
    justify-self: start;
  }

  .gate-sign {
    grid-area: 4 / 2;
    justify-self: start;
  }

  .fstatus-was {
    grid-area: 5 / 1;
  }
}

.fstatus-word,
.fstatus-walk-label,
.fstatus-second-label,
.fstatus-gate-label {
  font-size: 0.6875rem;
  font-weight: 650;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text-muted);
}

/* The departure time carries the state as well as the word above it. It is the
   figure the eye lands on, and on time or not is the thing it should say
   without being read. That holds once the aeroplane has gone too: the time it
   actually went is the answer to the same question, so it takes the same two
   colours. En route has no time to show, and neither has a cancellation, where
   the word stands in for one. */
.fstatus.is-ontime .fstatus-word,
.fstatus.is-ontime .fstatus-time,
.fstatus.is-done-ontime .fstatus-word,
.fstatus.is-done-ontime .fstatus-time {
  color: var(--klc-success-strong);
}

.fstatus.is-delayed .fstatus-word,
.fstatus.is-cancelled .fstatus-word,
.fstatus.is-delayed .fstatus-time,
.fstatus.is-cancelled .fstatus-time,
.fstatus.is-done-late .fstatus-word,
.fstatus.is-done-late .fstatus-time {
  color: var(--klc-danger);
}

/* A cancelled flight has no time to show, so the word stands in for one and is
   set like one. A small caption over an empty value row would be introducing
   something that is not there. */
.fstatus.is-cancelled .fstatus-word {
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  text-transform: none;
}

/* The four values in this row are one rhythm: same size, same weight, same
   line. They were 1.5, 1.25, 1.125 and 1.25rem with two different line heights
   between them, which is what made the row look assembled rather than set.
   Rank comes from position, colour and the sign - not from size. */
.fstatus-time,
.fstatus-walk-time,
.fstatus-second-value,
.gate-sign {
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.fstatus-time {
  color: var(--klc-text);
}

.fstatus-was {
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--klc-text-muted);
}

/* When to leave for the gate. Second only to the departure time, and the one
   value here you actually act on, so it sits beside it rather than in the run of
   details underneath. */
.fstatus-walk-label {
  font-size: 0.6875rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text-muted);
}

.fstatus-walk-time {
  color: var(--klc-text);
}

/* The gate as the terminal shows it: yellow, dark type, and the bus or the
   walker on the sign so you know before you look it up. */
.gate-sign {
  /* inline-block rather than inline-flex: a flex container takes its baseline
     from its first item, which here is the icon, so the sign would line up on
     the bus rather than on the figures beside it. */
  display: inline-block;
  padding: 0.0625rem 0.4375rem;
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-gate);
  color: var(--klc-gate-ink);
}

.gate-sign > .bi {
  margin-right: 0.375rem;
  /* Set by eye against the rendered sign, not calculated: the glyph does not sit
     where its em box suggests, so deriving this from cap heights put it two
     pixels low. It is in the icon's own em, which is what vertical-align
     resolves against, and it holds for an icon of 0.875rem beside figures of
     1.375rem. Change either size and it wants looking at again. */
  vertical-align: 0.17em;
  font-size: 0.875rem;
  /* The bus is the one worth noticing: it is the gate that costs you time. */
  opacity: 0.75;
}

.gate-sign.is-bus > .bi {
  opacity: 1;
}

/* Everything else, on one line, quiet. */
.fstatus-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.75rem;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--klc-text-secondary);
}

.meta-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  white-space: nowrap;
}

.meta-item > .bi {
  font-size: 0.8125rem;
  line-height: 1;
  color: var(--klc-text-muted);
}

/* Everything else on this line is a plain fact about the flight. A delay is the
   reason the times above it moved, so it carries the same red they do and takes
   the icon with it, instead of leaving it muted like the rest. */
.meta-item.is-delay {
  color: var(--klc-danger);
}

.meta-item.is-delay > .bi {
  color: inherit;
}

.meta-dot {
  flex: 0 0 auto;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background-color: var(--klc-border-strong);
}

/* A link in that line still has to be reachable with a thumb. */
/* Boxed rather than set as a sentence: the schedule is not a reading of how this
   flight is going, it is what the timetable says, and on its own under the row it
   read as a loose thought. Same frame the day chain and the deadhead marker use. */
.meta-item.is-chip {
  /* Two type sizes in one box needs both halves of the fix. On the baseline, so
     the label sits on the line the figures stand on rather than floating a
     fraction above it. And more padding above than below, because baseline
     alignment leaves the descender space of the larger size sitting empty under
     the figures - which is what put the whole chip's ink high in its box. */
  align-items: baseline;
  padding: 0.3125rem 0.375rem 0.1875rem;
  /* Hung by its own inset so the label starts on the same line as the word above
     it. Boxed text is otherwise indented by its padding, and against a column of
     figures that reads as a wonky row. */
  margin-left: calc(-0.375rem - 1px);
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-3);
}

.meta-chip-label {
  /* The row sets a reading line-height, which inside a box leaves descender space
     under text that has no descenders. Both parts hug their letters instead, so
     the padding is what you see. */
  line-height: 1;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  /* Letter-spacing is added after the last letter too, which widens the gap to
     the time by exactly that much. Take it back. */
  margin-right: -0.05em;
  text-transform: uppercase;
  color: var(--klc-text-muted);
}

.meta-chip-value {
  line-height: 1;
  font-weight: 700;
  color: var(--klc-text);
}

.meta-link {
  position: relative;
  color: var(--klc-brand-strong);
  font-weight: 600;
}

.meta-link > .bi {
  color: inherit;
}

.meta-link::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2.75rem;
  transform: translateY(-50%);
}

.meta-link:hover {
  color: var(--klc-brand);
}

/* Things you can open, shaped so they do not read as data. */
.fstatus-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.875rem;
}

.flight-action {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  min-height: 2.25rem;
  padding: 0 0.875rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-pill);
  background-color: var(--klc-surface);
  color: var(--klc-brand-strong);
  font-size: 0.8125rem;
  font-weight: 600;
}

.flight-action::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2.75rem;
  transform: translateY(-50%);
}

.flight-action:hover {
  background-color: var(--klc-brand-soft);
  border-color: var(--klc-brand);
  color: var(--klc-brand-strong);
}


/* METAR/TAF highlighting */
.bg-avwx-warning { color: var(--klc-warning); font-weight: 650; }
.bg-avwx-danger { color: var(--klc-danger); font-weight: 650; }

/* Times, flight numbers and registrations line up when they're tabular. */
.badge,
.tsat-widget-time,
.table td {
  font-variant-numeric: tabular-nums;
}

/* Embedded video: the instruction clip carried a fixed 640x360, which is wider
   than any phone. */
.video-embed {
  width: 100%;
  max-width: 40rem;
  height: auto;
  aspect-ratio: 16 / 9;
  border-radius: var(--klc-radius);
  background-color: #000;
  display: block;
  margin: 0 auto;
}

/* Instructional screenshots: bounded by the column, never wider. The previous
   markup combined img-fluid with a 350px width and a 1rem margin either side,
   which overflowed a 360px phone by about 32px. */
.help-figure {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
  margin: 1rem auto;
  border-radius: var(--klc-radius);
  border: 1px solid var(--klc-border);
}

/* Nothing in a card should be able to push the page sideways. */
.card-body img,
.card-body video,
.card-body iframe {
  max-width: 100%;
}

/* The pairing code sits in its own column beside the manual key. */
.jetlog-last-used {
  margin-top: 0.75rem;
  margin-bottom: 0.75rem;
}

.qr-code-image {
  display: block;
  /* Centred in its column, whatever the column is doing around it. */
  margin-inline: auto;
  width: 9.375rem;
  height: auto;
  aspect-ratio: 1;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  /* The QR needs a light plate to stay scannable in dark mode. */
  background: #fff;
  padding: 0.5rem;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background-color: var(--klc-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(1.25rem + env(safe-area-inset-top)) 1.25rem
    calc(1.25rem + env(safe-area-inset-bottom));
  /* Was an inline 999999999999999. Above the Bootstrap modal layer is enough. */
  z-index: 1060;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* The colleague card.
   Opened from a crew row on the flight list and from the search page. A
   centred dialog with room for it, a sheet from the bottom edge on a phone -
   which is where it is actually reached for, one-handed. */
.colleague-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--klc-surface);
  padding: 1.5rem 1.25rem 1.25rem;
  border-radius: var(--klc-radius-lg);
  width: min(21rem, calc(100vw - 2.5rem));
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  box-shadow: var(--klc-shadow-lg);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: colleague-in var(--klc-normal) var(--klc-ease) both;
}

/* A band of brand colour behind the top of the card, for the avatar to sit on. */
.colleague-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  /* Behind the upper part of the photograph, which starts below the card's
     1.5rem of top padding. */
  height: 10rem;
  z-index: -1;
  background-image: linear-gradient(165deg, var(--klc-brand-soft), var(--klc-surface) 92%);
}

@keyframes colleague-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem) scale(0.97);
  }
}

@media (max-width: 575.98px) {
  .modal-overlay {
    align-items: flex-end;
    padding: 0;
  }

  .colleague-card {
    width: 100%;
    max-width: none;
    max-height: 88vh;
    border-radius: var(--klc-radius-lg) var(--klc-radius-lg) 0 0;
    padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
    animation-name: colleague-sheet-in;
  }
}

@keyframes colleague-sheet-in {
  from {
    transform: translateY(100%);
  }
}

.colleague-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 2.25rem;
  height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  color: var(--klc-text-muted);
  font-size: 0.8125rem;
  cursor: pointer;
}

.colleague-close:hover,
.colleague-close:focus-visible {
  background-color: var(--klc-surface-2);
  color: var(--klc-text);
}

/* The card is where you look someone up to see who they are, so the photograph
   is the card. 14rem asks for more than the 400 x 400 an upload is cropped to
   (change_profile_pic.php) on a 2x screen or denser, so a poor photograph can
   look soft - which reads better than a sharp one too small to recognise. */
.colleague-card .profilePicture {
  width: 14rem;
  height: 14rem;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--klc-border), var(--klc-shadow-sm);
}

.colleague-name {
  font-size: 1.0625rem;
  margin: 0.75rem 0 0;
  overflow-wrap: anywhere;
}

.colleague-rank {
  display: flex;
  gap: 0.375rem;
  justify-content: center;
  margin-top: 0.4375rem;
}

/* The card is a small space; the standard divider is spaced for a page. */
.colleague-card .divider-label {
  margin: 1rem 0 0.75rem;
}

/* The mail dialog.
   Recipients are rows you tap, not a list of checkboxes: the input still drives
   the state and carries the value, but it is the row that responds. */
.mail-recipients {
  margin-bottom: 1.25rem;
}

.mail-recipients-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.mail-recipients-head .group-label {
  margin: 0;
}

.mail-recipients-count {
  flex: 1;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--klc-text-muted);
}

.mail-recipients-toggle {
  padding: 0.125rem 0.5rem;
  border: 0;
  border-radius: var(--klc-radius-sm);
  background-color: transparent;
  color: var(--klc-brand-strong);
  font-size: 0.75rem;
  font-weight: 650;
  cursor: pointer;
}

.mail-recipients-toggle:hover,
.mail-recipients-toggle:focus-visible {
  background-color: var(--klc-surface-2);
}

.mail-recipient-list {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.mail-recipient {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  padding: 0.4375rem 0.625rem;
  min-height: 2.75rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  background-color: var(--klc-surface);
  cursor: pointer;
  transition: background-color var(--klc-fast) var(--klc-ease),
    border-color var(--klc-fast) var(--klc-ease);
}

/* The input still holds the value the form posts; the row is the control. */
.mail-recipient-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.mail-recipient .profilePicture {
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 auto;
  border-radius: 50%;
}

.mail-recipient-name {
  flex: 1;
  min-width: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--klc-text);
  overflow-wrap: anywhere;
}

.mail-recipient .badge {
  flex: 0 0 auto;
}

.mail-recipient-check {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--klc-border-strong);
  border-radius: 50%;
  color: transparent;
  font-size: 0.6875rem;
}

.mail-recipient:has(.mail-recipient-input:checked) {
  border-color: var(--klc-brand);
  background-color: var(--klc-brand-soft);
}

.mail-recipient:has(.mail-recipient-input:checked) .mail-recipient-check {
  border-color: var(--klc-brand-strong);
  background-color: var(--klc-brand-strong);
  color: var(--klc-text-on-brand);
}

.mail-recipient:has(.mail-recipient-input:focus-visible) {
  box-shadow: 0 0 0 3px var(--klc-brand-soft);
}

@media (max-width: 575.98px) {
  /* Pinned to the bottom edge rather than pushed there by a flex box that has
     to be exactly as tall as the viewport - which on a phone it never quite
     is, so the sheet stopped short and left a band of page below it. */
  #messageColleaguesModal .modal-dialog {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    max-width: none;
    min-height: 0;
  }

  #messageColleaguesModal .modal-content {
    max-height: 88vh;
    border-radius: var(--klc-radius-lg) var(--klc-radius-lg) 0 0;
    border-bottom: 0;
  }

  /* The dialog stops growing and scrolls instead, so it never takes the whole
     screen for a message nobody has typed yet. */
  #messageColleaguesModal .modal-body {
    overflow-y: auto;
    padding: 1rem 1.25rem;
  }

  #messageColleaguesModal textarea.form-control {
    min-height: 6.5rem;
  }

  #messageColleaguesModal .modal-footer {
    padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
  }
}

.colleague-actions {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

.colleague-action {
  flex: 1 1 4rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-height: 3.625rem;
  padding: 0.5rem 0.25rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  background-color: var(--klc-surface-2);
  color: var(--klc-text-secondary);
  font-size: 0.6875rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  transition: background-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease);
}

.colleague-action .bi {
  font-size: 1.0625rem;
  line-height: 1;
  color: var(--klc-brand-strong);
}

.colleague-action.is-whatsapp .bi {
  color: #25d366;
}

.colleague-action:hover,
.colleague-action:focus-visible {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
  text-decoration: none;
}

/* The flight they were on last, as a row rather than two stacked headings. */
.colleague-flight {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 0.875rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius);
  background-color: var(--klc-surface-2);
  color: var(--klc-text);
  text-align: left;
}

.colleague-flight:hover,
.colleague-flight:focus-visible {
  background-color: var(--klc-surface-3);
  text-decoration: none;
}

.colleague-flight-number {
  font-weight: 700;
  font-size: 0.875rem;
  white-space: nowrap;
}

.colleague-flight-route {
  flex: 1;
  min-width: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--klc-text-muted);
  white-space: nowrap;
}

.colleague-flight-out {
  flex: 0 0 auto;
  font-size: 0.75rem;
  color: var(--klc-text-muted);
}

.noscroll {
  overflow: hidden;
}

.copyright p {
  margin: 0;
}

.copyright p + p {
  margin-top: 0.25rem;
}

.copyright .copyright-version,
.copyright .copyright-credits {
  font-size: 0.75rem;
  color: var(--klc-text-muted);
}

.copyright p:last-child {
  margin-top: 0.75rem;
}

.copyright {
  padding: 1.5rem 1rem 1rem;
  margin-top: auto;
  font-size: 0.8125rem;
  text-align: center;
  color: var(--klc-text-muted);
}

.copyright a {
  color: var(--klc-text-secondary);
}

/* --------------------------------------------------------------------------
   16. Flight list
   One card per duty. The route is the headline, the date is a quiet index down
   the left, and live status sits underneath as colour-led chips.
   -------------------------------------------------------------------------- */

.flight-card {
  overflow: hidden;
  /* A floor for anything that scrolls a card into view on its own. The one place
     that does it deliberately - collapsing a long crew list - measures the header
     and the day heading instead, because a heading with a rest line on it is close
     to twice the height of a bare one and no single value clears both. */
  scroll-margin-top: var(--klc-anchor-offset);
  /* The crew faces count down from a z-index of 20 so the cockpit paints on top
     of the stack. Without a stacking context of its own that ordering is page
     wide, and the avatars rise over the day heading they scroll under. */
  position: relative;
  z-index: 0;
}

.flight-card .card-body {
  padding: 0;
}

.flight-body {
  display: flex;
  flex-direction: column;
}

/* Header row: date block + route */
.flight-body > .flight-row:first-child {
  display: flex;
  align-items: stretch;
  gap: 0;
  border-bottom: 1px solid var(--klc-border);
}

/* Two things at once, both about where this page's edges are.

   It starts flush against the header, so the first day heading is already at the
   position it sticks to. With a gap above it the reader can scroll back to a
   state the heading never occupies, which reads as the heading coming unstuck.

   And it hands the width cap to its children rather than taking it here: a day
   heading spans the whole column, tint and rules and all, which it cannot do
   from inside a container that is itself capped and centred. So the container
   runs the full width and everything inside it takes the same inset - the
   gutter, or whatever lines it up with a 64rem column, whichever is larger. */
#page-content-wrapper > .container-fluid.flight-page {
  max-width: none;
  padding-top: 0;
  padding-inline: 0;
  --column-inset: max(var(--klc-gutter), calc((100% - 64rem) / 2 + var(--klc-gutter)));
}

.flight-page > .alert,
/* The cards take the column inset; the two bands run the full width and set it as
   padding instead, so neither may take it twice. Cards now sit inside a per-day
   section, which is what lets each heading stop sticking at the end of its day. */
#flight-list > *:not(.flight-day):not(.flight-free):not(.flight-day-group),
.flight-day-group > *:not(.flight-day) {
  margin-inline: var(--column-inset);
}

.flight-page > .alert {
  margin-top: 1rem;
}

/* A day's sectors read as one block, with the date said once above them rather
   than on every card. That also gives each card the width the date block used
   to take, which the route and the flight number now have. */
.flight-day {
  position: sticky;
  /* One pixel higher than the header, so the band's own top border lands underneath
     the header's - which paints over it at a far greater z-index. Level with it, the
     two 1px rules stacked into a 2px line the moment a band pinned, while the first
     band of the list carries no top border and showed 1px. Same heading, two
     thicknesses, depending only on how far you had scrolled. */
  top: calc(var(--klc-header-height) - 1px);
  z-index: 2;
  /* Two columns: the date holds the left, the day's route the right. It used to
     be three with the route centred between the date and a sector count, but the
     chain says how many legs there are by having them. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.625rem;
  /* No margin, top or bottom. Above: the space between two days is carried by the
     section before this one, so a band stays pinned right up to the moment the next
     one arrives to take its place.

     Below: a sticky box is held by its *margin* box, so a bottom margin here ended
     the section a margin's width under the visible band. The outgoing band started
     sliding away while the incoming one was still that far short of touching it, and
     the strip of page colour between them stayed there for the whole handoff. With
     no margin the two meet edge to edge and one plainly pushes the other out. The
     space under the band belongs to the cards now. */
  margin-block: 0;
  padding-block: 0.4375rem 0.375rem;
  /* The page colour rather than a surface of its own: this is a heading, and a
     third filled band would compete with the cards it introduces. Opaque so the
     cards pass cleanly underneath it. */
  /* It sticks under the header, and in the page colour it slid under there almost
     unseen. A tint of its own marks it at any width - a rule at its leading edge
     only works on a phone, because the band runs the full window while the cards
     stay in a column, and on a monitor that rule ends up nowhere near them. */
  background-color: var(--klc-day-band);
  border-block: 1px solid var(--klc-border);
  /* The band and its rules run the full width while the date and the chain sit on
     the same line as the cards. */
  padding-inline: var(--column-inset);
  font-size: 0.75rem;
}

/* The first heading is already at its sticking position when the page loads, so
   its top rule would land on the header's own and read as one thick line. */
/* Reaches through the per-day section the heading now sits in. Without the
   section part this stopped matching, and the first heading came back with a top
   margin and a rule that lands on the header's own. */
#flight-list > .flight-day-group:first-child > .flight-day {
  margin-top: 0;
  border-top: 0;
}

/* The night before a day, said in that day's band. It is the reason the day is a
   new one, so it belongs on the same object rather than on a strip floating above
   it, competing with the band for the same boundary. */
.flight-day.has-rest {
  row-gap: 0.1875rem;
}

.flight-day-rest {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 0.3125rem;
  flex-wrap: wrap;
  padding-top: 0.3125rem;
  border-top: 1px dashed var(--klc-border-strong);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--klc-text-muted);
}

.flight-day-rest > .bi {
  font-size: 0.75rem;
  line-height: 1;
}

.flight-day-rest > .bi::before {
  display: block;
  vertical-align: 0;
}

.flight-day-rest b {
  color: var(--klc-text-secondary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Whole days with no duty on them. A band of their own rather than a duration on
   a strip: the list used to jump from Monday to Friday and leave the reader to
   work out what they had off. Dashed and unfilled, because nothing happens here. */
/* Days the rest ran straight through. Right-aligned under the day's route chip, so
   the band reads down its right edge: where the day goes, and what it cost to get
   there. Quieter than that chip - it is a footnote to the rest, not a heading. */
.flight-day-over {
  margin-left: auto;
  flex: 0 0 auto;
  padding: 0.0625rem 0.3125rem;
  border: 1px dashed var(--klc-border-strong);
  border-radius: var(--klc-radius-sm);
  font-weight: 700;
  color: var(--klc-text-secondary);
}

.flight-free {
  /* The same two columns as a day heading, so the date lands in the same place and
     the chip lines up with the one above it. Only the weight differs: dashed rules
     and no fill, because a day off should not sit as heavily in the list as a day
     that has something on it. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.5rem;
  margin-block: 0 0.5rem;
  padding-block: 0.4375rem 0.375rem;
  padding-inline: var(--column-inset);
  border-block: 1px dashed var(--klc-border-strong);
  /* Same size as a day heading, so the two chips are the same height and sit on the
     same line as each other down the right edge of the list. */
  font-size: 0.75rem;
  color: var(--klc-text-muted);
}

.flight-free-what {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.0625rem 0.375rem;
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-2);
  border: 1px solid var(--klc-border);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--klc-text-secondary);
}

.flight-free-what::before {
  display: block;
  vertical-align: 0;
}

.flight-free-when {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The gap between one day and the next, held inside the day it follows, so a band
   is never released before the next one has arrived. An ::after rather than padding
   on the section: a sticky box is confined to its container's content box and
   padding falls outside it, and the band would let go early all over again. */
/* The gap the band used to carry as a bottom margin. On the card it looks the same
   and stays out of the band's margin box, which is what the handoff is measured by. */
.flight-day-group > .flight-day + * {
  margin-top: 0.5rem;
}

.flight-day-group::after {
  content: "";
  display: block;
  height: 0.875rem;
}

.flight-day-name {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text);
}

/* Where the day begins and ends, taken across every sector in it. */
.flight-day-route {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.0625rem 0.375rem;
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-2);
  border: 1px solid var(--klc-border);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--klc-text-secondary);
}

/* A separator, not a connector: it says the stations follow one another without
   claiming there is a leg drawn between these two in particular. */
.flight-day-via {
  opacity: 0.4;
  font-weight: 400;
  margin-inline: 0.0625rem;
}

/* What sits between two flights: a turnaround, a night in a hotel, or time at
   home. What it is on the left, how long it lasts on the right, the rule
   between them. Underneath each other the durations line up into a column you
   can read down without reading the labels. */
.flight-gap {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.625rem 0;
  /* Set in from the cards on both sides, so the strip reads as something tucked
     between them rather than as a row of its own width. The inset matches the
     card's own gutter, which is where the route above it starts. */
  padding-inline: 1rem;
}

/* Set as a micro caption, the same as the day heading above it and the labels
   inside the card, so it reads as part of the furniture rather than as a line
   of prose that wandered between two cards. */
.flight-gap-what {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  flex: 0 0 auto;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--klc-text-muted);
}

.flight-gap-what > .bi {
  font-size: 0.875rem;
  /* Cancels the tracking on the glyph, which has no letters to space. */
  letter-spacing: 0;
  opacity: 0.75;
}

/* Bootstrap drops every icon by 0.125em so it sits well beside text of its own
   size. Here the row centres the icon against a smaller label itself, and that
   drop lands on top of the centring - so the glyph has to be given back its own
   box before it can be centred in it. */
.flight-gap-what > .bi::before {
  vertical-align: 0;
}

.flight-gap-rule {
  flex: 1;
  min-width: 0.75rem;
  border-top: 1px solid var(--klc-border);
}

/* Off duty: the line itself is broken. A turnaround keeps you at work and keeps
   its rule solid, so a week of trips has a visible shape before you read a word
   of it. */
.flight-gap.is-break .flight-gap-rule {
  border-top-style: dashed;
  border-top-color: var(--klc-border-strong);
}

.flight-gap-time {
  flex: 0 0 auto;
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--klc-text-secondary);
}

.flight-ident {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.875rem 1rem;
}

/* Route: the headline. Airport codes carry the meaning, so give them size. */
.flight-route {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  font-size: 1.375rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--klc-text);
}

.flight-port {
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

.flight-route > .flight-port:last-child {
  text-align: right;
}

/* What a duty day is, where the route would be. A size down from the airport
   codes, because a name or a roster code runs longer than three letters. */
.flight-duty {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--klc-text);
}

.flight-duty > .bi {
  font-size: 1rem;
  color: var(--klc-text-secondary);
}

/* The leg between the two ports, drawn rather than written. */
/* The leg fills whatever is left, so the two airports sit at the outer edges of
   the card rather than huddling on the left. */
.flight-leg {
  flex: 1 1 auto;
  position: relative;
  height: 1px;
  background: var(--klc-border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
}

.flight-leg::before,
.flight-leg::after {
  content: "";
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--klc-border-strong);
  top: 50%;
  transform: translateY(-50%);
}

.flight-leg::before { left: 0; }
.flight-leg::after { right: 0; }

.flight-leg .bi {
  position: relative;
  font-size: 0.8125rem;
  color: var(--klc-brand);
  background: var(--klc-surface);
  padding: 0 0.3125rem;
  transform: rotate(90deg);
}

.flight-meta {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
}

/* Which aeroplane sits at the far end of the line that names the flight: the two
   are the same kind of fact, and the space between them was empty anyway. It
   shrinks before the flight number does, because the number is the thing you
   are looking for. */
.flight-meta > .meta-item {
  margin-left: auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-block;
  font-size: 0.75rem;
}

.flight-meta > .meta-item > .bi {
  margin-right: 0.3125rem;
}

.flight-number {
  font-weight: 650;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

.flight-time {
  font-variant-numeric: tabular-nums;
}

/* The row aligns on the baseline, which would drop a three pixel dot onto it
   rather than beside the text. */
.flight-meta .meta-dot {
  align-self: center;
}

/* After the time, separated the way the boarding pass separates its meta line.
   This is where the DH/ prefix used to be buried in the number. */
.flight-dh {
  align-self: center;
  padding: 0.0625rem 0.3125rem;
  border: 1px solid var(--klc-border-strong);
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-3);
  color: var(--klc-text-secondary);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.5;
}

/* Status and crew sections */
.flight-span {
  padding: 0.875rem 1rem;
}

/* The crew row is always emitted, empty or not, so it gets no border or fill of
   its own - an empty strip would read as a stray bar. The divider appears only
   when the row actually holds a crew list or the message explaining why it
   does not. (The status row above it is emitted conditionally in JS, so it
   needs no equivalent.) */
.flight-body > .flight-row:last-child .flight-span {
  padding-top: 0;
}

/* The crew handle is the card's bottom edge, so the inset below it would show as
   a strip of card under the bar. Only when there is a crew list: the message
   explaining there is none still needs its margin. */
.flight-body > .flight-row:last-child .flight-span {
  padding-bottom: 0;
}

/* Chips that are actually links carry a tap target, not just a label. The
   padding stays visually the same; the reach is extended. */
a.badge {
  position: relative;
  padding-top: 0.4375rem;
  padding-bottom: 0.4375rem;
}

a.badge::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  min-height: 2.75rem;
}

/* Crew. The flight list and the colleagues page render a crew member the same
   way; the three layouts differ only in wrapper and avatar size. These rules
   replace a block of selectors that compensated for the old Bootstrap-grid
   markup rather than describing a component. */
.flight-span .profilePicture {
  border-width: 2px;
  box-shadow: 0 0 0 1px var(--klc-border);
}

/* Flight deck reads dark and cabin reads brand, both from the badge variants
   the rest of the app uses. An earlier edit folded those two selectors into the
   deadhead rule below, which gave every rank the deadhead colours and lost the
   distinction entirely. */
.crew-dh {
  background-color: var(--klc-surface-3);
  color: var(--klc-text-muted);
  border-color: var(--klc-border-strong);
}



/* Refresh spinner and connection state, pinned below the header rather than at
   hardcoded pixel offsets. */
/* The flight list refreshes itself every minute. A spinner appearing over the
   header each time read as something going wrong; a hairline along the header's
   bottom edge says "working" without asking to be looked at. Anchored to the
   header, so it needs no offset of its own. */
.app-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  overflow: hidden;
  pointer-events: none;
}

.app-progress::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 35%;
  border-radius: 2px;
  background-color: var(--klc-brand);
  animation: app-progress 1.1s var(--klc-ease) infinite;
}

@keyframes app-progress {
  from {
    transform: translateX(-110%);
  }

  to {
    transform: translateX(400%);
  }
}

/* Without motion the bar still has to read as activity, so it dims instead. */
@media (prefers-reduced-motion: reduce) {
  .app-progress::after {
    width: 100%;
    animation: app-progress-pulse 1.4s ease-in-out infinite;
  }

  @keyframes app-progress-pulse {
    0%, 100% {
      opacity: 0.25;
    }

    50% {
      opacity: 1;
    }
  }
}

/* Losing the connection is worth interrupting for, so this one is a chip that
   hangs below the header rather than a hairline. */
.app-offline {
  position: absolute;
  left: 50%;
  bottom: -0.9375rem;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.375rem;
  white-space: nowrap;
  padding: 0.25rem 0.75rem;
  border-radius: var(--klc-radius-pill);
  background-color: var(--klc-danger);
  color: var(--klc-text-on-brand);
  font-size: 0.75rem;
  font-weight: 650;
  box-shadow: var(--klc-shadow);
}

@media (max-width: 575.98px) {
  .flight-route {
    font-size: 1.1875rem;
  }

  .flight-ident,
  .flight-span {
    padding-left: 0.875rem;
    padding-right: 0.875rem;
  }
}

/* --------------------------------------------------------------------------
   17. Colleague search
   The whole page is one question, so the fields sit on a single line on a
   desktop and stack on a phone, with the flight number given the room.
   -------------------------------------------------------------------------- */

.search-panel .card-body {
  padding: 1.25rem;
}

.search-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
}

.search-field {
  display: flex;
  flex-direction: column;
  flex: 1 1 9rem;
  min-width: 0;
}

.search-field-flight {
  flex: 2 1 12rem;
}

.search-field label {
  margin-bottom: 0.375rem;
}

.search-field-flight .form-control {
  font-size: 1.125rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.search-submit {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5625rem 1.25rem;
  height: 2.75rem;
}

.search-note {
  display: flex;
  align-items: flex-start;
  gap: 0.4375rem;
  margin: 1rem 0 0;
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
}

.search-note .bi {
  color: var(--klc-brand);
  flex: 0 0 auto;
  margin-top: 0.125rem;
}

@media (max-width: 575.98px) {
  .search-field,
  .search-field-flight {
    flex: 1 1 100%;
  }

  .search-submit {
    width: 100%;
    justify-content: center;
  }
}

/* --------------------------------------------------------------------------
   18. iCal agenda's
   Creating one leads. Each existing calendar shows its state, its URL, and the
   one action that matters - subscribing - before sharing and settings.
   -------------------------------------------------------------------------- */

.ical-new-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}

.ical-new-row .form-control {
  flex: 1;
  min-width: 0;
}

.ical-new-row .btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  white-space: nowrap;
}

.ical-new-note {
  display: flex;
  align-items: flex-start;
  gap: 0.4375rem;
  margin: 0.875rem 0 0;
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
}

.ical-new-note .bi {
  color: var(--klc-brand);
  flex: 0 0 auto;
  margin-top: 0.125rem;
}

/* One card per calendar: what it is and how it is doing at the top, the two
   things you came to do with it next, and the settings folded away below in
   groups rather than nine boxes in a row. */
.ical-card {
  overflow: clip;
}

.ical-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--klc-border);
}

/* The calendar that names colleagues is the one you must not pass on, so it says
   so where the card introduces itself rather than somewhere in its body, and the
   card carries a mark of its own. */
.ical-head-text {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* The mark never wraps and never shrinks: a long calendar name gives way to it
   instead, so a flagged card stays exactly as tall as the rest. */
.ical-private {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  white-space: nowrap;
}

.ical-card.is-private {
  border-left: 3px solid var(--klc-brand-strong);
}

.ical-card.is-private .ical-head {
  background-color: var(--klc-brand-soft);
}

.ical-name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--klc-text);
}

/* Deleting a calendar is not the thing you came for, so it is quiet until you
   reach for it. */
.ical-delete {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--klc-radius-sm);
  color: var(--klc-text-muted);
  transition: background-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease);
}

.ical-delete:hover,
.ical-delete:focus {
  background-color: var(--klc-danger-soft);
  color: var(--klc-danger);
}

.ical-body {
  padding: 1rem;
}

/* How the sync is doing: a line rather than a badge, because it reports a state
   you glance at, not a label you act on. */
/* Both of these report what this calendar is doing, so they share a row and wrap
   together rather than one floating between the status and the address. */
.ical-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.625rem;
  margin-bottom: 0.875rem;
}

.ical-state-text {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.75rem;
  color: var(--klc-text-muted);
}

.ical-state-text > .bi {
  flex: 0 0 auto;
  font-size: 0.8125rem;
}

.ical-state.is-success .bi {
  color: var(--klc-success);
}

.ical-state.is-warning .bi {
  color: var(--klc-warning);
}

.ical-state.is-danger .bi {
  color: var(--klc-danger);
}

.ical-url {
  display: block;
  margin-bottom: 0.75rem;
  overflow-wrap: anywhere;
}

.ical-subscribe {
  margin-bottom: 0.5rem;
}

.ical-share {
  display: flex;
  gap: 0.5rem;
}

.ical-share .btn {
  flex: 1;
  min-width: 0;
}

/* A disclosure, not a fourth button: it sits under three of them, and a pill
   here read as one more thing to press. A rule above it and a chevron on the
   right say what it actually does. */
.ical-settings-toggle {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  margin-top: 0.875rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--klc-border);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--klc-text-secondary);
}

.ical-settings-toggle:hover,
.ical-settings-toggle:focus {
  color: var(--klc-brand-strong);
  text-decoration: none;
}

.ical-settings-chevron {
  margin-left: auto;
  font-size: 0.75rem;
  transition: transform var(--klc-fast) var(--klc-ease);
}

.ical-settings-toggle[aria-expanded="true"] .ical-settings-chevron {
  transform: rotate(180deg);
}

/* The form opens directly under the row that opened it, and needs the gap that
   row's own padding cannot give it. */
.ical-settings {
  padding-top: 0.875rem;
}

/* Nine options in a row is a wall. Four headings turn it into a list you can
   scan for the one you came to change. */
.ical-group {
  margin-bottom: 1.125rem;
}

.ical-group-title {
  display: block;
  margin-bottom: 0.4375rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--klc-text-muted);
}

.ical-group .form-group:last-child {
  margin-bottom: 0;
}

@media (max-width: 400px) {
  .ical-new-row {
    flex-direction: column;
  }

  .ical-new-row .btn {
    justify-content: center;
  }
}

/* Crew list, shared by the colleague search results. Row per person: photo,
   rank, name - so the ranks form a column you can scan. */
.result-head {
  padding: 0.25rem 0 1rem;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--klc-border);
}

.result-head .flight-route {
  font-size: 1.25rem;
  margin-bottom: 0.375rem;
}

/* The crew, collapsed.
   A row of faces overlapping by a quarter, so every one is still readable, each
   marked flight deck or cabin. This is the whole crew presentation now: the
   list, compact list and grid it replaced were three renderings of the same
   people, and expanding this covers what all three were for. */
/* No inset of its own: the faces line up with the handle's label below them and
   with everything above them, which is what stops them reading as loose. The
   room underneath is what they come to rest on. */
.crew-stack {
  width: 100%;
  display: grid;
  grid-template-rows: 1fr;
  padding: 0.5rem 0 0.75rem;
  border: 0;
  background-color: transparent;
  text-align: left;
  cursor: pointer;
  transition: grid-template-rows var(--klc-normal) var(--klc-ease),
              padding-block var(--klc-normal) var(--klc-ease);
}

/* Open, the stack has gone into the drawer: the row it sat on closes up behind
   it so nothing empty is left above the list. */
.crew.is-open .crew-stack {
  grid-template-rows: 0fr;
  padding-block: 0;
  pointer-events: none;
}

.crew-stack-faces {
  display: flex;
  /* Shrinkable, so a course or a positioning flight with eleven names tightens
     the stack instead of running off the card. */
  flex: 0 1 auto;
  min-width: 0;
  --face: 2.5rem;
  --slot: 1.875rem;
  /* The ring around each picture sits outside its slot, so hold it back before
     clipping the tail of a crew too long to fit any other way. */
  padding: 2px;
  margin: -2px;
  overflow: hidden;
  min-height: 0;
  transform-origin: left center;
  transition: opacity var(--klc-normal) var(--klc-ease),
              transform var(--klc-normal) var(--klc-ease);
}

/* Down and away as one piece, while the named rows unfold underneath. */
.crew.is-open .crew-stack-faces {
  opacity: 0;
  transform: translateY(1.25rem) scale(0.82);
}

/* Put back by a refresh rather than by a tap: the panel does not animate, so
   neither should the stack. */
.crew.is-restored .crew-stack-faces {
  transition: none;
}


/* Each face is a narrow slot holding a picture wider than itself; the overflow
   is the overlap. Squeeze the slots and the stack closes up on its own. */
.crew-face {
  position: relative;
  display: block;
  flex: 0 0 var(--slot);
  line-height: 0;
  /* Later siblings paint over earlier ones, which put the cockpit underneath
     the cabin. The crew arrives ranked, so counting down puts the front of the
     aeroplane on top of the stack. */
  z-index: calc(20 - var(--z, 0));
}

/* The last picture has nothing to overlap, so its slot holds the whole width. */
.crew-face:last-child {
  flex: 0 0 var(--face);
}

.crew-face .profilePicture {
  width: var(--face);
  height: var(--face);
  /* Overflowing the slot is the point, so the Bootstrap clamp has to go. */
  max-width: none;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--klc-surface);
}

/* What they are doing on this flight: up front, down the back, or travelling as
   a passenger. Measured from the top left, which is the one corner that stays
   put while the slots shrink. */
.crew-face::after {
  content: "";
  position: absolute;
  left: calc(var(--face) - 0.75rem);
  top: calc(var(--face) - 0.75rem);
  width: 0.6875rem;
  height: 0.6875rem;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--klc-surface);
}

.crew-face.is-deck::after {
  background-color: var(--klc-crew-deck);
}

.crew-face.is-cabin::after {
  background-color: var(--klc-crew-cabin);
}

/* Deadheading crew work neither side of the door today, and that is worth
   spelling out: a third colour of dot is not something anyone can decode. The
   marker widens into a chip, still anchored to the bottom right of the picture.
   The faces paint front-to-back, so a chip is never covered by its neighbour. */
.crew-face.is-dh::after {
  content: "DH";
  left: calc(var(--face) - 1.125rem);
  top: calc(var(--face) - 0.8125rem);
  width: 1.0625rem;
  height: 0.75rem;
  border-radius: 0.375rem;
  background-color: var(--klc-surface-3);
  box-shadow: 0 0 0 2px var(--klc-surface), inset 0 0 0 1px var(--klc-border-strong);
  font-size: 0.5rem;
  font-weight: 700;
  line-height: 0.75rem;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--klc-text-secondary);
}

/* The foot of the card: wall to wall, with a fill of its own so it belongs to
   the card rather than to the list. It is there on every card, whether or not
   anyone else on this flight uses the app, which is what keeps the weather from
   going missing along with the colleagues. */
.crew-foot {
  display: flex;
  align-items: stretch;
  /* Out through the inset the row carries, to meet the card on both sides. The
     card clips to its own radius, so the corners follow it. */
  width: calc(100% + 2rem);
  margin-inline: -1rem;
  border-top: 1px solid var(--klc-border);
  background-color: var(--klc-surface-2);
}

/* Left: the handle that opens the crew. It stays put - closed the stack sits
   above it, open the named list does - so nothing has to swap ends. */
.crew-nudge {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.5rem 1rem;
  border: 0;
  background-color: transparent;
  color: var(--klc-text-muted);
  cursor: pointer;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
  transition: background-color var(--klc-fast) var(--klc-ease),
              color var(--klc-fast) var(--klc-ease);
}

.crew-nudge:hover,
.crew-nudge:focus-visible {
  background-color: var(--klc-surface-3);
  color: var(--klc-text);
}

/* Nobody else on this flight uses the app. Still a strip rather than the dashed
   panel of three lines this used to be, on a card that has nothing to say. */
.crew-nudge.is-empty {
  cursor: default;
}

.crew-nudge.is-empty:hover {
  background-color: transparent;
  color: var(--klc-text-muted);
}

.crew-foot-split {
  flex: 0 0 1px;
  background-color: var(--klc-border);
  margin-block: 0.375rem;
}

/* Right: what belongs to the flight itself rather than to its status. */
.crew-foot-act {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.5rem 1rem;
  color: var(--klc-brand-strong);
  white-space: nowrap;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: background-color var(--klc-fast) var(--klc-ease);
}

.crew-foot-act:hover,
.crew-foot-act:focus-visible {
  background-color: var(--klc-surface-3);
  color: var(--klc-brand-strong);
  text-decoration: none;
}

.crew-nudge-what {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  min-width: 0;
}

/* The same three colours as the dots on the faces, so the make-up of the crew
   reads before the list is open at all. */
.crew-nudge-mix {
  display: inline-flex;
  align-items: center;
  gap: 0.1875rem;
}

.crew-nudge-mix i {
  display: block;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
}

.crew-nudge-mix .is-deck {
  background-color: var(--klc-crew-deck);
}

.crew-nudge-mix .is-cabin {
  background-color: var(--klc-crew-cabin);
}

/* The faces mark a deadhead with a "DH" chip rather than a third colour, for the
   reason given there: a third colour is not something anyone can decode. A chip
   will not fit at this size, so the marker here is the one that is not filled. */
.crew-nudge-mix .is-dh {
  background-color: transparent;
  box-shadow: inset 0 0 0 1.5px var(--klc-crew-dh);
}

.crew-nudge-chevron {
  flex: 0 0 auto;
  font-size: 0.75rem;
  line-height: 1;
  transition: transform var(--klc-normal) var(--klc-ease);
}

/* Bootstrap Icons pull ::before off the baseline by -0.125em, which on a strip
   this short reads as an arrow sitting high. */
.crew-nudge-chevron::before {
  display: block;
  vertical-align: 0;
}

.crew.is-open .crew-nudge-chevron {
  transform: rotate(180deg);
}

/* Transitioning to a height nobody has measured: the panel is a grid whose
   single row goes from 0fr to 1fr. */
.crew-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--klc-normal) var(--klc-ease);
}

.crew.is-open .crew-panel {
  grid-template-rows: 1fr;
}

.crew-panel-inner {
  overflow: hidden;
  min-height: 0;
}

/* The rows arrive one after another rather than all at once. */
.crew.is-open .crew-row {
  animation: crew-row-in var(--klc-normal) var(--klc-ease) both;
  animation-delay: calc(var(--i, 0) * 40ms);
}

/* Put back by a refresh rather than opened by hand: no stagger. The movement
   reads as an arrival, and nothing arrived. */
.crew.is-restored .crew-row {
  animation: none;
}

@keyframes crew-row-in {
  from {
    opacity: 0;
    transform: translateY(-0.25rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .crew-panel,
  .crew-stack,
  .crew-stack-faces,
  .crew-nudge-chevron {
    transition: none;
  }

  .crew.is-open .crew-row {
    animation: none;
  }
}

/* The crew section's heading, with mailing them as its action. */
.crew-head {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  margin-bottom: 0.25rem;
}

.crew-head-label {
  flex: 1;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--klc-text-muted);
}

.crew-mail {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.3125rem 0.5rem;
  border: 0;
  border-radius: var(--klc-radius-sm);
  background-color: transparent;
  color: var(--klc-brand-strong);
  font-size: 0.75rem;
  font-weight: 650;
  cursor: pointer;
  transition: background-color var(--klc-fast) var(--klc-ease);
}

.crew-mail .bi {
  font-size: 0.875rem;
}

.crew-mail:hover,
.crew-mail:focus-visible {
  background-color: var(--klc-surface-2);
}

/* The visible control is smaller than a thumb; the hit area is not. */
.crew-mail::after {
  content: "";
  position: absolute;
  inset: -0.4375rem -0.25rem;
}

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

/* Only on a crew too long to read down: which end of the aeroplane, and how many.
   A deadhead group past six takes the warning colour, because that is the number
   that says a roster has not been kept up rather than that a crew is travelling. */
.crew-group {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  margin: 0.5rem 0 0.125rem;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--klc-text-muted);
}

.crew-group::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: var(--klc-border);
}

.crew-group-count {
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.crew-group.is-many {
  color: var(--klc-warning-strong);
}

.crew-group:first-child {
  margin-top: 0.125rem;
}

.crew-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.25rem 0;
  border-bottom: 1px solid var(--klc-border);
  /* Anchor for the hit area below. */
  position: relative;
}

.crew-row:last-child {
  border-bottom: 0;
}

/* A heading draws its own rule, so the row above it should not draw a second. */
.crew-row:has(+ .crew-group) {
  border-bottom: 0;
}

.crew-avatar {
  flex: 0 0 auto;
  line-height: 0;
  display: flex;
  align-items: center;
  min-height: 2.25rem;
}

.crew-avatar .profilePicture {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
}

.crew-row .crew-rank + .crew-dh,
.crew-row .crew-dh {
  margin-left: -0.375rem;
}

.crew-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--klc-text);
  overflow-wrap: anywhere;
  display: flex;
  align-items: center;
  min-height: 2.25rem;
}

/* The rows are shorter than a comfortable tap target, so the name's hit area
   covers the whole row instead of its own text. The avatar beside it opens the
   same colleague, so nothing is lost by sitting over it. */
.crew-name::after {
  content: "";
  position: absolute;
  inset: 0;
}

.crew-name:hover {
  color: var(--klc-brand-strong);
}

/* Grote foto's: the same rows, laid out as tiles. Nobody remembers thirty names
   but everybody knows the faces, so the picture leads and the name follows it.
   The rows are unchanged - only the direction they run in and what size the
   photograph is - which is why this is a class over the list rather than a
   second rendering. */
#flight-list.is-crew-grid .crew-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: 0.875rem 0.5rem;
  padding: 0.375rem 0 0.25rem;
}

/* Wrapping rather than a column, so the rank and the deadhead chip stay beside
   each other on a line of their own: as siblings in a column they would stack. */
#flight-list.is-crew-grid .crew-row {
  flex-wrap: wrap;
  justify-content: center;
  /* From the top, not spread over the tile: a name that wraps to two lines makes
     its tile the tallest in the row, and centred content would then leave that
     row's photographs and chips each sitting at their own height. */
  align-content: flex-start;
  text-align: center;
  gap: 0.3125rem;
  padding: 0;
  border-bottom: 0;
}

#flight-list.is-crew-grid .crew-avatar,
#flight-list.is-crew-grid .crew-name {
  flex: 0 0 100%;
  justify-content: center;
  min-height: 0;
}

/* The whole point of the view. 4.5rem asks a shade more of the 400 x 400 an
   upload is cropped to than a 2x screen has to give, which is the trade the
   colleague card already makes: a soft face you recognise beats a sharp one
   you cannot. */
#flight-list.is-crew-grid .crew-avatar .profilePicture {
  width: 4.5rem;
  height: 4.5rem;
}

#flight-list.is-crew-grid .crew-name {
  font-size: 0.8125rem;
  line-height: 1.3;
}

/* A heading is about everything under it, so it takes the width of the list
   rather than a tile's worth of it. */
#flight-list.is-crew-grid .crew-group {
  grid-column: 1 / -1;
  margin: 0.375rem 0 0;
}

/* The gap already sets them apart; the overlap that closes it belongs to the
   row, where the two chips sit against the name. */
#flight-list.is-crew-grid .crew-row .crew-dh {
  margin-left: 0;
}

/* --------------------------------------------------------------------------
   19. Page index
   The jump list at the top of Instellingen and Help.
   -------------------------------------------------------------------------- */

.page-index {
  display: grid;
  gap: 0.375rem;
}

@media (min-width: 576px) {
  .page-index {
    grid-template-columns: 1fr 1fr;
    gap: 0.375rem 1rem;
  }
}

.page-index a {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--klc-radius-sm);
  color: var(--klc-text);
  font-weight: 500;
  font-size: 0.875rem;
  transition: background-color var(--klc-fast) var(--klc-ease),
    color var(--klc-fast) var(--klc-ease);
}

.page-index a .bi {
  color: var(--klc-text-muted);
  font-size: 1rem;
  width: 1.25rem;
  flex: 0 0 1.25rem;
  text-align: center;
}

.page-index a:hover {
  background-color: var(--klc-surface-3);
  color: var(--klc-brand-strong);
}

.page-index a:hover .bi {
  color: var(--klc-brand);
}

/* An icon in a section header makes a long page scannable. */
.card-header-icon {
  color: var(--klc-text-muted);
  margin-right: 0.5rem;
  font-size: 0.9375rem;
}

/* Deleting an account should not look like changing a preference. */
.card-danger {
  border-color: var(--klc-danger);
}

.card-danger .card-header {
  color: var(--klc-danger);
  border-bottom-color: var(--klc-danger);
  background-color: var(--klc-danger-soft);
}

.card-danger .card-header-icon {
  color: var(--klc-danger);
}

/* A registered device or passkey: what it is, when it was last used, and the
   one action that applies to it. Used instead of cards nested inside a card. */
.device-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--klc-border);
}

.device-row:last-of-type {
  border-bottom: 0;
}

.device-row-icon {
  font-size: 1.125rem;
  color: var(--klc-text-muted);
  flex: 0 0 auto;
}

.device-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.device-row-name {
  font-weight: 650;
  font-size: 0.9375rem;
  color: var(--klc-text);
  overflow-wrap: anywhere;
}

.device-row-meta {
  font-size: 0.75rem;
  color: var(--klc-text-muted);
  font-variant-numeric: tabular-nums;
}

.device-row form {
  flex: 0 0 auto;
  margin: 0;
}

/* A code you read out loud or type into another app. */
.pairing-code {
  font-family: var(--klc-font-mono);
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-align: center;
  color: var(--klc-text);
  background-color: var(--klc-surface-2);
  border: 1px dashed var(--klc-border-strong);
  border-radius: var(--klc-radius);
  padding: 1rem 0.75rem;
  margin: 0.875rem 0;
  user-select: all;
}

/* Save confirmation on Instellingen. Was two hand-rolled boxes pinned with a
   z-index of 9999999999, which painted over modals and the navigation. */
/* Transient feedback. Fixed above the bars, stacked so two at once never sit
   on top of each other. */
.toast-stack {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--klc-bottomnav-height) + 1rem + env(safe-area-inset-bottom));
  width: calc(100% - 2rem);
  max-width: 26rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 1036;
  pointer-events: none;
}

.toast-item {
  animation: toast-in var(--klc-normal) var(--klc-ease);
}

.toast-item.is-leaving {
  opacity: 0;
  transition: opacity 200ms var(--klc-ease);
}

.toast-item .alert {
  margin: 0;
  box-shadow: var(--klc-shadow-lg);
  pointer-events: auto;
}

/* A server flash may carry a line break; it arrives as text, not markup. */
.toast-item .flash-body {
  white-space: pre-line;
}

@media (min-width: 768px) {
  .toast-stack {
    /* The rail is fixed and paints over this (1038 against 1036), so a stack
       centred on the viewport had its left part hidden behind it on a desktop.
       Centre it in the content column instead, and keep it out from under the
       rail when that column is narrow. */
    left: calc(var(--klc-sidebar-width) + (100% - var(--klc-sidebar-width)) / 2);
    width: calc(100% - var(--klc-sidebar-width) - 2rem);
    bottom: 1.5rem;
  }
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Someone who has asked for less motion still gets the message. */
@media (prefers-reduced-motion: reduce) {
  .toast-item {
    animation: none;
  }
}

/* Rank and deadhead chips are a fixed column so the names line up. Twelve
   copies of an inline width used to do this. */
.crew-rank,
.badge.crew-rank {
  display: inline-flex;
  justify-content: center;
  width: 3.25rem;
  font-size: 0.6875rem;
}

.crew-dh,
.badge.crew-dh {
  display: inline-flex;
  justify-content: center;
  width: 2.5rem;
  font-size: 0.625rem;
  letter-spacing: 0.03em;
}

/* --------------------------------------------------------------------------
   20. Bijdragen
   The amount is the point of the page, so it leads. Declining stays available
   but does not compete with it.
   -------------------------------------------------------------------------- */

/* The one card in the app that asks for something, so it is allowed to glow a
   little - the same warm wash the amount block used to carry, moved to the card
   itself now that the block is gone. */
.donate-card {
  background:
    radial-gradient(120% 8rem at 50% 0%, var(--klc-accent-soft) 0%, transparent 70%),
    var(--klc-surface);
}

.donate-card .card-body {
  padding: 1.75rem 1.25rem 1.5rem;
}

.donate-title {
  margin: 0 0 0.5rem;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--klc-text);
}


.donate-intro {
  color: var(--klc-text-secondary);
  font-size: 0.875rem;
  margin-bottom: 1.5rem;
}

.donate-label {
  display: block;
  margin-bottom: 0.5rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--klc-text-muted);
}

/* Fixed amounts rather than a slider: picking three euros out of fifteen steps
   with a thumb is a fiddle on a phone, and one of these is a single tap. The
   last one hands the amount to bunq, which the page could always do and had
   never offered. */
.donate-choice {
  display: flex;
  gap: 0.375rem;
  margin-bottom: 1rem;
}

/* The control is the radio; the label is what you see and hit. */
.donate-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.donate-chip {
  flex: 1;
  min-width: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0 0.25rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface);
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--klc-text);
  white-space: nowrap;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: border-color var(--klc-fast) var(--klc-ease),
    background-color var(--klc-fast) var(--klc-ease),
    transform var(--klc-fast) var(--klc-ease);
}

.donate-chip-cur {
  margin-right: 0.125rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--klc-text-muted);
}

.donate-chip.is-other {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--klc-text-secondary);
}

/* Filled rather than outlined: at a glance you see which one you picked, not
   which one has a slightly bluer edge. */
.donate-radio:checked + .donate-chip {
  border-color: transparent;
  background-color: var(--klc-brand-strong);
  color: #ffffff;
  box-shadow: var(--klc-shadow-sm);
}

.donate-radio:checked + .donate-chip .donate-chip-cur {
  color: rgba(255, 255, 255, 0.72);
}

.donate-chip:active {
  transform: scale(0.97);
}

.donate-radio:focus-visible + .donate-chip {
  outline: 2px solid var(--klc-brand-strong);
  outline-offset: 2px;
}

.donate-secondary {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--klc-border);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.875rem;
}

.donate-secondary .btn-block {
  margin: 0;
}

/* The two secondary actions are buttons in their own forms now, so a token can
   travel with them. display:contents keeps the buttons as the flex children of
   .donate-secondary, which is what gives the first one its full width. */
.donate-secondary form {
  display: contents;
}

.donate-decline {
  /* Was an anchor; undo what a browser gives a button that it did not have. */
  background: none;
  border: 0;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0 0.75rem;
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
  font-weight: 500;
}

.donate-decline:hover {
  color: var(--klc-text-secondary);
}

/* Not a second ask but a way to help without paying anything yourself, which is
   why it carries DigitalOcean's own blue on its edge rather than a logo drawn
   from memory. */
.referral {
  --referral-accent: #0069ff;
  border-left: 3px solid var(--referral-accent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .referral {
    --referral-accent: #4d94ff;
  }
}

:root[data-theme="dark"] .referral {
  --referral-accent: #4d94ff;
}

.referral-kop {
  display: block;
  margin-bottom: 0.3125rem;
  font-size: 0.625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--referral-accent);
}

.referral-titel {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--klc-text);
}

.referral-p {
  margin-bottom: 0.875rem;
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
}

.referral-btn .bi {
  font-size: 0.75rem;
  opacity: 0.75;
}

/* Two related forms inside one card, rather than cards nested in cards. */
.panel-split {
  display: grid;
  gap: 1.5rem;
}

@media (min-width: 768px) {
  .panel-split {
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
  }

  .panel-split > .panel-part + .panel-part {
    padding-left: 2rem;
    border-left: 1px solid var(--klc-border);
  }
}

@media (max-width: 767.98px) {
  .panel-split > .panel-part + .panel-part {
    padding-top: 1.5rem;
    border-top: 1px solid var(--klc-border);
  }
}

.panel-title {
  font-size: 0.9375rem;
  font-weight: 650;
  margin-bottom: 0.25rem;
}

.panel-note {
  font-size: 0.8125rem;
  color: var(--klc-text-muted);
  margin-bottom: 1rem;
}

/* --------------------------------------------------------------------------
   21. Notice page
   Outcomes that used to arrive as a browser alert(): account deletion
   confirmed, link expired, and so on.
   -------------------------------------------------------------------------- */

.notice-block {
  text-align: center;
}

.notice-icon {
  font-size: 2.25rem;
  display: block;
  margin-bottom: 0.875rem;
  color: var(--klc-text-muted);
}

.notice-block h1 {
  margin-bottom: 0.75rem;
}

.notice-body {
  color: var(--klc-text-secondary);
  font-size: 0.9375rem;
  text-align: left;
}

.notice-body p:last-child {
  margin-bottom: 0;
}

.notice-success .notice-icon { color: var(--klc-success); }
.notice-warning .notice-icon { color: var(--klc-warning); }
.notice-danger .notice-icon { color: var(--klc-danger); }
.notice-info .notice-icon { color: var(--klc-brand); }

/* --------------------------------------------------------------------------
   22. Summary hero
   Every data screen opens with the number you came for, before any detail.
   -------------------------------------------------------------------------- */

.summary-hero {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 1.5rem 1.375rem;
  margin-bottom: 1.25rem;
  border-radius: var(--klc-radius-lg);
  border: 1px solid var(--klc-border);
  background:
    radial-gradient(120% 140% at 0% 0%, var(--klc-brand-soft) 0%, transparent 60%),
    var(--klc-surface);
  box-shadow: var(--klc-shadow-sm);
}

.summary-hero-label {
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--klc-text-muted);
}

.summary-hero-value {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.05;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

.summary-hero-meta {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
}

.summary-hero-meta .bi {
  color: var(--klc-brand);
}

/* --------------------------------------------------------------------------
   23. Daggeld ledger
   -------------------------------------------------------------------------- */

.ledger-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 1.125rem;
  border-bottom: 1px solid var(--klc-border);
}

.ledger-month {
  font-size: 0.9375rem;
  font-weight: 650;
  color: var(--klc-text);
}

.ledger-total {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

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

.ledger-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1.125rem;
  border-bottom: 1px solid var(--klc-border);
  font-size: 0.875rem;
}

.ledger-row:last-child {
  border-bottom: 0;
}

.ledger-date {
  flex: 0 0 3.75rem;
  color: var(--klc-text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ledger-dest {
  flex: 1;
  font-weight: 650;
  color: var(--klc-text);
  letter-spacing: -0.01em;
}

.ledger-amount {
  flex: 0 0 auto;
  font-weight: 650;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

.ledger-row-empty {
  color: var(--klc-text-muted);
  justify-content: center;
}

/* A month whose total could not be worked out. The dash is the absence of a
   number, not a number - so it takes the muted colour the empty row does,
   rather than the weight a real total carries. */
.ledger-total-unknown {
  color: var(--klc-text-muted);
  font-weight: 650;
}

/* Why the total is missing, at the foot of the card it belongs to. A notice
   rather than an alert: nothing is wrong with the roster, only with what we
   know about exchange rates. */
.ledger-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0;
  padding: 0.625rem 1.125rem;
  border-top: 1px solid var(--klc-border);
  background-color: var(--klc-warning-soft);
  color: var(--klc-warning);
  font-size: 0.78125rem;
  line-height: 1.45;
}

.ledger-note .bi {
  flex: none;
  margin-top: 0.05rem;
}

/* --------------------------------------------------------------------------
   24. Weather
   Conditions lead; the hourly strip and the numbers follow.
   -------------------------------------------------------------------------- */

/* The station stays put while its days scroll past, the way the day heading
   does in the flight list - two nightstops of two days each is more than one
   screen, and the name is what tells you which weather you are reading. */
.wx-head {
  position: sticky;
  top: var(--klc-header-height);
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem var(--wx-inset);
  background-color: var(--klc-surface);
  /* It is the card's first child, so it carries the card's own top corners
     rather than squaring them off once it lifts over the content. */
  border-radius: calc(var(--klc-radius) - 1px) calc(var(--klc-radius) - 1px) 0 0;
  border-bottom: 1px solid var(--klc-border);
}

/* The rest of the app names stations by their code; this page said only the
   city, which made it the odd one out. */
.wx-iata {
  margin-left: 0.5rem;
  padding: 0.0625rem 0.3125rem;
  border: 1px solid var(--klc-border);
  border-radius: 0.25rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--klc-text-muted);
  vertical-align: 0.08em;
}

.wx-station {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--klc-text);
}

.wx-dates {
  font-size: 0.75rem;
  color: var(--klc-text-muted);
  font-weight: 500;
  white-space: nowrap;
}

.wx-day + .wx-day {
  border-top: 1px solid var(--klc-border);
}

.wx-summary {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 1rem var(--wx-inset) 0.75rem;
}

.wx-icon {
  width: 3.25rem;
  height: 3.25rem;
  flex: 0 0 auto;
  /* Every condition is normalised to 46 units of ink in a 64 unit box, which
     leaves 9 either side. Pulling the box back by exactly that puts the drawing
     on the same column as the figures and the strip below it. */
  margin-left: calc(3.25rem * -9 / 64);
}

/* WeatherAPI draws its conditions at whatever size suits each picture: measured
   over all 48 of them, the artwork runs 46px wide inside the 64px box for most,
   55 for the ones carrying a sun, 52 for those at night, and 36 or 17 for a
   clear sky. They are centred, so the columns only look crooked because the
   pictures differ in size. Evening that out is all it takes. The clear night is
   capped rather than matched: a crescent blown up nearly three times reads as
   soft, and a small moon is no crime. */
[data-wx-art="wide-day"] {
  transform: scale(0.84);
}

[data-wx-art="wide-night"] {
  transform: scale(0.88);
}

[data-wx-art="clear-day"] {
  transform: scale(1.28);
}

[data-wx-art="clear-night"] {
  transform: scale(1.8);
}

/* The capped crescent keeps more of its box than the rest, so the shared pull
   back is not enough for it: measured after its own scaling, its drawing starts
   14 units in rather than 9. */
.wx-icon[data-wx-art="clear-night"] {
  margin-left: calc(3.25rem * -14 / 64);
}

.wx-summary-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.wx-date {
  font-size: 0.9375rem;
  font-weight: 650;
  color: var(--klc-text);
  text-transform: capitalize;
}

.wx-condition {
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
}

.wx-temps {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}

.wx-max {
  font-size: 1.625rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--klc-text);
  line-height: 1;
}

.wx-min {
  font-size: 1rem;
  font-weight: 550;
  color: var(--klc-text-muted);
}

/* Hourly strip: scrolls sideways rather than wrapping into a grid. The mask
   fades the trailing edge, which is the only cue that there is more to see -
   a touch device shows no scrollbar until you already know to swipe. */
.wx-hours {
  display: flex;
  gap: 0.125rem;
  overflow-x: auto;
  /* The same inset as the heading above and the figures below: once the current
     hour is tinted, its left edge is a line you can see, and it has to sit on
     the column the rest of the card is built on. */
  padding: 0.25rem var(--wx-inset) 0.75rem;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  /* Without this the snapport runs to the padding box, so the browser settles
     the first hour flush against the edge and scrolls the inset above out of
     sight - the padding is set, and then immediately scrolled away. */
  scroll-padding-inline: var(--wx-inset);
  /* The strip lives strictly between the card's two columns: nothing is painted
     in either inset, so an hour scrolled part way out disappears on the same
     line the heading starts on rather than running to the card's edge. The
     right also fades before it gets there, which is the only cue that there is
     more to see - a touch device shows no scrollbar until you already know to
     swipe. The opaque run starts a pixel early so rounding cannot shave the
     tinted hour at rest. */
  -webkit-mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--wx-inset) - 1px),
      #000 calc(100% - 3.25rem),
      transparent calc(100% - var(--wx-inset)));
  mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--wx-inset) - 1px),
      #000 calc(100% - 3.25rem),
      transparent calc(100% - var(--wx-inset)));
}

/* Once scrolled to the end there is nothing more to hint at. */
/* Scrolled to the end there is nothing left to hint at, so the fade goes - but
   the two columns stay. */
.wx-hours.is-scrolled-end {
  -webkit-mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--wx-inset) - 1px),
      #000 calc(100% - var(--wx-inset)),
      transparent calc(100% - var(--wx-inset)));
  mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--wx-inset) - 1px),
      #000 calc(100% - var(--wx-inset)),
      transparent calc(100% - var(--wx-inset)));
}

.wx-hour {
  flex: 0 0 auto;
  width: 3.25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.0625rem;
  padding: 0.5rem 0.25rem;
  border-radius: var(--klc-radius-sm);
  scroll-snap-align: start;
  transition: background-color var(--klc-fast) var(--klc-ease);
}

/* The strip already begins at the current hour; this says so. */
.wx-hour.is-now {
  background-color: var(--klc-brand-soft);
  border-radius: 0.5rem;
}

.wx-hour.is-now .wx-hour-time {
  color: var(--klc-brand-strong);
  font-weight: 700;
}

/* Only worth the ink once it is likely: below a third it is noise. */
.wx-hour-rain {
  margin-top: 0.125rem;
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1;
  color: var(--klc-info);
}

.wx-hour:hover {
  background-color: var(--klc-surface-3);
}

.wx-hour img {
  width: 1.75rem;
  height: 1.75rem;
}

.wx-hour-time {
  font-size: 0.6875rem;
  font-weight: 650;
  color: var(--klc-text-muted);
  font-variant-numeric: tabular-nums;
}

.wx-hour-temp {
  font-size: 0.8125rem;
  font-weight: 650;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

/* Numbers worth calling out. */
/* Three figures do not need three boxes. One row instead, the way the flight
   card states its own detail: the icon says which measure it is, the figure
   carries the weight, and the unit trails it quietly. */
.wx-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 1.25rem;
  /* It belongs to the day above it, not to the hours below, so it sits close
     under the summary and leaves the wider gap before the strip. */
  /* Bootstrap's glyphs sit hard against the left of their box where text and
     the weather artwork both carry a little air, so this row reads as if it
     pokes out. Two pixels back settles it optically. */
  padding: 0 var(--wx-inset) 0.25rem;
  padding-left: calc(var(--wx-inset) + 0.125rem);
  font-size: 0.8125rem;
  color: var(--klc-text-secondary);
}

/* A day with no hours in range ends on this row, and then it needs the card's
   own bottom margin rather than the small gap meant for the strip. */
.wx-stats:last-child {
  padding-bottom: 1.125rem;
}

.wx-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  color: inherit;
}

/* The one stat that leaves the app is also the one that has to be hit with a
   thumb, so it is padded out and pulled back in again - a target without a
   box. */
a.wx-stat {
  padding: 0.375rem 0.5rem;
  margin: -0.375rem -0.5rem;
  border-radius: var(--klc-radius-sm);
  text-decoration: none;
}

a.wx-stat:hover,
a.wx-stat:focus {
  background-color: var(--klc-surface-2);
  color: var(--klc-text-secondary);
  text-decoration: none;
}

.wx-stat .bi {
  color: var(--klc-brand-strong);
  font-size: 0.9375rem;
  flex: 0 0 auto;
}

.wx-stat-value {
  font-weight: 700;
  color: var(--klc-text);
  font-variant-numeric: tabular-nums;
}

/* The arrow flies with the wind rather than pointing into it, the way a sock
   does. Which quarter it came from is on the label beside it, because that is how
   it is spoken. Rotating the glyph needs a box of its own: a bi is an inline
   ::before, and a transform on an inline box does nothing. */
.wx-stat .wx-wind {
  display: inline-flex;
  transform: rotate(var(--wx-wind-deg, 0deg));
}

/* Specific enough to beat `.wx-stat .bi` above, which otherwise sizes and
   colours this one too: it is a hint that the link leaves the app, not a
   measure of its own, and it should not read as loud as the sun beside it. */
.wx-stat .wx-stat-out {
  font-size: 0.6875rem;
  color: var(--klc-text-muted);
  opacity: 0.8;
}

.wx-stat-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The sticky heading is pushed back down by the foot of its own card as that
   card scrolls away, which lands it exactly where the card should be rounding
   off - and it paints above the border, so its square bottom corners cut the
   card's round ones. Clipping the card to its own shape settles it. `clip`
   rather than `hidden`: hidden would make this a scroll container and the
   heading would stop sticking to the page at all. */
.wx-card {
  /* One inset for the whole card. The heading, the day, the figures, the strip
     and the footnote all read from it, so they cannot drift apart again. */
  --wx-inset: 0.75rem;
  overflow: clip;
}

/* The strip runs on the destination's clock while the rest of the app runs on
   UTC, so it has to say so - but once for the station, as a footnote, rather
   than as a heading standing over every day. */
.wx-tz {
  padding: 0 var(--wx-inset) 0.75rem;
  text-align: right;
  font-size: 0.625rem;
  color: var(--klc-text-muted);
}

.wx-unavailable {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: var(--wx-inset);
  color: var(--klc-text-muted);
  font-size: 0.875rem;
}

.wx-unavailable .bi {
  font-size: 1.125rem;
  flex: 0 0 auto;
}

/* --------------------------------------------------------------------------
   25. Empty states
   -------------------------------------------------------------------------- */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 3.5rem 1.5rem;
  color: var(--klc-text-secondary);
}

.empty-state > .bi {
  font-size: 2.75rem;
  color: var(--klc-text-muted);
  opacity: 0.5;
  margin-bottom: 1rem;
}

.empty-state h3 {
  font-size: 1.0625rem;
  margin-bottom: 0.375rem;
}

.empty-state-code {
  margin-top: 0.875rem;
}

.empty-state .btn {
  margin-top: 0.25rem;
}

.empty-state p {
  font-size: 0.875rem;
  color: var(--klc-text-muted);
  max-width: 24rem;
  margin-bottom: 0;
}

@media (max-width: 575.98px) {
  .summary-hero-value {
    font-size: 2rem;
  }


  .ledger-date {
    flex-basis: 3.5rem;
  }
}

/* --------------------------------------------------------------------------
   26. Auth pages
   The front door: sign in, register, reset, passkey. One centred column on a
   quiet background, with the brand doing the work instead of a bare heading.
   -------------------------------------------------------------------------- */

.auth-page {
  min-height: 100vh;
  /* The small viewport: the page then fits without forcing the scroll that
     collapses the address bar in the first place. */
  min-height: 100svh;
  display: flex;
  /* copyright.php renders inside <main>, so this has to stack: as a row the
     footer became a second column beside the card and pushed the page sideways. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: calc(2rem + env(safe-area-inset-top)) 1rem
    calc(3rem + env(safe-area-inset-bottom));
  /* No background of its own: the globe is painted on the canvas via the body,
     and anything here would cover it. */
  position: relative;
}

/* Whatever the browser paints outside the page itself - the rubber band at the
   end of a scroll, the strip behind a floating address bar - comes from the
   body, not from .auth-page. Without this it falls back to the page default and
   shows up as a band along the bottom. */
/* The background of the auth pages.
   It goes on the body because a browser propagates that to the canvas, which
   is the only surface covering everything - behind a floating address bar, into
   the overscroll bounce, past the safe area.
   The colour covers the canvas outright. An image does not: it is positioned
   against the body's box, so anything past the end of that box falls back to
   the colour alone. That is why the gradient here ends on exactly the
   background colour - the seam is then invisible wherever it lands. */
body:has(.auth-page) {
  background-color: #041c2f;
  background-image: linear-gradient(
    to bottom,
    #072e4a 0%,
    #05243a 45%,
    #041c2f 78%,
    #041c2f 100%
  );
}


/* The network is painted on a layer of its own, pinned to the viewport.
   background-attachment: fixed would have been the obvious way to do that, but
   iOS Safari ignores it and falls back to scroll - which sizes the artwork
   against the whole document instead of the screen, and on a page taller than
   the viewport that pushed the routes off the edges. A fixed element is sized
   against the viewport everywhere.

   The artwork is transparent, so the gradient above shows through it. Centred
   on the hub, so cropping to whatever shape the viewport is loses the outer
   edges rather than the middle. Eleven kilobytes of vector, so a phone can
   have it too. */
.auth-page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.auth-page > * {
  position: relative;
  z-index: 1;
}

/* From here there is room for the map to be a thing of its own: it takes the
   left of the screen and the card sits off-centre to the right of it. */
@media (min-width: 992px) {
  .auth-page {
    align-items: flex-end;
    padding-inline: clamp(2rem, 5vw, 5rem);
  }

  /* .container carries Bootstrap's margin-inline: auto, and an auto margin
     wins over align-items in flexbox - which left the card centred while the
     footer moved across. Both are pushed over explicitly instead. */
  .auth-page > .container,
  .auth-page .copyright {
    margin-left: auto;
    margin-right: 0;
  }

  /* The map moves off to the left and keeps its own proportions - whichever of
     the two the screen runs out of first decides how big it gets, so it never
     runs past an edge in either direction. Over it, a fade so there is nothing
     hard-edged where the artwork stops: one tone, brought up gradually across
     most of the width and ending on exactly the colour underneath it, so there
     is no seam and no flat wall of light blue behind the card. */
  /* Three layers on one element, in this order: a wash across the right to keep
     the form off the dots, the moving half of the panel, and the globe under
     both.

     They have to share an element. The globe and the moving half are separate
     images because an animated one has to be re-rasterised in full on every
     frame, and the globe carries five thousand land dots that would be redrawn
     along with the aircraft. But two images on two different elements are laid
     out against two different boxes the moment one of them is not honoured -
     background-attachment: fixed is ignored on iOS, which put the globe against
     the body box and the aircraft against the viewport, and the routes no
     longer met the hub. On one element they are sized and centred identically
     by construction. */
  .auth-page::before {
    background-image: linear-gradient(
        to right,
        rgba(2, 22, 38, 0) 0%,
        rgba(2, 22, 38, 0.1) 44%,
        rgba(2, 24, 40, 0.42) 60%,
        rgba(2, 26, 44, 0.72) 74%,
        rgba(3, 28, 46, 0.86) 100%
      ),
      url("../network.svg.php?layer=motion"),
      url("../network.svg.php");
    background-size: 100% 100%, cover, cover;
    background-position: center, center, center;
    background-repeat: no-repeat, no-repeat, no-repeat;
  }
}

/* Everything outside the card is now sitting on deep blue. */
.auth-page .copyright {
  color: rgba(255, 255, 255, 0.72);
}

.auth-page .copyright a {
  color: #ffffff;
}

.auth-page .copyright .copyright-version,
.auth-page .copyright .copyright-credits {
  color: rgba(255, 255, 255, 0.55);
}

/* The brand inside the card: mark, wordmark, and the page's own name beneath.
   The page name is the h1 - it is what this page is - while the wordmark above
   it is only a label, so the visual order and the document order differ on
   purpose. It also absorbs the old heading, which is where the height that
   used to sit above the card comes back. */
.auth-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.375rem;
  padding-top: 0;
}

.auth-lockup-mark {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.9rem;
  box-shadow: var(--klc-shadow-sm);
}

.auth-lockup-name {
  font-size: 1.3125rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--klc-text);
}

.auth-lockup-title {
  margin: -0.25rem 0 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--klc-text-muted);
}

.auth-page > .container {
  max-width: 30rem;
  width: 100%;
}

.auth-page .copyright {
  margin-top: 1.5rem;
  padding-bottom: 0;
  /* Same box as the card above it, so the centred text lines up with it
     instead of centring on its own ragged width once the stack moves right. */
  width: 100%;
  max-width: 30rem;
}

/* The card lifts off the panel rather than sitting flat on it. */
@media (min-width: 992px) {
  .auth-page .form_block {
    box-shadow: var(--klc-shadow-lg);
  }
}

.auth-page .row > [class^="col-"],
.auth-page .row > [class*=" col-"] {
  flex: 0 0 100%;
  max-width: 100%;
}


/* The card carries a lockup, two fields, a checkbox, a captcha of fixed height,
   two buttons and a divider between them. On a tablet in landscape that stack
   plus the footer is taller than the viewport, so the rhythm inside the card is
   tighter than the one the rest of the app uses. */
.auth-page .form_block {
  padding: 1.25rem 1.5rem;
}

.auth-page .form-group {
  margin-bottom: 0.875rem;
}

.auth-page .form-check {
  padding: 0.5rem 0.875rem;
}

.auth-page .divider-label {
  margin: 0.875rem 0;
}

/* A rule with a label in the middle: "of" on the sign-in page, the rest period
   between two flights on the flight list. */
.divider-label {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0;
  color: var(--klc-text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.divider-label::before,
.divider-label::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: var(--klc-border);
}

/* The rest period reads as a duration, not as a section heading. */
/* The app logo on a hand-off page (LogTen), sitting where an empty-state
   icon normally goes. */
.handoff-mark {
  width: 4rem;
  height: 4rem;
  border-radius: 14px;
  box-shadow: 0 0 0 1px var(--klc-border);
  margin-bottom: 0.25rem;
}

.passkey-intro {
  color: var(--klc-text-secondary);
  font-size: 0.875rem;
  margin-bottom: 1.25rem;
}

.auth-footnote {
  margin: 1.25rem 0 0;
  text-align: center;
  font-size: 0.875rem;
  color: var(--klc-text-muted);
}

.auth-footnote a {
  font-weight: 650;
}

.auth-page .form_block h3 {
  margin-bottom: 0.25rem;
}

.auth-page .form_block hr {
  margin: 0.875rem 0 1rem;
}

.auth-page .btn-block {
  padding: 0.625rem 1rem;
}

/* Turnstile draws a fixed 300x65 widget of its own design. Stretching it to the
   card width looked wrong next to everything else, so leave it at its natural
   size and simply centre it with room either side. */
.auth-page .cf-turnstile {
  margin-top: 0.75rem;
  min-height: 65px;
  display: flex;
  justify-content: center;
}

/* Only if the card is narrower than the widget's fixed width does it scale. */
@media (max-width: 359px) {
  .auth-page .cf-turnstile {
    transform: scale(0.9);
    transform-origin: top center;
    margin-bottom: -0.375rem;
  }
}

/* A landscape tablet is barely shorter than the stack is tall, and the footer
   was the part that fell off the bottom. Tighten the vertical rhythm there
   instead of asking for a scroll. */
@media (min-width: 992px) and (max-height: 900px) {
  .auth-page {
    padding-block: calc(1.25rem + env(safe-area-inset-top))
      calc(1.25rem + env(safe-area-inset-bottom));
  }

  .auth-lockup-mark {
    width: 2.5rem;
    height: 2.5rem;
  }

  .auth-page .form_block {
    padding: 1rem 1.5rem;
  }

  .auth-page .form-group {
    margin-bottom: 0.75rem;
  }

  .auth-page .form_block hr {
    margin: 0.75rem 0 0.875rem;
  }

  .auth-page .divider-label {
    margin: 0.75rem 0;
  }

  .auth-page .copyright {
    margin-top: 0.75rem;
    padding: 0.5rem 1rem 0;
  }

  .auth-page .copyright p:last-child {
    margin-top: 0.375rem;
  }
}

@media (max-width: 575.98px) {
  .auth-page {
    /* The insets have to be repeated here: this replaces the padding set
       further up, and without them the brand sits under the status bar once
       the app is on the home screen. */
    padding: calc(1rem + env(safe-area-inset-top)) 0.75rem
      calc(2rem + env(safe-area-inset-bottom));
    justify-content: flex-start;
  }

  .auth-page .form_block {
    padding: 1.25rem;
    border-radius: var(--klc-radius);
  }
}

/* --------------------------------------------------------------------------
   METAR and TAF
   A dialog holding two aerodromes' reports, shown exactly as issued. Everything
   here is about giving that text room: no frames, no captions repeating what
   the reports already say, and only as much division as it takes to see where
   one report ends and the next begins.
   -------------------------------------------------------------------------- */

/* What the dialog was opened for, and the way out of it, on one row. */
.avwx-top {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 0.875rem 0.5rem 1rem;
}

.avwx-subtitle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 0.75rem;
}

/* Deliberately the same chip as the day heading in the flight list: this dialog
   is opened from a card there, and the route is what ties the two together. */
.avwx-route {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  flex: 0 0 auto;
  padding: 0.0625rem 0.375rem;
  border: 1px solid var(--klc-border);
  border-radius: var(--klc-radius-sm);
  background-color: var(--klc-surface-2);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--klc-text-secondary);
}

.avwx-route > .bi {
  font-size: 0.625rem;
  letter-spacing: 0;
  color: var(--klc-text-muted);
}

.avwx-when {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--klc-text-muted);
}

.avwx-close {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  opacity: 1;
  font-size: 1.375rem;
  line-height: 1;
  color: var(--klc-text-muted);
}

.avwx-close:hover,
.avwx-close:focus-visible {
  background-color: var(--klc-surface-2);
  color: var(--klc-text);
  opacity: 1;
}

.avwx-body {
  padding: 0 1rem 1rem;
}

/* Between two aerodromes: the heaviest rule here, and the only one that
   separates one station's reports from another's. */
.avwx-station + .avwx-station {
  margin-top: 0.875rem;
  padding-top: 0.875rem;
  border-top: 2px solid var(--klc-border-strong);
}

.avwx-raw {
  font-family: var(--klc-font-mono);
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--klc-text);
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Observation and forecast come from one aerodrome, so the rule between them is
   the lightest: dotted, and close on both sides. */
.avwx-raw + .avwx-raw {
  margin-top: 0.375rem;
  padding-top: 0.375rem;
  border-top: 1px dotted var(--klc-border-strong);
}

/* --------------------------------------------------------------------------
   26b. iOS focus zoom
   Safari on iPhone and iPad zooms the viewport when you focus a control whose
   text is smaller than 16px, and then leaves the page zoomed. Our fields are
   15px, which is enough to trigger it on every form in the app.

   The fix is the text size, not the viewport: locking scale with
   maximum-scale=1 would also stop anyone pinching to zoom, which is a real
   accessibility loss. This raises the type to 16px on iOS only - the guard is
   true on WebKit for iOS and nowhere else - so nothing changes on a desktop.
   -------------------------------------------------------------------------- */

@supports (-webkit-touch-callout: none) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]):not([type="reset"]),
  select,
  textarea,
  .form-control,
  .form-control-sm,
  .custom-select,
  .input-group-text {
    font-size: 16px;
  }
}

/* --------------------------------------------------------------------------
   27. Loading states
   The app uses a big icon plus two headings in several places; centre and
   soften it so it reads as intentional rather than broken.
   -------------------------------------------------------------------------- */

#flight-list > .display-4,
#weatherInfo > .display-4,
.display-4 > .bi {
  color: var(--klc-text-muted);
  opacity: 0.65;
}

/* --------------------------------------------------------------------------
   28. Responsive
   Crew read this on a phone far more often than on a desktop.
   -------------------------------------------------------------------------- */

@media (max-width: 767.98px) {
  body {
    font-size: 0.9375rem;
  }

  .card-body {
    padding: 0.9375rem;
  }

  .card-header {
    padding: 0.75rem 0.9375rem;
  }

  .modal-content {
    border-radius: var(--klc-radius);
  }

}

/* --------------------------------------------------------------------------
   29. Print
   -------------------------------------------------------------------------- */

@media print {
  .nav-rail,
  .nav-scrim,
  .app-header,
  .bottom-nav,
  .tsat-widget,
  .toast-stack,
  .app-progress,
  .app-offline,
  .copyright,
  .app-header-actions {
    display: none !important;
  }

  html,
  body {
    background: #fff;
    color: #000;
  }

  /* The rail is gone, so the content should use the whole page. */
  #page-content-wrapper {
    margin-left: 0;
    padding-bottom: 0;
    min-height: 0;
  }

  #page-content-wrapper > .container-fluid {
    max-width: none;
    padding: 0;
    --klc-gutter: 0px;
  }

  .card,
  .flight-card,
  .wx-card {
    border: 1px solid #ccc;
    box-shadow: none;
    break-inside: avoid;
  }

  /* The hourly strip cannot be scrolled on paper. */
  .wx-hours {
    overflow-x: visible;
    flex-wrap: wrap;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.75em;
    color: #555;
  }
}
