Compare commits

..

1 Commits

Author SHA1 Message Date
Mariia Shabelnik
7a9f716f62 Merge branch 'release/v1'
Some checks failed
ci/cd / Build (push) Failing after 1s
2024-01-08 12:11:44 +01:00
37 changed files with 2936 additions and 2689 deletions

View File

@ -7,12 +7,12 @@ jobs:
runs-on: all
steps:
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Declare variables
run: |
echo "sha_short=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV"
echo "run_name=$(echo "$GITHUB_REPOSITORY" | sed "s/\//_/")" >> "$GITHUB_ENV"
echo "domain_name="www.msweb.io"" >> "$GITHUB_ENV"
echo "domain_name="www.mariia.art"" >> "$GITHUB_ENV"
echo "docker_port="80"" >> "$GITHUB_ENV"
- name: Build docker
run: |
@ -21,4 +21,8 @@ jobs:
run: |
docker container rm -f ${{ env.run_name }} || true
docker run -d --network=web --restart unless-stopped --name ${{ env.run_name }} ${{ gitea.repository }}:${{ env.sha_short }}
/usr/bin/caddyconf --add --url ${{ env.domain_name }} --name ${{ env.run_name }} --port ${{ env.docker_port }}
/usr/local/bin/caddycontrol -host ${{ env.domain_name }} -dial ${{ env.run_name }}:${{ env.docker_port }}

View File

@ -1,12 +1,14 @@
FROM node:20-alpine AS builder
FROM node:17.1.0-alpine3.12 as builder
WORKDIR /app
COPY package*.json ./
COPY package.json ./
COPY package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
COPY ./ ./
RUN NODE_ENV=production npm run build
FROM caddy:2-alpine
WORKDIR /usr/share/caddy
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=builder /app/dist .
COPY --from=builder ./app/dist .

View File

@ -2,43 +2,27 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=5.0"
/>
<meta
name="description"
content="Mariia Shabelnik - Frontend Engineer portfolio showcasing web development projects and skills"
/>
<meta name="theme-color" content="#f0f0f3" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>, <text y=%22.9em%22 font-size=%2290%22>
🫰
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>
<text y=%22.9em%22 font-size=%2290%22>
👋
</text>
</svg>"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- nav font -->
<link
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap"
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap"
rel="stylesheet"
/>
<!-- body font -->
<link
href="https://fonts.googleapis.com/css2?family=Jura:wght@300..700&display=swap"
href="https://fonts.googleapis.com/css2?family=Ubuntu+Mono:wght@400;700&display=swap"
rel="stylesheet"
/>
<!-- headline-title font -->
<link
href="https://fonts.googleapis.com/css2?family=Sora:wght@100..800&display=swap"
rel="stylesheet"
/>
<!-- logo font -->
<link
href="https://fonts.googleapis.com/css2?family=Sixtyfour&display=swap"
href="https://fonts.googleapis.com/css2?family=Tektur:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>

4694
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,11 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.9.7",
"hamburger-react": "^2.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-image-gallery": "^1.3.0",
"react-intersection-observer": "^9.16.0",
"react-router-dom": "^6.3.0",
"react-scroll-motion": "^0.3.0",
"recoil": "^0.7.7"
@ -23,10 +21,10 @@
"devDependencies": {
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react": "^2.0.0",
"autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"vite": "^6.3.5"
"vite": "^3.0.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#0288D1" d="M42,37c0,2.762-2.238,5-5,5H11c-2.761,0-5-2.238-5-5V11c0-2.762,2.239-5,5-5h26c2.762,0,5,2.238,5,5V37z"/><path fill="#FFF" d="M12 19H17V36H12zM14.485 17h-.028C12.965 17 12 15.888 12 14.499 12 13.08 12.995 12 14.514 12c1.521 0 2.458 1.08 2.486 2.499C17 15.887 16.035 17 14.485 17zM36 36h-5v-9.099c0-2.198-1.225-3.698-3.192-3.698-1.501 0-2.313 1.012-2.707 1.99C24.957 25.543 25 26.511 25 27v9h-5V19h5v2.616C25.721 20.5 26.85 19 29.738 19c3.578 0 6.261 2.25 6.261 7.274L36 36 36 36z"/></svg>

Before

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,16 +1,18 @@
//import "./App.css";
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Experience from "./pages/Experience";
import Contact from "./pages/Contact";
import Footer from "./components/Footer";
import Header from "./components/Header";
import ExperianceDetail from "./pages/ExperienceDetail";
import FullPage from "./pages/FullPage";
import { RecoilRoot } from "recoil";
import ScrollToAnchor from "./components/ScrollToAnchor";
import AnimatedBackground from "./components/AnimatedBackground";
function App() {
return (
<div className="text-gray-600">
<AnimatedBackground />
<div className="text-white">
<RecoilRoot>
<main className=" min-h-screen">
<Header />
@ -21,6 +23,7 @@ function App() {
<Route path="/contact" element={<Contact />} />*/}
<Route path="/experience/:id" element={<ExperianceDetail />} />
</Routes>
<Footer />
</main>
</RecoilRoot>
</div>

View File

@ -1,97 +0,0 @@
import { useEffect, useRef } from 'react';
const AnimatedBackground = () => {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let animationFrameId;
let lastScrollY = 0;
// Set canvas size
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Create dots
const dots = [];
const dotCount = 80; // Reduced from 100 for smoother performance
for (let i = 0; i < dotCount; i++) {
dots.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 1,
speed: Math.random() * 0.3 + 0.1, // Reduced speed for smoother movement
});
}
// Animation loop
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw dots
ctx.fillStyle = 'rgba(150, 150, 150, 0.3)'; // Increased from 0.2
dots.forEach(dot => {
ctx.beginPath();
ctx.arc(dot.x, dot.y, dot.size * 1.2, 0, Math.PI * 2); // Increased size
ctx.fill();
});
// Draw connections
ctx.strokeStyle = 'rgba(150, 150, 150, 0.15)'; // Increased from 0.1
ctx.lineWidth = 1; // Increased from 0.8
dots.forEach((dot1, i) => {
dots.slice(i + 1).forEach(dot2 => {
const dx = dot1.x - dot2.x;
const dy = dot1.y - dot2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 180) { // Increased connection distance
ctx.beginPath();
ctx.moveTo(dot1.x, dot1.y);
ctx.lineTo(dot2.x, dot2.y);
ctx.stroke();
}
});
});
animationFrameId = requestAnimationFrame(animate);
};
// Handle scroll
const handleScroll = () => {
const scrollY = window.scrollY;
const scrollDiff = scrollY - lastScrollY;
dots.forEach(dot => {
dot.y += scrollDiff * dot.speed * 0.15; // Reduced movement speed
if (dot.y > canvas.height) dot.y = 0;
if (dot.y < 0) dot.y = canvas.height;
});
lastScrollY = scrollY;
};
window.addEventListener('scroll', handleScroll);
animate();
// Cleanup
return () => {
window.removeEventListener('resize', resizeCanvas);
window.removeEventListener('scroll', handleScroll);
cancelAnimationFrame(animationFrameId);
};
}, []);
return (
<canvas
ref={canvasRef}
className="fixed top-0 left-0 w-full h-full pointer-events-none -z-10"
/>
);
};
export default AnimatedBackground;

View File

@ -1,35 +1,10 @@
import { FaLinkedin, FaEnvelope } from "react-icons/fa";
function Footer() {
const fullYear = new Date().getFullYear();
const email = "mariia.shabelnik@gmail.com"; // Replace with your email
const linkedinUrl = "https://www.linkedin.com/in/mariia-shabelnik/"; // Replace with your LinkedIn URL
return (
<footer className="container mx-auto my-10 px-6 sticky top-[95vh]">
<div className="flex flex-col items-center gap-4">
<div className="flex gap-6">
<a
href={`mailto:${email}`}
className="text-gray-500 hover:text-highlight transition-colors duration-300"
aria-label="Send email to Mariia"
>
<FaEnvelope className="w-6 h-6" />
</a>
<a
href={linkedinUrl}
target="_blank"
rel="noopener noreferrer"
className="text-gray-500 hover:text-highlight transition-colors duration-300"
aria-label="Visit Mariia's LinkedIn profile"
>
<FaLinkedin className="w-6 h-6" />
</a>
</div>
<div className="text-center text-sm text-gray-500">
<div className=" text-center text-sm text-white/50 ">
© {fullYear} Mariia Shabelnik, all rights reserved
</div>
</div>
</footer>
);
}

View File

@ -13,17 +13,17 @@ function Header() {
}, [isOpen]);
const menu = [
{ link: "/#about", title: ".about( )" },
{ link: "/#experience", title: ".projects( )" },
{ link: "/#about", title: ".me()" },
{ link: "/#experience", title: ".experience()" },
{ link: "/#contact", title: ".contact()" },
];
const menuUI = menu.map((item) => {
let className = "drop-shadow-doublelight hover:drop-shadow-active";
if (`/${location.hash}` === item.link) {
let className = "drop-shadow-light hover:drop-shadow-doublelight";
if (location.pathname === item.link) {
className += " text-white";
} else {
className += " text-gray-500";
className += " text-white/40";
}
return (
<li className="my-10 text-xl md:my-0 md:text-base" key={item.link}>
@ -33,9 +33,6 @@ function Header() {
setOpen(false);
}}
to={item.link}
aria-current={
location.hash === item.link.slice(2) ? "page" : undefined
}
>
{item.title}
</Link>
@ -43,19 +40,10 @@ function Header() {
);
});
const headerClasses = [
"sticky",
"top-0",
"z-40",
"backdrop-blur-sm",
"h-16",
"border-b-[1px]",
"border-white",
"rounded-b-[1rem]",
];
const headerClasses = ["sticky", "top-0", "z-40", "backdrop-blur-md", "h-16"];
const logoClasses = ["hover:drop-shadow-light"];
const overlayMenu = [
"bg-white/90",
"bg-black/90",
"z-50",
"fixed",
"left-0",
@ -66,64 +54,37 @@ function Header() {
"p-2",
];
if (isOpen) {
headerClasses.push("bg-white/70");
logoClasses.push("drop-shadow-gray");
headerClasses.push("bg-black/90");
logoClasses.push("drop-shadow-yellow");
} else {
headerClasses.push("bg-bgColor/70");
logoClasses.push("drop-shadow-active");
headerClasses.push("bg-bgColor/90");
logoClasses.push("drop-shadow-doublelight");
}
return (
<>
{isOpen && (
<nav
className={overlayMenu.join(" ")}
aria-label="Mobile navigation menu"
role="navigation"
>
<div className={overlayMenu.join(" ")}>
<div className=" h-[calc(100vh-20rem)] flex flex-col justify-center">
<ul role="menu" aria-label="Mobile menu items">
{menuUI}
</ul>
<ul>{menuUI}</ul>
</div>
</div>
</nav>
)}
<header className={headerClasses.join(" ")}>
<div className="container mx-auto h-full max-w-7xl">
<nav
className="flex items-center h-full px-10"
aria-label="Main navigation"
role="navigation"
>
<div className="flex-none text-3xl md:text-4xl font-logo text-[#6d6d6d]">
<Link
className={logoClasses.join(" ")}
to="/#start"
aria-label="Home"
>
MS
<div className="container mx-auto h-full">
<nav className="flex items-center h-full px-2">
<div className="flex-none text-3xl md:text-4xl font-black font-headline ">
<Link className={logoClasses.join(" ")} to="/#start">
MS.
</Link>
</div>
<div className="grow"></div>
<div className="flex-none hidden md:block ">
<ul
className="flex flex-row gap-4 text-lg"
role="menu"
aria-label="Main menu items"
>
{menuUI}
</ul>
<ul className="flex flex-row gap-4 text-lg">{menuUI}</ul>
</div>
<div className="flex-none block md:hidden">
<Hamburger
toggled={isOpen}
toggle={setOpen}
duration={0.9}
aria-label="Toggle mobile menu"
aria-expanded={isOpen}
aria-controls="mobile-menu"
/>
<Hamburger toggled={isOpen} toggle={setOpen} duration={0.9} />
</div>
</nav>
</div>

View File

@ -1,20 +0,0 @@
import React from "react";
import { GoChevronLeft } from "react-icons/go";
const LeftNav = React.memo(({ disabled, onClick }) => {
return (
<button
type="button"
className="image-gallery-icon image-gallery-left-nav text-2xl md:text-4xl text-highlight"
disabled={disabled}
onClick={onClick}
aria-label="Previous Slide"
>
<GoChevronLeft />
</button>
);
});
LeftNav.displayName = "LeftNav";
export default LeftNav;

View File

@ -1,25 +0,0 @@
import React from "react";
// import { bool, func } from "prop-types";
// import SVG from "src/components/SVG";
const PlayPause = React.memo(({ isPlaying, onClick }) => {
return (
<button
type="button"
className="image-gallery-icon image-gallery-play-button"
onClick={onClick}
aria-label="Play or Pause Slideshow"
>
<SVG strokeWidth={2} icon={isPlaying ? "pause" : "play"} />
</button>
);
});
PlayPause.displayName = "PlayPause";
// PlayPause.propTypes = {
// isPlaying: bool.isRequired,
// onClick: func.isRequired,
// };
export default PlayPause;

View File

@ -1,19 +0,0 @@
import React from "react";
import { GoChevronRight } from "react-icons/go";
const RightNav = React.memo(({ disabled, onClick }) => {
return (
<button
type="button"
className="image-gallery-icon image-gallery-right-nav text-2xl md:text-4xl text-highlight"
disabled={disabled}
onClick={onClick}
aria-label="Next Slide"
>
<GoChevronRight />
</button>
);
});
RightNav.displayName = "RightNav";
export default RightNav;

View File

@ -9,17 +9,10 @@ function ScrollToAnchor() {
// https://jasonwatmore.com/react-router-v6-listen-to-location-route-change-without-history-listen
useEffect(() => {
if (location.hash) {
lastHash.current = location.hash.slice(1);
lastHash.current = location.hash.slice(1); // safe hash for further use after navigation
}
if (lastHash.current === "contact") {
// Scroll to bottom of the page
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior: "smooth"
});
lastHash.current = "";
} else if (lastHash.current && document.getElementById(lastHash.current)) {
if (lastHash.current && document.getElementById(lastHash.current)) {
setTimeout(() => {
document
.getElementById(lastHash.current)

View File

@ -1,16 +1,13 @@
function Tags({ listOfTags }) {
const tagList = listOfTags.map((itemTag, keyTag) => {
return (
<div
className="bg-gray-500 py-1 px-2 rounded-xl shadow-md hover:shadow-lg transition-shadow duration-200 hover:bg-gray-600"
key={keyTag}
>
<div className={`bg-gray-800 py-1 px-2 rounded-xl `} key={keyTag}>
{itemTag}
</div>
);
});
return (
<div className="text-tags flex flex-wrap gap-2 font-tags text-white">
<div className="text-tags flex flex-wrap gap-2 font-tags text-highlight ">
{tagList}
</div>
);

View File

@ -4,8 +4,8 @@
@tailwind components;
@tailwind utilities;
body {
background-color: #f0f0f3;
background-color: #031417;
}
::selection {
background: #caebfa;
background: #7d9c00b1;
}

View File

@ -1,115 +1,40 @@
import { useRecoilValue } from "recoil";
import Tags from "../components/Tags";
import { skillsAtom } from "../store";
import { motion } from "framer-motion";
import { useInView } from "react-intersection-observer";
function About() {
const skills = useRecoilValue(skillsAtom);
const [ref, inView] = useInView({
triggerOnce: true,
threshold: 0.1,
});
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: "easeOut",
},
},
};
console.log(skills);
return (
<section className="relative" aria-labelledby="about-heading">
<div id="about" className="absolute"></div>
<div className="min-h-screen flex items-center">
<motion.div
ref={ref}
variants={containerVariants}
initial="hidden"
animate={inView ? "visible" : "hidden"}
>
<motion.h2
variants={itemVariants}
id="about-heading"
className="mb-6 text-subTPhone md:text-subT font-headline"
>
About me<span className="text-highlight">.</span>
</motion.h2>
<motion.div variants={itemVariants} className="flex flex-col md:flex-row gap-6">
<motion.article variants={itemVariants} className="basis-6/12 text-base font-body">
<div className="relative">
<div id="about" className=" absolute -top-16 "></div>
<h2 className="mb-2 text-subTPhone md:text-subT font-headline">
About<span className=" text-highlight">.</span>
</h2>
<div className="flex flex-col md:flex-row">
<div className="basis-2/3 text-base">
<p>
"I'm a Stockholm-based developer passionate about building
innovative, secure digital experiences with a focus on both
frontend and backend technologies. With expertise in JavaScript,
React, TypeScript, and a keen eye for web design, I create
solutions that combine functionality with creativity. I use
Node.js for backend development and SQL for database management,
ensuring my applications are both robust and scalable. I also
have a deep passion for 3D design, which adds a unique,
immersive dimension to my work. I thrive in collaborative
environments that encourage continuous learning and value
creative problem-solving. Let's collaborate to push the
boundaries of digital innovation!"
{/* As a prospective Frontend Developer, I seek a challenging career
As a prospective Frontend Developer, I seek a challenging career
opportunity in the IT industry, where I can collaborate with a
dynamic team, continually learn, and foster innovation. My
dedication lies in transforming design concepts into
user-friendly experiences and achieving organizational goals
through creativity and teamwork. I aspire to engage in work that
allows the utilization of technical and creative skills to
contribute effectively to the growth of an organization. If you
think you've got an opening that I might like, let's connect 🔗 */}
dedication lies in transforming design concepts into user-friendly
experiences and achieving organizational goals through creativity
and teamwork. I aspire to engage in work that allows the utilization
of technical and creative skills to contribute effectively to the
growth of an organization. If you think you've got an opening that I
might like, let's connect 🔗
</p>
</motion.article>
<motion.figure variants={itemVariants} className="border">
<img
className="rounded-3xl"
src="/img/ProfileMe.jpg"
alt="Mariia Shabelnik's profile photo"
/>
</motion.figure>
<motion.aside variants={itemVariants} className="basis-1/3" aria-labelledby="skills-heading">
<h2 id="skills-heading" className="sr-only">
Skills and Technologies
</h2>
<section aria-labelledby="languages-heading">
<h3 id="languages-heading" className="mb-2 text-subTMini">
Languages:
</h3>
<Tags listOfTags={skills.languages} />
</section>
<section aria-labelledby="frameworks-heading">
<h3 id="frameworks-heading" className="my-2 text-subTMini">
Frameworks:
</h3>
<Tags listOfTags={skills.frameworks} />
</section>
<section aria-labelledby="tools-heading">
<h3 id="tools-heading" className="my-2 text-subTMini">
Tools I use:
</h3>
<Tags listOfTags={skills.tools} />
</section>
</motion.aside>
</motion.div>
</motion.div>
</div>
</section>
<div className=" basis-1/3">
<h4 className="mb-2 text-subTMini">Languages:</h4>
<Tags listOfTags={skills.languages} />
<h4 className="my-2 text-subTMini">Frameworks:</h4>{" "}
<Tags listOfTags={skills.frameworks} />
<h4 className="my-2 text-subTMini">Tools I use:</h4>{" "}
<Tags listOfTags={skills.tools} />
</div>
</div>
</div>
);
}

View File

@ -2,8 +2,7 @@ function Contact() {
return (
<div className="relative">
<div id="contact" className=" absolute -top-16 "></div>
<h1>Contact me</h1>
<p></p>
<h1>Welcome to C#ontact</h1>
</div>
);
}

View File

@ -2,105 +2,62 @@ import { Link } from "react-router-dom";
import { useRecoilValue } from "recoil";
import { projectsAtom } from "../store";
import Tags from "../components/Tags";
import { motion } from "framer-motion";
import { useInView } from "react-intersection-observer";
function Experiance() {
const experianceList = useRecoilValue(projectsAtom);
const [ref, inView] = useInView({
triggerOnce: true,
threshold: 0.1,
});
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: "easeOut",
},
},
};
const experianceListUI = experianceList.map((item, key) => {
const even = key % 2;
const position = key % 2;
const imgClasses = ["basis-1/3", "hidden", "md:block"];
if (position === 1) {
imgClasses.push("md:order-last");
}
return (
<motion.article
ref={ref}
variants={itemVariants}
className={`flex-1 gap-4 mb-12 bg-[#f0f0f3] border shadow-box ${
(even === 0 && "rounded-tl-3xl rounded-br-3xl") ||
"rounded-tr-3xl rounded-bl-3xl"
}`}
key={item.id}
>
<div className={`flex pt-4 md:pt-14 flex-col `}>
<div className="px-4 mb-4">
<h3 className="text-subTMiniPhone md:text-subTMini font-ht">
<section className="flex flex-col md:flex-row gap-4 mb-12" key={item.id}>
<div className={imgClasses.join(" ")}>
<img className="rounded-md" src={item.img[0]} />
</div>
<div className="basis-2/3 flex flex-col">
<div>
<h2 className="text-subTMiniPhone md:text-subTMini mb-4 ">
{item.title}
</h3>
</h2>
</div>
<figure className="basis-5/12">
<div>
<img
className="w-full"
className="block md:hidden mb-4 rounded-md"
src={item.img[0]}
alt={`Preview of ${item.title} project`}
/>
</figure>
<div className="grow"></div>
<div className="flex flex-col px-4 pt-4 pb-8 basis-3/12">
<div className="mb-4">
<Tags listOfTags={item.tags} />
</div>
<div className="text-base mb-6">
<div className="text-base mb-6 line-clamp-3 md:line-clamp-6">
<p>{item.info}</p>
</div>
<Tags listOfTags={item.tags} />
<div className="grow"></div>
<div className=" text-right">
<Link
className=" drop-shadow-doublelight hover:drop-shadow-light "
to={`/experience/${item.id}`}
>
read more
</Link>
</div>
</div>
</motion.article>
</section>
);
});
return (
<section className="relative" aria-labelledby="projects-heading">
<div
id="experience"
className="absolute -top-16"
aria-hidden="true"
></div>
<div className="min-h-screen">
<h2
id="projects-heading"
className="text-subTPhone md:text-subT font-headline mb-6"
>
Projects and experience<span className="text-highlight">.</span>
<div className="relative">
<div id="experience" className=" absolute -top-16 "></div>
<h2 className=" text-subTPhone md:text-subT font-headline">
Experience<span className=" text-highlight">.</span>
</h2>
<motion.div
ref={ref}
variants={containerVariants}
initial="hidden"
animate={inView ? "visible" : "hidden"}
className="py-4 grid grid-cols-1 md:grid-cols-2 gap-8"
>
{experianceListUI}
</motion.div>
<div className="py-4">{experianceListUI}</div>
</div>
</section>
);
}

View File

@ -4,8 +4,6 @@ import { projectsAtom } from "../store";
import ImageGallery from "react-image-gallery";
import Tags from "../components/Tags";
import { IoChevronBackOutline as BackButton } from "react-icons/io5";
import LeftNav from "../components/LeftNav";
import RightNav from "../components/RightNav";
function ExperianceDetail() {
const { id } = useParams();
@ -50,16 +48,16 @@ function ExperianceDetail() {
</button>
<div className="flex flex-col">
<div className="flex-1">
<h1 className="text-subTPhone md:text-subT font-headline">
<h1 className=" text-subTPhone md:text-subT">
Project: {myProject.title}
</h1>
</div>
<div className="flex-1 mt-2 mb-6 min-h-[80px]">
<div className="flex-1 mt-2 mb-6">
<Tags listOfTags={myProject.tags} />
</div>{" "}
</div>
</div>
<div className="flex flex-col md:flex-row gap-8">
<div className="w-full md:w-1/2">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<ImageGallery
showThumbnails={false}
showBullets={true}
@ -67,19 +65,10 @@ function ExperianceDetail() {
slideDuration={1000}
slideInterval={4000}
items={projectImg}
renderLeftNav={(onClick, disabled) => (
<LeftNav onClick={onClick} disabled={disabled} />
)}
renderRightNav={(onClick, disabled) => (
<RightNav onClick={onClick} disabled={disabled} />
)}
/>
</div>
<div className="w-full md:w-1/2 flex flex-col min-h-[300px]">
<div className="flex-1">
<h2 className="text-xl font-semibold mb-4">{myProject.subtitle}</h2>
<p className="text-base leading-relaxed">{myProject.description}</p>
</div>
<div className="flex-1 text-base">
{myProject.info}
<div className=" drop-shadow-doublelight hover:drop-shadow-light my-2 text-end">
{linkUI}
</div>

View File

@ -1,15 +1,15 @@
import About from "./About";
import Footer from "../components/Footer";
import Contact from "./Contact";
import Experiance from "./Experience";
import Start from "./Start";
function FullPage() {
return (
<div className="container mx-auto px-10 max-w-6xl">
<div className="container mx-auto px-2">
<Start />
<About />
<Experiance />
<Footer />
<Contact />
</div>
);
}

78
src/pages/Home.jsx Normal file
View File

@ -0,0 +1,78 @@
import {
Animator,
ScrollContainer,
ScrollPage,
batch,
Fade,
FadeIn,
FadeOut,
Move,
MoveIn,
MoveOut,
Sticky,
StickyIn,
StickyOut,
Zoom,
ZoomIn,
ZoomOut,
} from "react-scroll-motion";
function Home() {
const ZoomInScrollOut = batch(StickyIn(), FadeIn(), ZoomIn());
const FadeUp = batch(Fade(), Move(), Sticky());
return (
<ScrollContainer>
<ScrollPage>
<Animator animation={batch(Fade(), Sticky(), MoveOut(0, 200))}>
<span>Hello, my name is Mariia 😀</span>
</Animator>
<span style={{ fontSize: "40px" }}>
<Animator animation={MoveIn(-1000, 0)}>Hello Guys 👋🏻</Animator>
</span>
</ScrollPage>
<ScrollPage>
<Animator animation={ZoomInScrollOut}>
<span style={{ fontSize: "40px" }}>I'm Frontend developer </span>
</Animator>
</ScrollPage>
<ScrollPage>
<Animator animation={FadeUp}>
<span style={{ fontSize: "40px" }}>I'm Webbdesigner </span>
</Animator>
</ScrollPage>
<ScrollPage>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
}}
>
<span style={{ fontSize: "40px" }}>
<Animator animation={MoveIn(1000, 0)}>Nice to meet you 🙋🏻</Animator>
- I'm Dante Chun -
<Animator animation={MoveOut(1000, 0)}>Good bye 🏻</Animator>
<Animator animation={MoveOut(-1000, 0)}>See you 💛</Animator>
</span>
</div>
</ScrollPage>
<ScrollPage>
<Animator animation={batch(Fade(), Sticky())}>
<span style={{ fontSize: "40px" }}>Done</span>
<br />
<span style={{ fontSize: "30px" }}>
There's FadeAnimation, MoveAnimation, StickyAnimation, ZoomAnimation
</span>
</Animator>
</ScrollPage>
</ScrollContainer>
);
}
export default Home;

View File

@ -1,110 +1,23 @@
import { motion, useAnimation } from "framer-motion";
import { useEffect } from "react";
function Start() {
const greeting = "Hello, my name is Maria.";
const greetingArray = greeting.split("");
const role = "I'm a Frontend Engineer.";
const roleArray = role.split("");
const controls = useAnimation();
const contentControls = useAnimation();
useEffect(() => {
// Start the second animation after the first line completes
const timer1 = setTimeout(() => {
controls.start("visible");
}, greetingArray.length * 100 + 500); // Total time for first line + 500ms delay
// Start the content animation after the second line completes
const timer2 = setTimeout(() => {
contentControls.start("visible");
}, (greetingArray.length + roleArray.length) * 100 + 1000); // Total time for both lines + 1000ms delay
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
};
}, []);
return (
<div className="relative">
<div id="start" className="absolute -top-16 "></div>
<div className=" min-h-screen flex items-center">
<div className="">
<h1 className="text-titlePhone md:text-title md:leading-tight mb-2 font-ht">
{greetingArray.map((letter, index) => (
<motion.span
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.1,
delay: index * 0.1,
}}
>
{letter}
</motion.span>
))}
</h1>
<h2 className="text-subTPhone md:text-subT font-ht">
{roleArray.map((letter, index) => (
<motion.span
key={index}
initial={{ opacity: 0 }}
animate={controls}
variants={{
visible: { opacity: 1 }
}}
transition={{
duration: 0.1,
delay: index * 0.1,
}}
>
{letter}
</motion.span>
))}
</h2>
<motion.div
initial={{ opacity: 0 }}
animate={contentControls}
variants={{
visible: { opacity: 1 }
}}
transition={{
duration: 0.8,
ease: "easeOut"
}}
>
<p className="text-base md:w-1/2 w-full text-left my-4 font-body">
I'm a Stockholm-based web developer with startup experience,
building things with JavaScript, React, and Node.js. I'm also into
3D design and art, and my mini poodle,{" "}
<span className="font-black">Oreo</span>{" "}
<img
src="/img/oreo-logo.png"
alt="oreo_logo"
className="inline h-"
/>{" "}
, is always in my creative space. Let's chat!
</p>
<div>
<a
href="https://www.linkedin.com/in/mariia-shabelnik/"
target="_blank"
className="flex items-center gap-2 hover:text-highlight transition-colors duration-300"
>
<img
src="/img/icons8-linkedin.svg"
alt="LinkedIn icon"
className="inline h-6"
/>
LinkedIn
</a>
</div>
</motion.div>
</div>
<h1 className="text-titlePhone md:text-title md:leading-tight mb-2 font-headline">
Hello, my name is Maria<span className=" text-highlight">.</span>
</h1>
<h2 className="text-subTPhone md:text-subT font-headline">
I'm a<span className=" text-highlight"> Frontend Engineer</span>
</h2>
<p className="text-base">
In my recent journey as a junior frontend engineer, I've gained
valuable experience through involvement in various projects within
startup environments. Passionate about art and design, I've developed
my skills beyond coding. Let's connect and explore opportunities
together! By the way, you might catch a glimpse of my black mini
poodle, Oreo, who's an integral part of my creative space.
</p>
</div>
</div>
);

View File

@ -4,10 +4,8 @@ const experianceList = [
{
id: "46bf76ae-8915-4e5d-ae92-4151be80e75a",
title: "Netzero web",
subtitle: "Lead UI/UX Designer & Frontend Developer",
info: "As the lead UI/UX designer and frontend developer for Net0's sustainability platform, I spearheaded the creation of an intuitive system that seamlessly integrates eco-conscious practices with modern technology. Built with React and TypeScript, the platform features a comprehensive dashboard for tracking carbon emissions, implementing sustainable practices, and generating detailed reports. I collaborated closely with the backend team to ensure smooth integration with the Node.js and PostgreSQL infrastructure, while maintaining a strong focus on user experience and accessibility throughout the development process.",
info: "Discover the intersection of sustainability and technology in my collaboration with Net0. As the creative force behind the UI/UX design, I meticulously crafted a seamless and visually appealing user experience. Bringing this vision to life, I also spearheaded the frontend development, using React to construct an intuitive and efficient system for Net0. Immerse yourself in the synergy of eco-conscious practices and cutting-edge technology as we navigate towards a greener future, one React component at a time.",
img: ["/img/net0_0.png", "/img/net0_1.png", "/img/net0_2.png"],
previewImg: "/img/mockup_net0.png",
tags: [
"React",
"Vite",
@ -21,47 +19,18 @@ const experianceList = [
"Javascript",
],
link: "https://www.net0.se",
sortOrder: 2
},
{
id: "55515a25-deb1-451c-bc7d-006d293f54aa",
title: "Lets fly",
subtitle: "Full Stack Developer",
info: "Developed a comprehensive aviation services platform using Next.js and WordPress, focusing on creating a seamless experience for both service providers and clients. Implemented GraphQL for efficient data management and real-time updates, while utilizing Tailwind CSS for responsive and modern design. The platform features a sophisticated booking system, real-time availability tracking, and integrated payment processing. I worked closely with the client to ensure the WordPress integration met their specific requirements while maintaining high performance and security standards throughout the development process.",
info: "Letsfly is a website aimed at providing a platform for aviation-related services and information. Built on Next.js, a popular React framework, the website leverages its server-side rendering (SSR) capability to enhance performance and user experience. Tailwind was used as the CSS framework to quickly and efficiently create an attractive and responsive design. WordPress, a well-known Content Management System (CMS), was employed as the content management system to administer and publish content on the website. The reason for choosing WordPress was a client request; he was familiar with this system and wanted to be able to modify the content himself. GraphQL was selected as the API layer to enable efficient data management and flexible data queries between the frontend and backend.",
img: ["/img/letsfly_1.png", "/img/letsfly_2.png", "/img/letsfly_3.png"],
tags: ["React", "NextJS", "Wordpress", "GraphQL", "MySQL", "Tailwind"],
link: "https://preview.letsfly.app/",
previewImg: "/img/mockup_net0.png",
sortOrder: 3
},
{
id: 4,
title: "DataTjej",
subtitle: "Board Member & Web Developer",
info: "As a board member of DataTjej, I'm actively involved in shaping the future of this non-profit organization that supports women and non-binary individuals in tech. Currently leading the development of a new website using Next.js and Tailwind CSS, focusing on creating a modern, accessible platform. The project includes automating connections to various solutions like our podcast and event management systems, streamlining our digital presence and member engagement.",
img: ["/images/datatjej-placeholder.jpg"],
previewImg: "/images/datatjej-placeholder.jpg",
tags: ["Board Member", "Next.js", "Tailwind CSS", "Prisma","TypeScript", "Web Development", "Automation"],
link: "https://datatjej.se",
sortOrder: 1
},
{
id: 5,
title: "DataTjej",
subtitle: "IT Support & User Management",
info: "As part of the DataTjej board, I manage user access and IT support for our growing community of over 6000 members. I handle user permissions, group management, and ensure secure access to our various platforms. This includes setting up and maintaining appropriate access levels for different user groups, troubleshooting technical issues, and implementing security best practices. I also work on automating user management processes and maintaining our IT infrastructure using Google Cloud services to support our community's needs.",
img: ["/images/datatjej-it-placeholder.jpg"],
previewImg: "/images/datatjej-it-placeholder.jpg",
tags: ["User Management", "IT Support", "Access Control", "Security", "Automation", "Community Management", "Google Cloud"],
link: "https://datatjej.se",
sortOrder: 4
}
];
// Sort the list by sortOrder
const sortedExperianceList = [...experianceList].sort((a, b) => a.sortOrder - b.sortOrder);
export const projectsAtom = atom({ key: "projects", default: sortedExperianceList });
export const projectsAtom = atom({ key: "projects", default: experianceList });
export const skillsAtom = atom({
key: "skills",
default: {
@ -85,11 +54,10 @@ export const skillsAtom = atom({
"MySQL",
"Docker",
"Visual Studio Code",
"Rapid API",
"Postman API",
"TablePlus",
"Trello",
"Figma",
"Blender",
],
},
});

View File

@ -3,14 +3,9 @@ export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
fontFamily: {
//logo font
logo: ["Sixtyfour", "system-ui"],
//headline-title text
ht: ["Sora", "system-ui"],
//body text
body: ["Jura", "system-ui"],
//nav
tags: ["Raleway", "system-ui"],
sans: ["Ubuntu Mono", "system-ui"],
headline: ["Tektur", "system-ui"],
tags: ["Ubuntu Mono", "system-ui"],
},
fontSize: {
title: ["5rem", { lineHeight: "5rem", fontWeight: "900" }],
@ -21,7 +16,7 @@ export default {
subTMiniPhone: ["1.2rem", { lineHeight: "1.2rem", fontWeight: "800" }],
tags: ["0.8rem", { fontWeight: "400" }],
sm: "0.8rem",
base: "1.2rem",
base: "1rem",
xl: "1.25rem",
"2xl": "1.563rem",
"3xl": "1.953rem",
@ -30,24 +25,20 @@ export default {
},
extend: {
colors: {
neon: "#caebfa",
bgColor: "#F0F0F3",
highlight: "#D2D2D2",
neon: "#ccff00",
bgColor: "#031417",
highlight: "#ccff00",
},
//20px 20px 60px #bebebe, -20px -20px 60px #ffffff;
boxShadow: {
box: "8px 8px 16px #cacaca,-8px -8px 16px #f6f6f6",
// box: "rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset;",
},
dropShadow: {
light: "0 0 5px theme('colors.sky.100')",
gray: "0 0 9px #8c92a8",
doublelight: ["0 0 5px #96989a", "0 0 15px #a0d9fa"],
//doublelight:
light: "0 0 5px theme('colors.indigo.800')",
yellow: "0 0 9px theme('colors.lime.400')",
doublelight: [
"0 0 5px theme('colors.lime.400')",
"0 0 15px theme('colors.yellow.400')",
],
active: [
"0 0 5px theme('colors.sky.300')",
"0 0 15px theme('colors.sky.100')",
"0 0 5px theme('colors.lime.400')",
"0 0 15px theme('colors.indigo.950')",
],
},
},