Immersive

Immersive

A dark room for the image. The controls fade after two seconds and return on any movement.

A real lightbox, open inside the frame. Use ←→ to page and Esc to close it.

import { createLightbox } from "@reuse-sh/lightbox";
import "@reuse-sh/lightbox/styles.css";
import "./lightbox-style.css";
import { addChrome } from "./lightbox-chrome.js";

const lightbox = createLightbox({
  className: "lightbox-style",
});

lightbox.open(photos, 0);
addChrome(lightbox, photos);

lightbox-style.css

.lightbox-style {
  --lightbox-pad: 16px;
  --lightbox-side: 0px;
  --lightbox-caption: 0px;
  --lightbox-strip: 0px;
  --lightbox-bar: 0px;
  --lightbox-under: 0px;
  --lightbox-bottom: calc(var(--lightbox-caption) + var(--lightbox-strip) + var(--lightbox-bar) + var(--lightbox-under));
  --lightbox-ring: 0 0 0 0.5px rgb(0 0 0 / 10%), 0 1px 2px rgb(0 0 0 / 6%), 0 2px 6px rgb(0 0 0 / 4%);
  --reuse-lightbox-background: #fcfcfc;
  --reuse-lightbox-foreground: #1f1f1f;
  --reuse-lightbox-control: #fff;
}
.lightbox-style *, .lightbox-style *::before, .lightbox-style *::after { box-sizing: border-box; }
.lightbox-style .reuse-lightbox-viewport {
  width: calc(100% - var(--lightbox-side));
  height: calc(100% - var(--lightbox-bottom));
}
.lightbox-style .reuse-lightbox-button {
  width: 40px;
  height: 40px;
  padding: 0;
  color: inherit;
  box-shadow: var(--lightbox-ring);
  backdrop-filter: none;
  transition: background-color 150ms ease, transform 160ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms ease;
}
.lightbox-style .reuse-lightbox-button svg { width: 18px; height: 18px; }
.lightbox-style .reuse-lightbox-button:hover { background: #f4f4f4; }
.lightbox-style .reuse-lightbox-button:active:not(:disabled) { transform: scale(0.95); }
.lightbox-style .reuse-lightbox-button:disabled { opacity: 0.3; }
.lightbox-style .reuse-lightbox-close, .lightbox-style .reuse-lightbox-zoom { top: var(--lightbox-pad); }
.lightbox-style .reuse-lightbox-close { right: var(--lightbox-pad); }
.lightbox-style .reuse-lightbox-zoom { right: calc(var(--lightbox-pad) + 48px); }
.lightbox-style .reuse-lightbox-footer {
  right: calc(var(--lightbox-side) + var(--lightbox-pad));
  bottom: calc(var(--lightbox-under) + var(--lightbox-pad));
  left: var(--lightbox-pad);
  gap: 10px;
}
.lightbox-style .reuse-lightbox-counter {
  padding: 0;
  background: none;
  backdrop-filter: none;
  font-size: 13px;
}

.lightbox-style .reuse-lightbox-prev,
.lightbox-style .reuse-lightbox-next { top: calc((100% - var(--lightbox-bottom)) / 2 - 20px); }
.lightbox-style .reuse-lightbox-prev { left: var(--lightbox-pad); }
.lightbox-style .reuse-lightbox-next { right: calc(var(--lightbox-side) + var(--lightbox-pad)); }

.lightbox-style .reuse-lightbox-counter {
  position: fixed;
  z-index: 2;
  top: var(--lightbox-pad);
  left: var(--lightbox-pad);
  display: grid;
  height: 40px;
  padding: 0 14px;
  place-items: center;
  border-radius: 999px;
  background: #fff;
  box-shadow: var(--lightbox-ring);
  font-weight: 500;
}

.lightbox-style .reuse-lightbox-zoom { display: none; }

.lightbox-style { --lightbox-caption: 30px; }
.lightbox-style .reuse-lightbox-caption {
  order: 1;
  padding: 0;
  background: none;
  color: #4a4a4a;
  font-size: 14px;
  line-height: 20px;
}

.lightbox-style {
  --lightbox-ring: none;
  --reuse-lightbox-background: #09090a;
  --reuse-lightbox-foreground: #fff;
  --reuse-lightbox-control: rgb(255 255 255 / 8%);
}
.lightbox-style .reuse-lightbox-button:hover { background: rgb(255 255 255 / 14%); }

.lightbox-style .reuse-lightbox-caption { color: rgb(255 255 255 / 72%); }

.lightbox-style .reuse-lightbox-counter {
  height: auto;
  padding: 0;
  top: calc(var(--lightbox-pad) + 12px);
  left: calc(var(--lightbox-pad) + 4px);
  background: none;
  color: rgb(255 255 255 / 60%);
  font-family: ui-monospace, monospace;
  font-size: 12px;
  font-weight: 400;
}

.lightbox-style :is(.reuse-lightbox-button, .reuse-lightbox-counter, .reuse-lightbox-caption, .reuse-lightbox-thumbnails, .lightbox-style-toolbar) {
  transition: opacity 400ms ease, background-color 150ms ease, transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
}
.lightbox-style[data-idle], .lightbox-style[data-idle] .reuse-lightbox-frame { cursor: none; }
.lightbox-style[data-idle] :is(.reuse-lightbox-button, .reuse-lightbox-counter, .reuse-lightbox-caption, .reuse-lightbox-thumbnails, .lightbox-style-toolbar) {
  opacity: 0;
  transition-duration: 700ms;
}

@media (max-width: 559px) {
  .lightbox-style { --lightbox-pad: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox-style, .lightbox-style * { transition-duration: 0ms !important; }
}

lightbox-chrome.js

export function addChrome(lightbox, items) {
  const dialog = document.querySelector("dialog.lightbox-style");
  const part = (name) => dialog.querySelector(`.reuse-lightbox-${name}`);
  const syncs = [];
  const cleanups = [];

  let timer = 0;
  const wake = () => {
    dialog.removeAttribute("data-idle");
    clearTimeout(timer);
    timer = setTimeout(() => dialog.setAttribute("data-idle", ""), 2000);
  };
  const types = ["pointermove", "pointerdown", "keydown", "wheel"];
  for (const type of types) dialog.addEventListener(type, wake, { passive: true });
  wake();
  cleanups.push(() => clearTimeout(timer));

  const sync = (state) => syncs.forEach((fn) => fn(state));
  sync(lightbox.getState());
  const stop = lightbox.subscribe((state) => {
    if (state.isOpen) sync(state);
    else if (!state.isClosing) {
      stop();
      cleanups.forEach((fn) => fn());
    }
  });
}