/* ButtFold. PLAN.md section 6: this is built to look like the Apple app, which is a
   requirement and not a preference. Every hex value below is taken from the live app by
   frequency of use, and --bf-stage was measured from a render rather than computed. */

:root {
  --bf-ground:        #0B0A1F;   /* page ground */
  --bf-ground-deep:   #05040E;   /* page gradient floor, footer */
  --bf-ground-raised: #181432;   /* cards, pills at rest */
  --bf-stage:         #0D0D26;   /* 3D stage clear colour: sRGB of the app's linear
                                    RGB (0.047, 0.039, 0.122), measured from a render */
  --bf-action:        #2B5CE6;   /* primary blue: buttons, selected pills */
  --bf-text-1:        #FFFFFF;
  --bf-text-2:        #6B7C93;   /* secondary text */
  --bf-text-3:        #B6BFD0;   /* dim text */
  --bf-text-4:        #8A93A8;   /* dimmer text */
  --bf-cyan:          #22E5FF;   /* accent; sheet on the ribbon */
  --bf-magenta:       #FF3D9A;   /* accent + error text; helix on the ribbon */
  --bf-amber:         #FCB900;   /* disclosure line, logo dot */
  --bf-pale-blue:     #8FB4FF;

  /* Ribbon colouring, the thing a viewer actually sees. Helix magenta, sheet cyan, coil
     slate, exactly as the app draws it. */
  --bf-helix:  var(--bf-magenta);
  --bf-sheet:  var(--bf-cyan);
  --bf-coil:   #6B7C93;

  --bf-radius: 12px;
  --bf-radius-pill: 999px;
  --bf-max: 1100px;

  /* Inter for UI, Roboto Mono for readouts and numbers: the closest web equivalents to the
     app's system font under the house rules. Apple's SF is not licensed for the web, so
     this is close rather than identical, and PLAN section 13 says so. */
  --bf-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --bf-mono: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  color-scheme: dark;
}

/* The app is dark and only dark. A light variant would not be the app, and the
   resemblance is the requirement, so this commits to one look and paints it explicitly
   rather than borrowing the viewer's. */

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

body {
  background: linear-gradient(180deg, var(--bf-ground) 0%, var(--bf-ground) 60%,
                              var(--bf-ground-deep) 100%);
  background-attachment: fixed;
  color: var(--bf-text-3);
  font-family: var(--bf-sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--bf-pale-blue); text-decoration: none; }
a:hover { color: var(--bf-cyan); text-decoration: underline; }

.wrap { max-width: var(--bf-max); margin: 0 auto; padding: 0 20px; }

/* ---------------------------------------------------------------- the house header ---
   Structure is mandatory and comes from the marcs-vibe-coding skill: amber logo dot,
   "Marc C. Deller, D.Phil." linking to marcdeller.com, a contact link, sticky, never
   overlapping content. The same skill says the header takes the app's own palette rather
   than the default blue, so it sits on --bf-ground with --bf-action accents. The dot stays
   #FCB900, which happens to be both the skill's amber and PhoneFold's. */

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(11, 10, 31, 0.86);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.site-header .wrap {
  display: flex; align-items: center; gap: 14px;
  min-height: 56px; flex-wrap: wrap;
}
.logo-dot {
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--bf-amber); flex: 0 0 auto;
  box-shadow: 0 0 12px rgba(252, 185, 0, 0.55);
}
.site-header .who { color: var(--bf-text-1); font-weight: 600; font-size: 15px; }
.site-header .who:hover { color: var(--bf-cyan); }
.site-header .spacer { flex: 1 1 auto; }
.site-header .header-link { color: var(--bf-text-2); font-size: 14px; }
.site-header .header-link:hover { color: var(--bf-cyan); }
.site-header .shop-link { color: var(--bf-action); font-weight: 600; font-size: 14px; }
.site-header .shop-link:hover { color: var(--bf-cyan); }

/* The header must stay one line. Measured at 390 px: the full shop-link wording wraps to
   three lines and pushes the protein name, the disclosure and the stage below the fold. */
.on-narrow { display: none; }
@media (max-width: 720px) {
  .on-wide { display: none; }
  .on-narrow { display: inline; }
  .site-header .wrap { gap: 10px; flex-wrap: nowrap; }
  .site-header .who { font-size: 14px; white-space: nowrap; }
  .site-header .header-link { display: none; }   /* the footer carries the contact link */
  .title-block { padding: 18px 0 10px; }
  .controls { gap: 6px 12px; }
  .control { flex: 1 1 100%; }
  .segmented { overflow-x: auto; max-width: 100%; }
  /* On a phone the page scrolls anyway, so the stage takes a share of the viewport rather
     than what is left over: half of it, which is what the app gives it. */
  .stage-wrap { height: clamp(280px, 48vh, 460px); }
}

/* ---------------------------------------------------------------- title block -------
   The protein name as a large title, and directly beneath it in amber the engine
   disclosure line. Exactly where the phone app puts it: in the frame, not in an About
   page. PLAN section 7. */

.title-block { padding: 16px 0 6px; }
.protein-name {
  font-size: clamp(26px, 4.2vw, 36px); line-height: 1.1;
  font-weight: 700; color: var(--bf-text-1); margin: 0;
  letter-spacing: -0.02em;
}
.protein-sub { color: var(--bf-text-2); font-size: 14px; margin: 4px 0 0; }
.disclosure {
  color: var(--bf-amber); font-size: 13.5px; margin: 6px 0 0;
  font-weight: 500;
}

/* ---------------------------------------------------------------- controls ------------
   One wrapping row rather than three stacked ones. Measured: three rows cost about 110 px
   and pushed the stage and the transport off a 900 px screen. The structure is the subject;
   the controls are not. */

.controls {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 20px; margin: 10px 0 12px;
}
.control { display: flex; align-items: center; gap: 8px; }
.control > .label {
  color: var(--bf-text-2); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; white-space: nowrap;
}
.segmented {
  display: inline-flex; background: var(--bf-ground-raised);
  border-radius: var(--bf-radius-pill); padding: 3px; gap: 2px;
  border: 1px solid rgba(255, 255, 255, 0.05);
}
.segmented button {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--bf-text-3); font: 500 12.5px/1 var(--bf-sans);
  padding: 7px 12px; border-radius: var(--bf-radius-pill);
  transition: background 120ms ease, color 120ms ease;
  white-space: nowrap;
}
.segmented button:hover:not([aria-pressed="true"]) { color: var(--bf-text-1); }
.segmented button[aria-pressed="true"] { background: var(--bf-action); color: #fff; }
.segmented button[disabled] { opacity: 0.4; cursor: not-allowed; }

/* ---------------------------------------------------------------- the viewer ----------
   The structure and its two time series, side by side. The charts used to sit below the
   readouts, where they added about 130 px and pushed the transport off a short screen; the
   stage is the subject, so it keeps roughly 60% of the width and the charts take the rest
   and stack. Below 900 px the two columns become one, because a 40% column of a phone is
   not a chart, it is a smear. */

.viewer {
  display: grid;
  grid-template-columns: minmax(0, 60fr) minmax(0, 40fr);
  gap: 14px;
  /* Stretch, not start: the LEFT column decides the row height - it is the stage, whose
     height is computed from the viewport - and the charts fill whatever that turns out to
     be. With `align-items: start` the two columns sized themselves independently and the
     charts came out 25 px taller than the stage at 1366x768, which put the transport back
     below the fold. */
  align-items: stretch;
}
.viewer-main { min-width: 0; }
.viewer-side { display: flex; flex-direction: column; min-height: 0; }

@media (max-width: 900px) {
  .viewer { grid-template-columns: 1fr; }
  .viewer-side { height: auto; }
}

/* ---------------------------------------------------------------- the stage ---------- */

.stage-wrap {
  position: relative; background: var(--bf-stage);
  border-radius: var(--bf-radius); overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.05);
  /* Sized from what is left after everything above and below it, so the transport is on
     screen without scrolling. The subtrahend is measured, not guessed: header 56, title
     block ~86, controls ~44, readouts ~52, transport ~56, plus margins. Measured in a real
     browser at four common sizes rather than added up on paper: 330 was five pixels short
     at 1280x800 and 1366x768, which is exactly the kind of arithmetic that is right when
     written and wrong three commits later. tests/stage_screenshot.mjs now checks the Play
     bar's bottom edge against the fold at each of them. The floor keeps a very short window
     usable by letting the page scroll rather than crushing the stage. */
  height: clamp(260px, calc(100vh - 344px), 560px);
  touch-action: none;   /* drag orbits the structure; it must not scroll the page */
}
.stage-wrap canvas { display: block; width: 100%; height: 100%; }

/* The persistent engine badge. Small, in a corner, always visible while anything plays,
   and it never scrolls away while the animation runs. PLAN section 7. */
.stage-badge {
  position: absolute; left: 12px; bottom: 12px;
  background: rgba(5, 4, 14, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--bf-radius-pill);
  padding: 6px 12px; font: 500 12px/1.3 var(--bf-mono);
  color: var(--bf-text-3); max-width: calc(100% - 24px);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.stage-badge .engine { color: var(--bf-amber); }
.stage-badge .where { color: var(--bf-text-2); }

.stage-hint {
  position: absolute; right: 12px; bottom: 12px;
  font: 500 11px/1 var(--bf-mono); color: var(--bf-text-2);
  opacity: 0.7; pointer-events: none;
}

/* ---------------------------------------------------------------- readouts ------------ */

.readouts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
  gap: 2px; margin: 2px 0 0;
  background: rgba(255, 255, 255, 0.04); border-radius: 0 0 var(--bf-radius) var(--bf-radius);
  overflow: hidden;
}
/* One row beside the stage, always. Two rows cost 57 px and put the transport back below
   the fold, so the labels are short enough to fit six columns in a 60% one: the units are
   in the label and the values speak for themselves. */
@media (min-width: 901px) {
  .readouts { grid-template-columns: repeat(6, 1fr); }
  .readout .k { font-size: 9.5px; letter-spacing: 0.06em; white-space: nowrap; }
}
.readout { background: var(--bf-ground); padding: 7px 12px; }
.readout .k {
  display: block; color: var(--bf-text-2); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.09em;
}
.readout .v {
  display: block; font-family: var(--bf-mono); font-size: 15px;
  color: var(--bf-text-1); font-variant-numeric: tabular-nums; margin-top: 2px;
}
.readout .v.helix { color: var(--bf-helix); }
.readout .v.sheet { color: var(--bf-sheet); }
.readout .v.coil  { color: var(--bf-coil); }

/* ---------------------------------------------------------------- charts -------------- */

/* Stacked, one above the other, filling the column beside the stage. */
.charts {
  display: grid; grid-template-rows: 1fr 1fr; gap: 12px; margin: 0;
  flex: 1 1 auto; min-height: 0;
}
@media (max-width: 900px) {
  .charts { grid-template-rows: none; grid-template-columns: 1fr 1fr; margin: 14px 0 0; }
}
@media (max-width: 620px) { .charts { grid-template-columns: 1fr; } }
.chart {
  background: var(--bf-ground-raised); border-radius: var(--bf-radius);
  padding: 10px 12px 8px; border: 1px solid rgba(255, 255, 255, 0.04);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
}
.chart h3 {
  margin: 0 0 6px; font: 500 11px/1 var(--bf-sans); color: var(--bf-text-2);
  text-transform: uppercase; letter-spacing: 0.09em;
}
/* `flex: 1 1 0` with `min-height: 0`, NOT `1 1 auto` with a minimum. A canvas's intrinsic
   size comes from its width and height attributes, which `prepare()` in player.js sets from
   the element's own measured size - so an auto basis feeds the canvas's height back into
   itself and the chart column grows a little every layout. */
.chart canvas { display: block; width: 100%; flex: 1 1 0; min-height: 0; }
.chart .legend { font: 500 10px/1.4 var(--bf-mono); color: var(--bf-text-2); }
.chart .legend .h { color: var(--bf-helix); }
.chart .legend .e { color: var(--bf-sheet); }
.chart .legend .c { color: var(--bf-coil); }

/* ---------------------------------------------------------------- transport ----------- */

.transport {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 12px 0 6px;
}
.play {
  appearance: none; border: 0; cursor: pointer;
  background: var(--bf-action); color: #fff;
  font: 600 15px/1 var(--bf-sans);
  padding: 12px 26px; border-radius: var(--bf-radius-pill);
  min-width: 116px;
}
.play:hover { filter: brightness(1.12); }
.play:active { filter: brightness(0.94); }
.seek { flex: 1 1 200px; min-width: 160px; accent-color: var(--bf-action); }
.frame-count {
  font-family: var(--bf-mono); font-size: 13px; color: var(--bf-text-2);
  font-variant-numeric: tabular-nums;
}
.volume-label {
  font-size: 11px; color: var(--bf-text-2); text-transform: uppercase;
  letter-spacing: 0.08em;
}
.volume { flex: 0 1 110px; accent-color: var(--bf-action); }

/* What the score cost, stated rather than hidden. A trajectory that loses most of its
   contact events to the per-bar cap should say so instead of just sounding thin. */
.score-summary {
  margin: 0; font: 500 12px/1.5 var(--bf-mono); color: var(--bf-text-2);
}
.audio-note { margin: 4px 0 0; font-size: 13px; color: var(--bf-magenta); }
.audio-note:empty { display: none; }
.live-status { margin: 4px 0 0; font: 500 12px/1.5 var(--bf-mono); color: var(--bf-cyan); }
.live-status:empty { display: none; }

/* ---------------------------------------------------------------- gallery ------------- */

.section-head {
  display: flex; align-items: baseline; gap: 12px; margin: 34px 0 12px;
}
.section-head h2 {
  margin: 0; font-size: 20px; color: var(--bf-text-1); font-weight: 650;
  letter-spacing: -0.01em;
}
.section-head .note { color: var(--bf-text-2); font-size: 13px; }

.gallery {
  display: flex; gap: 12px; overflow-x: auto; padding: 4px 0 14px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.card {
  flex: 0 0 190px; scroll-snap-align: start;
  background: var(--bf-ground-raised); border-radius: var(--bf-radius);
  border: 1px solid rgba(255, 255, 255, 0.05);
  padding: 14px; cursor: pointer; text-align: left;
  appearance: none; color: inherit; font: inherit;
  transition: border-color 120ms ease, transform 120ms ease;
}
.card:hover { transform: translateY(-2px); border-color: rgba(255, 255, 255, 0.14); }
.card[aria-pressed="true"] { border-color: var(--bf-action); box-shadow: 0 0 0 1px var(--bf-action); }
.card .name { display: block; color: var(--bf-text-1); font-weight: 600; font-size: 15px; }
.card .sub {
  display: block; color: var(--bf-text-2); font-size: 12px; margin-top: 4px;
  font-family: var(--bf-mono);
}
.card .quality {
  display: block; color: var(--bf-text-4); font-size: 11px; margin-top: 8px;
  font-family: var(--bf-mono);
}

/* ---------------------------------------------------------------- honesty ------------- */

.honesty {
  margin: 30px 0; padding: 18px 20px;
  background: var(--bf-ground-raised); border-radius: var(--bf-radius);
  border-left: 3px solid var(--bf-amber);
  color: var(--bf-text-3); font-size: 15px;
}
.honesty p { margin: 0; }

/* ---------------------------------------------------------------- shop window --------- */

.shop-feature {
  background: var(--bf-ground-raised); border-radius: var(--bf-radius);
  border: 1px solid rgba(255, 255, 255, 0.06);
  padding: 22px 24px; margin: 4px 0 8px;
}
.shop-feature h3 {
  margin: 0 0 8px; font-size: 22px; color: var(--bf-text-1); font-weight: 650;
  letter-spacing: -0.01em;
}
.shop-feature .blurb { margin: 0 0 12px; color: var(--bf-text-3); max-width: 60ch; }
.shop-feature .platforms {
  margin: 0 0 16px; font: 500 12px/1.5 var(--bf-mono); color: var(--bf-text-2);
}
.store-link {
  display: inline-block; background: var(--bf-action); color: #fff;
  font: 600 15px/1 var(--bf-sans); padding: 13px 24px;
  border-radius: var(--bf-radius-pill); text-decoration: none;
}
.store-link:hover { filter: brightness(1.12); color: #fff; text-decoration: none; }

/* Not dimmed. An app in review is a fact about the app, not a disabled state, and greying
   it out would read as "broken" rather than as "not yet". */
.status-tag {
  display: inline-block; margin: 0 0 10px;
  background: rgba(252, 185, 0, 0.14); color: var(--bf-amber);
  border: 1px solid rgba(252, 185, 0, 0.32);
  border-radius: var(--bf-radius-pill); padding: 8px 16px;
  font: 500 13px/1 var(--bf-sans);
}
.fallback-link { display: block; font-size: 14px; }

.card-static { cursor: default; }
.card-static:hover { transform: none; border-color: rgba(255, 255, 255, 0.05); }
.card .blurb-small {
  display: block; color: var(--bf-text-4); font-size: 12px; margin-top: 8px;
  line-height: 1.45;
}
.gallery .card { flex: 0 0 220px; }

/* ---------------------------------------------------------------- footer -------------- */

.site-footer {
  margin-top: 42px; padding: 26px 0 40px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  color: var(--bf-text-2); font-size: 13px;
}
.site-footer .joke { color: var(--bf-text-4); font-style: italic; }

/* ---------------------------------------------------------------- utilities ----------- */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.no-wasm-note {
  display: none; color: var(--bf-magenta); font-size: 13px; margin-top: 8px;
}
body.no-wasm .no-wasm-note { display: block; }

@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
}
