feat: HyperCard-style top title bar (logo, project name, share, user menu)

Replace the mid-page .project-bar with a proper top nav bar pinned above the
video/timeline, styled like a classic Mac window title bar (centered title
flanked by racing stripes).

- left: TL wordmark + thumbnail-status indicator
- center: ‹ back-to-projects + project name, between the title stripes
- right: Share popover and a user dropdown with Log out
- Share offers two clearly named links, copied to clipboard:
  "Copy link to this moment" (current context + playhead) and
  "Copy link to the whole project" (root)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-30 09:58:54 -04:00
parent 28aafc047f
commit 96da4a6bd8
2 changed files with 105 additions and 20 deletions

View file

@ -436,12 +436,45 @@ html.dark .timeline-head {
.upload-pct { font-family: var(--mono); font-size: 11px; min-width: 36px; text-align: right; }
/* editor: project bar above the timeline — a Mac window title bar (selected) */
.project-bar { display: flex; gap: 10px; align-items: center; padding: 4px 8px;
background: var(--ink); color: var(--paper); font-family: var(--chicago);
font-size: 12px; }
.project-bar a { color: var(--paper); text-decoration: underline; }
.project-bar .project-name { color: var(--paper); }
.project-bar .logout-link { margin-left: auto; color: var(--paper); text-decoration: underline; }
/* --- top bar: HyperCard window title bar -------------------------------- */
.titlebar {
flex-shrink: 0; display: flex; align-items: center; gap: 8px;
padding: 3px 8px; background: var(--paper); color: var(--ink);
border-bottom: 2px solid var(--ink);
font-family: var(--chicago); font-size: 12px; user-select: none;
}
.titlebar-left, .titlebar-right { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.titlebar-center { flex: 1; min-width: 0; display: flex; align-items: center;
justify-content: center; gap: 8px; }
/* the signature racing-stripes flanking the centered window title */
.titlebar-center::before, .titlebar-center::after {
content: ""; flex: 1 1 0; min-width: 6px; height: 11px;
background: repeating-linear-gradient(to bottom, var(--ink) 0 1px, transparent 1px 2px);
}
.tl-logo { font-weight: bold; letter-spacing: 1px; padding: 0 5px;
background: var(--ink); color: var(--paper); }
.title-back { text-decoration: none; color: var(--ink); font-size: 16px; line-height: 1;
padding: 0 5px; }
.title-back:hover { background: var(--ink); color: var(--paper); }
.title-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
max-width: 40vw; padding: 0 4px; background: var(--paper); }
.titlebar-status { font-size: 10px; color: var(--mute); }
.titlebar-status.err { color: #e66; }
/* bar buttons + popover menus (share / user) */
.menu-wrap { position: relative; }
.bar-btn { font-family: var(--chicago); font-size: 11px; background: var(--paper);
color: var(--ink); border: 1px solid var(--ink); border-radius: 0;
padding: 1px 7px; cursor: pointer; }
.bar-btn:hover { background: var(--ink); color: var(--paper); }
.menu-backdrop { position: fixed; inset: 0; z-index: 30; background: transparent; }
.menu-pop { position: absolute; top: calc(100% + 3px); right: 0; z-index: 31;
min-width: 190px; background: var(--paper); border: 1px solid var(--ink);
box-shadow: 2px 2px 0 var(--ink); padding: 4px; }
.menu-pop button { display: block; width: 100%; text-align: left; background: var(--paper);
color: var(--ink); border: none; padding: 5px 7px; cursor: pointer;
font-family: var(--chicago); font-size: 11px; white-space: nowrap; }
.menu-pop button:hover { background: var(--ink); color: var(--paper); }
/* --- annotation / script pane tabs ------------------------------------- */
.pane-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }

View file

@ -957,33 +957,85 @@
[script-pane]
(if draft? [annotation-form] [commentary]))]]))
;; --- top bar: HyperCard-style window title bar ---------------------------
(defn- abs-url
"Absolute, shareable URL for a same-origin fragment route."
[rel]
(str (.. js/window -location -origin) (.. js/window -location -pathname) rel))
(defn- copy-text! [text]
(when-let [cb (.-clipboard js/navigator)]
(.writeText cb text)))
(defn share-menu []
(let [open? (r/atom false)
copied (r/atom nil)]
(fn []
(let [proj @(rf/subscribe [::subs/project])
stack @(rf/subscribe [::subs/stack])
playhead @(rf/subscribe [::subs/playhead])
id (:id proj)
grab (fn [rel which]
(copy-text! (abs-url rel))
(reset! copied which)
(js/setTimeout #(reset! copied nil) 1600))]
[:div.menu-wrap
[:button.bar-btn {:on-click #(swap! open? not)} "Share ▾"]
(when @open?
[:<>
[:div.menu-backdrop {:on-click #(reset! open? false)}]
[:div.menu-pop
[:button {:on-click #(grab (routes/project-url id stack playhead) :moment)}
(if (= @copied :moment) "✓ Link copied" "Copy link to this moment")]
[:button {:on-click #(grab (routes/project-url id [:root] nil) :project)}
(if (= @copied :project) "✓ Link copied" "Copy link to the whole project")]]])]))))
(defn user-menu []
(let [open? (r/atom false)]
(fn []
(let [user @(rf/subscribe [::subs/username])]
[:div.menu-wrap
[:button.bar-btn {:on-click #(swap! open? not)} (str (or user "account") " ▾")]
(when @open?
[:<>
[:div.menu-backdrop {:on-click #(reset! open? false)}]
[:div.menu-pop
[:button {:on-click #(do (reset! open? false)
(rf/dispatch [::events/logout]))} "Log out"]]])]))))
(defn top-bar []
(let [proj @(rf/subscribe [::subs/project])
authed? @(rf/subscribe [::subs/authed?])
thumb-status @(rf/subscribe [::subs/thumbnail-status])]
[:div.titlebar
[:div.titlebar-left
[:span.tl-logo "TL"]
(when (#{"pending" "running"} thumb-status)
[:span.titlebar-status "thumbnails…"])
(when (= "failed" thumb-status)
[:span.titlebar-status.err "thumbnails failed"])]
[:div.titlebar-center
[:a.title-back {:href (routes/href :projects) :title "Back to projects"} "‹"]
[:span.title-name (:name proj)]]
[:div.titlebar-right
[share-menu]
(when authed? [user-menu])]]))
(defn editor []
(let [status @(rf/subscribe [::subs/status])
proj @(rf/subscribe [::subs/project])
authed? @(rf/subscribe [::subs/authed?])
thumb-status @(rf/subscribe [::subs/thumbnail-status])
load-err @(rf/subscribe [::subs/load-error])
;; "picker mode": a draft is open (pick clips/frame for marks); + linking
;; (pick clips/frame/annotations for a link). Drives the pulsing glow.
authoring? (some? @(rf/subscribe [::subs/draft-group]))
linking? (some? @(rf/subscribe [::subs/linking]))]
[:div.app {:class (str (when authoring? "picking ") (when linking? "linking"))}
[top-bar]
[:div.top
[:div.video-pane [video-monitor] [frame-readout]]
[:div.annot-pane [annot-pane]]]
[:div.divider-h {:on-pointer-down drag-top!}]
[:div.timeline-pane
[:div.project-bar
[:a.home-link {:href (routes/href :projects)} "← projects"]
[:span.project-name (:name proj)]
(when (#{"pending" "running"} thumb-status)
[:span.muted "thumbnails processing"])
(when (= "failed" thumb-status)
[:span.err "thumbnail generation failed"])
(when authed?
[:a.logout-link {:href "#" :on-click #(do (.preventDefault %)
(rf/dispatch [::events/logout]))}
"log out"])]
[breadcrumbs]
[toolbar]
[:div.timeline-scroll