@charset "utf-8";
/* ============================================================
   Flex Angles — a native-iOS mockup.

   A hard reset of the visual language. The two rejected builds
   inherited a printed-guidebook look: paper ground, serif display
   titles, an intro paragraph, headed prose sections, horizontal
   rules between everything. That is a document, and documents are
   read while apps are operated. None of it is here.

   Built in the order gestalt-for-ui.md prescribes.

   1 FIGURE     named per screen, in the comments in app.js
   2 OBJECTS    a screen is: nav bar, 1–4 grouped lists, tab bar
   3 GROUND     dark renders four distinct surfaces, light three
                plus a fill. ONE surface token per theme is what
                flattened v1, so none of them is reused loosely
   4 SPACING    every gap is a ratio, never an absolute. Inside a
                group 0 (a hairline). Between groups 32px plus the
                header's own 6px — measured at 54–134px on screen
   5 TRAITS     one meaning each, in the table below
   6 BOUNDARIES the grouped inset list, which is common region,
                and nothing else. Zero <hr>. No rule is a spacer
   7 TYPE       last. Three families, one job each, and never two
                inside one sentence

   TRAITS — one meaning each, nothing else may use them
   ┌──────────────────────────┬─────────────────────────────────┐
   │ accent, on a whole row   │ this row is an ACTION           │
   │ accent, on the value     │ this row is content and its     │
   │   only (.row--pend)      │ value is what needs doing       │
   │ accent, tinted tab       │ the tab you are on              │
   │   — and nothing else. Never emphasis, never data. v1's     │
   │     accent did four jobs, so it carried none               │
   │ ui-serif, tabular        │ a measured angle in degrees      │
   │ ui-monospace             │ a literal file path or code name │
   │ green                    │ complete                        │
   │ amber                    │ inside the measurement error    │
   │ uppercase 12px muted     │ a section header. grouping only │
   │ muted right text         │ this row's secondary value      │
   │ chevron pointing RIGHT   │ this row pushes a new screen    │
   │ chevron pointing DOWN    │ this row expands in place       │
   │ no chevron + 32% ink     │ locked, not tappable            │
   └──────────────────────────┴─────────────────────────────────┘
   ============================================================ */


/* ---------------------------------------------------------- 3. GROUND
   Four levels per theme, and they are steps you can see rather
   than a few percent of each other. In dark, ground is true black
   and a row is #1c1c1e — an 11% lightness step, reinforced by the
   12px radius and by section headers sitting on the ground while
   rows sit on the raised level.

   Light is defined on bare :root so a page with no theme attribute
   and no media query still has a complete palette. Dark is
   redefined twice: once under prefers-color-scheme guarded against
   an explicit light choice, once under [data-theme=dark] so the
   in-app switch wins in both directions. */
:root {
  color-scheme: light dark;

  --l0: #f2f2f7;                    /* ground — the page behind everything */
  --l1: #ffffff;                    /* raised — list rows, cards */
  --l2: rgba(249, 249, 249, 0.82);  /* chrome — nav bar, tab bar, translucent */
  --l3: #f2f2f7;                    /* sheet ground — above a dimmed screen */
  --l4: #ffffff;                    /* raised inside a sheet */

  --ink:   #000000;
  --ink-2: rgba(60, 60, 67, 0.62);  /* secondary text, right-aligned values */
  --ink-3: rgba(60, 60, 67, 0.32);  /* chevrons, disabled */
  --sep:   rgba(60, 60, 67, 0.26);  /* the inset row separator */
  --hair:  rgba(60, 60, 67, 0.20);  /* the nav/tab hairline */
  --fill:  rgba(120, 120, 128, 0.12);
  --press: rgba(60, 60, 67, 0.08);

  --accent: #007aff;
  --ok:     #248a3d;
  --warn:   #b25000;
  --scrim:  rgba(0, 0, 0, 0.4);

  --ui:  -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
  --num: ui-serif, "New York", Georgia, serif;

  --gut: 16px;                      /* the inset of every grouped list */
  --gap-out: 32px;                  /* between groups */
  --nav-h: 44px;
  --tab-h: 49px;
  --r: 12px;                        /* iOS grouped-list corner */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --l0: #000000;
    --l1: #1c1c1e;
    --l2: rgba(22, 22, 24, 0.82);
    --l3: #1c1c1e;
    --l4: #2c2c2e;

    --ink:   #ffffff;
    --ink-2: rgba(235, 235, 245, 0.62);
    --ink-3: rgba(235, 235, 245, 0.32);
    --sep:   rgba(84, 84, 88, 0.65);
    --hair:  rgba(84, 84, 88, 0.5);
    --fill:  rgba(120, 120, 128, 0.24);
    --press: rgba(235, 235, 245, 0.1);

    --accent: #0a84ff;
    --ok:     #30d158;
    --warn:   #ff9f0a;
    --scrim:  rgba(0, 0, 0, 0.55);
  }
}

:root[data-theme="dark"] {
  --l0: #000000;
  --l1: #1c1c1e;
  --l2: rgba(22, 22, 24, 0.82);
  --l3: #1c1c1e;
  --l4: #2c2c2e;

  --ink:   #ffffff;
  --ink-2: rgba(235, 235, 245, 0.62);
  --ink-3: rgba(235, 235, 245, 0.32);
  --sep:   rgba(84, 84, 88, 0.65);
  --hair:  rgba(84, 84, 88, 0.5);
  --fill:  rgba(120, 120, 128, 0.24);
  --press: rgba(235, 235, 245, 0.1);

  --accent: #0a84ff;
  --ok:     #30d158;
  --warn:   #ff9f0a;
  --scrim:  rgba(0, 0, 0, 0.55);
}


/* ---------------------------------------------------------- reset */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;                 /* the view scrolls, not the page */
}

body {
  background: var(--l0);
  color: var(--ink);
  font: 400 17px/1.35 var(--ui);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a { color: var(--accent); text-decoration: none; }
p { margin: 0; }
ol, ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3 { margin: 0; font-weight: 600; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }


/* ---------------------------------------------------------- shell */
#app {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}


/* ---------------------------------------------------------- nav bar
   Back chevron plus the previous screen's NAME on the left, the
   current title centred. The hairline appears only once scrolled —
   iOS behaviour and the reason a nav bar reads as chrome rather
   than as a header band. */
.nav {
  position: relative;
  z-index: 40;
  flex: 0 0 auto;
  padding-top: env(safe-area-inset-top);
  background: var(--l2);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 0.5px 0 rgba(0, 0, 0, 0);
  transition: box-shadow 0.2s linear;
}
.nav[data-scrolled="true"] { box-shadow: 0 0.5px 0 var(--hair); }
/* on a large-title screen at rest the bar shares the page ground, so
   the title area reads as one bar rather than as a strip above one.
   The blur and the hairline both arrive on the first scroll. */
.nav[data-large="true"][data-scrolled="false"] {
  background: var(--l0);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.nav__row {
  position: relative;
  height: var(--nav-h);
  display: flex;
  align-items: center;
}

.nav__title {
  position: absolute;
  left: 100px; right: 100px;
  text-align: center;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 0.18s linear;
}
.nav__title[data-hidden="true"] { opacity: 0; }

/* 44px tall, and the label extends the target left to the edge */
.nav__back, .nav__act {
  position: relative;
  z-index: 1;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--accent);
  font-size: 17px;
  letter-spacing: -0.01em;
  padding: 0 8px 0 6px;
  /* a hard cap only as a backstop — fitNav() in app.js decides the
     label and the title's matching inset by measuring both strings */
  max-width: 170px;
}
.nav__act { margin-left: auto; padding: 0 10px; }
.nav__back[hidden], .nav__act[hidden] { display: none; }
.nav__back span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav__back:active, .nav__act:active { opacity: 0.4; }
.nav__chev { flex: 0 0 auto; }


/* ---------------------------------------------------------- view stack */
.stack {
  position: relative;
  flex: 1 1 auto;
  overflow: hidden;
}

.view {
  position: absolute;
  inset: 0;
  background: var(--l0);
  overflow: hidden;
}
.view[hidden] { display: none !important; }

.scroll {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* fixed chrome needs matching scroll padding — v1 finding 7 */
  padding-bottom: calc(var(--tab-h) + env(safe-area-inset-bottom) + 28px);
  scrollbar-width: none;
}
/* a phone shows a transient indicator, a desktop browser shows a
   permanent bar. Hide it so the mockup reads the way the phone will. */
.scroll::-webkit-scrollbar { width: 0; height: 0; }
.view[data-notabs] .scroll { padding-bottom: calc(env(safe-area-inset-bottom) + 28px); }

/* motion: a push from the right for a forward navigation, and that
   is all. The outgoing screen slides a third of the way and dims,
   which is the iOS parallax. No page-load reveals anywhere. */
@keyframes push-in   { from { transform: translate3d(100%, 0, 0); } to { transform: none; } }
@keyframes push-out  { from { transform: none; } to { transform: translate3d(-32%, 0, 0); opacity: 0.6; } }
@keyframes pop-in    { from { transform: translate3d(-32%, 0, 0); opacity: 0.6; } to { transform: none; opacity: 1; } }
@keyframes pop-out   { from { transform: none; } to { transform: translate3d(100%, 0, 0); } }

.view[data-anim="push-in"]  { animation: push-in  0.34s cubic-bezier(0.32, 0.72, 0, 1) both; z-index: 2; box-shadow: -8px 0 24px rgba(0,0,0,0.14); }
.view[data-anim="push-out"] { animation: push-out 0.34s cubic-bezier(0.32, 0.72, 0, 1) both; z-index: 1; }
.view[data-anim="pop-in"]   { animation: pop-in   0.3s  cubic-bezier(0.32, 0.72, 0, 1) both; z-index: 1; }
.view[data-anim="pop-out"]  { animation: pop-out  0.3s  cubic-bezier(0.32, 0.72, 0, 1) both; z-index: 2; box-shadow: -8px 0 24px rgba(0,0,0,0.14); }

@media (prefers-reduced-motion: reduce) {
  .view[data-anim] { animation-duration: 0.01s; }
}


/* ---------------------------------------------------------- tab bar
   Four tabs, icon above a 10px label, active tinted, everything
   else muted. 49px plus the safe area, and it is the reason every
   scroller carries bottom padding. */
.tabs {
  position: relative;
  z-index: 40;
  flex: 0 0 auto;
  display: flex;
  background: var(--l2);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 -0.5px 0 var(--hair);
  padding-bottom: env(safe-area-inset-bottom);
}

.tab {
  flex: 1 1 0;
  min-height: var(--tab-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 4px 0 3px;
  color: var(--ink-2);
}
.tab[aria-selected="true"] { color: var(--accent); }
.tab__lab { font-size: 10px; line-height: 1.1; letter-spacing: 0.01em; font-weight: 500; }
.tab:active { opacity: 0.5; }
/* the first-run screens own the whole window, and .tabs sets its own
   display, so the UA stylesheet's [hidden] rule loses without this —
   the same reason .view[hidden] needs one. */
.tabs[hidden] { display: none !important; }
.tab svg { display: block; }


/* ---------------------------------------------------------- large title
   Shrinks to the small centred title in the nav bar as you scroll.
   Only on the three tab roots — a pushed screen gets the small
   centred title straight away. */
.large {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
  padding: 4px var(--gut) 0;
  margin-bottom: 8px;
}


/* ---------------------------------------------------------- 4. SPACING + 6. BOUNDARIES
   The section header does the grouping: 32px above, 6px below. The
   ratio to the 0px between two rows inside one group is as large as
   it gets, which is the point — v1's finding 3 was this inverted. */
.sec { margin-top: var(--gap-out); }
.sec:first-child { margin-top: 18px; }
.large + .sec { margin-top: 10px; }

.sec__h {
  padding: 0 var(--gut) 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.sec__foot {
  padding: 7px var(--gut) 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-2);
}
.sec__foot strong { font-weight: 600; color: var(--ink); }

/* the grouped inset list — the single most important thing on the
   screen. Raised surface, 12px radius, inset 16px from both edges. */
.rows {
  background: var(--l1);
  border-radius: var(--r);
  margin: 0 var(--gut);
  overflow: hidden;
}


/* ---------------------------------------------------------- the row */
.row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 11px var(--gut);
  text-align: left;
  color: inherit;
  background: none;
}
a.row, button.row { -webkit-user-select: none; user-select: none; }
a.row:active, button.row:active { background: var(--press); }

/* separators start at the TEXT and stop at the right edge. Never
   full-bleed. This is the detail that most says native or not.

   One rule for every kind of group member — row, disclosure, photo
   slot, table — so nothing in a group can drift out of the pattern
   later. The first child never gets one. */
.rows > * { position: relative; }
.rows > * + *::before {
  content: "";
  position: absolute;
  left: var(--gut);
  right: 0;
  top: 0;
  height: 0.5px;
  background: var(--sep);
}
/* a row with a mark or a number before its text insets the
   separator to where the TEXT starts, not to the row's own padding */
.rows > .row--lead + .row--lead::before { left: 58px; }

.row__body { min-width: 0; flex: 1 1 auto; display: block; text-align: left; }
.row__t {
  display: block;
  font-size: 17px;
  letter-spacing: -0.02em;
  line-height: 1.25;
}
/* 2px to its own title against 32px to the next group — the ratio
   is what groups a row with its own subtitle */
.row__s {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  line-height: 1.3;
  color: var(--ink-2);
}
.row__val {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 17px;
  letter-spacing: -0.02em;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.row__chev { flex: 0 0 auto; color: var(--ink-3); }

.row[data-locked] { color: var(--ink-3); }
.row[data-locked] .row__val { color: var(--ink-3); }
.row[aria-disabled="true"] { pointer-events: none; }

/* the tint is the app's own controls, and a whole row that is an
   action rather than a destination is one of them */
.row--do .row__t { color: var(--accent); }
.row--warn .row__t { color: var(--warn); }
/* a row whose CONTENT is content and whose VALUE is the call to
   action — a tree question, an unconfirmed reading. Tinting the whole
   row here put four blue paragraphs on one screen and flattened it. */
.row--pend .row__val { color: var(--accent); }

/* A row with a leading mark. The mark's 24px circle is centred in a
   44px target, and the row's left padding drops to 6px so the circle
   still LOOKS like it starts at 16px. Text then starts at 58px, which
   is where the separator is inset to. */
.row--lead { padding: 4px var(--gut) 4px 6px; gap: 8px; }
.row--lead .row__body { padding: 7px 0; }
.row__lead {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
}
.mark {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1.6px solid var(--ink-3);
  display: grid;
  place-items: center;
  color: transparent;
}
.mark[data-on="true"] { background: var(--ok); border-color: var(--ok); color: #fff; }
.mark svg { display: block; }

/* the position number, when a row needs one. Not accent — accent is
   the app's controls only, and v1's finding 4 was list numerals
   wearing the accent. */
.pos {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink-2);
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}


/* ---------------------------------------------------------- 7. TYPE — the measurement
   ui-serif maps to New York on Apple platforms, which is
   characterful and entirely local. It is used for exactly one
   thing: a measured angle in degrees. Never inside a sentence. */
.deg {
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  font-weight: 400;
}
.deg--xl { font-size: 68px; line-height: 1; }
.deg--l  { font-size: 34px; line-height: 1.05; }
.deg--m  { font-size: 22px; line-height: 1.1; }
.deg--s  { font-size: 17px; }
/* inline, at 17 or 22px, New York's ° needs no help. At display size
   it becomes a prominent ring, so the two big sizes shrink and raise
   it — checked on screen, both ways round. */
.deg__u { color: var(--ink-2); }
.deg--xl .deg__u,
.deg--l .deg__u { font-size: 0.5em; vertical-align: 0.55em; }

code {
  font: 500 0.88em/1 ui-monospace, "SF Mono", Menlo, monospace;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--fill);
}


/* ---------------------------------------------------------- next-up row
   The strongest idea in v1, kept: one verb plus the specific thing,
   never "Continue". It gets a region of its own, its own section,
   and a tinted verb. Three stacked cues on one row and nothing
   else on the screen gets more than one, so it stays the figure. */
.next { display: flex; align-items: center; gap: 12px; min-height: 60px; }
.next__t { display: block; font-size: 17px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.25; }
.next__s { display: block; margin-top: 3px; font-size: 13px; line-height: 1.3; color: var(--ink-2); }
.next__ic {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  display: grid;
  place-items: center;
}
.next--wait .next__ic { background: var(--warn); }


/* ---------------------------------------------------------- blocks inside a list group
   A group can hold something that is not a row — a photo slot, a
   paragraph, a table. It keeps the group's surface and radius so it
   stays one object. */
.pad { padding: 14px var(--gut); }
.pad p + p { margin-top: 10px; }

.body {
  font-size: 15px;
  line-height: 1.47;
  color: var(--ink);
}
.body--2 { color: var(--ink-2); }

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 4px 9px;
  border-radius: 7px;
  background: var(--fill);
  color: var(--ink-2);
}
.chip--k { color: var(--ink); }

/* the photo slot — ONE object with two states, not two objects.

   His six front split graphics ship in assets/; the other slots have
   no file yet and still name the one they want. The placeholder is
   the DEFAULT and app.js adds .is-ok once the image has actually
   loaded, so a missing file resolves to the placeholder without
   flashing a broken image on the way.

   The placeholder is 16/9, his export ratio, and a loaded photo
   keeps its own shape. Those used to be the same rule: every
   graphic he exports per position is 1521x855, so the front split,
   pike and bridge sets are all 16/9 and none of them move.

   Shoulders is the exception, and it is his, not ours. He never
   exported per-position graphics for it — the shoulder material is
   six landscape A4 pages, and the photos inside them run from 0.96
   to 1.94. Four of the nine are portrait. Forcing those into 16/9
   cuts the legs off a standing body, and a body cropped at the knee
   reads as a broken image. That is a worse break in the set than a
   hero that is taller on one screen, so the ratio gives way and the
   photo survives. Within a skill the shapes still agree, because
   they came off the same page.

   No border, no shadow, no radius. The slot is full bleed inside
   .rows, which is already the enclosure — a second boundary inside
   the first spends contrast the photo should be getting, and the
   photo is the figure on this screen. */
.shot { display: block; margin: 0; }
.shot__i {
  display: none;
  width: 100%;
  height: auto;
  background: var(--fill);
}
.shot.is-ok .shot__i { display: block; }
.shot.is-ok .slot { display: none; }

.slot {
  aspect-ratio: 16 / 9;
  background: var(--fill);
  display: grid;
  place-items: center;
  text-align: center;
  padding: 20px;
  gap: 6px;
  align-content: center;
}
.slot__f {
  font: 500 12px/1.3 ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--ink-2);
}
.slot__w { font-size: 12px; line-height: 1.4; color: var(--ink-3); max-width: 26em; }

/* the one place there is no .rows to do the enclosing: a slot inside
   a disclosure is inset by the gutter, so square corners there read
   as a clipping bug rather than as full bleed */
.dis__b .shot__i,
.dis__b .slot { border-radius: 8px; }

.steps { counter-reset: s; }
.steps li {
  position: relative;
  counter-increment: s;
  padding: 0 0 0 30px;
  font-size: 15px;
  line-height: 1.47;
}
.steps li + li { margin-top: 11px; }
.steps li::before {
  content: counter(s);
  position: absolute;
  left: 0; top: 0.1em;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink-2);
  font: 600 11px/20px var(--ui);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.bullets li {
  position: relative;
  padding-left: 16px;
  font-size: 15px;
  line-height: 1.47;
}
.bullets li + li { margin-top: 9px; }
.bullets li::before {
  content: "";
  position: absolute;
  left: 3px; top: 0.62em;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--ink-3);
}

/* key/value inside a group — the pair gap is 2px against the 12px
   to the next pair, so the eye groups a key with its own value */
.kv { display: grid; gap: 12px; }
.kv > div > dt { font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2); }
.kv > div > dd { margin: 2px 0 0; font-size: 15px; line-height: 1.45; }


/* ---------------------------------------------------------- disclosure */
.dis { display: block; }
.dis > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 11px var(--gut);
  font-size: 17px;
  letter-spacing: -0.02em;
  list-style: none;
  cursor: pointer;
}
.dis > summary::-webkit-details-marker { display: none; }
.dis > summary:active { background: var(--press); }
/* points DOWN, not right. A right chevron already means "this pushes
   a new screen", and one trait may only carry one meaning. */
.dis__tw {
  margin-left: auto;
  color: var(--ink-3);
  transform: rotate(90deg);
  transition: transform 0.2s ease;
}
.dis[open] .dis__tw { transform: rotate(-90deg); }
.dis__b { padding: 0 var(--gut) 14px; }
.dis__b > * + * { margin-top: 11px; }


/* the clip — his drill video, one tap behind the drill's name.

   Full bleed inside .rows like the photo slot, so the corners and the
   surface are the group's and nothing is drawn twice. 16/9 because
   every one of his 111 clips is 960×540; the box holds that shape
   before the first frame arrives, so the prescription under it does
   not jump when it does. Nothing plays until someone taps play. */
.clip {
  display: block;
  margin: 0;
  aspect-ratio: 16 / 9;
  background: var(--fill);
}
.clip__v {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* the group that holds the clip gets a hairline of its own. Most of
   his clips open on a black frame, and in dark the page is black too,
   so without an edge the 12px group vanishes and the play button
   floats on the bare page (figure–ground). The border is on the
   container, where the video cannot paint over it. In light it sits
   against a black box on grey and costs nothing. */
.rows--clip { border: 0.5px solid var(--sep); }

/* his words. Body type, and a touch more air between paragraphs than
   a two-line note gets, because this is two minutes of talking. */
.talk p + p { margin-top: 12px; }

/* ---------------------------------------------------------- table
   Wide content scrolls inside its own container. The page body
   never scrolls sideways. */
.tbl-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tbl { border-collapse: collapse; width: 100%; min-width: 460px; }
.tbl th, .tbl td {
  text-align: left;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.3;
  white-space: nowrap;
}
.tbl th {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.tbl tbody tr + tr td { box-shadow: inset 0 0.5px 0 var(--sep); }
.tbl td:first-child, .tbl th:first-child { padding-left: var(--gut); }
.tbl .slot-l { font-weight: 600; font-variant-numeric: tabular-nums; }
.tbl .drill { white-space: normal; min-width: 150px; }
.tbl .mono { font-variant-numeric: tabular-nums; }
/* his log grid is four narrow columns, so it fits 390px and does not
   need the program table's 460px floor */
.tbl--log { min-width: 0; }
.tbl--log th, .tbl--log td { padding: 7px 5px; }
.tbl--log td:first-child, .tbl--log th:first-child { padding-left: var(--gut); }


/* ---------------------------------------------------------- buttons */
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 50px;
  border-radius: var(--r);
  background: var(--accent);
  color: #fff;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.btn:active { opacity: 0.75; }
.btn--2 { background: var(--fill); color: var(--accent); }
.btn[disabled] { background: var(--fill); color: var(--ink-3); pointer-events: none; }
.btn-wrap { padding: 0 var(--gut); margin-top: var(--gap-out); }

.seg {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--fill);
  border-radius: 9px;
  margin: 0 var(--gut);
}
.seg button {
  flex: 1 1 0;
  min-height: 44px;
  border-radius: 7px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
}
.seg button[aria-pressed="true"] { background: var(--l1); font-weight: 600; box-shadow: 0 0.5px 2px rgba(0,0,0,0.12); }


/* ---------------------------------------------------------- 5. TRAITS — status
   Green means complete. Amber means inside the measurement error.
   Neither is ever used for anything else. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.tag--ok { color: var(--ok); }
.tag--warn { color: var(--warn); }
.tag__dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

.callout {
  padding: 12px var(--gut);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  border-left: 3px solid var(--warn);
  background: var(--l1);
}
.callout__h { font-weight: 600; margin-bottom: 3px; }


/* ---------------------------------------------------------- measure
   Confirm or correct, never accept. The automatic guess is a
   starting position for three points the user drags. */
.mz {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--fill);
  overflow: hidden;
  touch-action: none;
}
.mz svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.mz__hint {
  position: absolute;
  left: 0; right: 0; bottom: 8px;
  text-align: center;
  font-size: 12px;
  color: var(--ink-3);
  pointer-events: none;
}
.pin {
  position: absolute;
  width: 44px; height: 44px;        /* the target, not the dot */
  margin: -22px 0 0 -22px;
  display: grid;
  place-items: center;
  touch-action: none;
  cursor: grab;
}
.pin::after {
  content: "";
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--l1);
  border: 3px solid var(--accent);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.pin[data-drag="true"] { cursor: grabbing; }
.pin[data-drag="true"]::after { transform: scale(1.25); }
.pin__l {
  position: absolute;
  top: -6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  pointer-events: none;
}

.read {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 16px var(--gut) 14px;
}
.read__meta { font-size: 13px; line-height: 1.4; color: var(--ink-2); }


/* ---------------------------------------------------------- sheet
   Sheets, not pages, for anything modal: rounded top corners, a
   grabber, and the screen behind it visible and dimmed. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--scrim);
  opacity: 0;
  transition: opacity 0.28s ease;
}
.scrim[data-open="true"] { opacity: 1; }

.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 61;
  max-height: 88%;
  display: flex;
  flex-direction: column;
  background: var(--l3);
  color: var(--ink);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -1px 24px rgba(0, 0, 0, 0.28);
  transform: translate3d(0, 100%, 0);
  transition: transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
  padding-bottom: env(safe-area-inset-bottom);
}
.sheet[data-open="true"] { transform: none; }
.grabber {
  flex: 0 0 auto;
  width: 36px; height: 5px;
  border-radius: 3px;
  background: var(--ink-3);
  margin: 6px auto 0;
}
.sheet__h {
  flex: 0 0 auto;
  padding: 12px var(--gut) 4px;
  text-align: center;
}
.sheet__t { font-size: 17px; font-weight: 600; letter-spacing: -0.02em; }
.sheet__s { margin-top: 4px; font-size: 13px; line-height: 1.4; color: var(--ink-2); }
.sheet__b { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; padding-bottom: 14px; scrollbar-width: none; }
.sheet__b::-webkit-scrollbar { width: 0; height: 0; }
/* inside a sheet the raised level has to step up again, or the rows
   disappear into the sheet's own surface */
.sheet .rows { background: var(--l4); }
@media (prefers-reduced-motion: reduce) {
  .sheet, .scrim { transition-duration: 0.01s; }
}


/* ---------------------------------------------------------- text field
   The same control as the mockup select below — same height, same
   fill, same radius — because there is no reason for two. Used by
   the sport question and by his log's cells. */
.txt {
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  font: 400 17px var(--ui);
  color: var(--ink);
  background: var(--fill);
  border: 0;
  border-radius: 9px;
  -webkit-appearance: none;
  appearance: none;
}
.txt::placeholder { color: var(--ink-3); }
/* a grid cell. Still 44px tall — this gets tapped with a thumb in
   the middle of a rest interval, which is the least forgiving case
   in the whole app, so it does not get to be the exception. */
.txt--cell {
  width: 100%;
  min-width: 52px;
  padding: 0 8px;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  border-radius: 7px;
}


/* ---------------------------------------------------------- lede
   One line of context between a pushed screen's question and its
   answers. The existing idiom for context is .sec__foot, which
   sits UNDER a group — a fork screen needs it above, or the reader
   picks a branch before reading why. Same size as .body body--2,
   with the list's own 16px gutter. */
.lede {
  padding: 0 var(--gut);
  margin: 0 0 6px;
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink-2);
}
.large + .lede { margin-top: 2px; }


/* ---------------------------------------------------------- number in a row
   A typed angle. Every other value in the app is read-only and
   sits in .row__val; this is the one screen where the value is
   entered, so it reuses .txt's height, fill and radius and takes
   .row__val's place. Serif and tabular, because the TRAITS table
   gives that pairing exactly one meaning — an angle in degrees —
   and a typed angle is still an angle. */
.numin {
  position: relative;
  flex: 0 0 auto;
  margin-left: auto;
}
.txt--val {
  width: 106px;
  text-align: right;
  padding-right: 26px;                /* the ° sits in here */
  font-family: var(--num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
/* inside the field's own box and full height, so it centres exactly
   the way the input centres its text. Sitting outside as a flex
   sibling put it a line too high — a form control's baseline is not
   its text's baseline. */
.numin__u {
  position: absolute;
  top: 0; right: 11px;
  height: 100%;
  display: flex;
  align-items: center;
  font-family: var(--num);
  font-size: 17px;
  color: var(--ink-2);
  pointer-events: none;
}

/* a derived value with its own margin under it. .pad only spaces
   p + p, and this block stacks a key/value, a status tag and one or
   two paragraphs, all of which are one statement about one number. */
.dec { padding: 14px var(--gut); }
.dec > * + * { margin-top: 9px; }
.dec .tag { display: flex; }


/* ---------------------------------------------------------- the guide
   One scale for the whole guide — the three prep screens and the
   four positions. Everything is a step up from the app's own
   sizes, and the step is the point: this is the only part of the
   app read while you are on the floor with the phone somewhere
   else, so 15px body copy is not enough.

   Type sizes and the distance each one is for:
     72px  the hold count — 16mm of cap height, about 27 arcmin at
           two metres, where 20/20 acuity runs out around 5
     28px  the position name, glanceable across a room
     20px  steps, cues and row titles — comfortable at arm's
           length, marginal at two metres. Steps get read before
           you get into the position, not during it.

   Scoped under .gd so nothing outside the guide moves. */
.gd .row__t { font-size: 20px; line-height: 1.3; }
.gd .row__s { font-size: 15px; margin-top: 3px; }
.gd .row__val { font-size: 17px; }
.gd .sec__foot { font-size: 15px; }
.gd .lede { font-size: 17px; }
.gd .body { font-size: 20px; line-height: 1.5; }

.gd .steps li { font-size: 20px; line-height: 1.45; padding-left: 40px; }
.gd .steps li + li { margin-top: 16px; }
.gd .steps li::before {
  width: 28px; height: 28px;
  font-size: 14px;
  line-height: 28px;
  top: -0.05em;
}
.gd .bullets li { font-size: 20px; line-height: 1.45; padding-left: 22px; }
.gd .bullets li + li { margin-top: 14px; }
.gd .bullets li::before { left: 5px; top: 0.66em; width: 5px; height: 5px; }

.gd__n {
  display: block;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.15;
}
.gd__alt {
  display: block;
  margin-top: 5px;
  font-size: 15px;
  color: var(--ink-2);
}


/* ---------------------------------------------------------- hold timer
   Not the serif: the TRAITS table gives ui-serif + tabular one
   meaning, an angle in degrees, and seconds are not degrees. So
   the count is the UI face at display size with tabular figures,
   which keeps the digits from shifting as it counts down.

   Green on completion is the existing "complete" trait. Running
   carries no colour of its own — the number changing is the
   signal, and tinting it would spend the accent, which already
   means "a control". */
/* the position name, its other number and the hold count are ONE
   object, so they share a block rather than sitting in a group as
   separate members with a hairline between them */
.gdhead { padding: 14px var(--gut) 16px; }
.hold { margin-top: 16px; }
.hold__c { display: flex; align-items: baseline; gap: 2px; }
.hold__n {
  font-size: 72px;
  line-height: 1.05;
  font-weight: 600;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}
.hold__u { font-size: 28px; font-weight: 500; color: var(--ink-2); }
.hold[data-state="done"] .hold__n,
.hold[data-state="done"] .hold__u { color: var(--ok); }
.hold__lab { margin-top: 4px; font-size: 17px; line-height: 1.35; color: var(--ink-2); }
.hold .btn { margin-top: 16px; }


/* ---------------------------------------------------------- mockup controls
   A review affordance, not an app feature, and labelled as one. */
.mock select {
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  font: 400 15px var(--ui);
  color: var(--ink);
  background: var(--fill);
  border: 0;
  border-radius: 9px;
  -webkit-appearance: none;
  appearance: none;
}

/* ---- phase 1: the server -------------------------------------------
   One banner the mockup never needed. It appears only while a save has
   failed to reach the server, and goes when the next one lands. */
.banner{position:fixed;left:0;right:0;top:0;z-index:60;padding:calc(8px + env(safe-area-inset-top)) 16px 8px;background:var(--warn);color:#fff;font:600 13px/1.35 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;text-align:center}
.login .txt{width:100%;box-sizing:border-box}
