Base du thème : gabarits, jetons et effets

This commit is contained in:
Yasser Boulayoune 2026-10-08 10:12:00 +02:00
commit e06ad42369
6 changed files with 327 additions and 0 deletions

4
404.php Normal file
View file

@ -0,0 +1,4 @@
<?php
/* 404 : même page que toutes les autres erreurs (modèle inc/error-template.php généré par ~/yb-erreurs/build.py).
Ancienne 404 du thème : ~/backups/404.php.bak-20261009 */
yb2026_render_error(404, 'Page introuvable', esc_html__('Cette adresse ne mène nulle part. Elle a peut-être changé, ou il y a une faute de frappe.'));

14
footer.php Normal file
View file

@ -0,0 +1,14 @@
</main><!-- /#yb-root -->
<footer class="site-footer">
<div class="site-footer__logo">YB<span class="lime">.</span></div>
<div class="spin-circle" aria-hidden="true"></div>
<div class="site-footer__copy">
<span><?php echo esc_html(mb_strtoupper(yb2026_location_long())); ?></span>
<span>© <?php echo esc_html(date('Y')); ?> YASSER BOULAYOUNE · TOUS DROITS RÉSERVÉS · <a href="<?php echo esc_url(home_url('/mentions-legales/')); ?>">MENTIONS LÉGALES</a> · <a href="<?php echo esc_url(home_url('/politique-de-confidentialite/')); ?>">CONFIDENTIALITÉ</a></span>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>

77
header.php Normal file
View file

@ -0,0 +1,77 @@
<?php
$yb_static = isset($_GET['ybstatic']);
$yb_nav = array(
array('ACCUEIL', home_url('/')),
array('QUI SUIS-JE', home_url('/qui-suis-je/')),
array('COMPÉTENCES', home_url('/competences/')),
array('RÉALISATIONS', home_url('/realisation/')),
array('BLOG', home_url('/blog/')),
array('CONTACT', home_url('/contact/')),
);
$yb_path = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
$yb_email = yb2026_id('email');
$yb_tel = yb2026_id('telephone');
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?> class="<?php echo $yb_static ? 'yb-static' : 'is-entering'; ?>"<?php echo yb2026_season_attrs(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#0a0a0a">
<script>/* ybTheme : applique le thème mémorisé avant le premier rendu (pas de flash) */
(function(){try{var t=localStorage.getItem('yb-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t);
var h=document.documentElement,s=h.getAttribute('data-season');
if(h.hasAttribute('data-no-season')){}else if(localStorage.getItem('yb-sx')){h.setAttribute('data-sx','');var p=localStorage.getItem('yb-sp');if(p==='none'){if(s){h.removeAttribute('data-season');h.setAttribute('data-season-off',s);}}else if(p)h.setAttribute('data-season',p);}
else if(s&&localStorage.getItem('yb-season')==='off'){h.removeAttribute('data-season');h.setAttribute('data-season-off',s);}}catch(e){}})();</script>
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<!-- Wipe (rideau lime) : couvre l'écran dès le premier paint grâce à html.is-entering -->
<div class="yb-wipe" aria-hidden="true"><span class="yb-wipe__label"></span></div>
<script>
/* ybWipe : libellé du rideau repris de la page précédente ; filet de sécurité :
si effects.js ne se charge pas, le rideau se lève quand même. */
(function(){try{var l=sessionStorage.getItem('ybWipe');if(l)document.querySelector('.yb-wipe__label').textContent=l;}catch(e){}
setTimeout(function(){document.documentElement.classList.remove('is-entering');},2500);})();
</script>
<div class="scroll-progress" aria-hidden="true"></div>
<header class="site-nav">
<a class="site-nav__logo" href="<?php echo esc_url(home_url('/')); ?>" data-wipe="ACCUEIL">YB<span class="lime">©</span>2026</a>
<nav class="site-nav__links" aria-label="Navigation principale">
<?php foreach ($yb_nav as $item) :
$active = trim((string) parse_url($item[1], PHP_URL_PATH), '/') === $yb_path; ?>
<a href="<?php echo esc_url($item[1]); ?>" data-wipe="<?php echo esc_attr($item[0]); ?>"<?php echo $active ? ' class="is-active" aria-current="page"' : ''; ?>><?php echo esc_html($item[0]); ?></a>
<?php endforeach; ?>
</nav>
<div class="site-nav__right">
<?php yb2026_season_toggle(); ?>
<button type="button" class="theme-toggle" data-theme-toggle aria-label="Passer en mode clair" title="Mode clair / sombre">
<svg class="theme-toggle__sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.5v2.2M12 19.3v2.2M4.6 4.6l1.6 1.6M17.8 17.8l1.6 1.6M2.5 12h2.2M19.3 12h2.2M4.6 19.4l1.6-1.6M17.8 6.2l1.6-1.6"/></svg>
<svg class="theme-toggle__moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1z"/></svg>
</button>
<div class="site-nav__badge">@ <?php echo esc_html(mb_strtoupper(yb2026_id('entreprise'))); ?></div>
<button type="button" class="site-nav__burger" data-menu-toggle aria-expanded="false" aria-controls="yb-menu">MENU</button>
</div>
</header>
<div class="mobile-menu" id="yb-menu" data-menu aria-hidden="true">
<nav class="mobile-menu__links" aria-label="Menu mobile">
<?php foreach ($yb_nav as $i => $item) :
$active = trim((string) parse_url($item[1], PHP_URL_PATH), '/') === $yb_path; ?>
<a href="<?php echo esc_url($item[1]); ?>" data-wipe="<?php echo esc_attr($item[0]); ?>" class="mobile-menu__link<?php echo $active ? ' is-active' : ''; ?>" style="--i:<?php echo $i; ?>">
<span class="mobile-menu__txt"><?php echo esc_html($item[0]); ?><span class="mobile-menu__arrow">→</span></span>
</a>
<?php endforeach; ?>
</nav>
<div class="mobile-menu__contact">
<a <?php echo yb2026_pv_href('mailto:' . $yb_email); ?>><?php echo yb2026_pv($yb_email); ?> ↗</a>
<a <?php echo yb2026_pv_href(yb2026_tel_href($yb_tel)); ?>><?php echo yb2026_pv($yb_tel); ?> ↗</a>
<div class="mobile-menu__loc"><?php echo esc_html(mb_strtoupper(yb2026_location_long())); ?></div>
</div>
</div>
<main id="yb-root">

31
page-blog.php Normal file
View file

@ -0,0 +1,31 @@
<?php
/* Page « Blog » (slug /blog/) : liste éditoriale des articles */
get_header();
$posts_q = new WP_Query(array(
'post_type' => 'post',
'posts_per_page' => 50,
'no_found_rows' => true,
));
?>
<div class="page-wrap">
<div class="kicker">JOURNAL TECHNIQUE</div>
<h1 class="page-title" data-reveal>LE <span class="outline-lime">BLOG</span></h1>
<div class="blog-list">
<?php $i = 0; while ($posts_q->have_posts()) : $posts_q->the_post(); $i++;
$cat = get_the_category();
$tag = $cat ? strtoupper($cat[0]->name) : strtoupper(get_the_date('M Y')); ?>
<a class="blog-row" href="<?php the_permalink(); ?>" data-cursor-label="LIRE ↗" data-wipe="LECTURE">
<span class="blog-num mono"><?php echo str_pad($i, 2, '0', STR_PAD_LEFT); ?></span>
<span class="blog-title"><?php the_title(); ?></span>
<span class="blog-tag mono"><?php echo esc_html($tag); ?></span>
<span class="blog-arrow mono">→</span>
</a>
<?php endwhile; wp_reset_postdata(); ?>
</div>
<div class="page-foot-note mono">CHAQUE PROJET A SON ARTICLE : RETOURS D'EXPÉRIENCE, PANNES ET CORRECTIFS INCLUS.</div>
</div>
<?php get_footer(); ?>

123
page-qui-suis-je.php Normal file
View file

@ -0,0 +1,123 @@
<?php
/* Page « Qui suis-je » : portrait, fiche en terminal, compétences en jauges typographiques,
parcours en frise tracée au scroll. Données : page d'admin Profil (inc/profile.php). */
get_header();
$photo = function_exists('get_field') ? get_field('photo', 'option') : '';
if (!$photo) $photo = home_url('/wp-content/uploads/2026/03/IMG_2852-e1774648448626.png');
$entreprise = yb2026_id('entreprise');
$fiche = array(
'poste' => yb2026_id('poste') . ', ' . $entreprise,
'localisation' => yb2026_location_long(),
'email' => yb2026_id('email'),
'telephone' => yb2026_id('telephone'),
'mobilite' => yb2026_id('mobilite'),
);
/* Intro : 1re phrase en manifeste, le reste en texte courant ; l'entreprise passe en lime */
$intro = yb2026_id('intro');
$parts = preg_split('/(?<=[.!?])\s+/u', trim($intro), 2);
$lime = function ($t) use ($entreprise) {
$h = esc_html($t);
return $entreprise ? str_replace(esc_html($entreprise), '<span class="lime">' . esc_html($entreprise) . '</span>', $h) : $h;
};
$skills = yb2026_profile('yb_skills');
$parcours = yb2026_profile('yb_parcours');
?>
<div class="qs">
<!-- ── Ouverture : titre géant + portrait ───────────────────────────── -->
<section class="qs-hero page-wrap">
<div class="qs-hero__text">
<div class="kicker">À PROPOS</div>
<h1 class="qs-title" data-reveal>QUI<br><span class="outline-lime">SUIS-JE ?</span></h1>
<p class="qs-lead" data-reveal><?php echo $lime($parts[0]); ?></p>
<?php if (!empty($parts[1])) : ?><p class="qs-sub" data-reveal><?php echo $lime($parts[1]); ?></p><?php endif; ?>
</div>
<figure class="qs-portrait" data-reveal>
<div class="qs-portrait__frame" data-tilt>
<img src="<?php echo esc_url($photo); ?>" alt="Portrait de Yasser Boulayoune">
</div>
<figcaption class="qs-portrait__cap">YASSER BOULAYOUNE · <?php echo esc_html(mb_strtoupper(yb2026_id('localisation'))); ?></figcaption>
</figure>
</section>
<!-- ── Fiche : terminal « whoami » tapé en direct ───────────────────── -->
<section class="page-wrap qs-who">
<div class="term qs-term" data-reveal>
<div class="term-bar">
<span class="term-dot term-dot--r"></span><span class="term-dot term-dot--y"></span><span class="term-dot term-dot--g"></span>
<span class="term-title">yass@servyass: ~</span>
</div>
<div class="term-body qs-term__body" data-type>
<div class="qs-term__line"><span class="lime">yass@servyass</span>:~$ <span data-type-cmd>whoami --details</span></div>
<?php foreach ($fiche as $k => $v) : if (!trim($v, ', ')) continue; ?>
<div class="qs-term__row" data-type-row><span class="qs-term__k"><?php echo esc_html($k); ?></span><span class="qs-term__v"><?php echo in_array($k, array('email', 'telephone'), true) ? yb2026_pv($v) : esc_html($v); ?></span></div>
<?php endforeach; ?>
<div class="qs-term__line" data-type-row><span class="lime">yass@servyass</span>:~$ <span class="qs-term__caret"></span></div>
</div>
</div>
</section>
<!-- ── Compétences techniques : noms géants qui se remplissent de lime ── -->
<?php if ($skills) : ?>
<section class="qs-skills" aria-label="Compétences techniques">
<div class="page-wrap">
<div class="kicker">COMPÉTENCES TECHNIQUES</div>
<h2 class="cp-h" data-reveal>CE QUE JE <span class="outline-lime">MAÎTRISE</span></h2>
</div>
<ul class="qs-gauges">
<?php foreach ($skills as $sk) : $pct = max(0, min(100, (int) $sk['pct'])); ?>
<li class="qs-gauge" style="--pct:<?php echo $pct; ?>%" data-bar>
<span class="qs-gauge__name" data-text="<?php echo esc_attr(mb_strtoupper($sk['name'])); ?>"><?php echo esc_html(mb_strtoupper($sk['name'])); ?></span>
<span class="qs-gauge__meta"><span><?php echo esc_html($sk['level']); ?></span><b><?php echo $pct; ?>%</b></span>
</li>
<?php endforeach; ?>
</ul>
</section>
<?php endif; ?>
<!-- ── Parcours : frise, ligne lime tracée au fil du scroll ─────────── -->
<?php if ($parcours) : ?>
<section class="qs-path page-wrap" aria-label="Parcours">
<div class="kicker">PARCOURS</div>
<h2 class="cp-h" data-reveal>D'OÙ JE <span class="outline-lime">VIENS</span></h2>
<div class="qs-tl" data-timeline>
<span class="qs-tl__line" aria-hidden="true"><span class="qs-tl__fill"></span></span>
<ol class="qs-tl__list">
<?php foreach ($parcours as $i => $e) :
preg_match_all('/\d{4}/', $e['date'], $yy);
$years = array_values(array_unique($yy[0]));
$year = $years ? $years[0] : '';
$now = $i === 0 && stripos($e['date'], 'depuis') !== false; ?>
<li class="qs-tl__item<?php echo $now ? ' is-now' : ''; ?>" data-reveal>
<span class="qs-tl__dot" aria-hidden="true"></span>
<div class="qs-tl__year"><?php echo esc_html($year); ?><?php if (count($years) > 1) : ?><small>→ <?php echo esc_html(end($years)); ?></small><?php endif; ?></div>
<div class="qs-tl__body">
<div class="qs-tl__date"><?php echo esc_html(mb_strtoupper($e['date'])); ?><?php if ($now) : ?> <span class="qs-tl__now">MAINTENANT</span><?php endif; ?></div>
<h3 class="qs-tl__title"><?php echo esc_html($e['titre']); ?></h3>
<div class="qs-tl__place"><?php echo esc_html(mb_strtoupper($e['lieu'])); ?></div>
<p class="qs-tl__desc"><?php echo esc_html($e['desc']); ?></p>
</div>
</li>
<?php endforeach; ?>
</ol>
</div>
</section>
<?php endif; ?>
<!-- ── Bandeau des casquettes + appel à l'échange ───────────────────── -->
<section class="skills-mq qs-mq" aria-hidden="true">
<div class="marquee">
<div class="marquee-track skills-mq__track">
<?php for ($k = 0; $k < 2; $k++) : ?><span><?php echo esc_html(mb_strtoupper(yb2026_id('poste'))); ?> <i class="lime">✦</i> <?php echo esc_html(mb_strtoupper(str_replace(' · ', ' ✦ ', yb2026_id('casquettes')))); ?> <i class="lime">✦</i> <?php echo esc_html(mb_strtoupper(yb2026_id('localisation'))); ?> <i class="lime">✦</i>&nbsp;</span><?php endfor; ?>
</div>
</div>
</section>
<section class="cta">
<div class="kicker" data-reveal><?php echo esc_html(mb_strtoupper(yb2026_id('accroche'))); ?></div>
<a class="cta__title" href="<?php echo esc_url(home_url('/contact/')); ?>" data-wipe="CONTACT" data-cursor-label="M'ÉCRIRE" data-reveal>TRAVAILLONS<br><span class="outline-lime">ENSEMBLE →</span></a>
</section>
</div>
<?php get_footer(); ?>

78
single-realisation.php Normal file
View file

@ -0,0 +1,78 @@
<?php
/* Étude de cas d'une réalisation */
get_header();
the_post();
$id = get_the_ID();
$num = yb2026_num($id);
$tag = get_post_meta($id, '_yb_tag', true);
if (!$tag) $tag = strtoupper((string) get_post_meta($id, '_real_technologies', true));
$year = get_post_meta($id, '_yb_year', true);
$role = get_post_meta($id, '_yb_role', true);
$desc = get_post_meta($id, '_yb_desc', true);
$note = get_post_meta($id, '_yb_note', true);
$live = get_post_meta($id, '_real_url', true);
$sections = json_decode((string) get_post_meta($id, '_yb_sections', true), true);
/* Projet suivant (par ordre, boucle sur le premier) */
$all = yb2026_get_projects();
$next = null;
foreach ($all as $i => $p) {
if ($p->ID === $id) { $next = $all[($i + 1) % count($all)]; break; }
}
?>
<article class="case">
<div class="case-hero">
<?php the_post_thumbnail('full', array('class' => 'case-hero-img')); ?>
<div class="case-hero-fade"></div>
<div class="case-hero-text">
<div class="kicker rise rise-1"><?php echo esc_html($num); ?> · ÉTUDE DE CAS<?php echo $year ? ' · ' . esc_html($year) : ''; ?></div>
<h1 class="case-title rise rise-2"><?php echo esc_html(yb2026_short_title(get_post())); ?></h1>
</div>
</div>
<div class="case-cols">
<aside class="case-meta article__meta">
<?php if ($year) : ?><div><div class="case-meta-k">ANNÉE</div><div><?php echo esc_html($year); ?></div></div><?php endif; ?>
<?php if ($role) : ?><div><div class="case-meta-k">RÔLE</div><div><?php echo esc_html($role); ?></div></div><?php endif; ?>
<?php if ($tag) : ?><div><div class="case-meta-k">STACK</div><div class="lime"><?php echo esc_html($tag); ?></div></div><?php endif; ?>
<?php if ($live) : ?><a class="pill-btn" href="<?php echo esc_url($live); ?>" target="_blank" rel="noopener">PROJET EN LIGNE ↗</a><?php endif; ?>
</aside>
<div class="case-body">
<?php if ($desc) : ?><div class="case-lead rise rise-3"><?php echo esc_html($desc); ?></div><?php endif; ?>
<?php if (is_array($sections) && $sections) : ?>
<?php foreach ($sections as $sec) : ?>
<div class="case-sec" data-reveal>
<h2 class="case-sec-h"><span class="lime">/ </span><?php echo esc_html($sec['h']); ?></h2>
<p class="case-sec-p"><?php echo esc_html($sec['p']); ?></p>
</div>
<?php endforeach; ?>
<?php
/* Mode sections : les images de l'éditeur (captures) sont affichées en plus,
sinon elles n'apparaîtraient nulle part. */
preg_match_all('#<figure\b[^>]*wp-block-image[^>]*>.*?</figure>#s', apply_filters('the_content', get_the_content()), $yb_figs);
if (!empty($yb_figs[0])) : ?>
<div class="prose case-figs" data-reveal><?php echo implode("\n", $yb_figs[0]); ?></div>
<?php endif; ?>
<?php else : ?>
<div class="prose" data-reveal><?php the_content(); ?></div>
<?php endif; ?>
<?php if ($note) : ?><div class="note-l mono"><?php echo esc_html($note); ?></div><?php endif; ?>
</div>
</div>
<?php if ($next) : ?>
<a class="case-next" href="<?php echo esc_url(get_permalink($next)); ?>" data-cursor-label="VOIR LE PROJET" data-wipe="<?php echo esc_attr(mb_strtoupper(yb2026_short_title($next))); ?>">
<div>
<div class="case-next-k mono">PROJET SUIVANT</div>
<div class="case-next-name"><?php echo esc_html(yb2026_short_title($next)); ?></div>
</div>
<div class="case-next-arrow mono">→</div>
</a>
<?php endif; ?>
</article>
<?php get_footer(); ?>