@kongyo2/cards-css 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/active-registry.js +10 -0
- package/dist/active-registry.js.map +1 -0
- package/dist/dom.js +66 -0
- package/dist/dom.js.map +1 -0
- package/dist/holo-card.js +450 -0
- package/dist/holo-card.js.map +1 -0
- package/dist/holo-cards.css +601 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/math.js +4 -0
- package/dist/math.js.map +1 -0
- package/dist/orientation.js +64 -0
- package/dist/orientation.js.map +1 -0
- package/dist/spring.js +123 -0
- package/dist/spring.js.map +1 -0
- package/dist/subscribers.js +26 -0
- package/dist/subscribers.js.map +1 -0
- package/dist/textures.js +178 -0
- package/dist/textures.js.map +1 -0
- package/dist/ticker.js +32 -0
- package/dist/ticker.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist-types/active-registry.d.ts +6 -0
- package/dist-types/active-registry.d.ts.map +1 -0
- package/dist-types/dom.d.ts +18 -0
- package/dist-types/dom.d.ts.map +1 -0
- package/dist-types/holo-card.d.ts +58 -0
- package/dist-types/holo-card.d.ts.map +1 -0
- package/dist-types/index.d.ts +13 -0
- package/dist-types/index.d.ts.map +1 -0
- package/dist-types/math.d.ts +4 -0
- package/dist-types/math.d.ts.map +1 -0
- package/dist-types/orientation.d.ts +13 -0
- package/dist-types/orientation.d.ts.map +1 -0
- package/dist-types/spring.d.ts +32 -0
- package/dist-types/spring.d.ts.map +1 -0
- package/dist-types/subscribers.d.ts +10 -0
- package/dist-types/subscribers.d.ts.map +1 -0
- package/dist-types/textures.d.ts +23 -0
- package/dist-types/textures.d.ts.map +1 -0
- package/dist-types/ticker.d.ts +7 -0
- package/dist-types/ticker.d.ts.map +1 -0
- package/dist-types/types.d.ts +21 -0
- package/dist-types/types.d.ts.map +1 -0
- package/package.json +75 -0
- package/src/active-registry.ts +15 -0
- package/src/dom.ts +79 -0
- package/src/holo-card.ts +525 -0
- package/src/index.ts +35 -0
- package/src/math.ts +6 -0
- package/src/orientation.ts +83 -0
- package/src/spring.ts +158 -0
- package/src/styles/base.css +262 -0
- package/src/styles/effects/cosmos.css +143 -0
- package/src/styles/effects/glitter.css +103 -0
- package/src/styles/effects/holo.css +127 -0
- package/src/styles/effects/reverse.css +55 -0
- package/src/styles/index.css +5 -0
- package/src/subscribers.ts +30 -0
- package/src/textures.ts +310 -0
- package/src/ticker.ts +46 -0
- package/src/types.ts +22 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
.holo-card[data-effect="holo"] .holo-card__shine {
|
|
2
|
+
--scanlines-space: 1px;
|
|
3
|
+
--scanlines-light: #666;
|
|
4
|
+
--scanlines-dark: black;
|
|
5
|
+
|
|
6
|
+
--bars: 3%;
|
|
7
|
+
--bar-color: hsla(0, 0%, 70%, 1);
|
|
8
|
+
--bar-bg: hsla(0, 0%, 0%, 1);
|
|
9
|
+
|
|
10
|
+
background-image:
|
|
11
|
+
repeating-linear-gradient(
|
|
12
|
+
110deg,
|
|
13
|
+
var(--violet),
|
|
14
|
+
var(--blue),
|
|
15
|
+
var(--green),
|
|
16
|
+
var(--yellow),
|
|
17
|
+
var(--red),
|
|
18
|
+
var(--violet),
|
|
19
|
+
var(--blue),
|
|
20
|
+
var(--green),
|
|
21
|
+
var(--yellow),
|
|
22
|
+
var(--red),
|
|
23
|
+
var(--violet),
|
|
24
|
+
var(--blue),
|
|
25
|
+
var(--green),
|
|
26
|
+
var(--yellow),
|
|
27
|
+
var(--red)
|
|
28
|
+
),
|
|
29
|
+
repeating-linear-gradient(
|
|
30
|
+
90deg,
|
|
31
|
+
var(--scanlines-dark) calc(var(--scanlines-space) * 0),
|
|
32
|
+
var(--scanlines-dark) calc(var(--scanlines-space) * 2),
|
|
33
|
+
var(--scanlines-light) calc(var(--scanlines-space) * 2),
|
|
34
|
+
var(--scanlines-light) calc(var(--scanlines-space) * 4)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
background-position:
|
|
38
|
+
calc(((50% - var(--background-x)) * 2.6) + 50%) calc(((50% - var(--background-y)) * 3.5) + 50%),
|
|
39
|
+
center center;
|
|
40
|
+
|
|
41
|
+
background-size:
|
|
42
|
+
400% 400%,
|
|
43
|
+
cover;
|
|
44
|
+
|
|
45
|
+
background-blend-mode: overlay;
|
|
46
|
+
filter: brightness(1.1) contrast(1.1) saturate(1.2);
|
|
47
|
+
mix-blend-mode: color-dodge;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.holo-card[data-effect="holo"] .holo-card__shine::before {
|
|
51
|
+
content: "";
|
|
52
|
+
|
|
53
|
+
background-image:
|
|
54
|
+
repeating-linear-gradient(
|
|
55
|
+
90deg,
|
|
56
|
+
var(--bar-bg) calc(var(--bars) * 2),
|
|
57
|
+
var(--bar-color) calc(var(--bars) * 3),
|
|
58
|
+
var(--bar-bg) calc(var(--bars) * 3.5),
|
|
59
|
+
var(--bar-color) calc(var(--bars) * 4),
|
|
60
|
+
var(--bar-bg) calc(var(--bars) * 5),
|
|
61
|
+
var(--bar-bg) calc(var(--bars) * 14)
|
|
62
|
+
),
|
|
63
|
+
repeating-linear-gradient(
|
|
64
|
+
90deg,
|
|
65
|
+
var(--bar-bg) calc(var(--bars) * 2),
|
|
66
|
+
var(--bar-color) calc(var(--bars) * 3),
|
|
67
|
+
var(--bar-bg) calc(var(--bars) * 3.5),
|
|
68
|
+
var(--bar-color) calc(var(--bars) * 4),
|
|
69
|
+
var(--bar-bg) calc(var(--bars) * 5),
|
|
70
|
+
var(--bar-bg) calc(var(--bars) * 10)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
background-position:
|
|
74
|
+
calc((((50% - var(--background-x)) * 1.65) + 50%) + (var(--background-y) * 0.5)) var(--background-x),
|
|
75
|
+
calc((((50% - var(--background-x)) * -0.9) + 50%) - (var(--background-y) * 0.75)) var(--background-y);
|
|
76
|
+
|
|
77
|
+
background-size:
|
|
78
|
+
200% 200%,
|
|
79
|
+
200% 200%;
|
|
80
|
+
|
|
81
|
+
background-blend-mode: screen;
|
|
82
|
+
filter: brightness(1.15) contrast(1.1);
|
|
83
|
+
mix-blend-mode: hard-light;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.holo-card[data-effect="holo"] .holo-card__shine::after {
|
|
87
|
+
content: "";
|
|
88
|
+
|
|
89
|
+
background-image: radial-gradient(
|
|
90
|
+
farthest-corner circle at var(--pointer-x) var(--pointer-y),
|
|
91
|
+
hsla(0, 0%, 90%, 0.8) 0%,
|
|
92
|
+
hsla(0, 0%, 78%, 0.1) 25%,
|
|
93
|
+
hsl(0, 0%, 0%) 90%
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
background-position: center center;
|
|
97
|
+
background-size: cover;
|
|
98
|
+
|
|
99
|
+
mix-blend-mode: luminosity;
|
|
100
|
+
filter: brightness(0.6) contrast(4);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media screen and (max-width: 900px) {
|
|
104
|
+
.holo-card[data-effect="holo"] .holo-card__shine {
|
|
105
|
+
--scanlines-space: 0.5px;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.holo-card[data-effect="holo"] .holo-card__glare {
|
|
110
|
+
opacity: calc(var(--card-opacity) * 0.8);
|
|
111
|
+
filter: brightness(0.8) contrast(1.5);
|
|
112
|
+
mix-blend-mode: overlay;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.holo-card[data-effect="holo"] .holo-card__glare::after {
|
|
116
|
+
content: "";
|
|
117
|
+
|
|
118
|
+
background-image: radial-gradient(
|
|
119
|
+
farthest-corner circle at var(--pointer-x) var(--pointer-y),
|
|
120
|
+
hsl(180, 100%, 95%) 5%,
|
|
121
|
+
hsla(0, 0%, 39%, 0.25) 55%,
|
|
122
|
+
hsla(0, 0%, 0%, 0.36) 110%
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
mix-blend-mode: overlay;
|
|
126
|
+
filter: brightness(0.6) contrast(3);
|
|
127
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.holo-card[data-effect="reverse"] {
|
|
2
|
+
--foil-brightness: 0.55;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.holo-card[data-effect="reverse"] .holo-card__shine {
|
|
6
|
+
background-image:
|
|
7
|
+
radial-gradient(circle at var(--pointer-x) var(--pointer-y), #fff 5%, #000 50%, #fff 80%),
|
|
8
|
+
linear-gradient(-45deg, #000 15%, #fff, #000 85%), var(--foil);
|
|
9
|
+
|
|
10
|
+
background-blend-mode: soft-light, difference;
|
|
11
|
+
background-size:
|
|
12
|
+
120% 120%,
|
|
13
|
+
200% 200%,
|
|
14
|
+
cover;
|
|
15
|
+
background-position:
|
|
16
|
+
center center,
|
|
17
|
+
calc(100% * var(--pointer-from-left)) calc(100% * var(--pointer-from-top)),
|
|
18
|
+
center center;
|
|
19
|
+
|
|
20
|
+
filter: brightness(var(--foil-brightness)) contrast(1.5) saturate(1);
|
|
21
|
+
mix-blend-mode: color-dodge;
|
|
22
|
+
|
|
23
|
+
opacity: calc((1.5 * var(--card-opacity)) - var(--pointer-from-center));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.holo-card[data-effect="reverse"] .holo-card__glare {
|
|
27
|
+
opacity: var(--card-opacity);
|
|
28
|
+
|
|
29
|
+
background-image: radial-gradient(
|
|
30
|
+
farthest-corner circle at var(--pointer-x) var(--pointer-y),
|
|
31
|
+
hsla(0, 0%, 100%, 0.8) 10%,
|
|
32
|
+
hsla(0, 0%, 100%, 0.5) 20%,
|
|
33
|
+
hsla(0, 0%, 0%, 0.75) 90%
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
filter: brightness(0.7) contrast(1.5);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.holo-card[data-effect="reverse"] .holo-card__glare::after {
|
|
40
|
+
content: "";
|
|
41
|
+
opacity: var(--card-opacity);
|
|
42
|
+
|
|
43
|
+
background-image: radial-gradient(
|
|
44
|
+
farthest-corner circle at var(--pointer-x) var(--pointer-y),
|
|
45
|
+
hsl(0, 0%, 100%) 10%,
|
|
46
|
+
hsla(0, 0%, 100%, 0.5) 20%,
|
|
47
|
+
hsla(0, 0%, 0%, 0.5) 120%
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
filter: brightness(1) contrast(1.5);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.holo-card[data-effect="reverse"]:not(.holo-card--masked) .holo-card__shine {
|
|
54
|
+
--foil: none;
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class Subscribers<T> {
|
|
2
|
+
private readonly fns = new Set<(value: T) => void>();
|
|
3
|
+
private readonly getCurrent: () => T;
|
|
4
|
+
|
|
5
|
+
constructor(getCurrent: () => T) {
|
|
6
|
+
this.getCurrent = getCurrent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
subscribe(fn: (value: T) => void): () => void {
|
|
10
|
+
this.fns.add(fn);
|
|
11
|
+
fn(this.getCurrent());
|
|
12
|
+
return () => {
|
|
13
|
+
this.fns.delete(fn);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
emit(value: T): void {
|
|
18
|
+
for (const fn of this.fns) {
|
|
19
|
+
fn(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get size(): number {
|
|
24
|
+
return this.fns.size;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
clear(): void {
|
|
28
|
+
this.fns.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/textures.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
export interface TextureOptions {
|
|
2
|
+
seed?: number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface Textures {
|
|
6
|
+
grain: string;
|
|
7
|
+
glitter: string;
|
|
8
|
+
cosmosBottom: string;
|
|
9
|
+
cosmosMiddle: string;
|
|
10
|
+
cosmosTop: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_TEXTURE_SEED = 0x9e3779b9;
|
|
14
|
+
|
|
15
|
+
const mulberry32 = (seed: number): (() => number) => {
|
|
16
|
+
let a = seed >>> 0;
|
|
17
|
+
return () => {
|
|
18
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
19
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
20
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
21
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const svgToDataUri = (svg: string): string => {
|
|
26
|
+
const cleaned = svg
|
|
27
|
+
.replace(/>\s+</g, "><")
|
|
28
|
+
.replace(/\s{2,}/g, " ")
|
|
29
|
+
.trim();
|
|
30
|
+
const encoded = cleaned
|
|
31
|
+
.replace(/%/g, "%25")
|
|
32
|
+
.replace(/</g, "%3C")
|
|
33
|
+
.replace(/>/g, "%3E")
|
|
34
|
+
.replace(/#/g, "%23")
|
|
35
|
+
.replace(/&/g, "%26")
|
|
36
|
+
.replace(/"/g, "'")
|
|
37
|
+
.replace(/\n/g, "%0A");
|
|
38
|
+
return `data:image/svg+xml,${encoded}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const svgDocument = (width: number, height: number, defs: string, body: string): string =>
|
|
42
|
+
svgToDataUri(
|
|
43
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' viewBox='0 0 ${width} ${height}'>` +
|
|
44
|
+
(defs ? `<defs>${defs}</defs>` : "") +
|
|
45
|
+
body +
|
|
46
|
+
`</svg>`,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const pick = <T>(rng: () => number, items: readonly T[]): T => {
|
|
50
|
+
const value = items[Math.floor(rng() * items.length)];
|
|
51
|
+
return value ?? (items[0] as T);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const rand = (rng: () => number, min: number, max: number): number => min + rng() * (max - min);
|
|
55
|
+
|
|
56
|
+
const circle = (
|
|
57
|
+
x: number,
|
|
58
|
+
y: number,
|
|
59
|
+
r: number,
|
|
60
|
+
fill: string,
|
|
61
|
+
opacity: number,
|
|
62
|
+
opts: { rDigits?: number; filter?: string } = {},
|
|
63
|
+
): string => {
|
|
64
|
+
const filter = opts.filter ? ` filter='url(${opts.filter})'` : "";
|
|
65
|
+
return `<circle cx='${x.toFixed(1)}' cy='${y.toFixed(1)}' r='${r.toFixed(opts.rDigits ?? 2)}' fill='${fill}' opacity='${opacity.toFixed(2)}'${filter}/>`;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const gaussianBlurFilter = (id: string, std: number, margin: string, size: string): string =>
|
|
69
|
+
`<filter id='${id}' x='${margin}' y='${margin}' width='${size}' height='${size}'><feGaussianBlur stdDeviation='${std}'/></filter>`;
|
|
70
|
+
|
|
71
|
+
const discreteTable = (keep: number, steps = 32): string => {
|
|
72
|
+
const ones = Math.min(steps, Math.max(1, Math.round(steps * keep)));
|
|
73
|
+
const cells: string[] = [];
|
|
74
|
+
for (let i = 0; i < steps - ones; i += 1) {
|
|
75
|
+
cells.push("0");
|
|
76
|
+
}
|
|
77
|
+
for (let i = 0; i < ones; i += 1) {
|
|
78
|
+
cells.push("1");
|
|
79
|
+
}
|
|
80
|
+
return cells.join(" ");
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
interface SpeckleLayer {
|
|
84
|
+
freq: number;
|
|
85
|
+
keep: number;
|
|
86
|
+
color: string;
|
|
87
|
+
opacity: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const speckleField = (
|
|
91
|
+
idBase: string,
|
|
92
|
+
seed: number,
|
|
93
|
+
layers: readonly SpeckleLayer[],
|
|
94
|
+
stitch = true,
|
|
95
|
+
): { defs: string; body: string } => {
|
|
96
|
+
let defs = "";
|
|
97
|
+
let body = "";
|
|
98
|
+
const stitchTiles = stitch ? "stitch" : "noStitch";
|
|
99
|
+
layers.forEach((layer, index) => {
|
|
100
|
+
const id = `${idBase}${index}`;
|
|
101
|
+
defs +=
|
|
102
|
+
`<filter id='${id}' x='0%' y='0%' width='100%' height='100%'>` +
|
|
103
|
+
`<feTurbulence type='fractalNoise' baseFrequency='${layer.freq}' numOctaves='1' seed='${(seed + index * 37) % 9973}' stitchTiles='${stitchTiles}' result='n'/>` +
|
|
104
|
+
`<feColorMatrix in='n' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0' result='a'/>` +
|
|
105
|
+
`<feComponentTransfer in='a' result='m'><feFuncA type='discrete' tableValues='${discreteTable(layer.keep)}'/></feComponentTransfer>` +
|
|
106
|
+
`<feComposite in='SourceGraphic' in2='m' operator='in'/>` +
|
|
107
|
+
`</filter>`;
|
|
108
|
+
body += `<rect width='100%' height='100%' fill='${layer.color}' filter='url(#${id})' opacity='${layer.opacity}'/>`;
|
|
109
|
+
});
|
|
110
|
+
return { defs, body };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const clusterBlobs = (
|
|
114
|
+
rng: () => number,
|
|
115
|
+
width: number,
|
|
116
|
+
height: number,
|
|
117
|
+
count: number,
|
|
118
|
+
palette: readonly string[],
|
|
119
|
+
blurId: string,
|
|
120
|
+
radius: [number, number],
|
|
121
|
+
opacity: [number, number],
|
|
122
|
+
): string => {
|
|
123
|
+
let blobs = "";
|
|
124
|
+
for (let i = 0; i < count; i += 1) {
|
|
125
|
+
blobs += circle(
|
|
126
|
+
rng() * width,
|
|
127
|
+
rng() * height,
|
|
128
|
+
rand(rng, radius[0], radius[1]),
|
|
129
|
+
pick(rng, palette),
|
|
130
|
+
rand(rng, opacity[0], opacity[1]),
|
|
131
|
+
{ rDigits: 1 },
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return `<g filter='url(${blurId})'>${blobs}</g>`;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const brightStars = (rng: () => number, width: number, height: number, count: number, glowId: string): string => {
|
|
138
|
+
let stars = "";
|
|
139
|
+
for (let i = 0; i < count; i += 1) {
|
|
140
|
+
const x = rng() * width;
|
|
141
|
+
const y = rng() * height;
|
|
142
|
+
const r = rand(rng, 1.1, 2.6);
|
|
143
|
+
stars +=
|
|
144
|
+
circle(x, y, r * 2.6, "#ffffff", 0.14, { rDigits: 1, filter: glowId }) +
|
|
145
|
+
circle(x, y, r, "#ffffff", rand(rng, 0.75, 1));
|
|
146
|
+
}
|
|
147
|
+
return stars;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const ringClusters = (rng: () => number, width: number, height: number, count: number, color: string): string => {
|
|
151
|
+
let out = "";
|
|
152
|
+
for (let i = 0; i < count; i += 1) {
|
|
153
|
+
const cx = rng() * width;
|
|
154
|
+
const cy = rng() * height;
|
|
155
|
+
const radius = rand(rng, 14, 30);
|
|
156
|
+
const dots = 26 + Math.floor(rng() * 22);
|
|
157
|
+
for (let k = 0; k < dots; k += 1) {
|
|
158
|
+
const angle = rng() * Math.PI * 2;
|
|
159
|
+
const rr = radius * rand(rng, 0.78, 1.28);
|
|
160
|
+
out += circle(
|
|
161
|
+
cx + Math.cos(angle) * rr,
|
|
162
|
+
cy + Math.sin(angle) * rr * 1.05,
|
|
163
|
+
rand(rng, 0.5, 1.5),
|
|
164
|
+
color,
|
|
165
|
+
rand(rng, 0.4, 0.9),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const COSMOS_W = 512;
|
|
173
|
+
const COSMOS_H = 716;
|
|
174
|
+
|
|
175
|
+
const cosmosDefs = (extraDefs: string): string =>
|
|
176
|
+
gaussianBlurFilter("blur", 2.6, "-30%", "160%") + gaussianBlurFilter("glow", 2.4, "-200%", "500%") + extraDefs;
|
|
177
|
+
|
|
178
|
+
export const grainTexture = (seed: number): string =>
|
|
179
|
+
svgDocument(
|
|
180
|
+
200,
|
|
181
|
+
200,
|
|
182
|
+
`<filter id='grain' x='0%' y='0%' width='100%' height='100%'>` +
|
|
183
|
+
`<feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='${seed % 9973}' stitchTiles='stitch' result='n'/>` +
|
|
184
|
+
`<feColorMatrix in='n' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0' result='a'/>` +
|
|
185
|
+
`<feComponentTransfer in='a' result='m'><feFuncA type='discrete' tableValues='${discreteTable(0.28)}'/></feComponentTransfer>` +
|
|
186
|
+
`<feFlood flood-color='#ffffff' result='w'/>` +
|
|
187
|
+
`<feComposite in='w' in2='m' operator='in'/>` +
|
|
188
|
+
`</filter>`,
|
|
189
|
+
`<rect width='100%' height='100%' fill='#000000'/>` +
|
|
190
|
+
`<rect width='100%' height='100%' filter='url(#grain)' opacity='0.4'/>`,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
export const glitterTexture = (seed: number): string => {
|
|
194
|
+
const { defs, body } = speckleField("gl", seed, [
|
|
195
|
+
{ freq: 0.82, keep: 0.42, color: "#8f8f8f", opacity: 0.85 },
|
|
196
|
+
{ freq: 0.7, keep: 0.22, color: "#dcdcdc", opacity: 1 },
|
|
197
|
+
{ freq: 0.6, keep: 0.08, color: "#ffffff", opacity: 1 },
|
|
198
|
+
]);
|
|
199
|
+
const rng = mulberry32(seed + 99);
|
|
200
|
+
const flares = brightStars(rng, 240, 240, 16, "#glow");
|
|
201
|
+
return svgDocument(
|
|
202
|
+
240,
|
|
203
|
+
240,
|
|
204
|
+
`${gaussianBlurFilter("glow", 1.5, "-200%", "500%")}${defs}`,
|
|
205
|
+
`<rect width='100%' height='100%' fill='#050505'/>${body}${flares}`,
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const cosmosLayer = (
|
|
210
|
+
idBase: string,
|
|
211
|
+
seed: number,
|
|
212
|
+
layers: readonly SpeckleLayer[],
|
|
213
|
+
paint: (rng: () => number, body: string) => string,
|
|
214
|
+
): string => {
|
|
215
|
+
const rng = mulberry32(seed);
|
|
216
|
+
const { defs, body } = speckleField(idBase, seed, layers, false);
|
|
217
|
+
return svgDocument(COSMOS_W, COSMOS_H, cosmosDefs(defs), paint(rng, body));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const cosmosBottom = (seed: number): string =>
|
|
221
|
+
cosmosLayer(
|
|
222
|
+
"csb",
|
|
223
|
+
seed,
|
|
224
|
+
[
|
|
225
|
+
{ freq: 0.85, keep: 0.32, color: "#97a3c8", opacity: 0.8 },
|
|
226
|
+
{ freq: 0.72, keep: 0.13, color: "#ffffff", opacity: 0.95 },
|
|
227
|
+
{ freq: 0.76, keep: 0.05, color: "#9fb6ff", opacity: 0.85 },
|
|
228
|
+
{ freq: 0.78, keep: 0.04, color: "#ffc2d8", opacity: 0.8 },
|
|
229
|
+
],
|
|
230
|
+
(rng, body) => {
|
|
231
|
+
const clusters = clusterBlobs(
|
|
232
|
+
rng,
|
|
233
|
+
COSMOS_W,
|
|
234
|
+
COSMOS_H,
|
|
235
|
+
26,
|
|
236
|
+
["#465777", "#6d6088", "#9a7790", "#aab3cc", "#566f9e"],
|
|
237
|
+
"#blur",
|
|
238
|
+
[5, 14],
|
|
239
|
+
[0.18, 0.4],
|
|
240
|
+
);
|
|
241
|
+
const stars = brightStars(rng, COSMOS_W, COSMOS_H, 24, "#glow");
|
|
242
|
+
return `<rect width='100%' height='100%' fill='#04030c'/>${clusters}${body}${stars}`;
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const cosmosMiddle = (seed: number): string =>
|
|
247
|
+
cosmosLayer(
|
|
248
|
+
"csm",
|
|
249
|
+
seed,
|
|
250
|
+
[
|
|
251
|
+
{ freq: 0.66, keep: 0.2, color: "#241a4e", opacity: 0.95 },
|
|
252
|
+
{ freq: 0.58, keep: 0.11, color: "#4a2168", opacity: 0.9 },
|
|
253
|
+
{ freq: 0.52, keep: 0.055, color: "#7a1f6b", opacity: 0.85 },
|
|
254
|
+
{ freq: 0.5, keep: 0.03, color: "#c87a3a", opacity: 0.85 },
|
|
255
|
+
],
|
|
256
|
+
(rng, body) => {
|
|
257
|
+
const clusters = clusterBlobs(
|
|
258
|
+
rng,
|
|
259
|
+
COSMOS_W,
|
|
260
|
+
COSMOS_H,
|
|
261
|
+
16,
|
|
262
|
+
["#241a4e", "#3a2168", "#1a1430"],
|
|
263
|
+
"#blur",
|
|
264
|
+
[7, 18],
|
|
265
|
+
[0.45, 0.8],
|
|
266
|
+
);
|
|
267
|
+
return `${clusters}${body}`;
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const cosmosTop = (seed: number): string =>
|
|
272
|
+
cosmosLayer(
|
|
273
|
+
"cst",
|
|
274
|
+
seed,
|
|
275
|
+
[
|
|
276
|
+
{ freq: 0.62, keep: 0.06, color: "#6a6a76", opacity: 0.85 },
|
|
277
|
+
{ freq: 0.52, keep: 0.035, color: "#42424c", opacity: 0.9 },
|
|
278
|
+
],
|
|
279
|
+
(rng, body) => {
|
|
280
|
+
const rings = ringClusters(rng, COSMOS_W, COSMOS_H, 7, "#54545f");
|
|
281
|
+
return `${body}${rings}`;
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
export const generateTextures = (options: TextureOptions = {}): Textures => {
|
|
286
|
+
const seed = options.seed ?? DEFAULT_TEXTURE_SEED;
|
|
287
|
+
return {
|
|
288
|
+
grain: grainTexture(seed),
|
|
289
|
+
glitter: glitterTexture(seed + 1),
|
|
290
|
+
cosmosBottom: cosmosBottom(seed + 2),
|
|
291
|
+
cosmosMiddle: cosmosMiddle(seed + 3),
|
|
292
|
+
cosmosTop: cosmosTop(seed + 4),
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const TEXTURE_VARIABLES = {
|
|
297
|
+
grain: "--hc-grain",
|
|
298
|
+
glitter: "--hc-glitter",
|
|
299
|
+
cosmosBottom: "--hc-cosmos-bottom",
|
|
300
|
+
cosmosMiddle: "--hc-cosmos-middle",
|
|
301
|
+
cosmosTop: "--hc-cosmos-top",
|
|
302
|
+
} as const satisfies Record<keyof Textures, string>;
|
|
303
|
+
|
|
304
|
+
export const texturesToCssVariables = (textures: Textures): Record<string, string> => {
|
|
305
|
+
const vars: Record<string, string> = {};
|
|
306
|
+
for (const key of Object.keys(TEXTURE_VARIABLES) as (keyof Textures)[]) {
|
|
307
|
+
vars[TEXTURE_VARIABLES[key]] = `url("${textures[key]}")`;
|
|
308
|
+
}
|
|
309
|
+
return vars;
|
|
310
|
+
};
|
package/src/ticker.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const now = (): number => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
2
|
+
|
|
3
|
+
const raf: (cb: (time: number) => void) => void =
|
|
4
|
+
typeof requestAnimationFrame !== "undefined"
|
|
5
|
+
? (cb) => requestAnimationFrame(cb)
|
|
6
|
+
: (cb) => setTimeout(() => cb(now()), 1000 / 60);
|
|
7
|
+
|
|
8
|
+
interface Task {
|
|
9
|
+
c: (time: number) => boolean;
|
|
10
|
+
f: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TaskHandle {
|
|
14
|
+
promise: Promise<void>;
|
|
15
|
+
abort: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tasks = new Set<Task>();
|
|
19
|
+
|
|
20
|
+
const runTasks = (time: number): void => {
|
|
21
|
+
tasks.forEach((task) => {
|
|
22
|
+
if (!task.c(time)) {
|
|
23
|
+
tasks.delete(task);
|
|
24
|
+
task.f();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (tasks.size !== 0) {
|
|
28
|
+
raf(runTasks);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const loop = (callback: (time: number) => boolean): TaskHandle => {
|
|
33
|
+
let task: Task;
|
|
34
|
+
if (tasks.size === 0) {
|
|
35
|
+
raf(runTasks);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
promise: new Promise<void>((fulfil) => {
|
|
39
|
+
task = { c: callback, f: fulfil };
|
|
40
|
+
tasks.add(task);
|
|
41
|
+
}),
|
|
42
|
+
abort: () => {
|
|
43
|
+
tasks.delete(task);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type HoloEffect = "none" | "holo" | "reverse" | "cosmos" | "glitter";
|
|
2
|
+
|
|
3
|
+
export interface HoloCardOptions {
|
|
4
|
+
effect?: HoloEffect;
|
|
5
|
+
interactive?: boolean;
|
|
6
|
+
activateOnClick?: boolean;
|
|
7
|
+
gyroscope?: boolean;
|
|
8
|
+
showcase?: boolean;
|
|
9
|
+
glow?: string;
|
|
10
|
+
aspectRatio?: number;
|
|
11
|
+
textureSeed?: number;
|
|
12
|
+
mask?: string;
|
|
13
|
+
foil?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CreateHoloCardOptions extends HoloCardOptions {
|
|
17
|
+
image: string;
|
|
18
|
+
imageAlt?: string;
|
|
19
|
+
back?: string;
|
|
20
|
+
backAlt?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|