Unify timeline placement and gesture geometry

This commit is contained in:
Your Name 2026-10-01 23:35:22 -04:00
parent 4e5e02c856
commit 0cb9d0ebf6
16 changed files with 702 additions and 121 deletions

View file

@ -56,6 +56,18 @@
--aim: #8d4bd6;
--aim-stage: #c79bf2;
/* A THIRD, and the last. SELECTED IN THE TIMELINE had been --sel-bg, which is
#cfe0f5 against spans drawn in #dfe8f4: two pale blues a hair apart, in the
one place where which row and which clip is selected decides what the pane
is showing — an expanded lane opens exactly the selected clip. The fix is
not a stronger blue, which stays in the family the spans already own, but a
different hue entirely. Ochre: warm against every cool surface in the pane,
clear of --aim's violet, --live's green and the playhead's red, and dark
enough at full strength to carry white type on a 21px block. */
--pick: #a85c00;
--pick-bg: #ffd9a0;
--pick-fg: #4a2800;
/* Auto-key is a recording state, distinct from selection and aiming. */
--live: #239447;
--live-bg: #e2f5e7;
@ -975,6 +987,14 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
as tall as its rows instead, and at least as tall as the body. */
align-items: flex-start;
overflow: auto;
/* THE GUTTER IS ALWAYS THERE, whether or not there is a scrollbar in it. Every
mark in this pane is a percentage of the tracks column, and the column is
what is left of the body after the labels -- so a vertical scrollbar
appearing took 15px out of the width and moved the ruler, the frame grid,
every clip and the playhead sideways, mid-gesture. Expanding a lane's portal
adds seven rows at once, which is exactly when a person is looking at the
frame they are aiming for. */
scrollbar-gutter: stable;
}
.tl-labels,
@ -1039,11 +1059,38 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
overflow: hidden;
}
.tl-label.on { background: var(--sel-bg); }
/* Selected, in the timeline's own colour. The bar down the left edge is the
half that survives at a glance: a row 21px tall scrolled past in a column of
twenty reads its left edge before it reads its fill. */
.tl-label.on {
background: var(--pick-bg);
box-shadow: inset 3px 0 0 var(--pick);
color: var(--pick-fg);
}
.tl-label.on .kind { color: var(--pick); }
.tl-label.aimed { box-shadow: inset 0 0 0 2px var(--aim); }
/* Both at once, and they must still be two readable facts: the aim's ring sits
inside the pick's edge rather than replacing it. */
.tl-label.on.aimed { box-shadow: inset 3px 0 0 var(--pick), inset 0 0 0 2px var(--aim); }
.tl-label:hover:not(.on) { background: #fff; }
.tl-label .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tl-label .kind { color: var(--dim); }
/* A lane's row: the strip glyph, a faint warm ground under the name, and a
track behind its blocks — enough that a lane is told from the instance rows
around it while scanning, and not so much that it shouts. */
.tl-ico {
flex: 0 0 12px;
line-height: 0;
color: var(--dim);
}
.tl-ico > svg { display: block; width: 11px; height: 11px; }
.tl-label.lane { background: #edeae2; }
.tl-label.lane .name { font-weight: 600; }
.tl-label.lane.on { background: var(--pick-bg); }
.tl-label.lane.on .tl-ico { color: var(--pick); }
.tl-label.lane:hover:not(.on) { background: #f8f6f0; }
.tl-track.lane { background: rgba(0, 0, 0, 0.035); }
.tl-name-input { min-width: 0; flex: 1; font: inherit; }
.tl-rename { margin-left: auto; padding: 0 3px; border: 0; background: none; color: var(--dim); }
.tl-rename:hover { color: var(--fg); }
@ -1138,6 +1185,22 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
.tl-label.drop-front { box-shadow: inset 0 2px 0 var(--sel); }
.tl-label.drop-back { box-shadow: inset 0 -2px 0 var(--sel); }
/* Selected: the clip block and the plain bar alike, in the pick colour. A clip
block had no selected state at all, which is why the portal under an expanded
lane looked like it opened at random. */
.tl-cel.on {
background: var(--pick);
border-color: var(--pick);
color: #fff;
font-weight: 600;
}
.tl-cel.on:hover:not(:disabled) { background: var(--pick); filter: brightness(1.12); }
.tl-span.on { background: var(--pick-bg); border-color: var(--pick); }
.tl-span.on.dense {
background: repeating-linear-gradient(
-45deg, var(--pick-bg) 0 3px, #f0bf77 3px 6px);
}
/* A node's bar slides it along its symbol's time. */
.tl-span.movable { cursor: ew-resize; touch-action: none; }
.tl-span.movable.sliding { border-color: var(--sel); }
@ -1155,7 +1218,10 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
.tl-junction {
position: absolute;
top: -1px;
left: 0;
/* Centred on the CUT, which is the clips' shared border-box edge. A clip block
is a button with a 1px border, so `left: 0` is one pixel inside it and the
handle sat a pixel to the right of the line it drags. */
left: -1px;
bottom: -1px;
width: 12px;
transform: translateX(-50%);
@ -1163,7 +1229,18 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
touch-action: none;
z-index: 3;
}
.tl-cel-label { display: block; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
/* `nowrap` is load-bearing. A block narrower than its name wrapped it at the
hyphen — "symbol-" then "8" — and the button is `overflow: visible` so that
second line spilled out below the track as a row of loose digits under the
lane, which read as corrupt timing rather than as a long name in a short
block. One line, clipped. */
.tl-cel-label {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
}
.tl-span.ghost {
background: transparent;
border: 1px dashed var(--sel);