Browse Source

fix: draw toolbar is now a floating popover — no layout shift

- Toolbar removed from page flow; rendered as position:absolute inside a
  relative anchor div next to the toggle button
- Popover anchored to bottom-right of toggle, floats above all UI
- animate-pop-in: cubic-bezier spring for snappy entrance feel
- Removed standalone toolbar block that was pushing controls down

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude Dev 1 month ago
parent
commit
a8f28b585d
2 changed files with 63 additions and 50 deletions
  1. 2 0
      src/app/globals.css
  2. 61 50
      src/components/video-player/VideoPlayer.tsx

+ 2 - 0
src/app/globals.css

@@ -263,11 +263,13 @@ body {
 @keyframes fadeIn   { from { opacity: 0; } to { opacity: 1; } }
 @keyframes slideUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
 @keyframes scaleIn { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
+@keyframes popIn  { from { opacity: 0; transform: scale(0.92) translateY(4px); } to { opacity: 1; transform: scale(1) translateY(0); } }
 @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position:  200% 0; } }
 
 .animate-fade-in   { animation: fadeIn 0.2s ease-out; }
 .animate-slide-up  { animation: slideUp 0.25s ease-out; }
 .animate-scale-in  { animation: scaleIn 0.15s ease-out; }
+.animate-pop-in    { animation: popIn 0.15s cubic-bezier(0.34, 1.56, 0.64, 1); }
 
 /* ── Glass surface ───────────────────────────────────────────────── */
 .glass {

+ 61 - 50
src/components/video-player/VideoPlayer.tsx

@@ -347,43 +347,6 @@ export function VideoPlayer({
         onCommentClick={onCommentClick}
       />
 
-      {/* ── Draw toolbar (appears after timeline + bottom controls) ─── */}
-      {drawMode && (
-        <div className="flex flex-wrap items-center gap-2 px-1 pb-2">
-          <span className="text-xs shrink-0" style={{ color: 'var(--text-muted)' }}>Draw:</span>
-          {(['pen', 'arrow', 'rect', 'ellipse'] as Tool[]).map(t => (
-            <button
-              key={t}
-              onClick={() => onDrawToolChange(t)}
-              className={`px-2 py-1 rounded text-xs font-medium transition-colors ${
-                drawTool === t ? 'bg-indigo-600 text-white' : 'bg-white/10 text-white/80 hover:bg-white/20'
-              }`}
-            >
-              {t.charAt(0).toUpperCase() + t.slice(1)}
-            </button>
-          ))}
-          <div className="w-px h-5 bg-white/20 shrink-0" />
-          {COLORS.map(c => (
-            <button
-              key={c.value}
-              className={`w-5 h-5 rounded-full border-2 transition-transform hover:scale-125 ${
-                drawColor === c.value ? 'border-white scale-125' : 'border-transparent'
-              }`}
-              style={{ backgroundColor: c.value }}
-              onClick={() => onDrawColorChange(c.value)}
-              title={c.name}
-            />
-          ))}
-          <div className="w-px h-5 bg-white/20 shrink-0" />
-          <button
-            onClick={() => onDrawModeChange(false)}
-            className="text-xs text-white/60 hover:text-white px-2 py-1 rounded bg-white/10 hover:bg-white/20 transition-colors shrink-0"
-          >
-            Done
-          </button>
-        </div>
-      )}
-
       {/* ── Bottom controls row ───────────────────────────────────────── */}
       <div className="flex items-center gap-1 px-1 pb-2">
 
@@ -487,19 +450,67 @@ export function VideoPlayer({
           ))}
         </select>
 
-        {/* Draw mode toggle */}
-        <button
-          onClick={() => {
-            if (!drawMode) { videoRef.current?.pause(); onDrawModeChange(true); }
-            else { onDrawModeChange(false); }
-          }}
-          className={`p-1.5 rounded transition-colors ${drawMode ? 'bg-indigo-600 text-white' : 'bg-white/10 text-white/70 hover:bg-white/20'}`}
-          title="Toggle draw mode (C)"
-        >
-          <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
-            <path strokeLinecap="round" strokeLinejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
-          </svg>
-        </button>
+        {/* Draw mode toggle — popover anchor */}
+        <div className="relative">
+          <button
+            onClick={() => {
+              if (!drawMode) { videoRef.current?.pause(); onDrawModeChange(true); }
+              else { onDrawModeChange(false); }
+            }}
+            className={`p-1.5 rounded transition-colors ${drawMode ? 'bg-indigo-600 text-white' : 'bg-white/10 text-white/70 hover:bg-white/20'}`}
+            title="Toggle draw mode (C)"
+          >
+            <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
+              <path strokeLinecap="round" strokeLinejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
+            </svg>
+          </button>
+
+          {/* Floating draw toolbar popover */}
+          {drawMode && (
+            <div
+              className="absolute bottom-full mb-2 right-0 z-50 flex flex-wrap items-center gap-2 px-3 py-2.5 rounded-xl animate-pop-in"
+              style={{
+                background: 'rgba(15,17,28,0.97)',
+                border: '1px solid rgba(99,102,241,0.30)',
+                backdropFilter: 'blur(12px)',
+                boxShadow: '0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(99,102,241,0.1)',
+                minWidth: '220px',
+              }}
+            >
+              <span className="text-xs shrink-0 pr-1" style={{ color: 'var(--text-muted)' }}>Draw:</span>
+              {(['pen', 'arrow', 'rect', 'ellipse'] as Tool[]).map(t => (
+                <button
+                  key={t}
+                  onClick={() => onDrawToolChange(t)}
+                  className={`px-2 py-1 rounded text-xs font-medium transition-colors ${
+                    drawTool === t ? 'bg-indigo-600 text-white' : 'bg-white/10 text-white/80 hover:bg-white/20'
+                  }`}
+                >
+                  {t.charAt(0).toUpperCase() + t.slice(1)}
+                </button>
+              ))}
+              <div className="w-px h-5 bg-white/10 shrink-0" />
+              {COLORS.map(c => (
+                <button
+                  key={c.value}
+                  className={`w-5 h-5 rounded-full border-2 transition-transform hover:scale-125 ${
+                    drawColor === c.value ? 'border-white scale-125' : 'border-transparent'
+                  }`}
+                  style={{ backgroundColor: c.value }}
+                  onClick={() => onDrawColorChange(c.value)}
+                  title={c.name}
+                />
+              ))}
+              <div className="w-px h-5 bg-white/10 shrink-0" />
+              <button
+                onClick={() => onDrawModeChange(false)}
+                className="text-xs text-white/60 hover:text-white px-2 py-1 rounded bg-white/10 hover:bg-white/20 transition-colors shrink-0"
+              >
+                Done
+              </button>
+            </div>
+          )}
+        </div>
 
         {/* Fullscreen */}
         <button