@hyperframes/studio 0.8.123 → 0.8.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{n as Qi}from"./index-Dt7lWtY7.js";/*!
1
+ import{n as Qi}from"./index-BuSTrK-Z.js";/*!
2
2
  * Copyright (c) 2026-present, Vanilagy and contributors
3
3
  *
4
4
  * This Source Code Form is subject to the terms of the Mozilla Public
package/dist/index.html CHANGED
@@ -14,8 +14,8 @@
14
14
  document.documentElement.dataset.theme = "paper";
15
15
  }
16
16
  </script>
17
- <script type="module" crossorigin src="/assets/index-Dt7lWtY7.js"></script>
18
- <link rel="stylesheet" crossorigin href="/assets/index-LrNlT5Tg.css">
17
+ <script type="module" crossorigin src="/assets/index-BuSTrK-Z.js"></script>
18
+ <link rel="stylesheet" crossorigin href="/assets/index-D11atkBt.css">
19
19
  </head>
20
20
  <body>
21
21
  <div data-hf-id="hf-aph5" id="root"></div>
package/dist/index.js CHANGED
@@ -4187,7 +4187,7 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
4187
4187
  drag.element.key ?? drag.element.id,
4188
4188
  !isMusicTrack(drag.element)
4189
4189
  );
4190
- const snap = snapMoveToTargets(
4190
+ const snapped = snapMoveToTargets(
4191
4191
  nextMove.start,
4192
4192
  drag.element.duration,
4193
4193
  targets,
@@ -4197,6 +4197,9 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
4197
4197
  dragMaxStart + drag.element.duration,
4198
4198
  ctx3.gridStep
4199
4199
  );
4200
+ const target = snapTarget(snapped);
4201
+ const missed = target !== null && !guideIfSaved(drag.element, { start: snapped.start }, target, pps);
4202
+ const snap = missed ? { start: nextMove.start, snapTime: null, snapType: null } : snapped;
4200
4203
  const group = resolveGroupDrag(drag, ctx3);
4201
4204
  const dragKey = drag.element.key ?? drag.element.id;
4202
4205
  const passengers = ctx3.elements.filter(
@@ -4213,8 +4216,7 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
4213
4216
  return { ...drag, started: true };
4214
4217
  }
4215
4218
  const { track: previewTrack, insertRow } = placement;
4216
- const target = placement.start === snap.start && snap.snapTime !== null && snap.snapType !== null ? { time: snap.snapTime, type: snap.snapType } : null;
4217
- const guide = guideIfSaved(drag.element, { start: snap.start }, target, pps);
4219
+ const guide = placement.start === snap.start ? snapTarget(snap) : null;
4218
4220
  return {
4219
4221
  ...drag,
4220
4222
  started: true,
@@ -4230,6 +4232,7 @@ function computeDragPreview(drag, clientX, clientY, ctx3) {
4230
4232
  snapType: guide?.type ?? null
4231
4233
  };
4232
4234
  }
4235
+ var snapTarget = (s) => s.snapTime !== null && s.snapType !== null ? { time: s.snapTime, type: s.snapType } : null;
4233
4236
  function guideIfSaved(element, clip, target, pps) {
4234
4237
  if (!target) return null;
4235
4238
  const saved2 = savedClipEdges(element, clip.start, clip.duration);
@@ -4273,30 +4276,30 @@ function computeResizePreview(resize2, clientX, ctx3) {
4273
4276
  );
4274
4277
  const gridStep = ctx3.gridStep ?? 0;
4275
4278
  let snap = null;
4276
- if (trimTargets.length > 0 || gridStep > 0) {
4277
- const snapSecs = TIMELINE_SNAP_PX / Math.max(pps, 1);
4278
- if (resize2.edge === "end") {
4279
- const edgeTime2 = nextResize.start + nextResize.duration;
4280
- const { time: snapped, target } = snapTimelineTime(edgeTime2, trimTargets, snapSecs, gridStep);
4281
- const snappedDuration = Math.round((snapped - nextResize.start) * 1e3) / 1e3;
4282
- if (target && snapped <= maxEnd + 1e-6 && snappedDuration >= resolveTimelineMinDuration() - 1e-6) {
4283
- if (snapped !== edgeTime2) nextResize = { ...nextResize, duration: snappedDuration };
4284
- snap = guideIfSaved(resize2.element, nextResize, target, pps);
4285
- }
4286
- } else {
4287
- const { time: snapped, target } = snapTimelineTime(
4288
- nextResize.start,
4289
- trimTargets,
4290
- snapSecs,
4291
- gridStep
4292
- );
4293
- const clip = { ...nextResize, playbackRate: resize2.element.playbackRate };
4294
- const delta = snapped - nextResize.start;
4295
- const bounds = clipStartTrimDeltaBounds(clip, minStart, resolveTimelineMinDuration());
4296
- if (target && delta >= bounds.minDelta - 1e-6 && delta <= bounds.maxDelta + 1e-6) {
4297
- if (snapped !== nextResize.start) nextResize = applyClipStartTrimDelta(clip, delta);
4298
- snap = guideIfSaved(resize2.element, nextResize, target, pps);
4299
- }
4279
+ const snapSecs = TIMELINE_SNAP_PX / Math.max(pps, 1);
4280
+ if (resize2.edge === "end") {
4281
+ const edgeTime2 = nextResize.start + nextResize.duration;
4282
+ const { time: snapped, target } = snapTimelineTime(edgeTime2, trimTargets, snapSecs, gridStep);
4283
+ const snappedDuration = Math.round((snapped - nextResize.start) * 1e3) / 1e3;
4284
+ if (target && snapped <= maxEnd + 1e-6 && snappedDuration >= resolveTimelineMinDuration() - 1e-6) {
4285
+ const onTarget = snapped === edgeTime2 ? nextResize : { ...nextResize, duration: snappedDuration };
4286
+ snap = guideIfSaved(resize2.element, onTarget, target, pps);
4287
+ if (snap) nextResize = onTarget;
4288
+ }
4289
+ } else {
4290
+ const { time: snapped, target } = snapTimelineTime(
4291
+ nextResize.start,
4292
+ trimTargets,
4293
+ snapSecs,
4294
+ gridStep
4295
+ );
4296
+ const clip = { ...nextResize, playbackRate: resize2.element.playbackRate };
4297
+ const delta = snapped - nextResize.start;
4298
+ const bounds = clipStartTrimDeltaBounds(clip, minStart, resolveTimelineMinDuration());
4299
+ if (target && delta >= bounds.minDelta - 1e-6 && delta <= bounds.maxDelta + 1e-6) {
4300
+ const onTarget = snapped === nextResize.start ? nextResize : applyClipStartTrimDelta(clip, delta);
4301
+ snap = guideIfSaved(resize2.element, onTarget, target, pps);
4302
+ if (snap) nextResize = onTarget;
4300
4303
  }
4301
4304
  }
4302
4305
  return {
@@ -15955,6 +15958,7 @@ import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
15955
15958
  var BAR_STEP = 3;
15956
15959
  var rendersWaveform = (el) => el.tag === "audio";
15957
15960
  var FADE_GHOST_OPACITY = 0.27;
15961
+ var WAVEFORM_LAYER_Z = 10;
15958
15962
  function paintWaveformBars(context, bars, height, waveformBarRgb, waveformBaselineRgb, amplitudes) {
15959
15963
  bars.forEach((bar, index) => {
15960
15964
  const opacity = loudnessToOpacity(amplitudes[index] ?? 0);
@@ -16113,7 +16117,7 @@ var AudioWaveform = memo10(function AudioWaveform2({
16113
16117
  else clip.removeAttribute("data-audio-muted");
16114
16118
  return () => clip.removeAttribute("data-audio-muted");
16115
16119
  }, [muted]);
16116
- return /* @__PURE__ */ jsx30("div", { ref: rootRef, className: "absolute inset-0", children: /* @__PURE__ */ jsxs17("div", { className: "absolute inset-0 overflow-hidden", style: { zIndex: 10 }, children: [
16120
+ return /* @__PURE__ */ jsx30("div", { ref: rootRef, className: "absolute inset-0", children: /* @__PURE__ */ jsxs17("div", { className: "absolute inset-0 overflow-hidden", style: { zIndex: WAVEFORM_LAYER_Z }, children: [
16117
16121
  /* @__PURE__ */ jsx30(
16118
16122
  "canvas",
16119
16123
  {
@@ -18468,7 +18472,7 @@ var OutOfSyncBadge = memo12(function OutOfSyncBadge2({ el }) {
18468
18472
  "aria-label": `Out of sync with ${partnerName} by ${label} frames`,
18469
18473
  title: `Out of sync with ${partnerName} by ${label} \u2014 click for Move or Slip into Sync`,
18470
18474
  "data-testid": "out-of-sync-badge",
18471
- className: "absolute bottom-0.5 left-1 z-[7] cursor-pointer rounded-[3px] bg-red-600 px-1 font-mono text-[9px] leading-[14px] text-white tabular-nums",
18475
+ className: "absolute bottom-0.5 left-1 z-[12] cursor-pointer rounded-[3px] bg-red-600 px-1 font-mono text-[9px] leading-[14px] text-white tabular-nums",
18472
18476
  onPointerDown: stop,
18473
18477
  onDoubleClick: stop,
18474
18478
  onClick: open,
@@ -24016,6 +24020,7 @@ var TimelineCanvas = memo16(function TimelineCanvas2() {
24016
24020
  draggedClip?.started && draggedClip.insertRow == null && draggedRowIndex >= 0 && /* @__PURE__ */ jsx59(
24017
24021
  "div",
24018
24022
  {
24023
+ "data-testid": "timeline-drag-landing",
24019
24024
  className: "absolute pointer-events-none",
24020
24025
  style: {
24021
24026
  top: props.rowGeometry.getRowTop(draggedRowIndex) + CLIP_Y,
@@ -50698,7 +50703,8 @@ function TextClipContent({ text }) {
50698
50703
 
50699
50704
  // src/player/components/AudibleVideoClipContent.tsx
50700
50705
  import { jsx as jsx115, jsxs as jsxs84 } from "react/jsx-runtime";
50701
- var AUDIBLE_VIDEO_WAVE_SHARE = 0.38;
50706
+ var AUDIBLE_VIDEO_WAVE_SHARE = 0.5;
50707
+ var AUDIO_CLIP_SURFACE = "var(--timeline-clip-audio-bg)";
50702
50708
  function AudibleVideoClipContent({
50703
50709
  thumbnail,
50704
50710
  waveform
@@ -50709,9 +50715,9 @@ function AudibleVideoClipContent({
50709
50715
  /* @__PURE__ */ jsx115(
50710
50716
  "div",
50711
50717
  {
50712
- className: "absolute inset-x-0 bottom-0 bg-black/30",
50718
+ className: "audible-video-wave absolute inset-x-0 bottom-0",
50713
50719
  "data-testid": "audible-video-wave",
50714
- style: { height: waveHeight },
50720
+ style: { height: waveHeight, backgroundColor: AUDIO_CLIP_SURFACE },
50715
50721
  children: waveform
50716
50722
  }
50717
50723
  )
@@ -50760,10 +50766,20 @@ function ClipPeakMarks({
50760
50766
  },
50761
50767
  run.from
50762
50768
  )),
50763
- /* @__PURE__ */ jsxs85("span", { className: "absolute top-0 right-1 font-mono text-[9px] leading-none text-red-400", children: [
50764
- "\u25B2 peaks ",
50765
- dbText2(peakDbfs)
50766
- ] })
50769
+ /* @__PURE__ */ jsxs85(
50770
+ "span",
50771
+ {
50772
+ className: "absolute top-0 right-1 font-mono text-[9px] leading-none text-red-400",
50773
+ "data-peak-badge": true,
50774
+ children: [
50775
+ "\u25B2",
50776
+ /* @__PURE__ */ jsxs85("span", { "data-peak-text": true, children: [
50777
+ " peaks ",
50778
+ dbText2(peakDbfs)
50779
+ ] })
50780
+ ]
50781
+ }
50782
+ )
50767
50783
  ]
50768
50784
  }
50769
50785
  )
@@ -87441,7 +87457,6 @@ function FrameyGlyph({
87441
87457
  // src/components/OpenInDesktopButton.tsx
87442
87458
  import { jsx as jsx203, jsxs as jsxs161 } from "react/jsx-runtime";
87443
87459
  var ROUTE = "/api/open-in-desktop";
87444
- var DOWNLOAD_URL = "https://hyperframes.dev/studio/download";
87445
87460
  var GAZE = 2.2;
87446
87461
  var OPENING_MS = 1600;
87447
87462
  function useDesktopRoute() {
@@ -87449,7 +87464,10 @@ function useDesktopRoute() {
87449
87464
  useEffect133(() => {
87450
87465
  let live = true;
87451
87466
  studioApiFetch(ROUTE).then((res) => res.ok ? res.json() : null).then((body) => {
87452
- if (live && body?.available === true) setRoute({ handoff: body.handoff === true });
87467
+ if (!live || body?.available !== true) return;
87468
+ const handoff = body.handoff === true;
87469
+ const downloadUrl = typeof body.downloadUrl === "string" ? body.downloadUrl : null;
87470
+ if (handoff || downloadUrl) setRoute({ handoff, downloadUrl });
87453
87471
  }).catch(() => {
87454
87472
  });
87455
87473
  return () => {
@@ -87491,7 +87509,7 @@ function OpenInDesktopButton() {
87491
87509
  const open = async () => {
87492
87510
  if (!route.handoff) {
87493
87511
  trackStudioEvent("toolbar_action", { action: "open_in_desktop" });
87494
- setDownloadUrl(DOWNLOAD_URL);
87512
+ setDownloadUrl(route.downloadUrl);
87495
87513
  return;
87496
87514
  }
87497
87515
  if (opening) return;
@@ -87501,7 +87519,7 @@ function OpenInDesktopButton() {
87501
87519
  const res = await studioApiFetch(ROUTE, { method: "POST" });
87502
87520
  const result = await res.json();
87503
87521
  if (result.opened) {
87504
- showToast("Framey is opening this project in HyperFrames Studio", "info");
87522
+ showToast("Framey is opening this project in the HyperFrames desktop app", "info");
87505
87523
  await new Promise((done) => setTimeout(done, OPENING_MS));
87506
87524
  } else setDownloadUrl(result.downloadUrl);
87507
87525
  } catch {
@@ -87523,17 +87541,20 @@ function OpenInDesktopButton() {
87523
87541
  arrow: true,
87524
87542
  "aria-label": "Meet Framey",
87525
87543
  className: "w-72",
87526
- trigger: /* @__PURE__ */ jsx203(
87544
+ trigger: /* @__PURE__ */ jsxs161(
87527
87545
  Button,
87528
87546
  {
87529
87547
  ref: trigger,
87530
87548
  variant: "ghost",
87531
87549
  "data-testid": "header-open-in-desktop",
87532
87550
  "data-opening": opening || void 0,
87533
- title: "Open this project in HyperFrames Studio, the desktop app, and edit it with Framey",
87534
- className: "hf-framey-trigger gap-1.5 pl-1.5",
87535
- icon: /* @__PURE__ */ jsx203(FrameyGlyph, { size: 20, className: "hf-framey", eyeRef: eye }),
87536
- children: opening ? "Opening in the app\u2026" : "Edit with Framey"
87551
+ title: "Open this project in the HyperFrames desktop app and edit it with Framey",
87552
+ className: "hf-framey-trigger gap-1.5 border border-accent/40 bg-accent/10 pl-1 font-semibold text-text-0 enabled:hover:bg-accent/20",
87553
+ icon: /* @__PURE__ */ jsx203(FrameyGlyph, { size: 24, className: "hf-framey", eyeRef: eye }),
87554
+ children: [
87555
+ opening ? "Opening in the app\u2026" : "Edit with Framey",
87556
+ !opening && /* @__PURE__ */ jsx203("span", { className: "rounded-full bg-accent px-1.5 text-[10px] leading-4 font-bold text-on-accent", children: "New" })
87557
+ ]
87537
87558
  }
87538
87559
  ),
87539
87560
  children: [
@@ -87541,7 +87562,7 @@ function OpenInDesktopButton() {
87541
87562
  /* @__PURE__ */ jsx203(FrameyGlyph, { size: 36 }),
87542
87563
  /* @__PURE__ */ jsx203("p", { className: "text-step-12 font-medium text-text-0", children: "Meet Framey" })
87543
87564
  ] }),
87544
- /* @__PURE__ */ jsx203("p", { className: "mt-2 text-text-3", children: "I live in HyperFrames Studio, the free desktop app for macOS:" }),
87565
+ /* @__PURE__ */ jsx203("p", { className: "mt-2 text-balance text-text-3", children: "I live in the free HyperFrames desktop app:" }),
87545
87566
  /* @__PURE__ */ jsxs161("ul", { className: "mt-1.5 grid gap-1 text-text-1", children: [
87546
87567
  /* @__PURE__ */ jsx203("li", { children: "Chat to change anything in the video" }),
87547
87568
  /* @__PURE__ */ jsx203("li", { children: "Draw on a frame to point at it" }),
@@ -87557,7 +87578,7 @@ function OpenInDesktopButton() {
87557
87578
  rel: "noopener noreferrer",
87558
87579
  className: cn(buttonBase, buttonVariants.primary, buttonSizes.md),
87559
87580
  onClick: () => trackStudioEvent("toolbar_action", { action: "get_desktop_app" }),
87560
- children: "Get HyperFrames Studio"
87581
+ children: "Get the desktop app"
87561
87582
  }
87562
87583
  )
87563
87584
  ] })
@@ -92969,7 +92990,7 @@ var RenderQueueItem = memo54(function RenderQueueItem2({
92969
92990
  // src/components/renders/FfmpegRequiredNotice.tsx
92970
92991
  import { memo as memo55, useEffect as useEffect148, useRef as useRef181, useState as useState148 } from "react";
92971
92992
  import { jsx as jsx228, jsxs as jsxs183 } from "react/jsx-runtime";
92972
- var DOWNLOAD_URL2 = "https://ffmpeg.org/download.html";
92993
+ var DOWNLOAD_URL = "https://ffmpeg.org/download.html";
92973
92994
  var CUE_MS = 1600;
92974
92995
  var FOCUS_RING = "outline-hidden focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-studio-accent";
92975
92996
  var FfmpegRequiredNotice = memo55(function FfmpegRequiredNotice2({
@@ -93033,7 +93054,7 @@ var FfmpegRequiredNotice = memo55(function FfmpegRequiredNotice2({
93033
93054
  /* @__PURE__ */ jsx228(
93034
93055
  "a",
93035
93056
  {
93036
- href: DOWNLOAD_URL2,
93057
+ href: DOWNLOAD_URL,
93037
93058
  target: "_blank",
93038
93059
  rel: "noreferrer",
93039
93060
  className: `rounded-xs py-0.5 text-[10px] text-panel-text-2 underline-offset-2 transition-colors hover:text-panel-text-0 hover:underline ${FOCUS_RING}`,