/* === SWWS — Featured gallery (B&W default, color on hover) ============ / / Cible le nouveau bloc / #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card__cover { / N&B propre, un peu d’intensité / filter: grayscale(1) contrast(1.08) brightness(0.96); transition: filter .45s ease, transform .35s ease; will-change: filter, transform; } / Couleur + léger zoom au hover/focus (desktop & clavier) / @media (hover:hover) { #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card.gallery:hover .notion-collection-card__cover, #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card.gallery:focus-within .notion-collection-card__cover { filter: grayscale(0) contrast(1.02) brightness(1.0); transform: scale(1.03); } } / Mobile/tactile : pas de zoom (évite les sauts), couleur au tap (active) / @media (hover:none) { #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card.gallery:active .notion-collection-card__cover { filter: grayscale(0) contrast(1.02) brightness(1.0); } } / Option: ajout d'un grain très léger pour un rendu éditorial (facultatif) / #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card__cover::after { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px); background-size: 2px 2px; / grain fin / mix-blend-mode: overlay; opacity: .25; transition: opacity .35s ease; } @media (hover:hover) { #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card.gallery:hover .notion-collection-card__cover::after { opacity: .12; / un peu moins de grain en couleur / } } / Accessibilité: si l’utilisateur préfère moins d’animations / @media (prefers-reduced-motion: reduce) { #block-29a95fdcc23e80c9abecc3777b9f2655 .notion-collection-card__cover { transition: none; } } / === SWWS — Titres des cartes Notion : fond blanc + hover jaune ============ / .notion-property.notion-property__title.notion-collection-card__property.title.notion-semantic-string { background: #fff; / fond blanc / color: #000; / texte noir / padding: 0px 012px; display: inline-block; border-radius: 0px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: background 0.3s ease, color 0.3s ease, box-shadow 0.3s ease; } / === SWWS — YOYO Horizontal Gallery (aspect ratio edition) === / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection__header-wrapper { margin-bottom: 12px; } / Rail horizontal / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery { display: grid !important; grid-auto-flow: column; grid-auto-columns: minmax(180px, 36vw); gap: 18px; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; padding: 8px 24px 24px; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; scrollbar-width: none; position: relative; } #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery::-webkit-scrollbar { display: none; } / Cartes / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery { scroll-snap-align: center; border-radius: 12px; overflow: hidden; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; } #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery:hover { transform: translateY(-2px); border-color: rgba(255,255,255,0.18); box-shadow: 0 10px 30px rgba(0,0,0,.25); } / Couverture image — proportion fixe / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card__cover { width: 100%; aspect-ratio: 4 / 5; / 👈 proportion stable (4:5 = vertical élégant) / object-fit: cover !important; object-position: center 45% !important; display: block; } / Contenu sous l’image / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card__content { padding: 12px 12px 14px; color: #5B5B5B; font-family: "Space Grotesk", ui-sans-serif, system-ui, sans-serif; font-size: 18px; line-height: 1.45; background: transparent; } / Effet “edges fade” pour indiquer le scroll / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery { -webkit-mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%); mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%); } / ---- Mobile adjustments ---- / @media (max-width: 720px) { #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery { grid-auto-columns: 30vw; / carte presque plein écran / gap: 16px; padding: 8px 12px 24px; } #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card__cover { aspect-ratio: 5 / 4; / plus proche du carré, mieux sur téléphone / } } / YOYOLIGHT — ne cacher le header vide qu'en version téléphone / @media (max-width: 768px) { #block-29b95fdcc23e8076ae45cee0bceb4255 > .notion-collection__header-wrapper { display: none !important; margin: 0 !important; padding: 0 !important; } / petit espace fluide entre le titre H2 et la galerie / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery { margin-top: 8px !important; } } / Fix mobile : supprimer l’espace laissé par le header-wrapper intermédiaire / @media (max-width: 768px) { / Masque le header vide + retire son espace / #block-29b95fdcc23e801a95bfcfe99896e893 > .notion-collection__header-wrapper { display: none !important; margin: 0 !important; padding: 0 !important; height: 0 !important; } / Si Notion insère un paragraphe vide, on le neutralise aussi / #block-29b95fdcc23e801a95bfcfe99896e893 .notion-text:empty, #block-29b95fdcc23e801a95bfcfe99896e893 .notion-semantic-string:empty { display: none !important; } / Ajoute un mini-espacement au-dessus du rail pour respirer (optionnel) / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-gallery { margin-top: 8px !important; / ajuste à 0 si tu veux collé / } } / Overlay uses --hover-img set by JS / #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery { position: relative; overflow: hidden; } #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery::after { content: ""; position: absolute; inset: 0; background-image: var(--hover-img); background-size: cover; background-position: center 45%; opacity: 0; transition: opacity .35s ease; pointer-events: none; } / Desktop hover / @media (hover:hover) { #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery.swws-lamp-ready:hover::after { opacity: 1; } } / Mobile tap */ @media (hover:none) { #block-29b95fdcc23e8076ae45cee0bceb4255 .notion-collection-card.gallery.swws-lamp-ready:active::after { opacity: 1; } }
<!-- === SWWS Landing Hero — ASCII Canvas Background (Quality MAX) ===== -->
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Roboto+Mono:wght@400&display=swap" rel="stylesheet">
<style>
.swws-hero, .swws-hero * { box-sizing: border-box; }
.swws-hero {
position: relative;
height: 100dvh; width: 100%;
isolation: isolate; overflow: clip;
display: grid; place-items: center;
background: #000; color: #fff; text-align: center;
font-family: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
}
/* Canvas ASCII en fond /
.swws-ascii { position:absolute; inset:0; width:100%; height:100%; display:block; z-index:1; }
/ Vignettage doux pour lisibilité du texte /
.swws-hero::after{
content:""; position:absolute; inset:0; pointer-events:none; z-index:2;
background: radial-gradient(120% 85% at 50% 12%, transparent 0 58%, rgba(0,0,0,.45) 100%);
}
.swws-hero__content { position:relative; z-index:5; padding:2rem 1rem; max-width:min(900px, 90vw); }
.swws-eyebrow{
display:inline-flex; align-items:center; gap:.6rem; padding:.4rem .7rem; border-radius:999px;
background:rgba(255,255,255,.12); backdrop-filter:blur(8px);
font-weight:600; font-size:18px; letter-spacing:.08em; text-transform:uppercase;
}
.swws-dot{ width:.8rem; aspect-ratio:1; border-radius:50%; background:#FFFF00; box-shadow:0 0 .5rem #FFFF00; }
/ H2 dynamique par lignes (déjà validé) /
.swws-h2{ display:flex; flex-direction:column; align-items:center; overflow:hidden;
font-weight:700; font-size:clamp(32px,6vw,72px); line-height:1.1; letter-spacing:-0.015em; color:#fff; }
.swws-h2 span{ display:block; transform:translateY(120%); opacity:0; animation:swwsLineReveal .9s ease-out forwards; }
@keyframes swwsLineReveal{ to{ transform:translateY(0); opacity:1; } }
.swws-h2 span:nth-child(1){animation-delay:.2s}
.swws-h2 span:nth-child(2){animation-delay:.45s}
.swws-h2 span:nth-child(3){animation-delay:.7s}
.swws-h2 span:nth-child(4){animation-delay:.95s}
.swws-contact{ font-weight:400; font-size:16px; line-height:1.7; color:#fff; opacity:.9; margin-bottom:2rem; }
.swws-contact a{ color:#fff; text-decoration:underline; text-underline-offset:3px; transition:color .2s ease; }
.swws-contact a:hover{ color:#feedcf; }
.swws-cta{
display:inline-flex; align-items:center; gap:.6rem; padding:.9rem 1.4rem; border-radius:0px;
color:#111; background:rgba(254,237,207,0.9); border:1px solid rgba(255,255,0,.35); text-decoration:none;
font-weight:600; font-size:15px; box-shadow:0 10px 30px rgba(0,0,0,.25);
transition:transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.swws-cta:hover{ transform:translateY(-2px); box-shadow:0 14px 40px rgba(0,0,0,.35); background:rgba(255,255,0,1); color:#111; }
.swws-cta svg{ width:18px; height:18px; }
.swws-hero__bottom-fade{ position:absolute; inset:auto 0 0 0; height 160px; background:linear-gradient(to bottom, transparent, #fff); pointer-events:none; z-index:6; }
@media (prefers-color-scheme: dark){ .swws-hero__bottom-fade{ background:linear-gradient(to bottom, transparent, #0b0d16); } }
@media (prefers-reduced-motion: reduce){
.swws-h2 span{ animation:none !important; opacity:1 !important; transform:none !important; }
}
</style>
<section id="swws-hero" class="swws-hero" aria-label="Sebastien Wierinck WorkShop — Hero">
<canvas id="swws-ascii" class="swws-ascii" aria-hidden="true"></canvas>
<div class="swws-hero__content">
<span class="swws-eyebrow"><span class="swws-dot"></span> Sebastien Wierinck WorkShop</span>
<h2 class="swws-h2">
<span>A design practice</span>
<span>at the intersection of</span>
<span>furniture, public art,</span>
<span>and digital craft.</span>
</h2>
<p class="swws-contact">
Get in touch → <a href="mailto:sw@swws.net">sw@swws.net</a><br>
Always open to collaborations, commissions, and professional opportunities.
</p>
<a class="swws-cta" href="https://swws.net/works">
Explore our Portfolio
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M5 12h12M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
</div>
<div class="swws-hero__bottom-fade"></div>
</section>
<script>
/ === ASCII Canvas — Quality MAX with auto density & perf guards ===== /
(() => {
const canvas = document.getElementById('swws-ascii');
if (!canvas) return;
const ctx = canvas.getContext('2d', { alpha: true, willReadFrequently: true });
// Images (ordre validé)
const IMAGES = [
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/775095d7-97ff-423c-8787-04648236bdb6/005/w=3840,quality=90,fit=scale-down',
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/e00f1546-30d8-444d-a663-cb1d9946d1b6/Buffet-Enfilade-SebastienWierinckSebastienNormand061/w=1920,quality=90,fit=scale-down',
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/dd8fc821-2964-4b81-ba44-aa04cee3f03a/BodyFold-Septeme-SWierinckSebastienNormand013/w=3840,quality=90,fit=scale-down',
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/f6a0da9d-d7c7-41c3-b17a-c9ea5bf66fec/CD104-02/w=1920,quality=90,fit=scale-down',
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/527cd0ef-b6eb-4a9d-bb9d-ac0ffa7d7faa/WORMS-SWWSSbastienNormand008_WEB/w=3840,quality=90,fit=scale-down',
'https://images.spr.so/cdn-cgi/imagedelivery/j42No7y-dcokJuNgXeA0ig/29cd6995-4e66-4d1b-8201-cd78b7abdfdf/J1-OS015-001c/w=1920,quality=90,fit=scale-down'
];
// Paramètres qualité
const CHARSET = '@%#WS$9876543210?!abc;:+=-,._ '; // sombre → clair (long = finesse)
const COLOR_MODE = 'mono'; // 'mono' ou 'color'
const BASE_CELL_DESKTOP = 5; // plus petit = plus de détails (5 recommandé pour Retina)
const BASE_CELL_MOBILE = 4; // densité plus légère pour mobile
const MAX_CELLS = 180000; // garde-fou perf (colonnes * lignes)
const CONTRAST = 1.1;
const BRIGHTNESS = 1.05;
const SLIDE_MS = 6000;
let imgIndex = 0, currentImg = null, slideTimer = null;
// Offscreen canvas pour downsample
const off = ('OffscreenCanvas' in window) ? new OffscreenCanvas(1,1) : document.createElement('canvas');
const octx = off.getContext('2d', { willReadFrequently: true });
// Chargement d'image
function loadImage(src){
return new Promise((resolve, reject)=>{
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
// DPR & resize
function fitCanvasToParent(){
const parent = canvas.parentElement;
const dpr = Math.min(window.devicePixelRatio || 1, 2); // cap à 2
const w = parent.clientWidth;
const h = parent.clientHeight;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
canvas.width = Math.floor(w * dpr);
canvas.height = Math.floor(h * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function calcGrid(){
const W = canvas.clientWidth, H = canvas.clientHeight;
const isMobile = Math.min(W, H) < 820; // heuristique téléphone/tablette portrait
// Base cell size selon device
const baseCell = isMobile ? BASE_CELL_MOBILE : BASE_CELL_DESKTOP;
// Ajustement par DPR (plus fin sur Retina)
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const dprBoost = dpr >= 1.5 ? 0.85 : 1.0; // un peu plus dense si Retina
// Densité adaptative selon surface (évite 4K trop lourd)
const area = W * H;
let densityFactor = 1.4;
if (area > 2000000) densityFactor = 1.15; // 2MP
if (area > 3500000) densityFactor = 1.3; // 3.5MP
const CELL = baseCell * dprBoost * densityFactor;
// Estimation lignes/colonnes (ratio lignes ~ 1.9 pour caractères)
let cols = Math.ceil(W / CELL);
let rows = Math.ceil(H / (CELL * 1.0));
// Garde-fou : limite le nb total de “cellules”
const total = cols * rows;
if (total > MAX_CELLS){
const scale = Math.sqrt(total / MAX_CELLS);
cols = Math.floor(cols / scale);
rows = Math.floor(rows / scale);
}
return { cols, rows, cellPx: CELL };
}
function drawASCII(){
if (!currentImg) return;
const W = canvas.clientWidth, H = canvas.clientHeight;
const { cols, rows } = calcGrid();
// 1) Dessin “cover” de l’image source dans l’offscreen à la résolution du grid
const sW = currentImg.width, sH = currentImg.height;
const scale = Math.max(cols / sW, rows / sH);
const dW = sW * scale, dH = sH * scale;
const dx = (cols - dW) * 0.5;
const dy = (rows - dH) * 0.55; // focus légèrement plus bas
if (off.width) { off.width = cols; off.height = rows; }
else { off.width = cols; off.height = rows; } // pour OffscreenCanvas
octx.imageSmoothingEnabled = true;
octx.clearRect(0, 0, cols, rows);
octx.drawImage(currentImg, dx, dy, dW, dH);
const imgData = octx.getImageData(0, 0, cols, rows).data;
// 2) Peindre le fond
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, W, H);
// 3) Rendu caractères
const charW = W / cols;
const charH = H / rows;
// Police optimisée (Roboto Mono = très propre)
ctx.textBaseline = 'top';
ctx.font = ${Math.ceil(charH * 1.0)}px 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
// Coloriage
for (let y = 0; y < rows; y++){
for (let x = 0; x < cols; x++){
const i = (y * cols + x) * 4;
let r = imgData[i] / 255;
let g = imgData[i+1] / 255;
let b = imgData[i+2] / 255;
// Luminance perceptuelle
let l = (0.2126r + 0.7152g + 0.0722b);
l = Math.min(1, Math.max(0, (l - 0.5) * CONTRAST + 0.5)) * BRIGHTNESS;
const ci = Math.min(CHARSET.length - 1, Math.floor((1 - l) * (CHARSET.length - 1)));
const ch = CHARSET[ci];
if (COLOR_MODE === 'color'){
ctx.fillStyle = rgb(${Math.round(r*255)},${Math.round(g*255)},${Math.round(b*255)});
} else {
const c = Math.round(l * 255);
ctx.fillStyle = rgb(${c},${c},${c});
}
ctx.fillText(ch, x * charW, y * charH);
}
}
}
async function showImage(i){
try {
currentImg = await loadImage(IMAGES[i]);
drawASCII();
} catch(e){ console.warn('ASCII load error', e); }
}
function onResize(){
fitCanvasToParent();
// debounce (évite de redessiner trop souvent pendant un resize)
clearTimeout(onResize._t);
onResize._t = setTimeout(drawASCII, 60);
}
function startSlides(){
clearInterval(slideTimer);
slideTimer = setInterval(() => {
imgIndex = (imgIndex + 1) % IMAGES.length;
showImage(imgIndex);
}, SLIDE_MS);
}
// Pause quand onglet caché (perf)
document.addEventListener('visibilitychange', () => {
if (document.hidden) { clearInterval(slideTimer); }
else { startSlides(); drawASCII(); }
});
// Boot
fitCanvasToParent();
showImage(imgIndex);
startSlides();
window.addEventListener('resize', onResize, { passive:true });
})();
</script>
<script>
(function () {
const GALLERY_ID = "#block-29b95fdcc23e8076ae45cee0bceb4255"; // ta galerie YOYO
const LAMP_PROP_SELECTOR = ".property-7c5b5265"; // propriété "Lampe on"
function getFileURL(el) {
// <img>
const img = el.querySelector('img');
if (img?.src) return img.src;
// <source srcset> (dans <picture>)
const source = el.querySelector('source[srcset]');
if (source?.srcset) return source.srcset.split(' ')[0];
// <a href>
const a = el.querySelector('a[href]');
if (a?.href) return a.href;
return null;
}
function hydrateGallery() {
const root = document.querySelector(GALLERY_ID);
if (!root) return;
root.querySelectorAll(".notion-collection-card.gallery").forEach(card => {
// Cherche la propriété "Lampe on" par sa classe unique
const lampRow = card.querySelector(LAMP_PROP_SELECTOR);
if (!lampRow) return;
const url = getFileURL(lampRow);
if (!url) return;
// 1) Injecte l'URL dans une variable CSS sur la carte
card.style.setProperty("--hover-img", url('${url}'));
card.classList.add("swws-lamp-ready");
// 2) Cache visuellement la propriété (reste dans le DOM)
Object.assign(lampRow.style, {
display: "none",
visibility: "hidden",
height: "0",
margin: "0",
padding: "0",
border: "none"
});
});
}
const run = () => requestAnimationFrame(hydrateGallery);
document.addEventListener("DOMContentLoaded", run);
window.addEventListener("load", run);
const mo = new MutationObserver(run);
mo.observe(document.documentElement, { childList: true, subtree: true });
})();
</script>