teacup-os 0.1.0 → 0.1.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.
- package/dist/teacup.js +21 -2
- package/kit/icons.ts +39 -0
- package/kit/lucide/LICENSE +43 -0
- package/kit/lucide/a-large-small.svg +18 -0
- package/kit/lucide/bell.svg +16 -0
- package/kit/lucide/file.svg +16 -0
- package/kit/lucide/folder.svg +15 -0
- package/kit/lucide/history.svg +17 -0
- package/kit/lucide/house.svg +16 -0
- package/kit/lucide/image.svg +17 -0
- package/kit/lucide/info.svg +17 -0
- package/kit/lucide/layout-dashboard.svg +18 -0
- package/kit/lucide/layout-grid.svg +18 -0
- package/kit/lucide/search.svg +16 -0
- package/kit/lucide/shield.svg +15 -0
- package/kit/lucide/sparkles.svg +18 -0
- package/kit/lucide/sun-moon.svg +19 -0
- package/kit/lucide/trash-2.svg +19 -0
- package/kit/lucide/users.svg +18 -0
- package/kit/lucide/x.svg +16 -0
- package/kit/src/client.ts +18 -4
- package/kit/src/components.css +4 -0
- package/kit/src/manifest.ts +7 -1
- package/kit/src/no-zoom.ts +15 -0
- package/kit/src/protocol.ts +9 -3
- package/kit/src/teacup-above.ts +11 -0
- package/kit/src/wallpapers.ts +48 -0
- package/kit/system.ts +8 -1
- package/kit/widgets.css +16 -16
- package/package.json +1 -1
- package/template/app.json +1 -0
- package/template/icon.svg +1 -1
package/dist/teacup.js
CHANGED
|
@@ -145,6 +145,11 @@ function validateManifest(m, folder2, opts = {}) {
|
|
|
145
145
|
typeof m.description === "string" && m.description.length >= 10 && m.description.length <= 140,
|
|
146
146
|
"description must be one sentence of 10 to 140 characters"
|
|
147
147
|
);
|
|
148
|
+
const sub = m.subtitle;
|
|
149
|
+
need(
|
|
150
|
+
sub === void 0 || words(sub, 3, 40) && !/[.!]$/.test(sub),
|
|
151
|
+
"subtitle must be 3 to 40 characters, no full stop"
|
|
152
|
+
);
|
|
148
153
|
need(m.kind === "kit" || m.kind === "framed", "kind must be kit or framed");
|
|
149
154
|
if (opts.outside)
|
|
150
155
|
need(m.url === `https://${m.id}.backed.cc/v/${m.version}/`, "an outside app lives at its own address on backed.cc");
|
|
@@ -346,9 +351,23 @@ function smallTextBoxes(file, dir) {
|
|
|
346
351
|
}
|
|
347
352
|
return out;
|
|
348
353
|
}
|
|
349
|
-
|
|
354
|
+
const ICON_MAX_BYTES = 4e3;
|
|
355
|
+
function iconProblems(icon, exception = false) {
|
|
350
356
|
if (!icon.trimStart().startsWith("<svg")) return ["icon.svg is missing or isn't an SVG"];
|
|
351
|
-
|
|
357
|
+
if (/<script|<foreignObject|<iframe|<use|<image|\son[a-z]+\s*=|href\s*=|javascript:|url\(\s*['"]?[a-z]+:/i.test(icon))
|
|
358
|
+
return ["icon.svg may only draw shapes: no scripts, event handlers, links, pictures or embedded content"];
|
|
359
|
+
if (exception) return [];
|
|
360
|
+
const problems = [];
|
|
361
|
+
const root = icon.match(/<svg[^>]*>/)?.[0] ?? "";
|
|
362
|
+
if (!/viewBox="0 0 24 24"/.test(root))
|
|
363
|
+
problems.push('icon.svg should be a Lucide glyph on its 24 grid: viewBox="0 0 24 24" (see lucide.dev)');
|
|
364
|
+
if (/<text\b/i.test(icon)) problems.push("icon.svg has words in it; an icon is a glyph, never text");
|
|
365
|
+
const strokes = [...icon.matchAll(/stroke-width\s*[=:]\s*"?([\d.]+)/g)].map((m) => m[1]);
|
|
366
|
+
if (strokes.some((w) => w !== "2"))
|
|
367
|
+
problems.push("icon.svg's stroke should be 2, as every Teacup icon's is (Lucide's own weight)");
|
|
368
|
+
if (Buffer.byteLength(icon) > ICON_MAX_BYTES)
|
|
369
|
+
problems.push(`icon.svg is ${Buffer.byteLength(icon)} bytes; keep it under ${ICON_MAX_BYTES} (one glyph)`);
|
|
370
|
+
return problems;
|
|
352
371
|
}
|
|
353
372
|
const glow = (at, colour, clear, size = "120% 90%", reach = "55%") => `radial-gradient(${size} at ${at}, ${colour} 0%, ${clear} ${reach})`;
|
|
354
373
|
const WALLPAPERS = [
|
package/kit/icons.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Teacup's one icon family: Lucide (https://lucide.dev), outlines only, ISC licence (kit/lucide/LICENSE). Only the
|
|
2
|
+
// glyphs Teacup uses are copied in, as they come in lucide-static (1.70.0), into kit/lucide/<name>.svg: add one by
|
|
3
|
+
// copying its file there. Every glyph is drawn on Lucide's 24 grid, in the text's colour, with one stroke weight.
|
|
4
|
+
|
|
5
|
+
const files = import.meta.glob<string>("./lucide/*.svg", { query: "?raw", import: "default", eager: true });
|
|
6
|
+
|
|
7
|
+
/** Each glyph's drawing (what's inside its <svg>), by name. */
|
|
8
|
+
export const GLYPHS: Record<string, string> = Object.fromEntries(
|
|
9
|
+
Object.entries(files).map(([path, svg]) => [
|
|
10
|
+
path.replace(/^.*\/|\.svg$/g, ""),
|
|
11
|
+
(svg.match(/<svg[^>]*>([\s\S]*)<\/svg>/)?.[1] ?? "").replace(/\s+/g, " ").trim(),
|
|
12
|
+
]),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/** The stroke every glyph is drawn with: Lucide's own 2 on its 24 grid. */
|
|
16
|
+
export const STROKE = 2;
|
|
17
|
+
|
|
18
|
+
/** A glyph as inline SVG, in the text's colour. */
|
|
19
|
+
export const glyph = (name: string, cls = "glyph", stroke = STROKE) =>
|
|
20
|
+
`<svg class="${cls}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${stroke}" ` +
|
|
21
|
+
`stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${GLYPHS[name] ?? ""}</svg>`;
|
|
22
|
+
|
|
23
|
+
/** The Assistant's orb in the light (apps/assistant/icon.svg is its dark self; shell icons.css draws the same). */
|
|
24
|
+
const ORB_LIGHT =
|
|
25
|
+
"<style>.orb-bg-a{stop-color:#fff}.orb-bg-b{stop-color:#e8e6f3}.orb-sq{fill:#6a4fe8}.orb-core{fill:#14151e}</style>";
|
|
26
|
+
|
|
27
|
+
/** Whether an app's mark is drawn edge to edge (the Assistant's orb), not as a glyph in the middle of its tile. */
|
|
28
|
+
export const isFullMark = (svg: string) => /<svg[^>]*\sdata-full/.test(svg);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One of Teacup's own app marks as an image address, as it should look now: a mark that follows light and dark
|
|
32
|
+
* (data-themed) carries its light colours unless the page is dark.
|
|
33
|
+
*/
|
|
34
|
+
export function markPicture(svg: string): string {
|
|
35
|
+
const dark = globalThis.document?.documentElement.dataset.theme === "dark";
|
|
36
|
+
let s = svg.includes("xmlns") ? svg : svg.replace("<svg", '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
37
|
+
if (!dark && s.includes("data-themed")) s = s.replace(/(<svg[^>]*>)/, `$1${ORB_LIGHT}`);
|
|
38
|
+
return `data:image/svg+xml,${encodeURIComponent(s)}`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-a-large-small"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="m15 16 2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16" />
|
|
15
|
+
<path d="M15.697 14h5.606" />
|
|
16
|
+
<path d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" />
|
|
17
|
+
<path d="M3.304 13h6.392" />
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-bell"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M10.268 21a2 2 0 0 0 3.464 0" />
|
|
15
|
+
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-file"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
|
|
15
|
+
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-folder"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-history"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|
15
|
+
<path d="M3 3v5h5" />
|
|
16
|
+
<path d="M12 7v5l4 2" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-house"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
|
|
15
|
+
<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-image"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
|
15
|
+
<circle cx="9" cy="9" r="2" />
|
|
16
|
+
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-info"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<circle cx="12" cy="12" r="10" />
|
|
15
|
+
<path d="M12 16v-4" />
|
|
16
|
+
<path d="M12 8h.01" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-layout-dashboard"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<rect width="7" height="9" x="3" y="3" rx="1" />
|
|
15
|
+
<rect width="7" height="5" x="14" y="3" rx="1" />
|
|
16
|
+
<rect width="7" height="9" x="14" y="12" rx="1" />
|
|
17
|
+
<rect width="7" height="5" x="3" y="16" rx="1" />
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-layout-grid"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<rect width="7" height="7" x="3" y="3" rx="1" />
|
|
15
|
+
<rect width="7" height="7" x="14" y="3" rx="1" />
|
|
16
|
+
<rect width="7" height="7" x="14" y="14" rx="1" />
|
|
17
|
+
<rect width="7" height="7" x="3" y="14" rx="1" />
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-search"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="m21 21-4.34-4.34" />
|
|
15
|
+
<circle cx="11" cy="11" r="8" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-shield"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-sparkles"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" />
|
|
15
|
+
<path d="M20 2v4" />
|
|
16
|
+
<path d="M22 4h-4" />
|
|
17
|
+
<circle cx="4" cy="20" r="2" />
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-sun-moon"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 2v2" />
|
|
15
|
+
<path d="M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715" />
|
|
16
|
+
<path d="M16 12a4 4 0 0 0-4-4" />
|
|
17
|
+
<path d="m19 5-1.256 1.256" />
|
|
18
|
+
<path d="M20 12h2" />
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-trash-2"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M10 11v6" />
|
|
15
|
+
<path d="M14 11v6" />
|
|
16
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
|
|
17
|
+
<path d="M3 6h18" />
|
|
18
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-users"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
|
15
|
+
<path d="M16 3.128a4 4 0 0 1 0 7.744" />
|
|
16
|
+
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
|
17
|
+
<circle cx="9" cy="7" r="4" />
|
|
18
|
+
</svg>
|
package/kit/lucide/x.svg
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.70.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-x"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M18 6 6 18" />
|
|
15
|
+
<path d="m6 6 12 12" />
|
|
16
|
+
</svg>
|
package/kit/src/client.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
// resolves to a stand-alone context, so apps work either way.
|
|
4
4
|
|
|
5
5
|
import { type MenuCall, shortcutOf } from "./menus";
|
|
6
|
+
import { stopPageZoom } from "./no-zoom";
|
|
6
7
|
import {
|
|
7
8
|
APP_HEADER,
|
|
8
9
|
APPROVAL_HEADER,
|
|
9
|
-
type AppFolder,
|
|
10
10
|
type AppNotifications,
|
|
11
11
|
type Change,
|
|
12
12
|
type HostContext,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
type RpcResponse,
|
|
18
18
|
type ShareItem,
|
|
19
19
|
} from "./protocol";
|
|
20
|
-
|
|
21
20
|
import type { Listener, Teacup } from "./teacup";
|
|
21
|
+
import { teacupFrom } from "./teacup-above";
|
|
22
22
|
|
|
23
23
|
export type { Teacup } from "./teacup";
|
|
24
24
|
|
|
@@ -62,8 +62,7 @@ const TEACUP = OUTSIDE ? teacupAbove() : location.origin;
|
|
|
62
62
|
* is served by `teacup dev` on port 8791).
|
|
63
63
|
*/
|
|
64
64
|
function teacupAbove() {
|
|
65
|
-
|
|
66
|
-
return above === "http://localhost:8790" || above === "https://staging.kiit.fyi" ? above : "https://kiit.fyi";
|
|
65
|
+
return teacupFrom(location.ancestorOrigins?.[0], typeof window === "undefined" ? "" : window.name);
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
/** Whether something takes typing: a text box, a text area, or anything editable (not a checkbox or a button). */
|
|
@@ -157,6 +156,8 @@ export async function connect(appInfo: { name: string; version?: string }, timeo
|
|
|
157
156
|
addEventListener("focusin", (e) => typesIn(e.target as Element) && say(true));
|
|
158
157
|
addEventListener("focusout", () => setTimeout(() => say(typesIn(document.activeElement)), 120));
|
|
159
158
|
}
|
|
159
|
+
// A pinch inside an app never zooms all of Teacup on a phone or iPad; the app still gets the touches (no-zoom.ts).
|
|
160
|
+
if (inside) stopPageZoom();
|
|
160
161
|
// Teacup's own keys reach it from inside an app too, unless the app used them: ⌘K (Ctrl+K) for the quick ask, and
|
|
161
162
|
// Escape, which closes it.
|
|
162
163
|
if (inside && !OUTSIDE)
|
|
@@ -167,6 +168,19 @@ export async function connect(appInfo: { name: string; version?: string }, timeo
|
|
|
167
168
|
if (quick || e.key === "Escape") notify("teacup/key", { key: quick ? "quick-ask" : "escape" });
|
|
168
169
|
});
|
|
169
170
|
const appId = result?.app?.id ?? null;
|
|
171
|
+
// A link from one of Teacup's own apps to Teacup's own pages (the developer pages, another app) goes to Teacup,
|
|
172
|
+
// which opens it in a window, rather than taking the place of Teacup (on an installed iPhone, with no way back).
|
|
173
|
+
if (inside && !OUTSIDE && appId)
|
|
174
|
+
addEventListener("click", (e) => {
|
|
175
|
+
const a = (e.target as Element | null)?.closest?.("a[href]") as HTMLAnchorElement | null;
|
|
176
|
+
if (!a || e.defaultPrevented || a.hasAttribute("download")) return;
|
|
177
|
+
const u = new URL(a.href);
|
|
178
|
+
// Other sites open as they always have (a new tab; on an iPhone, Safari's own sheet with Done).
|
|
179
|
+
if (u.origin !== location.origin) return;
|
|
180
|
+
if (a.target !== "_blank" && u.pathname.startsWith(`/apps/${appId}/`)) return;
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
void request("teacup/open-link", { url: a.href }).catch(() => {});
|
|
183
|
+
});
|
|
170
184
|
// The app's menu shortcuts, pressed inside its window, go up to Teacup, which runs the item as if it were chosen.
|
|
171
185
|
const shortcuts = new Set(result?.shortcuts ?? []);
|
|
172
186
|
const onKey = (e: KeyboardEvent) => {
|
package/kit/src/components.css
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
body {
|
|
22
22
|
padding-bottom: var(--teacup-bottom-room, 0px);
|
|
23
23
|
}
|
|
24
|
+
/* For a page whose own box scrolls (not the page itself): its end clear of the Home pill too. */
|
|
25
|
+
.tc-bottom-room {
|
|
26
|
+
padding-bottom: var(--teacup-bottom-room, 0px);
|
|
27
|
+
}
|
|
24
28
|
:root {
|
|
25
29
|
--teacup-screen: calc(100dvh - var(--teacup-bottom-room, 0px));
|
|
26
30
|
}
|
package/kit/src/manifest.ts
CHANGED
|
@@ -131,6 +131,8 @@ export interface Manifest {
|
|
|
131
131
|
version: string;
|
|
132
132
|
/** One sentence, shown in the store and to Claude. */
|
|
133
133
|
description: string;
|
|
134
|
+
/** A few words under its name in lists ("Notes you write together"); left out, the start of the description. */
|
|
135
|
+
subtitle?: string;
|
|
134
136
|
/** kit: built in apps/<id>/ with the kit. framed: an older page Teacup shows as it is. */
|
|
135
137
|
kind: "kit" | "framed";
|
|
136
138
|
/** Where it opens on Teacup's site, ending in "/". */
|
|
@@ -197,7 +199,6 @@ const COLLECTION = /^[a-z][a-z0-9_]{1,30}$/;
|
|
|
197
199
|
const RESERVED = ["teacup", "history", "docs", "system", "files", "mail", "people", "approvals", "messages"];
|
|
198
200
|
/** Reserved names that are Teacup's own system apps (Files), which only they may use. */
|
|
199
201
|
const SYSTEM_IDS = ["files"];
|
|
200
|
-
|
|
201
202
|
/** Fields decision 31 reserves for later (server code Teacup runs, AI, apps without screens). */
|
|
202
203
|
const NOT_YET = ["server", "ai", "screens"];
|
|
203
204
|
|
|
@@ -222,6 +223,11 @@ export function validateManifest(m: Partial<Manifest>, folder?: string, opts: {
|
|
|
222
223
|
typeof m.description === "string" && m.description.length >= 10 && m.description.length <= 140,
|
|
223
224
|
"description must be one sentence of 10 to 140 characters",
|
|
224
225
|
);
|
|
226
|
+
const sub = m.subtitle;
|
|
227
|
+
need(
|
|
228
|
+
sub === undefined || (words(sub, 3, 40) && !/[.!]$/.test(sub)),
|
|
229
|
+
"subtitle must be 3 to 40 characters, no full stop",
|
|
230
|
+
);
|
|
225
231
|
need(m.kind === "kit" || m.kind === "framed", "kind must be kit or framed");
|
|
226
232
|
if (opts.outside)
|
|
227
233
|
need(m.url === `https://${m.id}.backed.cc/v/${m.version}/`, "an outside app lives at its own address on backed.cc");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// On a phone or iPad, Teacup never zooms as a whole when pinched, as a phone's own home screen and apps never do
|
|
2
|
+
// (docs/os.md). Safari zooms the whole page on a pinch, even one made inside an app's window, unless the page stops
|
|
3
|
+
// its gesture; the viewport's maximum-scale stops it elsewhere. Shared by the shell and the kit, so a pinch anywhere
|
|
4
|
+
// is caught. An app that wants a pinch for its own content (a photo, a map) still gets every touch and pointer
|
|
5
|
+
// event, and handles the pinch itself; only the zoom of all of Teacup is stopped. Laptops keep their own zoom.
|
|
6
|
+
|
|
7
|
+
/** A touch-first device: no mouse or trackpad that hovers (core/form.ts draws the same line). */
|
|
8
|
+
const touchFirst = () => !matchMedia("(pointer: fine) and (hover: hover)").matches;
|
|
9
|
+
|
|
10
|
+
export function stopPageZoom(doc: Document = document) {
|
|
11
|
+
if (typeof matchMedia === "undefined" || !touchFirst()) return;
|
|
12
|
+
// Safari's own pinch events: stopped, so the page doesn't zoom.
|
|
13
|
+
for (const type of ["gesturestart", "gesturechange", "gestureend"])
|
|
14
|
+
doc.addEventListener(type, (e) => e.preventDefault(), { passive: false });
|
|
15
|
+
}
|
package/kit/src/protocol.ts
CHANGED
|
@@ -40,8 +40,12 @@ export interface HostContext {
|
|
|
40
40
|
/** Each person's settings for Teacup, kept on the server (docs/os.md, decision 24). */
|
|
41
41
|
export interface Prefs {
|
|
42
42
|
appearance?: Appearance;
|
|
43
|
+
/** The desktop's wallpaper: one of Teacup's (wallpapers.ts) or "photo", your own desktop photo. */
|
|
43
44
|
wallpaper?: string;
|
|
44
|
-
|
|
45
|
+
/** The phone's wallpaper; until it's chosen, the desktop's if that's one of Teacup's, else Teacup's default. */
|
|
46
|
+
wallpaperPhone?: string;
|
|
47
|
+
/** Text size: "auto" (or unset) follows the device's own where the browser says it; the rest are fixed. */
|
|
48
|
+
text?: "auto" | "standard" | "large" | "larger";
|
|
45
49
|
/** The widgets on the home screen, in order: "today" or "<app>.<widget>". */
|
|
46
50
|
widgets?: string[];
|
|
47
51
|
/** Which page of the phone's home screen each widget is on (the first, 0, unless moved). */
|
|
@@ -54,8 +58,10 @@ export interface Prefs {
|
|
|
54
58
|
notify?: Record<string, boolean>;
|
|
55
59
|
/** Each app's own settings, by app and setting id. */
|
|
56
60
|
apps?: Record<string, Record<string, boolean | string>>;
|
|
57
|
-
/** When your own
|
|
61
|
+
/** When your own desktop photo last changed, so every device fetches the new one. */
|
|
58
62
|
wallpaperAt?: number;
|
|
63
|
+
/** When your own phone photo last changed. */
|
|
64
|
+
wallpaperPhoneAt?: number;
|
|
59
65
|
/** Today's weather switched off: nothing about it is fetched. Shown unless this is true. */
|
|
60
66
|
weatherHidden?: boolean;
|
|
61
67
|
/** The town Today's weather is for; where you are (Cloudflare's estimate) when there's none, or null. */
|
|
@@ -176,7 +182,7 @@ export interface HomeScreen {
|
|
|
176
182
|
/** The folder it's in, if any. */
|
|
177
183
|
folder?: string;
|
|
178
184
|
/** Its icon: the tile's background, and a picture of the mark on it (a data: address, or the app's own). */
|
|
179
|
-
icon: { bg: string; picture: string };
|
|
185
|
+
icon: { bg: string; picture: string; full?: boolean };
|
|
180
186
|
}[];
|
|
181
187
|
/** Your folders of apps, in the order they sit. */
|
|
182
188
|
folders: AppFolder[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Which Teacup an outside app is inside (kit/src/client.ts): kiit.fyi, its staging copy, or Teacup on the maker's
|
|
2
|
+
// own computer. Kept apart from the client so it can be tested without a browser.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Which of the three Teacups an outside app is in, from the browser's word for the page above it, or (Firefox has no
|
|
6
|
+
* `ancestorOrigins`) the frame's name, which the shell sets to `teacup:<its origin>`. Only ever one of the three.
|
|
7
|
+
*/
|
|
8
|
+
export function teacupFrom(ancestor: string | undefined, frameName: string): string {
|
|
9
|
+
const above = ancestor ?? /^teacup:(.+)$/.exec(frameName)?.[1];
|
|
10
|
+
return above === "http://localhost:8790" || above === "https://staging.kiit.fyi" ? above : "https://kiit.fyi";
|
|
11
|
+
}
|
package/kit/src/wallpapers.ts
CHANGED
|
@@ -75,6 +75,54 @@ export const WALLPAPERS: Wallpaper[] = [
|
|
|
75
75
|
/** The default, until someone chooses. */
|
|
76
76
|
export const DEFAULT_WALLPAPER = "sky";
|
|
77
77
|
|
|
78
|
+
/** Each layout has its own wallpaper: the desktop's (landscape) and the phone's (portrait). */
|
|
79
|
+
export type PhotoLayout = "desktop" | "phone";
|
|
80
|
+
|
|
81
|
+
/** How long a photo's longest side is kept, for each layout: enough for a large laptop screen, or a phone's. */
|
|
82
|
+
export const PHOTO_EDGE: Record<PhotoLayout, number> = { desktop: 2560, phone: 1300 };
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The wallpaper a layout shows: its own choice; for a phone not yet chosen, the desktop's if that's one of Teacup's
|
|
86
|
+
* own (as it was before each had its own), never the desktop's photo; otherwise Teacup's default. `photoAt` is the
|
|
87
|
+
* version of that layout's photo to fetch, when it shows one.
|
|
88
|
+
*/
|
|
89
|
+
export function wallpaperFor(
|
|
90
|
+
prefs: { wallpaper?: string; wallpaperAt?: number; wallpaperPhone?: string; wallpaperPhoneAt?: number },
|
|
91
|
+
layout: PhotoLayout,
|
|
92
|
+
): { id: string; photoAt: number | null } {
|
|
93
|
+
const ours = (id: string | undefined) => !!id && WALLPAPERS.some((w) => w.id === id);
|
|
94
|
+
const id =
|
|
95
|
+
layout === "desktop"
|
|
96
|
+
? (prefs.wallpaper ?? DEFAULT_WALLPAPER)
|
|
97
|
+
: (prefs.wallpaperPhone ?? (ours(prefs.wallpaper) ? prefs.wallpaper : undefined) ?? DEFAULT_WALLPAPER);
|
|
98
|
+
if (id !== "photo") return { id: ours(id) ? id : DEFAULT_WALLPAPER, photoAt: null };
|
|
99
|
+
const at = layout === "desktop" ? prefs.wallpaperAt : prefs.wallpaperPhoneAt;
|
|
100
|
+
// Chosen, but that layout has no photo of its own: Teacup's default instead of an empty grey.
|
|
101
|
+
return at ? { id: "photo", photoAt: at } : { id: DEFAULT_WALLPAPER, photoAt: null };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The part of a picture `w` by `h` that fills a layout's screen shape (16:10 for a desktop, 9:19.5 for a phone),
|
|
106
|
+
* centred as near as it can be on the focus point (fx, fy, each 0 to 1), and the size to draw it at.
|
|
107
|
+
*/
|
|
108
|
+
export function photoFrame(w: number, h: number, layout: PhotoLayout, fx = 0.5, fy = 0.5) {
|
|
109
|
+
const aspect = layout === "desktop" ? 16 / 10 : 9 / 19.5;
|
|
110
|
+
const cw = Math.min(w, h * aspect);
|
|
111
|
+
const ch = cw / aspect;
|
|
112
|
+
const clamp = (v: number, max: number) => Math.round(Math.min(Math.max(v, 0), max));
|
|
113
|
+
const sx = clamp(fx * w - cw / 2, w - cw);
|
|
114
|
+
const sy = clamp(fy * h - ch / 2, h - ch);
|
|
115
|
+
const scale = Math.min(1, PHOTO_EDGE[layout] / Math.max(cw, ch));
|
|
116
|
+
return {
|
|
117
|
+
sx,
|
|
118
|
+
sy,
|
|
119
|
+
sw: Math.round(cw),
|
|
120
|
+
sh: Math.round(ch),
|
|
121
|
+
width: Math.round(cw * scale),
|
|
122
|
+
height: Math.round(ch * scale),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
78
126
|
/** The CSS that draws them on Teacup's `.wall`, by the page's data-wallpaper and data-theme. */
|
|
79
127
|
export function wallpapersCss(): string {
|
|
80
128
|
return WALLPAPERS.map((w) => {
|
package/kit/system.ts
CHANGED
|
@@ -5,4 +5,11 @@
|
|
|
5
5
|
export { DEVICE_FEATURES, GUIDELINES_VERSION, type Manifest } from "./src/manifest";
|
|
6
6
|
export { mergePrefs } from "./src/prefs";
|
|
7
7
|
export { type Appearance, type HomeScreen, KIT_VERSION, type Prefs, type WeatherPlace } from "./src/protocol";
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_WALLPAPER,
|
|
10
|
+
PHOTO_EDGE,
|
|
11
|
+
type PhotoLayout,
|
|
12
|
+
photoFrame,
|
|
13
|
+
WALLPAPERS,
|
|
14
|
+
wallpaperFor,
|
|
15
|
+
} from "./src/wallpapers";
|
package/kit/widgets.css
CHANGED
|
@@ -80,12 +80,12 @@ button.widget,
|
|
|
80
80
|
}
|
|
81
81
|
.w-today .wd-big {
|
|
82
82
|
font-weight: 300;
|
|
83
|
-
font-size: 44px;
|
|
83
|
+
font-size: calc(44px * var(--text-scale, 1));
|
|
84
84
|
letter-spacing: -0.04em;
|
|
85
85
|
line-height: 1;
|
|
86
86
|
}
|
|
87
87
|
.w-today .wd-sub {
|
|
88
|
-
font-size: 15px;
|
|
88
|
+
font-size: calc(15px * var(--text-scale, 1));
|
|
89
89
|
color: var(--wg-soft);
|
|
90
90
|
white-space: nowrap;
|
|
91
91
|
}
|
|
@@ -98,7 +98,7 @@ button.widget,
|
|
|
98
98
|
align-items: center;
|
|
99
99
|
gap: 6px;
|
|
100
100
|
font-weight: 300;
|
|
101
|
-
font-size: 26px;
|
|
101
|
+
font-size: calc(26px * var(--text-scale, 1));
|
|
102
102
|
letter-spacing: -0.02em;
|
|
103
103
|
line-height: 1;
|
|
104
104
|
}
|
|
@@ -122,7 +122,7 @@ button.widget,
|
|
|
122
122
|
stroke: #d8902a;
|
|
123
123
|
}
|
|
124
124
|
.w-today .wd-say {
|
|
125
|
-
font-size: 13px;
|
|
125
|
+
font-size: calc(13px * var(--text-scale, 1));
|
|
126
126
|
color: var(--wg-soft);
|
|
127
127
|
margin-top: 6px;
|
|
128
128
|
white-space: nowrap;
|
|
@@ -136,7 +136,7 @@ button.widget,
|
|
|
136
136
|
margin-top: 12px;
|
|
137
137
|
padding: 8px 0;
|
|
138
138
|
border-block: 1px solid var(--wg-hair);
|
|
139
|
-
font-size: 12px;
|
|
139
|
+
font-size: calc(12px * var(--text-scale, 1));
|
|
140
140
|
color: var(--wg-soft);
|
|
141
141
|
}
|
|
142
142
|
.w-today .wd-hours div {
|
|
@@ -150,7 +150,7 @@ button.widget,
|
|
|
150
150
|
}
|
|
151
151
|
.w-today .wd-hours b {
|
|
152
152
|
font-weight: 400;
|
|
153
|
-
font-size: 13px;
|
|
153
|
+
font-size: calc(13px * var(--text-scale, 1));
|
|
154
154
|
color: var(--wg-ink);
|
|
155
155
|
}
|
|
156
156
|
.w-today ul {
|
|
@@ -168,7 +168,7 @@ button.widget,
|
|
|
168
168
|
grid-template-columns: 8px 1fr;
|
|
169
169
|
gap: 10px;
|
|
170
170
|
align-items: baseline;
|
|
171
|
-
font-size: 14px;
|
|
171
|
+
font-size: calc(14px * var(--text-scale, 1));
|
|
172
172
|
}
|
|
173
173
|
.w-today li span,
|
|
174
174
|
.w-today .wd-count span {
|
|
@@ -203,7 +203,7 @@ button.widget,
|
|
|
203
203
|
display: grid;
|
|
204
204
|
margin-top: auto;
|
|
205
205
|
padding-top: 8px;
|
|
206
|
-
font-size: 12px;
|
|
206
|
+
font-size: calc(12px * var(--text-scale, 1));
|
|
207
207
|
gap: 7px;
|
|
208
208
|
grid-template-columns: 7px 1fr;
|
|
209
209
|
}
|
|
@@ -215,10 +215,10 @@ button.widget,
|
|
|
215
215
|
margin-top: 4px;
|
|
216
216
|
}
|
|
217
217
|
.w-today .wd-sub {
|
|
218
|
-
font-size: 12px;
|
|
218
|
+
font-size: calc(12px * var(--text-scale, 1));
|
|
219
219
|
}
|
|
220
220
|
.w-today .wd-now {
|
|
221
|
-
font-size: 18px;
|
|
221
|
+
font-size: calc(18px * var(--text-scale, 1));
|
|
222
222
|
gap: 4px;
|
|
223
223
|
}
|
|
224
224
|
.w-today .wd-now .wd-sky {
|
|
@@ -229,14 +229,14 @@ button.widget,
|
|
|
229
229
|
grid-template-columns: repeat(3, 1fr);
|
|
230
230
|
margin-top: 8px;
|
|
231
231
|
padding: 6px 0;
|
|
232
|
-
font-size: 11px;
|
|
232
|
+
font-size: calc(11px * var(--text-scale, 1));
|
|
233
233
|
}
|
|
234
234
|
.w-today .wd-hours .wd-sky {
|
|
235
235
|
width: 15px;
|
|
236
236
|
height: 15px;
|
|
237
237
|
}
|
|
238
238
|
.w-today .wd-hours b {
|
|
239
|
-
font-size: 11px;
|
|
239
|
+
font-size: calc(11px * var(--text-scale, 1));
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
@@ -249,13 +249,13 @@ button.widget,
|
|
|
249
249
|
}
|
|
250
250
|
.w-app .wd-big {
|
|
251
251
|
font-weight: 300;
|
|
252
|
-
font-size: 40px;
|
|
252
|
+
font-size: calc(40px * var(--text-scale, 1));
|
|
253
253
|
letter-spacing: -0.03em;
|
|
254
254
|
line-height: 1.1;
|
|
255
255
|
margin-top: 10px;
|
|
256
256
|
}
|
|
257
257
|
.w-app .wd-sub {
|
|
258
|
-
font-size: 15px;
|
|
258
|
+
font-size: calc(15px * var(--text-scale, 1));
|
|
259
259
|
color: var(--wg-soft);
|
|
260
260
|
margin-top: 2px;
|
|
261
261
|
}
|
|
@@ -281,7 +281,7 @@ button.widget,
|
|
|
281
281
|
padding: 0;
|
|
282
282
|
display: grid;
|
|
283
283
|
gap: 6px;
|
|
284
|
-
font-size: 15px;
|
|
284
|
+
font-size: calc(15px * var(--text-scale, 1));
|
|
285
285
|
overflow: hidden;
|
|
286
286
|
}
|
|
287
287
|
.w-app li {
|
|
@@ -317,7 +317,7 @@ button.widget,
|
|
|
317
317
|
min-height: 0;
|
|
318
318
|
}
|
|
319
319
|
.widget-box .w-today .wd-big {
|
|
320
|
-
font-size: 34px;
|
|
320
|
+
font-size: calc(34px * var(--text-scale, 1));
|
|
321
321
|
}
|
|
322
322
|
.widget-box .w-today .wd-hours {
|
|
323
323
|
margin-top: 8px;
|
package/package.json
CHANGED
package/template/app.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"name": "__NAME__",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"description": "__NAME__: say in one sentence what it does and who it's for.",
|
|
6
|
+
"subtitle": "__NAME__ in a few words",
|
|
6
7
|
"kind": "kit",
|
|
7
8
|
"url": "/apps/__ID__/",
|
|
8
9
|
"iconBackground": "linear-gradient(160deg,#ffffff,#e9e6df)",
|
package/template/icon.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg viewBox="0 0
|
|
1
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="#2b2f36" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><!-- Lucide: box --><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>
|