/* Editor layout: the field dominates. Controls are a compact top bar, a bottom toolbar,
   and a bottom sheet inspector. On wide viewports the inspector docks as a side panel. */
.editor {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.editor__stage {
  flex: 1;
  min-height: 0;
  position: relative;
  display: flex;
}

.editor__field {
  flex: 1;
  min-width: 0;
  min-height: 0;
  position: relative;
}

.editor__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none; /* we own every gesture on the field */
  -webkit-user-select: none;
  user-select: none;
}

/*
 * The hint centres in the turf the corner controls DO NOT own, not in the field.
 *
 * Centred on the field it reserved 65px a side, which was enough for the old view buttons and
 * is not enough for either corner the redesign put controls in: it overlapped the OFF / DEF pill
 * by about 33px, and once it dropped below the pill it ran under the camera capsule instead,
 * which is 46px wide and 134px tall and so occupies every row the hint could use. The hint is
 * opaque and paints later, so an overlap is not a near miss — it covers the control.
 *
 * So the hint's containing band starts after the camera capsule and ends at the right margin,
 * and it sits one gap below the pill. `width: fit-content` with both `left` and `right` set is
 * what centres it in that band and clamps it to it: it is centred in the turf a coach can
 * actually see, which is what centred was ever for.
 */
.editor__hint {
  position: absolute;
  /* clears the camera capsule: its left margin, its 44px button plus two hairlines, one gap */
  --hint-inset-start: calc(var(--space-3) + 46px + var(--space-2));
  left: var(--hint-inset-start);
  right: var(--space-3);
  /* clears the OFF / DEF pill: its top margin, its 36px height, one gap */
  top: calc(var(--space-3) + 36px + var(--space-2));
  /* `left` + `right` + a width over-constrains the box, so `right` is dropped and the band has
     to be stated again as a max-width — otherwise a long hint runs off the field at 320px. */
  width: fit-content;
  max-width: calc(100% - var(--hint-inset-start) - var(--space-3));
  margin-inline: auto;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--field-overlay);
  color: var(--text);
  border: var(--border-width) solid var(--hairline);
  box-shadow: var(--elev-2);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-semibold);
  pointer-events: none;
  /* It wraps rather than ellipsising. The band is 242px at 320px and the longest hint — the one
     a coach with read-only access sees on every play — is 46 characters, so nowrap here meant
     truncating the sentence that explains why he cannot edit. Two balanced lines say all of it. */
  text-align: center;
  text-wrap: balance;
  z-index: 2;
}

/* Floating quick actions for the current selection (top-right of the field). */
/*
 * The camera: zoom in, zoom out, turn the field — ONE capsule, not three floating slabs.
 *
 * Each button used to carry its own fill, hairline and shadow, so one idea was drawn as three
 * separate surfaces stacked 6px apart over the turf. The surface belongs to the group; the
 * buttons inside it are divided by a rule, the way a segmented control divides its options.
 */
.editor__viewctl {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 2;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--field-overlay);
  border: var(--border-width) solid var(--hairline);
  box-shadow: var(--elev-2);
}

/* `:not(:active)` because this rule and `.btn:active` have identical specificity and editor.css
   loads after components.css — an unconditional transparent background here swallowed the press
   state, so the three camera buttons gave no feedback at all once the capsule took the surface
   off them. */
.editor__viewctl .btn:not(:active) {
  background: transparent;
}

.editor__viewctl .btn {
  border: 0;
  border-radius: 0;
  color: var(--text);
}

.editor__viewctl .btn + .btn {
  border-top: var(--border-width) solid var(--hairline);
}

/* Bottom toolbar */
.editor__toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 8px calc(8px + var(--safe-bottom));
  background: var(--surface);
  border-top: var(--border-width) solid var(--hairline);
}

/*
 * The tools, in the bar they already belong to.
 *
 * They sat in a sunken, bordered, 12px-radius tray INSIDE `.editor__toolbar`, which is itself a
 * surface with a hairline along its top: three levels of surface stacked under four buttons —
 * bar, tray, button — to hold a four-item exclusive choice.
 */
.editor__modes {
  display: flex;
  gap: var(--space-1);
}

.editor__modes .btn {
  min-width: 44px;
  padding: 0 var(--space-2);
  border: 0;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
  flex-direction: column;
  gap: 2px;
}

.editor__modes .btn .ico {
  width: var(--icon-md);
  height: var(--icon-md);
}

.editor__toolbar .spacer {
  flex: 1;
}

/* Selection bar: overlays the bottom of the field so selecting never reflows the canvas. */
.editor__selbar {
  position: absolute;
  left: var(--space-2);
  right: var(--space-2);
  bottom: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--field-overlay);
  color: var(--text);
  box-shadow: var(--elev-2);
  z-index: 2;
}


.editor__selbar .title {
  flex: 1;
  min-width: 0;
  font-weight: var(--fw-bold);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * Icon-only modes below 420px.
 *
 * The budget is what sets the threshold, not a round number: 16 padding + four modes + three
 * actions + gaps. With labels a mode button needs about 52px, which comes to 400 — so a 430px
 * phone reads Select / Route / Block / Pan, and a 390px one gets the icons with the names as
 * their accessible labels and tooltips.
 */
@media (max-width: 419px) {
  .editor__modes .btn .label {
    display: none;
  }

  .editor__modes .btn {
    padding: 0;
  }
}

/* The smallest phone still in use. Seven controls have to share 320px — four modes, Play, Add
   and Properties — so the gaps come in and every button sits on the 40px touch floor rather
   than the roomier 44px the rest of the app uses. Below that floor a control stops being
   reliably tappable, so it is the gaps that give, not the targets.
   Budget: 12 padding + 172 modes + 120 actions + 8 gaps = 312. */
@media (max-width: 360px) {
  .editor__toolbar {
    gap: 2px;
    padding-left: 6px;
    padding-right: 6px;
  }

  .editor__modes {
    gap: 2px;
    padding: 3px;
  }

  .editor__modes .btn {
    min-width: 40px;
    min-height: 40px;
  }

  .editor__toolbar > .btn--icon {
    width: 40px;
    min-width: 40px;
    min-height: 40px;
  }
}

/* Docked inspector on wide screens */
.editor__panel {
  display: none;
  width: 320px;
  flex-shrink: 0;
  background: var(--surface);
  border-left: var(--border-width) solid var(--hairline);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-4);
}

.editor__panel .sheet__body {
  padding: 0;
}

@media (min-width: 900px) {
  /*
   * Three panes, laid out so the toolbar belongs to the FIELD rather than to the window.
   *
   * It used to run the full width, which put Play, Add and Properties underneath the inspector
   * — at the far right of a 1600px screen, a long way from the field they act on. `display:
   * contents` on the stage lifts the field and the panel into this grid without a wrapper, so
   * the toolbar can take the field's column and the panel can span both rows.
   */
  .editor {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: minmax(0, 1fr) auto;
  }

  .editor__stage {
    display: contents;
  }

  .editor__field {
    grid-column: 1;
    grid-row: 1;
  }

  .editor__toolbar {
    grid-column: 1;
    grid-row: 2;
  }

  .editor__panel {
    display: block;
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .editor__panel[data-collapsed="true"] {
    display: none;
  }

  .editor__modes .btn {
    flex-direction: row;
    gap: var(--space-2);
    font-size: var(--fs-secondary);
  }
}

/* More room for the inspector once the window can spare it, but capped: past this the rows
   get longer without getting more readable, and the field is what wants the width. */
@media (min-width: 1280px) {
  .editor__panel {
    width: 360px;
    padding: var(--space-4);
  }
}

@media (min-width: 1600px) {
  .editor__panel {
    width: 400px;
  }
}

/* Route library grid */
.route-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--space-2);
}

/* A route in the library: the glyph is the content and it has an edge of its own, the same
   argument that took the box off a play thumbnail on Home. What is left is a hairline, so the
   grid reads as a grid. */
.route-grid button {
  min-height: 100px;
  border-radius: var(--radius-md);
  background: transparent;
  border: var(--border-width) solid var(--hairline);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
}

.route-grid canvas {
  width: 56px;
  height: 56px;
}

/* Roster rows in the play panel */
/*
 * A player in a list — the roster, the handoff targets, the formation picker.
 *
 * It used to arrive as a filled, rounded, bordered rectangle, which in a roster of eleven made
 * eleven identical slabs down the panel and put a second containment level inside whatever was
 * already holding them. What identifies a player is his token and his name; what separates two
 * rows is the rule between them.
 */
/*
 * The token a coach is about to add, drawn beside what it will say.
 *
 * A picture rather than a paragraph: the Add Player sheet now chooses shape, size and display
 * label, and four words describing a small circle are harder to read than the small circle.
 * Rendered by renderTokenPreview, the same code that draws the field.
 */
.add-player__preview {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--control-h);
}

.add-player__preview canvas {
  flex: none;
  width: 44px;
  height: 44px;
}

.roster-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: calc(100% + var(--space-2) * 2);
  min-height: var(--control-h);
  padding: var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  border-radius: var(--radius-xs);
  background: transparent;
  text-align: left;
  border: 0;
  transition: background var(--dur-fast) var(--ease-standard);
}

.roster-row + .roster-row {
  border-top: var(--border-width) solid var(--hairline);
  border-radius: 0;
}

/*
 * A list of them. No gap: the hairline above is the separation, and a gap over a divider turns
 * it into a rule floating above each row instead of a line between two.
 *
 * Its four call sites used to be a `.stack` with an inline 6px gap, which is both off the 4px
 * grid and the thing this rule is here to stop. The headings and the closing note carry their
 * own space, because that space belongs between the parts of the list and not between plays.
 */
.roster-list {
  display: flex;
  flex-direction: column;
}

.roster-list > .section-label {
  margin-top: var(--space-4);
}

.roster-list > .section-label:first-child {
  margin-top: 0;
}

.roster-list > .hint {
  margin-top: var(--space-3);
}

.roster-row:active {
  background: var(--surface-hover);
}

/* Selected carries the accent AND a rule down its leading edge, so it is never colour alone. */
.roster-row[aria-pressed="true"] {
  background: var(--accent-soft);
  box-shadow: inset 2px 0 0 var(--accent);
}

.roster-row canvas {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}

.roster-row__text {
  min-width: 0;
}

.roster-row__name {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.roster-row .meta {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--text-2);
}

/* Snap control: tap toggles, press and hold opens precision + grid options. */
.snap-btn {
  position: relative;
}

.snap-badge:empty {
  display: none;
}

.snap-badge {
  position: absolute;
  right: 3px;
  bottom: 2px;
  min-width: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  /* Not `--lh-caption`: the badge is a 14px circle and the line box is what centres the digit
     in it. The role's 16px would make it an oval. */
  line-height: 14px;
  text-align: center;
  pointer-events: none;
}

.editor__selbar-text {
  flex: 1;
  min-width: 0;
}

.coord-readout {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}


.spot-summary {
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  font-weight: var(--fw-bold);
  color: var(--text);
}

/* ---- Live zone size slider ------------------------------------------------------------
 *
 * Floats over the TOP of the field, between the two columns of round buttons. Top rather
 * than bottom because a bottom sheet can cover 72dvh on a phone, and a size control you
 * cannot see the effect of is the thing this replaces.
 */
.editor__zonebar {
  position: absolute;
  top: var(--space-3);
  left: 62px;
  right: 62px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--field-overlay);
  color: var(--text);
  border: var(--border-width) solid var(--hairline);
  /* `--elev-2`, like every other thing floating on the turf. It carried the `--shadow` legacy
     alias, which resolves to `--elev-3` — the modal depth. */
  box-shadow: var(--elev-2);
}


.editor__zonebar-label {
  flex: 0 0 auto;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
  color: var(--text-2);
}

.zone-size-slider {
  flex: 1;
  min-width: 0;
  /* The slider owns its own gesture; without this the field's pan/zoom handling and the
     browser's scroll fight the drag on touch. */
  touch-action: none;
}

.zone-size-readout {
  flex: 0 0 auto;
  min-width: 62px;
  text-align: right;
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-bold);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* On a narrow phone the readout is the label: "18 × 9 yd" says what "Size" would. */
@media (max-width: 400px) {
  .editor__zonebar {
    left: 58px;
    right: 58px;
    gap: var(--space-2);
    padding: 6px 10px;
  }

  .editor__zonebar-label {
    display: none;
  }
}

/* One play you can pull a side out of, in the Import Players sheet. The name and the side
   buttons share a row on anything wider than a small phone, and stack below that so the
   buttons keep a full-width touch target rather than being squeezed next to a long name. */
/* One play in the Import Players sheet. It was a tinted, rounded slab per row, so a sheet
   listing eight plays was eight boxes inside a surface that is already a surface — the same
   shape `.roster-row` carried until this pass, left behind in the sheet next door. */
.import-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  background: transparent;
}

.import-row + .import-row {
  border-top: var(--border-width) solid var(--hairline);
}

.import-row__body {
  flex: 1 1 140px;
  min-width: 0;
}

.import-row__title {
  font-weight: var(--fw-bold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.import-row__meta {
  color: var(--text-2);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  margin-top: 2px;
}

.import-row .btn-row {
  flex: 0 1 auto;
}

/* A player's sequence: three bands, one per side of the snap, each in the phase's own colour.
   `--phase-color` and `--step-color` are set inline from the field theme, so the list and the
   art on the field agree about what pre-snap looks like. */
.sequence-phase {
  border-left: 3px solid var(--phase-color, var(--accent));
  padding-left: 8px;
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.sequence-phase__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.sequence-phase__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--phase-color, var(--accent));
  flex-shrink: 0;
}

/* PRE-SNAP · SNAP · POST-SNAP, in the display role and the phase's own colour. The 0.08em it
   used to track out by was the widest in the app and the one place a label was already being
   asked to carry more weight than the type scale gives it; the face carries it now. The line
   box is unchanged, so the 8px dot beside it has not moved. */
.sequence-phase__name {
  color: var(--phase-color, var(--text-2));
}

/* A count beside a heading is metadata, not a status to be noticed — and the rows it counts
   are directly underneath it. A bare tabular number says it. */
.sequence-phase__count {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* One step: what it is, who it involves, and which side of the snap it happens on. */
/* One step. It used to be a tinted, rounded card inside a phase band that is already a
   labelled, colour-ruled container, with a sunken segmented track and a shadowed thumb inside
   it again — four levels of surface for one action. The coloured rule down its leading edge
   says which side of the snap it happens on, which is all the boundary was for. */
.sequence-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  background: transparent;
  border-left: 3px solid var(--step-color, transparent);
}

.sequence-row + .sequence-row {
  border-top: var(--border-width) solid var(--hairline);
}

/* The ball arriving. Shown where it happens, edited where it is stored — on the other player. */
.sequence-row--derived {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  background: var(--surface-hover);
  border: 1px dashed var(--hairline-strong);
  border-left: 3px solid var(--step-color, transparent);
}

.sequence-row__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.sequence-row__icon {
  font-size: var(--fs-label);
  line-height: var(--lh-label);
  width: 20px;
  flex-shrink: 0;
  text-align: center;
}

.sequence-row__body {
  flex: 1;
  min-width: 0;
}

.sequence-row__title {
  font-weight: var(--fw-bold);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sequence-row__meta {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A token preview next to a name, so picking a player is recognising him rather than guessing. */
.roster-row__token {
  display: flex;
  flex-shrink: 0;
}

/* ---- Playback transport -----------------------------------------------------------------
 *
 * Floats at the BOTTOM of the field, just above the toolbar. One row, because requirement 18
 * is the binding constraint: a phone has to keep the field visible while the play runs, and a
 * two-row transport on a 320px screen eats the backfield. Everything that does not fit is
 * behind the ⋯ button, in a sheet.
 */
.playback {
  position: absolute;
  left: var(--space-2);
  right: var(--space-2);
  bottom: var(--space-2);
  z-index: 3;
  padding: var(--space-2);
  border-radius: var(--radius-lg);
  background: var(--field-overlay);
  color: var(--text);
  border: var(--border-width) solid var(--hairline);
  box-shadow: var(--elev-2);
}


.playback__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* The scrubber owns a whole row. See the note in playbackBar.js: sharing one with the
   transport left it 54px wide, which is not a control anybody can stop a play with. */
.playback__row--scrub {
  gap: var(--space-3);
  padding: 0 4px;
}

.playback__row--transport .spacer {
  flex: 1;
}

.playback__btn {
  flex: 0 0 auto;
  background: transparent;
  border: none;
}

.playback__btn--play {
  background: var(--accent);
  color: var(--text-on-accent);
  border-radius: var(--radius-pill);
}

/* The scrubber and the bands under it share a stacking context so the snap marker can sit on
   the track without being part of the input. */
.playback__track {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 34px;
}

.playback__scrub {
  width: 100%;
  margin: 0;
  /* The scrubber owns its own gesture; without this the field's pan/zoom handling and the
     browser's scroll fight the drag on touch. */
  touch-action: none;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
}

/* Phase colour on the track: violet before the snap, pink at it, the route colour after —
   the same scheme the field art and the sequence editor use, so nobody learns a second one. */
.playback__scrub::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--playback-bands, var(--hairline-strong));
}

.playback__scrub::-moz-range-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--playback-bands, var(--hairline-strong));
}

.playback__scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -7px;
  border-radius: var(--radius-pill);
  background: var(--text);
  border: 2px solid var(--surface);
  box-shadow: var(--elev-2);
}

.playback__scrub::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  background: var(--text);
  border: 2px solid var(--surface);
}

/* The snap, as a real mark on the timeline rather than an implied boundary between two
   colours: it is the moment every other time on the bar is measured against. */
.playback__snap {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 18px;
  margin-left: -1px;
  transform: translateY(-50%);
  background: var(--text);
  border-radius: 1px;
  box-shadow: 0 0 0 1px var(--surface);
  pointer-events: none;
}

.playback__time {
  flex: 0 0 auto;
  min-width: 46px;
  text-align: right;
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-bold);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Coverage technique: a label and a control per row, so three choices fit a phone without three
   full-width blocks of vertical space. */
.technique {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-2);
}

.technique__label {
  color: var(--text-2);
}

/* What is left of `.toggle-row`: a heading and the switch row under it. The tinted rounded
   rectangle, the 14px name and the filled accent On/Off button inside it are all gone — a
   boolean is a switch, which the library has had all along. */
.toggle-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* The QB release clock. Quiet until it matters: grey while there is time, amber as it runs down,
   red once the play has gone past the limit it was written for. A warning, never a blocker. */
/* Quiet until it matters, which its own comment always said it should be: no fill and no
   border while there is time, and the warning tints below bring both when the clock runs down.
   Card → pill → pill was two containment levels for a two-word readout. */
.playback__clock {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: transparent;
  border: var(--border-width) solid transparent;
  font-variant-numeric: tabular-nums;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}

.playback__clock-label {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  color: var(--text-2);
}

.playback__clock-value {
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.playback__clock[data-state='warn'] {
  background: var(--warning-soft);
  border-color: var(--warning-line);
}

.playback__clock[data-state='warn'] .playback__clock-value {
  color: var(--warning);
}

.playback__clock[data-state='over'] {
  background: var(--danger-soft);
  border-color: var(--danger-line);
}

.playback__clock[data-state='over'] .playback__clock-value,
.playback__clock[data-state='over'] .playback__clock-label {
  color: var(--danger);
}

/* A play that runs over the limit stays marked for the whole run, not just at the moment the
   clock passes the number: it is a fact about the play, not about where the cursor is. */
.playback__clock--over {
  box-shadow: inset 0 0 0 1px var(--danger-line);
}

.playback__rate {
  flex: 0 0 auto;
  min-width: 40px;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  color: var(--text);
  background: transparent;
  border-color: transparent;
}

/* A play with nothing drawn on it still opens the bar — it just says so. */
.playback[data-empty='true'] .playback__track,
.playback[data-empty='true'] .playback__time {
  opacity: 0.45;
}

/* On a small phone the ⋯ and ✕ are the first things to go: the sheet is reachable from the
   More menu, and closing is what the Play button in the toolbar already toggles. */
@media (max-width: 380px) {
  .playback {
    left: 6px;
    right: 6px;
    padding: 5px 6px;
  }

  .playback__row {
    gap: 3px;
  }

  .playback__time {
    min-width: 40px;
    font-size: var(--fs-caption);
    line-height: var(--lh-caption);
  }
}

/* ---- Timing sheet ----------------------------------------------------------------------- */

/* A player in the timing sheet: a heading with his steps under it. Six tinted rounded
   rectangles down a sheet that is already a surface is the complaint this pass exists for. */
.timing-player {
  padding: var(--space-2) 0;
}

.timing-player + .timing-player {
  border-top: var(--border-width) solid var(--hairline);
}

.timing-player__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: 6px;
}

/*
 * THE ONE SMALL-CAPS HEADING THAT IS NOT IN THE DISPLAY FACE, and it is deliberate.
 *
 * Every other label of this shape names something the app decided — a section, a phase, a
 * football category — and takes the display role from base.css. This one renders
 * `playerLabel()`, which is a jersey number, a position, or whatever a coach typed in the
 * player's Label field. It is his words, so it keeps the interface face: the display face is
 * an accent on the app's own vocabulary, never on the content.
 *
 * It keeps the interface small-caps declarations written out, because it is now the only rule
 * that uses them.
 */
.timing-player__name {
  font-size: var(--fs-section);
  line-height: var(--lh-section);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-section);
  text-transform: uppercase;
  color: var(--text-3);
}

.timing-row {
  border-left: 3px solid var(--step-color, var(--accent));
  padding: 6px 0 6px 8px;
  margin-bottom: 6px;
}

.timing-row--event {
  opacity: 0.85;
}

.timing-row__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.timing-row__icon {
  flex: 0 0 auto;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.timing-row__body {
  flex: 1;
  min-width: 0;
}

.timing-row__title {
  font-weight: var(--fw-bold);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
}

.timing-row__meta {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--text-2);
}

/* Three fields across, so a coach sees start, length and speed as one statement about the
   step rather than three separate settings to hunt for. */
.timing-row__fields {
  display: grid;
  grid-template-columns: 1fr 1fr 1.2fr;
  gap: var(--space-2);
  margin-top: 6px;
}

.timing-row__field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-2);
}

.timing-row__num,
.timing-row__speed {
  width: 100%;
  min-height: 38px;
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-variant-numeric: tabular-nums;
}

/* The release-limit control and its verdict on this play. */
.timing__input {
  width: 92px;
  min-height: 40px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-variant-numeric: tabular-nums;
}

.timing__verdict {
  margin: 0;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--text-2);
}

.timing__verdict--over {
  color: var(--danger);
  font-weight: var(--fw-semibold);
  border-left: 3px solid var(--danger-line);
  padding-left: 8px;
}

.timing-row__span {
  margin-top: 4px;
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-semibold);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

/* Coach markers on the strip. Nothing creates one yet — the teaching phase will — but the
   strip can already draw them, so that phase adds a way to make markers rather than a second
   idea of where on the timeline they sit. */
.playback__markers {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.playback__marker {
  position: absolute;
  top: 50%;
  width: 3px;
  height: 12px;
  margin-left: -1.5px;
  transform: translateY(-50%);
  border-radius: 2px;
  background: var(--warning);
  box-shadow: 0 0 0 1px var(--surface);
}

/* ── Which sides are on the field ───────────────────────────────────────────────────
   Opposite the zoom controls, in the field's other top corner. It states the current
   filter rather than only offering to change it, which is the whole reason it came out
   of the More menu. */

.editor__sidesctl {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
}

.editor__sides {
  gap: var(--space-2);
  min-height: 36px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--field-overlay);
  color: var(--text);
  border: var(--border-width) solid var(--hairline);
  box-shadow: var(--elev-2);
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
}

.editor__sides:active {
  background: var(--field-overlay);
  opacity: 0.85;
}

/* ── Inspector ──────────────────────────────────────────────────────────────────────
   Sections stacked under an identity strip that never collapses. The strip is the answer
   to "what am I looking at"; hiding it would make every section below it ambiguous. */

.inspector {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.player-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-1);
}

.player-head canvas {
  flex-shrink: 0;
}

.player-head__body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.player-head__name {
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

.player-head__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.inspector__actions {
  margin-top: var(--space-2);
}

/* A group inside a section carries no boundary of its own — it never did need one, and since
   the refinement pass `.group` has none anywhere. What is left here is the height: rows nested
   one level in are dense rows, not screen rows. */
.section__body > .group > .group-row {
  min-height: var(--control-h);
}

/* ── Playback is a mode, not a tool ─────────────────────────────────────────────────
   Watching a play and building one are different things to be doing, so the editing
   chrome stands down while the play runs: the tool group, Add and Properties are all
   about changing a play and none of them applies to watching one. What is left is the
   field, the transport, and an unmissable way back. */

/* Gone while watching: the tools, Add, Properties, and the toolbar's own play button — the
   transport has one, and two of them a thumb's width apart is a question, not a control.
   The zoom and orientation buttons STAY: looking at a running play from the other end of the
   field is watching, not editing. */
.editor[data-mode="playback"] .editor__modes,
.editor[data-mode="playback"] .editor__toolbar > .btn--primary,
.editor[data-mode="playback"] .editor__props,
.editor[data-mode="playback"] .editor__play {
  display: none;
}

/* With nothing left in it, the toolbar goes too, and the field takes the space. Leaving an
   empty bar on screen to hold one button would be the opposite of prioritising the field. */
.editor[data-mode="playback"] .editor__toolbar {
  display: none;
}

/*
 * And the docked inspector with it.
 *
 * A phone has never shown properties while a play runs — the sheet is dismissed — but on a
 * tablet the panel stayed, so 320 to 400 pixels of the play's name, ball spot, formation and
 * technique controls sat beside a running play, editing chrome that playback had supposedly
 * stood down. Nothing is lost: it comes back the moment Done is pressed.
 */
.editor[data-mode="playback"] .editor__panel {
  display: none;
}

/* The one way back to editing: a labelled button, not a bare ✕ — which was the least obvious
   control on the bar and the only one whose job is leaving. */
.playback__btn--done {
  gap: var(--space-1);
  min-height: 38px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  background: transparent;
  border-color: transparent;
  color: var(--accent);
  font-size: var(--fs-secondary);
  font-weight: var(--fw-semibold);
}

/* ── Application chrome ──────────────────────────────────────────────────────────────
 *
 * While the editor is open the document behaves like an application: its labels are not
 * paragraphs, and a finger held on one is not a request to select a word.
 *
 * KEYED ON <html>, NOT ON `.editor`, AND THAT IS THE WHOLE POINT. The editor's own app bar is a
 * SIBLING of `.editor` rather than a descendant (editorView.js builds `div.view` as
 * `[header.appbar, div.editor]`), and every bottom sheet, menu and toast is appended to
 * `document.body` by its component rather than into the view. A rule scoped to `.editor` would
 * reach the field, the floating controls, the bottom toolbar and the docked inspector — and
 * would miss the app bar, the inspector SHEET, and the menu items the task named out loud.
 * `ui/appChrome.js` sets and clears the attribute with the editor's lifetime, so no other
 * screen is affected and nothing is left behind if a coach navigates away mid-gesture.
 *
 * `-webkit-touch-callout` is the load-bearing half on iOS. Suppressing `contextmenu` stops the
 * desktop right-click menu and does nothing about the Copy / Look Up sheet and the blue
 * selection handles a long press raises on iPhone — that is this property, and the app had no
 * declaration of it anywhere before now.
 */
:root[data-app-chrome] body {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/*
 * ...AND EVERYWHERE A COACH ACTUALLY NEEDS IT BACK.
 *
 * Coaching notes, the per-player note, the rename field, every position, jersey, yard line,
 * timing and colour input, every native picker. These are the app's OWN content, and a caret
 * that cannot select, a long press that will not offer Paste, or a number a coach cannot
 * double-tap to replace would be a worse bug than the one above fixes.
 *
 * Stated as `text` on the elements themselves rather than by not inheriting: on WebKit a text
 * control under an inherited `none` can lose its caret and its selection handles even though it
 * still takes focus, so the override has to be explicit.
 *
 * `[data-selectable]` is the hook for a value the app PRINTS that a coach has to be able to take
 * away — nothing carries it today, and it is here so the next share code, export blob or
 * diagnostic string is one attribute rather than another rule in this block.
 *
 * THIS LIST HAS A TWIN IN JAVASCRIPT: `SELECTABLE` in `ui/appChrome.js`, which decides where the
 * browser's context menu is still allowed to open. Change one and change the other — a coach who
 * can put a caret in a note but gets no Paste on it is what happens when only one of them moves.
 */
:root[data-app-chrome] input,
:root[data-app-chrome] textarea,
:root[data-app-chrome] select,
:root[data-app-chrome] option,
:root[data-app-chrome] [contenteditable=""],
:root[data-app-chrome] [contenteditable="true"],
:root[data-app-chrome] [contenteditable="plaintext-only"],
:root[data-app-chrome] [data-selectable] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* ...EXCEPT a slider, which is an `input` with nothing in it to select.
   The scrubber and the zone-size control are held down and dragged — a begin/preview/commit
   gesture each — so a long press on one is the START OF AN EDIT, not a request for a menu. They
   take the exception above by being inputs and have no use for either half of it. */
:root[data-app-chrome] input[type="range"] {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/*
 * A share code is `user-select: all` on purpose — one tap takes the whole code, which is the
 * documented fallback when the clipboard API is unavailable (team/accessView.js). It keeps
 * `all` rather than being downgraded to `text` by the rule above, and it gets its callout back
 * so the iOS Copy menu a coach reaches for still appears.
 *
 * It cannot fire today: the only share code in the app is on the Team access screen, and
 * application chrome is only ever on in the editor. It is here because the cost of being wrong
 * is a coach who cannot copy the code he is reading down a phone, and because the day a sheet
 * shows one — or the day application chrome covers more than the editor — this is already right.
 */
:root[data-app-chrome] .share-code {
  -webkit-user-select: all;
  user-select: all;
  -webkit-touch-callout: default;
}

/* No dragging a picture out of the interface. A crest is part of a control here, not an image
   to be lifted onto a desktop, and on touch the drag menu is the other half of the long-press
   problem above. `canvas` is on the list because the field is one, and it is the largest thing
   a finger can come to rest on. */
:root[data-app-chrome] img,
:root[data-app-chrome] svg,
:root[data-app-chrome] canvas {
  -webkit-user-drag: none;
}
