@eduardoalvarez/arrecife 0.5.0 → 0.6.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.
- package/CHANGELOG.md +52 -0
- package/README.md +706 -470
- package/dist/brand/index.cjs +112 -95
- package/dist/brand/index.d.cts +40 -39
- package/dist/brand/index.d.ts +40 -39
- package/dist/brand/index.js +5 -4
- package/dist/catalog-D13txprv.d.cts +78 -0
- package/dist/catalog-D13txprv.d.ts +78 -0
- package/dist/chart/index.cjs +100 -83
- package/dist/chart/index.d.cts +66 -66
- package/dist/chart/index.d.ts +66 -66
- package/dist/chart/index.js +14 -12
- package/dist/chunk-25YNFCIF.js +141 -0
- package/dist/{chunk-YZ2SDOVZ.js → chunk-6O3KWB6P.js} +30 -30
- package/dist/chunk-CKRSQPTX.js +36 -0
- package/dist/{chunk-ZEOQKRQ7.js → chunk-DKCN7BAL.js} +1 -1
- package/dist/chunk-GCRII2KQ.js +86 -0
- package/dist/chunk-JMOOFZ3B.js +42 -0
- package/dist/chunk-O4TAH7YJ.js +276 -0
- package/dist/chunk-ODBFN44D.js +45 -0
- package/dist/{chunk-VPT32GPG.js → chunk-PMN7NR3G.js} +2 -2
- package/dist/chunk-XKYHTOUJ.js +27 -0
- package/dist/form/index.cjs +109 -92
- package/dist/form/index.d.cts +43 -42
- package/dist/form/index.d.ts +43 -42
- package/dist/form/index.js +25 -23
- package/dist/index.cjs +1068 -929
- package/dist/index.d.cts +770 -773
- package/dist/index.d.ts +770 -773
- package/dist/index.js +629 -675
- package/dist/{label-DuTvJGxD.d.ts → label-MgHFKnFy.d.cts} +3 -3
- package/dist/{label-DuTvJGxD.d.cts → label-MgHFKnFy.d.ts} +3 -3
- package/dist/og/index.cjs +130 -130
- package/dist/og/index.d.cts +93 -89
- package/dist/og/index.d.ts +93 -89
- package/dist/og/index.js +106 -106
- package/dist/shiki/index.cjs +28 -30
- package/dist/shiki/index.d.cts +4 -4
- package/dist/shiki/index.d.ts +4 -4
- package/dist/shiki/index.js +12 -12
- package/dist/theme/index.cjs +97 -0
- package/dist/theme/index.d.cts +144 -0
- package/dist/theme/index.d.ts +144 -0
- package/dist/theme/index.js +2 -0
- package/dist/tokens/index.cjs +133 -86
- package/dist/tokens/index.d.cts +246 -161
- package/dist/tokens/index.d.ts +246 -161
- package/dist/tokens/index.js +2 -2
- package/dist/tokens/theme.css +133 -98
- package/dist/variants/index.cjs +192 -0
- package/dist/variants/index.d.cts +192 -0
- package/dist/variants/index.d.ts +192 -0
- package/dist/variants/index.js +3 -0
- package/llms.txt +810 -744
- package/package.json +20 -11
- package/dist/catalogo-Du5ID-Hi.d.cts +0 -77
- package/dist/catalogo-Du5ID-Hi.d.ts +0 -77
- package/dist/chunk-E3OMP2DL.js +0 -36
- package/dist/chunk-KPZNNMV5.js +0 -83
- package/dist/chunk-NHS7ETKJ.js +0 -27
- package/dist/chunk-TSPJOM6K.js +0 -229
- package/dist/chunk-UOWIDFCB.js +0 -81
- package/dist/tema/index.cjs +0 -94
- package/dist/tema/index.d.cts +0 -110
- package/dist/tema/index.d.ts +0 -110
- package/dist/tema/index.js +0 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cn } from './chunk-
|
|
3
|
-
import { naming, tagline } from './chunk-
|
|
1
|
+
import { fins, ASSETS_PATH, poses, faces } from './chunk-CKRSQPTX.js';
|
|
2
|
+
import { cn } from './chunk-XKYHTOUJ.js';
|
|
3
|
+
import { naming, tagline } from './chunk-O4TAH7YJ.js';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
basePath =
|
|
6
|
+
function Isotype({
|
|
7
|
+
background = "dark",
|
|
8
|
+
basePath = ASSETS_PATH,
|
|
9
9
|
alt = "",
|
|
10
10
|
className,
|
|
11
11
|
...props
|
|
12
12
|
}) {
|
|
13
|
-
const
|
|
13
|
+
const file = background === "dark" ? fins.foam : fins.color;
|
|
14
14
|
return /* @__PURE__ */ jsx(
|
|
15
15
|
"img",
|
|
16
16
|
{
|
|
17
|
-
src: `${basePath}/${
|
|
17
|
+
src: `${basePath}/${file}`,
|
|
18
18
|
alt,
|
|
19
19
|
width: 147,
|
|
20
20
|
height: 111,
|
|
@@ -24,44 +24,44 @@ function Isotipo({
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
function Logo({
|
|
27
|
-
|
|
28
|
-
basePath =
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
background = "dark",
|
|
28
|
+
basePath = ASSETS_PATH,
|
|
29
|
+
isotypeOnly = false,
|
|
30
|
+
withTagline = false,
|
|
31
31
|
className,
|
|
32
32
|
...props
|
|
33
33
|
}) {
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const withFin = /* @__PURE__ */ jsx(
|
|
35
|
+
Isotype,
|
|
36
36
|
{
|
|
37
|
-
|
|
37
|
+
background,
|
|
38
38
|
basePath,
|
|
39
|
-
alt:
|
|
40
|
-
className:
|
|
39
|
+
alt: isotypeOnly ? naming.wordmark : "",
|
|
40
|
+
className: withTagline ? "h-9 w-auto" : "h-7 w-auto"
|
|
41
41
|
}
|
|
42
42
|
);
|
|
43
43
|
const wordmark = /* @__PURE__ */ jsx("span", { className: "text-ui font-display text-text-primary font-bold tracking-[-0.02em]", children: naming.wordmark });
|
|
44
|
-
if (
|
|
45
|
-
return /* @__PURE__ */ jsx("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children:
|
|
44
|
+
if (isotypeOnly) {
|
|
45
|
+
return /* @__PURE__ */ jsx("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: withFin });
|
|
46
46
|
}
|
|
47
|
-
if (
|
|
47
|
+
if (withTagline) {
|
|
48
48
|
return /* @__PURE__ */ jsxs("span", { className: cn("gap-step-sm inline-flex items-center", className), ...props, children: [
|
|
49
|
-
|
|
49
|
+
withFin,
|
|
50
50
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "bg-hairline h-8 w-px shrink-0" }),
|
|
51
51
|
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
|
|
52
52
|
wordmark,
|
|
53
|
-
/* @__PURE__ */ jsx("span", { className: "text-eyebrow font-mono text-text-muted uppercase", children: tagline.
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "text-eyebrow font-mono text-text-muted uppercase", children: tagline.short })
|
|
54
54
|
] })
|
|
55
55
|
] });
|
|
56
56
|
}
|
|
57
57
|
return /* @__PURE__ */ jsxs("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: [
|
|
58
|
-
|
|
58
|
+
withFin,
|
|
59
59
|
wordmark
|
|
60
60
|
] });
|
|
61
61
|
}
|
|
62
|
-
function
|
|
62
|
+
function Mascot({
|
|
63
63
|
pose,
|
|
64
|
-
basePath =
|
|
64
|
+
basePath = ASSETS_PATH,
|
|
65
65
|
alt = "",
|
|
66
66
|
className,
|
|
67
67
|
...props
|
|
@@ -76,9 +76,9 @@ function Mascota({
|
|
|
76
76
|
}
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
basePath =
|
|
79
|
+
function MascotFace({
|
|
80
|
+
expression,
|
|
81
|
+
basePath = ASSETS_PATH,
|
|
82
82
|
alt = "",
|
|
83
83
|
className,
|
|
84
84
|
...props
|
|
@@ -86,7 +86,7 @@ function CaraDeMascota({
|
|
|
86
86
|
return /* @__PURE__ */ jsx(
|
|
87
87
|
"img",
|
|
88
88
|
{
|
|
89
|
-
src: `${basePath}/${
|
|
89
|
+
src: `${basePath}/${faces[expression]}`,
|
|
90
90
|
alt,
|
|
91
91
|
className: cn("h-auto w-full max-w-24 select-none", className),
|
|
92
92
|
...props
|
|
@@ -94,4 +94,4 @@ function CaraDeMascota({
|
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export {
|
|
97
|
+
export { Isotype, Logo, Mascot, MascotFace };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/brand/catalog.ts
|
|
2
|
+
var faces = {
|
|
3
|
+
annoyed: "face-annoyed.png",
|
|
4
|
+
confused: "face-confused.png",
|
|
5
|
+
hearts: "face-hearts.png",
|
|
6
|
+
laughing: "face-laughing.png",
|
|
7
|
+
shades: "face-shades.png",
|
|
8
|
+
waiting: "face-waiting.png",
|
|
9
|
+
wink: "face-wink.png"
|
|
10
|
+
};
|
|
11
|
+
var poses = {
|
|
12
|
+
desk: "pose-desk.png",
|
|
13
|
+
"laptop-coffee": "pose-laptop-coffee.png",
|
|
14
|
+
peek: "pose-peek.png",
|
|
15
|
+
surf: "pose-surf.png"
|
|
16
|
+
};
|
|
17
|
+
var fins = {
|
|
18
|
+
/** Two blues. For light backgrounds. */
|
|
19
|
+
color: "fin.png",
|
|
20
|
+
/** Single-ink silhouette. For dark backgrounds. */
|
|
21
|
+
foam: "fin-foam.png"
|
|
22
|
+
};
|
|
23
|
+
var faceUsage = {
|
|
24
|
+
wink: "Forms, a friendly footnote",
|
|
25
|
+
waiting: "No results, waiting state",
|
|
26
|
+
laughing: "Success, subscription confirmed",
|
|
27
|
+
shades: "Module or course completed",
|
|
28
|
+
hearts: "Gratitude, article OG",
|
|
29
|
+
confused: "404, page not found",
|
|
30
|
+
annoyed: "Server error, build failure"
|
|
31
|
+
};
|
|
32
|
+
var ASSETS_PATH = "/brand";
|
|
33
|
+
var faceList = Object.keys(faces);
|
|
34
|
+
var poseList = Object.keys(poses);
|
|
35
|
+
|
|
36
|
+
export { ASSETS_PATH, faceList, faceUsage, faces, fins, poseList, poses };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/theme/index.ts
|
|
2
|
+
var THEME_KEY = "arrecife-theme";
|
|
3
|
+
var THEME_ATTRIBUTE = "data-theme";
|
|
4
|
+
var THEME_EVENT = "arrecife:theme";
|
|
5
|
+
var isTheme = (value) => value === "dark" || value === "light";
|
|
6
|
+
function currentTheme() {
|
|
7
|
+
if (typeof document === "undefined") return "dark";
|
|
8
|
+
const set = document.documentElement.getAttribute(THEME_ATTRIBUTE);
|
|
9
|
+
return isTheme(set) ? set : "dark";
|
|
10
|
+
}
|
|
11
|
+
function storedTheme() {
|
|
12
|
+
if (typeof localStorage === "undefined") return null;
|
|
13
|
+
try {
|
|
14
|
+
const value = localStorage.getItem(THEME_KEY);
|
|
15
|
+
return isTheme(value) ? value : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function preferredTheme({ base } = {}) {
|
|
21
|
+
const stored = storedTheme();
|
|
22
|
+
if (stored) return stored;
|
|
23
|
+
if (base) return base;
|
|
24
|
+
if (typeof matchMedia === "undefined") return "dark";
|
|
25
|
+
return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
26
|
+
}
|
|
27
|
+
function applyTheme(theme, persist = true) {
|
|
28
|
+
if (typeof document === "undefined") return;
|
|
29
|
+
document.documentElement.setAttribute(THEME_ATTRIBUTE, theme);
|
|
30
|
+
document.documentElement.style.colorScheme = theme;
|
|
31
|
+
if (persist) {
|
|
32
|
+
try {
|
|
33
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
dispatchEvent(new CustomEvent(THEME_EVENT, { detail: theme }));
|
|
38
|
+
}
|
|
39
|
+
function toggleTheme() {
|
|
40
|
+
const next = currentTheme() === "dark" ? "light" : "dark";
|
|
41
|
+
applyTheme(next);
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
function watchTheme(onChange, options = {}) {
|
|
45
|
+
if (typeof window === "undefined") return () => {
|
|
46
|
+
};
|
|
47
|
+
const own = (event) => {
|
|
48
|
+
const detail = event.detail;
|
|
49
|
+
if (isTheme(detail)) onChange(detail);
|
|
50
|
+
};
|
|
51
|
+
const otherTab = (event) => {
|
|
52
|
+
if (event.key !== THEME_KEY) return;
|
|
53
|
+
const theme = isTheme(event.newValue) ? event.newValue : preferredTheme(options);
|
|
54
|
+
applyTheme(theme, false);
|
|
55
|
+
onChange(theme);
|
|
56
|
+
};
|
|
57
|
+
addEventListener(THEME_EVENT, own);
|
|
58
|
+
addEventListener("storage", otherTab);
|
|
59
|
+
return () => {
|
|
60
|
+
removeEventListener(THEME_EVENT, own);
|
|
61
|
+
removeEventListener("storage", otherTab);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function themeScript({ base } = {}) {
|
|
65
|
+
return [
|
|
66
|
+
"(function () {",
|
|
67
|
+
" var KEY = " + JSON.stringify(THEME_KEY) + ";",
|
|
68
|
+
" function resolve() {",
|
|
69
|
+
" try {",
|
|
70
|
+
" var stored = localStorage.getItem(KEY);",
|
|
71
|
+
' if (stored === "dark" || stored === "light") return stored;',
|
|
72
|
+
" } catch (e) {}",
|
|
73
|
+
base ? " return " + JSON.stringify(base) + ";" : ' return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";',
|
|
74
|
+
" }",
|
|
75
|
+
" function apply() {",
|
|
76
|
+
" var theme = resolve();",
|
|
77
|
+
" document.documentElement.setAttribute(" + JSON.stringify(THEME_ATTRIBUTE) + ", theme);",
|
|
78
|
+
" document.documentElement.style.colorScheme = theme;",
|
|
79
|
+
" }",
|
|
80
|
+
" apply();",
|
|
81
|
+
' document.addEventListener("astro:after-swap", apply);',
|
|
82
|
+
"})();"
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { THEME_ATTRIBUTE, THEME_EVENT, THEME_KEY, applyTheme, currentTheme, preferredTheme, storedTheme, themeScript, toggleTheme, watchTheme };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { text } from './chunk-ODBFN44D.js';
|
|
2
|
+
import { cn } from './chunk-XKYHTOUJ.js';
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
|
+
import 'class-variance-authority';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var LABEL = {
|
|
8
|
+
display: "h1",
|
|
9
|
+
stat: "p",
|
|
10
|
+
h1: "h1",
|
|
11
|
+
h2: "h2",
|
|
12
|
+
h3: "h3",
|
|
13
|
+
body: "p",
|
|
14
|
+
lead: "p",
|
|
15
|
+
ui: "p",
|
|
16
|
+
label: "span",
|
|
17
|
+
tag: "span",
|
|
18
|
+
meta: "p",
|
|
19
|
+
chip: "span",
|
|
20
|
+
eyebrow: "p"
|
|
21
|
+
};
|
|
22
|
+
function Text({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
tone,
|
|
26
|
+
as,
|
|
27
|
+
asChild = false,
|
|
28
|
+
measure,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}) {
|
|
32
|
+
const scale = variant ?? "body";
|
|
33
|
+
const clamp = measure ?? scale === "body";
|
|
34
|
+
const classes = cn(text({ variant, tone }), clamp && "max-w-measure", className);
|
|
35
|
+
if (asChild) {
|
|
36
|
+
return /* @__PURE__ */ jsx(Slot, { className: classes, ...props, children });
|
|
37
|
+
}
|
|
38
|
+
const Label = as ?? LABEL[scale];
|
|
39
|
+
return /* @__PURE__ */ jsx(Label, { className: classes, ...props, children });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { Text };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// src/tokens/tokens.ts
|
|
2
|
+
var dark = {
|
|
3
|
+
background: "#091319",
|
|
4
|
+
// abyss · page background
|
|
5
|
+
surface: "#10202B",
|
|
6
|
+
// trench · cards and panels
|
|
7
|
+
surfaceRaised: "#17303E",
|
|
8
|
+
// current · menus, active tabs
|
|
9
|
+
border: "#22414F",
|
|
10
|
+
// control borders
|
|
11
|
+
hairline: "#1E3441",
|
|
12
|
+
// subtle dividers
|
|
13
|
+
hairlineHover: "#2C4D5D",
|
|
14
|
+
// hairline on card hover
|
|
15
|
+
textPrimary: "#EDF4F3",
|
|
16
|
+
// foam 16.84:1
|
|
17
|
+
textSecondary: "#A7BCC4",
|
|
18
|
+
// haze 9.50:1
|
|
19
|
+
textMuted: "#71919C",
|
|
20
|
+
// plankton 5.57:1 never under 13px
|
|
21
|
+
accent: "#35D6C0",
|
|
22
|
+
// biolume 10.31:1 interactive
|
|
23
|
+
accentHover: "#5FE3D1",
|
|
24
|
+
accentOn: "#06171A",
|
|
25
|
+
// ink over biolume
|
|
26
|
+
warm: "#F2A65A",
|
|
27
|
+
// sand 9.28:1 human and conversion
|
|
28
|
+
warmHover: "#F7BB7D",
|
|
29
|
+
warmOn: "#2A1605",
|
|
30
|
+
// ink over sand
|
|
31
|
+
success: "#4FB477",
|
|
32
|
+
warning: "#E8A33D",
|
|
33
|
+
error: "#E15757",
|
|
34
|
+
// 4.51:1 over surface
|
|
35
|
+
/**
|
|
36
|
+
* The destructive fill. It is NOT `error`, and the difference is the role.
|
|
37
|
+
*
|
|
38
|
+
* `error` is a TEXT color: it has to read against a dark surface, so it sits
|
|
39
|
+
* mid-red. `danger` is a FILL: what has to read is the ink ON TOP of it, so it
|
|
40
|
+
* goes lighter. Same reasoning as `accent` and `accentOn` — one is measured
|
|
41
|
+
* against the page, the other against what it carries.
|
|
42
|
+
*
|
|
43
|
+
* Measured: ink 6.53:1 over the fill and 7.92:1 over the hover; the fill
|
|
44
|
+
* itself 6.71:1 over abyss, 5.94 over surface and 4.91 over surfaceRaised, so
|
|
45
|
+
* the outline variant reads as text on all three.
|
|
46
|
+
*/
|
|
47
|
+
danger: "#F4736B",
|
|
48
|
+
dangerHover: "#F78D86",
|
|
49
|
+
dangerOn: "#2B0A08"
|
|
50
|
+
// ink over danger 6.53:1
|
|
51
|
+
};
|
|
52
|
+
var light = {
|
|
53
|
+
background: "#F6F2EA",
|
|
54
|
+
// paper
|
|
55
|
+
surface: "#FFFFFF",
|
|
56
|
+
surfaceRaised: "#EFE9DE",
|
|
57
|
+
border: "#E6DFD2",
|
|
58
|
+
hairline: "#EBE6DC",
|
|
59
|
+
hairlineHover: "#D3C8B2",
|
|
60
|
+
// hairline on card hover
|
|
61
|
+
textPrimary: "#0B1524",
|
|
62
|
+
// 16.40:1
|
|
63
|
+
textSecondary: "#3D4B58",
|
|
64
|
+
textMuted: "#626A75",
|
|
65
|
+
// 4.53:1 over surfaceRaised
|
|
66
|
+
accent: "#0D7C6F",
|
|
67
|
+
// dark biolume 4.55:1
|
|
68
|
+
accentHover: "#0C7466",
|
|
69
|
+
accentOn: "#FFFFFF",
|
|
70
|
+
warm: "#A65B27",
|
|
71
|
+
// dark sand 4.54:1
|
|
72
|
+
warmHover: "#96511F",
|
|
73
|
+
warmOn: "#FFF7EE",
|
|
74
|
+
success: "#0F6B52",
|
|
75
|
+
warning: "#8D6111",
|
|
76
|
+
// 4.51:1 over surfaceRaised
|
|
77
|
+
error: "#C0392B",
|
|
78
|
+
/**
|
|
79
|
+
* In light mode `danger` and `error` land on the same hex, and that is not an
|
|
80
|
+
* oversight: over paper a red dark enough to carry white ink is also the red
|
|
81
|
+
* that reads as text. In dark they part company because the fill has to be
|
|
82
|
+
* lighter than the text color, not darker.
|
|
83
|
+
*
|
|
84
|
+
* Measured: ink 5.11:1 over the fill and 6.61:1 over the hover; the fill
|
|
85
|
+
* itself 4.87:1 over paper, 5.44 over surface and 4.50 over surfaceRaised —
|
|
86
|
+
* the last one is exactly on the AA line, which is where every light semantic
|
|
87
|
+
* in this palette sits.
|
|
88
|
+
*/
|
|
89
|
+
danger: "#C0392B",
|
|
90
|
+
dangerHover: "#A32F22",
|
|
91
|
+
dangerOn: "#FFF6F4"
|
|
92
|
+
// 5.11:1 over danger
|
|
93
|
+
};
|
|
94
|
+
var colors = { dark, light };
|
|
95
|
+
var brand = {
|
|
96
|
+
/** The mascot's body. 4.22:1 → FILL ONLY, NEVER text. */
|
|
97
|
+
body: "#3E7CB1",
|
|
98
|
+
/** Spot pattern. */
|
|
99
|
+
spots: "#C2D7E7",
|
|
100
|
+
/** Hull · outline, and the background of code blocks. */
|
|
101
|
+
hull: "#0B1524"
|
|
102
|
+
};
|
|
103
|
+
var fonts = {
|
|
104
|
+
/** Headlines and large numbers ONLY. Never body copy. */
|
|
105
|
+
display: '"Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif',
|
|
106
|
+
/** Body and interface. */
|
|
107
|
+
sans: '"Geist", ui-sans-serif, system-ui, sans-serif',
|
|
108
|
+
/** Code, paths, labels, metadata, the CLI signature. */
|
|
109
|
+
mono: '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace'
|
|
110
|
+
};
|
|
111
|
+
var typeScale = {
|
|
112
|
+
display: { family: "display", size: 76, lineHeight: 0.96, weight: 800, tracking: "-0.035em" },
|
|
113
|
+
/** Large metrics. Numbers, not prose: line height 1 and no descenders. */
|
|
114
|
+
stat: { family: "display", size: 46, lineHeight: 1, weight: 800, tracking: "-0.035em" },
|
|
115
|
+
h1: { family: "display", size: 44, lineHeight: 1.05, weight: 700, tracking: "-0.03em" },
|
|
116
|
+
h2: { family: "display", size: 30, lineHeight: 1.1, weight: 600, tracking: "-0.02em" },
|
|
117
|
+
h3: { family: "display", size: 25, lineHeight: 1.15, weight: 600, tracking: "-0.02em" },
|
|
118
|
+
body: { family: "sans", size: 18, lineHeight: 1.75, weight: 400 },
|
|
119
|
+
/**
|
|
120
|
+
* 17px. The large button, and the deck on interior pages.
|
|
121
|
+
*
|
|
122
|
+
* It earns its own step because the identity document uses it TWICE, in two
|
|
123
|
+
* different pieces: «lg 15/30 r12» with 17px text, and «h1 44/700 · deck 17px
|
|
124
|
+
* · context paragraph 15px». One use would not have justified splitting the
|
|
125
|
+
* gap between `ui` (15) and `body` (18); two did.
|
|
126
|
+
*/
|
|
127
|
+
lead: { family: "sans", size: 17, lineHeight: 1.5, weight: 400 },
|
|
128
|
+
ui: { family: "sans", size: 15, lineHeight: 1.6, weight: 400 },
|
|
129
|
+
label: { family: "sans", size: 13, lineHeight: 1.5, weight: 500 },
|
|
130
|
+
/**
|
|
131
|
+
* The status-badge scale: sans 12.5/500, squared off.
|
|
132
|
+
*
|
|
133
|
+
* That is half a pixel below `label`, and this time the half pixel matters: a
|
|
134
|
+
* status badge sits inside a table or next to a title, and at 13 it competed
|
|
135
|
+
* with the text it accompanies. The document specifies 12.5 and it shows.
|
|
136
|
+
*/
|
|
137
|
+
tag: { family: "sans", size: 12.5, lineHeight: 1.4, weight: 500 },
|
|
138
|
+
/**
|
|
139
|
+
* The category and metric scale: mono 11.5.
|
|
140
|
+
*
|
|
141
|
+
* It breaks the floor set by `limits.minScreenPx`, deliberately. That floor
|
|
142
|
+
* protects TEXT — what you read in a sentence — and a one-word pill is not
|
|
143
|
+
* running text: it is a mark. Contrast is still measured and still passes AA
|
|
144
|
+
* (plankton 5.57:1 over abyss), which is the part that is not negotiable.
|
|
145
|
+
*
|
|
146
|
+
* At 13 the three badge families grew past the size of a small button and
|
|
147
|
+
* outweighed the title they accompany. See `docs/decisions.md`.
|
|
148
|
+
*/
|
|
149
|
+
chip: { family: "mono", size: 11.5, lineHeight: 1.4, weight: 400 },
|
|
150
|
+
/**
|
|
151
|
+
* Mono with NO transform: dates, paths, versions, file names, the footer
|
|
152
|
+
* signature and metric labels. It is most of the mono in the system.
|
|
153
|
+
*
|
|
154
|
+
* It exists because `eyebrow` ships `uppercase` and that is not a preference
|
|
155
|
+
* you can switch off: it IS the eyebrow scale. An `18 Aug 2026` in small caps
|
|
156
|
+
* or a `pose-laptop-coffee.png` in all caps are wrong, and without this step
|
|
157
|
+
* the only way out was a `normal-case` at every call site.
|
|
158
|
+
*
|
|
159
|
+
* The document says 12.5. This is 13 for the same reason `textMuted` was
|
|
160
|
+
* corrected: `limits.minScreenPx` is 13 and plankton is «never under 13px».
|
|
161
|
+
* This scale is exactly where muted metadata gets written, so putting it at
|
|
162
|
+
* 12.5 would have made its most common use illegible by half a pixel.
|
|
163
|
+
*/
|
|
164
|
+
meta: { family: "mono", size: 13, lineHeight: 1.6, weight: 400 },
|
|
165
|
+
eyebrow: { family: "mono", size: 12, tracking: "0.12em", transform: "uppercase" }
|
|
166
|
+
};
|
|
167
|
+
var limits = {
|
|
168
|
+
/** Absolute minimum on screen. */
|
|
169
|
+
minScreenPx: 13,
|
|
170
|
+
/** Absolute minimum in print. */
|
|
171
|
+
minPrintPt: 12,
|
|
172
|
+
/** Maximum body measure. */
|
|
173
|
+
measure: "68ch"
|
|
174
|
+
};
|
|
175
|
+
var radius = {
|
|
176
|
+
chip: 6,
|
|
177
|
+
control: 10,
|
|
178
|
+
card: 14,
|
|
179
|
+
panel: 16,
|
|
180
|
+
pill: 999
|
|
181
|
+
};
|
|
182
|
+
var control = {
|
|
183
|
+
sm: 14,
|
|
184
|
+
md: 22,
|
|
185
|
+
lg: 30,
|
|
186
|
+
/** Icon button: square, no text. */
|
|
187
|
+
icon: 42,
|
|
188
|
+
/**
|
|
189
|
+
* The dense icon button, 32×32.
|
|
190
|
+
*
|
|
191
|
+
* 42 is the right measure for a control you hit with a thumb, and the four
|
|
192
|
+
* reading sites are where that fits. `cursos` is the odd one out: it is the
|
|
193
|
+
* only admin app of the set, with three actions per table row, and at 42 the
|
|
194
|
+
* row grows with them — the original used 24 and 28.
|
|
195
|
+
*
|
|
196
|
+
* It is 32 and not 28 because 32 is `sm`'s height. A dense icon button lines
|
|
197
|
+
* up with a small text button, so a toolbar mixing the two stays on one
|
|
198
|
+
* baseline; 28 would have been a fifth height that matches nothing.
|
|
199
|
+
*
|
|
200
|
+
* It does not replace `icon`. A page's primary action stays at 42; this is for
|
|
201
|
+
* a row of a table. See `docs/decisions.md` § 22.
|
|
202
|
+
*/
|
|
203
|
+
iconSm: 32
|
|
204
|
+
};
|
|
205
|
+
var spacing = {
|
|
206
|
+
stepXs: 8,
|
|
207
|
+
stepSm: 12,
|
|
208
|
+
stepMd: 16,
|
|
209
|
+
stepLg: 26,
|
|
210
|
+
stepXl: 40,
|
|
211
|
+
section: 96
|
|
212
|
+
};
|
|
213
|
+
var size = {
|
|
214
|
+
/** Navigation bar height. */
|
|
215
|
+
nav: 64,
|
|
216
|
+
/** Reading column width. */
|
|
217
|
+
content: 760,
|
|
218
|
+
/** Maximum page width. */
|
|
219
|
+
wide: 1180
|
|
220
|
+
};
|
|
221
|
+
var syntax = {
|
|
222
|
+
/** The hull. It is the block background in both modes. */
|
|
223
|
+
background: brand.hull,
|
|
224
|
+
/** Identifiers, functions, types, variables. */
|
|
225
|
+
identifier: dark.textPrimary,
|
|
226
|
+
/** Strings, numbers, booleans, null. */
|
|
227
|
+
literal: dark.accent,
|
|
228
|
+
/** Keywords, control flow, `import`, `this`. */
|
|
229
|
+
keyword: dark.warm,
|
|
230
|
+
/** Comments and punctuation. */
|
|
231
|
+
comment: dark.textMuted,
|
|
232
|
+
/** `markup.deleted`, `invalid`. */
|
|
233
|
+
invalid: dark.error
|
|
234
|
+
};
|
|
235
|
+
var series = {
|
|
236
|
+
dark: [dark.accent, dark.warm, brand.body, dark.textMuted],
|
|
237
|
+
light: [light.accent, light.warm, brand.body, light.textMuted]
|
|
238
|
+
};
|
|
239
|
+
var deep = "#0D2129";
|
|
240
|
+
var gradient = {
|
|
241
|
+
dark: {
|
|
242
|
+
hero: `linear-gradient(160deg, ${dark.background} 60%, ${deep} 100%)`,
|
|
243
|
+
section: `linear-gradient(150deg, ${dark.surface} 0%, ${deep} 100%)`,
|
|
244
|
+
/** The one used by the article OG template. 145°, from the document. */
|
|
245
|
+
og: `linear-gradient(145deg, ${dark.background} 55%, ${deep} 100%)`
|
|
246
|
+
},
|
|
247
|
+
light: {
|
|
248
|
+
hero: `linear-gradient(160deg, ${light.background} 60%, ${light.surfaceRaised} 100%)`,
|
|
249
|
+
section: `linear-gradient(150deg, ${light.surface} 0%, ${light.surfaceRaised} 100%)`,
|
|
250
|
+
og: `linear-gradient(145deg, ${light.background} 55%, ${light.surfaceRaised} 100%)`
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
var shadow = {
|
|
254
|
+
standard: "0 1px 2px rgba(0, 0, 0, 0.35)"
|
|
255
|
+
};
|
|
256
|
+
var motion = {
|
|
257
|
+
duration: "150ms",
|
|
258
|
+
easing: "ease-out",
|
|
259
|
+
properties: "color, background-color, border-color, fill, stroke"
|
|
260
|
+
};
|
|
261
|
+
var tagline = {
|
|
262
|
+
/** Hero. */
|
|
263
|
+
long: "Ayudo a equipos de ingenier\xEDa a escalar con criterio",
|
|
264
|
+
/** Header, one line. */
|
|
265
|
+
short: "Ayudo a equipos a escalar con criterio",
|
|
266
|
+
/** LinkedIn. */
|
|
267
|
+
en: "Helping engineering teams scale with judgment"
|
|
268
|
+
};
|
|
269
|
+
var naming = {
|
|
270
|
+
wordmark: "Eduardo \xC1lvarez",
|
|
271
|
+
mascot: "Tiburonc\xEDn",
|
|
272
|
+
/** The domain, for the footer CLI signature: `$ cd ~/eduardoalvarez.dev/2026`. */
|
|
273
|
+
domain: "eduardoalvarez.dev"
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export { brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, size, spacing, syntax, tagline, typeScale };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
// src/variants/typography.ts
|
|
4
|
+
var text = cva("", {
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
display: "text-display font-display",
|
|
8
|
+
// Large metrics: 46px of display. It is the only scale designed for a
|
|
9
|
+
// number, which is why its line height is 1.
|
|
10
|
+
stat: "text-stat font-display",
|
|
11
|
+
h1: "text-h1 font-display",
|
|
12
|
+
h2: "text-h2 font-display",
|
|
13
|
+
h3: "text-h3 font-display",
|
|
14
|
+
body: "text-body font-sans",
|
|
15
|
+
// 17px: the deck on interior pages and the large button.
|
|
16
|
+
lead: "text-lead font-sans",
|
|
17
|
+
ui: "text-ui font-sans",
|
|
18
|
+
label: "text-label font-sans",
|
|
19
|
+
// The two badge scales. `tag` is status (sans 12.5/500) and `chip` is
|
|
20
|
+
// category and metric (mono 11.5). Neither transforms the text.
|
|
21
|
+
tag: "text-tag font-sans",
|
|
22
|
+
chip: "text-chip font-mono",
|
|
23
|
+
// Mono with NO transform: dates, paths, versions, file names and the
|
|
24
|
+
// footer signature. It is the step that was missing, and the reason every
|
|
25
|
+
// call site ended up writing `variant="eyebrow" className="normal-case"`.
|
|
26
|
+
meta: "text-meta font-mono",
|
|
27
|
+
// `uppercase` does not fit in a size token: text-transform is not a
|
|
28
|
+
// modifier of --text-*. Which is why the variant sets it, not theme.css.
|
|
29
|
+
eyebrow: "text-eyebrow font-mono uppercase"
|
|
30
|
+
},
|
|
31
|
+
tone: {
|
|
32
|
+
primary: "text-text-primary",
|
|
33
|
+
secondary: "text-text-secondary",
|
|
34
|
+
muted: "text-text-muted",
|
|
35
|
+
accent: "text-accent",
|
|
36
|
+
warm: "text-warm",
|
|
37
|
+
success: "text-success",
|
|
38
|
+
warning: "text-warning",
|
|
39
|
+
error: "text-error"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: { variant: "body", tone: "primary" }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export { text };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { naming, tagline, motion, shadow, series,
|
|
1
|
+
import { naming, tagline, motion, shadow, series, syntax, gradient, size, spacing, control, radius, limits, typeScale, fonts, brand, colors } from './chunk-O4TAH7YJ.js';
|
|
2
2
|
|
|
3
3
|
// src/tokens/index.ts
|
|
4
4
|
var tokens = {
|
|
@@ -12,7 +12,7 @@ var tokens = {
|
|
|
12
12
|
spacing,
|
|
13
13
|
size,
|
|
14
14
|
gradient,
|
|
15
|
-
|
|
15
|
+
syntax,
|
|
16
16
|
series,
|
|
17
17
|
shadow,
|
|
18
18
|
motion,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spacing, control, radius, typeScale } from './chunk-O4TAH7YJ.js';
|
|
2
|
+
import { clsx } from 'clsx';
|
|
3
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
var kebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
6
|
+
var scales = Object.keys(typeScale);
|
|
7
|
+
var radii = Object.keys(radius);
|
|
8
|
+
var namespaces = Object.keys(spacing).map(kebab);
|
|
9
|
+
var controls = Object.keys(control).map((name) => `control-${kebab(name)}`);
|
|
10
|
+
var twMerge = extendTailwindMerge({
|
|
11
|
+
extend: {
|
|
12
|
+
classGroups: {
|
|
13
|
+
"font-size": [{ text: scales }],
|
|
14
|
+
rounded: [{ rounded: radii }],
|
|
15
|
+
p: [{ p: namespaces }],
|
|
16
|
+
px: [{ px: [...namespaces, ...controls] }],
|
|
17
|
+
py: [{ py: namespaces }],
|
|
18
|
+
gap: [{ gap: namespaces }],
|
|
19
|
+
size: [{ size: controls }]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
function cn(...inputs) {
|
|
24
|
+
return twMerge(clsx(inputs));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { cn };
|