/* =========================================================================
   NORTHWIND

   One file, no libraries. Every block below is independent, so deleting any
   one of them leaves the rest working.

   Nothing here is needed for the page to work. With JavaScript switched off
   the form still posts to send.php, the photographs still show, and the
   tabs still work, because they are radio buttons and CSS rather than
   script. This file only adds movement and saves a page reload.
   ========================================================================= */
(function () {
  'use strict';

  var slow = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  /* --- 1. the year in the footer --------------------------------------- */

  var year = document.getElementById('year');
  if (year) { year.textContent = String(new Date().getFullYear()); }


  /* --- 2. smooth scrolling to a section -------------------------------- */

  document.addEventListener('click', function (e) {
    var link = e.target.closest ? e.target.closest('a[href^="#"]') : null;
    if (!link) { return; }
    var id = link.getAttribute('href');
    if (!id || id === '#') { return; }
    var target = document.querySelector(id);
    if (!target) { return; }
    e.preventDefault();
    target.scrollIntoView({ behavior: slow ? 'auto' : 'smooth', block: 'start' });
    // Keep the address bar honest, so the link can still be copied.
    if (history.replaceState) { history.replaceState(null, '', id); }
  });


  /* --- 3. the header and the back to top button ------------------------- */

  /* The header starts transparent over the dark hero and turns solid once
     the page has moved. Both it and the back to top button watch the same
     scroll position, so they share one listener. */
  var bar = document.getElementById('bar');
  var top = document.getElementById('totop');

  function onScroll() {
    var y = window.pageYOffset || document.documentElement.scrollTop;
    if (bar) { bar.classList.toggle('stuck', y > 40); }
    if (top) { top.classList.toggle('show', y > 700); }
  }
  window.addEventListener('scroll', onScroll, { passive: true });
  onScroll();


  /* --- 4. the parallax band ---------------------------------------------- */

  /* The picture inside the band is 140% of the band's height, pulled up
     20%, so there is 20% of slack above and below it. We never move it more
     than 18% of the band height, which keeps an edge from showing. */
  var bands = [].slice.call(document.querySelectorAll('[data-parallax]'));

  if (bands.length && !slow) {
    var DRIFT = 0.16;
    var frame = 0;

    function place() {
      frame = 0;
      var mid = window.innerHeight / 2;
      for (var i = 0; i < bands.length; i++) {
        var pic = bands[i];
        var box = pic.parentNode.getBoundingClientRect();
        if (box.bottom < -200 || box.top > window.innerHeight + 200) { continue; }
        var limit = box.height * 0.18;
        var shift = (box.top + box.height / 2 - mid) * DRIFT;
        if (shift >  limit) { shift =  limit; }
        if (shift < -limit) { shift = -limit; }
        pic.style.transform = 'translate3d(0,' + shift.toFixed(1) + 'px,0)';
      }
    }

    /* Cancel and re-request rather than a "busy" flag. A background tab
       never runs the callback, and a flag left set would freeze the
       parallax for good once the tab came back. */
    function ask() {
      if (frame) { cancelAnimationFrame(frame); }
      frame = requestAnimationFrame(place);
    }

    window.addEventListener('scroll', ask, { passive: true });
    window.addEventListener('resize', ask);
    window.addEventListener('pageshow', ask);
    document.addEventListener('visibilitychange', function () {
      if (!document.hidden) { ask(); }
    });
    place();
  }


  /* --- 5. the photograph popup ------------------------------------------ */

  var box    = document.getElementById('lightbox');
  var shots  = [].slice.call(document.querySelectorAll('.work-item'));

  if (box && shots.length) {
    var pic     = box.querySelector('.lb-img');
    var cap     = box.querySelector('.lb-cap');
    var shut    = box.querySelector('.lb-close');
    var back    = box.querySelector('.lb-prev');
    var forward = box.querySelector('.lb-next');
    var at      = 0;
    var opener  = null;

    function show(i) {
      at = (i + shots.length) % shots.length;
      var thumb = shots[at].querySelector('img');
      // currentSrc is whichever file the browser actually chose, WebP or
      // JPEG, and it is already in the cache, so the popup opens instantly.
      pic.src = thumb.currentSrc || thumb.src;
      pic.alt = thumb.alt || '';
      cap.textContent = shots[at].getAttribute('data-cap') || thumb.alt || '';
    }

    function open(i) {
      opener = document.activeElement;
      show(i);
      box.hidden = false;
      document.body.classList.add('lb-open');
      shut.focus();
    }

    function close() {
      box.hidden = true;
      document.body.classList.remove('lb-open');
      // An empty src counts as a request for the page itself on some
      // browsers, so the attribute goes rather than being blanked.
      pic.removeAttribute('src');
      if (opener && opener.focus) { opener.focus(); }
    }

    shots.forEach(function (btn, i) {
      btn.addEventListener('click', function () { open(i); });
    });

    shut.addEventListener('click', close);
    back.addEventListener('click', function () { show(at - 1); });
    forward.addEventListener('click', function () { show(at + 1); });
    box.addEventListener('click', function (e) { if (e.target === box) { close(); } });

    document.addEventListener('keydown', function (e) {
      if (box.hidden) { return; }
      if (e.key === 'Escape')     { close(); }
      if (e.key === 'ArrowLeft')  { show(at - 1); }
      if (e.key === 'ArrowRight') { show(at + 1); }
      if (e.key === 'Tab') {
        // Keep the keyboard inside the popup while it is open.
        var stops = [shut, back, forward];
        var i = stops.indexOf(document.activeElement);
        e.preventDefault();
        stops[(i + (e.shiftKey ? -1 : 1) + stops.length) % stops.length].focus();
      }
    });

    // Swipe, for a phone.
    var x0 = null;
    box.addEventListener('touchstart', function (e) { x0 = e.touches[0].clientX; }, { passive: true });
    box.addEventListener('touchend', function (e) {
      if (x0 === null) { return; }
      var dx = e.changedTouches[0].clientX - x0;
      if (Math.abs(dx) > 45) { show(at + (dx < 0 ? 1 : -1)); }
      x0 = null;
    }, { passive: true });
  }


  /* --- 6. the form ------------------------------------------------------- */

  var forms = [].slice.call(document.querySelectorAll('form.enq'));

  forms.forEach(function (form) {

    /* The timestamp. send.php refuses anything sent within two and a half
       seconds of the page loading, which no human manages and most bots do.
       It is stamped here rather than printed into the HTML so that a cached
       copy of the page cannot arrive with an old time on it. */
    var stamp = form.querySelector('input[name="ts"]');
    if (stamp) { stamp.value = String(Date.now()); }

    var say = form.querySelector('.reply');

    function clearErrors() {
      form.querySelectorAll('.err').forEach(function (p) { p.textContent = ''; });
      form.querySelectorAll('.field.bad').forEach(function (f) { f.classList.remove('bad'); });
    }

    function showError(name, text) {
      var slot = form.querySelector('.err[data-for="' + name + '"]');
      if (!slot) { return; }
      slot.textContent = text;
      var field = slot.closest('.field');
      if (field) { field.classList.add('bad'); }
    }

    /* A light check before the post, so an obvious mistake is caught without
       a round trip. send.php checks everything again, properly, because
       anything done here can be switched off by whoever is submitting. */
    function looksWrong() {
      var bad = false;
      var email = form.querySelector('[name="email"]');

      if (email && email.value.trim() === '') {
        showError('email', 'Please give us an email address, so we can send the proposal somewhere.');
        bad = true;
      } else if (email && email.value.indexOf('@') < 1) {
        showError('email', 'That email address does not look right. Please check it.');
        bad = true;
      }
      return bad;
    }

    form.addEventListener('submit', function (e) {
      clearErrors();
      if (say) { say.textContent = ''; say.className = 'reply'; }

      if (looksWrong()) {
        e.preventDefault();
        var first = form.querySelector('.field.bad input');
        if (first) { first.focus(); }
        return;
      }

      // Everything past here needs fetch. Without it the form posts
      // normally and send.php answers with a page of its own.
      if (!window.fetch) { return; }
      e.preventDefault();

      var button = form.querySelector('button[type="submit"]');
      var label  = button ? button.textContent : '';
      if (button) { button.disabled = true; button.textContent = 'Sending...'; }

      fetch(form.action, {
        method: 'POST',
        body: new FormData(form),
        headers: { 'X-Requested-With': 'fetch', 'Accept': 'application/json' }
      })
        .then(function (r) { return r.json().catch(function () { return { ok: false, message: 'Something went wrong. Please try again.' }; }); })
        .then(function (data) {
          if (button) { button.disabled = false; button.textContent = label; }

          if (data.ok) {
            replaceWithThanks(form, data.message);
            return;
          }
          if (data.errors) {
            Object.keys(data.errors).forEach(function (k) { showError(k, data.errors[k]); });
          }
          if (say) { say.textContent = data.message || 'Please check the form.'; say.className = 'reply bad'; }
          var first = form.querySelector('.field.bad input, .field.bad select, .field.bad textarea');
          if (first) { first.focus(); }
        })
        .catch(function () {
          if (button) { button.disabled = false; button.textContent = label; }
          if (say) { say.textContent = 'We could not reach the server. Please try again, or call us.'; say.className = 'reply bad'; }
        });
    });
  });

  /* Swap the sent form for a thank you, in place, so the page does not
     jump and nothing can be submitted twice. */
  function replaceWithThanks(form, message) {
    var done = document.createElement('div');
    done.className = 'done';
    done.setAttribute('role', 'status');

    var tick = document.createElement('span');
    tick.className = 'tick';

    var head = document.createElement('h3');
    head.textContent = 'Thank you';

    var body = document.createElement('p');
    body.textContent = message || 'Your brief is with us and we will be in touch.';

    done.appendChild(tick);
    done.appendChild(head);
    done.appendChild(body);

    form.parentNode.replaceChild(done, form);
    done.scrollIntoView({ behavior: slow ? 'auto' : 'smooth', block: 'center' });
  }

}());
