Skip to content

Commit bb266a2

Browse files
authored
Redesign timeline UI (#56)
1 parent 24e07ed commit bb266a2

10 files changed

Lines changed: 785 additions & 52 deletions

File tree

celstomp/celstomp-app.js

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@
362362

363363

364364
function renderBounds() {
365+
fxctx.setTransform(1, 0, 0, 1, 0, 0);
366+
fxctx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
365367
setTransform(bctx);
366368
setTransform(fxctx);
367369
bctx.fillStyle = "#2a2f38";
@@ -902,7 +904,13 @@
902904
safeSetChecked(onion, onionEnabled);
903905
safeSetChecked(psnap, playSnapped);
904906
toggle?.addEventListener("click", () => {
905-
if (isPlaying) pausePlayback(); else startPlayback();
907+
if (isPlaying) {
908+
pausePlayback();
909+
toggle.classList.remove("playing");
910+
} else {
911+
startPlayback();
912+
toggle.classList.add("playing");
913+
}
906914
});
907915
prevF?.addEventListener("click", () => gotoFrame(stepBySnap(-1)));
908916
nextF?.addEventListener("click", () => gotoFrame(stepBySnap(1)));
@@ -919,6 +927,95 @@
919927
safeSetValue(snapValue, v);
920928
updateHUD();
921929
});
930+
const gridToggle = $("tlGridBtn");
931+
const gridSnapToggle = $("tlGridSnapBtn");
932+
const rulersToggle = $("tlRulersBtn");
933+
const guideSnapToggle = $("tlGuideSnapBtn");
934+
const addHGuideBtn = $("addHGuideBtn");
935+
const addVGuideBtn = $("addVGuideBtn");
936+
const clearGuidesBtn = $("clearGuidesBtn");
937+
const guideModeHint = $("guideModeHint");
938+
const gridSizeInput = $("tlGridSize");
939+
940+
let guidePlacementMode = null;
941+
942+
if (gridToggle) {
943+
gridToggle.addEventListener("click", e => {
944+
gridEnabled = !gridEnabled;
945+
gridToggle.classList.toggle("active", gridEnabled);
946+
queueRenderAll();
947+
});
948+
}
949+
if (gridSizeInput) {
950+
gridSizeInput.addEventListener("change", e => {
951+
const v = Math.max(8, Math.min(128, parseInt(e.target.value) || 32));
952+
gridSize = v;
953+
e.target.value = v;
954+
queueRenderAll();
955+
});
956+
}
957+
if (gridSnapToggle) {
958+
gridSnapToggle.addEventListener("click", e => {
959+
gridSnap = !gridSnap;
960+
gridSnapToggle.classList.toggle("active", gridSnap);
961+
});
962+
}
963+
if (rulersToggle) {
964+
rulersToggle.addEventListener("click", e => {
965+
rulersEnabled = !rulersEnabled;
966+
rulersToggle.classList.toggle("active", rulersEnabled);
967+
queueRenderAll();
968+
});
969+
}
970+
if (guideSnapToggle) {
971+
guideSnapToggle.addEventListener("click", e => {
972+
guideSnap = !guideSnap;
973+
guideSnapToggle.classList.toggle("active", guideSnap);
974+
});
975+
}
976+
function setGuidePlacementMode(mode) {
977+
guidePlacementMode = mode;
978+
if (addHGuideBtn) addHGuideBtn.classList.toggle("active", mode === "horizontal");
979+
if (addVGuideBtn) addVGuideBtn.classList.toggle("active", mode === "vertical");
980+
if (guideModeHint) {
981+
guideModeHint.hidden = !mode;
982+
guideModeHint.textContent = mode === "horizontal" ? "Click Canvas To Place H Guide" : mode === "vertical" ? "Click Canvas To Place V Guide" : "";
983+
}
984+
if (!mode) {
985+
document.body.classList.remove("guide-place-mode");
986+
document.body.classList.remove("guide-place-h");
987+
document.body.classList.remove("guide-place-v");
988+
} else {
989+
document.body.classList.add("guide-place-mode");
990+
document.body.classList.toggle("guide-place-h", mode === "horizontal");
991+
document.body.classList.toggle("guide-place-v", mode === "vertical");
992+
}
993+
}
994+
if (addHGuideBtn) {
995+
addHGuideBtn.addEventListener("click", e => {
996+
if (guidePlacementMode === "horizontal") {
997+
setGuidePlacementMode(null);
998+
} else {
999+
setGuidePlacementMode("horizontal");
1000+
}
1001+
});
1002+
}
1003+
if (addVGuideBtn) {
1004+
addVGuideBtn.addEventListener("click", e => {
1005+
if (guidePlacementMode === "vertical") {
1006+
setGuidePlacementMode(null);
1007+
} else {
1008+
setGuidePlacementMode("vertical");
1009+
}
1010+
});
1011+
}
1012+
if (clearGuidesBtn) {
1013+
clearGuidesBtn.addEventListener("click", () => {
1014+
guides = [];
1015+
queueRenderAll();
1016+
});
1017+
}
1018+
window.__celstompSetGuidePlacementMode = setGuidePlacementMode;
9221019
function rebuildTimelineKeepFrame() {
9231020
const cur = currentFrame;
9241021
buildTimeline();
@@ -953,6 +1050,7 @@
9531050
const showLeft = $("showLeftEdge");
9541051
const showRight = $("showRightEdge");
9551052
const showTl = $("showTimelineEdge");
1053+
const timelineEl = $("timeline");
9561054
const tLeft = $("toggleSidebarBtn");
9571055
const tRight = $("toggleRightbarBtn");
9581056
function applyLayoutChange() {
@@ -974,8 +1072,28 @@
9741072
}
9751073
function setTimelineOpen(open) {
9761074
app.classList.toggle("tl-collapsed", !open);
1075+
document.body?.classList.toggle("tl-collapsed", !open);
1076+
if (timelineEl) {
1077+
timelineEl.hidden = !open;
1078+
timelineEl.style.display = open ? "" : "none";
1079+
timelineEl.setAttribute("aria-hidden", open ? "false" : "true");
1080+
}
1081+
if (showTl) {
1082+
showTl.style.display = open ? "none" : "block";
1083+
}
9771084
applyLayoutChange();
9781085
}
1086+
if (!document._celstompPanelToggleDelegated) {
1087+
document._celstompPanelToggleDelegated = true;
1088+
document.addEventListener("click", e => {
1089+
if (e.target.closest("#hideLeftPanelBtn")) setLeftOpen(false);
1090+
if (e.target.closest("#hideRightPanelBtn")) setRightOpen(false);
1091+
if (e.target.closest("#hideTimelineBtn")) setTimelineOpen(false);
1092+
if (e.target.closest("#showLeftEdge")) setLeftOpen(true);
1093+
if (e.target.closest("#showRightEdge")) setRightOpen(true);
1094+
if (e.target.closest("#showTimelineEdge")) setTimelineOpen(true);
1095+
});
1096+
}
9791097
setLeftOpen(true);
9801098
setRightOpen(true);
9811099
setTimelineOpen(true);

celstomp/css/components/island.css

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,50 @@
5454
cursor: grabbing;
5555
}
5656

57+
.islandDock.drag-lock-candidate {
58+
outline: 2px solid rgba(92, 179, 255, 0.6);
59+
outline-offset: -2px;
60+
}
61+
62+
.islandDock.right-locked {
63+
left: auto !important;
64+
right: var(--dock-gap);
65+
top: calc(var(--header-h) + var(--dock-gap));
66+
bottom: calc(var(--timeline-h) + var(--dock-gap-bottom));
67+
height: auto !important;
68+
width: min(320px, 36vw);
69+
}
70+
71+
.islandDock.right-locked .islandHeader {
72+
background: rgba(92, 179, 255, 0.12);
73+
border-bottom-color: rgba(92, 179, 255, 0.35);
74+
}
75+
76+
.islandLockHint {
77+
position: fixed;
78+
top: calc(var(--header-h) + 14px);
79+
right: 10px;
80+
width: 180px;
81+
height: calc(100vh - var(--header-h) - var(--timeline-h) - 24px);
82+
border: 2px dashed rgba(92, 179, 255, 0.32);
83+
border-radius: 10px;
84+
background: rgba(92, 179, 255, 0.08);
85+
color: rgba(180, 225, 255, 0.9);
86+
display: none;
87+
align-items: center;
88+
justify-content: center;
89+
text-align: center;
90+
font-size: 11px;
91+
letter-spacing: 0.08em;
92+
text-transform: uppercase;
93+
z-index: 39;
94+
pointer-events: none;
95+
}
96+
97+
.islandLockHint.active {
98+
display: flex;
99+
}
100+
57101
.islandTitle{
58102
font-size: 12px;
59103
letter-spacing: 0.08em;
@@ -222,6 +266,30 @@
222266
overflow-x: hidden;
223267
}
224268

269+
.islandSideBody {
270+
scrollbar-width: thin;
271+
scrollbar-color: rgba(255,255,255,0.24) rgba(0,0,0,0.22);
272+
}
273+
274+
.islandSideBody::-webkit-scrollbar {
275+
width: 10px;
276+
}
277+
278+
.islandSideBody::-webkit-scrollbar-track {
279+
background: rgba(0,0,0,0.24);
280+
border-radius: 999px;
281+
}
282+
283+
.islandSideBody::-webkit-scrollbar-thumb {
284+
background: rgba(255,255,255,0.24);
285+
border-radius: 999px;
286+
border: 2px solid rgba(0,0,0,0.22);
287+
}
288+
289+
.islandSideBody::-webkit-scrollbar-thumb:hover {
290+
background: rgba(255,255,255,0.34);
291+
}
292+
225293
.islandSideGrid{
226294
display: flex;
227295
flex-direction: column;

celstomp/css/components/layers.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@ body.swatch-reordering{
104104
border-radius: 0px;
105105
}
106106

107+
#islandLayersSlot {
108+
scrollbar-width: thin;
109+
scrollbar-color: rgba(255,255,255,0.24) rgba(0,0,0,0.22);
110+
}
111+
112+
#islandLayersSlot::-webkit-scrollbar {
113+
width: 10px;
114+
}
115+
116+
#islandLayersSlot::-webkit-scrollbar-track {
117+
background: rgba(0,0,0,0.24);
118+
border-radius: 999px;
119+
}
120+
121+
#islandLayersSlot::-webkit-scrollbar-thumb {
122+
background: rgba(255,255,255,0.24);
123+
border-radius: 999px;
124+
border: 2px solid rgba(0,0,0,0.22);
125+
}
126+
127+
#islandLayersSlot::-webkit-scrollbar-thumb:hover {
128+
background: rgba(255,255,255,0.34);
129+
}
130+
107131
#islandLayersSlot #layerSeg{
108132
display: flex !important;
109133
flex-direction: column !important;

celstomp/css/components/overlays.css

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,124 @@
120120
opacity: .95;
121121
}
122122

123+
.canvasTextEntry {
124+
position: fixed;
125+
inset: 0;
126+
display: none;
127+
align-items: center;
128+
justify-content: center;
129+
background: rgba(0, 0, 0, 0.32);
130+
z-index: 9400;
131+
}
132+
133+
.canvasTextEntry.open {
134+
display: flex;
135+
}
136+
137+
.canvasTextEntryCard {
138+
width: min(420px, calc(100vw - 24px));
139+
border: 1px solid rgba(255, 255, 255, 0.16);
140+
background: rgba(16, 20, 28, 0.96);
141+
border-radius: 10px;
142+
box-shadow: 0 16px 42px rgba(0, 0, 0, 0.45);
143+
padding: 12px;
144+
display: grid;
145+
gap: 10px;
146+
}
147+
148+
.canvasTextEntryLabel {
149+
font-size: 12px;
150+
color: #c7d0db;
151+
letter-spacing: 0.04em;
152+
text-transform: uppercase;
153+
}
154+
155+
.canvasTextEntryInput {
156+
width: 100%;
157+
min-height: 36px;
158+
border: 1px solid rgba(255, 255, 255, 0.14);
159+
background: rgba(0, 0, 0, 0.28);
160+
color: #e2e8f0;
161+
border-radius: 8px;
162+
padding: 8px 10px;
163+
}
164+
165+
.canvasTextEntryInput:focus {
166+
outline: none;
167+
border-color: rgba(92, 179, 255, 0.8);
168+
box-shadow: 0 0 0 2px rgba(92, 179, 255, 0.2);
169+
}
170+
171+
.canvasTextEntryOptions {
172+
display: grid;
173+
grid-template-columns: repeat(2, minmax(0, 1fr));
174+
gap: 8px;
175+
}
176+
177+
.canvasTextEntryOpt {
178+
display: grid;
179+
gap: 4px;
180+
}
181+
182+
.canvasTextEntryOpt > span {
183+
font-size: 11px;
184+
color: #b7c3d2;
185+
letter-spacing: 0.03em;
186+
}
187+
188+
.canvasTextEntrySelect,
189+
.canvasTextEntryNum {
190+
width: 100%;
191+
min-height: 32px;
192+
border: 1px solid rgba(255, 255, 255, 0.14);
193+
background: rgba(0, 0, 0, 0.28);
194+
color: #e2e8f0;
195+
border-radius: 8px;
196+
padding: 6px 8px;
197+
}
198+
199+
.canvasTextEntrySelect:focus,
200+
.canvasTextEntryNum:focus {
201+
outline: none;
202+
border-color: rgba(92, 179, 255, 0.8);
203+
box-shadow: 0 0 0 2px rgba(92, 179, 255, 0.2);
204+
}
205+
206+
.canvasTextEntryOptCheck {
207+
align-items: center;
208+
grid-template-columns: auto 1fr;
209+
gap: 8px;
210+
border: 1px solid rgba(255, 255, 255, 0.12);
211+
border-radius: 8px;
212+
background: rgba(255, 255, 255, 0.04);
213+
padding: 6px 8px;
214+
}
215+
216+
.canvasTextEntryOptCheck input {
217+
margin: 0;
218+
}
219+
220+
.canvasTextEntryActions {
221+
display: flex;
222+
justify-content: flex-end;
223+
gap: 8px;
224+
}
225+
226+
.canvasTextEntryBtn {
227+
min-height: 34px;
228+
padding: 0 12px;
229+
border-radius: 8px;
230+
border: 1px solid rgba(255, 255, 255, 0.14);
231+
background: rgba(255, 255, 255, 0.06);
232+
color: #d0d8e2;
233+
}
234+
235+
.canvasTextEntryBtnPrimary {
236+
border-color: rgba(92, 179, 255, 0.6);
237+
background: rgba(92, 179, 255, 0.18);
238+
color: #9fd5ff;
239+
}
240+
123241
.infoList{
124242
margin: 0;
125243
padding-left: 18px;

0 commit comments

Comments
 (0)