/* ============================================================
   CalcHub — Design System
   Farger: #1B3A5C primær · #2E6DA4 sekundær · #E8A020 aksent
   ============================================================ */

/* ── TOKENS ── */
:root {
  --ds-primary:          #1B3A5C;
  --ds-primary-dark:     #132A42;
  --ds-primary-light:    #234B74;
  --ds-secondary:        #2E6DA4;
  --ds-secondary-dark:   #255887;
  --ds-secondary-light:  #3D84BF;
  --ds-accent:           #E8A020;
  --ds-accent-dark:      #C8881A;
  --ds-accent-light:     #F0B840;

  --ds-bg:               #F2F4F7;
  --ds-surface:          #FFFFFF;
  --ds-border:           #D1D5DB;
  /* Was #9CA3AF, which is 2.30:1 on --ds-bg. This is the border of
     .btn-outline-secondary and the stroke of the CR chart axes, so it is a
     control and graphics boundary and WCAG 1.4.11 wants 3:1. Now 3.38:1 on
     the page background and 3.73:1 on a card. */
  --ds-border-strong:    #7C8593;

  --ds-text:             #111827;
  --ds-text-secondary:   #374151;
  /* Was #6B7280, which is 4.39:1 on --ds-bg, under AA for body text. Now
     5.56:1 on the page background and 6.13:1 on a card, and still clearly
     muted against --ds-text-secondary at 9.35:1. */
  --ds-text-muted:       #5B6270;
  --ds-text-on-dark:     #F1F5F9;
  --ds-text-on-accent:   #1A1200;

  /* The status colours are only ever drawn as text or as a border on their
     own tinted background, never as a fill behind white text, so they are
     tuned for legibility as text. --ds-fail was #DC2626 at 4.41:1 on
     --ds-fail-bg, the one status colour under AA, and it is the colour that
     carries "this component does not pass". Now 5.24:1. */
  --ds-pass:             #15803D;
  --ds-pass-bg:          #F0FDF4;
  --ds-fail:             #C81E1E;
  --ds-fail-bg:          #FEF2F2;
  --ds-review:           #B45309;
  --ds-review-bg:        #FFFBEB;
  --ds-info:             #2E6DA4;
  --ds-info-bg:          #EFF6FF;

  /* The keyboard focus ring. It was written out as a raw rgba() in every
     place that needed one, in two different hues, so a focus ring could be
     Bootstrap blue on one control and the app's own blue on the next. */
  --ds-focus-ring:       rgba(46, 109, 164, 0.25);

  --ds-topbar-h:         3.86rem;
  --ds-sidebar-w:        16.57rem;

  --ds-font:             system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --ds-font-mono:        ui-monospace, "Cascadia Code", "Fira Code", monospace;

  /* ── SPACING SCALE ──
     Every padding, margin and gap in this file comes from here, so the
     density of the whole product is one edit. The px notes assume the 14px
     end of the html clamp() below, which is what a 1440px laptop gets.

     Thirteen steps rather than the eight the plan asked for. Eight forced
     1.25rem onto 1.5rem, which moved eleven declarations by 3.5px, and a
     scale that has to shift the layout to be adopted is not the same scale.
     The resolution sits in the small end because that is where a dense
     tool lives. */
  --ds-space-1:          0.125rem;  /*  2px */
  --ds-space-2:          0.25rem;   /*  4px */
  --ds-space-3:          0.375rem;  /*  5px */
  --ds-space-4:          0.5rem;    /*  7px */
  --ds-space-5:          0.625rem;  /*  9px */
  --ds-space-6:          0.75rem;   /* 11px */
  --ds-space-7:          0.875rem;  /* 12px */
  --ds-space-8:          1rem;      /* 14px */
  --ds-space-9:          1.25rem;   /* 18px */
  --ds-space-10:         1.5rem;    /* 21px */
  --ds-space-11:         2rem;      /* 28px */
  --ds-space-12:         2.5rem;    /* 35px */
  --ds-space-13:         3rem;      /* 42px */

  /* ── TYPE SCALE ──
     Replaces 34 distinct font-size values that had no relationship to each
     other. Body text in this product is --ds-type-base, not --ds-type-md:
     an engineer reads tables and traces, not prose, and the app was already
     built at that density. */
  --ds-type-2xs:         0.64rem;   /*  9px, badges and micro labels */
  --ds-type-xs:          0.72rem;   /* 10px, section labels, help text */
  --ds-type-sm:          0.8rem;    /* 11px, dense table and nav text */
  --ds-type-base:        0.88rem;   /* 12px, body */
  --ds-type-md:          1rem;      /* 14px, emphasised body */
  --ds-type-lg:          1.15rem;   /* 16px, card and panel titles */
  --ds-type-xl:          1.35rem;   /* 19px, page titles */
  --ds-type-2xl:         1.55rem;   /* 22px, result headline figures */

  /* Not a step of the type scale, and deliberately named apart from it: the
     confirmation glyph on the checkout result pages is a graphic that happens
     to be drawn with a font. The scale exists so text has a rhythm; sizing a
     decorative icon by it would either shrink the icon to look like a heading
     or push a display step into the scale that no text would ever use. */
  --ds-icon-hero:        4rem;
}

/* ── BREAKPOINTS ──
   Three tiers, and no others. Before this the codebase used 480, 575.98, 767,
   767.98, 768, 991 and 991.98 across five stylesheets for what is really three
   decisions, so a rule could take effect one pixel away from the rule it was
   written to accompany.

   They are Bootstrap 5's own max-width breakpoints, because the app renders
   Bootstrap's responsive utility classes on the same elements. Bootstrap's
   min-width queries use whole numbers, so a hand-written `max-width: 768px`
   overlaps its `min-width: 768px` and both apply at exactly 768px. The .98
   offsets are what keep the two systems from fighting over that pixel.

     --ds-bp-sm   575.98px   below Bootstrap sm, phones
     --ds-bp-md   767.98px   below Bootstrap md, large phones and portrait tablets
     --ds-bp-lg   991.98px   below Bootstrap lg, where the sidebar goes off-canvas

   The custom properties below are declarations, not machinery: a custom
   property cannot be used inside an @media condition, so the literals still
   have to be written out. tests/test_design_system.py is what actually holds
   the set closed. */
:root {
  --ds-bp-sm: 575.98px;
  --ds-bp-md: 767.98px;
  --ds-bp-lg: 991.98px;
}

/* ── BOOTSTRAP OVERRIDES: sharp corners everywhere ── */
:root {
  --bs-border-radius:             0 !important;
  --bs-border-radius-sm:          0 !important;
  --bs-border-radius-lg:          0 !important;
  --bs-border-radius-xl:          0 !important;
  --bs-border-radius-2xl:         0 !important;
  --bs-border-radius-pill:        2em !important; /* keep pill for tiny inline badges if needed */
  --bs-btn-border-radius:         0 !important;
  --bs-input-border-radius:       0 !important;
  --bs-card-border-radius:        0 !important;
  --bs-badge-border-radius:       0 !important;
  --bs-alert-border-radius:       0 !important;
  --bs-dropdown-border-radius:    0 !important;
  --bs-nav-pills-border-radius:   0 !important;
  --bs-modal-border-radius:       0 !important;
  --bs-toast-border-radius:       0 !important;
  --bs-popover-border-radius:     0 !important;
  --bs-pagination-border-radius:  0 !important;
  --bs-list-group-border-radius:  0 !important;
  --bs-form-select-border-radius: 0 !important;
}

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

/* Fluid root size: 14px on a ~1440px laptop viewport, scaling up to 17px
   on a ~2560px (27") display, clamped at both ends so it never runs away
   on ultra-wide monitors or shrinks below legibility on narrow ones. */
html { font-size: clamp(14px, 0.268vw + 10.14px, 17px); }

body {
  font-family: var(--ds-font);
  background: var(--ds-bg);
  color: var(--ds-text);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Skip link. Off-screen rather than display:none, because a hidden element
   is not focusable and a skip link that cannot be focused is not a skip
   link. z-index sits above the topbar (100) and the sidebar backdrop. */
.skip-link {
  position: fixed;
  top: var(--ds-space-4);
  left: var(--ds-space-4);
  z-index: 1100;
  transform: translateY(calc(-100% - var(--ds-space-10)));
  padding: var(--ds-space-4) var(--ds-space-9);
  background: var(--ds-surface);
  color: var(--ds-primary);
  font-size: var(--ds-type-sm);
  font-weight: 600;
  text-decoration: none;
  border: 2px solid var(--ds-primary);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Reduced motion, globally. auth.css already gates its own entrance
   animation behind (prefers-reduced-motion: no-preference), but that only
   covered one page; every transition in this file, the off-canvas sidebar
   slide included, was unconditional. Motion is not removed, it is reduced to
   a duration short enough not to read as movement, which keeps state changes
   from happening with no transition at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── APP LAYOUT ── */
.app-layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* No overflow: hidden here. It used to clip anything wider than the shell out
   of existence rather than letting it be scrolled to, which is how a wide
   trace table could lose its right-hand columns with no scrollbar to say so.
   .main-content owns the scrolling instead, and min-width: 0 on it is what
   stops a wide child from stretching the flex item and pushing the page into
   a horizontal scroll. */
.layout-shell {
  display: flex;
  flex: 1;
  min-height: 0;
}

/* ── TOPBAR ── */
.topbar {
  height: var(--ds-topbar-h);
  background: var(--ds-primary);
  border-bottom: 2px solid var(--ds-accent);
  display: flex;
  align-items: center;
  padding: 0 var(--ds-space-8);
  gap: var(--ds-space-6);
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 1040;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: var(--ds-space-4);
  width: calc(var(--ds-sidebar-w) - 1rem);
  flex-shrink: 0;
  color: #fff;
  font-weight: 700;
  font-size: var(--ds-type-md);
  letter-spacing: 0.5px;
  text-decoration: none;
}

.topbar-brand:hover { color: var(--ds-accent); }

.topbar-brand-icon {
  color: var(--ds-accent);
  font-size: var(--ds-type-md);
}

.topbar-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  flex: 0 0 2.25rem;
  border: 1px solid rgba(255,255,255,0.2);
  background: transparent;
  color: #fff;
}

.topbar-menu-btn:hover,
.topbar-menu-btn:focus-visible {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.45);
}

.topbar-sep {
  width: 1px;
  height: 20px;
  background: rgba(255,255,255,0.15);
  flex-shrink: 0;
}

.topbar-spacer { flex: 1; }

.topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
}

/* Unit toggle */
.unit-toggle {
  display: flex;
  align-items: center;
  gap: 0;
  border: 1px solid rgba(255,255,255,0.2);
  overflow: hidden;
}

.unit-toggle-btn {
  background: transparent;
  border: none;
  color: rgba(255,255,255,0.55);
  font-size: var(--ds-type-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: var(--ds-space-2) var(--ds-space-5);
  cursor: pointer;
  font-family: var(--ds-font);
  transition: background 0.12s, color 0.12s;
}

.unit-toggle-btn.active {
  background: rgba(255,255,255,0.15);
  color: #fff;
}

.unit-toggle-btn:hover:not(.active) {
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.85);
}

/* User dropdown in topbar */
.topbar-user-btn {
  display: flex;
  align-items: center;
  gap: var(--ds-space-4);
  background: transparent;
  border: 1px solid rgba(255,255,255,0.2);
  color: rgba(255,255,255,0.85);
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-2) var(--ds-space-6);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
  font-family: var(--ds-font);
}

.topbar-user-btn:hover {
  border-color: rgba(255,255,255,0.45);
  background: rgba(255,255,255,0.08);
  color: #fff;
}

.topbar-dropdown {
  background: var(--ds-primary-dark);
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  min-width: 200px;
}

.topbar-dropdown .dropdown-item {
  color: rgba(255,255,255,0.75);
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-4) var(--ds-space-8);
}

.topbar-dropdown .dropdown-item:hover,
.topbar-dropdown .dropdown-item:focus {
  background: rgba(255,255,255,0.08);
  color: #fff;
}

.topbar-dropdown .dropdown-divider {
  border-color: rgba(255,255,255,0.1);
}

.topbar-auth-link {
  color: rgba(255,255,255,0.7);
  text-decoration: none;
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-2) var(--ds-space-5);
  border: 1px solid transparent;
  transition: color 0.12s, border-color 0.12s;
}

.topbar-auth-link:hover {
  color: #fff;
  border-color: rgba(255,255,255,0.25);
}

.topbar-auth-link.signup {
  border-color: var(--ds-accent);
  color: var(--ds-accent);
  font-weight: 600;
}

.topbar-auth-link.signup:hover {
  background: var(--ds-accent);
  color: var(--ds-text-on-accent);
}

/* ── SIDEBAR ── */
.sidebar {
  width: var(--ds-sidebar-w);
  flex-shrink: 0;
  background: var(--ds-primary);
  overflow-y: auto;
  height: calc(100vh - var(--ds-topbar-h));
  position: sticky;
  top: var(--ds-topbar-h);
  border-right: 1px solid rgba(0,0,0,0.25);
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,0.12) transparent;
}

.sidebar::-webkit-scrollbar { width: 3px; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); }

.sidebar-backdrop {
  display: none;
}

.sidebar-section { padding: var(--ds-space-9) 0 var(--ds-space-4); }

.sidebar-section-label {
  padding: 0 var(--ds-space-7) var(--ds-space-4);
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  /* Was 0.35, which composites to 2.82:1 on --ds-primary. 0.62 gives 5.52:1.
     The hierarchy against .sidebar-link is carried by size, weight and caps,
     not by being too faint to read. */
  color: rgba(255,255,255,0.62);
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--ds-space-4);
  padding: var(--ds-space-4) var(--ds-space-7);
  font-size: var(--ds-type-sm);
  color: rgba(255,255,255,0.68);
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: background 0.1s, color 0.1s, border-color 0.1s;
  margin-bottom: 1px;
}

.sidebar-link:hover {
  background: rgba(255,255,255,0.07);
  color: #fff;
}

.sidebar-link.active {
  background: rgba(255,255,255,0.1);
  color: #fff;
  border-left-color: var(--ds-accent);
}

.sidebar-link-badge {
  margin-left: auto;
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  /* Was 0.4, at 3.23:1. Measured against the pill, not the sidebar: the
     0.08 white background lightens --ds-primary to #2D4A69, so the same
     alpha buys less contrast here than it does one rule up. 0.62 would only
     reach 4.65:1 against that; 0.68 gives 5.24:1. */
  color: rgba(255,255,255,0.68);
  background: rgba(255,255,255,0.08);
  padding: var(--ds-space-1) var(--ds-space-3);
}

.sidebar-divider {
  margin: var(--ds-space-5) var(--ds-space-7);
  border: none;
  border-top: 1px solid rgba(255,255,255,0.07);
}

/* ── MAIN CONTENT ── */
.main-content {
  flex: 1;
  overflow-y: auto;
  min-width: 0;
  height: calc(100vh - var(--ds-topbar-h));
}

/* 21px/28px at a 14px root. The block padding came down a step from 1.75rem
   when the scale went in, which is the one place the tokenisation was allowed
   to move the layout: A1 asks for dashboard density, and this is the rule that
   sets how much of the screen every page spends on margin. */
.content-body {
  padding: var(--ds-space-10) var(--ds-space-11);
}

/* Off-canvas shell, below --ds-bp-lg. Raised from 768px: between 768 and 991
   the fixed 16.57rem sidebar left a portrait tablet about 535px of content,
   which is narrower than the wizard forms are built for. */
@media (max-width: 991.98px) {
  .topbar {
    padding-inline: var(--ds-space-4);
    gap: var(--ds-space-3);
  }

  .topbar-menu-btn {
    display: inline-flex;
    /* 44x44 touch target. The desktop rule keeps 2.25rem, where a pointer is
       doing the aiming and WCAG 2.5.8's 24px floor already clears. */
    width: 2.75rem;
    height: 2.75rem;
    flex-basis: 2.75rem;
    min-width: 44px;
    min-height: 44px;
  }

  .topbar-brand {
    width: auto;
  }

  .topbar > .topbar-sep {
    display: none;
  }

  .topbar-user-name {
    display: none;
  }

  .topbar-user-btn {
    padding-inline: var(--ds-space-4);
  }

  .sidebar {
    position: fixed;
    z-index: 1035;
    left: 0;
    top: var(--ds-topbar-h);
    transform: translateX(-100%);
    /* visibility, not just transform. A translated-off panel still takes tab
       stops and still reads to a screen reader, so the closed sidebar was
       thirteen invisible links sitting in front of the page content.

       The 0.18s is a duration, not a delay. visibility interpolates
       discretely: given a duration it holds the visible value for the whole
       slide and flips at the end, which is exactly the behaviour wanted. The
       delay form (`visibility 0s linear 0.18s`) reads as though it should do
       the same and does not, verified in the browser: the panel stayed
       visible and tabbable indefinitely after closing. */
    visibility: hidden;
    transition: transform 0.18s ease, visibility 0.18s;
    box-shadow: 10px 0 28px rgba(0,0,0,0.28);
  }

  .sidebar.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.18s ease, visibility 0s;
  }

  .sidebar-backdrop {
    position: fixed;
    z-index: 1030;
    inset: var(--ds-topbar-h) 0 0;
    width: 100%;
    border: 0;
    background: rgba(10, 20, 30, 0.5);
  }

  .sidebar-backdrop.is-open {
    display: block;
  }

  body.sidebar-open {
    overflow: hidden;
  }

  .main-content {
    width: 100%;
  }

  .content-body {
    padding: var(--ds-space-8) var(--ds-space-6);
  }

  /* Touch targets. Applied only here, because forcing 44px rows on the
     desktop sidebar would undo the density A1 is after, and a mouse does not
     need them. */
  .sidebar-link {
    min-height: 44px;
  }

  .unit-toggle-btn {
    min-height: 44px;
    /* min-width as well as padding: "SI" is two characters, so padding alone
       left the button 34px wide and only the height cleared 44. */
    min-width: 44px;
    padding-inline: var(--ds-space-7);
  }

  .topbar-user-btn {
    min-height: 44px;
  }
}

/* Auth pages (login, signup, etc.) — narrow centered layout */
.content-body.auth-page {
  max-width: 480px;
  margin: 0 auto;
  padding-top: var(--ds-space-13);
}

/* ── MESSAGES / ALERTS ── */
.messages-zone {
  padding: var(--ds-space-4) var(--ds-space-11) 0;
}

/* ── TYPOGRAPHY ── */
h1 { font-size: var(--ds-type-2xl);  font-weight: 700; }
h2 { font-size: var(--ds-type-xl);   font-weight: 600; }
h3 { font-size: var(--ds-type-lg);   font-weight: 600; }
h4 { font-size: var(--ds-type-md);   font-weight: 600; }
h5 { font-size: var(--ds-type-base); font-weight: 600; }
h6 { font-size: var(--ds-type-sm);   font-weight: 600; }

.page-title {
  font-size: var(--ds-type-xl);
  font-weight: 700;
  color: var(--ds-primary);
  margin-bottom: var(--ds-space-2);
  line-height: 1.2;
}

.page-subtitle {
  font-size: var(--ds-type-sm);
  color: var(--ds-text-muted);
  margin-bottom: var(--ds-space-10);
}

.section-label {
  font-size: var(--ds-type-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ds-text-muted);
  margin-bottom: var(--ds-space-4);
}

/* ── BUTTONS ── */
.btn {
  border-radius: 0 !important;
  font-size: var(--ds-type-sm);
  font-weight: 500;
  padding: var(--ds-space-4) var(--ds-space-8);
  letter-spacing: 0.01em;
  transition: background 0.1s, border-color 0.1s, color 0.1s;
}

.btn-lg { padding: var(--ds-space-5) var(--ds-space-10); font-size: var(--ds-type-base); }
.btn-sm { padding: var(--ds-space-2) var(--ds-space-6); font-size: var(--ds-type-xs); }

.btn-primary {
  background: var(--ds-secondary);
  border-color: var(--ds-secondary);
  color: #fff;
}
.btn-primary:hover, .btn-primary:focus {
  background: var(--ds-secondary-dark);
  border-color: var(--ds-secondary-dark);
  color: #fff;
}

.btn-dark-navy {
  background: var(--ds-primary);
  border: 1px solid var(--ds-primary-dark);
  color: #fff;
}
.btn-dark-navy:hover {
  background: var(--ds-primary-dark);
  color: #fff;
}

.btn-accent {
  background: var(--ds-accent);
  border: 1px solid var(--ds-accent-dark);
  color: var(--ds-text-on-accent);
  font-weight: 600;
}
.btn-accent:hover {
  background: var(--ds-accent-dark);
  color: var(--ds-text-on-accent);
}

.btn-outline-secondary {
  border-color: var(--ds-border-strong);
  color: var(--ds-text-secondary);
  background: transparent;
}
.btn-outline-secondary:hover {
  background: var(--ds-bg);
  border-color: var(--ds-text-secondary);
  color: var(--ds-text);
}

/* ── CARDS ── */
.ds-card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  margin-bottom: var(--ds-space-9);
}

.ds-card-header {
  display: flex;
  align-items: baseline;
  gap: var(--ds-space-6);
  border-bottom: 1px solid var(--ds-border);
  padding: var(--ds-space-7) var(--ds-space-9);
  background: var(--ds-bg);
}

.ds-card-title {
  font-size: var(--ds-type-base);
  font-weight: 700;
  color: var(--ds-primary);
  margin: 0;
  letter-spacing: 0.01em;
}

.ds-card-body { padding: var(--ds-space-9); }

/* The card had a header and a body but never a footer, so the three dashboard
   cards drew one by hand, three times, with the same two declarations. A card
   that ends in an action row is a pattern, not a one-off. */
.ds-card-footer {
  padding: var(--ds-space-6) var(--ds-space-9);
  border-top: 1px solid var(--ds-border);
}

.ds-card-footer--actions {
  display: flex;
  gap: var(--ds-space-4);
}

/* Cards standing side by side in a row, where the footers should line up even
   though the bodies hold different amounts. Pair with Bootstrap's .h-100. */
.ds-card--fill {
  display: flex;
  flex-direction: column;
}

.ds-card--fill > .ds-card-body {
  flex: 1;
}

/* ── FORM ELEMENTS ── */
.form-control,
.form-select {
  border-radius: 0 !important;
  border-color: var(--ds-border);
  font-size: var(--ds-type-sm);
  color: var(--ds-text);
  background: var(--ds-surface);
  padding: var(--ds-space-3) var(--ds-space-5);
}

.form-control:focus,
.form-select:focus {
  border-color: var(--ds-secondary);
  box-shadow: 0 0 0 2px rgba(46,109,164,0.18);
  outline: none;
}

.form-label {
  font-size: var(--ds-type-sm);
  font-weight: 500;
  color: var(--ds-text-secondary);
  margin-bottom: var(--ds-space-2);
}

.form-text {
  font-size: var(--ds-type-xs);
  color: var(--ds-text-muted);
}

.input-group-text {
  border-radius: 0 !important;
  background: var(--ds-bg);
  border-color: var(--ds-border);
  color: var(--ds-text-muted);
  font-size: var(--ds-type-sm);
}

/* ── STATUS BADGES ── */
.badge { border-radius: 0 !important; }

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-type-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 1px solid;
}

.status-badge-pass   { background: var(--ds-pass-bg);   color: var(--ds-pass);   border-color: rgba(21,128,61,0.25); }
.status-badge-fail   { background: var(--ds-fail-bg);   color: var(--ds-fail);   border-color: rgba(200,30,30,0.25); }
.status-badge-review { background: var(--ds-review-bg); color: var(--ds-review); border-color: rgba(180,83,9,0.25); }
.status-badge-info   { background: var(--ds-info-bg);   color: var(--ds-info);   border-color: rgba(46,109,164,0.25); }

/* ── Normalized acceptance-check utilization ───────────────────────────── */
.utilization-summary {
  width: min(100%, 34rem);
  min-height: 3.5rem;
  margin: var(--ds-space-6) auto var(--ds-space-8);
  padding: var(--ds-space-5) var(--ds-space-7);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-8);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-left: 4px solid var(--ds-info);
  border-radius: 0.35rem;
  box-shadow: 0 1px 2px rgba(17, 24, 39, 0.06);
}

.utilization-summary--safe {
  background: var(--ds-pass-bg);
  border-left-color: var(--ds-pass);
}

.utilization-summary--warning {
  background: var(--ds-review-bg);
  border-left-color: var(--ds-review);
}

.utilization-summary--critical {
  background: var(--ds-fail-bg);
  border-left-color: var(--ds-fail);
}

.utilization-summary-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.utilization-summary-label {
  color: var(--ds-text);
  font-size: var(--ds-type-xs);
  font-weight: 700;
  letter-spacing: 0.035em;
  text-transform: uppercase;
}

.utilization-summary-name {
  overflow: hidden;
  color: var(--ds-text-muted);
  font-size: var(--ds-type-xs);
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.utilization-summary-value {
  flex: 0 0 auto;
  color: var(--ds-info);
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-xl);
  line-height: 1;
}

.utilization-summary--safe .utilization-summary-value { color: var(--ds-pass); }
.utilization-summary--warning .utilization-summary-value { color: var(--ds-review); }
.utilization-summary--critical .utilization-summary-value { color: var(--ds-fail); }

.utilization-checks {
  width: min(100%, 78rem);
  margin-right: auto;
  margin-left: auto;
}

.utilization-checks-body {
  padding: 0;
}

.utilization-checks table {
  font-size: var(--ds-type-sm);
}

.utilization-checks th:last-child,
.utilization-checks td:last-child {
  min-width: 8.5rem;
}

.utilization-checks .status-badge {
  white-space: nowrap;
}

.utilization-check-name {
  font-weight: 500;
}

.utilization-measure {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
  white-space: nowrap;
}

.utilization-placeholder {
  color: var(--ds-text-muted);
}

.utilization-check-row--failed {
  background: var(--ds-fail-bg);
}

.utilization-value {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
  white-space: nowrap;
}

.utilization-value--safe { color: var(--ds-pass); }
.utilization-value--warning { color: var(--ds-review); }
.utilization-value--critical { color: var(--ds-fail); }

@media (max-width: 575.98px) {
  .utilization-summary {
    align-items: flex-start;
  }

  .utilization-summary-name {
    white-space: normal;
  }
}

.part-badge {
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  background: rgba(46,109,164,0.1);
  color: var(--ds-secondary);
  border: 1px solid rgba(46,109,164,0.2);
  padding: var(--ds-space-1) var(--ds-space-3);
}

/* ── RESULT BANNERS ── */
.result-banner {
  display: flex;
  align-items: center;
  gap: var(--ds-space-6);
  padding: var(--ds-space-7) var(--ds-space-9);
  font-size: var(--ds-type-base);
  font-weight: 600;
  border-left: 4px solid;
  margin-bottom: var(--ds-space-10);
}

.result-banner i { font-size: var(--ds-type-md); }

.result-banner-pass   { background: var(--ds-pass-bg);   border-color: var(--ds-pass);   color: var(--ds-pass); }
.result-banner-fail   { background: var(--ds-fail-bg);   border-color: var(--ds-fail);   color: var(--ds-fail); }
.result-banner-review { background: var(--ds-review-bg); border-color: var(--ds-review); color: var(--ds-review); }

/* ── VERDICT CARD HEADER — the accept/reject decision baked into the
   Acceptance Criteria card itself, instead of a separate floating banner ── */
.verdict-header {
  display: flex;
  align-items: center;
  gap: var(--ds-space-6);
  padding: var(--ds-space-8) var(--ds-space-9);
  border-bottom: 3px solid;
}

.verdict-header-icon {
  font-size: var(--ds-type-2xl);
  line-height: 1;
  flex-shrink: 0;
}

.verdict-header-text {
  font-size: var(--ds-type-md);
  font-weight: 700;
}

.verdict-header-value {
  font-weight: 400;
  font-size: var(--ds-type-base);
  opacity: 0.85;
}

.verdict-header-ref {
  margin-left: auto;
  font-size: var(--ds-type-xs);
  font-weight: 400;
  opacity: 0.7;
  white-space: nowrap;
}

.verdict-header--pass { background: var(--ds-pass-bg);   border-color: var(--ds-pass);   color: var(--ds-pass); }
.verdict-header--fail { background: var(--ds-fail-bg);   border-color: var(--ds-fail);   color: var(--ds-fail); }
.verdict-header--review { background: var(--ds-review-bg); border-color: var(--ds-review); color: var(--ds-review); }

.verdict-reasons {
  padding: var(--ds-space-6) var(--ds-space-9);
  margin: 0;
  font-size: var(--ds-type-sm);
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-bg);
}

.verdict-reasons li { margin-bottom: var(--ds-space-2); }
.verdict-reasons li:last-child { margin-bottom: 0; }

/* ── ALERTS ── */
.alert {
  border-radius: 0 !important;
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-6) var(--ds-space-8);
  border-width: 0;
  border-left: 3px solid;
}

.alert-info    { background: var(--ds-info-bg);   color: var(--ds-info);   border-color: var(--ds-info); }
.alert-success { background: var(--ds-pass-bg);   color: var(--ds-pass);   border-color: var(--ds-pass); }
.alert-warning { background: var(--ds-review-bg); color: var(--ds-review); border-color: var(--ds-review); }
.alert-danger  { background: var(--ds-fail-bg);   color: var(--ds-fail);   border-color: var(--ds-fail); }

/* Raised by static/js/unit_guard.js when a unit switch cleared values that
   were typed under the previous system. Styled like a warning alert, but not
   dismissible: losing entered values is not something to be able to miss. */
.unit-guard-notice {
  background: var(--ds-review-bg);
  color: var(--ds-review);
  border-left: 3px solid var(--ds-review);
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-6) var(--ds-space-8);
  margin-bottom: var(--ds-space-8);
}

/* ── TABLES ── */
.table {
  font-size: var(--ds-type-sm);
  --bs-table-striped-bg: var(--ds-bg);
  --bs-table-hover-bg: rgba(46,109,164,0.05);
  border-color: var(--ds-border);
}

.table thead th {
  font-size: var(--ds-type-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ds-text-secondary);
  background: var(--ds-bg);
  border-bottom: 2px solid var(--ds-border);
  padding: var(--ds-space-4) var(--ds-space-6);
  white-space: nowrap;
}

.table td {
  padding: var(--ds-space-4) var(--ds-space-6);
  border-color: var(--ds-border);
  vertical-align: middle;
}

/* Shared CLF sensitivity plot/table key. */
.clf-sensitivity-plot-column {
  width: 3.5rem;
  text-align: center;
}

.table thead th.clf-sensitivity-dimension-header {
  text-transform: none;
}

.clf-sensitivity-point-marker {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  border: 1px solid #111;
  vertical-align: middle;
}

.clf-sensitivity-point-marker--circle {
  border-radius: 50%;
}

.clf-sensitivity-point-marker--diamond {
  transform: rotate(45deg);
}

.clf-sensitivity-point-marker--triangle {
  width: 0;
  height: 0;
  border: 0;
  border-right: 0.4rem solid transparent;
  border-bottom: 0.7rem solid var(--clf-sensitivity-point-color);
  border-left: 0.4rem solid transparent;
}

/* Dark-header calculation table */
.table-calc thead th {
  background: var(--ds-primary);
  color: rgba(255,255,255,0.85);
  border-color: var(--ds-primary-dark);
}

/* ── FORMULA / CALCULATION TRACE ── */
.formula-block {
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  border-left: 3px solid var(--ds-secondary);
  padding: var(--ds-space-5) var(--ds-space-7);
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
  color: var(--ds-text-secondary);
  overflow-x: auto;
}

code {
  font-family: var(--ds-font-mono);
  font-size: 0.82em;
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  padding: var(--ds-space-1) var(--ds-space-2);
  color: var(--ds-secondary);
}

.calc-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--ds-space-2) var(--ds-space-8);
  padding: var(--ds-space-3) var(--ds-space-5);
  font-size: var(--ds-type-sm);
  border-bottom: 1px solid var(--ds-border);
  align-items: baseline;
}

.calc-row:nth-child(even) { background: var(--ds-bg); }

.calc-label { color: var(--ds-text-secondary); }

.calc-value {
  font-family: var(--ds-font-mono);
  font-weight: 500;
  text-align: right;
  white-space: nowrap;
}

.calc-ref {
  color: var(--ds-text-muted);
  font-size: var(--ds-type-xs);
  text-align: right;
  white-space: nowrap;
}

/* ── WIZARD STEPPER ── */
.calculator-clear-all-actions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--ds-space-6);
}

.calculator-clear-all-actions[hidden] {
  display: none !important;
}

.calculator-clear-all-button {
  white-space: nowrap;
}

.content-body.calculator-inputs-cleared
  :is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step)
  .stepper-circle {
  background: var(--ds-accent) !important;
  color: var(--ds-text-on-accent) !important;
  transition: none !important;
}

.wizard-stepper {
  /* One knob for the circle. The connector line derives its vertical offset
     from it, so the two cannot drift apart the way they did while nine
     wizards each carried their own copy of this component. */
  --ds-stepper-circle: 40px;
  box-sizing: border-box;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  overflow-x: auto;
  background: var(--ds-surface);
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.wizard-stepper > .stepper-container {
  display: flex;
  align-items: center;
  justify-content: center;
  width: max-content;
  max-width: none;
  min-width: 0;
  margin: 0 auto;
}

.stepper-item {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  outline: none;
}

.module-home-wizard-stepper .stepper-item {
  cursor: default;
}

.stepper-item:focus-visible .stepper-circle {
  box-shadow: 0 0 0 0.2rem var(--ds-focus-ring);
}

.stepper-circle {
  width: var(--ds-stepper-circle);
  height: var(--ds-stepper-circle);
  border-radius: 50%;
  background: var(--ds-border);
  color: var(--ds-text-muted);
  font-size: var(--ds-type-md);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: all 0.3s;
}

.stepper-item.active .stepper-circle {
  background: var(--ds-info);
  color: #fff;
}

.stepper-item.completed .stepper-circle {
  background: var(--ds-pass);
  color: #fff;
}

.stepper-label {
  max-width: 100px;
  margin-top: 8px;
  color: var(--ds-text-muted);
  font-size: 12px;
  font-weight: 500;
  text-align: center;
  white-space: nowrap;
}

.stepper-item.active .stepper-label {
  color: var(--ds-info);
  font-weight: 600;
}

.stepper-item.completed .stepper-label {
  color: var(--ds-pass);
}

.wizard-stepper > .stepper-container > .stepper-line {
  flex: 0 0 64px;
  width: 64px;
  min-width: 32px;
  height: 2px;
  /* Meet the circle at its centre. The container centres its children on the
     item's full height, label included, which left the line sitting 13.6px
     low. Three of the nine copied blocks patched that with a bottom margin;
     deriving the offset fixes it once, for every stepper. */
  align-self: flex-start;
  margin: calc(var(--ds-stepper-circle) / 2 - 1px) 10px 0;
  background: var(--ds-border);
}

.stepper-item.completed + .stepper-line,
.stepper-item.status-complete + .stepper-line,
.stepper-item.status-ok + .stepper-line {
  background: var(--ds-pass);
}

@media (max-width: 767.98px) {
  .wizard-stepper {
    width: 100%;
    padding: 16px 12px;
  }

  .wizard-stepper > .stepper-container,
  .wizard-stepper.result-wizard-stepper > .stepper-container {
    min-width: 0;
  }

  .wizard-stepper > .stepper-container > .stepper-line {
    flex-basis: 16px;
    width: 16px;
    min-width: 12px;
    margin-inline: 4px;
  }

  .wizard-stepper:not(.result-wizard-stepper) .stepper-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .wizard-stepper:not(.result-wizard-stepper) {
    --ds-stepper-circle: 35px;
  }

  .wizard-stepper:not(.result-wizard-stepper) .stepper-circle {
    font-size: 14px;
  }
}

/* ── STEP STATUS COLOURS ──
   Both the horizontal stepper and the desktop side nav list their steps as
   [data-step] items and must agree, so the suffix is applied inside :is()
   rather than after a comma, which would split it into two unrelated
   selectors. These rules were built as a string in wizard_step_status.js and
   injected as a <style> element at runtime, which meant the product's step
   colours were unreviewable, uncacheable and unreachable for a strict CSP.
   The !important is kept: it is what lets a status beat the plain
   active/completed colours further up this file. */
:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-complete .stepper-circle,
:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-ok .stepper-circle {
  background: var(--ds-pass) !important;
  color: #fff !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-missing .stepper-circle {
  background: var(--ds-accent) !important;
  color: var(--ds-text-on-accent) !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-warning .stepper-circle {
  background: var(--ds-review) !important;
  color: #fff !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-error .stepper-circle {
  background: var(--ds-fail) !important;
  color: #fff !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-neutral .stepper-circle {
  background: var(--ds-info) !important;
  color: #fff !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).active .stepper-circle {
  box-shadow: 0 0 0 0.2rem rgba(46, 109, 164, 0.18);
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-missing.active .stepper-label {
  color: var(--ds-accent-dark) !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-warning.active .stepper-label {
  color: var(--ds-review) !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-error.active .stepper-label {
  color: var(--ds-fail) !important;
}

:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-complete.active .stepper-label,
:is(.wizard-stepper .stepper-item, .wizard-side-nav .wizard-side-step).status-ok.active .stepper-label {
  color: var(--ds-pass) !important;
}

/* ── RESULT STEPPER ──
   The same component on a result page, where every data step is behind you.
   It is a flat panel rather than a raised card, its steps are links, and its
   connector reads as completed by default. Everything else, including the
   status colours above, comes from the stepper. */
.result-wizard-stepper {
  border: 1px solid var(--ds-border);
  border-radius: 0;
  box-shadow: none;
}

.result-wizard-stepper a.result-stepper-item {
  color: inherit;
  text-decoration: none;
}

.result-wizard-stepper [data-results-current="true"] {
  cursor: default;
}

.result-wizard-stepper > .stepper-container > .stepper-line {
  background: var(--ds-pass);
}

/* ── SELECTION CARDS (geometry/variant/damage-type picker grids, all wizards) ── */
.selection-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--ds-space-8);
  margin-top: var(--ds-space-6);
}

.variant-cards {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

.pc-level2-variant-symbol {
  display: block;
  width: 6.5rem;
  height: 4rem;
  margin: 0 auto var(--ds-space-5);
  overflow: visible;
}

/* Two wizards need a denser grid than the 260px default. Both used to get it
   by redefining .selection-cards in their own <style> block, and because both
   pages also tag the grid .crack-cards, the same marker class meant a 140px
   grid on one page and a wrap-flex row on the other. Named variants say which
   is which. */
.selection-cards--compact {
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--ds-space-7);
  margin-top: var(--ds-space-4);
}

.selection-cards--compact .selection-card {
  padding: var(--ds-space-8) var(--ds-space-5);
}

.selection-cards--inline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-8);
}

.selection-cards--inline .selection-card {
  flex: 1;
  min-width: 150px;
  max-width: 200px;
}

@media (max-width: 767.98px) {
  .selection-cards--inline {
    flex-direction: column;
  }

  .selection-cards--inline .selection-card {
    max-width: none;
  }
}

.selection-card {
  border: 2px solid var(--ds-border);
  padding: var(--ds-space-9);
  text-align: center;
  cursor: pointer;
  background: var(--ds-surface);
  transition: border-color 0.15s, background 0.15s;
}

.selection-card:hover:not(.disabled) {
  border-color: var(--ds-secondary);
  background: var(--ds-info-bg);
}

.selection-card.selected,
.selection-card.active {
  border-color: var(--ds-secondary);
  background: var(--ds-info-bg);
  outline: 2px solid var(--ds-secondary);
  outline-offset: -2px;
}

.selection-card.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.selection-card h5 {
  margin: var(--ds-space-4) 0;
}

.selection-card ul {
  list-style: none;
  padding: 0;
  margin-top: var(--ds-space-7);
}

.selection-card ul li {
  padding: var(--ds-space-2) 0;
}

.selection-card ul li::before {
  content: "✓ ";
  color: var(--ds-pass);
  font-weight: bold;
}

/* Reference diagrams (e.g. crack geometry) embedded inside a selection card */
.crack-diagram {
  margin-bottom: var(--ds-space-5);
}

.crack-diagram svg {
  display: block;
  margin: 0 auto;
}

/* ── FORM GRID (legacy form.as_p compatibility) ── */
.form-grid p {
  display: grid;
  grid-template-columns: minmax(0,180px) minmax(0,120px) auto;
  column-gap: var(--ds-space-4);
  align-items: center;
  margin-bottom: var(--ds-space-4);
}

.form-grid p input,
.form-grid p select { max-width: 100%; }

/* The twelve GML level 1 and level 2 form pages lay their fields out in a
   table and want one fixed input width for the column to line up. Each of
   them carried the same rule in its own <style> block, unscoped, so it
   resized every input and select on the page. Scoped to the form now. */
.fixed-width-fields input[type="text"],
.fixed-width-fields input[type="number"],
.fixed-width-fields select {
  width: 230px;
}

/* ── APP NAVBAR (legacy override) ── */
.app-navbar {
  background: var(--ds-primary) !important;
  border-bottom: 2px solid var(--ds-accent);
  padding: 0;
  position: sticky;
  top: 0;
  z-index: 1040;
}

/* ── DROPDOWN menus in Bootstrap context ── */
.dropdown-menu {
  border-radius: 0 !important;
}

/* ── WIZARD FOOTER (the back/continue bar under a wizard step) ──
   Written out in two GML wizards and referenced by a third. */
.wizard-footer {
  border-top: 1px solid var(--ds-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--ds-space-7) var(--ds-space-10);
}

/* ── COLLAPSE CHEVRON ──
   The caret that flips when a Bootstrap collapse opens. */
.collapse-icon {
  transition: transform 0.2s ease-in-out;
}

[data-bs-toggle="collapse"][aria-expanded="true"] .collapse-icon {
  transform: rotate(180deg);
}

/* ── TOOLTIPS AND THE HELP ICON THAT OPENS THEM ──
   Both were written inline on the CLF cylinder and round bar wizards, which
   meant a help icon looked like a help icon on two pages out of the product.
   The 13px tooltip text is snapped to the type scale here. */
.info-tooltip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 6px;
  color: var(--ds-text-muted);
  cursor: help;
  vertical-align: middle;
  transition: color 0.2s ease;
}

.info-tooltip:hover {
  color: var(--ds-info);
}

.info-tooltip i {
  font-size: var(--ds-type-md);
}

.reference-thickness-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--ds-text-muted);
  line-height: 1;
  text-decoration: none;
}

.reference-thickness-info:hover,
.reference-thickness-info:focus-visible {
  color: var(--ds-info);
}

.tooltip-inner {
  max-width: 300px;
  padding: 8px 12px;
  font-size: var(--ds-type-base);
  line-height: 1.4;
  text-align: left;
}

/* ── UTILITY ── */
.text-muted   { color: var(--ds-text-muted) !important; }
.text-primary { color: var(--ds-primary) !important; }

.border-accent { border-color: var(--ds-accent) !important; }

hr { border-color: var(--ds-border); opacity: 1; }

/* ── UTILITIES THAT REPLACED style="" ATTRIBUTES (A7 part 2) ──
   Every class here exists because the same declaration was written inline in
   the templates, often dozens of times. They are named u-* so it stays
   obvious which layer they belong to, and they take their values from the
   scales rather than restating the pixel value that was inline.

   Two deliberate choices:

   .u-hidden is not `display: none !important`, unlike Bootstrap's .d-none.
   About forty of these elements are shown again by a script writing
   element.style.display, which is still allowed (style-src-attr governs the
   attribute in the markup, not CSSOM writes). An inline declaration beats a
   class rule only while that rule is not !important, so !important here would
   break every one of those toggles silently.

   The font sizes are snapped to the nearest step of the type scale. Inline,
   they were 0.6, 0.75, 0.76, 0.78, 0.8, 0.82, 0.85 and 0.9rem plus four em
   variants, which is the same drift A1 removed from the stylesheet. */
.u-hidden { display: none; }

.u-type-2xs  { font-size: var(--ds-type-2xs); }
.u-type-xs   { font-size: var(--ds-type-xs); }
.u-type-sm   { font-size: var(--ds-type-sm); }
.u-type-base { font-size: var(--ds-type-base); }
.u-type-md   { font-size: var(--ds-type-md); }

.u-mono      { font-family: var(--ds-font-mono); }
.u-nowrap    { white-space: nowrap; }
.u-scroll-x  { overflow-x: auto; }
.u-flush     { padding: 0; }
.u-center    { display: block; margin-inline: auto; }

/* Column width hints for the wide engineering tables. These percentages are
   the tables' own data, not a scale: a sensitivity run has ten columns whose
   proportions were chosen for their contents, and there is nothing to round
   them to. They are here rather than inline only so the attribute can go. */
.u-col-status { width: 80px; }
.u-col-9  { width: 9%; }
.u-col-10 { width: 10%; }
.u-col-11 { width: 11%; }
.u-col-13 { width: 13%; }
.u-col-15 { width: 15%; }
.u-col-17 { width: 17%; }

/* A CSV or file preview: as much as fits, then scroll. */
.u-preview-scroll {
  max-height: 200px;
  overflow-y: auto;
  font-size: var(--ds-type-sm);
}

/* A card body that hands its whole width to a wide table: no padding of its
   own, and the table scrolls inside it rather than pushing the page sideways
   (A2 requires no horizontal scroll on the page itself). */
.ds-card-body.u-table-host {
  padding: 0;
  overflow-x: auto;
}

/* ── LEGACY FORM TABLE ──
   The GML, LML and PC geometry forms render `{% for field in form %}` into a
   two-column table: the widget on the left, its label and help text on the
   right. Twenty-six templates carried the same three inline declarations for
   it. Keep the selectors on the class rather than on bare td, so the result
   tables elsewhere are untouched. */
.form-table td {
  vertical-align: top;
  padding: var(--ds-space-2) var(--ds-space-4);
}

.form-table .form-table-submit {
  padding-top: var(--ds-space-7);
}

/* Help text under a field label. Left at the size it was rendering at, the
   type scale's nearest step, rather than moved to --ds-type-xs: A5 is about
   to rewrite what these strings say, and changing what they say and how big
   they are in the same pass makes neither reviewable. */
.form-help {
  font-size: var(--ds-type-sm);
}

/* ── DEV COMPONENTS PAGE ── */
.ds-swatch {
  width: 56px;
  height: 56px;
  display: inline-block;
  border: 1px solid rgba(0,0,0,0.12);
}

.ds-swatch-label {
  font-size: var(--ds-type-xs);
  color: var(--ds-text-muted);
  margin-top: var(--ds-space-2);
  font-family: var(--ds-font-mono);
}

/* Scale rulers on /dev/components/. The step is carried in a data attribute
   rather than a style attribute so this page does not add to the inline-style
   pile that A7 has to clear before style-src can drop 'unsafe-inline'. */
.ds-scale-row {
  display: flex;
  align-items: center;
  gap: var(--ds-space-6);
  padding: var(--ds-space-2) 0;
  border-bottom: 1px solid var(--ds-border);
}

.ds-scale-name {
  flex: 0 0 9rem;
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-xs);
  color: var(--ds-text-secondary);
}

.ds-scale-value {
  flex: 0 0 5rem;
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-2xs);
  color: var(--ds-text-muted);
}

.ds-space-bar {
  height: 0.75rem;
  background: var(--ds-secondary);
  flex: 0 0 auto;
}

.ds-space-bar[data-step="1"]  { width: var(--ds-space-1); }
.ds-space-bar[data-step="2"]  { width: var(--ds-space-2); }
.ds-space-bar[data-step="3"]  { width: var(--ds-space-3); }
.ds-space-bar[data-step="4"]  { width: var(--ds-space-4); }
.ds-space-bar[data-step="5"]  { width: var(--ds-space-5); }
.ds-space-bar[data-step="6"]  { width: var(--ds-space-6); }
.ds-space-bar[data-step="7"]  { width: var(--ds-space-7); }
.ds-space-bar[data-step="8"]  { width: var(--ds-space-8); }
.ds-space-bar[data-step="9"]  { width: var(--ds-space-9); }
.ds-space-bar[data-step="10"] { width: var(--ds-space-10); }
.ds-space-bar[data-step="11"] { width: var(--ds-space-11); }
.ds-space-bar[data-step="12"] { width: var(--ds-space-12); }
.ds-space-bar[data-step="13"] { width: var(--ds-space-13); }

.ds-type-sample[data-step="2xs"]  { font-size: var(--ds-type-2xs); }
.ds-type-sample[data-step="xs"]   { font-size: var(--ds-type-xs); }
.ds-type-sample[data-step="sm"]   { font-size: var(--ds-type-sm); }
.ds-type-sample[data-step="base"] { font-size: var(--ds-type-base); }
.ds-type-sample[data-step="md"]   { font-size: var(--ds-type-md); }
.ds-type-sample[data-step="lg"]   { font-size: var(--ds-type-lg); }
.ds-type-sample[data-step="xl"]   { font-size: var(--ds-type-xl); }
.ds-type-sample[data-step="2xl"]  { font-size: var(--ds-type-2xl); }

.component-section {
  margin-bottom: var(--ds-space-12);
}

.component-section-title {
  font-size: var(--ds-type-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ds-text-muted);
  border-bottom: 1px solid var(--ds-border);
  padding-bottom: var(--ds-space-4);
  margin-bottom: var(--ds-space-9);
}

/* ── INLINE VALIDATION BADGES ──
   The pass/warn/fail pill that gml_validation.js and lml_validation.js put
   under a field while the engineer types. Both scripts built this CSS as a
   string and injected it as a <style> element, in Bootstrap's subtle palette
   rather than the product's, so the same badge read as two different things
   depending on which module you were in. One definition, on the status
   tokens. The class names keep their module prefixes because the scripts
   assign them. */
.gml-vbadge,
.lml-vbadge {
  display: none;
  padding: var(--ds-space-2) var(--ds-space-4);
  border-radius: 6px;
  font-size: var(--ds-type-sm);
  transition: all 0.2s ease;
}

.gml-vbadge.gml-ok,
.lml-vbadge.lml-ok {
  display: block !important;
  background: var(--ds-pass-bg);
  color: var(--ds-pass);
}

.gml-vbadge.gml-warn,
.lml-vbadge.lml-warn {
  display: block !important;
  background: var(--ds-review-bg);
  color: var(--ds-review);
}

.gml-vbadge.gml-fail,
.lml-vbadge.lml-fail {
  display: block !important;
  background: var(--ds-fail-bg);
  color: var(--ds-fail);
}

/* ── MAIN STEP RESULTS (main_step_results.html) ──
   The summary of output values that sits above the trace. */
.main-step-results__list {
  display: grid;
}

.main-step-result {
  border-bottom: 1px solid var(--ds-border);
  padding: var(--ds-space-7) var(--ds-space-8);
}

.main-step-result:last-child {
  border-bottom: 0;
}

.main-step-result__heading {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-4);
}

.main-step-result__number {
  color: var(--ds-primary);
  font-weight: 700;
}

.main-step-result__name {
  font-weight: 600;
}

.main-step-result__outputs {
  display: grid;
  gap: var(--ds-space-4);
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  margin-top: var(--ds-space-5);
}

.main-step-result__output {
  align-items: baseline;
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  display: flex;
  gap: var(--ds-space-5);
  justify-content: space-between;
  min-width: 0;
  padding: var(--ds-space-4) var(--ds-space-5);
}

.main-step-result__label,
.main-step-result__empty {
  font-size: var(--ds-type-sm);
}

.main-step-result__value {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
  text-align: right;
}

/* ── RESULTS BLOCK (results_block.html) ── */

/* Step row */
.rb-step {
  border-bottom: 1px solid var(--ds-border);
}

.rb-step--nested {
  background: var(--ds-bg);
  border-left: 2px solid var(--ds-border);
  margin: var(--ds-space-4) var(--ds-space-4) 0;
  border-bottom: none;
}

.rb-step-header {
  display: flex;
  align-items: center;
  padding: var(--ds-space-5) var(--ds-space-8);
  cursor: pointer;
  font-size: var(--ds-type-sm);
  font-weight: 500;
  color: var(--ds-text);
  user-select: none;
  gap: var(--ds-space-2);
  transition: background 0.1s;
  flex-wrap: wrap;
}

.rb-step-header:hover { background: rgba(46,109,164,0.05); }

.rb-step-name { font-weight: 600; }

/* step_number was carried all the way through the trace and rendered nowhere. */
.rb-step-number {
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  color: var(--ds-secondary);
  letter-spacing: 0.03em;
}

/* CalcRecord.description, the prose the orchestrator wrote about the step. */
.rb-step-description {
  font-size: var(--ds-type-xs);
  color: var(--ds-text-secondary);
  margin: 0 0 var(--ds-space-4);
}

/* Permalink to one step, so it can be cited in an email. Kept quiet until the
   row is hovered or the link itself is focused. */
.rb-step-anchor {
  color: var(--ds-text-muted);
  font-size: var(--ds-type-2xs);
  margin-left: var(--ds-space-2);
  opacity: 0;
  text-decoration: none;
  transition: opacity 0.1s;
}

.rb-step-header:hover .rb-step-anchor,
.rb-step-anchor:focus-visible {
  opacity: 1;
}

.rb-step-anchor:hover { color: var(--ds-secondary); }

.ds-ref-badge {
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  background: rgba(46,109,164,0.08);
  color: var(--ds-secondary);
  border: 1px solid rgba(46,109,164,0.18);
  padding: var(--ds-space-1) var(--ds-space-3);
}

/* The badge reads at two sizes, and until A7 part 2 only one of them was in
   the stylesheet. Thirty-four of its thirty-eight uses are the geometry,
   level and variant badges in a result-page header, and every one of those
   overrode the size inline. Those are this variant; the base size stays for
   the GOVERNING markers and the equation references inside a trace step,
   which sit in running text and should not compete with it. */
.ds-ref-badge--title {
  font-size: var(--ds-type-sm);
}

.step-number-badge {
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  background: var(--ds-primary);
  color: #fff;
  padding: var(--ds-space-1) var(--ds-space-3);
  letter-spacing: 0.03em;
}

.rb-step-preview {
  font-size: var(--ds-type-sm);
  font-family: var(--ds-font-mono);
  margin-left: auto;
}

/* Step body */
.rb-step-body {
  padding: var(--ds-space-6) var(--ds-space-8) var(--ds-space-8) var(--ds-space-11);
  border-top: 1px solid var(--ds-border);
  background: var(--ds-surface);
}

/* Open/closed is expressed once, as aria-expanded on the header, and the two
   visual consequences follow from it here. The JS used to write both an inline
   display and an inline transform, which meant the state lived in three places
   and the server-rendered state was only right by luck. */
.rb-step-header[aria-expanded="false"] + .rb-step-body {
  display: none;
}

.rb-chevron {
  font-size: var(--ds-type-xs);
  transition: transform 0.15s;
}

.rb-step-header[aria-expanded="true"] .rb-chevron {
  transform: rotate(90deg);
}

.rb-section { margin-bottom: var(--ds-space-7); }

.rb-section-label {
  font-size: var(--ds-type-2xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ds-text-muted);
  margin-bottom: var(--ds-space-3);
}

/* Variable grid */
.rb-var-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--ds-space-4);
}

.rb-var-card {
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  padding: var(--ds-space-4) var(--ds-space-5);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--ds-space-2) var(--ds-space-2);
  font-size: var(--ds-type-sm);
}

.rb-var-card--output {
  border-color: rgba(46,109,164,0.25);
  background: rgba(239,246,255,0.6);
}

.rb-symbol { font-family: var(--ds-font-mono); color: var(--ds-text-secondary); }
.rb-eq     { color: var(--ds-text-muted); }
.rb-val    { font-family: var(--ds-font-mono); font-weight: 600; }
.rb-val--result { color: var(--ds-secondary); font-size: var(--ds-type-base); }
.rb-unit   { font-size: var(--ds-type-xs); color: var(--ds-text-muted); }
.rb-desc   { width: 100%; color: var(--ds-text-muted); font-size: var(--ds-type-xs); margin-top: var(--ds-space-1); }

/* KaTeX formula containers */
.rb-formula-katex {
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  border-left: 3px solid var(--ds-secondary);
  padding: var(--ds-space-5) var(--ds-space-8);
  overflow-x: auto;
  font-size: var(--ds-type-md);
}

.rb-formula-substituted {
  margin-top: var(--ds-space-3);
  border-left-color: var(--ds-accent);
  font-size: var(--ds-type-base);
}

.rb-formula-ref {
  margin-top: var(--ds-space-3);
  text-align: right;
  font-size: var(--ds-type-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ds-text-muted);
}

/*
 * KaTeX fallback. If KaTeX fails to load, static/js/calc_trace.js puts
 * .katex-unavailable on <html>. The LaTeX source would then sit on the page
 * as raw backslash noise, so we swap it for the plain-text formula, which is
 * always readable. A step that has no plain formula keeps its LaTeX source,
 * unreadable beats invisible.
 */
.rb-formula-fallback,
.rb-katex-notice { display: none; }
.katex-unavailable .rb-katex-notice { display: block; }
.katex-unavailable .rb-formula-fallback { display: block; }
.katex-unavailable .rb-formula-src { display: none; }

/* Plain-text (non-LaTeX) formula box */
.rb-formula-plain code {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-base);
  color: var(--ds-text);
  background: none;
  padding: 0;
  white-space: normal;
  word-break: break-word;
}

/* Chart embedded in a calculation trace step */
.rb-chart-wrap {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  padding: var(--ds-space-6);
  max-width: 640px;
}

/* Children container */
.rb-children {
  border: 1px solid var(--ds-border);
  border-left: 2px solid var(--ds-border);
}

/* Heading-only step — visual section divider within a calculation trace */
.rb-step-heading {
  padding: var(--ds-space-4) var(--ds-space-8);
  font-size: var(--ds-type-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ds-secondary);
  background: var(--ds-bg);
  border-bottom: 1px solid var(--ds-border);
}


/* ===========================================================================
   Shared assessment-result components
   Used by every module's result_summary so per-module CSS (.lml-/.pc-/.cr-*)
   can be retired in favour of one consistent design-system look.
   =========================================================================== */

/* Metric / key-value cards (RSF, MAWP, thicknesses, …) */
.ds-metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--ds-space-5);
}
.ds-metric-card {
  background: var(--ds-bg);
  border: 1px solid var(--ds-border);
  padding: var(--ds-space-5) var(--ds-space-6);
}
.ds-metric-card--pass { border-color: rgba(25,135,84,0.35); background: rgba(25,135,84,0.06); }
.ds-metric-card--fail { border-color: rgba(220,53,69,0.35); background: rgba(220,53,69,0.06); }
.ds-metric-card--warn { border-color: rgba(225,160,32,0.4); background: rgba(225,160,32,0.07); }
.ds-metric-label {
  font-size: var(--ds-type-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ds-text-muted);
  margin-bottom: var(--ds-space-2);
}
.ds-metric-value {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-md);
  font-weight: 600;
  color: var(--ds-text);
}

/* Inline check chip used in compact criteria lists */
.ds-check-line {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  font-size: var(--ds-type-sm);
  padding: var(--ds-space-1) 0;
}

/* Standard spacing for the actions footer (nav + save + export) */
.result-actions > * { margin-bottom: var(--ds-space-8); }

/* Discreet disclaimer link at the bottom of every app page (punkt 15) */
.app-footer {
  padding: var(--ds-space-8) var(--ds-space-11) var(--ds-space-10);
  font-size: var(--ds-type-sm);
  color: var(--ds-text-muted);
  border-top: 1px solid var(--ds-border);
  margin-top: var(--ds-space-10);
}
.app-footer a {
  color: var(--ds-text-muted);
  text-decoration: underline;
}

/* Cookie notice — necessary-cookies-only acknowledgement, see home/_cookie_consent.html */
.cookie-consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1050;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-8);
  flex-wrap: wrap;
  background: var(--ds-primary);
  color: var(--ds-text-on-dark);
  padding: var(--ds-space-7) var(--ds-space-10);
  font-size: var(--ds-type-base);
}
.cookie-consent--visible {
  display: flex;
}
.cookie-consent p {
  margin: 0;
}
.cookie-consent a {
  color: var(--ds-text-on-dark);
  text-decoration: underline;
}

/* Module home header. The heading reads left like the rest of the app, while
   the shared schematic is centered against the full content page rather than
   against a narrow right-hand column. */
.module-home-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ds-space-8);
  margin-bottom: var(--ds-space-11);
}

.module-home-heading {
  min-width: 0;
  flex: 1 1 auto;
  max-width: 60ch;
}

/* .page-subtitle carries a large bottom margin so it can end a page header on
   its own. Here the intro follows it, so the gap belongs after the pair. */
.module-home-standard {
  margin-bottom: var(--ds-space-2);
}

.module-home-intro {
  font-size: var(--ds-type-base);
  color: var(--ds-text-secondary);
  margin-bottom: 0;
}

.module-home-figure {
  width: min(100%, 300px);
  max-width: 100%;
  align-self: center;
  text-align: center;
}

.module-home-figure :is(svg, img) {
  display: block;
  margin-inline: auto;
  max-width: 100%;
  height: auto;
}

.module-home-icon {
  font-size: var(--ds-type-2xl);
  color: var(--ds-secondary);
}

@media (max-width: 767.98px) {
  .module-home-header {
    gap: var(--ds-space-8);
  }

  .module-home-figure {
    width: min(100%, 300px);
  }
}

/* Component selection grid — the card grid every assessment module lands on.
   Each card links straight into that module's wizard. */
.component-selection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
  gap: var(--ds-space-10);
  margin-bottom: var(--ds-space-12);
}

/* Cards share the grid's row tracks so the header/body divider lines up across
   a row however long each title or description runs. Without subgrid the flex
   fallback still bottom-aligns the button; only the divider drifts. */
.component-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ds-border);
  background: var(--ds-surface);
  overflow: hidden;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

@supports (grid-template-rows: subgrid) {
  .component-card {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
  }
}

.component-card:hover {
  border-color: var(--ds-secondary);
  box-shadow: 0 0.5rem 1.5rem rgba(46, 109, 164, 0.15);
}

.component-card-header {
  padding: var(--ds-space-11) var(--ds-space-10) var(--ds-space-9);
  text-align: center;
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-bg);
}

.component-card-icon {
  color: var(--ds-secondary);
  margin-bottom: var(--ds-space-7);
}

.component-card-figure {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 5rem;
  margin-bottom: var(--ds-space-5);
  color: #6c757d;
}

.component-card-figure svg {
  display: block;
  width: 8.25rem;
  height: 5rem;
  overflow: visible;
}

.component-card-figure .component-figure-body,
.component-card-figure .component-figure-detail {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.component-card-figure .component-figure-body {
  stroke-width: 2;
}

.component-card-figure .component-figure-fill {
  fill: #d9dee3;
}

.component-card-figure .component-figure-detail {
  stroke-width: 1.4;
}

.component-card-figure .component-figure-crack {
  fill: none;
  stroke: #dc3545;
  stroke-width: 2.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.component-card-header h5 {
  font-size: var(--ds-type-xl);
  font-weight: 600;
  margin-bottom: var(--ds-space-4);
  color: var(--ds-text);
}

.component-card-desc {
  font-size: var(--ds-type-base);
  color: var(--ds-text-muted);
  margin-bottom: 0;
}

.component-card-body {
  display: flex;
  flex-direction: column;
  padding: var(--ds-space-9) var(--ds-space-10) var(--ds-space-10);
  text-align: center;
}

.component-card-body .btn {
  margin-top: auto;
}

@media (max-width: 767.98px) {
  .component-selection-grid {
    grid-template-columns: 1fr;
  }
}

/* Compact Level 1 / Level 2 picker used by CLF assessment wizards. */
.selection-cards.level-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 220px));
  gap: var(--ds-space-6);
}

.level-cards .level-card {
  display: flex;
  align-items: center;
  gap: var(--ds-space-5);
  flex: none;
  min-width: 0;
  width: auto;
  min-height: 48px;
  padding: var(--ds-space-6) var(--ds-space-7);
  color: var(--ds-text);
  font: inherit;
  font-weight: 600;
  justify-content: center;
  text-align: center;
  text-decoration: none;
}

.module-home-level-cards {
  margin-bottom: var(--ds-space-12);
}

/* Shared responsive frame for every CLF residual-stress preview. Chart.js is
   configured to fill this frame, giving the engineering plots one consistent
   proportion without hard-coded canvas dimensions in individual calculators. */
.clf-residual-stress-chart-frame {
  position: relative;
  width: 100%;
  max-width: 900px;
  aspect-ratio: 2 / 1;
  margin-inline: auto;
}

.clf-residual-stress-chart-frame > canvas {
  position: absolute;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
}

@media (max-width: 767.98px) {
  .clf-residual-stress-chart-frame {
    aspect-ratio: 1.45 / 1;
  }
}

/* ── DASHBOARD (A7 part 2) ──
   accounts/dashboard.html. D1 asked for a working surface rather than a
   profile page, and E6 built it: start an assessment, pick one up, then
   account. The three-equal-card layout and the classes that served it
   (.dashboard-assessment, .dashboard-note, .dashboard-empty) are gone with
   it. .dashboard-facts survives because the account panel still lists
   facts. */

.dashboard-facts {
  width: 100%;
  font-size: var(--ds-type-sm);
  border-collapse: collapse;
}

.dashboard-facts td:first-child {
  color: var(--ds-text-muted);
  padding: var(--ds-space-3) var(--ds-space-4) var(--ds-space-3) 0;
  width: 35%;
}

/* A band of the workspace. The page is a sequence of these, not a grid of
   cards, so that the order on screen is the order of the work. */
.workspace-section {
  margin-bottom: var(--ds-space-12);
}

.workspace-section:last-child {
  margin-bottom: 0;
}

.workspace-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ds-space-6);
  margin-bottom: var(--ds-space-6);
  padding-bottom: var(--ds-space-4);
  border-bottom: 1px solid var(--ds-border);
}

/* .section-label is the shared small-caps label; the heading here is an <h2>
   carrying it, so the element is semantic and the look is borrowed. */
.workspace-heading .section-label {
  margin: 0;
}

.workspace-heading__action {
  flex-shrink: 0;
  font-size: var(--ds-type-xs);
  font-weight: 600;
  color: var(--ds-secondary);
  text-decoration: none;
}

.workspace-heading__action:hover {
  text-decoration: underline;
}

/* One tile per damage type. This is the control D1 said the dashboard was
   missing: before this, an engineer who logged in had to find the sidebar
   before they could start anything. */
.start-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-4);
  height: 100%;
  min-height: 44px;
  padding: var(--ds-space-6) var(--ds-space-7);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  text-decoration: none;
  transition: border-color 0.18s ease, background 0.18s ease;
}

.start-card:hover {
  border-color: var(--ds-secondary);
  background: #fff;
  text-decoration: none;
}

.start-card:focus-visible {
  outline: 2px solid var(--ds-secondary);
  outline-offset: 2px;
}

.start-card__name {
  font-size: var(--ds-type-sm);
  font-weight: 600;
  line-height: 1.35;
  color: var(--ds-primary);
}

.start-card__go {
  flex-shrink: 0;
  font-size: var(--ds-type-2xs);
  color: var(--ds-border-strong);
  transition: transform 0.18s ease, color 0.18s ease;
}

.start-card:hover .start-card__go {
  transform: translateX(3px);
  color: var(--ds-secondary);
}

/* A saved assessment, with enough on the row to recognise it: module,
   geometry, level, date and outcome. D1 listed all five; the old row showed
   a title, a level and a date, so two assessments of different components
   read identically. */
.recent-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-6);
  padding: var(--ds-space-6) var(--ds-space-9);
  border-bottom: 1px solid var(--ds-border);
}

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

.recent-row__main {
  min-width: 0;
}

.recent-row__title {
  display: block;
  font-size: var(--ds-type-sm);
  font-weight: 600;
  color: var(--ds-text);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recent-row__title:hover {
  text-decoration: underline;
}

.recent-row__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-2) var(--ds-space-6);
  margin-top: var(--ds-space-2);
  font-size: var(--ds-type-xs);
  color: var(--ds-text-muted);
}

.recent-row__end {
  display: flex;
  align-items: center;
  gap: var(--ds-space-4);
  flex-shrink: 0;
}

/* An empty state names what is missing and what to do about it. */
.workspace-empty {
  padding: var(--ds-space-11) var(--ds-space-9);
}

.workspace-empty__lead {
  font-size: var(--ds-type-md);
  font-weight: 600;
  color: var(--ds-primary);
  margin-bottom: var(--ds-space-2);
}

.workspace-empty__body {
  font-size: var(--ds-type-sm);
  color: var(--ds-text-muted);
  max-width: 52ch;
  margin-bottom: 0;
}

@media (max-width: 575.98px) {
  .recent-row {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--ds-space-4);
  }
}

/* ── PRICING (D2, second half) ──
   payments/pricing.html renders inside the app shell, so unlike the landing
   page it follows the --ds-* scales rather than landing.css. One plan is
   offered, so this is a statement beside a plan, not a tower comparison. */
.pricing-lead {
  font-size: var(--ds-type-md);
  line-height: 1.7;
  color: var(--ds-text-secondary);
  max-width: 54ch;
  margin-bottom: var(--ds-space-10);
}

.pricing-fact {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: var(--ds-space-6);
  padding: var(--ds-space-6) 0;
}

.pricing-fact + .pricing-fact {
  border-top: 1px solid var(--ds-border);
}

.pricing-fact dt {
  font-size: var(--ds-type-sm);
  font-weight: 700;
  color: var(--ds-primary);
}

.pricing-fact dd {
  margin: 0;
  font-size: var(--ds-type-sm);
  line-height: 1.65;
  color: var(--ds-text-muted);
}

.plan-card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-top: 3px solid var(--ds-accent);
  padding: var(--ds-space-11);
}

.plan-card__name {
  font-size: var(--ds-type-lg);
  font-weight: 700;
  color: var(--ds-primary);
  margin-bottom: var(--ds-space-6);
}

.plan-card__price {
  display: flex;
  align-items: baseline;
  gap: var(--ds-space-4);
  margin-bottom: var(--ds-space-2);
}

/* Tabular figures so a price never shifts width between renders. */
.plan-card__amount {
  font-size: var(--ds-type-2xl);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ds-primary);
  line-height: 1.1;
}

.plan-card__unit,
.plan-card__note {
  font-size: var(--ds-type-xs);
  color: var(--ds-text-muted);
}

.plan-card__note {
  margin-bottom: 0;
}

/* Hairlines between features, none above the first or below the last, so the
   group reads as a group rather than as a table. No checkmark column: a tick
   beside every line says nothing that the line being present does not. */
.plan-features {
  list-style: none;
  margin: var(--ds-space-10) 0;
  padding: 0;
}

.plan-features li {
  font-size: var(--ds-type-sm);
  line-height: 1.6;
  color: var(--ds-text-secondary);
  padding: var(--ds-space-6) 0;
}

.plan-features li + li {
  border-top: 1px solid var(--ds-border);
}

.plan-card__active {
  font-size: var(--ds-type-xs);
  color: var(--ds-text-muted);
  margin: var(--ds-space-6) 0 0;
}

@media (max-width: 575.98px) {
  .pricing-fact {
    grid-template-columns: 1fr;
    gap: var(--ds-space-2);
  }

  .plan-card {
    padding: var(--ds-space-9);
  }
}

/* The busy state from static/js/pending_action.js. The spin is damped to a
   near-still 0.01ms by the global prefers-reduced-motion rule, which is why
   the label carries the meaning and the spinner only decorates it. */
.pending-spinner {
  display: inline-block;
  width: var(--ds-space-7);
  height: var(--ds-space-7);
  margin-right: var(--ds-space-4);
  vertical-align: -2px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: pending-spin 0.7s linear infinite;
}

@keyframes pending-spin {
  to { transform: rotate(360deg); }
}

/* A button that holds an icon and nothing else. */
.btn.btn-icon {
  padding: var(--ds-space-1) var(--ds-space-3);
}

.u-rule { margin-block: var(--ds-space-6); }

/* Small shell details that were written inline in base.html. */
.unit-toggle-btn.active { cursor: default; }
.u-contents { display: contents; }
.u-flush-block { margin: 0; }
.sidebar-section--tight { padding-top: var(--ds-space-4); }
.sidebar-section-label--tight { padding-top: var(--ds-space-2); }

.dev-link-list {
  font-size: var(--ds-type-base);
  line-height: 2.2;
}

/* ── CLF SENSITIVITY POINT PALETTE (A7 part 2) ──
   The sensitivity tables paint one marker per iteration, and the colour is
   chosen per row in Python. That was style="background-color: {{ ... }}",
   which style-src-attr no longer allows. The palette is a closed set, so the
   choice is expressible as a class; the colours themselves are still the ones
   in assessment_clf/core/sensitivity_display.py, and tests/test_security.py
   fails if the two lists stop matching. */

.clf-point-color-1 { --clf-sensitivity-point-color: #0072B2; }
.clf-point-color-2 { --clf-sensitivity-point-color: #E69F00; }
.clf-point-color-3 { --clf-sensitivity-point-color: #009E73; }
.clf-point-color-4 { --clf-sensitivity-point-color: #CC79A7; }
.clf-point-color-5 { --clf-sensitivity-point-color: #D55E00; }
.clf-point-color-6 { --clf-sensitivity-point-color: #6F42C1; }
.clf-point-color-7 { --clf-sensitivity-point-color: #56B4E9; }
.clf-point-color-8 { --clf-sensitivity-point-color: #8C564B; }
.clf-point-color-9 { --clf-sensitivity-point-color: #17A2B8; }
.clf-point-color-10 { --clf-sensitivity-point-color: #7A8B00; }
.clf-point-color-baseline { --clf-sensitivity-point-color: #343A40; }

/* The marker shapes read the property, so a shape and a colour compose
   instead of every combination needing its own rule. */
.clf-sensitivity-point-marker--circle,
.clf-sensitivity-point-marker--diamond {
  background-color: var(--clf-sensitivity-point-color);
}


/* The sensitivity panel's own chrome, previously inline in four templates.
   The header keeps its purple: it is the one panel colour that identifies
   this analysis, and changing it is a design decision, not a CSP one. The
   max-flaw row does not keep its #0d6efd, which is Bootstrap's blue and the
   same pre-A1 leftover A7 part 1 found in nine wizard steppers. */
.clf-sensitivity-header { background-color: #6F42C1 !important; }

.clf-sensitivity-chart { max-height: 320px; }
.clf-sensitivity-chart--tall { max-height: 360px; }

.clf-sensitivity-max-flaw-row { border-top: 2px solid var(--ds-info); }

/* The stress-distribution preview a wizard draws while the engineer is still
   entering the profile. Same height cap as the sensitivity chart, different
   thing, so it gets its own name rather than borrowing that one. */
.clf-preview-chart { max-height: 320px; }

/* An acceptance-criteria table: the check, the numbers it compared, and
   whether it passed. Eleven result templates render one, and each of them
   carried the failing-value styling as a Django {% if %} inside a style
   attribute, which is the one shape a static stylesheet cannot take over.
   It is a conditional class now. */
.criteria-value {
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
}

.criteria-value--failing {
  color: var(--ds-fail);
  font-weight: 600;
}

.criteria-row--failing {
  background: var(--ds-fail-bg);
}

/* The palette on /dev/components/. Each swatch now paints itself from the
   token it is labelled with, rather than from a hex copied next to it, so a
   changed token shows up on the page that exists to show the tokens. The hex
   in the caption is still text and still hand-written. */
.ds-swatch--primary         { background: var(--ds-primary); }
.ds-swatch--primary-dark    { background: var(--ds-primary-dark); }
.ds-swatch--primary-light   { background: var(--ds-primary-light); }
.ds-swatch--secondary       { background: var(--ds-secondary); }
.ds-swatch--secondary-light { background: var(--ds-secondary-light); }
.ds-swatch--accent          { background: var(--ds-accent); }
.ds-swatch--accent-dark     { background: var(--ds-accent-dark); }
.ds-swatch--bg              { background: var(--ds-bg); border: 1px solid var(--ds-border); }
.ds-swatch--surface         { background: var(--ds-surface); border: 1px solid var(--ds-border); }
.ds-swatch--pass            { background: var(--ds-pass); }
.ds-swatch--fail            { background: var(--ds-fail); }
.ds-swatch--review          { background: var(--ds-review); }

.calc-row--total { font-weight: 600; background: var(--ds-bg); }
.calc-value--pass { font-weight: 700; color: var(--ds-pass); }
.ds-card--accent { border-left: 3px solid var(--ds-accent); }

.u-text-secondary { color: var(--ds-text-secondary); }

/* ── THE LAST OF THE INLINE DECLARATIONS (A7 part 2) ── */

.u-inline      { display: inline; }
.u-pre-line    { white-space: pre-line; }
.u-no-caps     { text-transform: none; }
.u-clickable   { cursor: pointer; }
.u-scroll-box  { max-height: 500px; overflow-y: auto; }
.u-rule--wide  { margin-block: var(--ds-space-9); }
.u-col-20      { width: 20%; }
.u-col-25      { width: 25%; }
.u-col-30      { width: 30%; }
.u-col-50      { width: 50%; }

.u-preview-scroll--sm { max-height: 180px; }
.u-preview-scroll--md { max-height: 240px; }

/* A short explanatory block set slightly tighter than body copy. */
.note-block {
  font-size: var(--ds-type-sm);
  line-height: 1.45;
}

/* A caption under a figure or a preview image. */
.figure-caption-sm {
  font-size: var(--ds-type-sm);
  margin-top: var(--ds-space-3);
}

/* An indented sub-parameter row in the CLF FAD parameter table. */
.fad-subparameter { padding-left: var(--ds-space-11); }

.fad-plot-canvas { max-height: 400px; }

.fad-plot-marker {
  font-size: var(--ds-type-lg);
  color: var(--ds-text);
}

/* A fixed-height frame a chart is drawn into. */
.chart-frame-360 {
  position: relative;
  height: 360px;
}

/* The narrow single-purpose pages: change password, and its confirmation. */
.form-page-narrow {
  max-width: 440px;
  margin: var(--ds-space-11) auto 0;
}

.form-page-icon {
  color: var(--ds-pass);
  font-size: var(--ds-type-2xl);
  margin-bottom: var(--ds-space-6);
}

/* A footer that sits under a card body without a rule of its own above the
   page background. */
.ds-card-footer--plain {
  border-bottom: none;
  background: var(--ds-surface);
}

/* A destructive action that should not shout: red text, ordinary border. */
.btn-quiet-danger {
  color: var(--ds-fail);
  border-color: var(--ds-border);
}

/* Secondary figures beside a verdict: present, not competing with it. */
.result-subtle {
  font-size: var(--ds-type-xs);
  font-weight: 400;
  opacity: 0.82;
}

/* A form beside its geometry figure, with the figure staying in view while
   the form scrolls. Used by the PC and HBD Level 1 forms. */
.form-with-figure {
  display: flex;
  gap: var(--ds-space-11);
  align-items: flex-start;
}

.form-with-figure__fields { flex: 1 1 60%; }
.form-with-figure__fields--full { flex: 1 1 100%; }

.form-with-figure__figure {
  flex: 1 1 40%;
  max-width: 420px;
}

.form-with-figure__sticky {
  position: sticky;
  top: var(--ds-space-7);
}

.form-with-figure__heading { padding-bottom: var(--ds-space-4); }

.geometry-preview-frame { position: relative; }

.geometry-preview-image {
  width: 100%;
  height: auto;
  border: 1px solid var(--ds-border);
  border-radius: 4px;
}

.geometry-preview-note { padding-top: var(--ds-space-3); }

/* Part 6: a quiet, unscaled metal-surface illustration beside its grade input. */
.pit-density-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  align-items: start;
  gap: var(--ds-space-11);
}

.pit-density-form > * { min-width: 0; }

.pit-density {
  width: 100%;
  max-width: 560px;
  margin-bottom: 0;
  overflow: hidden;
  border: 1px solid var(--ds-border);
  border-radius: 4px;
  background: var(--ds-surface);
}

.pit-density__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ds-space-4) var(--ds-space-8);
  padding: var(--ds-space-8) var(--ds-space-9) 0;
  color: var(--ds-primary);
  font-size: var(--ds-type-base);
  font-weight: 600;
}

.pit-density__grade {
  color: var(--ds-text-muted);
  font-family: var(--ds-font-mono);
  font-size: var(--ds-type-sm);
  font-weight: 400;
  white-space: nowrap;
}

.pit-density__image {
  display: block;
  width: 100%;
  height: auto;
}

.pit-density__empty {
  display: grid;
  place-items: center;
  min-height: 180px;
  padding: var(--ds-space-10);
  color: var(--ds-text-muted);
  text-align: center;
}

.pit-density .u-hidden { display: none; }

.pit-density__caption {
  margin: 0;
  padding: var(--ds-space-8) var(--ds-space-9);
  border-top: 1px solid var(--ds-border);
  color: var(--ds-text-muted);
  font-size: var(--ds-type-sm);
  line-height: 1.6;
}

@media (max-width: 991.98px) {
  .pit-density-layout { grid-template-columns: minmax(0, 1fr); }
  .pit-density-form { flex-direction: column; }
  .pit-density-form > * { flex: none; width: 100%; }
  .pit-density-form .form-with-figure__figure { max-width: 560px; margin-inline: auto; }
}

/* The bar under the last step of the GML wizard. */
.wizard-summary-bar {
  border-top: 1px solid var(--ds-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--ds-space-7) var(--ds-space-11);
}

.u-space-between  { justify-content: space-between; }
.u-stack-stretch  { flex-direction: column; align-items: stretch; }
.u-gap-6          { gap: var(--ds-space-6); }
.u-hero-icon      { font-size: var(--ds-icon-hero); }

.u-w-220 { width: 220px; }
.u-max-w-400  { max-width: 400px; }
.u-max-w-420  { max-width: 420px; }
.u-max-w-520  { max-width: 520px; }
.u-max-w-820  { max-width: 820px; }
.u-max-w-880  { max-width: 880px; }

.u-min-w-9rem  { min-width: 9rem; }
.u-min-w-22rem { min-width: 22rem; }

/* The failure reasons listed under an outcome banner. */
.outcome-reason {
  font-size: var(--ds-type-sm);
  font-weight: 400;
  padding-left: var(--ds-space-9);
}

/* The circled step number in the component-type assumption panel. */
.assumption-step-badge {
  width: 1.75rem;
  height: 1.75rem;
  font-weight: 700;
  line-height: 1;
}

/* /dev/ user list: which row is the session's own user. */
.dev-current-user { background: rgba(46,109,164,0.08); }

/* ── ERROR PAGES (404 / 500) ────────────────────────────────────────────────
   500.html extends nothing and renders without a request, so it cannot rely
   on the app shell. Everything it needs is here, including the standalone
   page frame, because a CSP without 'unsafe-inline' leaves no other route. */
.error-page { max-width: 40rem; }

.error-page__code {
  font-size: var(--ds-type-2xl);
  font-weight: 700;
  color: var(--ds-text-muted);
  margin-bottom: var(--ds-space-2);
  line-height: 1;
}

.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-6);
  margin-top: var(--ds-space-10);
}

/* The 500 page has no sidebar or topbar to sit inside, so it centres itself. */
.error-page-standalone {
  background: var(--ds-bg);
  color: var(--ds-text);
}

.error-page--standalone {
  margin: 0 auto;
  padding: var(--ds-space-13) var(--ds-space-10);
}
