Bienvenue sur mon portfolio !
Ce projet met en avant mes compétences, mes projets et mon parcours de façon moderne, dynamique et responsive.
Le design adopte le style Arena, un concept d'éditorial premium inspiré de l'univers sportif et de la culture japonaise, associant une esthétique minimaliste sombre, des typographies fortes et des effets de verre soignés (glassmorphism).
- 🌍 Multilingue : Support complet et réactif du Français, Anglais et Turc (contenus, libellés d'interface, bandeau défilant et libellés d'accessibilité). La langue choisie est mémorisée.
- 🌓 Mode sombre/clair : Transition de thème fluide, choix mémorisé et appliqué avant le premier affichage (aucun flash du thème sombre).
- 📱 Design responsive : Testé du 320 px à l'ultrawide 3440 px, en portrait et en paysage, sur Chromium et WebKit.
- ♿ Accessibilité : Cibles tactiles d'au moins 44 × 44 px,
aria-labeltraduits dans les trois langues, respect deprefers-reduced-motion. - ✨ Animations fluides : Révélation progressive et élégante des sections au défilement (reveal-on-scroll) via un plugin sur mesure.
- 🎨 Aesthetics Arena : Fiche joueur en en-tête, bandeau de défilement style live-ticker, et cartes de saison détaillées pour les projets.
- ⚡ Performances optimisées : Chargement ultra-rapide et utilisation d'images au format WebP optimisées avec
@nuxt/image. - 🔍 SEO : Balises Open Graph et Twitter Card, lien canonique,
sitemap.xmletrobots.txt. Le titre et les descriptions suivent la langue active dans le navigateur ; le HTML prérendu, lu par les réseaux sociaux, est en français. - 🚫 Page 404 dédiée : Page « Hors du terrain » trilingue, prérendue dans
404.htmlpour GitHub Pages.
- Nuxt 3 - Framework Vue.js pour applications web modernes (SSR & Génération Statique)
- Vue.js 3 - Composition API & Reactivité
- TypeScript - Typage robuste et sécurisé
- Vanilla CSS (Variables CSS & Grid/Flexbox) - Design system sur mesure léger et performant (sans framework tiers type Tailwind)
- @nuxt/image - Optimisation automatique des images
-
Cloner le dépôt :
git clone https://github.com/Deniz09OK/Portfolio.git cd Portfolio -
Installer les dépendances :
npm install
-
Lancer le projet en mode développement :
npm run dev
-
Accéder au site :
Ouvrez votre navigateur sur http://localhost:3000.
npm run dev: Lance le serveur de développement local.npm run build: Compile le projet pour la production.npm run generate: Génère le site entièrement statique dans le dossier.output/public.npm run preview: Prévisualise localement le site généré.npm run typecheck: Vérifie les types avecvue-tsc(lance d'abordnuxt prepare).npm run test: Lance les tests Playwright sur le site généré (lancernpm run generateavant).npm run test:unit: Lance les tests unitaires descripts/htb-compare.mjs(node --test, sans navigateur ni génération).
Les tests Playwright (tests/portfolio.spec.ts) tournent sur le site statique généré, servi comme sur GitHub Pages par tests/serve.mjs (404.html compris), sur trois profils : Chromium desktop, Chromium mobile (Pixel 7) et WebKit mobile (iPhone 15). Ils vérifient :
- le rendu des trois langues, sans erreur JavaScript ;
- que chaque projet affiche un seul statut (lien vers le code, « Confidentiel » ou « En développement ») ;
- la navigation vers chaque section, desktop et burger mobile ;
- l'ouverture et la fermeture des modales (clic et Échap) ;
- l'absence de défilement horizontal à 320 px dans les trois langues ;
- des cibles tactiles d'au moins 44 × 44 px sur mobile ;
- l'application du thème clair avant le chargement de l'application, et sa mémorisation ;
- une vraie page 404 pour les URL inconnues.
npm run generate
npx playwright install chromium webkit # une seule fois
npm testPortfolio/
├── .github/workflows/ # Workflows d'automatisation
│ ├── deploy.yml # CI/CD : typecheck, génération, tests Playwright, déploiement sur GitHub Pages, puis tag de version
│ └── htb-sync.yml # Synchro des stats Hack The Box (public/data/htb.json), toutes les 12 h, uniquement si une donnée affichée change
├── assets/css/ # Design System & Stylesheets par composants
│ ├── arena.css # Point d'entrée important toutes les feuilles de style
│ ├── fonts.css # Polices auto-hébergées (@font-face vers public/fonts/)
│ ├── base.css # Réinitialisation globale et règles HTML par défaut
│ ├── variables.css # Thèmes de couleurs (Variables HSL) et polices globales
│ ├── utils.css # Utilitaires globaux et animations communes
│ ├── topbar.css # Styles de la barre supérieure (TopBar.vue)
│ ├── ticker.css # Styles du bandeau défilant (LiveTicker.vue)
│ ├── hero.css # Styles de la fiche de présentation (HeroCard.vue)
│ ├── matches.css # Styles de la section des projets (MatchesSection.vue)
│ ├── career.css # Styles de la timeline académique/professionnelle (CareerSection.vue)
│ ├── drills.css # Styles de la section des compétences (DrillsSection.vue)
│ ├── htb.css # Styles des blocs Hack The Box et TryHackMe (HtbStats.vue & ThmStats.vue)
│ ├── offcourt.css # Styles de la section hors-clavier et modales (OffCourtSection.vue & OffModal.vue)
│ ├── languages.css # Styles de la section des langues (LanguagesSection.vue)
│ └── contact.css # Styles de la section de contact (ContactSection.vue)
├── components/ # Composants Vue du portfolio
│ ├── TopBar.vue # En-tête de page (changement de thème & sélecteur de langue)
│ ├── LiveTicker.vue # Bandeau de défilement dynamique (style bandeau sportif)
│ ├── HeroCard.vue # Fiche joueur de présentation principale
│ ├── MatchesSection.vue # Section projets ("Card de la saison")
│ ├── CareerSection.vue # Expériences et Études ("Saisons jouées" sous forme de timeline)
│ ├── DrillsSection.vue # Compétences techniques ("Entraînement quotidien")
│ ├── HtbStats.vue # Statistiques Hack The Box (lit public/data/htb.json)
│ ├── ThmStats.vue # Statistiques TryHackMe (lit public/data/thm.json)
│ ├── OffCourtSection.vue # Activités annexes ("Hors du clavier")
│ ├── OffModal.vue # Composant modale pour le détail des activités annexes
│ ├── LanguagesSection.vue # Indicateurs de maîtrise linguistique
│ └── ContactSection.vue # Liens de contact ("Sifflet final")
├── composables/ # Logique réutilisable
│ ├── useLang.ts # Gestion de l'internationalisation (FR / EN / TR)
│ ├── useTheme.ts # Gestionnaire de thème sombre/clair
│ └── useThm.ts # Chargement et validation de public/data/thm.json
├── data/ # Données de l'application
│ └── portfolio.ts # Données structurées et traduites du portfolio
├── types/ # Définitions TypeScript
│ └── portfolio.ts # Interfaces décrivant la structure du contenu du portfolio
├── plugins/ # Plugins personnalisés de Nuxt
│ ├── reveal.client.ts # Gestion des animations d'entrée au scroll
│ └── reveal.server.ts # Compatibilité SSR pour les animations
├── server/plugins/ # Plugins Nitro
│ └── csp.ts # Content-Security-Policy en balise <meta> (hachage SHA-256 des scripts inline)
├── scripts/ # Scripts Node lancés par la CI
│ ├── fetch-htb.mjs # Récupère les stats Hack The Box et écrit public/data/htb.json
│ ├── htb-compare.mjs # Décide si la synchro mérite d'être écrite (ignore les champs volatils non affichés)
│ └── fetch-thm.mjs # Récupère les stats TryHackMe et écrit public/data/thm.json
├── tests/ # Tests de bout en bout
│ ├── portfolio.spec.ts # Suite Playwright
│ ├── htb.spec.ts # Pagination, filtres et swipe de la section Hack The Box
│ ├── htb-compare.unit.mjs # Tests unitaires de la comparaison de la synchro HTB (node:test, sans navigateur)
│ └── serve.mjs # Serveur statique reproduisant GitHub Pages (404.html)
├── public/ # Ressources statiques
│ ├── data/ # Données synchronisées automatiquement
│ │ ├── htb.json # Stats Hack The Box
│ │ └── thm.json # Stats TryHackMe
│ ├── fonts/ # Polices auto-hébergées (woff2)
│ ├── portrait.jpg # Portrait utilisé en hero et dans les balises Open Graph
│ ├── cv-deniz-ok-fr.pdf # CV téléchargeable (FR)
│ ├── cv-deniz-ok-en.pdf # CV téléchargeable (EN/TR)
│ ├── favicon.ico # Favicon de repli pour les navigateurs qui le demandent par défaut
│ ├── sitemap.xml # Plan du site pour les moteurs de recherche
│ └── robots.txt # Autorisation d'indexation et lien vers le sitemap
├── app.vue # Fiche racine englobant les composants
├── error.vue # Page 404 trilingue, prérendue en 404.html pour GitHub Pages
├── nuxt.config.ts # Fichier de configuration de Nuxt (BaseURL, SEO, prérendu de la 404)
├── package.json # Dépendances et scripts npm
├── playwright.config.ts # Configuration Playwright (profils desktop et mobile)
└── tsconfig.json # Fichier de configuration TypeScript
Le projet est configuré pour se déployer automatiquement sur GitHub Pages à l'aide de GitHub Actions, et servi sur le domaine personnalisé portfolio-deniz.me.
Chaque commit poussé sur la branche main déclenche le workflow défini dans .github/workflows/deploy.yml qui :
- Installe les dépendances.
- Vérifie les types avec
npm run typecheck. Une nouvelle erreur de types fait échouer la CI. - Exécute
npm run generatepour compiler le site statique. - Lance les tests Playwright sur ce build. Si un test échoue, rien n'est déployé et le rapport est disponible dans les artefacts du workflow.
- Déploie sur GitHub Pages le dossier
.output/publicqui vient d'être testé. - Crée un tag de version sur le commit déployé (job
tag, voir ci-dessous).
Sur une pull request, seuls la vérification des types, la génération et les tests sont exécutés.
Après chaque déploiement réussi sur main, le job tag de deploy.yml crée et pousse un tag annoté vMAJEUR.MINEUR.PATCH sur le commit déployé. La version est calculée à partir des messages Conventional Commits depuis le dernier tag de ce format : feat donne une version mineure, fix et perf un patch, type!: ou BREAKING CHANGE une version majeure. Les autres types (docs, chore, ci, etc.) ne créent pas de tag, pas plus que les commits automatiques chore(htb) de la synchro. Un tag existant n'est jamais déplacé et seul le tag est poussé (aucun commit).
La page 404 est générée à partir de error.vue : Nuxt produit toujours /404.html comme une coquille vide remplie en JavaScript, la route /404 est donc rendue côté serveur puis écrite dans 404.html via le hook Nitro prerender:generate (voir nuxt.config.ts).
La section « Hack The Box » lit public/data/htb.json, généré par scripts/fetch-htb.mjs (données publiques uniquement : rang, points, boxes résolues, saison en cours). Le workflow .github/workflows/htb-sync.yml l'exécute toutes les 12 h, à la demande, et à chaque push sur main, commite le JSON s'il a changé puis relance deploy.yml.
- Secret requis :
API_TOKEN(Settings → Secrets and variables → Actions). En local, la même variable peut être mise dans un.env(ignoré par git). API_TOKENreste un secret de CI : seulscripts/fetch-htb.mjsle lit, jamais l'application Nuxt. Ne jamais le déclarer dansruntimeConfig, ni le préfixerNUXT_PUBLIC_ouVITE_(ces valeurs sont copiées dans le JavaScript public_nuxt/*.js), ni le passer à l'étapenpm run generatededeploy.yml. Le navigateur ne reçoit quehtb.json, déjà formaté.node scripts/fetch-htb.mjsmet à jour le JSON ;node scripts/fetch-htb.mjs --probeteste les endpoints (statuts et clés uniquement, jamais le jeton).- En cas d'erreur API (401, 403, 429, timeout), l'ancien JSON est conservé.
- Le JSON n'est réécrit (et donc commité, puis redéployé) que si une donnée affichée change : nouvelle box, flags, points ou ligue de saison, bloods, machines root/user. Les champs volatils non affichés (
season.rank,season.totalPlayers,profile.globalRanking,profile.rankProgress,profile.points,profile.respects) sont ignorés par la comparaison (scripts/htb-compare.mjs) : s'ils sont seuls à changer, le fichier est conservé tel quel,updatedAtcompris (« aucune modification significative »). Quand le fichier est réécrit, tous les champs sont à jour. Si le composantHtbStats.vueaffiche un jour l'un de ces champs, le retirer deVOLATILE_FIELDS. - Affichage (
components/HtbStats.vue) : une bande de stats (machines root, répartition difficulté et système, dernière machine, saison), les boxes non rootées dans « En cours », puis les boxes rootées paginées par 5 (flèches, indicateur « 1–5 / N », swipe sur mobile) avec deux filtres combinables (système, niveau). Les puces de filtre et les barres de répartition sont calculées depuishtb.json: une nouvelle difficulté ou un nouveau système apparaît sans modifier le code.
La section « TryHackMe » lit public/data/thm.json, généré par scripts/fetch-thm.mjs à partir de l'API publique de TryHackMe : rooms complétées, Top %, rang, niveau, badges et points. La série, la ligue et l'avatar ne sont jamais conservés.
La mise à jour est manuelle : tryhackme.com bloque (HTTP 429) les requêtes automatisées serveur-à-serveur, depuis GitHub Actions comme via un relais Cloudflare Worker (probable détection d'une empreinte non-navigateur). Le workflow htb-sync.yml ne synchronise donc plus que Hack The Box. De temps en temps, depuis ton poste :
node scripts/fetch-thm.mjs
git add public/data/thm.json && git commit -m "chore(thm): mise à jour des stats TryHackMe"- Aucun secret ni variable d'environnement requis.
- La réponse est validée (types et bornes) avant écriture. Panne passagère (réseau, 403, 429, 5xx) : jusqu'à 3 tentatives avec backoff, puis l'ancien JSON est conservé. Réponse invalide : l'ancien JSON est conservé et le script échoue.
Réalisé avec 💙 par Deniz OK
- 🌐 Portfolio Live
- 💻 GitHub
- 🦊 GitLab