Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a9f716f62 |
@ -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 }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
12
Dockerfile
@ -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 .
|
||||
|
||||
|
||||
30
index.html
@ -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
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 116 KiB |
@ -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 |
|
Before Width: | Height: | Size: 375 KiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 641 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 546 KiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 980 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.0 KiB |
11
src/App.jsx
@ -1,18 +1,20 @@
|
||||
//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">
|
||||
<main className=" min-h-screen">
|
||||
<Header />
|
||||
<ScrollToAnchor />
|
||||
<Routes>
|
||||
@ -21,6 +23,7 @@ function App() {
|
||||
<Route path="/contact" element={<Contact />} />*/}
|
||||
<Route path="/experience/:id" element={<ExperianceDetail />} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
</main>
|
||||
</RecoilRoot>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,17 +13,17 @@ function Header() {
|
||||
}, [isOpen]);
|
||||
|
||||
const menu = [
|
||||
{ link: "/#about", title: ".about( )" },
|
||||
{ link: "/#experience", title: ".projects( )" },
|
||||
{ link: "/#contact", title: ".contact( )" },
|
||||
{ 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="h-[calc(100vh-20rem)] flex flex-col justify-center">
|
||||
<ul role="menu" aria-label="Mobile menu items">
|
||||
{menuUI}
|
||||
</ul>
|
||||
<div className={overlayMenu.join(" ")}>
|
||||
<div className=" h-[calc(100vh-20rem)] flex flex-col justify-center">
|
||||
<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>
|
||||
<div className="flex-none hidden md:block ">
|
||||
<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>
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
body {
|
||||
background-color: #f0f0f3;
|
||||
background-color: #031417;
|
||||
}
|
||||
::selection {
|
||||
background: #caebfa;
|
||||
background: #7d9c00b1;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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,20 +65,11 @@ 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="drop-shadow-doublelight hover:drop-shadow-light my-2 text-end">
|
||||
<div className="flex-1 text-base">
|
||||
{myProject.info}
|
||||
<div className=" drop-shadow-doublelight hover:drop-shadow-light my-2 text-end">
|
||||
{linkUI}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
|
||||
40
src/store.js
@ -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",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@ -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')",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||