@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 +1 -1
- package/dist/index.d.ts +14 -43
- package/dist/index.js +40 -351
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
|
1104
|
-
*
|
|
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
|
|
1820
|
-
*
|
|
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
|
|
3861
|
-
* doors registry's "a door is what you bring". One row per channel
|
|
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'
|
|
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
|
|
3900
|
-
*
|
|
3901
|
-
*
|
|
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 = "
|
|
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
|
-
*
|
|
4351
|
-
* fleet thumbnail,
|
|
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 = "
|
|
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) {
|