feat: InspiraUI
Some checks failed
/ surge (push) Successful in 2m41s
/ build-and-deploy-to-vercel (push) Successful in 2m59s
/ lint-build-and-check (push) Has been cancelled
/ playwright (push) Has been cancelled

This commit is contained in:
严浩
2025-04-01 11:55:16 +08:00
parent db2c210a09
commit 8f2a77702b
36 changed files with 1053 additions and 372 deletions

View File

@ -1,8 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
// https://oxc.rs/docs/guide/usage/linter/rules/unicorn/no-new-array.html
// "rules": {
// "unicorn/no-new-array": "warn"
// },
"rules": {
"unicorn/no-useless-spread": "off"
},
"ignorePatterns": ["src/shadcn/**", "src/components/InspiraUI/**"]
}

View File

@ -3,9 +3,9 @@
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
"source.fixAll.oxc": "explicit",
"source.fixAll.eslint": "never",
"source.fixAll.stylelint": "never",
"source.fixAll.oxc": "never",
// "source.fixAll.eslint": "never",
// "source.fixAll.stylelint": "never",
// "source.fixAll.oxc": "never",
"source.organizeImports": "never"
},
"editor.formatOnSave": true,

View File

@ -89,6 +89,8 @@
"@eslint/compat": "^1.2.7",
"@faker-js/faker": "^9.6.0",
"@iconify-json/carbon": "^1.2.8",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/mdi": "^1.2.3",
"@iconify/utils": "^2.3.0",
"@playwright/test": "^1.51.1",
"@primevue/auto-import-resolver": "^4.3.3",

20
pnpm-lock.yaml generated
View File

@ -149,6 +149,12 @@ importers:
'@iconify-json/carbon':
specifier: ^1.2.8
version: 1.2.8
'@iconify-json/logos':
specifier: ^1.2.4
version: 1.2.4
'@iconify-json/mdi':
specifier: ^1.2.3
version: 1.2.3
'@iconify/utils':
specifier: ^2.3.0
version: 2.3.0
@ -915,6 +921,12 @@ packages:
'@iconify-json/carbon@1.2.8':
resolution: {integrity: sha512-6xh4YiFBz6qoSnB3XMe23WvjTJroDFXB17J1MbiT7nATFe+70+em1acRXr8hgP/gYpwFMHFc4IvjA/IPTPnTzg==}
'@iconify-json/logos@1.2.4':
resolution: {integrity: sha512-XC4If5D/hbaZvUkTV8iaZuGlQCyG6CNOlaAaJaGa13V5QMYwYjgtKk3vPP8wz3wtTVNVEVk3LRx1fOJz+YnSMw==}
'@iconify-json/mdi@1.2.3':
resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@ -5314,6 +5326,14 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/logos@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/mdi@1.2.3':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
'@iconify/utils@2.3.0':

View File

@ -20,7 +20,6 @@ const themeConfig = computed(() => {
</a-config-provider>
<DynamicDialog /> <ConfirmDialog /> <Toast />
<FluidCursor />
</template>
<style>

View File

@ -1,22 +1,39 @@
<template>
<div
ref="bubbleParentContainer"
class="relative h-72 w-full overflow-hidden"
>
<div ref="bubbleCanvasContainer"></div>
<div
:style="{
'--bubbles-blur': `${blur}px`,
}"
class="absolute inset-0 z-[2] size-full backdrop-blur-[--bubbles-blur]"
>
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import {
Clock,
Color,
MathUtils,
Mesh,
PerspectiveCamera,
Scene,
ShaderMaterial,
SphereGeometry,
Vector3,
Color,
MathUtils,
Mesh,
Clock,
WebGLRenderer,
} from 'three';
import { onBeforeUnmount, onMounted, ref } from 'vue';
Scene,
PerspectiveCamera,
} from "three";
import { ref, onMounted, onBeforeUnmount } from "vue";
defineProps({
blur: {
default: 0,
type: Number,
default: 0,
},
});
@ -42,14 +59,16 @@ const SPHERE_COUNT = 250;
const SPHERE_SCALE_COEFF = 3;
const ORBIT_MIN = SPHERE_SCALE_COEFF + 2;
const ORBIT_MAX = ORBIT_MIN + 10;
const RAND_SEED = 898_211_544;
const RAND_SEED = 898211544;
const rand = seededRandom(RAND_SEED);
const { cos, PI, sin } = Math;
const { PI, cos, sin } = Math;
const PI2 = PI * 2;
const sizes = new Array(SPHERE_COUNT).fill(0).map(() => randRange(1) * Math.pow(randRange(), 3));
const orbitRadii = new Array(SPHERE_COUNT).fill(0).map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));
const orbitRadii = new Array(SPHERE_COUNT)
.fill(0)
.map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const positions: [number, number, number][] = orbitRadii.map((rad, i) => [
@ -76,28 +95,90 @@ const bgMaterial = getGradientMaterial(
);
bgMaterial.uniforms.uTemperatureVariancePeriod.value = new Vector3(0, 0, 0.1);
function animate() {
requestAnimationFrame(animate);
function seededRandom(a: number) {
return function () {
a |= 0;
a = (a + 0x9e3779b9) | 0;
var t = a ^ (a >>> 16);
t = Math.imul(t, 0x21f0aaad);
t = t ^ (t >>> 15);
t = Math.imul(t, 0x735a2d97);
return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
};
}
const elapsed = clock.getElapsedTime();
const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;
function randRange(n = 1) {
return rand() * n;
}
bgMaterial.uniforms.uTemperature.value = temperature;
bgMaterial.uniforms.uElapsedTime.value = elapsed;
function rgb(r: number, g: number, b: number) {
return new Color(r / 255, g / 255, b / 255);
}
sphereMaterial.uniforms.uTemperature.value = temperature;
sphereMaterial.uniforms.uElapsedTime.value = elapsed;
function getGradientMaterial(
colorBottomWarm: Color,
colorTopWarm: Color,
colorBottomCool: Color,
colorTopCool: Color,
) {
return new ShaderMaterial({
uniforms: {
colorBottomWarm: {
value: new Color().copy(colorBottomWarm),
},
colorTopWarm: {
value: new Color().copy(colorTopWarm),
},
colorBottomCool: {
value: new Color().copy(colorBottomCool),
},
colorTopCool: {
value: new Color().copy(colorTopCool),
},
uTemperature: {
value: 0.0,
},
uTemperatureVariancePeriod: {
value: new Vector3(0.08, 0.1, 0.2),
},
uElapsedTime: {
value: 0,
},
},
vertexShader: `
uniform vec4 uTemperatureVariancePeriod;
uniform float uTemperature;
uniform float uElapsedTime;
varying float topBottomMix;
varying float warmCoolMix;
// Floating effect for spheres
for (const [index, sphere] of spheres.entries()) {
const basePosition = positions[index];
const floatFactor = 2; // Adjust this value to control float intensity
const speed = 0.3; // Adjust this value to control float speed
const floatY = sin(elapsed * speed + index) * floatFactor;
sphere.position.y = basePosition[1] + floatY;
}
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
topBottomMix = normal.y;
warmCoolMix = 0.6 * uTemperature +
0.4 * (sin(
(uElapsedTime + gl_Position.x) * uTemperatureVariancePeriod.x +
(uElapsedTime + gl_Position.y) * uTemperatureVariancePeriod.y +
(uElapsedTime + gl_Position.z) * uTemperatureVariancePeriod.z) * 0.5 + 0.5);
}
`,
fragmentShader: `
uniform vec3 colorBottomWarm;
uniform vec3 colorTopWarm;
uniform vec3 colorBottomCool;
uniform vec3 colorTopCool;
renderer.render(scene, camera);
varying float topBottomMix;
varying float warmCoolMix;
void main() {
gl_FragColor = vec4(mix(
mix(colorTopCool, colorTopWarm, warmCoolMix),
mix(colorBottomCool, colorBottomWarm, warmCoolMix),
topBottomMix), 1.0);
}
`,
});
}
function createScene() {
@ -119,7 +200,7 @@ function createScene() {
sphereMaterial.depthTest = true; // Keep this true for depth sorting
if (bubbleCanvasContainer.value) {
bubbleCanvasContainer.value.append(renderer.domElement);
bubbleCanvasContainer.value.appendChild(renderer.domElement);
}
// Create the background mesh
@ -138,12 +219,18 @@ function createScene() {
const frustumWidth = frustumHeight * aspect;
// Scale the background geometry to match the camera's frustum size
bgMesh.scale.set(frustumWidth / bgGeometry.parameters.radius, frustumHeight / bgGeometry.parameters.radius, 1);
bgMesh.scale.set(
frustumWidth / bgGeometry.parameters.radius,
frustumHeight / bgGeometry.parameters.radius,
1,
);
scene.add(bgMesh); // Add the backgrou
// Create sphere meshes
const orbitRadii = new Array(SPHERE_COUNT).fill(0).map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));
const orbitRadii = new Array(SPHERE_COUNT)
.fill(0)
.map(() => MathUtils.lerp(ORBIT_MIN, ORBIT_MAX, randRange()));
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
const positions = orbitRadii.map((rad, i) => [
@ -165,85 +252,28 @@ function createScene() {
clock = new Clock();
}
function getGradientMaterial(colorBottomWarm: Color, colorTopWarm: Color, colorBottomCool: Color, colorTopCool: Color) {
return new ShaderMaterial({
fragmentShader: `
uniform vec3 colorBottomWarm;
uniform vec3 colorTopWarm;
uniform vec3 colorBottomCool;
uniform vec3 colorTopCool;
function animate() {
requestAnimationFrame(animate);
varying float topBottomMix;
varying float warmCoolMix;
const elapsed = clock.getElapsedTime();
const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;
void main() {
gl_FragColor = vec4(mix(
mix(colorTopCool, colorTopWarm, warmCoolMix),
mix(colorBottomCool, colorBottomWarm, warmCoolMix),
topBottomMix), 1.0);
}
`,
uniforms: {
colorBottomCool: {
value: new Color().copy(colorBottomCool),
},
colorBottomWarm: {
value: new Color().copy(colorBottomWarm),
},
colorTopCool: {
value: new Color().copy(colorTopCool),
},
colorTopWarm: {
value: new Color().copy(colorTopWarm),
},
uElapsedTime: {
value: 0,
},
uTemperature: {
value: 0,
},
uTemperatureVariancePeriod: {
value: new Vector3(0.08, 0.1, 0.2),
},
},
vertexShader: `
uniform vec4 uTemperatureVariancePeriod;
uniform float uTemperature;
uniform float uElapsedTime;
varying float topBottomMix;
varying float warmCoolMix;
bgMaterial.uniforms.uTemperature.value = temperature;
bgMaterial.uniforms.uElapsedTime.value = elapsed;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
topBottomMix = normal.y;
warmCoolMix = 0.6 * uTemperature +
0.4 * (sin(
(uElapsedTime + gl_Position.x) * uTemperatureVariancePeriod.x +
(uElapsedTime + gl_Position.y) * uTemperatureVariancePeriod.y +
(uElapsedTime + gl_Position.z) * uTemperatureVariancePeriod.z) * 0.5 + 0.5);
}
`,
sphereMaterial.uniforms.uTemperature.value = temperature;
sphereMaterial.uniforms.uElapsedTime.value = elapsed;
// Floating effect for spheres
spheres.forEach((sphere, index) => {
const basePosition = positions[index];
const floatFactor = 2; // Adjust this value to control float intensity
const speed = 0.3; // Adjust this value to control float speed
const floatY = sin(elapsed * speed + index) * floatFactor;
sphere.position.y = basePosition[1] + floatY;
});
}
function randRange(n = 1) {
return rand() * n;
}
function rgb(r: number, g: number, b: number) {
return new Color(r / 255, g / 255, b / 255);
}
function seededRandom(a: number) {
return function () {
a = Math.trunc(a);
a = Math.trunc(a + 0x9e_37_79_b9);
let t = a ^ (a >>> 16);
t = Math.imul(t, 0x21_f0_aa_ad);
t = t ^ (t >>> 15);
t = Math.imul(t, 0x73_5a_2d_97);
return ((t = t ^ (t >>> 15)) >>> 0) / 4_294_967_296;
};
renderer.render(scene, camera);
}
function updateRendererSize() {
@ -261,34 +291,26 @@ function updateRendererSize() {
const frustumWidth = frustumHeight * camera.aspect;
// Get the background mesh and update its scale
const bgMesh = scene.children.find((obj) => obj instanceof Mesh && obj.geometry === bgGeometry) as Mesh;
const bgMesh = scene.children.find(
(obj) => obj instanceof Mesh && obj.geometry === bgGeometry,
) as Mesh;
if (bgMesh) {
bgMesh.scale.set(frustumWidth / bgGeometry.parameters.radius, frustumHeight / bgGeometry.parameters.radius, 1);
bgMesh.scale.set(
frustumWidth / bgGeometry.parameters.radius,
frustumHeight / bgGeometry.parameters.radius,
1,
);
}
}
onMounted(() => {
createScene();
updateRendererSize();
window.addEventListener('resize', updateRendererSize);
window.addEventListener("resize", updateRendererSize);
animate();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', updateRendererSize); // Cleanup on component unmount
window.removeEventListener("resize", updateRendererSize); // Cleanup on component unmount
});
</script>
<template>
<div ref="bubbleParentContainer" class="relative h-72 w-full overflow-hidden">
<div ref="bubbleCanvasContainer"></div>
<div
:style="{
'--bubbles-blur': `${blur}px`,
}"
class="absolute inset-0 z-[2] size-full backdrop-blur-[--bubbles-blur]"
>
<slot />
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as BubblesBg } from "./BubblesBg.vue";

View File

@ -1,22 +1,29 @@
<template>
<canvas
ref="starsCanvas"
:class="cn('absolute inset-0 w-full h-full', $props.class)"
></canvas>
</template>
<script setup lang="ts">
import { cn } from '@/shadcn/lib/utils';
import { onMounted, ref } from 'vue';
import { cn } from "@/shadcn/lib/utils";
import { onMounted, ref } from "vue";
interface Star {
speed: number;
x: number;
y: number;
z: number;
speed: number;
}
const props = withDefaults(
defineProps<{
class?: string;
color?: string;
count?: number;
class?: string;
}>(),
{
color: '#FFF',
color: "#FFF",
count: 200,
},
);
@ -30,7 +37,7 @@ onMounted(() => {
const canvas = starsCanvas.value;
if (!canvas) return;
window.addEventListener('resize', resizeCanvas);
window.addEventListener("resize", resizeCanvas);
resizeCanvas(); // Call it initially to set correct size
perspective = canvas.width / 2;
@ -39,41 +46,39 @@ onMounted(() => {
// Initialize stars
for (let i = 0; i < props.count; i++) {
stars.push({
speed: Math.random() * 5 + 2, // Speed for falling effect
x: (Math.random() - 0.5) * 2 * canvas.width,
y: (Math.random() - 0.5) * 2 * canvas.height,
z: Math.random() * canvas.width,
speed: Math.random() * 5 + 2, // Speed for falling effect
});
}
animate(); // Start animation
});
// Function to animate the stars
function animate() {
const canvas = starsCanvas.value;
if (!canvas) return;
function hexToRgb() {
let hex = props.color.replace(/^#/, "");
ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas for each frame
for (const star of stars) {
drawStar(star);
// Move star towards the screen (decrease z)
star.z -= star.speed;
// Reset star when it reaches the viewer (z = 0)
if (star.z <= 0) {
star.z = canvas.width;
star.x = (Math.random() - 0.5) * 2 * canvas.width;
star.y = (Math.random() - 0.5) * 2 * canvas.height;
}
// If the hex code is 3 characters, expand it to 6 characters
if (hex.length === 3) {
hex = hex
.split("")
.map((char) => char + char)
.join("");
}
requestAnimationFrame(animate); // Continue animation
// Parse the r, g, b values from the hex string
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255; // Extract the red component
const g = (bigint >> 8) & 255; // Extract the green component
const b = bigint & 255; // Extract the blue component
// Return the RGB values as a string separated by spaces
return {
r,
g,
b,
};
}
// Function to draw a star with a sharp line and blurred trail
@ -81,7 +86,7 @@ function drawStar(star: Star) {
const canvas = starsCanvas.value;
if (!canvas) return;
ctx = canvas.getContext('2d');
ctx = canvas.getContext("2d");
if (!ctx) return;
const scale = perspective / (perspective + star.z); // 3D perspective scale
@ -123,26 +128,31 @@ function drawStar(star: Star) {
ctx.fill();
}
function hexToRgb() {
let hex = props.color.replace(/^#/, '');
// Function to animate the stars
function animate() {
const canvas = starsCanvas.value;
if (!canvas) return;
// If the hex code is 3 characters, expand it to 6 characters
if (hex.length === 3) {
hex = [...hex].map((char) => char + char).join('');
}
ctx = canvas.getContext("2d");
if (!ctx) return;
// Parse the r, g, b values from the hex string
const bigint = Number.parseInt(hex, 16);
const r = (bigint >> 16) & 255; // Extract the red component
const g = (bigint >> 8) & 255; // Extract the green component
const b = bigint & 255; // Extract the blue component
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas for each frame
// Return the RGB values as a string separated by spaces
return {
b,
g,
r,
};
stars.forEach((star) => {
drawStar(star);
// Move star towards the screen (decrease z)
star.z -= star.speed;
// Reset star when it reaches the viewer (z = 0)
if (star.z <= 0) {
star.z = canvas.width;
star.x = (Math.random() - 0.5) * 2 * canvas.width;
star.y = (Math.random() - 0.5) * 2 * canvas.height;
}
});
requestAnimationFrame(animate); // Continue animation
}
// Set canvas to full screen
@ -154,7 +164,3 @@ function resizeCanvas() {
canvas.height = canvas.clientHeight;
}
</script>
<template>
<canvas ref="starsCanvas" :class="cn('absolute inset-0 w-full h-full', $props.class)"></canvas>
</template>

View File

@ -0,0 +1 @@
export { default as FallingStarsBg } from "./FallingStarsBg.vue";

View File

@ -0,0 +1,65 @@
<template>
<div
:class="
cn(
'border-beam',
'pointer-events-none absolute inset-0 rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]',
'![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]',
'after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]',
props.class,
)
"
></div>
</template>
<script setup lang="ts">
import { cn } from "@/shadcn/lib/utils";
import { computed } from "vue";
interface BorderBeamProps {
class?: string;
size?: number;
duration?: number;
borderWidth?: number;
anchor?: number;
colorFrom?: string;
colorTo?: string;
delay?: number;
}
const props = withDefaults(defineProps<BorderBeamProps>(), {
size: 200,
duration: 15000,
anchor: 90,
borderWidth: 1.5,
colorFrom: "#ffaa40",
colorTo: "#9c40ff",
delay: 0,
});
const durationInSeconds = computed(() => `${props.duration}s`);
const delayInSeconds = computed(() => `${props.delay}s`);
</script>
<style scoped>
.border-beam {
--size: v-bind(size);
--duration: v-bind(durationInSeconds);
--anchor: v-bind(anchor);
--border-width: v-bind(borderWidth);
--color-from: v-bind(colorFrom);
--color-to: v-bind(colorTo);
--delay: v-bind(delayInSeconds);
}
.animate-border-beam::after {
content: "";
animation: border-beam-anim var(--duration) infinite linear;
}
@keyframes border-beam-anim {
to {
offset-distance: 100%;
}
}
</style>

View File

@ -0,0 +1 @@
export { default as BorderBeam } from "./BorderBeam.vue";

View File

@ -1,54 +1,3 @@
<!-- https://inspira-ui.com/components/cards/card-spotlight -->
<script setup lang="ts">
import { cn } from '@/shadcn/lib/utils';
import { computed, type HTMLAttributes, onMounted, ref } from 'vue';
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class'];
gradientColor?: string;
gradientOpacity?: number;
gradientSize?: number;
slotClass?: HTMLAttributes['class'];
}>(),
{
class: '',
gradientColor: '#262626',
gradientOpacity: 0.8,
gradientSize: 200,
slotClass: '',
},
);
const mouseX = ref(-props.gradientSize * 10);
const mouseY = ref(-props.gradientSize * 10);
function handleMouseLeave() {
mouseX.value = -props.gradientSize * 10;
mouseY.value = -props.gradientSize * 10;
}
function handleMouseMove(e: MouseEvent) {
const target = e.currentTarget as HTMLElement;
const rect = target.getBoundingClientRect();
mouseX.value = e.clientX - rect.left;
mouseY.value = e.clientY - rect.top;
}
onMounted(() => {
mouseX.value = -props.gradientSize * 10;
mouseY.value = -props.gradientSize * 10;
});
const backgroundStyle = computed(() => {
return `radial-gradient(
circle at ${mouseX.value}px ${mouseY.value}px,
${props.gradientColor} 0%,
rgba(0, 0, 0, 0) 70%
)`;
});
</script>
<template>
<div
:class="[
@ -70,3 +19,53 @@ const backgroundStyle = computed(() => {
></div>
</div>
</template>
<script setup lang="ts">
import { cn } from "@/shadcn/lib/utils";
import { ref, computed, onMounted, type HTMLAttributes } from "vue";
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"];
slotClass?: HTMLAttributes["class"];
gradientSize?: number;
gradientColor?: string;
gradientOpacity?: number;
}>(),
{
class: "",
slotClass: "",
gradientSize: 200,
gradientColor: "#262626",
gradientOpacity: 0.8,
},
);
const mouseX = ref(-props.gradientSize * 10);
const mouseY = ref(-props.gradientSize * 10);
function handleMouseMove(e: MouseEvent) {
const target = e.currentTarget as HTMLElement;
const rect = target.getBoundingClientRect();
mouseX.value = e.clientX - rect.left;
mouseY.value = e.clientY - rect.top;
}
function handleMouseLeave() {
mouseX.value = -props.gradientSize * 10;
mouseY.value = -props.gradientSize * 10;
}
onMounted(() => {
mouseX.value = -props.gradientSize * 10;
mouseY.value = -props.gradientSize * 10;
});
const backgroundStyle = computed(() => {
return `radial-gradient(
circle at ${mouseX.value}px ${mouseY.value}px,
${props.gradientColor} 0%,
rgba(0, 0, 0, 0) 70%
)`;
});
</script>

View File

@ -0,0 +1 @@
export { default as CardSpotlight } from "./CardSpotlight.vue";

View File

@ -0,0 +1,76 @@
<template>
<div
ref="dockRef"
:class="
cn(
'supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max rounded-2xl border p-2 backdrop-blur-md transition-all gap-4',
orientation === 'vertical' && 'flex-col w-[58px] h-max',
props.class,
dockClass,
)
"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { DataOrientation, Direction } from "./types";
import {
MOUSE_X_INJECTION_KEY,
MOUSE_Y_INJECTION_KEY,
MAGNIFICATION_INJECTION_KEY,
DISTANCE_INJECTION_KEY,
ORIENTATION_INJECTION_KEY,
} from "./injectionKeys";
import { cn } from "@/shadcn/lib/utils";
import type { HTMLAttributes } from "vue";
interface DockProps {
class?: HTMLAttributes["class"];
magnification?: number;
distance?: number;
direction?: Direction;
orientation?: DataOrientation;
}
const props = withDefaults(defineProps<DockProps>(), {
magnification: 60,
distance: 140,
direction: "middle",
orientation: "horizontal",
});
const dockRef = ref<HTMLElement | null>(null);
const mouseX = ref(Infinity);
const mouseY = ref(Infinity);
const magnification = computed(() => props.magnification);
const distance = computed(() => props.distance);
const dockClass = computed(() => ({
"items-start": props.direction === "top",
"items-center": props.direction === "middle",
"items-end": props.direction === "bottom",
}));
function onMouseMove(e: MouseEvent) {
requestAnimationFrame(() => {
mouseX.value = e.pageX;
mouseY.value = e.pageY;
});
}
function onMouseLeave() {
requestAnimationFrame(() => {
mouseX.value = Infinity;
mouseY.value = Infinity;
});
}
provide(MOUSE_X_INJECTION_KEY, mouseX);
provide(MOUSE_Y_INJECTION_KEY, mouseY);
provide(ORIENTATION_INJECTION_KEY, props.orientation);
provide(MAGNIFICATION_INJECTION_KEY, magnification);
provide(DISTANCE_INJECTION_KEY, distance);
</script>

View File

@ -0,0 +1,61 @@
<template>
<div
ref="iconRef"
class="flex aspect-square cursor-pointer items-center justify-center rounded-full transition-all duration-200 ease-out"
:style="{
width: `${iconWidth}px`,
height: `${iconWidth}px`,
}"
:hovered="{
marginLeft: margin,
marginRight: margin,
}"
>
<slot />
</div>
</template>
<script setup lang="ts">
import {
MOUSE_X_INJECTION_KEY,
MOUSE_Y_INJECTION_KEY,
MAGNIFICATION_INJECTION_KEY,
DISTANCE_INJECTION_KEY,
ORIENTATION_INJECTION_KEY,
} from "./injectionKeys";
const iconRef = ref<HTMLDivElement | null>(null);
const mouseX = inject(MOUSE_X_INJECTION_KEY, ref(Infinity));
const mouseY = inject(MOUSE_Y_INJECTION_KEY, ref(Infinity));
const distance = inject(DISTANCE_INJECTION_KEY);
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
const magnification = inject(MAGNIFICATION_INJECTION_KEY);
const isVertical = computed(() => orientation === "vertical");
const margin = ref(0);
function calculateDistance(val: number) {
if (isVertical.value) {
const bounds = iconRef.value?.getBoundingClientRect() || {
y: 0,
height: 0,
};
return val - bounds.y - bounds.height / 2;
}
const bounds = iconRef.value?.getBoundingClientRect() || { x: 0, width: 0 };
return val - bounds.x - bounds.width / 2;
}
const iconWidth = computed(() => {
const distanceCalc = isVertical.value
? calculateDistance(mouseY.value)
: calculateDistance(mouseX.value);
if (!distance?.value || !magnification?.value) return 40;
if (Math.abs(distanceCalc) < distance?.value) {
return (1 - Math.abs(distanceCalc) / distance?.value) * magnification?.value + 40;
}
return 40;
});
</script>

View File

@ -0,0 +1,14 @@
<template>
<div
:class="
cn('relative block bg-secondary', orientation === 'vertical' ? 'w-4/5 h-0.5' : 'h-4/5 w-0.5')
"
></div>
</template>
<script setup lang="ts">
import { cn } from "@/shadcn/lib/utils";
import { ORIENTATION_INJECTION_KEY } from "./injectionKeys";
const orientation = inject(ORIENTATION_INJECTION_KEY, "vertical");
</script>

View File

@ -0,0 +1,5 @@
export { default as Dock } from "./Dock.vue";
export { default as DockIcon } from "./DockIcon.vue";
export { default as DockSeparator } from "./DockSeparator.vue";
export type DataOrientation = "vertical" | "horizontal";

View File

@ -0,0 +1,11 @@
import type { Ref, InjectionKey, ComputedRef } from "vue";
import type { DataOrientation } from "./types";
export const MOUSE_X_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
export const MOUSE_Y_INJECTION_KEY = Symbol() as InjectionKey<Ref<number>>;
export const MAGNIFICATION_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
export const DISTANCE_INJECTION_KEY = Symbol() as InjectionKey<ComputedRef<number>>;
export const ORIENTATION_INJECTION_KEY = Symbol() as InjectionKey<DataOrientation>;

View File

@ -0,0 +1,2 @@
export type DataOrientation = "vertical" | "horizontal";
export type Direction = "top" | "middle" | "bottom";

View File

@ -1,34 +1,3 @@
<!-- https://inspira-ui.com/components/buttons/gradient-button -->
<script lang="ts" setup>
import { cn } from '@/shadcn/lib/utils';
import { computed } from 'vue';
interface GradientButtonProps {
bgColor?: string;
blur?: number;
borderRadius?: number;
borderWidth?: number;
class?: string;
colors?: string[];
duration?: number;
}
const props = withDefaults(defineProps<GradientButtonProps>(), {
bgColor: '#000',
blur: 4,
borderRadius: 8,
borderWidth: 2,
colors: () => ['#FF0000', '#FFA500', '#FFFF00', '#008000', '#0000FF', '#4B0082', '#EE82EE', '#FF0000'],
duration: 2500,
});
const durationInMilliseconds = computed(() => `${props.duration}ms`);
const allColors = computed(() => props.colors.join(', '));
const borderWidthInPx = computed(() => `${props.borderWidth}px`);
const borderRadiusInPx = computed(() => `${props.borderRadius}px`);
const blurPx = computed(() => `${props.blur}px`);
</script>
<template>
<button
:class="
@ -44,9 +13,48 @@ const blurPx = computed(() => `${props.blur}px`);
</button>
</template>
<script lang="ts" setup>
import { cn } from "@/shadcn/lib/utils";
import { computed } from "vue";
interface GradientButtonProps {
borderWidth?: number;
colors?: string[];
duration?: number;
borderRadius?: number;
blur?: number;
class?: string;
bgColor?: string;
}
const props = withDefaults(defineProps<GradientButtonProps>(), {
colors: () => [
"#FF0000",
"#FFA500",
"#FFFF00",
"#008000",
"#0000FF",
"#4B0082",
"#EE82EE",
"#FF0000",
],
duration: 2500,
borderWidth: 2,
borderRadius: 8,
blur: 4,
bgColor: "#000",
});
const durationInMilliseconds = computed(() => `${props.duration}ms`);
const allColors = computed(() => props.colors.join(", "));
const borderWidthInPx = computed(() => `${props.borderWidth}px`);
const borderRadiusInPx = computed(() => `${props.borderRadius}px`);
const blurPx = computed(() => `${props.blur}px`);
</script>
<style scoped>
.animate-rainbow::before {
content: '';
content: "";
background: conic-gradient(v-bind(allColors));
animation: rotate-rainbow v-bind(durationInMilliseconds) linear infinite;
filter: blur(v-bind(blurPx));

View File

@ -0,0 +1 @@
export { default as GradientButton } from "./GradientButton.vue";

View File

@ -0,0 +1,60 @@
<template>
<button
ref="buttonRef"
:class="
cn(
'group relative w-auto cursor-pointer overflow-hidden rounded-full border bg-background p-2 px-6 text-center font-semibold',
props.class,
)
"
>
<div class="flex items-center gap-2">
<div
class="size-2 scale-100 rounded-lg bg-primary transition-all duration-300 group-hover:scale-[100.8]"
></div>
<span
class="inline-block whitespace-nowrap transition-all duration-300 group-hover:translate-x-12 group-hover:opacity-0"
>
{{ text }}
</span>
</div>
<div
class="absolute top-0 z-10 flex size-full translate-x-12 items-center justify-center gap-2 text-primary-foreground opacity-0 transition-all duration-300 group-hover:-translate-x-5 group-hover:opacity-100"
>
<span class="whitespace-nowrap">{{ text }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-arrow-right"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</div>
</button>
</template>
<script lang="ts" setup>
import { cn } from "@/shadcn/lib/utils";
import { ref } from "vue";
interface Props {
text?: string;
class?: string;
}
const props = withDefaults(defineProps<Props>(), {
text: "Button",
});
const buttonRef = ref<HTMLButtonElement>();
</script>
<style></style>

View File

@ -0,0 +1 @@
export { default as InteractiveHoverButton } from "./InteractiveHoverButton.vue";

View File

@ -0,0 +1,107 @@
<template>
<button
:class="
cn(
'group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-black',
'transform-gpu transition-transform duration-300 ease-in-out active:translate-y-px',
$props.class,
)
"
:style="{
'--spread': '90deg',
'--shimmer-color': shimmerColor,
'--radius': borderRadius,
'--speed': shimmerDuration,
'--cut': shimmerSize,
'--bg': background,
}"
>
<div :class="cn('-z-30 blur-[2px]', 'absolute inset-0 overflow-visible [container-type:size]')">
<div
class="animate-shimmer-btn-shimmer-slide absolute inset-0 h-[100cqh] [aspect-ratio:1] [border-radius:0] [mask:none]"
>
<div
class="animate-shimmer-btn-spin-around absolute -inset-full w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]"
/>
</div>
</div>
<slot />
<div
:class="
cn(
'insert-0 absolute size-full',
'rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]',
// transition
'transform-gpu transition-all duration-300 ease-in-out',
// on hover
'group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]',
// on click
'group-active:shadow-[inset_0_-10px_10px_#ffffff3f]',
)
"
/>
<div
class="absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]"
/>
</button>
</template>
<script lang="ts" setup>
import { cn } from '@/shadcn/lib/utils';
type ShimmerButtonProps = {
shimmerColor?: string;
shimmerSize?: string;
borderRadius?: string;
shimmerDuration?: string;
background?: string;
class?: string;
};
withDefaults(defineProps<ShimmerButtonProps>(), {
shimmerColor: "#ffffff",
shimmerSize: "0.05em",
shimmerDuration: "3s",
borderRadius: "100px",
background: "rgba(0, 0, 0, 1)",
});
</script>
<style scoped>
@keyframes shimmer-btn-shimmer-slide {
to {
transform: translate(calc(100cqw - 100%), 0);
}
}
@keyframes shimmer-btn-spin-around {
0% {
transform: translateZ(0) rotate(0);
}
15%,
35% {
transform: translateZ(0) rotate(90deg);
}
65%,
85% {
transform: translateZ(0) rotate(270deg);
}
100% {
transform: translateZ(0) rotate(360deg);
}
}
.animate-shimmer-btn-shimmer-slide {
animation: shimmer-btn-shimmer-slide var(--speed) ease-in-out infinite alternate;
}
.animate-shimmer-btn-spin-around {
animation: shimmer-btn-spin-around calc(var(--speed) * 2) infinite linear;
}
</style>

View File

@ -0,0 +1 @@
export { default as ShimmerButton } from "./ShimmerButton.vue";

View File

@ -0,0 +1,159 @@
<template>
<div
ref="containerRef"
class="relative size-full overflow-hidden will-change-transform"
:style="{ background }"
>
<canvas
ref="canvasRef"
class="absolute inset-0 size-full"
/>
</div>
</template>
<script setup lang="ts">
import { useRafFn, templateRef } from "@vueuse/core";
import { ref, onMounted, onBeforeUnmount } from "vue";
interface Props {
background?: string;
particleColor?: string;
minSize?: number;
maxSize?: number;
speed?: number;
particleDensity?: number;
}
interface Particle {
x: number;
y: number;
size: number;
opacity: number;
vx: number;
vy: number;
phase: number;
phaseSpeed: number;
}
const props = withDefaults(defineProps<Props>(), {
background: "#0d47a1",
particleColor: "#ffffff",
minSize: 1,
maxSize: 3,
speed: 4,
particleDensity: 120,
});
const containerRef = templateRef<HTMLElement | null>("containerRef");
const canvasRef = templateRef<HTMLCanvasElement | null>("canvasRef");
const particles = ref<Particle[]>([]);
const ctx = ref<CanvasRenderingContext2D | null>(null);
// Adjust canvas size on mount and resize
function resizeCanvas() {
if (!canvasRef.value || !containerRef.value) return;
const dpr = window.devicePixelRatio || 1;
const rect = containerRef.value.getBoundingClientRect();
canvasRef.value.width = rect.width * dpr;
canvasRef.value.height = rect.height * dpr;
if (ctx.value) {
ctx.value.scale(dpr, dpr);
}
}
function generateParticles(): void {
const newParticles: Particle[] = [];
const count = props.particleDensity;
for (let i = 0; i < count; i++) {
const baseSpeed = 0.05;
const speedVariance = Math.random() * 0.3 + 0.7;
newParticles.push({
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * (props.maxSize - props.minSize) + props.minSize,
opacity: Math.random() * 0.5 + 0.3,
vx: (Math.random() - 0.5) * baseSpeed * speedVariance * props.speed,
vy: ((Math.random() - 0.5) * baseSpeed - baseSpeed * 0.3) * speedVariance * props.speed,
phase: Math.random() * Math.PI * 2,
phaseSpeed: 0.015,
});
}
particles.value = newParticles;
}
function updateAndDrawParticles() {
if (!ctx.value || !canvasRef.value) return;
const canvas = canvasRef.value;
ctx.value.clearRect(0, 0, canvas.width, canvas.height);
particles.value = particles.value.map((particle) => {
let newX = particle.x + particle.vx;
let newY = particle.y + particle.vy;
if (newX < -2) newX = 102;
if (newX > 102) newX = -2;
if (newY < -2) newY = 102;
if (newY > 102) newY = -2;
const newPhase = (particle.phase + particle.phaseSpeed) % (Math.PI * 2);
const opacity = 0.3 + (Math.sin(newPhase) * 0.3 + 0.3);
// Draw particle
ctx.value!.beginPath();
ctx.value!.arc(
(newX * canvas.width) / 100,
(newY * canvas.height) / 100,
particle.size,
0,
Math.PI * 2,
);
ctx.value!.fillStyle = `${props.particleColor}${Math.floor(opacity * 255)
.toString(16)
.padStart(2, "0")}`;
ctx.value!.fill();
return {
...particle,
x: newX,
y: newY,
phase: newPhase,
opacity,
};
});
}
const { pause, resume } = useRafFn(updateAndDrawParticles, { immediate: false });
// Handle window resize
let resizeObserver: ResizeObserver | undefined;
onMounted(() => {
if (!canvasRef.value) return;
ctx.value = canvasRef.value.getContext("2d");
resizeCanvas();
generateParticles();
// Set up resize observer
resizeObserver = new ResizeObserver(resizeCanvas);
if (containerRef.value) {
resizeObserver.observe(containerRef.value);
}
resume();
});
onBeforeUnmount(() => {
pause();
if (resizeObserver && containerRef.value) {
resizeObserver.unobserve(containerRef.value);
}
});
</script>

View File

@ -0,0 +1 @@
export { default as Sparkles } from "./Sparkles.vue";

59
src/pages/Home.page.vue Normal file
View File

@ -0,0 +1,59 @@
<script setup lang="tsx">
const VITE_BUILD_COMMIT = import.meta.env.VITE_BUILD_COMMIT;
import { routes } from 'vue-router/auto-routes';
definePage({
name: 'Home',
meta: {
title: '首页',
hidden: true,
},
});
import { useHead, useSeoMeta } from '@unhead/vue';
useHead({
bodyAttrs: { class: { overflow: true } },
title: 'Hello World',
titleTemplate: (title) => `${title} | My App`,
// Deduping
// script: [{ key: '123', src: '/script.js' }],
});
useSeoMeta({
title: 'vue-ts-example 首页',
description: 'vue-ts-example 首页',
});
consola.info('routes', routes);
const FComponent: import('vue').FunctionalComponent<{ prop: string }> = (props /* context */) => (
<>
<a
class="green"
href="https://cn.vuejs.org/guide/extras/render-function#typing-functional-components"
rel="noopener noreferrer"
target="_blank"
>
函数式组件: https://cn.vuejs.org/guide/extras/render-function#typing-functional-components
</a>
<p>函数式组件接收到的 prop 值为</p>
<pre>{JSON.stringify(props, null, 2)}</pre>
</>
);
</script>
<template>
<div b="1px solid pink" mt-2 p-2>
<ul>
<li>
<router-link class="green" :to="{ name: 'DataLoadersId', params: { id: 520 } }">Data Loaders</router-link>
</li>
</ul>
</div>
<div b="1px solid pink" mt-2 p-2>
<FComponent prop="Hello World" style="margin-top: 1rem"></FComponent>
</div>
<SendSms class="mt-2!" />
<div b="1px solid pink" mt-2 p-2>commit: {{ VITE_BUILD_COMMIT }}</div>
</template>

View File

@ -8,20 +8,20 @@ import SomeIcon from '~icons/svg/pacman';
<template>
<div b="1px solid pink" mt-2 text-4 p-2 space-y-2>
<!-- <div b="1px solid pink">
<div b="1px solid pink">
<div>@iconify-json/carbon/icons.json</div>
<div i-carbon-face-cool text-orange />
</div> -->
</div>
<div b="1px solid pink">
<div>Icons({ autoInstall: true })</div>
<icon-carbon-face-cool class="text-yellow" w-8 h-8 />
</div>
<!-- <div b="1px solid pink">
<div b="1px solid pink">
<div>pacman.svg</div>
<div class="icon:pacman text-(pink)" />
</div> -->
</div>
<div b="1px solid pink">
<div>pacman.svg</div>

View File

@ -19,4 +19,20 @@ const bgColor = computed(() => (isDarkTheme.value ? '#000' : '#fff'));
Card Spotlight
</CardSpotlight>
</div>
<Dock class="mb-6">
<DockIcon>
<Icon-mdi-github class="size-full" />
</DockIcon>
<DockSeparator />
<DockIcon>
<Icon-mdi-google-drive class="size-full" />
</DockIcon>
<DockIcon>
<Icon-logos-notion-icon class="size-full" />
</DockIcon>
<DockIcon>
<Icon-logos-whatsapp-icon class="size-full" />
</DockIcon>
</Dock>
</template>

View File

@ -1,33 +0,0 @@
<script setup lang="ts">
import {
PATTERN_BACKGROUND_DIRECTION,
PATTERN_BACKGROUND_SPEED,
PATTERN_BACKGROUND_VARIANT,
} from '@/components/InspiraUI/pattern-background';
</script>
<template>
<div class="p-4 h-screen w-screen">
<PatternBackground
:animate="true"
:direction="PATTERN_BACKGROUND_DIRECTION.TopRight"
:variant="PATTERN_BACKGROUND_VARIANT.Dot"
class="flex h-full w-full flex-col items-center gap-4 overflow-hidden rounded-lg border px-4 py-8"
:speed="PATTERN_BACKGROUND_SPEED.Slow"
>
<h3
class="relative z-20 bg-gradient-to-b from-neutral-200 to-neutral-500 bg-clip-text py-8 text-3xl font-bold text-transparent"
>
Spline
</h3>
<Spline scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode" />
</PatternBackground>
</div>
</template>
<style scoped></style>
<route lang="yaml">
meta:
layout: false
</route>

View File

@ -1,53 +1,85 @@
<script setup lang="tsx">
const VITE_BUILD_COMMIT = import.meta.env.VITE_BUILD_COMMIT;
import { routes } from 'vue-router/auto-routes';
<script setup lang="ts">
import {
PATTERN_BACKGROUND_DIRECTION,
PATTERN_BACKGROUND_SPEED,
PATTERN_BACKGROUND_VARIANT,
} from '@/components/InspiraUI/pattern-background';
import { useLayout } from '@/layouts/sakai-vue/composables/layout';
definePage({ meta: { title: '首页' } });
import { useHead, useSeoMeta } from '@unhead/vue';
useHead({
bodyAttrs: { class: { overflow: true } },
title: 'Hello World',
titleTemplate: (title) => `${title} | My App`,
// Deduping
// script: [{ key: '123', src: '/script.js' }],
});
useSeoMeta({
description: '首页',
title: 'Hello World',
});
consola.info('routes', routes);
const FComponent: import('vue').FunctionalComponent<{ prop: string }> = (props /* context */) => (
<>
<a
class="green"
href="https://cn.vuejs.org/guide/extras/render-function#typing-functional-components"
rel="noopener noreferrer"
target="_blank"
>
函数式组件: https://cn.vuejs.org/guide/extras/render-function#typing-functional-components
</a>
<p>函数式组件接收到的 prop 值为</p>
<pre>{JSON.stringify(props, null, 2)}</pre>
</>
);
const { isDarkTheme, toggleDarkMode } = useLayout();
const GradientButton_bgColor = computed(() => (isDarkTheme.value ? '#000' : '#fff'));
const particlesColor = computed(() => (isDarkTheme.value ? '#FFFFFF' : '#000000'));
</script>
<template>
<div b="1px solid pink" mt-2 p-2>
<ul>
<li>
<router-link class="green" :to="{ name: 'DataLoadersId', params: { id: 520 } }">Data Loaders</router-link>
</li>
</ul>
</div>
<div b="1px solid pink" mt-2 p-2>
<FComponent prop="Hello World" style="margin-top: 1rem"></FComponent>
</div>
<div class="p-4 h-screen w-screen relative">
<PatternBackground
:animate="true"
:direction="PATTERN_BACKGROUND_DIRECTION.TopRight"
:variant="PATTERN_BACKGROUND_VARIANT.Dot"
class="flex h-full w-full flex-col items-center gap-4 overflow-hidden border rounded-lg px-4 py-8"
:speed="PATTERN_BACKGROUND_SPEED.Slow"
shadow-2xl
>
<h3
class="relative z-20 bg-gradient-to-b from-neutral-200 to-neutral-500 bg-clip-text py-0 text-3xl font-bold text-transparent"
>
Spline
</h3>
<SendSms class="mt-2!" />
<div b="1px solid pink" mt-2 p-2>commit: {{ VITE_BUILD_COMMIT }}</div>
<RouterLink :to="{ name: 'Home' }">
<GradientButton :bg-color="GradientButton_bgColor">Zooooooooooom 🚀 </GradientButton>
<!-- <InteractiveHoverButton text="Zooooooooooom 🚀" /> -->
</RouterLink>
<ShimmerButton class="isolate shadow-2xl" shimmer-size="2px" @click="toggleDarkMode">
<span
class="whitespace-pre-wrap text-center text-sm font-medium leading-none tracking-tight text-white lg:text-lg dark:from-white dark:to-slate-900/10"
>
{{ isDarkTheme ? '🌜' : '🌞' }}
</span>
</ShimmerButton>
<div class="relative h-40 w-[40rem]">
<div
class="absolute inset-x-20 top-0 h-[2px] w-3/4 bg-gradient-to-r from-transparent via-indigo-500 to-transparent blur-sm"
/>
<div
class="absolute inset-x-20 top-0 h-px w-3/4 bg-gradient-to-r from-transparent via-indigo-500 to-transparent"
/>
<div
class="absolute inset-x-60 top-0 h-[5px] w-1/4 bg-gradient-to-r from-transparent via-sky-500 to-transparent blur-sm"
/>
<div
class="absolute inset-x-60 top-0 h-px w-1/4 bg-gradient-to-r from-transparent via-sky-500 to-transparent"
/>
<Sparkles
background="transparent"
:min-size="0.4"
:max-size="1.4"
:particle-density="1200"
class="size-full"
:particle-color="particlesColor"
/>
<div class="absolute inset-0 size-full [mask-image:radial-gradient(350px_200px_at_top,transparent_20%,white)]">
<!-- bg-white dark:bg-black -->
</div>
</div>
<Spline scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode" />
<BorderBeam :size="250" :duration="12" :delay="9" :border-width="2" />
</PatternBackground>
<FluidCursor />
</div>
</template>
<style scoped></style>
<route lang="yaml">
meta:
layout: false
</route>

View File

@ -22,9 +22,9 @@ router.onError((error) => {
export { router, setupLayoutsResult };
export function install({ app }: { app: import('vue').App<Element> }) {
app
// Register the plugin before the router
// 在路由之前注册插件
.use(DataLoaderPlugin, { router })
// adding the router will trigger the initial navigation
// 添加路由会触发初始导航
.use(router);
}
// ========================================================================

View File

@ -1,20 +0,0 @@
import 'vue-router';
// declare module 'vue-router' {
// interface RouteMeta {
// // fromList?: RouteLocationNormalized[]
// }
// interface Router {
// stack: {
// // list: RouteLocationNormalizedLoaded[]
// // currentStackIndex: number
// }
// }
// interface RouteLocationNormalizedGeneric {
// // from: RouteLocationNormalizedLoaded
// // stackIndex: number
// }
// }
export {};

2
typed-router.d.ts vendored
View File

@ -24,7 +24,7 @@ declare module 'vue-router/auto-routes' {
'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'DataLoadersIdSub1UserId': RouteRecordInfo<'DataLoadersIdSub1UserId', '/data-loaders/:id/sub-1/:userId', { id: ParamValue<true>, userId: ParamValue<true> }, { id: ParamValue<false>, userId: ParamValue<false> }>,
'FlowbiteSidebar': RouteRecordInfo<'FlowbiteSidebar', '/FlowbiteSidebar', Record<never, never>, Record<never, never>>,
'IndexNew': RouteRecordInfo<'IndexNew', '/index-new', Record<never, never>, Record<never, never>>,
'Home': RouteRecordInfo<'Home', '/Home', Record<never, never>, Record<never, never>>,
'PageAPI': RouteRecordInfo<'PageAPI', '/Page/API', Record<never, never>, Record<never, never>>,
'PageFonts': RouteRecordInfo<'PageFonts', '/Page/fonts', Record<never, never>, Record<never, never>>,
'PageIcons': RouteRecordInfo<'PageIcons', '/Page/Icons', Record<never, never>, Record<never, never>>,

View File

@ -37,6 +37,9 @@ export default defineConfig(({ command, mode }) => {
// // 'vendor/nprogress': ['nprogress'],
// // 'vendor/formkit': ['@formkit/auto-animate'],
// },
manualChunks: {
'vendor/Cesium': ['cesium'],
},
},
},
sourcemap: mode !== 'production' || env.VITE_SOURCE_MAP === 'true',