i did some work
|
@ -3,6 +3,8 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"react-parallax-tilt": "^1.7.224",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"typewriter-effect": "^2.21.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import Main from './components/main';
|
||||
import Navbar from './components/navbar';
|
||||
import InfogramBelowMain from './components/infobelowmain';
|
||||
// import logo from './logo.svg';
|
||||
// import './App.css';
|
||||
|
||||
|
@ -7,6 +9,8 @@ function App() {
|
|||
return (
|
||||
<div style={{ zIndex: 9999 }}>
|
||||
<Main />
|
||||
<InfogramBelowMain />
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
BIN
src/assets/avatar.png
Normal file
After Width: | Height: | Size: 273 KiB |
6
src/assets/icons/css.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="#89b4fa" d="M1.5 1.5h13L13 13l-5 2-5-2z" />
|
||||
<path stroke="#cdd6f4" d="M5 4.5h6l-.5 6-2.5 1-2.5-1-.08-1m1.08-2h4" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 295 B |
6
src/assets/icons/html.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="#fab387" d="M1.5 1.5h13L13 13l-5 2-5-2z" />
|
||||
<path stroke="#cdd6f4" d="M11 4.5H5l.25 3h5.5l-.25 3-2.5 1-2.5-1-.08-1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 298 B |
6
src/assets/icons/javascript.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke="#f9e2af" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4.5 11c0 .828427.6715729 1.5 1.5 1.5.8284271 0 1.5-.671573 1.5-1.5V7.5M12.5 8.75C12.5 8.05964406 11.9627417 7.5 11.3 7.5L10.7 7.5C10.0372583 7.5 9.5 8.05964406 9.5 8.75 9.5 9.44035594 10.0372583 10 10.7 10L11.3 10C11.9627417 10 12.5 10.5596441 12.5 11.25 12.5 11.9403559 11.9627417 12.5 11.3 12.5L10.7 12.5C10.0372583 12.5 9.5 11.9403559 9.5 11.25" />
|
||||
<path d="m 4,1.5 h 8 c 1.385,0 2.5,1.115 2.5,2.5 v 8 c 0,1.385 -1.115,2.5 -2.5,2.5 H 4 C 2.615,14.5 1.5,13.385 1.5,12 V 4 C 1.5,2.615 2.615,1.5 4,1.5 Z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 700 B |
6
src/assets/icons/nodejs.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- <path stroke="#cdd6f4" d="M 4.5,4.5 H 12 c 0.828427,0 1.5,0.6715729 1.5,1.5 V 6.5 M 6,13.5 H 2 C 1.1715729,13.5 0.5,12.828427 0.5,12 V 3.5 c 0,-0.5522847 0.44771525,-1 1,-1 h 5 c 0.5522847,0 1,0.4477153 1,1 v 1" /> -->
|
||||
<path stroke="#a6e3a1" d="M12.5,8.57587555 L15.5,10.2901613 L15.5,13.7098387 L12.5,15.4241244 L9.5,13.7098387 L9.5,10.2901613 L12.5,8.57587555 Z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 537 B |
8
src/assets/icons/reactjs.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke="#89dceb" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8 10.8c4.14 0 7.5-1.25 7.5-2.8S12.14 5.2 8 5.2.5 6.45.5 8s3.36 2.8 7.5 2.8" />
|
||||
<path d="M5.52 9.4c2.07 3.5 4.86 5.72 6.23 4.95 1.37-.78.8-4.24-1.27-7.75C8.41 3.1 5.62.88 4.25 1.65c-1.37.78-.8 4.24 1.27 7.75" />
|
||||
<path d="M5.52 6.6c-2.07 3.5-2.64 6.97-1.27 7.75 1.37.77 4.16-1.45 6.23-4.95s2.64-6.97 1.27-7.75C10.38.88 7.59 3.1 5.52 6.6" />
|
||||
<path d="M8.5 8a.5.5 0 01-.5.5.5.5 0 01-.5-.5.5.5 0 01.5-.5.5.5 0 01.5.5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 612 B |
1
src/assets/icons/readme.txt
Normal file
|
@ -0,0 +1 @@
|
|||
import icons from https://github.com/catppuccin/vscode-icons/tree/main/icons/mocha
|
6
src/assets/icons/typescript.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" stroke="#89b4fa" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 1.5h8A2.5 2.5 0 0114.5 4v8a2.5 2.5 0 01-2.5 2.5H4A2.5 2.5 0 011.5 12V4A2.5 2.5 0 014 1.5" />
|
||||
<path d="M12.5 8.75c0-.69-.54-1.25-1.2-1.25h-.6c-.66 0-1.2.56-1.2 1.25S10.04 10 10.7 10h.6c.66 0 1.2.56 1.2 1.25s-.54 1.25-1.2 1.25h-.6c-.66 0-1.2-.56-1.2-1.25m-3-3.75v5M5 7.5h3" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 469 B |
|
@ -29,7 +29,7 @@
|
|||
.background {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: 120vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
|
@ -37,8 +37,28 @@
|
|||
overflow: hidden;
|
||||
/* @apply bg-base-300; */
|
||||
/* @apply bg-gradient-to-b from-ctp-mantle to-ctp-crust; */
|
||||
background: linear-gradient( var(--mantle), var(--crust));
|
||||
/* BG CURNHBF */
|
||||
/* background: linear-gradient( var(--mantle), var(--crust)); */
|
||||
/* backdrop-filter: blur(10px); */
|
||||
/* background: linear-gradient(300deg,#222,var(--crust)); */
|
||||
/* background-size: 180% 180%; */
|
||||
/* animation: gradient-animation 18s ease infinite; */
|
||||
}
|
||||
html,body {
|
||||
background: linear-gradient(300deg,#222,var(--crust));
|
||||
background-size: 180% 180%;
|
||||
animation: gradient-animation 18s ease infinite;
|
||||
}
|
||||
@keyframes gradient-animation {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
.background li {
|
||||
position: absolute;
|
||||
|
|
46
src/components/infobelowmain/index.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import Icon from "../static/Icons";
|
||||
|
||||
export default function InfogramBelowMain() {
|
||||
return <div className="mt-20 ml-20 container">
|
||||
<h1 className="ml-25 font-bold text-3xl">
|
||||
Some basic stuff about <span className="text-highlight">me</span>
|
||||
</h1>
|
||||
<p>
|
||||
I started programming during the Covid Pandemic in 2020.
|
||||
<br />
|
||||
<br />I am fluent in new like programming languages such as
|
||||
<i>
|
||||
<b className='text-highlight'>
|
||||
{' '}
|
||||
<Icon icon="css"/> CSS , <Icon icon="html" /> HTML, <Icon icon="javascript" />Javascript and <Icon icon="typescript" />Typescript.{' '}
|
||||
</b>
|
||||
</i>
|
||||
<br />
|
||||
<br /> My field of Interest's are building new
|
||||
<i>
|
||||
<b className='text-highlight '>
|
||||
Web Technologies and Products{' '}
|
||||
</b>{' '}
|
||||
and also in areas related to{' '}
|
||||
<b className='text-highlight '>
|
||||
Backend Services, CLI Services
|
||||
</b>
|
||||
</i>
|
||||
<br />
|
||||
<br />
|
||||
Whenever possible, I also apply my passion for developing products
|
||||
with <b className='text-highlight '><Icon icon="nodejs" /> Node.js</b> and
|
||||
<i>
|
||||
<b className='text-highlight '>
|
||||
{' '}
|
||||
Modern Javascript Library and Frameworks
|
||||
</b>
|
||||
</i>
|
||||
like
|
||||
<i>
|
||||
<b className='text-highlight '> <Icon icon="reactjs" />React.js</b>
|
||||
</i>
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
}
|
0
src/components/infobelowmain/style.css
Normal file
|
@ -30,3 +30,38 @@ background: transparent;
|
|||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.wave {
|
||||
animation-name: wave-animation; /* Refers to the name of your @keyframes element below */
|
||||
animation-duration: 2.5s; /* Change to speed up or slow down */
|
||||
animation-iteration-count: infinite;
|
||||
transform-origin: 70% 70%; /* Pivot around the bottom-left palm */
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes wave-animation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
10% {
|
||||
transform: rotate(14deg);
|
||||
} /* The following five values can be played with to make the waving more or less extreme */
|
||||
20% {
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotate(14deg);
|
||||
}
|
||||
40% {
|
||||
transform: rotate(-4deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotate(0deg);
|
||||
} /* Reset for the last half to pause */
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,33 @@
|
|||
import './avatar.css'
|
||||
import avatar from '../../assets/avatar.png'
|
||||
import ScrollAnimation from 'react-animate-on-scroll'
|
||||
import Tilt from 'react-parallax-tilt'
|
||||
import Typewriter from './type'
|
||||
import { useEffect, useRef } from 'react'
|
||||
// import 'animate.css/animate.css'
|
||||
export default function Main() {
|
||||
const mainEl = useRef(null)
|
||||
// useEffect(() => {
|
||||
// if (document.activeElement !== mainEl.current) {
|
||||
// // do something
|
||||
// mainEl.current.className
|
||||
// }
|
||||
// })
|
||||
return (
|
||||
<div className='hero min-h-screen ' >
|
||||
<div style={{ animation: "fadeInDown", animationDuration: "1.5s", zIndex: 9999 }} className='animate__animated animate__fadeIn' >
|
||||
<div className='hero-content flex-col lg:flex-row max-w-xl' style={{ zIndex: 9999 }}>
|
||||
<div className={'hero min-h-screen'} ref={mainEl}>
|
||||
<div style={{ animation: "fadeInDown", animationDuration: "1.5s", zIndex: 5 }} className={ 'animate__animated animate__fadeIn' } >
|
||||
<div className='hero-content flex-col lg:flex-row max-w-xl' style={{ zIndex: 5 }}>
|
||||
<div className='avatar'>
|
||||
<Tilt glareEnable glareColor={'#f9e2af'}>
|
||||
<img src={'favicon.png'} />
|
||||
<img src={avatar} />
|
||||
</Tilt>
|
||||
</div>
|
||||
|
||||
<div style={{ zIndex: 9999 }}>
|
||||
|
||||
<h1 className='text-5xl font-bold zeon-word' >Saahild.com</h1>
|
||||
<p>WIP.</p>
|
||||
<h1 className='text-5xl font-bold'><span className={'wave'}>👋🏾 </span> Hi im <span className='text-highlight'>Saahil</span></h1>
|
||||
<p>Welcome to my site.</p>
|
||||
<Typewriter />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
14
src/components/main/type.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Typewriter from 'typewriter-effect'
|
||||
export default function typewriter() {
|
||||
return <Typewriter
|
||||
options={{
|
||||
strings: ['Developer', 'Gamer', 'Builder'],
|
||||
autoStart: true,
|
||||
loop: true,
|
||||
deleteSpeed: 100,
|
||||
wrapperClassName: 'text-highlight',
|
||||
cursor: ' |',
|
||||
devMode: process.env.NODE_ENV !== 'production'
|
||||
}}
|
||||
/>
|
||||
}
|
32
src/components/navbar/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import "./nav.css"
|
||||
function NavLinks() {
|
||||
return <>
|
||||
<li><a>About</a></li>
|
||||
<li><a>Projects</a></li>
|
||||
<li><a>Contact</a></li>
|
||||
</>
|
||||
}
|
||||
export default function Navbar() {
|
||||
return <div className="navbar" style={{ zIndex: 9999, background: "var(--base)", position: "fixed", }}>
|
||||
<div className="navbar-start">
|
||||
<div className="dropdown">
|
||||
<div tabIndex={0} role="button" className="btn btn-ghost lg:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
|
||||
</div>
|
||||
<ul tabIndex={0} className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 nav-links">
|
||||
<NavLinks />
|
||||
</ul>
|
||||
</div>
|
||||
<a className="btn btn-ghost text-xl">saahild.com</a>
|
||||
|
||||
<div className="navbar-center hidden lg:flex">
|
||||
<ul className="menu menu-horizontal px-1 nav-links">
|
||||
<NavLinks />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="navbar-end">
|
||||
{/* <a className="btn">Button</a> */}
|
||||
</div>
|
||||
</div>
|
||||
}
|
4
src/components/navbar/nav.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.nav-links {
|
||||
font-size:medium;
|
||||
@apply font-bold;
|
||||
}
|
17
src/components/static/Icons.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import cssIcon from "../../assets/icons/css.svg"
|
||||
import htmlIcon from "../../assets/icons/html.svg"
|
||||
import reactjs from "../../assets/icons/reactjs.svg"
|
||||
import nodejs from "../../assets/icons/nodejs.svg"
|
||||
import typescript from "../../assets/icons/typescript.svg"
|
||||
import javascript from "../../assets/icons/javascript.svg"
|
||||
const icons:any = {
|
||||
"css": cssIcon,
|
||||
"html": htmlIcon,
|
||||
"reactjs": reactjs,
|
||||
"nodejs": nodejs,
|
||||
"typescript": typescript,
|
||||
javascript
|
||||
}
|
||||
export default function Icon({ icon, className }: { icon: string, className?: string }) {
|
||||
return <img src={icons[icon]} className={"inline-flex hover:scale-125 duration-500 linear " + (className ?? "")} />
|
||||
}
|
|
@ -30,6 +30,19 @@
|
|||
--mantle: #181825;
|
||||
--crust: #11111b;
|
||||
}
|
||||
.text-highlight {
|
||||
color: var(--mauve);
|
||||
/* color: linear-gradient(var(--mauve), var(--mantle); */
|
||||
/* -webkit-background-clip: text; */
|
||||
/* -webkit-text-fill-color: transparent; */
|
||||
@apply ease-in duration-500;
|
||||
|
||||
}
|
||||
.text-highlight:hover {
|
||||
background: linear-gradient(var(--mauve), var(--yellow));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
|
|
@ -6,15 +6,22 @@ import App from './App';
|
|||
import reportWebVitals from './reportWebVitals';
|
||||
import Background from './bg/main';
|
||||
import { injectOneko } from './scripts/oneko';
|
||||
import Navbar from './components/navbar';
|
||||
import { runTitle } from './scripts/title';
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
document.title = "Saahild.com"
|
||||
injectOneko();
|
||||
runTitle()
|
||||
document.title = 'React App';
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Background />
|
||||
|
||||
<Navbar />
|
||||
<App />
|
||||
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
|
20
src/scripts/log.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
export default function startingLog():void {
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
console.log(
|
||||
`%c What's up?`,
|
||||
`font-size: 120px;
|
||||
font-family: "Poppins", sans-serif;
|
||||
background-size: 1000% 1000%;
|
||||
animation: gradient 1s ease infinite;
|
||||
`
|
||||
)
|
||||
console.log(
|
||||
`%c Thanks for visting this site you can dontate me at https://ko-fi.com/saahil\n Try to find the easter egg in this site🥚\n HINT: to use it you must use the console it is a very VERY hard one`,
|
||||
`font-size: 15px;`
|
||||
)
|
||||
if (isDev) {
|
||||
console.debug(
|
||||
'This message will show during production build. & development build'
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,240 +1,287 @@
|
|||
// oneko.js: https://github.com/adryd325/oneko.js
|
||||
// oneko.js: https://github.com/tylxr59/oneko.js/blob/main/oneko.js
|
||||
// modified by @NeonGamerBot-QK
|
||||
export const nekoEl = document.createElement("div");
|
||||
export function deleteOneko(): void {
|
||||
return nekoEl.remove();
|
||||
}
|
||||
export function injectOneko():void {
|
||||
const isReducedMotion = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
|
||||
// oneko.js: https://github.com/adryd325/oneko.js
|
||||
|
||||
if (isReducedMotion) return;
|
||||
export function injectOneko() {
|
||||
const isReducedMotion = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
|
||||
|
||||
if (isReducedMotion) return;
|
||||
|
||||
let nekoPosX = 32;
|
||||
let nekoPosY = 32;
|
||||
const nekoEl = document.createElement("div");
|
||||
|
||||
let mousePosX = 0;
|
||||
let mousePosY = 0;
|
||||
let nekoPosX = 32;
|
||||
let nekoPosY = 32;
|
||||
|
||||
let frameCount = 0;
|
||||
let idleTime = 0;
|
||||
let idleAnimation: null | number | string = null;
|
||||
let idleAnimationFrame = 0;
|
||||
let mousePosX = 0;
|
||||
let mousePosY = 0;
|
||||
|
||||
const nekoSpeed = 10;
|
||||
const spriteSets = {
|
||||
idle: [[-3, -3]],
|
||||
alert: [[-7, -3]],
|
||||
scratchSelf: [
|
||||
[-5, 0],
|
||||
[-6, 0],
|
||||
[-7, 0],
|
||||
],
|
||||
scratchWallN: [
|
||||
[0, 0],
|
||||
[0, -1],
|
||||
],
|
||||
scratchWallS: [
|
||||
[-7, -1],
|
||||
[-6, -2],
|
||||
],
|
||||
scratchWallE: [
|
||||
[-2, -2],
|
||||
[-2, -3],
|
||||
],
|
||||
scratchWallW: [
|
||||
[-4, 0],
|
||||
[-4, -1],
|
||||
],
|
||||
tired: [[-3, -2]],
|
||||
sleeping: [
|
||||
[-2, 0],
|
||||
[-2, -1],
|
||||
],
|
||||
N: [
|
||||
[-1, -2],
|
||||
[-1, -3],
|
||||
],
|
||||
NE: [
|
||||
[0, -2],
|
||||
[0, -3],
|
||||
],
|
||||
E: [
|
||||
[-3, 0],
|
||||
[-3, -1],
|
||||
],
|
||||
SE: [
|
||||
[-5, -1],
|
||||
[-5, -2],
|
||||
],
|
||||
S: [
|
||||
[-6, -3],
|
||||
[-7, -2],
|
||||
],
|
||||
SW: [
|
||||
[-5, -3],
|
||||
[-6, -1],
|
||||
],
|
||||
W: [
|
||||
[-4, -2],
|
||||
[-4, -3],
|
||||
],
|
||||
NW: [
|
||||
[-1, 0],
|
||||
[-1, -1],
|
||||
],
|
||||
};
|
||||
let frameCount = 0;
|
||||
let idleTime = 0;
|
||||
let idleAnimation:any = null;
|
||||
let idleAnimationFrame = 0;
|
||||
|
||||
function init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = "true";
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "fixed";
|
||||
nekoEl.style.pointerEvents = "none";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = Number.MAX_VALUE.toString();
|
||||
let nekoFile = "https://saahild.com/oneko.gif"
|
||||
const curScript = document.currentScript
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
const nekoSpeed = 10;
|
||||
const spriteSets = {
|
||||
idle: [[-3, -3]],
|
||||
alert: [[-7, -3]],
|
||||
scratchSelf: [
|
||||
[-5, 0],
|
||||
[-6, 0],
|
||||
[-7, 0],
|
||||
],
|
||||
scratchWallN: [
|
||||
[0, 0],
|
||||
[0, -1],
|
||||
],
|
||||
scratchWallS: [
|
||||
[-7, -1],
|
||||
[-6, -2],
|
||||
],
|
||||
scratchWallE: [
|
||||
[-2, -2],
|
||||
[-2, -3],
|
||||
],
|
||||
scratchWallW: [
|
||||
[-4, 0],
|
||||
[-4, -1],
|
||||
],
|
||||
tired: [[-3, -2]],
|
||||
sleeping: [
|
||||
[-2, 0],
|
||||
[-2, -1],
|
||||
],
|
||||
N: [
|
||||
[-1, -2],
|
||||
[-1, -3],
|
||||
],
|
||||
NE: [
|
||||
[0, -2],
|
||||
[0, -3],
|
||||
],
|
||||
E: [
|
||||
[-3, 0],
|
||||
[-3, -1],
|
||||
],
|
||||
SE: [
|
||||
[-5, -1],
|
||||
[-5, -2],
|
||||
],
|
||||
S: [
|
||||
[-6, -3],
|
||||
[-7, -2],
|
||||
],
|
||||
SW: [
|
||||
[-5, -3],
|
||||
[-6, -1],
|
||||
],
|
||||
W: [
|
||||
[-4, -2],
|
||||
[-4, -3],
|
||||
],
|
||||
NW: [
|
||||
[-1, 0],
|
||||
[-1, -1],
|
||||
],
|
||||
};
|
||||
|
||||
document.body.appendChild(nekoEl);
|
||||
function init() {
|
||||
nekoEl.id = "oneko";
|
||||
nekoEl.ariaHidden = "true";
|
||||
nekoEl.style.width = "32px";
|
||||
nekoEl.style.height = "32px";
|
||||
nekoEl.style.position = "fixed";
|
||||
nekoEl.style.pointerEvents = "auto";
|
||||
nekoEl.style.imageRendering = "pixelated";
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
nekoEl.style.zIndex = "99999999";
|
||||
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
let nekoFile = "https://saahild.com/oneko.gif"
|
||||
const curScript = document.currentScript
|
||||
if (curScript && curScript.dataset.cat) {
|
||||
nekoFile = curScript.dataset.cat
|
||||
}
|
||||
nekoEl.style.backgroundImage = `url(${nekoFile})`;
|
||||
|
||||
let lastFrameTimestamp: undefined | number = undefined;
|
||||
document.body.appendChild(nekoEl);
|
||||
|
||||
function onAnimationFrame(timestamp: number) {
|
||||
// Stops execution if the neko element is removed from DOM
|
||||
if (!nekoEl.isConnected) {
|
||||
return;
|
||||
}
|
||||
if (!lastFrameTimestamp) {
|
||||
lastFrameTimestamp = timestamp;
|
||||
}
|
||||
if (timestamp - lastFrameTimestamp > 100) {
|
||||
lastFrameTimestamp = timestamp
|
||||
frame()
|
||||
}
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
document.addEventListener("mousemove", function (event) {
|
||||
mousePosX = event.clientX;
|
||||
mousePosY = event.clientY;
|
||||
});
|
||||
|
||||
function setSprite(name: string, frame: number) {
|
||||
//@ts-ignore
|
||||
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
|
||||
if (nekoPosX < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallW");
|
||||
}
|
||||
if (nekoPosY < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallN");
|
||||
}
|
||||
if (nekoPosX > window.innerWidth - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallE");
|
||||
}
|
||||
if (nekoPosY > window.innerHeight - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallS");
|
||||
}
|
||||
idleAnimation =
|
||||
avalibleIdleAnimations[
|
||||
Math.floor(Math.random() * avalibleIdleAnimations.length)
|
||||
];
|
||||
}
|
||||
|
||||
switch (idleAnimation) {
|
||||
case "sleeping":
|
||||
if (idleAnimationFrame < 8) {
|
||||
setSprite("tired", 0);
|
||||
break;
|
||||
}
|
||||
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
|
||||
if (idleAnimationFrame > 192) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
case "scratchWallN":
|
||||
case "scratchWallS":
|
||||
case "scratchWallE":
|
||||
case "scratchWallW":
|
||||
case "scratchSelf":
|
||||
setSprite(idleAnimation, idleAnimationFrame);
|
||||
if (idleAnimationFrame > 9) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSprite("idle", 0);
|
||||
return;
|
||||
}
|
||||
idleAnimationFrame += 1;
|
||||
}
|
||||
|
||||
function frame() {
|
||||
frameCount += 1;
|
||||
const diffX = nekoPosX - mousePosX;
|
||||
const diffY = nekoPosY - mousePosY;
|
||||
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
|
||||
|
||||
if (distance < nekoSpeed || distance < 48) {
|
||||
idle();
|
||||
return;
|
||||
}
|
||||
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
|
||||
if (idleTime > 1) {
|
||||
setSprite("alert", 0);
|
||||
// count down after being alerted before moving
|
||||
idleTime = Math.min(idleTime, 7);
|
||||
idleTime -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let direction;
|
||||
direction = diffY / distance > 0.5 ? "N" : "";
|
||||
direction += diffY / distance < -0.5 ? "S" : "";
|
||||
direction += diffX / distance > 0.5 ? "W" : "";
|
||||
direction += diffX / distance < -0.5 ? "E" : "";
|
||||
setSprite(direction, frameCount);
|
||||
|
||||
nekoPosX -= (diffX / distance) * nekoSpeed;
|
||||
nekoPosY -= (diffY / distance) * nekoSpeed;
|
||||
|
||||
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
|
||||
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
|
||||
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
}
|
||||
|
||||
init();
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
let lastFrameTimestamp: any ;
|
||||
|
||||
function onAnimationFrame(timestamp: any) {
|
||||
// Stops execution if the neko element is removed from DOM
|
||||
if (!nekoEl.isConnected) {
|
||||
return;
|
||||
}
|
||||
if (!lastFrameTimestamp) {
|
||||
lastFrameTimestamp = timestamp;
|
||||
}
|
||||
if (timestamp - lastFrameTimestamp > 100) {
|
||||
lastFrameTimestamp = timestamp
|
||||
frame()
|
||||
}
|
||||
window.requestAnimationFrame(onAnimationFrame);
|
||||
}
|
||||
|
||||
function setSprite(name: any, frame: any) {
|
||||
//@ts-ignore
|
||||
const sprite = spriteSets[name][frame % spriteSets[name].length];
|
||||
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
|
||||
}
|
||||
|
||||
function resetIdleAnimation() {
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
}
|
||||
|
||||
function idle() {
|
||||
idleTime += 1;
|
||||
|
||||
// every ~ 20 seconds
|
||||
if (
|
||||
idleTime > 10 &&
|
||||
Math.floor(Math.random() * 200) == 0 &&
|
||||
idleAnimation == null
|
||||
) {
|
||||
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
|
||||
if (nekoPosX < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallW");
|
||||
}
|
||||
if (nekoPosY < 32) {
|
||||
avalibleIdleAnimations.push("scratchWallN");
|
||||
}
|
||||
if (nekoPosX > window.innerWidth - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallE");
|
||||
}
|
||||
if (nekoPosY > window.innerHeight - 32) {
|
||||
avalibleIdleAnimations.push("scratchWallS");
|
||||
}
|
||||
idleAnimation =
|
||||
avalibleIdleAnimations[
|
||||
Math.floor(Math.random() * avalibleIdleAnimations.length)
|
||||
];
|
||||
}
|
||||
|
||||
switch (idleAnimation) {
|
||||
case "sleeping":
|
||||
if (idleAnimationFrame < 8) {
|
||||
setSprite("tired", 0);
|
||||
break;
|
||||
}
|
||||
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
|
||||
if (idleAnimationFrame > 192) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
case "scratchWallN":
|
||||
case "scratchWallS":
|
||||
case "scratchWallE":
|
||||
case "scratchWallW":
|
||||
case "scratchSelf":
|
||||
setSprite(idleAnimation, idleAnimationFrame);
|
||||
if (idleAnimationFrame > 9) {
|
||||
resetIdleAnimation();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setSprite("idle", 0);
|
||||
return;
|
||||
}
|
||||
idleAnimationFrame += 1;
|
||||
}
|
||||
|
||||
function explodeHearts() {
|
||||
const parent = nekoEl.parentElement;
|
||||
const rect = nekoEl.getBoundingClientRect();
|
||||
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
const centerX = rect.left + rect.width / 2 + scrollLeft;
|
||||
const centerY = rect.top + rect.height / 2 + scrollTop;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const heart = document.createElement('div');
|
||||
heart.className = 'heart';
|
||||
heart.textContent = '❤';
|
||||
const offsetX = (Math.random() - 0.5) * 50;
|
||||
const offsetY = (Math.random() - 0.5) * 50;
|
||||
heart.style.left = `${centerX + offsetX - 16}px`;
|
||||
heart.style.top = `${centerY + offsetY - 16}px`;
|
||||
heart.style.transform = `translate(-50%, -50%) rotate(${Math.random() * 360}deg)`;
|
||||
parent?.appendChild(heart);
|
||||
|
||||
setTimeout(() => {
|
||||
parent?.removeChild(heart);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
@keyframes heartBurst {
|
||||
0% { transform: scale(0); opacity: 1; }
|
||||
100% { transform: scale(1); opacity: 0; }
|
||||
}
|
||||
.heart {
|
||||
position: absolute;
|
||||
font-size: 2em;
|
||||
animation: heartBurst 1s ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
color: var(--mauve);
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
nekoEl.addEventListener('click', explodeHearts);
|
||||
|
||||
function frame() {
|
||||
frameCount += 1;
|
||||
const diffX = nekoPosX - mousePosX;
|
||||
const diffY = nekoPosY - mousePosY;
|
||||
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
|
||||
|
||||
if (distance < nekoSpeed || distance < 48) {
|
||||
idle();
|
||||
return;
|
||||
}
|
||||
|
||||
idleAnimation = null;
|
||||
idleAnimationFrame = 0;
|
||||
|
||||
if (idleTime > 1) {
|
||||
setSprite("alert", 0);
|
||||
// count down after being alerted before moving
|
||||
idleTime = Math.min(idleTime, 7);
|
||||
idleTime -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let direction;
|
||||
direction = diffY / distance > 0.5 ? "N" : "";
|
||||
direction += diffY / distance < -0.5 ? "S" : "";
|
||||
direction += diffX / distance > 0.5 ? "W" : "";
|
||||
direction += diffX / distance < -0.5 ? "E" : "";
|
||||
setSprite(direction, frameCount);
|
||||
|
||||
nekoPosX -= (diffX / distance) * nekoSpeed;
|
||||
nekoPosY -= (diffY / distance) * nekoSpeed;
|
||||
|
||||
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
|
||||
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
|
||||
|
||||
nekoEl.style.left = `${nekoPosX - 16}px`;
|
||||
nekoEl.style.top = `${nekoPosY - 16}px`;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
export function stopOneko() {
|
||||
return document.getElementById('oneko')?.remove()
|
||||
}
|
20
src/scripts/title.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
let title0: null | string = null
|
||||
let interId: any = null
|
||||
export function runTitle() {
|
||||
if(interId) clearInterval(interId)
|
||||
interId = setInterval(() => {
|
||||
if(document.title !== 'Come Back :(') {
|
||||
title0 = document.title
|
||||
}
|
||||
if (document.hasFocus()) {
|
||||
document.title = title0 || "Saahild.com"
|
||||
} else {
|
||||
document.title = 'Come Back :('
|
||||
}
|
||||
}, 200)
|
||||
|
||||
}
|
||||
export function stopTitle() {
|
||||
if(!interId) return false;
|
||||
return clearInterval(interId)
|
||||
}
|
22
yarn.lock
|
@ -1614,6 +1614,18 @@
|
|||
resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz"
|
||||
integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==
|
||||
|
||||
"@mdi/js@^7.4.47":
|
||||
version "7.4.47"
|
||||
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-7.4.47.tgz#7d8a4edc9631bffeed80d1ec784f9beae559a76a"
|
||||
integrity sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==
|
||||
|
||||
"@mdi/react@^1.6.1":
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@mdi/react/-/react-1.6.1.tgz#624313593ae8065d2a09878ca81beb3e4b676b03"
|
||||
integrity sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||
version "5.1.1-v1"
|
||||
resolved "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz"
|
||||
|
@ -7724,7 +7736,7 @@ prompts@^2.0.1, prompts@^2.4.2:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.9, prop-types@^15.8.1:
|
||||
prop-types@^15.5.9, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -9168,6 +9180,14 @@ typescript@^4.9.5:
|
|||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
typewriter-effect@^2.21.0:
|
||||
version "2.21.0"
|
||||
resolved "https://registry.yarnpkg.com/typewriter-effect/-/typewriter-effect-2.21.0.tgz#7150f12fd2c188248ab2b2b031f77c0663d24c54"
|
||||
integrity sha512-Y3VL1fuJpUBj0gS4OTXBLzy1gnYTYaBuVuuO99tGNyTkkub5CXi+b/hsV7Og9fp6HlhogOwWJwgq7iXI5sQlEg==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
raf "^3.4.1"
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz"
|
||||
|
|