Updated Landing Page

This commit is contained in:
Utkarsh-Singhal-26
2025-08-25 11:09:54 +05:30
parent b0ef547816
commit 7133425258
12 changed files with 1977 additions and 422 deletions

View File

@@ -113,6 +113,9 @@
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
color: #fff;
border-top: 1px solid rgba(255, 255, 255, 0.07);
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
border-right: 1px solid rgba(255, 255, 255, 0.07);
border: none;
border-radius: 50px;
cursor: pointer;
@@ -126,7 +129,7 @@
.cta-button span {
background-color: #0b0b0b;
margin-left: 1em;
margin-right: calc(1em - 8px);
margin-right: calc(1em - 10px);
padding-top: 0.1em;
height: 45px;
border-radius: 50px;

View File

@@ -1,9 +1,9 @@
.features-section {
position: relative;
margin-top: 12em;
padding: 8rem 2rem 4em;
padding: 0 2rem 4em;
padding-bottom: 0 !important;
z-index: 22;
transform: translateY(-4rem);
user-select: none;
}
@@ -12,59 +12,22 @@
margin: 0 auto;
}
.features-header {
text-align: center;
margin-bottom: 4rem;
}
.features-title {
font-size: 4rem;
font-weight: 600;
letter-spacing: -2px;
color: #fff;
margin-bottom: 0.2rem;
background: linear-gradient(135deg, #fff 0%, #60fa89 20%, #55f788 40%, #00ff62 60%, #55f799 80%, #fff 100%);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
animation: gradientShift 4s ease-in-out infinite;
display: inline-block;
white-space: nowrap;
}
.features-title * {
background: inherit;
background-size: inherit;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: inherit;
}
@keyframes gradientShift {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
@media (max-width: 1024px) {
.features-section {
position: relative;
margin-top: 4rem;
padding: 0 2rem 4em;
padding-bottom: 0 !important;
z-index: 22;
transform: translateY(0);
user-select: none;
}
}
.features-subtitle {
font-size: 1.2rem;
color: #fff;
text-shadow:
0 0 2px rgba(255, 255, 255, 0.1),
0 0 4px rgba(255, 255, 255, 0.3),
0 0 8px rgba(255, 255, 255, 0.4),
0 0 136px rgba(0, 255, 98, 0.9);
font-weight: 400;
margin: 0;
text-align: center;
@media (max-width: 768px) {
}
@media (max-width: 480px) {
}
.bento-grid {
@@ -367,9 +330,8 @@
@media (max-width: 479px) {
.features-section {
padding: 4rem 1rem 2rem;
padding: 0 1rem 2rem;
padding-bottom: 0;
margin-top: 4em;
}
.features-title {
@@ -449,8 +411,7 @@
@media (min-width: 768px) {
.features-section {
padding: 6rem 2rem 3rem;
margin-top: 6em;
padding: 0 2rem 3rem;
}
.feature-card {
@@ -496,9 +457,8 @@
@media (min-width: 50rem) {
.features-section {
padding: 8rem 2rem 4rem;
padding: 0 2rem 4rem;
padding-bottom: 0;
margin-top: 8em;
}
.bento-grid {
@@ -543,8 +503,7 @@
@media (max-height: 500px) and (orientation: landscape) {
.features-section {
margin-top: 2em;
padding: 2rem 1rem;
padding: 0 2rem 1rem;
}
.feature-card {

View File

@@ -1,11 +1,6 @@
<template>
<div class="features-section">
<div class="features-container">
<div class="features-header">
<h3 class="features-title">Zero cost, all the cool.</h3>
<p class="features-subtitle">Everything you need to add flair to your websites</p>
</div>
<div class="bento-grid" ref="gridRef">
<ParticleCard class="feature-card card1" :disable-animations="isMobile">
<div class="messages-gif-wrapper">
@@ -29,7 +24,7 @@
<CountUp v-else :to="80" />
+
</h2>
<h3>Curated Components</h3>
<h3>Creative Components</h3>
<p>Growing weekly &amp; only getting better</p>
</ParticleCard>

View File

@@ -1,6 +1,6 @@
.landing-footer {
position: relative;
margin-top: 8rem;
margin-top: 4rem;
padding: 2.4rem;
border-top: 1px solid rgba(149, 184, 148, 0.1);
background: linear-gradient(to bottom, transparent, #0b0b0b);

View File

@@ -1,10 +1,38 @@
<template>
<div class="landing-content">
<img
:src="landingBlur"
alt=""
aria-hidden="true"
class="landing-gradient-blur"
draggable="false"
:style="{ zIndex: 5 }"
/>
<img
:src="landingBlur"
alt=""
aria-hidden="true"
class="landing-gradient-blur"
draggable="false"
:style="{ zIndex: 5 }"
/>
<div class="hero-main-content">
<FadeContent class="hero-tag-fade" blur>
<router-link to="/backgrounds/gradient-blinds" class="hero-new-badge-container">
<span class="hero-new-badge">New 🎉</span>
<div class="hero-new-badge-text">
<span>Gradient Blinds</span>
<GoArrowRight />
</div>
</router-link>
</FadeContent>
<h1 class="landing-title">
<ResponsiveSplitText
:is-mobile="isMobile"
text="Animated Vue components"
text="Vue Components"
class-name="hero-split"
split-type="chars"
:delay="30"
@@ -16,7 +44,7 @@
<ResponsiveSplitText
:is-mobile="isMobile"
text="for creative developers"
text="For Creative Developers"
class-name="hero-split"
split-type="chars"
:delay="30"
@@ -29,9 +57,9 @@
:is-mobile="isMobile"
class-name="landing-subtitle"
split-type="words"
:delay="10"
:delay="25"
:duration="1"
text="Eighty-plus snippets, ready to be dropped into your Vue projects"
text="Highly customizable animated components that make your React projects truly stand out"
/>
<router-link to="/text-animations/split-text" class="landing-button">
@@ -44,45 +72,14 @@
</div>
</router-link>
</div>
<div v-if="!isMobile" class="hero-cards-container">
<div class="hero-card hero-card-1" @click="openUrl('https://vue-bits.dev/backgrounds/dot-grid')">
<div class="w-full h-full relative hero-dot-grid">
<DotGrid
base-color="#ffffff"
active-color="rgba(138, 43, 226, 0.9)"
:dot-size="8"
:gap="16"
:proximity="50"
/>
<div class="placeholder-card"></div>
</div>
</div>
<div class="hero-cards-row">
<div class="hero-card hero-card-2" @click="openUrl('https://vue-bits.dev/backgrounds/letter-glitch')">
<LetterGlitch class-name="hero-glitch" :glitch-colors="['#ffffff', '#999999', '#333333']" />
<div class="placeholder-card"></div>
</div>
<div class="hero-card hero-card-3" @click="openUrl('https://vue-bits.dev/backgrounds/squares')">
<Squares border-color="#fff" :speed="0.2" direction="diagonal" hover-fill-color="#fff" />
<div class="placeholder-card"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, h, defineComponent } from 'vue';
import DotGrid from '@/content/Backgrounds/DotGrid/DotGrid.vue';
import SplitText from '@/content/TextAnimations/SplitText/SplitText.vue';
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue';
import Squares from '@/content/Backgrounds/Squares/Squares.vue';
import { defineComponent, h, onMounted, onUnmounted, ref } from 'vue';
import landingBlur from '../../../assets/common/landing-blur.svg';
import FadeContent from '../../../content/Animations/FadeContent/FadeContent.vue';
const ResponsiveSplitText = defineComponent({
props: {
@@ -122,10 +119,6 @@ const ResponsiveSplitText = defineComponent({
}
});
const openUrl = (url: string) => {
window.open(url);
};
const isMobile = ref(false);
const checkIsMobile = () => {

View File

@@ -0,0 +1,392 @@
<script setup lang="ts">
import { Camera, Geometry, Mesh, Program, Renderer, Transform } from 'ogl';
import { onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
const vertex = /* glsl */ `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = /* glsl */ `
precision mediump float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 uOffset;
uniform float uRotation;
uniform float focalLength;
uniform float speed1;
uniform float speed2;
uniform float dir2;
uniform float bend1;
uniform float bend2;
uniform float bendAdj1;
uniform float bendAdj2;
uniform float uOpacity;
const float lt = 0.3;
const float pi = 3.141592653589793;
const float pi2 = pi * 2.0;
const float pi_2 = pi * 0.5;
#define MAX_STEPS 15
void mainImage(out vec4 C, in vec2 U) {
float t = iTime * pi;
float s = 1.0;
float d = 0.0;
vec2 R = iResolution;
vec2 m = vec2(0.0);
vec3 o = vec3(0.0, 0.0, -7.0);
vec3 u = normalize(vec3((U - 0.5 * R) / R.y, focalLength));
vec3 k = vec3(0.0);
vec3 p;
float t1 = t * 0.7;
float t2 = t * 0.9;
float tSpeed1 = t * speed1;
float tSpeed2 = t * speed2 * dir2;
for (int step = 0; step < MAX_STEPS; ++step) {
p = o + u * d;
p.x -= 15.0;
float px = p.x;
float wob1 = bend1 + bendAdj1 + sin(t1 + px * 0.8) * 0.1;
float wob2 = bend2 + bendAdj2 + cos(t2 + px * 1.1) * 0.1;
float px2 = px + pi_2;
vec2 baseOffset = vec2(px, px2);
vec2 sinOffset = sin(baseOffset + tSpeed1) * wob1;
vec2 cosOffset = cos(baseOffset + tSpeed2) * wob2;
vec2 yz = p.yz;
float wSin = length(yz - sinOffset) - lt;
float wCos = length(yz - cosOffset) - lt;
k.x = max(px + lt, wSin);
k.y = max(px + lt, wCos);
float current = min(k.x, k.y);
s = min(s, current);
if (s < 0.001 || d > 400.0) break;
d += s * 0.7;
}
vec3 c = max(cos(d * pi2) - s * sqrt(d) - k, 0.0);
c.gb += 0.1;
vec3 vueGreen = vec3(0.259, 0.722, 0.514); // #42B883
vec3 vueDark = vec3(0.208, 0.286, 0.369); // #35495e
vec3 finalColor = vec3(0.0);
if (k.x < k.y) {
finalColor = vueGreen * c.x;
} else {
finalColor = vueDark * c.y;
}
float intensity = max(finalColor.r, max(finalColor.g, finalColor.b));
if (intensity < 0.15) discard;
finalColor = finalColor * 0.4 + finalColor.brg * 0.6 + finalColor * finalColor;
C = vec4(clamp(finalColor, 0.0, 1.0), uOpacity);
}
void main() {
vec2 coord = gl_FragCoord.xy + uOffset;
coord -= 0.5 * iResolution;
float c = cos(uRotation), s = sin(uRotation);
coord = mat2(c, -s, s, c) * coord;
coord += 0.5 * iResolution;
vec4 color;
mainImage(color, coord);
gl_FragColor = color;
}
`;
interface AnimationConfig {
xOffset: number;
yOffset: number;
rotationDeg: number;
focalLength: number;
speed1: number;
speed2: number;
dir2: number;
bend1: number;
bend2: number;
fadeInDuration: number;
}
const props = withDefaults(defineProps<Partial<AnimationConfig>>(), {
xOffset: 0,
yOffset: 0,
rotationDeg: 0,
focalLength: 1,
speed1: 0.1,
speed2: 0.1,
dir2: 1.0,
bend1: 0.9,
bend2: 0.6,
fadeInDuration: 2000
});
const isMobile = ref(false);
const isVisible = ref(true);
const containerRef = ref<HTMLElement | null>(null);
const uniformOffset = ref(new Float32Array([props.xOffset, props.yOffset]));
const uniformResolution = ref(new Float32Array([1, 1]));
const rendererRef = ref<Renderer | null>(null);
const fadeStartTime = ref<number | null>(null);
const lastTimeRef = ref(0);
const pausedTimeRef = ref(0);
const propsRef = ref({
xOffset: props.xOffset,
yOffset: props.yOffset,
rotationDeg: props.rotationDeg,
focalLength: props.focalLength,
speed1: props.speed1,
speed2: props.speed2,
dir2: props.dir2,
bend1: props.bend1,
bend2: props.bend2,
fadeInDuration: props.fadeInDuration
});
propsRef.value = {
xOffset: props.xOffset,
yOffset: props.yOffset,
rotationDeg: props.rotationDeg,
focalLength: props.focalLength,
speed1: props.speed1,
speed2: props.speed2,
dir2: props.dir2,
bend1: props.bend1,
bend2: props.bend2,
fadeInDuration: props.fadeInDuration
};
let removeResizeListener: (() => void) | null = null;
let removeObserver: (() => void) | null = null;
const handleResize = () => {
const checkIsMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
removeResizeListener = () => {
window.removeEventListener('resize', checkIsMobile);
};
};
const handleObserver = () => {
if (!containerRef.value || isMobile) return;
const observer = new IntersectionObserver(
([entry]) => {
isVisible.value = entry.isIntersecting;
},
{
rootMargin: '50px',
threshold: 0.1
}
);
observer.observe(containerRef.value);
removeObserver = () => observer.disconnect();
};
onMounted(() => {
handleResize();
handleObserver();
});
onUnmounted(() => {
removeResizeListener?.();
removeObserver?.();
});
watch(
isMobile,
() => {
handleObserver();
},
{
deep: true
}
);
let cleanup: (() => void) | null = null;
const setup = () => {
if (isMobile.value) {
return;
}
const renderer = new Renderer({
alpha: true,
dpr: Math.min(window.devicePixelRatio, 1),
antialias: false,
depth: false,
stencil: false,
powerPreference: 'high-performance'
});
rendererRef.value = renderer;
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
if (containerRef.value) {
containerRef.value.appendChild(gl.canvas);
}
const camera = new Camera(gl);
const scene = new Transform();
const geometry = new Geometry(gl, {
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }
});
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
iTime: { value: 0 },
iResolution: { value: uniformResolution.value },
uOffset: { value: uniformOffset.value },
uRotation: { value: 0 },
focalLength: { value: props.focalLength },
speed1: { value: props.speed1 },
speed2: { value: props.speed2 },
dir2: { value: props.dir2 },
bend1: { value: props.bend1 },
bend2: { value: props.bend2 },
bendAdj1: { value: 0 },
bendAdj2: { value: 0 },
uOpacity: { value: 0 }
}
});
new Mesh(gl, { geometry, program }).setParent(scene);
const resize = () => {
const { width, height } = containerRef.value?.getBoundingClientRect() || { width: 0, height: 0 };
renderer.setSize(width, height);
uniformResolution.value[0] = width * renderer.dpr;
uniformResolution.value[1] = height * renderer.dpr;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
};
resize();
const ro = new ResizeObserver(resize);
if (containerRef.value) {
ro.observe(containerRef.value);
}
let rafId: number;
const loop = (now: number) => {
const {
xOffset: xOff,
yOffset: yOff,
rotationDeg: rot,
focalLength: fLen,
fadeInDuration: fadeDur
} = propsRef.value;
if (isVisible.value) {
if (lastTimeRef.value === 0) {
lastTimeRef.value = now - pausedTimeRef.value;
}
const t = (now - lastTimeRef.value) * 0.001;
if (fadeStartTime.value === null && t > 0.1) {
fadeStartTime.value = now;
}
let opacity = 0;
if (fadeStartTime.value !== null) {
const fadeElapsed = now - fadeStartTime.value;
opacity = Math.min(fadeElapsed / fadeDur, 1);
opacity = 1 - Math.pow(1 - opacity, 3);
}
uniformOffset.value[0] = xOff;
uniformOffset.value[1] = yOff;
program.uniforms.iTime.value = t;
program.uniforms.uRotation.value = (rot * Math.PI) / 180;
program.uniforms.focalLength.value = fLen;
program.uniforms.uOpacity.value = opacity;
renderer.render({ scene, camera });
} else {
if (lastTimeRef.value !== 0) {
pausedTimeRef.value = now - lastTimeRef.value;
lastTimeRef.value = 0;
}
}
rafId = requestAnimationFrame(loop);
};
rafId = requestAnimationFrame(loop);
cleanup = () => {
cancelAnimationFrame(rafId);
ro.disconnect();
renderer.gl.canvas.remove();
};
};
onMounted(() => {
setup();
});
onBeforeUnmount(() => {
cleanup?.();
});
watch(
() => [isMobile, isVisible],
() => {
cleanup?.();
setup();
},
{
deep: true
}
);
</script>
<template>
<div
v-if="!isMobile"
ref="containerRef"
:style="{
position: 'absolute',
inset: 0,
overflow: 'hidden',
width: '100vw',
height: '100vh'
}"
>
<div
:style="{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 200,
background: 'linear-gradient(to top, #060010, transparent)',
pointerEvents: 'none',
zIndex: 1
}"
/>
</div>
</template>

View File

@@ -17,10 +17,11 @@
max-width: 1200px;
user-select: none;
margin: 0 auto;
background: linear-gradient(135deg, #3aed6d, rgba(24, 255, 93, 0.6));
background: linear-gradient(135deg, rgba(58, 237, 109, 1), rgba(23, 255, 66, 0.6));
background-size: 200% 200%;
border-radius: 16px;
border-radius: 50px;
padding: 4rem 3rem;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.5);
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
@@ -40,9 +41,9 @@
width: 100%;
height: 100%;
background-image: url('/assets/grain.webp');
filter: brightness(3);
background-size: 500px 500px;
filter: invert(100%);
mix-blend-mode: multiply;
mix-blend-mode: lighten;
background-repeat: repeat;
opacity: 1;
pointer-events: none;
@@ -56,9 +57,8 @@
.start-building-title {
color: #0b0b0b;
font-size: 2.6rem;
font-weight: 600;
margin: 0;
font-size: 3rem;
font-weight: 500;
line-height: 1;
}
@@ -66,19 +66,20 @@
color: #0b0b0b;
font-size: 1.2rem;
font-weight: 500;
margin: -1rem 0 0 0;
opacity: 0.9;
opacity: 0.6;
max-width: 600px;
line-height: 1.4;
line-height: 1;
}
.start-building-button {
background: transparent;
background: white;
border: 1px solid white;
color: #0b0b0b;
border: 2px solid #0b0b0b;
padding: 0.6rem 1.6rem;
font-size: 1.1rem;
font-weight: 600;
padding: 0.6rem 1.8rem;
font-size: 1rem;
font-weight: 500;
letter-spacing: -0.2px;
border-radius: 50px;
cursor: pointer;
transition: all 0.2s ease;
@@ -111,7 +112,6 @@
.start-building-card {
padding: 3rem 2rem;
border-radius: 12px;
gap: 1.25rem;
}

View File

@@ -2,8 +2,8 @@
<section class="start-building-section">
<div class="start-building-container">
<div class="start-building-card">
<h2 class="start-building-title">Start exploring Vue Bits</h2>
<p class="start-building-subtitle">Animations, components, backgrounds - it's all here</p>
<h2 class="start-building-title">Start Exploring</h2>
<p class="start-building-subtitle">Animations, Components, Backgrounds - One Click Away</p>
<router-link to="/text-animations/split-text" class="start-building-button">Browse Components</router-link>
</div>