décors ponctuels (oiseaux, fleurs) sur home, pages internes et footer
162
CLAUDE.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
This directory (`src/`) is a **git submodule** of the parent `docker-erable` repo. It holds the Drupal 10 application; the parent repo holds the Docker environment (`Docker/`, `docker-compose.yml`, `Makefile`, `ressources/db.sql`).
|
||||||
|
|
||||||
|
- `src/` (this repo, `drupal-erable.git`) — Drupal codebase, custom theme & modules, exported config.
|
||||||
|
- `../` (parent repo, `docker-erable.git`) — Docker setup, DB seed, Makefile commands.
|
||||||
|
|
||||||
|
Branches: `master` (dev), `stage` (preprod), `prod` (prod). A `refonte-graphique` branch is currently active for the ongoing visual redesign — **all work happens there**, not on `master`. `git push origin master` from `src/` will appear up-to-date even after committing on `refonte-graphique`; push the branch by name.
|
||||||
|
|
||||||
|
The parent repo references the submodule under its own branch set (`master`, `prod`). After pulling parent changes, run `git submodule update --recursive --checkout`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All commands are run **from the parent directory** (`docker-erable/`), never directly from `src/`. Never use `docker-compose` directly — always go through the Makefile.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make build # first-time build (uses host UID/GID via build args — do not skip)
|
||||||
|
make up # start the stack (php, nginx, mysql, redis, phpmyadmin)
|
||||||
|
make down # stop
|
||||||
|
make logs # follow logs
|
||||||
|
make ps # status
|
||||||
|
make exec_php # shell into the PHP container (where drush lives)
|
||||||
|
make exec_mysql # shell into the MySQL container
|
||||||
|
make dump_db # dump erable DB to ressources/erable-<date>-local.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
After pulling code changes that affect Drupal config/schema:
|
||||||
|
```sh
|
||||||
|
make maj_config # composer install + drush updb + drush cim + drush cr + drush pag
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual Drupal ops (also available as Make targets):
|
||||||
|
```sh
|
||||||
|
make composer_install # composer install --no-dev in the php container
|
||||||
|
make updb # drush updb -y
|
||||||
|
make cim # drush config-import -y
|
||||||
|
make cr # drush cr (cache rebuild)
|
||||||
|
make pag # drush pag all for nodes & taxonomy terms
|
||||||
|
```
|
||||||
|
|
||||||
|
For ad-hoc drush use, `make exec_php` then run `drush ...` from `/var/www/erable/`.
|
||||||
|
|
||||||
|
### Drush gotcha (Make targets broken)
|
||||||
|
|
||||||
|
The Make targets that wrap drush (`cr`, `cim`, `updb`, `crd`, `pag`, and the composite `maj_config`) are currently broken because the container's global `drush` is the Drush Launcher, which errors out against Drush 12+ (`Drush Launcher is not compatible with Drush 12+`).
|
||||||
|
|
||||||
|
Until the parent `Makefile` is fixed, call the vendor binary directly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker exec erable-php-1 /var/www/erable/vendor/bin/drush cr
|
||||||
|
docker exec erable-php-1 /var/www/erable/vendor/bin/drush cim -y
|
||||||
|
docker exec erable-php-1 /var/www/erable/vendor/bin/drush updb -y
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside an `exec_php` shell, use `vendor/bin/drush` (or `./vendor/bin/drush` from `/var/www/erable/`) — not the bare `drush`.
|
||||||
|
|
||||||
|
### When to run drush cr
|
||||||
|
|
||||||
|
**Every Twig template edit must be followed by a cache rebuild**, otherwise Drupal keeps serving the previously rendered markup. This applies to anything under `web/themes/erabletheme/templates/` and any custom module template (e.g. `web/modules/custom/erable_mod/templates/`). SCSS/CSS changes do NOT require `drush cr` (the CSS file is served as-is by Drupal). Run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker exec erable-php-1 /var/www/erable/vendor/bin/drush cr
|
||||||
|
```
|
||||||
|
|
||||||
|
after every `.twig` edit, then hard-reload the browser.
|
||||||
|
|
||||||
|
Site URL (via reverse proxy on the host): `http://dev.erable.fr` (container exposed on `:8980`). phpMyAdmin on `http://dev.phpmyadmin.erable.fr` (`:8981`). See parent `README.md` for the nginx/apache vhost snippets.
|
||||||
|
|
||||||
|
## Theme build
|
||||||
|
|
||||||
|
The custom theme is `web/themes/erabletheme/`. SCSS is compiled to CSS via **Dart Sass** (`sass` package, not `node-sass`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd web/themes/erabletheme
|
||||||
|
npm install # first time only
|
||||||
|
npm run sass # runs: sass --watch --no-source-map scss/styles.scss:css/styles.css
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled `css/styles.css` is what Drupal serves (see `erabletheme.libraries.yml`). The watch must be running while editing SCSS, or styles won't update. If `npm run sass` ever appears to output into `scss/` itself, the script has been mis-set — the entry must be the `IN:OUT` form `scss/styles.scss:css/styles.css`, not two positional args.
|
||||||
|
|
||||||
|
Compile failures surface in the rendered page as garbage in `body::before { content: "..." }` (the Sass watcher injects the error there). To see them from a script: `curl -s http://dev.erable.fr | grep -A2 'body::before'`.
|
||||||
|
|
||||||
|
## Custom code
|
||||||
|
|
||||||
|
- **`web/themes/erabletheme/`** — base theme: `stark`. SCSS entry is `scss/styles.scss`, which imports `global/`, `partials/`, and per-page files (`_home.scss`, `_projets.scss`, `_actualites.scss`, …). Twig templates override blocks, regions, nodes and views; node templates live under `templates/node/` per content type (`actualite`, `projet`, `partenaire`, `equipe`, `ressource`, `meetup`). The single JS file is `js/erabletheme.js`. Slick carousel is vendored under `librairies/slick-1.8.1/`. Rellax is also vendored under `librairies/` (powers the parallax background).
|
||||||
|
- **`web/modules/custom/erable_mod/`** — site-specific module (routes, blocks, controllers under `src/`, templates under `templates/`). Notable: `SitesMap` block (`src/Plugin/Block/SitesMap.php`) renders project markers on the home map; its SVG marker template is at `assets/svg/point.svg` and the Twig wrapper at `templates/svg-mapsites.html.twig`.
|
||||||
|
- **`web/modules/custom/loginregisterblock/`** — combined login + register block.
|
||||||
|
|
||||||
|
## Theme design system (post-refonte)
|
||||||
|
|
||||||
|
The redesign on `refonte-graphique` introduces a token-driven design system. **All typography sizes are in `rem`**, never `px` (one exception: deliberate "magic numbers" tied to a slick-carousel structural quirk in `_carousel.scss`, commented as such).
|
||||||
|
|
||||||
|
### Tokens (single source of truth)
|
||||||
|
|
||||||
|
- `scss/global/variables/_colors.scss` — brand colors. `$fluo_green: #33ffc4` is the new accent; `$teal` is aliased to `$fluo_green` so legacy `$teal` usages propagate automatically. Other tokens: `$page_bg: #f9f9f9` (site background), `$text_grey: #4a4a49`, `$dark_green`, `$beige`. Gradient mixins `beige_gradient()` and `white_beige_gradient()` live here too.
|
||||||
|
- `scss/global/variables/_typography.scss` — font-size scale + line-heights + weights + semantic mixins. All sizes in `rem`:
|
||||||
|
- `$fs_xs: 0.8125rem` (≈13px), `$fs_sm: 0.9375rem` (≈15px), `$fs_md: 1.0625rem` (≈17px), `$fs_lg: 1.375rem` (≈22px), `$fs_xl: 2.25rem` (≈36px), `$fs_2xl: 3.5625rem` (≈57px).
|
||||||
|
- Mixins (**use these, do not hardcode font properties**): `main_title()`, `sous_titre()`, `sous_titre_alt()` (intermediate Vogun 22→36px), `main_text_content()`, `meta_text()`, `cta_text()`, `fluo_button()` (the universal CTA — propagates to `.more-link` and `footer.fluo_links` site-wide).
|
||||||
|
- `scss/global/_fonts.scss` — `@font-face` declarations. **Vogun** (titles/CTA, `fonts/Vogun/Vogun-Regular.woff2`) and **Marianne** (body) are the only typefaces. Barlow has been removed.
|
||||||
|
- `scss/global/variables/_layout.scss` — breakpoints: `$breakpoint_tablet: 760px`, `$breakpoint_desktop: 1080px`, `$breakpoint_desktop_large: 1600px`. Plus `$x_margin` for horizontal page padding.
|
||||||
|
|
||||||
|
### Responsive philosophy
|
||||||
|
|
||||||
|
Mobile-first via `vw` for proportional sizing + `max-width` (or aspect ratios) to cap on large screens. Avoid hard `px` breakpoints in component sizing — use the breakpoint variables for layout shifts only.
|
||||||
|
|
||||||
|
### Refonte branch conventions
|
||||||
|
|
||||||
|
- This is a redesign branch, so **replace** legacy code rather than stacking new styles on top. Removing the old `$teal` value, renaming Barlow out, deleting unused SVGs (e.g. `feuille.svg`) is the norm here.
|
||||||
|
- Semantic mixin names (`main_title`, `sous_titre`) are kept even when their visual content is overhauled — this preserves call-sites across all per-page SCSS files.
|
||||||
|
- It's OK to edit Twig block templates (e.g. `block--views-block--home-blocks-block-1.html.twig`, `block--sitesmap-block.html.twig`) when restructuring layouts. Avoid editing core templates like `node.html.twig` or `field--body.html.twig` to limit side effects.
|
||||||
|
- The CTA "En savoir plus" on the home is reparented by JS (see `js/erabletheme.js` — `intro.appendChild(moreLink)`) because `smart_trim` renders `.more-link` deep inside `article > div`. This is intentional and lets the separator stop at text-end instead of button-end.
|
||||||
|
|
||||||
|
### Décors parallax v2
|
||||||
|
|
||||||
|
All decor systems are JS-driven (injection + positioning + parallax where applicable). Common entry points live in `Drupal.behaviors.erabletheme.attach` inside `erabletheme.js`; positions recompute on `window.load`, `window.resize` (debounced 200ms). Body scroll is on `body` (not `html`) — see `_global.scss` (`body { overflow: scroll }`, `html { overflow: hidden }`). Rellax's `wrapper: 'body'` and the custom parallax scroll listener both depend on this.
|
||||||
|
|
||||||
|
Assets live in `assets/new-bg-shapes/` (frises, mid-left, home-diapo) and `assets/drawings/` (oiseaux, fleurs ponctuels).
|
||||||
|
|
||||||
|
1. **Side frises** (`.bgImg.decor-left/right`) — single `.decor-tile` in `page.html.twig` cloned by JS (`setupBackgroundTiles`) to fill `layout-container.offsetHeight`, then animated by Rellax (`new Rellax('.bgImg', { wrapper: 'body' })`). SCSS: `_background.scss` (positions in `%` of tile). `.decor-once` images (cut-at-top) are removed from clones.
|
||||||
|
|
||||||
|
2. **Mid-left column decors** (`.col-decor`, `mid-left-1.png` + `mid-left-2.png`) — `setupColDecor` / `positionColDecor` / `applyColDecorParallax`. Two cibles:
|
||||||
|
- `.fullpage:not(.large-container)` (internal pages): stack of up to 2 PNG at the bottom-left outer edge of the column. Sizes + gaps proportional to colH, min/max clamped. Display thresholds: 50% colH for both, 30% for one.
|
||||||
|
- `.map-projets` (home "projets" block): single PNG via `singleOnly: true`, threshold 50%.
|
||||||
|
Custom parallax (subtle upward translate, capped ±30px). Bord droit of PNG calé on bord gauche extérieur via `translateX(-100%)`. Visibility responsive: `_background.scss`.
|
||||||
|
|
||||||
|
3. **Footer ornements** (`.footer-decor`, `bird-3.png` + `flower-2.png`) — Twig in `page.html.twig` inside `<footer>`. CSS-only positioning in `_footer.scss`: `position: absolute; bottom: 100%` posed on the footer top edge. Footer raised to `z-index: 3` (cf. `_global.scss`) so the decors overlay the column bottom. `pointer-events: none`.
|
||||||
|
|
||||||
|
4. **Random page decors** (`.page-decor`) — `setupPageDecor` / `positionPageDecor`. One PNG per `.fullpage` (excluding `aside .fullpage` sidebars), tirage déterministe par colonne (index stocked on `col.dataset.pageDecorIdx`). Pool: `bird-1, bird-2, flower-1, flower-3` (flower-2 + bird-3 réservés au footer). Width by breakpoint, anchor switches to `.page-header-outside` on mobile. `flower-3` overlaps 40% inside (vs 20% default) because of thin stem.
|
||||||
|
|
||||||
|
5. **Home decors above carousel** (`.home-above-decor`, `flower-1` + `bird-1`) — `setupHomeAboveDecor`. Static (no parallax), debord `overlap` px on the carousel cards top edge, horizontal `offsetVw` from viewport edges.
|
||||||
|
|
||||||
|
6. **Home decors on carousel** (`.home-diapo-decor`, `home-diapo-left.png` + `home-diapo-right.png`) — `setupHomeDiapoDecor`. Anchored to `.slick-list` top (not `.carousel_container` to avoid catching the dots). Custom parallax shared with col-decor (same `applyColDecorParallax` siblings: `applyHomeDiapoParallax`). Width × `decorWidthScale()` (1× desktop, 1.5× tablet, 1.8× mobile).
|
||||||
|
|
||||||
|
7. **Home decors around projets block** (`.home-projet-decor`, `bird-2` + `flower-3`) — `setupHomeProjetDecor`. Anchored to `.map-projets`. `bird-2` at top-left (debord on the block left edge in desktop; `-3vw` from viewport in mobile/tablet). `flower-3` truncated by the viewport right edge (`right: -20px`), centered at 1/3 from block top. Static (no parallax).
|
||||||
|
|
||||||
|
The `decorWidthScale()` helper (mobile 1.8× / tablette 1.5× / desktop 1×) is shared by home-diapo, home-above, home-projet to keep widths visually comparable across screen sizes. Footer decors do NOT use it (pure CSS).
|
||||||
|
|
||||||
|
## Drupal configuration
|
||||||
|
|
||||||
|
- Exported config lives in `config/sync/` (and `config/tmp/`). Edits to config in the UI must be exported with `drush cex -y` and committed; conversely, after pulling config changes, run `drush cim -y` (via the vendor binary — see Drush gotcha).
|
||||||
|
- The project uses `wikimedia/composer-merge-plugin` to merge `web/profiles/drupal-starterkit-profile/composer.json` (itself a submodule from `figureslibres.io`).
|
||||||
|
- Drupal core is `^10.2`. Key contrib modules: `paragraphs`, `webform`, `panels`/`page_manager`, `geofield`+`leaflet` (project map), `smart_date`, `smart_trim` (responsible for `.more-link` rendering deep inside fields — see refonte conventions), `social_media_links`, `view_unpublished`.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- DB is auto-seeded from `${DB_IMPORTE_FILE}` (set in `.env` in the parent repo) on **first** `make up` only. To reload a dump later, copy the SQL into the mysql container and re-import manually — see "Updating manualy the mysql db" in the parent README.
|
||||||
|
- Credentials default to `root` / `erable` (DB name `erable`).
|
||||||
|
|
||||||
|
## Twig debugging
|
||||||
|
|
||||||
|
Twig theme debug is currently ON in this dev environment: every rendered region/block/field has `<!-- FILE NAME SUGGESTIONS -->` HTML comments listing candidate template names in priority order. To find which template controls a region of the page: View Source → search for the markup → read the suggestions above it. The active template is the first one that exists.
|
||||||
|
|
||||||
|
## Patterns to know
|
||||||
|
|
||||||
|
- Do not commit `web/sites/default/settings.local.php`, `salt.txt`, or the `files/` directory — they are environment-specific (copies of templates live in parent `ressources/drupal/`).
|
||||||
|
- The `php` container runs as the **host user** (USER_UID/USER_GID baked at build time) so file ownership stays clean. If you re-`make build` on a different machine, files written by the container will switch UID.
|
||||||
|
- xdebug is wired (see `Docker/php-8.1-fpm/` and the `XDEBUG_INI` mount). `host.docker.internal` resolves to the host on Linux via the `extra_hosts` mapping.
|
||||||
|
- Penpot is the design source of truth for the refonte. Frames of interest in the connected file: `1 - accueil - integration` (id `8997888b-7408-8014-8008-0cf456861e09`) for the home, and `3 - Single projet` (id `cb2f4013-441f-800a-8007-d81a04408009`) for the side-decor reference. Use `mcp__penpot__export_shape` to pull PNG/SVG references.
|
||||||
BIN
web/themes/erabletheme/assets/drawings/bird-1.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
web/themes/erabletheme/assets/drawings/bird-2.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
web/themes/erabletheme/assets/drawings/bird-3.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
web/themes/erabletheme/assets/drawings/flower-1.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
web/themes/erabletheme/assets/drawings/flower-2.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
web/themes/erabletheme/assets/drawings/flower-3.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
web/themes/erabletheme/assets/new-bg-shapes/home-diapo-left.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
web/themes/erabletheme/assets/new-bg-shapes/home-diapo-right.png
Normal file
|
After Width: | Height: | Size: 295 KiB |
@@ -79,7 +79,7 @@ body .layout-container main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body .layout-container footer {
|
body .layout-container footer {
|
||||||
z-index: 0;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
body .layout-container #background {
|
body .layout-container #background {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
@@ -399,6 +399,7 @@ footer.fluo_links p {
|
|||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
padding: 1rem 3vw;
|
padding: 1rem 3vw;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
@media (min-width: 1080px) {
|
@media (min-width: 1080px) {
|
||||||
.layout-container > footer {
|
.layout-container > footer {
|
||||||
@@ -407,6 +408,33 @@ footer.fluo_links p {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.layout-container > footer > .footer-decor {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
width: 18vw;
|
||||||
|
height: auto;
|
||||||
|
transform: translateY(15%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@media (min-width: 1080px) {
|
||||||
|
.layout-container > footer > .footer-decor {
|
||||||
|
width: 8vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout-container > footer > .footer-decor-bird {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.layout-container > footer > .footer-decor-flower {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
@media (min-width: 1080px) {
|
||||||
|
.layout-container > footer > .footer-decor-bird {
|
||||||
|
left: -4vw;
|
||||||
|
}
|
||||||
|
.layout-container > footer > .footer-decor-flower {
|
||||||
|
right: -4vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
.layout-container > footer #footer_middle {
|
.layout-container > footer #footer_middle {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1182,11 +1210,12 @@ main.main-login .login > div > div:not(.hidden) form .button:hover, main.main-lo
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 5vh;
|
||||||
}
|
}
|
||||||
@media (min-width: 1080px) {
|
@media (min-width: 1080px) {
|
||||||
.map-projets-section {
|
.map-projets-section {
|
||||||
margin-top: 10vh;
|
margin-top: 10vh;
|
||||||
margin-bottom: 10vh;
|
margin-bottom: 4vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,6 +1470,11 @@ main:not(:has(#block-erabletheme-leprogramme-2)):not(:has(#block-erabletheme-vie
|
|||||||
border-bottom: 7px solid #33ffc4;
|
border-bottom: 7px solid #33ffc4;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 1080px) {
|
||||||
|
.layout-content .fullpage {
|
||||||
|
margin-bottom: 4vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
.layout-content .fullpage h2 {
|
.layout-content .fullpage h2 {
|
||||||
margin-top: 5vh !important;
|
margin-top: 5vh !important;
|
||||||
margin-bottom: 7vh;
|
margin-bottom: 7vh;
|
||||||
@@ -1492,7 +1526,7 @@ main:not(:has(#block-erabletheme-leprogramme-2)):not(:has(#block-erabletheme-vie
|
|||||||
color: #4a4a49;
|
color: #4a4a49;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
.layout-content .fullpage .fullpage_content p:first-of-type {
|
.layout-content .fullpage .fullpage_content :is(p, h1, h2, h3, h4, h5, h6):first-child {
|
||||||
margin-top: 7vh;
|
margin-top: 7vh;
|
||||||
}
|
}
|
||||||
.layout-content .fullpage .fullpage_content .liens_fixed > div > div, .layout-content .fullpage .fullpage_content .file_fixed > div > div,
|
.layout-content .fullpage .fullpage_content .liens_fixed > div > div, .layout-content .fullpage .fullpage_content .file_fixed > div > div,
|
||||||
|
|||||||
@@ -440,14 +440,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Décors mid-left : 0, 1 ou 2 PNG empilés au pied gauche extérieur
|
// Décors mid-left (col-decor) : PNG empilés au pied gauche
|
||||||
// de chaque .fullpage interne (pages non-index). Tailles et gaps
|
// extérieur d'une colonne. Position absolue dans .layout-container,
|
||||||
// proportionnels à colH, bornés min/max. On n'affiche un motif
|
|
||||||
// que si la pile tient sous 50% colH (les deux) ou 30% (un seul),
|
|
||||||
// sinon la forme déborderait dans le contenu en haut.
|
|
||||||
// Position absolue calculée en px dans .layout-container ;
|
|
||||||
// bord droit du PNG calé sur bord gauche colonne via
|
// bord droit du PNG calé sur bord gauche colonne via
|
||||||
// translateX(-100%). Parallax custom plus bas (sans Rellax).
|
// translateX(-100%). Tailles + gaps proportionnels à colH (bornés
|
||||||
|
// min/max). Affichage conditionnel : seuils 50%/30% colH.
|
||||||
|
// - .fullpage:not(.large-container) : pile de 2 (mid-left-1+2).
|
||||||
|
// - .map-projets (home) : 1 seul (mid-left-1) via singleOnly.
|
||||||
|
// Parallax custom (sans Rellax) plus bas.
|
||||||
// Visibilité responsive : _background.scss.
|
// Visibilité responsive : _background.scss.
|
||||||
//
|
//
|
||||||
const layoutContainer = document.querySelector('.layout-container');
|
const layoutContainer = document.querySelector('.layout-container');
|
||||||
@@ -455,6 +455,17 @@
|
|||||||
|
|
||||||
function clamp(min, val, max) { return Math.max(min, Math.min(val, max)); }
|
function clamp(min, val, max) { return Math.max(min, Math.min(val, max)); }
|
||||||
|
|
||||||
|
// Coefficient appliqué aux décors home (oiseaux + fleurs) hors
|
||||||
|
// desktop : on grossit les images pour qu'elles restent lisibles
|
||||||
|
// sur écrans étroits (le viewport rétrécit mais les éléments
|
||||||
|
// décoratifs en vw deviennent visuellement trop petits).
|
||||||
|
function decorWidthScale() {
|
||||||
|
const w = window.innerWidth;
|
||||||
|
if (w < 760) return 1.8; // mobile
|
||||||
|
if (w < 1080) return 1.5; // tablette
|
||||||
|
return 1; // desktop
|
||||||
|
}
|
||||||
|
|
||||||
function colDecorMetrics(colH) {
|
function colDecorMetrics(colH) {
|
||||||
return {
|
return {
|
||||||
gapBottom: clamp(120, 0.08 * colH, 320),
|
gapBottom: clamp(120, 0.08 * colH, 320),
|
||||||
@@ -478,6 +489,7 @@
|
|||||||
if (!layoutContainer) return;
|
if (!layoutContainer) return;
|
||||||
layoutContainer.querySelectorAll('.col-decor').forEach(n => n.remove());
|
layoutContainer.querySelectorAll('.col-decor').forEach(n => n.remove());
|
||||||
colDecorState.length = 0;
|
colDecorState.length = 0;
|
||||||
|
// Colonnes .fullpage internes : pile de 2 PNG (mid-left-1 et -2).
|
||||||
document.querySelectorAll('.fullpage:not(.large-container)').forEach(col => {
|
document.querySelectorAll('.fullpage:not(.large-container)').forEach(col => {
|
||||||
const img1 = createColDecorImg(1);
|
const img1 = createColDecorImg(1);
|
||||||
const img2 = createColDecorImg(2);
|
const img2 = createColDecorImg(2);
|
||||||
@@ -485,6 +497,14 @@
|
|||||||
layoutContainer.appendChild(img2);
|
layoutContainer.appendChild(img2);
|
||||||
colDecorState.push({ img1, img2, col });
|
colDecorState.push({ img1, img2, col });
|
||||||
});
|
});
|
||||||
|
// Bloc .map-projets (home) : un seul PNG via singleOnly.
|
||||||
|
document.querySelectorAll('.map-projets').forEach(col => {
|
||||||
|
const img1 = createColDecorImg(1);
|
||||||
|
const img2 = createColDecorImg(2);
|
||||||
|
layoutContainer.appendChild(img1);
|
||||||
|
layoutContainer.appendChild(img2);
|
||||||
|
colDecorState.push({ img1, img2, col, singleOnly: true });
|
||||||
|
});
|
||||||
positionColDecor();
|
positionColDecor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,36 +514,44 @@
|
|||||||
const lcTopAbs = lcRect.top + window.scrollY;
|
const lcTopAbs = lcRect.top + window.scrollY;
|
||||||
const lcLeftAbs = lcRect.left + window.scrollX;
|
const lcLeftAbs = lcRect.left + window.scrollX;
|
||||||
for (const entry of colDecorState) {
|
for (const entry of colDecorState) {
|
||||||
const { img1, img2, col } = entry;
|
const { img1, img2, col, singleOnly } = entry;
|
||||||
const r = col.getBoundingClientRect();
|
const r = col.getBoundingClientRect();
|
||||||
const colH = r.height;
|
const colH = r.height;
|
||||||
const m = colDecorMetrics(colH);
|
const m = colDecorMetrics(colH);
|
||||||
const stack2H = m.gapBottom + m.h2;
|
const stack1H = m.gapBottom + m.h2 + m.gapBetween + m.h1;
|
||||||
const stack1H = stack2H + m.gapBetween + m.h1;
|
// singleOnly : un seul motif (img1) si la place le permet
|
||||||
const showBoth = stack1H <= colH * 0.5;
|
// (= gapBottom + h1 sous 50% colH).
|
||||||
const showOne = !showBoth && stack2H <= colH * 0.3;
|
const showBoth = !singleOnly && stack1H <= colH * 0.5;
|
||||||
img1.style.display = showBoth ? 'block' : 'none';
|
const showOne = !showBoth && (m.gapBottom + m.h2) <= colH * 0.3;
|
||||||
img2.style.display = (showBoth || showOne) ? 'block' : 'none';
|
const showSingle = singleOnly && (m.gapBottom + m.h1) <= colH * 0.5;
|
||||||
entry.visible1 = showBoth;
|
entry.visible1 = showBoth || showSingle;
|
||||||
entry.visible2 = showBoth || showOne;
|
entry.visible2 = !singleOnly && (showBoth || showOne);
|
||||||
if (!showBoth && !showOne) continue;
|
img1.style.display = entry.visible1 ? 'block' : 'none';
|
||||||
|
img2.style.display = entry.visible2 ? 'block' : 'none';
|
||||||
|
if (!entry.visible1 && !entry.visible2) continue;
|
||||||
const colBottomAbs = r.bottom + window.scrollY;
|
const colBottomAbs = r.bottom + window.scrollY;
|
||||||
const leftRel = (r.left + window.scrollX) - lcLeftAbs;
|
const leftRel = (r.left + window.scrollX) - lcLeftAbs;
|
||||||
const top2 = colBottomAbs - m.gapBottom - m.h2;
|
if (singleOnly) {
|
||||||
img2.style.top = (top2 - lcTopAbs) + 'px';
|
const top1 = colBottomAbs - m.gapBottom - m.h1;
|
||||||
img2.style.left = leftRel + 'px';
|
|
||||||
img2.style.height = m.h2 + 'px';
|
|
||||||
// Référence scroll : motif centré dans le viewport.
|
|
||||||
entry.scrollRef2 = top2 + m.h2 / 2 - window.innerHeight / 2;
|
|
||||||
if (showBoth) {
|
|
||||||
const top1 = top2 - m.gapBetween - m.h1;
|
|
||||||
img1.style.top = (top1 - lcTopAbs) + 'px';
|
img1.style.top = (top1 - lcTopAbs) + 'px';
|
||||||
img1.style.left = leftRel + 'px';
|
img1.style.left = leftRel + 'px';
|
||||||
img1.style.height = m.h1 + 'px';
|
img1.style.height = m.h1 + 'px';
|
||||||
entry.scrollRef1 = top1 + m.h1 / 2 - window.innerHeight / 2;
|
entry.scrollRef1 = top1 + m.h1 / 2 - window.innerHeight / 2;
|
||||||
|
} else {
|
||||||
|
const top2 = colBottomAbs - m.gapBottom - m.h2;
|
||||||
|
img2.style.top = (top2 - lcTopAbs) + 'px';
|
||||||
|
img2.style.left = leftRel + 'px';
|
||||||
|
img2.style.height = m.h2 + 'px';
|
||||||
|
entry.scrollRef2 = top2 + m.h2 / 2 - window.innerHeight / 2;
|
||||||
|
if (showBoth) {
|
||||||
|
const top1 = top2 - m.gapBetween - m.h1;
|
||||||
|
img1.style.top = (top1 - lcTopAbs) + 'px';
|
||||||
|
img1.style.left = leftRel + 'px';
|
||||||
|
img1.style.height = m.h1 + 'px';
|
||||||
|
entry.scrollRef1 = top1 + m.h1 / 2 - window.innerHeight / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Premier rendu du parallax après (re)position.
|
|
||||||
applyColDecorParallax();
|
applyColDecorParallax();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +587,7 @@
|
|||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
colDecorRafPending = false;
|
colDecorRafPending = false;
|
||||||
applyColDecorParallax();
|
applyColDecorParallax();
|
||||||
|
applyHomeDiapoParallax();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!document.body.dataset.colDecorScrollWired) {
|
if (!document.body.dataset.colDecorScrollWired) {
|
||||||
@@ -566,6 +595,279 @@
|
|||||||
document.body.dataset.colDecorScrollWired = 'true';
|
document.body.dataset.colDecorScrollWired = 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Décor aléatoire en haut à droite de chaque .fullpage. Tirage
|
||||||
|
// parmi drawings/ (excluant flower-2 + bird-3 déjà au footer).
|
||||||
|
// Bas de l'image overlap le haut de l'ancre (20%, sauf flower-3
|
||||||
|
// à 40% car tige fine). Mobile : ancre = .page-header-outside.
|
||||||
|
// Largeur par breakpoint.
|
||||||
|
//
|
||||||
|
const PAGE_DECOR_POOL = ['bird-1.png', 'bird-2.png', 'flower-1.png', 'flower-3.png'];
|
||||||
|
const pageDecorState = [];
|
||||||
|
|
||||||
|
function pageDecorWidthVw() {
|
||||||
|
const w = window.innerWidth;
|
||||||
|
if (w < 1080) return 22; // mobile + tablette
|
||||||
|
return 10; // desktop
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPageDecor() {
|
||||||
|
if (!layoutContainer) return;
|
||||||
|
layoutContainer.querySelectorAll('.page-decor').forEach(n => n.remove());
|
||||||
|
pageDecorState.length = 0;
|
||||||
|
document.querySelectorAll('.fullpage').forEach(col => {
|
||||||
|
// Exclure les .fullpage situées dans une sidebar (vue projets
|
||||||
|
// imbriquée). On ne décore que les colonnes de contenu.
|
||||||
|
if (col.closest('aside')) return;
|
||||||
|
// Tirage déterministe par colonne : on stocke l'index sur le
|
||||||
|
// dataset pour qu'un resize ne change pas l'image affichée.
|
||||||
|
let idx = col.dataset.pageDecorIdx;
|
||||||
|
if (idx == null) {
|
||||||
|
idx = Math.floor(Math.random() * PAGE_DECOR_POOL.length);
|
||||||
|
col.dataset.pageDecorIdx = String(idx);
|
||||||
|
} else {
|
||||||
|
idx = parseInt(idx, 10);
|
||||||
|
}
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = 'page-decor';
|
||||||
|
img.setAttribute('aria-hidden', 'true');
|
||||||
|
img.setAttribute('alt', '');
|
||||||
|
img.src = `/themes/erabletheme/assets/drawings/${PAGE_DECOR_POOL[idx]}`;
|
||||||
|
img.style.cssText = 'position:absolute;width:auto;height:auto;pointer-events:none;z-index:0;';
|
||||||
|
layoutContainer.appendChild(img);
|
||||||
|
pageDecorState.push({ img, col });
|
||||||
|
});
|
||||||
|
positionPageDecor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionPageDecor() {
|
||||||
|
if (!pageDecorState.length) return;
|
||||||
|
const lcRect = layoutContainer.getBoundingClientRect();
|
||||||
|
const lcTopAbs = lcRect.top + window.scrollY;
|
||||||
|
const lcLeftAbs = lcRect.left + window.scrollX;
|
||||||
|
const widthVw = pageDecorWidthVw();
|
||||||
|
const widthPx = (widthVw / 100) * window.innerWidth;
|
||||||
|
const isMobile = window.innerWidth < 760;
|
||||||
|
for (const entry of pageDecorState) {
|
||||||
|
const img = entry.img;
|
||||||
|
if (!img.naturalWidth) {
|
||||||
|
img.addEventListener('load', positionPageDecor, { once: true });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const heightPx = widthPx * (img.naturalHeight / img.naturalWidth);
|
||||||
|
// Mobile : ancre = .page-header-outside (titre/retour) si présent,
|
||||||
|
// sinon la colonne elle-même.
|
||||||
|
let anchorEl = entry.col;
|
||||||
|
if (isMobile) {
|
||||||
|
const layoutContent = entry.col.closest('.layout-content');
|
||||||
|
const phOutside = layoutContent && layoutContent.querySelector('.page-header-outside');
|
||||||
|
if (phOutside) anchorEl = phOutside;
|
||||||
|
}
|
||||||
|
const r = anchorEl.getBoundingClientRect();
|
||||||
|
const anchorTopAbs = r.top + window.scrollY;
|
||||||
|
const anchorRightAbs = r.right + window.scrollX;
|
||||||
|
// flower-3 a une tige fine en haut → on la pousse plus dans
|
||||||
|
// l'ancre (40%) pour qu'elle ne soit pas perdue dans la marge.
|
||||||
|
const overlapRatio = img.src.includes('flower-3') ? 0.4 : 0.2;
|
||||||
|
const top = anchorTopAbs + heightPx * overlapRatio - heightPx;
|
||||||
|
const left = anchorRightAbs - widthPx * 0.7; // 30% dépasse à droite
|
||||||
|
img.style.width = widthPx + 'px';
|
||||||
|
img.style.top = (top - lcTopAbs) + 'px';
|
||||||
|
img.style.left = (left - lcLeftAbs) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Décors home-diapo-{right,left} : ancrés aux bords droit/gauche
|
||||||
|
// du viewport, leur haut dépassant au-dessus du haut des cards
|
||||||
|
// du carousel home (overlapTop px). Parallax custom.
|
||||||
|
//
|
||||||
|
const HOME_DIAPO_CONFIGS = [
|
||||||
|
{ side: 'right', file: 'home-diapo-right.png', widthVw: 30, overlapTop: 160 },
|
||||||
|
{ side: 'left', file: 'home-diapo-left.png', widthVw: 28, overlapTop: 80 },
|
||||||
|
];
|
||||||
|
const homeDiapoState = [];
|
||||||
|
|
||||||
|
// Ancre cible : .slick-list (cards visibles), avec fallbacks.
|
||||||
|
// .carousel_container inclut les dots → bas trop bas, à éviter.
|
||||||
|
function getCarouselCardsBox() {
|
||||||
|
return document.querySelector('.carousel_container .slick-list')
|
||||||
|
|| document.querySelector('.carousel_container .slick-container')
|
||||||
|
|| document.querySelector('.carousel_container');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupHomeDiapoDecor() {
|
||||||
|
if (!layoutContainer) return;
|
||||||
|
layoutContainer.querySelectorAll('.home-diapo-decor').forEach(n => n.remove());
|
||||||
|
homeDiapoState.length = 0;
|
||||||
|
const cardsBox = getCarouselCardsBox();
|
||||||
|
if (!cardsBox) return;
|
||||||
|
for (const cfg of HOME_DIAPO_CONFIGS) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = `home-diapo-decor home-diapo-decor-${cfg.side}`;
|
||||||
|
img.setAttribute('aria-hidden', 'true');
|
||||||
|
img.setAttribute('alt', '');
|
||||||
|
img.src = `/themes/erabletheme/assets/new-bg-shapes/${cfg.file}`;
|
||||||
|
img.style.cssText = `position:absolute;${cfg.side}:0;width:auto;height:auto;pointer-events:none;z-index:0;`;
|
||||||
|
layoutContainer.appendChild(img);
|
||||||
|
homeDiapoState.push({ img, widthVw: cfg.widthVw, overlapTop: cfg.overlapTop, cardsBox });
|
||||||
|
}
|
||||||
|
positionHomeDiapoDecor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionHomeDiapoDecor() {
|
||||||
|
if (!homeDiapoState.length) return;
|
||||||
|
const lcRect = layoutContainer.getBoundingClientRect();
|
||||||
|
const lcTopAbs = lcRect.top + window.scrollY;
|
||||||
|
const scale = decorWidthScale();
|
||||||
|
for (const entry of homeDiapoState) {
|
||||||
|
const widthPx = (entry.widthVw / 100) * window.innerWidth * scale;
|
||||||
|
const cRect = entry.cardsBox.getBoundingClientRect();
|
||||||
|
const cardsTopAbs = cRect.top + window.scrollY;
|
||||||
|
// Top = haut des cards - overlapTop : le haut de l'image
|
||||||
|
// dépasse de cette valeur au-dessus du haut des cards.
|
||||||
|
const top = cardsTopAbs - entry.overlapTop;
|
||||||
|
entry.img.style.width = widthPx + 'px';
|
||||||
|
entry.img.style.top = (top - lcTopAbs) + 'px';
|
||||||
|
entry.scrollRef = top - window.innerHeight / 2;
|
||||||
|
}
|
||||||
|
applyHomeDiapoParallax();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHomeDiapoParallax() {
|
||||||
|
if (!homeDiapoState.length) return;
|
||||||
|
const scrollY = window.scrollY || document.body.scrollTop || 0;
|
||||||
|
for (const { img, scrollRef } of homeDiapoState) {
|
||||||
|
if (scrollRef == null) continue;
|
||||||
|
const dY = clamp(-COL_DECOR_PARALLAX_AMP,
|
||||||
|
-(scrollY - scrollRef) * COL_DECOR_PARALLAX_SPEED,
|
||||||
|
COL_DECOR_PARALLAX_AMP);
|
||||||
|
img.style.transform = `translate3d(0, ${dY}px, 0)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Décors home-above : flower-1 (gauche) et bird-1 (droite),
|
||||||
|
// au-dessus des cards, leur bas débordant de `overlap` px sur le
|
||||||
|
// haut des cards. offsetVw : décalage horizontal vers l'intérieur.
|
||||||
|
// Pas de parallax.
|
||||||
|
//
|
||||||
|
const HOME_ABOVE_CONFIGS = [
|
||||||
|
{ side: 'left', file: 'drawings/flower-1.png', widthVw: 15, overlap: 40, offsetVw: 8 },
|
||||||
|
{ side: 'right', file: 'drawings/bird-1.png', widthVw: 15, overlap: 40, offsetVw: 4 },
|
||||||
|
];
|
||||||
|
const homeAboveState = [];
|
||||||
|
|
||||||
|
function setupHomeAboveDecor() {
|
||||||
|
if (!layoutContainer) return;
|
||||||
|
layoutContainer.querySelectorAll('.home-above-decor').forEach(n => n.remove());
|
||||||
|
homeAboveState.length = 0;
|
||||||
|
const cardsBox = getCarouselCardsBox();
|
||||||
|
if (!cardsBox) return;
|
||||||
|
for (const cfg of HOME_ABOVE_CONFIGS) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = `home-above-decor home-above-decor-${cfg.side}`;
|
||||||
|
img.setAttribute('aria-hidden', 'true');
|
||||||
|
img.setAttribute('alt', '');
|
||||||
|
img.src = `/themes/erabletheme/assets/${cfg.file}`;
|
||||||
|
img.style.cssText = `position:absolute;${cfg.side}:${cfg.offsetVw}vw;width:auto;height:auto;pointer-events:none;z-index:0;`;
|
||||||
|
layoutContainer.appendChild(img);
|
||||||
|
homeAboveState.push({ img, widthVw: cfg.widthVw, overlap: cfg.overlap, cardsBox });
|
||||||
|
}
|
||||||
|
positionHomeAboveDecor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionHomeAboveDecor() {
|
||||||
|
if (!homeAboveState.length) return;
|
||||||
|
const lcRect = layoutContainer.getBoundingClientRect();
|
||||||
|
const lcTopAbs = lcRect.top + window.scrollY;
|
||||||
|
const lcHeight = lcRect.height;
|
||||||
|
const scale = decorWidthScale();
|
||||||
|
for (const entry of homeAboveState) {
|
||||||
|
const widthPx = (entry.widthVw / 100) * window.innerWidth * scale;
|
||||||
|
const cRect = entry.cardsBox.getBoundingClientRect();
|
||||||
|
const cardsTopAbs = cRect.top + window.scrollY;
|
||||||
|
// Le BAS de l'image est à `overlap` px sous le haut des cards :
|
||||||
|
// l'image est essentiellement au-dessus du carousel, débord en bas.
|
||||||
|
const imgBottom = cardsTopAbs + entry.overlap;
|
||||||
|
const bottomFromLcBottom = (lcTopAbs + lcHeight) - imgBottom;
|
||||||
|
entry.img.style.width = widthPx + 'px';
|
||||||
|
entry.img.style.bottom = bottomFromLcBottom + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Décors home-projet, ancrés au bloc .map-projets :
|
||||||
|
// - bird-2 en haut à gauche, débordant à gauche du bloc en
|
||||||
|
// desktop, ou collé à -3vw du viewport en mobile/tablette.
|
||||||
|
// - flower-3 collée au bord droit du viewport (tronquée), centre
|
||||||
|
// vertical à 1/3 depuis le haut du bloc.
|
||||||
|
// Pas de parallax.
|
||||||
|
//
|
||||||
|
const HOME_PROJET_CONFIGS = [
|
||||||
|
{ file: 'drawings/bird-2.png', widthVw: 15, anchor: 'block-left-top' },
|
||||||
|
{ file: 'drawings/flower-3.png', widthVw: 7, anchor: 'viewport-right' },
|
||||||
|
];
|
||||||
|
const homeProjetState = [];
|
||||||
|
|
||||||
|
function setupHomeProjetDecor() {
|
||||||
|
if (!layoutContainer) return;
|
||||||
|
layoutContainer.querySelectorAll('.home-projet-decor').forEach(n => n.remove());
|
||||||
|
homeProjetState.length = 0;
|
||||||
|
const block = document.querySelector('.map-projets');
|
||||||
|
if (!block) return;
|
||||||
|
for (const cfg of HOME_PROJET_CONFIGS) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = `home-projet-decor home-projet-decor-${cfg.anchor}`;
|
||||||
|
img.setAttribute('aria-hidden', 'true');
|
||||||
|
img.setAttribute('alt', '');
|
||||||
|
img.src = `/themes/erabletheme/assets/${cfg.file}`;
|
||||||
|
img.style.cssText = 'position:absolute;width:auto;height:auto;pointer-events:none;z-index:0;';
|
||||||
|
layoutContainer.appendChild(img);
|
||||||
|
homeProjetState.push({ img, anchor: cfg.anchor, widthVw: cfg.widthVw, block });
|
||||||
|
}
|
||||||
|
positionHomeProjetDecor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionHomeProjetDecor() {
|
||||||
|
if (!homeProjetState.length) return;
|
||||||
|
const lcRect = layoutContainer.getBoundingClientRect();
|
||||||
|
const lcTopAbs = lcRect.top + window.scrollY;
|
||||||
|
const lcLeftAbs = lcRect.left + window.scrollX;
|
||||||
|
const scale = decorWidthScale();
|
||||||
|
for (const entry of homeProjetState) {
|
||||||
|
const img = entry.img;
|
||||||
|
if (!img.naturalWidth) {
|
||||||
|
img.addEventListener('load', positionHomeProjetDecor, { once: true });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const widthPx = (entry.widthVw / 100) * window.innerWidth * scale;
|
||||||
|
const heightPx = widthPx * (img.naturalHeight / img.naturalWidth);
|
||||||
|
const bRect = entry.block.getBoundingClientRect();
|
||||||
|
const blockTopAbs = bRect.top + window.scrollY;
|
||||||
|
const blockLeftAbs = bRect.left + window.scrollX;
|
||||||
|
const blockHeightPx = bRect.height;
|
||||||
|
img.style.width = widthPx + 'px';
|
||||||
|
if (entry.anchor === 'block-left-top') {
|
||||||
|
// bird-2 remontée de 0.6× sa hauteur au-dessus du bloc.
|
||||||
|
const top = blockTopAbs - heightPx * 0.6;
|
||||||
|
if (window.innerWidth < 1080) {
|
||||||
|
img.style.left = '-3vw';
|
||||||
|
} else {
|
||||||
|
img.style.left = (blockLeftAbs - widthPx * 0.5 - lcLeftAbs) + 'px';
|
||||||
|
}
|
||||||
|
img.style.top = (top - lcTopAbs) + 'px';
|
||||||
|
img.style.right = '';
|
||||||
|
} else if (entry.anchor === 'viewport-right') {
|
||||||
|
// flower-3 : tronquée par le bord droit du viewport.
|
||||||
|
const top = blockTopAbs + blockHeightPx / 3 - heightPx / 2;
|
||||||
|
img.style.right = '-20px';
|
||||||
|
img.style.left = '';
|
||||||
|
img.style.top = (top - lcTopAbs) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Décors latéraux parallax v2.
|
// Décors latéraux parallax v2.
|
||||||
// Cloner le .decor-tile autant de fois que nécessaire pour couvrir
|
// Cloner le .decor-tile autant de fois que nécessaire pour couvrir
|
||||||
@@ -618,10 +920,14 @@
|
|||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
setupBackgroundTiles();
|
setupBackgroundTiles();
|
||||||
setupColDecor();
|
setupColDecor();
|
||||||
|
setupHomeDiapoDecor();
|
||||||
|
setupHomeAboveDecor();
|
||||||
|
setupHomeProjetDecor();
|
||||||
|
setupPageDecor();
|
||||||
initRellax();
|
initRellax();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resize debouncé : recalcule le nombre de tiles + col-decor,
|
// Resize debouncé : recalcule le nombre de tiles + tous les décors,
|
||||||
// repart Rellax propre.
|
// repart Rellax propre.
|
||||||
let bgResizeTimer = null;
|
let bgResizeTimer = null;
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
@@ -629,6 +935,10 @@
|
|||||||
bgResizeTimer = setTimeout(() => {
|
bgResizeTimer = setTimeout(() => {
|
||||||
setupBackgroundTiles();
|
setupBackgroundTiles();
|
||||||
setupColDecor();
|
setupColDecor();
|
||||||
|
setupHomeDiapoDecor();
|
||||||
|
setupHomeAboveDecor();
|
||||||
|
setupHomeProjetDecor();
|
||||||
|
setupPageDecor();
|
||||||
initRellax();
|
initRellax();
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -168,6 +168,9 @@ main:has(#block-erabletheme-views-block-projets-block-1) {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
border-bottom: 7px solid $fluo_green;
|
border-bottom: 7px solid $fluo_green;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@media (min-width: $breakpoint_desktop) {
|
||||||
|
margin-bottom: 4vh;
|
||||||
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 5vh !important;
|
margin-top: 5vh !important;
|
||||||
margin-bottom: 7vh;
|
margin-bottom: 7vh;
|
||||||
@@ -201,7 +204,12 @@ main:has(#block-erabletheme-views-block-projets-block-1) {
|
|||||||
@include main_text_content();
|
@include main_text_content();
|
||||||
}
|
}
|
||||||
|
|
||||||
p:first-of-type {
|
// Margin-top sur le tout premier élément textuel du contenu,
|
||||||
|
// peu importe son type (p / h1-h6) ET peu importe la profondeur
|
||||||
|
// de wrapping (Drupal wrappe souvent les champs dans une ou deux
|
||||||
|
// <div>). :first-child garantit qu'on ne matche que le tout
|
||||||
|
// premier (vs :first-of-type qui matche le 1er de chaque type).
|
||||||
|
:is(p, h1, h2, h3, h4, h5, h6):first-child {
|
||||||
margin-top: 7vh;
|
margin-top: 7vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,9 +146,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 5vh;
|
||||||
@media (min-width: $breakpoint_desktop) {
|
@media (min-width: $breakpoint_desktop) {
|
||||||
margin-top: 10vh;
|
margin-top: 10vh;
|
||||||
margin-bottom: 10vh;
|
margin-bottom: 4vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
footer {
|
footer {
|
||||||
z-index: 0;
|
// Au-dessus de main pour que les ornements posés sur le footer
|
||||||
|
// (cf. .footer-decor) passent par-dessus le bas de la colonne.
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
#background {
|
#background {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
|||||||
@@ -19,12 +19,38 @@
|
|||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
padding: 1rem $x_margin;
|
padding: 1rem $x_margin;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
@media (min-width: $breakpoint_desktop) {
|
@media (min-width: $breakpoint_desktop) {
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
margin-left: 25vw;
|
margin-left: 25vw;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ornements décoratifs (oiseau / fleur) "posés" sur le bord haut du
|
||||||
|
// footer : bord bas légèrement entré dans le footer, le reste dépasse
|
||||||
|
// au-dessus. En desktop le footer fait 50vw centré ; les ornements
|
||||||
|
// sont à cheval sur les bords du footer (moitié dedans, moitié hors).
|
||||||
|
// Le footer est passé au-dessus de main (cf. _global.scss) pour que
|
||||||
|
// les ornements recouvrent le bas de la colonne.
|
||||||
|
// pointer-events:none pour ne pas bloquer les liens du footer.
|
||||||
|
> .footer-decor {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
width: 18vw;
|
||||||
|
height: auto;
|
||||||
|
transform: translateY(15%);
|
||||||
|
pointer-events: none;
|
||||||
|
@media (min-width: $breakpoint_desktop) {
|
||||||
|
width: 8vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .footer-decor-bird { left: 0; }
|
||||||
|
> .footer-decor-flower { right: 0; }
|
||||||
|
@media (min-width: $breakpoint_desktop) {
|
||||||
|
> .footer-decor-bird { left: -4vw; }
|
||||||
|
> .footer-decor-flower { right: -4vw; }
|
||||||
|
}
|
||||||
|
|
||||||
#footer_middle {
|
#footer_middle {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -86,6 +86,10 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer role="contentinfo">
|
<footer role="contentinfo">
|
||||||
|
{# Ornements décoratifs au-dessus du footer (à cheval entre fullpage
|
||||||
|
et footer). Purement visuels. #}
|
||||||
|
<img class="footer-decor footer-decor-bird" src="/themes/erabletheme/assets/drawings/bird-3.png" aria-hidden="true" alt="">
|
||||||
|
<img class="footer-decor footer-decor-flower" src="/themes/erabletheme/assets/drawings/flower-2.png" aria-hidden="true" alt="">
|
||||||
<section id="footer_top">{{ page.footer_top }}</section>
|
<section id="footer_top">{{ page.footer_top }}</section>
|
||||||
<section id="footer_middle">
|
<section id="footer_middle">
|
||||||
<section id="footer_left">{{ page.footer_left }}</section>
|
<section id="footer_left">{{ page.footer_left }}</section>
|
||||||
|
|||||||