Skip to the write-up

Self-initiated concept · April 2024

Noctis Studio A darkroom, not a gallery

Stillness, shot like cinema. The concept imagines a studio shooting eighteen weddings a year with every frame graded by hand, and a site that keeps the lights off for the same reason. It carries the largest interaction layer of the ten.

Sector
Photography · London
Art direction
Dark premium cinematic
JavaScript
25.6 KB, 796 lines — the largest here
Breakpoints
19 media queries
Imagery
10 photographs, 0 inline SVG

What this project demonstrates

Two of the four disciplines, and the write-up below is the evidence for both.

  • Websites The script tag carries an onerror handler that strips the JS class, so a failed load reverts every clip-path wipe to its settled state instead of leaving a black screen.
  • Mobile apps Not an app — the touch-and-pointer interaction an app is made of: a drag held by pointer capture, so it keeps receiving events when the pointer leaves the element and the browser releases it on pointerup, with arrow buttons, a live counter and keyboard paging beside it.
The whole Noctis Studio page in one capture: the hero and its camera readout, the drag strip of selected works, the three disciplines, the atelier, the process, packages and the booking diary
The finished page end to end. Scroll inside the frame to read it.

01 · Overview

A darkroom, not a gallery

Photographers' websites usually make one of two mistakes: a lightbox grid that treats every image as interchangeable, or a full-bleed slideshow that lets you look at nothing for very long. For this concept I imagined Noctis as a studio that grades every frame by hand and takes eighteen weddings a year, so the site had to be as selective as the booking calendar.

It behaves like a darkroom instead. The ground is near-black, the only colour is a signal red used the way a camera uses it, reveals arrive as a shutter wipe rather than a fade, and the work sits on a strip you drag — a contact sheet, not a carousel.

02 · The idea

Available light, hand-graded

Three decisions carry the build, and each of them borrows the discipline of the equipment rather than a web convention.

  • Red is a signal

    Record dots, frame counters, one call to action. Never a background, never decoration — exactly how the camera uses it.

  • Wipes, not fades

    Every reveal is a clip-path travelling in one direction. It reads as a shutter, which is the one metaphor this brand has earned.

  • A cursor that behaves

    A custom dot on fine pointers only, which never replaces the system cursor over text or a form field.

03 · Goals

What the page had to do

  1. Show work at a size worth showing

    A horizontal strip where each frame is big enough to judge, and where moving through it is a deliberate act rather than an autoplay.

  2. Explain three disciplines without three pages

    Weddings, editorial and commercial in one section under one grade, because the studio treats them the same way.

  3. Publish real availability

    A booking diary computed from the current date, opening on the first of the month, instead of a form that promises someone will be in touch.

  4. Never trap the reader in the dark

    If the script fails, the onerror handler strips the JS class so nothing stays hidden behind a wipe that will never run.

04 · Build order

Ten modules, in order

The script is written as ten numbered modules, and they went in in the order the page depends on them: the grade, then the frame around it, then the motion, then the two things that take an enquiry.

  1. The grade

    Fourteen custom properties on :root: near-black, a lifted black so panels read without a border, a silver and two hairline greys, and three reds — the brand red, a deeper shade for white type on a red fill at 4.95:1, and a brighter one for red type on the near-black ground at 4.64:1.

  2. Header and overlay nav

    A condensing header and a full-screen menu with a tab trap. These came first, because everything else on the page sits under them.

  3. The wipe

    One easing curve, one clip-path direction, applied through a single class so every section reveals identically.

  4. The drag rail

    Pointer capture, arrow buttons, a live counter and keyboard focus safety — the largest single module in the build.

  5. The diary and the form

    Availability derived from the actual date, then a five-field booking enquiry that quotes a reference back.

05 · Architecture

Three files

No bundler, no slider library and no dependency of any kind. The rail, the cursor, the wipes and the diary are 796 hand-written lines across ten modules in one file.

noctis-studio/

index.html
31.2 KB
6 sections, 10 photographs, 0 inline SVG, 1 form
styles.css
39.5 KB
14 root custom properties, 19 media queries, 2 reduced-motion blocks
script.js
25.6 KB
796 lines in 10 numbered modules

The stack

  • Pointer capture
  • clip-path
  • Custom cursor
  • IntersectionObserver
  • Date maths
  • CSS Grid
  • Marcellus
  • Figtree

Accessibility surface

  • Tab trap
  • aria-live counter
  • Offset anchors
  • Keyboard rail
  • onerror fallback
  • pointer: fine gate
  • Reduced motion

06 · What fought back

Problems, and what beat them

  1. Challenge

    Drag-to-scroll built with document-level mousemove listeners breaks the moment the cursor leaves the element, and leaks a listener every time the pointer is lost.

    Solution

    setPointerCapture. The element keeps receiving events wherever the cursor goes, and the browser releases the capture itself on pointerup or pointercancel, with no cleanup code to forget.

  2. Challenge

    A drag that finishes on top of a link fires a click, so releasing the mouse after moving the rail navigates away from the page.

    Solution

    Track the total travel and swallow the click in the capture phase if it exceeds a few pixels. Below the threshold it was a click; above it, it was a drag.

  3. Challenge

    A custom cursor is the fastest way to make a site feel broken: it lags on a trackpad, disappears over inputs and means nothing at all on touch.

    Solution

    It is gated behind a fine-pointer query, so it never exists on touch, and the real cursor is restored over every interactive element rather than hidden globally.

  4. Challenge

    Clip-path reveals hide content by default, and this is the darkest page in the set — a failed script would leave a black screen with no explanation.

    Solution

    The script tag carries an onerror that removes the JS class, so a failed load reverts every wipe to its settled state instead of hiding the studio's entire portfolio.

07 · Worth keeping

The parts worth keeping

  • Selected Works

    A drag strip with arrows, a counter and keyboard paging. Every input method reaches every frame.

  • The booking diary

    Availability derived from the actual date and opening on the first of the month — real scarcity rather than a claimed one.

  • The camera HUD

    A frame readout in the hero that borrows the viewfinder's own typography. Pure CSS, no canvas and no image.

  • We keep the lights off

    An atelier section that explains the grade — the one place the studio talks about method instead of showing results.

08 · Run it live

Run it here

The real build, embedded and running. Nothing is fetched from it until you ask, so the page you are reading stays as light as it looks.

Skip the live preview

The real build, running in the page. The live preview is an embedded page. Press Escape to leave it.

Open it full screen

Please rotate your device,
This is a vertical build.