/* Booher article typography + layout
   Scope: .bp-article (the <article>) and .bp-content (the_content() wrapper).
   Rename these two selectors if your theme already has equivalents. */

:root {
  --bp-paper: #faf8f4;
  --bp-ink: oklch(0.23 0.012 60);
  --bp-ink-2: oklch(0.40 0.012 60);
  --bp-muted: oklch(0.46 0.012 60);
  --bp-rule: oklch(0.88 0.008 80);
  --bp-rule-soft: oklch(0.90 0.008 80);
  --bp-accent: oklch(0.50 0.14 40);
  --bp-accent-wash: oklch(0.95 0.025 60);

  --bp-serif: 'Source Serif 4', Georgia, serif;
  --bp-sans: 'IBM Plex Sans', system-ui, sans-serif;
  --bp-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --bp-measure: clamp(600px, 48vw, 700px);  /* text column */
  --bp-gap: clamp(40px, 4vw, 64px);         /* text → margin gutter */
  --bp-margin: clamp(200px, 17vw, 260px);   /* sidenote / caption column */
  --bp-bleed: 140px;                        /* extra width on each side for .alignwide */
}

body { background: var(--bp-paper); -webkit-font-smoothing: antialiased; }

.bp-article {
  font-family: var(--bp-serif);
  font-optical-sizing: auto;
  font-size: clamp(19px, 17px + 0.2vw, 21px);
  line-height: 1.62;
  color: var(--bp-ink);
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
  padding: 72px 0 120px;
}
.bp-article a { color: var(--bp-accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.bp-article a:hover { color: var(--bp-ink); }

/* ---------- The grid ---------- */
.bp-header,
.bp-content,
.bp-footer {
  display: grid;
  grid-template-columns:
    [full-start] minmax(24px, 1fr)
    [wide-start] minmax(0, var(--bp-bleed))
    [text-start] minmax(0, var(--bp-measure))
    [text-end] var(--bp-gap)
    [margin-start] var(--bp-margin)
    [margin-end] minmax(0, var(--bp-bleed))
    [wide-end] minmax(24px, 1fr)
    [full-end];
}
.bp-content > * { grid-column: text; margin-top: 0; margin-bottom: 1.1em; }

/* ---------- Header ---------- */
.bp-header > * { grid-column: text-start / margin-end; }
.bp-header { margin-bottom: 48px; }
.bp-header > :last-child { padding-bottom: 28px; border-bottom: 1px solid var(--bp-rule); }
.bp-kicker {
  font-family: var(--bp-sans); font-size: 13px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--bp-accent);
  margin: 0 0 20px;
}
.bp-kicker a { color: inherit; text-decoration: none; }
.bp-title {
  font-family: var(--bp-serif);
  font-size: clamp(34px, 3.6vw, 52px); line-height: 1.18; font-weight: 600;
  letter-spacing: 0; margin: 0 0 20px; text-wrap: balance;
}
.bp-byline {
  font-family: var(--bp-sans); font-size: 15px; color: var(--bp-muted);
  display: flex; flex-wrap: wrap; gap: 10px; margin: 0;
}
.bp-byline a { color: inherit; }

/* ---------- Body text ---------- */
.bp-content p { text-wrap: pretty; hyphens: auto; }
.bp-content > p:first-of-type { hyphens: manual; }
.bp-content > p:first-of-type::first-letter {
  float: left; font-size: 4.1em; line-height: .78; font-weight: 600;
  margin: .1em .1em 0 -.02em; color: var(--bp-ink);
}
.bp-content > p.no-dropcap::first-letter { all: unset; }
.bp-content h2 { font-size: 32px; line-height: 1.15; font-weight: 600; letter-spacing: -.012em; margin: 1.8em 0 .5em; text-wrap: balance; }
.bp-content h3 { font-family: var(--bp-sans); font-size: 14px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; margin: 2em 0 .6em; }
.bp-content mark, .bp-term { background: none; color: inherit; font-weight: 600; }
.bp-content code, .bp-part { font-family: var(--bp-mono); font-size: .84em; font-weight: 500; background: none; padding: 0; }

/* ---------- Figures (Image block) ---------- */
.bp-content > figure,
.bp-content > .wp-block-image { margin: 40px 0 48px; }
.bp-content img { max-width: 100%; height: auto; display: block; }
.bp-content figure img { mix-blend-mode: multiply; } /* white PNG backgrounds melt into the paper */

.bp-content > .alignwide,
.bp-content > .alignfull {
  grid-column: full;
  display: grid;
  grid-template-columns: subgrid;
  max-width: none; width: auto;
}
.bp-content > .alignwide > *,
.bp-content > .alignwide > a > img { grid-column: wide; width: 100%; }
.bp-content > .alignfull > * { grid-column: full; width: 100%; }
.bp-content > .alignwide > a { display: contents; }

.bp-content figcaption,
.bp-content .wp-element-caption {
  font-family: var(--bp-sans); font-size: 13.5px; line-height: 1.5;
  color: var(--bp-ink-2); text-align: left; margin: 14px 0 0;
}
.bp-content > .alignwide > figcaption,
.bp-content > .alignwide > .wp-element-caption { grid-column: margin; }
.bp-content figcaption strong { color: var(--bp-ink); }

/* Embeds / interactives: use Wide alignment on Custom HTML or Embed blocks */
.bp-content > .wp-block-html.alignwide,
.bp-content > .wp-block-embed.alignwide { margin: 28px 0 52px; }

/* ---------- Spans text + margin (add class "bp-span") ---------- */
.bp-content > .bp-span,
.bp-content > .katex-display,
.bp-content > .wp-block-pullquote,
.bp-content > .wp-block-quote.is-style-large { grid-column: text-start / margin-end; }

/* ---------- Sidenotes ----------
   Group block (class "bp-has-note") containing:
     1. the paragraph
     2. a paragraph with class "bp-note"                              */
.bp-content > .bp-has-note { position: relative; }
.bp-content > .bp-has-note > p:not(.bp-note) { margin: 0; }
.bp-note {
  position: absolute; top: 6px;
  left: calc(100% + var(--bp-gap)); width: var(--bp-margin);
  margin: 0 !important;
  font-family: var(--bp-sans); font-size: 13.5px !important; line-height: 1.5 !important;
  color: var(--bp-ink-2); hyphens: manual;
}
.bp-note strong { color: var(--bp-ink); }
.bp-note.is-bottom { top: auto; bottom: 0; }

/* ---------- Math (KaTeX) ---------- */
.bp-content .katex { font-size: 1.05em; }
.bp-content .katex-display { margin: 8px 0 28px; overflow-x: auto; overflow-y: hidden; }

/* ---------- Tables ---------- */
.bp-content .wp-block-table { margin: 20px 0 40px; }
.bp-content table {
  width: 100%; border-collapse: collapse;
  font-family: var(--bp-sans); font-size: 15px; line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.bp-content thead { border-bottom: 1px solid var(--bp-ink) !important; }
.bp-content th {
  text-align: left; font-weight: 600; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--bp-ink-2); padding: 0 12px 10px 0; border: 0;
}
.bp-content td { padding: 10px 12px 10px 0; border: 0; border-bottom: 1px solid var(--bp-rule-soft); }
.bp-content th:not(:first-child), .bp-content td:not(:first-child) { text-align: right; padding-left: 12px; }
.bp-content th:last-child, .bp-content td:last-child { padding-right: 0; }
.bp-content tr.is-highlight td, .bp-content td mark { background: var(--bp-accent-wash); font-weight: 600; }

/* ---------- Pull quote ---------- */
.bp-content .wp-block-pullquote,
.bp-content .wp-block-quote.is-style-large {
  border: 0; padding: 0; margin: 36px 0 40px; text-align: left;
}
.bp-content .wp-block-pullquote blockquote,
.bp-content .wp-block-quote.is-style-large p {
  font-size: clamp(26px, 3vw, 34px); line-height: 1.25; font-style: italic;
  letter-spacing: -.01em; max-width: 26ch; text-wrap: balance; margin: 0;
}

/* ---------- Number strip (Columns block, class "bp-stats") ---------- */
.bp-content > .bp-stats {
  grid-column: text-start / margin-end;
  display: grid !important; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0 !important;
  margin: 24px 0 44px;
  border-top: 1px solid var(--bp-ink); border-bottom: 1px solid var(--bp-rule);
}
.bp-stats > * { padding: 20px; border-right: 1px solid var(--bp-rule); margin: 0 !important; }
.bp-stats > *:first-child { padding-left: 0; }
.bp-stats > *:last-child { border-right: 0; padding-right: 0; }
.bp-stats p:first-child { font-size: 44px; line-height: 1; font-weight: 500; letter-spacing: -.02em; margin: 0; }
.bp-stats p:last-child { font-family: var(--bp-sans); font-size: 13px; color: var(--bp-ink-2); margin: 10px 0 0; }

/* ---------- Footer ---------- */
.bp-footer > * { grid-column: text-start / margin-end; }
.bp-footer-inner {
  margin-top: 64px; padding-top: 24px; border-top: 1px solid var(--bp-ink);
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 20px 40px;
  font-family: var(--bp-sans); font-size: 14px; color: var(--bp-ink-2);
}
.bp-label { font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; margin-right: 10px; }
.bp-footer .bp-prev a { font-family: var(--bp-serif); font-size: 18px; font-style: italic; }

/* ---------- Narrow screens: single column, notes inline ---------- */
@media (max-width: 1080px) {
  .bp-header, .bp-content, .bp-footer {
    grid-template-columns: [full-start] 20px [wide-start text-start margin-start] minmax(0, 1fr) [wide-end text-end margin-end] 20px [full-end];
  }
  .bp-note { position: static; width: auto; margin: 12px 0 28px !important; }
  .bp-content > .bp-has-note > p:not(.bp-note) { margin-bottom: 0; }
}

/* ============================================================
   Palimpsest integration layer
   Added when the Gate Post redesign was merged into the theme.
   Everything below neutralises Palimpsest / Bootstrap rules that
   would otherwise fight the grid above. This file is enqueued
   only on single posts, but selectors stay scoped anyway.
   ============================================================ */

/* Paper colour, single posts only (theme sets --color-bg: #fff). */
body.single-post { background: var(--bp-paper); }

/* The Bootstrap container around <main> must not cap the grid.
   single.php drops .container-xxl on posts; this is belt and braces. */
.single-post #site-main { max-width: none; width: auto; padding-left: 0; padding-right: 0; }
.single-post #site-main > .bp-article { max-width: none; }

/* Palimpsest styles content links with a gold underline gradient and a
   fixed 1.25rem size via `main article a`. Undo it inside the article. */
.bp-article a,
.bp-article .bp-content a,
.bp-content a {
  font-size: inherit;
  line-height: inherit;
  background-image: none;
  background-size: 0 0;
  border-bottom: 0;
  padding: 0;
  color: var(--bp-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.bp-article a:hover,
.bp-content a:hover {
  color: var(--bp-ink);
  background-image: none;
  background-size: 0 0;
  border-bottom-color: transparent;
}
.bp-kicker a, .bp-byline a, .bp-footer a { text-decoration: none; }
.bp-footer a:hover, .bp-kicker a:hover, .bp-byline a:hover { text-decoration: underline; }

/* Theme sets every heading in europa sans with -1.5px tracking. */
.bp-article h1, .bp-article h2, .bp-article h3,
.bp-article h4, .bp-article h5, .bp-article h6 {
  font-family: var(--bp-serif);
  color: var(--bp-ink);
  letter-spacing: normal;
  text-align: left;
}
.bp-content h3 { font-family: var(--bp-sans); color: var(--bp-ink-2); }
.bp-content h4 {
  font-family: var(--bp-serif); font-size: 21px; font-weight: 600;
  line-height: 1.3; margin: 1.6em 0 .4em;
}
.bp-content h5, .bp-content h6 {
  font-family: var(--bp-sans); font-size: 13px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; margin: 1.8em 0 .5em;
}

/* Lists: the theme only styled .entry-content lists, which no longer exists. */
.bp-content ul, .bp-content ol { padding-left: 1.4em; margin: 0 0 1.1em; }
.bp-content li { margin-bottom: .5em; }
.bp-content li > ul, .bp-content li > ol { margin: .5em 0 0; }
.bp-content blockquote:not(.has-text-align-center) { margin-left: 0; }

/* Inline colour marks: the author uses <mark class="has-inline-color"> as a
   colour key in illustrated posts. Keep those colours; only plain <mark>
   gets the "highlighted term" treatment. */
.bp-content mark.has-inline-color { color: inherit; font-weight: 600; background: none; }
.bp-content mark[style*="color"] { background: none; }

/* Groups: the theme caps every child of a group at 720px and centres it,
   which breaks .bp-has-note sidenotes. */
.bp-content .wp-block-group > .wp-block-group__inner-container > *,
.bp-content > .bp-has-note > * {
  max-width: none; margin-left: 0; margin-right: 0;
}
.bp-content > .bp-has-note,
.bp-content > .wp-block-group.bp-has-note { position: relative; }
.bp-content > .bp-has-note > .wp-block-group__inner-container { position: relative; }

/* Columns / number strip: theme adds 1rem side padding to every columns block. */
.bp-content > .bp-stats,
.bp-content > .wp-block-columns { padding-left: 0; padding-right: 0; }

/* Tables: theme sets 0.5em padding on every cell and a border-collapse table. */
.bp-content .wp-block-table { overflow-x: auto; }
.bp-content .wp-block-table th { padding: 0 12px 10px 0; }
.bp-content .wp-block-table td { padding: 10px 12px 10px 0; }
.bp-content .wp-block-table th:not(:first-child),
.bp-content .wp-block-table td:not(:first-child) { padding-left: 12px; }
.bp-content .wp-block-table th:last-child,
.bp-content .wp-block-table td:last-child { padding-right: 0; }

/* Images: kill the theme's 100vw alignfull hacks, the grid handles width. */
.bp-content .wp-block-image.alignfull img,
.bp-content .wp-block-image .alignfull { width: 100%; }
.bp-content .wp-block-image.alignleft,
.bp-content .wp-block-image.alignright { width: auto; max-width: 344px; }

/* Legacy posts: images inserted before the redesign carry size-large /
   size-full with no alignment. Let them use the margin column instead of
   shrinking to the 660px measure. Delete this block to make every
   unaligned figure sit at text width. */
.bp-content > figure.wp-block-image.size-large:not(.alignwide):not(.alignfull):not(.alignleft):not(.alignright),
.bp-content > figure.wp-block-image.size-full:not(.alignwide):not(.alignfull):not(.alignleft):not(.alignright) {
  grid-column: text-start / margin-end;
}

/* Raw HTML / embed blocks (Spline viewers, iframes, gists). */
.bp-content > .wp-block-html,
.bp-content > .wp-block-embed { margin: 28px 0 44px; }
.bp-content > .wp-block-html iframe,
.bp-content > .wp-block-embed iframe { max-width: 100%; }

/* MathJax (the theme loads MathJax 3, not KaTeX, per post meta). */
.bp-content mjx-container[display="true"] {
  margin: 8px 0 28px; overflow-x: auto; overflow-y: hidden;
}
.bp-content > mjx-container[display="true"] { grid-column: text-start / margin-end; }

/* Block-style aliases, so the editor's Sidenote / Paragraph + sidenote /
   Number strip styles work without typing class names. */
.bp-content > .is-style-bp-has-note-style { position: relative; }
.bp-content > .is-style-bp-has-note-style > p:not(.bp-note):not(.is-style-bp-note-style) { margin: 0; }
.is-style-bp-note-style {
  position: absolute; top: 6px;
  left: calc(100% + var(--bp-gap)); width: var(--bp-margin);
  margin: 0 !important;
  font-family: var(--bp-sans); font-size: 13.5px !important; line-height: 1.5 !important;
  color: var(--bp-ink-2); hyphens: manual;
}
.bp-content > .is-style-bp-stats-style {
  grid-column: text-start / margin-end;
  display: grid !important; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0 !important;
  margin: 24px 0 44px; padding: 0;
  border-top: 1px solid var(--bp-ink); border-bottom: 1px solid var(--bp-rule);
}
.is-style-bp-stats-style > * { padding: 20px; border-right: 1px solid var(--bp-rule); margin: 0 !important; }
.is-style-bp-stats-style > *:first-child { padding-left: 0; }
.is-style-bp-stats-style > *:last-child { border-right: 0; padding-right: 0; }
.is-style-bp-stats-style p:first-child { font-size: 44px; line-height: 1; font-weight: 500; letter-spacing: -.02em; margin: 0; }
.is-style-bp-stats-style p:last-child { font-family: var(--bp-sans); font-size: 13px; color: var(--bp-ink-2); margin: 10px 0 0; }

/* ---------- Everything after the article keeps the text measure ---------- */
.bp-after {
  display: grid;
  grid-template-columns:
    [full-start] minmax(24px, 1fr)
    [wide-start] minmax(0, var(--bp-bleed))
    [text-start] minmax(0, var(--bp-measure))
    [text-end] var(--bp-gap)
    [margin-start] var(--bp-margin)
    [margin-end] minmax(0, var(--bp-bleed))
    [wide-end] minmax(24px, 1fr)
    [full-end];
  font-family: var(--bp-sans);
  color: var(--bp-ink-2);
  padding-bottom: 96px;
}
.bp-after > * { grid-column: text-start / margin-end; }
.bp-after .author-bio { border-color: var(--bp-rule) !important; }
.bp-after .author-name { font-family: var(--bp-serif); font-size: 22px; color: var(--bp-ink); }
.bp-after .author-description, .bp-after .author-other-posts { font-size: 15px; }
.bp-after #post-comments .comments-wrap,
.bp-after #respond.wrap-md { max-width: none; padding-left: 0; padding-right: 0; }
.bp-after a { color: var(--bp-accent); }

@media (max-width: 1080px) {
  .bp-after {
    grid-template-columns: [full-start] 20px [wide-start text-start margin-start] minmax(0, 1fr) [wide-end text-end margin-end] 20px [full-end];
  }
}

/* ---------- Narrow screens ---------- */
@media (max-width: 1080px) {
  .is-style-bp-note-style { position: static; width: auto; margin: 12px 0 28px !important; }
  .bp-content > figure.wp-block-image.size-large:not(.alignwide):not(.alignfull),
  .bp-content > figure.wp-block-image.size-full:not(.alignwide):not(.alignfull) {
    grid-column: text;
  }
}

@media (max-width: 782px) {
  .bp-article { padding: 40px 0 72px; }
  .bp-header > *, .bp-footer > * { grid-column: text; }
  .bp-header { margin-bottom: 36px; }
  .bp-content > p:first-of-type::first-letter { font-size: 3.4em; }
}

/* ---------- Mid widths: keep a readable measure ----------
   The handoff collapses to `minmax(0, 1fr)` below 1080px, which would let
   body text run the full viewport width.
   Re-centre a capped column instead; .alignwide still gets extra room and
   .alignfull still bleeds edge to edge. */
@media (max-width: 1080px) {
  .bp-header, .bp-content, .bp-footer, .bp-after {
    grid-template-columns:
      [full-start] minmax(20px, 1fr)
      [wide-start] minmax(0, 60px)
      [text-start margin-start] minmax(0, 700px)
      [text-end margin-end] minmax(0, 60px)
      [wide-end] minmax(20px, 1fr)
      [full-end];
  }
}

/* MathJax's assistive MathML is visually clipped; make sure it can never
   widen the page on a narrow screen. */
.bp-content mjx-assistive-mml { position: absolute !important; }

/* ---------- Phones: 20px gutters, nothing else ---------- */
@media (max-width: 782px) {
  .bp-header, .bp-content, .bp-footer, .bp-after {
    grid-template-columns:
      [full-start] 20px
      [wide-start text-start margin-start] minmax(0, 1fr)
      [wide-end text-end margin-end] 20px
      [full-end];
  }
}

/* ---------- Comments ----------
   The comments template carries Bootstrap .container-md / .wrap-md, which
   re-centre an 800px column inside the grid track and indent everything.
   Let the .bp-after grid own the width instead. */
.bp-after #post-comments,
.bp-after #respond,
.bp-after .comments-wrap,
.bp-after .container-md,
.bp-after .wrap-md {
  max-width: none; width: auto; margin-left: 0; margin-right: 0;
  padding-left: 0; padding-right: 0;
}
.bp-after #respond .comments-wrap {
  background: none !important; border: 0 !important;
  border-top: 1px solid var(--bp-rule) !important;
  padding: 24px 0 0 !important;
}
.bp-after #post-comments h3,
.bp-after #respond h3 {
  font-family: var(--bp-sans); font-size: 11px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--bp-ink-2);
  margin: 40px 0 16px;
}
.bp-after .comment-list { margin: 0; }
.bp-after .comment-list > li { padding: 0; margin-bottom: 16px; }
.bp-after .comment-list .children > li { padding: 16px 0 0 24px; }
.bp-after .comment {
  border: 0; border-left: 2px solid var(--bp-rule); border-radius: 0;
  box-shadow: none; background: none; padding: 2px 0 2px 20px;
}
.bp-after .comment-author h4 { font-family: var(--bp-sans); font-size: 15px; font-weight: 600; color: var(--bp-ink); margin: 0; }
.bp-after .comment-meta { font-size: 13px; margin: 2px 0 10px; }
.bp-after .comment-meta a { color: var(--bp-muted); text-decoration: none; }
.bp-after .comment-body p { font-family: var(--bp-serif); font-size: 17px; line-height: 1.55; color: var(--bp-ink); }
.bp-after .reply a { font-family: var(--bp-sans); font-size: 13px; }
.bp-after #commentform label { font-family: var(--bp-sans); font-size: 13px; color: var(--bp-ink-2); }
.bp-after #commentform .form-control { border-radius: 0; border-color: var(--bp-rule); background: #fff; }
.bp-after #respond p { font-family: var(--bp-sans); font-size: 14px; }
.bp-after .alert { border-radius: 0; font-family: var(--bp-sans); font-size: 14px; }

/* The comments markup still carries .wrap-md, whose link rule forces
   1.25rem plus the gold underline. Strip it inside .bp-after too. */
.bp-after .wrap-md a,
.bp-after .comments-wrap a,
.bp-after .comment-meta a,
.bp-after a {
  font-size: inherit; line-height: inherit;
  background-image: none; background-size: 0 0;
  border-bottom: 0; padding: 0;
}
.bp-after .comment-meta a { font-size: 13px; }

/* The footer band now closes the article, so the old 120px tail is dead space. */
.bp-article { padding-bottom: 40px; }

/* ============================================================
   3D scene figures  —  figure.gate-fig
   ============================================================
   Markup (hand-written in a Custom HTML block, or emitted by the
   palimpsest/spline block):

     <figure class="gate-fig" style="--ar:1280/540; --ar-phone:3/4;">
       <spline-viewer url="…/scene.splinecode"></spline-viewer>
       <figcaption>…</figcaption>
     </figure>

   --ar        desktop aspect ratio (required in practice; 16/9 fallback)
   --ar-phone  optional override below 700px

   WHY THE ASPECT RATIO IS LOAD-BEARING
   <spline-viewer> is an unknown element with no intrinsic box, and the
   viewer's shadow-DOM canvas is width:100%/height:100%. With no definite
   height to resolve against, the canvas measures the host, the host
   measures the canvas, and it climbs until the GPU refuses the texture
   ("Texture size (… x 61036) exceeded maximum texture size"). --ar plus
   `contain: size` makes the host size a pure function of CSS, so the
   feedback loop cannot form.

   BREAKOUT TECHNIQUE — CHANGED FROM THE SPEC
   The usual `left:50%; transform:translateX(-50%); width:min(1200px,100vw-32px)`
   is for a block-level content column. .bp-content is a CSS grid with named
   columns (text / wide / full), so the figure uses `grid-column: wide` and is
   capped with max-width instead. Reasons:
     - the grid already reserves exactly this breakout track, so figures line
       up with wide images instead of landing near them by arithmetic;
     - 100vw includes the scrollbar gutter and would cause a horizontal
       scrollbar on Windows/Linux;
     - a transform on the figure would become the containing block for the
       viewer and clip any fixed-position UI it renders.
   The visible result is what the spec asked for: up to 1200px on desktop,
   edge to edge on phones, caption at reading width. */

.bp-content > figure.gate-fig,
.bp-content > .gate-fig {
  grid-column: wide;
  justify-self: center;
  width: 100%;
  max-width: 1200px;
  margin: 2.2em 0;
  padding: 0;
}

/* Placement modifiers, used by the palimpsest/spline block's Width control
   and available to hand-written figures as plain classes. */
.bp-content > .gate-fig.is-w-text { grid-column: text; max-width: none; }
.bp-content > .gate-fig.is-w-span { grid-column: text-start / margin-end; max-width: none; }
.bp-content > .gate-fig.is-w-wide { grid-column: wide; }
.bp-content > .gate-fig.is-w-full { grid-column: full; max-width: none; }

.gate-fig > spline-viewer {
  display: block;
  width: 100%;
  height: auto !important;
  aspect-ratio: var(--ar, 16 / 9);
  max-height: 90vh;
  border-radius: 14px;
  overflow: hidden;
  contain: size layout;        /* the canvas can never resize its own host */
  background: transparent;
  touch-action: pan-y;         /* the canvas must not swallow page scrolling */
}
/* Spline's own low-res poster, shown until the runtime upgrades the element. */
.gate-fig > spline-viewer > img { width: 100%; height: 100%; object-fit: cover; display: block; }

.gate-fig > figcaption {
  max-width: var(--bp-measure);
  margin: .6em auto 0;
  font: 400 14px/1.45 var(--bp-sans);
  color: var(--bp-ink-2);
  text-align: left;
}
.gate-fig > figcaption b,
.gate-fig > figcaption strong { color: var(--bp-ink); font-weight: 600; }

@media (max-width: 1080px) {
  .bp-content > figure.gate-fig,
  .bp-content > .gate-fig { margin: 1.8em 0; }
}

@media (max-width: 700px) {
  /* `grid-column: full` is the scrollbar-safe equivalent of width:100vw. */
  .bp-content > figure.gate-fig,
  .bp-content > .gate-fig { grid-column: full; max-width: none; }
  .gate-fig > spline-viewer {
    aspect-ratio: var(--ar-phone, var(--ar, 16 / 9));
    /* Floor: Spline keeps a fixed vertical FOV, so canvas height drives how
       large the subject draws. A wide desktop ratio (2.37:1) collapses to a
       165px strip on a 390px phone, which renders the mechanism unreadable
       and the drag target unusable. Never go below ~62vw, whatever --ar says. */
    min-height: 62vw;
    border-radius: 0;
  }
  .gate-fig > figcaption { padding: 0 20px; }
}

/* Fallback: a bare <spline-viewer> that was never wrapped in a figure still
   gets a definite box rather than running away. */
.bp-content > spline-viewer {
  grid-column: wide;
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--ar, 16 / 9);
  max-height: 90vh;
  margin: 2.2em 0;
  border-radius: 14px;
  overflow: hidden;
  contain: size layout;
  touch-action: pan-y;
}
.bp-content > spline-viewer > img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- Scene states ----------
   spline-lazy.js publishes data-spline-state on the figure so a scene that
   cannot render never leaves a blank rectangle. */
.gate-fig > spline-viewer { background: var(--bp-accent-wash); }
.gate-fig[data-spline-state="ready"] > spline-viewer { background: transparent; }

/* Waiting for the scene: a quiet pulse rather than an empty box. */
@keyframes bp-spline-wait { 0%,100% { opacity: .55 } 50% { opacity: .85 } }
.gate-fig[data-spline-state="idle"] > spline-viewer,
.gate-fig[data-spline-state="loading"] > spline-viewer {
  animation: bp-spline-wait 1.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .gate-fig[data-spline-state="idle"] > spline-viewer,
  .gate-fig[data-spline-state="loading"] > spline-viewer { animation: none; }
}

/* Cannot render here: say so inside the box instead of showing nothing.
   The message lives on the FIGURE, not the viewer: once the runtime upgrades
   <spline-viewer> it owns a shadow root, and a host's ::after is light-DOM
   content that a shadow root without a <slot> never renders. */
.gate-fig[data-spline-state="error"],
.gate-fig[data-spline-state="unsupported"] { position: relative; }
.gate-fig[data-spline-state="error"] > spline-viewer,
.gate-fig[data-spline-state="unsupported"] > spline-viewer {
  animation: none;
  background: var(--bp-accent-wash);
  outline: 1px solid var(--bp-rule);
  outline-offset: -1px;
}
.gate-fig[data-spline-state="error"]::after,
.gate-fig[data-spline-state="unsupported"]::after {
  content: "Interactive 3D scene \2014 this browser cannot display it.";
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--bp-spline-box, auto); aspect-ratio: var(--ar-phone, var(--ar, 16 / 9));
  display: flex; align-items: center; justify-content: center;
  padding: 0 24px; text-align: center; text-wrap: balance;
  font: 400 13.5px/1.5 var(--bp-sans); color: var(--bp-ink-2);
  pointer-events: none;
}
@media (min-width: 701px) {
  .gate-fig[data-spline-state="error"]::after,
  .gate-fig[data-spline-state="unsupported"]::after { aspect-ratio: var(--ar, 16 / 9); }
}
.gate-fig[data-spline-reason="webgpu-required"]::after {
  content: "Interactive 3D scene \2014 this one needs a WebGPU-capable browser (Chrome, Edge, or Safari 18+).";
}
