/* tooto - the thin layer over bare.style.
 *
 * bare.style (vendor/bare, MIT, v0.2.1) owns the shell, the rail, the topbar, the
 * inspector, the cards, the tables, the forms, the buttons and the type. This file
 * is only two things:
 *
 *   1. --bs-* hooks, which is the framework's documented way to customise a
 *      component. No bare.style selector is overridden and no bs- class is
 *      redefined anywhere in this file.
 *   2. The four things the framework genuinely does not have, each marked below:
 *      the NLE timeline, the bounded 16:9 / 9:16 stage, the render surfaces that
 *      paint in a project palette, and full-screen mode.
 *
 * This file is unlayered on purpose. Every bare.style file declares the same
 * layer order and unlayered rules win over all of them, so nothing here needs a
 * stronger selector and nothing needs !important.
 *
 * THE ACCENT IS THE PROJECT PALETTE. bare.style routes every component's accent
 * through --bs-accent and its relatives, so pointing that one family at the
 * selected palette makes the rail's current item, focus rings, the primary button,
 * selection and the playhead all follow the project. That is the whole palette
 * feature and it costs six declarations rather than a rule per component.
 *
 * CONTRAST. The five palette accents are measured against the lightest surface
 * each theme puts them on - bare.style's --s-hover #262a3d on dark and --s-card
 * #ffffff on light - and against --s-rail #e6e8f2, the darkest light surface:
 *
 *              dark on #262a3d   light on #ffffff   light on #e6e8f2
 *   midnight       8.6:1              6.2:1              5.1:1
 *   paper          5.4:1              7.7:1              6.3:1
 *   ocean          7.5:1              6.8:1              5.5:1
 *   forest         8.6:1              6.3:1              5.1:1
 *   plum           5.9:1              7.6:1              6.2:1
 *
 * All ten clear 4.5:1. Body ink is the framework's own --bs-text / --bs-text-
 * secondary / --bs-text-muted and is not touched; note that on dark the framework
 * routes muted TEXT to --color-ash (6.5:1 on --s-card) and keeps --sec #85879f for
 * scrollbars and borders, where its 4.46:1 on --s-card does not carry a letter.
 */

:root {
  /* Space Mono is banned in this codebase - it is a tell, in the same class as an
     em dash - and tokens.css sets --font-eyebrow to it. Overridden to the
     framework's own --font-mono stack, which is the system monospace and costs no
     request. Both spellings are set because the token is --font-eyebrow and the
     component hooks are named --bs-*, and a future component reading the prefixed
     name must not fall back to the banned face. */
  --font-eyebrow: var(--font-mono);
  --bs-font-eyebrow: var(--font-mono);

  /* Instrument density, through the hooks the components expose.
     NOT the rail: its own file documents 5.5rem as measured, because 4.5rem
     truncated "Sources" to "Sourc..." and a label that has stopped being a label
     is worse than a wider rail. Overriding it to 3.25rem here made every item
     wider than the track that held it and pushed the first one four pixels off
     the left edge of the screen at every width. The framework had already done
     this measurement; the fix is to stop arguing with it. */
  --bs-shell-topbar-pad: var(--bs-space-3);
  --bs-shell-topbar-gap: var(--bs-space-2);
  --bs-shell-main-pad: var(--bs-space-4);
  --bs-shell-inspector-size: 17rem;
  --bs-shell-inspector-min: 14rem;
  --bs-shell-inspector-max: 24rem;
  --bs-shell-inspector-pad: var(--bs-space-3);
}

/* --- the palette drives the accent ------------------------------------------
   --acc-l and --acc-d are the same hue re-picked to carry a label and a 1px
   stroke on a pale and on a dark panel: #f5c518 measures 1.7:1 on white and is
   unusable there, and #c8102e measures 3.4:1 on --s-card dark. The theme blocks
   below choose between them, so a palette is one pair of values and never a
   per-component rule. --pal-* are core/project.mjs verbatim and are read only by
   the render surfaces at the end of this file. */

:root,
:root[data-palette='midnight'] {
  --pal-bg: #0b0b0d; --pal-fg: #ffffff; --pal-accent: #f5c518; --pal-muted: #9a9aa4;
  --acc-l: #7a5c00; --acc-d: #f5c518;
}
:root[data-palette='paper'] {
  --pal-bg: #faf9f6; --pal-fg: #14141a; --pal-accent: #c8102e; --pal-muted: #5a5a66;
  --acc-l: #a80c25; --acc-d: #ff7183;
}
:root[data-palette='ocean'] {
  --pal-bg: #07131f; --pal-fg: #eaf4ff; --pal-accent: #35c5f0; --pal-muted: #7f98ad;
  --acc-l: #0a6382; --acc-d: #45cdf5;
}
:root[data-palette='forest'] {
  --pal-bg: #0a1410; --pal-fg: #eefaf1; --pal-accent: #4ade80; --pal-muted: #7f9c8a;
  --acc-l: #10703a; --acc-d: #5ce38d;
}
:root[data-palette='plum'] {
  --pal-bg: #140b1b; --pal-fg: #f6eeff; --pal-accent: #c084fc; --pal-muted: #9c8aad;
  --acc-l: #6f2fbb; --acc-d: #c990ff;
}

/* The dark assignment is written twice because a media query and a class cannot
   be one selector, and the query targets :root:not(.theme-light) so a pinned
   light panel is never matched by it. This mirrors how tokens.css resolves its
   own themes, so the two cannot disagree about which theme is showing. */
:root,
:root.theme-light,
.theme-light {
  --bs-accent: var(--acc-l);
  --bs-accent-hover: color-mix(in srgb, var(--acc-l) 86%, #000000);
  --bs-accent-active: color-mix(in srgb, var(--acc-l) 78%, #000000);
  --bs-accent-subtle: color-mix(in srgb, var(--acc-l) 12%, transparent);
  --bs-accent-subtle-hover: color-mix(in srgb, var(--acc-l) 20%, transparent);
  --bs-accent-border: var(--acc-l);
  --bs-focus-ring: var(--acc-l);
  --bs-on-accent: #ffffff;
  --bs-select-bg: var(--acc-l);
  --bs-select-fg: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) {
    --bs-accent: var(--acc-d);
    --bs-accent-hover: color-mix(in srgb, var(--acc-d) 88%, #ffffff);
    --bs-accent-active: color-mix(in srgb, var(--acc-d) 80%, #000000);
    --bs-accent-subtle: color-mix(in srgb, var(--acc-d) 14%, transparent);
    --bs-accent-subtle-hover: color-mix(in srgb, var(--acc-d) 22%, transparent);
    --bs-accent-border: var(--acc-d);
    --bs-focus-ring: var(--acc-d);
    --bs-on-accent: #0b0b0d;
    --bs-select-bg: var(--acc-d);
    --bs-select-fg: #0b0b0d;
  }
}

:root.theme-dark,
.theme-dark {
  --bs-accent: var(--acc-d);
  --bs-accent-hover: color-mix(in srgb, var(--acc-d) 88%, #ffffff);
  --bs-accent-active: color-mix(in srgb, var(--acc-d) 80%, #000000);
  --bs-accent-subtle: color-mix(in srgb, var(--acc-d) 14%, transparent);
  --bs-accent-subtle-hover: color-mix(in srgb, var(--acc-d) 22%, transparent);
  --bs-accent-border: var(--acc-d);
  --bs-focus-ring: var(--acc-d);
  --bs-on-accent: #0b0b0d;
  --bs-select-bg: var(--acc-d);
  --bs-select-fg: #0b0b0d;
}

/* The framework's shell owns the viewport, so the document must not also scroll.
   Screen only: the shell's own print rule unwinds the frame into a document, and
   a locked body would cut that to one page. */
@media screen {
  html,
  body { block-size: 100%; overflow: hidden; }
}

/* Numbers are mono and tabular wherever they appear, so a column of timecodes is
   a column rather than a ragged edge. bare.style ships .bs-table__num for the
   table case; this is the same idea for the readouts outside one. */
.tt-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* ===========================================================================
   1. THE STAGE AND THE FRAME - not in the framework
   ===========================================================================
   THE BUG THIS FIXES. The preview was sized from its width with aspect-ratio, so
   its height was a function of width and nothing bounded it against the height
   available. Past a certain window width it grew taller than the viewport and
   pushed the transport and most of the timeline off the bottom of the screen.

   The shape that fixes it, and every part of it is load-bearing:

     - the editor column is a grid whose stage row is minmax(0, 1fr). A plain 1fr
       is an auto-minimum track and refuses to go under its content, which is the
       same trap bare.style documents on .bs-shell's own rows.
     - every ancestor from the stage up to the shell carries min-block-size: 0.
       Grid and flex items default to min-height:auto, and one missing link in
       that chain silently defeats everything below it. This was the actual bug.
     - the frame is bounded on BOTH axes and sized on neither: max-inline-size and
       max-block-size are 100%, inline-size and block-size are auto, and
       aspect-ratio holds the shape. Whichever axis runs out first wins, so a
       short wide stage limits by height and leaves space at the sides, and a tall
       narrow one limits by width and leaves space above and below. */

/* The editor's own control strip. A cluster would wrap each group onto its own
   row at the first squeeze; this keeps the groups together and lets the whole
   strip scroll instead, which is what a toolbar in an editor does. */
.tt-bar {
  display: flex;
  align-items: center;
  gap: var(--bs-space-2);
  min-inline-size: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  padding-block-end: 1px;
}
.tt-bar::-webkit-scrollbar { block-size: 0; }
.tt-bar__group { display: flex; align-items: center; gap: 2px; flex: none; }
.tt-bar__end { margin-inline-start: auto; }
.tt-bar > .bs-eyebrow { flex: none; }

.tt-editor {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  block-size: 100%;
  min-block-size: 0;
  min-inline-size: 0;
  gap: var(--bs-space-2);
  padding: var(--bs-space-3);
}

/* THE CELLS MUST STRETCH, AND THE CENTRING BELONGS ONE LEVEL DOWN.
   place-items:center here was a second version of the same bug: a centred grid
   item is sized from its content rather than from its track, so the cell had no
   definite height, the frame's max-block-size:100% had nothing to resolve
   against, and both canvases collapsed to about 78x44 while still holding a
   correct 16:9 and still fitting "inside" the stage. Every assertion passed and
   the picture was a postage stamp. The tracks are explicit minmax(0,1fr), the
   cells take the default stretch, and the centring happens inside each cell. */
.tt-stage {
  min-block-size: 0;
  min-inline-size: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  gap: var(--bs-space-2);
  overflow: hidden;
  /* A neutral near-black surround whatever the theme: a viewer border is a
     reference and a pale one lies about the picture. */
  background-color: #050507;
  border-radius: var(--bs-radius-2);
  padding: var(--bs-space-2);
}

/* One canvas by default; two in "both", and which axis they divide depends on the
   room rather than on the mode. Side by side needs landscape AND width, because
   two 16:9 canvases in a 900px-wide portrait window are two postage stamps. */
.tt-stage--both {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
}
@media (min-width: 60em) and (orientation: landscape) {
  .tt-stage--both {
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* Each canvas gets its own cell and is bounded inside it independently, which is
   what keeps the 9:16 one from dictating the height of the 16:9 one. */
.tt-cell {
  min-block-size: 0;
  min-inline-size: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* A size container, so the frame below can be measured against BOTH of the
     cell's axes. This is what replaces the auto/auto sizing that did not work. */
  container-type: size;
}

/* WHY THE SIZE IS COMPUTED RATHER THAN LEFT TO auto/auto.
   The obvious shape - max-inline-size and max-block-size 100% with both sizes
   auto - only bounds a box that already has a size to be bounded. A non-replaced
   block in a centred grid cell has inline-size:auto meaning shrink-to-fit, so it
   took its CONTENT width, the aspect ratio derived a height from that, and both
   canvases settled at about 78x44 while still holding a perfect 16:9 and still
   fitting inside the stage. Correct by every assertion and unwatchable.
   min() against the cell's own two axes states the intent directly: as wide as
   the cell allows, or as wide as the cell's height allows at this ratio,
   whichever is smaller. cqh and cqi resolve against .tt-cell because an element
   is never its own query container, so the frame's own container-type below only
   governs its text. */
.tt-frame {
  --tt-num: 16;
  --tt-den: 9;
  aspect-ratio: var(--tt-num) / var(--tt-den);
  inline-size: min(100%, calc(100cqh * var(--tt-num) / var(--tt-den)));
  block-size: auto;
  max-inline-size: 100%;
  max-block-size: 100%;
  background-color: var(--pal-bg);
  color: var(--pal-fg);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(8px, 2.4cqi, 22px);
  container-type: inline-size;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08), var(--bs-shadow-4);
  transition: background-color var(--bs-duration-4) var(--ease-out), color var(--bs-duration-4) var(--ease-out);
}
.tt-frame--mobile { --tt-num: 9; --tt-den: 16; }

/* Everything inside the frame is sized in container query units, so the title
   card looks the same at 320px wide and at 900px and neither needs a breakpoint. */
.tt-frame__kick {
  font-family: var(--font-mono);
  font-size: clamp(6px, 2.2cqi, 11px);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pal-accent);
  margin-block-end: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tt-frame__title {
  font-size: clamp(11px, 7.2cqi, 34px);
  line-height: 1.08;
  letter-spacing: -0.032em;
  font-weight: 650;
  max-inline-size: 16ch;
}
.tt-frame__rule {
  block-size: clamp(2px, 0.9cqi, 4px);
  inline-size: clamp(24px, 13cqi, 60px);
  background-color: var(--pal-accent);
  margin-block-start: clamp(4px, 2cqi, 12px);
}
.tt-frame__cap {
  margin-block-start: clamp(5px, 2.6cqi, 14px);
  align-self: flex-start;
  max-inline-size: 100%;
  font-size: clamp(6px, 2.9cqi, 13px);
  line-height: 1.35;
  padding: 0.35em 0.7em;
  border-radius: var(--bs-radius-1);
  background-color: color-mix(in srgb, var(--pal-fg) 13%, transparent);
}
/* The vertical cut has its own safe areas: a 9:16 frame is read past on a phone
   with chrome top and bottom, so the copy sits inside them rather than under
   them. Held here as one block so swapping to the format's real per-orientation
   shape is an edit to these three values. */
.tt-frame--mobile { padding-block: clamp(22px, 9cqi, 64px); }
.tt-frame--mobile .tt-frame__title { font-size: clamp(11px, 9cqi, 40px); max-inline-size: 12ch; }
.tt-frame--mobile .tt-frame__cap { font-size: clamp(6px, 3.6cqi, 15px); }

/* The decode surfaces. Off screen rather than display:none, because a hidden
   video element is allowed to stop decoding in some engines and the whole point of
   the pair is that the idle one is already decoding the next shot. */
.tt-video-host {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  inset-block-start: 0;
  inset-inline-start: 0;
}

/* The picture. Both frames are canvases fed by the same element, so the portrait
   cut is the same decoded frame with a different crop rather than a second
   player. */
.tt-canvas {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  background-color: #000000;
  display: block;
}
.tt-offline {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background-color: rgba(0, 0, 0, 0.72);
  color: #ffffff;
  font-family: var(--font-mono);
  font-size: clamp(8px, 3cqi, 14px);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  z-index: 2;
}

.tt-frame__cursor {
  position: absolute;
  inset-inline-end: 20%;
  inset-block-start: 32%;
  inline-size: clamp(10px, 5cqi, 26px);
  color: var(--pal-accent);
}
.tt-frame__cursor::after {
  content: '';
  position: absolute;
  inset: -30%;
  border: 1px solid currentColor;
  border-radius: 50%;
  opacity: 0.3;
}

/* ===========================================================================
   2. THE TIMELINE - not in the framework
   ===========================================================================
   bare.style has flow-timeline, but that is an activity feed: a vertical list of
   events with a marker and a time. An edit timeline is a different object - a
   fixed track gutter, a ruler in timecode, lanes, a playhead, and clips whose
   geometry is a proportion of the film's duration - so it is written here rather
   than bent out of one. It reads the framework's tokens throughout and introduces
   no colour of its own. */

.tt-tl {
  border: var(--bs-border-1) solid var(--bs-border-color);
  border-radius: var(--bs-radius-2);
  background-color: var(--s-card);
  overflow: hidden;
  min-inline-size: 0;
}
.tt-tl__bar {
  display: flex;
  align-items: center;
  gap: var(--bs-space-2);
  block-size: 1.75rem;
  padding-inline: var(--bs-space-2);
  background-color: var(--s-top);
  border-block-end: var(--bs-border-1) solid var(--bs-border-color);
  font-family: var(--font-eyebrow);
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--bs-text-muted);
  min-inline-size: 0;
}
.tt-tl__bar > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tt-tl__bar .tt-num { margin-inline-start: auto; text-transform: none; letter-spacing: 0; flex: none; }

/* The gutter is fixed and the right side scrolls, so a zoomed timeline moves under
   track names that stay put. Two columns rather than four cells: the ruler and the
   lanes have to scroll together or the timecode stops describing what is under it. */
/* 14.25rem is measured rather than chosen: the row holds 1rem of padding, a
   3.25rem name, a 0.25rem gap and a 9.1rem mixer, and at 13.5rem the decibel
   readout ran under the lanes and lost its B. The check that catches this is the
   readout's own right edge against the gutter's, not scrollWidth on the text -
   text fits its own box while the box is the thing being covered. */
.tt-tl__body {
  display: grid;
  grid-template-columns: var(--tt-gutter, 14.25rem) minmax(0, 1fr);
  min-inline-size: 0;
}
@media (max-width: 37.5em) { :root { --tt-gutter: 6.5rem; --tt-lane: 1.5rem; } }

.tt-tl__gutter {
  display: grid;
  grid-template-rows: 1.25rem auto;
  background-color: var(--s-top);
  border-inline-end: var(--bs-border-1) solid var(--bs-border-color);
  min-inline-size: 0;
}
.tt-tl__scroll {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  min-inline-size: 0;
  display: grid;
  grid-template-rows: 1.25rem auto;
  /* A timeline is scrubbed by dragging a clip, so the browser must not claim the
     horizontal gesture for its own panning. */
  touch-action: pan-y;
}

.tt-tl__corner {
  background-color: var(--s-top);
  border-block-end: var(--bs-border-1) solid var(--bs-border-color);
}
.tt-tl__ruler {
  position: relative;
  background-color: var(--s-top);
  border-block-end: var(--bs-border-1) solid var(--bs-border-color);
  min-inline-size: 0;
  cursor: pointer;
}
.tt-tick {
  position: absolute;
  inset-block: 0;
  border-inline-start: var(--bs-border-1) solid var(--bs-border-color);
  padding-inline-start: 0.25rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.5625rem;
  line-height: 1.25rem;
  color: var(--bs-text-muted);
  white-space: nowrap;
  pointer-events: none;
}

/* A re-render is a span of TIME, not a clip, so it is marked on the ruler where
   time is read rather than in a lane that belongs to one track. Translucent, so
   the timecode under it stays readable. */
.tt-band {
  position: absolute;
  inset-block: 0;
  pointer-events: none;
  border-radius: var(--bs-radius-1);
}
.tt-band--done { background-color: color-mix(in srgb, var(--bs-text-ok) 26%, transparent); }
.tt-band--marked {
  background-color: color-mix(in srgb, var(--bs-accent) 22%, transparent);
  border-inline: var(--bs-border-1) solid var(--bs-accent);
}

.tt-tl__names { background-color: var(--s-top); min-inline-size: 0; }
.tt-tl__name {
  block-size: var(--tt-lane, 2.25rem);
  display: flex;
  align-items: center;
  gap: var(--bs-space-1);
  padding-inline: var(--bs-space-2);
  border-block-end: var(--bs-border-1) solid var(--bs-border-color);
  min-inline-size: 0;
}
.tt-tl__name:last-child { border-block-end: 0; }
.tt-tl__name b {
  /* The names were truncating to "Vo...", "Eff..." and "M..." because the gutter
     had to hold a name, two buttons, a slider and a readout in 11rem. A track
     whose name is one letter is not labelled. */
  flex: none;
  inline-size: 3.25rem;
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--bs-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-inline-size: 0;
}
.tt-tl__gain {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.5625rem;
  color: var(--bs-text-muted);
  flex: none;
}
@media (max-width: 37.5em) { .tt-tl__gain { display: none; } }

.tt-tl__lanes { position: relative; min-inline-size: 0; background-color: var(--s-card); }
.tt-lane {
  block-size: var(--tt-lane, 2.25rem);
  position: relative;
  min-inline-size: 0;
  border-block-end: var(--bs-border-1) solid var(--bs-border-color);
}
.tt-lane:last-child { border-block-end: 0; }
/* Striping, the way every editor marks its tracks so the eye can follow one
   across a long film. */
.tt-lane:nth-child(even) { background-color: var(--s-prev); }

.tt-clip {
  position: absolute;
  inset-block: 2px;
  min-inline-size: 2px;
  border-radius: var(--bs-radius-1);
  border: var(--bs-border-1) solid var(--bs-border-color);
  background-color: var(--s-hover);
  color: var(--bs-text-muted);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  line-height: 1;
  display: flex;
  align-items: center;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  cursor: grab;
  /* The clip owns the horizontal gesture; without this the scroller takes it and
     a drag scrolls the timeline instead of moving the clip. */
  touch-action: none;
  transition: background-color var(--bs-duration-2) var(--ease-out), color var(--bs-duration-2) var(--ease-out);
}
/* DISCOVERABILITY. A feature nobody can see is a feature nobody has. The drag
   worked from the first build and the operator could not find it, which is the
   same defect as it not working. These rules are the whole fix: the cursor says
   grab, the edges grow visible grips on hover, and the body lifts. */
.tt-clip:hover {
  color: var(--bs-text);
  border-color: var(--bs-accent);
  background-color: color-mix(in srgb, var(--bs-accent) 18%, var(--s-hover));
  box-shadow: 0 0 0 1px var(--bs-accent), var(--bs-shadow-2);
  z-index: 5;
}
.tt-clip:active { cursor: grabbing; }

/* The grip: two rules at each end, drawn only on hover so a resting timeline
   stays quiet. This is the part that says "this edge is draggable". */
.tt-clip__handle::after {
  content: '';
  position: absolute;
  inset-block: 20%;
  inset-inline-start: 50%;
  inline-size: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background-color: var(--bs-on-accent);
  opacity: 0;
  transition: opacity var(--bs-duration-2) var(--ease-out);
}
.tt-clip:hover .tt-clip__handle::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline: 0;
  background-color: var(--bs-accent);
}
.tt-clip:hover .tt-clip__handle::after { opacity: 0.9; }
.tt-clip__handle:hover::before { background-color: var(--bs-accent-hover); }
/* Selection is a state, and state is the only thing on this panel that is
   coloured. */
.tt-clip[aria-pressed='true'] {
  background-color: var(--bs-accent);
  border-color: var(--bs-accent);
  color: var(--bs-on-accent);
  z-index: 2;
}
.tt-clip__label {
  position: relative;
  z-index: 1;
  padding-inline: 0.25rem;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

/* The waveform is drawn from peaks measured off the file, so it is the audio and
   not a texture. currentColor is what the canvas paints with, which is why a
   selected clip's waveform flips to the on-accent ink with the rest of it. */
.tt-clip__wave {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  opacity: 0.55;
  pointer-events: none;
}
.tt-clip[aria-pressed='true'] .tt-clip__wave { opacity: 0.75; }

/* A source that is not on disk. The clip stays on the timeline and stays
   editable, because the edit is to the document and the document is fine; what is
   missing is the media, and saying so is more use than hiding the clip. */
.tt-clip--offline {
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 5px,
    color-mix(in srgb, var(--bs-text-danger) 22%, transparent) 5px 10px
  );
  border-color: color-mix(in srgb, var(--bs-text-danger) 50%, transparent);
  color: var(--bs-text-danger);
}

.tt-clip--moving { cursor: grabbing; opacity: 0.85; z-index: 4; }
.tt-clip--reject { outline: 2px solid var(--bs-text-danger); outline-offset: -1px; }

/* Trim handles. Six pixels is the smallest target that can be hit reliably with a
   mouse; the touch case gets a wider one because a fingertip is not a pixel. */
.tt-clip__handle {
  position: absolute;
  inset-block: 0;
  inline-size: 9px;
  /* col-resize rather than ew-resize: it is the cursor every editor uses for a
     trim, so it reads as "change the length" rather than "move". */
  cursor: col-resize;
  z-index: 3;
}
.tt-clip__handle--start { inset-inline-start: 0; }
.tt-clip__handle--end { inset-inline-end: 0; }
@media (pointer: coarse) {
  /* A fingertip is about 9mm. 18px is the smallest grip that can be hit without
     grabbing the clip body by accident. */
  .tt-clip__handle { inline-size: 18px; }
}

/* THE MIXER. gainDb is a field on the track in core/project.mjs, so the slider
   writes there and the panel reads there; there is no parallel volume model. */
.tt-mix {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-inline-size: 0;
}
.tt-mix__btn {
  inline-size: 1.125rem;
  block-size: 1.125rem;
  flex: none;
  border: var(--bs-border-1) solid var(--bs-border-color);
  border-radius: var(--bs-radius-1);
  background-color: transparent;
  color: var(--bs-text-muted);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.tt-mix__btn:hover { color: var(--bs-text); border-color: var(--bs-border-strong); }
.tt-mix__btn[aria-pressed='true'] {
  background-color: var(--bs-accent);
  border-color: var(--bs-accent);
  color: var(--bs-on-accent);
}
.tt-mix__gain {
  inline-size: 3rem;
  min-inline-size: 0;
  flex: none;
  /* The framework's range is sized for a form row; in a 26px track header it has
     to lose its own padding to fit at all. */
  --bs-range-thumb-size: 0.625rem;
  --bs-range-track-size: 0.1875rem;
  block-size: 0.75rem;
}
.tt-mix__db {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.5625rem;
  color: var(--bs-text-muted);
  inline-size: 3.1rem;
  text-align: end;
  flex: none;
}
@media (max-width: 37.5em) {
  .tt-mix__gain, .tt-mix__db { display: none; }
}

.tt-playhead {
  position: absolute;
  inset-block: 0;
  inline-size: 1px;
  background-color: var(--bs-accent);
  pointer-events: none;
  z-index: 3;
}
.tt-playhead::before {
  content: '';
  position: absolute;
  inset-block-start: -1.25rem;
  inset-inline-start: -0.25rem;
  inline-size: 0.5625rem;
  block-size: 0.5625rem;
  background-color: var(--bs-accent);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

/* The transport. A deck: buttons, the timecode pair, the position bar. */
.tt-transport {
  display: flex;
  align-items: center;
  gap: var(--bs-space-2);
  block-size: 1.875rem;
  padding-inline: var(--bs-space-2);
  background-color: var(--s-top);
  border: var(--bs-border-1) solid var(--bs-border-color);
  border-radius: var(--bs-radius-2);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.6875rem;
  color: var(--bs-text-secondary);
  min-inline-size: 0;
}
.tt-transport__bar {
  flex: 1;
  min-inline-size: 0;
  block-size: 3px;
  border-radius: var(--bs-radius-full);
  background-color: var(--bs-border-color);
  position: relative;
  overflow: hidden;
}
.tt-transport__bar i { position: absolute; inset-block: 0; inset-inline-start: 0; background-color: var(--bs-accent); display: block; }

/* ===========================================================================
   3. RENDER SURFACES - not in the framework
   ===========================================================================
   A project thumbnail is a picture of the video, so it is painted in the palette's
   own ground and accent rather than in a panel surface. bare.style's card owns the
   shell around it; this is only the media area inside .bs-card__media. */

.tt-thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  background-color: var(--sw-bg);
  color: var(--sw-fg);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--bs-space-2);
  container-type: inline-size;
  min-inline-size: 0;
}
.tt-thumb__kick {
  font-family: var(--font-mono);
  font-size: clamp(6px, 3.6cqi, 9px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sw-acc);
  margin-block-end: auto;
  padding-inline-end: 4.25rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tt-thumb__ov {
  font-size: clamp(9px, 6cqi, 14px);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.tt-thumb__rule { block-size: 2px; inline-size: 1.75rem; background-color: var(--sw-acc); margin-block-start: 0.375rem; }
/* Sits on the render rather than a panel surface, so it carries its own plate:
   white on 40% black is 10.4:1 over the palest of the five palettes. */
.tt-thumb__badge {
  position: absolute;
  inset-block-start: var(--bs-space-2);
  inset-inline-end: var(--bs-space-2);
  font-family: var(--font-mono);
  font-size: 0.5rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: var(--bs-radius-1);
  padding: 0.125rem 0.375rem;
  background-color: rgba(0, 0, 0, 0.4);
  color: #ffffff;
}

/* The palette swatches. Painted in the palette's ground and accent for the same
   reason: a swatch is a render. */
.tt-sw {
  inline-size: 1.125rem;
  block-size: 1.125rem;
  border-radius: var(--bs-radius-1);
  border: var(--bs-border-1) solid var(--bs-border-color);
  padding: 0;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  flex: none;
  background-color: var(--sw-bg);
}
.tt-sw::after {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: 42%;
  background-color: var(--sw-acc);
}
.tt-sw[aria-pressed='true'] { box-shadow: 0 0 0 1px var(--s-card), 0 0 0 2px var(--bs-accent); }
.tt-sw-row { display: flex; align-items: center; gap: var(--bs-space-1); }

/* ===========================================================================
   4. FULL SCREEN - not in the framework
   ===========================================================================
   Two paths to one appearance. The Fullscreen API is used where it is granted;
   iOS Safari refuses requestFullscreen on an arbitrary element, so the fallback
   is a fixed full-viewport layer. Both end up with the same class on the root and
   therefore the same rules, which is what keeps the two indistinguishable to a
   reader and stops the fallback becoming a second design. */

.tt-fs .bs-shell-rail,
.tt-fs .bs-shell-topbar { display: none; }

/* The shell's own grid still owns the frame, so full screen is expressed by
   collapsing its template rather than by taking the editor out of it. */
.tt-fs .bs-shell {
  grid-template-areas: 'main';
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}

/* In full screen the inspector becomes a slide-over rather than disappearing:
   losing access to it would be a different product, not a bigger canvas. */
.tt-fs .bs-shell-inspector {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: var(--bs-z-drawer);
  inline-size: min(20rem, 88vw);
  border-inline-start: var(--bs-border-1) solid var(--bs-border-color);
  box-shadow: var(--bs-shadow-4);
  transform: translateX(101%);
  transition: transform var(--bs-duration-3) var(--ease-out);
}
.tt-fs.tt-fs-insp .bs-shell-inspector { transform: none; }

/* The element the browser actually promotes. Without this the promoted element
   keeps its grid size and paints letterboxed inside a black page. */
.tt-editor:fullscreen,
.tt-editor:-webkit-full-screen {
  inline-size: 100vw;
  block-size: 100vh;
  background-color: var(--s-main);
}

/* Views. Only one is in the flow at a time; the rest are out of the tab order as
   well as off the screen, which is what [hidden] carries and a class does not. */
.tt-view { display: none; min-inline-size: 0; }
.tt-view.is-active { display: block; }
.tt-view--fill.is-active { display: grid; block-size: 100%; min-block-size: 0; }

/* The editor view needs the work area to stop scrolling and become a fixed frame,
   because its timeline is pinned rather than paged. Every other view keeps the
   framework's scrolling main. */
.bs-shell[data-view='editor'] .bs-shell-main { overflow: hidden; min-block-size: 0; }
.bs-shell[data-view='editor'] .bs-shell-main__content { padding: 0; block-size: 100%; min-block-size: 0; }

@media (prefers-reduced-motion: reduce) {
  .tt-fs .bs-shell-inspector { transition: none; }
}


/* ---------------------------------------------------------------------------
 * SNAP LINE, CONTEXT MENU, KEY HINTS
 *
 * All three exist for the same reason as the cursors above: the behaviour was
 * already there and nothing on screen admitted it.
 * ------------------------------------------------------------------------- */

/* Drawn only while a drag is actually snapped to something, so the line is a
   statement about this drag rather than permanent furniture. */
.tt-snap {
  position: absolute;
  inset-block: 0;
  inline-size: 1px;
  background-color: var(--bs-text-ok);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--bs-text-ok) 40%, transparent);
  pointer-events: none;
  z-index: 6;
}
.tt-snap::before {
  content: attr(data-snap-label);
  position: absolute;
  inset-block-start: -1.15rem;
  inset-inline-start: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.5rem;
  white-space: nowrap;
  padding: 1px 4px;
  border-radius: var(--bs-radius-1);
  background-color: var(--bs-text-ok);
  color: var(--s-main);
}

/* The framework's menu, positioned at the pointer. bare.style owns every rule
   inside it; this only places it.
   The framework's list is display:none until its own trigger reports expanded,
   and it places itself against that trigger. A context menu has no trigger: it
   opens where the pointer is. Inside this host only, the list is shown and left
   in flow so the host is what gets measured and placed - the framework's own
   look, ours to position. A hidden list measures 0x0, which is what put the menu
   off screen: the clamp had nothing to subtract. */
.tt-menu-host {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: var(--bs-z-popover);
  inline-size: max-content;
}
.tt-menu-host > .bs-menu__list {
  display: block;
  position: static;
  inline-size: max-content;
  max-inline-size: 18rem;
  max-block-size: 70vh;
}

.tt-keys {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bs-space-2);
  align-items: center;
  padding: 0.3rem var(--bs-space-2);
  border-block-start: var(--bs-border-1) solid var(--bs-border-color);
  background-color: var(--s-top);
  font-size: 0.625rem;
  color: var(--bs-text-muted);
  min-inline-size: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.tt-keys::-webkit-scrollbar { block-size: 0; }
.tt-keys b { font-weight: 500; color: var(--bs-text-secondary); }
.tt-keys kbd {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  padding: 1px 4px;
  border: var(--bs-border-1) solid var(--bs-border-color);
  border-radius: var(--bs-radius-1);
  background-color: var(--s-card);
  color: var(--bs-text-secondary);
}
.tt-keys span { display: inline-flex; align-items: center; gap: 0.25rem; white-space: nowrap; }
