@thebase/ui 1.1.1 → 1.1.3

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 (45) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +21 -6
  3. package/bin/create-baseui-app.mjs +464 -4
  4. package/dist/apidoc/img/bash-plain.svg +1 -0
  5. package/dist/apidoc/img/c-original.svg +1 -0
  6. package/dist/apidoc/img/csharp-original.svg +1 -0
  7. package/dist/apidoc/img/dart-original.svg +1 -0
  8. package/dist/apidoc/img/go-original-wordmark.svg +1 -0
  9. package/dist/apidoc/img/java-original.svg +1 -0
  10. package/dist/apidoc/img/javascript-original.svg +1 -0
  11. package/dist/apidoc/img/kotlin-original.svg +10 -0
  12. package/dist/apidoc/img/lua.svg +7 -0
  13. package/dist/apidoc/img/nodejs-original.svg +1 -0
  14. package/dist/apidoc/img/objectivec-plain.svg +1 -0
  15. package/dist/apidoc/img/php-original.svg +2 -0
  16. package/dist/apidoc/img/python-original.svg +1 -0
  17. package/dist/apidoc/img/ruby-plain.svg +1 -0
  18. package/dist/apidoc/img/rust-original.svg +1 -0
  19. package/dist/apidoc/img/swift-original.svg +1 -0
  20. package/dist/apidoc/img/windows8-original.svg +1 -0
  21. package/dist/baseui.esm.js +33 -11
  22. package/dist/baseui.min.css +1 -1
  23. package/dist/baseui.min.js +33 -11
  24. package/dist/baseui.pwa.esm.js +53 -0
  25. package/dist/baseui.registry.json +42 -5
  26. package/dist/baseui.templates.xml +469 -1
  27. package/dist/pwa-icons/apple-touch-icon.png +0 -0
  28. package/dist/pwa-icons/favicon-16x16.png +0 -0
  29. package/dist/pwa-icons/favicon-32x32.png +0 -0
  30. package/dist/pwa-icons/favicon.ico +0 -0
  31. package/dist/pwa-icons/icon-192.png +0 -0
  32. package/dist/pwa-icons/icon-512.png +0 -0
  33. package/dist/types/pwa.d.ts +45 -0
  34. package/docs/analytics.md +4 -4
  35. package/docs/blocks.md +2 -1
  36. package/docs/components/apidoc.md +66 -0
  37. package/docs/components/input.md +17 -0
  38. package/docs/components/markdown.md +1 -1
  39. package/docs/icons.md +1 -1
  40. package/docs/installation.md +2 -2
  41. package/docs/owl-components.md +1 -1
  42. package/docs/registry.md +1 -1
  43. package/docs/theming.md +1 -1
  44. package/docs/usage-guide.md +43 -4
  45. package/package.json +8 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [v1.1.3] - 2026-10-13
8
+
9
+ ### Added
10
+ - `ApiDoc` component (`b-ui="apidoc"` / pure Owl `ApiDoc`): renders an OpenAPI 3 JSON document as browsable API documentation — grouped sidebar, per-operation parameters, request body, responses, callbacks, a Try It panel with request samples in 17 languages, and an Authorize dialog (API key, HTTP basic, bearer, OpenID Connect, OAuth2 flows). Props include `src`, `spec`, `serverUrl`, `theme`, `themeToggle`, `hash`, `operation`, `tryIt`, `authorize`, `languages`, `language`, `assetBase`, `oauthRedirectUri`, `height`; events `baseui:load`, `baseui:error`, `baseui:navigate`. Built from BaseUI's own `Button`, `Badge`, `Dialog`, `Tabs`, `Breadcrumb`, `Input`, `Textarea`, `NativeSelect` and `Checkbox`. See `docs/components/apidoc.md`.
11
+ - `apidoc-01` block (full-page API reference) and the `examples/apidoc/` demo with a Petstore spec.
12
+ - `Input`: Material-style outlined mode with `label`, `supportingText`, `errorText`, `leadingIcon`/`trailingIcon` and a `maxLength` counter (documented in `docs/components/input.md`).
13
+ - PWA toolkit: default logo and favicon set (`dist/pwa-icons/`: `favicon.ico`, 16/32 px favicons, `apple-touch-icon.png`, `icon-192.png`, `icon-512.png`). `create-baseui-app pwa` copies it into `<path>/icons/` without overwriting existing files, and the default manifest icons and precache list point at it. New optional `iconsBase` (manifest + service worker) and `manifestUrl` (service worker) config keys.
14
+ - Examples: the examples/landing site is now an installable, offline-capable PWA (`examples/pwa/`, generated from `baseui.pwa.config.js`). The service worker is served from `/examples-sw.js`; `deploy_landing.sh` stages it at the site root. New `momo_demo/` static `b-ui` business-admin demo.
15
+ - Build: ApiDoc's language logos are copied to `dist/apidoc/img/` and published (`dist/apidoc` added to `package.json` `files`).
16
+
17
+ ### Changed
18
+ - PWA toolkit: the default precache list no longer includes `/favicon.svg`; it precaches the default icon files instead.
19
+ - `npm run dev` server: serves `.ico` as `image/x-icon` and `.mjs` as JavaScript.
20
+ - Docs and examples: production pin examples now use `@1.1.3`; component counts in `docs/owl-components.md` and `docs/registry.md` now say 74.
21
+
22
+ ## [v1.1.2] - 2026-10-06
23
+
24
+ Backfilled entry (1.1.2 shipped without release notes).
25
+
26
+ ### Added
27
+ - PWA toolkit: `@thebase/ui/pwa` (`dist/baseui.pwa.esm.js`, types in `dist/types/pwa.d.ts`) exports `buildManifest` and `buildServiceWorker`.
28
+ - `create-baseui-app` CLI: `create`, `init`, `convert` and `pwa` commands to scaffold a BaseUI project and generate `manifest.webmanifest` + `sw.js`. See `docs/usage-guide.md` §5b and `docs/components/pwa.md`.
29
+ - Examples: the landing page shows PWA install and visitor tracking (`examples/tracking.js`).
30
+
5
31
  ## [v1.1.1] - 2026-10-04
6
32
 
7
33
  ### Added
package/README.md CHANGED
@@ -4,7 +4,7 @@ The Base UI is a CDN-installable UI kit built with Bootstrap 5 styling and Owl-e
4
4
 
5
5
  Its strong points: **no build step** — every component runs straight from a CDN `<script>`/import map, nothing to compile; **modular component design** — each component is a self-contained unit you import or mark up individually, not a monolith; **vanilla JavaScript/TypeScript** — no framework lock-in, no JSX, no compiler-specific syntax to learn; and this combination makes it well suited to building **WYSIWYG website builders**, where components must mount, unmount, and re-render dynamically from user-driven markup or drag-and-drop edits rather than a fixed build-time component tree.
6
6
 
7
- Both APIs load straight from a CDN — jsDelivr or unpkg — no build step, no self-hosting required. The snippets below use the `@latest` tag for readability; pin an exact version instead (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) for any production page, since `@latest` can change what your page loads the moment a new version ships. Pure Owl usage:
7
+ Both APIs load straight from a CDN — jsDelivr or unpkg — no build step, no self-hosting required. The snippets below use the `@latest` tag for readability; pin an exact version instead (e.g. `@1.1.3`, or `@1` to take only non-breaking updates) for any production page, since `@latest` can change what your page loads the moment a new version ships. Pure Owl usage:
8
8
 
9
9
  ```html
10
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/baseui.min.css">
@@ -50,7 +50,7 @@ If you have no Owl app, use the static `b-ui` markup adapter instead — one sty
50
50
 
51
51
  `dist/baseui.min.js`/`baseui.esm.js` are fully self-contained and back both APIs above: Bootstrap's JS bundle, the Owl runtime, every pure Owl component class, and the theme helper are all embedded in load order (Bootstrap, then Owl, then BaseUI) — one file, nothing else to load first. There is no separate `baseui.owl.*`/`baseui.components.esm.js`/`baseui.theme.esm.js`. (`dist/bootstrap.bundle.min.js` still ships standalone, unmodified, for a project that wants vendored Bootstrap JS on its own — BaseUI itself never needs it loaded separately.)
52
52
 
53
- BaseUI exposes every component (73 in the current registry) as a real Owl component class — see [Pure Owl Components](docs/owl-components.md) for the complete API, npm/bundler usage, and mixing with the static adapter.
53
+ BaseUI exposes every component (74 in the current registry) as a real Owl component class — see [Pure Owl Components](docs/owl-components.md) for the complete API, npm/bundler usage, and mixing with the static adapter.
54
54
 
55
55
  For app-level theme controls, import `createThemeController`/`BaseUITheme` as `@base/theme` in browser import maps (pointing at the same `dist/baseui.esm.js`) or straight from `@thebase/ui` in npm/bundler apps, to create persisted light/dark toggles like the documentation pages use.
56
56
 
@@ -70,9 +70,24 @@ BaseUI also ships an optional, cookieless web-analytics service (from 1.1.0). It
70
70
 
71
71
  From npm, use `import { init, track } from "@thebase/ui/analytics"`; for a classic `<script>` tag, `dist/baseui.analytics.min.js` sets `window.thebase.analytics`. It covers automatic and SPA pageviews, engagement (time on page and scroll depth), outbound links, file downloads, form submissions, and `thebase-event-*` class tagging. See [Analytics](docs/analytics.md) for every option, the payload format, opt-out, and sample Go Fiber / FastAPI backends. [`examples/analytics/`](examples/analytics/) holds a ready-to-run BaseUI dashboard for the collected data.
72
72
 
73
+ ## PWA toolkit and CLI
74
+
75
+ BaseUI can scaffold and ship installable apps (from 1.1.2). The `create-baseui-app` CLI creates a static-importmap or Vite project (`create`, `init`, `convert`) and generates `manifest.webmanifest`, `sw.js` and a default icon set (`pwa`, icons from 1.1.3); the same generators are exported from `@thebase/ui/pwa` as `buildManifest` / `buildServiceWorker`.
76
+
77
+ ```sh
78
+ npx create-baseui-app create my-app
79
+ npx create-baseui-app pwa . --config baseui.pwa.config.js
80
+ ```
81
+
82
+ See [Usage Guide §5b](docs/usage-guide.md) and the [PWA component](docs/components/pwa.md). The examples site itself is an installable PWA (`examples/pwa/`).
83
+
84
+ ## ApiDoc
85
+
86
+ New in 1.1.3: `ApiDoc` (`b-ui="apidoc"`) renders an OpenAPI 3 JSON file as browsable API docs — grouped sidebar, parameters, request bodies, responses, a Try It panel with code samples in 17 languages, and an Authorize dialog. See [ApiDoc](docs/components/apidoc.md), the `apidoc-01` block, and the live demo in [`examples/apidoc/`](examples/apidoc/).
87
+
73
88
  ## Release notes
74
89
 
75
- See [CHANGELOG.md](CHANGELOG.md) for the full version history. Current version: **1.1.1**.
90
+ See [CHANGELOG.md](CHANGELOG.md) for the full version history. Current version: **1.1.3**.
76
91
 
77
92
  ## Versioning policy
78
93
 
@@ -91,7 +106,7 @@ What each release type means:
91
106
 
92
107
  Anything prefixed `_`, undocumented internals, and the `docs/`/`examples/` sources are not part of the public API. Neither are the source-level authoring helpers described in [Component Authoring](docs/component-authoring.md) (`optional()`, `slotProps`, the `open-state.js` hooks, `bindBootstrapOverlay()`): they aren't exported from the package and may change in any release. Deprecated APIs keep working until the next major and are flagged in the CHANGELOG first.
93
108
 
94
- Pinning: use an exact version (`@1.1.1`) when you need byte-identical output, or the major range (`@1` on jsDelivr/unpkg, `^1.0.0` in npm) to receive fixes and features without breaking changes. Avoid `@latest` in production — it will move to the next major.
109
+ Pinning: use an exact version (`@1.1.3`) when you need byte-identical output, or the major range (`@1` on jsDelivr/unpkg, `^1.0.0` in npm) to receive fixes and features without breaking changes. Avoid `@latest` in production — it will move to the next major.
95
110
 
96
111
  ## Project Documents
97
112
 
@@ -129,7 +144,7 @@ npm run build
129
144
  npm run dev
130
145
  ```
131
146
 
132
- Open `examples/index.html` through the dev server to test the browser build.
147
+ Open `examples/index.html` through the dev server to test the browser build. Standalone demos: `examples/apidoc/` (ApiDoc with a Petstore spec), `examples/analytics/` (dashboard + Go/FastAPI backends), and `momo_demo/` (static `b-ui` business-admin page).
133
148
 
134
149
  `examples/index.html` is the single Owl app shell for docs, the component gallery, the block gallery, icons, and the chart catalog, all reachable through one `main-nav` and hash routes (`#/docs`, `#/components`, `#/blocks`, `#/icons`, `#/chart`). Old bookmarked URLs like `/docs` and `/blocks` redirect to their hash-route equivalent. Each block has a shadcn-style Preview/Code/responsive viewer plus its source stylesheet colocated in `examples/blocks/`. `examples/charts/index.html` mirrors the current shadcn chart catalog with interactive BaseUI examples for area, bar, line, pie, radar, radial, and tooltip charts, and is embedded inline at `#/chart`.
135
150
 
@@ -145,6 +160,6 @@ BaseUI includes the full Lucide SVG icon set as a built-in icon library. Use `<s
145
160
 
146
161
  ## Distribution
147
162
 
148
- The npm package publishes the built `dist/` artifacts plus package metadata. The browser build bundles Bootstrap CSS, and `dist/baseui.min.js`/`baseui.esm.js` embed Bootstrap's JS bundle, the Owl runtime, every pure Owl component class, and the theme helper directly (Bootstrap then Owl then BaseUI, in that load order) — consumers install BaseUI with one stylesheet and one script tag for either API. Bootstrap's JS drives every component that has a Bootstrap JS equivalent (modal, dropdown, collapse, tab, toast, carousel, tooltip, popover, alert); BaseUI's `.behavior.js`/`runtime/*.js` helpers wrap Bootstrap's JS API instead of reimplementing it, for both the static and pure-Owl APIs. `dist/bootstrap.bundle.min.js` also ships standalone (unmodified) and `dist/baseui.icons.esm.js` stays a separate, deliberate exception (2000+ generated icon components would blow the main bundle's size budget), as does the opt-in analytics service (`dist/baseui.analytics.esm.js`/`.min.js`, so pages that don't track pay nothing for it) — everything else is one file. Every shipped CSS/JS artifact is minified — there are no unminified twins to keep in sync.
163
+ The npm package publishes the built `dist/` artifacts plus package metadata. The browser build bundles Bootstrap CSS, and `dist/baseui.min.js`/`baseui.esm.js` embed Bootstrap's JS bundle, the Owl runtime, every pure Owl component class, and the theme helper directly (Bootstrap then Owl then BaseUI, in that load order) — consumers install BaseUI with one stylesheet and one script tag for either API. Bootstrap's JS drives every component that has a Bootstrap JS equivalent (modal, dropdown, collapse, tab, toast, carousel, tooltip, popover, alert); BaseUI's `.behavior.js`/`runtime/*.js` helpers wrap Bootstrap's JS API instead of reimplementing it, for both the static and pure-Owl APIs. `dist/bootstrap.bundle.min.js` also ships standalone (unmodified) and `dist/baseui.icons.esm.js` stays a separate, deliberate exception (2000+ generated icon components would blow the main bundle's size budget), as do the ApiDoc language logos (`dist/apidoc/img/`, loaded on demand) and the opt-in analytics service (`dist/baseui.analytics.esm.js`/`.min.js`, so pages that don't track pay nothing for it) — everything else is one file. Every shipped CSS/JS artifact is minified — there are no unminified twins to keep in sync.
149
164
 
150
165
  The published tarball also carries `docs/*.md` and `docs/components/` (see the "Project Documents" section above), `README.md`, `CHANGELOG.md`, and `LICENSE` — `docs/v2/` and `docs/component-authoring.md` are deliberately excluded from `package.json`'s `files` list since they're development-time-only references for working inside this repo.
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
5
6
 
6
7
  const ownPackageJson = JSON.parse(
7
8
  readFileSync(new URL("../package.json", import.meta.url), "utf8"),
@@ -16,13 +17,18 @@ function toPackageName(name) {
16
17
  .replace(/^-+|-+$/g, "") || "baseui-app";
17
18
  }
18
19
 
19
- /** Builds the fixed set of Vite + BaseUI scaffold files for a project named `appName`. */
20
- function buildTemplateFiles(appName, baseuiVersion) {
21
- const title = appName
20
+ /** Turns a kebab/snake-case app name into a human-readable title, e.g. "qr-page" -> "Qr Page". */
21
+ function toTitle(appName) {
22
+ return appName
22
23
  .split(/[-_]/)
23
24
  .filter(Boolean)
24
25
  .map((word) => word[0].toUpperCase() + word.slice(1))
25
26
  .join(" ");
27
+ }
28
+
29
+ /** Builds the fixed set of Vite + BaseUI scaffold files for a project named `appName`. */
30
+ function buildTemplateFiles(appName, baseuiVersion) {
31
+ const title = toTitle(appName);
26
32
 
27
33
  return {
28
34
  "package.json": JSON.stringify(
@@ -140,6 +146,375 @@ npm run dev
140
146
  };
141
147
  }
142
148
 
149
+ /** Builds one page's HTML shell: mounts #app and loads that page's own entry script. */
150
+ function pageHtml(title, entryScript) {
151
+ return `<!doctype html>
152
+ <html lang="en" b-theme="light">
153
+ <head>
154
+ <meta charset="utf-8" />
155
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
156
+ <title>${title}</title>
157
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
158
+ </head>
159
+ <body class="bg-body text-body">
160
+ <div id="app"></div>
161
+ <script type="module" src="${entryScript}"></script>
162
+ </body>
163
+ </html>
164
+ `;
165
+ }
166
+
167
+ /** Builds one page's entry script: loads its templates/component and mounts via the shared mountPage helper. */
168
+ function pageMain(componentName, templatesImportPath, componentImportPath) {
169
+ return `import "@thebase/ui/baseui.css";
170
+ import pageTemplates from "${templatesImportPath}?raw";
171
+ import { ${componentName} } from "${componentImportPath}";
172
+ import { mountPage } from "../mount-page.js";
173
+ import "../style.css";
174
+
175
+ await mountPage(${componentName}, pageTemplates);
176
+ `;
177
+ }
178
+
179
+ /** Builds a 4-page Vite + BaseUI starter (landing, login, signup, an authenticated home shell) for a
180
+ * project named `appName` — each page is its own Owl root mounted by its own HTML entry, composed
181
+ * from the same blocks as `examples/blocks/login-03.html`, `signup-03.html` and `sidebar-02.html`. */
182
+ function buildStarterFiles(appName, baseuiVersion) {
183
+ const title = toTitle(appName);
184
+
185
+ return {
186
+ "package.json": JSON.stringify(
187
+ {
188
+ name: appName,
189
+ private: true,
190
+ version: "0.0.1",
191
+ type: "module",
192
+ scripts: {
193
+ dev: "vite",
194
+ build: "vite build",
195
+ preview: "vite preview",
196
+ },
197
+ dependencies: {
198
+ "@thebase/ui": `^${baseuiVersion}`,
199
+ },
200
+ devDependencies: {
201
+ vite: "^8.2.1",
202
+ },
203
+ },
204
+ null,
205
+ 2,
206
+ ) + "\n",
207
+
208
+ "vite.config.js": `import { defineConfig } from "vite";
209
+ import { fileURLToPath } from "node:url";
210
+
211
+ /** Resolves a file in this config's own directory, Node-ESM-safe (no __dirname in ESM). */
212
+ const here = (file) => fileURLToPath(new URL(file, import.meta.url));
213
+
214
+ export default defineConfig({
215
+ server: { open: true },
216
+ build: {
217
+ rollupOptions: {
218
+ input: {
219
+ main: here("./index.html"),
220
+ login: here("./login.html"),
221
+ signup: here("./signup.html"),
222
+ home: here("./home.html"),
223
+ },
224
+ },
225
+ },
226
+ });
227
+ `,
228
+
229
+ ".gitignore": `node_modules/\ndist/\n`,
230
+
231
+ "public/favicon.svg": `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
232
+ fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
233
+ <rect width="24" height="24" rx="5" fill="#0f172a" stroke="none" />
234
+ <path d="M8 12h8" />
235
+ <path d="M12 8v8" />
236
+ </svg>
237
+ `,
238
+
239
+ "src/style.css": `/* App-specific styles layered on top of @thebase/ui/baseui.css go here. */\n`,
240
+
241
+ "src/mount-page.js": `import { mount } from "@thebase/ui";
242
+ import baseuiTemplates from "@thebase/ui/dist/baseui.templates.xml?raw";
243
+
244
+ /** Both files are full <templates>...</templates> documents; strip each one's outer wrapper before
245
+ * joining them, otherwise the merged string has two root elements and Owl's XML parser throws. */
246
+ const stripTemplatesWrapper = (xml) =>
247
+ xml.replace(/^\\s*<templates[^>]*>/, "").replace(/<\\/templates>\\s*$/, "");
248
+
249
+ /** Merges BaseUI's component templates with one page's own templates and mounts its root component. */
250
+ export async function mountPage(Root, pageTemplatesXml) {
251
+ const templates = \`<templates>\${stripTemplatesWrapper(baseuiTemplates)}\${stripTemplatesWrapper(pageTemplatesXml)}</templates>\`;
252
+ await mount(Root, document.getElementById("app"), { templates });
253
+ }
254
+ `,
255
+
256
+ "index.html": pageHtml(title, "/src/landing/main.js"),
257
+ "src/landing/main.js": pageMain("Landing", "./Landing.xml", "./Landing.js"),
258
+ "src/landing/Landing.js": `import { Component } from "@thebase/ui";
259
+ import { Button, Card, CardBody, CardHeader, CardTitle, Icon } from "@thebase/ui";
260
+
261
+ /** Marketing landing page: header with a login link, hero pitch, feature grid, footer. */
262
+ export class Landing extends Component {
263
+ static template = "app.Landing";
264
+ static components = { Button, Card, CardBody, CardHeader, CardTitle, Icon };
265
+ }
266
+ `,
267
+ "src/landing/Landing.xml": `<templates xml:space="preserve">
268
+ <t t-name="app.Landing">
269
+ <div class="d-flex flex-column min-vh-100">
270
+ <header class="border-bottom">
271
+ <div class="container d-flex align-items-center justify-content-between py-3">
272
+ <a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
273
+ <Icon name="'gallery-vertical-end'"/>
274
+ ${title}
275
+ </a>
276
+ <Button variant="'outline'" onClick="() => window.location.assign('/login.html')">Login</Button>
277
+ </div>
278
+ </header>
279
+
280
+ <section class="container py-5 text-center">
281
+ <h1 class="display-5 fw-bold mb-3">Build faster with ${title}</h1>
282
+ <p class="lead text-muted mx-auto mb-4" style="max-width: 40rem">
283
+ A starter scaffold wired up with BaseUI — pure Owl components, Bootstrap styling, and the pages you need to ship day one.
284
+ </p>
285
+ <div class="d-flex justify-content-center gap-3">
286
+ <Button icon="'rocket'" onClick="() => window.location.assign('/signup.html')">Get started</Button>
287
+ <Button variant="'outline'" onClick="() => window.location.assign('/login.html')">Login</Button>
288
+ </div>
289
+ </section>
290
+
291
+ <section class="container py-5">
292
+ <div class="row g-4">
293
+ <div class="col-12 col-md-4">
294
+ <Card className="'h-100'">
295
+ <CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'zap'"/><CardTitle>Fast by default</CardTitle></CardHeader>
296
+ <CardBody>Vite dev server and production build, pre-wired to BaseUI's CDN or local dist.</CardBody>
297
+ </Card>
298
+ </div>
299
+ <div class="col-12 col-md-4">
300
+ <Card className="'h-100'">
301
+ <CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'layout-dashboard'"/><CardTitle>Pages included</CardTitle></CardHeader>
302
+ <CardBody>Landing, login, signup and an authenticated home shell, ready to extend.</CardBody>
303
+ </Card>
304
+ </div>
305
+ <div class="col-12 col-md-4">
306
+ <Card className="'h-100'">
307
+ <CardHeader className="'d-flex align-items-center gap-2'"><Icon name="'component'"/><CardTitle>Pure Owl components</CardTitle></CardHeader>
308
+ <CardBody>Every page composes real BaseUI components — swap or extend without fighting the markup.</CardBody>
309
+ </Card>
310
+ </div>
311
+ </div>
312
+ </section>
313
+
314
+ <footer class="border-top mt-auto">
315
+ <div class="container py-4 d-flex align-items-center justify-content-between">
316
+ <small class="text-muted">© ${title}. All rights reserved.</small>
317
+ <a href="/login.html" class="small text-decoration-underline">Login</a>
318
+ </div>
319
+ </footer>
320
+ </div>
321
+ </t>
322
+ </templates>
323
+ `,
324
+
325
+ "login.html": pageHtml(`Login — ${title}`, "/src/login/main.js"),
326
+ "src/login/main.js": pageMain("Login", "./Login.xml", "./Login.js"),
327
+ "src/login/Login.js": `import { Component } from "@thebase/ui";
328
+ import { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label } from "@thebase/ui";
329
+
330
+ /** Login page (ported from examples/blocks/login-03.html): email/password card, redirects home on submit. */
331
+ export class Login extends Component {
332
+ static template = "app.Login";
333
+ static components = { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label };
334
+ }
335
+ `,
336
+ "src/login/Login.xml": `<templates xml:space="preserve">
337
+ <t t-name="app.Login">
338
+ <div class="d-flex flex-column align-items-center justify-content-center gap-4 min-vh-100 bg-body-tertiary p-4">
339
+ <div class="d-flex flex-column align-items-center gap-4 col-12 col-sm-8 col-md-6 col-lg-4">
340
+ <a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
341
+ <Icon name="'gallery-vertical-end'"/>
342
+ ${title}
343
+ </a>
344
+ <Card className="'w-100'">
345
+ <CardBody>
346
+ <form class="d-flex flex-column gap-4">
347
+ <div class="d-flex flex-column gap-2 text-center">
348
+ <h2 class="h4 mb-0">Login to your account</h2>
349
+ <p class="text-muted mb-0">Enter your email below to login to your account</p>
350
+ </div>
351
+ <FieldGroup>
352
+ <Field>
353
+ <Label>Email</Label>
354
+ <Input type="'email'" placeholder="'m@example.com'"/>
355
+ </Field>
356
+ <Field>
357
+ <div class="d-flex align-items-center justify-content-between">
358
+ <Label>Password</Label>
359
+ <a href="#" class="small text-decoration-underline">Forgot your password?</a>
360
+ </div>
361
+ <Input type="'password'"/>
362
+ </Field>
363
+ <Button icon="'log-in'" onClick="() => window.location.assign('/home.html')">Login</Button>
364
+ <Button variant="'outline'" icon="'mail'">Login with email</Button>
365
+ </FieldGroup>
366
+ <p class="text-center small mb-0">Don&apos;t have an account? <a href="/signup.html" class="text-decoration-underline">Sign up</a></p>
367
+ </form>
368
+ </CardBody>
369
+ </Card>
370
+ </div>
371
+ </div>
372
+ </t>
373
+ </templates>
374
+ `,
375
+
376
+ "signup.html": pageHtml(`Sign up — ${title}`, "/src/signup/main.js"),
377
+ "src/signup/main.js": pageMain("Signup", "./Signup.xml", "./Signup.js"),
378
+ "src/signup/Signup.js": `import { Component } from "@thebase/ui";
379
+ import { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label } from "@thebase/ui";
380
+
381
+ /** Signup page (ported from examples/blocks/signup-03.html): single email field, sends the user to login after submit. */
382
+ export class Signup extends Component {
383
+ static template = "app.Signup";
384
+ static components = { Button, Card, CardBody, Field, FieldGroup, Icon, Input, Label };
385
+ }
386
+ `,
387
+ "src/signup/Signup.xml": `<templates xml:space="preserve">
388
+ <t t-name="app.Signup">
389
+ <div class="d-flex flex-column align-items-center justify-content-center gap-4 min-vh-100 bg-body-tertiary p-4">
390
+ <div class="d-flex flex-column align-items-center gap-4 col-12 col-sm-8 col-md-6 col-lg-4">
391
+ <a href="/" class="d-inline-flex align-items-center gap-2 fw-medium text-decoration-none">
392
+ <Icon name="'gallery-vertical-end'"/>
393
+ ${title}
394
+ </a>
395
+ <Card className="'w-100'">
396
+ <CardBody>
397
+ <form class="d-flex flex-column gap-4">
398
+ <div class="d-flex flex-column gap-2 text-center">
399
+ <h2 class="h4 mb-0">Create an account</h2>
400
+ <p class="text-muted mb-0">Enter your email below to create your account</p>
401
+ </div>
402
+ <FieldGroup>
403
+ <Field>
404
+ <Label>Email</Label>
405
+ <Input type="'email'" placeholder="'m@example.com'"/>
406
+ </Field>
407
+ <Button icon="'user-plus'" onClick="() => window.location.assign('/login.html')">Sign up</Button>
408
+ <Button variant="'outline'" icon="'mail'">Sign up with email</Button>
409
+ </FieldGroup>
410
+ <p class="text-center small mb-0">Already have an account? <a href="/login.html" class="text-decoration-underline">Login</a></p>
411
+ </form>
412
+ </CardBody>
413
+ </Card>
414
+ </div>
415
+ </div>
416
+ </t>
417
+ </templates>
418
+ `,
419
+
420
+ "home.html": pageHtml(`Home — ${title}`, "/src/home/main.js"),
421
+ "src/home/main.js": pageMain("Home", "./Home.xml", "./Home.js"),
422
+ "src/home/Home.js": `import { Component, useState } from "@thebase/ui";
423
+ import {
424
+ Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
425
+ Button, Icon, InputGroup, InputGroupAddon, InputGroupInput, Separator,
426
+ Shell, ShellContent, ShellHeader, ShellMain, ShellNav, ShellSidebar, ShellSidebarHeader,
427
+ SidebarGroupCollapsible, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger,
428
+ } from "@thebase/ui";
429
+
430
+ /** Authenticated home shell (ported from examples/blocks/sidebar-02.html): collapsible sidebar nav
431
+ * + sticky header, reached after login/signup; owns the sidebar's collapsed/expanded state. */
432
+ export class Home extends Component {
433
+ static template = "app.Home";
434
+ static components = {
435
+ Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,
436
+ Button, Icon, InputGroup, InputGroupAddon, InputGroupInput, Separator,
437
+ Shell, ShellContent, ShellHeader, ShellMain, ShellNav, ShellSidebar, ShellSidebarHeader,
438
+ SidebarGroupCollapsible, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarTrigger,
439
+ };
440
+
441
+ setup() {
442
+ this.state = useState({ collapsed: false });
443
+ }
444
+ }
445
+ `,
446
+ "src/home/Home.xml": `<templates xml:space="preserve">
447
+ <t t-name="app.Home">
448
+ <Shell className="'border'" collapsed="state.collapsed" onCollapsedChange="(value) => { state.collapsed = value; }">
449
+ <ShellSidebar>
450
+ <ShellSidebarHeader>
451
+ <a href="/" class="d-flex align-items-center gap-2 text-decoration-none fw-medium">
452
+ <Icon name="'gallery-vertical-end'"/>
453
+ <span class="d-flex flex-column lh-sm">
454
+ <span>${title}</span>
455
+ <small class="text-body-secondary fw-normal">Dashboard</small>
456
+ </span>
457
+ </a>
458
+ </ShellSidebarHeader>
459
+ <InputGroup>
460
+ <InputGroupAddon><Icon name="'search'"/></InputGroupAddon>
461
+ <InputGroupInput type="'search'" placeholder="'Search...'"/>
462
+ </InputGroup>
463
+ <ShellNav className="'d-flex flex-column gap-2'">
464
+ <SidebarGroupCollapsible label="'Getting Started'" defaultOpen="true">
465
+ <SidebarMenu>
466
+ <SidebarMenuItem><SidebarMenuButton href="'#'" icon="'layout-dashboard'" active="true">Overview</SidebarMenuButton></SidebarMenuItem>
467
+ <SidebarMenuItem><SidebarMenuButton href="'#'" icon="'users'">Customers</SidebarMenuButton></SidebarMenuItem>
468
+ <SidebarMenuItem><SidebarMenuButton href="'#'" icon="'settings'">Settings</SidebarMenuButton></SidebarMenuItem>
469
+ </SidebarMenu>
470
+ </SidebarGroupCollapsible>
471
+ </ShellNav>
472
+ </ShellSidebar>
473
+ <ShellMain>
474
+ <ShellHeader className="'border-bottom p-3'">
475
+ <SidebarTrigger/>
476
+ <Separator orientation="'vertical'"/>
477
+ <Breadcrumb>
478
+ <BreadcrumbList>
479
+ <BreadcrumbItem><BreadcrumbPage>Overview</BreadcrumbPage></BreadcrumbItem>
480
+ </BreadcrumbList>
481
+ </Breadcrumb>
482
+ <Button variant="'outline'" size="'sm'" className="'ms-auto'" icon="'log-out'" onClick="() => window.location.assign('/index.html')">Logout</Button>
483
+ </ShellHeader>
484
+ <ShellContent className="'d-flex flex-column gap-3'">
485
+ <div class="row row-cols-3 g-3">
486
+ <div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
487
+ <div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
488
+ <div class="col"><div class="ratio ratio-16x9 bg-body-secondary rounded-4"></div></div>
489
+ </div>
490
+ <div class="flex-grow-1 bg-body-secondary rounded-4"></div>
491
+ </ShellContent>
492
+ </ShellMain>
493
+ </Shell>
494
+ </t>
495
+ </templates>
496
+ `,
497
+
498
+ "README.md": `# ${title}
499
+
500
+ Scaffolded with \`@thebase/ui\`'s CLI (\`create-baseui-app init\`): Vite + BaseUI, pure Owl API, 4 pages.
501
+
502
+ - \`/\` — landing page (header, hero, features, footer)
503
+ - \`/login.html\` — login (redirects to \`/home.html\` on submit)
504
+ - \`/signup.html\` — signup (redirects to \`/login.html\` on submit)
505
+ - \`/home.html\` — authenticated home shell (sidebar + header, logout returns to \`/\`)
506
+
507
+ None of the pages call a real backend yet — wire up \`Login\`/\`Signup\`'s button \`onClick\` handlers to
508
+ your own auth API once you have one.
509
+
510
+ \`\`\`sh
511
+ npm install
512
+ npm run dev
513
+ \`\`\`
514
+ `,
515
+ };
516
+ }
517
+
143
518
  /** Writes each template file under targetDir, skipping any that already exist (convert mode is additive/non-destructive). */
144
519
  function writeFiles(targetDir, files, { overwrite }) {
145
520
  const written = [];
@@ -173,6 +548,7 @@ function parseFlags(argv) {
173
548
  if (arg === "--no-install") flags.install = false;
174
549
  else if (arg === "--force") flags.force = true;
175
550
  else if (arg === "--version") flags.version = argv[++i];
551
+ else if (arg === "--config") flags.config = argv[++i];
176
552
  else positional.push(arg);
177
553
  }
178
554
  return { flags, positional };
@@ -205,6 +581,37 @@ function cmdCreate(argv) {
205
581
  console.log(`\nNext steps:\n cd ${rawName}${flags.install ? "" : "\n npm install"}\n npm run dev`);
206
582
  }
207
583
 
584
+ /** Scaffolds the 4-page starter (landing/login/signup/home) in a new ./<app-name> folder. */
585
+ function cmdInit(argv) {
586
+ const { flags, positional } = parseFlags(argv);
587
+ const rawName = positional[0];
588
+ if (!rawName) {
589
+ console.error("Usage: create-baseui-app init <app-name> [--version <x.y.z>] [--no-install] [--force]");
590
+ process.exit(1);
591
+ }
592
+ const appName = toPackageName(rawName);
593
+ const targetDir = path.resolve(process.cwd(), rawName);
594
+
595
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0 && !flags.force) {
596
+ console.error(`Directory "${rawName}" already exists and is not empty. Use --force to write into it anyway.`);
597
+ process.exit(1);
598
+ }
599
+
600
+ const baseuiVersion = flags.version || ownPackageJson.version;
601
+ const files = buildStarterFiles(appName, baseuiVersion);
602
+ const { written } = writeFiles(targetDir, files, { overwrite: flags.force ?? false });
603
+
604
+ console.log(`Scaffolded a 4-page Vite + BaseUI starter in ${targetDir}`);
605
+ written.forEach((f) => console.log(` create ${f}`));
606
+
607
+ if (flags.install) runInstall(targetDir);
608
+
609
+ console.log(
610
+ `\nNext steps:\n cd ${rawName}${flags.install ? "" : "\n npm install"}\n npm run dev` +
611
+ `\n\nPages: / (landing), /login.html, /signup.html, /home.html`,
612
+ );
613
+ }
614
+
208
615
  function cmdConvert(argv) {
209
616
  const { flags, positional } = parseFlags(argv);
210
617
  const targetDir = path.resolve(process.cwd(), positional[0] || ".");
@@ -248,6 +655,46 @@ function cmdConvert(argv) {
248
655
  console.log(`\nNext steps:\n npm run dev`);
249
656
  }
250
657
 
658
+ /** Generates manifest.webmanifest + sw.js for a PWA from a `baseui.pwa.config.js` the user wrote,
659
+ * using @thebase/ui's buildManifest/buildServiceWorker (see docs/usage-guide.md "PWA toolkit"). */
660
+ async function cmdPwa(argv) {
661
+ const { flags, positional } = parseFlags(argv);
662
+ const targetDir = path.resolve(process.cwd(), positional[0] || ".");
663
+ const configPath = path.resolve(targetDir, flags.config || "baseui.pwa.config.js");
664
+
665
+ if (!existsSync(configPath)) {
666
+ console.error(
667
+ `Config file not found: ${configPath}\n` +
668
+ "Create it first — export default { manifest: {...}, serviceWorker: {...} }, " +
669
+ "see docs/usage-guide.md \"PWA toolkit\" for the full config shape.",
670
+ );
671
+ process.exit(1);
672
+ }
673
+
674
+ const { manifest, serviceWorker } = (await import(pathToFileURL(configPath).href)).default;
675
+ const { buildManifest, buildServiceWorker, DEFAULT_ICON_FILES } = await import("../dist/baseui.pwa.esm.js");
676
+
677
+ writeFileSync(
678
+ path.join(targetDir, "manifest.webmanifest"),
679
+ JSON.stringify(buildManifest(manifest), null, 2) + "\n",
680
+ );
681
+ writeFileSync(path.join(targetDir, "sw.js"), buildServiceWorker(serviceWorker));
682
+
683
+ copyDefaultIcons(path.join(targetDir, "icons"), DEFAULT_ICON_FILES);
684
+
685
+ console.log(`Wrote manifest.webmanifest and sw.js in ${targetDir}`);
686
+ }
687
+
688
+ /** Gives a PWA the default BaseUI logo/favicon set, without replacing any icon the app already has. */
689
+ function copyDefaultIcons(iconsDir, files) {
690
+ mkdirSync(iconsDir, { recursive: true });
691
+ const source = new URL("../dist/pwa-icons/", import.meta.url);
692
+ for (const file of files) {
693
+ const target = path.join(iconsDir, file);
694
+ if (!existsSync(target)) copyFileSync(new URL(file, source), target);
695
+ }
696
+ }
697
+
251
698
  function printHelp() {
252
699
  console.log(`@thebase/ui project CLI
253
700
 
@@ -258,6 +705,17 @@ Usage:
258
705
  create-baseui-app convert [path] [--version <x.y.z>] [--no-install]
259
706
  Turn an existing (non-npm) folder into a Vite + BaseUI project, in place.
260
707
  Never overwrites files that already exist; only fills in what's missing.
708
+
709
+ create-baseui-app init <app-name> [--version <x.y.z>] [--no-install] [--force]
710
+ Scaffold a ready-to-start 4-page project in ./<app-name>: a landing page
711
+ (header/hero/features/footer), login, signup, and an authenticated home
712
+ shell (sidebar + header) — ported from the login-03/signup-03/sidebar-02
713
+ example blocks.
714
+
715
+ create-baseui-app pwa [path] [--config <file>]
716
+ Generate manifest.webmanifest + sw.js in [path] (default ".") from a
717
+ baseui.pwa.config.js config file (default name, override with --config).
718
+ Also copies the default logo/favicon set into [path]/icons/ (existing files kept).
261
719
  `);
262
720
  }
263
721
 
@@ -265,4 +723,6 @@ const [, , command, ...rest] = process.argv;
265
723
 
266
724
  if (command === "create") cmdCreate(rest);
267
725
  else if (command === "convert") cmdConvert(rest);
726
+ else if (command === "init") cmdInit(rest);
727
+ else if (command === "pwa") await cmdPwa(rest);
268
728
  else printHelp();
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#293138" d="M112.205 26.129 71.8 2.142A15.326 15.326 0 0 0 64.005 0c-2.688 0-5.386.717-7.796 2.152L15.795 26.14C10.976 28.999 8 34.289 8 40.018v47.975c0 5.729 2.967 11.019 7.796 13.878L56.2 125.858A15.193 15.193 0 0 0 63.995 128a15.32 15.32 0 0 0 7.796-2.142l40.414-23.987c4.819-2.86 7.796-8.16 7.796-13.878V40.007c0-5.718-2.967-11.019-7.796-13.878zm-31.29 74.907.063 3.448c0 .418-.267.889-.588 1.06l-2.046 1.178c-.321.16-.6-.032-.6-.45l-.032-3.394c-1.745.728-3.523.9-4.647.45-.214-.086-.31-.397-.225-.761l.739-3.116c.064-.246.193-.493.364-.643a.725.725 0 0 1 .193-.139c.117-.064.235-.075.332-.032 1.22.407 2.773.214 4.272-.535 1.907-.964 3.18-2.913 3.16-4.84-.022-1.757-.964-2.474-3.267-2.496-2.934.01-5.675-.567-5.718-4.894-.032-3.555 1.81-7.26 4.744-9.595l-.032-3.48c0-.428.257-.9.589-1.07l1.98-1.264c.322-.161.6.043.6.46l.033 3.48c1.456-.578 2.72-.738 3.865-.47.247.064.364.406.257.802l-.77 3.084a1.372 1.372 0 0 1-.354.622.825.825 0 0 1-.203.15c-.108.053-.204.064-.3.053-.525-.118-1.767-.385-3.727.6-2.056 1.038-2.773 2.827-2.763 4.155.022 1.585.825 2.066 3.63 2.11 3.738.064 5.344 1.691 5.387 5.45.053 3.684-1.917 7.657-4.937 10.077zm21.18-5.794c0 .322-.042.621-.31.771l-10.216 6.211c-.267.161-.482.022-.482-.3V99.29c0-.321.193-.492.46-.653l10.067-6.018c.268-.16.482-.022.482.3zm7.026-58.993L70.89 59.86c-4.765 2.784-8.278 5.911-8.288 11.662v47.107c0 3.437 1.392 5.665 3.523 6.318a12.81 12.81 0 0 1-2.12.204c-2.239 0-4.445-.61-6.383-1.757L17.219 99.408c-3.951-2.345-6.403-6.725-6.403-11.426V40.007c0-4.7 2.452-9.08 6.403-11.426L57.634 4.594a12.555 12.555 0 0 1 6.382-1.756c2.238 0 4.444.61 6.382 1.756l40.415 23.987c3.33 1.981 5.579 5.397 6.21 9.242-1.36-2.86-4.38-3.63-7.902-1.574z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#a9bacd" d="M125 50c-4-32-24-50-62-50C29 0 3 24 3 64c0 39 24 64 64 64 32 0 55-19 58-50H87c-2 11-8 20-20 20-21 0-24-16-24-33 0-23 8-35 22-35 13 0 20 7 22 20z" /></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#9B4F96" d="M115.4 30.7L67.1 2.9c-.8-.5-1.9-.7-3.1-.7-1.2 0-2.3.3-3.1.7l-48 27.9c-1.7 1-2.9 3.5-2.9 5.4v55.7c0 1.1.2 2.4 1 3.5l106.8-62c-.6-1.2-1.5-2.1-2.4-2.7z"/><path fill="#68217A" d="M10.7 95.3c.5.8 1.2 1.5 1.9 1.9l48.2 27.9c.8.5 1.9.7 3.1.7 1.2 0 2.3-.3 3.1-.7l48-27.9c1.7-1 2.9-3.5 2.9-5.4V36.1c0-.9-.1-1.9-.6-2.8l-106.6 62z"/><path fill="#fff" d="M85.3 76.1C81.1 83.5 73.1 88.5 64 88.5c-13.5 0-24.5-11-24.5-24.5s11-24.5 24.5-24.5c9.1 0 17.1 5 21.3 12.5l13-7.5c-6.8-11.9-19.6-20-34.3-20-21.8 0-39.5 17.7-39.5 39.5s17.7 39.5 39.5 39.5c14.6 0 27.4-8 34.2-19.8l-12.9-7.6zM97 66.2l.9-4.3h-4.2v-4.7h5.1L100 51h4.9l-1.2 6.1h3.8l1.2-6.1h4.8l-1.2 6.1h2.4v4.7h-3.3l-.9 4.3h4.2v4.7h-5.1l-1.2 6h-4.9l1.2-6h-3.8l-1.2 6h-4.8l1.2-6h-2.4v-4.7H97zm4.8 0h3.8l.9-4.3h-3.8l-.9 4.3z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#00c4b3" d="M35.2 34.9l-8.3-8.3v59.7l.1 2.8c0 1.3.2 2.8.7 4.3l65.6 23.1 16.3-7.2-74.4-74.4z"/><path d="M27.7 93.4zm81.9 15.9l-16.3 7.2-65.4-23.1c1.3 4.8 4 10.1 7 13.2l21.3 21.2 47.6.1 5.8-18.6z" fill="#22d3c5"/><path fill="#0075c9" d="M1.7 65.1C-.4 67.3.7 72 4 75.5l14.7 14.8 9.2 3.3c-.3-1.5-.7-3-.7-4.3l-.1-2.8-.2-59.8m82.7 82.6l7.2-16.4-23-65.6c-1.5-.3-3-.6-4.3-.7l-2.9-.1-59.6.1"/><path d="M93.6 27.3c.2 0 .2 0 0 0 .2 0 .2 0 0 0zm16 82l17.7-5.8V54.8l-20.4-20.5c-3-3-8.3-5.8-13.2-7l23.1 65.6" fill="#00a8e1"/><path fill="#00c4b3" d="M90.5 18.2L75.7 3.5c-3.4-3.4-8-4.4-10.4-2.3L26.9 26.6h59.5l2.9.1c1.3 0 2.8.2 4.3.7l-3.1-9.2z"/></svg>