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.
@@ -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 = 40;
11
- var TRAFFIC_LIGHT_Y = 14;
12
- var TRAFFIC_LIGHT_RADIUS = 6;
13
- var TRAFFIC_LIGHTS_START_X = 16;
14
- var TRAFFIC_LIGHT_GAP = 22;
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 bg = darkMode ? "#2d2d2d" : "#e8e8e8";
31
- const addressBg = darkMode ? "#1a1a1a" : "#ffffff";
32
- const addressBorder = darkMode ? "#444444" : "#d0d0d0";
33
- const textColor = darkMode ? "#999999" : "#666666";
34
- const tbarH = TITLE_BAR_HEIGHT * dpr;
35
- const tlY = TRAFFIC_LIGHT_Y * dpr;
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 aBarH = ADDRESS_BAR_HEIGHT * dpr;
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
- (light) => `<circle cx="${light.cx}" cy="${tlY}" r="${tlR}" fill="${light.fill}"/>`
48
- ).join("\n ");
49
- const addressBarWidth = width - aBarMargin * 2;
50
- const addressBarX = aBarMargin;
51
- const addressBarY = (tbarH - aBarH) / 2;
52
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${tbarH}">
53
- <rect width="${width}" height="${tbarH}" fill="${bg}"/>
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
- <rect x="${addressBarX}" y="${addressBarY}" width="${addressBarWidth}" height="${aBarH}"
56
- rx="${6 * dpr}" ry="${6 * dpr}" fill="${addressBg}" stroke="${addressBorder}" stroke-width="${dpr}"/>
57
- <text x="${width / 2}" y="${tlY + 4 * dpr}" text-anchor="middle"
58
- font-family="system-ui, -apple-system, sans-serif" font-size="${fontSize}" fill="${textColor}">
59
- localhost
60
- </text>
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 {