Migration vers architecture Svelte + Kirby inspirée de design-to-pack
- Mise en place de Svelte 4 avec Vite pour le frontend (SPA)
- Simplification des templates PHP (header/footer minimalistes)
- Création de templates JSON pour API (home, about, expertise, portfolio, jouer, game, blog, article, project)
- Ajout d'un controller de site pour définir genericData globalement
- Structure des stores Svelte (page, navigation, locale, site)
- Router avec navaid pour navigation SPA et interception des liens
- Composants layout (Header, Footer, Cursor) et vues de base
- Build Vite vers assets/dist/ (index.js/css)
- Header PHP détecte assets/dist pour basculer dev/prod
Architecture fonctionnelle de base établie, à améliorer et compléter.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:30:15 +01:00
|
|
|
|
<script>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
import { onMount } from 'svelte'
|
|
|
|
|
|
import { slides } from '@state/slides.svelte'
|
2026-03-12 15:05:25 +01:00
|
|
|
|
import { navigation } from '@state/navigation.svelte'
|
2026-03-05 17:13:50 +01:00
|
|
|
|
import { createScrollNav } from '@composables/useScrollNav.svelte.js'
|
|
|
|
|
|
import GalleryAnimation from '@components/ui/GalleryAnimation.svelte'
|
2026-03-10 08:12:20 +01:00
|
|
|
|
import ResponsivePicture from '@components/ui/ResponsivePicture.svelte'
|
2026-03-05 17:13:50 +01:00
|
|
|
|
|
2026-02-07 08:26:28 +01:00
|
|
|
|
let { data } = $props()
|
2026-03-05 17:13:50 +01:00
|
|
|
|
|
|
|
|
|
|
// --- State ---
|
|
|
|
|
|
let currentIndex = $state(0)
|
|
|
|
|
|
let sectionEl = $state(null)
|
|
|
|
|
|
|
|
|
|
|
|
// --- Derived ---
|
2026-03-09 11:57:28 +01:00
|
|
|
|
const isActive = $derived(slides.active?.id === 'portfolio')
|
|
|
|
|
|
const projects = $derived(data?.projects ?? [])
|
2026-03-09 13:40:33 +01:00
|
|
|
|
const backgroundImage = $derived(data?.backgroundImage ?? null)
|
2026-03-05 17:13:50 +01:00
|
|
|
|
const currentProject = $derived(projects[currentIndex] ?? null)
|
|
|
|
|
|
|
2026-03-12 15:05:25 +01:00
|
|
|
|
$effect(() => {
|
|
|
|
|
|
if (isActive) navigation.setScrolled(currentIndex > 0)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-06 12:23:47 +01:00
|
|
|
|
// Capture du hash synchrone avant que tout effect puisse le modifier
|
|
|
|
|
|
const initialHash = window.location.hash.slice(1)
|
|
|
|
|
|
|
2026-03-06 12:17:58 +01:00
|
|
|
|
// --- Ancres ---
|
|
|
|
|
|
function setAnchor(index) {
|
|
|
|
|
|
const slug = projects[index]?.slug
|
|
|
|
|
|
if (!slug) return
|
|
|
|
|
|
history.replaceState(null, '', '#' + slug)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearAnchor() {
|
|
|
|
|
|
history.replaceState(null, '', window.location.pathname + window.location.search)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialisation depuis l'ancre URL — une seule fois quand projects est prêt
|
|
|
|
|
|
$effect(() => {
|
2026-03-06 12:23:47 +01:00
|
|
|
|
if (projects.length === 0 || !initialHash) return
|
|
|
|
|
|
const idx = projects.findIndex(p => p.slug === initialHash)
|
2026-03-06 12:17:58 +01:00
|
|
|
|
if (idx > 0) currentIndex = idx
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-05 17:13:50 +01:00
|
|
|
|
// --- Scroll nav composable ---
|
|
|
|
|
|
const nav = createScrollNav({
|
|
|
|
|
|
isActive: () => isActive,
|
|
|
|
|
|
onNavigate: (dir) => {
|
|
|
|
|
|
const next = dir === 'down'
|
|
|
|
|
|
? Math.min(currentIndex + 1, projects.length - 1)
|
|
|
|
|
|
: Math.max(currentIndex - 1, 0)
|
|
|
|
|
|
if (next === currentIndex) return false
|
|
|
|
|
|
currentIndex = next
|
2026-03-06 12:17:58 +01:00
|
|
|
|
setAnchor(next)
|
2026-03-05 17:13:50 +01:00
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// --- onMount ---
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
|
sectionEl?.addEventListener('wheel', nav.onWheel, { passive: false })
|
|
|
|
|
|
window.addEventListener('keydown', nav.onKeyDown)
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
sectionEl?.removeEventListener('wheel', nav.onWheel)
|
|
|
|
|
|
window.removeEventListener('keydown', nav.onKeyDown)
|
|
|
|
|
|
nav.destroy()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// --- Effect: reset when slide deactivated ---
|
2026-03-06 12:23:47 +01:00
|
|
|
|
// wasActive évite que clearAnchor() s'exécute au montage initial
|
|
|
|
|
|
// (isActive est false avant l'initialisation des slides)
|
|
|
|
|
|
let wasActive = false
|
2026-03-05 17:13:50 +01:00
|
|
|
|
$effect(() => {
|
2026-03-06 12:23:47 +01:00
|
|
|
|
if (isActive) {
|
|
|
|
|
|
wasActive = true
|
|
|
|
|
|
} else if (wasActive) {
|
2026-03-05 17:13:50 +01:00
|
|
|
|
nav.reset()
|
|
|
|
|
|
currentIndex = 0
|
2026-03-06 12:17:58 +01:00
|
|
|
|
clearAnchor()
|
2026-03-06 12:23:47 +01:00
|
|
|
|
wasActive = false
|
2026-03-05 17:13:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
})
|
Migration vers architecture Svelte + Kirby inspirée de design-to-pack
- Mise en place de Svelte 4 avec Vite pour le frontend (SPA)
- Simplification des templates PHP (header/footer minimalistes)
- Création de templates JSON pour API (home, about, expertise, portfolio, jouer, game, blog, article, project)
- Ajout d'un controller de site pour définir genericData globalement
- Structure des stores Svelte (page, navigation, locale, site)
- Router avec navaid pour navigation SPA et interception des liens
- Composants layout (Header, Footer, Cursor) et vues de base
- Build Vite vers assets/dist/ (index.js/css)
- Header PHP détecte assets/dist pour basculer dev/prod
Architecture fonctionnelle de base établie, à améliorer et compléter.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:30:15 +01:00
|
|
|
|
</script>
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
<div class="portfolio-gallery mobile-only" aria-hidden="true">
|
|
|
|
|
|
<GalleryAnimation images={currentProject.imagesGallery} backgroundColor={currentProject.galleryBackgroundColor} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-05 17:13:50 +01:00
|
|
|
|
<section
|
2026-03-09 18:54:51 +01:00
|
|
|
|
class="portfolio golden-grid"
|
2026-03-09 11:57:28 +01:00
|
|
|
|
style={backgroundImage ? `--background-image: url('${backgroundImage}')` : ''}
|
2026-03-05 17:13:50 +01:00
|
|
|
|
bind:this={sectionEl}
|
|
|
|
|
|
ontouchstart={nav.onTouchStart}
|
|
|
|
|
|
ontouchend={nav.onTouchEnd}
|
|
|
|
|
|
aria-label="Portfolio"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- Decorative vertical lines -->
|
|
|
|
|
|
<div class="vertical-line-start" aria-hidden="true"></div>
|
|
|
|
|
|
<div class="vertical-line vertical-line-col8" aria-hidden="true"></div>
|
|
|
|
|
|
<div class="vertical-line-center" aria-hidden="true"></div>
|
|
|
|
|
|
<div class="vertical-line vertical-line-col14" aria-hidden="true"></div>
|
|
|
|
|
|
<div class="vertical-line-end" aria-hidden="true"></div>
|
|
|
|
|
|
|
|
|
|
|
|
{#if currentProject}
|
2026-03-12 18:37:58 +01:00
|
|
|
|
<!-- Galerie animation (gauche desktop / plein écran mobile) -->
|
|
|
|
|
|
<div class="portfolio-gallery desktop-only" aria-hidden="true">
|
2026-03-09 13:40:33 +01:00
|
|
|
|
<GalleryAnimation images={currentProject.imagesGallery} backgroundColor={currentProject.galleryBackgroundColor} />
|
2026-03-05 17:13:50 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Mockup device (centre) -->
|
|
|
|
|
|
<div class="portfolio-mockup">
|
2026-03-10 08:12:20 +01:00
|
|
|
|
<ResponsivePicture
|
|
|
|
|
|
src={currentProject.mockup}
|
|
|
|
|
|
srcset={currentProject.mockupSrcset}
|
|
|
|
|
|
webp={currentProject.mockupWebp}
|
|
|
|
|
|
sizes="(max-width: 700px) 90vw, 25vw"
|
|
|
|
|
|
alt={currentProject.title}
|
|
|
|
|
|
cls="portfolio-mockup-img"
|
|
|
|
|
|
/>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
</div>
|
2026-03-12 18:37:58 +01:00
|
|
|
|
<img class="content-background mobile-only" src="/assets/img/BG GAME MOBILE.408a3a253492f65d39f8.png" alt="">
|
2026-03-05 17:13:50 +01:00
|
|
|
|
|
|
|
|
|
|
<!-- Infos projet (droite) -->
|
|
|
|
|
|
<div class="portfolio-text" aria-live="polite">
|
|
|
|
|
|
<h2>{currentProject.title}</h2>
|
2026-03-09 13:40:33 +01:00
|
|
|
|
<h3 class="portfolio-catchphrase gradient-blue">{@html currentProject.catchPhrase}</h3>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
<div class="portfolio-description">{@html currentProject.description}</div>
|
|
|
|
|
|
<div class="portfolio-keywords">
|
|
|
|
|
|
{#each currentProject.keywords as kw}
|
|
|
|
|
|
<p><strong>{kw.label} :</strong> {kw.text}</p>
|
|
|
|
|
|
{/each}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="portfolio-links">
|
2026-03-09 13:40:33 +01:00
|
|
|
|
{#each currentProject.externalLinks as link}
|
2026-03-09 11:57:28 +01:00
|
|
|
|
<a href={link.url} target="_blank" rel="noopener noreferrer" class="button earth-icon">{link.label}</a>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
{/each}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
<!-- Sidebar navigation -->
|
2026-03-05 17:13:50 +01:00
|
|
|
|
<nav class="portfolio-nav" aria-label="Projets">
|
2026-03-06 17:32:22 +01:00
|
|
|
|
<ul role="list">
|
|
|
|
|
|
{#each projects as project, i}
|
2026-03-06 17:38:25 +01:00
|
|
|
|
<li class="portfolio-nav-item" class:active={i === currentIndex}>
|
2026-03-06 17:32:22 +01:00
|
|
|
|
<button
|
|
|
|
|
|
aria-current={i === currentIndex ? 'true' : undefined}
|
|
|
|
|
|
aria-label={project.title}
|
|
|
|
|
|
onclick={() => { currentIndex = i; setAnchor(i) }}
|
|
|
|
|
|
>
|
2026-03-06 17:38:25 +01:00
|
|
|
|
{#if project.thumbnail?.length}
|
|
|
|
|
|
<img src={project.thumbnail} alt="" />
|
|
|
|
|
|
{/if}
|
2026-03-06 17:32:22 +01:00
|
|
|
|
<span class="portfolio-nav-number" aria-hidden="true">{String(i + 1).padStart(2, '0')}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
{/each}
|
|
|
|
|
|
</ul>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
</nav>
|
2026-03-12 18:37:58 +01:00
|
|
|
|
|
|
|
|
|
|
<!-- Arrows + counter -->
|
|
|
|
|
|
<div class="portfolio-arrows font-face-danzza" aria-label="Navigation projets">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="portfolio-arrow portfolio-arrow--up"
|
|
|
|
|
|
aria-label="Projet précédent"
|
|
|
|
|
|
disabled={currentIndex === 0}
|
|
|
|
|
|
onclick={() => { if (currentIndex > 0) { currentIndex--; setAnchor(currentIndex) } }}
|
|
|
|
|
|
></button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="portfolio-arrow portfolio-arrow--down"
|
|
|
|
|
|
aria-label="Projet suivant"
|
|
|
|
|
|
disabled={currentIndex >= projects.length - 1}
|
|
|
|
|
|
onclick={() => { if (currentIndex < projects.length - 1) { currentIndex++; setAnchor(currentIndex) } }}
|
|
|
|
|
|
></button>
|
|
|
|
|
|
<span class="portfolio-counter">{String(currentIndex + 1).padStart(2, '0')}/{String(projects.length).padStart(2, '0')}</span>
|
|
|
|
|
|
</div>
|
2026-03-05 17:13:50 +01:00
|
|
|
|
</section>
|
Migration vers architecture Svelte + Kirby inspirée de design-to-pack
- Mise en place de Svelte 4 avec Vite pour le frontend (SPA)
- Simplification des templates PHP (header/footer minimalistes)
- Création de templates JSON pour API (home, about, expertise, portfolio, jouer, game, blog, article, project)
- Ajout d'un controller de site pour définir genericData globalement
- Structure des stores Svelte (page, navigation, locale, site)
- Router avec navaid pour navigation SPA et interception des liens
- Composants layout (Header, Footer, Cursor) et vues de base
- Build Vite vers assets/dist/ (index.js/css)
- Header PHP détecte assets/dist pour basculer dev/prod
Architecture fonctionnelle de base établie, à améliorer et compléter.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:30:15 +01:00
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.portfolio {
|
2026-03-09 11:57:28 +01:00
|
|
|
|
background-image: var(--background-image, none);
|
|
|
|
|
|
background-position: center;
|
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
|
background-size: cover;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Custom vertical lines */
|
|
|
|
|
|
.vertical-line-col8 {
|
|
|
|
|
|
grid-area: 1/8 / span 20 / span 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.vertical-line-col14 {
|
|
|
|
|
|
grid-area: 1/14 / span 20 / span 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Desktop layout */
|
|
|
|
|
|
.portfolio-gallery {
|
|
|
|
|
|
grid-area: 1/1 / span 20 / span 7;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-mockup {
|
|
|
|
|
|
grid-area: 6/7 / span 10 / span 4;
|
|
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 08:12:20 +01:00
|
|
|
|
.portfolio-mockup :global(picture),
|
|
|
|
|
|
.portfolio-mockup :global(.portfolio-mockup-img) {
|
2026-03-05 17:13:50 +01:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-text {
|
2026-03-06 16:42:25 +01:00
|
|
|
|
grid-area: 7/11 / span 6 / span 5;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
color: var(--color-text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-text h2 {
|
2026-03-06 16:42:25 +01:00
|
|
|
|
font-size: var(--font-size-title-main);
|
2026-03-06 17:32:22 +01:00
|
|
|
|
font-weight: 700;
|
2026-03-06 16:42:25 +01:00
|
|
|
|
text-transform: uppercase;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
line-height: 1.1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-catchphrase {
|
2026-03-06 16:42:25 +01:00
|
|
|
|
font-family: "Danzza Medium", sans-serif;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
font-size: var(--font-size-subtitle);
|
2026-03-06 16:42:25 +01:00
|
|
|
|
font-weight: 600;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
color: var(--color-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-description {
|
2026-03-06 16:42:25 +01:00
|
|
|
|
font-size: var(--font-size-subtitle);
|
2026-03-05 17:13:50 +01:00
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-keywords {
|
|
|
|
|
|
font-size: var(--font-size-caption);
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-keywords p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-links {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Sidebar navigation */
|
2026-03-06 17:32:22 +01:00
|
|
|
|
.portfolio-nav ul {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
display: contents;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 17:13:50 +01:00
|
|
|
|
.portfolio-nav {
|
|
|
|
|
|
grid-area: 4/17 / span 14 / span 4;
|
2026-03-06 16:42:25 +01:00
|
|
|
|
padding-right: 8rem;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 17:39:22 +01:00
|
|
|
|
.portfolio-nav-item:not(:last-child) {
|
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 17:38:25 +01:00
|
|
|
|
.portfolio-nav-item button {
|
2026-03-05 17:13:50 +01:00
|
|
|
|
display: flex;
|
2026-03-06 16:42:25 +01:00
|
|
|
|
align-items: flex-start;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transform: scale(0.85);
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
transition: transform 0.6s var(--ease-standard), opacity 0.6s var(--ease-standard);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 11:57:28 +01:00
|
|
|
|
.portfolio-nav-item:hover button {
|
2026-03-09 13:20:28 +01:00
|
|
|
|
transform: scale(1.25);
|
2026-03-09 11:57:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 17:38:25 +01:00
|
|
|
|
.portfolio-nav-item.active button {
|
2026-03-06 16:42:25 +01:00
|
|
|
|
transform: scale(1.5) translateX(-20%);
|
2026-03-05 17:13:50 +01:00
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-number {
|
|
|
|
|
|
color: var(--color-text);
|
2026-03-06 16:42:25 +01:00
|
|
|
|
font-size: .5rem;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item img {
|
|
|
|
|
|
width: 48px;
|
|
|
|
|
|
height: 48px;
|
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
/* Arrows + counter */
|
|
|
|
|
|
.portfolio-arrows {
|
|
|
|
|
|
grid-area: 18/16 / span 2 / span 3;
|
|
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-size: var(--font-size-caption);
|
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-arrow {
|
|
|
|
|
|
width: 13px;
|
|
|
|
|
|
min-width: 13px;
|
|
|
|
|
|
height: 13px;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
background-position: center;
|
|
|
|
|
|
background-size: contain;
|
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
transition: opacity 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-arrow:disabled {
|
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-arrow--up {
|
|
|
|
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2'%3E%3Cpath d='M2 8L6 4L10 8'/%3E%3C/svg%3E");
|
|
|
|
|
|
margin-right: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-arrow--down {
|
|
|
|
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2'%3E%3Cpath d='M2 4L6 8L10 4'/%3E%3C/svg%3E");
|
|
|
|
|
|
margin-right: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-counter {
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 17:13:50 +01:00
|
|
|
|
/* Mobile (≤ 700px) */
|
2026-03-10 18:55:37 +01:00
|
|
|
|
@media (max-width: 700px) {
|
2026-03-12 18:37:58 +01:00
|
|
|
|
|
|
|
|
|
|
.portfolio {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Gallery over background, under content */
|
2026-03-05 17:13:50 +01:00
|
|
|
|
.portfolio-gallery {
|
2026-03-12 18:37:58 +01:00
|
|
|
|
position: fixed;
|
|
|
|
|
|
transform: translateX(-10vw);
|
|
|
|
|
|
width: 120vw;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-background {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: -7rem;
|
|
|
|
|
|
width: 100vw;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
z-index: 5;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
/* Mockup — centered top, behind text */
|
2026-03-05 17:13:50 +01:00
|
|
|
|
.portfolio-mockup {
|
2026-03-12 18:37:58 +01:00
|
|
|
|
grid-area: 4/4 / span 8 / span 14;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
.portfolio-mockup :global(.portfolio-mockup-img) {
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Text — over mockup, centered */
|
2026-03-05 17:13:50 +01:00
|
|
|
|
.portfolio-text {
|
2026-03-12 18:37:58 +01:00
|
|
|
|
grid-area: 9/3 / span 7 / span 16;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
z-index: var(--z-content);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 18:37:58 +01:00
|
|
|
|
.portfolio-text h2 {
|
|
|
|
|
|
font-family: "Danzza Bold";
|
|
|
|
|
|
font-size: var(--font-size-title-section-mobile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-catchphrase {
|
|
|
|
|
|
font-size: var(--font-size-subtitle-mobile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-description {
|
|
|
|
|
|
font-size: var(--font-size-paragraph-mobile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Hide keywords on mobile */
|
|
|
|
|
|
.portfolio-keywords {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Nav thumbnails — horizontal, compact */
|
2026-03-05 17:13:50 +01:00
|
|
|
|
.portfolio-nav {
|
2026-03-12 18:37:58 +01:00
|
|
|
|
grid-area: 17/4 / span 1 / span 14;
|
2026-03-05 17:13:50 +01:00
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
justify-content: center;
|
2026-03-12 18:37:58 +01:00
|
|
|
|
padding-right: 0;
|
|
|
|
|
|
height: 75px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item:not(:last-child) {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item button {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item.active button {
|
|
|
|
|
|
transform: scale(1.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item:hover button {
|
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-item img {
|
|
|
|
|
|
width: 45px;
|
|
|
|
|
|
height: 50px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.portfolio-nav-number {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Arrows — bottom right */
|
|
|
|
|
|
.portfolio-arrows {
|
|
|
|
|
|
grid-area: 18/15 / span 1 / span 3;
|
|
|
|
|
|
font-size: var(--font-size-caption-mobile, 11px);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Tablet (701px–912px) */
|
|
|
|
|
|
@media (min-width: 701px) and (max-width: 912px) {
|
|
|
|
|
|
.portfolio-text h2 {
|
|
|
|
|
|
font-size: var(--font-size-title-main-tablet);
|
2026-03-05 17:13:50 +01:00
|
|
|
|
}
|
Migration vers architecture Svelte + Kirby inspirée de design-to-pack
- Mise en place de Svelte 4 avec Vite pour le frontend (SPA)
- Simplification des templates PHP (header/footer minimalistes)
- Création de templates JSON pour API (home, about, expertise, portfolio, jouer, game, blog, article, project)
- Ajout d'un controller de site pour définir genericData globalement
- Structure des stores Svelte (page, navigation, locale, site)
- Router avec navaid pour navigation SPA et interception des liens
- Composants layout (Header, Footer, Cursor) et vues de base
- Build Vite vers assets/dist/ (index.js/css)
- Header PHP détecte assets/dist pour basculer dev/prod
Architecture fonctionnelle de base établie, à améliorer et compléter.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:30:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 17:13:50 +01:00
|
|
|
|
/* Reduced motion */
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
|
.portfolio-nav-item {
|
|
|
|
|
|
transition: none;
|
|
|
|
|
|
}
|
Migration vers architecture Svelte + Kirby inspirée de design-to-pack
- Mise en place de Svelte 4 avec Vite pour le frontend (SPA)
- Simplification des templates PHP (header/footer minimalistes)
- Création de templates JSON pour API (home, about, expertise, portfolio, jouer, game, blog, article, project)
- Ajout d'un controller de site pour définir genericData globalement
- Structure des stores Svelte (page, navigation, locale, site)
- Router avec navaid pour navigation SPA et interception des liens
- Composants layout (Header, Footer, Cursor) et vues de base
- Build Vite vers assets/dist/ (index.js/css)
- Header PHP détecte assets/dist pour basculer dev/prod
Architecture fonctionnelle de base établie, à améliorer et compléter.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 16:30:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|