@screenly/edge-apps 0.0.1

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/bin/edge-apps-scripts.ts +9 -0
  4. package/configs/playwright.ts +44 -0
  5. package/eslint.config.ts +43 -0
  6. package/package.json +111 -0
  7. package/scripts/cli.ts +293 -0
  8. package/scripts/copy-assets.ts +26 -0
  9. package/scripts/cors-proxy-server.ts +154 -0
  10. package/scripts/create.ts +118 -0
  11. package/src/assets/fonts/Inter-Medium.woff2 +0 -0
  12. package/src/assets/fonts/Inter-Regular.woff2 +0 -0
  13. package/src/assets/fonts/Inter-SemiBold.woff2 +0 -0
  14. package/src/assets/images/icons/chancesleet.svg +4 -0
  15. package/src/assets/images/icons/clear-night.svg +5 -0
  16. package/src/assets/images/icons/clear.svg +11 -0
  17. package/src/assets/images/icons/cloudy.svg +4 -0
  18. package/src/assets/images/icons/drizzle.svg +5 -0
  19. package/src/assets/images/icons/fewdrops.svg +4 -0
  20. package/src/assets/images/icons/fog.svg +6 -0
  21. package/src/assets/images/icons/haze.svg +6 -0
  22. package/src/assets/images/icons/mostly-cloudy-night.svg +7 -0
  23. package/src/assets/images/icons/mostly-cloudy.svg +13 -0
  24. package/src/assets/images/icons/partially-cloudy-night.svg +6 -0
  25. package/src/assets/images/icons/partially-cloudy.svg +12 -0
  26. package/src/assets/images/icons/partlysunny.svg +6 -0
  27. package/src/assets/images/icons/rain-night.svg +8 -0
  28. package/src/assets/images/icons/rainy.svg +6 -0
  29. package/src/assets/images/icons/sleet-night.svg +14 -0
  30. package/src/assets/images/icons/sleet.svg +4 -0
  31. package/src/assets/images/icons/snow.svg +19 -0
  32. package/src/assets/images/icons/thunderstorm-night.svg +9 -0
  33. package/src/assets/images/icons/thunderstorm.svg +7 -0
  34. package/src/assets/images/icons/windy.svg +6 -0
  35. package/src/assets/images/screenly.svg +10 -0
  36. package/src/styles/base/fonts.css +30 -0
  37. package/src/styles/base/index.css +7 -0
  38. package/src/styles/base/reset.css +37 -0
  39. package/src/styles/calendar-app.css +52 -0
  40. package/src/styles/index.css +10 -0
  41. package/src/styles/tokens/colors.css +4 -0
  42. package/src/styles/tokens/index.css +10 -0
  43. package/src/styles/tokens/shadows.css +8 -0
  44. package/src/styles/tokens/spacing.css +6 -0
  45. package/src/styles/tokens/typography.css +9 -0
  46. package/src/styles/tokens/z-index.css +12 -0
package/scripts/cli.ts ADDED
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * CLI command dispatcher for edge-apps-scripts
4
+ */
5
+
6
+ import { execSync, spawn, type ChildProcess } from 'child_process'
7
+ import fs from 'fs'
8
+ import path from 'path'
9
+ import { fileURLToPath } from 'url'
10
+ import { createCommand } from './create'
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+ const libraryRoot = path.dirname(__dirname)
15
+
16
+ const commands = {
17
+ lint: {
18
+ description: 'Run ESLint with shared configuration',
19
+ handler: lintCommand,
20
+ },
21
+ dev: {
22
+ description: 'Start Vite development server',
23
+ handler: devCommand,
24
+ },
25
+ build: {
26
+ description: 'Build application for production',
27
+ handler: buildCommand,
28
+ },
29
+ 'build:dev': {
30
+ description: 'Build application in development mode with watch',
31
+ handler: buildDevCommand,
32
+ },
33
+ 'type-check': {
34
+ description: 'Run TypeScript type checking',
35
+ handler: typeCheckCommand,
36
+ },
37
+ screenshots: {
38
+ description: 'Capture screenshots at all supported resolutions',
39
+ handler: screenshotsCommand,
40
+ },
41
+ create: {
42
+ description:
43
+ 'Initialize a scaffolded Edge App (replaces template placeholders)',
44
+ handler: createCommand,
45
+ },
46
+ }
47
+
48
+ /**
49
+ * Helper: Get NODE_PATH with library's node_modules included
50
+ */
51
+ function getNodePath(): string {
52
+ const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
53
+ const existingNodePath = process.env.NODE_PATH || ''
54
+ return existingNodePath
55
+ ? `${libraryNodeModules}${path.delimiter}${existingNodePath}`
56
+ : libraryNodeModules
57
+ }
58
+
59
+ /**
60
+ * Helper: Setup signal handlers for spawned processes
61
+ */
62
+ function setupSignalHandlers(child: ChildProcess): void {
63
+ const handleSignal = (signal: string) => {
64
+ child.kill(signal as NodeJS.Signals)
65
+ child.on('exit', () => process.exit(0))
66
+ }
67
+ process.on('SIGINT', () => handleSignal('SIGINT'))
68
+ process.on('SIGTERM', () => handleSignal('SIGTERM'))
69
+ }
70
+
71
+ /**
72
+ * Helper: Spawn a process with common options and signal handling
73
+ */
74
+ function spawnWithSignalHandling(
75
+ command: string,
76
+ args: string[],
77
+ errorMessage: string,
78
+ ): void {
79
+ const child = spawn(command, args, {
80
+ stdio: 'inherit',
81
+ cwd: process.cwd(),
82
+ shell: process.platform === 'win32',
83
+ env: {
84
+ ...process.env,
85
+ NODE_PATH: getNodePath(),
86
+ },
87
+ })
88
+
89
+ child.on('error', (err) => {
90
+ console.error(errorMessage, err)
91
+ process.exit(1)
92
+ })
93
+
94
+ setupSignalHandlers(child)
95
+ }
96
+
97
+ /**
98
+ * Helper: Get Vite binary and config paths
99
+ */
100
+ function getVitePaths(): { viteBin: string; configPath: string } {
101
+ return {
102
+ viteBin: path.resolve(libraryRoot, 'node_modules', '.bin', 'vite'),
103
+ configPath: path.resolve(libraryRoot, 'vite.config.ts'),
104
+ }
105
+ }
106
+
107
+ async function lintCommand(args: string[]) {
108
+ try {
109
+ const eslintBin = path.resolve(
110
+ libraryRoot,
111
+ 'node_modules',
112
+ '.bin',
113
+ 'eslint',
114
+ )
115
+
116
+ const eslintArgs = [
117
+ '--config',
118
+ path.resolve(libraryRoot, 'eslint.config.ts'),
119
+ '.',
120
+ ...args,
121
+ ]
122
+
123
+ execSync(
124
+ `"${eslintBin}" ${eslintArgs.map((arg) => `"${arg}"`).join(' ')}`,
125
+ {
126
+ stdio: 'inherit',
127
+ cwd: process.cwd(),
128
+ },
129
+ )
130
+ } catch {
131
+ process.exit(1)
132
+ }
133
+ }
134
+
135
+ async function devCommand(args: string[]) {
136
+ try {
137
+ const { viteBin, configPath } = getVitePaths()
138
+ const viteArgs = ['--config', configPath, ...args]
139
+
140
+ spawnWithSignalHandling(viteBin, viteArgs, 'Failed to start dev server:')
141
+ } catch {
142
+ process.exit(1)
143
+ }
144
+ }
145
+
146
+ async function buildCommand(args: string[]) {
147
+ try {
148
+ const { viteBin, configPath } = getVitePaths()
149
+ const viteArgs = ['build', '--config', configPath, ...args]
150
+
151
+ execSync(`"${viteBin}" ${viteArgs.map((arg) => `"${arg}"`).join(' ')}`, {
152
+ stdio: 'inherit',
153
+ cwd: process.cwd(),
154
+ env: {
155
+ ...process.env,
156
+ NODE_PATH: getNodePath(),
157
+ },
158
+ })
159
+ } catch {
160
+ process.exit(1)
161
+ }
162
+ }
163
+
164
+ async function buildDevCommand(args: string[]) {
165
+ try {
166
+ const { viteBin, configPath } = getVitePaths()
167
+ const viteArgs = [
168
+ 'build',
169
+ '--watch',
170
+ '--sourcemap',
171
+ '--config',
172
+ configPath,
173
+ ...args,
174
+ ]
175
+
176
+ spawnWithSignalHandling(viteBin, viteArgs, 'Failed to start build process:')
177
+ } catch {
178
+ process.exit(1)
179
+ }
180
+ }
181
+
182
+ async function typeCheckCommand(args: string[]) {
183
+ try {
184
+ const tscBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'tsc')
185
+
186
+ const tscArgs = [
187
+ '--noEmit',
188
+ '--project',
189
+ path.resolve(libraryRoot, 'tsconfig.json'),
190
+ ...args,
191
+ ]
192
+
193
+ execSync(`"${tscBin}" ${tscArgs.map((arg) => `"${arg}"`).join(' ')}`, {
194
+ stdio: 'inherit',
195
+ cwd: process.cwd(),
196
+ })
197
+ } catch {
198
+ process.exit(1)
199
+ }
200
+ }
201
+
202
+ async function convertPngsToWebP(screenshotsDir: string): Promise<void> {
203
+ const { default: sharp } = await import('sharp')
204
+ const pngFiles = fs
205
+ .readdirSync(screenshotsDir)
206
+ .filter((f) => f.endsWith('.png'))
207
+
208
+ for (const file of pngFiles) {
209
+ const pngPath = path.join(screenshotsDir, file)
210
+ const webpPath = path.join(screenshotsDir, file.replace('.png', '.webp'))
211
+ await sharp(pngPath).webp().toFile(webpPath)
212
+ fs.unlinkSync(pngPath)
213
+ }
214
+ }
215
+
216
+ async function screenshotsCommand(_args: string[]) {
217
+ try {
218
+ const playwrightBin = path.resolve(
219
+ process.cwd(),
220
+ 'node_modules',
221
+ '.bin',
222
+ 'playwright',
223
+ )
224
+ const playwrightConfig = path.resolve(
225
+ libraryRoot,
226
+ 'configs',
227
+ 'playwright.ts',
228
+ )
229
+ execSync(`"${playwrightBin}" test --config "${playwrightConfig}"`, {
230
+ stdio: 'inherit',
231
+ cwd: process.cwd(),
232
+ env: {
233
+ ...process.env,
234
+ NODE_PATH: getNodePath(),
235
+ },
236
+ })
237
+
238
+ const screenshotsDir = path.resolve(process.cwd(), 'screenshots')
239
+ if (fs.existsSync(screenshotsDir)) {
240
+ await convertPngsToWebP(screenshotsDir)
241
+ }
242
+ } catch (error) {
243
+ console.error(
244
+ 'Failed to run screenshot tests. Ensure `@playwright/test` is installed and the Playwright config file exists.',
245
+ )
246
+ if (error instanceof Error && error.message) {
247
+ console.error(error.message)
248
+ }
249
+ process.exit(1)
250
+ }
251
+ }
252
+
253
+ export async function run() {
254
+ const args = process.argv.slice(2)
255
+
256
+ if (args.length === 0) {
257
+ printHelp()
258
+ process.exit(1)
259
+ }
260
+
261
+ const cmd = args[0]
262
+ const cmdArgs = args.slice(1)
263
+
264
+ if (cmd === '--help' || cmd === '-h') {
265
+ printHelp()
266
+ process.exit(0)
267
+ }
268
+
269
+ if (!(cmd in commands)) {
270
+ console.error(`Unknown command: ${cmd}`)
271
+ printHelp()
272
+ process.exit(1)
273
+ }
274
+
275
+ const handler = commands[cmd as keyof typeof commands].handler
276
+ await handler(cmdArgs)
277
+ }
278
+
279
+ function printHelp() {
280
+ console.log(`
281
+ edge-apps-scripts - Shared tooling for Screenly Edge Apps
282
+
283
+ Usage: edge-apps-scripts <command> [options]
284
+
285
+ Commands:
286
+ ${Object.entries(commands)
287
+ .map(([name, { description }]) => ` ${name.padEnd(12)} ${description}`)
288
+ .join('\n')}
289
+
290
+ Options:
291
+ --help, -h Show this help message
292
+ `)
293
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Copy assets from src to dist after TypeScript compilation
4
+ */
5
+
6
+ import { cpSync, existsSync, mkdirSync } from 'fs'
7
+ import { dirname } from 'path'
8
+ import { fileURLToPath } from 'url'
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = dirname(__filename)
12
+ const libraryRoot = dirname(__dirname)
13
+
14
+ const srcAssets = `${libraryRoot}/src/assets`
15
+ const distAssets = `${libraryRoot}/dist/assets`
16
+
17
+ if (existsSync(srcAssets)) {
18
+ // Ensure dist directory exists
19
+ mkdirSync(dirname(distAssets), { recursive: true })
20
+
21
+ // Copy assets
22
+ cpSync(srcAssets, distAssets, { recursive: true })
23
+ console.log('✓ Copied assets to dist/')
24
+ } else {
25
+ console.warn('⚠ No assets directory found in src/')
26
+ }
@@ -0,0 +1,154 @@
1
+ import http from 'http'
2
+ import https from 'https'
3
+ import { URL } from 'url'
4
+
5
+ // Listen on a specific host via the HOST environment variable
6
+ const host = process.env.HOST || '0.0.0.0'
7
+ // Listen on a specific port via the PORT environment variable
8
+ const port = process.env.PORT || 8080
9
+
10
+ // This script is for development purposes only and should never be deployed to production.
11
+ // Disabling TLS certificate validation is a critical security risk.
12
+ // Only use this in local development environments.
13
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
14
+
15
+ function setCorsHeaders(res: http.ServerResponse) {
16
+ res.setHeader('Access-Control-Allow-Origin', '*')
17
+ res.setHeader(
18
+ 'Access-Control-Allow-Methods',
19
+ 'GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH',
20
+ )
21
+ res.setHeader('Access-Control-Allow-Headers', '*')
22
+ res.setHeader('Access-Control-Max-Age', '86400')
23
+ }
24
+
25
+ function makeProxyRequest(
26
+ httpModule: typeof http | typeof https,
27
+ parsedUrl: URL,
28
+ req: http.IncomingMessage,
29
+ res: http.ServerResponse,
30
+ ) {
31
+ const options = {
32
+ hostname: parsedUrl.hostname,
33
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
34
+ path: parsedUrl.pathname + parsedUrl.search,
35
+ method: req.method,
36
+ headers: {
37
+ ...req.headers,
38
+ host: parsedUrl.host,
39
+ },
40
+ rejectUnauthorized: false,
41
+ }
42
+
43
+ delete options.headers['x-forwarded-for']
44
+ delete options.headers['x-forwarded-proto']
45
+ delete options.headers['x-forwarded-host']
46
+
47
+ const proxyReq = httpModule.request(options, (proxyRes) => {
48
+ const excludeHeaders = [
49
+ 'connection',
50
+ 'keep-alive',
51
+ 'proxy-authenticate',
52
+ 'proxy-authorization',
53
+ 'te',
54
+ 'trailers',
55
+ 'transfer-encoding',
56
+ 'upgrade',
57
+ ]
58
+
59
+ Object.keys(proxyRes.headers).forEach((key) => {
60
+ if (!excludeHeaders.includes(key.toLowerCase())) {
61
+ res.setHeader(key, proxyRes.headers[key])
62
+ }
63
+ })
64
+
65
+ res.writeHead(proxyRes.statusCode)
66
+ proxyRes.pipe(res)
67
+ })
68
+
69
+ proxyReq.on('error', (error) => {
70
+ console.error('Proxy error:', error.message)
71
+ if (!res.headersSent) {
72
+ res.writeHead(500, { 'Content-Type': 'application/json' })
73
+ res.end(
74
+ JSON.stringify({
75
+ error: 'Proxy error',
76
+ message: error.message,
77
+ }),
78
+ )
79
+ }
80
+ })
81
+
82
+ proxyReq.setTimeout(30000, () => {
83
+ proxyReq.destroy()
84
+ if (!res.headersSent) {
85
+ res.writeHead(504, { 'Content-Type': 'application/json' })
86
+ res.end(
87
+ JSON.stringify({
88
+ error: 'Gateway timeout',
89
+ }),
90
+ )
91
+ }
92
+ })
93
+
94
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
95
+ req.pipe(proxyReq)
96
+ } else {
97
+ proxyReq.end()
98
+ }
99
+ }
100
+
101
+ function handleProxyRequest(
102
+ req: http.IncomingMessage,
103
+ res: http.ServerResponse,
104
+ ) {
105
+ setCorsHeaders(res)
106
+
107
+ if (req.method === 'OPTIONS') {
108
+ res.writeHead(200)
109
+ res.end()
110
+ return
111
+ }
112
+
113
+ const urlMatch = req.url.match(/^\/(https?:\/\/.+)$/)
114
+
115
+ if (!urlMatch) {
116
+ res.writeHead(400, { 'Content-Type': 'application/json' })
117
+ res.end(
118
+ JSON.stringify({
119
+ error: 'Invalid request format. Expected: /{protocol}://{host}/{path}',
120
+ }),
121
+ )
122
+ return
123
+ }
124
+
125
+ const targetUrl = urlMatch[1]
126
+
127
+ try {
128
+ const parsedUrl = new URL(targetUrl)
129
+ console.log(`Proxying ${req.method} request to: ${targetUrl}`)
130
+
131
+ const httpModule = parsedUrl.protocol === 'https:' ? https : http
132
+ makeProxyRequest(httpModule, parsedUrl, req, res)
133
+ } catch (error) {
134
+ console.error('Invalid URL:', targetUrl, error.message)
135
+ res.writeHead(400, { 'Content-Type': 'application/json' })
136
+ res.end(
137
+ JSON.stringify({
138
+ error: 'Invalid URL format',
139
+ message: error.message,
140
+ }),
141
+ )
142
+ }
143
+ }
144
+
145
+ const server = http.createServer((req, res) => {
146
+ handleProxyRequest(req, res)
147
+ })
148
+
149
+ // Start the server
150
+ server.listen(port, host, () => {
151
+ console.log(
152
+ `Running CORS Proxy (Node.js built-ins) on http://${host}:${port}`,
153
+ )
154
+ })
@@ -0,0 +1,118 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ const TEXT_EXTENSIONS = new Set([
5
+ '.ts',
6
+ '.tsx',
7
+ '.js',
8
+ '.jsx',
9
+ '.html',
10
+ '.css',
11
+ '.scss',
12
+ '.json',
13
+ '.yml',
14
+ '.yaml',
15
+ '.md',
16
+ '.txt',
17
+ '.svg',
18
+ '.gitignore',
19
+ '.ignore',
20
+ ])
21
+
22
+ const SKIP_DIRS = new Set(['node_modules', 'dist', '.git'])
23
+
24
+ function toTitleCase(kebab: string): string {
25
+ return kebab
26
+ .split('-')
27
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
28
+ .join(' ')
29
+ }
30
+
31
+ function walkTextFiles(dir: string): string[] {
32
+ const results: string[] = []
33
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ const fullPath = path.join(dir, entry.name)
35
+ if (entry.isDirectory()) {
36
+ if (!SKIP_DIRS.has(entry.name)) results.push(...walkTextFiles(fullPath))
37
+ } else if (
38
+ entry.isFile() &&
39
+ (TEXT_EXTENSIONS.has(path.extname(entry.name)) ||
40
+ TEXT_EXTENSIONS.has(entry.name))
41
+ ) {
42
+ results.push(fullPath)
43
+ }
44
+ }
45
+ return results
46
+ }
47
+
48
+ function replaceInFile(
49
+ filePath: string,
50
+ replacements: Record<string, string>,
51
+ ): void {
52
+ const original = fs.readFileSync(filePath, 'utf-8')
53
+ const updated = Object.entries(replacements).reduce(
54
+ (src, [placeholder, value]) => src.replaceAll(placeholder, value),
55
+ original,
56
+ )
57
+ if (updated !== original) fs.writeFileSync(filePath, updated, 'utf-8')
58
+ }
59
+
60
+ export async function createCommand(_args: string[]) {
61
+ const projectRoot = process.cwd()
62
+ const pkgPath = path.join(projectRoot, 'package.json')
63
+
64
+ let pkg: Record<string, unknown>
65
+ try {
66
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
67
+ } catch (error) {
68
+ console.error(
69
+ `Failed to read or parse package.json at ${pkgPath}. ` +
70
+ 'Make sure you are running this command from an Edge App project root.',
71
+ )
72
+ if (error instanceof Error && error.message) {
73
+ console.error(`Details: ${error.message}`)
74
+ }
75
+ process.exitCode = 1
76
+ return
77
+ }
78
+
79
+ const rawName =
80
+ typeof pkg.name === 'string' && pkg.name.length > 0
81
+ ? pkg.name
82
+ : path.basename(projectRoot) || 'my-edge-app'
83
+ const appName = rawName.replace(/^@[^/]+\//, '').replace(/\//g, '-')
84
+ const appTitle = toTitleCase(appName)
85
+ const appDescription = `${appTitle} - Screenly Edge App`
86
+
87
+ const replacements: Record<string, string> = {
88
+ '{{APP_NAME}}': appName,
89
+ '{{APP_TITLE}}': appTitle,
90
+ '{{APP_DESCRIPTION}}': appDescription,
91
+ }
92
+
93
+ console.log(`\nInitializing Edge App: ${appTitle}`)
94
+
95
+ for (const filePath of walkTextFiles(projectRoot)) {
96
+ replaceInFile(filePath, replacements)
97
+ }
98
+
99
+ const updatedPkg = { ...pkg }
100
+ delete updatedPkg['bun-create']
101
+ fs.writeFileSync(pkgPath, JSON.stringify(updatedPkg, null, 2) + '\n', 'utf-8')
102
+
103
+ console.log(`
104
+ Done! Your Edge App is ready.
105
+
106
+ Next steps:
107
+ 1. Add an id field to screenly.yml and screenly_qc.yml.
108
+
109
+ 2. Install dependencies:
110
+ bun install
111
+
112
+ 3. Start the dev server:
113
+ bun run dev
114
+
115
+ 4. Deploy when ready:
116
+ bun run deploy
117
+ `)
118
+ }
@@ -0,0 +1,4 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M38.6202 23.2384C38.6202 20.4784 36.4002 18.3184 33.7002 18.3184C33.1002 18.3184 32.5602 18.4384 32.0202 18.6184C31.8402 16.5784 30.1602 14.8984 28.0602 14.8984C25.8402 14.8984 24.0402 16.6984 24.0402 18.9184C24.0402 19.3984 24.1602 19.8784 24.2802 20.2984C24.1002 20.2384 23.8602 20.2384 23.6802 20.2384C21.4602 20.2384 19.6602 22.0384 19.6602 24.2584C19.6602 26.4184 21.4002 28.2184 23.5602 28.2784H33.8802C36.5202 27.9784 38.6202 25.8784 38.6202 23.2384Z" fill="white" stroke="white" stroke-width="0.72" stroke-linejoin="round"/>
3
+ <path d="M47.7001 34.4C47.7001 29.8 44.0001 26.2 39.5001 26.2C38.5001 26.2 37.6001 26.4 36.7001 26.7C36.4001 23.3 33.6001 20.5 30.1001 20.5C26.4001 20.5 23.4001 23.5 23.4001 27.2C23.4001 28 23.6001 28.8 23.8001 29.5C23.5001 29.4 23.1001 29.4 22.8001 29.4C19.1001 29.4 16.1001 32.4 16.1001 36.1C16.1001 39.7 19.0001 42.7 22.6001 42.8H39.8001C44.2001 42.3 47.7001 38.8 47.7001 34.4Z" fill="white" stroke="white" stroke-width="1.2" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M23.3 21.5L24 22.7L25.2 23.3L24 24L23.3 25.2L22.7 24L21.5 23.3L22.7 22.7L23.3 21.5Z" fill="#FFA500"/>
3
+ <path d="M43.3 31.5L44 32.7L45.2 33.3L44 34L43.3 35.2L42.7 34L41.5 33.3L42.7 32.7L43.3 31.5Z" fill="#FFA500"/>
4
+ <path d="M34.5 33.1992C34.5 29.4992 36.5 26.2992 39.5 24.4992C38 23.5992 36.3 23.1992 34.5 23.1992C29 23.1992 24.5 27.6992 24.5 33.1992C24.5 38.6992 29 43.1992 34.5 43.1992C36.3 43.1992 38 42.6992 39.5 41.8992C36.5 40.1992 34.5 36.8992 34.5 33.1992Z" fill="#FFA500" stroke="#FFA500" stroke-width="2" stroke-linejoin="round"/>
5
+ </svg>
@@ -0,0 +1,11 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M32 41V44" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
3
+ <path d="M25.636 38.3633L23.5147 40.4846" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
4
+ <path d="M23 32H20" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
5
+ <path d="M25.636 25.6367L23.5147 23.5154" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
6
+ <path d="M32 23V20" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
7
+ <path d="M38.364 25.6367L40.4853 23.5154" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
8
+ <path d="M41 32H44" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
9
+ <path d="M38.364 38.3633L40.4853 40.4846" stroke="#FFA500" stroke-width="2" stroke-linecap="round"/>
10
+ <path d="M32 37C34.7614 37 37 34.7614 37 32C37 29.2386 34.7614 27 32 27C29.2386 27 27 29.2386 27 32C27 34.7614 29.2386 37 32 37Z" fill="#FFA500" stroke="#FFA500" stroke-width="2"/>
11
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M77.2403 46.4798C77.2403 40.9598 72.8003 36.6398 67.4003 36.6398C66.2003 36.6398 65.1203 36.8798 64.0403 37.2398C63.6803 33.1598 60.3203 29.7998 56.1203 29.7998C51.6803 29.7998 48.0803 33.3998 48.0803 37.8398C48.0803 38.7998 48.3203 39.7598 48.5603 40.5998C48.2003 40.4798 47.7203 40.4798 47.3603 40.4798C42.9203 40.4798 39.3203 44.0798 39.3203 48.5198C39.3203 52.8398 42.8003 56.4398 47.1203 56.5598H67.7603C73.0403 55.9598 77.2403 51.7598 77.2403 46.4798Z" fill="white" stroke="white" stroke-width="1.44" stroke-linejoin="round"/>
3
+ <path d="M95.4002 68.8C95.4002 59.6 88.0002 52.4 79.0002 52.4C77.0002 52.4 75.2002 52.8 73.4002 53.4C72.8002 46.6 67.2002 41 60.2002 41C52.8002 41 46.8002 47 46.8002 54.4C46.8002 56 47.2002 57.6 47.6002 59C47.0002 58.8 46.2002 58.8 45.6002 58.8C38.2002 58.8 32.2002 64.8 32.2002 72.2C32.2002 79.4 38.0002 85.4 45.2002 85.6H79.6002C88.4002 84.6 95.4002 77.6 95.4002 68.8Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M95.4002 68.8C95.4002 59.6 88.0002 52.4 79.0002 52.4C77.0002 52.4 75.2002 52.8 73.4002 53.4C72.8002 46.6 67.2002 41 60.2002 41C52.8002 41 46.8002 47 46.8002 54.4C46.8002 56 47.2002 57.6 47.6002 59C47.0002 58.8 46.2002 58.8 45.6002 58.8C38.2002 58.8 32.2002 64.8 32.2002 72.2C32.2002 79.4 38.0002 85.4 45.2002 85.6H79.6002C88.4002 84.6 95.4002 77.6 95.4002 68.8Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
3
+ <path d="M55.8359 91.8867L53.0576 107.644" stroke="white" stroke-width="4" stroke-linecap="round" stroke-dasharray="8 14"/>
4
+ <path d="M68.3477 90.0312L65.5693 105.788" stroke="white" stroke-width="4" stroke-linecap="round" stroke-dasharray="8 14"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M95.4002 68.8C95.4002 59.6 88.0002 52.4 79.0002 52.4C77.0002 52.4 75.2002 52.8 73.4002 53.4C72.8002 46.6 67.2002 41 60.2002 41C52.8002 41 46.8002 47 46.8002 54.4C46.8002 56 47.2002 57.6 47.6002 59C47.0002 58.8 46.2002 58.8 45.6002 58.8C38.2002 58.8 32.2002 64.8 32.2002 72.2C32.2002 79.4 38.0002 85.4 45.2002 85.6H79.6002C88.4002 84.6 95.4002 77.6 95.4002 68.8Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
3
+ <path d="M61.835 89.8857L59.0566 105.643" stroke="white" stroke-width="4" stroke-linecap="round" stroke-dasharray="8 14"/>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M95.2 59.8C95.2 50.6 87.8 43.4 78.8 43.4C76.8 43.4 75 43.8 73.2 44.4C72.6 37.6 67 32 60 32C52.6 32 46.6 38 46.6 45.4C46.6 47 47 48.6 47.4 50C46.8 49.8 46 49.8 45.4 49.8C38 49.8 32 55.8 32 63.2C32 70.4 37.8 76.4 45 76.6H79.4C88.2 75.6 95.2 68.6 95.2 59.8Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
3
+ <path d="M34 84C53 84.0032 81 83.9987 91 84.0013" stroke="white" stroke-width="3" stroke-linecap="round"/>
4
+ <path d="M34 92C44.6667 92.0032 60.386 91.9987 66 92.0013" stroke="white" stroke-width="3" stroke-linecap="round"/>
5
+ <path d="M34 100C44.6667 100.003 60.386 99.9987 66 100.001" stroke="white" stroke-width="3" stroke-linecap="round"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M95.2 59.8C95.2 50.6 87.8 43.4 78.8 43.4C76.8 43.4 75 43.8 73.2 44.4C72.6 37.6 67 32 60 32C52.6 32 46.6 38 46.6 45.4C46.6 47 47 48.6 47.4 50C46.8 49.8 46 49.8 45.4 49.8C38 49.8 32 55.8 32 63.2C32 70.4 37.8 76.4 45 76.6H79.4C88.2 75.6 95.2 68.6 95.2 59.8Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
3
+ <path d="M34 84C53 84.0032 81 83.9987 91 84.0013" stroke="white" stroke-width="3" stroke-linecap="round"/>
4
+ <path d="M34 92C44.6667 92.0032 60.386 91.9987 66 92.0013" stroke="white" stroke-width="3" stroke-linecap="round"/>
5
+ <path d="M34 100C44.6667 100.003 60.386 99.9987 66 100.001" stroke="white" stroke-width="3" stroke-linecap="round"/>
6
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M77.2804 30.4004L78.4004 32.3204L80.3204 33.2804L78.4004 34.4004L77.2804 36.3204L76.3204 34.4004L74.4004 33.2804L76.3204 32.3204L77.2804 30.4004Z" fill="#FFA500"/>
3
+ <path d="M109.28 46.4004L110.4 48.3204L112.32 49.2804L110.4 50.4004L109.28 52.3204L108.32 50.4004L106.4 49.2804L108.32 48.3204L109.28 46.4004Z" fill="#FFA500"/>
4
+ <path d="M95.2002 49.1211C95.2002 43.2011 98.4002 38.0811 103.2 35.2011C100.8 33.7611 98.0802 33.1211 95.2002 33.1211C86.4002 33.1211 79.2002 40.3211 79.2002 49.1211C79.2002 57.9211 86.4002 65.1211 95.2002 65.1211C98.0802 65.1211 100.8 64.3211 103.2 63.0411C98.4002 60.3211 95.2002 55.0411 95.2002 49.1211Z" fill="#FFA500" stroke="#FFA500" stroke-width="3.2" stroke-linejoin="round"/>
5
+ <path d="M89.3453 49.9481C89.3453 43.3472 84.0359 38.1813 77.5785 38.1813C76.1435 38.1813 74.852 38.4683 73.5605 38.8988C73.13 34.0199 69.1121 30.002 64.0897 30.002C58.7803 30.002 54.4753 34.3069 54.4753 39.6163C54.4753 40.7643 54.7623 41.9123 55.0493 42.9168C54.6188 42.7733 54.0448 42.7733 53.6143 42.7733C48.3049 42.7733 44 47.0782 44 52.3876C44 57.5535 48.1614 61.8585 53.3274 62.002H78.009C84.3229 61.2845 89.3453 56.262 89.3453 49.9481Z" fill="white" stroke="white" stroke-width="1.72197" stroke-linejoin="round"/>
6
+ <path d="M95.4002 68.802C95.4002 59.602 88.0002 52.402 79.0002 52.402C77.0002 52.402 75.2002 52.802 73.4002 53.402C72.8002 46.602 67.2002 41.002 60.2002 41.002C52.8002 41.002 46.8002 47.002 46.8002 54.402C46.8002 56.002 47.2002 57.602 47.6002 59.002C47.0002 58.802 46.2002 58.802 45.6002 58.802C38.2002 58.802 32.2002 64.802 32.2002 72.202C32.2002 79.402 38.0002 85.402 45.2002 85.602H79.6002C88.4002 84.602 95.4002 77.602 95.4002 68.802Z" fill="white" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
7
+ </svg>