translucent stuff etc

This commit is contained in:
Your Name 2026-06-30 02:42:31 -04:00
parent 5bc53c03f3
commit 96639554e4
2 changed files with 128 additions and 58 deletions

View file

@ -54,20 +54,20 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
press. The default action (.save) gets the heavy Mac "default button" ring. */
.jump-btn, .add-btn, .edit-btn, .del-btn, .expand-btn, .back-btn, .crumb,
.play-btn, .step-btn, .row-x, .add-mark, .save, .cancel, .hl-ok, .hl-cancel,
.pane-tabs button {
.pane-tabs button, .track-toggle {
font-family: var(--chicago); background: var(--paper); color: var(--ink);
border: 1px solid var(--ink); border-radius: 8px; cursor: pointer; line-height: 1.3;
}
.jump-btn:hover, .add-btn:hover, .edit-btn:hover, .del-btn:hover, .expand-btn:hover,
.back-btn:hover, .crumb:hover, .play-btn:hover, .step-btn:hover, .row-x:hover,
.add-mark:hover, .save:hover, .cancel:hover, .hl-ok:hover, .hl-cancel:hover,
.pane-tabs button:hover {
.pane-tabs button:hover, .track-toggle:hover {
background: var(--hover);
}
.jump-btn:active, .add-btn:active, .edit-btn:active, .del-btn:active, .expand-btn:active,
.back-btn:active, .crumb:active, .play-btn:active, .step-btn:active, .row-x:active,
.add-mark:active, .save:active, .cancel:active, .hl-ok:active, .hl-cancel:active,
.pane-tabs button:active {
.pane-tabs button:active, .track-toggle:active {
background: var(--ink); color: var(--paper);
}
@ -289,10 +289,35 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
.add-annotation:hover { background: var(--paper); color: var(--ink); }
.add-annotation:active { box-shadow: 0 0 0 var(--ink); transform: translate(2px, 2px); }
.timeline-scroll { flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; }
.timeline { display: flex; align-items: flex-start; background: var(--paper); }
.timeline-scroll { flex: 1; overflow: hidden; min-height: 0; display: flex; }
.timeline {
flex: 1; min-width: 0; min-height: 0;
position: relative; display: flex; flex-direction: column; background: var(--paper);
}
.timeline-head {
position: absolute; top: 0; left: 0; right: 0; z-index: 12;
display: flex; align-items: flex-start;
background: rgba(255, 255, 255, .58);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
html.dark .timeline-head {
background: rgba(0, 0, 0, .58);
}
.timeline-body-scroll {
flex: 1; min-height: 0; display: flex; align-items: flex-start;
overflow-y: auto; overflow-x: hidden;
box-sizing: border-box;
}
.gutter { flex-shrink: 0; width: 150px; user-select: none; }
.gutter-lanes, .gutter-tracks {
flex-shrink: 0; width: 150px; user-select: none; transition: width .2s ease;
}
.gutter-lanes {
position: relative;
box-sizing: border-box; background: transparent; border-right: 1px solid var(--ink);
}
.track-toggle { display: none; }
.gutter-label {
box-sizing: border-box;
border-bottom: 1px solid var(--ink); border-right: 1px solid var(--ink);
@ -300,8 +325,18 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
background: var(--paper);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hscroll { overflow-x: auto; flex: 1; }
.content { position: relative; cursor: ew-resize; overflow: hidden; }
.hscroll {
overflow-x: auto; overflow-y: hidden;
flex: 1; min-width: 0;
overscroll-behavior-x: contain;
}
.lanes-hscroll { overflow: hidden; flex: 1; min-width: 0; }
.content { position: relative; cursor: ew-resize; }
.track-lane {
position: absolute; left: 0; box-sizing: border-box;
border-bottom: 1px dotted color-mix(in srgb, var(--ink) 22%, transparent);
pointer-events: none;
}
.track-row { position: relative; background: var(--paper); border-bottom: 1px solid var(--ink);
overflow: hidden; transition: height .3s ease, opacity .3s ease; }
@ -336,10 +371,15 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
/* --- annotation lane ---------------------------------------------------- */
.ann-lane-row { position: relative; height: 18px; }
.ann-lanes {
position: relative; background: transparent;
box-shadow: 0 1px 0 var(--ink);
}
.ann-bar { position: absolute; top: 2px; height: 14px; border-radius: 0; }
.ann-bar-label { position: absolute; top: 1px; font-family: var(--chicago); font-size: 9px;
line-height: 16px; color: var(--ink); white-space: nowrap;
pointer-events: none; }
.lane-playhead { bottom: 0; }
/* --- mobile: stack the top region vertically (video over annotation) ---- */
@media (max-width: 720px) {
@ -349,6 +389,17 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
.annot-pane { width: 100%; min-width: 0; flex: 1 1 0; min-height: 110px; }
.divider-h { height: 14px; }
.slider input { width: 120px; }
.track-toggle {
display: block; position: absolute; top: 3px; left: 4px; z-index: 10;
max-width: calc(100% - 8px); padding: 2px 6px; font-size: 10px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.timeline.tracks-collapsed .gutter-lanes,
.timeline.tracks-collapsed .gutter-tracks { width: 34px; }
.timeline.tracks-collapsed .gutter-label {
opacity: 0; padding: 0; border-right: 1px solid var(--ink);
pointer-events: none;
}
}
/* --- landing pages (project list / create) ----------------------------- */

View file

@ -244,7 +244,9 @@
(set! (.-scrollLeft el) (max 0 (- x (/ (.-clientWidth el) 2)))))))
(defn timeline []
(let [content (atom nil)]
(let [content (atom nil)
lanes-scroll (atom nil)
tracks-open? (r/atom true)]
(fn []
(let [scene @(rf/subscribe [::subs/scene])
fps @(rf/subscribe [::subs/fps])
@ -260,58 +262,75 @@
linking @(rf/subscribe [::subs/linking])
len @(rf/subscribe [::subs/length])
width (px len fps zoom)
lane-h (* 18 (count anns))
lane-h (max 18 (* 18 (count anns)))
tracks-h (* row-h (count tracks))
track-y (into {} (map-indexed (fn [i t] [(:id t) i]) tracks))]
(when playing? (r/after-render #(follow! fps zoom playhead)))
[:div.timeline
[:div.gutter
[:div {:style {:height lane-h}}]
(for [t tracks]
^{:key (:id t)}
[:div.gutter-label {:style {:height row-h :line-height (str (dec row-h) "px")}}
(:name t)])]
[:div.hscroll {:ref (fn [n] (reset! scroll-el n))}
[:div.content {:ref (fn [n] (reset! content n))
:on-mouse-down #(scrub! @content fps zoom %)
:style {:width width :height (+ lane-h (* row-h (count tracks)))}}
[:div.playhead {:style {:left (px playhead fps zoom)}} [:div.playhead-handle]]
;; annotation bars — solid for saved, dashed for the in-progress draft
(for [[i a] (map-indexed vector anns)
[j [lo hi]] (map-indexed vector (:bars a))]
^{:key (str (:id a) "-" j)}
[:div.ann-bar {:title (:name a)
:on-mouse-down (when-not (:draft a)
(fn [e] (.stopPropagation e)
[:div.timeline {:class (when-not @tracks-open? "tracks-collapsed")}
[:div.timeline-head
[:div.gutter-lanes {:style {:height lane-h}}
[:button.track-toggle {:title (if @tracks-open? "Collapse track names" "Show track names")
:on-click #(swap! tracks-open? not)}
(if @tracks-open? "<- HIDE" "->")]]
[:div.lanes-hscroll {:ref (fn [n] (reset! lanes-scroll n))}
[:div.ann-lanes {:style {:height lane-h :width width}
:on-mouse-down #(scrub! @content fps zoom %)}
[:div.playhead.lane-playhead {:style {:left (px playhead fps zoom)}}
[:div.playhead-handle]]
;; annotation bars — solid for saved, dashed for the in-progress draft
(for [[i a] (map-indexed vector anns)
[j [lo hi]] (map-indexed vector (:bars a))]
^{:key (str (:id a) "-" j)}
[:div.ann-bar {:title (:name a)
:on-mouse-down (when-not (:draft a)
(fn [e] (.stopPropagation e)
(if linking
(do (.preventDefault e) ; pick: link to this timeline
(commit-link! {:kind :timeline :ref (:id a)} (:name a)))
(rf/dispatch [::events/expand (:id a)]))))
:style {:top (+ 2 (* i 18)) :height 14
:left (px lo fps zoom) :width (max 4 (px (- hi lo) fps zoom))
:background (str (:color a) (if (:draft a) "44" "cc"))
:cursor (if (:draft a) "default" "pointer")
:border-radius 2
:border (str (if (:draft a) "1px dashed " "1px solid ") (:color a))}}])
(for [[i a] (map-indexed vector anns) :let [[lo _] (first (:bars a))] :when lo]
^{:key (str "lbl-" (:id a))}
[:div.ann-bar-label {:style {:top (* i 18)
:left (+ 4 (px lo fps zoom))}} (:name a)])]]]
[:div.timeline-body-scroll {:style {:padding-top lane-h}}
[:div.gutter-tracks
(for [t tracks]
^{:key (:id t)}
[:div.gutter-label {:style {:height row-h :line-height (str (dec row-h) "px")}}
(:name t)])]
[:div.hscroll {:ref (fn [n] (reset! scroll-el n))
:on-scroll #(when @lanes-scroll
(set! (.-scrollLeft @lanes-scroll)
(.. % -target -scrollLeft)))}
[:div.content.track-content {:ref (fn [n] (reset! content n))
:on-mouse-down #(scrub! @content fps zoom %)
:style {:width width :height tracks-h}}
[:div.playhead {:style {:left (px playhead fps zoom)}}]
(for [[i t] (map-indexed vector tracks)]
^{:key (str "lane-" (:id t))}
[:div.track-lane {:style {:top (* i row-h) :width width :height row-h}}])
;; clips (while authoring, click to fill the focused endpoint)
(for [{:keys [mark track src local] :as seg} segs]
(let [[c d] local]
^{:key (str mark "-" (first src))}
[:div.clip {:class (when authoring? "clickable")
:on-mouse-down (when authoring?
(fn [e] (.stopPropagation e) (.preventDefault e)
(if linking
(do (.preventDefault e) ; pick: link to this timeline
(commit-link! {:kind :timeline :ref (:id a)} (:name a)))
(rf/dispatch [::events/expand (:id a)]))))
:style {:position "absolute" :top (+ 2 (* i 18)) :height 14
:left (px lo fps zoom) :width (max 4 (px (- hi lo) fps zoom))
:background (str (:color a) (if (:draft a) "44" "cc"))
:cursor (if (:draft a) "default" "pointer")
:border-radius 2
:border (str (if (:draft a) "1px dashed " "1px solid ") (:color a))}}])
(for [[i a] (map-indexed vector anns) :let [[lo _] (first (:bars a))] :when lo]
^{:key (str "lbl-" (:id a))}
[:div.ann-bar-label {:style {:position "absolute" :top (* i 18)
:left (+ 4 (px lo fps zoom))}} (:name a)])
;; clips (while authoring, click to fill the focused endpoint)
(for [{:keys [mark track src local] :as seg} segs]
(let [[c d] local]
^{:key (str mark "-" (first src))}
[:div.clip {:class (when authoring? "clickable")
:on-mouse-down (when authoring?
(fn [e] (.stopPropagation e) (.preventDefault e)
(if linking
(commit-link! (scene/seg-point scene seg 0)
(clip-label scene segs (:mark seg)))
(rf/dispatch [::events/draft-click-seg (:mark seg)]))))
:style {:left (px c fps zoom) :width (max 1 (px (- d c) fps zoom))
:top (+ lane-h (* (get track-y track 0) row-h)) :height (- row-h 2)
:line-height (str (- row-h 2) "px") :position "absolute"}}
[clip-thumbnail-layer thumbs fps zoom row-h seg]
[:div.clip-name (clip-label scene segs mark)]]))]]]))))
(commit-link! (scene/seg-point scene seg 0)
(clip-label scene segs (:mark seg)))
(rf/dispatch [::events/draft-click-seg (:mark seg)]))))
:style {:left (px c fps zoom) :width (max 1 (px (- d c) fps zoom))
:top (* (get track-y track 0) row-h) :height (- row-h 2)
:line-height (str (- row-h 2) "px") :position "absolute"}}
[clip-thumbnail-layer thumbs fps zoom row-h seg]
[:div.clip-name (clip-label scene segs mark)]]))]]]]))))
;; --- links: inline markdown chips that seek the timeline ------------------
;; A link is a ref-point named in the content as `[label](mark:ref@at)` (see