@vosjs/studio-core 0.5.1 → 0.7.0

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.
package/dist/index.d.ts CHANGED
@@ -922,6 +922,15 @@ interface EndCard {
922
922
  headline?: string;
923
923
  sub?: string;
924
924
  wordmark?: string;
925
+ /**
926
+ * The brand's MARK as an image (a take-dir path or a URL the render page
927
+ * can load), with its aspect (width over height), drawn above the
928
+ * wordmark; a wide mark (a stylised wordmark asset) stands alone.
929
+ */
930
+ mark?: {
931
+ key: string;
932
+ aspect: number;
933
+ };
925
934
  /**
926
935
  * The words' colour (any CSS colour). The house presets are white for
927
936
  * footage; over a light plate the brand's ink is what reads. Absent =
@@ -1050,6 +1059,12 @@ interface TextOverlayClip extends OverlayClipBase {
1050
1059
  letterSpacing?: number;
1051
1060
  /** Line height multiplier (default OVERLAY_LINE_HEIGHT). */
1052
1061
  lineHeight?: number;
1062
+ /**
1063
+ * Legibility shadow strength 0..1 (the preset's when absent; 0 = none).
1064
+ * Words on a plate ground carry none: the shadow exists for words over
1065
+ * footage.
1066
+ */
1067
+ shadow?: number;
1053
1068
  /** Text outline, drawn under the fill. */
1054
1069
  stroke?: TextOverlayStroke;
1055
1070
  /** Background pill behind the text block (absent = none). */
package/dist/index.js CHANGED
@@ -1266,6 +1266,7 @@ var CARD_FOV = 30;
1266
1266
  var OVERLAY_Z = -2;
1267
1267
  var CARD_Z = -4;
1268
1268
  var BACKGROUND_Z = -6;
1269
+ var CARD_OVERSCAN = 1.25;
1269
1270
  var CAMERA_NEAR = 0.1;
1270
1271
  var CAMERA_FAR = 100;
1271
1272
  function planeSizeAtDepth(distance, fovDeg, aspect) {
@@ -1361,6 +1362,7 @@ function resolveOverlayStyle(clip) {
1361
1362
  )
1362
1363
  } : {},
1363
1364
  ...clip.color ? { color: clip.color } : {},
1365
+ ...clip.shadow !== void 0 ? { shadow: Math.min(1, Math.max(0, clip.shadow)) } : {},
1364
1366
  fontStyle: clip.italic ? "italic" : "normal",
1365
1367
  align: clip.align ?? "center",
1366
1368
  letterSpacing: clip.letterSpacing ?? 0,
@@ -2850,11 +2852,44 @@ function expandEndCard(doc, outputDuration) {
2850
2852
  enter: "rise",
2851
2853
  exit: "none",
2852
2854
  align: "center",
2853
- ...card.ink ? { color: card.ink } : {}
2855
+ ...card.ink ? { color: card.ink } : {},
2856
+ shadow: 0
2854
2857
  });
2855
- if (card.headline?.trim()) clips.push(text("endcard-title", card.headline.trim(), "title", 0.44, 0.35));
2856
- if (card.sub?.trim()) clips.push(text("endcard-sub", card.sub.trim(), "caption", 0.57, 0.5));
2857
- if (card.wordmark?.trim()) clips.push(text("endcard-mark", card.wordmark.trim(), "label", 0.88, 0.65));
2858
+ if (card.headline?.trim())
2859
+ clips.push(text("endcard-title", card.headline.trim(), "title", 0.44, 0.35));
2860
+ if (card.sub?.trim())
2861
+ clips.push(text("endcard-sub", card.sub.trim(), "caption", 0.57, 0.5));
2862
+ const mark = card.mark;
2863
+ const wide = !!mark && mark.aspect > 2.2;
2864
+ if (mark && mark.key) {
2865
+ const size = resolveExportSize(doc);
2866
+ const designW = 1080 * size.width / Math.max(1, size.height);
2867
+ const hPx = wide ? 44 : 30;
2868
+ const clip = {
2869
+ id: "endcard-markimg",
2870
+ kind: "image",
2871
+ key: mark.key,
2872
+ width: Math.min(0.9, hPx * Math.max(0.2, mark.aspect) / designW),
2873
+ radius: 0,
2874
+ shadow: "none",
2875
+ start: endStart + 0.65,
2876
+ duration: Math.max(0.3, seconds - 0.65),
2877
+ transform: { x: 0.5, y: wide ? 0.88 : 0.835, scale: 1, rotation: 0 },
2878
+ enter: "rise",
2879
+ exit: "none"
2880
+ };
2881
+ clips.push(clip);
2882
+ }
2883
+ if (card.wordmark?.trim() && !wide)
2884
+ clips.push(
2885
+ text(
2886
+ "endcard-mark",
2887
+ card.wordmark.trim(),
2888
+ "label",
2889
+ mark && mark.key ? 0.895 : 0.88,
2890
+ 0.65
2891
+ )
2892
+ );
2858
2893
  const overlays = [...doc.overlays ?? [], ...clips];
2859
2894
  return { doc: { ...doc, segments, overlays }, endStart, seconds };
2860
2895
  }
@@ -3526,24 +3561,33 @@ var ON_FRAME = `(ctx, content, dt) => {
3526
3561
  // the GPU texture allocated at the original dims and re-uploads the new canvas
3527
3562
  // against stale dims \u2192 stretch/duplicate; dispose() forces a full realloc) and
3528
3563
  // rebuilding each frustum-filling plane at the new aspect + syncing the camera.
3529
- if (cv.width !== W || cv.height !== H) {
3564
+ // Card OVERSCAN (stage.ts CARD_OVERSCAN): a frame whose inset bleeds the
3565
+ // card past an edge paints it on a canvas and a plane larger than the
3566
+ // frame by ck about the centre, so a tilt that turns the bled edge back
3567
+ // toward the viewer shows card there, not the texture's edge. Every doc
3568
+ // that keeps its card inside the frame (and the flat stub, which has no
3569
+ // card layer) stays at 1: byte- and pixel-identical.
3570
+ var d = ctx.data || {}
3571
+ var ckIns = (d.frame && d.frame.inset) || {}
3572
+ var ck = r.card && (ckIns.left < 0 || ckIns.right < 0 || ckIns.top < 0 || ckIns.bottom < 0) ? ${CARD_OVERSCAN} : 1
3573
+ var cW = Math.round(W * ck), cH = Math.round(H * ck)
3574
+ if (cv.width !== cW || cv.height !== cH) {
3530
3575
  var THREE = ctx.THREE
3531
3576
  var aspect = W / H
3532
3577
  var cm = ctx.camera
3533
3578
  if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }
3534
- var lyr = [[bg, ${BACKGROUND_Z}], [card, ${CARD_Z}], [ov, ${OVERLAY_Z}]]
3579
+ var lyr = [[bg, ${BACKGROUND_Z}, 1], [card, ${CARD_Z}, ck], [ov, ${OVERLAY_Z}, 1]]
3535
3580
  for (var Li = 0; Li < lyr.length; Li++) {
3536
- var Ly = lyr[Li][0], Lz = lyr[Li][1]
3537
- if (Ly.canvas) { Ly.canvas.width = W; Ly.canvas.height = H }
3581
+ var Ly = lyr[Li][0], Lz = lyr[Li][1], Lk = lyr[Li][2]
3582
+ if (Ly.canvas) { Ly.canvas.width = Math.round(W * Lk); Ly.canvas.height = Math.round(H * Lk) }
3538
3583
  if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()
3539
3584
  if (Ly.mesh && THREE) {
3540
3585
  if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()
3541
- var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
3586
+ var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2) * Lk
3542
3587
  Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect, Lh)
3543
3588
  }
3544
3589
  }
3545
3590
  }
3546
- var d = ctx.data || {}
3547
3591
  var frame = d.frame || {}
3548
3592
  var TL = globalThis.__vosTimeline
3549
3593
  // Output-timeline seconds (engine-fed master clock) \u2192 source seconds on screen.
@@ -3862,7 +3906,15 @@ var ON_FRAME = `(ctx, content, dt) => {
3862
3906
 
3863
3907
  // The CARD layer canvas starts transparent each frame \u2014 the padding around
3864
3908
  // the contain-fit card shows the background layer through the plane's alpha.
3865
- c.clearRect(0, 0, W, H)
3909
+ // Under overscan the canvas is larger than the frame and every frame-space
3910
+ // draw below rides a base translation to its centre.
3911
+ if (ck !== 1) c.setTransform(1, 0, 0, 1, 0, 0)
3912
+ c.clearRect(0, 0, cv.width, cv.height)
3913
+ if (ck !== 1) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)
3914
+ // Footage is nearly always drawn SMALLER than it was recorded (a 1920
3915
+ // recording in a padded 1080 card, or a 440-wide tile): the default
3916
+ // bilinear tap aliases and blurs a large downscale; 'high' resamples.
3917
+ c.imageSmoothingQuality = 'high'
3866
3918
 
3867
3919
  // video destination rect (contain within the padded area). The optional browser-bar
3868
3920
  // strip is part of the card: it takes barH from the available height and the video
@@ -3973,14 +4025,21 @@ var ON_FRAME = `(ctx, content, dt) => {
3973
4025
  // corners, drawn by the clip alone, hid theirs inside the arc.
3974
4026
  var shD = ceZs * (cardW + cardH) + 2 * (W + H)
3975
4027
  var shX = cardX - shD / ceZs
3976
- // soft shadow behind the card (bar strip + video)
4028
+ // The card shadow is LAYERED: three shadows whose blur and offset grow
4029
+ // while each stays at a low alpha (shadow is the strength they share),
4030
+ // so the card reads as lifted a little off the ground. One wide layer at
4031
+ // the full strength was the old paint, and at the default 0.4 it read as
4032
+ // a dark pool under the card.
3977
4033
  if (shadow > 0) {
3978
- c.save()
3979
- c.shadowColor = 'rgba(' + shRgb + ',' + shadow + ')'
3980
- c.shadowBlur = 60 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 24 * s2
3981
- c.fillStyle = '#000'
3982
- rr(shX, cardY, cardW, cardH, radius); c.fill()
3983
- c.restore()
4034
+ var shL = [[3, 1, 0.2], [14, 6, 0.22], [48, 22, 0.3]]
4035
+ for (var shI = 0; shI < shL.length; shI++) {
4036
+ c.save()
4037
+ c.shadowColor = 'rgba(' + shRgb + ',' + +(shadow * shL[shI][2]).toFixed(3) + ')'
4038
+ c.shadowBlur = shL[shI][0] * s2; c.shadowOffsetX = shD; c.shadowOffsetY = shL[shI][1] * s2
4039
+ c.fillStyle = '#000'
4040
+ rr(shX, cardY, cardW, cardH, radius); c.fill()
4041
+ c.restore()
4042
+ }
3984
4043
  }
3985
4044
  // contact shadow: tight and close, over the ambient layer
3986
4045
  if (shC > 0) {
@@ -5884,8 +5943,8 @@ var LOOKS = {
5884
5943
  cardWidth: 0.84,
5885
5944
  headroom: 0.16,
5886
5945
  radius: 12,
5887
- shadow: 0.22,
5888
- shadowContact: 0.16,
5946
+ shadow: 0.42,
5947
+ shadowContact: 0,
5889
5948
  border: 0.08,
5890
5949
  borderColor: "#000000"
5891
5950
  },
@@ -5895,8 +5954,8 @@ var LOOKS = {
5895
5954
  cardWidth: 0.84,
5896
5955
  headroom: 0.16,
5897
5956
  radius: 12,
5898
- shadow: 0.35,
5899
- shadowContact: 0.2,
5957
+ shadow: 0.5,
5958
+ shadowContact: 0,
5900
5959
  border: 0
5901
5960
  },
5902
5961
  dark: {
@@ -5905,8 +5964,8 @@ var LOOKS = {
5905
5964
  cardWidth: 0.86,
5906
5965
  headroom: 0.14,
5907
5966
  radius: 12,
5908
- shadow: 0.5,
5909
- shadowContact: 0.3,
5967
+ shadow: 0.7,
5968
+ shadowContact: 0,
5910
5969
  shadowColor: "#000000",
5911
5970
  border: 0.12,
5912
5971
  borderColor: "#ffffff"