clipwise 0.7.2 → 0.9.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.ko.md +128 -13
- package/README.md +105 -14
- package/dist/cli/index.js +2912 -1874
- package/dist/compose/frame-worker.js +88 -38
- package/dist/index.d.ts +6275 -374
- package/dist/index.js +750 -65
- package/package.json +3 -2
- package/skills/clipwise.md +83 -8
- package/templates/motion/feature-callout.html +81 -0
- package/templates/motion/intro-title.html +146 -0
- package/templates/motion/kinetic-type.html +205 -0
- package/templates/motion/vignette.html +288 -0
|
@@ -7,13 +7,11 @@ import sharp7 from "sharp";
|
|
|
7
7
|
|
|
8
8
|
// src/effects/frame.ts
|
|
9
9
|
import sharp from "sharp";
|
|
10
|
-
var TITLE_BAR_HEIGHT =
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var ADDRESS_BAR_HEIGHT = 24;
|
|
16
|
-
var ADDRESS_BAR_MARGIN = 70;
|
|
10
|
+
var TITLE_BAR_HEIGHT = 48;
|
|
11
|
+
var TRAFFIC_LIGHT_RADIUS = 6.5;
|
|
12
|
+
var TRAFFIC_LIGHTS_START_X = 22;
|
|
13
|
+
var TRAFFIC_LIGHT_GAP = 20;
|
|
14
|
+
var URL_PILL_HEIGHT = 30;
|
|
17
15
|
var IPHONE_BEZEL = { sides: 12, top: 50, bottom: 34 };
|
|
18
16
|
var IPHONE_OUTER_RADIUS = 47;
|
|
19
17
|
var IPHONE_INNER_RADIUS = 39;
|
|
@@ -26,38 +24,89 @@ var ANDROID_BEZEL = { sides: 8, top: 32, bottom: 20 };
|
|
|
26
24
|
var ANDROID_OUTER_RADIUS = 35;
|
|
27
25
|
var ANDROID_INNER_RADIUS = 30;
|
|
28
26
|
var ANDROID_CAMERA_RADIUS = 6;
|
|
29
|
-
function buildBrowserChromeSvg(width, darkMode, dpr = 1) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
function buildBrowserChromeSvg(width, darkMode, dpr = 1, url = "localhost") {
|
|
28
|
+
const c = darkMode ? {
|
|
29
|
+
bgTop: "#3c3c3e",
|
|
30
|
+
bgBottom: "#343436",
|
|
31
|
+
border: "#232325",
|
|
32
|
+
pillBg: "#28282a",
|
|
33
|
+
pillBorder: "#48484b",
|
|
34
|
+
text: "#d8d8da",
|
|
35
|
+
icon: "#a8a8ac",
|
|
36
|
+
iconDim: "#5f5f63"
|
|
37
|
+
} : {
|
|
38
|
+
bgTop: "#f8f7f6",
|
|
39
|
+
bgBottom: "#eeedeb",
|
|
40
|
+
border: "#d8d6d3",
|
|
41
|
+
pillBg: "#ffffff",
|
|
42
|
+
pillBorder: "#dedcd9",
|
|
43
|
+
text: "#3a3a3c",
|
|
44
|
+
icon: "#6f6f72",
|
|
45
|
+
iconDim: "#bdbdc0"
|
|
46
|
+
};
|
|
47
|
+
const h = TITLE_BAR_HEIGHT * dpr;
|
|
48
|
+
const midY = h / 2;
|
|
36
49
|
const tlR = TRAFFIC_LIGHT_RADIUS * dpr;
|
|
37
50
|
const tlStartX = TRAFFIC_LIGHTS_START_X * dpr;
|
|
38
51
|
const tlGap = TRAFFIC_LIGHT_GAP * dpr;
|
|
39
|
-
const
|
|
40
|
-
const aBarMargin = ADDRESS_BAR_MARGIN * dpr;
|
|
41
|
-
const fontSize = 12 * dpr;
|
|
52
|
+
const s = dpr;
|
|
42
53
|
const trafficLights = [
|
|
43
|
-
{ cx: tlStartX, fill: "#ff5f57" },
|
|
44
|
-
{ cx: tlStartX + tlGap, fill: "#febc2e" },
|
|
45
|
-
{ cx: tlStartX + tlGap * 2, fill: "#28c840" }
|
|
46
|
-
].map(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
{ cx: tlStartX, fill: "#ff5f57", stroke: "#e0443e" },
|
|
55
|
+
{ cx: tlStartX + tlGap, fill: "#febc2e", stroke: "#d89e24" },
|
|
56
|
+
{ cx: tlStartX + tlGap * 2, fill: "#28c840", stroke: "#1ea133" }
|
|
57
|
+
].map((l) => `<circle cx="${l.cx}" cy="${midY}" r="${tlR}" fill="${l.fill}" stroke="${l.stroke}" stroke-width="${0.5 * s}"/>`).join("\n ");
|
|
58
|
+
const navX = tlStartX + tlGap * 2 + 34 * s;
|
|
59
|
+
const back = `<path d="M ${navX + 4 * s} ${midY - 6 * s} l ${-6 * s} ${6 * s} l ${6 * s} ${6 * s}"
|
|
60
|
+
fill="none" stroke="${c.icon}" stroke-width="${1.8 * s}" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
61
|
+
const fwdX = navX + 28 * s;
|
|
62
|
+
const forward = `<path d="M ${fwdX - 4 * s} ${midY - 6 * s} l ${6 * s} ${6 * s} l ${-6 * s} ${6 * s}"
|
|
63
|
+
fill="none" stroke="${c.iconDim}" stroke-width="${1.8 * s}" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
64
|
+
const relX = fwdX + 28 * s;
|
|
65
|
+
const reload = `<path d="M ${relX + 6 * s} ${midY - 3.5 * s} a ${6 * s} ${6 * s} 0 1 0 ${1.2 * s} ${5.5 * s}"
|
|
66
|
+
fill="none" stroke="${c.icon}" stroke-width="${1.7 * s}" stroke-linecap="round"/>
|
|
67
|
+
<path d="M ${relX + 6.4 * s} ${midY - 7.5 * s} l ${0.4 * s} ${4.6 * s} l ${-4.6 * s} ${-0.4 * s} z" fill="${c.icon}"/>`;
|
|
68
|
+
const fontSize = 12.5 * dpr;
|
|
69
|
+
const pillH = URL_PILL_HEIGHT * dpr;
|
|
70
|
+
const pillW = Math.max(200 * s, Math.min(width * 0.42, 520 * s));
|
|
71
|
+
const pillX = (width - pillW) / 2;
|
|
72
|
+
const pillY = midY - pillH / 2;
|
|
73
|
+
const textW = url.length * fontSize * 0.56;
|
|
74
|
+
const lockX = width / 2 - textW / 2 - 16 * s;
|
|
75
|
+
const lockY = midY - 5 * s;
|
|
76
|
+
const padlock = `
|
|
77
|
+
<rect x="${lockX}" y="${lockY + 4 * s}" width="${9 * s}" height="${7 * s}" rx="${1.5 * s}" fill="${c.icon}"/>
|
|
78
|
+
<path d="M ${lockX + 2 * s} ${lockY + 4 * s} v ${-2 * s} a ${2.5 * s} ${2.5 * s} 0 0 1 ${5 * s} 0 v ${2 * s}"
|
|
79
|
+
fill="none" stroke="${c.icon}" stroke-width="${1.4 * s}"/>`;
|
|
80
|
+
const dotsX = width - 26 * s;
|
|
81
|
+
const dots = [-4.5, 0, 4.5].map((dy) => `<circle cx="${dotsX}" cy="${midY + dy * s}" r="${1.6 * s}" fill="${c.icon}"/>`).join("");
|
|
82
|
+
const avatar = `
|
|
83
|
+
<circle cx="${width - 56 * s}" cy="${midY}" r="${11 * s}" fill="url(#cwAvatar)"/>
|
|
84
|
+
<text x="${width - 56 * s}" y="${midY + 4 * s}" text-anchor="middle"
|
|
85
|
+
font-family="system-ui, -apple-system, sans-serif" font-size="${10.5 * dpr}" font-weight="600" fill="#ffffff">S</text>`;
|
|
86
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${h}">
|
|
87
|
+
<defs>
|
|
88
|
+
<linearGradient id="cwChromeBg" x1="0" y1="0" x2="0" y2="1">
|
|
89
|
+
<stop offset="0" stop-color="${c.bgTop}"/>
|
|
90
|
+
<stop offset="1" stop-color="${c.bgBottom}"/>
|
|
91
|
+
</linearGradient>
|
|
92
|
+
<linearGradient id="cwAvatar" x1="0" y1="0" x2="1" y2="1">
|
|
93
|
+
<stop offset="0" stop-color="#818cf8"/>
|
|
94
|
+
<stop offset="1" stop-color="#6366f1"/>
|
|
95
|
+
</linearGradient>
|
|
96
|
+
</defs>
|
|
97
|
+
<rect width="${width}" height="${h}" fill="url(#cwChromeBg)"/>
|
|
98
|
+
<rect y="${h - s}" width="${width}" height="${s}" fill="${c.border}"/>
|
|
54
99
|
${trafficLights}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
${back}
|
|
101
|
+
${forward}
|
|
102
|
+
${reload}
|
|
103
|
+
<rect x="${pillX}" y="${pillY}" width="${pillW}" height="${pillH}"
|
|
104
|
+
rx="${pillH / 2}" ry="${pillH / 2}" fill="${c.pillBg}" stroke="${c.pillBorder}" stroke-width="${s}"/>
|
|
105
|
+
${padlock}
|
|
106
|
+
<text x="${width / 2 + 7 * s}" y="${midY + fontSize * 0.35}" text-anchor="middle"
|
|
107
|
+
font-family="system-ui, -apple-system, sans-serif" font-size="${fontSize}" fill="${c.text}">${url}</text>
|
|
108
|
+
${dots}
|
|
109
|
+
${avatar}
|
|
61
110
|
</svg>`;
|
|
62
111
|
}
|
|
63
112
|
function buildIPhoneFrameSvg(totalWidth, totalHeight, screenWidth, screenHeight, darkMode, dpr = 1) {
|
|
@@ -203,9 +252,9 @@ async function applyMobileFrame(frameBuffer, deviceType, darkMode, frameWidth, f
|
|
|
203
252
|
{ input: maskedScreen, left: bezel.sides, top: bezel.top }
|
|
204
253
|
]).png().toBuffer();
|
|
205
254
|
}
|
|
206
|
-
async function buildBrowserChromeBuffer(viewportWidth, darkMode, dpr = 1) {
|
|
255
|
+
async function buildBrowserChromeBuffer(viewportWidth, darkMode, dpr = 1, url = "localhost") {
|
|
207
256
|
const tbarH = TITLE_BAR_HEIGHT * dpr;
|
|
208
|
-
const chromeSvg = buildBrowserChromeSvg(viewportWidth, darkMode, dpr);
|
|
257
|
+
const chromeSvg = buildBrowserChromeSvg(viewportWidth, darkMode, dpr, url);
|
|
209
258
|
return sharp(Buffer.from(chromeSvg)).resize(viewportWidth, tbarH).png().toBuffer();
|
|
210
259
|
}
|
|
211
260
|
async function applyDeviceFrame(frameBuffer, config, frameWidth, frameHeight, dpr = 1) {
|
|
@@ -214,7 +263,7 @@ async function applyDeviceFrame(frameBuffer, config, frameWidth, frameHeight, dp
|
|
|
214
263
|
case "browser": {
|
|
215
264
|
const tbarH = TITLE_BAR_HEIGHT * dpr;
|
|
216
265
|
const totalHeight = frameHeight + tbarH;
|
|
217
|
-
const chromeSvg = buildBrowserChromeSvg(frameWidth, config.darkMode, dpr);
|
|
266
|
+
const chromeSvg = buildBrowserChromeSvg(frameWidth, config.darkMode, dpr, config.url);
|
|
218
267
|
const chromeBuffer = Buffer.from(chromeSvg);
|
|
219
268
|
const canvas = await sharp({
|
|
220
269
|
create: {
|
|
@@ -643,7 +692,8 @@ async function buildStaticLayers(effects, output, viewportWidth, dpr) {
|
|
|
643
692
|
browserChromePng = await buildBrowserChromeBuffer(
|
|
644
693
|
viewportWidth,
|
|
645
694
|
effects.deviceFrame.darkMode,
|
|
646
|
-
dpr
|
|
695
|
+
dpr,
|
|
696
|
+
effects.deviceFrame.url
|
|
647
697
|
);
|
|
648
698
|
}
|
|
649
699
|
return {
|