<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Gouvernails &#8211; Brian Mura</title>
	<atom:link href="https://brianmura.fr/tag/gouvernails/feed/" rel="self" type="application/rss+xml" />
	<link>https://brianmura.fr</link>
	<description></description>
	<lastBuildDate>Fri, 12 Jun 2026 11:11:34 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>

<image>
	<url>https://brianmura.fr/wp-content/uploads/2024/12/cropped-courtesy-logo-32x32.jpg</url>
	<title>Gouvernails &#8211; Brian Mura</title>
	<link>https://brianmura.fr</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Gouvernails</title>
		<link>https://brianmura.fr/sculpture/gouvernails/</link>
		
		<dc:creator><![CDATA[Brian Mura]]></dc:creator>
		<pubDate>Mon, 09 Dec 2024 07:19:07 +0000</pubDate>
				<category><![CDATA[Sculpture]]></category>
		<category><![CDATA[Gouvernails]]></category>
		<guid isPermaLink="false">https://brianmura.fr/?p=517</guid>

					<description><![CDATA[Gouvernails &#124; zinc &#124; bois &#124; ~ 170 x 120 cm. &#124; 2017.]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="517" class="elementor elementor-517">
				<div class="elementor-element elementor-element-718e851 e-flex e-con-boxed wpr-particle-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent" data-id="718e851" data-element_type="container" data-e-type="container">
					<div class="e-con-inner">
				<div class="elementor-element elementor-element-dc15d93 elementor-widget elementor-widget-html" data-id="dc15d93" data-element_type="widget" data-e-type="widget" data-widget_type="html.default">
				<div class="elementor-widget-container">
					
<!-- =============================================
     CONTENEUR PRINCIPAL DU VIEWER
     L'id unique permet d'avoir plusieurs viewers
     sur le même site sans conflits JS/CSS.
============================================= -->
<div id="gouvernails-viewer" class="serie-viewer">

  <!-- =============================================
       PANNEAU GAUCHE — titre, méta, description, player
  ============================================= -->
  <aside id="gouvernails-caption" class="serie-caption">

    <!-- Titre de la série -->
    <div class="serie-title">Gouvernails</div>

    <!-- Métadonnées (matériaux, dimensions, date...) -->
          <div class="serie-meta">
                  <span>- Tôles de Zinc</span>
                  <span>- Bois</span>
                  <span>~1.70 x 1.20 m. </span>
                  <span>- 2017</span>
              </div>
    
    <!-- Description poétique + cryptogramme + images liées -->
    
    <!-- =============================================
         PLAYER AUDIO — affiché uniquement si $audio est rempli
         Structure identique au player de la page Éditions,
         adaptée avec des IDs préfixés par $id pour unicité.
    ============================================= -->
    
  </aside>

  <!-- =============================================
       STAGE DESKTOP — image ou vidéo principale
       Navigable via flèches, clavier, swipe
  ============================================= -->
  <div id="gouvernails-stage" class="serie-stage">

    
      <!-- Premier média affiché au chargement -->
              <img decoding="async"
          id="gouvernails-img"
          class="serie-img"
          src="https://brianmura.fr/wp-content/uploads/2024/12/gouvernail-3.jpg"
          alt="Gouvernails — 1"
        >
      
    
    <!-- Flèches et compteur — affichés uniquement si plus d'un média -->
          <button class="serie-prev" aria-label="Précédent">&#8592;</button>
      <button class="serie-next" aria-label="Suivant">&#8594;</button>
      <div class="serie-counter">1 / 2</div>
    
  </div>

  <!-- =============================================
       GRILLE MOBILE — médias empilés verticalement
       Visible uniquement sur mobile (CSS)
  ============================================= -->
  <div id="gouvernails-mobile-grid" class="serie-mobile-grid">
    
              <!-- Image mobile — cliquable pour ouvrir la lightbox -->
        <img decoding="async"
          src="https://brianmura.fr/wp-content/uploads/2024/12/gouvernail-3.jpg"
          alt="Gouvernails — 1"
          class="serie-mobile-img"
          data-index="0"
        >
      
    
              <!-- Image mobile — cliquable pour ouvrir la lightbox -->
        <img decoding="async"
          src="https://brianmura.fr/wp-content/uploads/2024/12/gouvernail-2-1.jpg"
          alt="Gouvernails — 2"
          class="serie-mobile-img"
          data-index="1"
        >
      
      </div>

  <!-- =============================================
       LIGHTBOX MOBILE — plein écran sur tap d'image
  ============================================= -->
  <div id="gouvernails-lightbox" class="serie-lightbox" aria-hidden="true">
    <button class="slb-close">✕</button>
    <button class="slb-prev">&#8592;</button>
    <img decoding="async" class="slb-img" src="" alt="">
    <button class="slb-next">&#8594;</button>
    <div class="slb-counter"></div>
  </div>

</div>
<script>
(function () {

  /* =============================================
     IDENTIFIANTS ET DONNÉES
     viewerId : préfixe unique pour tous les IDs
     media    : tableau de médias (images/vidéos)
     total    : nombre de médias
     current  : index du média actuellement affiché
  ============================================= */
  const viewerId = 'gouvernails';
  const media    = [{"type":"image","src":"https:\/\/brianmura.fr\/wp-content\/uploads\/2024\/12\/gouvernail-3.jpg"},{"type":"image","src":"https:\/\/brianmura.fr\/wp-content\/uploads\/2024\/12\/gouvernail-2-1.jpg"}];
  const total    = media.length;
  let   current  = 0;

  /* =============================================
     ÉLÉMENTS DU STAGE (desktop)
     stage   : conteneur de l'image/vidéo principale
     counter : affichage "X / Y"
     btnPrev : flèche gauche
     btnNext : flèche droite
     viewer  : conteneur global du viewer
  ============================================= */
  const stage   = document.getElementById(viewerId + '-stage');
  const counter = stage ? stage.querySelector('.serie-counter') : null;
  const btnPrev = stage ? stage.querySelector('.serie-prev')    : null;
  const btnNext = stage ? stage.querySelector('.serie-next')    : null;
  const viewer  = document.getElementById(viewerId + '-viewer');

  /* =============================================
     getOrCreateMedia(item)
     Affiche le bon média (image ou vidéo) dans le stage.
     Si un élément du même type existe déjà, on
     change seulement son src (plus rapide, pas de
     re-création DOM). Sinon on crée un nouvel élément.
  ============================================= */
  function getOrCreateMedia(item) {

    /* Récupère l'élément actuellement affiché dans le stage */
    const existing = document.getElementById(viewerId + '-img');

    if (item.type === 'video') {

      /* ── Cas vidéo : une vidéo est déjà là ── */
      if (existing && existing.tagName === 'VIDEO') {
        existing.src      = item.src;
        existing.loop     = item.loop === true;
        existing.muted    = item.autoplay === true;
        existing.autoplay = item.autoplay === true;
        existing.load(); /* recharge avec le nouveau src */

        /* Si autoplay demandé : attend que les métadonnées
           soient chargées avant de lancer la lecture */
        if (item.autoplay) {
          existing.addEventListener('loadedmetadata', () => {
            existing.play().catch(() => {});
          }, { once: true }); /* once:true = l'écouteur se retire après le 1er appel */
        }
        return; /* on s'arrête ici, pas besoin de créer un nouvel élément */
      }

      /* ── Cas vidéo : pas encore de vidéo dans le stage ── */
      const vid = document.createElement('video');
      vid.id        = viewerId + '-img';    /* ID unique pour le retrouver */
      vid.className = 'serie-img serie-video';
      vid.controls  = true;                 /* affiche les contrôles natifs */
      vid.setAttribute('playsinline', '');  /* évite le plein écran forcé sur iOS */
      vid.loop      = item.loop     === true;
      vid.muted     = item.autoplay === true; /* autoplay exige muted */
      vid.autoplay  = item.autoplay === true;
      vid.style.cssText = 'max-width:100%; max-height:100%; object-fit:contain;';
      vid.src = item.src; /* src en dernier pour que les attributs soient posés avant */

      /* Remplace l'élément existant ou prépend au stage */
      if (existing) existing.replaceWith(vid);
      else stage.prepend(vid);

      /* Autoplay : attend loadedmetadata pour play() */
      if (item.autoplay) {
        vid.addEventListener('loadedmetadata', () => {
          vid.play().catch(() => {});
        }, { once: true });
      }

    } else {

      /* ── Cas image : une image est déjà là ── */
      if (existing && existing.tagName === 'IMG') {
        existing.src = item.src;
        existing.alt = 'Gouvernails — ' + (current + 1);
        return;
      }

      /* ── Cas image : pas encore d'image dans le stage ── */
      const img = document.createElement('img');
      img.id        = viewerId + '-img';
      img.className = 'serie-img';
      img.src       = item.src;
      img.alt       = 'Gouvernails — ' + (current + 1);
      img.style.cursor = 'pointer'; /* indique qu'on peut cliquer */

      /* Clic sur l'image = média suivant */
      img.addEventListener('click', () => go(current + 1));

      if (existing) existing.replaceWith(img);
      else stage.prepend(img);
    }
  }

  /* =============================================
     go(n)
     Navigue vers le média d'index n.
     Gère la boucle (dernier → premier et inversement).
  ============================================= */
  function go(n) {
    current = (n + total) % total; /* modulo pour boucler */
    getOrCreateMedia(media[current]);
    if (counter) counter.textContent = (current + 1) + ' / ' + total;
  }

  /* =============================================
     CLIC SUR L'IMAGE INITIALE
     L'image générée en PHP n'a pas encore
     l'écouteur de clic — on l'ajoute ici.
  ============================================= */
  const initImg = document.getElementById(viewerId + '-img');
  if (initImg && initImg.tagName === 'IMG') {
    initImg.style.cursor = 'pointer';
    initImg.addEventListener('click', () => go(current + 1));
  }

  /* =============================================
     NAVIGATION CLAVIER
     Flèches gauche/droite sur le clavier physique
  ============================================= */
  if (btnPrev) btnPrev.addEventListener('click', () => go(current - 1));
  if (btnNext) btnNext.addEventListener('click', () => go(current + 1));

  document.addEventListener('keydown', e => {
    if (e.key === 'ArrowLeft')  go(current - 1);
    if (e.key === 'ArrowRight') go(current + 1);
  });

  /* =============================================
     SWIPE DESKTOP (viewer entier)
     Détecte un glissement horizontal et navigue.
     Seuil de 40px pour éviter les faux positifs.
  ============================================= */
  let touchX = null;
  if (viewer) {
    viewer.addEventListener('touchstart', e => {
      touchX = e.touches[0].clientX;
    }, { passive: true });

    viewer.addEventListener('touchend', e => {
      if (touchX === null) return;
      const diff = touchX - e.changedTouches[0].clientX;
      /* diff > 0 : swipe vers la gauche → suivant */
      /* diff < 0 : swipe vers la droite → précédent */
      if (Math.abs(diff) > 40) go(current + (diff > 0 ? 1 : -1));
      touchX = null;
    }, { passive: true });
  }

  /* =============================================
     PLAYER AUDIO
     Structure identique au player de la page Éditions,
     IDs préfixés par viewerId pour unicité.
  ============================================= */
  const playerEl = document.getElementById(viewerId + '-player');

  if (playerEl) {

    /* Récupération de tous les éléments du player */
    const audioEl   = document.getElementById(viewerId + '-audio');
    const playBtn   = document.getElementById(viewerId + '-player-play');
    const playIcon  = document.getElementById(viewerId + '-player-play-icon');
    const prevBtn   = document.getElementById(viewerId + '-player-prev');
    const nextBtn   = document.getElementById(viewerId + '-player-next');
    const volumeEl  = document.getElementById(viewerId + '-player-volume');
    const playlistEl= document.getElementById(viewerId + '-player-playlist');
    const timeCurEl = document.getElementById(viewerId + '-player-time-cur');
    const timeTotEl = document.getElementById(viewerId + '-player-time-tot');

    /* Récupère les pistes depuis les <li> de la playlist */
    const trackEls = [...playlistEl.querySelectorAll('.pl-track')];

    let trackIdx = 0; /* index de la piste en cours */

    /* ──────────────────────────────────────────
       fmt(s) — formate des secondes en "M:SS"
       Exemple : fmt(90) → "1:30"
    ────────────────────────────────────────── */
    function fmt(s) {
      if (isNaN(s) || !isFinite(s)) return '0:00';
      const m  = Math.floor(s / 60);
      const ss = Math.floor(s % 60).toString().padStart(2, '0');
      return `${m}:${ss}`;
    }

    /* ──────────────────────────────────────────
       setPlayIcon(playing)
       Remplace l'icône SVG du bouton play/pause
       selon l'état de lecture.
    ────────────────────────────────────────── */
    function setPlayIcon(playing) {
      playIcon.innerHTML = playing
        /* Icône pause : deux rectangles verticaux */
        ? `<rect x="6" y="4" width="4" height="16" rx="1"/>
           <rect x="14" y="4" width="4" height="16" rx="1"/>`
        /* Icône play : triangle */
        : `<polygon points="5 3 19 12 5 21 5 3"/>`;
    }

    /* ──────────────────────────────────────────
       loadTrack(i)
       Charge la piste d'index i dans l'élément audio.
       Met à jour la classe 'active' dans la playlist.
       Remet les temps à zéro.
    ────────────────────────────────────────── */
    function loadTrack(i) {
      trackIdx      = i;
      audioEl.src   = trackEls[i].dataset.src; /* src depuis data-src du <li> */
      timeCurEl.textContent = '0:00';
      timeTotEl.textContent = '0:00';

      /* Met à jour la classe active dans la playlist */
      trackEls.forEach((t, j) => t.classList.toggle('active', j === i));
    }

    /* ── Bouton play/pause ── */
    playBtn.addEventListener('click', () => {
      audioEl.paused ? audioEl.play() : audioEl.pause();
    });

    /* ── Mise à jour de l'icône selon l'état ── */
    audioEl.addEventListener('play',  () => setPlayIcon(true));
    audioEl.addEventListener('pause', () => setPlayIcon(false));

    /* ── Mise à jour du temps pendant la lecture ── */
    audioEl.addEventListener('timeupdate', () => {
      timeCurEl.textContent = fmt(audioEl.currentTime);
      timeTotEl.textContent = fmt(audioEl.duration);
    });

    /* ── Piste suivante automatique à la fin ── */
    audioEl.addEventListener('ended', () => {
      if (trackEls.length < 2) return;
      loadTrack((trackIdx + 1) % trackEls.length);
      audioEl.play();
    });

    /* ── Bouton précédent ── */
    prevBtn.addEventListener('click', () => {
      loadTrack((trackIdx - 1 + trackEls.length) % trackEls.length);
      audioEl.play();
    });

    /* ── Bouton suivant ── */
    nextBtn.addEventListener('click', () => {
      loadTrack((trackIdx + 1) % trackEls.length);
      audioEl.play();
    });

    /* ── Clic sur une piste dans la playlist ── */
    trackEls.forEach((t, i) => {
      t.addEventListener('click', () => {
        loadTrack(i);
        audioEl.play();
      });
    });

    /* ── Volume : valeur initiale 0.7, mise à jour en temps réel ── */
    volumeEl.value = 0.7;
    audioEl.volume = 0.7;
    volumeEl.addEventListener('input', () => {
      audioEl.volume = volumeEl.value;
    });

    /* Charge la première piste dès l'initialisation */
    loadTrack(0);
  }

  /* =============================================
     LIGHTBOX MOBILE
     S'ouvre au tap sur une image de la grille mobile.
     Navigation par flèches, swipe, Escape.
  ============================================= */
  const mobileGrid = document.getElementById(viewerId + '-mobile-grid');
  const lightbox   = document.getElementById(viewerId + '-lightbox');

  if (mobileGrid && lightbox) {

    const lbImg     = lightbox.querySelector('.slb-img');
    const lbPrev    = lightbox.querySelector('.slb-prev');
    const lbNext    = lightbox.querySelector('.slb-next');
    const lbClose   = lightbox.querySelector('.slb-close');
    const lbCounter = lightbox.querySelector('.slb-counter');

    /* Uniquement les images — les vidéos ne s'ouvrent pas en lightbox */
    const imgItems = media.filter(m => m.type === 'image');
    let   lbCurrent = 0;

    /* ── Navigue dans la lightbox ── */
    function lbGo(n) {
      lbCurrent     = (n + imgItems.length) % imgItems.length;
      lbImg.src     = imgItems[lbCurrent].src;
      lbCounter.textContent = (lbCurrent + 1) + ' / ' + imgItems.length;
    }

    /* ── Ouvre la lightbox à l'index donné ── */
    function openLightbox(index) {
      lbCurrent = index;
      lbImg.src = imgItems[index].src;
      lbCounter.textContent = (index + 1) + ' / ' + imgItems.length;
      lightbox.classList.add('open');
      lightbox.setAttribute('aria-hidden', 'false');
      document.body.style.overflow = 'hidden'; /* bloque le scroll de fond */
    }

    /* ── Ferme la lightbox ── */
    function closeLightbox() {
      lightbox.classList.remove('open');
      lightbox.setAttribute('aria-hidden', 'true');
      document.body.style.overflow = ''; /* restaure le scroll */
    }

    /* Tap sur une image de la grille → ouvre la lightbox */
    mobileGrid.querySelectorAll('.serie-mobile-img').forEach((img, i) => {
      img.addEventListener('click', () => openLightbox(i));
    });

    lbClose.addEventListener('click', closeLightbox);
    lbPrev.addEventListener('click',  () => lbGo(lbCurrent - 1));
    lbNext.addEventListener('click',  () => lbGo(lbCurrent + 1));

    /* ── Swipe dans la lightbox ── */
    let lbTouchX = null;
    lightbox.addEventListener('touchstart', e => {
      lbTouchX = e.touches[0].clientX;
    }, { passive: true });

    lightbox.addEventListener('touchend', e => {
      if (lbTouchX === null) return;
      const diff = lbTouchX - e.changedTouches[0].clientX;
      if (Math.abs(diff) > 40) lbGo(lbCurrent + (diff > 0 ? 1 : -1));
      lbTouchX = null;
    }, { passive: true });

    /* ── Escape ferme la lightbox ── */
    document.addEventListener('keydown', e => {
      if (e.key === 'Escape' && lightbox.classList.contains('open')) closeLightbox();
    });

    /* ── Clic sur le fond noir ferme la lightbox ── */
    lightbox.addEventListener('click', e => {
      if (e.target === lightbox) closeLightbox();
    });
  }

}());
</script>
				</div>
				</div>
					</div>
				</div>
				</div>
		]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
