mirror of
https://github.com/BreizhHardware/portfolio.git
synced 2026-01-18 16:37:22 +01:00
feat(experience): Add proper experience declaration
TODO: Translation of experience, Github statistiques and contact form
This commit is contained in:
2444
package-lock.json
generated
2444
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.12",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"@testing-library/jest-dom": "^6.6.4",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
@@ -20,6 +22,7 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"react-particles": "^2.12.2",
|
||||
"sass": "^1.86.0",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"tsparticles-slim": "^2.12.0",
|
||||
"typescript": "^5.8.3",
|
||||
"web-vitals": "^5.1.0",
|
||||
@@ -29,7 +32,7 @@
|
||||
"start": "vite",
|
||||
"build": "npx tsc && vite build",
|
||||
"serve": "vite preview",
|
||||
"build:css": "tailwindcss build -i src/index.css -o src/output.css",
|
||||
"build:css": "npx @tailwindcss/cli -i src/index.css -o src/output.css",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"test": "browserslist && cypress run"
|
||||
@@ -56,6 +59,7 @@
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@simonsmith/cypress-image-snapshot": "^10.0.2",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"prettier": "^3.6.2",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
|
||||
4495
pnpm-lock.yaml
generated
Normal file
4495
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@ import CV from "./components/CV.tsx";
|
||||
import Menu from "./components/Menu.tsx";
|
||||
import LoadingScreen from "./components/LoadingScreen.tsx";
|
||||
import ParticlesBackground from "./components/ParticlesBackground.tsx";
|
||||
import HomelabSection from "./components/HomelabSection.tsx";
|
||||
import GitHubStatsSection from "./components/GitHubStatsSection.tsx";
|
||||
import ContactSection from "./components/ContactSection.tsx";
|
||||
import TimelineSection from "./components/TimelineSection.tsx";
|
||||
@@ -71,9 +70,7 @@ function App() {
|
||||
<About />
|
||||
<Skills skills={data.skills} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="experience" />
|
||||
<TimelineSection />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="homelab" />
|
||||
<HomelabSection />
|
||||
<TimelineSection experience={data.experience} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="github" />
|
||||
<GitHubStatsSection />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="projects" />
|
||||
|
||||
@@ -61,6 +61,74 @@ const Felix = {
|
||||
skillLevel: 80,
|
||||
}
|
||||
],
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'work',
|
||||
title: 'Développeur - Alternant',
|
||||
organization: 'Horoquartz',
|
||||
location: 'Saint-Herblain, France',
|
||||
startDate: '2024',
|
||||
endDate: 'Present',
|
||||
current: true,
|
||||
description: [
|
||||
'Développement d\'un système de mise à jour pour les produits Horoquartz',
|
||||
'Conception et implémentation d\'APIs REST avec Node.js et Go',
|
||||
'Gestion des bases de données PostgreSQL',
|
||||
'Déploiement avec Docker et Kubernetes sur Azure'
|
||||
],
|
||||
technologies: ['Node.js', 'Go', 'PostgreSQL', 'Docker', 'Kubernetes', 'Azure']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'education',
|
||||
title: 'ISEN Nantes - Cycle Ingénieur',
|
||||
organization: 'Institut Supérieur de l\'Électronique et du Numérique',
|
||||
location: 'Nantes, France',
|
||||
startDate: '2022',
|
||||
endDate: '2027',
|
||||
current: true,
|
||||
description: [
|
||||
'Formation d\'ingénieur en informatique et nouvelles technologies',
|
||||
'Spécialisation en cybersécurité',
|
||||
'Projets en équipe et gestion de projet'
|
||||
],
|
||||
technologies: ['C', 'C++', 'Python', 'Linux', 'Réseaux']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'work',
|
||||
title: 'Support Technique & Admin VPS',
|
||||
organization: 'MercuryCloud',
|
||||
location: 'Remote',
|
||||
startDate: '2023',
|
||||
endDate: 'Present',
|
||||
current: true,
|
||||
description: [
|
||||
'Support technique pour serveurs de jeu et VPS',
|
||||
'Administration des services CPanel et Plesk',
|
||||
'Gestion du système WHMCS pour la facturation',
|
||||
'Virtualisation et maintenance des serveurs Linux'
|
||||
],
|
||||
technologies: ['Linux', 'Virtualisation', 'CPanel', 'Plesk', 'WHMCS']
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'achievement',
|
||||
title: 'Projet Robot - Coupe de France de Robotique',
|
||||
organization: 'Club Modelec ISEN',
|
||||
location: 'Nantes, France',
|
||||
startDate: '2023',
|
||||
endDate: '2023',
|
||||
description: [
|
||||
'Développement du système de contrôle du robot',
|
||||
'Interface utilisateur avec QT',
|
||||
'Déploiement sur Raspberry Pi',
|
||||
'Travail en équipe multidisciplinaire'
|
||||
],
|
||||
technologies: ['C++', 'QT', 'Raspberry Pi', 'Linux']
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
title: "Front end starter",
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
// @ts-ignore
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import ProjectCard from "./ProjectCard.tsx";
|
||||
import ProjectCard from "./ProjectCard";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
// @ts-ignore
|
||||
function Projects({projects}) {
|
||||
type Project = {
|
||||
title: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
link: string;
|
||||
};
|
||||
|
||||
interface ProjectsProps {
|
||||
projects: Project[];
|
||||
}
|
||||
|
||||
function Projects({projects}: ProjectsProps) {
|
||||
const { t } = useTranslation();
|
||||
return(
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-ignore
|
||||
import React, { useState } from 'react';
|
||||
import { FaExternalLinkAlt, FaTimes, FaGithub, FaCalendarAlt, FaCode} from "react-icons/fa";
|
||||
import React from 'react';
|
||||
import { FaExternalLinkAlt} from "react-icons/fa";
|
||||
import GitHubButton from 'react-github-btn';
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
@@ -8,194 +8,39 @@ interface Project {
|
||||
title: string;
|
||||
link: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
const ProjectModal: React.FC<{ project: Project; isOpen: boolean; onClose: () => void }> = ({
|
||||
project,
|
||||
isOpen,
|
||||
onClose
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
||||
<div
|
||||
className="bg-white dark:bg-gray-800 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto animate-fadeInUp"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-2xl font-bold text-gray-800 dark:text-gray-200">
|
||||
{project.title}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
||||
>
|
||||
<FaTimes size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Description détaillée */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center">
|
||||
<FaCode className="mr-2" />
|
||||
Description du projet
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
{t(`projects.${project.title}.description`)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Technologies */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3">
|
||||
Technologies utilisées
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full text-sm animate-slideInLeft"
|
||||
style={{ animationDelay: `${index * 100}ms` }}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Détails spécifiques au projet */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-2">
|
||||
Objectifs du projet
|
||||
</h4>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>• Développement d'une solution complète</li>
|
||||
<li>• Application des meilleures pratiques</li>
|
||||
<li>• Travail en équipe et gestion de projet</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||
<h4 className="font-semibold text-gray-800 dark:text-gray-200 mb-2">
|
||||
Compétences acquises
|
||||
</h4>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>• Architecture et conception</li>
|
||||
<li>• Développement full-stack</li>
|
||||
<li>• Tests et déploiement</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-wrap gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<a
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaExternalLinkAlt />
|
||||
<span>Voir le projet</span>
|
||||
</a>
|
||||
|
||||
{(project.title !== "MercuryCloud" && project.title !== "Alternance Horoquartz") && (
|
||||
<div className="flex space-x-2">
|
||||
<GitHubButton
|
||||
href={project.link}
|
||||
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||
data-icon="octicon-star"
|
||||
data-size="large"
|
||||
data-show-count="true"
|
||||
>
|
||||
Star
|
||||
</GitHubButton>
|
||||
<GitHubButton
|
||||
href={project.link + "/fork"}
|
||||
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||
data-icon="octicon-repo-forked"
|
||||
data-size="large"
|
||||
data-show-count="true"
|
||||
>
|
||||
Fork
|
||||
</GitHubButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectCard = ({ project }: ProjectCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { title, link, tags } = project;
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="group w-full sm:w-5/12 m-4 mx-auto p-6 rounded-xl border-2 border-gray-300 dark:border-gray-700 card-hover cursor-pointer transition-all duration-300"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-bold dark:text-gray-200 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||
{title}
|
||||
<div className="group w-full sm:w-5/12 m-4 mx-auto p-6 rounded-xl border-2 border-gray-300 dark:border-gray-700">
|
||||
<a href={link}>
|
||||
<h1 className="text-xl text-center font-bold dark:text-gray-200">
|
||||
{title}{" "}
|
||||
<FaExternalLinkAlt className="inline align-baseline" />
|
||||
</h1>
|
||||
<FaExternalLinkAlt className="text-gray-400 group-hover:text-blue-500 transition-colors" />
|
||||
</div>
|
||||
|
||||
</a>
|
||||
<hr className="my-4" />
|
||||
|
||||
<p className="dark:text-gray-300 text-sm line-clamp-3 mb-4">
|
||||
{t(`projects.${project.title}.description`)}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{project.tags.slice(0, 3).map((tag, index) => (
|
||||
<span key={index} className="px-3 py-1 text-xs border-2 rounded-full dark:text-gray-300 bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600">
|
||||
{tag}
|
||||
</span>
|
||||
<p className="dark:text-gray-300">{t(`projects.${project.title}.description`)}</p>
|
||||
<div className="mt-4 mb-8 flex flex-wrap justify-center items-center gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<div className="px-4 py-1 border-2 rounded-full dark:text-gray-300">{tag}</div>
|
||||
))}
|
||||
{project.tags.length > 3 && (
|
||||
<span className="px-3 py-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
+{project.tags.length - 3} autres
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-blue-600 dark:text-blue-400 font-medium">
|
||||
Voir les détails →
|
||||
</span>
|
||||
|
||||
{(title !== "MercuryCloud" && title !== "Alternance Horoquartz") && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<FaGithub className="text-gray-600 dark:text-gray-400" />
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">Open Source</span>
|
||||
<div className="w-full text-center">
|
||||
<GitHubButton href={link} data-color-scheme="no-preference: light; light: light; dark: dark;" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star ntkme/github-buttons on GitHub">Star</GitHubButton>
|
||||
{" "}
|
||||
<GitHubButton href={link + "/fork"} data-color-scheme="no-preference: light; light: light; dark: dark;" data-icon="octicon-repo-forked" data-size="large" data-show-count="true" aria-label="Fork ntkme/github-buttons on GitHub">Fork</GitHubButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProjectModal
|
||||
project={project}
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ const SkillLevel: React.FC<SkillLevelProps> = ({ level, skillName }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<div className="mt-2 relative">
|
||||
<div
|
||||
ref={badgeRef}
|
||||
className="inline-block px-3 py-1 rounded-full text-sm font-medium bg-gray-200 text-gray-800 border cursor-pointer dark:bg-gray-800 dark:text-gray-200 dark:border-gray-700"
|
||||
@@ -76,11 +76,7 @@ const SkillLevel: React.FC<SkillLevelProps> = ({ level, skillName }) => {
|
||||
{showTooltip && (
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className="fixed z-50 w-64 p-3 text-sm bg-gray-200 rounded-xl border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg dark:bg-gray-800 dark:text-gray-200 transition-shadow"
|
||||
style={{
|
||||
top: `${tooltipPosition.top}px`,
|
||||
left: `${tooltipPosition.left}px`,
|
||||
}}
|
||||
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 z-50 w-64 p-3 text-sm bg-gray-200 rounded-xl border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg dark:bg-gray-800 dark:text-gray-200 transition-shadow"
|
||||
>
|
||||
<p className="font-bold mb-1">{t(`skills.examples.${skillName}.title`)}</p>
|
||||
<p>{t(`skills.examples.${skillName}.description`)}</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaBriefcase, FaGraduationCap, FaCalendarAlt, FaMapMarkerAlt } from 'react-icons/fa';
|
||||
|
||||
interface TimelineItem {
|
||||
@@ -14,90 +15,8 @@ interface TimelineItem {
|
||||
current?: boolean;
|
||||
}
|
||||
|
||||
const TimelineSection: React.FC = () => {
|
||||
const timelineData: TimelineItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'work',
|
||||
title: 'Développeur - Alternant',
|
||||
organization: 'Horoquartz',
|
||||
location: 'Cesson-Sévigné, France',
|
||||
startDate: '2024',
|
||||
endDate: 'Présent',
|
||||
current: true,
|
||||
description: [
|
||||
'Développement d\'un système de mise à jour pour les produits Horoquartz',
|
||||
'Conception et implémentation d\'APIs REST avec Node.js et Go',
|
||||
'Gestion des bases de données PostgreSQL',
|
||||
'Déploiement avec Docker et Kubernetes sur Azure'
|
||||
],
|
||||
technologies: ['Node.js', 'Go', 'PostgreSQL', 'Docker', 'Kubernetes', 'Azure']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'education',
|
||||
title: 'ISEN Nantes - Cycle Ingénieur',
|
||||
organization: 'Institut Supérieur de l\'Électronique et du Numérique',
|
||||
location: 'Nantes, France',
|
||||
startDate: '2022',
|
||||
endDate: '2025',
|
||||
current: true,
|
||||
description: [
|
||||
'Formation d\'ingénieur en informatique et nouvelles technologies',
|
||||
'Spécialisation en développement logiciel et administration système',
|
||||
'Projets en équipe et gestion de projet'
|
||||
],
|
||||
technologies: ['C', 'C++', 'Python', 'Linux', 'Réseaux']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'work',
|
||||
title: 'Support Technique & Admin VPS',
|
||||
organization: 'MercuryCloud',
|
||||
location: 'Remote',
|
||||
startDate: '2021',
|
||||
endDate: '2024',
|
||||
description: [
|
||||
'Support technique pour serveurs de jeu et VPS',
|
||||
'Administration des services CPanel et Plesk',
|
||||
'Gestion du système WHMCS pour la facturation',
|
||||
'Virtualisation et maintenance des serveurs Linux'
|
||||
],
|
||||
technologies: ['Linux', 'Virtualisation', 'CPanel', 'Plesk', 'WHMCS']
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'achievement',
|
||||
title: 'Projet Robot - Coupe de France de Robotique',
|
||||
organization: 'Club Modelec ISEN',
|
||||
location: 'Nantes, France',
|
||||
startDate: '2023',
|
||||
endDate: '2023',
|
||||
description: [
|
||||
'Développement du système de contrôle du robot',
|
||||
'Interface utilisateur avec QT',
|
||||
'Déploiement sur Raspberry Pi',
|
||||
'Travail en équipe multidisciplinaire'
|
||||
],
|
||||
technologies: ['C++', 'QT', 'Raspberry Pi', 'Linux']
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'education',
|
||||
title: 'Classes Préparatoires Intégrées',
|
||||
organization: 'ISEN Nantes',
|
||||
location: 'Nantes, France',
|
||||
startDate: '2020',
|
||||
endDate: '2022',
|
||||
description: [
|
||||
'Mathématiques, Physique, Informatique',
|
||||
'Bases de la programmation en C',
|
||||
'Introduction aux systèmes embarqués'
|
||||
],
|
||||
technologies: ['C', 'Mathématiques', 'Physique', 'Électronique']
|
||||
}
|
||||
];
|
||||
|
||||
const TimelineSection: React.FC<{ experience: TimelineItem[] }> = ({ experience }) => {
|
||||
const { t } = useTranslation();
|
||||
const getIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'work':
|
||||
@@ -140,7 +59,7 @@ const TimelineSection: React.FC = () => {
|
||||
<div className="absolute left-4 md:left-8 top-0 bottom-0 w-0.5 bg-gray-300 dark:bg-gray-600"></div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{timelineData.map((item, index) => (
|
||||
{experience.map((item, index) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`relative pl-12 md:pl-20 animate-fadeInUp`}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import "tailwindcss";
|
||||
|
||||
135
src/other.css
135
src/other.css
@@ -1,137 +1,3 @@
|
||||
/* Animations personnalisées */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||
33% { transform: translateY(-10px) rotate(120deg); }
|
||||
66% { transform: translateY(10px) rotate(240deg); }
|
||||
}
|
||||
|
||||
@keyframes typewriter {
|
||||
from { width: 0; }
|
||||
to { width: 100%; }
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -1000px 0; }
|
||||
100% { background-position: 1000px 0; }
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-fadeInUp {
|
||||
animation: fadeInUp 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-slideInLeft {
|
||||
animation: slideInLeft 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-slideInRight {
|
||||
animation: slideInRight 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-shimmer {
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||
background-size: 1000px 100%;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Smooth scroll */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Loading transition */
|
||||
.loading-exit {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Hover effects */
|
||||
.card-hover {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dark .card-hover:hover {
|
||||
box-shadow: 0 20px 25px -5px rgba(255, 255, 255, 0.1), 0 10px 10px -5px rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* Progress bar animation */
|
||||
.progress-fill {
|
||||
transition: width 1.5s ease-in-out;
|
||||
background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Modal backdrop blur */
|
||||
.modal-backdrop {
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Navigation responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.menu-hidden ul {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.menu-hidden li a {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.menu-hidden {
|
||||
display: none;
|
||||
@@ -146,4 +12,5 @@ html {
|
||||
margin-left: 10em;
|
||||
margin-right: 10em;
|
||||
}
|
||||
|
||||
}
|
||||
2166
src/output.css
2166
src/output.css
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,13 @@ import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
viteTsconfigPaths(),
|
||||
svgr({
|
||||
include: '**/*.svg?react',
|
||||
|
||||
Reference in New Issue
Block a user