radiant-docs 0.1.41 → 0.1.42
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/package.json +1 -1
- package/template/astro.config.mjs +42 -40
- package/template/package-lock.json +7 -0
- package/template/package.json +1 -0
- package/template/src/components/Header.astro +150 -16
- package/template/src/components/MdxPage.astro +76 -22
- package/template/src/components/PagePagination.astro +44 -8
- package/template/src/components/Sidebar.astro +10 -1
- package/template/src/components/TableOfContents.astro +159 -53
- package/template/src/components/chat/AssistantDocsWidget.tsx +221 -8
- package/template/src/components/chat/AssistantEmbedPanel.tsx +1090 -104
- package/template/src/components/user/Accordion.astro +2 -2
- package/template/src/components/user/AccordionGroup.astro +1 -1
- package/template/src/components/user/Callout.astro +2 -2
- package/template/src/components/user/Card.astro +488 -0
- package/template/src/components/user/CardGradient.astro +964 -0
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/CodeGroup.astro +1 -1
- package/template/src/components/user/Column.astro +25 -0
- package/template/src/components/user/Columns.astro +200 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -1
- package/template/src/components/user/Image.astro +1 -1
- package/template/src/components/user/Step.astro +1 -1
- package/template/src/components/user/Steps.astro +1 -1
- package/template/src/components/user/Tab.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/layouts/Layout.astro +2 -4
- package/template/src/lib/assistant-chrome-defaults.ts +12 -0
- package/template/src/lib/assistant-embed-script.ts +209 -18
- package/template/src/lib/validation.ts +325 -75
- package/template/src/styles/global.css +81 -4
- package/template/src/components/chat/AskAiWidget.tsx +0 -2011
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Icon from "../ui/Icon.astro";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
title: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
interactive?: boolean;
|
|
9
|
+
glass?: boolean;
|
|
10
|
+
colors?: string[];
|
|
11
|
+
patternSeed?: string;
|
|
12
|
+
colorSeed?: string;
|
|
13
|
+
useThemeColor?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
title,
|
|
18
|
+
icon,
|
|
19
|
+
text,
|
|
20
|
+
interactive = false,
|
|
21
|
+
glass = false,
|
|
22
|
+
colors,
|
|
23
|
+
patternSeed,
|
|
24
|
+
colorSeed,
|
|
25
|
+
useThemeColor = false,
|
|
26
|
+
} = Astro.props as Props;
|
|
27
|
+
|
|
28
|
+
const DEFAULT_COVER_COLORS = ["#fb7185", "#fb923c", "#fcd34d", "#f9a8d4"];
|
|
29
|
+
|
|
30
|
+
function hashString(value: string): number {
|
|
31
|
+
let hash = 2166136261;
|
|
32
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
33
|
+
hash ^= value.charCodeAt(index);
|
|
34
|
+
hash = Math.imul(hash, 16777619);
|
|
35
|
+
}
|
|
36
|
+
return hash >>> 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function seededRatio(seed: number, salt: number): number {
|
|
40
|
+
let value = seed ^ Math.imul(salt, 0x9e3779b1);
|
|
41
|
+
value ^= value >>> 16;
|
|
42
|
+
value = Math.imul(value, 0x85ebca6b);
|
|
43
|
+
value ^= value >>> 13;
|
|
44
|
+
value = Math.imul(value, 0xc2b2ae35);
|
|
45
|
+
value ^= value >>> 16;
|
|
46
|
+
return (value >>> 0) / 4294967295;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function range(seed: number, salt: number, min: number, max: number): number {
|
|
50
|
+
return min + (max - min) * seededRatio(seed, salt);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clamp(value: number, min: number, max: number): number {
|
|
54
|
+
return Math.min(Math.max(value, min), max);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hue(value: number): number {
|
|
58
|
+
return ((value % 360) + 360) % 360;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseRgb(value: string): { r: number; g: number; b: number } | null {
|
|
62
|
+
const hex = value
|
|
63
|
+
.trim()
|
|
64
|
+
.match(/^#([a-f\d]{3}|[a-f\d]{4}|[a-f\d]{6}|[a-f\d]{8})$/i);
|
|
65
|
+
if (hex?.[1]) {
|
|
66
|
+
const raw =
|
|
67
|
+
hex[1].length === 3 || hex[1].length === 4
|
|
68
|
+
? hex[1]
|
|
69
|
+
.slice(0, 3)
|
|
70
|
+
.split("")
|
|
71
|
+
.map((part) => `${part}${part}`)
|
|
72
|
+
.join("")
|
|
73
|
+
: hex[1].slice(0, 6);
|
|
74
|
+
return {
|
|
75
|
+
r: Number.parseInt(raw.slice(0, 2), 16),
|
|
76
|
+
g: Number.parseInt(raw.slice(2, 4), 16),
|
|
77
|
+
b: Number.parseInt(raw.slice(4, 6), 16),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }): {
|
|
85
|
+
h: number;
|
|
86
|
+
s: number;
|
|
87
|
+
l: number;
|
|
88
|
+
} {
|
|
89
|
+
const red = r / 255;
|
|
90
|
+
const green = g / 255;
|
|
91
|
+
const blue = b / 255;
|
|
92
|
+
const max = Math.max(red, green, blue);
|
|
93
|
+
const min = Math.min(red, green, blue);
|
|
94
|
+
const lightness = (max + min) / 2;
|
|
95
|
+
const delta = max - min;
|
|
96
|
+
|
|
97
|
+
if (delta === 0) return { h: 0, s: 0, l: lightness * 100 };
|
|
98
|
+
|
|
99
|
+
const saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
|
100
|
+
let hueValue = 0;
|
|
101
|
+
|
|
102
|
+
if (max === red) {
|
|
103
|
+
hueValue = ((green - blue) / delta) % 6;
|
|
104
|
+
} else if (max === green) {
|
|
105
|
+
hueValue = (blue - red) / delta + 2;
|
|
106
|
+
} else {
|
|
107
|
+
hueValue = (red - green) / delta + 4;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
h: hue(hueValue * 60),
|
|
112
|
+
s: saturation * 100,
|
|
113
|
+
l: lightness * 100,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hslColor(h: number, s: number, l: number): string {
|
|
118
|
+
return `hsl(${Math.round(hue(h))}, ${Math.round(
|
|
119
|
+
clamp(s, 0, 100),
|
|
120
|
+
)}%, ${Math.round(clamp(l, 0, 100))}%)`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hueDistance(first: number, second: number): number {
|
|
124
|
+
const distance = Math.abs(hue(first) - hue(second));
|
|
125
|
+
return Math.min(distance, 360 - distance);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type PaletteRecipe = {
|
|
129
|
+
offsets: [number, number, number];
|
|
130
|
+
jitter: number;
|
|
131
|
+
saturation: [number, number, number];
|
|
132
|
+
lightness: [number, number, number];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const DEFAULT_PALETTE_RECIPE: PaletteRecipe = {
|
|
136
|
+
offsets: [-32, 28, 54],
|
|
137
|
+
jitter: 14,
|
|
138
|
+
saturation: [1.02, 0.96, 0.92],
|
|
139
|
+
lightness: [8, 0, 10],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const SEEDED_PALETTE_RECIPES: PaletteRecipe[] = [
|
|
143
|
+
DEFAULT_PALETTE_RECIPE,
|
|
144
|
+
{
|
|
145
|
+
offsets: [142, 190, -26],
|
|
146
|
+
jitter: 22,
|
|
147
|
+
saturation: [0.88, 0.8, 1.04],
|
|
148
|
+
lightness: [10, 14, 4],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
offsets: [118, 238, 34],
|
|
152
|
+
jitter: 18,
|
|
153
|
+
saturation: [0.9, 0.86, 1.06],
|
|
154
|
+
lightness: [9, 8, 1],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
offsets: [20, 46, -20],
|
|
158
|
+
jitter: 20,
|
|
159
|
+
saturation: [1.08, 0.98, 1.04],
|
|
160
|
+
lightness: [5, 12, 2],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
offsets: [164, 210, 30],
|
|
164
|
+
jitter: 20,
|
|
165
|
+
saturation: [0.86, 0.78, 1.02],
|
|
166
|
+
lightness: [12, 16, 3],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
offsets: [176, 32, 220],
|
|
170
|
+
jitter: 26,
|
|
171
|
+
saturation: [0.64, 0.72, 0.62],
|
|
172
|
+
lightness: [18, 14, 20],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
offsets: [88, 178, 274],
|
|
176
|
+
jitter: 24,
|
|
177
|
+
saturation: [1.16, 1.04, 1.08],
|
|
178
|
+
lightness: [2, 8, 4],
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
function getPaletteRecipe(seed: number, hasSeedValue: boolean): PaletteRecipe {
|
|
183
|
+
if (!hasSeedValue) return DEFAULT_PALETTE_RECIPE;
|
|
184
|
+
return SEEDED_PALETTE_RECIPES[seed % SEEDED_PALETTE_RECIPES.length];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getGeneratedPalette(seedSource: string, baseColor: string): string[] {
|
|
188
|
+
const baseRgb = parseRgb(baseColor) ?? { r: 31, g: 111, b: 235 };
|
|
189
|
+
const preferred = rgbToHsl(baseRgb);
|
|
190
|
+
const isNeutralPreference =
|
|
191
|
+
preferred.s < 12 || preferred.l < 16 || preferred.l > 88;
|
|
192
|
+
const colorSeed = hashString(seedSource);
|
|
193
|
+
const hasSeedValue = seedSource.includes("::");
|
|
194
|
+
const recipe = getPaletteRecipe(colorSeed, hasSeedValue);
|
|
195
|
+
const baseHue = isNeutralPreference
|
|
196
|
+
? range(colorSeed, 20, 8, 348)
|
|
197
|
+
: preferred.h;
|
|
198
|
+
const baseSaturation = isNeutralPreference
|
|
199
|
+
? range(colorSeed, 21, 62, 78)
|
|
200
|
+
: preferred.s;
|
|
201
|
+
const baseLightness = isNeutralPreference
|
|
202
|
+
? range(colorSeed, 22, 56, 68)
|
|
203
|
+
: preferred.l;
|
|
204
|
+
const usedHues = [baseHue];
|
|
205
|
+
|
|
206
|
+
return recipe.offsets.map((offset, index) => {
|
|
207
|
+
let colorHue =
|
|
208
|
+
baseHue +
|
|
209
|
+
offset +
|
|
210
|
+
range(colorSeed, 30 + index, -recipe.jitter, recipe.jitter);
|
|
211
|
+
|
|
212
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
213
|
+
if (usedHues.every((usedHue) => hueDistance(colorHue, usedHue) >= 18)) {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
colorHue += range(colorSeed, 40 + index + attempt, 28, 58);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
usedHues.push(colorHue);
|
|
220
|
+
return hslColor(
|
|
221
|
+
colorHue,
|
|
222
|
+
baseSaturation * recipe.saturation[index] +
|
|
223
|
+
range(colorSeed, 50 + index, -9, 9),
|
|
224
|
+
62 +
|
|
225
|
+
(baseLightness - 50) * 0.18 +
|
|
226
|
+
recipe.lightness[index] +
|
|
227
|
+
range(colorSeed, 60 + index, -6, 6),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function completePalette(
|
|
233
|
+
inputColors?: string[],
|
|
234
|
+
seedValue?: string,
|
|
235
|
+
): string[] | undefined {
|
|
236
|
+
const palette = inputColors
|
|
237
|
+
?.map((color) => color.trim())
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.slice(0, 4);
|
|
240
|
+
if (!palette?.length) return undefined;
|
|
241
|
+
if (palette.length === 4) return palette;
|
|
242
|
+
|
|
243
|
+
const seedSource = seedValue
|
|
244
|
+
? `${palette.join("|")}::${seedValue}`
|
|
245
|
+
: palette.join("|");
|
|
246
|
+
const generated = getGeneratedPalette(seedSource, palette[0]);
|
|
247
|
+
for (const color of generated) {
|
|
248
|
+
if (palette.length === 4) break;
|
|
249
|
+
palette.push(color);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return palette;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const patternSeedSource = patternSeed ? `${title}::${patternSeed}` : title;
|
|
256
|
+
const seed = hashString(patternSeedSource);
|
|
257
|
+
const explicitPalette = completePalette(colors, colorSeed);
|
|
258
|
+
const defaultPalette =
|
|
259
|
+
colorSeed !== undefined
|
|
260
|
+
? completePalette([DEFAULT_COVER_COLORS[0]], colorSeed)
|
|
261
|
+
: DEFAULT_COVER_COLORS;
|
|
262
|
+
const staticPalette =
|
|
263
|
+
explicitPalette ?? (useThemeColor ? undefined : defaultPalette);
|
|
264
|
+
const gradientColors = staticPalette ?? [
|
|
265
|
+
"color-mix(in oklab, var(--color-theme) 62%, #fda4af)",
|
|
266
|
+
"color-mix(in oklab, var(--color-theme) 48%, #93c5fd)",
|
|
267
|
+
"color-mix(in oklab, var(--color-theme) 56%, #f0abfc)",
|
|
268
|
+
"color-mix(in oklab, var(--color-theme) 58%, #60a5fa)",
|
|
269
|
+
];
|
|
270
|
+
const gradientStyle = [
|
|
271
|
+
`--rd-card-gradient-angle: ${Math.round(range(seed, 1, 112, 154))}deg`,
|
|
272
|
+
`--rd-card-gradient-x: ${Math.round(range(seed, 2, 12, 88))}%`,
|
|
273
|
+
`--rd-card-gradient-y: ${Math.round(range(seed, 3, 4, 44))}%`,
|
|
274
|
+
`--rd-card-gradient-x-2: ${Math.round(range(seed, 4, 32, 92))}%`,
|
|
275
|
+
`--rd-card-gradient-y-2: ${Math.round(range(seed, 5, 54, 96))}%`,
|
|
276
|
+
`--rd-card-gradient-color-1: ${gradientColors[0]}`,
|
|
277
|
+
`--rd-card-gradient-color-2: ${gradientColors[1]}`,
|
|
278
|
+
`--rd-card-gradient-color-3: ${gradientColors[2]}`,
|
|
279
|
+
`--rd-card-gradient-color-4: ${gradientColors[3]}`,
|
|
280
|
+
].join("; ");
|
|
281
|
+
const gradientColorData = staticPalette
|
|
282
|
+
? JSON.stringify(staticPalette)
|
|
283
|
+
: undefined;
|
|
284
|
+
const isIconOnly = Boolean(icon && !text);
|
|
285
|
+
const textSizeClass = icon ? "text-lg sm:text-xl" : "text-xl sm:text-2xl";
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
<div
|
|
289
|
+
class="rd-card-gradient-frame relative isolate flex min-h-44 w-full items-center justify-center overflow-hidden rounded-xl bg-transparent"
|
|
290
|
+
>
|
|
291
|
+
<div
|
|
292
|
+
class="rd-card-gradient-scale absolute inset-0 flex items-center justify-center"
|
|
293
|
+
>
|
|
294
|
+
<div
|
|
295
|
+
data-rd-card-gradient
|
|
296
|
+
data-rd-card-motion={interactive ? "true" : "false"}
|
|
297
|
+
data-rd-card-seed={patternSeedSource}
|
|
298
|
+
data-rd-card-color-seed={colorSeed}
|
|
299
|
+
data-rd-card-colors={gradientColorData}
|
|
300
|
+
style={gradientStyle}
|
|
301
|
+
class="rd-card-gradient absolute inset-0 isolate overflow-hidden bg-transparent"
|
|
302
|
+
>
|
|
303
|
+
<div class="rd-card-gradient-fallback absolute inset-0 -z-20"></div>
|
|
304
|
+
<div
|
|
305
|
+
class="pointer-events-none absolute inset-0 z-0 bg-[linear-gradient(120deg,rgba(255,255,255,0.36),rgba(255,255,255,0.06)_38%,rgba(255,255,255,0.18)_72%,rgba(255,255,255,0.04))]"
|
|
306
|
+
>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
<div
|
|
310
|
+
class:list={[
|
|
311
|
+
"rd-card-gradient-content relative z-10 flex flex-col items-center justify-center gap-2 px-5 text-center text-white drop-shadow-[0_1px_10px_rgba(0,0,0,0.18)]",
|
|
312
|
+
glass && "rd-card-gradient-content-glass rounded-2xl",
|
|
313
|
+
glass && isIconOnly && "rd-card-gradient-content-glass-icon",
|
|
314
|
+
]}
|
|
315
|
+
>
|
|
316
|
+
{icon && <Icon name={icon} class="size-9 text-white/70" />}
|
|
317
|
+
{
|
|
318
|
+
text && (
|
|
319
|
+
<span
|
|
320
|
+
class:list={[
|
|
321
|
+
"max-w-full wrap-break-word font-semibold leading-tight text-white/70",
|
|
322
|
+
textSizeClass,
|
|
323
|
+
]}
|
|
324
|
+
>
|
|
325
|
+
{text}
|
|
326
|
+
</span>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<script>
|
|
334
|
+
type ShaderModule = typeof import("@paper-design/shaders");
|
|
335
|
+
type ShaderMountInstance = InstanceType<ShaderModule["ShaderMount"]>;
|
|
336
|
+
type GradientState = {
|
|
337
|
+
mount: ShaderMountInstance;
|
|
338
|
+
hoverSpeed: number;
|
|
339
|
+
motionBound: boolean;
|
|
340
|
+
};
|
|
341
|
+
type CardGradientWindow = Window & {
|
|
342
|
+
__rdCardGradientListenersBound?: boolean;
|
|
343
|
+
__rdCardGradientPending?: WeakSet<HTMLElement>;
|
|
344
|
+
__rdCardGradientRegistry?: WeakMap<HTMLElement, GradientState>;
|
|
345
|
+
__rdCardShaderModulePromise?: Promise<ShaderModule>;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const cardWindow = window as CardGradientWindow;
|
|
349
|
+
const registry = (cardWindow.__rdCardGradientRegistry ??= new WeakMap());
|
|
350
|
+
const pending = (cardWindow.__rdCardGradientPending ??= new WeakSet());
|
|
351
|
+
const CARD_GRADIENT_GRAIN_MIXER = 0.075;
|
|
352
|
+
const CARD_GRADIENT_GRAIN_OVERLAY = 0.055;
|
|
353
|
+
const CARD_GRADIENT_HOVER_SPEED = 0.32;
|
|
354
|
+
|
|
355
|
+
function loadShaders(): Promise<ShaderModule> {
|
|
356
|
+
return (cardWindow.__rdCardShaderModulePromise ??=
|
|
357
|
+
import("@paper-design/shaders"));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function hashString(value: string): number {
|
|
361
|
+
let hash = 2166136261;
|
|
362
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
363
|
+
hash ^= value.charCodeAt(index);
|
|
364
|
+
hash = Math.imul(hash, 16777619);
|
|
365
|
+
}
|
|
366
|
+
return hash >>> 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function seededRatio(seed: number, salt: number): number {
|
|
370
|
+
let value = seed ^ Math.imul(salt, 0x9e3779b1);
|
|
371
|
+
value ^= value >>> 16;
|
|
372
|
+
value = Math.imul(value, 0x85ebca6b);
|
|
373
|
+
value ^= value >>> 13;
|
|
374
|
+
value = Math.imul(value, 0xc2b2ae35);
|
|
375
|
+
value ^= value >>> 16;
|
|
376
|
+
return (value >>> 0) / 4294967295;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function range(seed: number, salt: number, min: number, max: number): number {
|
|
380
|
+
return min + (max - min) * seededRatio(seed, salt);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function clamp(value: number, min: number, max: number): number {
|
|
384
|
+
return Math.min(Math.max(value, min), max);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function hue(value: number): number {
|
|
388
|
+
return ((value % 360) + 360) % 360;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function parseRgb(value: string): { r: number; g: number; b: number } | null {
|
|
392
|
+
const hex = value
|
|
393
|
+
.trim()
|
|
394
|
+
.match(/^#([a-f\d]{3}|[a-f\d]{4}|[a-f\d]{6}|[a-f\d]{8})$/i);
|
|
395
|
+
if (hex?.[1]) {
|
|
396
|
+
const raw =
|
|
397
|
+
hex[1].length === 3 || hex[1].length === 4
|
|
398
|
+
? hex[1]
|
|
399
|
+
.slice(0, 3)
|
|
400
|
+
.split("")
|
|
401
|
+
.map((part) => `${part}${part}`)
|
|
402
|
+
.join("")
|
|
403
|
+
: hex[1].slice(0, 6);
|
|
404
|
+
return {
|
|
405
|
+
r: Number.parseInt(raw.slice(0, 2), 16),
|
|
406
|
+
g: Number.parseInt(raw.slice(2, 4), 16),
|
|
407
|
+
b: Number.parseInt(raw.slice(4, 6), 16),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const rgb = value
|
|
412
|
+
.trim()
|
|
413
|
+
.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i);
|
|
414
|
+
if (!rgb) return null;
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
r: Number.parseFloat(rgb[1] ?? "0"),
|
|
418
|
+
g: Number.parseFloat(rgb[2] ?? "0"),
|
|
419
|
+
b: Number.parseFloat(rgb[3] ?? "0"),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }): {
|
|
424
|
+
h: number;
|
|
425
|
+
s: number;
|
|
426
|
+
l: number;
|
|
427
|
+
} {
|
|
428
|
+
const red = r / 255;
|
|
429
|
+
const green = g / 255;
|
|
430
|
+
const blue = b / 255;
|
|
431
|
+
const max = Math.max(red, green, blue);
|
|
432
|
+
const min = Math.min(red, green, blue);
|
|
433
|
+
const lightness = (max + min) / 2;
|
|
434
|
+
const delta = max - min;
|
|
435
|
+
|
|
436
|
+
if (delta === 0) return { h: 0, s: 0, l: lightness * 100 };
|
|
437
|
+
|
|
438
|
+
const saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
|
439
|
+
let hueValue = 0;
|
|
440
|
+
|
|
441
|
+
if (max === red) {
|
|
442
|
+
hueValue = ((green - blue) / delta) % 6;
|
|
443
|
+
} else if (max === green) {
|
|
444
|
+
hueValue = (blue - red) / delta + 2;
|
|
445
|
+
} else {
|
|
446
|
+
hueValue = (red - green) / delta + 4;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
h: hue(hueValue * 60),
|
|
451
|
+
s: saturation * 100,
|
|
452
|
+
l: lightness * 100,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function hslColor(h: number, s: number, l: number): string {
|
|
457
|
+
return `hsl(${Math.round(hue(h))}, ${Math.round(
|
|
458
|
+
clamp(s, 0, 100),
|
|
459
|
+
)}%, ${Math.round(clamp(l, 0, 100))}%)`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function hueDistance(first: number, second: number): number {
|
|
463
|
+
const distance = Math.abs(hue(first) - hue(second));
|
|
464
|
+
return Math.min(distance, 360 - distance);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
type PaletteRecipe = {
|
|
468
|
+
offsets: [number, number, number];
|
|
469
|
+
jitter: number;
|
|
470
|
+
saturation: [number, number, number];
|
|
471
|
+
lightness: [number, number, number];
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const DEFAULT_PALETTE_RECIPE: PaletteRecipe = {
|
|
475
|
+
offsets: [-32, 28, 54],
|
|
476
|
+
jitter: 14,
|
|
477
|
+
saturation: [1.02, 0.96, 0.92],
|
|
478
|
+
lightness: [8, 0, 10],
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const SEEDED_PALETTE_RECIPES: PaletteRecipe[] = [
|
|
482
|
+
DEFAULT_PALETTE_RECIPE,
|
|
483
|
+
{
|
|
484
|
+
offsets: [142, 190, -26],
|
|
485
|
+
jitter: 22,
|
|
486
|
+
saturation: [0.88, 0.8, 1.04],
|
|
487
|
+
lightness: [10, 14, 4],
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
offsets: [118, 238, 34],
|
|
491
|
+
jitter: 18,
|
|
492
|
+
saturation: [0.9, 0.86, 1.06],
|
|
493
|
+
lightness: [9, 8, 1],
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
offsets: [20, 46, -20],
|
|
497
|
+
jitter: 20,
|
|
498
|
+
saturation: [1.08, 0.98, 1.04],
|
|
499
|
+
lightness: [5, 12, 2],
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
offsets: [164, 210, 30],
|
|
503
|
+
jitter: 20,
|
|
504
|
+
saturation: [0.86, 0.78, 1.02],
|
|
505
|
+
lightness: [12, 16, 3],
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
offsets: [176, 32, 220],
|
|
509
|
+
jitter: 26,
|
|
510
|
+
saturation: [0.64, 0.72, 0.62],
|
|
511
|
+
lightness: [18, 14, 20],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
offsets: [88, 178, 274],
|
|
515
|
+
jitter: 24,
|
|
516
|
+
saturation: [1.16, 1.04, 1.08],
|
|
517
|
+
lightness: [2, 8, 4],
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
function getPaletteRecipe(
|
|
522
|
+
seed: number,
|
|
523
|
+
hasSeedValue: boolean,
|
|
524
|
+
): PaletteRecipe {
|
|
525
|
+
if (!hasSeedValue) return DEFAULT_PALETTE_RECIPE;
|
|
526
|
+
return SEEDED_PALETTE_RECIPES[seed % SEEDED_PALETTE_RECIPES.length];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function getGeneratedPalette(
|
|
530
|
+
seedSource: string,
|
|
531
|
+
baseColor: string,
|
|
532
|
+
): string[] {
|
|
533
|
+
const baseRgb = parseRgb(baseColor) ?? { r: 31, g: 111, b: 235 };
|
|
534
|
+
const preferred = rgbToHsl(baseRgb);
|
|
535
|
+
const isNeutralPreference =
|
|
536
|
+
preferred.s < 12 || preferred.l < 16 || preferred.l > 88;
|
|
537
|
+
const colorSeed = hashString(seedSource);
|
|
538
|
+
const hasSeedValue = seedSource.includes("::");
|
|
539
|
+
const recipe = getPaletteRecipe(colorSeed, hasSeedValue);
|
|
540
|
+
const baseHue = isNeutralPreference
|
|
541
|
+
? range(colorSeed, 20, 8, 348)
|
|
542
|
+
: preferred.h;
|
|
543
|
+
const baseSaturation = isNeutralPreference
|
|
544
|
+
? range(colorSeed, 21, 62, 78)
|
|
545
|
+
: preferred.s;
|
|
546
|
+
const baseLightness = isNeutralPreference
|
|
547
|
+
? range(colorSeed, 22, 56, 68)
|
|
548
|
+
: preferred.l;
|
|
549
|
+
const usedHues = [baseHue];
|
|
550
|
+
|
|
551
|
+
return recipe.offsets.map((offset, index) => {
|
|
552
|
+
let colorHue =
|
|
553
|
+
baseHue +
|
|
554
|
+
offset +
|
|
555
|
+
range(colorSeed, 30 + index, -recipe.jitter, recipe.jitter);
|
|
556
|
+
|
|
557
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
558
|
+
if (usedHues.every((usedHue) => hueDistance(colorHue, usedHue) >= 18)) {
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
colorHue += range(colorSeed, 40 + index + attempt, 28, 58);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
usedHues.push(colorHue);
|
|
565
|
+
return hslColor(
|
|
566
|
+
colorHue,
|
|
567
|
+
baseSaturation * recipe.saturation[index] +
|
|
568
|
+
range(colorSeed, 50 + index, -9, 9),
|
|
569
|
+
62 +
|
|
570
|
+
(baseLightness - 50) * 0.18 +
|
|
571
|
+
recipe.lightness[index] +
|
|
572
|
+
range(colorSeed, 60 + index, -6, 6),
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function completePalette(
|
|
578
|
+
inputColors: string[],
|
|
579
|
+
seedValue?: string,
|
|
580
|
+
): string[] | null {
|
|
581
|
+
const palette = inputColors
|
|
582
|
+
.map((color) => color.trim())
|
|
583
|
+
.filter(Boolean)
|
|
584
|
+
.slice(0, 4);
|
|
585
|
+
if (palette.length === 0) return null;
|
|
586
|
+
if (palette.length === 4) return palette;
|
|
587
|
+
|
|
588
|
+
const seedSource = seedValue
|
|
589
|
+
? `${palette.join("|")}::${seedValue}`
|
|
590
|
+
: palette.join("|");
|
|
591
|
+
const generated = getGeneratedPalette(seedSource, palette[0] ?? "");
|
|
592
|
+
for (const color of generated) {
|
|
593
|
+
if (palette.length === 4) break;
|
|
594
|
+
palette.push(color);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return palette;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function getElementPalette(element: HTMLElement): string[] | null {
|
|
601
|
+
const rawColors = element.dataset.rdCardColors;
|
|
602
|
+
if (!rawColors) return null;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const parsedColors = JSON.parse(rawColors);
|
|
606
|
+
if (
|
|
607
|
+
Array.isArray(parsedColors) &&
|
|
608
|
+
parsedColors.every((color) => typeof color === "string")
|
|
609
|
+
) {
|
|
610
|
+
return completePalette(parsedColors, element.dataset.rdCardColorSeed);
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function getThemeRgb(): { r: number; g: number; b: number } {
|
|
620
|
+
const themeColor = getComputedStyle(document.documentElement)
|
|
621
|
+
.getPropertyValue("--color-theme")
|
|
622
|
+
.trim();
|
|
623
|
+
return parseRgb(themeColor) ?? { r: 31, g: 111, b: 235 };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function rgbToHex({ r, g, b }: { r: number; g: number; b: number }): string {
|
|
627
|
+
const toHex = (channel: number) =>
|
|
628
|
+
Math.round(clamp(channel, 0, 255))
|
|
629
|
+
.toString(16)
|
|
630
|
+
.padStart(2, "0");
|
|
631
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function getThemePalette(element: HTMLElement): string[] {
|
|
635
|
+
const themeRgb = getThemeRgb();
|
|
636
|
+
const seedSource = element.dataset.rdCardColorSeed?.trim();
|
|
637
|
+
return (
|
|
638
|
+
completePalette([rgbToHex(themeRgb)], seedSource) ?? [
|
|
639
|
+
"#fb7185",
|
|
640
|
+
"#fb923c",
|
|
641
|
+
"#fcd34d",
|
|
642
|
+
"#f9a8d4",
|
|
643
|
+
]
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function getPalette(element: HTMLElement): string[] {
|
|
648
|
+
return getElementPalette(element) ?? getThemePalette(element);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function getUniforms(element: HTMLElement, shaders: ShaderModule) {
|
|
652
|
+
const seed = hashString(element.dataset.rdCardSeed ?? "");
|
|
653
|
+
const colors = getPalette(element);
|
|
654
|
+
|
|
655
|
+
return {
|
|
656
|
+
u_colors: colors.map(shaders.getShaderColorFromString),
|
|
657
|
+
u_colorsCount: colors.length,
|
|
658
|
+
u_distortion: range(seed, 30, 0.68, 0.86),
|
|
659
|
+
u_swirl: range(seed, 31, 0.08, 0.2),
|
|
660
|
+
u_grainMixer: CARD_GRADIENT_GRAIN_MIXER,
|
|
661
|
+
u_grainOverlay: CARD_GRADIENT_GRAIN_OVERLAY,
|
|
662
|
+
u_fit: shaders.ShaderFitOptions.cover,
|
|
663
|
+
u_scale: range(seed, 34, 1.08, 1.2),
|
|
664
|
+
u_rotation: range(seed, 35, -8, 8),
|
|
665
|
+
u_offsetX: range(seed, 36, -0.08, 0.08),
|
|
666
|
+
u_offsetY: range(seed, 37, -0.06, 0.06),
|
|
667
|
+
u_originX: 0.5,
|
|
668
|
+
u_originY: 0.5,
|
|
669
|
+
u_worldWidth: 0,
|
|
670
|
+
u_worldHeight: 0,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function bindMotion(element: HTMLElement, state: GradientState): void {
|
|
675
|
+
if (state.motionBound || element.dataset.rdCardMotion !== "true") return;
|
|
676
|
+
|
|
677
|
+
const root = element.closest<HTMLElement>("[data-rd-card-root]");
|
|
678
|
+
if (!root) return;
|
|
679
|
+
|
|
680
|
+
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
681
|
+
const start = () => {
|
|
682
|
+
if (!reducedMotion.matches) state.mount.setSpeed(state.hoverSpeed);
|
|
683
|
+
};
|
|
684
|
+
const stop = () => state.mount.setSpeed(0);
|
|
685
|
+
|
|
686
|
+
root.addEventListener("pointerenter", start);
|
|
687
|
+
root.addEventListener("pointerleave", stop);
|
|
688
|
+
root.addEventListener("focusin", start);
|
|
689
|
+
root.addEventListener("focusout", (event) => {
|
|
690
|
+
if (
|
|
691
|
+
event.relatedTarget instanceof Node &&
|
|
692
|
+
root.contains(event.relatedTarget)
|
|
693
|
+
) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
stop();
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
state.motionBound = true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async function mountGradient(element: HTMLElement): Promise<void> {
|
|
703
|
+
const existing = registry.get(element);
|
|
704
|
+
if (existing) {
|
|
705
|
+
const shaders = await loadShaders();
|
|
706
|
+
existing.mount.setUniforms(getUniforms(element, shaders));
|
|
707
|
+
element.dataset.rdCardShaderReady = "true";
|
|
708
|
+
bindMotion(element, existing);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (pending.has(element)) return;
|
|
713
|
+
pending.add(element);
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const shaders = await loadShaders();
|
|
717
|
+
if (registry.has(element)) return;
|
|
718
|
+
|
|
719
|
+
const seed = hashString(element.dataset.rdCardSeed ?? "");
|
|
720
|
+
const mount = new shaders.ShaderMount(
|
|
721
|
+
element,
|
|
722
|
+
shaders.meshGradientFragmentShader,
|
|
723
|
+
getUniforms(element, shaders),
|
|
724
|
+
{ alpha: true, antialias: true },
|
|
725
|
+
0,
|
|
726
|
+
Math.round(range(seed, 40, 4000, 32000)),
|
|
727
|
+
1.35,
|
|
728
|
+
900000,
|
|
729
|
+
);
|
|
730
|
+
requestAnimationFrame(() => {
|
|
731
|
+
requestAnimationFrame(() => {
|
|
732
|
+
if (registry.has(element)) {
|
|
733
|
+
element.dataset.rdCardShaderReady = "true";
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
const state = {
|
|
738
|
+
mount,
|
|
739
|
+
hoverSpeed: CARD_GRADIENT_HOVER_SPEED,
|
|
740
|
+
motionBound: false,
|
|
741
|
+
};
|
|
742
|
+
registry.set(element, state);
|
|
743
|
+
bindMotion(element, state);
|
|
744
|
+
} catch {
|
|
745
|
+
element.dataset.rdCardShaderUnavailable = "true";
|
|
746
|
+
} finally {
|
|
747
|
+
pending.delete(element);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function initCardGradients(): void {
|
|
752
|
+
const elements = document.querySelectorAll<HTMLElement>(
|
|
753
|
+
"[data-rd-card-gradient]",
|
|
754
|
+
);
|
|
755
|
+
if (elements.length === 0) return;
|
|
756
|
+
elements.forEach((element) => {
|
|
757
|
+
void mountGradient(element);
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
initCardGradients();
|
|
762
|
+
|
|
763
|
+
if (!cardWindow.__rdCardGradientListenersBound) {
|
|
764
|
+
document.addEventListener("astro:page-load", initCardGradients);
|
|
765
|
+
document.addEventListener("astro:after-swap", initCardGradients);
|
|
766
|
+
|
|
767
|
+
const observer = new MutationObserver(initCardGradients);
|
|
768
|
+
observer.observe(document.documentElement, {
|
|
769
|
+
attributes: true,
|
|
770
|
+
attributeFilter: ["class", "data-theme", "style"],
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
cardWindow.__rdCardGradientListenersBound = true;
|
|
774
|
+
}
|
|
775
|
+
</script>
|
|
776
|
+
|
|
777
|
+
<style>
|
|
778
|
+
.rd-card-gradient-frame::after {
|
|
779
|
+
content: "";
|
|
780
|
+
position: absolute;
|
|
781
|
+
inset: 0;
|
|
782
|
+
z-index: 30;
|
|
783
|
+
pointer-events: none;
|
|
784
|
+
border-radius: inherit;
|
|
785
|
+
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 30%);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.rd-card-gradient :global(canvas) {
|
|
789
|
+
opacity: 0;
|
|
790
|
+
filter: blur(8px) saturate(0.94) brightness(1.02);
|
|
791
|
+
transform: scale(1.018);
|
|
792
|
+
transform-origin: center;
|
|
793
|
+
transition:
|
|
794
|
+
opacity 2200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
795
|
+
filter 2200ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
796
|
+
transform 2400ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
797
|
+
will-change: opacity, filter, transform;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.rd-card-gradient[data-rd-card-shader-ready="true"] :global(canvas) {
|
|
801
|
+
opacity: 0.92;
|
|
802
|
+
filter: blur(0) saturate(1) brightness(1);
|
|
803
|
+
transform: scale(1);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.rd-card-gradient-content {
|
|
807
|
+
max-width: calc(100% - 2rem);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.rd-card-gradient-content-glass {
|
|
811
|
+
isolation: isolate;
|
|
812
|
+
min-width: min(8.5rem, calc(100% - 2rem));
|
|
813
|
+
padding: 0.85rem 1.25rem;
|
|
814
|
+
overflow: hidden;
|
|
815
|
+
border: 1px solid rgb(255 255 255 / 22%);
|
|
816
|
+
background: linear-gradient(
|
|
817
|
+
180deg,
|
|
818
|
+
rgb(255 255 255 / 18%),
|
|
819
|
+
rgb(255 255 255 / 10%)
|
|
820
|
+
);
|
|
821
|
+
box-shadow:
|
|
822
|
+
inset 0 1px 0 rgb(255 255 255 / 20%),
|
|
823
|
+
inset 0 0 0 1px rgb(255 255 255 / 6%),
|
|
824
|
+
0 10px 26px rgb(0 0 0 / 5%);
|
|
825
|
+
filter: drop-shadow(0 1px 6px rgb(255 255 255 / 10%));
|
|
826
|
+
backdrop-filter: blur(10px) saturate(1.1);
|
|
827
|
+
-webkit-backdrop-filter: blur(12px);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.rd-card-gradient-content-glass-icon {
|
|
831
|
+
inline-size: 4.5rem;
|
|
832
|
+
block-size: 4.5rem;
|
|
833
|
+
min-width: 0;
|
|
834
|
+
padding: 0;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.rd-card-gradient-content-glass::before,
|
|
838
|
+
.rd-card-gradient-content-glass::after {
|
|
839
|
+
content: "";
|
|
840
|
+
position: absolute;
|
|
841
|
+
inset: 0;
|
|
842
|
+
z-index: -1;
|
|
843
|
+
pointer-events: none;
|
|
844
|
+
border-radius: inherit;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.rd-card-gradient-content-glass::before {
|
|
848
|
+
background: linear-gradient(
|
|
849
|
+
180deg,
|
|
850
|
+
rgb(255 255 255 / 22%),
|
|
851
|
+
rgb(255 255 255 / 5%) 44%,
|
|
852
|
+
transparent
|
|
853
|
+
);
|
|
854
|
+
opacity: 0.7;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.rd-card-gradient-content-glass::after {
|
|
858
|
+
inset: -40% -55%;
|
|
859
|
+
background: linear-gradient(
|
|
860
|
+
115deg,
|
|
861
|
+
transparent 37%,
|
|
862
|
+
rgb(255 255 255 / 28%) 48%,
|
|
863
|
+
transparent 59%
|
|
864
|
+
);
|
|
865
|
+
opacity: 0;
|
|
866
|
+
transform: translateX(-34%) rotate(7deg);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
:global(
|
|
870
|
+
[data-rd-card-root][data-rd-card-link="true"]
|
|
871
|
+
.rd-card-gradient-content-glass::after
|
|
872
|
+
) {
|
|
873
|
+
transition:
|
|
874
|
+
transform 850ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
875
|
+
opacity 850ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
:global(
|
|
879
|
+
[data-rd-card-root][data-rd-card-link="true"]:hover
|
|
880
|
+
.rd-card-gradient-content-glass::after
|
|
881
|
+
),
|
|
882
|
+
:global(
|
|
883
|
+
[data-rd-card-root][data-rd-card-link="true"]:focus-within
|
|
884
|
+
.rd-card-gradient-content-glass::after
|
|
885
|
+
) {
|
|
886
|
+
opacity: 0.68;
|
|
887
|
+
transform: translateX(30%) rotate(7deg);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.rd-card-gradient-fallback {
|
|
891
|
+
background:
|
|
892
|
+
radial-gradient(
|
|
893
|
+
140% 112% at var(--rd-card-gradient-x) var(--rd-card-gradient-y),
|
|
894
|
+
color-mix(
|
|
895
|
+
in oklab,
|
|
896
|
+
var(--rd-card-gradient-color-2) 36%,
|
|
897
|
+
var(--rd-card-gradient-color-1) 64%
|
|
898
|
+
),
|
|
899
|
+
transparent 62%
|
|
900
|
+
),
|
|
901
|
+
radial-gradient(
|
|
902
|
+
128% 108% at var(--rd-card-gradient-x-2) var(--rd-card-gradient-y-2),
|
|
903
|
+
color-mix(
|
|
904
|
+
in oklab,
|
|
905
|
+
var(--rd-card-gradient-color-3) 30%,
|
|
906
|
+
var(--rd-card-gradient-color-1) 70%
|
|
907
|
+
),
|
|
908
|
+
transparent 68%
|
|
909
|
+
),
|
|
910
|
+
radial-gradient(
|
|
911
|
+
110% 92% at 6% 94%,
|
|
912
|
+
color-mix(in oklab, var(--rd-card-gradient-color-4) 24%, white 76%),
|
|
913
|
+
transparent 64%
|
|
914
|
+
),
|
|
915
|
+
linear-gradient(
|
|
916
|
+
var(--rd-card-gradient-angle),
|
|
917
|
+
color-mix(
|
|
918
|
+
in oklab,
|
|
919
|
+
var(--rd-card-gradient-color-1) 76%,
|
|
920
|
+
var(--rd-card-gradient-color-2) 24%
|
|
921
|
+
),
|
|
922
|
+
color-mix(
|
|
923
|
+
in oklab,
|
|
924
|
+
var(--rd-card-gradient-color-1) 78%,
|
|
925
|
+
var(--rd-card-gradient-color-4) 22%
|
|
926
|
+
)
|
|
927
|
+
54%,
|
|
928
|
+
color-mix(
|
|
929
|
+
in oklab,
|
|
930
|
+
var(--rd-card-gradient-color-1) 72%,
|
|
931
|
+
var(--rd-card-gradient-color-3) 28%
|
|
932
|
+
)
|
|
933
|
+
);
|
|
934
|
+
filter: saturate(1.08) blur(0.2px);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.rd-card-gradient-scale {
|
|
938
|
+
transform-origin: center;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
:global(
|
|
942
|
+
[data-rd-card-root][data-rd-card-link="true"] .rd-card-gradient-scale
|
|
943
|
+
) {
|
|
944
|
+
transition: transform 1000ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
:global(
|
|
948
|
+
[data-rd-card-root][data-rd-card-link="true"]:hover .rd-card-gradient-scale
|
|
949
|
+
),
|
|
950
|
+
:global(
|
|
951
|
+
[data-rd-card-root][data-rd-card-link="true"]:focus-within
|
|
952
|
+
.rd-card-gradient-scale
|
|
953
|
+
) {
|
|
954
|
+
transform: scale(1.03);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
@media (prefers-reduced-motion: reduce) {
|
|
958
|
+
.rd-card-gradient :global(canvas) {
|
|
959
|
+
transition: none;
|
|
960
|
+
filter: none;
|
|
961
|
+
transform: none;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
</style>
|