@nadicodeai/ui 12.1.136 → 12.1.138

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 (42) hide show
  1. package/dist/components/agent-orb.d.ts +5 -1
  2. package/dist/components/agent-orb.d.ts.map +1 -1
  3. package/dist/components/agent-orb.js +5 -1
  4. package/dist/components/brand.d.ts +5 -5
  5. package/dist/components/brand.d.ts.map +1 -1
  6. package/dist/components/brand.js +5 -5
  7. package/dist/components/catalog-entry.d.ts +5 -4
  8. package/dist/components/catalog-entry.d.ts.map +1 -1
  9. package/dist/components/catalog-entry.js +4 -3
  10. package/dist/components/company-card/renderer.d.ts +8 -0
  11. package/dist/components/company-card/renderer.d.ts.map +1 -1
  12. package/dist/components/company-card/renderer.js +122 -53
  13. package/dist/components/company-card.d.ts.map +1 -1
  14. package/dist/components/company-card.js +70 -39
  15. package/dist/components/hive-scene.d.ts.map +1 -1
  16. package/dist/components/hive-scene.js +46 -8
  17. package/dist/components/identity-mark.d.ts +4 -4
  18. package/dist/components/identity-mark.d.ts.map +1 -1
  19. package/dist/components/identity-mark.js +2 -3
  20. package/dist/components/scroll-area.js +3 -3
  21. package/dist/components/settings-section.d.ts +26 -15
  22. package/dist/components/settings-section.d.ts.map +1 -1
  23. package/dist/components/settings-section.js +23 -16
  24. package/dist/components/switch.d.ts +2 -1
  25. package/dist/components/switch.d.ts.map +1 -1
  26. package/dist/components/switch.js +3 -2
  27. package/dist/components/theme-mode-switcher.d.ts +1 -1
  28. package/dist/components/theme-mode-switcher.js +1 -1
  29. package/dist/internal/agent-orb-motion.d.ts +4 -0
  30. package/dist/internal/agent-orb-motion.d.ts.map +1 -1
  31. package/dist/internal/agent-orb-motion.js +1 -0
  32. package/dist/internal/agent-orb.d.ts +1 -0
  33. package/dist/internal/agent-orb.d.ts.map +1 -1
  34. package/dist/internal/agent-orb.js +207 -86
  35. package/dist/showcase/specimens/compositions.js +1 -1
  36. package/dist/showcase/specimens/inputs.d.ts.map +1 -1
  37. package/dist/showcase/specimens/inputs.js +2 -2
  38. package/dist/showcase/specimens/status.js +2 -2
  39. package/dist/showcase/usage.generated.d.ts.map +1 -1
  40. package/dist/showcase/usage.generated.js +13 -13
  41. package/dist/standalone/index.js +2 -2
  42. package/package.json +2 -2
@@ -35,7 +35,9 @@ export type AgentOrbProps = AgentOrbBaseProps & ({
35
35
  * @family Status
36
36
  * @when An Agent, anywhere: 20 in the sidebar, 32 in a row, 64 on her Profile card, 96 in an Agent
37
37
  * card, 160 on entry. There is no other Agent picture. The sizes are 20, 24, 32, 40, 64, 96 and
38
- * 160 px, and no other. On a photograph the orb takes the stronger `presence="over-picture"`
38
+ * 160 px, and no other. Detail follows the size the orb is drawn at: up to 32 px it draws the
39
+ * engine's lightest dot profile, at 40 px the middle one and from 64 px the full one, since a small
40
+ * orb cannot show the full profile's dots apart. On a photograph the orb takes the stronger `presence="over-picture"`
39
41
  * weight; everywhere else it is `plain`.
40
42
  * @instead A person is an `Avatar`. The system's own wait is the waiting line, never an orb. An orb
41
43
  * in artwork, such as a sign-in picture, takes `motion="ambient"` and claims no activity. An orb
@@ -47,6 +49,8 @@ export type AgentOrbProps = AgentOrbBaseProps & ({
47
49
  * Done celebrates once over 4.8 s, then rests at 72% of native particle speed with the Idle breath.
48
50
  * A state change interrupts the previous gesture; `motionKey` replays it and `paused` freezes it. A surface that only knows a machine checked in draws idle. No state draws
49
51
  * a circle around the orb. Ambient motion moves the form at its own pace with no state pose or cue.
52
+ * An orb at rest (idle, ambient, or done after its celebration) paints 15 frames a second; every
53
+ * other state, gesture and the celebration paint 30. The pace is the same at either rate.
50
54
  * Reduced motion: movement stops, a thinking or working orb breathes between full ink and 0.45
51
55
  * opacity so busy still reads as busy, an ambient orb holds still, and waiting and blocked keep
52
56
  * their centered signs without an entrance gesture.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-orb.d.ts","sourceRoot":"","sources":["../../src/components/agent-orb.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,OAAO,CAAC;AAE9D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EACV,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,KAAK,iBAAiB,GAAG;IACvB,4FAA4F;IAC5F,IAAI,EAAE,cAAc,CAAC;IACrB,wFAAwF;IACxF,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,qEAAqE;IACrE,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAC3C,CAAC;IAAE,MAAM,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,GACrF,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,KAAK,EACL,IAAS,EACT,QAAkB,EAClB,MAAc,EACd,KAAc,EACd,MAAiB,EACjB,SAAa,EACb,UAAkB,EAClB,KAAK,EACL,SAAS,GACV,EAAE,aAAa,+BAyDf"}
1
+ {"version":3,"file":"agent-orb.d.ts","sourceRoot":"","sources":["../../src/components/agent-orb.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,OAAO,CAAC;AAE9D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EACV,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,KAAK,iBAAiB,GAAG;IACvB,4FAA4F;IAC5F,IAAI,EAAE,cAAc,CAAC;IACrB,wFAAwF;IACxF,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,qEAAqE;IACrE,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAC3C,CAAC;IAAE,MAAM,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,GACrF,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,KAAK,EACL,IAAS,EACT,QAAkB,EAClB,MAAc,EACd,KAAc,EACd,MAAiB,EACjB,SAAa,EACb,UAAkB,EAClB,KAAK,EACL,SAAS,GACV,EAAE,aAAa,+BAyDf"}
@@ -9,7 +9,9 @@ import { cn } from "../lib/utils.js";
9
9
  * @family Status
10
10
  * @when An Agent, anywhere: 20 in the sidebar, 32 in a row, 64 on her Profile card, 96 in an Agent
11
11
  * card, 160 on entry. There is no other Agent picture. The sizes are 20, 24, 32, 40, 64, 96 and
12
- * 160 px, and no other. On a photograph the orb takes the stronger `presence="over-picture"`
12
+ * 160 px, and no other. Detail follows the size the orb is drawn at: up to 32 px it draws the
13
+ * engine's lightest dot profile, at 40 px the middle one and from 64 px the full one, since a small
14
+ * orb cannot show the full profile's dots apart. On a photograph the orb takes the stronger `presence="over-picture"`
13
15
  * weight; everywhere else it is `plain`.
14
16
  * @instead A person is an `Avatar`. The system's own wait is the waiting line, never an orb. An orb
15
17
  * in artwork, such as a sign-in picture, takes `motion="ambient"` and claims no activity. An orb
@@ -21,6 +23,8 @@ import { cn } from "../lib/utils.js";
21
23
  * Done celebrates once over 4.8 s, then rests at 72% of native particle speed with the Idle breath.
22
24
  * A state change interrupts the previous gesture; `motionKey` replays it and `paused` freezes it. A surface that only knows a machine checked in draws idle. No state draws
23
25
  * a circle around the orb. Ambient motion moves the form at its own pace with no state pose or cue.
26
+ * An orb at rest (idle, ambient, or done after its celebration) paints 15 frames a second; every
27
+ * other state, gesture and the celebration paint 30. The pace is the same at either rate.
24
28
  * Reduced motion: movement stops, a thinking or working orb breathes between full ink and 0.45
25
29
  * opacity so busy still reads as busy, an ambient orb holds still, and waiting and blocked keep
26
30
  * their centered signs without an entrance gesture.
@@ -3,15 +3,15 @@ type BrandLogoProps = Omit<React.ComponentProps<"svg">, "aria-hidden" | "aria-la
3
3
  alt?: string;
4
4
  decoding?: React.ComponentProps<"img">["decoding"];
5
5
  };
6
- /** The plain n. Use it where the name does not fit. */
6
+ /** The mark: the n, nadicode's logo. Use it where the company is recognised by its sign. */
7
7
  declare function BrandMark({ alt, className, width, height, decoding, ...props }: BrandLogoProps): React.JSX.Element;
8
- /** The company logo: nadicode in full. Use it wherever the company is named. */
9
8
  /**
10
- * The company logo: nadicode in full.
9
+ * The wordmark: nadicode in full.
11
10
  *
12
11
  * @family Status
13
- * @when Wherever the company is named by its logo: the Portal's sidebar, the website's header, a
14
- * footer. `BrandMark`, the plain n, where the name does not fit.
12
+ * @when Wherever the company is named: the Portal's sidebar, the website's header, a footer.
13
+ * `BrandMark`, the n, where the company is recognised by its sign or the name does not fit; never
14
+ * the two side by side.
15
15
  * @instead A product's own mark (Nadia, Alveare) is that product's. A platform's is `PlatformMark`.
16
16
  * The company named in a sentence is text.
17
17
  * @behaviour The letters take the text colour of what they sit in, so one form serves every ground.
@@ -1 +1 @@
1
- {"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../../src/components/brand.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,KAAK,cAAc,GAAG,IAAI,CACxB,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAC3B,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CACnD,GAAG;IACF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;CACpD,CAAC;AAwCF,uDAAuD;AACvD,iBAAS,SAAS,CAAC,EACjB,GAAgB,EAChB,SAAS,EACT,KAAU,EACV,MAAW,EACX,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBAkBhB;AAED,gFAAgF;AAChF;;;;;;;;;;;;;GAaG;AACH,iBAAS,aAAa,CAAC,EACrB,GAAgB,EAChB,SAAS,EACT,KAAW,EACX,MAAW,EACX,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBAkBhB;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../../src/components/brand.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,KAAK,cAAc,GAAG,IAAI,CACxB,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAC3B,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CACnD,GAAG;IACF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;CACpD,CAAC;AAwCF,4FAA4F;AAC5F,iBAAS,SAAS,CAAC,EACjB,GAAgB,EAChB,SAAS,EACT,KAAU,EACV,MAAW,EACX,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBAkBhB;AAED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,aAAa,CAAC,EACrB,GAAgB,EAChB,SAAS,EACT,KAAW,EACX,MAAW,EACX,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBAkBhB;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC"}
@@ -28,19 +28,19 @@ const BRAND_PAINTS = { ink: "currentColor", dot: "var(--nc-logo-dot)" };
28
28
  function resolved(geometry) {
29
29
  return resolveLogoGeometry(geometry, { paints: BRAND_PAINTS });
30
30
  }
31
- /** The plain n. Use it where the name does not fit. */
31
+ /** The mark: the n, nadicode's logo. Use it where the company is recognised by its sign. */
32
32
  function BrandMark({ alt = "nadicode", className, width = 32, height = 32, decoding, ...props }) {
33
33
  void decoding;
34
34
  const geometry = resolved(logoMarkGeometry);
35
35
  return (_jsx("svg", { "data-slot": "brand-mark", xmlns: "http://www.w3.org/2000/svg", viewBox: geometry.viewBox, width: width, height: height, className: cn("block size-6", className), ...accessibilityProps(alt), ...props, children: renderGeometryNodes(geometry.nodes) }));
36
36
  }
37
- /** The company logo: nadicode in full. Use it wherever the company is named. */
38
37
  /**
39
- * The company logo: nadicode in full.
38
+ * The wordmark: nadicode in full.
40
39
  *
41
40
  * @family Status
42
- * @when Wherever the company is named by its logo: the Portal's sidebar, the website's header, a
43
- * footer. `BrandMark`, the plain n, where the name does not fit.
41
+ * @when Wherever the company is named: the Portal's sidebar, the website's header, a footer.
42
+ * `BrandMark`, the n, where the company is recognised by its sign or the name does not fit; never
43
+ * the two side by side.
44
44
  * @instead A product's own mark (Nadia, Alveare) is that product's. A platform's is `PlatformMark`.
45
45
  * The company named in a sentence is text.
46
46
  * @behaviour The letters take the text colour of what they sit in, so one form serves every ground.
@@ -6,8 +6,9 @@ import type { PageCommand } from "./page-actions.js";
6
6
  import type { RowMessage } from "../internal/row-message.js";
7
7
  export type { RowMessage };
8
8
  /**
9
- * The line icon a catalog entry names. A name outside `CATALOG_ICONS`, or
10
- * none, takes its kind's own icon, the one the Skills or Plugins section has.
9
+ * The line icon a catalog entry names. A name outside `CATALOG_ICONS`, none,
10
+ * or the sparkle, which no product draws for an entry, takes its kind's own
11
+ * icon, the one the Skills or Plugins section has.
11
12
  */
12
13
  export declare function catalogIcon(name: string | null | undefined, kind: "skill" | "plugin"): LucideIcon;
13
14
  export type CatalogEntryAdd = PageCommand & {
@@ -23,13 +24,13 @@ export type CatalogEntryAdd = PageCommand & {
23
24
  * @family Data
24
25
  * @when A reusable thing a person can add: a skill, a plugin or an app, on a Library shelf (`LibraryShelf`) or in the drawer that adds one to an Agent (`CatalogDrawer`).
25
26
  * @instead A record the person already holds is a row of its collection (`CollectionPage`). An Agent template is its `AgentCard`. Figures compared across entries are a `DataTable`.
26
- * @behaviour The kind's tile at 40: purple for a skill, blue for a plugin, an app's own logo. The name is the link to its own page. One Add control, outline with a leading plus; once held, a muted check and `added`'s word take its place. A menu of one command that is not destructive, with no Add, is that command's outline button; otherwise the three dots. A failed or refused add puts its message in the line's place, with its retry before the entry's own controls.
27
+ * @behaviour The kind's tile at 40: a skill's own icon on the neutral ringed tile, a plugin's on blue, an app's own logo. The name is the link to its own page. One Add control, outline with a leading plus; once held, a muted check and `added`'s word take its place. A menu of one command that is not destructive, with no Add, is that command's outline button; otherwise the three dots. A failed or refused add puts its message in the line's place, with its retry before the entry's own controls.
27
28
  * @words The name as the catalogue gives it; one line saying what it does, a sentence: "Classifies incoming requests and prepares the next step." The control is a verb: "Add".
28
29
  * @access The tile is decorative; the name is the link's text. A failed add's message is announced as an alert.
29
30
  */
30
31
  export type CatalogEntryProps = {
31
32
  id: string;
32
- /** The kind's tile at 40: { kind: "record", icon: catalogIcon(name, "skill"), tone: "skill" }, or an app's logo. */
33
+ /** The kind's tile at 40: { kind: "record", icon: catalogIcon(name, "skill") }, or an app's logo. */
33
34
  mark: IdentityMarkProps;
34
35
  name: string;
35
36
  link?: {
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-entry.d.ts","sourceRoot":"","sources":["../../src/components/catalog-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,KAAK,UAAU,EAiBhB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,YAAY,EAAE,UAAU,EAAE,CAAC;AAwC3B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAEjG;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,yFAAyF;IACzF,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,oHAAoH;IACpH,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,KAAK,SAAS,CAAC;KAC3E,CAAC;IACF,WAAW,EAAE,SAAS,CAAC;IACvB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;KAAE,CAAC;IAC3D,4EAA4E;IAC5E,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACvC,CAAC"}
1
+ {"version":3,"file":"catalog-entry.d.ts","sourceRoot":"","sources":["../../src/components/catalog-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,KAAK,UAAU,EAiBhB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,YAAY,EAAE,UAAU,EAAE,CAAC;AAwC3B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAEjG;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,yFAAyF;IACzF,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,qGAAqG;IACrG,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,KAAK,SAAS,CAAC;KAC3E,CAAC;IACF,WAAW,EAAE,SAAS,CAAC;IACvB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;KAAE,CAAC;IAC3D,4EAA4E;IAC5E,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACvC,CAAC"}
@@ -32,9 +32,10 @@ function isCatalogIcon(name) {
32
32
  return Object.hasOwn(ICONS, name);
33
33
  }
34
34
  /**
35
- * The line icon a catalog entry names. A name outside `CATALOG_ICONS`, or
36
- * none, takes its kind's own icon, the one the Skills or Plugins section has.
35
+ * The line icon a catalog entry names. A name outside `CATALOG_ICONS`, none,
36
+ * or the sparkle, which no product draws for an entry, takes its kind's own
37
+ * icon, the one the Skills or Plugins section has.
37
38
  */
38
39
  export function catalogIcon(name, kind) {
39
- return name && isCatalogIcon(name) ? ICONS[name] : FALLBACK_ICONS[kind];
40
+ return name && name !== "sparkles" && isCatalogIcon(name) ? ICONS[name] : FALLBACK_ICONS[kind];
40
41
  }
@@ -1,6 +1,14 @@
1
+ import * as webgpu from "vgpu";
2
+ import type { Gpu, Surface, Target } from "vgpu";
1
3
  import { type FoilColors } from "./scene.js";
4
+ type Webgpu = Pick<typeof webgpu, "clock" | "frameLoop" | "init"> & {
5
+ surface: (gpu: Gpu, canvas: HTMLCanvasElement, options: {
6
+ dpr: [number, number];
7
+ }) => Target & Pick<Surface, "onResize">;
8
+ };
2
9
  type RendererOptions = {
3
10
  colors: FoilColors;
11
+ vgpu?: Webgpu;
4
12
  /** The card's current turn in radians, for the page to turn the card itself. */
5
13
  onTilt: (tiltX: number, tiltY: number) => void;
6
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/components/company-card/renderer.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,gFAAgF;IAChF,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,eAAe;;;;EA+HjF"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/components/company-card/renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAA0B,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEzE,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AASvD,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC,GAAG;IAClE,OAAO,EAAE,CACP,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE;QAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAC/B,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;CACzC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,eAAe;;;;EA+LjF"}
@@ -1,14 +1,18 @@
1
1
  // nadicode: vgpu's holographic card renderer (MIT, https://github.com/vercel-labs/vgpu,
2
- // examples/holographic-card, revision 6fa27bb4), with its WebGPU set-up, frame
3
- // loop and cleanup as they are. The pointer is read on the whole page, as the
4
- // example reads it on its whole stage, so the foil wakes as the pointer comes
5
- // within one card width; the turn it computes is handed to the page, which
6
- // turns the card and its words together.
7
- import { clock, frameLoop, init, surface } from "vgpu";
2
+ // examples/holographic-card, revision 6fa27bb4), with its WebGPU set-up and
3
+ // cleanup as they are.
4
+ import * as webgpu from "vgpu";
8
5
  import { approach, SWEEP_MS, SWEEP_TURN } from "./approach.js";
9
6
  import { createScene } from "./scene.js";
7
+ const AT_REST = 1e-4;
8
+ /**
9
+ * The first frame after the loop starts again counts as one frame of the
10
+ * 60 fps it runs at, not the time it was stopped.
11
+ */
12
+ const FRAME_SECONDS = 1 / 60;
10
13
  export function createRenderer(canvas, options) {
11
- const { colors, onTilt } = options;
14
+ const { colors, onTilt, vgpu = webgpu } = options;
15
+ const { clock, frameLoop, init, surface } = vgpu;
12
16
  let disposed = false;
13
17
  let gpu;
14
18
  let removeInput = () => { };
@@ -33,6 +37,7 @@ export function createRenderer(canvas, options) {
33
37
  if (disposed)
34
38
  return;
35
39
  const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
40
+ const time = clock(context);
36
41
  let targetHover = 0;
37
42
  let pointerX = 0;
38
43
  let pointerY = 0;
@@ -46,52 +51,32 @@ export function createRenderer(canvas, options) {
46
51
  // from its top-left corner to its bottom-right, then rests. It tells a
47
52
  // person at a glance that this is a card that answers the pointer.
48
53
  let sweepStart = null;
49
- // Under reduced motion the card keeps still: the light moves only with the pointer.
50
- requestSweep = () => {
51
- if (!motion.matches)
52
- sweepStart = performance.now();
53
- };
54
- const move = (event) => {
55
- if (!event.isPrimary)
56
- return;
57
- const near = approach(canvas.getBoundingClientRect(), event.clientX, event.clientY);
58
- // A pointer elsewhere on the page leaves a sweep to finish; one that
59
- // comes near takes the card over.
60
- if (near.hover > 0)
61
- sweepStart = null;
62
- if (sweepStart !== null)
63
- return;
64
- pointerX = near.x;
65
- pointerY = near.y;
66
- targetHover = near.hover;
67
- };
68
- const leave = () => {
69
- if (sweepStart === null)
70
- targetHover = 0;
71
- };
72
- const up = (event) => {
73
- if (event.pointerType !== "mouse")
74
- leave();
75
- };
76
- const root = document.documentElement;
77
- window.addEventListener("pointermove", move, { passive: true });
78
- window.addEventListener("pointerdown", move, { passive: true });
79
- window.addEventListener("pointercancel", leave);
80
- window.addEventListener("pointerup", up);
81
- root.addEventListener("pointerleave", leave);
82
- const unsubscribeResize = output.onResize(() => {
83
- shader.set({ params: { resolution: output.size } });
84
- });
85
- removeInput = () => {
86
- window.removeEventListener("pointermove", move);
87
- window.removeEventListener("pointerdown", move);
88
- window.removeEventListener("pointercancel", leave);
89
- window.removeEventListener("pointerup", up);
90
- root.removeEventListener("pointerleave", leave);
91
- unsubscribeResize();
54
+ let pointer = null;
55
+ let needsDraw = true;
56
+ let onScreen = false;
57
+ let loop = null;
58
+ const stop = () => {
59
+ loop?.stop();
60
+ loop = null;
92
61
  };
93
- const time = clock(context);
94
- frameLoop(context, (currentFrame) => {
62
+ const settled = (targetX, targetY, within) => sweepStart === null &&
63
+ Math.abs(targetX - tiltX) <= within &&
64
+ Math.abs(targetY - tiltY) <= within &&
65
+ Math.abs(targetHover - hover) <= within &&
66
+ (targetHover === 0 ||
67
+ (Math.abs(pointerX - lightX) <= within && Math.abs(pointerY - lightY) <= within));
68
+ const draw = (currentFrame) => {
69
+ if (pointer) {
70
+ const near = approach(canvas.getBoundingClientRect(), pointer.clientX, pointer.clientY);
71
+ pointer = null;
72
+ if (near.hover > 0)
73
+ sweepStart = null;
74
+ if (sweepStart === null) {
75
+ pointerX = near.x;
76
+ pointerY = near.y;
77
+ targetHover = near.hover;
78
+ }
79
+ }
95
80
  let turn = null;
96
81
  if (sweepStart !== null) {
97
82
  const progress = (performance.now() - sweepStart) / SWEEP_MS;
@@ -116,6 +101,11 @@ export function createRenderer(canvas, options) {
116
101
  const targetY = motion.matches
117
102
  ? 0
118
103
  : (turn?.[1] ?? -Math.max(-1, Math.min(1, pointerY)) * 0.12 * targetHover);
104
+ if (!needsDraw && settled(targetX, targetY, 0)) {
105
+ currentFrame.cancel();
106
+ stop();
107
+ return;
108
+ }
119
109
  const blend = motion.matches ? 1 : 1 - Math.exp(-10 * Math.min(time.deltaTime, 0.1));
120
110
  tiltX += (targetX - tiltX) * blend;
121
111
  tiltY += (targetY - tiltY) * blend;
@@ -124,10 +114,89 @@ export function createRenderer(canvas, options) {
124
114
  lightX += (pointerX - lightX) * blend;
125
115
  lightY += (pointerY - lightY) * blend;
126
116
  }
117
+ const resting = settled(targetX, targetY, AT_REST);
118
+ if (resting) {
119
+ tiltX = targetX;
120
+ tiltY = targetY;
121
+ hover = targetHover;
122
+ if (targetHover > 0) {
123
+ lightX = pointerX;
124
+ lightY = pointerY;
125
+ }
126
+ }
127
127
  onTilt(tiltX, tiltY);
128
128
  shader.set({ params: { tilt: [tiltX, tiltY], pointer: [lightX, lightY], hover } });
129
129
  currentFrame.pass(output, shader);
130
- }, { fps: 60 });
130
+ needsDraw = false;
131
+ if (resting)
132
+ stop();
133
+ };
134
+ const play = () => {
135
+ if (disposed || !onScreen || document.hidden) {
136
+ stop();
137
+ return;
138
+ }
139
+ if (loop)
140
+ return;
141
+ time.advance(FRAME_SECONDS);
142
+ loop = frameLoop(context, draw, { fps: 60 });
143
+ };
144
+ // Under reduced motion the card keeps still: the light moves only with the pointer.
145
+ requestSweep = () => {
146
+ if (motion.matches)
147
+ return;
148
+ sweepStart = performance.now();
149
+ play();
150
+ };
151
+ const move = (event) => {
152
+ if (!event.isPrimary)
153
+ return;
154
+ pointer = { clientX: event.clientX, clientY: event.clientY };
155
+ play();
156
+ };
157
+ const leave = () => {
158
+ pointer = null;
159
+ if (sweepStart === null)
160
+ targetHover = 0;
161
+ play();
162
+ };
163
+ const up = (event) => {
164
+ if (event.pointerType !== "mouse")
165
+ leave();
166
+ };
167
+ const root = document.documentElement;
168
+ window.addEventListener("pointermove", move, { passive: true });
169
+ window.addEventListener("pointerdown", move, { passive: true });
170
+ window.addEventListener("pointercancel", leave);
171
+ window.addEventListener("pointerup", up);
172
+ root.addEventListener("pointerleave", leave);
173
+ document.addEventListener("visibilitychange", play);
174
+ motion.addEventListener("change", play);
175
+ // vgpu sizes the canvas on a frame, so a card whose box changes takes one to be drawn again.
176
+ const unsubscribeResize = output.onResize(() => {
177
+ shader.set({ params: { resolution: output.size } });
178
+ needsDraw = true;
179
+ });
180
+ const box = new ResizeObserver(play);
181
+ box.observe(canvas);
182
+ const visibility = new IntersectionObserver((entries) => {
183
+ onScreen = entries.at(-1)?.isIntersecting ?? false;
184
+ play();
185
+ });
186
+ visibility.observe(canvas);
187
+ removeInput = () => {
188
+ stop();
189
+ window.removeEventListener("pointermove", move);
190
+ window.removeEventListener("pointerdown", move);
191
+ window.removeEventListener("pointercancel", leave);
192
+ window.removeEventListener("pointerup", up);
193
+ root.removeEventListener("pointerleave", leave);
194
+ document.removeEventListener("visibilitychange", play);
195
+ motion.removeEventListener("change", play);
196
+ unsubscribeResize();
197
+ box.disconnect();
198
+ visibility.disconnect();
199
+ };
131
200
  })().catch((cause) => {
132
201
  if (disposed)
133
202
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"company-card.d.ts","sourceRoot":"","sources":["../../src/components/company-card.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,MAAM,EACN,IAAI,EACJ,WAAmB,GACpB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,+BAiSA"}
1
+ {"version":3,"file":"company-card.d.ts","sourceRoot":"","sources":["../../src/components/company-card.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AA0BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,MAAM,EACN,IAAI,EACJ,WAAmB,GACpB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,+BAsTA"}
@@ -17,6 +17,10 @@ const FOIL = {
17
17
  const STRIPE = 34;
18
18
  /** How much of the card must be on screen before it turns, when the caller waits for it. */
19
19
  const IN_VIEW = 0.6;
20
+ function setChanged(element, property, value) {
21
+ if (element.style.getPropertyValue(property) !== value)
22
+ element.style.setProperty(property, value);
23
+ }
20
24
  function reason(cause) {
21
25
  return cause instanceof Error ? cause.message : String(cause);
22
26
  }
@@ -67,7 +71,6 @@ export function CompanyCard({ name, domain, edition, issued, labels, logo, sweep
67
71
  const cardRef = useRef(null);
68
72
  const lightRef = useRef(null);
69
73
  const stripesRef = useRef(null);
70
- const frame = useRef(0);
71
74
  const canvasRef = useRef(null);
72
75
  const [gpuFoil, setGpuFoil] = useState(false);
73
76
  const sweepRef = useRef(() => { });
@@ -102,8 +105,8 @@ export function CompanyCard({ name, domain, edition, issued, labels, logo, sweep
102
105
  const created = createRenderer(canvas, {
103
106
  colors: foilColors(card, edition),
104
107
  onTilt: (tiltX, tiltY) => {
105
- card.style.setProperty("--tilt-x", `${tiltY.toFixed(4)}rad`);
106
- card.style.setProperty("--tilt-y", `${tiltX.toFixed(4)}rad`);
108
+ setChanged(card, "--tilt-x", `${tiltY.toFixed(4)}rad`);
109
+ setChanged(card, "--tilt-y", `${tiltX.toFixed(4)}rad`);
107
110
  },
108
111
  });
109
112
  renderer = created;
@@ -130,16 +133,17 @@ export function CompanyCard({ name, domain, edition, issued, labels, logo, sweep
130
133
  setGpuFoil(false);
131
134
  };
132
135
  }, [edition, seen]);
133
- // The SVG foil behaves as the shader does: it turns once in the light when it
134
- // appears and when it is issued, and wakes as the pointer comes within one
135
- // card width, easing its turn by how near the pointer is.
136
136
  useEffect(() => {
137
137
  const card = cardRef.current;
138
138
  if (!seen || gpuFoil || !card)
139
139
  return;
140
140
  const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
141
+ let onScreen = typeof IntersectionObserver === "undefined";
142
+ let sweepStart = null;
141
143
  let sweepFrame = 0;
142
- let sweeping = false;
144
+ let pointer = null;
145
+ let pointerFrame = 0;
146
+ const playing = () => onScreen && !document.hidden;
143
147
  const light = (x, y) => {
144
148
  lightRef.current?.setAttribute("cx", x.toFixed(3));
145
149
  lightRef.current?.setAttribute("cy", y.toFixed(3));
@@ -162,59 +166,86 @@ export function CompanyCard({ name, domain, edition, issued, labels, logo, sweep
162
166
  }
163
167
  };
164
168
  const stopSweep = () => {
165
- sweeping = false;
169
+ sweepStart = null;
166
170
  cancelAnimationFrame(sweepFrame);
167
171
  };
172
+ const step = (now) => {
173
+ if (sweepStart === null)
174
+ return;
175
+ const progress = Math.min(1, (now - sweepStart) / SWEEP_MS);
176
+ const along = progress * progress * (3 - 2 * progress);
177
+ const rise = Math.sin(Math.PI * progress);
178
+ show(-0.05 + along * 1.1, 0.22 + along * 0.56, rise ** 0.6 * 0.95, rise * MAX_TILT_DEGREES * 0.3, -Math.sin(2 * Math.PI * along) * ((SWEEP_TURN * 180) / Math.PI));
179
+ if (progress < 1)
180
+ sweepFrame = requestAnimationFrame(step);
181
+ else {
182
+ sweepStart = null;
183
+ rest();
184
+ }
185
+ };
186
+ const follow = () => {
187
+ const at = pointer;
188
+ pointer = null;
189
+ if (!at)
190
+ return;
191
+ const near = approach(card.getBoundingClientRect(), at.clientX, at.clientY);
192
+ if (near.hover > 0)
193
+ stopSweep();
194
+ if (sweepStart !== null)
195
+ return;
196
+ if (near.hover === 0)
197
+ return rest();
198
+ const x = Math.min(1, Math.max(0, (near.x + 1) / 2));
199
+ const y = Math.min(1, Math.max(0, (near.y + 1) / 2));
200
+ show(x, y, near.hover, (0.5 - y) * 2 * MAX_TILT_DEGREES * near.hover, (x - 0.5) * 2 * MAX_TILT_DEGREES * near.hover);
201
+ };
202
+ const play = () => {
203
+ cancelAnimationFrame(sweepFrame);
204
+ cancelAnimationFrame(pointerFrame);
205
+ if (!playing())
206
+ return;
207
+ if (sweepStart !== null)
208
+ sweepFrame = requestAnimationFrame(step);
209
+ if (pointer)
210
+ pointerFrame = requestAnimationFrame(follow);
211
+ };
168
212
  const sweep = () => {
169
213
  if (motion.matches)
170
214
  return;
171
- stopSweep();
172
- sweeping = true;
173
- const start = performance.now();
174
- const step = (now) => {
175
- const progress = Math.min(1, (now - start) / SWEEP_MS);
176
- const along = progress * progress * (3 - 2 * progress);
177
- const rise = Math.sin(Math.PI * progress);
178
- show(-0.05 + along * 1.1, 0.22 + along * 0.56, rise ** 0.6 * 0.95, rise * MAX_TILT_DEGREES * 0.3, -Math.sin(2 * Math.PI * along) * ((SWEEP_TURN * 180) / Math.PI));
179
- if (progress < 1)
180
- sweepFrame = requestAnimationFrame(step);
181
- else {
182
- sweeping = false;
183
- rest();
184
- }
185
- };
186
- sweepFrame = requestAnimationFrame(step);
215
+ sweepStart = performance.now();
216
+ play();
187
217
  };
188
218
  const move = (event) => {
189
219
  if (!event.isPrimary)
190
220
  return;
191
- const near = approach(card.getBoundingClientRect(), event.clientX, event.clientY);
192
- if (near.hover > 0)
193
- stopSweep();
194
- if (sweeping)
195
- return;
196
- cancelAnimationFrame(frame.current);
197
- frame.current = requestAnimationFrame(() => {
198
- if (near.hover === 0)
199
- return rest();
200
- const x = Math.min(1, Math.max(0, (near.x + 1) / 2));
201
- const y = Math.min(1, Math.max(0, (near.y + 1) / 2));
202
- show(x, y, near.hover, (0.5 - y) * 2 * MAX_TILT_DEGREES * near.hover, (x - 0.5) * 2 * MAX_TILT_DEGREES * near.hover);
203
- });
221
+ pointer = { clientX: event.clientX, clientY: event.clientY };
222
+ cancelAnimationFrame(pointerFrame);
223
+ if (playing())
224
+ pointerFrame = requestAnimationFrame(follow);
204
225
  };
205
226
  const leave = () => {
206
- if (!sweeping)
227
+ if (sweepStart === null)
207
228
  rest();
208
229
  };
209
230
  window.addEventListener("pointermove", move, { passive: true });
210
231
  document.documentElement.addEventListener("pointerleave", leave);
232
+ document.addEventListener("visibilitychange", play);
233
+ const visibility = onScreen
234
+ ? undefined
235
+ : new IntersectionObserver((entries) => {
236
+ onScreen = entries.at(-1)?.isIntersecting ?? false;
237
+ play();
238
+ });
239
+ visibility?.observe(card);
211
240
  sweepRef.current = sweep;
212
241
  sweep();
213
242
  return () => {
214
243
  window.removeEventListener("pointermove", move);
215
244
  document.documentElement.removeEventListener("pointerleave", leave);
245
+ document.removeEventListener("visibilitychange", play);
246
+ visibility?.disconnect();
216
247
  stopSweep();
217
- cancelAnimationFrame(frame.current);
248
+ cancelAnimationFrame(pointerFrame);
218
249
  sweepRef.current = () => { };
219
250
  rest();
220
251
  };
@@ -1 +1 @@
1
- {"version":3,"file":"hive-scene.d.ts","sourceRoot":"","sources":["../../src/components/hive-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IAC9B,SAAS,EAAE,SAAS,SAAS,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IAC3E,IAAI,EAAE,aAAa,CAAC;IACpB,qFAAqF;IACrF,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,kFAAkF;IAClF,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,mFAAmF;IACnF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,QAAQ,CAAC;IAC7D,iEAAiE;IACjE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAmLF;;;;;;;;;;;;;GAaG;AACH,iBAAS,SAAS,CAAC,EACjB,IAAI,EACJ,MAAkB,EAClB,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBAsNhB;AAED,2DAA2D;AAC3D,iBAAS,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,qBAM9E;AAED,iEAAiE;AACjE,iBAAS,QAAQ,CAAC,EAChB,QAAQ,EACR,EAAE,EACF,IAAa,GACd,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB,qBAMA;AAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"hive-scene.d.ts","sourceRoot":"","sources":["../../src/components/hive-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IAC9B,SAAS,EAAE,SAAS,SAAS,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG;IAC3E,IAAI,EAAE,aAAa,CAAC;IACpB,qFAAqF;IACrF,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,kFAAkF;IAClF,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,mFAAmF;IACnF,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,QAAQ,CAAC;IAC7D,iEAAiE;IACjE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAmLF;;;;;;;;;;;;;GAaG;AACH,iBAAS,SAAS,CAAC,EACjB,IAAI,EACJ,MAAkB,EAClB,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,cAAc,qBA8PhB;AAED,2DAA2D;AAC3D,iBAAS,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,qBAM9E;AAED,iEAAiE;AACjE,iBAAS,QAAQ,CAAC,EAChB,QAAQ,EACR,EAAE,EACF,IAAa,GACd,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB,qBAMA;AAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC"}