Gatefold Documentation 1.1

Overlay Navigation

HTML Markup

Steps between the items inside an open overlay, without the overlay closing. The items are nested inside one panel — they are not eight sibling panels being swapped.


<div class="cel-overlay" data-cel-overlay-id="portfolio">

  <div class="cel-overlay-item" data-cel-overlay-item="portfolio-1">
    <div class="cel-overlay-media"><img src="…" alt=""></div>
    <div class="cel-overlay-body">
      <div class="cel-overlay-scroll">…</div>
    </div>
    <div class="cel-overlay-nav">
      <button class="cel-overlay-prev">Prev</button>
      <button class="cel-overlay-next">Next</button>
    </div>
  </div>

  <div class="cel-overlay-item" data-cel-overlay-item="portfolio-2"> … </div>

</div>

<!-- A trigger may name the item to open on, not just the overlay. -->
<button class="cel-overlay-trigger"
        data-cel-overlay-id="portfolio"
        data-cel-overlay-item="portfolio-3">Open the third</button>
Hook Where Purpose
.cel-overlay-item direct child of the panel One step. Two or more turns navigation on; fewer and the module ignores the panel.
data-cel-overlay-item on each item Its id, used by triggers and reported in the event.
.cel-overlay-prev / .cel-overlay-next anywhere inside the panel The controls. Delegated, so every item may carry its own copy at no cost.
data-cel-overlay-item on a trigger Optional. Opens the overlay on that item instead of the first.

Why one panel with eight items, rather than eight panels? Because overlay.js holds the focus trap, the inert bookkeeping and the Escape binding against one specific element, all of it private. Swapping panels behind its back would leave every one of those pointing at the wrong node. Nesting the items sidesteps the problem entirely.

celOverlayNav.init()

Collects the items in every overlay, binds one delegated listener per panel, and puts the stack into a valid state before anything is seen.


Initialization

celOverlayNav.init();

Called for you on DOMContentLoaded, after celOverlay.init(), at the bottom of main.js. Safe to call again: the per-panel listener is marked and skipped, and the global ones are bound once. Each call resets every overlay to its first item and replaces the previous options.

Custom Configuration

Pass an object to the call in main.js, for example celOverlayNav.init({ loop: false }).

Option Default Description
loop true Past the last item, back to the first.
duration 0.5 Dissolve duration, in seconds.
ease 'power1.inOut' Close to linear, on purpose — see the transition below.
imageScale 1.06 The arriving photograph settles out of this. Set to 1 for no movement at all.
keyboard true ← and → step while the overlay is open.

next() / prev() / to()


celOverlayNav.next();                  // the open overlay
celOverlayNav.prev('portfolio');       // a named one
celOverlayNav.to('portfolio-5');       // straight to an item
celOverlayNav.to('portfolio-5', 'portfolio');

With no id, the module uses whichever overlay is open, falling back to the first one it found.

Events

Event Fired Detail
cel:overlay-nav on every activation, including the first { id, index, item }

This is the module's entire published surface. Anything that wants to follow the active item listens for it and is told nothing else — which is exactly how the carousel stays in step without either script knowing about the other.

Behavior

  • Hidden items are made inert, synchronously, before the fade. visibility: hidden does not remove an element from the box tree, so without this the eight items would all stay in the tab order.
  • Focus follows the control you used. Where it should land is decided before anything is marked inert — marking an ancestor of the focused element inert blurs it, and by then there is nothing left to read.
  • Both scrollers are reset on every step. Each item keeps its own scroll position, and arriving at a new article halfway down it is disorienting. Both, because which one is live depends on the breakpoint.
  • The dialog's accessible name follows the item, or a screen reader would announce the first project's title for all eight.
  • A trigger that names an item is handled in the capture phase, so the item is in place before the panel starts coming up — otherwise the overlay opens on whatever was last read and then jumps.

The Step Transition

A one-sided dissolve. Only the arriving item changes opacity, and it does so on top of a departing item that stays fully opaque and perfectly still.


Why not a cross-fade

In a true cross-fade both items are at partial opacity at the same moment, so the panel background shows through the pair of them and the middle of every step washes out to a pale version of itself. Here the lower layer is opaque throughout: the picture is only ever one image over another, never either of them over the ground.

The departing item is hidden only once it is completely covered. Hiding it up front would drop the solid layer out from under the dissolve, which is the wash this construction exists to avoid.

Why nothing moves sideways

Sliding the items sideways would show the seam between photograph and text twice, a few pixels apart, for the length of every step. Both items always sit at the same x, so the panel's layout never appears to shift.

The image scale is the one movement, and it stays inside a clipping box, so it never moves any edge of the panel. It also runs 1.8× longer than the dissolve — ending together makes the step land twice, and letting the picture keep settling after the text has arrived is what stops it feeling like a slide change.

Why the easing is nearly linear

A dissolve is a change of substance, not a move, and it reads best at an even rate. A strongly eased one feels hesitant.

z-index is set explicitly on both items rather than left to source order — grid items take part in z-ordering without being positioned, and source order would put the arriving item underneath whenever you step backwards.