autokap 1.0.6 → 1.0.8

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.
Files changed (347) hide show
  1. package/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
  2. package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
  3. package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
  4. package/assets/cursors/macos.svg +4 -0
  5. package/assets/cursors/windows.svg +15 -0
  6. package/assets/devices/ipad-pro-11-m4.json +52 -0
  7. package/assets/devices/iphone-16-pro.json +53 -0
  8. package/assets/devices/macbook-air-13.json +45 -0
  9. package/assets/frames/MacBook Air 13.svg +242 -0
  10. package/assets/frames/Status bar - iPhone.png +0 -0
  11. Menu bar- iPad.png +0 -0
  12. package/assets/frames/iPad Pro M4 11_.png +0 -0
  13. package/assets/frames/iPhone 16 Pro.png +0 -0
  14. package/assets/icons/Cellular Connection.svg +3 -0
  15. package/assets/icons/Union.svg +6 -0
  16. package/assets/icons/Wifi.svg +3 -0
  17. package/assets/icons/battery.svg +5 -0
  18. package/assets/icons/battery_charging.svg +8 -0
  19. package/assets/skill/OPCODE-REFERENCE.md +607 -0
  20. package/assets/skill/README.md +39 -0
  21. package/assets/skill/SKILL.md +453 -468
  22. package/assets/skill/STUDIO-SKILL.md +476 -0
  23. package/assets/skill/references/examples.md +104 -0
  24. package/assets/skill/references/interactive-demo.md +225 -0
  25. package/assets/skill/references/mock-data.md +178 -0
  26. package/dist/abort.d.ts +5 -0
  27. package/dist/abort.js +44 -0
  28. package/dist/action-verifier.d.ts +29 -0
  29. package/dist/action-verifier.js +133 -0
  30. package/dist/agent-action-recovery.d.ts +45 -0
  31. package/dist/agent-action-recovery.js +370 -0
  32. package/dist/agent-message-utils.d.ts +21 -0
  33. package/dist/agent-message-utils.js +77 -0
  34. package/dist/agent-url-utils.d.ts +30 -0
  35. package/dist/agent-url-utils.js +138 -0
  36. package/dist/agent.d.ts +226 -0
  37. package/dist/agent.js +6666 -0
  38. package/dist/ak-tree.d.ts +39 -0
  39. package/dist/ak-tree.js +368 -0
  40. package/dist/alt-text.d.ts +26 -0
  41. package/dist/alt-text.js +55 -0
  42. package/dist/auth-capture.d.ts +17 -0
  43. package/dist/auth-capture.js +164 -0
  44. package/dist/benchmark.d.ts +59 -0
  45. package/dist/benchmark.js +135 -0
  46. package/dist/billing-operation-logging.d.ts +38 -0
  47. package/dist/billing-operation-logging.js +248 -0
  48. package/dist/browser-bar.d.ts +48 -0
  49. package/dist/browser-bar.js +284 -0
  50. package/dist/browser-pool.d.ts +7 -0
  51. package/dist/browser-pool.js +15 -5
  52. package/dist/browser-utils.d.ts +31 -0
  53. package/dist/browser-utils.js +97 -0
  54. package/dist/browser.d.ts +76 -1
  55. package/dist/browser.js +1657 -39
  56. package/dist/capture-alt-text.d.ts +12 -0
  57. package/dist/capture-alt-text.js +52 -0
  58. package/dist/capture-encryption.d.ts +10 -0
  59. package/dist/capture-encryption.js +41 -0
  60. package/dist/capture-language-preflight.d.ts +41 -0
  61. package/dist/capture-language-preflight.js +300 -0
  62. package/dist/capture-llm-page-identity.d.ts +15 -0
  63. package/dist/capture-llm-page-identity.js +128 -0
  64. package/dist/capture-model-resolution.d.ts +9 -0
  65. package/dist/capture-model-resolution.js +21 -0
  66. package/dist/capture-page-identity.d.ts +7 -0
  67. package/dist/capture-page-identity.js +352 -0
  68. package/dist/capture-preset-credentials.d.ts +62 -0
  69. package/dist/capture-preset-credentials.js +184 -0
  70. package/dist/capture-request-plan.d.ts +58 -0
  71. package/dist/capture-request-plan.js +264 -0
  72. package/dist/capture-run-optimizer.d.ts +139 -0
  73. package/dist/capture-run-optimizer.js +863 -0
  74. package/dist/capture-selector-memory.d.ts +31 -0
  75. package/dist/capture-selector-memory.js +345 -0
  76. package/dist/capture-session-profile-encryption.d.ts +2 -0
  77. package/dist/capture-session-profile-encryption.js +22 -0
  78. package/dist/capture-step-timeout.d.ts +10 -0
  79. package/dist/capture-step-timeout.js +30 -0
  80. package/dist/capture-strategy.d.ts +36 -0
  81. package/dist/capture-strategy.js +95 -0
  82. package/dist/capture-studio-sync.d.ts +23 -0
  83. package/dist/capture-studio-sync.js +172 -0
  84. package/dist/capture-surface-contract.d.ts +36 -0
  85. package/dist/capture-surface-contract.js +299 -0
  86. package/dist/capture-transition-engine.d.ts +28 -0
  87. package/dist/capture-transition-engine.js +292 -0
  88. package/dist/capture-variant-state.d.ts +56 -0
  89. package/dist/capture-variant-state.js +182 -0
  90. package/dist/capture-verification.d.ts +35 -0
  91. package/dist/capture-verification.js +95 -0
  92. package/dist/capture-viewport-lock.d.ts +48 -0
  93. package/dist/capture-viewport-lock.js +74 -0
  94. package/dist/circuit-breaker.d.ts +42 -0
  95. package/dist/circuit-breaker.js +119 -0
  96. package/dist/cli-config.d.ts +8 -1
  97. package/dist/cli-config.js +62 -6
  98. package/dist/cli-contract.d.ts +15 -0
  99. package/dist/cli-contract.js +167 -0
  100. package/dist/cli-runner-local.d.ts +12 -0
  101. package/dist/cli-runner-local.js +102 -0
  102. package/dist/cli-runner.d.ts +34 -0
  103. package/dist/cli-runner.js +433 -0
  104. package/dist/cli-utils.d.ts +0 -1
  105. package/dist/cli-utils.js +2 -5
  106. package/dist/cli.js +1005 -252
  107. package/dist/clip-orchestrator.d.ts +148 -0
  108. package/dist/clip-orchestrator.js +957 -0
  109. package/dist/clip-postprocess.d.ts +42 -0
  110. package/dist/clip-postprocess.js +201 -0
  111. package/dist/cookie-dismiss.d.ts +2 -0
  112. package/dist/cookie-dismiss.js +48 -13
  113. package/dist/cost-logging.d.ts +35 -0
  114. package/dist/cost-logging.js +242 -0
  115. package/dist/cost-resolution-monitor.d.ts +16 -0
  116. package/dist/cost-resolution-monitor.js +34 -0
  117. package/dist/credential-templates.d.ts +5 -0
  118. package/dist/credential-templates.js +60 -0
  119. package/dist/cursor-overlay-script.d.ts +6 -0
  120. package/dist/cursor-overlay-script.js +169 -0
  121. package/dist/dom-css-purger.d.ts +65 -0
  122. package/dist/dom-css-purger.js +333 -0
  123. package/dist/dom-font-inliner.d.ts +45 -0
  124. package/dist/dom-font-inliner.js +148 -0
  125. package/dist/dom-patch-resolver.d.ts +52 -0
  126. package/dist/dom-patch-resolver.js +242 -0
  127. package/dist/dom-serializer.d.ts +82 -0
  128. package/dist/dom-serializer.js +378 -0
  129. package/dist/element-capture.d.ts +13 -0
  130. package/dist/element-capture.js +522 -0
  131. package/dist/env-validation.d.ts +5 -0
  132. package/dist/env-validation.js +29 -0
  133. package/dist/execution-schema.d.ts +4423 -0
  134. package/dist/execution-schema.js +507 -0
  135. package/dist/execution-types.d.ts +886 -0
  136. package/dist/execution-types.js +65 -0
  137. package/dist/fonts-loader.d.ts +14 -0
  138. package/dist/fonts-loader.js +55 -0
  139. package/dist/hybrid-navigator.d.ts +138 -0
  140. package/dist/hybrid-navigator.js +468 -0
  141. package/dist/index.d.ts +18 -0
  142. package/dist/index.js +17 -0
  143. package/dist/legacy/agent-action-recovery.d.ts +45 -0
  144. package/dist/legacy/agent-action-recovery.js +370 -0
  145. package/dist/legacy/agent-message-utils.d.ts +21 -0
  146. package/dist/legacy/agent-message-utils.js +77 -0
  147. package/dist/legacy/agent-url-utils.d.ts +30 -0
  148. package/dist/legacy/agent-url-utils.js +138 -0
  149. package/dist/legacy/agent.d.ts +226 -0
  150. package/dist/legacy/agent.js +6666 -0
  151. package/dist/legacy/clip-orchestrator.d.ts +148 -0
  152. package/dist/legacy/clip-orchestrator.js +957 -0
  153. package/dist/legacy/credential-templates.d.ts +5 -0
  154. package/dist/legacy/credential-templates.js +60 -0
  155. package/dist/legacy/hybrid-navigator.d.ts +138 -0
  156. package/dist/legacy/hybrid-navigator.js +468 -0
  157. package/dist/legacy/llm-usage.d.ts +17 -0
  158. package/dist/legacy/llm-usage.js +45 -0
  159. package/dist/legacy/prompt-cache.d.ts +10 -0
  160. package/dist/legacy/prompt-cache.js +24 -0
  161. package/dist/legacy/prompts.d.ts +175 -0
  162. package/dist/legacy/prompts.js +1038 -0
  163. package/dist/legacy/tools.d.ts +4 -0
  164. package/dist/legacy/tools.js +216 -0
  165. package/dist/legacy/video-agent.d.ts +143 -0
  166. package/dist/legacy/video-agent.js +4788 -0
  167. package/dist/legacy/video-observation.d.ts +36 -0
  168. package/dist/legacy/video-observation.js +192 -0
  169. package/dist/legacy/video-planner.d.ts +12 -0
  170. package/dist/legacy/video-planner.js +501 -0
  171. package/dist/legacy/video-prompts.d.ts +37 -0
  172. package/dist/legacy/video-prompts.js +569 -0
  173. package/dist/legacy/video-tools.d.ts +3 -0
  174. package/dist/legacy/video-tools.js +59 -0
  175. package/dist/legacy/video-variant-state.d.ts +29 -0
  176. package/dist/legacy/video-variant-state.js +80 -0
  177. package/dist/legacy/vision-model.d.ts +17 -0
  178. package/dist/legacy/vision-model.js +74 -0
  179. package/dist/llm-healer.d.ts +63 -0
  180. package/dist/llm-healer.js +166 -0
  181. package/dist/llm-provider.d.ts +29 -0
  182. package/dist/llm-provider.js +80 -0
  183. package/dist/llm-usage.d.ts +17 -0
  184. package/dist/llm-usage.js +45 -0
  185. package/dist/logger.d.ts +6 -2
  186. package/dist/logger.js +15 -1
  187. package/dist/mockup-html.d.ts +119 -0
  188. package/dist/mockup-html.js +263 -0
  189. package/dist/mockup.d.ts +187 -0
  190. package/dist/mockup.js +869 -0
  191. package/dist/mouse-animation.d.ts +46 -0
  192. package/dist/mouse-animation.js +114 -0
  193. package/dist/opcode-actions.d.ts +42 -0
  194. package/dist/opcode-actions.js +511 -0
  195. package/dist/opcode-runner.d.ts +51 -0
  196. package/dist/opcode-runner.js +770 -0
  197. package/dist/openrouter-client.d.ts +40 -0
  198. package/dist/openrouter-client.js +16 -0
  199. package/dist/overlay-engine.d.ts +24 -0
  200. package/dist/overlay-engine.js +176 -0
  201. package/dist/overlay-utils.d.ts +14 -0
  202. package/dist/overlay-utils.js +13 -0
  203. package/dist/postcondition.d.ts +16 -0
  204. package/dist/postcondition.js +269 -0
  205. package/dist/posthog.d.ts +4 -0
  206. package/dist/posthog.js +26 -0
  207. package/dist/program-patcher.d.ts +25 -0
  208. package/dist/program-patcher.js +44 -0
  209. package/dist/prompt-cache.d.ts +10 -0
  210. package/dist/prompt-cache.js +24 -0
  211. package/dist/prompts.d.ts +175 -0
  212. package/dist/prompts.js +1038 -0
  213. package/dist/provider-config.d.ts +12 -0
  214. package/dist/provider-config.js +15 -0
  215. package/dist/recovery-chain.d.ts +37 -0
  216. package/dist/recovery-chain.js +350 -0
  217. package/dist/remote-browser.d.ts +215 -0
  218. package/dist/remote-browser.js +360 -0
  219. package/dist/safari-browser-bar.d.ts +15 -0
  220. package/dist/safari-browser-bar.js +95 -0
  221. package/dist/safari-toolbar-asset.d.ts +15 -0
  222. package/dist/safari-toolbar-asset.js +12 -0
  223. package/dist/security.d.ts +21 -0
  224. package/dist/security.js +608 -0
  225. package/dist/selector-resolver.d.ts +34 -0
  226. package/dist/selector-resolver.js +181 -0
  227. package/dist/semantic-resolver.d.ts +35 -0
  228. package/dist/semantic-resolver.js +161 -0
  229. package/dist/server-capture-runtime.d.ts +125 -0
  230. package/dist/server-capture-runtime.js +585 -0
  231. package/dist/server-credit-usage.d.ts +12 -0
  232. package/dist/server-credit-usage.js +41 -0
  233. package/dist/server-posthog.d.ts +2 -0
  234. package/dist/server-posthog.js +16 -0
  235. package/dist/server-project-webhooks.d.ts +59 -0
  236. package/dist/server-project-webhooks.js +123 -0
  237. package/dist/server-screenshot-watermark.d.ts +7 -0
  238. package/dist/server-screenshot-watermark.js +60 -0
  239. package/dist/session-profile.d.ts +86 -0
  240. package/dist/session-profile.js +1536 -0
  241. package/dist/sf-pro-fonts.d.ts +4 -0
  242. package/dist/sf-pro-fonts.js +7 -0
  243. package/dist/sf-pro-symbols.d.ts +1 -0
  244. package/dist/sf-pro-symbols.js +55 -0
  245. package/dist/skill-packaging.d.ts +28 -0
  246. package/dist/skill-packaging.js +169 -0
  247. package/dist/smart-wait.d.ts +27 -0
  248. package/dist/smart-wait.js +81 -0
  249. package/dist/status-bar-l10n.d.ts +14 -0
  250. package/dist/status-bar-l10n.js +177 -0
  251. package/dist/status-bar-render.d.ts +20 -0
  252. package/dist/status-bar-render.js +410 -0
  253. package/dist/status-bar.d.ts +53 -0
  254. package/dist/status-bar.js +620 -0
  255. package/dist/svg-browser-bar.d.ts +33 -0
  256. package/dist/svg-browser-bar.js +206 -0
  257. package/dist/svg-status-bar.d.ts +36 -0
  258. package/dist/svg-status-bar.js +597 -0
  259. package/dist/svg-text.d.ts +61 -0
  260. package/dist/svg-text.js +118 -0
  261. package/dist/tools.d.ts +4 -0
  262. package/dist/tools.js +216 -0
  263. package/dist/types.d.ts +240 -5
  264. package/dist/types.js +23 -1
  265. package/dist/v2/action-verifier.d.ts +29 -0
  266. package/dist/v2/action-verifier.js +133 -0
  267. package/dist/v2/alt-text.d.ts +26 -0
  268. package/dist/v2/alt-text.js +55 -0
  269. package/dist/v2/benchmark.d.ts +59 -0
  270. package/dist/v2/benchmark.js +135 -0
  271. package/dist/v2/capture-strategy.d.ts +30 -0
  272. package/dist/v2/capture-strategy.js +67 -0
  273. package/dist/v2/capture-verification.d.ts +35 -0
  274. package/dist/v2/capture-verification.js +95 -0
  275. package/dist/v2/circuit-breaker.d.ts +42 -0
  276. package/dist/v2/circuit-breaker.js +119 -0
  277. package/dist/v2/cli-runner-local.d.ts +11 -0
  278. package/dist/v2/cli-runner-local.js +91 -0
  279. package/dist/v2/cli-runner.d.ts +34 -0
  280. package/dist/v2/cli-runner.js +300 -0
  281. package/dist/v2/compiler-prompts.d.ts +27 -0
  282. package/dist/v2/compiler-prompts.js +123 -0
  283. package/dist/v2/compiler.d.ts +37 -0
  284. package/dist/v2/compiler.js +147 -0
  285. package/dist/v2/explorer.d.ts +41 -0
  286. package/dist/v2/explorer.js +56 -0
  287. package/dist/v2/index.d.ts +37 -0
  288. package/dist/v2/index.js +31 -0
  289. package/dist/v2/llm-healer.d.ts +62 -0
  290. package/dist/v2/llm-healer.js +166 -0
  291. package/dist/v2/llm-provider.d.ts +29 -0
  292. package/dist/v2/llm-provider.js +80 -0
  293. package/dist/v2/opcode-runner.d.ts +47 -0
  294. package/dist/v2/opcode-runner.js +634 -0
  295. package/dist/v2/overlay-engine.d.ts +24 -0
  296. package/dist/v2/overlay-engine.js +150 -0
  297. package/dist/v2/postcondition.d.ts +16 -0
  298. package/dist/v2/postcondition.js +249 -0
  299. package/dist/v2/program-patcher.d.ts +25 -0
  300. package/dist/v2/program-patcher.js +44 -0
  301. package/dist/v2/recovery-chain.d.ts +30 -0
  302. package/dist/v2/recovery-chain.js +368 -0
  303. package/dist/v2/schema.d.ts +2580 -0
  304. package/dist/v2/schema.js +295 -0
  305. package/dist/v2/selector-resolver.d.ts +34 -0
  306. package/dist/v2/selector-resolver.js +181 -0
  307. package/dist/v2/semantic-resolver.d.ts +35 -0
  308. package/dist/v2/semantic-resolver.js +161 -0
  309. package/dist/v2/smart-wait.d.ts +27 -0
  310. package/dist/v2/smart-wait.js +81 -0
  311. package/dist/v2/types.d.ts +444 -0
  312. package/dist/v2/types.js +19 -0
  313. package/dist/v2/web-playwright-local.d.ts +69 -0
  314. package/dist/v2/web-playwright-local.js +392 -0
  315. package/dist/version.d.ts +1 -0
  316. package/dist/version.js +5 -0
  317. package/dist/video-agent.d.ts +143 -0
  318. package/dist/video-agent.js +4788 -0
  319. package/dist/video-observation.d.ts +36 -0
  320. package/dist/video-observation.js +192 -0
  321. package/dist/video-planner.d.ts +12 -0
  322. package/dist/video-planner.js +501 -0
  323. package/dist/video-prompts.d.ts +37 -0
  324. package/dist/video-prompts.js +554 -0
  325. package/dist/video-tools.d.ts +3 -0
  326. package/dist/video-tools.js +59 -0
  327. package/dist/video-variant-state.d.ts +29 -0
  328. package/dist/video-variant-state.js +80 -0
  329. package/dist/vision-model.d.ts +17 -0
  330. package/dist/vision-model.js +74 -0
  331. package/dist/web-playwright-local.d.ts +126 -0
  332. package/dist/web-playwright-local.js +819 -0
  333. package/dist/ws-auth.d.ts +20 -0
  334. package/dist/ws-auth.js +70 -0
  335. package/dist/ws-broadcast.d.ts +34 -0
  336. package/dist/ws-broadcast.js +85 -0
  337. package/dist/ws-connection-limits.d.ts +12 -0
  338. package/dist/ws-connection-limits.js +44 -0
  339. package/dist/ws-handler-utils.d.ts +32 -0
  340. package/dist/ws-handler-utils.js +139 -0
  341. package/dist/ws-handler.d.ts +10 -0
  342. package/dist/ws-handler.js +1793 -0
  343. package/dist/ws-metrics-server.d.ts +9 -0
  344. package/dist/ws-metrics-server.js +31 -0
  345. package/dist/ws-server.d.ts +9 -0
  346. package/dist/ws-server.js +92 -0
  347. package/package.json +142 -71
@@ -0,0 +1,620 @@
1
+ // ── Status Bar HTML Generator ─────────────────────────────────────────
2
+ // Pure function — works in both server (Playwright) and client (React) contexts.
3
+ // SF Pro fonts are embedded as base64 data URIs (see sf-pro-fonts.ts).
4
+ //
5
+ // Icon shapes and positioning measured pixel-by-pixel from Apple's native
6
+ // iOS status bar reference PNG (402×62 @ 1x for iPhone 16 Pro Dynamic Island).
7
+ const DEFAULT_CONFIG = {
8
+ time: '9:41',
9
+ date: 'Sat 1 Mar',
10
+ menuBarApp: 'Safari',
11
+ menuBarItems: ['File', 'Edit', 'View', 'History', 'Bookmarks', 'Window', 'Help'],
12
+ signalStrength: 4,
13
+ showNetworkType: false,
14
+ networkType: '5G',
15
+ wifiStrength: 3,
16
+ batteryLevel: 100,
17
+ batteryCharging: false,
18
+ showBatteryPercentage: true,
19
+ colorScheme: 'light',
20
+ carrierName: 'Carrier',
21
+ autoLocale: true,
22
+ };
23
+ // Measured from Apple's native status bar assets (pixel-level analysis)
24
+ // Reference PNG: 402×62 @ 1x for iPhone 16 Pro Dynamic Island
25
+ // Time "9:41": cap height 13px → SF Pro font-size 17px (semibold, -0.4 tracking)
26
+ // Signal: x:288-307, 4 bars, 4px wide, 2px gap, heights [5,8,11,13]
27
+ // WiFi: x:315-331, 17px wide × 13px tall, filled fan with 3 tiers + gaps
28
+ // Battery: x:339-366, body 25px × 13px, nub 3px, total 28px
29
+ const DEFAULT_LAYOUTS = {
30
+ 'iphone-dynamic-island': {
31
+ leftInset: 56,
32
+ rightInset: 36,
33
+ centerGap: { left: 128, right: 274 },
34
+ fontSize: { time: 17, networkType: 13 },
35
+ iconSize: { signal: 20, wifi: 18, battery: 28 },
36
+ iconGap: 7,
37
+ },
38
+ 'iphone-notch': {
39
+ leftInset: 56,
40
+ rightInset: 36,
41
+ centerGap: { left: 128, right: 274 },
42
+ fontSize: { time: 17, networkType: 13 },
43
+ iconSize: { signal: 20, wifi: 18, battery: 28 },
44
+ iconGap: 7,
45
+ },
46
+ 'iphone-home-button': {
47
+ leftInset: 6,
48
+ rightInset: 6,
49
+ fontSize: { time: 14, networkType: 14 },
50
+ iconSize: { signal: 16, wifi: 14, battery: 24 },
51
+ iconGap: 4,
52
+ },
53
+ 'ipad': {
54
+ leftInset: 24,
55
+ rightInset: 24,
56
+ fontSize: { time: 15, networkType: 11 },
57
+ iconSize: { signal: 17, wifi: 15, battery: 25 },
58
+ iconGap: 6,
59
+ },
60
+ 'mac-notch': {
61
+ leftInset: 12,
62
+ rightInset: 12,
63
+ centerGap: { left: 700, right: 936 },
64
+ fontSize: { time: 13, networkType: 11 },
65
+ iconSize: { signal: 0, wifi: 14, battery: 22 },
66
+ iconGap: 8,
67
+ },
68
+ };
69
+ // ── SF Pro Font Face ──────────────────────────────────────────────────
70
+ // Subset woff2 fonts embedded as base64 data URIs so they work on any OS
71
+ // (macOS, Linux VPS, Docker) without requiring system font installation.
72
+ // local() is tried first so the native SF Pro is used when available.
73
+ import { SF_PRO_TEXT_REGULAR, SF_PRO_TEXT_SEMIBOLD, SF_PRO_DISPLAY_REGULAR, SF_PRO_DISPLAY_SEMIBOLD, } from './sf-pro-fonts.js';
74
+ import { SF_PRO_SYMBOLS } from './sf-pro-symbols.js';
75
+ export const STATUS_BAR_FONT_CSS = `
76
+ @font-face {
77
+ font-family: 'SF Pro Text';
78
+ src: local('SF Pro Text'), local('SF Pro'), local('.SFNSText'), local('SFProText-Regular'),
79
+ url('${SF_PRO_TEXT_REGULAR}') format('woff2');
80
+ font-weight: 400;
81
+ font-style: normal;
82
+ }
83
+ @font-face {
84
+ font-family: 'SF Pro Text';
85
+ src: local('SF Pro Text Semibold'), local('SF Pro'), local('.SFNSText-Semibold'), local('SFProText-Semibold'),
86
+ url('${SF_PRO_TEXT_SEMIBOLD}') format('woff2');
87
+ font-weight: 600;
88
+ font-style: normal;
89
+ }
90
+ @font-face {
91
+ font-family: 'SF Pro Display';
92
+ src: local('SF Pro Display'), local('SF Pro'), local('.SFNSDisplay'), local('SFProDisplay-Regular'),
93
+ url('${SF_PRO_DISPLAY_REGULAR}') format('woff2');
94
+ font-weight: 400;
95
+ font-style: normal;
96
+ }
97
+ @font-face {
98
+ font-family: 'SF Pro Display';
99
+ src: local('SF Pro Display Semibold'), local('SF Pro'), local('.SFNSDisplay-Semibold'), local('SFProDisplay-Semibold'),
100
+ url('${SF_PRO_DISPLAY_SEMIBOLD}') format('woff2');
101
+ font-weight: 600;
102
+ font-style: normal;
103
+ }
104
+ @font-face {
105
+ font-family: 'SF Pro Symbols';
106
+ src: url('${SF_PRO_SYMBOLS}') format('woff2');
107
+ font-weight: 400;
108
+ font-style: normal;
109
+ }
110
+ `;
111
+ const STATUS_BAR_FONT_STYLE_HTML = `<style>${STATUS_BAR_FONT_CSS}</style>`;
112
+ // ── Main Generator ────────────────────────────────────────────────────
113
+ export function generateStatusBarHtml(options) {
114
+ const cfg = { ...DEFAULT_CONFIG, ...options.config };
115
+ const layout = options.layout ?? DEFAULT_LAYOUTS[options.deviceType];
116
+ const { width, height } = options;
117
+ const color = cfg.colorScheme === 'dark' ? '#FFFFFF' : '#000000';
118
+ const fontStyles = options.includeFontStyles === false ? '' : STATUS_BAR_FONT_STYLE_HTML;
119
+ const fontFamily = "'SF Pro Text','SF Pro Display','-apple-system',BlinkMacSystemFont,system-ui,sans-serif";
120
+ if (options.deviceType === 'iphone-home-button') {
121
+ return fontStyles + buildIPhoneHomeButtonStatusBar(cfg, layout, width, height, color, fontFamily);
122
+ }
123
+ if (options.deviceType === 'iphone-dynamic-island' || options.deviceType === 'iphone-notch') {
124
+ return fontStyles + buildIPhoneStatusBar(cfg, layout, width, height, color, fontFamily);
125
+ }
126
+ if (options.deviceType === 'mac-notch') {
127
+ return fontStyles + buildMacMenuBar(cfg, layout, width, height, color, fontFamily);
128
+ }
129
+ return fontStyles + buildIPadStatusBar(cfg, layout, width, height, color, fontFamily);
130
+ }
131
+ // ── iPhone Status Bar ──────────────────────────────────────────────────
132
+ function buildIPhoneStatusBar(cfg, layout, width, height, color, fontFamily) {
133
+ const timeSize = layout.fontSize.time;
134
+ const networkSize = layout.fontSize.networkType;
135
+ const gap = layout.iconGap;
136
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
137
+ const signalSvg = renderSignalBars(cfg.signalStrength, color, signalW);
138
+ const wifiSvg = renderWifi(cfg.wifiStrength, color, wifiW);
139
+ const batterySvg = renderBattery(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
140
+ const networkLabel = cfg.showNetworkType
141
+ ? `<span style="font-size:${networkSize}px;font-weight:600;letter-spacing:0.2px;color:${color};line-height:1;">${escapeHtml(cfg.networkType)}</span>`
142
+ : '';
143
+ return `<div style="position:relative;width:${width}px;height:${height}px;font-family:${fontFamily};-webkit-font-smoothing:antialiased;overflow:hidden;">
144
+ <div style="position:absolute;left:${layout.leftInset}px;top:0;height:100%;display:flex;align-items:center;">
145
+ <span style="font-size:${timeSize}px;font-weight:600;letter-spacing:-0.4px;color:${color};line-height:1;">${escapeHtml(cfg.time)}</span>
146
+ </div>
147
+ <div style="position:absolute;right:${layout.rightInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap}px;">
148
+ ${signalSvg}
149
+ ${networkLabel}
150
+ ${wifiSvg}
151
+ ${batterySvg}
152
+ </div>
153
+ </div>`;
154
+ }
155
+ // ── iPhone Home Button Status Bar (SE / 8 / classic layout) ───────────
156
+ // Authentic iOS ≤ 15 classic status bar:
157
+ // Left: signal bars · carrier name (clipped if long) · Wi-Fi fan
158
+ // Center: time (semibold, perfectly centered)
159
+ // Right: battery % · battery icon
160
+ function buildIPhoneHomeButtonStatusBar(cfg, layout, width, height, color, fontFamily) {
161
+ const timeSize = layout.fontSize.time;
162
+ const labelSize = layout.fontSize.networkType;
163
+ const gap = layout.iconGap;
164
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
165
+ const signalSvg = renderSignalBars(cfg.signalStrength, color, signalW);
166
+ const wifiSvg = renderWifi(cfg.wifiStrength, color, wifiW);
167
+ const batterySvg = renderBattery(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
168
+ // Carrier label — classic iOS shows "Carrier" or "Orange" etc., NOT "5G"
169
+ const carrierLabel = `<span style="font-size:${labelSize}px;font-weight:400;color:${color};line-height:1;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(cfg.carrierName)}</span>`;
170
+ // Network type badge (LTE / 4G / 5G) shown after carrier when enabled
171
+ const networkBadge = cfg.showNetworkType
172
+ ? `<span style="font-size:${labelSize - 1}px;font-weight:600;color:${color};line-height:1;">${escapeHtml(cfg.networkType)}</span>`
173
+ : '';
174
+ const batteryPct = cfg.showBatteryPercentage
175
+ ? `<span style="font-size:${labelSize}px;font-weight:400;color:${color};line-height:1;">${cfg.batteryLevel}%</span>`
176
+ : '';
177
+ return `<div style="position:relative;width:${width}px;height:${height}px;font-family:${fontFamily};-webkit-font-smoothing:antialiased;overflow:hidden;">
178
+ <div style="position:absolute;left:${layout.leftInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap}px;">
179
+ ${signalSvg}
180
+ ${carrierLabel}
181
+ ${networkBadge}
182
+ ${wifiSvg}
183
+ </div>
184
+ <div style="position:absolute;left:50%;top:0;height:100%;transform:translateX(-50%);display:flex;align-items:center;">
185
+ <span style="font-size:${timeSize}px;font-weight:600;letter-spacing:-0.4px;color:${color};line-height:1;">${escapeHtml(cfg.time)}</span>
186
+ </div>
187
+ <div style="position:absolute;right:${layout.rightInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap}px;">
188
+ ${batteryPct}
189
+ ${batterySvg}
190
+ </div>
191
+ </div>`;
192
+ }
193
+ // ── iPad Status Bar ────────────────────────────────────────────────────
194
+ function buildIPadStatusBar(cfg, layout, width, height, color, fontFamily) {
195
+ const timeSize = layout.fontSize.time;
196
+ const gap = layout.iconGap;
197
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
198
+ const signalSvg = renderSignalBars(cfg.signalStrength, color, signalW);
199
+ const wifiSvg = renderWifi(cfg.wifiStrength, color, wifiW);
200
+ const batterySvg = renderBattery(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
201
+ const batteryLabel = `<span style="font-size:${layout.fontSize.networkType}px;font-weight:400;color:${color};line-height:1;">${cfg.batteryLevel}%</span>`;
202
+ const dateLabel = cfg.date
203
+ ? `<span style="font-size:${timeSize}px;font-weight:400;letter-spacing:-0.4px;color:${color};line-height:1;margin-left:16px;">${escapeHtml(cfg.date)}</span>`
204
+ : '';
205
+ return `<div style="position:relative;width:${width}px;height:${height}px;font-family:${fontFamily};-webkit-font-smoothing:antialiased;overflow:hidden;">
206
+ <div style="position:absolute;left:${layout.leftInset}px;top:0;height:100%;display:flex;align-items:center;">
207
+ <span style="font-size:${timeSize}px;font-weight:400;letter-spacing:-0.4px;color:${color};line-height:1;">${escapeHtml(cfg.time)}</span>
208
+ ${dateLabel}
209
+ </div>
210
+ <div style="position:absolute;right:${layout.rightInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap}px;">
211
+ ${signalSvg}
212
+ ${wifiSvg}
213
+ ${batteryLabel}
214
+ ${batterySvg}
215
+ </div>
216
+ </div>`;
217
+ }
218
+ // ── macOS Menu Bar ────────────────────────────────────────────────────
219
+ function buildMacMenuBar(cfg, layout, width, height, color, fontFamily) {
220
+ const fontSize = layout.fontSize.time;
221
+ const gap = layout.iconGap;
222
+ const { wifi: wifiW, battery: batteryW } = layout.iconSize;
223
+ const wifiSvg = renderWifi(cfg.wifiStrength, color, wifiW);
224
+ const batterySvg = renderBattery(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
225
+ const batteryLabel = `<span style="font-size:${fontSize - 1}px;font-weight:400;color:${color};line-height:1;">${cfg.batteryLevel}%</span>`;
226
+ // Deterministic subset font for macOS menu bar glyphs — SF Pro Symbols embeds
227
+ // the exact glyphs needed (Apple logo, search, CC, avatar) as a woff2 data URI.
228
+ const sfIcon = (char, size = fontSize) => `<span style="font-family:'SF Pro Symbols','SF Pro Text','SF Pro Display',system-ui;font-size:${size}px;color:${color};line-height:1;">${char}</span>`;
229
+ // Apple logo — U+F8FF renders as in SF Pro / Apple system fonts
230
+ const appleLogo = sfIcon('\uF8FF', fontSize + 3);
231
+ // SF Symbol characters (macOS Sequoia) — literal glyphs from SF Pro font
232
+ const searchIcon = sfIcon('􀊫'); // magnifyingglass
233
+ const ccIcon = sfIcon('􀜊'); // control center (switch.2)
234
+ const avatarIcon = sfIcon('􀉭'); // person.crop.circle
235
+ // App name (bold) + menu items (regular weight)
236
+ const appName = `<span style="font-size:${fontSize}px;font-weight:700;color:${color};line-height:1;">${escapeHtml(cfg.menuBarApp)}</span>`;
237
+ const menuItems = cfg.menuBarItems.map(item => `<span style="font-size:${fontSize}px;font-weight:400;color:${color};line-height:1;">${item}</span>`).join('');
238
+ // Date + time for right side
239
+ const dateTime = cfg.date
240
+ ? `<span style="font-size:${fontSize}px;font-weight:500;color:${color};line-height:1;">${escapeHtml(cfg.date)}</span>`
241
+ : '';
242
+ const timeLabel = `<span style="font-size:${fontSize}px;font-weight:500;color:${color};line-height:1;">${escapeHtml(cfg.time)}</span>`;
243
+ // macOS right-side order: WiFi → Battery% BatteryIcon → Search → CC → Avatar → Date Time
244
+ // macOS menu bar uses SF Pro Display (not Text) at 13px — matches native rendering
245
+ const macFontFamily = "'SF Pro Display','SF Pro Text','-apple-system',BlinkMacSystemFont,system-ui,sans-serif";
246
+ return `<div style="position:relative;width:${width}px;height:${height}px;font-family:${macFontFamily};-webkit-font-smoothing:antialiased;overflow:hidden;">
247
+ <div style="position:absolute;left:${layout.leftInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap + 6}px;">
248
+ ${appleLogo}
249
+ ${appName}
250
+ <div style="display:flex;align-items:center;gap:${gap + 10}px;">
251
+ ${menuItems}
252
+ </div>
253
+ </div>
254
+ <div style="position:absolute;right:${layout.rightInset}px;top:0;height:100%;display:flex;align-items:center;gap:${gap + 2}px;">
255
+ ${wifiSvg}
256
+ <div style="display:flex;align-items:center;gap:3px;">
257
+ ${cfg.showBatteryPercentage ? batteryLabel : ''}
258
+ ${batterySvg}
259
+ </div>
260
+ ${searchIcon}
261
+ ${ccIcon}
262
+ ${avatarIcon}
263
+ ${dateTime}
264
+ ${timeLabel}
265
+ </div>
266
+ </div>`;
267
+ }
268
+ // ── SVG Icon Renderers ─────────────────────────────────────────────────
269
+ // Paths extracted from native Apple iOS status bar SVG assets.
270
+ // Signal: 20×13 viewBox, 4 rounded bars
271
+ // WiFi: 18×13 viewBox, 3 tiers (outer arc, inner arc, dot)
272
+ // Battery: 28×13 viewBox, outline + nub + fill
273
+ // Native signal bar paths (from Cellular Connection.svg)
274
+ // Each bar is an individual path, ordered left→right (shortest→tallest)
275
+ const SIGNAL_BAR_PATHS = [
276
+ 'M2.13333 7.53962H1.06667C0.477563 7.53962 0 8.06421 0 8.71132V11.0547C0 11.7018 0.477563 12.2264 1.06667 12.2264H2.13333C2.72244 12.2264 3.2 11.7018 3.2 11.0547V8.71132C3.2 8.06421 2.72244 7.53962 2.13333 7.53962Z',
277
+ 'M7.43411 5.09433H6.36745C5.77834 5.09433 5.30078 5.62652 5.30078 6.28301V11.0377C5.30078 11.6942 5.77834 12.2264 6.36745 12.2264H7.43411C8.02322 12.2264 8.50078 11.6942 8.50078 11.0377V6.28301C8.50078 5.62652 8.02322 5.09433 7.43411 5.09433Z',
278
+ 'M11.7659 2.44528H12.8326C13.4217 2.44528 13.8992 2.97078 13.8992 3.61902V11.0527C13.8992 11.7009 13.4217 12.2264 12.8326 12.2264H11.7659C11.1768 12.2264 10.6992 11.7009 10.6992 11.0527V3.61902C10.6992 2.97078 11.1768 2.44528 11.7659 2.44528Z',
279
+ 'M19.2 1.14623C19.2 0.513183 18.7224 0 18.1333 0H17.0667C16.4776 0 16 0.513183 16 1.14623V11.0802C16 11.7132 16.4776 12.2264 17.0667 12.2264H18.1333C18.7224 12.2264 19.2 11.7132 19.2 11.0802V1.14623Z',
280
+ ];
281
+ // Native WiFi paths (from Wifi.svg) — 3 tiers, outer→inner→dot
282
+ const WIFI_TIER_PATHS = [
283
+ // Tier 3 (outer arc)
284
+ 'M8.5713 2.46628C11.0584 2.46639 13.4504 3.38847 15.2529 5.04195C15.3887 5.1696 15.6056 5.16799 15.7393 5.03834L17.0368 3.77487C17.1045 3.70911 17.1422 3.62004 17.1417 3.52735C17.1411 3.43467 17.1023 3.34603 17.0338 3.28104C12.3028 -1.09368 4.83907 -1.09368 0.108056 3.28104C0.039524 3.34598 0.000639766 3.4346 7.82398e-06 3.52728C-0.000624118 3.61996 0.0370483 3.70906 0.104689 3.77487L1.40255 5.03834C1.53615 5.16819 1.75327 5.1698 1.88893 5.04195C3.69167 3.38836 6.08395 2.46628 8.5713 2.46628Z',
285
+ // Tier 2 (inner arc)
286
+ 'M8.56795 6.68656C9.92527 6.68647 11.2341 7.19821 12.2403 8.12234C12.3763 8.2535 12.5907 8.25065 12.7234 8.11593L14.0106 6.79663C14.0784 6.72742 14.1161 6.63355 14.1151 6.536C14.1141 6.43844 14.0746 6.34536 14.0054 6.27757C10.9416 3.38672 6.19688 3.38672 3.13305 6.27757C3.06384 6.34536 3.02435 6.43849 3.02345 6.53607C3.02254 6.63366 3.06028 6.72752 3.12822 6.79663L4.41513 8.11593C4.54778 8.25065 4.76215 8.2535 4.89823 8.12234C5.90368 7.19882 7.21152 6.68713 8.56795 6.68656Z',
287
+ // Tier 1 (dot)
288
+ 'M11.0924 9.48011C11.0943 9.58546 11.0572 9.68703 10.9899 9.76084L8.81327 12.2156C8.74946 12.2877 8.66247 12.3283 8.5717 12.3283C8.48093 12.3283 8.39394 12.2877 8.33013 12.2156L6.1531 9.76084C6.08585 9.68697 6.04886 9.58537 6.05085 9.48002C6.05284 9.37467 6.09365 9.27491 6.16364 9.20429C7.55374 7.8904 9.58966 7.8904 10.9798 9.20429C11.0497 9.27497 11.0904 9.37476 11.0924 9.48011Z',
289
+ ];
290
+ function renderSignalBars(strength, color, size) {
291
+ // Native viewBox: 20×13 (from Cellular Connection.svg)
292
+ const viewW = 20; // native: 19.2 rounded to 20
293
+ const viewH = 13;
294
+ const paths = SIGNAL_BAR_PATHS.map((d, i) => {
295
+ const opacity = i < strength ? 1 : 0.25;
296
+ return `<path d="${d}" fill="${color}" opacity="${opacity}"/>`;
297
+ });
298
+ const h = size * (viewH / viewW);
299
+ return `<svg width="${size}" height="${h}" viewBox="0 0 ${viewW} ${viewH}" fill="none" style="display:block;">${paths.join('')}</svg>`;
300
+ }
301
+ function renderWifi(strength, color, size) {
302
+ // Native viewBox: 18×13 (from Wifi.svg)
303
+ const viewW = 18;
304
+ const viewH = 13;
305
+ // Tiers are ordered outer→inner→dot, mapped to strength 3→2→1
306
+ const tiers = WIFI_TIER_PATHS.map((d, i) => {
307
+ const tierLevel = 3 - i; // 3, 2, 1
308
+ const opacity = strength >= tierLevel ? 1 : 0.25;
309
+ return `<path d="${d}" fill="${color}" opacity="${opacity}"/>`;
310
+ });
311
+ const h = size * (viewH / viewW);
312
+ return `<svg width="${size}" height="${h}" viewBox="0 0 ${viewW} ${viewH}" fill="none" style="display:block;">${tiers.join('')}</svg>`;
313
+ }
314
+ export function renderBattery(level, charging, color, size, isDark = false) {
315
+ if (charging) {
316
+ // Charging battery icon (from assets/icons/battery_charging.svg)
317
+ // ViewBox: 35×16 — paths: 1,2=border, 3=nub, 4,5=inner fill, 6=bolt
318
+ const chargingGreen = isDark ? '#0DF07D' : '#32CB58';
319
+ const chargingBorder = '#ADADB1';
320
+ const boltColor = color; // black on light, white on dark
321
+ const w = size;
322
+ const h = size * (16 / 35);
323
+ return `<svg width="${w}" height="${h}" viewBox="0 0 35 16" fill="none" style="display:block;">
324
+ <path d="M17.332 13.9694L15.8203 15.8561H24.668C25.668 15.8561 26.582 15.778 27.4102 15.6217C28.2383 15.4576 28.9531 15.0748 29.5547 14.4733C30.1484 13.8795 30.5234 13.1725 30.6797 12.3522C30.8438 11.524 30.9258 10.61 30.9258 9.60998V6.2467C30.9258 5.2467 30.8438 4.33655 30.6797 3.51624C30.5234 2.68811 30.1484 1.97717 29.5547 1.38342C28.9531 0.78186 28.2383 0.402954 27.4102 0.246704C26.582 0.0826416 25.668 0.000610352 24.668 0.000610352H19.6992C19.7695 0.18811 19.8086 0.37561 19.8164 0.56311C19.8242 0.742798 19.8047 0.93811 19.7578 1.14905C19.7188 1.35999 19.6523 1.60608 19.5586 1.88733H24.9961C25.5977 1.88733 26.1875 1.94592 26.7656 2.06311C27.3516 2.17249 27.8203 2.40295 28.1719 2.75452C28.5312 3.11389 28.7656 3.58264 28.875 4.16077C28.9844 4.73108 29.0391 5.31702 29.0391 5.91858V9.92639C29.0391 10.5358 28.9844 11.1295 28.875 11.7076C28.7656 12.278 28.5312 12.7428 28.1719 13.1022C27.8203 13.4537 27.3516 13.6881 26.7656 13.8053C26.1875 13.9147 25.5977 13.9694 24.9961 13.9694H17.332Z" fill="${chargingBorder}"/>
325
+ <path d="M13.5937 1.88672L15.1055 9.53674e-07L6.25781 1.72716e-06C5.25781 1.81458e-06 4.34375 0.0781269 3.51562 0.234377C2.6875 0.39844 1.97265 0.781252 1.37109 1.38281C0.777343 1.97656 0.402343 2.6836 0.246093 3.50391C0.0820302 4.33203 -9.27501e-07 5.2461 -8.40078e-07 6.2461L-5.46051e-07 9.60938C-4.58628e-07 10.6094 0.0820309 11.5195 0.246093 12.3398C0.402344 13.168 0.777344 13.8789 1.37109 14.4727C1.97266 15.0742 2.6875 15.4531 3.51562 15.6094C4.34375 15.7734 5.25781 15.8555 6.25781 15.8555L11.2266 15.8555C11.1562 15.668 11.1172 15.4805 11.1094 15.293C11.1016 15.1133 11.1211 14.918 11.168 14.707C11.207 14.4961 11.2734 14.25 11.3672 13.9688L5.92969 13.9688C5.32812 13.9688 4.73828 13.9102 4.16016 13.793C3.57422 13.6836 3.10547 13.4531 2.75391 13.1016C2.39453 12.7422 2.16016 12.2734 2.05078 11.6953C1.94141 11.125 1.88672 10.5391 1.88672 9.9375L1.88672 5.92969C1.88672 5.32031 1.94141 4.72656 2.05078 4.14844C2.16016 3.57813 2.39453 3.11328 2.75391 2.75391C3.10547 2.40235 3.57422 2.16797 4.16016 2.05078C4.73828 1.94141 5.32812 1.88672 5.92969 1.88672L13.5937 1.88672Z" fill="${chargingBorder}"/>
326
+ <path d="M32.5195 4.89905V10.9576C32.832 10.9342 33.1562 10.7975 33.4922 10.5475C33.8281 10.2897 34.1094 9.93811 34.3359 9.4928C34.5703 9.04749 34.6875 8.52405 34.6875 7.92249C34.6875 7.32874 34.5703 6.8092 34.3359 6.36389C34.1094 5.91858 33.8281 5.56702 33.4922 5.3092C33.1562 5.05139 32.832 4.91467 32.5195 4.89905Z" fill="${chargingBorder}"/>
327
+ <path d="M4.32422 12.5162C4.58203 12.5787 4.91016 12.61 5.30859 12.61H12.1758L12.7969 10.9225H11.1328C10.5469 10.9225 10.043 10.7194 9.62109 10.3131C9.20703 9.90686 9 9.40686 9 8.81311C9 8.29749 9.17578 7.81702 9.52734 7.3717L12.8086 3.2467H5.34375C4.92969 3.2467 4.59375 3.28186 4.33594 3.35217C4.07812 3.41467 3.86719 3.52795 3.70312 3.69202C3.54688 3.85608 3.43359 4.06702 3.36328 4.32483C3.29297 4.57483 3.25781 4.90686 3.25781 5.32092V10.5592C3.25781 10.9655 3.29297 11.2936 3.36328 11.5436C3.43359 11.7936 3.54688 12.0006 3.70312 12.1647C3.86719 12.3287 4.07422 12.4459 4.32422 12.5162Z" fill="${chargingGreen}"/>
328
+ <path d="M21.6914 8.48499L18.4102 12.61H25.6172C26.0234 12.61 26.3516 12.5787 26.6016 12.5162C26.8516 12.4459 27.0586 12.3287 27.2227 12.1647C27.5273 11.86 27.6797 11.3248 27.6797 10.5592V5.29749C27.6797 4.89124 27.6445 4.56311 27.5742 4.31311C27.5039 4.06311 27.3867 3.85608 27.2227 3.69202C27.0586 3.52795 26.8516 3.41467 26.6016 3.35217C26.3516 3.28186 26.0234 3.2467 25.6172 3.2467H19.043L18.4219 4.9342H20.0742C20.668 4.9342 21.1719 5.13733 21.5859 5.54358C22.0078 5.94983 22.2188 6.44983 22.2188 7.04358C22.2188 7.5592 22.043 8.03967 21.6914 8.48499Z" fill="${chargingGreen}"/>
329
+ <path d="M10.7812 8.34432C10.6484 8.52401 10.582 8.68026 10.582 8.81307C10.582 8.96151 10.6328 9.0826 10.7344 9.17635C10.8438 9.2701 10.9727 9.31698 11.1211 9.31698H15.0938L12.9727 15.0123C12.8867 15.2623 12.8945 15.4654 12.9961 15.6217C13.1055 15.7779 13.2578 15.8599 13.4531 15.8678C13.6484 15.8678 13.8242 15.7701 13.9805 15.5748L20.4023 7.50057C20.5352 7.3287 20.6016 7.17635 20.6016 7.04354C20.6016 6.8951 20.5469 6.77401 20.4375 6.68026C20.3359 6.58651 20.2109 6.53963 20.0625 6.53963H16.0898L18.2109 0.844323C18.3047 0.594323 18.2969 0.391198 18.1875 0.234948C18.0781 0.0786978 17.9258 0.000572791 17.7305 0.000572791C17.543 -0.00723971 17.3672 0.0865103 17.2031 0.281823L10.7812 8.34432Z" fill="${boltColor}"/>
330
+ </svg>`;
331
+ }
332
+ // Non-charging battery icon (from assets/icons/battery.svg)
333
+ // ViewBox: 35×16 — paths: 1=inner fill, 2=border (evenodd), 3=nub
334
+ const batteryBorder = '#ADADB1';
335
+ const fillColor = color;
336
+ const w = size;
337
+ const h = size * (16 / 35);
338
+ return `<svg width="${w}" height="${h}" viewBox="0 0 35 16" fill="none" style="display:block;">
339
+ <path d="M12.8057 3.25098H19.042L19.043 3.24707H25.6172C26.0234 3.24707 26.3516 3.28223 26.6016 3.35254C26.8515 3.41504 27.0586 3.52834 27.2227 3.69238C27.3866 3.85642 27.5039 4.06357 27.5742 4.31348C27.6445 4.56343 27.6797 4.89179 27.6797 5.29785V10.5596C27.6796 11.325 27.5273 11.8604 27.2227 12.165C27.0586 12.329 26.8515 12.4463 26.6016 12.5166C26.3516 12.5791 26.0234 12.6104 25.6172 12.6104H5.30859C4.9102 12.6104 4.58202 12.5791 4.32422 12.5166C4.07431 12.4463 3.86716 12.329 3.70312 12.165C3.54695 12.0011 3.4336 11.7938 3.36328 11.5439C3.29299 11.294 3.25783 10.9657 3.25781 10.5596V5.32129C3.25781 4.90741 3.29303 4.57516 3.36328 4.3252C3.43357 4.06748 3.54697 3.85642 3.70312 3.69238C3.86717 3.52834 4.07816 3.41504 4.33594 3.35254C4.59375 3.28223 4.92969 3.24707 5.34375 3.24707H12.8086L12.8057 3.25098Z" fill="${fillColor}"/>
340
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M24.668 0.000976562C25.668 0.000976562 26.582 0.0830078 27.4102 0.24707C28.2383 0.403328 28.9531 0.782242 29.5547 1.38379C30.1483 1.9775 30.5235 2.68856 30.6797 3.5166C30.8437 4.33682 30.9258 5.24721 30.9258 6.24707V9.61035C30.9258 10.6102 30.8437 11.5245 30.6797 12.3525C30.5234 13.1727 30.1484 13.88 29.5547 14.4736C28.9532 15.0751 28.2382 15.458 27.4102 15.6221C26.582 15.7783 25.6679 15.8564 24.668 15.8564L10.5156 15.8574V15.8555H6.25781C5.25781 15.8555 4.34375 15.7734 3.51562 15.6094C2.6875 15.4531 1.97266 15.0742 1.37109 14.4727C0.777344 13.8789 0.402344 13.168 0.246094 12.3398C0.0820312 11.5195 9.34357e-08 10.6094 0 9.60938V6.24609C-6.73586e-08 5.24609 0.0820313 4.33203 0.246094 3.50391C0.402344 2.68359 0.777344 1.97656 1.37109 1.38281C1.97266 0.78125 2.6875 0.398438 3.51562 0.234375C4.34375 0.078125 5.25781 1.64241e-07 6.25781 0L24.668 0.000976562ZM5.92969 1.88672C5.32813 1.88672 4.73828 1.94141 4.16016 2.05078C3.57422 2.16797 3.10547 2.40234 2.75391 2.75391C2.39453 3.11328 2.16016 3.57813 2.05078 4.14844C1.94141 4.72656 1.88672 5.32031 1.88672 5.92969V9.9375C1.88672 10.5391 1.94141 11.125 2.05078 11.6953C2.16016 12.2734 2.39453 12.7422 2.75391 13.1016C3.10547 13.4531 3.57422 13.6836 4.16016 13.793C4.73828 13.9102 5.32813 13.9687 5.92969 13.9688L24.9961 13.9697C25.5976 13.9697 26.1875 13.915 26.7656 13.8057C27.3515 13.6885 27.8203 13.454 28.1719 13.1025C28.5312 12.7432 28.7656 12.2782 28.875 11.708C28.9844 11.13 29.039 10.536 29.0391 9.92676V5.91895C29.0391 5.3175 28.9843 4.73134 28.875 4.16113C28.7656 3.5831 28.5311 3.11423 28.1719 2.75488C27.8203 2.40333 27.3515 2.17286 26.7656 2.06348C26.1875 1.94629 25.5977 1.8877 24.9961 1.8877L13 1.88867V1.88672H5.92969Z" fill="${batteryBorder}"/>
341
+ <path d="M32.5195 4.89941C32.832 4.91504 33.1563 5.05176 33.4922 5.30957C33.828 5.56737 34.1094 5.91902 34.3359 6.36426C34.5702 6.80951 34.6875 7.32923 34.6875 7.92285C34.6875 8.52426 34.5703 9.04794 34.3359 9.49316C34.1094 9.93834 33.8281 10.2901 33.4922 10.5479C33.1563 10.7978 32.832 10.9346 32.5195 10.958V4.89941Z" fill="${batteryBorder}"/>
342
+ </svg>`;
343
+ }
344
+ // ── Utilities ──────────────────────────────────────────────────────────
345
+ function escapeHtml(str) {
346
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
347
+ }
348
+ // ── Pure SVG Generator (for sharp compositing — no Playwright) ────────
349
+ // Produces a self-contained SVG string with embedded fonts that can be
350
+ // rasterized by @resvg/resvg-js. The client-side HTML generator above
351
+ // remains unchanged.
352
+ /** Font CSS for embedding inside SVG <defs><style>. Uses only url() — no local() — because resvg doesn't support local font lookup. */
353
+ const SVG_FONT_CSS = `
354
+ @font-face { font-family: 'SF Pro Text'; src: url('${SF_PRO_TEXT_REGULAR}') format('woff2'); font-weight: 400; font-style: normal; }
355
+ @font-face { font-family: 'SF Pro Text'; src: url('${SF_PRO_TEXT_SEMIBOLD}') format('woff2'); font-weight: 600; font-style: normal; }
356
+ @font-face { font-family: 'SF Pro Display'; src: url('${SF_PRO_DISPLAY_REGULAR}') format('woff2'); font-weight: 400; font-style: normal; }
357
+ @font-face { font-family: 'SF Pro Display'; src: url('${SF_PRO_DISPLAY_SEMIBOLD}') format('woff2'); font-weight: 600; font-style: normal; }
358
+ @font-face { font-family: 'SF Pro Symbols'; src: url('${SF_PRO_SYMBOLS}') format('woff2'); font-weight: 400; font-style: normal; }
359
+ `;
360
+ const SVG_FONT_DEFS = `<defs><style>${SVG_FONT_CSS}</style></defs>`;
361
+ /**
362
+ * Generate a self-contained SVG string for the status bar.
363
+ * Used by the sharp compositing pipeline (no Playwright).
364
+ * The returned SVG includes embedded SF Pro fonts via @font-face data URIs.
365
+ */
366
+ export function generateStatusBarSvg(options) {
367
+ const cfg = { ...DEFAULT_CONFIG, ...options.config };
368
+ const layout = options.layout ?? DEFAULT_LAYOUTS[options.deviceType];
369
+ const { width, height } = options;
370
+ const color = cfg.colorScheme === 'dark' ? '#FFFFFF' : '#000000';
371
+ const FF = "'SF Pro Text','SF Pro Display',system-ui,sans-serif";
372
+ let inner;
373
+ if (options.deviceType === 'iphone-home-button') {
374
+ inner = buildIPhoneHomeButtonSvg(cfg, layout, width, height, color, FF);
375
+ }
376
+ else if (options.deviceType === 'iphone-dynamic-island' || options.deviceType === 'iphone-notch') {
377
+ inner = buildIPhoneSvg(cfg, layout, width, height, color, FF);
378
+ }
379
+ else if (options.deviceType === 'mac-notch') {
380
+ inner = buildMacMenuBarSvg(cfg, layout, width, height, color, FF);
381
+ }
382
+ else {
383
+ inner = buildIPadSvg(cfg, layout, width, height, color, FF);
384
+ }
385
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${SVG_FONT_DEFS}${inner}</svg>`;
386
+ }
387
+ // ── SVG icon fragment helpers ──────────────────────────────────────────
388
+ // Return just <g> groups (no wrapping <svg>) for embedding in parent SVG.
389
+ function signalBarsSvgFragment(strength, color, size) {
390
+ const viewW = 20;
391
+ const viewH = 13;
392
+ const w = size;
393
+ const h = size * (viewH / viewW);
394
+ const scale = w / viewW;
395
+ const paths = SIGNAL_BAR_PATHS.map((d, i) => {
396
+ const opacity = i < strength ? 1 : 0.25;
397
+ return `<path d="${d}" fill="${color}" opacity="${opacity}"/>`;
398
+ }).join('');
399
+ return { svg: `<g transform="scale(${scale})">${paths}</g>`, width: w, height: h };
400
+ }
401
+ function wifiSvgFragment(strength, color, size) {
402
+ const viewW = 18;
403
+ const viewH = 13;
404
+ const w = size;
405
+ const h = size * (viewH / viewW);
406
+ const scale = w / viewW;
407
+ const tiers = WIFI_TIER_PATHS.map((d, i) => {
408
+ const tierLevel = 3 - i;
409
+ const opacity = strength >= tierLevel ? 1 : 0.25;
410
+ return `<path d="${d}" fill="${color}" opacity="${opacity}"/>`;
411
+ }).join('');
412
+ return { svg: `<g transform="scale(${scale})">${tiers}</g>`, width: w, height: h };
413
+ }
414
+ function batterySvgFragment(level, charging, color, size, isDark) {
415
+ // Reuse the existing renderBattery which returns a complete <svg>.
416
+ // Extract just the inner content and wrap in a <g> with correct scaling.
417
+ const viewW = 35;
418
+ const viewH = 16;
419
+ const w = size;
420
+ const h = size * (viewH / viewW);
421
+ const scale = w / viewW;
422
+ // Build battery paths inline (same logic as renderBattery)
423
+ let paths;
424
+ if (charging) {
425
+ const chargingGreen = isDark ? '#0DF07D' : '#32CB58';
426
+ const chargingBorder = '#ADADB1';
427
+ const boltColor = color;
428
+ paths = `<path d="M17.332 13.9694L15.8203 15.8561H24.668C25.668 15.8561 26.582 15.778 27.4102 15.6217C28.2383 15.4576 28.9531 15.0748 29.5547 14.4733C30.1484 13.8795 30.5234 13.1725 30.6797 12.3522C30.8438 11.524 30.9258 10.61 30.9258 9.60998V6.2467C30.9258 5.2467 30.8438 4.33655 30.6797 3.51624C30.5234 2.68811 30.1484 1.97717 29.5547 1.38342C28.9531 0.78186 28.2383 0.402954 27.4102 0.246704C26.582 0.0826416 25.668 0.000610352 24.668 0.000610352H19.6992C19.7695 0.18811 19.8086 0.37561 19.8164 0.56311C19.8242 0.742798 19.8047 0.93811 19.7578 1.14905C19.7188 1.35999 19.6523 1.60608 19.5586 1.88733H24.9961C25.5977 1.88733 26.1875 1.94592 26.7656 2.06311C27.3516 2.17249 27.8203 2.40295 28.1719 2.75452C28.5312 3.11389 28.7656 3.58264 28.875 4.16077C28.9844 4.73108 29.0391 5.31702 29.0391 5.91858V9.92639C29.0391 10.5358 28.9844 11.1295 28.875 11.7076C28.7656 12.278 28.5312 12.7428 28.1719 13.1022C27.8203 13.4537 27.3516 13.6881 26.7656 13.8053C26.1875 13.9147 25.5977 13.9694 24.9961 13.9694H17.332Z" fill="${chargingBorder}"/>
429
+ <path d="M13.5937 1.88672L15.1055 9.53674e-07L6.25781 1.72716e-06C5.25781 1.81458e-06 4.34375 0.0781269 3.51562 0.234377C2.6875 0.39844 1.97265 0.781252 1.37109 1.38281C0.777343 1.97656 0.402343 2.6836 0.246093 3.50391C0.0820302 4.33203 -9.27501e-07 5.2461 -8.40078e-07 6.2461L-5.46051e-07 9.60938C-4.58628e-07 10.6094 0.0820309 11.5195 0.246093 12.3398C0.402344 13.168 0.777344 13.8789 1.37109 14.4727C1.97266 15.0742 2.6875 15.4531 3.51562 15.6094C4.34375 15.7734 5.25781 15.8555 6.25781 15.8555L11.2266 15.8555C11.1562 15.668 11.1172 15.4805 11.1094 15.293C11.1016 15.1133 11.1211 14.918 11.168 14.707C11.207 14.4961 11.2734 14.25 11.3672 13.9688L5.92969 13.9688C5.32812 13.9688 4.73828 13.9102 4.16016 13.793C3.57422 13.6836 3.10547 13.4531 2.75391 13.1016C2.39453 12.7422 2.16016 12.2734 2.05078 11.6953C1.94141 11.125 1.88672 10.5391 1.88672 9.9375L1.88672 5.92969C1.88672 5.32031 1.94141 4.72656 2.05078 4.14844C2.16016 3.57813 2.39453 3.11328 2.75391 2.75391C3.10547 2.40235 3.57422 2.16797 4.16016 2.05078C4.73828 1.94141 5.32812 1.88672 5.92969 1.88672L13.5937 1.88672Z" fill="${chargingBorder}"/>
430
+ <path d="M32.5195 4.89905V10.9576C32.832 10.9342 33.1562 10.7975 33.4922 10.5475C33.8281 10.2897 34.1094 9.93811 34.3359 9.4928C34.5703 9.04749 34.6875 8.52405 34.6875 7.92249C34.6875 7.32874 34.5703 6.8092 34.3359 6.36389C34.1094 5.91858 33.8281 5.56702 33.4922 5.3092C33.1562 5.05139 32.832 4.91467 32.5195 4.89905Z" fill="${chargingBorder}"/>
431
+ <path d="M4.32422 12.5162C4.58203 12.5787 4.91016 12.61 5.30859 12.61H12.1758L12.7969 10.9225H11.1328C10.5469 10.9225 10.043 10.7194 9.62109 10.3131C9.20703 9.90686 9 9.40686 9 8.81311C9 8.29749 9.17578 7.81702 9.52734 7.3717L12.8086 3.2467H5.34375C4.92969 3.2467 4.59375 3.28186 4.33594 3.35217C4.07812 3.41467 3.86719 3.52795 3.70312 3.69202C3.54688 3.85608 3.43359 4.06702 3.36328 4.32483C3.29297 4.57483 3.25781 4.90686 3.25781 5.32092V10.5592C3.25781 10.9655 3.29297 11.2936 3.36328 11.5436C3.43359 11.7936 3.54688 12.0006 3.70312 12.1647C3.86719 12.3287 4.07422 12.4459 4.32422 12.5162Z" fill="${chargingGreen}"/>
432
+ <path d="M21.6914 8.48499L18.4102 12.61H25.6172C26.0234 12.61 26.3516 12.5787 26.6016 12.5162C26.8516 12.4459 27.0586 12.3287 27.2227 12.1647C27.5273 11.86 27.6797 11.3248 27.6797 10.5592V5.29749C27.6797 4.89124 27.6445 4.56311 27.5742 4.31311C27.5039 4.06311 27.3867 3.85608 27.2227 3.69202C27.0586 3.52795 26.8516 3.41467 26.6016 3.35217C26.3516 3.28186 26.0234 3.2467 25.6172 3.2467H19.043L18.4219 4.9342H20.0742C20.668 4.9342 21.1719 5.13733 21.5859 5.54358C22.0078 5.94983 22.2188 6.44983 22.2188 7.04358C22.2188 7.5592 22.043 8.03967 21.6914 8.48499Z" fill="${chargingGreen}"/>
433
+ <path d="M10.7812 8.34432C10.6484 8.52401 10.582 8.68026 10.582 8.81307C10.582 8.96151 10.6328 9.0826 10.7344 9.17635C10.8438 9.2701 10.9727 9.31698 11.1211 9.31698H15.0938L12.9727 15.0123C12.8867 15.2623 12.8945 15.4654 12.9961 15.6217C13.1055 15.7779 13.2578 15.8599 13.4531 15.8678C13.6484 15.8678 13.8242 15.7701 13.9805 15.5748L20.4023 7.50057C20.5352 7.3287 20.6016 7.17635 20.6016 7.04354C20.6016 6.8951 20.5469 6.77401 20.4375 6.68026C20.3359 6.58651 20.2109 6.53963 20.0625 6.53963H16.0898L18.2109 0.844323C18.3047 0.594323 18.2969 0.391198 18.1875 0.234948C18.0781 0.0786978 17.9258 0.000572791 17.7305 0.000572791C17.543 -0.00723971 17.3672 0.0865103 17.2031 0.281823L10.7812 8.34432Z" fill="${boltColor}"/>`;
434
+ }
435
+ else {
436
+ const batteryBorder = '#ADADB1';
437
+ paths = `<path d="M12.8057 3.25098H19.042L19.043 3.24707H25.6172C26.0234 3.24707 26.3516 3.28223 26.6016 3.35254C26.8515 3.41504 27.0586 3.52834 27.2227 3.69238C27.3866 3.85642 27.5039 4.06357 27.5742 4.31348C27.6445 4.56343 27.6797 4.89179 27.6797 5.29785V10.5596C27.6796 11.325 27.5273 11.8604 27.2227 12.165C27.0586 12.329 26.8515 12.4463 26.6016 12.5166C26.3516 12.5791 26.0234 12.6104 25.6172 12.6104H5.30859C4.9102 12.6104 4.58202 12.5791 4.32422 12.5166C4.07431 12.4463 3.86716 12.329 3.70312 12.165C3.54695 12.0011 3.4336 11.7938 3.36328 11.5439C3.29299 11.294 3.25783 10.9657 3.25781 10.5596V5.32129C3.25781 4.90741 3.29303 4.57516 3.36328 4.3252C3.43357 4.06748 3.54697 3.85642 3.70312 3.69238C3.86717 3.52834 4.07816 3.41504 4.33594 3.35254C4.59375 3.28223 4.92969 3.24707 5.34375 3.24707H12.8086L12.8057 3.25098Z" fill="${color}"/>
438
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M24.668 0.000976562C25.668 0.000976562 26.582 0.0830078 27.4102 0.24707C28.2383 0.403328 28.9531 0.782242 29.5547 1.38379C30.1483 1.9775 30.5235 2.68856 30.6797 3.5166C30.8437 4.33682 30.9258 5.24721 30.9258 6.24707V9.61035C30.9258 10.6102 30.8437 11.5245 30.6797 12.3525C30.5234 13.1727 30.1484 13.88 29.5547 14.4736C28.9532 15.0751 28.2382 15.458 27.4102 15.6221C26.582 15.7783 25.6679 15.8564 24.668 15.8564L10.5156 15.8574V15.8555H6.25781C5.25781 15.8555 4.34375 15.7734 3.51562 15.6094C2.6875 15.4531 1.97266 15.0742 1.37109 14.4727C0.777344 13.8789 0.402344 13.168 0.246094 12.3398C0.0820312 11.5195 9.34357e-08 10.6094 0 9.60938V6.24609C-6.73586e-08 5.24609 0.0820313 4.33203 0.246094 3.50391C0.402344 2.68359 0.777344 1.97656 1.37109 1.38281C1.97266 0.78125 2.6875 0.398438 3.51562 0.234375C4.34375 0.078125 5.25781 1.64241e-07 6.25781 0L24.668 0.000976562ZM5.92969 1.88672C5.32813 1.88672 4.73828 1.94141 4.16016 2.05078C3.57422 2.16797 3.10547 2.40234 2.75391 2.75391C2.39453 3.11328 2.16016 3.57813 2.05078 4.14844C1.94141 4.72656 1.88672 5.32031 1.88672 5.92969V9.9375C1.88672 10.5391 1.94141 11.125 2.05078 11.6953C2.16016 12.2734 2.39453 12.7422 2.75391 13.1016C3.10547 13.4531 3.57422 13.6836 4.16016 13.793C4.73828 13.9102 5.32813 13.9687 5.92969 13.9688L24.9961 13.9697C25.5976 13.9697 26.1875 13.915 26.7656 13.8057C27.3515 13.6885 27.8203 13.454 28.1719 13.1025C28.5312 12.7432 28.7656 12.2782 28.875 11.708C28.9844 11.13 29.039 10.536 29.0391 9.92676V5.91895C29.0391 5.3175 28.9843 4.73134 28.875 4.16113C28.7656 3.5831 28.5311 3.11423 28.1719 2.75488C27.8203 2.40333 27.3515 2.17286 26.7656 2.06348C26.1875 1.94629 25.5977 1.8877 24.9961 1.8877L13 1.88867V1.88672H5.92969Z" fill="${batteryBorder}"/>
439
+ <path d="M32.5195 4.89941C32.832 4.91504 33.1563 5.05176 33.4922 5.30957C33.828 5.56737 34.1094 5.91902 34.3359 6.36426C34.5702 6.80951 34.6875 7.32923 34.6875 7.92285C34.6875 8.52426 34.5703 9.04794 34.3359 9.49316C34.1094 9.93834 33.8281 10.2901 33.4922 10.5479C33.1563 10.7978 32.832 10.9346 32.5195 10.958V4.89941Z" fill="${batteryBorder}"/>`;
440
+ }
441
+ return { svg: `<g transform="scale(${scale})">${paths}</g>`, width: w, height: h };
442
+ }
443
+ // ── SVG layout builders (no HTML, no CSS flexbox) ─────────────────────
444
+ /** Approximate text width for SF Pro at a given font size.
445
+ * Average character width is ~0.55em for SF Pro (measured).
446
+ * This is a conservative estimate — used only for right-alignment. */
447
+ function estimateTextWidth(text, fontSize, weight = 400) {
448
+ const avgCharWidth = weight >= 600 ? 0.58 : 0.53;
449
+ return text.length * fontSize * avgCharWidth;
450
+ }
451
+ function svgText(text, x, y, fontSize, color, opts = {}) {
452
+ const { weight = 400, letterSpacing, anchor = 'start', fontFamily } = opts;
453
+ const ls = letterSpacing != null ? ` letter-spacing="${letterSpacing}"` : '';
454
+ const ff = fontFamily ? ` font-family="${fontFamily}"` : '';
455
+ return `<text x="${x}" y="${y}" font-size="${fontSize}" font-weight="${weight}" fill="${color}" dominant-baseline="central" text-anchor="${anchor}"${ls}${ff}>${escapeHtml(text)}</text>`;
456
+ }
457
+ // iPhone Dynamic Island / Notch — time left, icons right
458
+ function buildIPhoneSvg(cfg, layout, width, height, color, fontFamily) {
459
+ const cy = height / 2;
460
+ const gap = layout.iconGap;
461
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
462
+ // Left: time
463
+ const timeText = svgText(cfg.time, layout.leftInset, cy, layout.fontSize.time, color, { weight: 600, letterSpacing: -0.4, fontFamily });
464
+ // Right group: [signal] [networkType?] [wifi] [battery] — right-aligned from (width - rightInset)
465
+ const signal = signalBarsSvgFragment(cfg.signalStrength, color, signalW);
466
+ const wifi = wifiSvgFragment(cfg.wifiStrength, color, wifiW);
467
+ const battery = batterySvgFragment(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
468
+ const networkLabelWidth = cfg.showNetworkType
469
+ ? estimateTextWidth(cfg.networkType, layout.fontSize.networkType, 600)
470
+ : 0;
471
+ // Compute total right group width
472
+ const items = [signal.width];
473
+ if (cfg.showNetworkType)
474
+ items.push(networkLabelWidth);
475
+ items.push(wifi.width, battery.width);
476
+ const totalRightWidth = items.reduce((a, b) => a + b, 0) + (items.length - 1) * gap;
477
+ let x = width - layout.rightInset - totalRightWidth;
478
+ let rightGroup = `<g transform="translate(${x},${cy - signal.height / 2})">${signal.svg}</g>`;
479
+ x += signal.width + gap;
480
+ if (cfg.showNetworkType) {
481
+ rightGroup += svgText(cfg.networkType, x, cy, layout.fontSize.networkType, color, { weight: 600, letterSpacing: 0.2, fontFamily });
482
+ x += networkLabelWidth + gap;
483
+ }
484
+ rightGroup += `<g transform="translate(${x},${cy - wifi.height / 2})">${wifi.svg}</g>`;
485
+ x += wifi.width + gap;
486
+ rightGroup += `<g transform="translate(${x},${cy - battery.height / 2})">${battery.svg}</g>`;
487
+ return `<g font-family="${fontFamily}">${timeText}${rightGroup}</g>`;
488
+ }
489
+ // iPhone Home Button — signal+carrier+wifi left, time center, battery right
490
+ function buildIPhoneHomeButtonSvg(cfg, layout, width, height, color, fontFamily) {
491
+ const cy = height / 2;
492
+ const gap = layout.iconGap;
493
+ const labelSize = layout.fontSize.networkType;
494
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
495
+ const signal = signalBarsSvgFragment(cfg.signalStrength, color, signalW);
496
+ const wifi = wifiSvgFragment(cfg.wifiStrength, color, wifiW);
497
+ const battery = batterySvgFragment(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
498
+ // Left group: signal + carrier + networkBadge? + wifi
499
+ let x = layout.leftInset;
500
+ let leftGroup = `<g transform="translate(${x},${cy - signal.height / 2})">${signal.svg}</g>`;
501
+ x += signal.width + gap;
502
+ leftGroup += svgText(cfg.carrierName, x, cy, labelSize, color, { fontFamily });
503
+ x += Math.min(80, estimateTextWidth(cfg.carrierName, labelSize)) + gap;
504
+ if (cfg.showNetworkType) {
505
+ leftGroup += svgText(cfg.networkType, x, cy, labelSize - 1, color, { weight: 600, fontFamily });
506
+ x += estimateTextWidth(cfg.networkType, labelSize - 1, 600) + gap;
507
+ }
508
+ leftGroup += `<g transform="translate(${x},${cy - wifi.height / 2})">${wifi.svg}</g>`;
509
+ // Center: time
510
+ const centerText = svgText(cfg.time, width / 2, cy, layout.fontSize.time, color, { weight: 600, letterSpacing: -0.4, anchor: 'middle', fontFamily });
511
+ // Right group: batteryPct? + battery
512
+ const batteryPctWidth = cfg.showBatteryPercentage ? estimateTextWidth(`${cfg.batteryLevel}%`, labelSize) : 0;
513
+ const rightTotalWidth = (cfg.showBatteryPercentage ? batteryPctWidth + gap : 0) + battery.width;
514
+ let rx = width - layout.rightInset - rightTotalWidth;
515
+ let rightGroup = '';
516
+ if (cfg.showBatteryPercentage) {
517
+ rightGroup += svgText(`${cfg.batteryLevel}%`, rx, cy, labelSize, color, { fontFamily });
518
+ rx += batteryPctWidth + gap;
519
+ }
520
+ rightGroup += `<g transform="translate(${rx},${cy - battery.height / 2})">${battery.svg}</g>`;
521
+ return `<g font-family="${fontFamily}">${leftGroup}${centerText}${rightGroup}</g>`;
522
+ }
523
+ // iPad — time+date left, icons right
524
+ function buildIPadSvg(cfg, layout, width, height, color, fontFamily) {
525
+ const cy = height / 2;
526
+ const gap = layout.iconGap;
527
+ const { signal: signalW, wifi: wifiW, battery: batteryW } = layout.iconSize;
528
+ // Left: time + date
529
+ let leftGroup = svgText(cfg.time, layout.leftInset, cy, layout.fontSize.time, color, { letterSpacing: -0.4, fontFamily });
530
+ if (cfg.date) {
531
+ const timeWidth = estimateTextWidth(cfg.time, layout.fontSize.time);
532
+ leftGroup += svgText(cfg.date, layout.leftInset + timeWidth + 16, cy, layout.fontSize.time, color, { letterSpacing: -0.4, fontFamily });
533
+ }
534
+ // Right: signal + wifi + batteryLabel + battery
535
+ const signal = signalBarsSvgFragment(cfg.signalStrength, color, signalW);
536
+ const wifi = wifiSvgFragment(cfg.wifiStrength, color, wifiW);
537
+ const battery = batterySvgFragment(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
538
+ const batteryLabelW = estimateTextWidth(`${cfg.batteryLevel}%`, layout.fontSize.networkType);
539
+ const rightItems = [signal.width, wifi.width, batteryLabelW, battery.width];
540
+ const totalRight = rightItems.reduce((a, b) => a + b, 0) + (rightItems.length - 1) * gap;
541
+ let x = width - layout.rightInset - totalRight;
542
+ let rightGroup = `<g transform="translate(${x},${cy - signal.height / 2})">${signal.svg}</g>`;
543
+ x += signal.width + gap;
544
+ rightGroup += `<g transform="translate(${x},${cy - wifi.height / 2})">${wifi.svg}</g>`;
545
+ x += wifi.width + gap;
546
+ rightGroup += svgText(`${cfg.batteryLevel}%`, x, cy, layout.fontSize.networkType, color, { fontFamily });
547
+ x += batteryLabelW + gap;
548
+ rightGroup += `<g transform="translate(${x},${cy - battery.height / 2})">${battery.svg}</g>`;
549
+ return `<g font-family="${fontFamily}">${leftGroup}${rightGroup}</g>`;
550
+ }
551
+ // macOS Menu Bar — apple+app+menus left, wifi+battery+icons+date+time right
552
+ function buildMacMenuBarSvg(cfg, layout, width, height, color, _fontFamily) {
553
+ const fontSize = layout.fontSize.time;
554
+ const cy = height / 2;
555
+ const gap = layout.iconGap;
556
+ const { wifi: wifiW, battery: batteryW } = layout.iconSize;
557
+ const macFF = "'SF Pro Display','SF Pro Text',system-ui,sans-serif";
558
+ const symbolsFF = "'SF Pro Symbols','SF Pro Text','SF Pro Display',system-ui,sans-serif";
559
+ // Left group: Apple logo + app name + menu items
560
+ let lx = layout.leftInset;
561
+ const bigGap = gap + 6;
562
+ const menuGap = gap + 10;
563
+ // Apple logo (U+F8FF)
564
+ let leftGroup = svgText('\uF8FF', lx, cy, fontSize + 3, color, { fontFamily: symbolsFF });
565
+ lx += estimateTextWidth('\uF8FF', fontSize + 3) + bigGap;
566
+ // App name (bold)
567
+ leftGroup += svgText(cfg.menuBarApp, lx, cy, fontSize, color, { weight: 700, fontFamily: macFF });
568
+ lx += estimateTextWidth(cfg.menuBarApp, fontSize, 700) + bigGap;
569
+ // Menu items
570
+ for (const item of cfg.menuBarItems) {
571
+ leftGroup += svgText(item, lx, cy, fontSize, color, { fontFamily: macFF });
572
+ lx += estimateTextWidth(item, fontSize) + menuGap;
573
+ }
574
+ // Right group: wifi + battery + search + CC + avatar + date + time
575
+ const wifi = wifiSvgFragment(cfg.wifiStrength, color, wifiW);
576
+ const battery = batterySvgFragment(cfg.batteryLevel, cfg.batteryCharging, color, batteryW, cfg.colorScheme === 'dark');
577
+ const batteryLabelW = cfg.showBatteryPercentage ? estimateTextWidth(`${cfg.batteryLevel}%`, fontSize - 1) : 0;
578
+ const smallGap = gap + 2;
579
+ // Symbol icons
580
+ const searchW = estimateTextWidth('􀊫', fontSize);
581
+ const ccW = estimateTextWidth('􀜊', fontSize);
582
+ const avatarW = estimateTextWidth('􀉭', fontSize);
583
+ const dateW = cfg.date ? estimateTextWidth(cfg.date, fontSize, 500) : 0;
584
+ const timeW = estimateTextWidth(cfg.time, fontSize, 500);
585
+ const rightItems = [
586
+ wifi.width, // wifi
587
+ (cfg.showBatteryPercentage ? batteryLabelW + 3 : 0) + battery.width, // battery group
588
+ searchW, ccW, avatarW, // symbol icons
589
+ ];
590
+ if (cfg.date)
591
+ rightItems.push(dateW);
592
+ rightItems.push(timeW);
593
+ const totalRight = rightItems.reduce((a, b) => a + b, 0) + (rightItems.length - 1) * smallGap;
594
+ let rx = width - layout.rightInset - totalRight;
595
+ // wifi
596
+ let rightGroup = `<g transform="translate(${rx},${cy - wifi.height / 2})">${wifi.svg}</g>`;
597
+ rx += wifi.width + smallGap;
598
+ // battery group: pct + icon
599
+ if (cfg.showBatteryPercentage) {
600
+ rightGroup += svgText(`${cfg.batteryLevel}%`, rx, cy, fontSize - 1, color, { fontFamily: macFF });
601
+ rx += batteryLabelW + 3;
602
+ }
603
+ rightGroup += `<g transform="translate(${rx},${cy - battery.height / 2})">${battery.svg}</g>`;
604
+ rx += battery.width + smallGap;
605
+ // search, CC, avatar
606
+ rightGroup += svgText('􀊫', rx, cy, fontSize, color, { fontFamily: symbolsFF });
607
+ rx += searchW + smallGap;
608
+ rightGroup += svgText('􀜊', rx, cy, fontSize, color, { fontFamily: symbolsFF });
609
+ rx += ccW + smallGap;
610
+ rightGroup += svgText('􀉭', rx, cy, fontSize, color, { fontFamily: symbolsFF });
611
+ rx += avatarW + smallGap;
612
+ // date + time
613
+ if (cfg.date) {
614
+ rightGroup += svgText(cfg.date, rx, cy, fontSize, color, { weight: 500, fontFamily: macFF });
615
+ rx += dateW + smallGap;
616
+ }
617
+ rightGroup += svgText(cfg.time, rx, cy, fontSize, color, { weight: 500, fontFamily: macFF });
618
+ return `<g font-family="${macFF}">${leftGroup}${rightGroup}</g>`;
619
+ }
620
+ //# sourceMappingURL=status-bar.js.map