add sliders styles for vintage TV interface; update index.css to include new sliders stylesheet
Some checks failed
Master Build / Build and Push Docker Image (amd64 and arm64) (push) Successful in 48s
Master Build / Deploy Live (push) Successful in 3s
Master Build / Deploy Local (Demo) (push) Has been cancelled

This commit is contained in:
José Henrique 2025-03-14 20:37:15 -03:00
parent 144acbd355
commit 41572df5af
3 changed files with 377 additions and 23 deletions

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { Terminal } from 'lucide-react';
import TerminalButton from './components/TerminalButton';
import ProfileContent from './components/ProfileContent';
@ -8,15 +8,31 @@ import SkillsContent from './components/SkillsContent';
import ResumeContent from './components/ResumeContent';
import PostsContent from './components/PostsContent';
import './styles/sliders.css'; // Import the new sliders CSS
type SliderRefType = React.RefObject<HTMLDivElement>;
function App() {
const [activeSection, setActiveSection] = React.useState('about');
const [isBooting, setIsBooting] = React.useState(true);
const [bootPhase, setBootPhase] = React.useState(0);
// New state for sliders
const [greenIntensity, setGreenIntensity] = useState(50); // 0 to 100
const [glitchIntensity, setGlitchIntensity] = useState(25); // 0 to 100
// New state for glitch effects
const [flickerOpacity, setFlickerOpacity] = useState(1); // 1 is normal, less than 1 creates flicker
const [showScanline, setShowScanline] = useState(false); // Controls the high-contrast scanline
const [scanlinePosition, setScanlinePosition] = useState(0); // Position of scanline (0-100%)
// Refs for the slider thumbs
const greenSliderRef = useRef<HTMLDivElement>(null);
const glitchSliderRef = useRef<HTMLDivElement>(null);
// TV Boot-up sequence
React.useEffect(() => {
if (!isBooting) return;
// Phase 0: Initial black screen (0ms)
// Phase 1: Quick white flash (100ms)
// Phase 2: Static noise (600ms)
@ -40,10 +56,188 @@ function App() {
};
}, [isBooting]);
// Screen flickering effect based on glitch intensity
useEffect(() => {
if (glitchIntensity <= 30) {
setFlickerOpacity(1); // No flickering
return;
}
let flickerInterval: number;
const flickerEffect = () => {
// The higher the glitch intensity, the more frequent the flickers
const flickerFrequency = Math.max(300, 1500 - (glitchIntensity * 10));
flickerInterval = window.setInterval(() => {
// Intensity affects how dim the screen gets during flickers
const minOpacity = Math.max(0.7, 0.95 - (glitchIntensity / 100) * 0.5);
// Create random flickering effect
const randomFlicker = Math.random() * (1 - minOpacity) + minOpacity;
setFlickerOpacity(randomFlicker);
// Reset back to normal after a short delay
setTimeout(() => {
setFlickerOpacity(1);
}, 50 + Math.random() * 100);
}, flickerFrequency);
};
flickerEffect();
return () => {
clearInterval(flickerInterval);
};
}, [glitchIntensity]);
// Horizontal scanline effect for higher glitch intensity
useEffect(() => {
if (glitchIntensity <= 50) {
setShowScanline(false);
return;
}
let scanlineInterval: number;
const scanlineEffect = () => {
// The higher the intensity, the more frequent the scanline appears
const scanlineFrequency = Math.max(1000, 5000 - (glitchIntensity * 40));
scanlineInterval = window.setInterval(() => {
// Show the scanline
setShowScanline(true);
// Animate the scanline movement
const duration = 300 + Math.random() * 700; // 300-1000ms
const startTime = Date.now();
const animateScanline = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(1, elapsed / duration);
setScanlinePosition(progress * 100);
if (progress < 1) {
requestAnimationFrame(animateScanline);
} else {
// Hide the scanline when animation completes
setShowScanline(false);
}
};
requestAnimationFrame(animateScanline);
}, scanlineFrequency);
};
scanlineEffect();
return () => {
clearInterval(scanlineInterval);
};
}, [glitchIntensity]);
// Function to handle slider dragging
const handleSliderDrag = (
event: React.MouseEvent,
sliderRef: SliderRefType,
setValueFn: React.Dispatch<React.SetStateAction<number>>
) => {
event.preventDefault();
if (!sliderRef.current) return;
const track = sliderRef.current.parentElement;
if (!track) return;
const trackRect = track.getBoundingClientRect();
const trackHeight = trackRect.height;
const trackTop = trackRect.top;
const handleMouseMove = (moveEvent: MouseEvent) => {
const y = moveEvent.clientY;
let percentage = 100 - (((y - trackTop) / trackHeight) * 100);
// Clamp value between 0 and 100
percentage = Math.min(100, Math.max(0, percentage));
setValueFn(percentage);
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
// Calculate green color based on intensity
const calculateGreenBackground = () => {
// Map the intensity from 0-100 to appropriate color values
const baseIntensity = Math.floor((greenIntensity / 100) * 35); // Max 35
return {
backgroundColor: `rgba(0, ${baseIntensity}, 0, 0.95)`,
backgroundImage: `
linear-gradient(0deg,
rgba(0, ${Math.max(5, baseIntensity + 3)}, 0, 1) 0%,
rgba(0, ${Math.max(15, baseIntensity + 10)}, 0, 1) 100%),
radial-gradient(
circle at center,
rgba(0, ${Math.max(20, baseIntensity + 20)}, 0, 1) 0%,
rgba(0, ${baseIntensity}, 0, 1) 100%
)
`
};
};
// Calculate glitch effects based on intensity
const calculateGlitchEffects = () => {
const whiteNoiseOpacity = 0.02 + (glitchIntensity / 100) * 0.2;
const chromaticAberrationAmount = (glitchIntensity / 100) * 1;
const scanLinesOpacity = 0.1 + (glitchIntensity / 100) * 1;
return {
whiteNoiseOpacity,
chromaticAberrationAmount,
scanLinesOpacity
};
};
const glitchEffects = calculateGlitchEffects();
return (
<div className="min-h-screen bg-gray-900 flex items-center justify-center overflow-hidden">
{/* TV Container with fixed proportions */}
<div className="w-[95%] max-w-[1400px] relative h-[90vh]">
{/* Vertical Sliders - Moved outside the TV frame */}
<div className="tv-sliders-container">
{/* Green Intensity Slider */}
<div className="tv-slider-wrapper">
<div className="tv-slider-track">
<div
ref={greenSliderRef}
className="tv-slider-thumb"
style={{ bottom: `${greenIntensity}%` }}
onMouseDown={(e) => handleSliderDrag(e, greenSliderRef, setGreenIntensity)}
></div>
</div>
<span className="tv-slider-label">Green</span>
</div>
{/* Glitch Intensity Slider */}
<div className="tv-slider-wrapper">
<div className="tv-slider-track">
<div
ref={glitchSliderRef}
className="tv-slider-thumb"
style={{ bottom: `${glitchIntensity}%` }}
onMouseDown={(e) => handleSliderDrag(e, glitchSliderRef, setGlitchIntensity)}
></div>
</div>
<span className="tv-slider-label">Glitch</span>
</div>
</div>
{/* TV Frame */}
<div className="absolute inset-0 tv-frame">
{/* TV frame texture overlay */}
@ -87,16 +281,48 @@ function App() {
</>
)}
{/* Static noise texture underlay */}
<div className="white-noise"></div>
{/* Screen Content with fixed height */}
<div className="relative h-full w-full p-6 font-mono text-[#00FF00] overflow-hidden z-10 screen-content">
{/* Static noise texture underlay - controlled by glitch slider */}
<div
className="white-noise"
style={{ opacity: glitchEffects.whiteNoiseOpacity }}
></div>
{/* Screen opacity flicker effect */}
<div
className="absolute inset-0 z-45 pointer-events-none transition-opacity duration-50"
style={{
opacity: 1 - flickerOpacity, // Inverse of flickerOpacity to create dimming
backgroundColor: 'rgba(0, 0, 0, 0.95)' // Black overlay for flickering
}}
></div>
{/* High-contrast horizontal scanline for high glitch intensity */}
{showScanline && (
<div
className="absolute left-0 right-0 z-45 pointer-events-none"
style={{
top: `${scanlinePosition}%`,
height: '4px',
backgroundColor: 'rgba(255, 255, 255, 0.7)',
boxShadow: '0 0 10px rgba(255, 255, 255, 0.7), 0 0 5px rgba(255, 255, 255, 0.5)',
borderTop: '1px solid rgba(255, 255, 255, 0.9)',
borderBottom: '1px solid rgba(255, 255, 255, 0.9)'
}}
></div>
)}
{/* Screen Content with fixed height - controlled by green slider */}
<div
className="relative h-full w-full p-6 font-mono text-[#00FF00] overflow-hidden z-10 screen-content"
style={{
...calculateGreenBackground(),
opacity: flickerOpacity // Apply flickering effect
}}
>
<div className="animate-pulse mb-4">
<Terminal className="inline-block mr-2" />
<span className="text-sm">echo "Hey there!"</span>
</div>
{/* Split Screen Container with fixed height */}
<div className="flex gap-8 h-[calc(100%-3rem)]">
{/* Left Column - Navigation */}
@ -138,10 +364,8 @@ function App() {
SHELL
</TerminalButton>
</div>
{/* Vertical Separator with embossed effect */}
<div className="vertical-separator"></div>
{/* Right Column - Content with scrolling */}
<div className="flex-1 overflow-y-auto pr-4 custom-scrollbar">
{activeSection === 'about' && <ProfileContent />}
@ -155,30 +379,45 @@ function App() {
</div>
</div>
</div>
{/* Enhanced Scan Lines Effect */}
<div className="scan-lines"></div>
{/* Chromatic Aberration Effect */}
<div className="chromatic-aberration"></div>
{/* Enhanced Scan Lines Effect - controlled by glitch slider */}
<div
className="scan-lines"
style={{
backgroundImage: `repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0px,
rgba(0, 0, 0, 0) 1px,
rgba(0, 0, 0, ${glitchEffects.scanLinesOpacity}) 1px,
rgba(0, 0, 0, ${glitchEffects.scanLinesOpacity}) 2px
)`
}}
></div>
{/* Chromatic Aberration Effect - controlled by glitch slider */}
<div
className="chromatic-aberration"
style={{
boxShadow: `
inset ${glitchEffects.chromaticAberrationAmount}px 0 0 rgba(255, 0, 0, 0.05),
inset -${glitchEffects.chromaticAberrationAmount}px 0 0 rgba(0, 255, 255, 0.05)
`
}}
></div>
{/* Multiple Glass Reflection Layers */}
<div className="glass-reflection"></div>
<div className="glass-reflection-2"></div>
<div className="corner-shine"></div>
{/* Screen Glare - Moving reflection */}
<div className="screen-glare"></div>
</div>
</div>
{/* TV Controls - Skeuomorphic Knobs */}
<div className="absolute -bottom-12 right-20 flex space-x-8">
<div className="tv-knob" style={{transform: 'rotate(-30deg)'}}></div>
<div className="tv-knob" style={{transform: 'rotate(15deg)'}}></div>
<div className="tv-knob" style={{transform: 'rotate(-10deg)'}}></div>
</div>
{/* TV Speaker Grills - Enhanced */}
<div className="absolute -bottom-6 left-20 flex space-x-1.5">
{[...Array(10)].map((_, i) => (
@ -191,7 +430,6 @@ function App() {
></div>
))}
</div>
{/* TV Brand Label */}
<div
className="absolute -bottom-10 left-1/2 transform -translate-x-1/2 bg-gradient-to-b from-gray-700 to-gray-900 px-4 py-1 rounded"
@ -209,7 +447,6 @@ function App() {
RETRO·VISION
</span>
</div>
{/* Power indicator light */}
<div className="absolute -bottom-8 right-[12%] w-3 h-3 rounded-full bg-gradient-to-b from-green-500 to-green-700"
style={{

View File

@ -6,4 +6,5 @@
@import './styles/buttons.css';
@import './styles/scrollbars.css';
@import './styles/skills.css';
@import './styles/matrix.css';
@import './styles/matrix.css';
@import './styles/sliders.css';

116
src/styles/sliders.css Normal file
View File

@ -0,0 +1,116 @@
/* Vintage TV Sliders */
.tv-sliders-container {
position: absolute;
left: -60px; /* Moved further left */
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 50px;
z-index: 100; /* Increased z-index to ensure visibility */
pointer-events: auto; /* Ensure clicks are registered */
}
.tv-slider-wrapper {
position: relative;
width: 40px;
height: 180px;
background: linear-gradient(90deg, #1a1a1a, #2a2a2a);
border-radius: 10px 0 0 10px;
box-shadow:
inset 1px 1px 3px rgba(255, 255, 255, 0.1),
inset -1px -1px 3px rgba(0, 0, 0, 0.5),
3px 3px 8px rgba(0, 0, 0, 0.6);
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
}
/* Slider track */
.tv-slider-track {
position: relative;
width: 8px;
height: 140px;
background: #0a0a0a;
border-radius: 4px;
box-shadow:
inset 0 1px 3px rgba(0, 0, 0, 0.8),
inset 0 -1px 1px rgba(255, 255, 255, 0.05);
}
/* Slider notches */
.tv-slider-track::before {
content: '';
position: absolute;
left: 50%;
top: 5%;
bottom: 5%;
width: 2px;
transform: translateX(-50%);
background: repeating-linear-gradient(
to bottom,
rgba(255, 255, 255, 0.08) 0px,
rgba(255, 255, 255, 0.08) 1px,
transparent 1px,
transparent 10px
);
}
/* Slider thumb */
.tv-slider-thumb {
position: absolute;
width: 24px;
height: 12px;
left: 50%;
background: linear-gradient(to bottom, #4a4a4a, #2a2a2a);
border-radius: 3px;
transform: translateX(-50%);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.6),
0 -1px 1px rgba(255, 255, 255, 0.1),
inset 0 1px 1px rgba(255, 255, 255, 0.2),
inset 0 -1px 1px rgba(0, 0, 0, 0.3);
cursor: pointer;
z-index: 2;
}
.tv-slider-thumb::before {
content: '';
position: absolute;
top: 3px;
left: 3px;
right: 3px;
height: 2px;
background: rgba(255, 255, 255, 0.15);
border-radius: 1px;
}
.tv-slider-thumb::after {
content: '';
position: absolute;
bottom: 3px;
left: 3px;
right: 3px;
height: 1px;
background: rgba(0, 0, 0, 0.2);
border-radius: 1px;
}
/* Slider labels */
.tv-slider-label {
font-family: 'Courier New', monospace;
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
margin-top: 8px;
letter-spacing: 0.5px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
white-space: nowrap;
transform: rotate(-90deg);
position: absolute;
bottom: -25px;
width: 80px;
text-align: center;
left: -20px;
}