Dev build
This commit is contained in:
parent
fa5fc6685c
commit
53fed5f4db
BIN
public/discord-logo.png
Normal file
BIN
public/discord-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
public/signal-logo.png
Normal file
BIN
public/signal-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
118
src/App.css
118
src/App.css
@ -131,3 +131,121 @@ body {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.animated-paragraph {
|
||||
font-size: 1.15rem;
|
||||
color: #e0e0e0;
|
||||
margin: 24px 0 32px 0;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.animated-paragraph .cursor {
|
||||
display: inline-block;
|
||||
width: 0.5em;
|
||||
height: 1.2em;
|
||||
background-color: #e0e0e0;
|
||||
margin-left: 2px;
|
||||
animation: blink 1s step-end infinite;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: rgba(35, 39, 47, 0.85);
|
||||
border-radius: 16px;
|
||||
padding: 32px 24px 24px 24px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.45);
|
||||
position: relative;
|
||||
min-width: 340px;
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 1.5px solid rgba(255,255,255,0.12);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.modal-caption {
|
||||
color: #e0e0e0;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: #EA4335;
|
||||
}
|
||||
|
||||
.discord-tag-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.discord-tag {
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #e0e0e0;
|
||||
background: #23272f;
|
||||
padding: 6px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #5865F2;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #5865F2;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 16px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.copy-btn:hover, .copy-btn:focus {
|
||||
background: #4752c4;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
98
src/App.tsx
98
src/App.tsx
@ -1,7 +1,7 @@
|
||||
import './App.css'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
function TypeWriter({ text }: { text: string }) {
|
||||
function TypeWriter({ text, onComplete }: { text: string, onComplete: () => void }) {
|
||||
const [displayText, setDisplayText] = useState('')
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
|
||||
@ -10,10 +10,12 @@ function TypeWriter({ text }: { text: string }) {
|
||||
const timeout = setTimeout(() => {
|
||||
setDisplayText(prev => prev + text[currentIndex])
|
||||
setCurrentIndex(prev => prev + 1)
|
||||
}, 100)
|
||||
}, 200)
|
||||
return () => clearTimeout(timeout)
|
||||
} else {
|
||||
onComplete()
|
||||
}
|
||||
}, [currentIndex, text])
|
||||
}, [currentIndex, text, onComplete])
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -23,14 +25,41 @@ function TypeWriter({ text }: { text: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
function SocialCard({ title, icon, description, href }: {
|
||||
function AnimatedParagraph({ text, startTyping }: { text: string, startTyping: boolean }) {
|
||||
const [displayText, setDisplayText] = useState('')
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [isTyping, setIsTyping] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (startTyping && currentIndex < text.length) {
|
||||
setIsTyping(true)
|
||||
const timeout = setTimeout(() => {
|
||||
setDisplayText(prev => prev + text[currentIndex])
|
||||
setCurrentIndex(prev => prev + 1)
|
||||
}, 50)
|
||||
return () => clearTimeout(timeout)
|
||||
} else if (currentIndex >= text.length) {
|
||||
setIsTyping(false)
|
||||
}
|
||||
}, [currentIndex, text, startTyping])
|
||||
|
||||
return (
|
||||
<p className="animated-paragraph">
|
||||
{displayText}
|
||||
{isTyping && <span className="cursor"></span>}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
function SocialCard({ title, icon, description, href, onClick }: {
|
||||
title: string,
|
||||
icon: string,
|
||||
description: string,
|
||||
href: string
|
||||
href: string,
|
||||
onClick?: () => void
|
||||
}) {
|
||||
return (
|
||||
<a href={href} className="social-card" target="_blank" rel="noopener noreferrer">
|
||||
<a href={href} className="social-card" target="_blank" rel="noopener noreferrer" onClick={e => { if (onClick) { e.preventDefault(); onClick(); } }}>
|
||||
<div className="social-card-content">
|
||||
<img src={icon} alt={title} className="social-card-icon" />
|
||||
<h3>{title}</h3>
|
||||
@ -40,16 +69,64 @@ function SocialCard({ title, icon, description, href }: {
|
||||
)
|
||||
}
|
||||
|
||||
function SignalPopup({ show, onClose }: { show: boolean, onClose: () => void }) {
|
||||
if (!show) return null
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||
<button className="modal-close" onClick={onClose}>×</button>
|
||||
<img src="/shikoro-signal.png" alt="Signal QR" className="modal-image" />
|
||||
<div className="modal-caption">Scan to chat with me on Signal!</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DiscordPopup({ show, onClose, tag }: { show: boolean, onClose: () => void, tag: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
if (!show) return null
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(tag)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1200)
|
||||
}
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||
<button className="modal-close" onClick={onClose}>×</button>
|
||||
<img src="/discord-logo.png" alt="Discord Logo" className="modal-image" style={{background:'#5865F2',padding:'16px'}} />
|
||||
<div className="modal-caption" style={{marginBottom:'12px'}}>Add me on Discord:</div>
|
||||
<div className="discord-tag-row">
|
||||
<span className="discord-tag">{tag}</span>
|
||||
<button className="copy-btn" onClick={handleCopy}>{copied ? 'Copied!' : 'Copy'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [titleComplete, setTitleComplete] = useState(false)
|
||||
const [showSignalPopup, setShowSignalPopup] = useState(false)
|
||||
const [showDiscordPopup, setShowDiscordPopup] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="about-container">
|
||||
<TypeWriter text="Shikoro.dev" />
|
||||
<TypeWriter
|
||||
text="Shikoro.dev"
|
||||
onComplete={() => setTitleComplete(true)}
|
||||
/>
|
||||
<AnimatedParagraph
|
||||
text="Hi, I like doing all sorts of IT projects. If you're interested in collaborating, feel free to contact me."
|
||||
startTyping={titleComplete}
|
||||
/>
|
||||
<div className="social-cards">
|
||||
<SocialCard
|
||||
title="Discord"
|
||||
icon="https://placehold.co/64x64/5865F2/FFFFFF/png?text=D"
|
||||
icon="/discord-logo.png"
|
||||
description="Add me on discord"
|
||||
href="#"
|
||||
onClick={() => setShowDiscordPopup(true)}
|
||||
/>
|
||||
<SocialCard
|
||||
title="Email"
|
||||
@ -59,11 +136,14 @@ function App() {
|
||||
/>
|
||||
<SocialCard
|
||||
title="Signal"
|
||||
icon="https://placehold.co/64x64/00AFF0/FFFFFF/png?text=S"
|
||||
icon="/signal-logo.png"
|
||||
description="Message me on signal"
|
||||
onClick={() => setShowSignalPopup(true)}
|
||||
href="#"
|
||||
/>
|
||||
</div>
|
||||
<SignalPopup show={showSignalPopup} onClose={() => setShowSignalPopup(false)} />
|
||||
<DiscordPopup show={showDiscordPopup} onClose={() => setShowDiscordPopup(false)} tag="shikoro.1" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user