Feat: footer reveal + refactor page About
All checks were successful
Deploy / Deploy to Production (push) Successful in 24s
All checks were successful
Deploy / Deploy to Production (push) Successful in 24s
- Footer global fixé en bas (App.svelte), masqué par le contenu des slides - margin-bottom sur .page-scrollable pour l'effet footer reveal (about, blog, article) - About : champ intro remplacé par heading (text) + subtitle (writer), blueprint + API + vue mis à jour - LanguageSwitcher : div → button avec hitbox élargie - i18n : clé our_team (fr: NOTRE ÉQUIPE / en: OUR TEAM) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
61607f8cd7
commit
b3b0580ab1
10 changed files with 148 additions and 130 deletions
|
|
@ -18,23 +18,14 @@ tabs:
|
||||||
content:
|
content:
|
||||||
type: fields
|
type: fields
|
||||||
fields:
|
fields:
|
||||||
intro:
|
heading:
|
||||||
type: writer
|
type: text
|
||||||
marks:
|
label: Titre
|
||||||
- bold
|
help: Titre principal de la section intro.
|
||||||
- italic
|
subtitle:
|
||||||
- green
|
type: text
|
||||||
- pixel
|
|
||||||
- underline
|
|
||||||
- strike
|
|
||||||
- clear
|
|
||||||
- link
|
|
||||||
nodes:
|
|
||||||
- heading
|
|
||||||
headings:
|
|
||||||
- 1
|
|
||||||
buttons: false
|
buttons: false
|
||||||
help: Section de texte centrée.
|
help: Texte d'accroche sous le titre.
|
||||||
body:
|
body:
|
||||||
label: Corps
|
label: Corps
|
||||||
type: blocks
|
type: blocks
|
||||||
|
|
@ -77,6 +68,6 @@ tabs:
|
||||||
image:
|
image:
|
||||||
ratio: 3/5
|
ratio: 3/5
|
||||||
cover: true
|
cover: true
|
||||||
back: '#0e1e43'
|
back: "#0e1e43"
|
||||||
text: "{{ file.memberName }}"
|
text: "{{ file.memberName }}"
|
||||||
info: "{{ file.role }}"
|
info: "{{ file.role }}"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ foreach ($page->body()->toBlocks() as $block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$specificData = [
|
$specificData = [
|
||||||
'intro' => $page->intro()->value(),
|
'heading' => $page->heading()->value(),
|
||||||
|
'subtitle' => $page->subtitle()->value(),
|
||||||
'body' => $bodyBlocks,
|
'body' => $bodyBlocks,
|
||||||
'team' => $page->files()->template('member')->sort('sort')->map(function ($file) {
|
'team' => $page->files()->template('member')->sort('sort')->map(function ($file) {
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import Cursor from '@components/layout/Cursor.svelte'
|
import Cursor from '@components/layout/Cursor.svelte'
|
||||||
import LanguageSwitcher from '@components/ui/LanguageSwitcher.svelte'
|
import LanguageSwitcher from '@components/ui/LanguageSwitcher.svelte'
|
||||||
|
|
||||||
|
import Footer from '@components/layout/Footer.svelte'
|
||||||
import Home from '@views/Home.svelte'
|
import Home from '@views/Home.svelte'
|
||||||
import About from '@views/About.svelte'
|
import About from '@views/About.svelte'
|
||||||
import Expertise from '@views/Expertise.svelte'
|
import Expertise from '@views/Expertise.svelte'
|
||||||
|
|
@ -95,6 +96,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<Footer />
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -127,6 +129,7 @@
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
@ -155,4 +158,12 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.site-footer) {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="lang-switcher font-face-danzza-bold clickable" onclick={switchLanguage}>
|
<button class="lang-switcher font-face-danzza-bold" onclick={switchLanguage}>
|
||||||
<p class="clickable">{locale.current === 'en' ? 'fr' : 'en'}</p>
|
{locale.current === 'en' ? 'fr' : 'en'}
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.lang-switcher {
|
.lang-switcher {
|
||||||
|
|
@ -22,7 +22,9 @@
|
||||||
left: 4vh;
|
left: 4vh;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: rgb(4, 254, 160);
|
color: rgb(4, 254, 160);
|
||||||
display: block;
|
background: none;
|
||||||
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ const dict = {
|
||||||
legal: { fr: "Mentions légales", en: "Legal notice" },
|
legal: { fr: "Mentions légales", en: "Legal notice" },
|
||||||
cookies: { fr: "Préférences cookies", en: "Cookie preferences" },
|
cookies: { fr: "Préférences cookies", en: "Cookie preferences" },
|
||||||
privacy: { fr: "Confidentialité", en: "Privacy" },
|
privacy: { fr: "Confidentialité", en: "Privacy" },
|
||||||
|
// About
|
||||||
|
our_team: { fr: "NOTRE ÉQUIPE", en: "OUR TEAM" },
|
||||||
// Menu
|
// Menu
|
||||||
menu: { fr: "MENU", en: "MENU" },
|
menu: { fr: "MENU", en: "MENU" },
|
||||||
connect: { fr: "CONNECT", en: "CONNECT" },
|
connect: { fr: "CONNECT", en: "CONNECT" },
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,11 @@
|
||||||
.page-scrollable {
|
.page-scrollable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
margin-bottom: var(--footer-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-scrollable .page-container {
|
||||||
max-width: 1200px;
|
margin-bottom: var(--footer-height);
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vertical Lines */
|
/* Vertical Lines */
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
* --z-header : barre de navigation (toujours au-dessus du menu)
|
* --z-header : barre de navigation (toujours au-dessus du menu)
|
||||||
* --z-cursor : curseur personnalisé (toujours au-dessus de tout)
|
* --z-cursor : curseur personnalisé (toujours au-dessus de tout)
|
||||||
*/
|
*/
|
||||||
--z-base: 1;
|
--z-base: 1;
|
||||||
--z-content: 5;
|
--z-content: 5;
|
||||||
--z-menu: 100;
|
--z-menu: 100;
|
||||||
--z-header: 200;
|
--z-header: 200;
|
||||||
--z-cursor: 9999;
|
--z-cursor: 9999;
|
||||||
|
|
||||||
/* Font sizes — desktop */
|
/* Font sizes — desktop */
|
||||||
--font-size-paragraph: 18px;
|
--font-size-paragraph: 18px;
|
||||||
|
|
@ -52,6 +52,9 @@
|
||||||
--font-size-button-tablet: 12px;
|
--font-size-button-tablet: 12px;
|
||||||
--font-size-caption-tablet: 11px;
|
--font-size-caption-tablet: 11px;
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
--footer-height: 18.75rem;
|
||||||
|
|
||||||
/* Easing */
|
/* Easing */
|
||||||
--ease-standard: cubic-bezier(0.65, 0, 0.35, 1);
|
--ease-standard: cubic-bezier(0.65, 0, 0.35, 1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { slides } from '@state/slides.svelte'
|
import { slides } from '@state/slides.svelte'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
|
import { t } from '@i18n'
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
const intro = $derived(data?.intro ?? '')
|
const heading = $derived(data?.heading ?? '')
|
||||||
|
const subtitle = $derived(data?.subtitle ?? '')
|
||||||
const body = $derived(data?.body ?? [])
|
const body = $derived(data?.body ?? [])
|
||||||
const members = $derived(data?.team ?? [])
|
const members = $derived(data?.team ?? [])
|
||||||
const isActive = $derived(slides.active?.id === 'about')
|
const isActive = $derived(slides.active?.id === 'about')
|
||||||
|
|
@ -72,86 +74,88 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="about page-scrollable" bind:this={sectionEl}>
|
<div class="about golden-grid page-scrollable" bind:this={sectionEl}>
|
||||||
|
<div class="page-container">
|
||||||
<!-- Intro -->
|
<!-- Intro -->
|
||||||
<section class="about-intro">
|
<section class="about-intro">
|
||||||
<div class="about-intro-content">
|
<div class="about-intro-content">
|
||||||
{@html intro}
|
{#if heading}<h1 class="heading">{heading}</h1>{/if}
|
||||||
</div>
|
<p class="subtitle">{subtitle}</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<!-- Body blocks (Mission, Manifeste…) -->
|
|
||||||
{#if body.length > 0}
|
|
||||||
<section class="about-body">
|
|
||||||
{#each body as block}
|
|
||||||
{#if block.type === 'text'}
|
|
||||||
<div class="about-body-block">
|
|
||||||
{@html block.html}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Team carousel -->
|
<!-- Body blocks (Mission, Manifeste…) -->
|
||||||
{#if members.length > 0}
|
{#if body.length > 0}
|
||||||
<section class="about-team">
|
<section class="about-body">
|
||||||
<h2 class="about-team-heading">OUR TEAM</h2>
|
{#each body as block}
|
||||||
|
{#if block.type === 'text'}
|
||||||
<div class="team-carousel-container">
|
<div class="about-body-block">
|
||||||
<div
|
{@html block.html}
|
||||||
class="team-grid"
|
|
||||||
style="transform: translateX(-{carouselOffset}px)"
|
|
||||||
ontouchstart={(e) => { touchStartX = e.touches[0].clientX }}
|
|
||||||
ontouchend={(e) => {
|
|
||||||
if (touchStartX === null) return
|
|
||||||
const delta = touchStartX - e.changedTouches[0].clientX
|
|
||||||
if (Math.abs(delta) > 50) delta > 0 ? nextSlide() : prevSlide()
|
|
||||||
touchStartX = null
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#each members as member}
|
|
||||||
<div class="team-member">
|
|
||||||
{#if member.photo}
|
|
||||||
<img src={member.photo} alt={member.name} class="team-member-image" draggable="false" />
|
|
||||||
{/if}
|
|
||||||
<h4 class="team-member-name">{member.name}</h4>
|
|
||||||
<p class="team-member-title">{member.role}</p>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/if}
|
||||||
</div>
|
{/each}
|
||||||
</div>
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="nav-buttons">
|
<!-- Team carousel -->
|
||||||
<button
|
{#if members.length > 0}
|
||||||
class="nav-button"
|
<section class="about-team">
|
||||||
disabled={currentSlide === 0}
|
<h2 class="about-team-heading">{t('our_team')}</h2>
|
||||||
onclick={prevSlide}
|
|
||||||
>← BEFORE</button>
|
|
||||||
|
|
||||||
<div class="pagination-indicator">
|
<div class="team-carousel-container">
|
||||||
{#each { length: totalSlides } as _, i}
|
<div
|
||||||
<button
|
class="team-grid"
|
||||||
class="pagination-dot"
|
style="transform: translateX(-{carouselOffset}px)"
|
||||||
class:active={i === currentSlide}
|
ontouchstart={(e) => { touchStartX = e.touches[0].clientX }}
|
||||||
aria-label="Slide {i + 1}"
|
ontouchend={(e) => {
|
||||||
onclick={() => goToSlide(i)}
|
if (touchStartX === null) return
|
||||||
></button>
|
const delta = touchStartX - e.changedTouches[0].clientX
|
||||||
{/each}
|
if (Math.abs(delta) > 50) delta > 0 ? nextSlide() : prevSlide()
|
||||||
|
touchStartX = null
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#each members as member}
|
||||||
|
<div class="team-member">
|
||||||
|
{#if member.photo}
|
||||||
|
<img src={member.photo} alt={member.name} class="team-member-image" draggable="false" />
|
||||||
|
{/if}
|
||||||
|
<h4 class="team-member-name">{member.name}</h4>
|
||||||
|
<p class="team-member-title">{member.role}</p>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div class="nav-buttons">
|
||||||
class="nav-button"
|
<button
|
||||||
disabled={currentSlide === totalSlides - 1}
|
class="nav-button"
|
||||||
onclick={nextSlide}
|
disabled={currentSlide === 0}
|
||||||
>NEXT →</button>
|
onclick={prevSlide}
|
||||||
</div>
|
>← BEFORE</button>
|
||||||
</section>
|
|
||||||
{/if}
|
<div class="pagination-indicator">
|
||||||
|
{#each { length: totalSlides } as _, i}
|
||||||
|
<button
|
||||||
|
class="pagination-dot"
|
||||||
|
class:active={i === currentSlide}
|
||||||
|
aria-label="Slide {i + 1}"
|
||||||
|
onclick={() => goToSlide(i)}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="nav-button"
|
||||||
|
disabled={currentSlide === totalSlides - 1}
|
||||||
|
onclick={nextSlide}
|
||||||
|
>NEXT →</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.about {
|
.about {
|
||||||
|
|
@ -161,15 +165,22 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
grid-area: 6/6/span 7/span 10;
|
||||||
|
height: 100%;
|
||||||
|
place-self: center;
|
||||||
|
text-align: center;
|
||||||
|
white-space: pre-line;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Intro ── */
|
/* ── Intro ── */
|
||||||
.about-intro {
|
.about-intro {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 8rem 3rem 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-intro-content {
|
.about-intro-content {
|
||||||
max-width: 800px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: "Danzza", sans-serif;
|
font-family: "Danzza", sans-serif;
|
||||||
font-size: var(--font-size-paragraph);
|
font-size: var(--font-size-paragraph);
|
||||||
|
|
@ -194,20 +205,17 @@
|
||||||
color: #04fea0;
|
color: #04fea0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-intro-content :global(p) {
|
.about-intro-content .subtitle {
|
||||||
font-size: var(--font-size-subtitle);
|
font-size: var(--font-size-subtitle);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
margin-top: 3.125rem;
|
||||||
|
padding-left: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Body blocks ── */
|
/* ── Body blocks ── */
|
||||||
.about-body {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem 3rem 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-body-block {
|
.about-body-block {
|
||||||
border-left: 2px solid #04fea0;
|
border-left: 2px solid #04fea0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
|
@ -221,12 +229,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-body-block :global(h3) {
|
.about-body-block :global(h3) {
|
||||||
font-family: "Terminal", sans-serif;
|
font-family: Danzza Medium,sans-serif;
|
||||||
font-size: var(--font-size-subtitle);
|
font-size: var(--font-size-paragraph);
|
||||||
color: #04fea0;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.about-body-block :global(h3::before) {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: .6rem;
|
||||||
|
height: .6rem;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
|
||||||
|
margin-right: .6rem;
|
||||||
|
margin-bottom: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.about-body-block :global(p) {
|
.about-body-block :global(p) {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Vue article — sous-composant de Blog.svelte.
|
* Vue article — sous-composant de Blog.svelte.
|
||||||
* Reçoit les données article via props, pas via le slide system.
|
* Reçoit les données article via props, pas via le slide system.
|
||||||
*/
|
*/
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
|
|
||||||
let { data, onBack } = $props()
|
let { data, onBack } = $props()
|
||||||
|
|
@ -114,7 +114,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { slides } from '@state/slides.svelte'
|
import { slides } from '@state/slides.svelte'
|
||||||
import { locale } from '@state/locale.svelte'
|
import { locale } from '@state/locale.svelte'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
import Article from '@views/Article.svelte'
|
import Article from '@views/Article.svelte'
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
|
|
||||||
|
|
@ -178,7 +178,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -211,7 +210,6 @@
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
|
||||||
padding: 0 10%;
|
padding: 0 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,12 +310,6 @@
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog :global(.site-footer) {
|
|
||||||
margin-left: -50px;
|
|
||||||
margin-right: -50px;
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Mobile --- */
|
/* --- Mobile --- */
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.blog-header {
|
.blog-header {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue