/* web-bank. Generated by scripts/build.py. Do not edit: change tokens/ or css/ and rebuild. */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");

/* ==== tokens ==== */
:root {
  /* color */
  --wb-color-ground: #ffffff;
  --wb-color-surface: #f2f3f5;
  --wb-color-surface-muted: #f2f3f5;
  --wb-color-row-hover: #f7f8f9;
  --wb-color-fill-hover: #f3f4f6;
  --wb-color-fill: #e5e7eb;
  --wb-color-ink: #1c1c1c;
  --wb-color-heading: #111111;
  --wb-color-soft: #686f7d;
  --wb-color-soft-strong: #374151;
  --wb-color-rule-section: #b9bec6;
  --wb-color-rule: #d8dbe0;
  --wb-color-control-border: #d1d5db;
  --wb-color-check-border: #9ca3af;
  --wb-color-accent: #0f172a;
  --wb-color-accent-hover: rgb(15 23 42 / 0.9);
  --wb-color-accent-ink: #ffffff;
  --wb-color-link: #0f172a;
  --wb-color-positive: #047857;
  --wb-color-negative: #b91c1c;
  --wb-color-destructive: #dc2626;
  --wb-color-destructive-hover: rgb(220 38 38 / 0.9);
  --wb-color-destructive-ink: #ffffff;
  --wb-color-destructive-ring: rgb(220 38 38 / 0.2);
  --wb-color-focus-ring: rgb(156 163 175 / 0.5);
  --wb-color-focus-border: #9ca3af;
  --wb-color-status-neutral-bg: #e5e7eb;
  --wb-color-status-neutral-ink: #374151;
  --wb-color-status-warn-bg: #fbe2c0;
  --wb-color-status-warn-ink: #7a3e06;
  --wb-color-status-danger-bg: #f8d6d6;
  --wb-color-status-danger-ink: #7f1d1d;
  --wb-color-status-ok-bg: #d3ecdf;
  --wb-color-status-ok-ink: #065f46;
  --wb-color-status-info-bg: #d9e4f7;
  --wb-color-status-info-ink: #1e3a8a;
  --wb-color-overlay: #ffffff;
  --wb-color-overlay-border: #d8dbe0;
  --wb-color-backdrop: rgb(0 0 0 / 0.5);
  --wb-color-tooltip: #0f172a;
  --wb-color-tooltip-ink: #ffffff;
  --wb-color-placeholder: #6b7280;
  --wb-color-checked: #0f172a;
  --wb-color-checked-ink: #ffffff;
  --wb-color-switch-off: #d1d5db;
  --wb-color-track: rgb(15 23 42 / 0.2);
  --wb-color-slider-track: #d8dbe0;
  --wb-color-skeleton: #e5e7eb;
  --wb-color-invalid: #dc2626;
  --wb-color-invalid-ring: rgb(220 38 38 / 0.2);
  --wb-color-error-ink: #b91c1c;
  --wb-color-scroll-edge: rgb(0 0 0 / 0.12);
  --wb-color-highlight: #0f172a;
  --wb-color-highlight-ink: #ffffff;
  --wb-color-danger-surface: #fef2f2;

  /* font */
  --wb-font-sans: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --wb-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* weight */
  --wb-weight-regular: 400;
  --wb-weight-medium: 500;
  --wb-weight-semibold: 600;
  --wb-weight-bold: 700;

  /* text */
  --wb-text-title: 2.8rem;
  --wb-text-standfirst: 1.15rem;
  --wb-text-section: 1.3rem;
  --wb-text-subsection: 1.05rem;
  --wb-text-figure: 1.15rem;
  --wb-text-quote: 1.1rem;
  --wb-text-body: 16px;
  --wb-text-kicker: 0.75rem;
  --wb-text-tool: 13px;
  --wb-text-min: 12px;
  --wb-text-tool-title: 1.75rem;
  --wb-text-tool-heading: 1.4rem;
  --wb-text-tool-section: 1rem;
  --wb-text-tool-figure: 1rem;
  --wb-text-caption: 0.85rem;
  --wb-text-title-narrow: 2rem;
  --wb-text-site-section: 2.25rem;
  --wb-text-site-section-narrow: 1.6rem;
  --wb-text-dash-figure: 1.25rem;

  /* ratio */
  --wb-ratio-small: 0.85em;
  --wb-ratio-label: 0.8em;
  --wb-ratio-head: 0.85em;

  /* leading */
  --wb-leading-body: 1.7;
  --wb-leading-heading: 1.15;
  --wb-leading-standfirst: 1.45;
  --wb-leading-control: 1.2;

  /* tracking */
  --wb-tracking-heading: -0.015em;
  --wb-tracking-figure: -0.02em;
  --wb-tracking-kicker: 0.08em;
  --wb-tracking-group: 0.06em;

  /* space */
  --wb-space-1: 0.25rem;
  --wb-space-2: 0.4rem;
  --wb-space-3: 0.5rem;
  --wb-space-4: 0.6rem;
  --wb-space-5: 0.75rem;
  --wb-space-6: 0.9rem;
  --wb-space-7: 1rem;
  --wb-space-8: 1.25rem;
  --wb-space-9: 1.5rem;
  --wb-space-10: 2rem;
  --wb-space-11: 2.2rem;
  --wb-space-12: 3rem;
  --wb-space-13: 3.5rem;
  --wb-space-14: 6rem;

  /* layout */
  --wb-layout-read-width: 960px;
  --wb-layout-gutter: 2rem;
  --wb-layout-gutter-narrow: 1rem;
  --wb-layout-tool-gutter: 1.5rem;
  --wb-layout-section-gap: 2.2rem;
  --wb-layout-tool-row-pad: 0.4rem;
  --wb-layout-site-width: 1200px;
  --wb-layout-portal-width: 1360px;

  /* radius */
  --wb-radius-control: 6px;
  --wb-radius-panel: 6px;
  --wb-radius-image: 8px;
  --wb-radius-pill: 999px;
  --wb-radius-small: 4px;
  --wb-radius-overlay: 8px;

  /* line */
  --wb-line-rule: 1px;
  --wb-line-focus: 3px;

  /* motion */
  --wb-motion-fast: 150ms;
  --wb-motion-base: 200ms;
  --wb-motion-ease: cubic-bezier(0.4, 0, 0.2, 1);

  /* shadow */
  --wb-shadow-overlay: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --wb-shadow-dialog: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);

  /* size */
  --wb-size-check: 1rem;
  --wb-size-switch-w: 2rem;
  --wb-size-switch-h: 1.15rem;
  --wb-size-icon: 1rem;
  --wb-size-avatar: 2rem;
  --wb-size-dialog: 32rem;
  --wb-size-sheet: 24rem;
  --wb-size-menu-min: 8rem;

  /* opacity */
  --wb-opacity-disabled: 0.5;

  /* layer */
  --wb-layer-overlay: 50;
  --wb-layer-toast: 100;

  /* chart */
  --wb-chart-series-1: #1d4f91;
  --wb-chart-series-2: #eb6834;
  --wb-chart-series-3: #79a7e3;
  --wb-chart-series-4: #eda100;
  --wb-chart-series-5: #8a5cc2;
  --wb-chart-sequential-1: #cde2fb;
  --wb-chart-sequential-2: #86b6ef;
  --wb-chart-sequential-3: #3987e5;
  --wb-chart-sequential-4: #1c5cab;
  --wb-chart-sequential-5: #0d366b;
  --wb-chart-up: #047857;
  --wb-chart-down: #b91c1c;
  --wb-chart-total: #0f172a;
  --wb-chart-grid: #e5e7eb;
  --wb-chart-axis: #b9bec6;
  --wb-chart-label: #6b7280;
  --wb-chart-value: #1c1c1c;
  --wb-chart-surface: #ffffff;
}

/* ==== css/base.css ==== */
/* Base: element defaults every page gets. Reading-page density (16px) is the default;
   the tool layout (.wb-tool) switches the page to 13px. Semantic tokens only. */

*, *::before, *::after { box-sizing: border-box; }

/* light only, whatever the viewer's system setting (house rule 7): form controls and scrollbars too */
html { color-scheme: light; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--wb-color-ground);
  color: var(--wb-color-ink);
  font-family: var(--wb-font-sans);
  font-size: var(--wb-text-body);
  line-height: var(--wb-leading-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ol, ul { margin: 0; }

h1, h2, h3, h4 {
  color: var(--wb-color-heading);
  font-weight: var(--wb-weight-bold);
  letter-spacing: var(--wb-tracking-heading);
  line-height: var(--wb-leading-heading);
}
h1 { font-size: var(--wb-text-title); }
h2 { font-size: var(--wb-text-section); }
h3 { font-size: var(--wb-text-subsection); }
h4 { font-size: var(--wb-text-body); }

@media (max-width: 640px) {
  h1 { font-size: var(--wb-text-title-narrow); }
}

a {
  color: var(--wb-color-link);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: var(--wb-line-rule);
}
a:hover { text-decoration-thickness: 0.1em; }

:focus-visible {
  outline: var(--wb-line-focus) solid var(--wb-color-focus-ring);
  outline-offset: 1px;
}

img, svg, video { display: block; max-width: 100%; height: auto; }

/* A closed popover or dialog stays hidden even when its component sets a display of its own. */
[popover]:not(:popover-open),
dialog:not([open]) { display: none; }

table { border-collapse: collapse; }

button, input, select, textarea { font: inherit; color: inherit; }

code, kbd, pre, samp { font-family: var(--wb-font-mono); font-size: 0.9em; }

/* Numbers. Green is for positive numbers only (house rule 2); nothing else is ever green. */
.wb-num { font-variant-numeric: tabular-nums; }
.wb-positive { color: var(--wb-color-positive); }
.wb-negative { color: var(--wb-color-negative); }

/* Small tracked capitals above a title or on a page's meta line. */
.wb-kicker {
  font-size: var(--wb-text-kicker);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

/* A row of actions: buttons side by side, wrapping on narrow screens. */
.wb-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-4);
}

.wb-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A wide thing (a tool table) scrolls inside itself on a narrow screen; the page never does. A soft
   shadow shows at an edge while there is more to scroll to on that side (the covers move with the
   content, the shadows stay with the box). */
.wb-scroll {
  position: relative;
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--wb-color-ground) 40%, transparent) left / var(--wb-space-10) 100% no-repeat local,
    linear-gradient(to left, var(--wb-color-ground) 40%, transparent) right / var(--wb-space-10) 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--wb-color-scroll-edge), transparent) left / var(--wb-space-5) 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--wb-color-scroll-edge), transparent) right / var(--wb-space-5) 100% no-repeat scroll;
}

/* On paper there is no edge to scroll to: the shadow gradients would print as grey bars down the
   sides of every table, and the content behind them has to be shown in full. */
@media print {
  .wb-scroll { overflow: visible; background: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; }
}

/* ==== css/components/accordion.css ==== */
/* Accordion. Native details/summary, no JavaScript:
     <div class="wb-accordion">
       <details class="wb-accordion__item">
         <summary class="wb-accordion__summary">Question
           <svg class="wb-accordion__chevron" aria-hidden="true">...</svg>
         </summary>
         <div class="wb-accordion__panel">Answer</div>
       </details>
     </div>
   Items are divided by hairlines, the summary is medium weight in heading colour with a chevron
   that turns when the item is open. No background fills. The browser's own open/close drives
   [open]; the script only needs to add aria-disabled="true" to a summary that cannot be opened.
   States: :hover (underline), :focus-visible, [open], [aria-disabled="true"]. */

.wb-accordion__item { border-bottom: var(--wb-line-rule) solid var(--wb-color-rule); }
.wb-accordion__item:last-child { border-bottom: 0; }

.wb-accordion__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wb-space-7);
  padding: var(--wb-space-7) 0;
  border-radius: var(--wb-radius-control);
  color: var(--wb-color-heading);
  font-weight: var(--wb-weight-medium);
  list-style: none;
  cursor: pointer;
}
.wb-accordion__summary::-webkit-details-marker { display: none; }
.wb-accordion__summary:hover { text-decoration: underline; }
.wb-accordion__summary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-accordion__chevron {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  color: var(--wb-color-soft);
  transition: transform var(--wb-motion-base) var(--wb-motion-ease);
}
.wb-accordion__item[open] .wb-accordion__chevron { transform: rotate(180deg); }

.wb-accordion__panel { padding-bottom: var(--wb-space-7); color: var(--wb-color-ink); }

.wb-accordion__summary[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

/* ==== css/components/alert-dialog.css ==== */
/* Alert dialog. The same surface and parts as .wb-dialog (css/components/dialog.css); only the
   differences live here. It asks a question the user must answer, so there is no close button and
   no way out except the footer's cancel. role="alertdialog" makes a screen reader announce it at
   once.
   Markup:
     <dialog class="wb-dialog wb-dialog--alert" role="alertdialog">
       <header class="wb-dialog__header">
         <div>
           <h2 class="wb-dialog__title">Withdraw the bid?</h2>
           <p class="wb-dialog__description">...</p>
         </div>
       </header>
       <footer class="wb-dialog__footer">
         <button class="wb-button wb-button--secondary">Cancel</button>
         <button class="wb-button wb-button--destructive">Withdraw bid</button>
       </footer>
     </dialog>
   States: the footer buttons keep their .wb-button states. */

.wb-dialog--alert .wb-dialog__header { display: block; }
.wb-dialog--alert .wb-dialog__description { margin-top: var(--wb-space-1); }

/* ==== css/components/alert.css ==== */
/* Alert. A short message that stays in the page: a confirmation, a warning, an error. A light
   panel with no border (Angus's pick, 24 Sep 2026) and an overlay radius; never a coloured stripe
   down one side (house rule 3).
   <div class="wb-alert" role="alert">
     <svg>icon</svg>                                  optional
     <div class="wb-alert__title">Title</div>
     <div class="wb-alert__description">One line, or a list.</div>
   </div>
   With no icon the first grid column closes, so the text starts at the padding edge.
   Variants: (default) and --destructive. Destructive colours the title and the icon and sits on a light red tint; the
   description stays in ink. A success message is the default variant: nothing is ever green
   except a positive number (house rule 2). */

.wb-alert {
  display: grid;
  grid-template-columns: 0 1fr;
  align-items: start;
  column-gap: 0;
  row-gap: var(--wb-space-1);
  padding: var(--wb-space-5) var(--wb-space-7);
  border: var(--wb-line-rule) solid transparent;
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-surface);
  color: var(--wb-color-ink);
}
.wb-alert:has(> svg) {
  grid-template-columns: var(--wb-size-icon) 1fr;
  column-gap: var(--wb-space-5);
}
.wb-alert > svg {
  width: var(--wb-size-icon);
  height: var(--wb-size-icon);
  transform: translateY(var(--wb-space-1));
  color: var(--wb-color-ink);
}

.wb-alert__title {
  grid-column: 2;
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
}

.wb-alert__description {
  grid-column: 2;
  color: var(--wb-color-soft);
}
.wb-alert__description ul {
  margin-top: var(--wb-space-1);
  padding-left: var(--wb-space-7);
}

.wb-alert--destructive { background: var(--wb-color-danger-surface); }
.wb-alert--destructive > svg { color: var(--wb-color-destructive); }
.wb-alert--destructive .wb-alert__title { color: var(--wb-color-destructive); }
.wb-alert--destructive .wb-alert__description { color: var(--wb-color-ink); }

/* ==== css/components/avatar.css ==== */
/* Avatar. A person or firm's picture, or their initials when there is none. Round, one size
   (--wb-size-avatar).
   <span class="wb-avatar"><img src="..." alt="Angus Taylor"></span>
   <span class="wb-avatar">AT</span>                     initials fallback
   .wb-avatars is an overlapping stack; each avatar carries a ground-coloured ring so the overlap
   reads against the one behind it. */

.wb-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--wb-size-avatar);
  height: var(--wb-size-avatar);
  flex-shrink: 0;
  overflow: hidden;
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-fill);
  color: var(--wb-color-soft);
  font-size: var(--wb-ratio-small);
  font-weight: var(--wb-weight-medium);
  line-height: 1;
  user-select: none;
}

.wb-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.wb-avatars { display: flex; }
.wb-avatars .wb-avatar { box-shadow: 0 0 0 calc(2 * var(--wb-line-rule)) var(--wb-color-ground); }
.wb-avatars .wb-avatar + .wb-avatar { margin-left: calc(-1 * var(--wb-space-1)); }

/* ==== css/components/breadcrumb.css ==== */
/* Breadcrumb. <nav aria-label="Breadcrumb" class="wb-breadcrumb">
     <ol class="wb-breadcrumb__list">
       <li class="wb-breadcrumb__item"><a class="wb-breadcrumb__link" href="#">Pipeline</a></li>
       <li class="wb-breadcrumb__sep" role="presentation" aria-hidden="true"></li>
       ...
       <li class="wb-breadcrumb__item"><span class="wb-breadcrumb__current" aria-current="page">Due diligence</span></li>
     </ol>
   </nav>
   The separator is a soft slash drawn in CSS; a long trail collapses to a
   .wb-breadcrumb__ellipsis item.
   States: link :hover, :focus-visible. */

.wb-breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-1) var(--wb-space-3);
  padding: 0;
  list-style: none;
}

.wb-breadcrumb__item { display: inline-flex; align-items: center; }

.wb-breadcrumb__link {
  color: var(--wb-color-soft);
  text-decoration: none;
  transition: color var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-breadcrumb__link:hover { color: var(--wb-color-heading); }
.wb-breadcrumb__link:focus-visible {
  outline: none;
  border-radius: var(--wb-radius-small);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-breadcrumb__current { color: var(--wb-color-heading); }

.wb-breadcrumb__sep { color: var(--wb-color-soft); user-select: none; }
.wb-breadcrumb__sep::before { content: "/"; }

.wb-breadcrumb__ellipsis { color: var(--wb-color-soft); }

/* ==== css/components/button-group.css ==== */
/* Button group. Buttons joined into one control: one shared border between neighbours,
   corners only on the outer ends. Horizontal only.
   <div class="wb-button-group" role="group" aria-label="...">
     <button class="wb-button wb-button--secondary">Approve</button>
     <button class="wb-button wb-button--secondary">Ask for the model</button>
   </div>
   Mainly .wb-button--secondary; a single primary may lead the group. Children are ordinary
   buttons and keep every .wb-button state (hover, focus-visible, active, disabled): the group
   adds the join and nothing else. The join is a negative margin, not a removed border, so the
   border rank is unchanged and the build's ban on side borders holds. */

.wb-button-group {
  display: inline-flex;
  align-items: stretch;
  width: fit-content;
}

/* neighbours overlap by one hairline, so only a single border shows at each seam */
.wb-button-group > .wb-button:not(:first-child) {
  margin-left: calc(-1 * var(--wb-line-rule));
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.wb-button-group > .wb-button:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* the focused child's ring must sit above its neighbours, or the join clips it */
.wb-button-group > .wb-button:focus-visible {
  position: relative;
  z-index: 1;
}

/* ==== css/components/button.css ==== */
/* Button. <button class="wb-button"> or <a class="wb-button">.
   Variants: (primary, the default) --secondary --ghost --destructive --link.
   Sizes: (default) --sm --lg --icon. Text size follows the page: 16px on reading pages, 13px in tools.
   States: :hover, :focus-visible, :active, :disabled / [aria-disabled="true"], [aria-busy="true"] (loading).
   Picks: filled navy, 6px corners, medium weight (round 3). Hover and focus from shadcn. */

.wb-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-3) var(--wb-space-7);
  border: var(--wb-line-rule) solid var(--wb-color-accent);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-accent);
  color: var(--wb-color-accent-ink);
  font: inherit;
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease),
              color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-button svg { width: 1em; height: 1em; flex-shrink: 0; pointer-events: none; }

.wb-button:hover,
.wb-button:active { background: var(--wb-color-accent-hover); border-color: var(--wb-color-accent-hover); }

.wb-button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

/* secondary: white with a border */
.wb-button--secondary {
  background: var(--wb-color-ground);
  border-color: var(--wb-color-control-border);
  color: var(--wb-color-heading);
}
.wb-button--secondary:hover,
.wb-button--secondary:active { background: var(--wb-color-fill-hover); border-color: var(--wb-color-control-border); }
.wb-button--secondary:focus-visible { border-color: var(--wb-color-focus-border); }

/* ghost: no fill until hovered */
.wb-button--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--wb-color-soft);
}
.wb-button--ghost:hover,
.wb-button--ghost:active { background: var(--wb-color-fill-hover); border-color: transparent; color: var(--wb-color-heading); }

/* destructive: for deleting or passing on something that cannot be undone */
.wb-button--destructive {
  background: var(--wb-color-destructive);
  border-color: var(--wb-color-destructive);
  color: var(--wb-color-destructive-ink);
}
.wb-button--destructive:hover,
.wb-button--destructive:active { background: var(--wb-color-destructive-hover); border-color: var(--wb-color-destructive-hover); }
.wb-button--destructive:focus-visible { box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-destructive-ring); }

/* link: an action that reads as a link */
.wb-button--link {
  background: transparent;
  border-color: transparent;
  color: var(--wb-color-link);
  text-underline-offset: 0.15em;
}
.wb-button--link:hover,
.wb-button--link:active { background: transparent; border-color: transparent; text-decoration: underline; }

/* sizes */
.wb-button--sm { padding: var(--wb-space-1) var(--wb-space-5); gap: var(--wb-space-2); }
.wb-button--lg { padding: var(--wb-space-4) var(--wb-space-9); }
.wb-button--icon { padding: var(--wb-space-3); aspect-ratio: 1; }
.wb-button--icon svg { width: calc(1em * var(--wb-leading-control)); height: calc(1em * var(--wb-leading-control)); }

/* disabled */
.wb-button:disabled,
.wb-button[aria-disabled="true"] { opacity: var(--wb-opacity-disabled); pointer-events: none; cursor: default; }

/* loading: a spinner before the label; the button keeps its width and cannot be pressed twice */
.wb-button[aria-busy="true"] { pointer-events: none; cursor: progress; }
.wb-button[aria-busy="true"]::before {
  content: "";
  width: 1em;
  height: 1em;
  border: 0.15em solid currentColor;
  border-right-color: transparent;
  border-radius: var(--wb-radius-pill);
  animation: wb-spin 0.7s linear infinite;
}

@keyframes wb-spin { to { transform: rotate(360deg); } }

/* ==== css/components/card.css ==== */
/* Card. A block for one item in a grid of like items: deals, hotels, lenders. A light grey panel
   with no border and no shadow (Angus's pick, 24 Sep 2026, over a hairline border).
   Cards are for a grid of like items, never for boxing the sections of a reading page: those are
   divided by rules, not by cards (house rule 8).
   Parts:  .wb-card__header       holds the title and description
           .wb-card__title        the item's name
           .wb-card__description  the soft line under it
           .wb-card__content      the body
           .wb-card__footer       a row of meta and actions
   .wb-cards is the grid the cards sit in: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)). */

.wb-card {
  display: flex;
  flex-direction: column;
  gap: var(--wb-space-9);
  padding: var(--wb-space-9) 0;
  border: var(--wb-line-rule) solid transparent;
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-surface);
  color: var(--wb-color-ink);
}

.wb-card__header {
  display: grid;
  gap: var(--wb-space-3);
  padding: 0 var(--wb-space-9);
}

.wb-card__title {
  font-weight: var(--wb-weight-semibold);
  color: var(--wb-color-heading);
  line-height: var(--wb-leading-heading);
}

.wb-card__description { color: var(--wb-color-soft); }

.wb-card__content { padding: 0 var(--wb-space-9); }

.wb-card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-5);
  padding: 0 var(--wb-space-9);
}

.wb-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--wb-space-9);
}

/* ==== css/components/chart.css ==== */
/* Chart. A <figure class="wb-chart"> drawn by js/charts.js from the JSON inside it. The page
   supplies the title and the data; colours, marks, legend, tooltip and table view come from here
   and from the chart tokens. One chart, one y-axis, always. */

.wb-chart { display: grid; gap: var(--wb-space-5); min-width: 0; }
/* no part of a chart (the plot, the table view) may widen it: a wide table scrolls inside itself */
.wb-chart > * { min-width: 0; }

.wb-chart figcaption { display: grid; gap: var(--wb-space-1); }
.wb-chart__title { font-size: inherit; font-weight: var(--wb-weight-semibold); }
.wb-tool .wb-chart__title { font-size: var(--wb-text-tool-section); font-weight: var(--wb-weight-semibold); }
.wb-chart__sub { font-size: var(--wb-ratio-small); color: var(--wb-color-soft); }

/* the plot takes its width from the chart's container, never from the drawn chart, so a narrower
   window shrinks it (charts.js redraws at the new width) instead of pushing the page sideways */
.wb-chart__plot { width: 100%; min-width: 0; height: 20rem; overflow: hidden; }
.wb-tool .wb-chart__plot { height: 16rem; }
.wb-chart__plot[data-wb-chart="donut"] { height: 16rem; }

/* legend: text in text colours; the swatch beside it carries the series colour */
.wb-chart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wb-space-2) var(--wb-space-8);
  padding: 0;
  list-style: none;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}
.wb-chart__legend li { display: inline-flex; align-items: center; gap: var(--wb-space-3); }
.wb-chart__legend i { width: 0.75em; height: 0.75em; border-radius: var(--wb-radius-small); }
.wb-chart__legend b { font-weight: var(--wb-weight-medium); color: var(--wb-color-ink); font-variant-numeric: tabular-nums; }

/* tooltip: ECharts puts this class on its tooltip element */
.wb-chart__tooltip {
  position: absolute;
  pointer-events: none;
  min-width: 8rem;
  padding: var(--wb-space-3) var(--wb-space-5) !important;
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border) !important;
  border-radius: var(--wb-radius-control) !important;
  background: var(--wb-color-overlay) !important;
  box-shadow: var(--wb-shadow-overlay) !important;
  font-family: var(--wb-font-sans) !important;
  font-size: var(--wb-ratio-small) !important;
  color: var(--wb-color-ink) !important;
  font-variant-numeric: tabular-nums;
}
.wb-chart__tooltip:empty { display: none; }
.wb-chart__tooltip b { display: block; margin-bottom: var(--wb-space-1); font-weight: var(--wb-weight-semibold); color: var(--wb-color-heading); }
.wb-chart__tooltip span { display: flex; align-items: center; gap: var(--wb-space-3); }
.wb-chart__tooltip i { width: 0.75em; height: 0.75em; border-radius: var(--wb-radius-small); }
.wb-chart__tooltip em { margin-left: auto; padding-left: var(--wb-space-7); font-style: normal; font-weight: var(--wb-weight-medium); }

/* table view, generated under every chart: the summary is a line of text, not a box across the
   chart; the table under it sits flush with the chart, at the chart's own size */
.wb-chart__table summary {
  width: fit-content;
  cursor: pointer;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}
.wb-chart__table summary:hover { color: var(--wb-color-heading); }
.wb-chart__table summary:focus-visible { outline: none; box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring); border-radius: var(--wb-radius-small); }
.wb-chart__table .wb-scroll { margin-top: var(--wb-space-3); }

/* ==== css/components/checkbox.css ==== */
/* Checkbox. Native <input type="checkbox" class="wb-checkbox">, drawn with appearance: none.
   The tick is a mask and the indeterminate state a bar, so no colour is baked into the CSS;
   both are painted in --wb-color-checked-ink on a --wb-color-checked fill.
   Wrap it in <label class="wb-check"> (field.css) to give it a clickable label.
   States: :checked, :indeterminate (and [aria-checked="mixed"] as a scriptless alias, for the
   catalogue and for authors who drive it with ARIA), :hover, :focus-visible,
   [aria-invalid="true"], :disabled.
   No script here: the later behaviour script sets input.indeterminate, which CSS already styles. */

.wb-checkbox {
  flex: none;
  display: inline-grid;
  place-content: center;
  width: var(--wb-size-check);
  height: var(--wb-size-check);
  margin: 0;
  border: var(--wb-line-rule) solid var(--wb-color-check-border);
  border-radius: var(--wb-radius-small);
  background: var(--wb-color-ground);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-checkbox::before {
  content: "";
  width: calc(var(--wb-size-check) * 0.8);
  height: calc(var(--wb-size-check) * 0.8);
  background-color: var(--wb-color-checked-ink);
  transform: scale(0);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M9%2016.17%204.83%2012l-1.42%201.41L9%2019%2021%207l-1.41-1.41z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M9%2016.17%204.83%2012l-1.42%201.41L9%2019%2021%207l-1.41-1.41z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  transition: transform var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-checkbox:hover { border-color: var(--wb-color-soft); }

.wb-checkbox:checked,
.wb-checkbox:indeterminate,
.wb-checkbox[aria-checked="mixed"] {
  background: var(--wb-color-checked);
  border-color: var(--wb-color-checked);
}

.wb-checkbox:checked::before { transform: scale(1); }

.wb-checkbox:indeterminate::before,
.wb-checkbox[aria-checked="mixed"]::before {
  width: calc(var(--wb-size-check) * 0.6);
  height: calc(var(--wb-size-check) * 0.12);
  border-radius: var(--wb-radius-pill);
  transform: scale(1);
  -webkit-mask-image: none;
  mask-image: none;
}

.wb-checkbox:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-checkbox[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-checkbox[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}

.wb-checkbox:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

/* ==== css/components/combobox.css ==== */
/* Combobox. A text input that filters a list of options as you type. Native HTML cannot do this,
   so it needs the behaviour script; use the native <select class="wb-select"> when a plain choice
   will do.
   Markup (WAI-ARIA combobox with a listbox popup):
     <input class="wb-input" role="combobox" aria-autocomplete="list" aria-expanded="false"
            aria-controls="lender-list" aria-haspopup="listbox" id="lender-input">
     <div class="wb-listbox" role="listbox" id="lender-list" aria-label="Lender" popover>
       <div class="wb-listbox__option" role="option" aria-selected="false">NatWest</div>
       <div class="wb-listbox__empty">No match</div>
     </div>
   The popup and its options are styled by select.css; only what differs is here. The script
   toggles: aria-expanded on the input; [data-active] on the option named by the input's
   aria-activedescendant; [hidden] on the empty row; the popover's open state; aria-selected on
   options. The input itself keeps its states from .wb-input (focus-visible, invalid, disabled). */

.wb-listbox__empty {
  padding: var(--wb-space-5);
  text-align: center;
  color: var(--wb-color-soft);
}

/* ==== css/components/command.css ==== */
/* Command menu. A searchable list of commands or records, opened in a dialog. It is the WAI-ARIA
   combobox-with-listbox pattern: the input keeps focus and points at the highlighted option with
   aria-activedescendant.
   Markup:
     <div class="wb-command">
       <div class="wb-command__search">
         <svg class="wb-command__icon">search</svg>
         <input class="wb-command__input" role="combobox" aria-expanded="true"
                aria-controls="command-list" aria-autocomplete="list"
                aria-activedescendant="command-item-1" placeholder="Search deals">
       </div>
       <div class="wb-command__list" role="listbox" id="command-list" aria-label="Commands">
         <div class="wb-command__group" role="group" aria-labelledby="command-group-deals">
           <div class="wb-command__heading" id="command-group-deals">Deals</div>
           <div class="wb-command__item" role="option" id="command-item-1" aria-selected="true">
             Project Larch<kbd class="wb-command__shortcut">⌘1</kbd>
           </div>
         </div>
         <div class="wb-command__empty">No deals match "bath".</div>
       </div>
       <div class="wb-command__footer">↑↓ to move · ↵ to open · Esc to close</div>
     </div>
   The script toggles: aria-expanded on the input; [hidden] on the empty row and on non-matching
   items and groups; aria-activedescendant on the input and aria-selected on the highlighted option;
   the dialog's open state.
   States: item :hover and [aria-selected="true"] (the same navy highlight as a menu),
   [aria-disabled="true"]; the input keeps its own states. */

.wb-command {
  width: var(--wb-size-dialog);
  max-width: 100%;
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  overflow: hidden;
  z-index: var(--wb-layer-overlay);
}

.wb-command__search {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-3) var(--wb-space-5);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  color: var(--wb-color-soft);
}

.wb-command__icon { width: var(--wb-size-icon); height: var(--wb-size-icon); flex-shrink: 0; }

.wb-command__input {
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--wb-color-ink);
  font: inherit;
}
.wb-command__input::placeholder { color: var(--wb-color-placeholder); }
.wb-command__input:focus-visible {
  outline: none;
  border-radius: var(--wb-radius-small);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-command__list {
  max-height: var(--wb-size-sheet);
  overflow-y: auto;
  padding: var(--wb-space-2);
}

.wb-command__group + .wb-command__group {
  margin-top: var(--wb-space-2);
  padding-top: var(--wb-space-2);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-command__heading {
  padding: var(--wb-space-2) var(--wb-space-5);
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-group);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

.wb-command__item {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-2) var(--wb-space-5);
  border-radius: var(--wb-radius-small);
  color: var(--wb-color-ink);
  cursor: default;
  user-select: none;
}
.wb-command__item:hover,
.wb-command__item[aria-selected="true"] { background: var(--wb-color-highlight); color: var(--wb-color-highlight-ink); }
.wb-command__item:hover .wb-command__shortcut,
.wb-command__item[aria-selected="true"] .wb-command__shortcut { color: inherit; }
.wb-command__item[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
}

.wb-command__shortcut {
  margin-left: auto;
  padding-left: var(--wb-space-5);
  font-family: var(--wb-font-mono);
  font-size: var(--wb-ratio-label);
  letter-spacing: var(--wb-tracking-group);
  color: var(--wb-color-soft);
}

.wb-command__empty {
  padding: var(--wb-space-8);
  text-align: center;
  color: var(--wb-color-soft);
}

.wb-command__footer {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-3) var(--wb-space-5);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
  font-size: var(--wb-ratio-label);
  color: var(--wb-color-soft);
}

/* A content hit, for a finder that searches a document rather than a list of commands. A command
   is one line and a shortcut; a hit is a name, the phrase it matched in its own case, and what
   kind of thing it is. Add .wb-command__hit inside .wb-command__item:

     <div class="wb-command__item" role="option">
       <span class="wb-command__hit">
         <span class="wb-command__hit-name">The Market</span>
         <span class="wb-command__hit-snippet">…trades at this <mark>rate</mark>, and what they hold…</span>
       </span>
       <span class="wb-command__kind">Chapter</span>
     </div>

   The snippet is the reason the reader can tell two hits apart, so it takes two lines and the
   matched phrase is marked rather than restyled. States follow .wb-command__item's. */

.wb-command__hit { display: grid; gap: var(--wb-space-2); min-width: 0; }

.wb-command__hit-name { font-weight: var(--wb-weight-medium); color: var(--wb-color-heading); }

.wb-command__hit-snippet {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}
.wb-command__hit-snippet mark { background: var(--wb-color-highlight); color: var(--wb-color-highlight-ink); }

.wb-command__kind {
  font-size: var(--wb-text-kicker);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
  white-space: nowrap;
}

/* the highlighted hit keeps its snippet readable: the item's own selected treatment carries it */
.wb-command__item[aria-selected="true"] .wb-command__hit-name,
.wb-command__item[aria-selected="true"] .wb-command__hit-snippet,
.wb-command__item[aria-selected="true"] .wb-command__kind { color: inherit; }

/* ==== css/components/contents.css ==== */
/* Contents — the deal portal's landing page, where the contents is also the summary. One row per
   chapter, all the same height, each carrying its description and its headline figures. A row
   that grows because its blurb ran on reads as emphasis it has not earned, so the blurb is held
   to two lines and the figures run across rather than stacking.

   <div class="wb-contents">
     <a class="wb-contents__row" href="#/c/asset">
       <span class="wb-contents__num">01</span>
       <span class="wb-contents__main">
         <span class="wb-contents__title">The Asset</span>
         <span class="wb-contents__blurb">The estate, the five titles, the consent record.</span>
       </span>
       <span class="wb-contents__figures">
         <div><dt>60 keys</dt><dd>17 Main House · 43 Stables</dd></div>
         <div><dt>12</dt><dd>Branded residences</dd></div>
       </span>
     </a>
   </div>

   A chapter that states no headline figures carries .wb-contents__meta (its view count) in place
   of the rail, rather than an empty rail.
   States: default, hover, focus-visible, active. */

.wb-contents { border-top: var(--wb-line-rule) solid var(--wb-color-rule-section); }

.wb-contents__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--wb-space-6) var(--wb-space-12);
  padding: var(--wb-space-9) 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  color: inherit;
  text-decoration: none;
}
.wb-contents__row:hover { background: var(--wb-color-row-hover); }
.wb-contents__row:active { background: var(--wb-color-fill); }
.wb-contents__row:focus-visible {
  outline: var(--wb-line-focus) solid var(--wb-color-focus-ring);
  outline-offset: calc(var(--wb-space-2) * -1);
}

.wb-contents__num {
  padding-top: var(--wb-space-2);
  font-size: var(--wb-text-caption);
  font-variant-numeric: tabular-nums;
  color: var(--wb-color-soft);
}

.wb-contents__main { display: grid; gap: var(--wb-space-3); }

.wb-contents__title {
  font-size: var(--wb-text-section);
  font-weight: var(--wb-weight-bold);
  letter-spacing: var(--wb-tracking-heading);
  line-height: var(--wb-leading-heading);
  color: var(--wb-color-heading);
}
.wb-contents__row:hover .wb-contents__title { color: var(--wb-color-accent); }

/* two lines, so every row is the same height and the contents reads as a rhythm */
.wb-contents__blurb {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  color: var(--wb-color-soft);
}

.wb-contents__figures { display: flex; gap: var(--wb-space-10); text-align: right; }
.wb-contents__figures > div { display: grid; gap: var(--wb-space-1); }
.wb-contents__figures dt {
  font-size: var(--wb-text-figure);
  font-weight: var(--wb-weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--wb-tracking-figure);
  color: var(--wb-color-accent);
}
.wb-contents__figures dd { margin: 0; font-size: var(--wb-text-caption); color: var(--wb-color-soft); }

.wb-contents__meta {
  padding-top: var(--wb-space-2);
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
  white-space: nowrap;
}

@media (max-width: 800px) {
  .wb-contents__row { grid-template-columns: auto minmax(0, 1fr); }
  .wb-contents__figures,
  .wb-contents__meta { grid-column: 2; text-align: left; flex-wrap: wrap; gap: var(--wb-space-6) var(--wb-space-9); }
}

/* ==== css/components/decision.css ==== */
/* Decision. The ask at the end of a note: what has to be decided, and the buttons to decide it.
   <section class="wb-decision"><h3>Decision needed</h3><p>Whether to ...</p>
     <div class="wb-actions"><button class="wb-button">Approve the bid</button> ... </div></section>
   On a reading page it is the last section, so the section rule above it comes from the layout.
   The buttons are there only when they do something: on a paper that leaves as a file, keep the
   heading and the words and drop the actions row. */

.wb-decision h3 { margin-bottom: var(--wb-space-3); }
.wb-decision .wb-actions { margin-top: var(--wb-space-7); }

/* ==== css/components/dialog.css ==== */
/* Dialog. A focused task or a short message over a dimmed page, opened with showModal().
   Markup:
     <dialog class="wb-dialog">
       <header class="wb-dialog__header">
         <div>
           <h2 class="wb-dialog__title">Edit deal</h2>
           <p class="wb-dialog__description">...</p>
         </div>
         <button class="wb-button wb-button--ghost wb-button--icon wb-dialog__close"
                 aria-label="Close">x</button>
       </header>
       <div class="wb-dialog__body">
         <div class="wb-field">
           <label class="wb-field__label" for="deal-name">Deal name</label>
           <input class="wb-input" id="deal-name">
         </div>
       </div>
       <footer class="wb-dialog__footer">
         <button class="wb-button wb-button--secondary">Cancel</button>
         <button class="wb-button">Save changes</button>
       </footer>
     </dialog>
   The script calls showModal() and close(); the markup is complete without it (the catalogue shows
   it with the open attribute). ::backdrop dims the page behind.
   States: close and footer buttons keep .wb-button states; the fields keep .wb-input states. */

.wb-dialog {
  width: min(var(--wb-size-dialog), 100% - 2rem);
  padding: var(--wb-space-9);
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-dialog);
}

.wb-dialog::backdrop { background: var(--wb-color-backdrop); }

.wb-dialog__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--wb-space-6);
}

.wb-dialog__title {
  font-size: var(--wb-text-section);
  font-weight: var(--wb-weight-bold);
  color: var(--wb-color-heading);
}

.wb-dialog__description {
  margin-top: var(--wb-space-2);
  color: var(--wb-color-soft);
}

.wb-dialog__close { flex-shrink: 0; }

.wb-dialog__body { display: grid; gap: var(--wb-space-6); margin-top: var(--wb-space-7); }

.wb-dialog__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--wb-space-4);
  margin-top: var(--wb-space-9);
}

/* on a phone the actions stack, the main one on top */
@media (max-width: 640px) {
  .wb-dialog__footer { flex-direction: column-reverse; }
  .wb-dialog__footer .wb-button { width: 100%; }
}

/* a <form> wrapping the header, body and footer takes no part in the layout: the parts lay out as
   if the form were not there */
.wb-dialog > form { display: contents; }

/* Media modifier: a photograph at the size it was taken, rather than a panel with a picture in
   it. The plain dialog is capped at --wb-size-dialog, which is right for a form and wrong for a
   plate, and it has no previous/next. Add .wb-dialog--media:

     <dialog class="wb-dialog wb-dialog--media">
       <div class="wb-dialog__body"><figure><img ...><figcaption>…</figcaption></figure></div>
       <footer class="wb-dialog__footer">previous · next · close</footer>
     </dialog>

   The image takes the room the viewport allows and never more than its own pixels, so a small
   photograph is shown at its size rather than upscaled. */

.wb-dialog--media {
  width: min(100% - var(--wb-space-10), 100vw);
  max-width: none;
  padding: var(--wb-space-7);
}
.wb-dialog--media .wb-dialog__body { margin-top: 0; justify-items: center; }
.wb-dialog--media figure { display: grid; gap: var(--wb-space-5); justify-items: center; }
/* width: auto alone still lets a grid item stretch, which upscales a small plate: the image is
   held to its own pixels and shrinks only when the viewport is narrower than they are */
.wb-dialog--media img {
  max-height: 72vh;
  max-width: 100%;
  width: auto;
  height: auto;
  justify-self: center;
  border-radius: var(--wb-radius-image);
}
.wb-dialog--media figcaption {
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
  text-align: center;
}

/* ==== css/components/dropdown-menu.css ==== */
/* Dropdown menu. A short list of actions behind one button. It needs the behaviour script for the
   keyboard (arrow keys, Home/End, Escape, type-ahead); the markup is the WAI-ARIA menu button
   pattern.
   Markup:
     <button class="wb-button" aria-haspopup="menu" aria-expanded="false" aria-controls="deal-menu">
       Deal actions
     </button>
     <div class="wb-menu" role="menu" id="deal-menu" popover>
       <div class="wb-menu__label">Deal</div>
       <button class="wb-menu__item" role="menuitem">Open model
         <kbd class="wb-menu__shortcut">⌘O</kbd></button>
       <button class="wb-menu__item" role="menuitemcheckbox" aria-checked="true">
         <span class="wb-menu__check" aria-hidden="true">check</span>Watch this deal</button>
       <div class="wb-menu__separator" role="separator"></div>
       <button class="wb-menu__item wb-menu__item--destructive" role="menuitem">Withdraw bid</button>
       <button class="wb-menu__item" role="menuitem" aria-disabled="true">Archive</button>
     </div>
   The script toggles: aria-expanded on the trigger; the popover's open state; roving tabindex
   (0 on the focused item, -1 on the rest); aria-checked on checkbox items; focus on items.
   States: item :hover / :focus-visible (the same navy highlight), [aria-checked="true"],
   [aria-disabled="true"] / :disabled; the trigger keeps its .wb-button states. */

.wb-menu {
  min-width: var(--wb-size-menu-min);
  padding: var(--wb-space-2);
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-overlay);
  z-index: var(--wb-layer-overlay);
}

.wb-menu__label {
  padding: var(--wb-space-2) var(--wb-space-5);
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-group);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

.wb-menu__item {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  width: 100%;
  padding: var(--wb-space-2) var(--wb-space-5);
  border: 0;
  border-radius: var(--wb-radius-small);
  background: transparent;
  color: var(--wb-color-ink);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              color var(--wb-motion-fast) var(--wb-motion-ease);
}

/* the highlighted item: navy with white text (Angus's pick, 24 Sep 2026) */
.wb-menu__item:hover,
.wb-menu__item:focus-visible {
  background: var(--wb-color-highlight);
  color: var(--wb-color-highlight-ink);
}
.wb-menu__item:focus-visible { outline: none; }
.wb-menu__item:hover .wb-menu__shortcut,
.wb-menu__item:focus-visible .wb-menu__shortcut { color: inherit; }

/* a checkbox item reserves the tick's width whether or not it is checked */
.wb-menu__check {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  visibility: hidden;
}
.wb-menu__item[aria-checked="true"] .wb-menu__check { visibility: visible; }

.wb-menu__shortcut {
  margin-left: auto;
  padding-left: var(--wb-space-5);
  font-family: var(--wb-font-mono);
  font-size: var(--wb-ratio-label);
  letter-spacing: var(--wb-tracking-group);
  color: var(--wb-color-soft);
}

.wb-menu__separator {
  height: var(--wb-line-rule);
  margin: var(--wb-space-2) calc(-1 * var(--wb-space-2));
  background: var(--wb-color-rule);
}

.wb-menu__item--destructive { color: var(--wb-color-destructive); }
.wb-menu__item--destructive:hover,
.wb-menu__item--destructive:focus-visible { background: var(--wb-color-destructive); color: var(--wb-color-destructive-ink); }

.wb-menu__item:disabled,
.wb-menu__item[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

/* ==== css/components/empty.css ==== */
/* Empty state. Shown where a list, table or panel has nothing in it yet: a centred icon in a
   soft circle, a title, one line of explanation, and an optional row of actions.
   <div class="wb-empty">
     <div class="wb-empty__header">
       <div class="wb-empty__media"><svg>icon</svg></div>
       <div class="wb-empty__title">No passed deals yet</div>
       <p class="wb-empty__description">Deals you pass on will be listed here.</p>
     </div>
     <div class="wb-empty__actions"><button class="wb-button">New deal</button></div>
   </div> */

.wb-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--wb-space-9);
  padding: var(--wb-space-12) var(--wb-space-7);
  text-align: center;
}

.wb-empty__header {
  display: grid;
  justify-items: center;
  gap: var(--wb-space-3);
}

.wb-empty__media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--wb-space-12);
  height: var(--wb-space-12);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-fill);
  color: var(--wb-color-soft);
}
.wb-empty__media svg {
  width: var(--wb-space-9);
  height: var(--wb-space-9);
}

.wb-empty__title {
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
  line-height: var(--wb-leading-heading);
}

.wb-empty__description {
  font-size: var(--wb-ratio-small);
  line-height: var(--wb-leading-standfirst);
  color: var(--wb-color-soft);
}

.wb-empty__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--wb-space-4);
}

/* ==== css/components/evidence.css ==== */
/* Evidence row — one fact about one thing, with the class of evidence behind it and, where the
   fact is a number in a comparable set, a bar showing where it sits. Built for a portal's
   registers: a fact, a figure, what kind of evidence it is, and the basis a reader could check.

   The evidence class is a word, not a colour: a filed account and a press mention differ in
   weight, not in hue, and a page that colours them reads as a traffic light. Filed takes the
   full ink; the softer the evidence, the softer the label. The bar takes the accent and never
   the positive green, because it is a comparison and not a judgment.

   <div class="wb-evidence">
     <div class="wb-evidence__row">
       <span class="wb-evidence__label">Payroll</span>
       <span class="wb-evidence__class" data-class="filed">filed</span>
       <span class="wb-evidence__value">19.2% of revenue</span>
       <span class="wb-evidence__bar" style="--wb-evidence-bar: 0.62"></span>
       <span class="wb-evidence__basis">Companies House, FY24 accounts, filed March 2025</span>
     </div>
   </div>

   --wb-evidence-bar is 0 to 1. data-class: filed | press | interview | derived | modelled |
   ruled | note. A row that is the subject rather than a comparable takes data-subject, and its
   bar is outlined rather than filled: it is underwritten, not collected.
   States: default, hover, and the empty case (.wb-evidence__none) — a thing that discloses
   nothing says so rather than showing an empty frame. */

/* The row answers to the width of the box it is in, not the width of the window: on a chapter
   view it sits in one track of a two-track grid, where a viewport media query would still be
   giving it five columns in 640px. */
.wb-evidence { container-type: inline-size; }

.wb-evidence__row {
  display: grid;
  /* every column is fixed so the bars start at one x: bars that begin in different places
     cannot be compared, which is the only reason the bar is there */
  grid-template-columns: minmax(0, 8rem) minmax(0, 11rem) 6rem minmax(6rem, 14rem) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--wb-space-2) var(--wb-space-6);
  padding: var(--wb-space-5) 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}
.wb-evidence__row:hover { background: var(--wb-color-row-hover); }
.wb-evidence__row[data-subject] { background: var(--wb-color-surface-muted); }

.wb-evidence__label { grid-column: 1; color: var(--wb-color-soft); }

.wb-evidence__value {
  grid-column: 2;
  font-variant-numeric: tabular-nums;
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-ink);
}

.wb-evidence__class {
  grid-column: 3;
  font-size: var(--wb-text-kicker);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}
.wb-evidence__class[data-class="filed"],
.wb-evidence__class[data-class="ruled"] { color: var(--wb-color-soft-strong); font-weight: var(--wb-weight-medium); }
.wb-evidence__class[data-class="modelled"],
.wb-evidence__class[data-class="derived"],
.wb-evidence__class[data-class="note"] { font-style: italic; }

/* the bar is a comparison, never a judgment: it takes the accent, not positive green */
.wb-evidence__bar {
  --wb-evidence-bar: 0;
  grid-column: 4;
  position: relative;
  align-self: center;
  height: var(--wb-space-3);
  background: var(--wb-color-track);
  border-radius: var(--wb-radius-small);
  overflow: hidden;
}
.wb-evidence__bar::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--wb-evidence-bar) * 100%);
  background: var(--wb-color-accent);
}
.wb-evidence__row[data-subject] .wb-evidence__bar::before {
  background: none;
  box-shadow: inset 0 0 0 var(--wb-line-focus) var(--wb-color-accent);
}

/* the basis sits beside the bar rather than under the row: it is the thing that makes the
   figure checkable, and a row that puts it on its own line reads as a footnote */
.wb-evidence__basis {
  grid-column: 5;
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}

/* nothing published: said plainly, not shown as an empty frame */
.wb-evidence__none { padding: var(--wb-space-5) 0; color: var(--wb-color-soft); font-style: italic; }

@container (max-width: 60rem) {
  .wb-evidence__row { grid-template-columns: minmax(0, 8rem) minmax(0, 11rem) 6rem minmax(0, 1fr); }
  .wb-evidence__basis { grid-column: 1 / -1; }
}
@container (max-width: 34rem) {
  .wb-evidence__row { grid-template-columns: minmax(0, 1fr) auto; }
  .wb-evidence__label { grid-column: 1; }
  .wb-evidence__value { grid-column: 1; }
  .wb-evidence__class { grid-column: 2; }
  .wb-evidence__bar { grid-column: 1 / -1; }
  .wb-evidence__basis { grid-column: 1 / -1; }
}

/* ==== css/components/field.css ==== */
/* Field. The wrapper every control sits in, so a label, a control, a help line and an error
   line always stack the same way. One vertical layout only.
   <div class="wb-field">
     <label class="wb-field__label" for="deal-name">Deal name</label>
     <input class="wb-input" id="deal-name" aria-describedby="deal-name-help">
     <p class="wb-field__help" id="deal-name-help">As it appears in the pipeline.</p>
     <p class="wb-field__error" id="deal-name-error">Enter a deal name.</p>
   </div>
   The error line is hidden until the control inside carries aria-invalid="true" (:has); the
   author links it with aria-describedby. Label is ink and medium weight, help is soft, error
   is --wb-color-error-ink, both at --wb-ratio-small.
   .wb-fields is a grid of fields, two or more across where there is room.
   <fieldset class="wb-fieldset"><legend>Deal team</legend> ... </fieldset> groups related fields.
   .wb-check is a label that wraps a checkbox or radio and its text (see checkbox.css and
   radio-group.css); gap --wb-space-3.
   The rate chart this file used to hold is css/components/rate-field.css.
   States: a field whose control is :disabled dims its label; :has([aria-invalid="true"]) shows
   the error. No script. */

.wb-field {
  display: grid;
  gap: var(--wb-space-3);
  min-width: 0;
}

.wb-field__label {
  color: var(--wb-color-ink);
  font-weight: var(--wb-weight-medium);
}

.wb-field__help {
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-field__error {
  display: none;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-error-ink);
}
.wb-field:has([aria-invalid="true"]) .wb-field__error { display: block; }

.wb-field:has(:disabled) .wb-field__label,
.wb-field:has([aria-disabled="true"]) .wb-field__label { opacity: var(--wb-opacity-disabled); }

/* a grid of fields */
.wb-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--wb-space-7) var(--wb-space-8);
}

/* a group of related fields */
.wb-fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--wb-space-7);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.wb-fieldset > legend {
  padding: 0;
  margin-bottom: var(--wb-space-3);
  color: var(--wb-color-heading);
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-bold);
  letter-spacing: var(--wb-tracking-heading);
}

/* a label wrapping a checkbox or radio and its text */
.wb-check {
  display: inline-flex;
  align-items: center;
  gap: var(--wb-space-3);
  cursor: pointer;
}
.wb-check:has(:disabled) { opacity: var(--wb-opacity-disabled); cursor: default; }

/* ==== css/components/figures.css ==== */
/* Key figures. A strip of big numbers (round 3), one equal column per figure.
   <dl class="wb-figures"><div><dt>Purchase price</dt><dd>£48.0m</dd></div> ... </dl>
   A positive number may carry .wb-positive; a negative one .wb-negative.
   Rules are hairlines, never heavier than the rules between sections (round 5 correction:
   the navy top rule is gone). Inside .wb-tool the strip drops its rules and its numbers
   shrink to tool size (css/layouts/tool.css). Two columns under 640px wide. */

.wb-figures {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  font-variant-numeric: tabular-nums;
}

.wb-figures > div {
  padding: var(--wb-space-6) var(--wb-space-7);
  border-right: var(--wb-line-rule) solid var(--wb-color-rule);
}
.wb-figures > div:first-child { padding-left: 0; }
.wb-figures > div:last-child { border-right: 0; }

.wb-figures dt {
  font-size: var(--wb-ratio-label);
  color: var(--wb-color-soft);
}

.wb-figures dd {
  margin-top: var(--wb-space-1);
  font-size: var(--wb-text-figure);
  font-weight: var(--wb-weight-semibold);
  letter-spacing: var(--wb-tracking-figure);
  color: var(--wb-color-heading);
}
.wb-figures dd.wb-positive { color: var(--wb-color-positive); }
.wb-figures dd.wb-negative { color: var(--wb-color-negative); }

@media (max-width: 640px) {
  .wb-figures { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wb-figures > div { padding-left: 0; border-right: 0; }
  .wb-figures > div:nth-child(2n - 1) { border-right: var(--wb-line-rule) solid var(--wb-color-rule); }
  .wb-figures > div:nth-child(2n) { padding-left: var(--wb-space-7); }
  .wb-figures > div:nth-child(n + 3) { border-top: var(--wb-line-rule) solid var(--wb-color-rule); }
}

/* ==== css/components/index-list.css ==== */
/* View index — the list of a chapter's views on its hub, repeating what the tab strip holds with
   a line about each. The tab strip is the index at the top of the screen; this is the index for
   someone who has read the chapter's lead exhibit and is deciding where to go next. A chapter
   with one view shows neither, and a register long enough that a list under it would never be
   read shows only the strip.

   <div class="wb-index">
     <a class="wb-index__row" href="#/c/market/rate" aria-current="page">
       <span class="wb-index__title">Rate</span>
       <span class="wb-index__kicker">34 hotels</span>
       <span class="wb-index__blurb">Every annual and monthly figure held, as a table.</span>
     </a>
   </div>

   States: default, hover, focus-visible, active, and the view you are on (aria-current="page"). */

.wb-index__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--wb-space-2) var(--wb-space-10);
  padding: var(--wb-space-6) 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  color: inherit;
  text-decoration: none;
}
.wb-index__row:hover { background: var(--wb-color-row-hover); }
.wb-index__row:active { background: var(--wb-color-fill); }
.wb-index__row:focus-visible {
  outline: var(--wb-line-focus) solid var(--wb-color-focus-ring);
  outline-offset: calc(var(--wb-space-2) * -1);
}

.wb-index__title {
  grid-column: 1;
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-semibold);
  color: var(--wb-color-heading);
}
.wb-index__row:hover .wb-index__title { color: var(--wb-color-accent); }

/* the figure or count against the view, on the title's own line */
.wb-index__kicker {
  grid-column: 2;
  grid-row: 1;
  font-variant-numeric: tabular-nums;
  color: var(--wb-color-soft-strong);
  white-space: nowrap;
}

.wb-index__blurb { grid-column: 1 / -1; color: var(--wb-color-soft); }

/* the view you are on: named, not hidden, so the index stays a map of the whole chapter */
.wb-index__row[aria-current="page"] { background: var(--wb-color-surface-muted); pointer-events: none; }
.wb-index__row[aria-current="page"] .wb-index__title { color: var(--wb-color-ink); }

@media (max-width: 640px) {
  .wb-index__row { grid-template-columns: minmax(0, 1fr); }
  .wb-index__kicker { grid-column: 1; grid-row: auto; white-space: normal; }
}

/* ==== css/components/input-group.css ==== */
/* Input with addon. A control with a fixed unit, prefix or trailing action:
   <div class="wb-input-group">
     <span class="wb-input-group__addon">£</span>
     <input class="wb-input" id="amount" type="text">
   </div>
   The group carries the border, the radius and the focus ring (:focus-within); the .wb-input
   inside drops its own border and ring, and a .wb-button after it sits flush to the edge. The
   addon goes before or after the control ("£", "%", "keys", an icon).
   States: :focus-within (ring), [aria-invalid="true"] inside, :has(:disabled) dims the group.
   No script. */

.wb-input-group {
  display: flex;
  align-items: stretch;
  width: 100%;
  min-width: 0;
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-ground);
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-input-group:focus-within {
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}
.wb-input-group:has([aria-invalid="true"]) { border-color: var(--wb-color-invalid); }
.wb-input-group:has([aria-invalid="true"]):focus-within {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}
.wb-input-group:has(:disabled) { opacity: var(--wb-opacity-disabled); pointer-events: none; }

.wb-input-group > .wb-input {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
  padding-left: var(--wb-space-5);
  padding-right: var(--wb-space-5);
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.wb-input-group:has(> .wb-input-group__addon:first-child) > .wb-input { padding-left: var(--wb-space-3); }
.wb-input-group:has(> .wb-input-group__addon:last-child) > .wb-input { padding-right: var(--wb-space-3); }
.wb-input-group > .wb-input:focus-visible { box-shadow: none; }

.wb-input-group__addon {
  display: flex;
  align-items: center;
  gap: var(--wb-space-2);
  color: var(--wb-color-soft);
  font-weight: var(--wb-weight-medium);
  white-space: nowrap;
  user-select: none;
}
.wb-input-group__addon:first-child { padding-left: var(--wb-space-5); }
.wb-input-group__addon:last-child { padding-right: var(--wb-space-5); }
.wb-input-group__addon svg {
  width: var(--wb-size-icon);
  height: var(--wb-size-icon);
  flex-shrink: 0;
  pointer-events: none;
}

/* a trailing button, flush to the edge */
.wb-input-group > .wb-button {
  flex: none;
  margin: calc(-1 * var(--wb-line-rule));
  border-radius: var(--wb-radius-control);
}
.wb-input-group > .wb-button:focus-visible { box-shadow: none; }

/* ==== css/components/input.css ==== */
/* Input. <input class="wb-input"> for text, email, number, date, search, password and file.
   No fixed height: padding and the control line-height make it exactly the height of a
   default .wb-button beside it, at 16px on a reading page and 13px in a tool page.
   Add .wb-input--num to right-align a number. Put it in a .wb-field (field.css) for a label,
   help and error.
   States: :placeholder-shown (placeholder colour), :focus-visible (ring), :read-only (muted
   fill), :disabled, [aria-invalid="true"]. There is no hover change (shadcn has none).
   No script. */

.wb-input {
  display: block;
  width: 100%;
  min-width: 0;
  padding: var(--wb-space-3) var(--wb-space-5);
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-ground);
  color: var(--wb-color-ink);
  font: inherit;
  line-height: var(--wb-leading-control);
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease),
              background-color var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-input::placeholder { color: var(--wb-color-placeholder); }

.wb-input:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-input[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-input[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}

.wb-input:read-only { background: var(--wb-color-surface-muted); }

.wb-input:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

.wb-input--num { text-align: right; font-variant-numeric: tabular-nums; }

.wb-input::file-selector-button {
  margin-right: var(--wb-space-5);
  padding: var(--wb-space-1) var(--wb-space-3);
  border: 0;
  border-radius: var(--wb-radius-small);
  background: var(--wb-color-surface-muted);
  color: var(--wb-color-ink);
  font: inherit;
  font-weight: var(--wb-weight-medium);
  cursor: pointer;
}
.wb-input::file-selector-button:hover { background: var(--wb-color-fill-hover); }

/* ==== css/components/kbd.css ==== */
/* Keyboard key. <kbd class="wb-kbd">⌘</kbd>.
   A combination is a group of keys: <span class="wb-kbd__group"> of .wb-kbd, with a plain
   <span>+</span> between them where a plus reads better than a gap.
   Size follows the surrounding text, so a key reads right on a reading page and in a tool page.
   Decorative, so it carries no states. */

.wb-kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5em;
  padding: 0.15em var(--wb-space-2);
  border-radius: var(--wb-radius-small);
  background: var(--wb-color-fill);
  color: var(--wb-color-soft);
  font-family: var(--wb-font-sans);
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  vertical-align: middle;
  user-select: none;
}

.wb-kbd__group {
  display: inline-flex;
  align-items: center;
  gap: var(--wb-space-2);
}

/* ==== css/components/kv.css ==== */
/* Label and value list. The terms of one thing, one per row: a lender's terms, a hotel's facts,
   the detail of a selected row. For a strip of a few big numbers use key figures instead; for
   many items compared side by side, a table.
   <dl class="wb-kv">
     <div><dt>Loan to cost</dt><dd>55%</dd></div>
     <div><dt>Margin over SONIA</dt><dd>4.25%</dd></div>
   </dl>
   Labels soft, values in ink, a hairline under each row. Size follows the surrounding text, so it
   reads at 16px on a page and 13px in a tool or a panel over a tool. */

.wb-kv { font-variant-numeric: tabular-nums; }

.wb-kv > div {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: var(--wb-space-7);
  align-items: baseline;
  padding: var(--wb-space-3) 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-kv dt { color: var(--wb-color-soft); }
.wb-kv dd { color: var(--wb-color-ink); }
.wb-kv dd.wb-positive { color: var(--wb-color-positive); }
.wb-kv dd.wb-negative { color: var(--wb-color-negative); }

/* ==== css/components/native-select.css ==== */
/* Native select. <select class="wb-select">. This is the default select: use it for a long,
   plain list of options. For options that need more than text, use the custom select.
   The browser's own arrow is removed and the house chevron is drawn on the right with two
   token-coloured gradient bands, so it reads right at 16px and 13px and never bakes in a
   colour. (A <select> is a replaced element and renders no pseudo-element, so the chevron
   cannot be a mask on the select itself; the bands give the same chevron in soft grey.)
   No fixed height: it matches a default .wb-button beside it. Put it in a .wb-field
   (field.css) for a label, help and error.
   States: :focus-visible (ring), [aria-invalid="true"], :disabled. No script. */

.wb-select {
  display: block;
  width: 100%;
  min-width: 0;
  padding: var(--wb-space-3) var(--wb-space-10) var(--wb-space-3) var(--wb-space-5);
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background-color: var(--wb-color-ground);
  background-image:
    linear-gradient(45deg, transparent 44%, var(--wb-color-soft) 44%, var(--wb-color-soft) 56%, transparent 56%),
    linear-gradient(135deg, transparent 44%, var(--wb-color-soft) 44%, var(--wb-color-soft) 56%, transparent 56%);
  background-position:
    right calc(var(--wb-space-5) + 0.55em) center,
    right var(--wb-space-5) center;
  background-size: 0.55em 0.55em, 0.55em 0.55em;
  background-repeat: no-repeat;
  color: var(--wb-color-ink);
  font: inherit;
  line-height: var(--wb-leading-control);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-select:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-select[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-select[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}

.wb-select:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

/* ==== css/components/notes.css ==== */
/* Notes. Numbered sources and footnotes at the end of a document, in small soft text.
   In the text: <sup class="wb-note-ref"><a href="#n1" id="r1">1</a></sup>
   At the end:  <ol class="wb-notes"><li id="n1">STR, trailing twelve months to August 2026. <a href="#r1">Back</a></li></ol>
   A list of sources with no references in the text is the same list without the links. */

.wb-notes {
  padding-left: var(--wb-space-9);
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}
.wb-notes li + li { margin-top: var(--wb-space-2); }
.wb-notes li::marker { font-variant-numeric: tabular-nums; }
.wb-notes a { color: inherit; }
.wb-notes li:target { color: var(--wb-color-ink); }

.wb-note-ref { font-size: var(--wb-ratio-label); line-height: 0; }
.wb-note-ref a { padding: 0 0.1em; text-decoration: none; color: var(--wb-color-link); }
.wb-note-ref a:hover { text-decoration: underline; }

/* ==== css/components/pagination.css ==== */
/* Pagination. <nav aria-label="Pagination" class="wb-pagination">
     <p class="wb-pagination__summary">Showing 21-40 of 214 deals</p>
     <ul class="wb-pagination__list">
       <li class="wb-pagination__item"><a class="wb-pagination__link" href="#" aria-label="Go to previous page">...</a></li>
       <li class="wb-pagination__item"><a class="wb-pagination__link" href="#">1</a></li>
       <li class="wb-pagination__item"><a class="wb-pagination__link" href="#" aria-current="page">2</a></li>
       <li class="wb-pagination__item"><span class="wb-pagination__ellipsis" aria-hidden="true">...</span></li>
       <li class="wb-pagination__item"><a class="wb-pagination__link" href="#" aria-label="Go to next page">...</a></li>
     </ul>
   </nav>
   The summary line is optional and sits at the left of the list. The current page takes the
   secondary-button look; the rest are ghost. Previous and next hide their word under 640px.
   States: :hover, :focus-visible, [aria-current="page"], [aria-disabled="true"] (previous on page one). */

.wb-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-4) var(--wb-space-8);
}

.wb-pagination__summary {
  margin-right: auto;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-pagination__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-1);
  padding: 0;
  list-style: none;
}

.wb-pagination__item { display: inline-flex; }

.wb-pagination__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wb-space-2);
  padding: var(--wb-space-2) var(--wb-space-5);
  border: var(--wb-line-rule) solid transparent;
  border-radius: var(--wb-radius-control);
  background: transparent;
  color: var(--wb-color-soft);
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease),
              color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-pagination__link svg { width: 1em; height: 1em; flex-shrink: 0; pointer-events: none; }

.wb-pagination__link:hover { background: var(--wb-color-fill-hover); color: var(--wb-color-heading); }

.wb-pagination__link:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-pagination__link[aria-current="page"] {
  background: var(--wb-color-ground);
  border-color: var(--wb-color-control-border);
  color: var(--wb-color-heading);
}
.wb-pagination__link[aria-current="page"]:hover { background: var(--wb-color-fill-hover); }

.wb-pagination__link[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

.wb-pagination__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--wb-space-2) var(--wb-space-5);
  color: var(--wb-color-soft);
}

@media (max-width: 640px) {
  .wb-pagination__label { display: none; }
}

/* ==== css/components/pill.css ==== */
/* Status label (shadcn: badge; the class keeps the name pill). A square-cornered tag on a firm tint
   for a row's status. Round 3 picked a coloured label over a dot and word; round 2 (24 Sep 2026)
   struck the pale rounded pill as "too Notion-y": corners are now the small radius, tints firmer.
   <span class="wb-pill wb-pill--warn">Needs you</span>
   Variants, by what the status means (never by the colour wanted):
     (neutral, the default)  nothing to do: quiet, watching, done, closed, declined, passed, draft
     --warn                  someone must act or chase: needs you, waiting on us, waiting on them, due soon
     --danger                something went wrong: overdue, failed, missed
     --ok                    on track: term sheet in, signed, completed as planned (green text on
                             the green tint: the one green text Angus allows, 24 Sep 2026)
     --info                  moving, nobody blocked: in progress, with credit committee, in review
   Size follows the surrounding text. As a link (<a class="wb-pill">) it gains hover and focus. */

.wb-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--wb-space-1);
  padding: 0.2em 0.5em;
  border-radius: var(--wb-radius-small);
  background: var(--wb-color-status-neutral-bg);
  color: var(--wb-color-status-neutral-ink);
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  text-decoration: none;
  vertical-align: middle;
}

.wb-pill--warn   { background: var(--wb-color-status-warn-bg);   color: var(--wb-color-status-warn-ink); }
.wb-pill--danger { background: var(--wb-color-status-danger-bg); color: var(--wb-color-status-danger-ink); }
.wb-pill--ok     { background: var(--wb-color-status-ok-bg);     color: var(--wb-color-status-ok-ink); }
.wb-pill--info   { background: var(--wb-color-status-info-bg);   color: var(--wb-color-status-info-ink); }

a.wb-pill:hover { text-decoration: underline; text-underline-offset: 0.15em; }
a.wb-pill:focus-visible { outline: none; box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring); }

/* ==== css/components/popover.css ==== */
/* Popover. A small panel of content anchored to a button. Native HTML shows and hides it (the
   popover attribute and popovertarget); the behaviour script only positions it near the trigger.
   Markup:
     <button class="wb-button" popovertarget="deal-team">Deal team</button>
     <div class="wb-popover" id="deal-team" popover>
       <p class="wb-popover__title">Deal team</p>
       <p class="wb-popover__description">Three people on this deal.</p>
       <ul class="wb-popover__list">
         <li>Marwa Haddad<small>OakNorth · lender</small></li>
       </ul>
     </div>
   States: the trigger keeps its .wb-button states; :popover-open shows the panel. */

.wb-popover {
  width: 18rem;
  padding: var(--wb-space-7);
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-overlay);
  z-index: var(--wb-layer-overlay);
}

.wb-popover__title {
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
}

.wb-popover__description {
  margin-top: var(--wb-space-2);
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-popover__list {
  display: grid;
  gap: var(--wb-space-5);
  margin: var(--wb-space-6) 0 0;
  padding: 0;
  list-style: none;
}
.wb-popover__list small {
  display: block;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

/* ==== css/components/progress.css ==== */
/* Progress. The native element, so it needs no script and announces itself to assistive
   technology: <progress class="wb-progress" value="62" max="100">62%</progress>.
   No value means indeterminate, which animates.
   Track --wb-color-track, bar --wb-color-accent, pill radius, height --wb-space-3.
   Styled for Chrome and Safari (::-webkit-progress-*) and Firefox (::-moz-progress-bar).
   An optional caption line sits above the bar as .wb-progress__label. */

.wb-progress {
  display: block;
  width: 100%;
  height: var(--wb-space-3);
  border: 0;
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-track);
  overflow: hidden;
  appearance: none;
  -webkit-appearance: none;
}

/* Chrome and Safari */
.wb-progress::-webkit-progress-bar {
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-track);
}
.wb-progress::-webkit-progress-value {
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-accent);
  transition: width var(--wb-motion-base) var(--wb-motion-ease);
}

/* Firefox */
.wb-progress::-moz-progress-bar {
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-accent);
}

/* indeterminate: a bar sliding across the track */
.wb-progress:indeterminate {
  background-image: linear-gradient(90deg, var(--wb-color-accent), var(--wb-color-accent));
  background-repeat: no-repeat;
  background-size: 35% 100%;
  animation: wb-progress-slide 1.4s var(--wb-motion-ease) infinite;
}
.wb-progress:indeterminate::-webkit-progress-bar { background: transparent; }
.wb-progress:indeterminate::-webkit-progress-value { background: transparent; }
.wb-progress:indeterminate::-moz-progress-bar { background: transparent; }

@keyframes wb-progress-slide {
  from { background-position: -50% 0; }
  to { background-position: 150% 0; }
}

/* caption line above the bar: what is happening on the left, how far on the right */
.wb-progress__label {
  display: flex;
  justify-content: space-between;
  gap: var(--wb-space-5);
  margin-bottom: var(--wb-space-2);
  font-size: var(--wb-ratio-label);
  color: var(--wb-color-soft);
}

/* ==== css/components/quote.css ==== */
/* Quote. On the light shaded panel (round 5 correction: panel, not hairlines). Never a side border.
   <blockquote class="wb-quote"><p>The market is not paying ...</p><footer>Site visit notes, 2 September</footer></blockquote> */

.wb-quote {
  margin: var(--wb-space-10) 0;
  padding: var(--wb-space-8) var(--wb-space-9);
  border-radius: var(--wb-radius-panel);
  background: var(--wb-color-surface);
  color: var(--wb-color-heading);
  font-size: var(--wb-text-quote);
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-body);
}

.wb-quote p + p { margin-top: var(--wb-space-3); }

.wb-quote footer {
  margin-top: var(--wb-space-3);
  font-size: var(--wb-text-caption);
  font-weight: var(--wb-weight-regular);
  color: var(--wb-color-soft);
}

.wb-quote:first-child { margin-top: 0; }
.wb-quote:last-child { margin-bottom: 0; }

/* ==== css/components/radio-group.css ==== */
/* Radio group. Native <input type="radio" class="wb-radio"> stacked in a
   <div class="wb-radio-group" role="radiogroup">, each wrapped in a <label class="wb-check">
   (field.css). Use it for one choice from a short, fixed list.
   Same size as the checkbox and round; checked shows a dot in --wb-color-checked and keeps the
   control border, as shadcn does.
   States: :checked, :hover, :focus-visible, [aria-invalid="true"], :disabled. No script. */

.wb-radio-group {
  display: grid;
  gap: var(--wb-space-5);
}

.wb-radio {
  flex: none;
  display: inline-grid;
  place-content: center;
  width: var(--wb-size-check);
  height: var(--wb-size-check);
  margin: 0;
  border: var(--wb-line-rule) solid var(--wb-color-check-border);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-ground);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-radio::before {
  content: "";
  width: calc(var(--wb-size-check) * 0.5);
  height: calc(var(--wb-size-check) * 0.5);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-checked);
  transform: scale(0);
  transition: transform var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-radio:hover { border-color: var(--wb-color-soft); }
.wb-radio:checked { border-color: var(--wb-color-checked); }
.wb-radio:checked::before { transform: scale(1); }

.wb-radio:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-radio[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-radio[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}

.wb-radio:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

/* ==== css/components/rate-field.css ==== */
/* Rate field — where one thing sits in a set of collected values. A row per member: its name on the
   left, its range drawn on a shared scale, its headline figure at the end, and one axis under the
   whole set. Angus picked this over an upright bar chart for the portal's rate field (round 8,
   26 September 2026): a bar chart compares heights, and this compares positions on a scale, which
   is the question a comparable set actually asks.

   Use it where every row is the same measure on one scale and the subject has to be visible among
   them. For a handful of categories that are not on a shared scale, use the bank's bar chart.

     <div class="wb-rate-field">
       <p class="wb-rate-field__legend"><span>Middle half of the collected year</span><span>Median marked</span></p>
       <div class="wb-rate-field__rows">
         <div class="wb-rate-field__row">
           <span class="wb-rate-field__label">The Royal Crescent</span>
           <span class="wb-rate-field__plot">
             <i class="wb-rate-field__bar" style="--wb-rate-field-x: 61.3%; --wb-rate-field-w: 21.7%"></i>
             <i class="wb-rate-field__median" style="--wb-rate-field-x: 68.7%"></i>
             <span class="wb-rate-field__value" style="--wb-rate-field-x: 84%">£412</span>
           </span>
         </div>
         <div class="wb-rate-field__row wb-rate-field__row--subject"> ... <i class="wb-rate-field__mark" ...> ... </div>
         <div class="wb-rate-field__axis"><span></span><span class="wb-rate-field__plot">
           <span class="wb-rate-field__base"></span>
           <span class="wb-rate-field__tick" style="--wb-rate-field-x: 0%">£0</span> ...
         </span></div>
       </div>
     </div>

   The page places every mark with --wb-rate-field-x (0-100%) and a bar's length with --wb-rate-field-w. It
   computes the percentages; this file draws them. A subject row takes --subject: its mark is a
   filled diamond and its bar is outlined rather than filled, because it is underwritten rather
   than collected and that distinction has to be visible rather than written.
   States: default, hover and focus-visible on a row that links out (--link). */

.wb-rate-field__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--wb-space-2) var(--wb-space-8);
  margin-bottom: var(--wb-space-7);
  font-size: var(--wb-text-min);
  color: var(--wb-color-soft);
}

.wb-rate-field__rows { position: relative; }

.wb-rate-field__row,
.wb-rate-field__axis {
  display: grid;
  grid-template-columns: minmax(0, var(--wb-size-menu-min)) minmax(0, 1fr);
  gap: 0 var(--wb-space-7);
}

.wb-rate-field__row {
  align-items: center;
  min-height: var(--wb-space-12);
  padding: var(--wb-space-1) 0;
}
.wb-rate-field__row + .wb-rate-field__row { border-top: var(--wb-line-rule) solid var(--wb-color-rule); }

.wb-rate-field__label {
  padding: var(--wb-space-4) 0;
  font-size: var(--wb-text-caption);
  line-height: var(--wb-leading-control);
  text-align: right;
  color: var(--wb-color-ink);
}
.wb-rate-field__row--subject .wb-rate-field__label {
  font-weight: var(--wb-weight-bold);
  color: var(--wb-color-accent);
}

.wb-rate-field__plot { position: relative; }

.wb-rate-field__bar,
.wb-rate-field__median,
.wb-rate-field__mark,
.wb-rate-field__value,
.wb-rate-field__tick { --wb-rate-field-x: 0%; }
.wb-rate-field__bar { --wb-rate-field-w: 0%; }

.wb-rate-field__bar {
  position: absolute;
  left: var(--wb-rate-field-x);
  width: var(--wb-rate-field-w);
  top: 50%;
  height: var(--wb-space-4);
  transform: translateY(-50%);
  background: var(--wb-color-track);
  border-radius: var(--wb-radius-small);
}
.wb-rate-field__row--subject .wb-rate-field__bar {
  background: none;
  box-shadow: inset 0 0 0 var(--wb-line-rule) var(--wb-color-accent);
}

/* the median is a gap cut through the bar, not a mark laid on it */
.wb-rate-field__median {
  position: absolute;
  left: var(--wb-rate-field-x);
  top: 50%;
  width: var(--wb-line-focus);
  height: var(--wb-space-8);
  transform: translate(-50%, -50%);
  background: var(--wb-color-ground);
}

.wb-rate-field__mark {
  position: absolute;
  left: var(--wb-rate-field-x);
  top: 50%;
  width: var(--wb-space-5);
  height: var(--wb-space-5);
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--wb-color-accent);
  border-radius: var(--wb-radius-small);
}

.wb-rate-field__value {
  position: absolute;
  left: var(--wb-rate-field-x);
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--wb-text-min);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--wb-color-soft);
}
.wb-rate-field__row--subject .wb-rate-field__value {
  font-weight: var(--wb-weight-bold);
  color: var(--wb-color-accent);
}

/* a row that opens the thing it names */
.wb-rate-field__row--link { cursor: pointer; }
.wb-rate-field__row--link:hover,
.wb-rate-field__row--link:focus-visible { background: var(--wb-color-row-hover); }
.wb-rate-field__row--link:hover .wb-rate-field__label,
.wb-rate-field__row--link:focus-visible .wb-rate-field__label { color: var(--wb-color-accent); }

.wb-rate-field__axis {
  height: var(--wb-space-9);
  margin-top: var(--wb-space-2);
}
.wb-rate-field__base {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--wb-line-rule);
  background: var(--wb-color-rule-section);
}
.wb-rate-field__tick {
  position: absolute;
  left: var(--wb-rate-field-x);
  top: var(--wb-space-2);
  transform: translateX(-50%);
  font-size: var(--wb-text-min);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--wb-color-soft);
}

@media (max-width: 640px) {
  .wb-rate-field__row,
  .wb-rate-field__axis { grid-template-columns: minmax(0, 5rem) minmax(0, 1fr); gap: 0 var(--wb-space-5); }
  .wb-rate-field__label { text-align: left; }
  /* room on the right for the last value and tick, which sit past their mark (9 Oct 2026) */
  .wb-rate-field__plot { margin-right: 4.5rem; }
}

/* ==== css/components/select.css ==== */
/* Select (custom). For a choice whose options need more than plain text: a second line, a group
   heading, a tick. For a plain list of options use the native <select class="wb-select">, which
   is the default; this one needs the behaviour script.
   Markup (WAI-ARIA "select-only combobox"):
     <button class="wb-listbox-trigger" role="combobox" aria-haspopup="listbox"
             aria-expanded="false" aria-controls="deal-stage">
       <span>First round</span>
       <svg class="wb-listbox-trigger__icon">chevron</svg>
     </button>
     <div class="wb-listbox" role="listbox" id="deal-stage" popover>
       <div class="wb-listbox__group" role="group" aria-labelledby="deal-stage-stage">
         <div class="wb-listbox__label" id="deal-stage-stage">Stage</div>
         <div class="wb-listbox__option" role="option" aria-selected="true">
           <span>First round<small>Bid submitted 12 Sep</small></span>
           <svg class="wb-listbox__tick">check</svg>
         </div>
         <div class="wb-listbox__option" role="option" aria-disabled="true">Passed</div>
       </div>
     </div>
   The script toggles: aria-expanded on the trigger; [data-active] on the option named by the
   trigger's aria-activedescendant; the popover's open state; aria-selected on options. The
   listbox class is shared with the combobox; only what differs lives in combobox.css.
   States: trigger :hover / :focus-visible / [aria-expanded="true"] / [aria-invalid="true"] /
   :disabled; option [data-active], :hover and [aria-selected="true"]. */

.wb-listbox-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--wb-space-3);
  width: 100%;
  min-width: var(--wb-size-menu-min);
  padding: var(--wb-space-3) var(--wb-space-5);
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-ground);
  color: var(--wb-color-ink);
  font: inherit;
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-listbox-trigger__icon {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  color: var(--wb-color-soft);
  transition: transform var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-listbox-trigger:hover { border-color: var(--wb-color-focus-border); }
.wb-listbox-trigger:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}
.wb-listbox-trigger[aria-expanded="true"] .wb-listbox-trigger__icon { transform: rotate(180deg); }
.wb-listbox-trigger[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-listbox-trigger[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}
.wb-listbox-trigger:disabled,
.wb-listbox-trigger[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

/* the popup: shared by the select and the combobox */
.wb-listbox {
  min-width: var(--wb-size-menu-min);
  max-height: var(--wb-size-sheet);
  padding: var(--wb-space-2);
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-overlay);
  overflow-y: auto;
  z-index: var(--wb-layer-overlay);
}

.wb-listbox__label {
  padding: var(--wb-space-2) var(--wb-space-5);
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-group);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

.wb-listbox__group + .wb-listbox__group {
  margin-top: var(--wb-space-2);
  padding-top: var(--wb-space-2);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-listbox__option {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-2) var(--wb-space-5);
  border-radius: var(--wb-radius-small);
  color: var(--wb-color-ink);
  cursor: default;
  user-select: none;
}
.wb-listbox__option > span { display: block; }
.wb-listbox__option small {
  display: block;
  font-size: var(--wb-ratio-small);
  font-weight: var(--wb-weight-regular);
  color: var(--wb-color-soft);
}
.wb-listbox__option:hover,
.wb-listbox__option[data-active] { background: var(--wb-color-highlight); color: var(--wb-color-highlight-ink); }
.wb-listbox__option:hover small,
.wb-listbox__option[data-active] small { color: inherit; }
.wb-listbox__option[aria-selected="true"] { font-weight: var(--wb-weight-medium); }
.wb-listbox__option[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
}

.wb-listbox__tick {
  width: 1em;
  height: 1em;
  margin-left: auto;
  flex-shrink: 0;
  color: var(--wb-color-soft);
  visibility: hidden;
}
.wb-listbox__option[aria-selected="true"] .wb-listbox__tick { visibility: visible; }

/* ==== css/components/separator.css ==== */
/* Separator. A hairline between content, never heavier than the rules between sections.
   Horizontal: <hr class="wb-separator">
   Vertical, between items in a row:
     <span class="wb-separator wb-separator--vertical" role="separator" aria-orientation="vertical"></span>
   Decorative, so it carries no states. Colour is --wb-color-rule, the hairline rank inside a section. */

.wb-separator {
  height: var(--wb-line-rule);
  margin: 0;
  border: 0;
  background: var(--wb-color-rule);
  flex-shrink: 0;
}

.wb-separator--vertical {
  display: inline-block;
  align-self: stretch;
  width: var(--wb-line-rule);
  height: auto;
  min-height: 1em;
  margin: 0 var(--wb-space-4);
  vertical-align: middle;
}

/* ==== css/components/sheet.css ==== */
/* Sheet. A panel that slides in from the edge to show or edit one item without leaving the list. It is a
   <dialog>; the script calls showModal() and close(). It is never left open beside the page (house
   rule 1: no column that sits empty); it is a moment, not a sidebar.
   Markup:
     <dialog class="wb-sheet">
       <header class="wb-sheet__header">
         <div>
           <h2 class="wb-sheet__title">Edit deal</h2>
           <p class="wb-sheet__description">...</p>
         </div>
         <button class="wb-button wb-button--ghost wb-button--icon wb-sheet__close"
                 aria-label="Close">x</button>
       </header>
       <div class="wb-sheet__body">
         <div class="wb-field">
           <label class="wb-field__label" for="sheet-name">Deal name</label>
           <input class="wb-input" id="sheet-name">
         </div>
       </div>
       <footer class="wb-sheet__footer">
         <button class="wb-button wb-button--secondary">Cancel</button>
         <button class="wb-button">Save changes</button>
       </footer>
     </dialog>
   .wb-sheet--left slides in from the left instead of the right. The panel has no border: its edge is
   its shadow and the dimmed backdrop behind it. The script toggles the dialog's open state; the
   slide is CSS.
   States: the close and footer buttons keep their .wb-button states; the fields keep .wb-input. */

.wb-sheet {
  position: fixed;
  inset-block: 0;
  right: 0;
  left: auto;
  width: min(var(--wb-size-sheet), 100%);
  height: 100%;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-dialog);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform var(--wb-motion-base) var(--wb-motion-ease);
}

.wb-sheet[open] {
  display: flex;
  flex-direction: column;
  transform: translateX(0);
}

.wb-sheet--left { right: auto; left: 0; transform: translateX(-100%); }
.wb-sheet--left[open] { transform: translateX(0); }

.wb-sheet::backdrop { background: var(--wb-color-backdrop); }

/* slide in when the dialog opens */
@starting-style {
  .wb-sheet[open] { transform: translateX(100%); }
  .wb-sheet--left[open] { transform: translateX(-100%); }
}

.wb-sheet__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--wb-space-6);
  padding: var(--wb-space-7);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-sheet__title {
  font-size: var(--wb-text-section);
  font-weight: var(--wb-weight-bold);
  color: var(--wb-color-heading);
}

.wb-sheet__description {
  margin-top: var(--wb-space-1);
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-sheet__close { flex-shrink: 0; }

.wb-sheet__body { flex: 1; display: grid; align-content: start; gap: var(--wb-space-6); padding: var(--wb-space-7); }



.wb-sheet__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--wb-space-4);
  padding: var(--wb-space-7);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
}

/* a <form> wrapping the header, body and footer takes no part in the layout: the parts lay out as
   if the form were not there */
.wb-sheet > form { display: contents; }

/* ==== css/components/skeleton.css ==== */
/* Skeleton. A grey block standing in for content that has not loaded yet.
   <span class="wb-skeleton"></span>
   Fills its container's width and stands one line tall by default; where the real content has a
   different size, the page sets width and height on the skeleton (see the catalogue examples).
   A gentle pulse, as shadcn's animate-pulse. Colour is --wb-color-skeleton, the muted fill rank. */

@keyframes wb-pulse {
  50% { opacity: 0.5; }
}

.wb-skeleton {
  display: block;
  width: 100%;
  height: 1em;
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-skeleton);
  animation: wb-pulse 2s var(--wb-motion-ease) infinite;
}

/* ==== css/components/slider.css ==== */
/* Slider. Native <input type="range" class="wb-slider">, so it works with no script and in any
   form. Track --wb-color-slider-track, the filled part --wb-color-checked, a round white thumb
   with a --wb-color-checked border.
   The filled part is driven by the --wb-slider-fill custom property: a percentage set inline on
   the input (style="--wb-slider-fill: 55%"). The later behaviour script keeps it in step with the
   value; the declaration below is the midpoint default.
   Parts:  .wb-slider__head   a row above the track holding the label and the value
           .wb-slider__value  the live readout ("55%"), tabular figures
   States: :hover and :focus-visible put a ring on the thumb; :disabled dims the whole control. */

.wb-slider {
  --wb-slider-fill: 50%;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--wb-space-2);
  margin: 0;
  border: 0;
  border-radius: var(--wb-radius-pill);
  background: linear-gradient(
    to right,
    var(--wb-color-checked) var(--wb-slider-fill),
    var(--wb-color-slider-track) var(--wb-slider-fill)
  );
  cursor: pointer;
}

.wb-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--wb-size-check);
  height: var(--wb-size-check);
  border: var(--wb-line-rule) solid var(--wb-color-checked);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-ground);
  transition: box-shadow var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-slider::-moz-range-thumb {
  width: var(--wb-size-check);
  height: var(--wb-size-check);
  border: var(--wb-line-rule) solid var(--wb-color-checked);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-ground);
  transition: box-shadow var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-slider::-moz-range-track { background: transparent; }
.wb-slider::-moz-focus-outer { border: 0; }

.wb-slider:hover::-webkit-slider-thumb { box-shadow: 0 0 0 var(--wb-space-1) var(--wb-color-focus-ring); }
.wb-slider:hover::-moz-range-thumb { box-shadow: 0 0 0 var(--wb-space-1) var(--wb-color-focus-ring); }

.wb-slider:focus-visible { outline: none; }
.wb-slider:focus-visible::-webkit-slider-thumb {
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}
.wb-slider:focus-visible::-moz-range-thumb {
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-slider:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

.wb-slider__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--wb-space-3);
  margin-bottom: var(--wb-space-3);
}

.wb-slider__value {
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
  font-variant-numeric: tabular-nums;
}

/* ==== css/components/spinner.css ==== */
/* Spinner. <span class="wb-spinner" role="status" aria-label="Loading"></span>.
   Identical to the spinner in .wb-button[aria-busy="true"]::before (css/components/button.css):
   the wb-spin keyframes are reused by name, never redefined here. Size 1em, so it follows the
   text around it; colour is currentColor. Shown or hidden by the page, so it carries no states. */

.wb-spinner {
  display: inline-block;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  border: 0.15em solid currentColor;
  border-right-color: transparent;
  border-radius: var(--wb-radius-pill);
  animation: wb-spin 0.7s linear infinite;
  vertical-align: middle;
}

/* ==== css/components/steps.css ==== */
/* Steps. A numbered list of next steps, each with its date on the right (the deal-note reference).
   <ol class="wb-steps"><li><b>01</b><span>Operator shortlist to three</span><span>By 19 Sep</span></li> ... </ol> */

.wb-steps { padding: 0; list-style: none; }

.wb-steps li {
  display: grid;
  grid-template-columns: 2em minmax(0, 1fr) auto;
  gap: var(--wb-space-6);
  align-items: baseline;
  padding: var(--wb-space-3) 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-steps li > b {
  font-weight: var(--wb-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--wb-color-heading);
}

.wb-steps li > span:last-child {
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
  white-space: nowrap;
}

/* ==== css/components/switch.css ==== */
/* Switch. Native <input type="checkbox" role="switch" class="wb-switch">, drawn with
   appearance: none. Off is --wb-color-switch-off, on is --wb-color-checked, and a ground-white
   thumb slides across with the fast motion. Wrap it in <label class="wb-check"> (field.css)
   for a label.
   States: :checked (on), :hover, :focus-visible, :disabled. No script. */

.wb-switch {
  position: relative;
  flex: none;
  display: inline-block;
  width: var(--wb-size-switch-w);
  height: var(--wb-size-switch-h);
  margin: 0;
  border: var(--wb-line-rule) solid transparent;
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-switch-off);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-switch::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: var(--wb-size-check);
  height: var(--wb-size-check);
  border-radius: var(--wb-radius-pill);
  background: var(--wb-color-ground);
  transform: translateY(-50%);
  transition: transform var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-switch:checked { background: var(--wb-color-checked); }
.wb-switch:checked::before {
  transform: translateX(calc(var(--wb-size-switch-w) - var(--wb-size-check) - 2 * var(--wb-line-rule))) translateY(-50%);
}

.wb-switch:hover { background: var(--wb-color-control-border); }
.wb-switch:checked:hover { background: var(--wb-color-accent-hover); }

.wb-switch:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-switch:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

/* ==== css/components/table.css ==== */
/* Table. <table class="wb-table">. Hairline under every row (round 3), column heads in soft
   grey, grouped rows under tracked-capital labels, the selected row on a grey fill.
   On a reading page it runs at 16px and sits flush with the text column; inside .wb-tool
   it runs at 13px with tight rows and spans the page (see css/layouts/tool.css).
   Wrap a table that can be wider than a phone in <div class="wb-scroll">.

   Parts:  th/td.wb-table__num      numbers: right-aligned, tabular
           td.wb-table__primary     the row's name, with an optional <small> line under it
           td.wb-table__soft        secondary columns (stage, owner, dates)
           th.wb-table__long        a free-text column (notes, next action): keeps room to read on a phone
   A row that opens its detail: make the name in the primary cell a link,
           <td class="wb-table__primary"><a href="#lender-sheet" data-wb-open="lender-sheet">OakNorth</a><small>...</small></td>
   (bank.js opens the sheet and keeps the address; mark the open row aria-selected="true").
           tr.wb-table__group       a group label row: <tr class="wb-table__group"><th colspan="N" scope="rowgroup">
           tr.wb-table__empty       the one row shown when there is nothing to list
   States: row :hover, tr[aria-selected="true"], row :focus-visible (rows given tabindex),
           table[aria-busy="true"] (loading). */

.wb-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.wb-table caption {
  caption-side: bottom;
  text-align: left;
  padding-top: var(--wb-space-3);
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-table th,
.wb-table td {
  padding: var(--wb-space-3) var(--wb-space-5);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  text-align: left;
  vertical-align: middle;
}
.wb-table th:first-child,
.wb-table td:first-child { padding-left: 0; }
.wb-table th:last-child,
.wb-table td:last-child { padding-right: 0; }

.wb-table thead th {
  font-size: var(--wb-ratio-head);
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-soft);
  white-space: nowrap;
}

.wb-table .wb-table__num { text-align: right; white-space: nowrap; }

.wb-table .wb-table__primary {
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
  white-space: nowrap;
}
.wb-table .wb-table__primary small {
  display: block;
  font-size: var(--wb-ratio-small);
  font-weight: var(--wb-weight-regular);
  color: var(--wb-color-soft);
}

/* secondary columns hold short values (a stage, an owner, a date): kept on one line, so "24 Sep" never
   breaks in two beside a long note */
.wb-table .wb-table__soft { color: var(--wb-color-soft); white-space: nowrap; }

.wb-table .wb-table__primary a { color: inherit; text-decoration: none; }
.wb-table .wb-table__primary a:hover { text-decoration: underline; text-underline-offset: 0.15em; }
.wb-table .wb-table__primary a:focus-visible { outline: none; box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring); border-radius: var(--wb-radius-small); }

.wb-table .wb-table__long { min-width: 16rem; }

.wb-table tr.wb-table__group > * {
  padding-top: var(--wb-space-8);
  padding-bottom: var(--wb-space-1);
  border-bottom: 0;
  font-size: var(--wb-ratio-label);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-group);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

.wb-table tfoot td {
  border-bottom: 0;
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

/* row states: hover is lighter than selected, so the selected row stays visible under the pointer */
.wb-table tbody tr:not(.wb-table__group):not(.wb-table__empty) > td {
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-table tbody tr:not(.wb-table__group):not(.wb-table__empty):hover > td { background: var(--wb-color-row-hover); }
.wb-table tbody tr[aria-selected="true"] > td { background: var(--wb-color-surface-muted); }
.wb-table tbody tr:focus-visible { outline: var(--wb-line-focus) solid var(--wb-color-focus-ring); outline-offset: calc(-1 * var(--wb-line-focus)); }

/* empty */
.wb-table tr.wb-table__empty > td {
  padding-top: var(--wb-space-12);
  padding-bottom: var(--wb-space-12);
  text-align: center;
  color: var(--wb-color-soft);
}

/* loading */
.wb-table[aria-busy="true"] tbody { opacity: 0.5; }

/* ==== css/components/tabs.css ==== */
/* Tabs. APG tabs markup, no JavaScript:
     <div class="wb-tabs">
       <div class="wb-tabs__list" role="tablist" aria-label="Deal sections">
         <button class="wb-tabs__tab" role="tab" id="tab-a" aria-selected="true"
                 aria-controls="panel-a" tabindex="0">Overview</button>
         ...
       </div>
       <div class="wb-tabs__panel" role="tabpanel" id="panel-a" aria-labelledby="tab-a" tabindex="0">...</div>
       <div class="wb-tabs__panel" role="tabpanel" id="panel-b" aria-labelledby="tab-b" tabindex="0" hidden>...</div>
     </div>
   The script toggles aria-selected and tabindex (roving: 0 on the selected tab, -1 on the rest)
   and the hidden attribute on the panels.

   House look, not shadcn's grey pill: the list is the top bar's view nav (soft text, the selected
   tab on a muted fill in heading colour, radius control) sitting on a hairline.
   States: [aria-selected="true"], :hover, :focus-visible, :disabled / [aria-disabled="true"]. */

.wb-tabs { display: flex; flex-direction: column; }

.wb-tabs__list {
  display: flex;
  gap: var(--wb-space-1);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-tabs__tab {
  padding: var(--wb-space-1) var(--wb-space-5);
  border: 0;
  border-radius: var(--wb-radius-control);
  background: transparent;
  color: var(--wb-color-soft);
  font: inherit;
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-tabs__tab:hover { color: var(--wb-color-heading); }

.wb-tabs__tab[aria-selected="true"] {
  color: var(--wb-color-heading);
  background: var(--wb-color-fill);
}

.wb-tabs__tab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-tabs__tab:disabled,
.wb-tabs__tab[aria-disabled="true"] {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: default;
}

.wb-tabs__panel { padding-top: var(--wb-space-7); }

.wb-tabs__panel:focus-visible {
  outline: none;
  border-radius: var(--wb-radius-control);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-tabs__panel[hidden] { display: none; }

/* ==== css/components/textarea.css ==== */
/* Textarea. <textarea class="wb-textarea" rows="4">. Starts at four rows and grows as the
   author drags its bottom corner (vertical resize only). Put it in a .wb-field (field.css)
   for a label, help and error.
   States: :placeholder-shown, :focus-visible (ring), :read-only (muted fill), :disabled,
   [aria-invalid="true"]. No script. */

.wb-textarea {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: calc(var(--wb-space-7) * 4);
  padding: var(--wb-space-3) var(--wb-space-5);
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-ground);
  color: var(--wb-color-ink);
  font: inherit;
  line-height: var(--wb-leading-body);
  resize: vertical;
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease),
              background-color var(--wb-motion-fast) var(--wb-motion-ease);
}

.wb-textarea::placeholder { color: var(--wb-color-placeholder); }

.wb-textarea:focus-visible {
  outline: none;
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-textarea[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-textarea[aria-invalid="true"]:focus-visible {
  border-color: var(--wb-color-invalid);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring);
}

.wb-textarea:read-only { background: var(--wb-color-surface-muted); }

.wb-textarea:disabled {
  opacity: var(--wb-opacity-disabled);
  pointer-events: none;
  cursor: not-allowed;
}

/* ==== css/components/toast.css ==== */
/* Toast. A short message that appears after an action, in the corner of the screen, and can be
   dismissed or acted on. The script builds and removes the items; the markup below is what it
   builds. aria-live="polite" makes a screen reader read the list when it changes.
   Markup:
     <ol class="wb-toaster" aria-live="polite">
       <li class="wb-toast">
         <span class="wb-toast__icon" aria-hidden="true">tick</span>
         <div class="wb-toast__content">
           <p class="wb-toast__title">Deal note sent</p>
           <p class="wb-toast__description">Marwa at OakNorth has the note and model.</p>
         </div>
         <button class="wb-button wb-button--secondary wb-button--sm wb-toast__action">View</button>
         <button class="wb-button wb-button--ghost wb-button--icon wb-toast__close"
                 aria-label="Dismiss">x</button>
       </li>
     </ol>
   The toaster is fixed bottom-right, and shows above an open dialog or sheet. .wb-toaster--static
   puts it in the flow, where it must sit in
   the page instead of over it (the catalogue).
   Variants: (default), --success (a tick in ink beside the words), --error (title and icon in the
   error ink). No text is ever green (house rule 2).
   States: the action and close buttons keep their .wb-button states. */

.wb-toaster {
  position: fixed;
  right: var(--wb-space-7);
  bottom: var(--wb-space-7);
  z-index: var(--wb-layer-toast);
  display: flex;
  flex-direction: column;
  gap: var(--wb-space-4);
  width: 25rem;
  max-width: calc(100% - 2 * var(--wb-space-7));
  margin: 0;
  padding: 0;
  list-style: none;
}

.wb-toaster--static { position: static; }
/* while a sheet is open on the right, toasts move to the left so they never cover its buttons */
body:has(.wb-sheet[open]:not(.wb-sheet--left)) .wb-toaster:not(.wb-toaster--static) { right: auto; left: var(--wb-space-7); }
body:has(.wb-sheet[open]:not(.wb-sheet--left)) .wb-toaster[popover]:not(.wb-toaster--static) { inset: auto auto var(--wb-space-7) var(--wb-space-7); }
/* bank.js makes the toaster a popover so it shows above an open dialog or sheet (the browser's top
   layer); these undo the browser's own popover box */
.wb-toaster[popover] { inset: auto var(--wb-space-7) var(--wb-space-7) auto; border: 0; background: transparent; overflow: visible; height: auto; }

.wb-toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--wb-space-4);
  padding: var(--wb-space-7);
  border: var(--wb-line-rule) solid var(--wb-color-overlay-border);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-overlay);
  color: var(--wb-color-ink);
  box-shadow: var(--wb-shadow-overlay);
}

.wb-toast__icon {
  flex-shrink: 0;
  width: var(--wb-size-icon);
  height: var(--wb-size-icon);
  margin-top: 0.15em;
  color: var(--wb-color-soft);
}

.wb-toast__content { flex: 1; min-width: 0; }

.wb-toast__title {
  font-weight: var(--wb-weight-medium);
  color: var(--wb-color-heading);
}

.wb-toast__description {
  margin-top: var(--wb-space-1);
  font-size: var(--wb-ratio-small);
  color: var(--wb-color-soft);
}

.wb-toast__action { flex-shrink: 0; align-self: center; }

/* the close button is the last item in the row, tucked into the corner, so it can never sit on
   top of the action button beside it */
.wb-toast__close {
  flex-shrink: 0;
  margin: calc(-1 * var(--wb-space-3)) calc(-1 * var(--wb-space-3)) 0 0;
}

/* variants */
.wb-toast--success .wb-toast__icon { color: var(--wb-color-ink); }
.wb-toast--error .wb-toast__title,
.wb-toast--error .wb-toast__icon { color: var(--wb-color-error-ink); }

/* ==== css/components/toggle-group.css ==== */
/* Toggle group. A row of .wb-toggle buttons joined into one control, like a button group:
   one shared border between neighbours, corners only on the outer ends.
   <div class="wb-toggle-group" role="group" aria-label="Pipeline stage">
     <button class="wb-toggle" aria-pressed="true">Screening</button>
     <button class="wb-toggle" aria-pressed="false">Diligence</button>
   </div>
   The script toggles each child's aria-pressed: several can be on, or only one when the group
   carries data-wb-select="one" (a view switch, a period). The group adds the join
   and nothing else. Add .wb-toggle--outline to each child for the bordered look. */

.wb-toggle-group {
  display: inline-flex;
  align-items: stretch;
  width: fit-content;
}

.wb-toggle-group > .wb-toggle:not(:first-child) {
  margin-left: calc(-1 * var(--wb-line-rule));
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.wb-toggle-group > .wb-toggle:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.wb-toggle-group > .wb-toggle:focus-visible {
  position: relative;
  z-index: 1;
}

/* ==== css/components/toggle.css ==== */
/* Toggle. A button that stays on or off.
   <button class="wb-toggle" aria-pressed="false">Bookmark</button>
   Variants: (ghost, the default) --outline.
   The script toggles aria-pressed; the button carries no state of its own. Pressed reads as the
   current tab in .wb-topbar__nav and .wb-tabs: --wb-color-fill with heading ink.
   States: :hover, :focus-visible, [aria-pressed="true"], [aria-invalid="true"],
           :disabled / [aria-disabled="true"]. */

.wb-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-3) var(--wb-space-4);
  border: var(--wb-line-rule) solid transparent;
  border-radius: var(--wb-radius-control);
  background: transparent;
  color: var(--wb-color-soft);
  font: inherit;
  font-weight: var(--wb-weight-medium);
  line-height: var(--wb-leading-control);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease),
              border-color var(--wb-motion-fast) var(--wb-motion-ease),
              color var(--wb-motion-fast) var(--wb-motion-ease),
              box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-toggle svg { width: 1em; height: 1em; flex-shrink: 0; pointer-events: none; }

.wb-toggle:hover { background: var(--wb-color-row-hover); color: var(--wb-color-heading); }

.wb-toggle:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

.wb-toggle[aria-pressed="true"] { background: var(--wb-color-fill); color: var(--wb-color-heading); }

/* outline: a bordered toggle, for standing alone on a white page */
.wb-toggle--outline {
  border-color: var(--wb-color-control-border);
  background: var(--wb-color-ground);
  color: var(--wb-color-ink);
}
.wb-toggle--outline:hover { background: var(--wb-color-row-hover); color: var(--wb-color-heading); }
.wb-toggle--outline:focus-visible { border-color: var(--wb-color-focus-border); }
.wb-toggle--outline[aria-pressed="true"] {
  background: var(--wb-color-fill);
  border-color: var(--wb-color-control-border);
  color: var(--wb-color-heading);
}

.wb-toggle[aria-invalid="true"] { border-color: var(--wb-color-invalid); }
.wb-toggle[aria-invalid="true"]:focus-visible { box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-invalid-ring); }

.wb-toggle:disabled,
.wb-toggle[aria-disabled="true"] { opacity: var(--wb-opacity-disabled); pointer-events: none; cursor: default; }

/* ==== css/components/tooltip.css ==== */
/* Tooltip. A short label for an icon or a control, shown on hover or keyboard focus. It is a
   popover with popover="manual" so the script alone controls when it opens; the trigger points at
   it with aria-describedby.
   Markup:
     <button class="wb-button wb-button--icon" aria-describedby="tip-model">...</button>
     <span class="wb-tooltip" role="tooltip" id="tip-model" popover="manual">Ask the model for v4</span>
   The script opens and closes the popover on the trigger's hover and focus and positions it above
   the trigger by default, or below with .wb-tooltip--below. The arrow sits in the flow (a rotated
   square), so the component needs no positioning of its own.
   States: shown and hidden; the trigger keeps its own states. */

.wb-tooltip {
  display: block;
  width: fit-content;
  padding: var(--wb-space-2) var(--wb-space-4);
  border: 0;
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-tooltip);
  color: var(--wb-color-tooltip-ink);
  font-size: var(--wb-ratio-label);
  line-height: var(--wb-leading-control);
  box-shadow: none;
  overflow: visible;
  z-index: var(--wb-layer-overlay);
}

/* the arrow: a rotated square sitting half outside the bubble, under the label by default
   (so the tooltip sits above the trigger) */
.wb-tooltip::after {
  content: "";
  display: block;
  width: var(--wb-space-3);
  height: var(--wb-space-3);
  margin: var(--wb-space-1) auto calc(-0.5 * var(--wb-space-3) - var(--wb-space-2));
  background: var(--wb-color-tooltip);
  transform: rotate(45deg);
}
.wb-tooltip--below::after { content: none; }

/* .wb-tooltip--below: the arrow above the label, so the tooltip sits under the trigger */
.wb-tooltip--below::before {
  content: "";
  display: block;
  width: var(--wb-space-3);
  height: var(--wb-space-3);
  margin: calc(-0.5 * var(--wb-space-3) - var(--wb-space-2)) auto var(--wb-space-1);
  background: var(--wb-color-tooltip);
  transform: rotate(45deg);
}

/* ==== css/components/topbar.css ==== */
/* Top bar. The white bar across the top of a tool page (round 3: white over grey).
   <header class="wb-topbar">
     <h1 class="wb-topbar__title">Pipeline</h1>
     <nav class="wb-topbar__nav"><a href="#" aria-current="page">Live</a><a href="#">Watching</a></nav>
     <label class="wb-topbar__search"><svg ...></svg><input type="search" placeholder="Search"></label>
     <div class="wb-actions"><button class="wb-button">New deal</button></div>
   </header>
   The title uses the tool-title size (round 4: 1.75rem, a clear step above body).
   States: nav link :hover, :focus-visible, [aria-current]; search :focus-within. */

.wb-topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-5) var(--wb-space-8);
  padding: var(--wb-space-5) var(--wb-layout-tool-gutter);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  background: var(--wb-color-ground);
}

.wb-topbar__title {
  font-size: var(--wb-text-tool-title);
  font-weight: var(--wb-weight-semibold);
}

/* A bar with more links than a phone is wide wraps them onto a second line instead of pushing the
   page wider (found on a five-chapter portal at 400px, 9 Oct 2026). */
.wb-topbar__nav { display: flex; flex-wrap: wrap; gap: var(--wb-space-1); min-width: 0; }

.wb-topbar__nav a {
  flex: none;
  white-space: nowrap;
  padding: var(--wb-space-1) var(--wb-space-5);
  border-radius: var(--wb-radius-control);
  color: var(--wb-color-soft);
  text-decoration: none;
  transition: background-color var(--wb-motion-fast) var(--wb-motion-ease), color var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-topbar__nav a:hover { color: var(--wb-color-heading); }
.wb-topbar__nav a[aria-current] { color: var(--wb-color-heading); background: var(--wb-color-fill); }
.wb-topbar__nav a:focus-visible { outline: none; box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring); }

.wb-topbar__search {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  margin-left: auto;
  min-width: 16rem;
  padding: var(--wb-space-2) var(--wb-space-5);
  border: var(--wb-line-rule) solid var(--wb-color-control-border);
  border-radius: var(--wb-radius-control);
  background: var(--wb-color-ground);
  color: var(--wb-color-soft);
  transition: border-color var(--wb-motion-fast) var(--wb-motion-ease), box-shadow var(--wb-motion-fast) var(--wb-motion-ease);
}
.wb-topbar__search svg { width: 1em; height: 1em; flex-shrink: 0; }
.wb-topbar__search input {
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--wb-color-ink);
}
.wb-topbar__search input::placeholder { color: var(--wb-color-soft); }
.wb-topbar__search:focus-within {
  border-color: var(--wb-color-focus-border);
  box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring);
}

@media (max-width: 640px) {
  .wb-topbar__search { order: 10; flex-basis: 100%; min-width: 0; margin-left: 0; }
}

/* ==== css/layouts/dashboard.css ==== */
/* Dashboard: numbers and charts for a portfolio, a fund or a hotel, read at a glance.
   A tool page (.wb-tool, 13px, full width, top bar) with three parts, top to bottom:

   <div class="wb-tool wb-dash">
     <header class="wb-topbar"> ... </header>
     <section class="wb-dash__head">
       <div><p class="wb-kicker">Portfolio · September 2026</p><h2>Six hotels, trading ahead of budget</h2></div>
       <div class="wb-actions"> period toggle group, export button </div>
       <dl class="wb-figures"> the headline numbers, up to six </dl>
     </section>
     <div class="wb-dash__grid">
       <figure class="wb-chart"> ... </figure>   (two per row on a wide screen, one on a narrow one)
     </div>
     <section> <div class="wb-tool__bar"> ... </div> table </section>
   </div>

   No sidebar. Each chart sits in its own bordered card (Angus's pick, 24 Sep 2026, over cells
   divided by hairlines). The headline figures are the largest numbers on any page but stay under
   the page's heading, which stays under the title (house rule 8; round 2, 24 Sep 2026). */

.wb-dash__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--wb-space-8) var(--wb-space-10);
  align-items: end;
  padding: var(--wb-space-9) var(--wb-layout-tool-gutter) var(--wb-space-8);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}
.wb-dash__head h2 { margin-top: var(--wb-space-1); }
.wb-dash__head > .wb-figures { grid-column: 1 / -1; }

.wb-dash .wb-dash__head .wb-figures dd { font-size: var(--wb-text-dash-figure); }

.wb-dash__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 32rem), 1fr));
  gap: var(--wb-space-7);
  padding: var(--wb-space-7) var(--wb-layout-tool-gutter);
}
.wb-dash__grid > * {
  min-width: 0;
  padding: var(--wb-space-9);
  border: var(--wb-line-rule) solid var(--wb-color-rule);
  border-radius: var(--wb-radius-overlay);
  background: var(--wb-color-ground);
}

@media (max-width: 640px) {
  .wb-dash__head { grid-template-columns: minmax(0, 1fr); }
}

/* ==== css/layouts/form.css ==== */
/* Form page: settings, a set-up screen, a long form. One centred column the width of a reading
   page, so no field runs 1,400px wide; fields sit two or three to a row in .wb-fields so each row
   fills the column. Sections are ruled like the reading page. A bar holding the save action stays
   in view at the bottom of the window while the form scrolls.

   Inside a tool (13px, with its top bar):
   <div class="wb-tool">
     <header class="wb-topbar"> ... </header>
     <form class="wb-form">
       <header class="wb-form__head"><p class="wb-kicker">Settings</p><h1>Project Larch</h1><p>One line on what this page sets.</p></header>
       <section><h2>Deal</h2><p>What this group of settings is for.</p><div class="wb-fields"> fields </div></section>
       <section> ... </section>
       <section class="wb-form__danger"><h2>Withdraw</h2><p>...</p><div class="wb-actions"><button class="wb-button wb-button--destructive">...</button></div></section>
       <div class="wb-form__bar"><span>Unsaved changes</span><div class="wb-actions"> Cancel, Save </div></div>
     </form>
   </div>
   The same markup works on its own at reading size (16px): leave out .wb-tool. */

.wb-form {
  max-width: var(--wb-layout-read-width);
  margin: 0 auto;
  padding: var(--wb-space-10) var(--wb-layout-gutter) 0;
}

.wb-form__head { padding-bottom: var(--wb-space-9); }
.wb-form__head h1 { margin: var(--wb-space-3) 0 var(--wb-space-3); }
.wb-tool .wb-form__head h1 { font-size: var(--wb-text-tool-title); }
.wb-form__head p:last-child { color: var(--wb-color-soft); }

.wb-form > section {
  display: grid;
  gap: var(--wb-space-7);
  padding: var(--wb-layout-section-gap) 0;
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
}
.wb-form > section > h2 { font-size: var(--wb-text-subsection); }
.wb-tool .wb-form > section > h2 { font-size: var(--wb-text-tool-section); font-weight: var(--wb-weight-semibold); }
.wb-form > section > h2 + p { margin-top: calc(-1 * var(--wb-space-5)); color: var(--wb-color-soft); }

.wb-form__danger > h2 { color: var(--wb-color-error-ink); }

/* the save bar: stays at the bottom of the window while there is form above it */
.wb-form__bar {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--wb-space-5) var(--wb-space-9);
  padding: var(--wb-space-7) 0;
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
  background: var(--wb-color-ground);
}
.wb-form__bar > span { font-size: var(--wb-ratio-small); color: var(--wb-color-soft); }

@media (max-width: 640px) {
  .wb-form { padding-left: var(--wb-layout-gutter-narrow); padding-right: var(--wb-layout-gutter-narrow); }
}

/* ==== css/layouts/portal.css ==== */
/* Deal portal: a deal's whole record as one page — the web equivalent of the IM. Chapters, each
   a hub with a tab strip of views; a view is one screen, the exhibit leads and the argument folds
   beneath it. 1360px wide, gutters included: wider than the website page because a portal carries
   registers and schedules. A masthead at the top with the finder, a chapter bar pinned at the
   foot. Nobody reads a portal front to back, so depth costs it nothing and the finder makes it
   findable — which is why a chapter is never cut for length, only for having no source.

   <header class="wb-portal__masthead">
     <a class="wb-portal__back" href="#/">Contents</a>
     <span class="wb-portal__where">Chapter 02 · The Ultra Luxury Market</span>
     <button class="wb-portal__find" data-wb-open="finder">Search</button>
     <span class="wb-portal__brand">Fawley Court</span>
   </header>
   <main class="wb-portal">
     <article class="wb-portal__view">
       <header class="wb-portal__head">
         <p class="wb-kicker">Chapter 02 · Ultra Luxury Market</p>
         <h1>The Ultra Luxury Market</h1>
         <p class="wb-portal__figure">34 hotels</p>
         <p class="wb-portal__blurb">The rate and calendar evidence that frames the underwrite.</p>
       </header>
       <nav class="wb-tabs"> ... </nav>
       <section class="wb-portal__exhibit"> the table, grid, register or chart </section>
       <details class="wb-accordion wb-accordion--basis"> ... reasoning &amp; basis ... </details>
       <nav class="wb-portal__pager"> ... </nav>
     </article>
   </main>
   <nav class="wb-portal__bar"> chapter chips, contents sheet, reference sheet </nav>

   Rule ranks (house rule 8): the masthead and the chapter bar are the heaviest rules on the
   page; the rule under a view head is next; rules inside an exhibit are hairlines. The title
   outranks every heading under it, and no heading inside a view outranks the view's own. */

.wb-portal {
  max-width: var(--wb-layout-portal-width);
  margin: 0 auto;
  padding: var(--wb-space-12) var(--wb-layout-gutter) var(--wb-space-14);
}

/* ---- masthead: where you are, on the left; who it is from, on the right ---- */

.wb-portal__masthead {
  position: sticky;
  top: 0;
  z-index: var(--wb-layer-overlay);
  display: flex;
  align-items: center;
  gap: var(--wb-space-5);
  max-width: none;
  padding: var(--wb-space-5) var(--wb-layout-gutter);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule-section);
  background: var(--wb-color-ground);
}

/* the brand in the masthead is a label that happens to link home, never underlined prose */
.wb-portal__masthead .wb-topbar__title a { text-decoration: none; color: inherit; }

.wb-portal__back {
  font-size: var(--wb-text-kicker);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-heading);
  text-decoration: none;
}
.wb-portal__back:hover { color: var(--wb-color-accent); }

.wb-portal__where {
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}

.wb-portal__find { margin-left: auto; }

.wb-portal__brand {
  font-size: var(--wb-text-kicker);
  font-weight: var(--wb-weight-semibold);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-heading);
}

/* ---- the view head: the title, its headline figure on the same line, the blurb under ---- */

.wb-portal__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--wb-space-3) var(--wb-space-10);
}
.wb-portal__head .wb-kicker { grid-column: 1 / -1; }
.wb-portal__head h1 { grid-column: 1; }

/* the figure sits against the title, never under it: a portal answers a question with a number */
.wb-portal__figure {
  grid-column: 2;
  justify-self: end;
  font-size: var(--wb-text-figure);
  font-weight: var(--wb-weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--wb-tracking-figure);
  color: var(--wb-color-accent);
}

/* the blurb runs the width of the head: no measure cap, house rule 4 */
.wb-portal__blurb {
  grid-column: 1 / -1;
  font-size: var(--wb-text-standfirst);
  line-height: var(--wb-leading-standfirst);
  color: var(--wb-color-soft);
}

/* ---- the view ---- */

/* One measure separates every block in a view — the exhibits, the index, the fold, the pager —
   so nothing has to remember its own spacing and a new block kind cannot arrive unspaced. */
.wb-portal__view,
.wb-portal .wb-tabs__panel {
  display: flex;
  flex-direction: column;
  gap: var(--wb-layout-section-gap);
}
/* a display of its own out-specifies the hidden attribute's, so say it here or every unselected
   tab panel renders under the selected one */
.wb-portal .wb-tabs__panel[hidden] { display: none; }
/* a flex item will not shrink below its content unless told: without this a wide table inside
   .wb-scroll widens the whole page instead of scrolling inside its own box */
.wb-portal__view > *,
.wb-portal .wb-tabs__panel > * { min-width: 0; }

/* only consecutive exhibits are ruled apart: a rule inside a view never outweighs the rules
   between views (house rule 8) */
.wb-portal__exhibit + .wb-portal__exhibit {
  padding-top: var(--wb-layout-section-gap);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
}

.wb-portal__exhibit > h2 { margin-bottom: var(--wb-space-5); }
.wb-portal__exhibit > h3:not(:first-child) { margin: var(--wb-space-10) 0 var(--wb-space-3); }
.wb-portal__exhibit > p + p { margin-top: var(--wb-space-6); }

.wb-portal__exhibit > .wb-table,
.wb-portal__exhibit > .wb-scroll,
.wb-portal__exhibit > .wb-figures,
.wb-portal__exhibit > .wb-chart { margin-top: var(--wb-space-6); }

/* a source line under the exhibit it belongs to */
.wb-portal__source {
  margin-top: var(--wb-space-5);
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}

/* two tracks: prose never sits alone beside dead space (house rule 1) */
.wb-portal__panes {
  /* the knob a view sets inline when its two tracks are not equal, e.g. style="--wb-portal-split: 2fr 1fr" */
  --wb-portal-split: minmax(0, 1fr) minmax(0, 1fr);
  display: grid;
  grid-template-columns: var(--wb-portal-split);
  gap: var(--wb-space-12);
}

/* ---- pager ---- */

.wb-portal__pager {
  display: flex;
  justify-content: space-between;
  gap: var(--wb-space-5) var(--wb-space-10);
  margin-top: var(--wb-space-9);
  padding-top: var(--wb-space-7);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
}
.wb-portal__pager a { color: var(--wb-color-heading); text-decoration: none; }
.wb-portal__pager a:hover { color: var(--wb-color-accent); }
.wb-portal__pager .wb-kicker { display: block; color: var(--wb-color-soft); }

/* ---- the chapter bar, pinned at the foot ---- */

.wb-portal__bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--wb-layer-overlay);
  display: flex;
  align-items: stretch;
  gap: var(--wb-space-5);
  padding: 0 var(--wb-layout-gutter);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
  background: var(--wb-color-ground);
}

/* the strip scrolls sideways on a narrow screen using the bank's own scroller, never a second
   overflow idiom of its own: <div class="wb-portal__chapters wb-scroll"> */
.wb-portal__chapters {
  display: flex;
  gap: var(--wb-space-8);
}

.wb-portal__chapter {
  display: flex;
  align-items: center;
  gap: var(--wb-space-3);
  padding: var(--wb-space-6) 0;
  border: 0;
  background: none;
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
  white-space: nowrap;
  cursor: pointer;
}
.wb-portal__chapter:hover { color: var(--wb-color-heading); }
.wb-portal__chapter[aria-current="true"] { color: var(--wb-color-heading); font-weight: var(--wb-weight-semibold); }
.wb-portal__chapter .wb-portal__num { color: var(--wb-color-soft); font-variant-numeric: tabular-nums; }

/* the bar covers the foot of the page, so the page ends above it */
.wb-portal { padding-bottom: var(--wb-space-14); }

/* ---- the item sub-layout: one comparable, one building, one counterparty ---- */

.wb-portal__item {
  max-width: var(--wb-layout-portal-width);
  margin: 0 auto;
  padding: var(--wb-space-12) var(--wb-layout-gutter) var(--wb-space-14);
}
.wb-portal__item > section + section {
  margin-top: var(--wb-layout-section-gap);
  padding-top: var(--wb-layout-section-gap);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
}
/* the photograph against what the property discloses, never a column of one beside nothing */
.wb-portal__duo {
  --wb-portal-split: minmax(0, 46fr) minmax(0, 54fr);
  display: grid;
  grid-template-columns: var(--wb-portal-split);
  gap: var(--wb-space-12);
  align-items: start;
}
.wb-portal__duo img { width: 100%; border-radius: var(--wb-radius-image); }

@media (max-width: 900px) {
  .wb-portal__panes,
  .wb-portal__duo { grid-template-columns: minmax(0, 1fr); gap: var(--wb-space-10); }
}

@media (max-width: 640px) {
  .wb-portal,
  .wb-portal__item { padding: var(--wb-space-10) var(--wb-layout-gutter-narrow) var(--wb-space-13); }
  .wb-portal__masthead,
  .wb-portal__bar { padding-left: var(--wb-layout-gutter-narrow); padding-right: var(--wb-layout-gutter-narrow); }
  .wb-portal__head { grid-template-columns: minmax(0, 1fr); }
  .wb-portal__figure { grid-column: 1; justify-self: start; }
  .wb-portal__where { display: none; }
  /* a phone stacks the pager's previous and next, and their long titles wrap (9 Oct 2026) */
  .wb-portal__pager,
  .wb-portal__pager .wb-pagination__list { flex-direction: column; align-items: flex-start; }
  .wb-portal__pager .wb-pagination__link { white-space: normal; justify-content: flex-start; text-align: left; }
}

/* ---- the blocks the portal's renderer places ----
   Arrangement only: every one of these positions bank components, and none sets a colour, a
   font or a size a token does not already give. The classes are the contract in
   docs/goals/260926-deal-portal/interfaces.md; js/portal.js emits them. */

/* the chapter's tab strip, above its views */
.wb-portal__tabs { flex-wrap: wrap; border-bottom: var(--wb-line-rule) solid var(--wb-color-rule); }

/* the filter row: the sets sit on one line, each scrolling in the bank's own scroller */
/* each set scrolls inside .wb-scroll rather than widening the page: a flex child holds its
   content width unless told otherwise, which is how the row still dragged the page sideways at
   390px after the scroller went in */
.wb-portal__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--wb-space-6) var(--wb-space-10);
}
.wb-portal__filters > * { min-width: 0; max-width: 100%; }

/* a filterable collection of like items: its filters above its card grid */
.wb-portal__collection {
  display: flex;
  flex-direction: column;
  gap: var(--wb-space-9);
}

/* a photo strip; each plate opens the lightbox */
.wb-portal__plates {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--wb-space-7);
}
.wb-portal__plates img { width: 100%; border-radius: var(--wb-radius-image); }
.wb-portal__plates figcaption { margin-top: var(--wb-space-3); font-size: var(--wb-text-caption); color: var(--wb-color-soft); }

/* statements, not bullets: a name and the sentence that qualifies it */
.wb-portal__bullets { display: grid; gap: var(--wb-space-6); }

/* the register and the schedule: their own search, then their rows */
/* one track, explicitly zero-minimum: a grid track sizes to its content's max-content by
   default, so a filter row that scrolls inside itself still stretched the track and the page
   with it */
.wb-portal__register,
.wb-portal__schedule {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wb-space-7);
}
.wb-portal__register-section { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--wb-space-4); }
.wb-portal__search { width: 100%; }

/* a source line under the exhibit it belongs to, and a note under a table */
.wb-portal__src,
.wb-portal__note { font-size: var(--wb-text-caption); color: var(--wb-color-soft); }

/* the fold: the argument beneath the exhibit it argues */
.wb-portal__fold { border-top: var(--wb-line-rule) solid var(--wb-color-rule); }

/* the finder, over everything */
.wb-portal__finder { width: min(var(--wb-layout-portal-width), 100% - var(--wb-space-10)); }

/* ---- motion: transform, opacity and stroke only, nothing that lays out ---- */

.wb-portal__stagger {
  /* the renderer sets --wb-i inline: the block's place in the view, so they arrive in order */
  --wb-i: 0;
  animation: wb-portal-in var(--wb-motion-base) var(--wb-motion-ease) both;
  animation-delay: calc(var(--wb-i) * var(--wb-motion-fast));
}
@keyframes wb-portal-in {
  from { opacity: 0; transform: translateY(var(--wb-space-5)); }
  to { opacity: 1; transform: none; }
}

/* leaving a view fades before the next is written, so a route change reads as one movement */
.wb-portal__view.is-leaving { opacity: 0; transition: opacity var(--wb-motion-fast) var(--wb-motion-ease); }
/* a filter change fades its grid rather than swapping it under the eye */
.is-swapping { opacity: 0; transition: opacity var(--wb-motion-fast) var(--wb-motion-ease); }

@media (prefers-reduced-motion: reduce) {
  .wb-portal__stagger { animation: none; }
  .wb-portal__view.is-leaving,
  .is-swapping { opacity: 1; transition: none; }
}

/* ==== css/layouts/read.css ==== */
/* Reading page: a deal note, memo or article. One column, 960px wide gutters included (rounds 3-4),
   text filling the column (house rule 4), sections divided by a hairline rule (round 4) with
   2.2rem either side (round 3). Settled layout: the deal-note reference in docs/taste-read.png.

   <main class="wb-read">
     <header class="wb-read__meta"><span class="wb-kicker">Deal note 04 · 11 September 2026</span><span class="wb-kicker">For discussion</span></header>
     <h1>Title</h1>
     <p class="wb-read__standfirst">One or two sentences.</p>
     <figure><img ...><figcaption>Caption</figcaption></figure>
     <section><h2>Heading</h2><p>...</p></section>
     <section> ... </section>
     <section class="wb-decision"> ... </section>
   </main>

   Rule ranks (house rule 8): the rules between sections are the heaviest on the page;
   the meta-line rule and every rule inside a section are lighter hairlines. */

.wb-read {
  max-width: var(--wb-layout-read-width);
  margin: 0 auto;
  padding: var(--wb-space-13) var(--wb-layout-gutter) var(--wb-space-14);
}

.wb-read__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--wb-space-3) var(--wb-space-10);
  padding-bottom: var(--wb-space-7);
  margin-bottom: var(--wb-space-10);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

.wb-read > h1 { margin-bottom: var(--wb-space-6); }

.wb-read__standfirst {
  margin-bottom: var(--wb-space-10);
  font-size: var(--wb-text-standfirst);
  line-height: var(--wb-leading-standfirst);
  color: var(--wb-color-soft);
}

.wb-read figure { margin-bottom: var(--wb-space-10); }
.wb-read figure img { width: 100%; border-radius: var(--wb-radius-image); }
.wb-read figure:not(.wb-chart) figcaption {
  margin-top: var(--wb-space-3);
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}

.wb-read section h2 { margin-bottom: var(--wb-space-5); }
.wb-read > figure + section,
.wb-read > p + section { margin-top: var(--wb-layout-section-gap); }

.wb-read section + section {
  margin-top: var(--wb-layout-section-gap);
  padding-top: var(--wb-layout-section-gap);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
}

.wb-read p + p,
.wb-read p + ul,
.wb-read p + ol:not(.wb-steps),
.wb-read ul + p,
.wb-read ol + p { margin-top: var(--wb-space-6); }

.wb-read section > .wb-figures,
.wb-read section > .wb-table,
.wb-read section > .wb-scroll { margin-top: var(--wb-space-4); }

/* A paragraph after an exhibit keeps the prose gap: the exhibit components carry a top margin only,
   so the paragraph sat flush against the table's, label list's or figure strip's last rule. */
.wb-read section > .wb-table + p,
.wb-read section > .wb-scroll + p,
.wb-read section > .wb-kv + p,
.wb-read section > .wb-figures + p { margin-top: var(--wb-space-6); }

.wb-read .wb-quote + p { margin-top: 0; }

/* on a reading page a row's name may wrap: the table has the column's width to read in */
.wb-read .wb-table .wb-table__primary { white-space: normal; }

/* Longer documents (the report page): subsections, charts and tables inside a section. A
   subsection heading takes space above it, never a rule: only sections are ruled. */
.wb-read section > h3:not(:first-child) { margin: var(--wb-space-10) 0 var(--wb-space-3); }
.wb-read section > h2 + h3 { margin-top: var(--wb-space-5); }
.wb-read section > .wb-chart,
.wb-read section > figure.wb-chart { margin: var(--wb-space-10) 0; }
.wb-read section > .wb-chart:last-child { margin-bottom: 0; }
.wb-read section > .wb-steps + p,
.wb-read section > p + .wb-steps { margin-top: var(--wb-space-5); }

@media (max-width: 640px) {
  .wb-read { padding: var(--wb-space-10) var(--wb-layout-gutter-narrow) var(--wb-space-12); }
}

/* ==== css/layouts/site.css ==== */
/* Website page (landing): the public face of a firm or a product. Light, one wide column
   (1200px, gutters included), sections divided by hairlines like the reading page.

   <header class="wb-site__header"><div class="wb-site__bar">
     <a class="wb-site__brand" href="/">Firm name</a>
     <nav class="wb-site__nav"><a href="#">Approach</a> ... </nav>
     <a class="wb-button" href="#">Contact</a>
   </div></header>
   <main class="wb-site">
     <section class="wb-site__hero">
       <div><p class="wb-kicker">...</p><h1>Title</h1></div>
       <div><p class="wb-site__lede">Two sentences.</p><div class="wb-actions">...</div></div>
       <figure class="wb-site__image"><img ...></figure>
     </section>
     <section><h2>Heading</h2><div class="wb-site__grid"><article><h3>..</h3><p>..</p></article> ...</div></section>
     <section class="wb-site__cta"> ... </section>
   </main>
   <footer class="wb-site__footer"><div class="wb-site__bar"> ... </div></footer>

   The hero stacks: kicker and title, then the lede and the actions under them, then the image
   (Angus's pick, 24 Sep 2026, over title and lede side by side). The grid's items sit under a
   hairline, not in boxes. */

.wb-site__bar,
.wb-site {
  max-width: var(--wb-layout-site-width);
  margin: 0 auto;
  padding-left: var(--wb-layout-gutter);
  padding-right: var(--wb-layout-gutter);
}

/* header */
.wb-site__header { border-bottom: var(--wb-line-rule) solid var(--wb-color-rule); }
.wb-site__header .wb-site__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wb-space-5) var(--wb-space-10);
  padding-top: var(--wb-space-7);
  padding-bottom: var(--wb-space-7);
}
.wb-site__brand {
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-bold);
  letter-spacing: var(--wb-tracking-heading);
  color: var(--wb-color-heading);
  text-decoration: none;
}
.wb-site__nav { display: flex; flex-wrap: wrap; gap: var(--wb-space-1) var(--wb-space-8); margin-left: auto; }
.wb-site__nav a { color: var(--wb-color-soft); text-decoration: none; }
.wb-site__nav a:hover,
.wb-site__nav a[aria-current] { color: var(--wb-color-heading); }
.wb-site__nav a:focus-visible { outline: none; box-shadow: 0 0 0 var(--wb-line-focus) var(--wb-color-focus-ring); border-radius: var(--wb-radius-small); }

/* sections */
.wb-site > section { padding: var(--wb-space-13) 0; }
.wb-site > section + section { border-top: var(--wb-line-rule) solid var(--wb-color-rule-section); }
.wb-site > section > h2,
.wb-site__cta h2 { font-size: var(--wb-text-site-section); margin-bottom: var(--wb-space-5); }
.wb-site > section > h2 + p { margin-bottom: var(--wb-space-10); color: var(--wb-color-soft); font-size: var(--wb-text-standfirst); line-height: var(--wb-leading-standfirst); }

/* hero */
.wb-site__hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wb-space-8);
}
.wb-site__hero h1 { margin-top: var(--wb-space-5); }
.wb-site__lede {
  font-size: var(--wb-text-standfirst);
  line-height: var(--wb-leading-standfirst);
  color: var(--wb-color-soft);
}
.wb-site__hero .wb-actions { margin-top: var(--wb-space-8); }
.wb-site__image { grid-column: 1 / -1; margin-top: var(--wb-space-6); }
.wb-site__image img { width: 100%; border-radius: var(--wb-radius-image); }
.wb-site__image figcaption { margin-top: var(--wb-space-3); font-size: var(--wb-text-caption); color: var(--wb-color-soft); }

/* a grid of like things: services, principles, people */
.wb-site__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--wb-space-12) var(--wb-space-10);
}
.wb-site__grid > * { padding-top: var(--wb-space-7); border-top: var(--wb-line-rule) solid var(--wb-color-rule); }
.wb-site__grid h3 { margin-bottom: var(--wb-space-3); }

/* the closing ask */
.wb-site__cta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--wb-space-9) var(--wb-space-12);
  align-items: center;
}
.wb-site__cta h2 { margin-bottom: var(--wb-space-3); }

/* footer */
.wb-site__footer { border-top: var(--wb-line-rule) solid var(--wb-color-rule); }
.wb-site__footer .wb-site__bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--wb-space-5) var(--wb-space-10);
  padding-top: var(--wb-space-10);
  padding-bottom: var(--wb-space-12);
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}
.wb-site__footer a { color: inherit; }

@media (max-width: 800px) {
  .wb-site__cta { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .wb-site__bar, .wb-site { padding-left: var(--wb-layout-gutter-narrow); padding-right: var(--wb-layout-gutter-narrow); }
  .wb-site > section { padding: var(--wb-space-11) 0; }
  .wb-site__nav { margin-left: 0; order: 3; flex-basis: 100%; }
  .wb-site > section > h2,
  .wb-site__cta h2 { font-size: var(--wb-text-site-section-narrow); }
}

/* ==== css/layouts/tool.css ==== */
/* Tool page: a pipeline, tracker or list you work in. Full width, 13px text (round 4), tight
   rows (round 4), the detail of the selected item in a grey strip above the list (round 3),
   never a pane down the side (house rule 1). Settled layout: docs/taste-work.png.

   <div class="wb-tool">
     <header class="wb-topbar"> ... </header>
     <section class="wb-tool__detail">
       <div><p class="wb-kicker">First round · bid due 17 Sep</p><h2>Project Larch</h2>
            <p class="wb-tool__sub">Bath · 148 keys</p><div class="wb-actions"> ... </div></div>
       <dl class="wb-figures"> ... </dl>
     </section>
     <section>
       <div class="wb-tool__bar"><h2>Live deals</h2><span>11 open, 3 need you</span></div>
       <div class="wb-scroll"><table class="wb-table"> ... </table></div>
     </section>
   </div>

   Components inside a tool page take tool density from here: 13px text, tighter rows,
   rule-free figures at tool size. Nothing else about them changes. */

.wb-tool {
  min-height: 100vh;
  font-size: var(--wb-text-tool);
  /* at 13px the smaller sizes (0.8em, 0.85em) fall under 11px: on a tool page every small line
     (labels, pills, column heads, notes) is the bank's smallest size instead */
  --wb-ratio-small: var(--wb-text-min);
  --wb-ratio-label: var(--wb-text-min);
  --wb-ratio-head: var(--wb-text-min);
}

.wb-tool__detail h2,
.wb-dash__head h2 { font-size: var(--wb-text-tool-heading); }

/* the selected item, above the list */
.wb-tool__detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: var(--wb-space-10);
  align-items: start;
  padding: var(--wb-space-8) var(--wb-layout-tool-gutter);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
  background: var(--wb-color-surface-muted);
}
.wb-tool__detail h2 { margin: var(--wb-space-1) 0; }
.wb-tool__sub { color: var(--wb-color-soft); }
.wb-tool__detail .wb-actions { margin-top: var(--wb-space-6); }

/* the heading row above a list */
.wb-tool__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--wb-space-3) var(--wb-space-7);
  padding: var(--wb-space-7) var(--wb-layout-tool-gutter) var(--wb-space-4);
}
.wb-tool__bar h2 { font-size: var(--wb-text-tool-section); font-weight: var(--wb-weight-semibold); }
.wb-tool__bar > :not(h2) { font-size: var(--wb-ratio-small); color: var(--wb-color-soft); }

/* tool density for components. A tool table keeps its columns and scrolls sideways on a phone
   (inside .wb-scroll) rather than wrapping every cell. */
.wb-tool .wb-table { min-width: 48rem; }
.wb-tool .wb-table th,
.wb-tool .wb-table td {
  padding-top: var(--wb-layout-tool-row-pad);
  padding-bottom: var(--wb-layout-tool-row-pad);
}
.wb-tool .wb-table thead th { padding-top: var(--wb-space-3); padding-bottom: var(--wb-space-3); }
.wb-tool .wb-table tr.wb-table__group > * { padding-top: var(--wb-space-8); padding-bottom: var(--wb-space-1); }
.wb-tool .wb-table th:first-child,
.wb-tool .wb-table td:first-child { padding-left: var(--wb-layout-tool-gutter); }
.wb-tool .wb-table th:last-child,
.wb-tool .wb-table td:last-child { padding-right: var(--wb-layout-tool-gutter); }

/* inside a box on the page (a chart's table view, a card, a dashboard cell) or a panel over it
   (sheet, dialog, popover) a table is not the page's list: no minimum width and no page gutters,
   so it sits flush with the box's own content and never pushes out of it */
.wb-tool :is(.wb-chart, .wb-card, .wb-dash__grid > *, .wb-sheet, .wb-dialog, .wb-popover) .wb-table { min-width: 0; }
.wb-tool :is(.wb-chart, .wb-card, .wb-dash__grid > *, .wb-sheet, .wb-dialog, .wb-popover) .wb-table :is(th, td):first-child { padding-left: 0; }
.wb-tool :is(.wb-chart, .wb-card, .wb-dash__grid > *, .wb-sheet, .wb-dialog, .wb-popover) .wb-table :is(th, td):last-child { padding-right: 0; }

.wb-tool .wb-figures { border-top: 0; border-bottom: 0; }
.wb-tool .wb-figures > div { padding: var(--wb-space-5) var(--wb-space-7) var(--wb-space-5) 0; border-right: 0; }
.wb-tool .wb-figures dd { font-size: var(--wb-text-tool-figure); }
.wb-tool .wb-fieldset > legend { font-size: var(--wb-text-tool-section); font-weight: var(--wb-weight-semibold); }

@media (max-width: 800px) {
  .wb-tool__detail { grid-template-columns: minmax(0, 1fr); gap: var(--wb-space-8); }
}
@media (max-width: 640px) {
  .wb-tool .wb-figures > div:nth-child(n + 3) { border-top: 0; }
}

/* ==== css/themes/portal.css ==== */
/* The portal theme — Fawley Court's register, as a token set and the component treatments that
   carry it. Angus ruled it on 26 September 2026: shown nine A-or-B pairs of the bank's treatment
   against Fawley's on real portal pages, he took Fawley on seven of eight and kept the bank's only
   for the finder. His words: "the design language for it is a lot more refined, less basic, than
   the web bank." Both sides ran Inter, so what he picked is the structure — the density, the
   rules, the tracked capitals, the absence of fills — and not a typeface.

   A page opts in with <html data-wb-theme="portal">. Nothing here reaches a page that does not,
   so the reading, report, tool, dashboard, form and landing layouts are untouched until they are
   moved over deliberately.

   The register, in words, is in the `deal-portal` skill. In short: nothing is boxed, structure is
   carried by hairlines and space, tabs are text on a baseline under an accent underline, tracked
   capitals carry the micro labels and nothing else shouts, tables are tight with a heavier rule
   under the head row, and the subject is outlined where the set is filled.

   Generated once from the exam's winning stylesheets and maintained here by hand. */


/* The theme runs the portal's tables and cards below the page's body size, and several
   components size themselves as a RATIO of whatever they sit in -- a status pill, a group label,
   a table's column head. A ratio of a smaller thing is smaller again, so they fell to 10.9px and
   9.6px against the bank's 12px floor. Flooring the ratios here fixes every one of them at once,
   rather than one class at a time each time the audit finds another. Fawley's own floor is
   11.5px; it is not ours. */
[data-wb-theme="portal"] {
  --wb-ratio-small: max(var(--wb-text-min), 0.85em);
  --wb-ratio-label: max(var(--wb-text-min), 0.8em);
  --wb-ratio-head: max(var(--wb-text-min), 0.85em);
}

/* ---- tabs — The chapter tab strip: text on a baseline, no pill and no fill. ---- */

[data-wb-theme="portal"] .wb-tabs__list {
  gap: 0;
  overflow-x: auto;
  background: var(--wb-color-ground);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule-section);
}

[data-wb-theme="portal"] .wb-tabs__tab {
  flex: none;
  min-height: var(--wb-space-12);
  padding: var(--wb-space-5) 0 var(--wb-space-4);
  border: 0;
  border-bottom: calc(var(--wb-line-rule) * 2) solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--wb-color-soft);
  font-size: var(--wb-text-caption);
  font-weight: var(--wb-weight-regular);
}

[data-wb-theme="portal"] .wb-tabs__tab + .wb-tabs__tab {
  margin-left: var(--wb-space-7);
}

[data-wb-theme="portal"] .wb-tabs__tab:hover,
[data-wb-theme="portal"] .wb-tabs__tab:focus-visible {
  color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-tabs__tab[aria-selected="true"],
[data-wb-theme="portal"] .wb-tabs__tab[aria-current="page"] {
  color: var(--wb-color-heading);
  background: transparent;
  font-weight: var(--wb-weight-bold);
  border-bottom-color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-tabs__tab .count,
[data-wb-theme="portal"] .wb-tabs__tab small {
  font-size: var(--wb-text-min);
  color: var(--wb-color-soft);
  font-variant-numeric: tabular-nums;
}

[data-wb-theme="portal"] .wb-tabs__tab[aria-selected="true"] .count,
[data-wb-theme="portal"] .wb-tabs__tab[aria-current="page"] .count {
  color: var(--wb-color-accent);
}

/* ---- table — Tables: tight rows, tracked-capital column heads, a heavier rule under the head. ---- */

[data-wb-theme="portal"] .wb-table {
  font-size: var(--wb-text-caption);
}

[data-wb-theme="portal"] .wb-table th,
[data-wb-theme="portal"] .wb-table td {
  padding: var(--wb-space-2) var(--wb-space-6) var(--wb-space-2) 0;
  vertical-align: top;
  line-height: var(--wb-leading-body);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

[data-wb-theme="portal"] .wb-table thead th {
  font-size: var(--wb-text-min);
  font-weight: var(--wb-weight-regular);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
  padding-bottom: var(--wb-space-4);
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule-section);
}

[data-wb-theme="portal"] .wb-table .wb-table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

[data-wb-theme="portal"] .wb-table .wb-table__primary small {
  font-size: var(--wb-text-min);
}

[data-wb-theme="portal"] .wb-table tr[aria-selected="true"] > td {
  background: var(--wb-color-fill);
  font-weight: var(--wb-weight-bold);
}

[data-wb-theme="portal"] .wb-table tr[aria-selected="true"] td:first-child {
  padding-left: var(--wb-space-6);
}

[data-wb-theme="portal"] .wb-table tr:last-child td {
  border-bottom: 0;
}

/* ---- fold — The reasoning & basis fold: a quiet rule under the exhibit, not a list item. ---- */

[data-wb-theme="portal"] .wb-accordion {
  border-top: var(--wb-line-rule) solid var(--wb-color-rule-section);
}

[data-wb-theme="portal"] .wb-accordion__item {
  border-bottom: 0;
}

[data-wb-theme="portal"] .wb-accordion__summary {
  justify-content: flex-start;
  gap: var(--wb-space-3);
  min-height: var(--wb-space-12);
  padding: var(--wb-space-5) 0 var(--wb-space-4);
  border-radius: 0;
  font-size: var(--wb-text-kicker);
  font-weight: var(--wb-weight-regular);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

[data-wb-theme="portal"] .wb-accordion__summary:hover,
[data-wb-theme="portal"] .wb-accordion__summary:focus-visible {
  color: var(--wb-color-accent);
  text-decoration: none;
}

[data-wb-theme="portal"] .wb-accordion .wb-accordion__chevron {
  width: 0.9em;
  height: 0.9em;
  color: var(--wb-color-soft);
  transform: rotate(-90deg);
}

[data-wb-theme="portal"] .wb-accordion__item[open] .wb-accordion__summary {
  color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-accordion__item[open] .wb-accordion__chevron {
  transform: rotate(0deg);
  color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-accordion__panel {
  padding: var(--wb-space-1) 0 var(--wb-space-8);
}

/* ---- masthead — The masthead: taller, the brand in tracked capitals, a heavier bottom rule. ---- */

[data-wb-theme="portal"] .wb-portal__masthead {
  gap: var(--wb-space-6);
  padding: var(--wb-space-7) var(--wb-layout-gutter);
  border-bottom: calc(var(--wb-line-rule) * 2) solid var(--wb-color-rule-section);
}

[data-wb-theme="portal"] .wb-portal__back {
  font-size: var(--wb-text-caption);
  font-weight: var(--wb-weight-medium);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-heading);
}

[data-wb-theme="portal"] .wb-portal__where {
  font-size: var(--wb-text-caption);
  color: var(--wb-color-soft);
}

/* the assembled portal composes the bank's top bar, so the brand arrives as wb-topbar__title;
   both spellings are the same thing and the theme has to reach both */
[data-wb-theme="portal"] .wb-portal__masthead .wb-topbar__title,
[data-wb-theme="portal"] .wb-portal__masthead .wb-topbar__title a,
[data-wb-theme="portal"] .wb-portal__brand {
  font-size: var(--wb-text-caption);
  font-weight: var(--wb-weight-semibold);
  letter-spacing: calc(var(--wb-tracking-kicker) * 2);
  text-transform: uppercase;
  color: var(--wb-color-heading);
}

/* ---- card — The card in a grid: no panel, the photograph flush, hairlines only. ---- */

[data-wb-theme="portal"] .wb-portal .wb-card {
  gap: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

[data-wb-theme="portal"] .wb-portal .wb-card > figure {
  margin: 0;
}

[data-wb-theme="portal"] .wb-portal .wb-card > figure img {
  width: 100%;
  height: auto;
  border-radius: 0;
}

[data-wb-theme="portal"] .wb-portal .wb-card__content {
  padding: var(--wb-space-6) 0 0;
}

[data-wb-theme="portal"] .wb-portal .wb-card__header,
[data-wb-theme="portal"] .wb-portal .wb-card__footer {
  padding-left: 0;
  padding-right: 0;
}

[data-wb-theme="portal"] .wb-portal .card-top {
  display: flex;
  align-items: baseline;
  gap: var(--wb-space-5);
}

[data-wb-theme="portal"] .wb-portal .card-top h3 {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-semibold);
  letter-spacing: var(--wb-tracking-heading);
  color: var(--wb-color-heading);
}

[data-wb-theme="portal"] .wb-portal .card-top .wb-num {
  flex: none;
  font-size: var(--wb-text-subsection);
  font-weight: var(--wb-weight-semibold);
  letter-spacing: var(--wb-tracking-figure);
  color: var(--wb-color-ink);
}

[data-wb-theme="portal"] .wb-portal .card-sub {
  display: flex;
  align-items: baseline;
  gap: var(--wb-space-5);
  margin-top: var(--wb-space-2);
  font-size: var(--wb-text-min);
  color: var(--wb-color-soft);
}

[data-wb-theme="portal"] .wb-portal .card-sub > span:last-child {
  margin-left: auto;
}

[data-wb-theme="portal"] .wb-portal .card-figs {
  display: block;
  gap: 0;
  margin-top: var(--wb-space-6);
  border-top: var(--wb-line-rule) solid var(--wb-color-rule);
}

[data-wb-theme="portal"] .wb-portal .card-fig {
  display: flex;
  align-items: baseline;
  gap: var(--wb-space-5);
  padding: var(--wb-space-4) 0;
  border-top: 0;
  border-bottom: var(--wb-line-rule) solid var(--wb-color-rule);
}

[data-wb-theme="portal"] .wb-portal .card-fig:last-child {
  border-bottom: 0;
}

[data-wb-theme="portal"] .wb-portal .card-fig > span:first-child {
  font-size: var(--wb-text-min);
  color: var(--wb-color-soft);
}

[data-wb-theme="portal"] .wb-portal .card-fig > span:last-child {
  margin-left: auto;
  text-align: right;
  font-size: var(--wb-text-caption);
  color: var(--wb-color-ink);
  font-variant-numeric: tabular-nums;
}

[data-wb-theme="portal"] .wb-portal .card-fig .wb-kicker {
  font-size: var(--wb-text-min);
  letter-spacing: var(--wb-tracking-kicker);
  text-transform: uppercase;
  color: var(--wb-color-soft);
}

/* ---- filters — The filters: underlined tabs, the second set smaller and right-aligned. ---- */

[data-wb-theme="portal"] .wb-portal .wb-portal__filters {
  align-items: baseline;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle-group {
  display: flex;
  align-items: baseline;
  gap: var(--wb-space-7);
  width: auto;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle-group > .wb-toggle {
  margin-left: 0;
  border-radius: 0;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle {
  padding: var(--wb-space-5) 0;
  min-height: var(--wb-space-12);
  border: 0;
  border-bottom: calc(var(--wb-line-rule) * 2) solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--wb-color-soft);
  font-size: var(--wb-text-section);
  font-weight: var(--wb-weight-regular);
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle:hover {
  color: var(--wb-color-accent);
  background: transparent;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle[aria-pressed="true"] {
  color: var(--wb-color-heading);
  background: transparent;
  font-weight: var(--wb-weight-bold);
  border-bottom-color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle .count {
  margin-left: var(--wb-space-2);
  font-size: var(--wb-text-min);
  color: var(--wb-color-soft);
  font-variant-numeric: tabular-nums;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters .wb-toggle[aria-pressed="true"] .count {
  color: var(--wb-color-accent);
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters > :last-child {
  margin-left: auto;
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters > :last-child .wb-toggle {
  font-size: calc(var(--wb-text-section) * 0.7);
  font-weight: var(--wb-weight-regular);
}

[data-wb-theme="portal"] .wb-portal .wb-portal__filters > :last-child .wb-toggle[aria-pressed="true"] {
  font-weight: var(--wb-weight-bold);
}
