feat(site): add engaging interactions (typewriter, progress, skills, particles, testimonials)
Ultraworked with Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
62
index.html
62
index.html
@@ -42,11 +42,18 @@
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script type="module" src="/main.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Scroll Progress Bar -->
|
||||||
|
<div class="scroll-progress" role="progressbar" aria-label="Page scroll progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
|
||||||
|
<!-- Particle Canvas -->
|
||||||
|
<canvas id="particles" aria-hidden="true"></canvas>
|
||||||
|
|
||||||
<header class="text-center py-1">
|
<header class="text-center py-1">
|
||||||
<h1>d@n tech</h1>
|
<h1>d@n tech</h1>
|
||||||
<p>Your partner in web development</p>
|
<p class="typewriter" data-text="Your partner in web development" aria-label="Your partner in web development"></p>
|
||||||
<p><a href="https://dustin.coffee" target="_blank" class="text-white">Visit my main site</a></p>
|
<p><a href="https://dustin.coffee" target="_blank" class="text-white">Visit my main site</a></p>
|
||||||
</header>
|
</header>
|
||||||
<div class="d-flex flex-column min-vh-100" style="position: relative;">
|
<div class="d-flex flex-column min-vh-100" style="position: relative;">
|
||||||
@@ -55,12 +62,37 @@
|
|||||||
<p>Welcome to d@n tech! We specialize in creating stunning and efficient websites for businesses of all sizes. Our team of experienced developers is dedicated to delivering high-quality web solutions that meet your unique needs.</p>
|
<p>Welcome to d@n tech! We specialize in creating stunning and efficient websites for businesses of all sizes. Our team of experienced developers is dedicated to delivering high-quality web solutions that meet your unique needs.</p>
|
||||||
|
|
||||||
<h2>Services</h2>
|
<h2>Services</h2>
|
||||||
<ul class="list-group">
|
<ul class="list-group skill-bars">
|
||||||
<li class="list-group-item">Custom Website Development</li>
|
<li class="list-group-item skill-item" data-skill="95">
|
||||||
<li class="list-group-item">Responsive Design</li>
|
<span class="skill-name">Custom Website Development</span>
|
||||||
<li class="list-group-item">E-commerce Solutions</li>
|
<div class="skill-bar-track">
|
||||||
<li class="list-group-item">SEO Optimization</li>
|
<div class="skill-bar-fill" style="--skill-level: 95%;"></div>
|
||||||
<li class="list-group-item">Website Maintenance</li>
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item skill-item" data-skill="90">
|
||||||
|
<span class="skill-name">Responsive Design</span>
|
||||||
|
<div class="skill-bar-track">
|
||||||
|
<div class="skill-bar-fill" style="--skill-level: 90%;"></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item skill-item" data-skill="85">
|
||||||
|
<span class="skill-name">E-commerce Solutions</span>
|
||||||
|
<div class="skill-bar-track">
|
||||||
|
<div class="skill-bar-fill" style="--skill-level: 85%;"></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item skill-item" data-skill="92">
|
||||||
|
<span class="skill-name">SEO Optimization</span>
|
||||||
|
<div class="skill-bar-track">
|
||||||
|
<div class="skill-bar-fill" style="--skill-level: 92%;"></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item skill-item" data-skill="98">
|
||||||
|
<span class="skill-name">Website Maintenance</span>
|
||||||
|
<div class="skill-bar-track">
|
||||||
|
<div class="skill-bar-fill" style="--skill-level: 98%;"></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -125,20 +157,24 @@
|
|||||||
<h2>Testimonials</h2>
|
<h2>Testimonials</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card testimonial-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">"It was a pleasure working with Dustin on my counseling practice website. Throughout the process, he was thoughtful, responsive, and truly understood my vision. In addition to bringing my ideas to life with a clean, professional design, he made the entire process enjoyable and easy. His attention to detail, creativity, and technical skills are excellent. As a result of Dustin's work, I now have a website that truly represents my practice. There is no better person to recommend than him!"</p>
|
<p class="card-text testimonial-excerpt">"It was a pleasure working with Dustin on my counseling practice website. Throughout the process, he was thoughtful, responsive, and truly understood my vision..."</p>
|
||||||
<p class="card-text">- Margaret Pemu, Pemu Counseling and Wellness</p>
|
<p class="card-text testimonial-full">"It was a pleasure working with Dustin on my counseling practice website. Throughout the process, he was thoughtful, responsive, and truly understood my vision. In addition to bringing my ideas to life with a clean, professional design, he made the entire process enjoyable and easy. His attention to detail, creativity, and technical skills are excellent. As a result of Dustin's work, I now have a website that truly represents my practice. There is no better person to recommend than him!"</p>
|
||||||
|
<p class="card-text testimonial-author">- Margaret Pemu, Pemu Counseling and Wellness</p>
|
||||||
|
<button class="testimonial-toggle" aria-label="Read full testimonial">Read More</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card testimonial-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">"Working with Dustin at d@n tech was an absolute pleasure from start to finish. He is funny, engaging, and listens well. He offered grace for my lack of technical skill and worked from simple examples provided, which translated into a functional and funky-professional website that represents my brand.
|
<p class="card-text testimonial-excerpt">"Working with Dustin at d@n tech was an absolute pleasure from start to finish. He is funny, engaging, and listens well. He offered grace for my lack of technical skill..."</p>
|
||||||
|
<p class="card-text testimonial-full">"Working with Dustin at d@n tech was an absolute pleasure from start to finish. He is funny, engaging, and listens well. He offered grace for my lack of technical skill and worked from simple examples provided, which translated into a functional and funky-professional website that represents my brand.
|
||||||
|
|
||||||
His patience and technical skills are truly impressive. Furthermore, he was responsive throughout the entire process, always open to feedback, and quick to implement changes. What I appreciated most was how he spent time working toward what I wanted and was supportive in my goals."</p>
|
His patience and technical skills are truly impressive. Furthermore, he was responsive throughout the entire process, always open to feedback, and quick to implement changes. What I appreciated most was how he spent time working toward what I wanted and was supportive in my goals."</p>
|
||||||
<p class="card-text"> -Laura, Woman-Owned Small Business</p>
|
<p class="card-text testimonial-author"> -Laura, Woman-Owned Small Business</p>
|
||||||
|
<button class="testimonial-toggle" aria-label="Read full testimonial">Read More</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
164
main.js
Normal file
164
main.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// ===== TYPEWRITER EFFECT =====
|
||||||
|
function initTypewriter() {
|
||||||
|
const el = document.querySelector('.typewriter');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const text = el.getAttribute('data-text');
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
el.textContent = '';
|
||||||
|
el.style.opacity = '1';
|
||||||
|
|
||||||
|
function type() {
|
||||||
|
if (i < text.length) {
|
||||||
|
el.textContent += text.charAt(i);
|
||||||
|
i++;
|
||||||
|
setTimeout(type, 80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(type, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== SCROLL PROGRESS BAR =====
|
||||||
|
function initScrollProgress() {
|
||||||
|
const progressBar = document.querySelector('.scroll-progress');
|
||||||
|
if (!progressBar) return;
|
||||||
|
|
||||||
|
function updateProgress() {
|
||||||
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
const scrolled = window.scrollY;
|
||||||
|
const progress = (scrolled / docHeight) * 100;
|
||||||
|
|
||||||
|
progressBar.style.setProperty('--scroll-progress', `${progress}%`);
|
||||||
|
progressBar.setAttribute('aria-valuenow', Math.round(progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', updateProgress, { passive: true });
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ANIMATED SKILLS BARS =====
|
||||||
|
function initSkillBars() {
|
||||||
|
const skillItems = document.querySelectorAll('.skill-item');
|
||||||
|
if (!skillItems.length) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('animate');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { threshold: 0.3 });
|
||||||
|
|
||||||
|
skillItems.forEach(item => observer.observe(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== PARTICLE BACKGROUND =====
|
||||||
|
function initParticles() {
|
||||||
|
const canvas = document.getElementById('particles');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
let particles = [];
|
||||||
|
let animationId;
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Particle {
|
||||||
|
constructor() {
|
||||||
|
this.x = Math.random() * canvas.width;
|
||||||
|
this.y = Math.random() * canvas.height;
|
||||||
|
this.vx = (Math.random() - 0.5) * 0.3;
|
||||||
|
this.vy = (Math.random() - 0.5) * 0.3;
|
||||||
|
this.radius = Math.random() * 1.5 + 0.5;
|
||||||
|
this.opacity = Math.random() * 0.3 + 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.x += this.vx;
|
||||||
|
this.y += this.vy;
|
||||||
|
|
||||||
|
if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
|
||||||
|
if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = `rgba(111, 78, 55, ${this.opacity})`;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
resize();
|
||||||
|
particles = [];
|
||||||
|
const particleCount = Math.floor((canvas.width * canvas.height) / 15000);
|
||||||
|
for (let i = 0; i < particleCount; i++) {
|
||||||
|
particles.push(new Particle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
particles.forEach(particle => {
|
||||||
|
particle.update();
|
||||||
|
particle.draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
animationId = requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
cancelAnimationFrame(animationId);
|
||||||
|
init();
|
||||||
|
animate();
|
||||||
|
});
|
||||||
|
|
||||||
|
init();
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== INTERACTIVE TESTIMONIALS =====
|
||||||
|
function initTestimonials() {
|
||||||
|
const testimonialCards = document.querySelectorAll('.testimonial-card');
|
||||||
|
|
||||||
|
testimonialCards.forEach(card => {
|
||||||
|
const toggle = card.querySelector('.testimonial-toggle');
|
||||||
|
const excerpt = card.querySelector('.testimonial-excerpt');
|
||||||
|
const full = card.querySelector('.testimonial-full');
|
||||||
|
|
||||||
|
if (!toggle || !excerpt || !full) return;
|
||||||
|
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
const isExpanded = card.classList.contains('expanded');
|
||||||
|
|
||||||
|
card.classList.toggle('expanded');
|
||||||
|
toggle.textContent = isExpanded ? 'Read More' : 'Read Less';
|
||||||
|
toggle.setAttribute('aria-expanded', !isExpanded);
|
||||||
|
|
||||||
|
if (!isExpanded) {
|
||||||
|
excerpt.style.display = 'none';
|
||||||
|
full.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
excerpt.style.display = 'block';
|
||||||
|
full.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== INITIALIZE ALL =====
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initTypewriter();
|
||||||
|
initScrollProgress();
|
||||||
|
initSkillBars();
|
||||||
|
initParticles();
|
||||||
|
initTestimonials();
|
||||||
|
});
|
||||||
197
styles.css
197
styles.css
@@ -219,16 +219,209 @@ footer {
|
|||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
color: white; /* Electric blue needs white text for contrast */
|
color: white;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
padding: 0.6em 1.2em;
|
padding: 0.6em 1.2em;
|
||||||
border-radius: 50px; /* Pill shape */
|
border-radius: 50px;
|
||||||
|
|
||||||
&:hover, &:focus, &:active {
|
&:hover, &:focus, &:active {
|
||||||
background-color: color-mix(in srgb, var(--color-primary), black 10%) !important;
|
background-color: color-mix(in srgb, var(--color-primary), black 10%) !important;
|
||||||
border-color: color-mix(in srgb, var(--color-primary), black 10%) !important;
|
border-color: color-mix(in srgb, var(--color-primary), black 10%) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== SCROLL PROGRESS BAR ===== */
|
||||||
|
.scroll-progress {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--color-primary) 0%,
|
||||||
|
var(--color-primary) var(--scroll-progress, 0%),
|
||||||
|
transparent var(--scroll-progress, 0%)
|
||||||
|
);
|
||||||
|
z-index: 9999;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== PARTICLE CANVAS ===== */
|
||||||
|
#particles {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TYPEWRITER EFFECT ===== */
|
||||||
|
.typewriter {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typewriter::after {
|
||||||
|
content: '|';
|
||||||
|
position: absolute;
|
||||||
|
right: -8px;
|
||||||
|
animation: blink 0.7s infinite;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 50% { opacity: 1; }
|
||||||
|
51%, 100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== ANIMATED SKILL BARS ===== */
|
||||||
|
.skill-bars {
|
||||||
|
--bs-list-group-bg: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1.25rem;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
transition: border-color 0.3s, background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item:hover {
|
||||||
|
border-left-color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-bar-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: color-mix(in srgb, var(--color-coffee-medium), transparent 85%);
|
||||||
|
border-radius: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
|
||||||
|
border-radius: 50px;
|
||||||
|
transition: width 1.5s cubic-bezier(0.65, 0, 0.35, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-bar-fill::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { transform: translateX(-100%); }
|
||||||
|
100% { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item.animate .skill-bar-fill {
|
||||||
|
width: var(--skill-level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== INTERACTIVE TESTIMONIALS ===== */
|
||||||
|
.testimonial-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-full {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-excerpt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-card.expanded .testimonial-excerpt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-card.expanded .testimonial-full {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-author {
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--color-coffee-medium);
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-toggle {
|
||||||
|
margin-top: 1rem;
|
||||||
|
background: var(--color-secondary);
|
||||||
|
color: var(--color-coffee-dark);
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
transition: transform 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-toggle:hover {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-toggle:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== REDUCED MOTION SUPPORT ===== */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typewriter::after {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-bar-fill::after {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user