/*
 * @file App.css
 * @copyright X Laboratory B.V - All Rights Reserved
 * Unauthorized copying of this file, via any medium is strictly prohibited
 * Proprietary and confidential
 * @author Andre Schiele <andre@deltaxlab.com>
 * @author Claude code
 * @date 2026/08/27
 * @brief Application layout and theme.
 *
 * The palette and typography are taken from www.deltaxlab.com: Open Sans with
 * very light, tightly tracked headings, brand blue #090093 and cyan #00BFFF on
 * navy #001130. The kiosk inverts the website's light ground to a deep-space
 * dark ground, which suits a large unattended display and matches the
 * company's space-robotics origin.
 *
 * Every colour, size and radius is a custom property, so rebranding means
 * editing this block alone.
 */

/* Open Sans is self-hosted: the design specification forbids requests to
   public font CDNs at runtime. See assets/fonts/LICENSE.txt. */
@font-face {
  font-family: "Open Sans";
  src: url("../assets/fonts/OpenSans-Variable-Latin.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Open Sans";
  src: url("../assets/fonts/OpenSans-Variable-LatinExt.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
                 U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
                 U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --font-family: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* The corporate lockup sets "Laboratory" in Helvetica Light. Where Helvetica
     exists it is used; elsewhere the fallback is Open Sans Light rather than
     Arial, because Arial has no light weight and a regular Arial next to the
     orbit-X reads much heavier than the original. */
  --font-wordmark: "Helvetica Neue", Helvetica, "Open Sans", Arial, sans-serif;

  /* Brand, from deltaxlab.com --------------------------------------------- */
  --brand-blue: #090093;
  --brand-cyan: #00bfff;
  --brand-navy: #001130;

  /* Ground.
     The reference is a Zumthor interior: one material, matte, lit rather than
     coloured. Surfaces are neutral graphite; the brand blue survives only as a
     deep field far below the fold, and cyan is reserved for signal - state,
     focus, live values. Nothing else is allowed to be saturated. */
  --background: #0a0b0c;
  --surface: rgba(255, 255, 255, 0.026);
  --surface-raised: rgba(255, 255, 255, 0.052);
  --border: rgba(255, 255, 255, 0.10);
  --border-strong: rgba(255, 255, 255, 0.26);

  --foreground: #e8edf2;
  --foreground-muted: #8a949e;
  --accent: var(--brand-cyan);
  --accent-contrast: #05121a;
  --signal: var(--brand-cyan);
  --focus: var(--brand-cyan);
  /* The only warm colour in the palette, and deliberately so: an error should
     not read as part of the design language. */
  --danger: #d8695f;

  /* Transition surfaces, consumed by Motion.css --------------------------- */
  --veil: rgba(255, 255, 255, 0.05);
  --veil-solid: #0a0b0c;

  /* Rhythm.
     Generous and regular. The vertical step is the same everywhere so the
     page reads as a small number of large blocks. */
  --tile-radius: 0px;
  --content-max-width: 78rem;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.75rem;
  --space-4: 3.5rem;
  --space-5: 6rem;
  --touch-target-min: 44px;

  --transition-fast: 180ms steps(4, end);
}

/*
 * Fluid typography: the root size scales continuously with the viewport
 * instead of stepping at breakpoints, so every rem-based size in the
 * stylesheet - text, spacing, tiles - scales with it.
 *
 * At 768 px this resolves to about 17 px, at 1440 px to 19 px, at 2560 px to
 * 23 px and at 4K to 28 px. That is what stops a page of prose reading as a
 * narrow column on a large display: the measure stays at a readable number of
 * characters, and the characters themselves get larger.
 */
html {
  font-size: clamp(16px, calc(14px + 0.36vw), 28px);
}

body {
  overflow-x: hidden;
  font-family: var(--font-family);
  background: var(--background);
  color: var(--foreground);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

/* The deep-space ground: a navy horizon glow, a brand-blue counter glow, and
   a faint technical grid. Fixed and non-interactive, so it costs nothing on
   navigation. */
#app::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1400px 900px at 50% -20%, rgba(255, 255, 255, 0.045), transparent 60%),
    radial-gradient(1100px 900px at 12% 115%, rgba(9, 0, 147, 0.30), transparent 62%),
    linear-gradient(180deg, #101215 0%, var(--background) 48%);
}

/* Two fixed layers: static bit noise, and a survey grid that fades out at the
   edges. Both are completely inert - the moving points are their own layer, see
   #speckles in Motion.css. */
#app::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.42;
  background-image:
    url("../assets/images/Grain.svg"),
    linear-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1px);
  background-size: 140px 140px, 100% 104px;
  mask-image: radial-gradient(circle at 50% 26%, #000 12%, transparent 82%);
}

#app {
  display: grid;
  /* header, content, progress rail, status bar */
  grid-template-rows: auto 1fr auto auto;
  height: 100%;
  position: relative;
}

#app.is-cursor-hidden,
#app.is-cursor-hidden * {
  cursor: none;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 20;
  padding: var(--space-1) var(--space-2);
  background: var(--accent);
  color: var(--accent-contrast);
}

.skip-link:focus {
  left: var(--space-2);
  top: var(--space-2);
}

/* ---------------------------------------------------------------- top bar */

#top-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  min-height: 72px;
  border-bottom: 1px solid var(--border);
  background: rgba(10, 11, 12, 0.62);
  backdrop-filter: blur(14px);
}

.top-bar__nav {
  display: flex;
  gap: var(--space-1);
  flex: 0 0 auto;
}

.top-bar__nav--end {
  margin-left: auto;
}

.control {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  min-height: var(--touch-target-min);
  padding: 0 var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
  background: var(--surface);
  color: var(--foreground-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: color var(--transition-fast), border-color var(--transition-fast),
              background var(--transition-fast);
}

.control:hover:not(:disabled) {
  color: var(--foreground);
  border-color: var(--border-strong);
  background: var(--surface-raised);
}

.control:disabled {
  opacity: 0.28;
  cursor: default;
}

.control[hidden] {
  display: none;
}

/* ------------------------------------------------------------ breadcrumbs */

.breadcrumbs {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

/* min-width: 0 on the list and its items defeats the flexbox default of
   min-width: auto, which otherwise stops them shrinking below their content
   width and pushes the whole chrome row wider than the viewport. */
.breadcrumbs__list {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  min-width: 0;
  gap: 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--foreground-muted);
  overflow: hidden;
}

.breadcrumbs__item {
  display: flex;
  align-items: center;
  white-space: nowrap;
  min-width: 0;
}

.breadcrumbs__item + .breadcrumbs__item::before {
  content: "";
  width: 18px;
  height: 1px;
  margin-right: 0.7rem;
  background: var(--border);
}

.breadcrumbs__link {
  color: var(--foreground-muted);
  padding: 0.35em 0;
  transition: color var(--transition-fast);
}

.breadcrumbs__link:hover {
  color: var(--accent);
}

.breadcrumbs__current {
  color: var(--foreground);
  overflow: hidden;
  text-overflow: ellipsis;
}

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

#page {
  position: relative;
  overflow-y: auto;
  /* The heading decodes through block glyphs that fall back to a different
     font with wider metrics, which can momentarily exceed the line box. A
     kiosk must never show a horizontal scrollbar, so clip rather than scroll. */
  overflow-x: hidden;
  padding: var(--space-5) var(--space-4);
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.page__node {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.page__error {
  color: var(--danger);
}

.node__header {
  margin-bottom: var(--space-5);
  /* Full width by design: product names such as "X-Control Guidance System"
     were being wrapped and clipped by an earlier max-width here. Body prose is
     still line-length limited further down, where it belongs. */
  max-width: none;
  width: 100%;
}

/* The website sets headings very light and tightly tracked. Kept, and pushed
   further for the large display. */
.node__title {
  font-size: clamp(2.4rem, 4.6vw, 4.8rem);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--foreground);
  width: 100%;
  max-width: none;
  overflow-wrap: break-word;
  hyphens: none;
}

/* The gradient headline is applied only where background-clip:text is
   supported. Without the guard, an unsupporting browser would paint the title
   in transparent and the page would look empty - unacceptable on an
   unattended display. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .node__title {
    background: linear-gradient(100deg, #f4f8fb 26%, #9fb2c2 105%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

  /*
   * ...except while the decode is running.
   *
   * The decode splits the heading into per-character cells, and a cell is
   * positioned, which puts it in its own painting context: the gradient the
   * heading clips to its own text does not reach through it. The characters
   * are painted in transparent and the gradient never arrives, so every
   * letter the band has not reached is invisible - the band appears to run on
   * ahead of a heading that only turns up when the decode ends.
   *
   * For the second or so that the band is travelling the heading is painted
   * in a flat colour instead. It is the light end of the same gradient, so
   * the hand-back at the end is not visible.
   */
  #app.is-decoding .node__title {
    background: none;
    color: var(--foreground);
    -webkit-text-fill-color: var(--foreground);
  }
}

.node__subtitle {
  margin-top: var(--space-3);
  font-size: clamp(1rem, 1.35vw, 1.35rem);
  font-weight: 300;
  line-height: 1.45;
  color: var(--foreground-muted);
  max-width: none;
}

/* A hairline rule under the header, fading out — a quiet technical marker. */
/* The wordmark needs a little more air than a text heading: its orbit hangs
   below the baseline and would otherwise crowd the line beneath it. */
.node__title--wordmark + .node__subtitle {
  margin-top: calc(var(--space-3) + var(--space-2));
}

.node__header::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: var(--space-4);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--signal) 0, var(--border) 22%, transparent 92%);
}

/* ----------------------------------------------------------------- tiles */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  gap: var(--space-3);
}

.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  min-height: 210px;
  text-align: left;
  text-decoration: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--transition-fast), border-color var(--transition-fast),
              background var(--transition-fast), box-shadow var(--transition-fast);
}

/* A cyan corner accent that grows on hover: reads as "active" without moving
   any layout. */
.tile::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--signal), transparent);
  transition: width var(--transition-fast);
}

.tile:hover,
.tile:focus-visible {
  background: var(--surface-raised);
  border-color: var(--border-strong);
  transform: translateY(-2px);
}

.tile:hover::after,
.tile:focus-visible::after {
  width: 100%;
}

/*
 * A page that exists but is not open yet looks exactly like every other tile.
 * Nothing is dimmed and nothing is greyed: the "In development" badge says
 * what is going on, and the tile simply does not open. The button carries
 * "disabled", so the click does nothing and it is out of the tab order, but
 * it keeps the same surface, border and hover as its neighbours.
 */
.tile--blocked {
  cursor: default;
}

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

/* The media band spans the tile edge to edge. Nothing may sit between it and
   the tile border: the tile's own padding lives on .tile__body instead. */
.tile__media {
  position: relative;
  display: block;
  width: 100%;
  margin: 0;
  aspect-ratio: 16 / 9;
  background: var(--brand-navy);
  overflow: hidden;
  line-height: 0;
}

/* Absolute inset rather than width/height 100 %: a fractional grid track gives
   the media box a fractional height, and a percentage-sized child can round to
   a hairline short of it, leaving a line of tile background along one edge. */
.tile__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0.72;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.tile:hover .tile__media img {
  opacity: 1;
  transform: scale(1.03);
}

.tile__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
}

/* Selection boxes lead with the capitalised name, then the description on a
   sub-line, then the vessel markers. */
.tile__title {
  font-size: 1.3rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.25;
  overflow-wrap: break-word;
}

.tile__summary {
  color: var(--foreground-muted);
  font-size: 0.98rem;
  font-weight: 300;
  line-height: 1.6;
}

.tile__badge {
  margin-top: var(--space-1);
  align-self: flex-start;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 0.3em 0.8em;
}

/* --------------------------------------------------------------- content */

.content__split {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}

@media (max-width: 1000px) {
  .content__split {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }
}

.prose,
.content > .prose__paragraph,
.content > .prose__bullets {
  /* A measure in ch, so it tracks the fluid root size: the column grows with
     the type rather than staying a fixed pixel width on a large display. */
  max-width: 72ch;
  font-size: clamp(1.02rem, 0.95rem + 0.2vw, 1.25rem);
  font-weight: 300;
  line-height: 1.72;
  color: #ccd8e4;
}

.prose p + p,
.content > * + * {
  margin-top: var(--space-3);
}

.prose__bullets {
  list-style: none;
  padding-left: 0;
}

.prose__bullets li {
  position: relative;
  padding-left: 1.9em;
}

.prose__bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 12px;
  height: 1px;
  background: var(--signal);
}

.prose__bullets li + li {
  margin-top: 0.7em;
}

/*
 * The frame around a picture.
 *
 * It used to be painted into the placeholder artwork, which meant a real
 * photograph arrived without it. It is drawn here instead, so every picture
 * gets the same treatment whatever the file behind it is: a hairline rule set
 * in from the edge, and a signal-coloured bracket at two opposing corners.
 *
 * Both are pseudo-elements over the image and are purely decorative, so they
 * never intercept a touch and never affect layout.
 */
.figure {
  /* A length, not a percentage: a percentage inset resolves against width for
     the sides and height for the top and bottom, which makes the frame thinner
     top-to-bottom on a wide picture. A rem is even on all four sides and still
     scales with the fluid type. */
  --frame-inset: 1.4rem;
  --frame-corner: 2.4rem;
  /* How far the frame's lower edge drops below the picture. This is the part
     that reads as deliberate - the frame is not simply a border. */
  --frame-protrude: 1rem;
}

/*
 * The picture's own box. The frame is drawn on this rather than on the figure,
 * so its geometry is fixed relative to the picture: inset at the top and
 * sides, dropping below the lower edge by --frame-protrude. Drawn on the
 * figure it was sized to the picture plus the caption, so its lower edge
 * landed wherever the caption happened to be and cut through it.
 */
.figure__media {
  position: relative;
  display: block;
  line-height: 0;
}

/* The frame is drawn on the picture's own box, so it bounds the picture and
   never the caption below it. */
.figure__media {
  position: relative;
  display: block;
  line-height: 0;
}

.figure img {
  width: 100%;
  display: block;
  border-radius: var(--tile-radius);
  border: 1px solid var(--border);
}

/* The hairline: inside the picture at the top and sides, below it at the
   bottom. */
.figure__media::before {
  content: "";
  position: absolute;
  top: var(--frame-inset);
  right: var(--frame-inset);
  left: var(--frame-inset);
  bottom: calc(var(--frame-protrude) * -1);
  border: 1px solid rgba(255, 255, 255, 0.22);
  pointer-events: none;
}

/* Two corner brackets, top-left and bottom-right, drawn as four bars. */
.figure__media::after {
  content: "";
  position: absolute;
  top: var(--frame-inset);
  right: var(--frame-inset);
  left: var(--frame-inset);
  bottom: calc(var(--frame-protrude) * -1);
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    linear-gradient(var(--signal), var(--signal)),
    linear-gradient(var(--signal), var(--signal)),
    linear-gradient(var(--signal), var(--signal)),
    linear-gradient(var(--signal), var(--signal));
  background-size:
    var(--frame-corner) 2px,
    2px var(--frame-corner),
    var(--frame-corner) 2px,
    2px var(--frame-corner);
  background-position:
    left top,
    left top,
    right bottom,
    right bottom;
}



/*
 * The caption starts below the frame's protruding lower edge, always. The
 * clearance is measured from the protrusion rather than guessed at, so a
 * caption that wraps to two lines behaves no differently from one that does
 * not, at any width.
 */
.figure__caption {
  margin-top: calc(var(--frame-protrude) + var(--space-2));
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--foreground-muted);
  padding-left: var(--space-2);
  border-left: 1px solid var(--border-strong);
}

/*
 * A stats bar states measured facts - accuracies, capacities, counts. Three
 * templates, chosen per block with "template" in the configuration:
 *
 *   bar     hairline grid, cells butted together        (default)
 *   cards   separated boxes, for two or three headline figures
 *   inline  compact rows, for putting figures under a paragraph
 *
 * The column count is either automatic or fixed by "columns".
 */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}

.stats--bar {
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
  overflow: hidden;
}

.stats__item {
  display: flex;
  flex-direction: column;
  gap: 0.4em;
  padding: var(--space-3);
}

.stats--bar .stats__item {
  background: rgba(255, 255, 255, 0.018);
}

/* Separated boxes: more air, each figure reads as its own claim. */
.stats--cards {
  gap: var(--space-2);
}

.stats--cards .stats__item {
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.022);
}

/* Compact: value and label on one line, for sitting directly under prose. */
.stats--inline {
  gap: 0 var(--space-4);
  border: 0;
  background: none;
}

.stats--inline .stats__item {
  flex-direction: row;
  align-items: baseline;
  gap: 0.7em;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border);
  background: none;
}

.stats--inline .stats__value {
  font-size: clamp(1.1rem, 1.4vw, 1.5rem);
  flex: 0 0 auto;
}

.stats--inline .stats__label {
  font-size: 0.68rem;
}

.stats__value {
  font-size: clamp(1.7rem, 2.7vw, 2.6rem);
  font-weight: 300;
  letter-spacing: -0.03em;
  color: var(--signal);
  line-height: 1;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

.stats__label {
  color: var(--foreground-muted);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* ----------------------------------------------------------------- media */

.media--image .figure img,
.media--video video {
  width: 100%;
  max-height: 72vh;
  object-fit: contain;
  background: #000;
  border-radius: var(--tile-radius);
}

.link-page {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.button {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding: 0 var(--space-3);
  border-radius: var(--tile-radius);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: box-shadow var(--transition-fast), transform var(--transition-fast);
}

.button--primary {
  background: transparent;
  border: 1px solid var(--signal);
  color: var(--signal);
}

.button--primary:hover {
  transform: translateY(-2px);
}

.link-page__href {
  color: var(--foreground-muted);
  font-size: 0.82rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  word-break: break-all;
}

/* ------------------------------------------------------------ status bar */

#status-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 46px;
  padding: 0 var(--space-4);
  border-top: 1px solid var(--border);
  background: rgba(10, 11, 12, 0.62);
  backdrop-filter: blur(14px);
  color: var(--foreground-muted);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.status-bar__mode {
  margin-left: auto;
  text-align: right;
}

/*
 * The colophon sits between the two, centred in whatever room is left. It
 * opts out of the bar's uppercasing: an email address set in capitals with
 * letterspacing stops looking like an address.
 */
.status-bar__colophon {
  display: flex;
  align-items: center;
  /* Three items now - notice, certification, address - so they have to be
     allowed to fall onto another line. Without this the row simply grows past
     the viewport and the whole page scrolls sideways. */
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1) var(--space-2);
  margin-inline: auto;
  padding-inline: var(--space-2);
  text-transform: none;
  letter-spacing: 0.04em;
  opacity: 0.72;
}

/* The certification mark sits inline with the colophon, at the height of the
   type around it. */
.status-bar__cert {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  white-space: nowrap;
  flex: 0 0 auto;
}

.cert-mark {
  display: block;
  height: 1.5em;
  /* Two things are needed here. The width has to be stated, because an inline
     SVG with width:auto resolves to zero in some engines rather than taking it
     from the viewBox. And it must not shrink: as a flex item in a crowded
     footer it is squeezed to nothing long before the text is. */
  width: calc(1.5em * 124 / 30);
  aspect-ratio: 124 / 30;
  flex: 0 0 auto;
}

.status-bar__colophon a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
}

.status-bar__colophon a:hover,
.status-bar__colophon a:focus-visible {
  color: var(--foreground);
}

/* On a narrow screen the bar has no room for three things side by side. The
   colophon takes its own line rather than disappearing - it is a legal notice
   on the public site, so it has to survive a phone. */
@media (max-width: 900px) {
  #status-bar {
    flex-wrap: wrap;
    padding-block: var(--space-1);
  }

  .status-bar__colophon {
    order: 3;
    flex-basis: 100%;
    justify-content: center;
    margin-inline: 0;
    white-space: normal;
    text-align: center;
  }
}

/*
 * The progress rail. Its padding and max-width mirror #page exactly, so the
 * track starts on the same vertical as the rule under every heading rather
 * than floating in the middle of the footer.
 */
.progress-rail {
  padding: var(--space-2) var(--space-4) 0;
}

.progress-rail[hidden] {
  display: none;
}

/*
 * The track is the same hairline as .node__header::after - signal, fading
 * through the border colour and out - drawn down the centre of a slightly
 * taller box so the travelling light has room to sit on it.
 */
.progress {
  position: relative;
  max-width: var(--content-max-width);
  margin: 0 auto;
  height: 3px;
  background-image: linear-gradient(90deg, var(--signal) 0, var(--border) 22%, transparent 92%);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 center;
}

/*
 * The travelling light. Elliptical, so it is thin at both ends and slightly
 * fuller in the middle, and lit from the leading edge backwards so the tail
 * dims behind it. Renderer.setProgress owns its width and offset.
 */
.progress__bar {
  position: absolute;
  top: 50%;
  left: 0;
  width: 0;
  height: 3px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: linear-gradient(
    90deg,
    rgba(238, 244, 250, 0) 0%,
    rgba(238, 244, 250, 0.18) 34%,
    rgba(238, 244, 250, 0.95) 74%,
    var(--signal) 100%
  );
  /* Matches the 200 ms progress tick, so the light appears to glide rather
     than step between updates. */
  transition: width 200ms linear, left 200ms linear;
}

/* --------------------------------------------------------------- overlay */

.overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(900px 600px at 50% 40%, rgba(9, 0, 147, 0.34), transparent 70%),
    rgba(0, 6, 15, 0.93);
  backdrop-filter: blur(6px);
  animation: page-in 600ms both;
}

.overlay[hidden] {
  display: none;
}

.overlay__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
  padding: var(--space-4);
}

.overlay__logo {
  width: min(46vw, 520px);
  height: auto;
}

.overlay__title {
  font-size: clamp(1.8rem, 4.4vw, 4rem);
  font-weight: 300;
  letter-spacing: -0.035em;
}

.overlay__hint {
  font-size: clamp(0.85rem, 1.3vw, 1.15rem);
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--brand-cyan);
  animation: pulse 2.8s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 1; }
}

/* ------------------------------------------------------- boot and errors */

.boot,
.fatal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand-navy);
  color: var(--foreground);
  padding: var(--space-4);
  z-index: 50;
}

.boot[hidden],
.fatal[hidden] {
  display: none;
}

.boot__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  color: var(--foreground-muted);
  font-size: 0.78rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
}

.boot__spinner {
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-top-color: var(--brand-cyan);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
}

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

.fatal__inner {
  max-width: 940px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--danger);
  border-radius: var(--tile-radius);
  padding: var(--space-4);
}

.fatal__kicker {
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--danger);
}

.fatal__title {
  margin-top: var(--space-2);
  font-size: 2.2rem;
  font-weight: 300;
  letter-spacing: -0.03em;
}

.fatal__text {
  margin-top: var(--space-2);
  color: var(--foreground-muted);
  line-height: 1.65;
  font-weight: 300;
}

.fatal__list {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--foreground);
}

.fatal__list[hidden] {
  display: none;
}

.fatal__list li {
  padding: var(--space-2);
  background: rgba(0, 0, 0, 0.35);
  border-left: 2px solid var(--danger);
  word-break: break-word;
}

.fatal__button {
  margin-top: var(--space-3);
  min-height: var(--touch-target-min);
  padding: 0 var(--space-3);
  border-radius: var(--tile-radius);
  background: linear-gradient(100deg, var(--brand-blue), var(--brand-cyan));
  color: #ffffff;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.82rem;
}

/* The noscript fallback cannot rely on JavaScript to unhide itself, and an
   inline style attribute would be blocked by the deployed Content-Security-
   Policy, so it gets its own class. */
.fatal--noscript {
  display: flex;
}


/* ------------------------------------------------- vessel type markers */

.node__meta,
.tile__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.node__meta {
  margin-top: var(--space-3);
}

.tile__meta {
  margin-top: auto;
  padding-top: var(--space-2);
}

.vessels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  list-style: none;
  padding: 0;
}

.vessels__item {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.35em 0.8em;
  border: 1px solid var(--border);
  border-radius: 2px;
  color: var(--brand-cyan);
  background: rgba(0, 191, 255, 0.06);
}

.vessels__label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vessels__item svg {
  flex: 0 0 auto;
}

.vessels--tile .vessels__item {
  padding: 0.3em 0.65em;
}

.vessels--tile .vessels__item svg {
  width: 18px;
  height: 18px;
}


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

.table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--tile-radius);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.98rem;
  font-weight: 300;
  text-align: left;
}

.table thead th {
  padding: var(--space-2) var(--space-3);
  background: rgba(0, 191, 255, 0.07);
  border-bottom: 1px solid var(--border-strong);
  color: var(--brand-cyan);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.table tbody th,
.table tbody td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  color: #ccd8e4;
  vertical-align: top;
}

.table tbody th {
  font-weight: 600;
  color: var(--foreground);
  white-space: nowrap;
}

.table tbody tr:last-child th,
.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr:hover th,
.table tbody tr:hover td {
  background: var(--surface);
}

/* ------------------------------------------------- vessel markers, status */

/* Which vessel a solution applies to is the single most useful thing to know
   at a glance, so the marker sits directly under the heading and inside the
   tile, and is the only place besides live values where the signal colour is
   allowed. */
.node__meta,
.tile__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.node__meta {
  margin-top: var(--space-3);
}

.tile__meta {
  margin-top: var(--space-2);
  gap: var(--space-1) var(--space-2);
}

.vessels {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  list-style: none;
  padding: 0;
}

.vessels__item {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.34em 0.8em 0.34em 0.62em;
  border: 1px solid rgba(0, 191, 255, 0.30);
  color: var(--signal);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

.vessels__item svg {
  width: 1.5em;
  height: 1.5em;
  flex: 0 0 auto;
}

.node__meta .vessels__item {
  font-size: 0.72rem;
  padding: 0.45em 0.95em 0.45em 0.75em;
}

/* Lifecycle status is a caveat, not a category, so it stays neutral. */
.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.4em 0.8em;
  border: 1px dashed var(--border-strong);
  color: var(--foreground-muted);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ----------------------------------------------------------------- table */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1rem;
  font-weight: 300;
}

.data-table th,
.data-table td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.data-table thead th {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--foreground-muted);
  background: rgba(255, 255, 255, 0.022);
  white-space: nowrap;
}

.data-table tbody th {
  font-weight: 400;
  color: var(--foreground);
}

.data-table tbody tr:last-child th,
.data-table tbody tr:last-child td {
  border-bottom: 0;
}

.data-table tbody tr:hover {
  background: rgba(255, 255, 255, 0.022);
}

/*
 * A band dividing one class of thing from the next: products, then projects,
 * then R&D, and so on. It is set like a column header rather than like a row,
 * because that is what it is - a heading for everything under it.
 */
.data-table__section th {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--signal);
  background: rgba(255, 255, 255, 0.03);
  border-top: 1px solid var(--border-strong);
  padding-top: var(--space-3);
  padding-bottom: var(--space-1);
  white-space: nowrap;
}

/* The first band needs no rule above it: the table head is already there. */
.data-table tbody tr:first-child.data-table__section th {
  border-top: 0;
  padding-top: var(--space-2);
}

.data-table__section:hover {
  background: none;
}

/*
 * A sub-project belongs under the project above it: stepped in, set smaller,
 * and quieter. The step is on the first column only - the owner, architect
 * and status stay in their columns so they can still be read down the page.
 */
.data-table__row--sub th:first-child {
  padding-left: var(--space-5);
  font-size: 0.86em;
  color: var(--foreground-muted);
}

.data-table__row--sub td {
  font-size: 0.86em;
  color: var(--foreground-muted);
}

/* An unfilled ownership cell must look unfilled. */
.data-table__todo {
  color: var(--foreground-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  opacity: 0.55;
}

/* -------------------------------------------------------- screen readers */

/* Navigation is announced once, from a dedicated live region, so the heading
   decode animation is never read out character by character. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}


/* -------------------------------------------------------------- wordmark */

/*
 * The company wordmark: the orbit-X as vector artwork, the rest in Helvetica
 * Light, at the two sizes named in configuration. Sizes arrive as inline
 * values from Renderer.renderWordmark, so the proportion is set in
 * config/navigation.json, not here.
 *
 * No corporate font is involved - the X was outlined from the original, see
 * assets/fonts/README.txt.
 */
.node__title--wordmark {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.22em;
  font-size: 1rem;          /* the children carry the real sizes, in em */
  letter-spacing: normal;
  line-height: 1;
  /* The X and the word are one lockup and never break apart - that is the
     mark, not a line of text. On a narrow screen the whole thing is scaled
     down instead, below. */
  flex-wrap: nowrap;
}

/*
 * Below the tablet breakpoint the lockup no longer fits at full size, so it
 * shrinks as a unit. The children are sized in em, so one font-size here
 * scales the X, the word and the gap between them together, and the
 * configured proportion is untouched.
 */
@media (max-width: 780px) {
  .node__title--wordmark {
    font-size: min(1rem, 2.4vw);
  }
}

/*
 * The glitch bands: three copies of the mark stacked on the real one, each
 * clipped to a horizontal strip. They carry the page's own ground colour, so
 * while a band is showing it covers the strip of the original underneath it
 * and puts its own, displaced copy there instead - the strip jumps sideways
 * rather than doubling.
 *
 * Invisible and completely idle until Wordmark.js puts is-zapping on the
 * heading, which it does for a few hundred milliseconds at a time.
 */
.wordmark__band {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  /* Only as wide as the mark. The heading is a full-width flex row, so an
     inset of 0 would paint the band's ground right across the starfield. */
  width: max-content;
  max-width: 100%;
  display: flex;
  align-items: baseline;
  gap: 0.22em;
  /* No ground of its own. The heading's box is taller than the glyphs, so a
     band filled with the page colour painted a rectangle over the starfield
     wherever it clipped empty space. The bands are displaced copies over the
     real mark, and the real mark flickers under them. */
  pointer-events: none;
  opacity: 0;
}

.wordmark__band--1 { clip-path: inset(4% 0 73% 0); }
.wordmark__band--2 { clip-path: inset(36% 0 45% 0); }
.wordmark__band--3 { clip-path: inset(67% 0 12% 0); }

.wordmark__symbol {
  font-weight: 400;
  line-height: 0.9;
  letter-spacing: normal;
}

/*
 * The orbit-X as artwork. Its height comes from wordmark.symbolSize, and the
 * glyph is square, so no width is needed.
 *
 * The orbit hangs below the baseline: in the original outline 22.8 % of the
 * block sits under it. An image aligned on a flex baseline rests its bottom
 * edge on that baseline, so it is pushed back down by exactly that fraction to
 * reproduce the lockup.
 */
.wordmark__symbol--image {
  width: auto;
  transform: translateY(22.8%);
  flex: 0 0 auto;
}

.wordmark__text {
  font-family: var(--font-wordmark);
  font-weight: 300;
  letter-spacing: -0.005em;
  line-height: 1;
}

/* The wordmark is a logo, so it is flat brand white rather than carrying the
   heading gradient. */
.node__title--wordmark,
.node__title--wordmark .wordmark__text {
  background: none;
  -webkit-text-fill-color: currentColor;
  color: #ffffff;
}

/* ------------------------------------------------------- the page raster */

/*
 * An invisible four-column raster. Blocks without an "area" flow into the next
 * free cells, so a page can mix placed and unplaced content freely.
 *
 * The raster collapses to two columns on a laptop and one on a tablet: a
 * quarter-width column of prose is unreadable below roughly 1200 px, and a
 * kiosk page that needs a horizontal scrollbar to be read is worse than one
 * that stacks.
 */
.content--grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4) var(--space-3);
  align-items: start;
}

.content--grid > * {
  margin-top: 0;              /* the grid gap owns the spacing */
  min-width: 0;
}

.content--grid .prose__paragraph,
.content--grid .prose__bullets {
  max-width: none;
}

.content--grid .figure img,
.content--grid .media--image .figure img {
  max-height: none;
}

@media (max-width: 1200px) {
  .content--grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* A placed block keeps its relative width but is re-flowed, because its
     original column numbers no longer exist. */
  .content--grid > * {
    grid-column: auto / span 1 !important;
    grid-row: auto !important;
  }
}

@media (max-width: 780px) {
  .content--grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------- motion switch */

/* "motion": { "enabled": false } in app.json removes the choreography without
   touching the stylesheet. */
#app[data-motion="off"] .page__node,
#app[data-motion="off"] .tiles__item,
#app[data-motion="off"] .node__header::after {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}

#app[data-motion="off"] #page::before,
#app[data-motion="off"] #page::after {
  animation: none !important;
  opacity: 0 !important;
}

/* ------------------------------------------------- the rotating strapline */

/*
 * The home strapline: a fixed opening and a phrase that changes on a timer.
 * The rotating half is given a stable minimum width so the line does not jump
 * as phrases of different lengths replace each other, and it is an
 * inline-block so the decode cells inside it lay out predictably.
 */
.subtitle__prefix {
  color: var(--foreground-muted);
}

.subtitle__rotating {
  display: inline-block;
  color: var(--foreground);
}

/*
 * Emphasis inside a rotating phrase - "Deep" in DeepTech. The subtitle is set
 * light, so 600 rather than 700: at this size a full bold beside weight 300
 * reads as two different typefaces rather than one word with a stressed
 * opening.
 */
.subtitle__strong {
  font-weight: 600;
  color: #ffffff;
}

/* The decode inside the strapline is smaller than in a heading, so the
   pictogram stamps are scaled to match the text around them. */
.node__subtitle .decode__glyph {
  width: 0.9em;
  height: 0.9em;
}

.node__subtitle .decode__noise {
  font-size: 0.9em;
}

/* ------------------------------------------------ headings inside a page */

/*
 * A section heading within a long page. The page title is the h1, so these
 * start at h2. Set in the same light weight as the title but much smaller, and
 * given a short rule above so a long page reads as a sequence of sections
 * rather than a wall.
 */
.prose__heading {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  font-size: clamp(1.4rem, 2.1vw, 2rem);
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--foreground);
  max-width: 34ch;
}

.content > .prose__heading:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

h3.prose__heading {
  font-size: clamp(1.1rem, 1.5vw, 1.35rem);
  margin-top: var(--space-4);
  padding-top: 0;
  border-top: 0;
}

/* Emphasis inside body text. The prose is set light, so 600 rather than a full
   bold: at this size 700 beside 300 reads as a different typeface. */
.rich__strong {
  font-weight: 600;
  color: var(--foreground);
}

/* --------------------------------------------------- introduction to a menu */

/* A menu may open with a few sentences before its tiles. */
/*
 * No measure cap: the opening line is a single statement and should stay on
 * one line wherever the screen allows it. It still wraps naturally when the
 * viewport is genuinely too narrow.
 */
.menu__intro {
  margin-bottom: var(--space-5);
  max-width: none;
}

/* Whatever a menu carries below its tiles, set off from them by the same
   interval that separates the opening text from the grid above. */
.menu__outro {
  margin-top: var(--space-5);
  max-width: none;
}

/* The opening text reads as part of the heading, so it sits close under the
   rule rather than at the full page-header distance. */
.page__node[data-has-intro] .node__header {
  margin-bottom: var(--space-3);
}

.menu__intro .prose__paragraph + .prose__paragraph {
  margin-top: var(--space-2);
}

/* On the home page the opening line sits directly under the rule and carries
   the eye into the tiles, so it is set a little larger than body prose. */
#app .menu__intro .prose__paragraph {
  font-size: clamp(1.05rem, 1.25vw, 1.4rem);
  line-height: 1.6;
  color: var(--foreground-muted);
}

#app .menu__intro .rich__strong {
  color: var(--foreground);
}

/* ------------------------------------------------------------ highlight */

/*
 * A highlight bar: one short statement set apart from the prose. Used to carry
 * the line that matters most on a product or solution page - a capability, a
 * proven result, a figure.
 *
 * Deliberately quiet: a rule in the signal colour and a change of size, no
 * panel and no background. On a page that is otherwise plain it reads as
 * emphasis rather than as a widget.
 */
.highlight {
  margin: var(--space-4) 0;
  padding: var(--space-2) 0 var(--space-2) var(--space-3);
  border-left: 2px solid var(--signal);
  max-width: 56ch;
}

.highlight__text {
  font-size: clamp(1.15rem, 1rem + 0.5vw, 1.7rem);
  font-weight: 300;
  line-height: 1.4;
  letter-spacing: -0.015em;
  color: var(--foreground);
}

.highlight__label {
  display: block;
  margin-bottom: 0.55em;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal);
}

/* In the four-column raster a highlight usually spans more than it is given by
   default, because a short line in a quarter column wraps badly. */
.content--grid > .highlight {
  margin: 0;
}

/* The brackets scale with the plate: longer on a full-width picture, shorter
   inside a quarter-width raster cell, so they read as the same detail. */
.content--grid > .figure {
  --frame-inset: 0.9rem;
  --frame-corner: 1.6rem;
}

#app[data-mode="kiosk"] .figure {
  --frame-corner: 3.2rem;
}

/* A picture that is the whole page carries a slightly wider frame, in keeping
   with the larger plate. */
.media--image .figure {
  --frame-inset: 1.8rem;
  --frame-corner: 3rem;
}

/* ------------------------------------------------------- clickable picture */

/*
 * A picture that opens something. The link wraps the whole figure, so the
 * frame and the caption are part of the target, and the frame's brackets pick
 * up the signal colour on hover to say the picture is live.
 */
.figure-link {
  display: block;
  text-decoration: none;
  cursor: pointer;
}

.figure-link .figure__media::before {
  transition: border-color var(--transition-fast);
}

.figure-link:hover .figure::before,
.figure-link:focus-visible .figure__media::before {
  border-color: var(--signal);
}

.figure-link:hover .figure img {
  opacity: 0.94;
}

.figure-link .figure img {
  transition: opacity var(--transition-fast);
}

/* -------------------------------------------------------- portrait inset */

/*
 * The small picture at the top right of a product page: the unit itself, with
 * the text running beside it, the way a photograph sits on a passport.
 *
 * A float rather than a grid column, deliberately - the text wraps around it
 * and closes underneath, so a short page does not leave a tall empty column
 * next to it. It carries the same frame as any other picture.
 *
 * No aspect ratio is imposed. A sensor is roughly square and a winch is wide;
 * forcing either into a fixed frame would crop it badly. The inset is sized by
 * width and each picture keeps its own proportions.
 */
.figure--portrait {
  float: right;
  width: clamp(9rem, 20%, 15rem);
  /* The same frame as the full-width plates, at the scale of the inset: sitting
     inside the picture at the top and sides, protruding below it. */
  --frame-inset: 0.7rem;
  --frame-protrude: 0.7rem;
  --frame-corner: 1.2rem;
  margin: 0 0 var(--space-3) var(--space-4);
}

.figure--portrait figcaption {
  font-size: 0.78rem;
  padding-left: var(--space-1);
}

/*
 * The larger inset: 1.75x the regular one, for a unit whose detail does not
 * survive being shrunk to a passport photograph. The frame grows with it, but
 * no further than the full-width plate's own frame - past that it stops
 * reading as an inset.
 */
.figure--portrait-large {
  width: clamp(15.75rem, 35%, 26.25rem);
  --frame-inset: 1.2rem;
  --frame-protrude: 1rem;
  --frame-corner: 2.1rem;
}

.figure--portrait-large figcaption {
  font-size: 0.82rem;
}

/* The block after the inset must not try to sit beside it and then wrap
   awkwardly: a stats bar, a table or the main picture always starts below. */
.content > .stats,
.content > .table-wrap,
.content > .figure:not(.figure--portrait),
.content > .figure-link {
  clear: right;
}

/*
 * On a narrow screen a floated inset squeezes the text into a column too thin
 * to read, so below the tablet breakpoint it stops floating and sits on its
 * own above the text, at a size that still reads as an inset rather than a
 * full-width plate.
 */
@media (max-width: 780px) {
  .figure--portrait {
    float: none;
    width: min(60%, 12rem);
    margin: 0 0 var(--space-3) 0;
  }

  .figure--portrait-large {
    width: min(85%, 20rem);
  }
}
