The surfaces v4 has that v5 did not. Each announces itself and each can be absent without the rest noticing, which is the point of the exercise: the frontend should read as v4, and nothing in it should be load-bearing for anything else. * SCENE AND PROJECT, in the column v4 keeps them in, which now says which scene it is about. Length on the timeline is the scene's own CROP and says outright that a regenerate reads the project's -- the rule that mixing the two is how a per-scene value quietly becomes everyone's. * RATINGS on a scene, and a clip carries its rating as a colour on its edge. Stored, not yet read: nothing in v5 learns from them yet. * A RULER, and clips as wide as they are long, so the strip reads as time rather than a row of equal boxes. Both ends bounded -- one long clip must not become the whole strip, and one short one must stay hittable. * THE PICKER WHEEL, on the middle button anywhere. It shows whatever announced itself to actions.js, so a part of the app that grew a new action is in the wheel without the wheel being told, and a part that failed to load is simply not among them. v4's three pinned buttons had a fixed idea of what could go in them. * THE WAY IN, and it closes -- on Escape, on the backdrop, on its own button. v4's could only be completed: a page refresh was the only way out, which makes it a trap rather than a wizard. Dismissing is a real answer. * File > rename, and the listing moves with it rather than going stale until a reload. Two things the kit refused, correctly, and now nobody has to know: a wheel of fewer than two items throws (there is nothing to aim between), so the shell does not offer one; and a strip cell with no ceiling took the whole strip.
143 lines
6.9 KiB
CSS
143 lines
6.9 KiB
CSS
/* Sized from the CONTAINER, not the viewport: a 280px panel lays out like a
|
|
* 280px panel however wide the window is. */
|
|
.cx-gallery-wrap { container-type: inline-size; width: 100%; }
|
|
.cx-gallery {
|
|
display: grid; gap: clamp(6px, 2.5cqw, 12px);
|
|
grid-template-columns: repeat(auto-fill, minmax(var(--cell, var(--cell-thumb)), 1fr));
|
|
}
|
|
.cx-gallery[data-cols="1"] { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
.cx-gallery[data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.cx-gallery[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.cx-gallery[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
|
|
.cx-cell {
|
|
display: flex; flex-direction: column; gap: var(--sp-2); padding: 0;
|
|
border: var(--bw) solid var(--line); border-radius: var(--r-3);
|
|
background: var(--elev-2); color: var(--text); cursor: pointer; overflow: hidden;
|
|
transition: border-color var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-cell:hover { border-color: var(--line-hover); }
|
|
.cx-cell.cx-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
|
|
.cx-cell-thumb {
|
|
position: relative; aspect-ratio: 16 / 10; background: var(--void);
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.cx-cell-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
.cx-cell-glyph { font-size: clamp(18px, calc(var(--cell, 96px) * .3), 34px); color: var(--faint); }
|
|
.cx-cell-badge, .cx-cell-duration {
|
|
position: absolute; bottom: var(--sp-1); font-size: var(--fs-8);
|
|
padding: 1px var(--sp-2); border-radius: var(--r-1);
|
|
background: color-mix(in srgb, var(--void) 70%, transparent); color: var(--hi, #fff);
|
|
}
|
|
.cx-cell-badge { left: var(--sp-1); }
|
|
.cx-cell-duration { right: var(--sp-1); font-variant-numeric: tabular-nums; }
|
|
.cx-cell-name {
|
|
font-size: clamp(10px, calc(var(--cell, 96px) * .115), 13px);
|
|
line-height: var(--lh-snug); padding: 0 var(--sp-3) var(--sp-3);
|
|
text-align: left; display: -webkit-box; -webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical; overflow: hidden;
|
|
}
|
|
|
|
/* Icons: as many captionless tiles as fit. Square, because without a name
|
|
under it a 16:10 tile reads as a cropped picture rather than a tile. */
|
|
.cx-gallery-icons {
|
|
grid-template-columns: repeat(auto-fill, minmax(var(--cell-icon), 1fr));
|
|
gap: var(--sp-2);
|
|
}
|
|
.cx-cell-icon { border-radius: var(--r-2); }
|
|
.cx-cell-icon .cx-cell-thumb { aspect-ratio: 1 / 1; }
|
|
|
|
/* List: the whole name, which is the reason to be in this view at all. */
|
|
.cx-media-list { display: flex; flex-direction: column; gap: var(--sp-1); }
|
|
.cx-media-row {
|
|
display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
|
|
align-items: center; gap: var(--sp-3);
|
|
padding: var(--sp-1) var(--sp-2); text-align: left; cursor: pointer;
|
|
border: var(--bw) solid transparent; border-radius: var(--r-2);
|
|
background: none; color: var(--text);
|
|
transition: border-color var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-media-row:hover { border-color: var(--line-hover); background: var(--elev-2); }
|
|
.cx-media-row.cx-on { border-color: var(--accent); background: var(--accent-soft); }
|
|
.cx-media-row-thumb {
|
|
position: relative; flex: none; inline-size: 40px; aspect-ratio: 16 / 10;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: var(--void); border-radius: var(--r-1); overflow: hidden;
|
|
}
|
|
.cx-media-row-name {
|
|
font-size: var(--fs-7); line-height: var(--lh-snug);
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.cx-media-row-hint { font-size: var(--fs-8); color: var(--faint); }
|
|
|
|
.cx-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--cell-card), 1fr)); gap: var(--sp-5); }
|
|
.cx-card {
|
|
display: flex; flex-direction: column; gap: var(--sp-2); text-align: left;
|
|
padding: var(--sp-6); border: var(--bw) solid var(--line); border-radius: var(--r-5);
|
|
background: var(--elev-1); color: var(--text); cursor: pointer;
|
|
transition: border-color var(--dur-1) var(--ease-standard), background var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-card:hover { border-color: var(--line-hover); }
|
|
.cx-card.cx-on { border-color: var(--accent); background: var(--accent-soft); }
|
|
.cx-card-icon { font-size: var(--fs-d3); line-height: 1; color: var(--accent); }
|
|
.cx-card-title { font-size: var(--fs-3); font-weight: var(--fw-semibold); }
|
|
|
|
/* A scroller that can actually shrink. Without an explicit floor a flex item's
|
|
automatic minimum is its CONTENT, so a strip of a dozen scenes made the
|
|
window as wide as the strip -- it scrolled sideways as a page instead of
|
|
scrolling inside itself, which is the one thing it exists to do. */
|
|
.cx-strip {
|
|
display: flex; gap: var(--sp-2); overflow-x: auto; padding-bottom: var(--sp-2);
|
|
min-inline-size: 0;
|
|
}
|
|
.cx-strip-cell {
|
|
/* A floor and a ceiling. A clip grows with its length (flex-grow, set by the
|
|
element), and one clip in a project must not become the whole strip. */
|
|
flex: none; width: 72px; min-inline-size: 72px; max-inline-size: var(--cell-panel);
|
|
aspect-ratio: 16 / 10; padding: 0;
|
|
border: var(--bw) solid var(--line); border-radius: var(--r-2);
|
|
background: var(--void); overflow: hidden; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.cx-strip-cell:hover { border-color: var(--accent); }
|
|
.cx-strip-face { inline-size: 100%; block-size: 100%; display: flex; align-items: center; justify-content: center; }
|
|
|
|
/* The result, shown whole. `contain` rather than `cover`: a generated frame
|
|
cropped to fit its box is a different picture from the one that was made. */
|
|
.cx-viewer { display: flex; min-block-size: 0; block-size: 100%; }
|
|
/* Letterbox black, not a panel colour: what is around a frame is part of how
|
|
the frame reads, and a dark grey surround makes a dark shot look washed. */
|
|
.cx-viewer-stage {
|
|
flex: 1 1 auto;
|
|
display: flex; align-items: center; justify-content: center;
|
|
min-block-size: 0; min-inline-size: 0;
|
|
background: var(--void);
|
|
}
|
|
.cx-viewer-media {
|
|
max-inline-size: 100%; max-block-size: 100%;
|
|
inline-size: auto; block-size: auto;
|
|
object-fit: contain;
|
|
}
|
|
|
|
/* A clip carries its rating as a colour on its edge -- the one thing about a
|
|
result you want to see without selecting it. */
|
|
.cx-strip-cell.cx-rated { border-color: var(--muted); }
|
|
.cx-strip-cell[data-rating="perfect"] { border-color: var(--good); }
|
|
.cx-strip-cell[data-rating="good"] { border-color: var(--teal); }
|
|
.cx-strip-cell[data-rating="wrong"] { border-color: var(--warn); }
|
|
.cx-strip-cell[data-rating="awful"] { border-color: var(--danger); }
|
|
|
|
/* Where one scene becomes the next. Positioned in percent so it tracks the row
|
|
above it at any width, and never taller than the numbers it draws. */
|
|
.cx-ruler {
|
|
position: relative; block-size: var(--lh-8); flex: none;
|
|
border-block-end: var(--bw) solid var(--line-2);
|
|
}
|
|
.cx-ruler-mark {
|
|
position: absolute; inset-block-start: 0;
|
|
padding-inline-start: var(--sp-1);
|
|
border-inline-start: var(--bw) solid var(--line);
|
|
font-size: var(--fs-8); line-height: var(--lh-8); color: var(--faint);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|