@nadicodeai/ui 12.1.52 → 12.1.53
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/LICENSES/VGPU-MIT.txt +21 -0
- package/NOTICE +12 -0
- package/dist/components/agent-card.d.ts.map +1 -1
- package/dist/components/agent-card.js +1 -1
- package/dist/components/agent-list-row.d.ts +28 -0
- package/dist/components/agent-list-row.d.ts.map +1 -0
- package/dist/components/agent-list-row.js +17 -0
- package/dist/components/approval-row.d.ts +21 -0
- package/dist/components/approval-row.d.ts.map +1 -0
- package/dist/components/approval-row.js +14 -0
- package/dist/components/channel-view.d.ts +89 -0
- package/dist/components/channel-view.d.ts.map +1 -0
- package/dist/components/channel-view.js +75 -0
- package/dist/components/company-card/approach.d.ts +22 -0
- package/dist/components/company-card/approach.d.ts.map +1 -0
- package/dist/components/company-card/approach.js +17 -0
- package/dist/components/company-card/engraving.d.ts +8 -0
- package/dist/components/company-card/engraving.d.ts.map +1 -0
- package/dist/components/company-card/engraving.js +56 -0
- package/dist/components/company-card/foil-colors.d.ts +4 -0
- package/dist/components/company-card/foil-colors.d.ts.map +1 -0
- package/dist/components/company-card/foil-colors.js +29 -0
- package/dist/components/company-card/renderer.d.ts +13 -0
- package/dist/components/company-card/renderer.d.ts.map +1 -0
- package/dist/components/company-card/renderer.js +138 -0
- package/dist/components/company-card/scene.d.ts +10 -0
- package/dist/components/company-card/scene.d.ts.map +1 -0
- package/dist/components/company-card/scene.js +25 -0
- package/dist/components/company-card/shader.d.ts +10 -0
- package/dist/components/company-card/shader.d.ts.map +1 -0
- package/dist/components/company-card/shader.js +165 -0
- package/dist/components/company-card/snapshot.d.ts +7 -0
- package/dist/components/company-card/snapshot.d.ts.map +1 -0
- package/dist/components/company-card/snapshot.js +37 -0
- package/dist/components/company-card.d.ts +31 -0
- package/dist/components/company-card.d.ts.map +1 -0
- package/dist/components/company-card.js +200 -0
- package/dist/components/desktop-shell.js +1 -1
- package/dist/components/entry-scene.d.ts +90 -0
- package/dist/components/entry-scene.d.ts.map +1 -0
- package/dist/components/entry-scene.js +41 -0
- package/dist/components/nadia-presence.d.ts +22 -0
- package/dist/components/nadia-presence.d.ts.map +1 -0
- package/dist/components/nadia-presence.js +45 -0
- package/dist/components/portal-sidebar-frame.d.ts +40 -0
- package/dist/components/portal-sidebar-frame.d.ts.map +1 -0
- package/dist/components/portal-sidebar-frame.js +14 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/styles/components.css +567 -0
- package/package.json +3 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// nadicode: vgpu's holographic card renderer (MIT, https://github.com/vercel-labs/vgpu,
|
|
2
|
+
// examples/holographic-card, revision 6fa27bb4), with its WebGPU set-up, frame
|
|
3
|
+
// loop and cleanup as they are. The pointer is read on the whole page, as the
|
|
4
|
+
// example reads it on its whole stage, so the foil wakes as the pointer comes
|
|
5
|
+
// within one card width; the turn it computes is handed to the page, which
|
|
6
|
+
// turns the card and its words together.
|
|
7
|
+
import { clock, frameLoop, init, surface } from "vgpu";
|
|
8
|
+
import { approach, SWEEP_MS, SWEEP_TURN } from "./approach.js";
|
|
9
|
+
import { createScene } from "./scene.js";
|
|
10
|
+
export function createRenderer(canvas, options) {
|
|
11
|
+
const { colors, onTilt } = options;
|
|
12
|
+
let disposed = false;
|
|
13
|
+
let gpu;
|
|
14
|
+
let removeInput = () => { };
|
|
15
|
+
let requestSweep = () => { };
|
|
16
|
+
const dispose = () => {
|
|
17
|
+
if (disposed)
|
|
18
|
+
return;
|
|
19
|
+
disposed = true;
|
|
20
|
+
removeInput();
|
|
21
|
+
gpu?.dispose();
|
|
22
|
+
};
|
|
23
|
+
const ready = (async () => {
|
|
24
|
+
const context = await init();
|
|
25
|
+
if (disposed) {
|
|
26
|
+
context.dispose();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
gpu = context;
|
|
30
|
+
const output = surface(context, canvas, { dpr: [1, 2] });
|
|
31
|
+
const shader = createScene(context, output, colors);
|
|
32
|
+
await shader.compile({ colors: [output.format] });
|
|
33
|
+
if (disposed)
|
|
34
|
+
return;
|
|
35
|
+
const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
36
|
+
let targetHover = 0;
|
|
37
|
+
let pointerX = 0;
|
|
38
|
+
let pointerY = 0;
|
|
39
|
+
let tiltX = 0;
|
|
40
|
+
let tiltY = 0;
|
|
41
|
+
let hover = 0;
|
|
42
|
+
let lightX = 0.2;
|
|
43
|
+
let lightY = -0.25;
|
|
44
|
+
// A sweep turns the card once in the light, the way a hand turns a card to
|
|
45
|
+
// see its foil: it leans one way and the other while the light crosses it
|
|
46
|
+
// from its top-left corner to its bottom-right, then rests. It tells a
|
|
47
|
+
// person at a glance that this is a card that answers the pointer.
|
|
48
|
+
let sweepStart = null;
|
|
49
|
+
// Under reduced motion the card keeps still: the light moves only with the pointer.
|
|
50
|
+
requestSweep = () => {
|
|
51
|
+
if (!motion.matches)
|
|
52
|
+
sweepStart = performance.now();
|
|
53
|
+
};
|
|
54
|
+
const move = (event) => {
|
|
55
|
+
if (!event.isPrimary)
|
|
56
|
+
return;
|
|
57
|
+
const near = approach(canvas.getBoundingClientRect(), event.clientX, event.clientY);
|
|
58
|
+
// A pointer elsewhere on the page leaves a sweep to finish; one that
|
|
59
|
+
// comes near takes the card over.
|
|
60
|
+
if (near.hover > 0)
|
|
61
|
+
sweepStart = null;
|
|
62
|
+
if (sweepStart !== null)
|
|
63
|
+
return;
|
|
64
|
+
pointerX = near.x;
|
|
65
|
+
pointerY = near.y;
|
|
66
|
+
targetHover = near.hover;
|
|
67
|
+
};
|
|
68
|
+
const leave = () => {
|
|
69
|
+
if (sweepStart === null)
|
|
70
|
+
targetHover = 0;
|
|
71
|
+
};
|
|
72
|
+
const up = (event) => {
|
|
73
|
+
if (event.pointerType !== "mouse")
|
|
74
|
+
leave();
|
|
75
|
+
};
|
|
76
|
+
const root = document.documentElement;
|
|
77
|
+
window.addEventListener("pointermove", move, { passive: true });
|
|
78
|
+
window.addEventListener("pointerdown", move, { passive: true });
|
|
79
|
+
window.addEventListener("pointercancel", leave);
|
|
80
|
+
window.addEventListener("pointerup", up);
|
|
81
|
+
root.addEventListener("pointerleave", leave);
|
|
82
|
+
const unsubscribeResize = output.onResize(() => {
|
|
83
|
+
shader.set({ params: { resolution: output.size } });
|
|
84
|
+
});
|
|
85
|
+
removeInput = () => {
|
|
86
|
+
window.removeEventListener("pointermove", move);
|
|
87
|
+
window.removeEventListener("pointerdown", move);
|
|
88
|
+
window.removeEventListener("pointercancel", leave);
|
|
89
|
+
window.removeEventListener("pointerup", up);
|
|
90
|
+
root.removeEventListener("pointerleave", leave);
|
|
91
|
+
unsubscribeResize();
|
|
92
|
+
};
|
|
93
|
+
const time = clock(context);
|
|
94
|
+
frameLoop(context, (currentFrame) => {
|
|
95
|
+
let turn = null;
|
|
96
|
+
if (sweepStart !== null) {
|
|
97
|
+
const progress = (performance.now() - sweepStart) / SWEEP_MS;
|
|
98
|
+
if (progress >= 1) {
|
|
99
|
+
sweepStart = null;
|
|
100
|
+
targetHover = 0;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const along = progress * progress * (3 - 2 * progress);
|
|
104
|
+
pointerX = -1.1 + 2.2 * along;
|
|
105
|
+
pointerY = -0.55 + 1.1 * along;
|
|
106
|
+
targetHover = Math.sin(Math.PI * progress) ** 0.6 * 0.95;
|
|
107
|
+
turn = [
|
|
108
|
+
-Math.sin(2 * Math.PI * along) * SWEEP_TURN,
|
|
109
|
+
Math.sin(Math.PI * progress) * SWEEP_TURN * 0.3,
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const targetX = motion.matches
|
|
114
|
+
? 0
|
|
115
|
+
: (turn?.[0] ?? Math.max(-1, Math.min(1, pointerX)) * 0.16 * targetHover);
|
|
116
|
+
const targetY = motion.matches
|
|
117
|
+
? 0
|
|
118
|
+
: (turn?.[1] ?? -Math.max(-1, Math.min(1, pointerY)) * 0.12 * targetHover);
|
|
119
|
+
const blend = motion.matches ? 1 : 1 - Math.exp(-10 * Math.min(time.deltaTime, 0.1));
|
|
120
|
+
tiltX += (targetX - tiltX) * blend;
|
|
121
|
+
tiltY += (targetY - tiltY) * blend;
|
|
122
|
+
hover += (targetHover - hover) * blend;
|
|
123
|
+
if (targetHover > 0) {
|
|
124
|
+
lightX += (pointerX - lightX) * blend;
|
|
125
|
+
lightY += (pointerY - lightY) * blend;
|
|
126
|
+
}
|
|
127
|
+
onTilt(tiltX, tiltY);
|
|
128
|
+
shader.set({ params: { tilt: [tiltX, tiltY], pointer: [lightX, lightY], hover } });
|
|
129
|
+
currentFrame.pass(output, shader);
|
|
130
|
+
}, { fps: 60 });
|
|
131
|
+
})().catch((error) => {
|
|
132
|
+
if (disposed)
|
|
133
|
+
return;
|
|
134
|
+
dispose();
|
|
135
|
+
throw error;
|
|
136
|
+
});
|
|
137
|
+
return { ready, dispose, sweep: () => requestSweep() };
|
|
138
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Gpu, type Target } from "vgpu";
|
|
2
|
+
/** The design-system colours the foil is struck in, as sRGB channels from 0 to 1. */
|
|
3
|
+
export type FoilColors = {
|
|
4
|
+
graphite: readonly [number, number, number];
|
|
5
|
+
ink: readonly [number, number, number];
|
|
6
|
+
/** Six hues in spectral order: the diffraction and the pearl run through them. */
|
|
7
|
+
hues: readonly (readonly [number, number, number])[];
|
|
8
|
+
};
|
|
9
|
+
export declare function createScene(gpu: Gpu, output: Target, colors: FoilColors): import("vgpu").Effect;
|
|
10
|
+
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../../../src/components/company-card/scene.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAGrD,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,kFAAkF;IAClF,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;CACtD,CAAC;AAEF,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,yBAsBvE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { effect } from "vgpu";
|
|
2
|
+
import { CARD_FOIL_SHADER } from "./shader.js";
|
|
3
|
+
export function createScene(gpu, output, colors) {
|
|
4
|
+
const [hue0, hue1, hue2, hue3, hue4, hue5] = colors.hues;
|
|
5
|
+
const shader = effect(gpu, CARD_FOIL_SHADER, {
|
|
6
|
+
label: "holographic-card",
|
|
7
|
+
set: {
|
|
8
|
+
params: {
|
|
9
|
+
resolution: output.size,
|
|
10
|
+
tilt: [0, 0],
|
|
11
|
+
pointer: [0.2, -0.25],
|
|
12
|
+
hover: 0,
|
|
13
|
+
graphite: colors.graphite,
|
|
14
|
+
ink: colors.ink,
|
|
15
|
+
hue0,
|
|
16
|
+
hue1,
|
|
17
|
+
hue2,
|
|
18
|
+
hue3,
|
|
19
|
+
hue4,
|
|
20
|
+
hue5,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return shader;
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The holographic foil's fragment shader, as a string.
|
|
3
|
+
*
|
|
4
|
+
* This package builds with tsc alone, so a `.wgsl` file would neither be
|
|
5
|
+
* emitted into `dist` nor compiled by a consumer's bundler. vgpu's `effect`
|
|
6
|
+
* takes WGSL source, so the shader travels as the module below and needs no
|
|
7
|
+
* loader in any host.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CARD_FOIL_SHADER = "\n// nadicode: vgpu's holographic card (MIT, https://github.com/vercel-labs/vgpu,\n// examples/holographic-card, revision 6fa27bb4). Its light model is unchanged.\n// The surface is the company card itself, 85.6 x 54 mm, which the page tilts;\n// vgpu's triangle, fractal and lettering give way to the card's own marks; and\n// every colour is a design-system token: the spectrum and the pearl fall in\n// pure bands of the identity hues, never a colour between two of them.\nstruct Params {\n resolution: vec2f,\n tilt: vec2f,\n pointer: vec2f,\n hover: f32,\n graphite: vec3f,\n ink: vec3f,\n hue0: vec3f,\n hue1: vec3f,\n hue2: vec3f,\n hue3: vec3f,\n hue4: vec3f,\n hue5: vec3f,\n}\n@group(0) @binding(0) var<uniform> params: Params;\n\nconst HALF = vec2f(0.856, 0.54);\nconst ROSETTE = vec2f(0.448, -0.016);\n\nfn stroke(distance: f32, width: f32, aa: f32) -> f32 {\n return 1.0 - smoothstep(width, width + aa, abs(distance));\n}\n\n// The identity hues in spectral order, each a band of its own: a blend of two\n// neighbours is a colour the palette does not have.\nfn palette(t: f32) -> vec3f {\n let hues = array<vec3f, 6>(params.hue0, params.hue1, params.hue2, params.hue3, params.hue4, params.hue5);\n return hues[min(u32(clamp(t, 0.0, 1.0) * 6.0), 5u)];\n}\n\n// vgpu's visible-light window, drawn with the identity band instead of display RGB.\nfn wavelengthColor(wavelength: f32) -> vec3f {\n let visible = smoothstep(0.380, 0.410, wavelength) * (1.0 - smoothstep(0.700, 0.780, wavelength));\n return palette((wavelength - 0.44) / 0.22) * visible * 0.8;\n}\n\n// Reflection grating approximation: m * wavelength = d * dot(L + V, across).\n// L and V point away from the surface; across is perpendicular to the grooves.\n// Based on the diffraction-order model in GPU Gems, chapter 8 (Jos Stam).\nfn diffraction(across: vec2f, lightAndView: vec2f, spacing: f32) -> vec3f {\n let pathDifference = spacing * abs(dot(lightAndView, across));\n let along = dot(lightAndView, vec2f(-across.y, across.x));\n // Finite, imperfect groove patches broaden the directional reflection.\n let envelope = exp(-along * along / 0.36);\n var reflected = vec3f(0);\n for (var order = 1; order <= 3; order++) {\n let m = f32(order);\n reflected += wavelengthColor(pathDifference / m) / (m * m);\n }\n return reflected * envelope;\n}\n\n// Broad pearlescence underneath the finer diffraction detail, in the same hues.\nfn pearlColor(phase: f32) -> vec3f {\n let cycle = abs(fract(phase * 0.5) * 2.0 - 1.0);\n return mix(palette(cycle), vec3f(0.82), 0.35);\n}\n\nfn grain(point: vec2f) -> f32 {\n let p = vec2u(abs(point) * 2400.0);\n var n = (p.x * 1597334677u) ^ (p.y * 3812015801u);\n n = (n ^ (n >> 16u)) * 2246822519u;\n return f32(n & 1023u) / 1023.0 - 0.5;\n}\n\nfn etchedPhase(p: vec2f) -> f32 {\n // Warp the surface before tracing contours, so their spacing flows in soft waves.\n let warp = vec2f(\n sin(p.y * 7.0 + sin(p.x * 4.0)) * 0.085,\n sin(p.x * 6.0 - p.y * 3.0) * 0.07\n );\n let q = p + warp - ROSETTE;\n let radius = length(q * vec2f(1.0, 0.86));\n return radius * 142.0 + sin(atan2(q.y, q.x) * 3.0 + radius * 8.0) * 1.7;\n}\n\n@fragment\nfn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n let p = (uv - 0.5) * 2.0 * HALF;\n let sx = sin(params.tilt.y);\n let cx = cos(params.tilt.y);\n let sy = sin(params.tilt.x);\n let cy = cos(params.tilt.x);\n let right = vec3f(cy, 0, -sy);\n let down = vec3f(sy * sx, cx, cy * sx);\n let normal = cross(right, down);\n let eye = vec3f(0, 0, 4.5);\n let hit = right * p.x + down * p.y;\n let aa = max(length(fwidth(p)), 0.0006);\n\n // Matte graphite remains dark; only the cursor's grazing light reveals the foil.\n let hover = clamp(params.hover, 0.0, 1.0);\n let lightCenter = params.pointer * HALF;\n let delta = p - lightCenter;\n let sweepDistance = delta.x * 0.72 + delta.y * 0.52 + sin(p.y * 4.0 + p.x * 3.0) * 0.08;\n let bandDistance = sweepDistance / 0.36;\n let lightBand = exp(-bandDistance * bandDistance);\n let glintDistance = sweepDistance / 0.085;\n let glint = exp(-glintDistance * glintDistance);\n let spotlight = exp(-dot(delta * vec2f(0.9, 1.1), delta * vec2f(0.9, 1.1)) * 2.6);\n let light = lightBand * spotlight * hover;\n let lightDirection = normalize(vec3f(lightCenter, 1.2) - hit);\n let viewDirection = normalize(eye - hit);\n let lightAndView = vec2f(dot(lightDirection + viewDirection, right), dot(lightDirection + viewDirection, down));\n let illumination = max(dot(normal, lightDirection), 0.0) * max(dot(normal, viewDirection), 0.0);\n let tint = params.ink * 0.78;\n let noise = grain(p + vec2f(2));\n var color = params.graphite + 0.008 * (0.9 - p.y);\n // Fine, surface-locked grain catches the grazing reflection without animated static.\n color += noise * (0.022 + light * 0.085);\n color += light * (vec3f(0.045) + tint * 0.065);\n\n // One engraving over the whole card, where vgpu keeps a triangle and a fractal.\n let contour = etchedPhase(p);\n // Transform screen derivatives back into the card plane: microscopic grooves\n // follow the visible contours, but their 1.65um spacing is independent of zoom.\n let dx = dpdx(p);\n let dy = dpdy(p);\n let gradient = vec2f(dpdx(contour) * dy.y - dpdy(contour) * dx.y, dpdy(contour) * dx.x - dpdx(contour) * dy.x);\n let across = gradient / max(length(gradient), 0.00000001);\n let foilDiffraction = diffraction(across, lightAndView, 1.65) * illumination;\n let contours = stroke(sin(contour), 0.06, min(fwidth(contour), 1.0));\n let reveal = hover * (0.06 + 0.24 * spotlight + light * 1.15);\n let pearlPhase = dot(lightAndView, vec2f(0.48, -0.32)) + p.y * 0.32 + contour * 0.003;\n let pearl = pearlColor(pearlPhase);\n color += pearl * light * 0.24;\n color += (pearl * 0.5 + vec3f(0.5)) * glint * spotlight * hover * 0.12;\n let sparkle = pow(max(noise + 0.5, 0.0), 24.0) * glint * spotlight * hover;\n color += pearl * sparkle * 0.22;\n let foil = vec3f(0.12, 0.14, 0.18) + pearl * 0.65 + foilDiffraction * 0.12;\n color += contours * 0.65 * foil * reveal;\n\n // A delicate spectral echo stays clipped to the same engraving.\n let foilPoint = p - (vec2f(0.007, -0.004) + params.tilt * 0.012);\n let foilPhase = etchedPhase(foilPoint);\n let foilLines = stroke(sin(foilPhase), 0.025, min(fwidth(foilPhase), 1.0));\n color += foilLines * 0.65 * (pearl + foilDiffraction * 0.2) * reveal * 0.22;\n\n // Sparse microdots and registration ticks emerge in the foil.\n let grid = (fract((p + 1.0) * 20.0) - 0.5) / 20.0;\n let dots = stroke(length(grid), 0.0008, aa * 0.4);\n color += dots * reveal * 0.17;\n let guide = abs(p) - (HALF - vec2f(0.07, 0.07));\n let horizontal = stroke(guide.y, 0.0006, aa * 0.5) * (1.0 - smoothstep(0.012, 0.017, abs(guide.x)));\n let vertical = stroke(guide.x, 0.0006, aa * 0.5) * (1.0 - smoothstep(0.012, 0.017, abs(guide.y)));\n color += max(horizontal, vertical) * hover * (0.12 + light * 0.22);\n\n return vec4f(color, 1);\n}\n";
|
|
10
|
+
//# sourceMappingURL=shader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../../src/components/company-card/shader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,u3NA4J5B,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The holographic foil's fragment shader, as a string.
|
|
3
|
+
*
|
|
4
|
+
* This package builds with tsc alone, so a `.wgsl` file would neither be
|
|
5
|
+
* emitted into `dist` nor compiled by a consumer's bundler. vgpu's `effect`
|
|
6
|
+
* takes WGSL source, so the shader travels as the module below and needs no
|
|
7
|
+
* loader in any host.
|
|
8
|
+
*/
|
|
9
|
+
export const CARD_FOIL_SHADER = /* wgsl */ `
|
|
10
|
+
// nadicode: vgpu's holographic card (MIT, https://github.com/vercel-labs/vgpu,
|
|
11
|
+
// examples/holographic-card, revision 6fa27bb4). Its light model is unchanged.
|
|
12
|
+
// The surface is the company card itself, 85.6 x 54 mm, which the page tilts;
|
|
13
|
+
// vgpu's triangle, fractal and lettering give way to the card's own marks; and
|
|
14
|
+
// every colour is a design-system token: the spectrum and the pearl fall in
|
|
15
|
+
// pure bands of the identity hues, never a colour between two of them.
|
|
16
|
+
struct Params {
|
|
17
|
+
resolution: vec2f,
|
|
18
|
+
tilt: vec2f,
|
|
19
|
+
pointer: vec2f,
|
|
20
|
+
hover: f32,
|
|
21
|
+
graphite: vec3f,
|
|
22
|
+
ink: vec3f,
|
|
23
|
+
hue0: vec3f,
|
|
24
|
+
hue1: vec3f,
|
|
25
|
+
hue2: vec3f,
|
|
26
|
+
hue3: vec3f,
|
|
27
|
+
hue4: vec3f,
|
|
28
|
+
hue5: vec3f,
|
|
29
|
+
}
|
|
30
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
31
|
+
|
|
32
|
+
const HALF = vec2f(0.856, 0.54);
|
|
33
|
+
const ROSETTE = vec2f(0.448, -0.016);
|
|
34
|
+
|
|
35
|
+
fn stroke(distance: f32, width: f32, aa: f32) -> f32 {
|
|
36
|
+
return 1.0 - smoothstep(width, width + aa, abs(distance));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The identity hues in spectral order, each a band of its own: a blend of two
|
|
40
|
+
// neighbours is a colour the palette does not have.
|
|
41
|
+
fn palette(t: f32) -> vec3f {
|
|
42
|
+
let hues = array<vec3f, 6>(params.hue0, params.hue1, params.hue2, params.hue3, params.hue4, params.hue5);
|
|
43
|
+
return hues[min(u32(clamp(t, 0.0, 1.0) * 6.0), 5u)];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// vgpu's visible-light window, drawn with the identity band instead of display RGB.
|
|
47
|
+
fn wavelengthColor(wavelength: f32) -> vec3f {
|
|
48
|
+
let visible = smoothstep(0.380, 0.410, wavelength) * (1.0 - smoothstep(0.700, 0.780, wavelength));
|
|
49
|
+
return palette((wavelength - 0.44) / 0.22) * visible * 0.8;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reflection grating approximation: m * wavelength = d * dot(L + V, across).
|
|
53
|
+
// L and V point away from the surface; across is perpendicular to the grooves.
|
|
54
|
+
// Based on the diffraction-order model in GPU Gems, chapter 8 (Jos Stam).
|
|
55
|
+
fn diffraction(across: vec2f, lightAndView: vec2f, spacing: f32) -> vec3f {
|
|
56
|
+
let pathDifference = spacing * abs(dot(lightAndView, across));
|
|
57
|
+
let along = dot(lightAndView, vec2f(-across.y, across.x));
|
|
58
|
+
// Finite, imperfect groove patches broaden the directional reflection.
|
|
59
|
+
let envelope = exp(-along * along / 0.36);
|
|
60
|
+
var reflected = vec3f(0);
|
|
61
|
+
for (var order = 1; order <= 3; order++) {
|
|
62
|
+
let m = f32(order);
|
|
63
|
+
reflected += wavelengthColor(pathDifference / m) / (m * m);
|
|
64
|
+
}
|
|
65
|
+
return reflected * envelope;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Broad pearlescence underneath the finer diffraction detail, in the same hues.
|
|
69
|
+
fn pearlColor(phase: f32) -> vec3f {
|
|
70
|
+
let cycle = abs(fract(phase * 0.5) * 2.0 - 1.0);
|
|
71
|
+
return mix(palette(cycle), vec3f(0.82), 0.35);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn grain(point: vec2f) -> f32 {
|
|
75
|
+
let p = vec2u(abs(point) * 2400.0);
|
|
76
|
+
var n = (p.x * 1597334677u) ^ (p.y * 3812015801u);
|
|
77
|
+
n = (n ^ (n >> 16u)) * 2246822519u;
|
|
78
|
+
return f32(n & 1023u) / 1023.0 - 0.5;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn etchedPhase(p: vec2f) -> f32 {
|
|
82
|
+
// Warp the surface before tracing contours, so their spacing flows in soft waves.
|
|
83
|
+
let warp = vec2f(
|
|
84
|
+
sin(p.y * 7.0 + sin(p.x * 4.0)) * 0.085,
|
|
85
|
+
sin(p.x * 6.0 - p.y * 3.0) * 0.07
|
|
86
|
+
);
|
|
87
|
+
let q = p + warp - ROSETTE;
|
|
88
|
+
let radius = length(q * vec2f(1.0, 0.86));
|
|
89
|
+
return radius * 142.0 + sin(atan2(q.y, q.x) * 3.0 + radius * 8.0) * 1.7;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@fragment
|
|
93
|
+
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
|
|
94
|
+
let p = (uv - 0.5) * 2.0 * HALF;
|
|
95
|
+
let sx = sin(params.tilt.y);
|
|
96
|
+
let cx = cos(params.tilt.y);
|
|
97
|
+
let sy = sin(params.tilt.x);
|
|
98
|
+
let cy = cos(params.tilt.x);
|
|
99
|
+
let right = vec3f(cy, 0, -sy);
|
|
100
|
+
let down = vec3f(sy * sx, cx, cy * sx);
|
|
101
|
+
let normal = cross(right, down);
|
|
102
|
+
let eye = vec3f(0, 0, 4.5);
|
|
103
|
+
let hit = right * p.x + down * p.y;
|
|
104
|
+
let aa = max(length(fwidth(p)), 0.0006);
|
|
105
|
+
|
|
106
|
+
// Matte graphite remains dark; only the cursor's grazing light reveals the foil.
|
|
107
|
+
let hover = clamp(params.hover, 0.0, 1.0);
|
|
108
|
+
let lightCenter = params.pointer * HALF;
|
|
109
|
+
let delta = p - lightCenter;
|
|
110
|
+
let sweepDistance = delta.x * 0.72 + delta.y * 0.52 + sin(p.y * 4.0 + p.x * 3.0) * 0.08;
|
|
111
|
+
let bandDistance = sweepDistance / 0.36;
|
|
112
|
+
let lightBand = exp(-bandDistance * bandDistance);
|
|
113
|
+
let glintDistance = sweepDistance / 0.085;
|
|
114
|
+
let glint = exp(-glintDistance * glintDistance);
|
|
115
|
+
let spotlight = exp(-dot(delta * vec2f(0.9, 1.1), delta * vec2f(0.9, 1.1)) * 2.6);
|
|
116
|
+
let light = lightBand * spotlight * hover;
|
|
117
|
+
let lightDirection = normalize(vec3f(lightCenter, 1.2) - hit);
|
|
118
|
+
let viewDirection = normalize(eye - hit);
|
|
119
|
+
let lightAndView = vec2f(dot(lightDirection + viewDirection, right), dot(lightDirection + viewDirection, down));
|
|
120
|
+
let illumination = max(dot(normal, lightDirection), 0.0) * max(dot(normal, viewDirection), 0.0);
|
|
121
|
+
let tint = params.ink * 0.78;
|
|
122
|
+
let noise = grain(p + vec2f(2));
|
|
123
|
+
var color = params.graphite + 0.008 * (0.9 - p.y);
|
|
124
|
+
// Fine, surface-locked grain catches the grazing reflection without animated static.
|
|
125
|
+
color += noise * (0.022 + light * 0.085);
|
|
126
|
+
color += light * (vec3f(0.045) + tint * 0.065);
|
|
127
|
+
|
|
128
|
+
// One engraving over the whole card, where vgpu keeps a triangle and a fractal.
|
|
129
|
+
let contour = etchedPhase(p);
|
|
130
|
+
// Transform screen derivatives back into the card plane: microscopic grooves
|
|
131
|
+
// follow the visible contours, but their 1.65um spacing is independent of zoom.
|
|
132
|
+
let dx = dpdx(p);
|
|
133
|
+
let dy = dpdy(p);
|
|
134
|
+
let gradient = vec2f(dpdx(contour) * dy.y - dpdy(contour) * dx.y, dpdy(contour) * dx.x - dpdx(contour) * dy.x);
|
|
135
|
+
let across = gradient / max(length(gradient), 0.00000001);
|
|
136
|
+
let foilDiffraction = diffraction(across, lightAndView, 1.65) * illumination;
|
|
137
|
+
let contours = stroke(sin(contour), 0.06, min(fwidth(contour), 1.0));
|
|
138
|
+
let reveal = hover * (0.06 + 0.24 * spotlight + light * 1.15);
|
|
139
|
+
let pearlPhase = dot(lightAndView, vec2f(0.48, -0.32)) + p.y * 0.32 + contour * 0.003;
|
|
140
|
+
let pearl = pearlColor(pearlPhase);
|
|
141
|
+
color += pearl * light * 0.24;
|
|
142
|
+
color += (pearl * 0.5 + vec3f(0.5)) * glint * spotlight * hover * 0.12;
|
|
143
|
+
let sparkle = pow(max(noise + 0.5, 0.0), 24.0) * glint * spotlight * hover;
|
|
144
|
+
color += pearl * sparkle * 0.22;
|
|
145
|
+
let foil = vec3f(0.12, 0.14, 0.18) + pearl * 0.65 + foilDiffraction * 0.12;
|
|
146
|
+
color += contours * 0.65 * foil * reveal;
|
|
147
|
+
|
|
148
|
+
// A delicate spectral echo stays clipped to the same engraving.
|
|
149
|
+
let foilPoint = p - (vec2f(0.007, -0.004) + params.tilt * 0.012);
|
|
150
|
+
let foilPhase = etchedPhase(foilPoint);
|
|
151
|
+
let foilLines = stroke(sin(foilPhase), 0.025, min(fwidth(foilPhase), 1.0));
|
|
152
|
+
color += foilLines * 0.65 * (pearl + foilDiffraction * 0.2) * reveal * 0.22;
|
|
153
|
+
|
|
154
|
+
// Sparse microdots and registration ticks emerge in the foil.
|
|
155
|
+
let grid = (fract((p + 1.0) * 20.0) - 0.5) / 20.0;
|
|
156
|
+
let dots = stroke(length(grid), 0.0008, aa * 0.4);
|
|
157
|
+
color += dots * reveal * 0.17;
|
|
158
|
+
let guide = abs(p) - (HALF - vec2f(0.07, 0.07));
|
|
159
|
+
let horizontal = stroke(guide.y, 0.0006, aa * 0.5) * (1.0 - smoothstep(0.012, 0.017, abs(guide.x)));
|
|
160
|
+
let vertical = stroke(guide.x, 0.0006, aa * 0.5) * (1.0 - smoothstep(0.012, 0.017, abs(guide.y)));
|
|
161
|
+
color += max(horizontal, vertical) * hover * (0.12 + light * 0.22);
|
|
162
|
+
|
|
163
|
+
return vec4f(color, 1);
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type FoilColors } from "./scene.js";
|
|
2
|
+
/**
|
|
3
|
+
* One frame of the foil at `width` × `height` device pixels, copied out before
|
|
4
|
+
* the browser presents it: a WebGPU canvas keeps no readable image afterwards.
|
|
5
|
+
*/
|
|
6
|
+
export declare function snapshotFoil(colors: FoilColors, width: number, height: number): Promise<HTMLCanvasElement>;
|
|
7
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/components/company-card/snapshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAKvD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC,CA2B5B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { frame, init, surface } from "vgpu";
|
|
2
|
+
import { createScene } from "./scene.js";
|
|
3
|
+
/** The pose a still of the card is taken in: lit from the upper left, nearly flat. */
|
|
4
|
+
const POSE = { tilt: [0.04, -0.03], pointer: [-0.25, -0.35], hover: 1 };
|
|
5
|
+
/**
|
|
6
|
+
* One frame of the foil at `width` × `height` device pixels, copied out before
|
|
7
|
+
* the browser presents it: a WebGPU canvas keeps no readable image afterwards.
|
|
8
|
+
*/
|
|
9
|
+
export async function snapshotFoil(colors, width, height) {
|
|
10
|
+
const gpu = await init();
|
|
11
|
+
const canvas = document.createElement("canvas");
|
|
12
|
+
Object.assign(canvas.style, {
|
|
13
|
+
position: "fixed",
|
|
14
|
+
left: "-10000px",
|
|
15
|
+
top: "0",
|
|
16
|
+
width: `${width / 2}px`,
|
|
17
|
+
height: `${height / 2}px`,
|
|
18
|
+
});
|
|
19
|
+
document.body.append(canvas);
|
|
20
|
+
try {
|
|
21
|
+
const output = surface(gpu, canvas, { dpr: [2, 2] });
|
|
22
|
+
const shader = createScene(gpu, output, colors);
|
|
23
|
+
await shader.compile({ colors: [output.format] });
|
|
24
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
25
|
+
shader.set({ params: { resolution: output.size, ...POSE } });
|
|
26
|
+
frame(gpu, (current) => current.pass(output, shader));
|
|
27
|
+
const still = document.createElement("canvas");
|
|
28
|
+
still.width = canvas.width;
|
|
29
|
+
still.height = canvas.height;
|
|
30
|
+
still.getContext("2d")?.drawImage(canvas, 0, 0);
|
|
31
|
+
return still;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
canvas.remove();
|
|
35
|
+
gpu.dispose();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type CompanyCardEdition = "company" | "partner";
|
|
2
|
+
export type CompanyCardLabels = {
|
|
3
|
+
edition: string;
|
|
4
|
+
since: string;
|
|
5
|
+
namePlaceholder: string;
|
|
6
|
+
domainPlaceholder: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The company's nadicode card: what the person is making while they sign up,
|
|
10
|
+
* and what the website shows being issued. It carries every word it shows as a
|
|
11
|
+
* prop, fills in as the form is typed and turns to the pointer, so the foil in
|
|
12
|
+
* its engraving catches the light. The words are the caller's, so the card is
|
|
13
|
+
* hidden from assistive technology and holds nothing interactive.
|
|
14
|
+
*/
|
|
15
|
+
export declare function CompanyCard({ name, domain, edition, issued, labels, logo, sweepInView, }: {
|
|
16
|
+
name: string;
|
|
17
|
+
domain: string;
|
|
18
|
+
edition: CompanyCardEdition;
|
|
19
|
+
issued: boolean;
|
|
20
|
+
labels: CompanyCardLabels;
|
|
21
|
+
/** The company's own logo, one ink in white; absent until someone has found it. */
|
|
22
|
+
logo?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Waits for most of the card to be on screen before it turns once in the
|
|
25
|
+
* light, for a page the reader scrolls the card into. A card the page shows
|
|
26
|
+
* already, such as the one beside a sign-up form, leaves this off and turns
|
|
27
|
+
* as soon as any of it is seen. Reduced motion holds it still either way.
|
|
28
|
+
*/
|
|
29
|
+
sweepInView?: boolean;
|
|
30
|
+
}): import("react").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=company-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"company-card.d.ts","sourceRoot":"","sources":["../../src/components/company-card.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAqBF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,MAAM,EACN,IAAI,EACJ,WAAmB,GACpB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,+BAsSA"}
|