@orangecheck/app-design 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -2
  3. package/dist/brand.d.mts +46 -0
  4. package/dist/brand.d.ts +46 -0
  5. package/dist/brand.js +144 -0
  6. package/dist/brand.js.map +1 -0
  7. package/dist/brand.mjs +136 -0
  8. package/dist/brand.mjs.map +1 -0
  9. package/dist/chrome.d.mts +64 -0
  10. package/dist/chrome.d.ts +64 -0
  11. package/dist/chrome.js +479 -0
  12. package/dist/chrome.js.map +1 -0
  13. package/dist/chrome.mjs +447 -0
  14. package/dist/chrome.mjs.map +1 -0
  15. package/dist/components.d.mts +474 -0
  16. package/dist/components.d.ts +474 -0
  17. package/dist/components.js +619 -0
  18. package/dist/components.js.map +1 -0
  19. package/dist/components.mjs +588 -0
  20. package/dist/components.mjs.map +1 -0
  21. package/dist/index.d.mts +21 -0
  22. package/dist/index.d.ts +21 -0
  23. package/dist/index.js +1597 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/index.mjs +1448 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/primitives.d.mts +140 -0
  28. package/dist/primitives.d.ts +140 -0
  29. package/dist/primitives.js +706 -0
  30. package/dist/primitives.js.map +1 -0
  31. package/dist/primitives.mjs +606 -0
  32. package/dist/primitives.mjs.map +1 -0
  33. package/dist/styles/fonts/inter-latin-ext.woff2 +0 -0
  34. package/dist/styles/fonts/inter-latin.woff2 +0 -0
  35. package/dist/styles/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  36. package/dist/styles/fonts/jetbrains-mono-latin.woff2 +0 -0
  37. package/dist/styles/fonts.css +74 -0
  38. package/dist/styles/motion.css +80 -0
  39. package/dist/styles/theme.css +227 -0
  40. package/dist/styles/tokens.css +152 -0
  41. package/dist/styles.css +14 -0
  42. package/dist/tokens.d.mts +42 -0
  43. package/dist/tokens.d.ts +42 -0
  44. package/dist/tokens.js +50 -0
  45. package/dist/tokens.js.map +1 -0
  46. package/dist/tokens.mjs +45 -0
  47. package/dist/tokens.mjs.map +1 -0
  48. package/package.json +114 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OrangeCheck
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 CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # @orangecheck/app-design
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The design system for **ochk.app**, built from Souheila's partner deck: Inter at
4
+ regular weight, a light canvas, one orange used sparingly, halftone photography and
5
+ the isometric check.
6
+
7
+ It is its own system. Every token and rule is scoped to `data-oc-app="ochk"`, so it
8
+ does nothing on a page without the attribute, and it never imports
9
+ `@orangecheck/design`. Stories: **design.ochk.io → ochk.app**.
10
+
11
+ ```css
12
+ /* globals.css */
13
+ @import 'tailwindcss';
14
+ @custom-variant dark (&:is(.dark *));
15
+ @source '../../node_modules/@orangecheck/app-design/dist';
16
+ @import '@orangecheck/app-design/styles.css';
17
+ ```
18
+
19
+ ```tsx
20
+ // _document.tsx
21
+ <Html lang="en" data-oc-app="ochk">
22
+ ```
23
+
24
+ Light and dark come from plain `next-themes` (`attribute="class"`). Do not mount the
25
+ family `OcThemeProvider` on the same page: it writes `data-oc-theme` and the shared
26
+ `oc_skin` cookie.
27
+
28
+ | Entry | What |
29
+ |---|---|
30
+ | `/brand` | `Mark`, `AppIcon`, `Lockup`, `IsoCheck`, `HalftoneSphere` |
31
+ | `/primitives` | `Button`, `Badge`, `Card`, form controls, `Dialog`, `Sheet`, `Popover`, `Tooltip`, `DropdownMenu`, `Tabs`, `Toaster` |
32
+ | `/components` | `Display`, `Eyebrow`, `Section`, `StepGrid`, `PackageCard`, `TruthTable`, `Sats`, `DataTable`, `Notice`, `ProgressSteps` … |
33
+ | `/chrome` | `SiteHeader`, `SiteFooter`, `AppShell`, `AccountMenu`, `ThemeToggle` |
34
+ | `/tokens` | the palette as data, for OG images and charts |
35
+
36
+ `yarn test` fails if any text token drops under WCAG AA in light or dark, or if a
37
+ rule could match without the attribute. `scripts/halftone.py` turns any photo into
38
+ the dot layer of a `HalftoneSphere`.
39
+
40
+ MIT © OrangeCheck. Inter and JetBrains Mono are SIL OFL 1.1.
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { SVGProps, CSSProperties } from 'react';
3
+
4
+ declare const MARK_FACES: {
5
+ readonly top: "#FED8AB";
6
+ readonly left: "#FB933C";
7
+ readonly right: "#EA580B";
8
+ };
9
+ interface MarkProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {
10
+ title?: string;
11
+ size?: number;
12
+ }
13
+ declare function Mark({ title, size, className, ...rest }: MarkProps): react.JSX.Element;
14
+ interface AppIconProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {
15
+ title?: string;
16
+ size?: number;
17
+ }
18
+ declare function AppIcon({ title, size, className, ...rest }: AppIconProps): react.JSX.Element;
19
+ interface LockupProps {
20
+ name?: string;
21
+ size?: number;
22
+ className?: string;
23
+ }
24
+ declare function Lockup({ name, size, className }: LockupProps): react.JSX.Element;
25
+
26
+ declare const SPHERE_GRADIENT = "radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)";
27
+ interface HalftoneSphereProps {
28
+ src: string;
29
+ alt: string;
30
+ stadium?: boolean;
31
+ float?: boolean;
32
+ className?: string;
33
+ style?: CSSProperties;
34
+ }
35
+ declare function HalftoneSphere({ src, alt, stadium, float, className, style }: HalftoneSphereProps): react.JSX.Element;
36
+
37
+ interface IsoCheckProps {
38
+ size?: number;
39
+ settle?: boolean;
40
+ shadow?: boolean;
41
+ title?: string;
42
+ className?: string;
43
+ }
44
+ declare function IsoCheck({ size, settle, shadow, title, className }: IsoCheckProps): react.JSX.Element;
45
+
46
+ export { AppIcon, type AppIconProps, HalftoneSphere, type HalftoneSphereProps, IsoCheck, type IsoCheckProps, Lockup, type LockupProps, MARK_FACES, Mark, type MarkProps, SPHERE_GRADIENT };
@@ -0,0 +1,46 @@
1
+ import * as react from 'react';
2
+ import { SVGProps, CSSProperties } from 'react';
3
+
4
+ declare const MARK_FACES: {
5
+ readonly top: "#FED8AB";
6
+ readonly left: "#FB933C";
7
+ readonly right: "#EA580B";
8
+ };
9
+ interface MarkProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {
10
+ title?: string;
11
+ size?: number;
12
+ }
13
+ declare function Mark({ title, size, className, ...rest }: MarkProps): react.JSX.Element;
14
+ interface AppIconProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {
15
+ title?: string;
16
+ size?: number;
17
+ }
18
+ declare function AppIcon({ title, size, className, ...rest }: AppIconProps): react.JSX.Element;
19
+ interface LockupProps {
20
+ name?: string;
21
+ size?: number;
22
+ className?: string;
23
+ }
24
+ declare function Lockup({ name, size, className }: LockupProps): react.JSX.Element;
25
+
26
+ declare const SPHERE_GRADIENT = "radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)";
27
+ interface HalftoneSphereProps {
28
+ src: string;
29
+ alt: string;
30
+ stadium?: boolean;
31
+ float?: boolean;
32
+ className?: string;
33
+ style?: CSSProperties;
34
+ }
35
+ declare function HalftoneSphere({ src, alt, stadium, float, className, style }: HalftoneSphereProps): react.JSX.Element;
36
+
37
+ interface IsoCheckProps {
38
+ size?: number;
39
+ settle?: boolean;
40
+ shadow?: boolean;
41
+ title?: string;
42
+ className?: string;
43
+ }
44
+ declare function IsoCheck({ size, settle, shadow, title, className }: IsoCheckProps): react.JSX.Element;
45
+
46
+ export { AppIcon, type AppIconProps, HalftoneSphere, type HalftoneSphereProps, IsoCheck, type IsoCheckProps, Lockup, type LockupProps, MARK_FACES, Mark, type MarkProps, SPHERE_GRADIENT };
package/dist/brand.js ADDED
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ var clsx = require('clsx');
4
+ var tailwindMerge = require('tailwind-merge');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ var merge = tailwindMerge.extendTailwindMerge({
8
+ extend: {
9
+ classGroups: {
10
+ "oca-type": [
11
+ "oca-display-1",
12
+ "oca-display-2",
13
+ "oca-display-3",
14
+ "oca-title",
15
+ "oca-title-sm",
16
+ "oca-eyebrow",
17
+ "oca-lead",
18
+ "oca-body",
19
+ "oca-numeral"
20
+ ]
21
+ }
22
+ }
23
+ });
24
+ function cn(...inputs) {
25
+ return merge(clsx.clsx(inputs));
26
+ }
27
+ var TOP = "0,3 2,2 6,4 14,0 16,1 6,6";
28
+ var LEFT = "0,3 6,6 6,8 0,5";
29
+ var RIGHT = "6,6 16,1 16,3 6,8";
30
+ var MARK_FACES = { top: "#FED8AB", left: "#FB933C", right: "#EA580B" };
31
+ function Mark({ title, size, className, ...rest }) {
32
+ return /* @__PURE__ */ jsxRuntime.jsxs(
33
+ "svg",
34
+ {
35
+ viewBox: "0 0 16 8",
36
+ width: size,
37
+ height: size ? size / 2 : void 0,
38
+ role: title ? "img" : void 0,
39
+ "aria-hidden": title ? void 0 : true,
40
+ className: cn("shrink-0", className),
41
+ ...rest,
42
+ children: [
43
+ title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }) : null,
44
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: LEFT, fill: MARK_FACES.left }),
45
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: RIGHT, fill: MARK_FACES.right }),
46
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: TOP, fill: MARK_FACES.top })
47
+ ]
48
+ }
49
+ );
50
+ }
51
+ function AppIcon({ title, size = 32, className, ...rest }) {
52
+ return /* @__PURE__ */ jsxRuntime.jsxs(
53
+ "svg",
54
+ {
55
+ viewBox: "0 0 24 24",
56
+ width: size,
57
+ height: size,
58
+ role: title ? "img" : void 0,
59
+ "aria-hidden": title ? void 0 : true,
60
+ className: cn("shrink-0", className),
61
+ ...rest,
62
+ children: [
63
+ title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }) : null,
64
+ /* @__PURE__ */ jsxRuntime.jsx(
65
+ "rect",
66
+ {
67
+ width: "24",
68
+ height: "24",
69
+ rx: "6.25",
70
+ style: { fill: "var(--oca-icon-tile, #0A0A0A)", stroke: "var(--oca-icon-ring, transparent)" },
71
+ strokeWidth: "0.5"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(4.25 8.1) scale(0.97)", children: [
75
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: LEFT, fill: MARK_FACES.left }),
76
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: RIGHT, fill: MARK_FACES.right }),
77
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: TOP, fill: MARK_FACES.top })
78
+ ] })
79
+ ]
80
+ }
81
+ );
82
+ }
83
+ function Lockup({ name = "orangecheck", size = 28, className }) {
84
+ return /* @__PURE__ */ jsxRuntime.jsxs(
85
+ "span",
86
+ {
87
+ className: cn("inline-flex items-center font-medium tracking-[-0.01em]", className),
88
+ style: { gap: size * 0.32, fontSize: size * 0.56 },
89
+ children: [
90
+ /* @__PURE__ */ jsxRuntime.jsx(AppIcon, { size }),
91
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: name })
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ var SPHERE_GRADIENT = "radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)";
97
+ function HalftoneSphere({ src, alt, stadium = false, float = false, className, style }) {
98
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative aspect-square w-full", className), style, children: [
99
+ stadium ? (
100
+ // Measured from the deck's title slide: a rounded square, its corners
101
+ // 17% of the sphere, level with the sphere's foot, reaching left behind
102
+ // the headline. On phones a ring inside the gutter, since the offset
103
+ // would push the page sideways.
104
+ /* @__PURE__ */ jsxRuntime.jsx(
105
+ "div",
106
+ {
107
+ "aria-hidden": true,
108
+ className: "oca-stadium pointer-events-none absolute inset-[-6%] rounded-full md:[inset:-12%_14%_0%_-26%] md:rounded-[17%]"
109
+ }
110
+ )
111
+ ) : null,
112
+ /* @__PURE__ */ jsxRuntime.jsx(
113
+ "div",
114
+ {
115
+ className: cn("relative size-full overflow-hidden rounded-full", float && "oca-animate-float"),
116
+ style: { background: SPHERE_GRADIENT },
117
+ children: /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className: "absolute inset-0 size-full select-none", draggable: false })
118
+ }
119
+ )
120
+ ] });
121
+ }
122
+ function IsoCheck({ size = 360, settle = false, shadow = true, title, className }) {
123
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative inline-block", settle && "oca-animate-settle", className), style: { width: size }, children: [
124
+ /* @__PURE__ */ jsxRuntime.jsx(Mark, { size, title, className: "relative z-10 block" }),
125
+ shadow ? /* @__PURE__ */ jsxRuntime.jsx(
126
+ "div",
127
+ {
128
+ "aria-hidden": true,
129
+ className: "absolute left-[8%] right-[8%] rounded-[50%] opacity-60 blur-xl",
130
+ style: { height: size * 0.08, bottom: -size * 0.04, background: "rgb(234 88 11 / 0.18)" }
131
+ }
132
+ ) : null
133
+ ] });
134
+ }
135
+
136
+ exports.AppIcon = AppIcon;
137
+ exports.HalftoneSphere = HalftoneSphere;
138
+ exports.IsoCheck = IsoCheck;
139
+ exports.Lockup = Lockup;
140
+ exports.MARK_FACES = MARK_FACES;
141
+ exports.Mark = Mark;
142
+ exports.SPHERE_GRADIENT = SPHERE_GRADIENT;
143
+ //# sourceMappingURL=brand.js.map
144
+ //# sourceMappingURL=brand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/cn.ts","../src/brand/mark.tsx","../src/brand/sphere.tsx","../src/brand/iso-check.tsx"],"names":["extendTailwindMerge","clsx","jsxs","jsx"],"mappings":"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","file":"brand.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n// tailwind-merge would treat `oca-display-1` and `text-foreground` as the same\n// group and drop one; teach it the package's own type utilities.\nconst merge = extendTailwindMerge<'oca-type'>({\n extend: {\n classGroups: {\n 'oca-type': [\n 'oca-display-1',\n 'oca-display-2',\n 'oca-display-3',\n 'oca-title',\n 'oca-title-sm',\n 'oca-eyebrow',\n 'oca-lead',\n 'oca-body',\n 'oca-numeral',\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]): string {\n return merge(clsx(inputs));\n}\n","import type { SVGProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/*\n * The isometric check, measured from Sou's deck (slide 9) onto a 16×8 grid:\n * a chevron with a 3-unit short arm and a 5-unit long arm, one unit thick,\n * extruded two units. Three faces, three oranges.\n */\n\nconst TOP = '0,3 2,2 6,4 14,0 16,1 6,6';\nconst LEFT = '0,3 6,6 6,8 0,5';\nconst RIGHT = '6,6 16,1 16,3 6,8';\n\nexport const MARK_FACES = { top: '#FED8AB', left: '#FB933C', right: '#EA580B' } as const;\n\nexport interface MarkProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {\n /** Accessible name. Omit to render the mark as decoration. */\n title?: string;\n /** Rendered width in px; height follows the 2:1 grid. */\n size?: number;\n}\n\nexport function Mark({ title, size, className, ...rest }: MarkProps) {\n return (\n <svg\n viewBox=\"0 0 16 8\"\n width={size}\n height={size ? size / 2 : undefined}\n role={title ? 'img' : undefined}\n aria-hidden={title ? undefined : true}\n className={cn('shrink-0', className)}\n {...rest}\n >\n {title ? <title>{title}</title> : null}\n <polygon points={LEFT} fill={MARK_FACES.left} />\n <polygon points={RIGHT} fill={MARK_FACES.right} />\n <polygon points={TOP} fill={MARK_FACES.top} />\n </svg>\n );\n}\n\nexport interface AppIconProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {\n title?: string;\n size?: number;\n}\n\n/** The app icon: the mark on an ink squircle, as in the deck's lockup. */\nexport function AppIcon({ title, size = 32, className, ...rest }: AppIconProps) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n role={title ? 'img' : undefined}\n aria-hidden={title ? undefined : true}\n className={cn('shrink-0', className)}\n {...rest}\n >\n {title ? <title>{title}</title> : null}\n <rect\n width=\"24\"\n height=\"24\"\n rx=\"6.25\"\n style={{ fill: 'var(--oca-icon-tile, #0A0A0A)', stroke: 'var(--oca-icon-ring, transparent)' }}\n strokeWidth=\"0.5\"\n />\n <g transform=\"translate(4.25 8.1) scale(0.97)\">\n <polygon points={LEFT} fill={MARK_FACES.left} />\n <polygon points={RIGHT} fill={MARK_FACES.right} />\n <polygon points={TOP} fill={MARK_FACES.top} />\n </g>\n </svg>\n );\n}\n\nexport interface LockupProps {\n /** The word beside the icon. */\n name?: string;\n /** Icon size in px; the word scales with it. */\n size?: number;\n className?: string;\n}\n\n/** Icon and wordmark, lowercase, as the deck sets it. */\nexport function Lockup({ name = 'orangecheck', size = 28, className }: LockupProps) {\n return (\n <span\n className={cn('inline-flex items-center font-medium tracking-[-0.01em]', className)}\n style={{ gap: size * 0.32, fontSize: size * 0.56 }}\n >\n <AppIcon size={size} />\n <span>{name}</span>\n </span>\n );\n}\n","import type { CSSProperties } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/*\n * The deck's photograph: a halftone portrait on an orange sphere that glows\n * near-white behind the subject and saturates at the rim, with a hairline\n * stadium behind it. The dot layer is an image of black dots on transparent,\n * made by scripts/halftone.py from any photo, so the sphere stays CSS and the\n * dots stay crisp at any size.\n */\n\nexport const SPHERE_GRADIENT =\n 'radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)';\n\nexport interface HalftoneSphereProps {\n /** The dot layer (black dots on transparent, already circular). */\n src: string;\n /** Describe the person or subject; empty string marks it decorative. */\n alt: string;\n /** Draw the hairline stadium behind the sphere, as the hero does. */\n stadium?: boolean;\n /** Let the sphere drift a few pixels. Off under reduced motion. */\n float?: boolean;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function HalftoneSphere({ src, alt, stadium = false, float = false, className, style }: HalftoneSphereProps) {\n return (\n <div className={cn('relative aspect-square w-full', className)} style={style}>\n {stadium ? (\n // Measured from the deck's title slide: a rounded square, its corners\n // 17% of the sphere, level with the sphere's foot, reaching left behind\n // the headline. On phones a ring inside the gutter, since the offset\n // would push the page sideways.\n <div\n aria-hidden\n className=\"oca-stadium pointer-events-none absolute inset-[-6%] rounded-full md:[inset:-12%_14%_0%_-26%] md:rounded-[17%]\"\n />\n ) : null}\n <div\n className={cn('relative size-full overflow-hidden rounded-full', float && 'oca-animate-float')}\n style={{ background: SPHERE_GRADIENT }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element -- a static dot layer, sized by CSS */}\n <img src={src} alt={alt} className=\"absolute inset-0 size-full select-none\" draggable={false} />\n </div>\n </div>\n );\n}\n","import { cn } from '../lib/cn';\nimport { Mark } from './mark';\n\nexport interface IsoCheckProps {\n /** Width in px; the check keeps its 2:1 grid. */\n size?: number;\n /** Drop it into place on first paint, the deck's closing gesture. */\n settle?: boolean;\n /** A soft floor shadow, so it reads as an object on the canvas. */\n shadow?: boolean;\n title?: string;\n className?: string;\n}\n\n/** The isometric check as an object: the deck's closing illustration. */\nexport function IsoCheck({ size = 360, settle = false, shadow = true, title, className }: IsoCheckProps) {\n return (\n <div className={cn('relative inline-block', settle && 'oca-animate-settle', className)} style={{ width: size }}>\n <Mark size={size} title={title} className=\"relative z-10 block\" />\n {shadow ? (\n <div\n aria-hidden\n className=\"absolute left-[8%] right-[8%] rounded-[50%] opacity-60 blur-xl\"\n style={{ height: size * 0.08, bottom: -size * 0.04, background: 'rgb(234 88 11 / 0.18)' }}\n />\n ) : null}\n </div>\n );\n}\n"]}
package/dist/brand.mjs ADDED
@@ -0,0 +1,136 @@
1
+ import { clsx } from 'clsx';
2
+ import { extendTailwindMerge } from 'tailwind-merge';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var merge = extendTailwindMerge({
6
+ extend: {
7
+ classGroups: {
8
+ "oca-type": [
9
+ "oca-display-1",
10
+ "oca-display-2",
11
+ "oca-display-3",
12
+ "oca-title",
13
+ "oca-title-sm",
14
+ "oca-eyebrow",
15
+ "oca-lead",
16
+ "oca-body",
17
+ "oca-numeral"
18
+ ]
19
+ }
20
+ }
21
+ });
22
+ function cn(...inputs) {
23
+ return merge(clsx(inputs));
24
+ }
25
+ var TOP = "0,3 2,2 6,4 14,0 16,1 6,6";
26
+ var LEFT = "0,3 6,6 6,8 0,5";
27
+ var RIGHT = "6,6 16,1 16,3 6,8";
28
+ var MARK_FACES = { top: "#FED8AB", left: "#FB933C", right: "#EA580B" };
29
+ function Mark({ title, size, className, ...rest }) {
30
+ return /* @__PURE__ */ jsxs(
31
+ "svg",
32
+ {
33
+ viewBox: "0 0 16 8",
34
+ width: size,
35
+ height: size ? size / 2 : void 0,
36
+ role: title ? "img" : void 0,
37
+ "aria-hidden": title ? void 0 : true,
38
+ className: cn("shrink-0", className),
39
+ ...rest,
40
+ children: [
41
+ title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
42
+ /* @__PURE__ */ jsx("polygon", { points: LEFT, fill: MARK_FACES.left }),
43
+ /* @__PURE__ */ jsx("polygon", { points: RIGHT, fill: MARK_FACES.right }),
44
+ /* @__PURE__ */ jsx("polygon", { points: TOP, fill: MARK_FACES.top })
45
+ ]
46
+ }
47
+ );
48
+ }
49
+ function AppIcon({ title, size = 32, className, ...rest }) {
50
+ return /* @__PURE__ */ jsxs(
51
+ "svg",
52
+ {
53
+ viewBox: "0 0 24 24",
54
+ width: size,
55
+ height: size,
56
+ role: title ? "img" : void 0,
57
+ "aria-hidden": title ? void 0 : true,
58
+ className: cn("shrink-0", className),
59
+ ...rest,
60
+ children: [
61
+ title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
62
+ /* @__PURE__ */ jsx(
63
+ "rect",
64
+ {
65
+ width: "24",
66
+ height: "24",
67
+ rx: "6.25",
68
+ style: { fill: "var(--oca-icon-tile, #0A0A0A)", stroke: "var(--oca-icon-ring, transparent)" },
69
+ strokeWidth: "0.5"
70
+ }
71
+ ),
72
+ /* @__PURE__ */ jsxs("g", { transform: "translate(4.25 8.1) scale(0.97)", children: [
73
+ /* @__PURE__ */ jsx("polygon", { points: LEFT, fill: MARK_FACES.left }),
74
+ /* @__PURE__ */ jsx("polygon", { points: RIGHT, fill: MARK_FACES.right }),
75
+ /* @__PURE__ */ jsx("polygon", { points: TOP, fill: MARK_FACES.top })
76
+ ] })
77
+ ]
78
+ }
79
+ );
80
+ }
81
+ function Lockup({ name = "orangecheck", size = 28, className }) {
82
+ return /* @__PURE__ */ jsxs(
83
+ "span",
84
+ {
85
+ className: cn("inline-flex items-center font-medium tracking-[-0.01em]", className),
86
+ style: { gap: size * 0.32, fontSize: size * 0.56 },
87
+ children: [
88
+ /* @__PURE__ */ jsx(AppIcon, { size }),
89
+ /* @__PURE__ */ jsx("span", { children: name })
90
+ ]
91
+ }
92
+ );
93
+ }
94
+ var SPHERE_GRADIENT = "radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)";
95
+ function HalftoneSphere({ src, alt, stadium = false, float = false, className, style }) {
96
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative aspect-square w-full", className), style, children: [
97
+ stadium ? (
98
+ // Measured from the deck's title slide: a rounded square, its corners
99
+ // 17% of the sphere, level with the sphere's foot, reaching left behind
100
+ // the headline. On phones a ring inside the gutter, since the offset
101
+ // would push the page sideways.
102
+ /* @__PURE__ */ jsx(
103
+ "div",
104
+ {
105
+ "aria-hidden": true,
106
+ className: "oca-stadium pointer-events-none absolute inset-[-6%] rounded-full md:[inset:-12%_14%_0%_-26%] md:rounded-[17%]"
107
+ }
108
+ )
109
+ ) : null,
110
+ /* @__PURE__ */ jsx(
111
+ "div",
112
+ {
113
+ className: cn("relative size-full overflow-hidden rounded-full", float && "oca-animate-float"),
114
+ style: { background: SPHERE_GRADIENT },
115
+ children: /* @__PURE__ */ jsx("img", { src, alt, className: "absolute inset-0 size-full select-none", draggable: false })
116
+ }
117
+ )
118
+ ] });
119
+ }
120
+ function IsoCheck({ size = 360, settle = false, shadow = true, title, className }) {
121
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative inline-block", settle && "oca-animate-settle", className), style: { width: size }, children: [
122
+ /* @__PURE__ */ jsx(Mark, { size, title, className: "relative z-10 block" }),
123
+ shadow ? /* @__PURE__ */ jsx(
124
+ "div",
125
+ {
126
+ "aria-hidden": true,
127
+ className: "absolute left-[8%] right-[8%] rounded-[50%] opacity-60 blur-xl",
128
+ style: { height: size * 0.08, bottom: -size * 0.04, background: "rgb(234 88 11 / 0.18)" }
129
+ }
130
+ ) : null
131
+ ] });
132
+ }
133
+
134
+ export { AppIcon, HalftoneSphere, IsoCheck, Lockup, MARK_FACES, Mark, SPHERE_GRADIENT };
135
+ //# sourceMappingURL=brand.mjs.map
136
+ //# sourceMappingURL=brand.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/cn.ts","../src/brand/mark.tsx","../src/brand/sphere.tsx","../src/brand/iso-check.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"brand.mjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n// tailwind-merge would treat `oca-display-1` and `text-foreground` as the same\n// group and drop one; teach it the package's own type utilities.\nconst merge = extendTailwindMerge<'oca-type'>({\n extend: {\n classGroups: {\n 'oca-type': [\n 'oca-display-1',\n 'oca-display-2',\n 'oca-display-3',\n 'oca-title',\n 'oca-title-sm',\n 'oca-eyebrow',\n 'oca-lead',\n 'oca-body',\n 'oca-numeral',\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]): string {\n return merge(clsx(inputs));\n}\n","import type { SVGProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/*\n * The isometric check, measured from Sou's deck (slide 9) onto a 16×8 grid:\n * a chevron with a 3-unit short arm and a 5-unit long arm, one unit thick,\n * extruded two units. Three faces, three oranges.\n */\n\nconst TOP = '0,3 2,2 6,4 14,0 16,1 6,6';\nconst LEFT = '0,3 6,6 6,8 0,5';\nconst RIGHT = '6,6 16,1 16,3 6,8';\n\nexport const MARK_FACES = { top: '#FED8AB', left: '#FB933C', right: '#EA580B' } as const;\n\nexport interface MarkProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {\n /** Accessible name. Omit to render the mark as decoration. */\n title?: string;\n /** Rendered width in px; height follows the 2:1 grid. */\n size?: number;\n}\n\nexport function Mark({ title, size, className, ...rest }: MarkProps) {\n return (\n <svg\n viewBox=\"0 0 16 8\"\n width={size}\n height={size ? size / 2 : undefined}\n role={title ? 'img' : undefined}\n aria-hidden={title ? undefined : true}\n className={cn('shrink-0', className)}\n {...rest}\n >\n {title ? <title>{title}</title> : null}\n <polygon points={LEFT} fill={MARK_FACES.left} />\n <polygon points={RIGHT} fill={MARK_FACES.right} />\n <polygon points={TOP} fill={MARK_FACES.top} />\n </svg>\n );\n}\n\nexport interface AppIconProps extends Omit<SVGProps<SVGSVGElement>, 'children'> {\n title?: string;\n size?: number;\n}\n\n/** The app icon: the mark on an ink squircle, as in the deck's lockup. */\nexport function AppIcon({ title, size = 32, className, ...rest }: AppIconProps) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n width={size}\n height={size}\n role={title ? 'img' : undefined}\n aria-hidden={title ? undefined : true}\n className={cn('shrink-0', className)}\n {...rest}\n >\n {title ? <title>{title}</title> : null}\n <rect\n width=\"24\"\n height=\"24\"\n rx=\"6.25\"\n style={{ fill: 'var(--oca-icon-tile, #0A0A0A)', stroke: 'var(--oca-icon-ring, transparent)' }}\n strokeWidth=\"0.5\"\n />\n <g transform=\"translate(4.25 8.1) scale(0.97)\">\n <polygon points={LEFT} fill={MARK_FACES.left} />\n <polygon points={RIGHT} fill={MARK_FACES.right} />\n <polygon points={TOP} fill={MARK_FACES.top} />\n </g>\n </svg>\n );\n}\n\nexport interface LockupProps {\n /** The word beside the icon. */\n name?: string;\n /** Icon size in px; the word scales with it. */\n size?: number;\n className?: string;\n}\n\n/** Icon and wordmark, lowercase, as the deck sets it. */\nexport function Lockup({ name = 'orangecheck', size = 28, className }: LockupProps) {\n return (\n <span\n className={cn('inline-flex items-center font-medium tracking-[-0.01em]', className)}\n style={{ gap: size * 0.32, fontSize: size * 0.56 }}\n >\n <AppIcon size={size} />\n <span>{name}</span>\n </span>\n );\n}\n","import type { CSSProperties } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/*\n * The deck's photograph: a halftone portrait on an orange sphere that glows\n * near-white behind the subject and saturates at the rim, with a hairline\n * stadium behind it. The dot layer is an image of black dots on transparent,\n * made by scripts/halftone.py from any photo, so the sphere stays CSS and the\n * dots stay crisp at any size.\n */\n\nexport const SPHERE_GRADIENT =\n 'radial-gradient(circle at 50% 42%, #fff8f3 0%, #ffe2d3 22%, #ffb48d 45%, #ff8a55 68%, #ff6a2c 100%)';\n\nexport interface HalftoneSphereProps {\n /** The dot layer (black dots on transparent, already circular). */\n src: string;\n /** Describe the person or subject; empty string marks it decorative. */\n alt: string;\n /** Draw the hairline stadium behind the sphere, as the hero does. */\n stadium?: boolean;\n /** Let the sphere drift a few pixels. Off under reduced motion. */\n float?: boolean;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function HalftoneSphere({ src, alt, stadium = false, float = false, className, style }: HalftoneSphereProps) {\n return (\n <div className={cn('relative aspect-square w-full', className)} style={style}>\n {stadium ? (\n // Measured from the deck's title slide: a rounded square, its corners\n // 17% of the sphere, level with the sphere's foot, reaching left behind\n // the headline. On phones a ring inside the gutter, since the offset\n // would push the page sideways.\n <div\n aria-hidden\n className=\"oca-stadium pointer-events-none absolute inset-[-6%] rounded-full md:[inset:-12%_14%_0%_-26%] md:rounded-[17%]\"\n />\n ) : null}\n <div\n className={cn('relative size-full overflow-hidden rounded-full', float && 'oca-animate-float')}\n style={{ background: SPHERE_GRADIENT }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element -- a static dot layer, sized by CSS */}\n <img src={src} alt={alt} className=\"absolute inset-0 size-full select-none\" draggable={false} />\n </div>\n </div>\n );\n}\n","import { cn } from '../lib/cn';\nimport { Mark } from './mark';\n\nexport interface IsoCheckProps {\n /** Width in px; the check keeps its 2:1 grid. */\n size?: number;\n /** Drop it into place on first paint, the deck's closing gesture. */\n settle?: boolean;\n /** A soft floor shadow, so it reads as an object on the canvas. */\n shadow?: boolean;\n title?: string;\n className?: string;\n}\n\n/** The isometric check as an object: the deck's closing illustration. */\nexport function IsoCheck({ size = 360, settle = false, shadow = true, title, className }: IsoCheckProps) {\n return (\n <div className={cn('relative inline-block', settle && 'oca-animate-settle', className)} style={{ width: size }}>\n <Mark size={size} title={title} className=\"relative z-10 block\" />\n {shadow ? (\n <div\n aria-hidden\n className=\"absolute left-[8%] right-[8%] rounded-[50%] opacity-60 blur-xl\"\n style={{ height: size * 0.08, bottom: -size * 0.04, background: 'rgb(234 88 11 / 0.18)' }}\n />\n ) : null}\n </div>\n );\n}\n"]}
@@ -0,0 +1,64 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface NavItem {
5
+ href: string;
6
+ label: ReactNode;
7
+ active?: boolean;
8
+ external?: boolean;
9
+ }
10
+ declare function SiteHeader({ home, brand, nav, actions, className, }: {
11
+ home?: string;
12
+ brand?: ReactNode;
13
+ nav?: NavItem[];
14
+ actions?: ReactNode;
15
+ className?: string;
16
+ }): react.JSX.Element;
17
+ interface FooterColumn {
18
+ heading: string;
19
+ links: NavItem[];
20
+ }
21
+ declare function SiteFooter({ columns, fineprint, meta, className, }: {
22
+ columns?: FooterColumn[];
23
+ fineprint?: ReactNode;
24
+ meta?: ReactNode;
25
+ className?: string;
26
+ }): react.JSX.Element;
27
+
28
+ interface ShellNavItem {
29
+ href: string;
30
+ label: string;
31
+ icon?: ReactNode;
32
+ active?: boolean;
33
+ badge?: ReactNode;
34
+ }
35
+ interface ShellNavGroup {
36
+ label?: string;
37
+ items: ShellNavItem[];
38
+ }
39
+ declare function AppShell({ nav, home, account, banner, footer, children, className, }: {
40
+ nav: ShellNavGroup[];
41
+ home?: string;
42
+ account?: ReactNode;
43
+ banner?: ReactNode;
44
+ footer?: ReactNode;
45
+ children: ReactNode;
46
+ className?: string;
47
+ }): react.JSX.Element;
48
+ interface AccountMenuItem {
49
+ label: string;
50
+ href?: string;
51
+ onSelect?: () => void;
52
+ icon?: ReactNode;
53
+ destructive?: boolean;
54
+ }
55
+ declare function AccountMenu({ name, detail, items, }: {
56
+ name: string;
57
+ detail?: string;
58
+ items: (AccountMenuItem | 'separator')[];
59
+ }): react.JSX.Element;
60
+ declare function ThemeToggle({ className }: {
61
+ className?: string;
62
+ }): react.JSX.Element;
63
+
64
+ export { AccountMenu, type AccountMenuItem, AppShell, type FooterColumn, type NavItem, type ShellNavGroup, type ShellNavItem, SiteFooter, SiteHeader, ThemeToggle };
@@ -0,0 +1,64 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface NavItem {
5
+ href: string;
6
+ label: ReactNode;
7
+ active?: boolean;
8
+ external?: boolean;
9
+ }
10
+ declare function SiteHeader({ home, brand, nav, actions, className, }: {
11
+ home?: string;
12
+ brand?: ReactNode;
13
+ nav?: NavItem[];
14
+ actions?: ReactNode;
15
+ className?: string;
16
+ }): react.JSX.Element;
17
+ interface FooterColumn {
18
+ heading: string;
19
+ links: NavItem[];
20
+ }
21
+ declare function SiteFooter({ columns, fineprint, meta, className, }: {
22
+ columns?: FooterColumn[];
23
+ fineprint?: ReactNode;
24
+ meta?: ReactNode;
25
+ className?: string;
26
+ }): react.JSX.Element;
27
+
28
+ interface ShellNavItem {
29
+ href: string;
30
+ label: string;
31
+ icon?: ReactNode;
32
+ active?: boolean;
33
+ badge?: ReactNode;
34
+ }
35
+ interface ShellNavGroup {
36
+ label?: string;
37
+ items: ShellNavItem[];
38
+ }
39
+ declare function AppShell({ nav, home, account, banner, footer, children, className, }: {
40
+ nav: ShellNavGroup[];
41
+ home?: string;
42
+ account?: ReactNode;
43
+ banner?: ReactNode;
44
+ footer?: ReactNode;
45
+ children: ReactNode;
46
+ className?: string;
47
+ }): react.JSX.Element;
48
+ interface AccountMenuItem {
49
+ label: string;
50
+ href?: string;
51
+ onSelect?: () => void;
52
+ icon?: ReactNode;
53
+ destructive?: boolean;
54
+ }
55
+ declare function AccountMenu({ name, detail, items, }: {
56
+ name: string;
57
+ detail?: string;
58
+ items: (AccountMenuItem | 'separator')[];
59
+ }): react.JSX.Element;
60
+ declare function ThemeToggle({ className }: {
61
+ className?: string;
62
+ }): react.JSX.Element;
63
+
64
+ export { AccountMenu, type AccountMenuItem, AppShell, type FooterColumn, type NavItem, type ShellNavGroup, type ShellNavItem, SiteFooter, SiteHeader, ThemeToggle };