@vosjs/studio-core 0.40.2 → 0.41.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/README.md CHANGED
@@ -45,7 +45,7 @@ The shared layers lower as one engine `stack` entry, `STUDIO_ENTRY_ID` (`'vosso.
45
45
 
46
46
  ## Destinations
47
47
 
48
- `DESTINATIONS` is the table of launch destinations (store listings, Product Hunt, social, OG cards, README loops) with exact pixel sizes, byte and duration ceilings, generated from `channel-specs.json` in `@vosjs/cli` and hash-gated by a test. `destinationById`, `destinationsForChannel`, `exportSizeFor(ratio, resolution)` and `resolveExportSize` size an export; `ExportResolution` is `720p | 1080p | 2k | 4k`.
48
+ `DESTINATIONS` is the table of video destinations (a YouTube demo, an X feed cut, a vertical cut for Shorts and LinkedIn, a GitHub README loop) with exact pixel sizes, byte and duration ceilings and a word policy, generated from `channel-specs.json` in `@vosjs/cli` and hash-gated by a test. `destinationById`, `destinationsForChannel`, `exportSizeFor(ratio, resolution)` and `resolveExportSize` size an export; `ExportResolution` is `720p | 1080p | 2k | 4k`.
49
49
 
50
50
  ## Exports by area
51
51
 
package/dist/index.d.ts CHANGED
@@ -1100,8 +1100,8 @@ interface FrameStyle {
1100
1100
  * How footage meets an off-ratio frame. 'contain' (default) fits the
1101
1101
  * whole card inside the padded area, letterboxing onto the background;
1102
1102
  * 'cover' makes the padded area the card and cover-fills it with footage,
1103
- * cropped around `focus` — what a 440x280 store tile or a 2.5:1 marquee
1104
- * demands ("fill the region"). Absent = contain; every existing doc is
1103
+ * cropped around `focus` — what a 9:16 cut of 16:9 footage demands
1104
+ * ("fill the region"). Absent = contain; every existing doc is
1105
1105
  * byte-identical.
1106
1106
  */
1107
1107
  fit?: 'contain' | 'cover';
@@ -1816,8 +1816,8 @@ interface ProjectDoc {
1816
1816
  media?: Media[];
1817
1817
  /**
1818
1818
  * The frame that stands for this document (OUTPUT seconds): the cover the
1819
- * shelf shows, the still a kit leads with. Absent, `docStillTime` derives
1820
- * it: the document's own last freeze (a poster's rest), else the hero
1819
+ * shelf shows. Absent, `docStillTime` derives it: the document's own
1820
+ * last freeze (the rest a closing hold ends on), else the hero
1821
1821
  * moment after the opening has entered. A template never sets it.
1822
1822
  */
1823
1823
  still?: number;
@@ -3857,8 +3857,8 @@ declare function wrapProgramLength(source: string): string;
3857
3857
  declare function lowerProgramDoc(doc: ProgramAnchorDoc, opts?: LowerProgramOptions): LoweredComposition;
3858
3858
 
3859
3859
  /**
3860
- * A destination is where a release's media goes — the output twin of the
3861
- * doors registry's "a door is what you bring". One row per channel asset,
3860
+ * A destination is where a release's video goes — the output twin of the
3861
+ * doors registry's "a door is what you bring". One row per channel video,
3862
3862
  * derived from the verified channel specs; `vos deliver` loops these and
3863
3863
  * the kit manifest records them.
3864
3864
  */
@@ -3868,58 +3868,29 @@ interface Destination {
3868
3868
  channel: string;
3869
3869
  asset: string;
3870
3870
  label: string;
3871
- kind: 'video' | 'still' | 'still-set';
3872
3871
  /** Reduced aspect ratio, the exportSizeFor convention. */
3873
3872
  ratio: string;
3874
3873
  px: {
3875
3874
  w: number;
3876
3875
  h: number;
3877
3876
  };
3878
- /** still-set only: how many the channel takes. */
3879
- count?: {
3880
- min: number;
3881
- max: number;
3882
- };
3883
- /**
3884
- * Image genre: 'screenshot' = real UI from the take (store policy demands
3885
- * real UX); 'card' = a COMPOSED cover — rendered from the maker's poster
3886
- * program when `vos deliver --poster` has one.
3887
- */
3888
- genre?: 'screenshot' | 'card';
3889
3877
  minSeconds?: number;
3890
3878
  maxSeconds?: number;
3891
3879
  maxBytes?: number;
3892
3880
  /** The format the kit renders. */
3893
- format: 'mp4' | 'png';
3881
+ format: 'mp4';
3894
3882
  /** What the channel accepts, in the spec's own words. */
3895
3883
  accepts: string;
3896
- /** How footage meets an off-ratio frame (a still fills, a video letterboxes). */
3897
- fit: 'contain' | 'cover';
3898
3884
  /**
3899
- * The destination's word policy: 'none' = the picture carries it alone (a
3900
- * tile, a store screenshot), 'allowed', 'expected' = a card that should
3901
- * say something. The picture checks read it.
3885
+ * The destination's word policy: 'none' = the picture carries it alone
3886
+ * (a README loop, so the beat captions drop), 'allowed', 'expected' = a
3887
+ * cut that should say something.
3902
3888
  */
3903
3889
  text: 'none' | 'allowed' | 'expected';
3904
- /**
3905
- * The rect, as fractions of the frame, every text box must sit inside:
3906
- * the platform's own chrome or crop covers the rest.
3907
- */
3908
- safe: {
3909
- x: number;
3910
- y: number;
3911
- w: number;
3912
- h: number;
3913
- };
3914
- /**
3915
- * The poster template a card destination renders from by default (the
3916
- * family that ships with the CLI); absent on screenshots and videos.
3917
- */
3918
- template?: string;
3919
3890
  notes: string;
3920
3891
  }
3921
3892
  declare const CHANNEL_SPECS_VERIFIED = "2026-08-04";
3922
- declare const CHANNEL_SPECS_HASH = "e48926c22737b5dfaabf5cb33916a50a5eccd4b93498c2059a1468bd963a0a86";
3893
+ declare const CHANNEL_SPECS_HASH = "0f12781634495af6af1b49845009f04810d75829cfb0088dbf903d136e330f25";
3923
3894
  declare const DESTINATIONS: Destination[];
3924
3895
  declare function destinationById(id: string): Destination | undefined;
3925
3896
  declare function destinationsForChannel(channel: string): Destination[];
@@ -4346,9 +4317,9 @@ declare function ownFreezes(doc: ProjectDoc): FreezeSpan[];
4346
4317
  /** The still a document with nothing to say gets: the old default. */
4347
4318
  declare const STILL_DEFAULT = 0.5;
4348
4319
  /**
4349
- * The output moment that STANDS for a document: its cover on the shelf,
4350
- * the still a kit leads with. One ladder, one derivation, read by the
4351
- * fleet thumbnail, the stills export and the kit alike; a template can
4320
+ * The output moment that STANDS for a document: its cover on the shelf.
4321
+ * One ladder, one derivation, read by every surface that takes a cover
4322
+ * (the fleet thumbnail, `vos frames --at-still`); a template can
4352
4323
  * never move it, because everything a template lays is stamped `from`.
4353
4324
  *
4354
4325
  * 1. `doc.still`, the author's own choice, clamped to the output.
package/dist/index.js CHANGED
@@ -7766,6 +7766,45 @@ var ON_FRAME = `(ctx, content, dt) => {
7766
7766
  if (bg.texture) bg.texture.needsUpdate = true
7767
7767
  }
7768
7768
 
7769
+ // The two painters every pass shares, and the overlay layer after the
7770
+ // loop too (the cam bubble, the dot across a boundary). They stand OUTSIDE
7771
+ // the loop on purpose: the program runs as a module, so it is strict, and
7772
+ // in strict code a function declared in a block exists only in that block.
7773
+ function rr(x, y, w, h, rd, cx) {
7774
+ var cc = cx || c
7775
+ if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }
7776
+ else { cc.beginPath(); cc.rect(x, y, w, h) }
7777
+ }
7778
+
7779
+ // The pointer's shape: an OS-style arrow (tip on the recorded point, a
7780
+ // black body with a white edge, the shape of the pointer on the viewer's
7781
+ // own screen) or the white dot every earlier take was cut with. One
7782
+ // painter for both draw sites (in the card, and the ghost across a
7783
+ // boundary); r is the dot's radius, so an arrow stands 2r tall.
7784
+ var curArrow = !!(d.cursorStyle && d.cursorStyle.style === 'arrow')
7785
+ function drawCur(cc, x, y, r, a, lw) {
7786
+ cc.save()
7787
+ if (curArrow) {
7788
+ var h = 2 * r
7789
+ var P = [[0, 0], [0, 0.72], [0.19, 0.55], [0.31, 0.83], [0.43, 0.78], [0.31, 0.5], [0.55, 0.5]]
7790
+ cc.beginPath()
7791
+ cc.moveTo(x, y)
7792
+ for (var pi = 1; pi < P.length; pi++) cc.lineTo(x + P[pi][0] * h, y + P[pi][1] * h)
7793
+ cc.closePath()
7794
+ cc.lineJoin = 'round'
7795
+ cc.fillStyle = 'rgba(0,0,0,' + (0.92 * a) + ')'
7796
+ cc.strokeStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
7797
+ cc.lineWidth = Math.max(1, lw * 0.75)
7798
+ cc.fill(); cc.stroke()
7799
+ } else {
7800
+ cc.fillStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
7801
+ cc.strokeStyle = 'rgba(0,0,0,' + (0.4 * a) + ')'
7802
+ cc.lineWidth = lw
7803
+ cc.beginPath(); cc.arc(x, y, r, 0, Math.PI * 2); cc.fill(); cc.stroke()
7804
+ }
7805
+ cc.restore()
7806
+ }
7807
+
7769
7808
  // The card paints in PASSES: the ghost first (the outgoing clip's last
7770
7809
  // frame on its own canvas, while a boundary is crossing), then the live
7771
7810
  // card. A pass swaps the canvas, the element, the media, the source
@@ -7880,12 +7919,6 @@ var ON_FRAME = `(ctx, content, dt) => {
7880
7919
  shRgb = parseInt(shHex.slice(1, 3), 16) + ',' + parseInt(shHex.slice(3, 5), 16) + ',' + parseInt(shHex.slice(5, 7), 16)
7881
7920
  }
7882
7921
 
7883
- function rr(x, y, w, h, rd, cx) {
7884
- var cc = cx || c
7885
- if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }
7886
- else { cc.beginPath(); cc.rect(x, y, w, h) }
7887
- }
7888
-
7889
7922
  c.save()
7890
7923
  // d.zoomSuppressed = editor aiming mode: the host merges it into ctx.data
7891
7924
  // while the focus overlay is up so the full frame renders. Never persisted.
@@ -8062,35 +8095,6 @@ var ON_FRAME = `(ctx, content, dt) => {
8062
8095
  // cursor coordinate space + drawn radius (shared by click effects + the dot)
8063
8096
  var space = (actM ? actM.cursorSpace : d.cursorSpace) || { w: vw, h: vh }
8064
8097
  var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5
8065
- // The pointer's shape: an OS-style arrow (tip on the recorded point, a
8066
- // black body with a white edge, the shape of the pointer on the viewer's
8067
- // own screen) or the white dot every earlier take was cut with. One
8068
- // painter for both draw sites (in the card, and the ghost across a
8069
- // boundary); r is the dot's radius, so an arrow stands 2r tall.
8070
- var curArrow = !!(d.cursorStyle && d.cursorStyle.style === 'arrow')
8071
- function drawCur(cc, x, y, r, a, lw) {
8072
- cc.save()
8073
- if (curArrow) {
8074
- var h = 2 * r
8075
- var P = [[0, 0], [0, 0.72], [0.19, 0.55], [0.31, 0.83], [0.43, 0.78], [0.31, 0.5], [0.55, 0.5]]
8076
- cc.beginPath()
8077
- cc.moveTo(x, y)
8078
- for (var pi = 1; pi < P.length; pi++) cc.lineTo(x + P[pi][0] * h, y + P[pi][1] * h)
8079
- cc.closePath()
8080
- cc.lineJoin = 'round'
8081
- cc.fillStyle = 'rgba(0,0,0,' + (0.92 * a) + ')'
8082
- cc.strokeStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
8083
- cc.lineWidth = Math.max(1, lw * 0.75)
8084
- cc.fill(); cc.stroke()
8085
- } else {
8086
- cc.fillStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'
8087
- cc.strokeStyle = 'rgba(0,0,0,' + (0.4 * a) + ')'
8088
- cc.lineWidth = lw
8089
- cc.beginPath(); cc.arc(x, y, r, 0, Math.PI * 2); cc.fill(); cc.stroke()
8090
- }
8091
- cc.restore()
8092
- }
8093
-
8094
8098
  // click effects \u2014 pure f(t): d.clicks are
8095
8099
  // OUTPUT-anchored records baked at lowering (sorted by ot; re-baked on every
8096
8100
  // edit, so they can't go stale), drawn UNDER the cursor and inside the zoom
@@ -10274,14 +10278,13 @@ function lowerProgramDoc(doc, opts = {}) {
10274
10278
 
10275
10279
  // src/destinations.ts
10276
10280
  var CHANNEL_SPECS_VERIFIED = "2026-08-04";
10277
- var CHANNEL_SPECS_HASH = "e48926c22737b5dfaabf5cb33916a50a5eccd4b93498c2059a1468bd963a0a86";
10281
+ var CHANNEL_SPECS_HASH = "0f12781634495af6af1b49845009f04810d75829cfb0088dbf903d136e330f25";
10278
10282
  var DESTINATIONS = [
10279
10283
  {
10280
10284
  id: "youtube-main-demo",
10281
10285
  channel: "youtube",
10282
10286
  asset: "main-demo",
10283
10287
  label: "YouTube demo",
10284
- kind: "video",
10285
10288
  ratio: "16:9",
10286
10289
  px: {
10287
10290
  w: 1920,
@@ -10291,14 +10294,7 @@ var DESTINATIONS = [
10291
10294
  maxSeconds: 120,
10292
10295
  format: "mp4",
10293
10296
  accepts: "mp4 (H.264+AAC)",
10294
- fit: "contain",
10295
10297
  text: "allowed",
10296
- safe: {
10297
- x: 0.05,
10298
- y: 0.08,
10299
- w: 0.9,
10300
- h: 0.84
10301
- },
10302
10298
  notes: "Captions burned in. The same public upload serves the Chrome Web Store promo video and Product Hunt video (both take YouTube URLs only)."
10303
10299
  },
10304
10300
  {
@@ -10306,7 +10302,6 @@ var DESTINATIONS = [
10306
10302
  channel: "x",
10307
10303
  asset: "feed-cut",
10308
10304
  label: "X feed cut",
10309
- kind: "video",
10310
10305
  ratio: "16:9",
10311
10306
  px: {
10312
10307
  w: 1920,
@@ -10317,14 +10312,7 @@ var DESTINATIONS = [
10317
10312
  maxBytes: 536870912,
10318
10313
  format: "mp4",
10319
10314
  accepts: "mp4 (H.264+AAC ONLY \u2014 HEVC/VP9/AV1 rejected)",
10320
- fit: "contain",
10321
10315
  text: "allowed",
10322
- safe: {
10323
- x: 0.05,
10324
- y: 0.08,
10325
- w: 0.9,
10326
- h: 0.84
10327
- },
10328
10316
  notes: "30\u201360s plays best; 140s/512MB is the free-tier ceiling. 1:1 also allowed. Native upload, never a link post."
10329
10317
  },
10330
10318
  {
@@ -10332,7 +10320,6 @@ var DESTINATIONS = [
10332
10320
  channel: "shorts-linkedin",
10333
10321
  asset: "vertical-cut",
10334
10322
  label: "Shorts / LinkedIn vertical cut",
10335
- kind: "video",
10336
10323
  ratio: "9:16",
10337
10324
  px: {
10338
10325
  w: 1080,
@@ -10342,14 +10329,7 @@ var DESTINATIONS = [
10342
10329
  maxSeconds: 90,
10343
10330
  format: "mp4",
10344
10331
  accepts: "mp4 (H.264+AAC)",
10345
- fit: "contain",
10346
10332
  text: "expected",
10347
- safe: {
10348
- x: 0.0833,
10349
- y: 0.1979,
10350
- w: 0.8333,
10351
- h: 0.6042
10352
- },
10353
10333
  notes: "Critical text inside a centered ~900\xD71160 safe zone \u2014 platform chrome covers the rest. Serves YouTube Shorts and LinkedIn native vertical."
10354
10334
  },
10355
10335
  {
@@ -10357,7 +10337,6 @@ var DESTINATIONS = [
10357
10337
  channel: "github",
10358
10338
  asset: "readme-loop",
10359
10339
  label: "GitHub README loop",
10360
- kind: "video",
10361
10340
  ratio: "16:9",
10362
10341
  px: {
10363
10342
  w: 1920,
@@ -10368,298 +10347,8 @@ var DESTINATIONS = [
10368
10347
  maxBytes: 10485760,
10369
10348
  format: "mp4",
10370
10349
  accepts: "mp4 (H.264)",
10371
- fit: "contain",
10372
10350
  text: "none",
10373
- safe: {
10374
- x: 0.05,
10375
- y: 0.08,
10376
- w: 0.9,
10377
- h: 0.84
10378
- },
10379
10351
  notes: "\u226410MB is the free-plan attachment ceiling; two-pass target bitrate from duration."
10380
- },
10381
- {
10382
- id: "youtube-thumbnail",
10383
- channel: "youtube",
10384
- asset: "thumbnail",
10385
- label: "YouTube thumbnail",
10386
- kind: "still",
10387
- ratio: "16:9",
10388
- px: {
10389
- w: 1280,
10390
- h: 720
10391
- },
10392
- genre: "card",
10393
- maxBytes: 2097152,
10394
- format: "png",
10395
- accepts: "png|jpg",
10396
- fit: "cover",
10397
- text: "expected",
10398
- safe: {
10399
- x: 0.05,
10400
- y: 0.08,
10401
- w: 0.9,
10402
- h: 0.84
10403
- },
10404
- template: "split-cover",
10405
- notes: ""
10406
- },
10407
- {
10408
- id: "cws-screenshot",
10409
- channel: "cws",
10410
- asset: "screenshot",
10411
- label: "Chrome Web Store screenshot",
10412
- kind: "still-set",
10413
- ratio: "8:5",
10414
- px: {
10415
- w: 1280,
10416
- h: 800
10417
- },
10418
- count: {
10419
- min: 1,
10420
- max: 5
10421
- },
10422
- genre: "screenshot",
10423
- format: "png",
10424
- accepts: "png|jpg",
10425
- fit: "cover",
10426
- text: "none",
10427
- safe: {
10428
- x: 0,
10429
- y: 0,
10430
- w: 1,
10431
- h: 1
10432
- },
10433
- notes: "Real UX only \u2014 misleading listing images are a removal-grade violation. Full bleed, square corners."
10434
- },
10435
- {
10436
- id: "cws-small-promo-tile",
10437
- channel: "cws",
10438
- asset: "small-promo-tile",
10439
- label: "Chrome Web Store small promo tile",
10440
- kind: "still",
10441
- ratio: "11:7",
10442
- px: {
10443
- w: 440,
10444
- h: 280
10445
- },
10446
- genre: "card",
10447
- format: "png",
10448
- accepts: "png|jpg",
10449
- fit: "cover",
10450
- text: "none",
10451
- safe: {
10452
- x: 0,
10453
- y: 0,
10454
- w: 1,
10455
- h: 1
10456
- },
10457
- template: "card-on-gradient",
10458
- notes: "Listings without one rank lower. No text; fill the region; subject centered."
10459
- },
10460
- {
10461
- id: "cws-marquee",
10462
- channel: "cws",
10463
- asset: "marquee",
10464
- label: "Chrome Web Store marquee",
10465
- kind: "still",
10466
- ratio: "5:2",
10467
- px: {
10468
- w: 1400,
10469
- h: 560
10470
- },
10471
- genre: "card",
10472
- format: "png",
10473
- accepts: "png|jpg",
10474
- fit: "cover",
10475
- text: "allowed",
10476
- safe: {
10477
- x: 0.05,
10478
- y: 0.08,
10479
- w: 0.9,
10480
- h: 0.84
10481
- },
10482
- template: "split-cover",
10483
- notes: "Carousel eligibility."
10484
- },
10485
- {
10486
- id: "cws-icon",
10487
- channel: "cws",
10488
- asset: "icon",
10489
- label: "Chrome Web Store icon",
10490
- kind: "still",
10491
- ratio: "1:1",
10492
- px: {
10493
- w: 128,
10494
- h: 128
10495
- },
10496
- format: "png",
10497
- accepts: "png",
10498
- fit: "cover",
10499
- text: "none",
10500
- safe: {
10501
- x: 0,
10502
- y: 0,
10503
- w: 1,
10504
- h: 1
10505
- },
10506
- notes: "96\xD796 art + 16px transparent padding."
10507
- },
10508
- {
10509
- id: "producthunt-thumbnail",
10510
- channel: "producthunt",
10511
- asset: "thumbnail",
10512
- label: "Product Hunt thumbnail",
10513
- kind: "still",
10514
- ratio: "1:1",
10515
- px: {
10516
- w: 240,
10517
- h: 240
10518
- },
10519
- genre: "card",
10520
- maxBytes: 3145728,
10521
- format: "png",
10522
- accepts: "gif|png",
10523
- fit: "cover",
10524
- text: "none",
10525
- safe: {
10526
- x: 0.06,
10527
- y: 0.06,
10528
- w: 0.88,
10529
- h: 0.88
10530
- },
10531
- template: "card-on-gradient",
10532
- notes: "GIF animates on hover only \u2014 the first frame must stand alone."
10533
- },
10534
- {
10535
- id: "producthunt-gallery",
10536
- channel: "producthunt",
10537
- asset: "gallery",
10538
- label: "Product Hunt gallery",
10539
- kind: "still-set",
10540
- ratio: "127:76",
10541
- px: {
10542
- w: 1270,
10543
- h: 760
10544
- },
10545
- count: {
10546
- min: 4,
10547
- max: 8
10548
- },
10549
- genre: "screenshot",
10550
- format: "png",
10551
- accepts: "png|jpg|gif",
10552
- fit: "cover",
10553
- text: "none",
10554
- safe: {
10555
- x: 0,
10556
- y: 0,
10557
- w: 1,
10558
- h: 1
10559
- },
10560
- notes: "First image is the hero."
10561
- },
10562
- {
10563
- id: "x-feed-image",
10564
- channel: "x",
10565
- asset: "feed-image",
10566
- label: "X feed image",
10567
- kind: "still",
10568
- ratio: "16:9",
10569
- px: {
10570
- w: 1200,
10571
- h: 675
10572
- },
10573
- genre: "card",
10574
- format: "png",
10575
- accepts: "png|jpg",
10576
- fit: "cover",
10577
- text: "allowed",
10578
- safe: {
10579
- x: 0.05,
10580
- y: 0.08,
10581
- w: 0.9,
10582
- h: 0.84
10583
- },
10584
- template: "split-cover",
10585
- notes: ""
10586
- },
10587
- {
10588
- id: "linkedin-feed-image",
10589
- channel: "linkedin",
10590
- asset: "feed-image",
10591
- label: "LinkedIn feed image",
10592
- kind: "still",
10593
- ratio: "400:209",
10594
- px: {
10595
- w: 1200,
10596
- h: 627
10597
- },
10598
- genre: "card",
10599
- format: "png",
10600
- accepts: "png|jpg",
10601
- fit: "cover",
10602
- text: "expected",
10603
- safe: {
10604
- x: 0.05,
10605
- y: 0.08,
10606
- w: 0.9,
10607
- h: 0.84
10608
- },
10609
- template: "split-cover",
10610
- notes: "1080\xD71350 vertical also performs in the mobile feed."
10611
- },
10612
- {
10613
- id: "og-card",
10614
- channel: "og",
10615
- asset: "card",
10616
- label: "OG card",
10617
- kind: "still",
10618
- ratio: "40:21",
10619
- px: {
10620
- w: 1200,
10621
- h: 630
10622
- },
10623
- genre: "card",
10624
- maxBytes: 1048576,
10625
- format: "png",
10626
- accepts: "png|jpg",
10627
- fit: "cover",
10628
- text: "expected",
10629
- safe: {
10630
- x: 0.05,
10631
- y: 0.0238,
10632
- w: 0.9,
10633
- h: 0.9524
10634
- },
10635
- template: "split-cover",
10636
- notes: "Text in the center ~1080\xD7600. Set twitter:card=summary_large_image explicitly \u2014 og:image alone gets the small card."
10637
- },
10638
- {
10639
- id: "github-social-preview",
10640
- channel: "github",
10641
- asset: "social-preview",
10642
- label: "GitHub social preview",
10643
- kind: "still",
10644
- ratio: "2:1",
10645
- px: {
10646
- w: 1280,
10647
- h: 640
10648
- },
10649
- genre: "card",
10650
- maxBytes: 1048576,
10651
- format: "png",
10652
- accepts: "png|jpg",
10653
- fit: "cover",
10654
- text: "expected",
10655
- safe: {
10656
- x: 0.0391,
10657
- y: 0.0781,
10658
- w: 0.9219,
10659
- h: 0.8438
10660
- },
10661
- template: "card-on-gradient",
10662
- notes: "Key text \u226550px from every edge."
10663
10352
  }
10664
10353
  ];
10665
10354
  function destinationById(id) {