Add prettier config, format codebase

This commit is contained in:
David Haz
2025-07-12 11:59:33 +03:00
parent ac8b2c04d8
commit f4d97ee94e
211 changed files with 10586 additions and 8810 deletions

View File

@@ -77,7 +77,9 @@
color: inherit;
font-weight: 400;
opacity: 0.6;
transition: opacity 0.3s ease, transform 0.2s ease;
transition:
opacity 0.3s ease,
transform 0.2s ease;
}
.nav-link:hover {
@@ -106,9 +108,7 @@
font-weight: 500;
padding: 0 0 0 1.4rem;
height: calc(60px - 2px);
background: linear-gradient(135deg,
rgb(30, 160, 63),
rgba(24, 47, 255, 0.6));
background: linear-gradient(135deg, rgb(30, 160, 63), rgba(24, 47, 255, 0.6));
background-size: 200% 200%;
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
@@ -121,14 +121,14 @@
align-items: center;
white-space: nowrap;
justify-content: space-between;
transition: .3s ease;
transition: 0.3s ease;
}
.cta-button span {
background-color: #0b0b0b;
margin-left: 1em;
margin-right: calc(1em - 8px);
padding-top: .1em;
padding-top: 0.1em;
height: 45px;
border-radius: 50px;
width: 100px;
@@ -143,16 +143,16 @@
margin-right: 6px;
width: 16px;
height: 16px;
transition: .3s ease;
transition: 0.3s ease;
}
.cta-button:hover {
transition: .3s ease;
transition: 0.3s ease;
}
.cta-button:hover span img {
transform: scale(1.2);
transition: .3s ease;
transition: 0.3s ease;
}
@media (max-width: 900px) {
@@ -307,4 +307,4 @@
.nav-cta-group {
gap: 2rem;
}
}
}

View File

@@ -7,22 +7,12 @@
<div class="nav-cta-group">
<nav class="landing-nav-items" ref="navRef">
<router-link
class="nav-link"
:class="{ 'active-link': activeItem === 'home' }"
to="/"
>
Home
</router-link>
<router-link class="nav-link" to="/text-animations/split-text">
Docs
</router-link>
<router-link class="nav-link" :class="{ 'active-link': activeItem === 'home' }" to="/">Home</router-link>
<router-link class="nav-link" to="/text-animations/split-text">Docs</router-link>
</nav>
<button
class="cta-button"
@click="openGitHub"
>
<button class="cta-button" @click="openGitHub">
Star On GitHub
<span ref="starCountRef" :style="{ opacity: 0 }">
<img :src="starIcon" alt="Star Icon" />
@@ -35,43 +25,48 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { gsap } from 'gsap'
import VueBitsLogo from '@/components/common/Logo.vue'
import { useStars } from '@/composables/useStars'
import starIcon from '@/assets/common/star.svg'
import './DisplayHeader.css'
import { ref, watch } from 'vue';
import { gsap } from 'gsap';
import VueBitsLogo from '@/components/common/Logo.vue';
import { useStars } from '@/composables/useStars';
import starIcon from '@/assets/common/star.svg';
import './DisplayHeader.css';
interface Props {
activeItem?: string | null;
}
defineProps<Props>()
defineProps<Props>();
const navRef = ref<HTMLElement | null>(null)
const starCountRef = ref<HTMLElement | null>(null)
const stars = useStars()
const navRef = ref<HTMLElement | null>(null);
const starCountRef = ref<HTMLElement | null>(null);
const stars = useStars();
const openGitHub = () => {
window.open('https://github.com/DavidHDev/vue-bits', '_blank')
}
window.open('https://github.com/DavidHDev/vue-bits', '_blank');
};
watch(stars, (newStars) => {
if (newStars && starCountRef.value) {
gsap.fromTo(starCountRef.value,
{
scale: 0,
width: 0,
opacity: 0
},
{
scale: 1,
width: "100px",
opacity: 1,
duration: 0.8,
ease: "back.out(1)"
}
)
}
}, { immediate: true })
watch(
stars,
newStars => {
if (newStars && starCountRef.value) {
gsap.fromTo(
starCountRef.value,
{
scale: 0,
width: 0,
opacity: 0
},
{
scale: 1,
width: '100px',
opacity: 1,
duration: 0.8,
ease: 'back.out(1)'
}
);
}
},
{ immediate: true }
);
</script>

View File

@@ -22,7 +22,7 @@
font-weight: 600;
letter-spacing: -2px;
color: #fff;
margin-bottom: .2rem;
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;
@@ -44,7 +44,6 @@
}
@keyframes gradientShift {
0%,
100% {
background-position: 0% 50%;
@@ -120,8 +119,6 @@
grid-column: 2 / 3;
grid-row: 2 / 3;
}
}
@media (min-width: 50rem) {
@@ -145,8 +142,6 @@
grid-column: 1 / 3;
grid-row: 2 / 3;
}
}
@media (min-width: 768px) and (max-width: 49.99rem) {
@@ -169,8 +164,6 @@
grid-column: 1 / 2;
grid-row: 2 / 3;
}
}
.feature-card {
@@ -212,14 +205,20 @@
position: absolute;
inset: 0;
padding: 1px;
background: radial-gradient(200px circle at var(--glow-x) var(--glow-y),
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
transparent 60%);
background: radial-gradient(
200px circle at var(--glow-x) var(--glow-y),
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
transparent 60%
);
border-radius: inherit;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: subtract;
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
pointer-events: none;
transition: opacity 0.3s ease;
@@ -230,8 +229,6 @@
background: #07160a;
}
.feature-card:hover::before {
opacity: 1;
}
@@ -314,7 +311,9 @@
}
.feature-card.particle-container:hover {
box-shadow: 0 4px 20px rgba(24, 78, 42, 0.4), 0 0 30px rgba(0, 255, 76, 0.2);
box-shadow:
0 4px 20px rgba(24, 78, 42, 0.4),
0 0 30px rgba(0, 255, 76, 0.2);
background: #07160b;
}
@@ -481,8 +480,6 @@
z-index: 2;
}
@media (max-width: 479px) {
.features-section {
padding: 4rem 1rem 2rem;
@@ -510,8 +507,6 @@
font-size: 4rem;
}
.feature-card h3 {
font-size: 1rem;
margin-bottom: 0.5rem;
@@ -553,8 +548,6 @@
font-size: 4rem;
}
.feature-card h3 {
font-size: 0.95rem;
margin-bottom: 0.4rem;
@@ -659,4 +652,4 @@
.feature-card p {
font-size: 0.8rem;
}
}
}

View File

@@ -3,6 +3,7 @@
<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>
@@ -13,11 +14,16 @@
<div className="messages-gif-wrapper">
<img src="/assets/messages.gif" alt="Messages animation" className="messages-gif" />
</div>
<h2>
<template v-if="isMobile">100</template>
<CountUp v-else :to="100" />%
<CountUp v-else :to="100" />
%
</h2>
<h3>Free &amp; Open Source</h3>
<p>Loved by developers around the world</p>
</ParticleCard>
@@ -25,19 +31,24 @@
<div className="components-gif-wrapper">
<img src="/assets/components.gif" alt="Components animation" className="components-gif" />
</div>
<h2>
<template v-if="isMobile">40</template>
<CountUp v-else :to="40" />+
<CountUp v-else :to="40" />
+
</h2>
<h3>Curated Components</h3>
<p>Growing weekly &amp; only getting better</p>
</ParticleCard>
<ParticleCard class="feature-card card4" :disable-animations="isMobile">
<h2>
Modern
</h2>
<h2>Modern</h2>
<h3>Technologies</h3>
<p>TypeScript + Tailwind, ready to ship</p>
</ParticleCard>
</div>
@@ -46,26 +57,26 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue'
import { gsap } from 'gsap'
import CountUp from '../../../content/Animations/CountUp/CountUp.vue'
import './FeatureCards.css'
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue';
import { gsap } from 'gsap';
import CountUp from '../../../content/Animations/CountUp/CountUp.vue';
import './FeatureCards.css';
const isMobile = ref(false)
const gridRef = ref<HTMLDivElement | null>(null)
const isMobile = ref(false);
const gridRef = ref<HTMLDivElement | null>(null);
const checkIsMobile = () => {
isMobile.value = window.innerWidth <= 768
}
isMobile.value = window.innerWidth <= 768;
};
onMounted(() => {
checkIsMobile()
window.addEventListener('resize', checkIsMobile)
})
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
});
onUnmounted(() => {
window.removeEventListener('resize', checkIsMobile)
})
window.removeEventListener('resize', checkIsMobile);
});
const ParticleCard = defineComponent({
name: 'ParticleCard',
@@ -76,114 +87,119 @@ const ParticleCard = defineComponent({
}
},
setup(props, { slots }) {
const cardRef = ref<HTMLDivElement | null>(null)
const particlesRef = ref<HTMLDivElement[]>([])
const timeoutsRef = ref<number[]>([])
const isHoveredRef = ref(false)
const memoizedParticles = ref<HTMLDivElement[]>([])
const particlesInit = ref(false)
const cardRef = ref<HTMLDivElement | null>(null);
const particlesRef = ref<HTMLDivElement[]>([]);
const timeoutsRef = ref<number[]>([]);
const isHoveredRef = ref(false);
const memoizedParticles = ref<HTMLDivElement[]>([]);
const particlesInit = ref(false);
const createParticle = (x: number, y: number): HTMLDivElement => {
const el = document.createElement('div')
el.className = 'particle'
const el = document.createElement('div');
el.className = 'particle';
el.style.cssText = `
position:absolute;width:4px;height:4px;border-radius:50%;
background:rgba(132,0,255,1);box-shadow:0 0 6px rgba(132,0,255,.6);
pointer-events:none;z-index:100;left:${x}px;top:${y}px;
`
return el
}
`;
return el;
};
const memoizeParticles = () => {
if (particlesInit.value || !cardRef.value) return
const { width, height } = cardRef.value.getBoundingClientRect()
if (particlesInit.value || !cardRef.value) return;
const { width, height } = cardRef.value.getBoundingClientRect();
Array.from({ length: 12 }).forEach(() => {
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height))
})
particlesInit.value = true
}
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height));
});
particlesInit.value = true;
};
const clearParticles = () => {
timeoutsRef.value.forEach(clearTimeout)
timeoutsRef.value = []
timeoutsRef.value.forEach(clearTimeout);
timeoutsRef.value = [];
particlesRef.value.forEach(p =>
gsap.to(p, {
scale: 0,
opacity: 0,
duration: 0.3,
ease: "back.in(1.7)",
ease: 'back.in(1.7)',
onComplete: () => {
if (p.parentNode) {
p.parentNode.removeChild(p)
p.parentNode.removeChild(p);
}
},
}
})
)
particlesRef.value = []
}
);
particlesRef.value = [];
};
const animateParticles = () => {
if (!cardRef.value || !isHoveredRef.value) return
if (!particlesInit.value) memoizeParticles()
if (!cardRef.value || !isHoveredRef.value) return;
if (!particlesInit.value) memoizeParticles();
memoizedParticles.value.forEach((particle, i) => {
const id = setTimeout(() => {
if (!isHoveredRef.value || !cardRef.value) return
const clone = particle.cloneNode(true) as HTMLDivElement
cardRef.value.appendChild(clone)
particlesRef.value.push(clone)
if (!isHoveredRef.value || !cardRef.value) return;
const clone = particle.cloneNode(true) as HTMLDivElement;
cardRef.value.appendChild(clone);
particlesRef.value.push(clone);
gsap.set(clone, { scale: 0, opacity: 0 })
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: "back.out(1.7)" })
gsap.set(clone, { scale: 0, opacity: 0 });
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)' });
gsap.to(clone, {
x: (Math.random() - 0.5) * 100,
y: (Math.random() - 0.5) * 100,
rotation: Math.random() * 360,
duration: 2 + Math.random() * 2,
ease: "none",
ease: 'none',
repeat: -1,
yoyo: true,
})
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: "power2.inOut", repeat: -1, yoyo: true })
}, i * 100)
timeoutsRef.value.push(id)
})
}
yoyo: true
});
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: 'power2.inOut', repeat: -1, yoyo: true });
}, i * 100);
timeoutsRef.value.push(id);
});
};
const handleMouseEnter = () => {
isHoveredRef.value = true
animateParticles()
}
isHoveredRef.value = true;
animateParticles();
};
const handleMouseLeave = () => {
isHoveredRef.value = false
clearParticles()
}
isHoveredRef.value = false;
clearParticles();
};
onMounted(() => {
if (props.disableAnimations || !cardRef.value) return
if (props.disableAnimations || !cardRef.value) return;
const node = cardRef.value
node.addEventListener('mouseenter', handleMouseEnter)
node.addEventListener('mouseleave', handleMouseLeave)
})
const node = cardRef.value;
node.addEventListener('mouseenter', handleMouseEnter);
node.addEventListener('mouseleave', handleMouseLeave);
});
onUnmounted(() => {
if (cardRef.value) {
cardRef.value.removeEventListener('mouseenter', handleMouseEnter)
cardRef.value.removeEventListener('mouseleave', handleMouseLeave)
cardRef.value.removeEventListener('mouseenter', handleMouseEnter);
cardRef.value.removeEventListener('mouseleave', handleMouseLeave);
}
isHoveredRef.value = false
clearParticles()
})
isHoveredRef.value = false;
clearParticles();
});
return () => h('div', {
ref: cardRef,
class: 'particle-container',
style: { position: 'relative', overflow: 'hidden' }
}, slots.default?.())
return () =>
h(
'div',
{
ref: cardRef,
class: 'particle-container',
style: { position: 'relative', overflow: 'hidden' }
},
slots.default?.()
);
}
})
});
const GlobalSpotlight = defineComponent({
name: 'GlobalSpotlight',
@@ -198,89 +214,88 @@ const GlobalSpotlight = defineComponent({
}
},
setup(props) {
const spotlightRef = ref<HTMLDivElement | null>(null)
const isInsideSectionRef = ref(false)
const spotlightRef = ref<HTMLDivElement | null>(null);
const isInsideSectionRef = ref(false);
const handleMouseMove = (e: MouseEvent) => {
if (!spotlightRef.value || !props.gridRef.value) return
const section = props.gridRef.value.closest('.features-section')
const rect = section?.getBoundingClientRect()
if (!spotlightRef.value || !props.gridRef.value) return;
const section = props.gridRef.value.closest('.features-section');
const rect = section?.getBoundingClientRect();
const inside =
rect &&
e.clientX >= rect.left && e.clientX <= rect.right &&
e.clientY >= rect.top && e.clientY <= rect.bottom
rect && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
isInsideSectionRef.value = inside
const cards = props.gridRef.value.querySelectorAll('.feature-card')
isInsideSectionRef.value = inside;
const cards = props.gridRef.value.querySelectorAll('.feature-card');
if (!inside) {
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
return
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
return;
}
let minDist = Infinity
const prox = 100, fade = 150
let minDist = Infinity;
const prox = 100,
fade = 150;
cards.forEach((card: HTMLElement) => {
const r = card.getBoundingClientRect()
const cx = r.left + r.width / 2
const cy = r.top + r.height / 2
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2
const ed = Math.max(0, d)
minDist = Math.min(minDist, ed)
const r = card.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2;
const ed = Math.max(0, d);
minDist = Math.min(minDist, ed);
const rx = ((e.clientX - r.left) / r.width) * 100
const ry = ((e.clientY - r.top) / r.height) * 100
let glow = 0
if (ed <= prox) glow = 1
else if (ed <= fade) glow = (fade - ed) / (fade - prox)
card.style.setProperty('--glow-x', `${rx}%`)
card.style.setProperty('--glow-y', `${ry}%`)
card.style.setProperty('--glow-intensity', String(glow))
})
const rx = ((e.clientX - r.left) / r.width) * 100;
const ry = ((e.clientY - r.top) / r.height) * 100;
let glow = 0;
if (ed <= prox) glow = 1;
else if (ed <= fade) glow = (fade - ed) / (fade - prox);
card.style.setProperty('--glow-x', `${rx}%`);
card.style.setProperty('--glow-y', `${ry}%`);
card.style.setProperty('--glow-intensity', String(glow));
});
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: "power2.out" })
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: "power2.out" })
}
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: 'power2.out' });
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0;
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: 'power2.out' });
};
const handleMouseLeave = () => {
isInsideSectionRef.value = false
isInsideSectionRef.value = false;
props.gridRef.value
?.querySelectorAll('.feature-card')
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
if (spotlightRef.value) {
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
}
}
};
onMounted(() => {
if (props.disableAnimations || !props.gridRef?.value) return
if (props.disableAnimations || !props.gridRef?.value) return;
const spotlight = document.createElement('div')
spotlight.className = 'global-spotlight'
const spotlight = document.createElement('div');
spotlight.className = 'global-spotlight';
spotlight.style.cssText = `
position:fixed;width:800px;height:800px;border-radius:50%;pointer-events:none;
background:radial-gradient(circle,rgba(132,0,255,.15) 0%,rgba(132,0,255,.08) 15%,
rgba(132,0,255,.04) 25%,rgba(132,0,255,.02) 40%,rgba(132,0,255,.01) 65%,transparent 70%);
z-index:200;opacity:0;transform:translate(-50%,-50%);mix-blend-mode:screen;
`
document.body.appendChild(spotlight)
spotlightRef.value = spotlight
`;
document.body.appendChild(spotlight);
spotlightRef.value = spotlight;
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseleave', handleMouseLeave)
})
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseleave', handleMouseLeave);
});
onUnmounted(() => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseleave', handleMouseLeave)
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseleave', handleMouseLeave);
if (spotlightRef.value?.parentNode) {
spotlightRef.value.parentNode.removeChild(spotlightRef.value)
spotlightRef.value.parentNode.removeChild(spotlightRef.value);
}
})
});
return () => null
return () => null;
}
})
</script>
});
</script>

View File

@@ -48,13 +48,13 @@
}
.footer-heart {
color: #27FF64;
color: #27ff64;
font-size: 1em;
display: inline-block;
}
.footer-creator-link {
color: #27FF64;
color: #27ff64;
text-decoration: none;
transition: color 0.2s ease;
}
@@ -152,4 +152,4 @@
padding: 0.5rem 0.75rem;
text-align: center;
}
}
}

View File

@@ -4,10 +4,14 @@
<div class="footer-content">
<div class="footer-left">
<img :src="vueBitsLogo" alt="Vue Bits" class="footer-logo" />
<p class="footer-description">
A library created with <i class="pi pi-heart-fill footer-heart"></i> by
A library created with
<i class="pi pi-heart-fill footer-heart"></i>
by
<a href="https://davidhaz.com/" target="_blank" class="footer-creator-link">this guy</a>
</p>
<p class="footer-copyright">© {{ currentYear }} Vue Bits</p>
</div>
@@ -15,15 +19,12 @@
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" rel="noopener noreferrer" class="footer-link">
GitHub
</a>
<router-link to="/text-animations/split-text" class="footer-link">
Docs
</router-link>
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">
CLI
</a>
<a href="https://reactbits.dev/" target="_blank" class="footer-link">
React Bits
</a>
<router-link to="/text-animations/split-text" class="footer-link">Docs</router-link>
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">CLI</a>
<a href="https://reactbits.dev/" target="_blank" class="footer-link">React Bits</a>
</div>
</div>
</footer>
@@ -31,10 +32,10 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg'
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue'
import './Footer.css'
import { computed } from 'vue';
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg';
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue';
import './Footer.css';
const currentYear = computed(() => new Date().getFullYear())
</script>
const currentYear = computed(() => new Date().getFullYear());
</script>

View File

@@ -2,18 +2,41 @@
<div class="landing-content">
<div class="hero-main-content">
<h1 class="landing-title">
<ResponsiveSplitText :is-mobile="isMobile" text="Animated Vue components" class-name="hero-split"
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
<ResponsiveSplitText
:is-mobile="isMobile"
text="Animated Vue components"
class-name="hero-split"
split-type="chars"
:delay="30"
:duration="2"
ease="elastic.out(0.5, 0.3)"
/>
<br />
<ResponsiveSplitText :is-mobile="isMobile" text="for creative developers" class-name="hero-split"
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
<ResponsiveSplitText
:is-mobile="isMobile"
text="for creative developers"
class-name="hero-split"
split-type="chars"
:delay="30"
:duration="2"
ease="elastic.out(0.5, 0.3)"
/>
</h1>
<ResponsiveSplitText :is-mobile="isMobile" class-name="landing-subtitle" split-type="words" :delay="10"
:duration="1" text="Eighty-plus snippets, ready to be dropped into your Vue projects" />
<ResponsiveSplitText
:is-mobile="isMobile"
class-name="landing-subtitle"
split-type="words"
:delay="10"
:duration="1"
text="Eighty-plus snippets, ready to be dropped into your Vue projects"
/>
<router-link to="/text-animations/split-text" class="landing-button">
<span>Browse Components</span>
<div class="button-arrow-circle">
<svg width="16" height="16" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
<path d="M6 12L10 8L6 4" stroke="#0b0b0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
@@ -25,8 +48,14 @@
<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" />
<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>
@@ -34,11 +63,13 @@
<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>
@@ -47,11 +78,11 @@
</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 { 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';
const ResponsiveSplitText = defineComponent({
props: {
@@ -71,7 +102,7 @@ const ResponsiveSplitText = defineComponent({
},
render() {
if (this.isMobile) {
return h('span', { class: this.className }, this.text)
return h('span', { class: this.className }, this.text);
} else {
return h(SplitText, {
text: this.text,
@@ -86,29 +117,29 @@ const ResponsiveSplitText = defineComponent({
rootMargin: this.rootMargin,
textAlign: this.textAlign,
onLetterAnimationComplete: this.onLetterAnimationComplete as (() => void) | undefined
})
});
}
}
})
});
const openUrl = (url: string) => {
window.open(url)
}
window.open(url);
};
const isMobile = ref(false)
const isMobile = ref(false);
const checkIsMobile = () => {
isMobile.value = window.innerWidth <= 768
}
isMobile.value = window.innerWidth <= 768;
};
onMounted(() => {
checkIsMobile()
window.addEventListener('resize', checkIsMobile)
})
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
});
onUnmounted(() => {
window.removeEventListener('resize', checkIsMobile)
})
window.removeEventListener('resize', checkIsMobile);
});
</script>
<style scoped>
@@ -119,4 +150,4 @@ onUnmounted(() => {
width: 100%;
height: 100%;
}
</style>
</style>

View File

@@ -1,29 +1,32 @@
<template>
<div v-if="!isMobile" ref="containerRef" :style="{
position: 'absolute',
inset: 0,
overflow: 'hidden',
width: '100vw',
height: '100vh'
}">
</div>
<div
v-if="!isMobile"
ref="containerRef"
:style="{
position: 'absolute',
inset: 0,
overflow: 'hidden',
width: '100vw',
height: '100vh'
}"
></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl'
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl';
interface Props {
xOffset?: number
yOffset?: number
rotationDeg?: number
focalLength?: number
speed1?: number
speed2?: number
dir2?: number
bend1?: number
bend2?: number
fadeInDuration?: number
xOffset?: number;
yOffset?: number;
rotationDeg?: number;
focalLength?: number;
speed1?: number;
speed2?: number;
dir2?: number;
bend1?: number;
bend2?: number;
fadeInDuration?: number;
}
const props = withDefaults(defineProps<Props>(), {
@@ -37,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
bend1: 0.9,
bend2: 0.6,
fadeInDuration: 2000
})
});
const vertex = /* glsl */ `
attribute vec2 position;
@@ -46,7 +49,7 @@ void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}
`
`;
const fragment = /* glsl */ `
precision mediump float;
@@ -143,40 +146,40 @@ void main() {
mainImage(color, coord);
gl_FragColor = color;
}
`
`;
const isMobile = ref(false)
const isVisible = ref(true)
const containerRef = ref<HTMLDivElement | 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 rafId = ref<number | null>(null)
const resizeObserver = ref<ResizeObserver | null>(null)
const intersectionObserver = ref<IntersectionObserver | null>(null)
const isMobile = ref(false);
const isVisible = ref(true);
const containerRef = ref<HTMLDivElement | 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 rafId = ref<number | null>(null);
const resizeObserver = ref<ResizeObserver | null>(null);
const intersectionObserver = ref<IntersectionObserver | null>(null);
const checkIsMobile = () => {
isMobile.value = window.innerWidth <= 768
}
isMobile.value = window.innerWidth <= 768;
};
const resize = () => {
if (!containerRef.value || !rendererRef.value) return
if (!containerRef.value || !rendererRef.value) return;
const { width, height } = containerRef.value.getBoundingClientRect()
rendererRef.value.setSize(width, height)
uniformResolution.value[0] = width * rendererRef.value.dpr
uniformResolution.value[1] = height * rendererRef.value.dpr
const { width, height } = containerRef.value.getBoundingClientRect();
rendererRef.value.setSize(width, height);
uniformResolution.value[0] = width * rendererRef.value.dpr;
uniformResolution.value[1] = height * rendererRef.value.dpr;
const gl = rendererRef.value.gl
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
gl.clear(gl.COLOR_BUFFER_BIT)
}
const gl = rendererRef.value.gl;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clear(gl.COLOR_BUFFER_BIT);
};
const initWebGL = () => {
if (isMobile.value || !containerRef.value) return
if (isMobile.value || !containerRef.value) return;
const renderer = new Renderer({
alpha: true,
@@ -184,20 +187,20 @@ const initWebGL = () => {
antialias: false,
depth: false,
stencil: false,
powerPreference: 'high-performance',
})
rendererRef.value = renderer
powerPreference: 'high-performance'
});
rendererRef.value = renderer;
const gl = renderer.gl
gl.clearColor(0, 0, 0, 0)
containerRef.value.appendChild(gl.canvas)
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
containerRef.value.appendChild(gl.canvas);
const camera = new Camera(gl)
const scene = new Transform()
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]) },
})
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }
});
const program = new Program(gl, {
vertex,
@@ -215,117 +218,117 @@ const initWebGL = () => {
bend2: { value: props.bend2 },
bendAdj1: { value: 0 },
bendAdj2: { value: 0 },
uOpacity: { value: 0 },
},
})
new Mesh(gl, { geometry, program }).setParent(scene)
uOpacity: { value: 0 }
}
});
new Mesh(gl, { geometry, program }).setParent(scene);
resize()
resize();
resizeObserver.value = new ResizeObserver(resize)
resizeObserver.value.observe(containerRef.value)
resizeObserver.value = new ResizeObserver(resize);
resizeObserver.value.observe(containerRef.value);
const loop = (now: number) => {
if (isVisible.value) {
if (lastTimeRef.value === 0) {
lastTimeRef.value = now - pausedTimeRef.value
lastTimeRef.value = now - pausedTimeRef.value;
}
const t = (now - lastTimeRef.value) * 0.001
const t = (now - lastTimeRef.value) * 0.001;
if (fadeStartTime.value === null && t > 0.1) {
fadeStartTime.value = now
fadeStartTime.value = now;
}
let opacity = 0
let opacity = 0;
if (fadeStartTime.value !== null) {
const fadeElapsed = now - fadeStartTime.value
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1)
opacity = 1 - Math.pow(1 - opacity, 3)
const fadeElapsed = now - fadeStartTime.value;
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1);
opacity = 1 - Math.pow(1 - opacity, 3);
}
uniformOffset.value[0] = props.xOffset
uniformOffset.value[1] = props.yOffset
uniformOffset.value[0] = props.xOffset;
uniformOffset.value[1] = props.yOffset;
program.uniforms.iTime.value = t
program.uniforms.uRotation.value = props.rotationDeg * Math.PI / 180
program.uniforms.focalLength.value = props.focalLength
program.uniforms.uOpacity.value = opacity
program.uniforms.iTime.value = t;
program.uniforms.uRotation.value = (props.rotationDeg * Math.PI) / 180;
program.uniforms.focalLength.value = props.focalLength;
program.uniforms.uOpacity.value = opacity;
renderer.render({ scene, camera })
renderer.render({ scene, camera });
} else {
if (lastTimeRef.value !== 0) {
pausedTimeRef.value = now - lastTimeRef.value
lastTimeRef.value = 0
pausedTimeRef.value = now - lastTimeRef.value;
lastTimeRef.value = 0;
}
}
rafId.value = requestAnimationFrame(loop)
}
rafId.value = requestAnimationFrame(loop);
};
rafId.value = requestAnimationFrame(loop)
}
rafId.value = requestAnimationFrame(loop);
};
const setupIntersectionObserver = () => {
if (!containerRef.value || isMobile.value) return
if (!containerRef.value || isMobile.value) return;
intersectionObserver.value = new IntersectionObserver(
([entry]) => {
isVisible.value = entry.isIntersecting
isVisible.value = entry.isIntersecting;
},
{
rootMargin: '50px',
threshold: 0.1,
threshold: 0.1
}
)
);
intersectionObserver.value.observe(containerRef.value)
}
intersectionObserver.value.observe(containerRef.value);
};
const cleanup = () => {
if (rafId.value) {
cancelAnimationFrame(rafId.value)
rafId.value = null
cancelAnimationFrame(rafId.value);
rafId.value = null;
}
if (resizeObserver.value) {
resizeObserver.value.disconnect()
resizeObserver.value = null
resizeObserver.value.disconnect();
resizeObserver.value = null;
}
if (intersectionObserver.value) {
intersectionObserver.value.disconnect()
intersectionObserver.value = null
intersectionObserver.value.disconnect();
intersectionObserver.value = null;
}
if (rendererRef.value) {
rendererRef.value.gl.canvas.remove()
rendererRef.value = null
rendererRef.value.gl.canvas.remove();
rendererRef.value = null;
}
window.removeEventListener('resize', checkIsMobile)
}
window.removeEventListener('resize', checkIsMobile);
};
onMounted(() => {
checkIsMobile()
window.addEventListener('resize', checkIsMobile)
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
if (!isMobile.value) {
initWebGL()
setupIntersectionObserver()
initWebGL();
setupIntersectionObserver();
}
})
});
onUnmounted(() => {
cleanup()
})
cleanup();
});
watch(isMobile, (newIsMobile) => {
watch(isMobile, newIsMobile => {
if (newIsMobile) {
cleanup()
cleanup();
} else {
initWebGL()
setupIntersectionObserver()
initWebGL();
setupIntersectionObserver();
}
})
</script>
});
</script>

View File

@@ -17,9 +17,7 @@
max-width: 1200px;
user-select: none;
margin: 0 auto;
background: linear-gradient(135deg,
#3aed6d,
rgba(24, 255, 93, 0.6));
background: linear-gradient(135deg, #3aed6d, rgba(24, 255, 93, 0.6));
background-size: 200% 200%;
border-radius: 16px;
padding: 4rem 3rem;
@@ -78,7 +76,7 @@
background: transparent;
color: #0b0b0b;
border: 2px solid #0b0b0b;
padding: .6rem 1.6rem;
padding: 0.6rem 1.6rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 50px;
@@ -88,7 +86,7 @@
.start-building-button:hover {
background: #0b0b0b;
color: #27FF64;
color: #27ff64;
}
@media (max-width: 1280px) {
@@ -159,4 +157,4 @@
padding: 0.75rem 1.5rem;
font-size: 0.95rem;
}
}
}

View File

@@ -3,16 +3,15 @@
<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>
<router-link to="/text-animations/split-text" class="start-building-button">
Browse Components
</router-link>
<router-link to="/text-animations/split-text" class="start-building-button">Browse Components</router-link>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import './StartBuilding.css'
</script>
import './StartBuilding.css';
</script>