@vibes.diy/prompts 14.3.202 → 14.3.204
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/llms/access.md +4 -4
- package/llms/backend.md +1 -1
- package/llms/blockly.md +2 -2
- package/llms/bluesky.md +1 -1
- package/llms/board-games.md +95 -1
- package/llms/calendar.md +1 -1
- package/llms/callai.md +4 -4
- package/llms/connections.md +1 -1
- package/llms/create-vibe.md +3 -3
- package/llms/fireproof.md +7 -7
- package/llms/github.md +6 -2
- package/llms/image-gen.md +2 -2
- package/llms/index.d.ts +28 -28
- package/llms/index.js +84 -84
- package/llms/index.js.map +1 -1
- package/llms/maps.js +14 -2
- package/llms/maps.js.map +1 -1
- package/llms/maps.md +212 -9
- package/llms/match-style.md +1 -1
- package/llms/motion.md +7 -7
- package/llms/p5.md +1 -1
- package/llms/physics.md +2 -2
- package/llms/reskin.js +8 -1
- package/llms/reskin.js.map +1 -1
- package/llms/reskin.md +99 -20
- package/llms/richtext.md +5 -5
- package/llms/spotify.md +1 -1
- package/llms/stripe-subscriptions.md +1 -1
- package/llms/stripe.md +3 -3
- package/llms/strudel.md +3 -3
- package/llms/three-js.md +4 -4
- package/llms/touch-games.js +1 -1
- package/llms/touch-games.js.map +1 -1
- package/llms/touch-games.md +3 -3
- package/llms/ui-blog.d.ts +2 -0
- package/llms/ui-blog.js +7 -0
- package/llms/ui-blog.js.map +1 -0
- package/llms/{pouf-blog.md → ui-blog.md} +10 -10
- package/llms/ui-booking.d.ts +2 -0
- package/llms/ui-booking.js +7 -0
- package/llms/ui-booking.js.map +1 -0
- package/llms/{pouf-booking.md → ui-booking.md} +8 -8
- package/llms/ui-calendar.d.ts +2 -0
- package/llms/ui-calendar.js +7 -0
- package/llms/ui-calendar.js.map +1 -0
- package/llms/{pouf-calendar.md → ui-calendar.md} +5 -5
- package/llms/ui-chat.d.ts +2 -0
- package/llms/ui-chat.js +7 -0
- package/llms/ui-chat.js.map +1 -0
- package/llms/{pouf-chat.md → ui-chat.md} +4 -4
- package/llms/ui-contact.d.ts +2 -0
- package/llms/ui-contact.js +7 -0
- package/llms/ui-contact.js.map +1 -0
- package/llms/{pouf-contact.md → ui-contact.md} +7 -7
- package/llms/ui-course.d.ts +2 -0
- package/llms/ui-course.js +7 -0
- package/llms/ui-course.js.map +1 -0
- package/llms/{pouf-course.md → ui-course.md} +9 -9
- package/llms/ui-crm.d.ts +2 -0
- package/llms/ui-crm.js +7 -0
- package/llms/ui-crm.js.map +1 -0
- package/llms/{pouf-crm.md → ui-crm.md} +5 -5
- package/llms/ui-dashboard.d.ts +2 -0
- package/llms/ui-dashboard.js +7 -0
- package/llms/ui-dashboard.js.map +1 -0
- package/llms/{pouf-dashboard.md → ui-dashboard.md} +7 -7
- package/llms/ui-editorial.d.ts +2 -0
- package/llms/ui-editorial.js +7 -0
- package/llms/ui-editorial.js.map +1 -0
- package/llms/{pouf-editorial.md → ui-editorial.md} +5 -5
- package/llms/ui-event.d.ts +2 -0
- package/llms/ui-event.js +7 -0
- package/llms/ui-event.js.map +1 -0
- package/llms/{pouf-event.md → ui-event.md} +5 -5
- package/llms/ui-feed.d.ts +2 -0
- package/llms/ui-feed.js +7 -0
- package/llms/ui-feed.js.map +1 -0
- package/llms/{pouf-feed.md → ui-feed.md} +5 -5
- package/llms/ui-game.d.ts +2 -0
- package/llms/ui-game.js +7 -0
- package/llms/ui-game.js.map +1 -0
- package/llms/{pouf-game.md → ui-game.md} +6 -6
- package/llms/ui-inbox.d.ts +2 -0
- package/llms/ui-inbox.js +7 -0
- package/llms/ui-inbox.js.map +1 -0
- package/llms/{pouf-inbox.md → ui-inbox.md} +5 -5
- package/llms/ui-inventory.d.ts +2 -0
- package/llms/ui-inventory.js +7 -0
- package/llms/ui-inventory.js.map +1 -0
- package/llms/{pouf-inventory.md → ui-inventory.md} +5 -5
- package/llms/ui-kanban.d.ts +2 -0
- package/llms/ui-kanban.js +7 -0
- package/llms/ui-kanban.js.map +1 -0
- package/llms/{pouf-kanban.md → ui-kanban.md} +5 -5
- package/llms/ui-landing.d.ts +2 -0
- package/llms/ui-landing.js +7 -0
- package/llms/ui-landing.js.map +1 -0
- package/llms/{pouf-landing.md → ui-landing.md} +6 -6
- package/llms/ui-login.d.ts +2 -0
- package/llms/ui-login.js +7 -0
- package/llms/ui-login.js.map +1 -0
- package/llms/{pouf-login.md → ui-login.md} +7 -7
- package/llms/ui-music.d.ts +2 -0
- package/llms/ui-music.js +7 -0
- package/llms/ui-music.js.map +1 -0
- package/llms/{pouf-music.md → ui-music.md} +6 -6
- package/llms/ui-onboarding.d.ts +2 -0
- package/llms/ui-onboarding.js +7 -0
- package/llms/ui-onboarding.js.map +1 -0
- package/llms/{pouf-onboarding.md → ui-onboarding.md} +5 -5
- package/llms/ui-pricing.d.ts +2 -0
- package/llms/ui-pricing.js +7 -0
- package/llms/ui-pricing.js.map +1 -0
- package/llms/{pouf-pricing.md → ui-pricing.md} +5 -5
- package/llms/ui-profile.d.ts +2 -0
- package/llms/ui-profile.js +7 -0
- package/llms/ui-profile.js.map +1 -0
- package/llms/{pouf-profile.md → ui-profile.md} +4 -4
- package/llms/ui-quiz.d.ts +2 -0
- package/llms/ui-quiz.js +7 -0
- package/llms/ui-quiz.js.map +1 -0
- package/llms/{pouf-quiz.md → ui-quiz.md} +6 -6
- package/llms/ui-settings.d.ts +2 -0
- package/llms/ui-settings.js +7 -0
- package/llms/ui-settings.js.map +1 -0
- package/llms/{pouf-settings.md → ui-settings.md} +5 -5
- package/llms/ui-storefront.d.ts +2 -0
- package/llms/ui-storefront.js +7 -0
- package/llms/ui-storefront.js.map +1 -0
- package/llms/{pouf-storefront.md → ui-storefront.md} +9 -9
- package/llms/ui-support.d.ts +2 -0
- package/llms/ui-support.js +7 -0
- package/llms/ui-support.js.map +1 -0
- package/llms/{pouf-support.md → ui-support.md} +5 -5
- package/llms/ui-testimonials.d.ts +2 -0
- package/llms/ui-testimonials.js +7 -0
- package/llms/ui-testimonials.js.map +1 -0
- package/llms/{pouf-testimonials.md → ui-testimonials.md} +6 -6
- package/llms/ui-todo.d.ts +2 -0
- package/llms/ui-todo.js +7 -0
- package/llms/ui-todo.js.map +1 -0
- package/llms/{pouf-todo.md → ui-todo.md} +5 -5
- package/llms/ui-weather.d.ts +2 -0
- package/llms/ui-weather.js +7 -0
- package/llms/ui-weather.js.map +1 -0
- package/llms/{pouf-weather.md → ui-weather.md} +5 -5
- package/llms/use-vibe.md +4 -4
- package/llms/use-viewer.md +5 -5
- package/llms/voxel.md +1 -1
- package/llms/web-audio.md +49 -26
- package/llms/worlds.md +3 -3
- package/llms/youtube.md +1 -1
- package/package.json +5 -5
- package/system-prompt-initial-instant.md +3 -3
- package/system-prompt.md +3 -3
- package/themes/colorsets.js +7 -7
- package/themes/colorsets.js.map +1 -1
- package/themes/looks/pouf/brief.md +87 -85
- package/themes/looks/pouf/catalog.generated.d.ts +1 -1
- package/themes/looks/pouf/catalog.generated.js +47 -17
- package/themes/looks/pouf/catalog.generated.js.map +1 -1
- package/themes/looks/pouf/variety.js +4 -4
- package/themes/looks/pouf/variety.js.map +1 -1
- package/llms/pouf-blog.d.ts +0 -2
- package/llms/pouf-blog.js +0 -6
- package/llms/pouf-blog.js.map +0 -1
- package/llms/pouf-booking.d.ts +0 -2
- package/llms/pouf-booking.js +0 -6
- package/llms/pouf-booking.js.map +0 -1
- package/llms/pouf-calendar.d.ts +0 -2
- package/llms/pouf-calendar.js +0 -6
- package/llms/pouf-calendar.js.map +0 -1
- package/llms/pouf-chat.d.ts +0 -2
- package/llms/pouf-chat.js +0 -6
- package/llms/pouf-chat.js.map +0 -1
- package/llms/pouf-contact.d.ts +0 -2
- package/llms/pouf-contact.js +0 -6
- package/llms/pouf-contact.js.map +0 -1
- package/llms/pouf-course.d.ts +0 -2
- package/llms/pouf-course.js +0 -6
- package/llms/pouf-course.js.map +0 -1
- package/llms/pouf-crm.d.ts +0 -2
- package/llms/pouf-crm.js +0 -6
- package/llms/pouf-crm.js.map +0 -1
- package/llms/pouf-dashboard.d.ts +0 -2
- package/llms/pouf-dashboard.js +0 -6
- package/llms/pouf-dashboard.js.map +0 -1
- package/llms/pouf-editorial.d.ts +0 -2
- package/llms/pouf-editorial.js +0 -6
- package/llms/pouf-editorial.js.map +0 -1
- package/llms/pouf-event.d.ts +0 -2
- package/llms/pouf-event.js +0 -6
- package/llms/pouf-event.js.map +0 -1
- package/llms/pouf-feed.d.ts +0 -2
- package/llms/pouf-feed.js +0 -6
- package/llms/pouf-feed.js.map +0 -1
- package/llms/pouf-game.d.ts +0 -2
- package/llms/pouf-game.js +0 -6
- package/llms/pouf-game.js.map +0 -1
- package/llms/pouf-inbox.d.ts +0 -2
- package/llms/pouf-inbox.js +0 -6
- package/llms/pouf-inbox.js.map +0 -1
- package/llms/pouf-inventory.d.ts +0 -2
- package/llms/pouf-inventory.js +0 -6
- package/llms/pouf-inventory.js.map +0 -1
- package/llms/pouf-kanban.d.ts +0 -2
- package/llms/pouf-kanban.js +0 -6
- package/llms/pouf-kanban.js.map +0 -1
- package/llms/pouf-landing.d.ts +0 -2
- package/llms/pouf-landing.js +0 -6
- package/llms/pouf-landing.js.map +0 -1
- package/llms/pouf-login.d.ts +0 -2
- package/llms/pouf-login.js +0 -6
- package/llms/pouf-login.js.map +0 -1
- package/llms/pouf-music.d.ts +0 -2
- package/llms/pouf-music.js +0 -6
- package/llms/pouf-music.js.map +0 -1
- package/llms/pouf-onboarding.d.ts +0 -2
- package/llms/pouf-onboarding.js +0 -6
- package/llms/pouf-onboarding.js.map +0 -1
- package/llms/pouf-pricing.d.ts +0 -2
- package/llms/pouf-pricing.js +0 -6
- package/llms/pouf-pricing.js.map +0 -1
- package/llms/pouf-profile.d.ts +0 -2
- package/llms/pouf-profile.js +0 -6
- package/llms/pouf-profile.js.map +0 -1
- package/llms/pouf-quiz.d.ts +0 -2
- package/llms/pouf-quiz.js +0 -6
- package/llms/pouf-quiz.js.map +0 -1
- package/llms/pouf-settings.d.ts +0 -2
- package/llms/pouf-settings.js +0 -6
- package/llms/pouf-settings.js.map +0 -1
- package/llms/pouf-storefront.d.ts +0 -2
- package/llms/pouf-storefront.js +0 -6
- package/llms/pouf-storefront.js.map +0 -1
- package/llms/pouf-support.d.ts +0 -2
- package/llms/pouf-support.js +0 -6
- package/llms/pouf-support.js.map +0 -1
- package/llms/pouf-testimonials.d.ts +0 -2
- package/llms/pouf-testimonials.js +0 -6
- package/llms/pouf-testimonials.js.map +0 -1
- package/llms/pouf-todo.d.ts +0 -2
- package/llms/pouf-todo.js +0 -6
- package/llms/pouf-todo.js.map +0 -1
- package/llms/pouf-weather.d.ts +0 -2
- package/llms/pouf-weather.js +0 -6
- package/llms/pouf-weather.js.map +0 -1
package/llms/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
|
package/llms/maps.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
export const mapsConfig = {
|
|
2
2
|
name: "maps",
|
|
3
3
|
label: "maps",
|
|
4
|
-
description: "
|
|
5
|
-
cues: [
|
|
4
|
+
description: "maps of every kind. World overviews (countries, travel journals, places visited, best places to live, territory games, choropleths) use bundled country outlines and SVG markers that render immediately. Street maps of places near the person (restaurants, coffee, parks, \"near me\", a city or zip, directions) use MapLibre vector tiles plus an OpenStreetMap place search that opens already full of real places, with friends' ratings and directions links on top",
|
|
5
|
+
cues: [
|
|
6
|
+
"world map",
|
|
7
|
+
"countries",
|
|
8
|
+
"places visited",
|
|
9
|
+
"choropleth",
|
|
10
|
+
"travel map",
|
|
11
|
+
"geography",
|
|
12
|
+
"near me",
|
|
13
|
+
"nearby",
|
|
14
|
+
"local map",
|
|
15
|
+
"street map",
|
|
16
|
+
"driving directions",
|
|
17
|
+
],
|
|
6
18
|
};
|
|
7
19
|
//# sourceMappingURL=maps.js.map
|
package/llms/maps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maps.js","sourceRoot":"","sources":["../../jsr/llms/maps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAc;IACnC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;IACb,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"maps.js","sourceRoot":"","sources":["../../jsr/llms/maps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAc;IACnC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;IACb,WAAW,EACT,6cAA6c;IAC/c,IAAI,EAAE;QACJ,WAAW;QACX,WAAW;QACX,gBAAgB;QAChB,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,SAAS;QACT,QAAQ;QACR,WAAW;QACX,YAAY;QACZ,oBAAoB;KACrB;CACF,CAAC"}
|
package/llms/maps.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
# Maps — a world overview
|
|
1
|
+
# Maps — a world overview, or a street map of places near the person
|
|
2
|
+
|
|
3
|
+
Maps come in two shapes, and the ask picks one. Countries, travel journals,
|
|
4
|
+
territory games and choropleths get the **world overview** below, drawn from
|
|
5
|
+
bundled outlines. An ask about places someone can walk or drive to (restaurants,
|
|
6
|
+
coffee, parks, "near me", "local", a city or zip, directions) gets the
|
|
7
|
+
**street map of places near the person** further down: vector tiles, a place
|
|
8
|
+
search, and a feed of real places that is full on the first screen.
|
|
9
|
+
|
|
10
|
+
## World overview
|
|
2
11
|
|
|
3
12
|
For world maps, travel journals, country comparisons and territory games, import
|
|
4
13
|
shapes and geometry from `@vibes.diy/geo`. The platform bundles the data and
|
|
@@ -21,7 +30,7 @@ oriented for D3; keep its rings intact.
|
|
|
21
30
|
|
|
22
31
|
```jsx
|
|
23
32
|
import React, { useState } from "react";
|
|
24
|
-
import { Button } from "@vibes.diy/
|
|
33
|
+
import { Button } from "@vibes.diy/ui";
|
|
25
34
|
import { countries, geoNaturalEarth1, geoPath } from "@vibes.diy/geo";
|
|
26
35
|
|
|
27
36
|
const projection = geoNaturalEarth1().fitExtent([[12, 12], [788, 408]], { type: "Sphere" });
|
|
@@ -65,12 +74,206 @@ country IDs and display the units and range in their legend; missing data has
|
|
|
65
74
|
its own fill and label.
|
|
66
75
|
|
|
67
76
|
World outlines come with the app; no runtime shape fetch is needed.
|
|
68
|
-
The data is generalized Natural Earth 4.1.0 via world-atlas 2.0.2. It is
|
|
69
|
-
|
|
70
|
-
map provider with its own coverage, credentials and attribution. Keep that choice
|
|
71
|
-
separate from a request to show results on a world map.
|
|
77
|
+
The data is generalized Natural Earth 4.1.0 via world-atlas 2.0.2. It is made for
|
|
78
|
+
world overviews; streets, neighborhoods and places to go are the street map's job.
|
|
72
79
|
|
|
73
|
-
Verify
|
|
80
|
+
Verify a world overview at phone width: outlines, matching fills and place
|
|
74
81
|
markers appear, tapping a result opens its details, the legend is readable, and
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
the outlines arrive with the app rather than from a shape download.
|
|
83
|
+
|
|
84
|
+
## Street map of places near the person
|
|
85
|
+
|
|
86
|
+
**A local ask opens on results.** "Vegan restaurants I can share with friends",
|
|
87
|
+
"coffee near me", "dog parks in my city": the first screen is a street map with
|
|
88
|
+
numbered pins and a feed of real places beside it, with no form to fill first.
|
|
89
|
+
The area comes from, in order: the area the person picked last time (kept in
|
|
90
|
+
`localStorage`), a city-level guess from their connection
|
|
91
|
+
(`https://get.geojs.io/v1/ip/geo.json`: `city`, `region`, `latitude`,
|
|
92
|
+
`longitude`; no permission prompt), and a well-known city when neither answers.
|
|
93
|
+
One line beside the results says what it shows and how the area was chosen: "38
|
|
94
|
+
spots within 5 mi of Portland, Oregon (a guess from your connection)".
|
|
95
|
+
|
|
96
|
+
That opening is for places the public world holds. When the ask is the person's
|
|
97
|
+
own collection ("places I've been", "my favorite spots", "my trips"), the app
|
|
98
|
+
opens on their own records and the place search is how they add the next one.
|
|
99
|
+
A public list never stands in for their own.
|
|
100
|
+
|
|
101
|
+
**The first paint reads well with no network.** Above the map, the area line,
|
|
102
|
+
the city or zip box and the location button render from code alone. The feed
|
|
103
|
+
holds framed placeholders until the places arrive, and the map box shows a quiet
|
|
104
|
+
background color until its tiles arrive. Keep the last good answer for each area
|
|
105
|
+
in `localStorage` and paint it before the next search goes out, so a return
|
|
106
|
+
visit opens instantly and a busy place search still shows real places.
|
|
107
|
+
|
|
108
|
+
**The location prompt runs only inside the handler of a button the person taps.**
|
|
109
|
+
A "Use my location" button calls `navigator.geolocation.getCurrentPosition`
|
|
110
|
+
from its click handler, rounds to three decimals (a block, not a doorstep), and
|
|
111
|
+
searches there. Never call it from an effect or at load: the guess already gave
|
|
112
|
+
them a map, and the button is the upgrade they choose. A city or zip box sits
|
|
113
|
+
beside it, looked up on submit with Photon
|
|
114
|
+
(`https://photon.komoot.io/api/?q=<text>&limit=5&lat=<lat>&lon=<lng>`, biased
|
|
115
|
+
toward the current area so "97214" finds Portland, not a village abroad).
|
|
116
|
+
Reverse lookups for a friendly label use `https://photon.komoot.io/reverse?lat=…&lon=…`.
|
|
117
|
+
|
|
118
|
+
**Draw the map with MapLibre and OpenFreeMap vector tiles**: free, keyless and
|
|
119
|
+
sharp at every zoom. Load the library on first render from its pinned URL, give
|
|
120
|
+
the map a container with a real height, and theme the pins with the app's own
|
|
121
|
+
classes:
|
|
122
|
+
|
|
123
|
+
```jsx
|
|
124
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
125
|
+
|
|
126
|
+
let lib;
|
|
127
|
+
const loadMapLibre = () => (lib ??= (() => {
|
|
128
|
+
const css = document.createElement("link");
|
|
129
|
+
css.rel = "stylesheet";
|
|
130
|
+
css.href = "https://unpkg.com/maplibre-gl@5.6.0/dist/maplibre-gl.css";
|
|
131
|
+
document.head.appendChild(css);
|
|
132
|
+
return import("https://esm.sh/maplibre-gl@5.6.0").then((m) => m.default ?? m);
|
|
133
|
+
})());
|
|
134
|
+
|
|
135
|
+
const styleUrl = () => matchMedia("(prefers-color-scheme: dark)").matches
|
|
136
|
+
? "https://tiles.openfreemap.org/styles/dark"
|
|
137
|
+
: "https://tiles.openfreemap.org/styles/positron"; // also: liberty, bright
|
|
138
|
+
|
|
139
|
+
export function PlaceMap({ places, center, selectedId, onPick }) {
|
|
140
|
+
const box = useRef(null);
|
|
141
|
+
const map = useRef(null);
|
|
142
|
+
const pins = useRef(new Map());
|
|
143
|
+
const [ready, setReady] = useState(false);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
let alive = true;
|
|
146
|
+
loadMapLibre().then((ml) => {
|
|
147
|
+
if (!alive) return;
|
|
148
|
+
map.current = new ml.Map({ container: box.current, style: styleUrl(),
|
|
149
|
+
center: [center.lng, center.lat], zoom: 12, attributionControl: false, cooperativeGestures: true });
|
|
150
|
+
map.current.addControl(new ml.NavigationControl({ showCompass: false }), "top-right");
|
|
151
|
+
map.current.on("load", () => alive && setReady(true));
|
|
152
|
+
});
|
|
153
|
+
return () => { alive = false; map.current?.remove(); };
|
|
154
|
+
}, []);
|
|
155
|
+
// Places arrive after the map: redraw whenever the list changes, once the map is ready.
|
|
156
|
+
const ids = places.map((p) => p.id).join("|");
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!ready) return;
|
|
159
|
+
loadMapLibre().then((ml) => {
|
|
160
|
+
pins.current.forEach((pin) => pin.remove());
|
|
161
|
+
pins.current = new Map();
|
|
162
|
+
places.forEach((place, i) => {
|
|
163
|
+
const el = document.createElement("button");
|
|
164
|
+
el.className = "grid size-8 place-items-center rounded-full border-2 border-background bg-primary text-xs font-bold text-primary-foreground shadow";
|
|
165
|
+
el.textContent = String(i + 1); // the same number the feed shows
|
|
166
|
+
el.setAttribute("aria-label", `${i + 1}. ${place.name}`);
|
|
167
|
+
el.onclick = () => onPick(place.id);
|
|
168
|
+
pins.current.set(place.id, new ml.Marker({ element: el }).setLngLat([place.lng, place.lat]).addTo(map.current));
|
|
169
|
+
});
|
|
170
|
+
if (places.length > 1) {
|
|
171
|
+
const b = new ml.LngLatBounds();
|
|
172
|
+
places.forEach((p) => b.extend([p.lng, p.lat]));
|
|
173
|
+
map.current.fitBounds(b, { padding: 48, maxZoom: 14 });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}, [ready, ids]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const pin = pins.current.get(selectedId);
|
|
179
|
+
if (pin) map.current.easeTo({ center: pin.getLngLat(), zoom: Math.max(map.current.getZoom(), 14) });
|
|
180
|
+
}, [selectedId]);
|
|
181
|
+
return <div className="relative h-80 overflow-hidden rounded-lg border border-border lg:h-screen">
|
|
182
|
+
<div ref={box} className="h-full w-full" role="region" aria-label="Map of places" />
|
|
183
|
+
<span className="absolute bottom-1 left-2 text-xs text-muted-foreground">© OpenFreeMap · © OpenStreetMap contributors</span>
|
|
184
|
+
</div>;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
MapLibre sets `position: relative` on the element it is handed, so size that
|
|
189
|
+
element with `h-full w-full` inside a sized wrapper; an `absolute inset-0` map
|
|
190
|
+
box collapses to zero height. Once the person drags the map, offer a "Search
|
|
191
|
+
this area" button that searches the map's center with a radius taken from its
|
|
192
|
+
bounds.
|
|
193
|
+
|
|
194
|
+
**Places come from OpenStreetMap through the app's own `backend.js`.** A `GET
|
|
195
|
+
/_api/places?lat=…&lng=…&r=…` route asks Overpass and answers the raw elements.
|
|
196
|
+
A read the backend makes is shared through the platform's read cache, so
|
|
197
|
+
friends opening the same area share one answer. When the owner has set a
|
|
198
|
+
`GEOAPIFY_KEY` secret, the same route asks Geoapify's hosted copy of
|
|
199
|
+
OpenStreetMap instead, which answers steadily:
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
const MIRRORS = ["https://overpass-api.de/api/interpreter", "https://overpass.private.coffee/api/interpreter"];
|
|
203
|
+
const json = (d, status = 200) => new Response(JSON.stringify(d), { status, headers: { "content-type": "application/json" } });
|
|
204
|
+
|
|
205
|
+
export async function fetch(request, ctx) {
|
|
206
|
+
const url = new URL(request.url);
|
|
207
|
+
if (url.pathname !== "/places") return json({ ok: false }, 404);
|
|
208
|
+
const lat = Number(url.searchParams.get("lat")), lng = Number(url.searchParams.get("lng"));
|
|
209
|
+
const r = Math.min(Number(url.searchParams.get("r")) || 8000, 40000);
|
|
210
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lng)) return json({ ok: false, reason: "Pick a spot on the map." }, 400);
|
|
211
|
+
try {
|
|
212
|
+
const key = ctx.secrets.GEOAPIFY_KEY;
|
|
213
|
+
if (key) {
|
|
214
|
+
const res = await ctx.fetch(`https://api.geoapify.com/v2/places?categories=catering.restaurant,catering.cafe` +
|
|
215
|
+
`&conditions=vegan&filter=circle:${lng},${lat},${r}&limit=100&apiKey=${encodeURIComponent(key)}`);
|
|
216
|
+
const d = await res.json();
|
|
217
|
+
// datasource.raw carries the OSM tags, so the page reads one shape either way.
|
|
218
|
+
const TYPES = { n: "node", w: "way", r: "relation" };
|
|
219
|
+
return json({ ok: true, elements: d.features.map(({ properties: p }) => {
|
|
220
|
+
const raw = p.datasource?.raw ?? {};
|
|
221
|
+
// Keep the OSM identity (osm-way-12 stays osm-way-12); a result with none gets its own namespace.
|
|
222
|
+
const osm = TYPES[raw.osm_type] && raw.osm_id;
|
|
223
|
+
return { type: osm ? TYPES[raw.osm_type] : "geoapify", id: osm ? raw.osm_id : p.place_id,
|
|
224
|
+
lat: p.lat, lon: p.lon, tags: { name: p.name, ...raw } };
|
|
225
|
+
}) });
|
|
226
|
+
}
|
|
227
|
+
const q = `[out:json][timeout:20];nwr["amenity"~"^(restaurant|cafe|fast_food)$"]["diet:vegan"~"^(only|yes)$"](around:${r},${lat},${lng});out center tags 300;`;
|
|
228
|
+
const elements = await Promise.any(MIRRORS.map(async (base) => {
|
|
229
|
+
const res = await ctx.fetch(`${base}?data=${encodeURIComponent(q)}`);
|
|
230
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
231
|
+
return (await res.json()).elements;
|
|
232
|
+
}));
|
|
233
|
+
return json({ ok: true, elements });
|
|
234
|
+
} catch {
|
|
235
|
+
return json({ ok: false, reason: "The place list is taking its time." }, 502);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Write the Overpass filter for what the person asked about, using the tags
|
|
241
|
+
OpenStreetMap actually carries: `diet:vegan` / `diet:vegetarian` (`only` or
|
|
242
|
+
`yes`), `cuisine=…`, `amenity=cafe|restaurant|bar|ice_cream`, `shop=bakery`,
|
|
243
|
+
`leisure=park|playground|dog_park`, `tourism=museum`. Geoapify names the same
|
|
244
|
+
things as `categories` (`catering.cafe`, `leisure.park`) and `conditions`
|
|
245
|
+
(`vegan`, `vegan.only`, `dogs`, `wheelchair`). Each element becomes a place:
|
|
246
|
+
`id: "osm-" + type + "-" + id`, name, coordinates (`lat`/`lon`, or `center` for a
|
|
247
|
+
building), `addr:*` for the address, `website`, `opening_hours`, `cuisine`.
|
|
248
|
+
Round the search point to two decimals so nearby visitors share a cached answer.
|
|
249
|
+
|
|
250
|
+
Overpass is run by volunteers and is often slow or busy. Give the search about
|
|
251
|
+
20 seconds, then show a "Try again" button and a link that opens the same area
|
|
252
|
+
on a directory that covers it (HappyCow for vegan food, the phone's maps app for
|
|
253
|
+
anything else). Real places only: an empty area says so and suggests the
|
|
254
|
+
nearest city or widening the circle. The person's own pins and notes are how a
|
|
255
|
+
missing spot gets in, never invented names.
|
|
256
|
+
|
|
257
|
+
"Top" and "best" name their ranking in that one line: "fully vegan first, then
|
|
258
|
+
friends' favorites, then the closest". Show up to 50, start with a circle of
|
|
259
|
+
about 5 miles, and widen it when fewer than 25 come back. Rank on what the data
|
|
260
|
+
holds: a strong tag such as `diet:vegan=only`, the friends' own ratings, how
|
|
261
|
+
complete the listing is (website, hours, cuisine, address), and distance.
|
|
262
|
+
|
|
263
|
+
**The person's layer sits on top of the places.** Ratings, comments, saves and
|
|
264
|
+
"been there" checks are the app's own records, keyed to the place's id
|
|
265
|
+
(`restaurantId: "osm-node-123"`) and carrying a snapshot of its name, address
|
|
266
|
+
and coordinates, so a note still reads well if the listing changes. Every
|
|
267
|
+
signed-in friend writes as themselves; pins the owner adds by hand join the feed
|
|
268
|
+
with their own note.
|
|
269
|
+
|
|
270
|
+
**Directions open the phone's maps app**:
|
|
271
|
+
`https://www.google.com/maps/dir/?api=1&destination=<lat>,<lng>&travelmode=driving`,
|
|
272
|
+
or `https://maps.apple.com/?daddr=<lat>,<lng>&dirflg=d` on Apple devices. A place
|
|
273
|
+
shares as the app's public address with `#place=<id>&at=<lat>,<lng>` in the
|
|
274
|
+
fragment. A friend opening it lands on that area with the place open.
|
|
275
|
+
|
|
276
|
+
Verify the first preview at phone width. The map paints, the numbered pins match
|
|
277
|
+
the feed, and tapping a pin brings its card into view. The location prompt
|
|
278
|
+
appears only after "Use my location". A typed zip moves the map, and the page
|
|
279
|
+
credits OpenStreetMap.
|
package/llms/match-style.md
CHANGED
|
@@ -95,7 +95,7 @@ Plain Tailwind utilities read the variables:
|
|
|
95
95
|
`font-sans` goes once on the root element and every child inherits the face.
|
|
96
96
|
|
|
97
97
|
**Every component is plain JSX with Tailwind utilities, written in the app itself.**
|
|
98
|
-
A match-style app imports no look package (`@vibes.diy/
|
|
98
|
+
A match-style app imports no look package (`@vibes.diy/ui`, `@vibes.diy/look`):
|
|
99
99
|
its buttons, cards, rows and fields are a few lines of JSX each, shaped by the
|
|
100
100
|
recipe below. Each extracted component file paints with the same utilities.
|
|
101
101
|
|
package/llms/motion.md
CHANGED
|
@@ -14,7 +14,7 @@ stops when the component goes away.
|
|
|
14
14
|
|
|
15
15
|
The sibling boundary: a list whose items fade and rise as they enter uses
|
|
16
16
|
the house flourish, the `starting:` Tailwind classes, with no engine at all;
|
|
17
|
-
a canvas that redraws every frame is `p5`, `worlds` or `physics`;
|
|
17
|
+
a canvas that redraws every frame is `p5`, `worlds` or `physics`; the UI's
|
|
18
18
|
components carry their own motion (a `Card` with `motion="lift"`, the
|
|
19
19
|
toasts, the sheets). Motion is the right call when something on the page
|
|
20
20
|
responds to scroll, to time, or to touch.
|
|
@@ -52,8 +52,8 @@ before the engine lands cancels the setup (measured: a `move` stopped during
|
|
|
52
52
|
the load moved nothing). `motionScope()` is the collector.
|
|
53
53
|
|
|
54
54
|
**Every helper reads the reduced-motion setting when it runs** and lands on
|
|
55
|
-
the end state instead of animating.
|
|
56
|
-
to
|
|
55
|
+
the end state instead of animating. The UI's own reduced-motion rule is scoped
|
|
56
|
+
to the UI's elements, so an animation the app authors gets no safety net from
|
|
57
57
|
it; the helpers are the net (measured with the media feature emulated: lists
|
|
58
58
|
land visible, reveals leave everything visible, the count shows its final
|
|
59
59
|
number, the press does nothing, scroll progress still reports).
|
|
@@ -212,7 +212,7 @@ export function pressSquash(target, { scale = 0.94 } = {}) {
|
|
|
212
212
|
### `App.jsx`
|
|
213
213
|
```jsx app=story
|
|
214
214
|
import React, { useEffect, useRef } from "react";
|
|
215
|
-
import { Button, Card, Heading, Row, Spacer, Stack, Text } from "@vibes.diy/
|
|
215
|
+
import { Button, Card, Heading, Row, Spacer, Stack, Text } from "@vibes.diy/ui";
|
|
216
216
|
import { countUp, motionScope, onEnter, pressSquash, reveal, scrollProgress, sequence, staggerIn } from "./lib/motion.js";
|
|
217
217
|
|
|
218
218
|
const CHAPTERS = [
|
|
@@ -297,7 +297,7 @@ export default function App() {
|
|
|
297
297
|
```
|
|
298
298
|
|
|
299
299
|
The JSX stays class-only: positions, sizes and colours are Tailwind classes
|
|
300
|
-
and
|
|
300
|
+
and component props, and Motion writes transforms and opacity onto the elements
|
|
301
301
|
through refs and selectors. The one place a style is set in the app's own
|
|
302
302
|
code is the progress bar's `transform` inside the scroll callback, through
|
|
303
303
|
its ref, which is what a scroll binding is for.
|
|
@@ -434,7 +434,7 @@ export function pressSquash(target, { scale = 0.94 } = {}) {
|
|
|
434
434
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
435
435
|
import { useFireproof } from "use-fireproof";
|
|
436
436
|
import { useVibe, useViewer } from "use-vibes";
|
|
437
|
-
import { Button, Card, Field, Heading, Input, Row, Skeleton, Spacer, Stack, Text } from "@vibes.diy/
|
|
437
|
+
import { Button, Card, Field, Heading, Input, Row, Skeleton, Spacer, Stack, Text } from "@vibes.diy/ui";
|
|
438
438
|
import { motionScope, move, pressSquash } from "./lib/motion.js";
|
|
439
439
|
|
|
440
440
|
export default function App() {
|
|
@@ -544,7 +544,7 @@ is the baseline and animates nothing, every later snapshot's ids are
|
|
|
544
544
|
remembered, and only a top note nobody has seen springs in, so a board that
|
|
545
545
|
loaded with twenty notes stands still and a reorder moves nothing. (A board
|
|
546
546
|
that is empty when it opens treats its first note as the baseline too; the
|
|
547
|
-
second note is the first to spring.)
|
|
547
|
+
second note is the first to spring.) The UI's `Card motion="lift"` carries
|
|
548
548
|
the hover, so the app writes none of its own.
|
|
549
549
|
|
|
550
550
|
## Mobile
|
package/llms/p5.md
CHANGED
|
@@ -24,7 +24,7 @@ the instance so remounts do not leak a second draw loop.
|
|
|
24
24
|
```jsx
|
|
25
25
|
import React from "react";
|
|
26
26
|
import p5 from "https://esm.sh/p5@1.11.13";
|
|
27
|
-
import { Button, Row, Stack } from "@vibes.diy/
|
|
27
|
+
import { Button, Row, Stack } from "@vibes.diy/ui";
|
|
28
28
|
|
|
29
29
|
const TAU = Math.PI * 2;
|
|
30
30
|
|
package/llms/physics.md
CHANGED
|
@@ -225,7 +225,7 @@ from the pieces so the next drop replays the last one exactly.
|
|
|
225
225
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
226
226
|
import { useFireproof } from "use-fireproof";
|
|
227
227
|
import { useVibe, useViewer } from "use-vibes";
|
|
228
|
-
import { Badge, Button, Card, Field, Heading, Input, Row, Segmented, Skeleton, Spacer, Stack, Text } from "@vibes.diy/
|
|
228
|
+
import { Badge, Button, Card, Field, Heading, Input, Row, Segmented, Skeleton, Spacer, Stack, Text } from "@vibes.diy/ui";
|
|
229
229
|
import { createWorld } from "./lib/physics.js";
|
|
230
230
|
|
|
231
231
|
const WIDTH = 360;
|
|
@@ -495,7 +495,7 @@ button that applies a force to every body.
|
|
|
495
495
|
### `App.jsx`
|
|
496
496
|
```jsx app=tumble
|
|
497
497
|
import React, { useEffect, useRef, useState } from "react";
|
|
498
|
-
import { Button, Field, Heading, Input, Row, Spacer, Stack, Text } from "@vibes.diy/
|
|
498
|
+
import { Button, Field, Heading, Input, Row, Spacer, Stack, Text } from "@vibes.diy/ui";
|
|
499
499
|
import { Matter, createWorld, cssColor } from "./lib/physics.js";
|
|
500
500
|
|
|
501
501
|
const { Bodies, Body, Composite } = Matter;
|
package/llms/reskin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const reskinConfig = {
|
|
2
2
|
name: "reskin",
|
|
3
3
|
label: "Reskin",
|
|
4
|
-
description: 'The person says "look like…", "make it feel…" or names a style or mood — "a 90s Mac app", "a newspaper", "chopped and screwed", "Y2K", "neo-brutalist", "like Stripe\'s dashboard" — with nothing to copy supplied: keep the look\'s components, logic and Look tab, and give them that appearance — the look\'s tokens re-scoped on one wrapper, its hook classes styled from a <style> string, web fonts, borders, texture, motion and a recomposed layout',
|
|
4
|
+
description: 'The person says "look like…", "make it feel…" or names a style or mood — "a 90s Mac app", "a newspaper", "chopped and screwed", "Y2K", "neo-brutalist", "like Stripe\'s dashboard" — with nothing to copy supplied: keep the look\'s components, logic and Look tab, and give them that appearance — the look\'s tokens re-scoped on one wrapper, its hook classes styled from a <style> string, web fonts, borders, texture, motion and a recomposed layout. Also the keepsake pattern for a thing people print, share or hand out — a ticket, invitation, menu, poster or certificate: one framed artifact drawn by the skin, with the components as its editing controls and a print style',
|
|
5
5
|
cues: [
|
|
6
6
|
"look like a",
|
|
7
7
|
"looks like a",
|
|
@@ -26,6 +26,13 @@ export const reskinConfig = {
|
|
|
26
26
|
"linear style",
|
|
27
27
|
"like linear",
|
|
28
28
|
"like stripe",
|
|
29
|
+
"printable",
|
|
30
|
+
"to print",
|
|
31
|
+
"print out",
|
|
32
|
+
"invitation card",
|
|
33
|
+
"a poster",
|
|
34
|
+
"certificate",
|
|
35
|
+
"place card",
|
|
29
36
|
],
|
|
30
37
|
};
|
|
31
38
|
//# sourceMappingURL=reskin.js.map
|
package/llms/reskin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reskin.js","sourceRoot":"","sources":["../../jsr/llms/reskin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reskin.js","sourceRoot":"","sources":["../../jsr/llms/reskin.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,WAAW,EACT,+pBAA+pB;IACjqB,IAAI,EAAE;QACJ,aAAa;QACb,cAAc;QACd,mBAAmB;QACnB,mBAAmB;QACnB,cAAc;QACd,aAAa;QACb,iBAAiB;QACjB,SAAS;QACT,aAAa;QACb,YAAY;QACZ,KAAK;QACL,WAAW;QACX,qBAAqB;QACrB,iBAAiB;QACjB,eAAe;QACf,cAAc;QACd,cAAc;QACd,gBAAgB;QAChB,kBAAkB;QAClB,iBAAiB;QACjB,cAAc;QACd,aAAa;QACb,aAAa;QACb,WAAW;QACX,UAAU;QACV,WAAW;QACX,iBAAiB;QACjB,UAAU;QACV,aAAa;QACb,YAAY;KACb;CACF,CAAC"}
|
package/llms/reskin.md
CHANGED
|
@@ -7,8 +7,8 @@ the app keeps its look's components, its logic and its Look tab, and wears that
|
|
|
7
7
|
appearance on top of them. Every `Card`, `Button`, `RowCard` and `Heading`
|
|
8
8
|
stays exactly where it is; what changes is how they are drawn.
|
|
9
9
|
|
|
10
|
-
This works on every look that shares the component surface (
|
|
11
|
-
looks built on its API): the same tokens, the same hook classes, the same
|
|
10
|
+
This works on every look that shares the component surface (the default look today,
|
|
11
|
+
and the looks built on its API): the same tokens, the same hook classes, the same
|
|
12
12
|
technique.
|
|
13
13
|
|
|
14
14
|
- Colour, a catalogue font or spacing alone is the Look tab (`set_app_style`);
|
|
@@ -30,24 +30,24 @@ so the wrapper's values are the ones every component reads.
|
|
|
30
30
|
The tokens: `--bg --surface --ink --text-secondary --purple --on-accent
|
|
31
31
|
--orange --info --up --down --idle --warn` (colour), `--r-card --r-control
|
|
32
32
|
--r-pill` (corners), `--lip --lip-row` (the cushion's edge), `--font-family`,
|
|
33
|
-
`--s1` … `--s7` (spacing), and the composite surfaces `--
|
|
34
|
-
--
|
|
33
|
+
`--s1` … `--s7` (spacing), and the composite surfaces `--ui-card --ui-row
|
|
34
|
+
--ui-control --ui-control-active --ui-field --ui-field-focus`. A
|
|
35
35
|
composite is resolved where it is declared, so a skin that changes the shadows
|
|
36
36
|
or the edge re-declares those composites on the wrapper too (`none` for a flat
|
|
37
37
|
skin).
|
|
38
38
|
|
|
39
39
|
**2. The look's parts are styled through their hook classes, from the wrapper.**
|
|
40
|
-
Every component carries a stable class: `.
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
rest of the package's `
|
|
40
|
+
Every component carries a stable class: `.ui-h1 .ui-h2 .ui-h3 .ui-card
|
|
41
|
+
.ui-rowcard .ui-btn .ui-blob .ui-empty .ui-stat .ui-eyebrow
|
|
42
|
+
.ui-grid .ui-input .ui-field .ui-badge .ui-tabs .ui-dialog`, and the
|
|
43
|
+
rest of the package's `ui-*` names. Write the skin as `[data-reskin] .ui-card
|
|
44
44
|
{ … }`. The components themselves take no `className` and no `style`: they keep
|
|
45
45
|
their props (`tone`, `size`, `variant`), and the skin reaches them by class.
|
|
46
46
|
|
|
47
47
|
**Portaled parts stay in the skin.** `Dialog`, `Select`, `Popover`, `Tooltip`
|
|
48
48
|
and `AlertDialog` render their open content under `<body>`, outside the
|
|
49
49
|
wrapper. So the token block is declared on `[data-reskin], body:has([data-reskin])`,
|
|
50
|
-
and a rule for a portaled part is written `body:has([data-reskin]) .
|
|
50
|
+
and a rule for a portaled part is written `body:has([data-reskin]) .ui-dialog
|
|
51
51
|
{ … }`. `body` sits below `:root`, so its tokens win for the whole page, the
|
|
52
52
|
open modal and dropdown included.
|
|
53
53
|
|
|
@@ -83,7 +83,7 @@ cushions, Chicago-like bitmap type (`Silkscreen`) over a plain grotesk body
|
|
|
83
83
|
import React, { useState } from "react";
|
|
84
84
|
import { useFireproof } from "use-fireproof";
|
|
85
85
|
import { useVibe } from "use-vibes";
|
|
86
|
-
import { Button, Card, Empty, Field, Heading, Input, Row, RowCard, Skeleton, Stack, Text } from "@vibes.diy/
|
|
86
|
+
import { Button, Card, Empty, Field, Heading, Input, Row, RowCard, Skeleton, Stack, Text } from "@vibes.diy/ui";
|
|
87
87
|
|
|
88
88
|
const SKIN = `
|
|
89
89
|
@import url('https://fonts.googleapis.com/css2?family=Silkscreen&family=Space+Grotesk:wght@400;600&display=swap');
|
|
@@ -92,8 +92,8 @@ const SKIN = `
|
|
|
92
92
|
--purple: #000000; --on-accent: #ffffff;
|
|
93
93
|
--r-card: 0px; --r-control: 0px; --r-pill: 0px; --lip: 0px; --lip-row: 0px;
|
|
94
94
|
--font-family: 'Space Grotesk', system-ui, sans-serif;
|
|
95
|
-
--
|
|
96
|
-
--
|
|
95
|
+
--ui-card: none; --ui-row: none; --ui-control: none; --ui-control-active: none;
|
|
96
|
+
--ui-field: inset 0 0 0 1px #000000; --ui-field-focus: inset 0 0 0 2px #000000;
|
|
97
97
|
min-height: 100vh; background: var(--bg); color: var(--ink); font-family: var(--font-family);
|
|
98
98
|
}
|
|
99
99
|
[data-reskin] .menubar { background: #ffffff; border-bottom: 1px solid #000000; padding: 2px 12px; font-family: 'Silkscreen', monospace; }
|
|
@@ -101,12 +101,12 @@ const SKIN = `
|
|
|
101
101
|
[data-reskin] .titlebar { height: 20px; border-bottom: 1px solid #000000; text-align: center; font-family: 'Silkscreen', monospace; font-size: 12px; line-height: 20px;
|
|
102
102
|
background: repeating-linear-gradient(#ffffff 0 1px, #000000 1px 2px); }
|
|
103
103
|
[data-reskin] .titlebar span { background: #ffffff; padding: 0 8px; }
|
|
104
|
-
[data-reskin] .
|
|
105
|
-
[data-reskin] .
|
|
106
|
-
[data-reskin] .
|
|
107
|
-
[data-reskin] .
|
|
108
|
-
[data-reskin] .
|
|
109
|
-
body:has([data-reskin]) .
|
|
104
|
+
[data-reskin] .ui-h1, [data-reskin] .ui-h2 { font-family: 'Silkscreen', monospace; letter-spacing: 0; }
|
|
105
|
+
[data-reskin] .ui-card, [data-reskin] .ui-rowcard { border: 1px solid #000000; }
|
|
106
|
+
[data-reskin] .ui-btn { border: 1px solid #000000; background: #ffffff; color: #000000; border-radius: 6px; }
|
|
107
|
+
[data-reskin] .ui-btn:active { background: #000000; color: #ffffff; }
|
|
108
|
+
[data-reskin] .ui-empty { border: 1px dashed #000000; }
|
|
109
|
+
body:has([data-reskin]) .ui-dialog { border: 1px solid #000000; border-radius: 0; box-shadow: 2px 2px 0 #000000; }
|
|
110
110
|
`;
|
|
111
111
|
|
|
112
112
|
export default function App() {
|
|
@@ -205,12 +205,91 @@ export default function App() {
|
|
|
205
205
|
}
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
## Keepsake — the thing they print, share or hand out
|
|
209
|
+
|
|
210
|
+
Tickets, invitations, menus, posters, certificates, place cards, a recipe
|
|
211
|
+
card: the designed object is the point, and a page of `Card`s and `RowCard`s
|
|
212
|
+
around it makes it look like an admin screen. Build one framed artifact and
|
|
213
|
+
let the components edit it.
|
|
214
|
+
|
|
215
|
+
- **The keepsake is plain elements styled by the skin.** One `.keepsake`
|
|
216
|
+
element with a fixed shape (`aspect-ratio: 5 / 7` for a card, `2 / 1` for a
|
|
217
|
+
ticket, `1 / 1.414` for an A-size poster) holds the words as `div`s and
|
|
218
|
+
`span`s with the app's own classes (`.keepsake-title`, `.keepsake-meta`), set
|
|
219
|
+
in the display face, with the border, ornament, paper and stamp drawn in the
|
|
220
|
+
`SKIN` string. It is never a `Card`, and its words are never `Heading`s:
|
|
221
|
+
the hook classes belong to the screen, the keepsake has its own.
|
|
222
|
+
- **The components are the editing controls around it.** `Field` and `Input`
|
|
223
|
+
for the words, `Segmented` for a variant (theme, size), `Button` for Print
|
|
224
|
+
and Share. On a wide screen they sit beside the keepsake; on a phone the
|
|
225
|
+
keepsake comes first and the controls follow, so the first frame is the
|
|
226
|
+
thing itself, filled with a believable example rather than blanks.
|
|
227
|
+
- **Print shows only the keepsake.** A `@media print` block in the skin hides
|
|
228
|
+
everything else and sets `@page` to the keepsake's size; the Print button
|
|
229
|
+
calls `window.print()`.
|
|
230
|
+
- **Its colours come from the wrapper's tokens**, so the Look tab and a later
|
|
231
|
+
reskin restyle the keepsake along with the controls.
|
|
232
|
+
|
|
233
|
+
```jsx
|
|
234
|
+
import React, { useState } from "react";
|
|
235
|
+
import { Button, Field, Input, Stack } from "@vibes.diy/ui";
|
|
236
|
+
|
|
237
|
+
const SKIN = `
|
|
238
|
+
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,600;1,400&display=swap');
|
|
239
|
+
[data-reskin] .studio { display: grid; gap: 24px; padding: 16px; grid-template-columns: 1fr; }
|
|
240
|
+
@media (min-width: 48rem) { [data-reskin] .studio { grid-template-columns: 22rem 1fr; align-items: start; } [data-reskin] .studio > .keepsake { order: 2; } }
|
|
241
|
+
[data-reskin] .keepsake { aspect-ratio: 5 / 7; width: min(100%, 26rem); margin-inline: auto; padding: 12%; display: grid; place-content: center; gap: 14px;
|
|
242
|
+
text-align: center; background: var(--surface); color: var(--ink); outline: 1px solid var(--ink); outline-offset: -14px;
|
|
243
|
+
box-shadow: 0 18px 40px color-mix(in srgb, var(--ink) 18%, transparent); font-family: 'Fraunces', serif; }
|
|
244
|
+
[data-reskin] .keepsake-eyebrow { font-style: italic; color: var(--text-secondary); }
|
|
245
|
+
[data-reskin] .keepsake-title { font-size: clamp(28px, 7vw, 44px); font-weight: 600; line-height: 1.05; }
|
|
246
|
+
[data-reskin] .keepsake-rule { width: 40%; height: 1px; margin-inline: auto; background: var(--purple); }
|
|
247
|
+
@media print {
|
|
248
|
+
@page { size: 5in 7in; margin: 0; }
|
|
249
|
+
body:has([data-reskin]) * { visibility: hidden; }
|
|
250
|
+
[data-reskin] .keepsake, [data-reskin] .keepsake * { visibility: visible; }
|
|
251
|
+
[data-reskin] .keepsake { position: fixed; inset: 0; width: 100%; box-shadow: none; }
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
|
|
255
|
+
export default function App() {
|
|
256
|
+
const [title, setTitle] = useState("Maya turns thirty");
|
|
257
|
+
const [when, setWhen] = useState("Saturday 14 June · 7pm · The Rooftop");
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div data-reskin>
|
|
261
|
+
<style>{SKIN}</style>
|
|
262
|
+
<div className="studio">
|
|
263
|
+
<div className="keepsake">
|
|
264
|
+
<span className="keepsake-eyebrow">You are invited</span>
|
|
265
|
+
<span className="keepsake-title">{title}</span>
|
|
266
|
+
<span className="keepsake-rule" />
|
|
267
|
+
<span>{when}</span>
|
|
268
|
+
</div>
|
|
269
|
+
<Stack gap={3}>
|
|
270
|
+
<Field label="Headline">{(id, d) => <Input id={id} describedBy={d} value={title} onChange={setTitle} />}</Field>
|
|
271
|
+
<Field label="When and where">{(id, d) => <Input id={id} describedBy={d} value={when} onChange={setWhen} />}</Field>
|
|
272
|
+
<Button onClick={() => window.print()}>Print the invitation</Button>
|
|
273
|
+
</Stack>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Board surface
|
|
281
|
+
|
|
282
|
+
A game board, a puzzle or a card table is the same split: the board is plain
|
|
283
|
+
elements the skin draws, the look's components frame it (score, buttons, the
|
|
284
|
+
win dialog). The board-games skill's _Board surface_ section has the worked
|
|
285
|
+
example.
|
|
286
|
+
|
|
208
287
|
## Reading a named style
|
|
209
288
|
|
|
210
289
|
Name the five things a stranger would recognise and set each one:
|
|
211
290
|
|
|
212
291
|
- **Ground and surfaces:** `--bg`, `--surface`, `--ink`, `--text-secondary`.
|
|
213
|
-
- **Type:** one display face for `.
|
|
292
|
+
- **Type:** one display face for `.ui-h1`/`.ui-h2`/`.ui-eyebrow`, one body
|
|
214
293
|
face on `--font-family`, the heading scale and tracking.
|
|
215
294
|
- **Edges:** corners (`--r-card`, `--r-control`, `--r-pill`), borders on the
|
|
216
295
|
hook classes, and the composite surfaces (`none` for flat, a hard offset for
|
|
@@ -223,5 +302,5 @@ Name the five things a stranger would recognise and set each one:
|
|
|
223
302
|
A newspaper is a cream ground, black ink, a serif masthead over a rule, column
|
|
224
303
|
grids and no shadows; a terminal is black, one phosphor green, a mono face and
|
|
225
304
|
a blinking cursor that starts lit; Y2K is chrome gradients, pill corners and a
|
|
226
|
-
glossy highlight on `.
|
|
305
|
+
glossy highlight on `.ui-btn`. The words on screen stay the app's own: the
|
|
227
306
|
style is borrowed, the names and logos of whoever made it famous are not.
|