@abuzareal/contour-ui 0.1.0

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 (179) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -0
  4. package/RELEASING.md +37 -0
  5. package/SECURITY.md +11 -0
  6. package/dist/components/hero/SceneBoundary.d.ts +14 -0
  7. package/dist/components/hero/SceneBoundary.js +11 -0
  8. package/dist/components/hero/Sculpture.d.ts +4 -0
  9. package/dist/components/hero/Sculpture.js +54 -0
  10. package/dist/components/three/ObjectStudy.d.ts +7 -0
  11. package/dist/components/three/ObjectStudy.js +65 -0
  12. package/dist/components/ui/Alert.d.ts +13 -0
  13. package/dist/components/ui/Alert.js +15 -0
  14. package/dist/components/ui/Avatar.d.ts +7 -0
  15. package/dist/components/ui/Avatar.js +12 -0
  16. package/dist/components/ui/Badge.d.ts +11 -0
  17. package/dist/components/ui/Badge.js +5 -0
  18. package/dist/components/ui/BrandMark.d.ts +7 -0
  19. package/dist/components/ui/BrandMark.js +5 -0
  20. package/dist/components/ui/Breadcrumbs.d.ts +10 -0
  21. package/dist/components/ui/Breadcrumbs.js +8 -0
  22. package/dist/components/ui/Button.d.ts +12 -0
  23. package/dist/components/ui/Button.js +6 -0
  24. package/dist/components/ui/Card.d.ts +13 -0
  25. package/dist/components/ui/Card.js +5 -0
  26. package/dist/components/ui/Checkbox.d.ts +9 -0
  27. package/dist/components/ui/Checkbox.js +14 -0
  28. package/dist/components/ui/CountUp.d.ts +9 -0
  29. package/dist/components/ui/CountUp.js +48 -0
  30. package/dist/components/ui/DataTable.d.ts +16 -0
  31. package/dist/components/ui/DataTable.js +24 -0
  32. package/dist/components/ui/DepthButton.d.ts +6 -0
  33. package/dist/components/ui/DepthButton.js +4 -0
  34. package/dist/components/ui/Dialog.d.ts +12 -0
  35. package/dist/components/ui/Dialog.js +27 -0
  36. package/dist/components/ui/Disclosure.d.ts +11 -0
  37. package/dist/components/ui/Disclosure.js +5 -0
  38. package/dist/components/ui/Divider.d.ts +5 -0
  39. package/dist/components/ui/Divider.js +6 -0
  40. package/dist/components/ui/DropdownMenu.d.ts +14 -0
  41. package/dist/components/ui/DropdownMenu.js +38 -0
  42. package/dist/components/ui/EmptyState.d.ts +9 -0
  43. package/dist/components/ui/EmptyState.js +4 -0
  44. package/dist/components/ui/ExternalLink.d.ts +4 -0
  45. package/dist/components/ui/ExternalLink.js +5 -0
  46. package/dist/components/ui/Field.d.ts +15 -0
  47. package/dist/components/ui/Field.js +6 -0
  48. package/dist/components/ui/FlipCard.d.ts +8 -0
  49. package/dist/components/ui/FlipCard.js +9 -0
  50. package/dist/components/ui/IconButton.d.ts +11 -0
  51. package/dist/components/ui/IconButton.js +5 -0
  52. package/dist/components/ui/Kbd.d.ts +5 -0
  53. package/dist/components/ui/Kbd.js +4 -0
  54. package/dist/components/ui/Magnetic.d.ts +8 -0
  55. package/dist/components/ui/Magnetic.js +19 -0
  56. package/dist/components/ui/Marquee.d.ts +7 -0
  57. package/dist/components/ui/Marquee.js +7 -0
  58. package/dist/components/ui/Pagination.d.ts +7 -0
  59. package/dist/components/ui/Pagination.js +15 -0
  60. package/dist/components/ui/Popover.d.ts +10 -0
  61. package/dist/components/ui/Popover.js +18 -0
  62. package/dist/components/ui/ProgressBar.d.ts +7 -0
  63. package/dist/components/ui/ProgressBar.js +6 -0
  64. package/dist/components/ui/RadioGroup.d.ts +14 -0
  65. package/dist/components/ui/RadioGroup.js +11 -0
  66. package/dist/components/ui/Reveal.d.ts +9 -0
  67. package/dist/components/ui/Reveal.js +24 -0
  68. package/dist/components/ui/RevealHeading.d.ts +12 -0
  69. package/dist/components/ui/RevealHeading.js +4 -0
  70. package/dist/components/ui/ScrollProgress.d.ts +1 -0
  71. package/dist/components/ui/ScrollProgress.js +27 -0
  72. package/dist/components/ui/SectionLabel.d.ts +8 -0
  73. package/dist/components/ui/SectionLabel.js +5 -0
  74. package/dist/components/ui/SegmentedControl.d.ts +10 -0
  75. package/dist/components/ui/SegmentedControl.js +20 -0
  76. package/dist/components/ui/SelectField.d.ts +13 -0
  77. package/dist/components/ui/SelectField.js +10 -0
  78. package/dist/components/ui/Skeleton.d.ts +6 -0
  79. package/dist/components/ui/Skeleton.js +5 -0
  80. package/dist/components/ui/Slider.d.ts +12 -0
  81. package/dist/components/ui/Slider.js +10 -0
  82. package/dist/components/ui/SocialIcons.d.ts +6 -0
  83. package/dist/components/ui/SocialIcons.js +7 -0
  84. package/dist/components/ui/Spinner.d.ts +6 -0
  85. package/dist/components/ui/Spinner.js +8 -0
  86. package/dist/components/ui/Stat.d.ts +8 -0
  87. package/dist/components/ui/Stat.js +4 -0
  88. package/dist/components/ui/Stepper.d.ts +10 -0
  89. package/dist/components/ui/Stepper.js +15 -0
  90. package/dist/components/ui/Switch.d.ts +8 -0
  91. package/dist/components/ui/Switch.js +7 -0
  92. package/dist/components/ui/Tabs.d.ts +13 -0
  93. package/dist/components/ui/Tabs.js +41 -0
  94. package/dist/components/ui/Tag.d.ts +5 -0
  95. package/dist/components/ui/Tag.js +8 -0
  96. package/dist/components/ui/TextArea.d.ts +10 -0
  97. package/dist/components/ui/TextArea.js +23 -0
  98. package/dist/components/ui/TextField.d.ts +12 -0
  99. package/dist/components/ui/TextField.js +13 -0
  100. package/dist/components/ui/ThemeToggle.d.ts +5 -0
  101. package/dist/components/ui/ThemeToggle.js +9 -0
  102. package/dist/components/ui/TiltCard.d.ts +8 -0
  103. package/dist/components/ui/TiltCard.js +23 -0
  104. package/dist/components/ui/Timeline.d.ts +9 -0
  105. package/dist/components/ui/Timeline.js +6 -0
  106. package/dist/components/ui/ToastProvider.d.ts +5 -0
  107. package/dist/components/ui/ToastProvider.js +29 -0
  108. package/dist/components/ui/Tooltip.d.ts +11 -0
  109. package/dist/components/ui/Tooltip.js +16 -0
  110. package/dist/fonts.css +3 -0
  111. package/dist/hooks/useClipboardCopy.d.ts +5 -0
  112. package/dist/hooks/useClipboardCopy.js +21 -0
  113. package/dist/hooks/useDismiss.d.ts +3 -0
  114. package/dist/hooks/useDismiss.js +22 -0
  115. package/dist/hooks/useHeaderScroll.d.ts +7 -0
  116. package/dist/hooks/useHeaderScroll.js +40 -0
  117. package/dist/hooks/useIdleTrigger.d.ts +1 -0
  118. package/dist/hooks/useIdleTrigger.js +19 -0
  119. package/dist/hooks/useInitialHashScroll.d.ts +1 -0
  120. package/dist/hooks/useInitialHashScroll.js +28 -0
  121. package/dist/hooks/useMobileMenu.d.ts +3 -0
  122. package/dist/hooks/useMobileMenu.js +34 -0
  123. package/dist/hooks/useMotionPreference.d.ts +1 -0
  124. package/dist/hooks/useMotionPreference.js +13 -0
  125. package/dist/hooks/usePrintPreparation.d.ts +1 -0
  126. package/dist/hooks/usePrintPreparation.js +29 -0
  127. package/dist/hooks/useScrollSpy.d.ts +1 -0
  128. package/dist/hooks/useScrollSpy.js +36 -0
  129. package/dist/hooks/useSmoothScroll.d.ts +1 -0
  130. package/dist/hooks/useSmoothScroll.js +18 -0
  131. package/dist/hooks/useTheme.d.ts +5 -0
  132. package/dist/hooks/useTheme.js +98 -0
  133. package/dist/hooks/useThreeStage.d.ts +28 -0
  134. package/dist/hooks/useThreeStage.js +155 -0
  135. package/dist/hooks/useToast.d.ts +11 -0
  136. package/dist/hooks/useToast.js +9 -0
  137. package/dist/index.d.ts +129 -0
  138. package/dist/index.js +68 -0
  139. package/dist/kit/actions.css +209 -0
  140. package/dist/kit/data-display.css +496 -0
  141. package/dist/kit/effects.css +144 -0
  142. package/dist/kit/feedback.css +316 -0
  143. package/dist/kit/forms.css +363 -0
  144. package/dist/kit/index.css +9 -0
  145. package/dist/kit/navigation.css +262 -0
  146. package/dist/kit/overlays.css +299 -0
  147. package/dist/lib/a11y.d.ts +3 -0
  148. package/dist/lib/a11y.js +3 -0
  149. package/dist/lib/classNames.d.ts +6 -0
  150. package/dist/lib/classNames.js +4 -0
  151. package/dist/lib/format.d.ts +3 -0
  152. package/dist/lib/format.js +3 -0
  153. package/dist/lib/keyboard.d.ts +8 -0
  154. package/dist/lib/keyboard.js +22 -0
  155. package/dist/lib/motion.d.ts +3 -0
  156. package/dist/lib/motion.js +4 -0
  157. package/dist/lib/safeHref.d.ts +2 -0
  158. package/dist/lib/safeHref.js +14 -0
  159. package/dist/lib/storage.d.ts +10 -0
  160. package/dist/lib/storage.js +25 -0
  161. package/dist/lib/themeBootstrap.d.ts +6 -0
  162. package/dist/lib/themeBootstrap.js +19 -0
  163. package/dist/lib/threeMaterials.d.ts +9 -0
  164. package/dist/lib/threeMaterials.js +62 -0
  165. package/dist/lib/threeStudies.d.ts +16 -0
  166. package/dist/lib/threeStudies.js +39 -0
  167. package/dist/motion.css +59 -0
  168. package/dist/reset.css +180 -0
  169. package/dist/scroll.d.ts +1 -0
  170. package/dist/scroll.js +1 -0
  171. package/dist/styles.css +23 -0
  172. package/dist/theme-toggle.css +38 -0
  173. package/dist/three.d.ts +8 -0
  174. package/dist/three.js +5 -0
  175. package/dist/tokens.css +127 -0
  176. package/dist/types.d.ts +6 -0
  177. package/dist/types.js +1 -0
  178. package/dist/ui.css +178 -0
  179. package/package.json +101 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Extract reusable React components, theme tokens, styling, and optional 3D/scroll entries from the standalone design-system app.
6
+ - Synchronize theme controls, tolerate blocked preference storage, and honor system reduced motion for theme changes.
7
+ - Reject executable link schemes, preserve tooltip descriptions, make hidden flip-card faces inert, and handle empty tabs and zero-range sliders.
8
+ - Ship typed ESM exports, optional font/reset styles, regression tests, and archive-content validation.
9
+
10
+ This is the first release. The 3D interface is experimental; React 19 compatibility has not been verified.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 abuzareal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # Contour UI
2
+
3
+ An independent React component library for editorial interfaces: paper and ink surfaces, an acid accent, readable typography, restrained motion, and optional Three.js objects.
4
+
5
+ The npm package is public under MIT; the development repository is private. Public consumers can inspect the distributed JavaScript, declarations, and CSS, but the repository and its issue tracker are accessible only to invited collaborators.
6
+
7
+ **Status:** initial `0.1.0` release. React 18.3 is supported; React 19 and React Server Component execution are not advertised as tested. Interactive components belong in a client boundary in frameworks such as Next.js. The root module can be imported on the server, and basic components can render there.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @abuzareal/contour-ui react@18 react-dom@18
13
+ ```
14
+
15
+ ```tsx
16
+ import "@abuzareal/contour-ui/reset.css"; // optional, global reset; omit with an existing reset
17
+ import "@abuzareal/contour-ui/fonts.css"; // optional, self-hosted font assets bundled by your app
18
+ import "@abuzareal/contour-ui/styles.css";
19
+ import {
20
+ Button,
21
+ TextField,
22
+ ToastProvider,
23
+ ThemeToggle,
24
+ } from "@abuzareal/contour-ui";
25
+
26
+ export function App() {
27
+ return (
28
+ <ToastProvider>
29
+ <main data-motion="off">
30
+ <ThemeToggle animate={false} />
31
+ <TextField label="City" placeholder="Pune" />
32
+ <Button variant="accent">Show forecast</Button>
33
+ </main>
34
+ </ToastProvider>
35
+ );
36
+ }
37
+ ```
38
+
39
+ For an app with its own reset, include equivalent native button/input defaults, border-box sizing, focus outlines, and the `[hidden]` rule. Styles use globally named component classes and `:root` theme tokens; they are not Shadow DOM isolated. Avoid redefining generic classes such as `.card` or `.button` elsewhere.
40
+
41
+ ## Theme and motion setup
42
+
43
+ All theme toggles read the same document state. Set `data-theme="light"` or `"dark"` on `<html>`. `useTheme(animate)` returns `{ theme, toggle }`; call `toggle(buttonElement)` for a user action. The existing `aa-theme` local-storage key is preserved for compatibility with the original portfolio. Preference storage is origin-local, not shared across different website domains.
44
+
45
+ To avoid a first-paint theme flash, inject the exported static `themeBootstrapScript` from `@abuzareal/contour-ui/theme-bootstrap` into the HTML head at build time. It has no user-supplied interpolation. A strict Content Security Policy needs the appropriate script hash or nonce; do not enable unsafe inline scripts generally.
46
+
47
+ Set `data-motion="off"` on your application wrapper when animation is disabled. `useMotionPreference()` tracks the system setting; combine it with your app's pause state. Pass the resulting `motionEnabled` to Three.js scenes and `animate` to theme controls. Reduced motion overrides theme reveal animations even if `animate` is true. No animation is required to operate the UI.
48
+
49
+ `useToast()` must run below `ToastProvider`. The library does not send messages, collect analytics, fetch application data, or require a service account. Links reject executable URL schemes; application authentication, validation, authorization, and data handling remain the application's responsibility.
50
+
51
+ ## Optional entries
52
+
53
+ ```sh
54
+ npm install three@^0.186.1 # only for the /three entry
55
+ npm install lenis@^1.3.26 # only for the /scroll entry
56
+ ```
57
+
58
+ ```tsx
59
+ import { lazy, Suspense } from "react";
60
+ const Sculpture = lazy(() =>
61
+ import("@abuzareal/contour-ui/three").then((module) => ({
62
+ default: module.Sculpture,
63
+ })),
64
+ );
65
+
66
+ // Provide a sized container and an application-owned static fallback.
67
+ <Suspense fallback={<p>Loading preview…</p>}>
68
+ <Sculpture motionEnabled={false} />
69
+ </Suspense>;
70
+ ```
71
+
72
+ `/three` also exports `ObjectStudy`, `useThreeStage`, geometry/material helpers, and their types. Treat the 3D API as experimental in this initial release. Scene setup functions are captured on mount; remount the scene when its setup configuration changes. Scenes require WebGL and should always have a visible fallback. No Three.js or Lenis imports occur in the core runtime graph.
73
+
74
+ ## Components and typed APIs
75
+
76
+ The package exports named components and props types. TypeScript declarations are shipped with every entry; editors provide the complete props API.
77
+
78
+ - Actions: `Button`, `IconButton`, `ExternalLink`, `SegmentedControl`, `ThemeToggle`.
79
+ - Forms: `Field`, `TextField`, `TextArea`, `SelectField`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`.
80
+ - Navigation: `Tabs`, `Breadcrumbs`, `Pagination`, `Stepper`, `DropdownMenu`.
81
+ - Data and layout: `Avatar`, `Badge`, `Card`, `DataTable`, `Disclosure`, `Divider`, `EmptyState`, `Kbd`, `SectionLabel`, `Stat`, `Tag`, `Timeline`.
82
+ - Feedback: `Alert`, `ProgressBar`, `Skeleton`, `Spinner`, `ToastProvider`, `useToast`.
83
+ - Motion and signature: `BrandMark`, `CountUp`, `DepthButton`, `FlipCard`, `Magnetic`, `Marquee`, `Reveal`, `RevealHeading`, `ScrollProgress`, `TiltCard`, `GitHubIcon`, `LinkedInIcon`.
84
+ - Overlays: `Dialog`, `Popover`, `Tooltip`, `SceneBoundary`.
85
+
86
+ Use visible labels for fields, concise action labels for buttons, accessible labels for icon-only controls, and text alongside status colors. Sortable tables expect scalar string/number row values. Pagination expects a positive integer page count and a page in that range. Flip cards switch through their button; the hidden side is inert.
87
+
88
+ Global colors, fonts, radii, duration, and spacing variables are in `tokens.css`. Override semantic tokens rather than copying components into your app. `styles.css` includes tokens and component styles; `fonts.css` and `reset.css` are separately opt-in. Do not import catalogue or portfolio CSS into other apps.
89
+
90
+ ## Independent repositories and updates
91
+
92
+ This repository contains only the library. The catalogue and portfolio remain separate repositories and consume the same published npm version. During first-release validation, a packed archive can be installed locally; replace the archive dependency with the registry version before committing consumer manifests.
93
+
94
+ ```sh
95
+ npm install @abuzareal/contour-ui@0.1.0 --save-exact
96
+ ```
97
+
98
+ Commit both `package.json` and `package-lock.json`. Dependabot proposes version updates in each consumer repository. Updates reach production after that project's checks, merge, rebuild, and deployment. A registry release does not change deployed applications automatically.
99
+
100
+ ## Development and release
101
+
102
+ ```sh
103
+ npm ci --ignore-scripts
104
+ npm run validate
105
+ npm audit
106
+ npm pack --dry-run
107
+ ```
108
+
109
+ See `RELEASING.md` for the release checklist, authentication, and trusted-publishing setup. The package ships only built code/styles and release documents, with no consumer installation scripts. The complete-source Git repository contains tests and tooling; those are excluded from the npm archive.
110
+
111
+ MIT licensed. Font dependencies retain their own font licenses; Lucide, React, Three.js, and Lenis retain their respective licenses.
package/RELEASING.md ADDED
@@ -0,0 +1,37 @@
1
+ # Releasing Contour UI
2
+
3
+ ## Initial release
4
+
5
+ 1. Verify `npm whoami` is `abuzareal`; the package is `@abuzareal/contour-ui` and public under MIT.
6
+ 2. Run `npm ci --ignore-scripts`, `npm run validate`, and `npm audit`. Review every advisory; do not release with unexplained audit findings.
7
+ 3. Test the actual packed archive in a clean React consumer without optional Three.js or Lenis, and in the catalogue and portfolio with their full browser suites.
8
+ 4. Check `npm pack --dry-run --json` and licenses. No private data, credentials, unrelated assets, maps, or app content may enter the archive.
9
+ 5. Push the reviewed source and release commit to the independent GitHub repository. The owner currently keeps this repository private; the npm distribution remains public under MIT.
10
+ 6. Set `private` to `false` only for an approved, validated release. Publish from the verified owner account: `npm publish --access public`. Complete npm's required authentication/2FA personally.
11
+ 7. Verify registry metadata, install the exact registry version in both consumers, rerun checks, and commit the manifest/lockfile changes. Do not commit temporary filesystem/archive dependencies.
12
+ 8. Tag the matching package version and record the release. Every future release requires a new version; npm versions cannot be overwritten.
13
+
14
+ ## Future trusted releases
15
+
16
+ After the first package exists, configure npm's GitHub trusted publisher for owner `abuzareal`, repository `contour-ui`, workflow `release.yml`. Authenticate interactively to set this relationship; do not paste credentials into code or chat. Enable two-factor authentication on the owner account and prefer disabling ordinary token publishing after trusted publishing has been successfully verified.
17
+
18
+ With account 2FA enabled, configure and verify the relationship using npm 11.15 or later:
19
+
20
+ ```sh
21
+ npm trust github @abuzareal/contour-ui --file release.yml --repo abuzareal/contour-ui --allow-publish
22
+ npm trust list @abuzareal/contour-ui
23
+ ```
24
+
25
+ The release workflow accepts a manually dispatched version already present in package.json and requires the matching `vVERSION` tag to identify its source. It reruns validation, dependency audit, and clean-consumer tests before publishing with provenance. Push the reviewed release commit to main before tagging. Never publish from a pull-request event or add a persistent npm token to the workflow.
26
+
27
+ The repository is currently private, so the configured workflow deliberately skips publishing. Release locally after running the checklist. Trusted publishing can be configured for a private repository, but npm does not support provenance attestations from private source repositories. If hosted releases are enabled later, review private-runner spending settings and remove the explicit provenance requirement for private source. The current workflow is ready for a future public repository and must not be presented as active automation while the repository stays private.
28
+
29
+ Increment PATCH for compatible fixes, MINOR for compatible additions, and MAJOR for incompatible APIs or substantial incompatible default behavior. While the package is at 0.x, document every change carefully and keep consumers pinned to exact versions. Review visual changes in consuming apps even when the API is compatible.
30
+
31
+ ## Dependency updates and cost
32
+
33
+ Dependabot is selected because it is integrated with GitHub and requires no extra hosted service or bot account. It proposes weekly npm and action updates; the library is not auto-merged. npm distribution is public. Standard-hosted Actions run only if the repository is public; checks run locally while it stays private. Do not enable paid runners, private npm distribution, or paid security subscriptions as part of this setup.
34
+
35
+ The validation workflows deliberately skip private repositories. If a consumer stays private, run its checks locally before merging dependency updates. Enabling private-repository Actions later requires reviewing that account's included usage and spending settings first.
36
+
37
+ Consumers install with `npm install @abuzareal/contour-ui@VERSION --save-exact`, commit their lockfile, run their checks, and redeploy. Keep the three Git repositories independent.
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security
2
+
3
+ Report suspected vulnerabilities privately using GitHub's private vulnerability reporting for this repository once enabled. If that option is unavailable, contact the maintainer privately through their GitHub profile; do not post exploit details or credentials in a public issue.
4
+
5
+ The latest published release is the supported version. Dependency audits identify known reported vulnerabilities, not all possible defects. No claim of complete security or formal accessibility certification is made.
6
+
7
+ The core has no telemetry, remote asset requests, data-fetching services, raw HTML injection API, or consumer installation scripts. React escapes text content. Link wrappers reject protocols outside HTTP, HTTPS, mailto, and tel. Consumers remain responsible for trusted children, callbacks, CSP, application data validation, and authorization.
8
+
9
+ Publication is limited to built library files and release documentation. The package-content gate rejects source maps, credentials/config files, archive files, and unexpected top-level paths. Optional rendering modules must be imported explicitly.
10
+
11
+ Release controls: lockfile review, full dependency audit, typechecking, linting, regression tests, package archive inspection, independent consumer builds and browser tests, and a documented release. GitHub workflows run with minimal permissions and pinned action revisions. Trusted publishing is preferred over long-lived npm tokens. Never commit `.npmrc`, `.env`, recovery codes, or tokens.
@@ -0,0 +1,14 @@
1
+ /** Error boundary that hides the 3D scene if its lazy module or renderer throws. */
2
+ import { Component, type ReactNode } from "react";
3
+ export type SceneBoundaryProps = {
4
+ children: ReactNode;
5
+ };
6
+ type SceneBoundaryState = {
7
+ failed: boolean;
8
+ };
9
+ export default class SceneBoundary extends Component<SceneBoundaryProps, SceneBoundaryState> {
10
+ state: SceneBoundaryState;
11
+ static getDerivedStateFromError(): SceneBoundaryState;
12
+ render(): ReactNode;
13
+ }
14
+ export {};
@@ -0,0 +1,11 @@
1
+ /** Error boundary that hides the 3D scene if its lazy module or renderer throws. */
2
+ import { Component } from "react";
3
+ export default class SceneBoundary extends Component {
4
+ state = { failed: false };
5
+ static getDerivedStateFromError() {
6
+ return { failed: true };
7
+ }
8
+ render() {
9
+ return this.state.failed ? null : this.props.children;
10
+ }
11
+ }
@@ -0,0 +1,4 @@
1
+ export type SculptureProps = {
2
+ motionEnabled: boolean;
3
+ };
4
+ export default function Sculpture({ motionEnabled }: SculptureProps): import("react").JSX.Element;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** Hero sculpture: three chrome/lime links that follow the pointer and drift while motion is on. */
3
+ import { useRef } from "react";
4
+ import * as THREE from "three";
5
+ import useThreeStage from "../../hooks/useThreeStage.js";
6
+ import { createChrome, createLime, createLinkGeometry, } from "../../lib/threeMaterials.js";
7
+ const restRotation = new THREE.Euler(-0.15, -0.1, -0.1);
8
+ function setupSculpture({ scene }) {
9
+ const chrome = createChrome();
10
+ const lime = createLime();
11
+ const geometry = createLinkGeometry();
12
+ // Three interlocking links arranged as one assembly.
13
+ const assembly = new THREE.Group();
14
+ const firstLink = new THREE.Mesh(geometry, chrome);
15
+ firstLink.position.set(-0.76, 0.42, 0);
16
+ firstLink.rotation.set(0.4, -0.3, -0.4);
17
+ const secondLink = new THREE.Mesh(geometry, lime);
18
+ secondLink.position.set(0.62, -0.15, 0.1);
19
+ secondLink.rotation.set(0.25, 1.2, -0.35);
20
+ const thirdLink = new THREE.Mesh(geometry, chrome);
21
+ thirdLink.position.set(0.25, -0.83, -0.13);
22
+ thirdLink.scale.setScalar(0.8);
23
+ thirdLink.rotation.set(1.3, 0.1, 0.5);
24
+ assembly.add(firstLink, secondLink, thirdLink);
25
+ assembly.rotation.copy(restRotation);
26
+ scene.add(assembly);
27
+ return {
28
+ update: ({ delta, elapsed, pointer }) => {
29
+ const damping = 1 - Math.exp(-delta * 4);
30
+ assembly.rotation.y = THREE.MathUtils.lerp(assembly.rotation.y, pointer.x * 0.65 + Math.sin(elapsed * 0.22) * 0.12, damping);
31
+ assembly.rotation.x = THREE.MathUtils.lerp(assembly.rotation.x, pointer.y * 0.4 + restRotation.x, damping);
32
+ assembly.position.y = Math.sin(elapsed * 0.65) * 0.07;
33
+ },
34
+ rest: () => {
35
+ assembly.rotation.copy(restRotation);
36
+ assembly.position.y = 0;
37
+ },
38
+ dispose: () => {
39
+ geometry.dispose();
40
+ chrome.dispose();
41
+ lime.dispose();
42
+ },
43
+ };
44
+ }
45
+ const cameraDistance = (aspect) => Math.max(8.6, 5.5 / aspect);
46
+ export default function Sculpture({ motionEnabled }) {
47
+ const hostRef = useRef(null);
48
+ useThreeStage(hostRef, {
49
+ motionEnabled,
50
+ setup: setupSculpture,
51
+ cameraDistance,
52
+ });
53
+ return (_jsx("div", { ref: hostRef, className: "sculpture-canvas", "data-state": "loading" }));
54
+ }
@@ -0,0 +1,7 @@
1
+ import { type ObjectStudyKind } from "../../lib/threeStudies.js";
2
+ export type ObjectStudyProps = {
3
+ label: string;
4
+ motionEnabled: boolean;
5
+ study: ObjectStudyKind;
6
+ };
7
+ export default function ObjectStudy({ label, motionEnabled, study, }: ObjectStudyProps): import("react").JSX.Element;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** Lays out a set of 3D objects in a responsive grid; each turns slowly and the set leans toward the pointer. */
3
+ import { useRef } from "react";
4
+ import * as THREE from "three";
5
+ import useThreeStage from "../../hooks/useThreeStage.js";
6
+ import { objectStudies } from "../../lib/threeStudies.js";
7
+ const spacing = 3.2;
8
+ const restTilt = new THREE.Euler(-0.25, 0.5, 0);
9
+ /** Four across on wide stages, two by two on narrow ones. */
10
+ const columnsFor = (aspect) => (aspect < 1.4 ? 2 : 4);
11
+ const cameraDistance = (aspect) => {
12
+ const columns = columnsFor(aspect);
13
+ const rows = 4 / columns;
14
+ const width = columns * spacing;
15
+ const height = rows * spacing;
16
+ // tan(17.5deg) * 2 converts a 35deg field of view into visible height per unit distance.
17
+ const perUnit = 2 * Math.tan(THREE.MathUtils.degToRad(17.5));
18
+ return Math.max(width / (perUnit * aspect), height / perUnit) + 1.2;
19
+ };
20
+ export default function ObjectStudy({ label, motionEnabled, study, }) {
21
+ const hostRef = useRef(null);
22
+ useThreeStage(hostRef, {
23
+ motionEnabled,
24
+ cameraDistance,
25
+ setup: ({ scene, camera }) => {
26
+ const set = objectStudies[study]();
27
+ const group = new THREE.Group();
28
+ set.objects.forEach((object) => {
29
+ object.rotation.copy(restTilt);
30
+ group.add(object);
31
+ });
32
+ scene.add(group);
33
+ const arrange = () => {
34
+ const columns = columnsFor(camera.aspect);
35
+ const rows = Math.ceil(set.objects.length / columns);
36
+ set.objects.forEach((object, index) => {
37
+ const column = index % columns;
38
+ const row = Math.floor(index / columns);
39
+ object.position.set((column - (columns - 1) / 2) * spacing, ((rows - 1) / 2 - row) * spacing, 0);
40
+ });
41
+ };
42
+ arrange();
43
+ return {
44
+ update: ({ delta, elapsed, pointer }) => {
45
+ arrange();
46
+ set.objects.forEach((object, index) => {
47
+ object.rotation.y += delta * 0.45;
48
+ object.rotation.x =
49
+ restTilt.x + Math.sin(elapsed * 0.6 + index) * 0.15;
50
+ });
51
+ const damping = 1 - Math.exp(-delta * 3);
52
+ group.rotation.y = THREE.MathUtils.lerp(group.rotation.y, pointer.x * 0.4, damping);
53
+ group.rotation.x = THREE.MathUtils.lerp(group.rotation.x, pointer.y * 0.3, damping);
54
+ },
55
+ rest: () => {
56
+ arrange();
57
+ group.rotation.set(0, 0, 0);
58
+ set.objects.forEach((object) => object.rotation.copy(restTilt));
59
+ },
60
+ dispose: set.dispose,
61
+ };
62
+ },
63
+ });
64
+ return (_jsx("div", { ref: hostRef, className: "stage-canvas", "data-state": "loading", role: "img", "aria-label": label }));
65
+ }
@@ -0,0 +1,13 @@
1
+ /** Inline or full-width message with a tone icon, 3px accent rule, optional action, and dismiss. */
2
+ import type { ReactNode } from "react";
3
+ export type AlertTone = "neutral" | "accent" | "success" | "info" | "warning" | "danger";
4
+ export type AlertProps = {
5
+ tone?: AlertTone;
6
+ title: string;
7
+ children?: ReactNode;
8
+ action?: ReactNode;
9
+ onDismiss?: () => void;
10
+ /** "banner" spans its container edge to edge on one line. */
11
+ layout?: "inline" | "banner";
12
+ };
13
+ export default function Alert({ tone, title, children, action, onDismiss, layout, }: AlertProps): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Asterisk, CircleCheck, CircleX, Info, TriangleAlert, X, } from "lucide-react";
3
+ import { cx } from "../../lib/classNames.js";
4
+ const toneIcons = {
5
+ neutral: _jsx(Asterisk, { size: 18 }),
6
+ accent: _jsx(Asterisk, { size: 18 }),
7
+ success: _jsx(CircleCheck, { size: 18 }),
8
+ info: _jsx(Info, { size: 18 }),
9
+ warning: _jsx(TriangleAlert, { size: 18 }),
10
+ danger: _jsx(CircleX, { size: 18 }),
11
+ };
12
+ export default function Alert({ tone = "neutral", title, children, action, onDismiss, layout = "inline", }) {
13
+ const urgent = tone === "warning" || tone === "danger";
14
+ return (_jsxs("div", { role: urgent ? "alert" : "status", className: cx("alert", `alert-${tone}`, `alert-${layout}`), children: [_jsx("span", { className: "alert-icon", "aria-hidden": "true", children: toneIcons[tone] }), _jsxs("div", { className: "alert-content", children: [_jsx("strong", { className: "alert-title", children: title }), children && _jsx("div", { className: "alert-body", children: children })] }), action && _jsx("div", { className: "alert-action", children: action }), onDismiss && (_jsx("button", { type: "button", className: "alert-dismiss", "aria-label": "Dismiss", onClick: onDismiss, children: _jsx(X, { size: 16 }) }))] }));
15
+ }
@@ -0,0 +1,7 @@
1
+ export type AvatarProps = {
2
+ name: string;
3
+ src?: string;
4
+ size?: "sm" | "md" | "lg" | "xl";
5
+ status?: "online" | "away";
6
+ };
7
+ export default function Avatar({ name, src, size, status, }: AvatarProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Round avatar showing a photo or Space Grotesk initials, with an optional presence dot. */
3
+ import { cx } from "../../lib/classNames.js";
4
+ const initialsOf = (name) => name
5
+ .split(" ")
6
+ .map((part) => part[0])
7
+ .slice(0, 2)
8
+ .join("")
9
+ .toUpperCase();
10
+ export default function Avatar({ name, src, size = "md", status, }) {
11
+ return (_jsxs("span", { className: cx("avatar", `avatar-${size}`), children: [src ? (_jsx("img", { src: src, alt: name, loading: "lazy", decoding: "async" })) : (_jsx("span", { role: "img", "aria-label": name, className: "avatar-initials", children: initialsOf(name) })), status && (_jsx("span", { className: cx("avatar-status", `avatar-status-${status}`), role: "img", "aria-label": status === "online" ? "Online" : "Away" }))] }));
12
+ }
@@ -0,0 +1,11 @@
1
+ /** Compact mono label for status and metadata; tones follow the status palette. */
2
+ import type { ReactNode } from "react";
3
+ export type BadgeTone = "accent" | "neutral" | "invert" | "success" | "info" | "warning" | "danger";
4
+ export type BadgeProps = {
5
+ children: ReactNode;
6
+ tone?: BadgeTone;
7
+ /** Leading status dot. */
8
+ dot?: boolean;
9
+ icon?: ReactNode;
10
+ };
11
+ export default function Badge({ children, tone, dot, icon, }: BadgeProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../../lib/classNames.js";
3
+ export default function Badge({ children, tone = "accent", dot = false, icon, }) {
4
+ return (_jsxs("span", { className: cx("badge mono", `badge-${tone}`), children: [dot && _jsx("span", { className: "badge-dot", "aria-hidden": "true" }), icon, children] }));
5
+ }
@@ -0,0 +1,7 @@
1
+ /** The "aa." monogram; links to the top of the page unless given another href. */
2
+ export type BrandMarkProps = {
3
+ label: string;
4
+ href?: string;
5
+ onClick?: () => void;
6
+ };
7
+ export default function BrandMark({ label, href, onClick, }: BrandMarkProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { safeHref } from "../../lib/safeHref.js";
3
+ export default function BrandMark({ label, href = "#hero", onClick, }) {
4
+ return (_jsxs("a", { href: safeHref(href), className: "brand", "aria-label": label, onClick: onClick, children: ["aa", _jsx("span", { children: "." })] }));
5
+ }
@@ -0,0 +1,10 @@
1
+ /** Mono breadcrumb trail separated by slashes, marking the current page. */
2
+ type Crumb = {
3
+ label: string;
4
+ href?: string;
5
+ };
6
+ export type BreadcrumbsProps = {
7
+ items: Crumb[];
8
+ };
9
+ export default function Breadcrumbs({ items }: BreadcrumbsProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { safeHref } from "../../lib/safeHref.js";
3
+ export default function Breadcrumbs({ items }) {
4
+ return (_jsx("nav", { "aria-label": "Breadcrumb", className: "breadcrumbs mono", children: _jsx("ol", { children: items.map((item, index) => {
5
+ const current = index === items.length - 1;
6
+ return (_jsx("li", { children: current || !item.href ? (_jsx("span", { "aria-current": current ? "page" : undefined, children: item.label })) : (_jsx("a", { href: safeHref(item.href), children: item.label })) }, item.label));
7
+ }) }) }));
8
+ }
@@ -0,0 +1,12 @@
1
+ /** Pill button in four emphasis levels and three sizes, with loading and icon slots. */
2
+ import type { ButtonHTMLAttributes, ReactNode } from "react";
3
+ import { type ButtonSize, type ButtonVariant } from "../../lib/classNames.js";
4
+ export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
5
+ variant?: ButtonVariant;
6
+ size?: ButtonSize;
7
+ /** Icon placed after the label; it nudges forward on hover. */
8
+ icon?: ReactNode;
9
+ leadingIcon?: ReactNode;
10
+ loading?: boolean;
11
+ };
12
+ export default function Button({ variant, size, icon, leadingIcon, loading, disabled, className, children, type, ...rest }: ButtonProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { buttonClassName, } from "../../lib/classNames.js";
3
+ import Spinner from "./Spinner.js";
4
+ export default function Button({ variant, size, icon, leadingIcon, loading = false, disabled, className, children, type = "button", ...rest }) {
5
+ return (_jsxs("button", { ...rest, type: type, className: buttonClassName(variant, size, className), disabled: disabled || loading, "aria-busy": loading || undefined, children: [loading ? _jsx(Spinner, { size: "sm", label: "Loading" }) : leadingIcon, _jsx("span", { children: children }), icon && _jsx("span", { className: "button-icon", children: icon })] }));
6
+ }
@@ -0,0 +1,13 @@
1
+ /** Content card in surface, ink, or acid tones with eyebrow, title, body, and footer slots. */
2
+ import type { ReactNode } from "react";
3
+ export type CardProps = {
4
+ title: string;
5
+ children?: ReactNode;
6
+ eyebrow?: string;
7
+ footer?: ReactNode;
8
+ icon?: ReactNode;
9
+ tone?: "surface" | "ink" | "acid";
10
+ /** Lifts on hover; use when the whole card is a link target. */
11
+ interactive?: boolean;
12
+ };
13
+ export default function Card({ title, children, eyebrow, footer, icon, tone, interactive, }: CardProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../../lib/classNames.js";
3
+ export default function Card({ title, children, eyebrow, footer, icon, tone = "surface", interactive = false, }) {
4
+ return (_jsxs("article", { className: cx("card", `card-${tone}`, interactive && "card-interactive"), children: [(eyebrow || icon) && (_jsxs("div", { className: "card-top mono", children: [eyebrow && _jsx("span", { children: eyebrow }), icon && _jsx("span", { className: "card-icon", children: icon })] })), _jsx("h3", { className: "card-title", children: title }), children && _jsx("div", { className: "card-body", children: children }), footer && _jsx("div", { className: "card-footer", children: footer })] }));
5
+ }
@@ -0,0 +1,9 @@
1
+ /** Square checkbox with an acid check, optional description, error, and indeterminate state. */
2
+ import { type InputHTMLAttributes } from "react";
3
+ export type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "id"> & {
4
+ label: string;
5
+ description?: string;
6
+ error?: string;
7
+ indeterminate?: boolean;
8
+ };
9
+ export default function Checkbox({ label, description, error, indeterminate, className, ...rest }: CheckboxProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Square checkbox with an acid check, optional description, error, and indeterminate state. */
3
+ import { useEffect, useId, useRef } from "react";
4
+ import { Check, Minus } from "lucide-react";
5
+ import { cx } from "../../lib/classNames.js";
6
+ export default function Checkbox({ label, description, error, indeterminate = false, className, ...rest }) {
7
+ const id = useId();
8
+ const ref = useRef(null);
9
+ useEffect(() => {
10
+ if (ref.current)
11
+ ref.current.indeterminate = indeterminate;
12
+ }, [indeterminate]);
13
+ return (_jsxs("div", { className: cx("choice", error && "choice-invalid", className), children: [_jsxs("span", { className: "choice-control", children: [_jsx("input", { ...rest, ref: ref, id: id, type: "checkbox", className: "choice-input", "aria-invalid": error ? true : undefined, "aria-describedby": error ? `${id}-error` : undefined }), _jsx("span", { className: "choice-box", "aria-hidden": "true", children: indeterminate ? (_jsx(Minus, { size: 13, strokeWidth: 3 })) : (_jsx(Check, { size: 13, strokeWidth: 3 })) })] }), _jsxs("label", { htmlFor: id, className: "choice-text", children: [_jsx("span", { className: "choice-label", children: label }), description && (_jsx("span", { className: "choice-description", children: description })), error && (_jsx("span", { id: `${id}-error`, className: "choice-error mono", children: error }))] })] }));
14
+ }
@@ -0,0 +1,9 @@
1
+ export type CountUpProps = {
2
+ to: number;
3
+ /** Milliseconds for the count. */
4
+ duration?: number;
5
+ /** Pads to this many digits, e.g. 2 renders 05. */
6
+ digits?: number;
7
+ suffix?: string;
8
+ };
9
+ export default function CountUp({ to, duration, digits, suffix, }: CountUpProps): import("react").JSX.Element;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Number that counts up the first time it scrolls into view; screen readers get the final value. */
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { motionDisabledFor } from "../../lib/motion.js";
5
+ const easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
6
+ export default function CountUp({ to, duration = 1400, digits = 0, suffix = "", }) {
7
+ const ref = useRef(null);
8
+ const [current, setCurrent] = useState(0);
9
+ useEffect(() => {
10
+ const element = ref.current;
11
+ if (!element)
12
+ return;
13
+ if (motionDisabledFor(element) || duration <= 0) {
14
+ setCurrent(to);
15
+ return;
16
+ }
17
+ let frame = 0;
18
+ const observer = new IntersectionObserver(([entry]) => {
19
+ if (!entry.isIntersecting)
20
+ return;
21
+ if (motionDisabledFor(element)) {
22
+ setCurrent(to);
23
+ observer.disconnect();
24
+ return;
25
+ }
26
+ observer.disconnect();
27
+ const start = performance.now();
28
+ const tick = (time) => {
29
+ if (motionDisabledFor(element)) {
30
+ setCurrent(to);
31
+ return;
32
+ }
33
+ const progress = Math.min((time - start) / duration, 1);
34
+ setCurrent(Math.round(easeOutCubic(progress) * to));
35
+ if (progress < 1)
36
+ frame = requestAnimationFrame(tick);
37
+ };
38
+ frame = requestAnimationFrame(tick);
39
+ });
40
+ observer.observe(element);
41
+ return () => {
42
+ observer.disconnect();
43
+ cancelAnimationFrame(frame);
44
+ };
45
+ }, [to, duration]);
46
+ const format = (value) => `${String(value).padStart(digits, "0")}${suffix}`;
47
+ return (_jsxs("span", { ref: ref, className: "count-up", children: [_jsx("span", { "aria-hidden": "true", children: format(current) }), _jsx("span", { className: "visually-hidden", children: format(to) })] }));
48
+ }
@@ -0,0 +1,16 @@
1
+ /** Editorial data table with mono headers, optional column sorting, and horizontal scroll on small screens. */
2
+ import { type ReactNode } from "react";
3
+ export type TableColumn<Row> = {
4
+ key: keyof Row & string;
5
+ header: string;
6
+ align?: "start" | "end";
7
+ sortable?: boolean;
8
+ render?: (row: Row) => ReactNode;
9
+ };
10
+ export type DataTableProps<Row> = {
11
+ caption: string;
12
+ columns: TableColumn<Row>[];
13
+ rows: Row[];
14
+ rowKey: keyof Row & string;
15
+ };
16
+ export default function DataTable<Row extends Record<string, string | number>>({ caption, columns, rows, rowKey, }: DataTableProps<Row>): import("react").JSX.Element;