@import url("/theme.css");
* { box-sizing: border-box; }
body {
  margin: 0;
  background-color: var(--bg);
  background-image: var(--paper-texture);
  background-size: var(--paper-texture-size);
  color: var(--text);
  font: 1rem/1.6 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main { width: min(1360px, calc(100% - 28px)); margin: 0 auto; padding: 28px 0 44px; }
h1 { margin: 0; font: 600 clamp(24px, 4vw, 42px)/1.05 Georgia, serif; letter-spacing: -0.025em; }
h2 { margin: 0 0 10px; font: 600 20px/1.2 Georgia, serif; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; color: var(--accent); }
a { color: var(--accent); }

/* Navbar ---------------------------------------------------------------- */
/* Shared navigation stays scoped to each entry point: overview pages link to
   Home, Research and Capsai; the original Bloom viewer keeps its own context
   link. Pages mark the current entry point in markup; no path script is needed. */
.navbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  padding: 13px 28px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-control);
}
.skip-link { position: absolute; inset-block-start: 1rem; inset-inline-start: -999px; z-index: 20; padding: .65rem .9rem; border: 1px solid var(--line); border-radius: .5rem; background: var(--panel); color: var(--text); }
.skip-link:focus-visible { inset-inline-start: 1rem; }
.nav-brand { font-weight: 600; font-size: 15px; letter-spacing: .01em; color: var(--text); text-decoration: none; }
.nav-brand:hover { color: var(--accent); }
.nav-links { display: flex; flex-wrap: wrap; gap: 20px; margin: 0; padding: 0; list-style: none; font-size: 14px; }
.nav-links a { color: var(--muted); text-decoration: none; }
.nav-links a:hover { color: var(--accent); }
.nav-links a[aria-current="page"] { color: var(--text); box-shadow: inset 0 -1px 0 var(--accent); }
.navbar a:focus-visible, .entry a:focus-visible, .entry-releases a:focus-visible, button:focus-visible, input:focus-visible, summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.nav-links a { display: inline-flex; min-height: 44px; align-items: center; }

/* Masthead ------------------------------------------------------------- */
.masthead { width: min(1180px, calc(100% - 28px)); margin: 0 auto; padding: 40px 0 8px; }
/* The portfolio is a reading column, not a dashboard, so it is narrower than
   the experiment page's masthead. */
body.home .masthead { max-width: 70ch; }
body.home main { max-width: 70ch; }
.brand { margin: 0 0 14px; color: var(--accent); font-size: 12px; letter-spacing: .18em; text-transform: uppercase; }
.lede { max-width: 62ch; margin: 14px 0 0; color: var(--muted); font-size: 16px; }
.notice { max-width: 62ch; margin: 16px 0 0; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--control-radius); background: var(--panel-2); color: var(--muted); }
.notice strong { color: var(--text); }

/* Portfolio entries ------------------------------------------------------ */
/* The home page is an index of work, not a dashboard, so each entry is a
   reading-width block with its own heading. The experiment cards below reuse
   .card for the runnable trials, and .entry stays separate so the two pages
   cannot drift into looking like each other. */
.entries { margin-top: 44px; padding-top: 28px; border-top: 1px solid var(--line); }
.entries > h2 { margin-bottom: 22px; font: 600 13px/1.2 ui-sans-serif, system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.entry {
  margin-bottom: 20px;
  padding: 22px 24px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--surface-radius);
  box-shadow: var(--shadow-raised);
}
.entry h3 { margin: 0 0 6px; font: 600 20px/1.25 Georgia, serif; }
.entry h3 a { color: var(--text); text-decoration: none; }
.entry h3 a:hover { color: var(--accent); }
.entry-kind { margin: 0 0 14px; color: var(--accent); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
.entry p { margin: 0 0 12px; line-height: 1.65; }
.entry p:last-child { margin-bottom: 0; }
.entry-links { margin: 0 0 12px; }
.entry-releases { margin: 0; padding-left: 20px; line-height: 1.9; }
.entry-releases a { text-decoration: none; }
.entry-releases a:hover { text-decoration: underline; }
.entry-note { color: var(--muted); font-size: 13px; }
.entry-note code { word-break: break-all; }

/* The homepage reads as a portfolio, not a dashboard. */
.home .entries { margin-top: 52px; padding-top: 0; border-top: 0; }
.home .entries > h2 { margin-bottom: 12px; color: var(--accent); }
.home .entry {
  margin: 0;
  padding: 26px 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  box-shadow: none;
}
.home .entry h3 { font-size: clamp(1.35rem, 3vw, 1.65rem); }
.home .entry p { max-width: 68ch; }
.home .entry-kind { font-size: 12px; }
.home .entry:last-child { border-bottom: 0; }

/* The experiment grid --------------------------------------------------- */
/* Two columns, not auto-fit. Four narrow columns made each sensor panel only
   383px wide and left every card over 950px tall, so the grid ran to four
   stacked rows. Two columns give the 4:3 panel room and keep the four trials
   readable as a single set of rows. */
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--content-gap); }
.tactile main { --research-layout-gap: clamp(1.5rem, 3vw, 3rem); container-name: research; container-type: inline-size; font-size: 1rem; line-height: 1.6; }
.research-split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: var(--research-layout-gap, 1.5rem); min-inline-size: 0; }
.research-split > * { min-inline-size: 0; }
.tactile .masthead { inline-size: 100%; margin: 0; padding-block: clamp(2rem, 4vw, 3.5rem) 1.5rem; }
.tactile .masthead h1 { max-inline-size: 17ch; line-height: 1.15; text-wrap: balance; }
.tactile .masthead .lede { max-inline-size: 65ch; margin: 0; font-size: 1.0625rem; line-height: 1.6; }
.research-context { margin-block-start: 3rem; }
.research-block { min-inline-size: 0; }
.research-block > p { max-inline-size: 65ch; }
.research-block .entry { margin-block: 1.25rem 0; }
.research-guide { margin-block-start: 3rem; padding-block-start: 1.5rem; border-block-start: 1px solid var(--line); }
.research-guide-body { max-inline-size: 65ch; }
.research-guide-body p { margin-block: 0 1rem; }
@container research (max-width: 50rem) {
  .research-split { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
  .research-context { row-gap: 2.5rem; }
}
.research-library { margin-block-start: 2.75rem; min-inline-size: 0; }
.research-library-heading { margin-block-end: 1.5rem; }
.research-library-heading p { margin: 0; color: var(--muted); font-size: 1rem; line-height: 1.6; }
.content-tabs { display: flex; flex-wrap: wrap; gap: .5rem; inline-size: fit-content; max-inline-size: 100%; padding: .375rem; border: 1px solid var(--line); border-radius: calc(var(--control-radius) + .375rem); background: var(--panel-2); box-shadow: var(--shadow-inset); }
.content-tab { display: inline-flex; flex-wrap: wrap; gap: .625rem; justify-content: center; align-items: center; min-block-size: var(--control-size); max-inline-size: 100%; padding: .625rem 1rem; border: 1px solid transparent; border-radius: var(--control-radius); background: transparent; font-size: 1rem; color: var(--muted); box-shadow: none; }
.content-tab[aria-selected="true"] { border-color: var(--accent); background: var(--surface-raised); color: var(--text); box-shadow: var(--shadow-control); }
.content-tab-count { font-size: .8125rem; font-variant-numeric: tabular-nums; }
.research-status { min-block-size: 1.5em; margin-block: .75rem 1.25rem; color: var(--muted); font-size: .875rem; }
.research-collection { min-inline-size: 0; scroll-margin-block-start: 6rem; }
.research-collection:focus-visible { outline: 2px solid var(--accent); outline-offset: 5px; }
.research-collection-heading { margin-block-end: 1.5rem; }
.research-collection-heading h3 { margin: 0; font-size: 1.25rem; line-height: 1.3; }
.research-collection-heading p { margin: 0; font-size: 1rem; line-height: 1.6; color: var(--muted); }
.research-empty { padding: clamp(1.25rem, 3vw, 2rem); border: 1px solid var(--line); border-radius: var(--surface-radius); background: var(--paper-surface); background-size: var(--paper-texture-size); box-shadow: var(--shadow-raised); }
.research-empty h4 { margin: 0; font-size: 1.125rem; line-height: 1.4; }
.research-empty p { max-inline-size: 65ch; margin-block: 0 1rem; color: var(--muted); font-size: 1rem; line-height: 1.6; }
.research-empty a { display: inline-flex; min-block-size: var(--control-size); align-items: center; text-underline-offset: .2em; }
.research-empty a:focus-visible, .research-materials a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.research-materials { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin: 0; padding: 1rem; list-style: none; }
.research-materials a { display: inline-flex; min-block-size: var(--control-size); align-items: center; overflow-wrap: anywhere; text-underline-offset: .2em; }
.research-card-note { padding-inline: 1rem; color: var(--muted); font-size: 1rem; line-height: 1.6; white-space: pre-line; }
.research-panels .card { min-inline-size: 0; }
.research-panels .card-head { flex-wrap: wrap; }
.research-panels .card-head h4 { margin: 0; font: 600 1.25rem/1.3 Georgia, serif; }
.research-panels .card-sum { font-size: 1rem; line-height: 1.6; }
.research-panels .rowtag { max-inline-size: 100%; font-size: .75rem; overflow-wrap: anywhere; }
.retry-load { min-block-size: var(--control-size); }
[hidden] { display: none !important; }
.loading { color: var(--muted); }
.card { border: 1px solid var(--line); border-radius: var(--surface-radius); background: var(--paper-surface); background-size: var(--paper-texture-size); box-shadow: var(--shadow-raised); overflow: hidden; display: flex; flex-direction: column; }
.card-head { display: flex; align-items: start; justify-content: space-between; gap: 14px; padding: 16px 16px 12px; }
.card-head h3 { margin: 0; font: 600 18px/1.2 Georgia, serif; }
.rowtag { flex: none; padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
.card-sum { margin: 0; padding: 0 16px 14px; color: var(--muted); font-size: 13px; }
.sensor { position: relative; aspect-ratio: 4 / 3; max-height: 300px; background: var(--stage-bg); }
/* The sensor is 4:3, so at a wide column it would tower over the timeline. The
   panel is capped in height and the sensor grid is centred inside it, which
   keeps all four cards the same height without cropping the image. */
canvas { width: 100%; height: 100%; display: block; }
.overlay { position: absolute; inset: 0; pointer-events: none; display: flex; justify-content: space-between; align-items: end; padding: 12px; color: var(--stage-muted); font-size: 11px; }
.timeline { padding: 14px; border-top: 1px solid var(--plot-grid); background: var(--plot-bg); }
.plot { width: 100%; height: 170px; display: block; }
.controls { display: grid; gap: 10px; padding: 12px 14px 14px; }
/* The two sliders share a row. Stacking label, output and range vertically made
   the control block 368px tall, which was most of a card's height. */
.control { display: grid; grid-template-columns: 5.5em 1fr auto; gap: 8px; align-items: center; }
label { color: var(--muted); font-size: 13px; }
output { color: var(--text); font-variant-numeric: tabular-nums; font-size: 13px; }
input[type=range] { accent-color: var(--accent); width: 100%; }
/* Four actions on one row rather than a 2x2 block. */
.buttons { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 6px; }
button, .file-label { appearance: none; min-block-size: var(--control-size); border: 1px solid var(--control-edge); border-radius: var(--control-radius); padding: 9px 11px; color: var(--text); background: var(--surface-raised); font: inherit; font-size: .875rem; cursor: pointer; text-align: center; }
.file-label { box-shadow: var(--shadow-control); }
@media (hover: hover) {
  button:not(:disabled):hover, .file-label:hover { border-color: var(--accent); }
}
button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 650; }
button:disabled { opacity: .45; cursor: default; border-color: var(--line); }
.file-input { display: none; }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.metric { padding: 6px 8px; border: 1px solid var(--line); border-radius: var(--control-radius); background: var(--panel-2); box-shadow: var(--shadow-inset); }
.metric b { display: block; font-size: 16px; font-variant-numeric: tabular-nums; }
.metric span { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .07em; }
.legend { display: flex; gap: 12px; color: var(--stage-muted); font-size: 11px; }
.legend i { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; }
.on { background: var(--plot-on); } .off { background: var(--plot-off); } .ref { background: var(--plot-reference); }
details { border-top: 1px solid var(--line); }
summary { cursor: pointer; padding: 11px 14px; color: var(--muted); font-size: 13px; }
.metadata { padding: 0 14px 14px; }
dl { display: grid; grid-template-columns: minmax(110px, .4fr) 1fr; gap: 5px 12px; margin: 0; font-size: 13px; }
dt { color: var(--muted); } dd { margin: 0; overflow-wrap: anywhere; }
.error { color: var(--status-error); }
.unavailable { padding: 16px; color: var(--muted); }
.unavailable code { display: block; margin-top: 6px; }

.about { margin-top: 44px; padding-top: 24px; border-top: 1px solid var(--line); color: var(--muted); max-width: 70ch; }
.about p { margin: 0 0 10px; }

/* The rig section. Height is aspect-driven so the stage never depends on a
   viewport guess, and the canvas is absolutely positioned so it cannot grow the
   stage it is measured against. */
.rig { margin-top: 44px; }
.rig-lede { margin: 0 0 14px; color: var(--muted); max-width: 70ch; }
.rig-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: 260px;
  max-height: 62vh;
  border: 1px solid var(--line);
  border-radius: 18px;
  overflow: hidden;
  background-color: var(--stage-bg);
  background-image: var(--draft-texture);
  background-size: 32px 32px;
  box-shadow: var(--shadow-raised);
}
.rig-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: pan-y; }
/* The status line and the stage are siblings inside a positioned frame, so the
   status can overlay the canvas without the viewer having to own it. State is
   read off the stage's single data-rig-state attribute, so there is one source
   of truth rather than two elements to keep in step. */
.rig-frame { position: relative; }
.rig-status {
  position: absolute;
  z-index: 1;
  inset: 0;
  display: grid;
  place-content: center;
  margin: 0;
  color: var(--stage-muted);
  font-size: 13px;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.rig-frame:has(.rig-stage[data-rig-state="ready"]) .rig-status { opacity: 0; }
.rig-frame:has(.rig-stage[data-rig-state="error"]) .rig-status { color: var(--stage-error); }
.rig-stage[data-rig-state="error"] { border-color: var(--status-error); }

/* One button per trial rather than a select: each trial is a separate 4 to
   6 MB recording fetch, and the cost of the choice belongs on the control
   that makes it. */
.rig-switch {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}
.rig-switch button {
  flex: 1 1 auto;
  min-width: 0;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  line-height: 1.25;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.rig-switch button:hover { border-color: var(--accent); color: var(--text); }
.rig-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rig-switch button[aria-pressed="true"] { border-color: var(--accent); color: var(--text); }
.rig-note { margin: 12px 0 0; color: var(--muted); font-size: 13px; }
@media (prefers-reduced-motion: reduce) {
  .rig-status { transition: none; }
}

/* A release page shows one experiment, so the grid narrows to a single column. */
body.release .grid { grid-template-columns: minmax(0, 860px); }
body.release .card { max-width: 860px; }
body.release .plot { height: 210px; }

/* Two columns need about 2 x 420px plus the gap and the page's side margins, so
   the single-column fallback starts below 880px rather than 900px. At 900px
   the grid was dropping to one full-width column while two still fitted. */
@media (max-width: 880px) {
  .grid { grid-template-columns: 1fr; }
  .rig-switch button, .buttons button, .file-label { min-height: 44px; }
  .control input[type=range] { min-height: 44px; }
}
@media (max-width: 600px) {
  .navbar { position: static; padding: 8px 14px; gap: 4px 12px; }
  .nav-links { gap: 4px 12px; font-size: 13px; }
  main { width: min(100% - 24px, 1360px); padding-top: 18px; }
  .masthead { width: min(100% - 24px, 1180px); padding-top: 24px; }
  .lede { font-size: 15px; }
  .entries { margin-top: 28px; padding-top: 20px; }
  .entry { padding: 18px 16px; border-radius: 14px; }
  .grid { gap: 12px; }
  .card { border-radius: 14px; }
  .card-head { padding: 14px 12px 10px; }
  .card-sum { padding: 0 12px 12px; }
  .sensor { max-height: none; }
  .controls { padding: 10px 12px 12px; }
  .buttons { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  button, .file-label { min-height: 44px; }
  .control { grid-template-columns: 4.5em minmax(0, 1fr) auto; gap: 6px; }
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rig-stage { min-height: 220px; max-height: 58vh; aspect-ratio: 4 / 3; }
  .rig-switch button { min-height: 44px; }
  .plot { height: 150px; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
