/* App shell — orchestrates state and mounts everything */
const { useState: useAS, useEffect: useAE, useRef: useAR, useMemo: useAM, useCallback: useAC } = React;
const PROTOTYPE_VIDEO_BY_BASENAME = window.PROTOTYPE_VIDEO_BY_BASENAME || {};
const PROTOTYPE_PROMPTS_BY_BASENAME = window.PROTOTYPE_PROMPTS_BY_BASENAME || {};
const VISUAL_CLIP_DURATION = 2.5;
const DEFAULT_SELECTED_CLIP_ID =
  (window.SEED_CLIPS || []).find((clip) => clip.track === 0)?.id || null;

function App() {
  const [media, setMedia] = useAS(window.SEED_MEDIA);
  const [clips, setClips] = useAS(window.SEED_CLIPS);
  const [selectedIds, setSelectedIds] = useAS(
    DEFAULT_SELECTED_CLIP_ID ? [DEFAULT_SELECTED_CLIP_ID] : [],
  );
  const [activeSelectedId, setActiveSelectedId] = useAS(DEFAULT_SELECTED_CLIP_ID);
  const [playhead, setPlayhead] = useAS(0);
  const [isPlaying, setIsPlaying] = useAS(false);
  const [chatOpen, setChatOpen] = useAS(false);
  const [tool, setTool] = useAS("select");
  const [tweaks, setTweaks] = useAS(window.TWEAKS || {});
  const [chatAnimateMode, setChatAnimateMode] = useAS("replace");
  const [pendingByClip, setPendingByClip] = useAS({});
  const rafRef = useAR(0);
  const lastT = useAR(0);
  const audioRef = useAR(null);
  const activeAudioClipIdRef = useAR(null);
  const activeAudioMediaIdRef = useAR(null);

  const totalDuration = useAM(() => clips.reduce((m, c) => Math.max(m, c.start + c.duration), 0), [clips]);
  const textOverlays = useAM(() => {
    const seeded = Array.isArray(window.SEED_TEXT_OVERLAYS) ? window.SEED_TEXT_OVERLAYS : [];
    return seeded
      .map((cue) => {
        const start = Number(cue.start) || 0;
        const rawEnd = cue.end == null ? totalDuration : Number(cue.end);
        const end = Math.max(start + 0.1, Number.isFinite(rawEnd) ? rawEnd : totalDuration);
        const x = Number(cue.x);
        const y = Number(cue.y);
        return {
          id: cue.id || `t_${start}`,
          start,
          end,
          text: String(cue.text || ""),
          x: Number.isFinite(x) ? x : 50,
          y: Number.isFinite(y) ? y : 50,
        };
      })
      .filter((cue) => cue.start < totalDuration)
      .sort((a, b) => (a.start - b.start) || a.id.localeCompare(b.id));
  }, [totalDuration]);
  const selectedClip = clips.find((c) => c.id === activeSelectedId) || null;
  const visualClips = useAM(
    () => clips.filter((c) => c.track === 0).sort((a, b) => a.start - b.start),
    [clips],
  );
  const selectedIdSet = useAM(() => new Set(selectedIds), [selectedIds]);
  const activeClip = visualClips.find(
    (c) => playhead >= c.start && playhead < c.start + c.duration,
  );
  const playerClip =
    activeClip ||
    (selectedClip && selectedClip.track === 0 ? selectedClip : null) ||
    visualClips[0] ||
    null;
  const activeAudioClip = clips
    .filter((c) => c.track === 1 && playhead >= c.start && playhead < c.start + c.duration)
    .sort((a, b) => b.start - a.start)[0];

  const usedIds = useAM(() => new Set(clips.map((c) => c.mediaId)), [clips]);

  // Warm up video assets to reduce stall/black flashes during clip switches on web.
  useAE(() => {
    const preloaders = media
      .filter((item) => item.type === "video")
      .map((item) => {
        const video = document.createElement("video");
        video.preload = "auto";
        video.muted = true;
        video.src = item.url;
        video.load();
        return video;
      });

    return () => {
      preloaders.forEach((video) => {
        video.pause();
        video.removeAttribute("src");
        video.load();
      });
    };
  }, [media]);

  const setClipPending = (clipId, text) => {
    setPendingByClip((prev) => {
      const next = { ...prev };
      if (text) next[clipId] = text;
      else delete next[clipId];
      return next;
    });
  };

  const handlePlayToggle = useAC(() => {
    setIsPlaying((playing) => {
      if (playing) return false;
      setPlayhead((p) => {
        if (totalDuration <= 0) return 0;
        return p >= totalDuration - 0.001 ? 0 : p;
      });
      return true;
    });
  }, [totalDuration]);

  const setSelection = (ids, activeId = null) => {
    const deduped = [];
    const seen = new Set();
    ids.forEach((id) => {
      if (!seen.has(id) && clips.some((c) => c.id === id)) {
        seen.add(id);
        deduped.push(id);
      }
    });
    const nextActive = activeId && deduped.includes(activeId) ? activeId : (deduped[0] || null);
    setSelectedIds(deduped);
    setActiveSelectedId(nextActive);
  };

  const handleTimelineSelect = ({ id, mode = "plain" }) => {
    if (mode === "clear" || !id) {
      setSelection([], null);
      return;
    }

    if (mode === "toggle") {
      const exists = selectedIdSet.has(id);
      const nextIds = exists ? selectedIds.filter((x) => x !== id) : [...selectedIds, id];
      const nextActive = exists
        ? (activeSelectedId === id ? (nextIds[0] || null) : activeSelectedId)
        : id;
      setSelection(nextIds, nextActive);
      return;
    }

    if (mode === "range") {
      const anchorId = activeSelectedId || selectedIds[0] || null;
      const anchor = clips.find((c) => c.id === anchorId);
      const target = clips.find((c) => c.id === id);
      if (anchor && target && anchor.track === target.track) {
        const sameTrack = clips
          .filter((c) => c.track === target.track)
          .sort((a, b) => (a.start - b.start) || a.id.localeCompare(b.id));
        const ai = sameTrack.findIndex((c) => c.id === anchor.id);
        const bi = sameTrack.findIndex((c) => c.id === target.id);
        if (ai >= 0 && bi >= 0) {
          const [from, to] = ai < bi ? [ai, bi] : [bi, ai];
          setSelection(sameTrack.slice(from, to + 1).map((c) => c.id), id);
          return;
        }
      }
    }

    setSelection([id], id);
  };

  // Apply accent from tweaks live
  useAE(() => {
    document.documentElement.style.setProperty("--accent", tweaks.accent || "#5cc9f5");
    const a = tweaks.accent || "#5cc9f5";
    // compute rgba helpers
    const hex = a.replace("#","");
    const r = parseInt(hex.slice(0,2),16), g = parseInt(hex.slice(2,4),16), b = parseInt(hex.slice(4,6),16);
    document.documentElement.style.setProperty("--accent-soft", `rgba(${r},${g},${b},0.16)`);
    document.documentElement.style.setProperty("--accent-line", `rgba(${r},${g},${b},0.45)`);
  }, [tweaks.accent]);

  // Keep selection and pending maps valid as clips are edited/replaced/removed.
  useAE(() => {
    const clipIds = new Set(clips.map((c) => c.id));
    const nextSelected = selectedIds.filter((id) => clipIds.has(id));
    if (nextSelected.length !== selectedIds.length) {
      setSelectedIds(nextSelected);
    }
    if (!activeSelectedId || !clipIds.has(activeSelectedId)) {
      const fallback = nextSelected[0] || null;
      if (fallback !== activeSelectedId) {
        setActiveSelectedId(fallback);
      }
    }
    setPendingByClip((prev) => {
      const next = {};
      let changed = false;
      Object.entries(prev).forEach(([id, text]) => {
        if (clipIds.has(id)) next[id] = text;
        else changed = true;
      });
      return changed ? next : prev;
    });
  }, [clips, selectedIds, activeSelectedId]);

  // Playback loop
  useAE(() => {
    if (!isPlaying) return;
    lastT.current = 0;
    const tick = (now) => {
      if (!lastT.current) lastT.current = now;
      const dt = (now - lastT.current) / 1000;
      lastT.current = now;
      setPlayhead((p) => {
        const next = p + dt;
        if (next >= totalDuration) {
          setIsPlaying(false);
          return totalDuration;
        }
        return next;
      });
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [isPlaying, totalDuration]);

  // Keyboard shortcuts
  useAE(() => {
    const onKey = (e) => {
      if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return;
      if (e.code === "Space") { e.preventDefault(); handlePlayToggle(); }
      if (e.key === "Delete" || e.key === "Backspace") {
        if (selectedIds.length > 0) {
          const selectedSet = new Set(selectedIds);
          setClips((cs) => cs.filter((c) => !selectedSet.has(c.id)));
          setSelection([], null);
        }
      }
      if (e.key === "Escape") { setChatOpen(false); setSelection([], null); }
      if (e.key === "ArrowLeft") setPlayhead((p) => Math.max(0, p - (e.shiftKey ? 1 : 1/24)));
      if (e.key === "ArrowRight") setPlayhead((p) => Math.min(totalDuration, p + (e.shiftKey ? 1 : 1/24)));
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [selectedIds, totalDuration, clips, handlePlayToggle]);

  // Keep timeline audio in sync with playhead and play state.
  useAE(() => {
    const audio = audioRef.current;
    if (!audio) return;

    if (!activeAudioClip) {
      if (!audio.paused) audio.pause();
      activeAudioClipIdRef.current = null;
      activeAudioMediaIdRef.current = null;
      return;
    }

    const asset = media.find((m) => m.id === activeAudioClip.mediaId && m.type === "audio");
    if (!asset) return;

    const needsNewSource =
      activeAudioClipIdRef.current !== activeAudioClip.id ||
      activeAudioMediaIdRef.current !== asset.id;

    if (needsNewSource) {
      audio.src = asset.url;
      audio.preload = "auto";
      activeAudioClipIdRef.current = activeAudioClip.id;
      activeAudioMediaIdRef.current = asset.id;
    }

    const local = Math.max(0, playhead - activeAudioClip.start + (activeAudioClip.trimIn || 0));
    const bounded = Math.min(local, Math.max(0, activeAudioClip.duration - 0.05));
    if (Math.abs((audio.currentTime || 0) - bounded) > 0.12) {
      try {
        audio.currentTime = bounded;
      } catch (_) {
        /* ignore browser seek races */
      }
    }

    if (isPlaying) {
      audio.play().catch(() => {
        /* ignore autoplay race in prototype */
      });
    } else if (!audio.paused) {
      audio.pause();
    }
  }, [activeAudioClip, media, playhead, isPlaying]);

  // Media DnD
  const onMediaDragStart = (e, id) => {
    e.dataTransfer.setData("text/media-id", id);
    e.dataTransfer.effectAllowed = "copy";
  };

  // Drop media onto a track — place at t, auto-fit after last neighbor if overlap
  const dropMedia = (mediaId, trackIdx, atTime) => {
    const droppedMedia = media.find((x) => x.id === mediaId);
    if (!droppedMedia) return;
    let m = droppedMedia;
    let seededEffects = [];
    let seededChat = [];

    if (m.type === "image") {
      const baseName = m.name.replace(/\.[^.]+$/, "");
      const prototypeVideo = PROTOTYPE_VIDEO_BY_BASENAME[baseName];
      if (prototypeVideo) {
        m = media.find((x) => x.id === prototypeVideo.id) || prototypeVideo;
        const seededPrompt = PROTOTYPE_PROMPTS_BY_BASENAME[baseName];
        seededEffects = [{ id: "e_" + Math.random().toString(36).slice(2,8), name: "AI Animate", kind: "ai", value: "1.00" }];
        if (seededPrompt) {
          seededChat = [
            { role: "user", text: seededPrompt },
            {
              role: "assistant",
              text: `Animation complete. Using local prototype render ${m.name}.`,
              effects: ["AI Animate"],
            },
          ];
        }
      }
    }

    if ((m.type === "audio" && trackIdx !== 1) || (m.type !== "audio" && trackIdx !== 0)) {
      trackIdx = m.type === "audio" ? 1 : 0;
    }
    const clipDuration = m.type === "audio"
      ? m.duration
      : (m.type === "video" ? (m.duration || VISUAL_CLIP_DURATION) : VISUAL_CLIP_DURATION);
    const trackClips = clips.filter((c) => c.track === trackIdx).sort((a,b) => a.start - b.start);
    // Try placing at the drop point; if overlaps, snap to right edge of previous neighbor.
    let start = atTime;
    const end = start + clipDuration;
    const overlap = trackClips.find((c) => start < c.start + c.duration && end > c.start);
    if (overlap) {
      start = overlap.start + overlap.duration; // snap right after
    }
    const newClip = {
      id: "c_" + Math.random().toString(36).slice(2,8),
      mediaId: m.id,
      name: m.name.replace(/\.[^.]+$/, ""),
      type: m.type,
      track: trackIdx,
      start,
      duration: clipDuration,
      trimIn: 0,
      effects: seededEffects,
      chat: seededChat,
    };
    setClips((cs) => [...cs, newClip]);
    setSelectedIds([newClip.id]);
    setActiveSelectedId(newClip.id);
  };

  // Move clip with snap-to-neighbor
  const moveClip = (id, nextStart) => {
    setClips((cs) => {
      const me = cs.find((c) => c.id === id); if (!me) return cs;
      const neighbors = cs.filter((c) => c.id !== id && c.track === me.track).sort((a,b) => a.start - b.start);
      let ns = Math.max(0, nextStart);
      const end = ns + me.duration;
      // snap threshold 0.18s
      const edges = [];
      neighbors.forEach((n) => { edges.push(n.start); edges.push(n.start + n.duration); });
      for (const e of edges) {
        if (Math.abs(ns - e) < 0.18) { ns = e; break; }
        if (Math.abs(end - e) < 0.18) { ns = e - me.duration; break; }
      }
      // avoid overlap — push up to neighbor edge
      for (const n of neighbors) {
        const ne = n.start + n.duration;
        if (ns < ne && ns + me.duration > n.start) {
          // collision: choose nearest side
          if (Math.abs(ns + me.duration - n.start) < Math.abs(ns - ne)) ns = n.start - me.duration;
          else ns = ne;
        }
      }
      ns = Math.max(0, ns);
      return cs.map((c) => c.id === id ? { ...c, start: ns } : c);
    });
  };

  const trimClip = (id, nextValue, side) => {
    setClips((cs) => cs.map((c) => {
      if (c.id !== id) return c;
      if (side === "right") {
        return { ...c, duration: Math.max(0.2, nextValue) };
      } else {
        const dt = nextValue;
        const newStart = Math.max(0, c.start + dt);
        const actualDt = newStart - c.start;
        const newDur = Math.max(0.2, c.duration - actualDt);
        return { ...c, start: newStart, duration: newDur };
      }
    }));
  };

  const removeEffect = (clipId, effectId) => {
    setClips((cs) => cs.map((c) => c.id === clipId ? { ...c, effects: c.effects.filter((e) => e.id !== effectId) } : c));
  };

  const addChat = (clipId, msg) => {
    setClips((cs) => cs.map((c) => {
      if (c.id !== clipId) return c;
      const next = { ...c, chat: [...c.chat, msg] };
      if (msg.role === "assistant" && msg.effects) {
        msg.effects.forEach((effName) => {
          const exists = next.effects.some((effect) => effect.kind === "ai" && effect.name === effName);
          if (!exists) {
            next.effects = [...next.effects, { id: "e_" + Math.random().toString(36).slice(2,8), name: effName, kind: "ai", value: "1.00" }];
          }
        });
      }
      return next;
    }));
  };

  // Toolbar actions
  const onToolAction = (action) => {
    if (action === "split" && selectedClip) {
      const c = selectedClip;
      if (playhead > c.start + 0.1 && playhead < c.start + c.duration - 0.1) {
        const leftDur = playhead - c.start;
        const rightDur = c.duration - leftDur;
        const right = { ...c, id: "c_" + Math.random().toString(36).slice(2,8), start: playhead, duration: rightDur, chat: [] };
        setClips((cs) => cs.map((x) => x.id === c.id ? { ...x, duration: leftDur } : x).concat(right));
      }
    }
    if (action === "delete" && selectedIds.length > 0) {
      const selectedSet = new Set(selectedIds);
      setClips((cs) => cs.filter((c) => !selectedSet.has(c.id)));
      setSelection([], null);
    }
  };

  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  const replaceClipWithPrototypeVideo = ({ clipId, sourceMedia }) => {
    const baseName = sourceMedia.name.replace(/\.[^.]+$/, "");
    if (sourceMedia.type === "video") {
      setClips((prev) =>
        prev.map((clip) =>
          clip.id === clipId
            ? {
                ...clip,
                duration: sourceMedia.duration || VISUAL_CLIP_DURATION,
                trimIn: 0,
              }
            : clip,
        ),
      );
      return { ok: true, alreadyVideo: true, videoName: sourceMedia.name, videoUrl: sourceMedia.url };
    }

    const prototypeVideo = PROTOTYPE_VIDEO_BY_BASENAME[baseName];
    if (!prototypeVideo) {
      return {
        ok: false,
        reason: `No matching prototype video found for "${baseName}" in /assets/videos.`,
      };
    }

    const resolvedVideo = media.find((item) => item.id === prototypeVideo.id) || prototypeVideo;
    setMedia((prev) => (prev.some((item) => item.id === resolvedVideo.id) ? prev : [resolvedVideo, ...prev]));
    setClips((prev) =>
      prev.map((clip) =>
        clip.id === clipId
          ? {
              ...clip,
              mediaId: resolvedVideo.id,
              type: "video",
              name: baseName,
              duration: resolvedVideo.duration || VISUAL_CLIP_DURATION,
              trimIn: 0,
            }
          : clip,
      ),
    );

    return { ok: true, alreadyVideo: false, videoName: resolvedVideo.name, videoUrl: resolvedVideo.url };
  };

  const animateClipFromPrompt = async (clipId, prompt, mode = "replace") => {
    const cleanPrompt = String(prompt || "").trim();
    if (!cleanPrompt) {
      throw new Error("Prompt is required.");
    }

    const orderedSelectedIds = clips.map((c) => c.id).filter((id) => selectedIdSet.has(id));
    const targetIds = selectedIdSet.has(clipId) && orderedSelectedIds.length > 0 ? orderedSelectedIds : [clipId];
    const targetClips = targetIds.map((id) => clips.find((c) => c.id === id)).filter(Boolean);

    if (targetClips.length === 0) {
      throw new Error("No clip selected.");
    }

    if (mode === "stitch") {
      addChat(targetClips[0].id, {
        role: "assistant",
        text: "Prototype mode: Stitch Selected is simulated by applying each clip's matching local video.",
      });
    }

    targetClips.forEach((clip) => {
      addChat(clip.id, { role: "user", text: mode === "stitch" ? `[Stitch Selected] ${cleanPrompt}` : cleanPrompt });
    });

    const results = [];
    for (const clip of targetClips) {
      const sourceMedia = media.find((item) => item.id === clip.mediaId);
      if (!sourceMedia) {
        addChat(clip.id, {
          role: "assistant",
          text: "Animation skipped: source media not found for this clip.",
        });
        results.push({ clipId: clip.id, ok: false, error: "source media not found" });
        continue;
      }
      if (sourceMedia.type === "audio") {
        addChat(clip.id, {
          role: "assistant",
          text: "Animation skipped: audio clips do not have image-to-video prototype renders.",
        });
        results.push({ clipId: clip.id, ok: false, error: "audio clips unsupported" });
        continue;
      }

      setClipPending(clip.id, "Applying local prototype animation...");
      await delay(220 + Math.floor(Math.random() * 220));

      try {
        const applied = replaceClipWithPrototypeVideo({ clipId: clip.id, sourceMedia });
        if (!applied.ok) {
          addChat(clip.id, { role: "assistant", text: `Animation skipped: ${applied.reason}` });
          results.push({ clipId: clip.id, ok: false, error: applied.reason });
          continue;
        }
        addChat(clip.id, {
          role: "assistant",
          text: applied.alreadyVideo
            ? "Animation complete. This clip already has a generated prototype video."
            : `Animation complete. Loaded local prototype render ${applied.videoName}.`,
          effects: ["AI Animate"],
        });
        results.push({ clipId: clip.id, ok: true, videoUrl: applied.videoUrl });
      } finally {
        setClipPending(clip.id, null);
      }
    }

    return {
      mode,
      processed: results.filter((x) => x.ok).length,
      skipped: results.filter((x) => !x.ok).length,
      results,
    };
  };

  return (
    <div className="shell" style={{paddingRight: 0}}>
      <div className="titlebar">
        <div className="lights">
          <span className="light r"/><span className="light y"/><span className="light g"/>
        </div>
        <div className="title-center">
          <span className="proj-dot"/>
          Emo Tool · untitled_reel.timeline
        </div>
        <div className="title-right">
          <span style={{color:"var(--text-3)"}}>autosaved · 4s ago</span>
          <button className="export-btn">Export</button>
        </div>
      </div>

      <div className="main">
        <div className="top-row">
          <MediaPanel media={media} onDragStart={onMediaDragStart} usedIds={usedIds}/>
          <Player
            currentTime={playhead}
            totalDuration={totalDuration}
            activeClip={playerClip}
            media={media}
            textOverlays={textOverlays}
            isPlaying={isPlaying}
            onPlayToggle={handlePlayToggle}
            onStep={(dir) => setPlayhead((p) => Math.max(0, Math.min(totalDuration, p + dir * 0.5)))}
          />
          <Inspector
            clip={selectedClip}
            media={media}
            onOpenChat={() => setChatOpen(true)}
            onRemoveEffect={removeEffect}
          />
        </div>

        <Toolbar
          tool={tool}
          setTool={setTool}
          onAction={onToolAction}
        />

        <Timeline
          clips={clips}
          media={media}
          totalDuration={totalDuration}
          playheadTime={playhead}
          selectedIds={selectedIds}
          activeSelectedId={activeSelectedId}
          isPlaying={isPlaying}
          textOverlays={textOverlays}
          showThumbs={tweaks.showClipThumbs}
          clipGap={tweaks.clipGap}
          showGrid={tweaks.showGrid}
          onSelect={handleTimelineSelect}
          onScrub={(t) => { setIsPlaying(false); setPlayhead(t); }}
          onDropMedia={dropMedia}
          onMoveClip={moveClip}
          onTrimClip={trimClip}
          onDeleteClip={(id) => setClips((cs) => cs.filter((c) => c.id !== id))}
        />
      </div>

      <ChatDrawer
        open={chatOpen && !!selectedClip}
        clip={selectedClip}
        media={media}
        selectedCount={selectedIds.length}
        animateMode={chatAnimateMode}
        onChangeAnimateMode={setChatAnimateMode}
        pendingText={selectedClip ? pendingByClip[selectedClip.id] : null}
        onClose={() => setChatOpen(false)}
        onSendPrompt={addChat}
        onAnimateClip={animateClipFromPrompt}
      />

      <Tweaks tweaks={tweaks} setTweaks={setTweaks}/>
      <audio ref={audioRef} hidden />
    </div>
  );
}

function Toolbar({ tool, setTool, onAction }) {
  const ToolBtn = ({ id, label, onClick, active, children }) => (
    <button
      className={"tool-btn " + (active ? "active" : "")}
      onClick={onClick}
      title={label}
    >
      {children}
    </button>
  );

  return (
    <div className="toolbar">
      <div className="tool-group">
        <ToolBtn id="sel" label="Select (V)" active={tool === "select"} onClick={() => setTool("select")}><Icon.Cursor/></ToolBtn>
      </div>
      <div className="tool-group">
        <ToolBtn label="Undo (⌘Z)"><Icon.Undo/></ToolBtn>
        <ToolBtn label="Redo (⌘⇧Z)"><Icon.Redo/></ToolBtn>
      </div>
      <div className="tool-group">
        <ToolBtn label="Split (S)" onClick={() => onAction("split")}><Icon.Split/></ToolBtn>
        <ToolBtn label="Trim"><Icon.Trim/></ToolBtn>
        <ToolBtn label="Cut"><Icon.Cut/></ToolBtn>
        <ToolBtn label="Delete (⌫)" onClick={() => onAction("delete")}><Icon.Delete/></ToolBtn>
      </div>
      <div className="tool-group">
        <ToolBtn label="Rectangle"><Icon.Rect/></ToolBtn>
        <ToolBtn label="Mask"><Icon.Mask/></ToolBtn>
        <ToolBtn label="Crop"><Icon.Crop/></ToolBtn>
        <ToolBtn label="Flip"><Icon.Flip/></ToolBtn>
        <ToolBtn label="Text"><Icon.Text/></ToolBtn>
      </div>
      <div className="tool-divider"/>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
