Video — Project Detail
A cinematic project-detail page for a fictional short film, built on a near-black canvas with letterboxed hero bars, amber accents, and JetBrains Mono timecodes. A large poster player with a scrubbable timeline anchors the layout, followed by a project meta panel (client, role, year, runtime), an expandable synopsis, a filmstrip stills gallery with a lightbox, a grouped credits list, tech specs, and a related-films rail. Vanilla JS drives play/scrub, the lightbox, credit filtering, and toasts.
MCP
Code
:root {
--bg: #0a0a0b;
--surface: #141416;
--surface-2: #1c1c1f;
--amber: #ffb020;
--amber-d: #e6971a;
--red: #ff4d4d;
--ink: #f4f4f6;
--muted: #8a8a92;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: Inter, system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
}
/* film grain overlay */
.grain {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9;
opacity: 0.05;
background-image:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.4) 0.5px, transparent 1px),
radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.3) 0.5px, transparent 1px),
radial-gradient(circle at 45% 85%, rgba(255, 255, 255, 0.35) 0.5px, transparent 1px);
background-size: 3px 3px, 4px 4px, 5px 5px;
mix-blend-mode: overlay;
}
a { color: inherit; text-decoration: none; }
.mono { font-family: var(--mono); font-feature-settings: "tnum"; }
:focus-visible {
outline: 2px solid var(--amber);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
gap: 20px;
padding: 16px 28px;
border-bottom: 1px solid var(--line);
position: sticky;
top: 0;
z-index: 8;
background: rgba(10, 10, 11, 0.82);
backdrop-filter: blur(12px);
}
.brand { display: flex; align-items: center; gap: 9px; font-weight: 800; letter-spacing: 0.06em; }
.brand-mark { color: var(--amber); font-size: 13px; }
.brand-name { font-size: 15px; }
.brand-dim { color: var(--muted); font-weight: 600; }
.crumbs {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--muted);
margin-left: 8px;
overflow: hidden;
}
.crumbs a:hover { color: var(--ink); }
.crumbs [aria-current] { color: var(--amber); font-weight: 600; }
.topbar-actions { margin-left: auto; display: flex; gap: 10px; }
.btn {
font: inherit;
font-weight: 600;
font-size: 13px;
border: 1px solid var(--line-2);
background: var(--surface-2);
color: var(--ink);
padding: 8px 14px;
border-radius: var(--r-md);
cursor: pointer;
transition: transform 0.12s, background 0.15s, border-color 0.15s;
display: inline-flex;
align-items: center;
gap: 7px;
white-space: nowrap;
}
.btn:hover { border-color: var(--amber); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn.ghost { background: transparent; }
.btn.ghost.is-on { color: var(--amber); border-color: var(--amber); }
.btn.solid {
background: var(--amber);
color: #17130a;
border-color: var(--amber);
}
.btn.solid:hover { background: var(--amber-d); border-color: var(--amber-d); }
.btn.full { width: 100%; justify-content: center; margin-top: 6px; }
/* ---------- Layout ---------- */
.wrap {
max-width: 1120px;
margin: 0 auto;
padding: 28px 28px 60px;
}
/* ---------- Hero player ---------- */
.player-shell { margin-bottom: 30px; }
.letterbox {
position: relative;
border-radius: var(--r-lg);
overflow: hidden;
border: 1px solid var(--line);
background: #000;
box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.9);
aspect-ratio: 21 / 9;
}
.poster {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
transition: transform 0.6s ease, filter 0.4s;
}
.letterbox[data-playing="true"] .poster { transform: scale(1.04); filter: saturate(1.1); }
.bars {
position: absolute;
left: 0;
right: 0;
height: 9%;
background: #000;
z-index: 2;
transition: opacity 0.5s;
}
.bars.top { top: 0; }
.bars.bottom { bottom: 0; }
.letterbox[data-playing="true"] .bars { opacity: 0.65; }
.runtime-badge {
position: absolute;
top: 14px;
left: 14px;
z-index: 4;
display: inline-flex;
align-items: center;
gap: 7px;
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--ink);
background: rgba(0, 0, 0, 0.55);
border: 1px solid var(--line-2);
padding: 5px 10px;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.rec-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--red);
box-shadow: 0 0 8px var(--red);
animation: pulse 1.6s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 4;
width: 84px;
height: 84px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: rgba(20, 20, 22, 0.55);
backdrop-filter: blur(6px);
color: var(--amber);
cursor: pointer;
display: grid;
place-items: center;
transition: transform 0.18s, background 0.2s, box-shadow 0.2s;
}
.play-btn:hover {
transform: translate(-50%, -50%) scale(1.08);
background: rgba(255, 176, 32, 0.16);
box-shadow: 0 0 0 8px rgba(255, 176, 32, 0.08);
}
.play-btn svg { width: 34px; height: 34px; fill: currentColor; }
.play-btn .ic-pause { display: none; }
.letterbox[data-playing="true"] .play-btn {
opacity: 0;
pointer-events: none;
}
.letterbox[data-playing="true"]:hover .play-btn { opacity: 1; pointer-events: auto; }
.letterbox[data-playing="true"] .play-btn .ic-play { display: none; }
.letterbox[data-playing="true"] .play-btn .ic-pause { display: block; }
.transport {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
display: flex;
align-items: center;
gap: 12px;
padding: 16px 18px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.85), transparent);
}
.mini-play {
font: inherit;
width: 34px;
height: 34px;
flex: 0 0 auto;
border-radius: 50%;
border: 1px solid var(--line-2);
background: rgba(0, 0, 0, 0.4);
color: var(--amber);
cursor: pointer;
font-size: 12px;
display: grid;
place-items: center;
transition: background 0.15s, border-color 0.15s;
}
.mini-play:hover { border-color: var(--amber); background: rgba(255, 176, 32, 0.12); }
.tc {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
color: var(--ink);
letter-spacing: 0.03em;
}
.tc.dim { color: var(--muted); font-weight: 500; }
.scrub {
position: relative;
flex: 1;
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
cursor: pointer;
}
.scrub-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--amber-d), var(--amber));
}
.scrub-thumb {
position: absolute;
top: 50%;
left: 0%;
transform: translate(-50%, -50%);
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--amber);
box-shadow: 0 0 0 4px rgba(255, 176, 32, 0.2);
transition: transform 0.12s;
}
.scrub:hover .scrub-thumb, .scrub:focus-visible .scrub-thumb { transform: translate(-50%, -50%) scale(1.2); }
/* ---------- Columns ---------- */
.cols {
display: grid;
grid-template-columns: 1fr 320px;
gap: 26px;
align-items: start;
}
.col-side { display: grid; gap: 20px; position: sticky; top: 84px; }
/* ---------- Title block ---------- */
.title-block { margin-bottom: 24px; }
.eyebrow {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--amber);
margin-bottom: 10px;
}
h1 {
margin: 0 0 10px;
font-size: clamp(32px, 6vw, 52px);
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.02;
}
.tagline { margin: 0 0 16px; font-size: 17px; color: var(--muted); max-width: 46ch; }
.award-row { display: flex; flex-wrap: wrap; gap: 10px; }
.award {
font-size: 12px;
font-weight: 600;
color: var(--ink);
background: var(--surface-2);
border: 1px solid var(--line);
padding: 6px 11px;
border-radius: 999px;
}
/* ---------- Panels ---------- */
.panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 22px;
margin-bottom: 22px;
}
.col-side .panel { margin-bottom: 0; }
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.panel-h {
margin: 0 0 14px;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.panel-head .panel-h { margin: 0; }
.count-chip, .chip {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
color: var(--amber);
background: rgba(255, 176, 32, 0.10);
border: 1px solid rgba(255, 176, 32, 0.25);
padding: 3px 9px;
border-radius: 999px;
}
/* synopsis */
.syn-body p { margin: 0 0 14px; color: #cfcfd6; font-size: 15.5px; }
.syn-body p:last-child { margin-bottom: 0; }
.syn-more[hidden] { display: none; }
.dir-note {
border-left: 2px solid var(--amber);
padding-left: 14px;
color: var(--muted) !important;
font-style: italic;
}
.dir-label {
display: inline-block;
font-family: var(--mono);
font-style: normal;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--amber);
margin-right: 6px;
}
.link-btn {
font: inherit;
font-weight: 600;
font-size: 13px;
color: var(--amber);
background: none;
border: none;
cursor: pointer;
padding: 6px 0;
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 4px;
}
.link-btn:hover { color: var(--amber-d); }
.link-btn span { transition: transform 0.2s; }
.link-btn:hover span { transform: translateY(2px); }
.link-btn[aria-expanded="true"] span { transform: rotate(180deg); }
/* ---------- Filmstrip / stills ---------- */
.filmstrip {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.still {
position: relative;
aspect-ratio: 16 / 10;
border-radius: var(--r-md);
overflow: hidden;
border: 1px solid var(--line);
cursor: pointer;
background-size: cover;
background-position: center;
transition: transform 0.2s, border-color 0.2s;
}
.still::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent 55%);
opacity: 0.5;
transition: opacity 0.2s;
}
.still:hover { transform: translateY(-3px) scale(1.02); border-color: var(--amber); }
.still:hover::after { opacity: 0.15; }
.still .still-tc {
position: absolute;
left: 8px;
bottom: 7px;
z-index: 2;
font-family: var(--mono);
font-size: 10px;
font-weight: 700;
color: var(--ink);
background: rgba(0, 0, 0, 0.5);
padding: 2px 6px;
border-radius: var(--r-sm);
}
/* ---------- Credits ---------- */
.filter-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.pill {
font: inherit;
font-size: 12px;
font-weight: 600;
color: var(--muted);
background: var(--surface-2);
border: 1px solid var(--line);
padding: 6px 12px;
border-radius: 999px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.pill:hover { color: var(--ink); border-color: var(--line-2); }
.pill[aria-selected="true"] {
color: #17130a;
background: var(--amber);
border-color: var(--amber);
}
.credits { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.credit {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
padding: 11px 4px;
border-bottom: 1px solid var(--line);
transition: background 0.15s, padding 0.15s;
}
.credit:last-child { border-bottom: none; }
.credit:hover { background: rgba(255, 255, 255, 0.02); padding-left: 10px; }
.credit .role { color: var(--muted); font-size: 13px; }
.credit .name { font-weight: 600; font-size: 14px; text-align: right; }
.credit .dept-tag {
font-family: var(--mono);
font-size: 9px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--amber);
margin-left: 8px;
opacity: 0.7;
}
/* ---------- Meta / specs ---------- */
.meta-list { margin: 0; display: grid; gap: 0; }
.meta-row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 14px;
padding: 10px 0;
border-bottom: 1px solid var(--line);
}
.meta-row:last-of-type { border-bottom: none; }
.meta-row dt { color: var(--muted); font-size: 13px; }
.meta-row dd { margin: 0; font-weight: 600; font-size: 14px; text-align: right; }
.spec-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.spec-list li {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 14px;
padding: 9px 0;
border-bottom: 1px solid var(--line);
font-size: 13px;
}
.spec-list li:last-child { border-bottom: none; }
.spec-list li span { color: var(--muted); }
.spec-list li b { font-size: 12.5px; color: var(--ink); }
/* ---------- Related ---------- */
.related { margin-top: 40px; }
.rel-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 18px;
}
.rel-head h2 { margin: 0; font-size: 20px; font-weight: 800; letter-spacing: -0.01em; }
.rel-rail {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.rel-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.rel-card:hover {
transform: translateY(-5px);
border-color: var(--line-2);
box-shadow: 0 24px 50px -24px rgba(0, 0, 0, 0.85);
}
.rel-thumb {
position: relative;
aspect-ratio: 16 / 9;
background-size: cover;
background-position: center;
}
.rel-thumb::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(10, 10, 11, 0.9), transparent 60%);
}
.rel-runtime {
position: absolute;
bottom: 9px;
right: 9px;
z-index: 2;
font-family: var(--mono);
font-size: 10px;
font-weight: 700;
color: var(--ink);
background: rgba(0, 0, 0, 0.6);
padding: 3px 7px;
border-radius: var(--r-sm);
}
.rel-body { padding: 13px 15px 16px; }
.rel-year {
font-family: var(--mono);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--amber);
}
.rel-title { font-weight: 700; font-size: 15px; margin: 4px 0 3px; }
.rel-meta { font-size: 12px; color: var(--muted); }
/* ---------- Footer ---------- */
.foot {
max-width: 1120px;
margin: 0 auto;
padding: 22px 28px 40px;
border-top: 1px solid var(--line);
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 12px;
color: var(--muted);
}
.foot .mono { color: var(--amber); opacity: 0.7; }
/* ---------- Lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 20;
display: grid;
place-items: center;
padding: 24px;
}
.lightbox[hidden] { display: none; }
.lb-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.86);
backdrop-filter: blur(6px);
animation: fade 0.25s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.lb-frame {
position: relative;
z-index: 2;
margin: 0;
width: min(920px, 100%);
animation: pop 0.28s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
@keyframes pop { from { transform: scale(0.94); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.lb-img {
aspect-ratio: 16 / 9;
border-radius: var(--r-lg);
border: 1px solid var(--line-2);
background-size: cover;
background-position: center;
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.4);
}
.lb-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 46px;
height: 46px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: rgba(20, 20, 22, 0.75);
color: var(--ink);
font-size: 24px;
line-height: 1;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
.lb-nav:hover { background: var(--amber); color: #17130a; border-color: var(--amber); }
.lb-nav.prev { left: -22px; }
.lb-nav.next { right: -22px; }
.lb-cap {
display: flex;
align-items: center;
gap: 12px;
margin-top: 14px;
font-size: 14px;
color: var(--ink);
}
.lb-cap .tc { color: var(--amber); }
.lb-close {
position: absolute;
top: -14px;
right: -14px;
width: 38px;
height: 38px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--surface-2);
color: var(--ink);
font-size: 15px;
cursor: pointer;
transition: background 0.15s, transform 0.15s;
}
.lb-close:hover { background: var(--red); border-color: var(--red); transform: rotate(90deg); }
/* ---------- Toast ---------- */
.toast-host {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
z-index: 30;
display: grid;
gap: 8px;
width: max-content;
max-width: 90vw;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
background: var(--surface-2);
border: 1px solid var(--line-2);
border-left: 3px solid var(--amber);
color: var(--ink);
padding: 11px 16px;
border-radius: var(--r-md);
font-size: 13.5px;
font-weight: 500;
box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.9);
animation: toast-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
.toast.out { animation: toast-out 0.25s forwards; }
.toast-ic { color: var(--amber); font-family: var(--mono); font-weight: 700; }
@keyframes toast-in { from { transform: translateY(14px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes toast-out { to { transform: translateY(14px); opacity: 0; } }
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.cols { grid-template-columns: 1fr; }
.col-side { position: static; }
.rel-rail { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.topbar { flex-wrap: wrap; gap: 12px; padding: 14px 18px; }
.crumbs { display: none; }
.topbar-actions { margin-left: auto; }
.wrap { padding: 20px 18px 48px; }
.letterbox { aspect-ratio: 16 / 10; }
.play-btn { width: 66px; height: 66px; }
.play-btn svg { width: 26px; height: 26px; }
.transport { padding: 12px 12px; gap: 9px; }
.tc { font-size: 11px; }
.filmstrip { grid-template-columns: repeat(2, 1fr); }
.rel-rail { grid-template-columns: 1fr; }
.panel { padding: 18px; }
.lb-nav.prev { left: 6px; }
.lb-nav.next { right: 6px; }
.lb-close { top: -10px; right: 6px; }
.foot { flex-direction: column; gap: 6px; padding: 20px 18px 32px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}(function () {
"use strict";
var $ = function (id) { return document.getElementById(id); };
/* ---------- Data ---------- */
var STILLS = [
{ img: "https://images.unsplash.com/photo-1478720568477-152d9b164e26?auto=format&fit=crop&w=1200&q=60", tc: "00:01:14", cap: "Keeper's routine — lamp room, dawn" },
{ img: "https://images.unsplash.com/photo-1500375592092-40eb2168fd21?auto=format&fit=crop&w=1200&q=60", tc: "00:03:48", cap: "The tide turns on the north reef" },
{ img: "https://images.unsplash.com/photo-1439066615861-d1af74d74000?auto=format&fit=crop&w=1200&q=60", tc: "00:06:02", cap: "Fog flattens the horizon line" },
{ img: "https://images.unsplash.com/photo-1502472584811-0a2f2feb8968?auto=format&fit=crop&w=1200&q=60", tc: "00:08:37", cap: "A signal light answers offshore" },
{ img: "https://images.unsplash.com/photo-1520962880247-cfaf541c8724?auto=format&fit=crop&w=1200&q=60", tc: "00:11:20", cap: "Oren counts down the beacon" },
{ img: "https://images.unsplash.com/photo-1505142468610-359e7d316be0?auto=format&fit=crop&w=1200&q=60", tc: "00:13:55", cap: "Final frame — the automated dawn" }
];
var CREDITS = [
{ role: "Director", name: "Mara Voss", dept: "Direction" },
{ role: "Editor", name: "Mara Voss", dept: "Direction" },
{ role: "Producer", name: "Ilya Renner", dept: "Direction" },
{ role: "Director of Photography", name: "Sena Okoro", dept: "Camera" },
{ role: "1st AC", name: "Tomas Beck", dept: "Camera" },
{ role: "Gaffer", name: "Priya Malhotra", dept: "Camera" },
{ role: "Production Designer", name: "Eli Fontaine", dept: "Art" },
{ role: "Colorist", name: "Wren Haas", dept: "Post" },
{ role: "Sound Designer", name: "Jonah Vega", dept: "Sound" },
{ role: "Composer", name: "Aster Lund", dept: "Sound" }
];
var RELATED = [
{ title: "Low Tide", year: "2024", meta: "Documentary short", runtime: "09:41", img: "https://images.unsplash.com/photo-1505118380757-91f5f5632de0?auto=format&fit=crop&w=800&q=60" },
{ title: "Salt & Signal", year: "2023", meta: "Music film", runtime: "04:12", img: "https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=800&q=60" },
{ title: "The Keeper", year: "2022", meta: "Narrative short", runtime: "17:30", img: "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=800&q=60" }
];
var TOTAL = 862; // 14:22 in seconds
/* ---------- Toast helper ---------- */
var host = $("toastHost");
function toast(msg, icon) {
var t = document.createElement("div");
t.className = "toast";
t.innerHTML = '<span class="toast-ic">' + (icon || "✓") + "</span><span></span>";
t.lastChild.textContent = msg;
host.appendChild(t);
setTimeout(function () {
t.classList.add("out");
setTimeout(function () { t.remove(); }, 260);
}, 2600);
}
/* ---------- Time formatting ---------- */
function fmt(sec) {
sec = Math.max(0, Math.min(TOTAL, Math.round(sec)));
var h = Math.floor(sec / 3600);
var m = Math.floor((sec % 3600) / 60);
var s = sec % 60;
var p = function (n) { return (n < 10 ? "0" : "") + n; };
return p(h) + ":" + p(m) + ":" + p(s);
}
/* ---------- Player transport ---------- */
var player = $("player");
var playBtn = $("playBtn");
var miniPlay = $("miniPlay");
var scrub = $("scrub");
var fill = $("scrubFill");
var thumb = $("scrubThumb");
var tcCurrent = $("tcCurrent");
var poster = player.querySelector(".poster");
var pos = 0;
var playing = false;
var timer = null;
function paintScrub() {
var pct = (pos / TOTAL) * 100;
fill.style.width = pct + "%";
thumb.style.left = pct + "%";
tcCurrent.textContent = fmt(pos);
scrub.setAttribute("aria-valuenow", Math.round(pos));
scrub.setAttribute("aria-valuetext", fmt(pos));
}
function setPlaying(state) {
playing = state;
player.setAttribute("data-playing", state ? "true" : "false");
playBtn.setAttribute("aria-label", state ? "Pause film" : "Play film");
miniPlay.textContent = state ? "❚❚" : "▶";
if (state) {
timer = setInterval(function () {
pos += 1;
if (pos >= TOTAL) { pos = TOTAL; setPlaying(false); toast("Playback complete", "✓"); }
paintScrub();
}, 250); // sped-up simulated transport
} else {
clearInterval(timer);
}
}
function togglePlay() { setPlaying(!playing); }
playBtn.addEventListener("click", togglePlay);
miniPlay.addEventListener("click", togglePlay);
/* seek on scrub */
function seekFromEvent(e) {
var rect = scrub.getBoundingClientRect();
var x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
pos = Math.max(0, Math.min(1, x / rect.width)) * TOTAL;
paintScrub();
}
var dragging = false;
scrub.addEventListener("pointerdown", function (e) {
dragging = true;
scrub.setPointerCapture(e.pointerId);
seekFromEvent(e);
});
scrub.addEventListener("pointermove", function (e) { if (dragging) seekFromEvent(e); });
scrub.addEventListener("pointerup", function () { dragging = false; });
scrub.addEventListener("keydown", function (e) {
var step = e.shiftKey ? 30 : 5;
if (e.key === "ArrowRight" || e.key === "ArrowUp") { pos = Math.min(TOTAL, pos + step); paintScrub(); e.preventDefault(); }
else if (e.key === "ArrowLeft" || e.key === "ArrowDown") { pos = Math.max(0, pos - step); paintScrub(); e.preventDefault(); }
else if (e.key === "Home") { pos = 0; paintScrub(); e.preventDefault(); }
else if (e.key === "End") { pos = TOTAL; paintScrub(); e.preventDefault(); }
});
/* spacebar toggles play when not typing in a field */
document.addEventListener("keydown", function (e) {
if (e.code === "Space" && document.activeElement && document.activeElement.tagName !== "BUTTON" && !lbOpen) {
var tag = document.activeElement.tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return;
togglePlay();
e.preventDefault();
}
});
paintScrub();
/* ---------- Synopsis toggle ---------- */
var synToggle = $("synToggle");
var synMore = $("synMore");
synToggle.addEventListener("click", function () {
var open = synMore.hasAttribute("hidden");
if (open) { synMore.removeAttribute("hidden"); } else { synMore.setAttribute("hidden", ""); }
synToggle.setAttribute("aria-expanded", open ? "true" : "false");
synToggle.firstChild.textContent = open ? "Hide director's notes " : "Read director's notes ";
});
/* ---------- Stills gallery ---------- */
var strip = $("filmstrip");
$("stillCount").textContent = STILLS.length + " frames";
STILLS.forEach(function (s, i) {
var el = document.createElement("button");
el.className = "still";
el.type = "button";
el.setAttribute("role", "listitem");
el.setAttribute("aria-label", "Open still " + (i + 1) + ": " + s.cap);
el.style.backgroundImage = "url('" + s.img + "')";
el.innerHTML = '<span class="still-tc">' + s.tc + "</span>";
el.addEventListener("click", function () { openLightbox(i); });
strip.appendChild(el);
});
/* ---------- Credits + filter ---------- */
var creditList = $("creditList");
var credFilters = $("credFilters");
var depts = ["All"];
CREDITS.forEach(function (c) { if (depts.indexOf(c.dept) === -1) depts.push(c.dept); });
function renderCredits(filter) {
creditList.innerHTML = "";
CREDITS.filter(function (c) { return filter === "All" || c.dept === filter; }).forEach(function (c) {
var li = document.createElement("li");
li.className = "credit";
li.innerHTML =
'<span class="role">' + c.role + '</span>' +
'<span class="name">' + c.name +
(filter === "All" ? '<span class="dept-tag">' + c.dept + "</span>" : "") +
"</span>";
creditList.appendChild(li);
});
}
depts.forEach(function (d, i) {
var b = document.createElement("button");
b.className = "pill";
b.type = "button";
b.setAttribute("role", "tab");
b.textContent = d;
b.setAttribute("aria-selected", i === 0 ? "true" : "false");
b.addEventListener("click", function () {
credFilters.querySelectorAll(".pill").forEach(function (p) { p.setAttribute("aria-selected", "false"); });
b.setAttribute("aria-selected", "true");
renderCredits(d);
});
credFilters.appendChild(b);
});
renderCredits("All");
/* ---------- Related rail ---------- */
var rail = $("relRail");
RELATED.forEach(function (r) {
var card = document.createElement("article");
card.className = "rel-card";
card.tabIndex = 0;
card.setAttribute("role", "button");
card.setAttribute("aria-label", r.title + " — " + r.meta + ", " + r.runtime);
card.innerHTML =
'<div class="rel-thumb" style="background-image:url(\'' + r.img + "')\">" +
'<span class="rel-runtime">' + r.runtime + "</span></div>" +
'<div class="rel-body"><div class="rel-year">' + r.year + "</div>" +
'<div class="rel-title">' + r.title + "</div>" +
'<div class="rel-meta">' + r.meta + "</div></div>";
var go = function () { toast('Opening "' + r.title + '"', "▶"); };
card.addEventListener("click", go);
card.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); go(); } });
rail.appendChild(card);
});
/* ---------- Lightbox ---------- */
var lightbox = $("lightbox");
var lbImg = $("lbImg");
var lbCap = $("lbCaption");
var lbIndex = $("lbIndex");
var lbOpen = false;
var current = 0;
var lastFocus = null;
function renderLightbox() {
var s = STILLS[current];
lbImg.style.backgroundImage = "url('" + s.img + "')";
lbCap.textContent = s.cap;
var p = function (n) { return (n < 10 ? "0" : "") + n; };
lbIndex.textContent = p(current + 1) + " / " + p(STILLS.length);
// sync hero poster to the selected still for a "swap the poster frame" feel
poster.style.backgroundImage =
"linear-gradient(120deg, rgba(255,176,32,.28), rgba(255,77,77,.20)), url('" + s.img + "')";
}
function openLightbox(i) {
current = i;
lastFocus = document.activeElement;
renderLightbox();
lightbox.hidden = false;
lbOpen = true;
$("lbClose").focus();
}
function closeLightbox() {
lightbox.hidden = true;
lbOpen = false;
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function step(d) {
current = (current + d + STILLS.length) % STILLS.length;
renderLightbox();
}
$("lbPrev").addEventListener("click", function () { step(-1); });
$("lbNext").addEventListener("click", function () { step(1); });
$("lbClose").addEventListener("click", closeLightbox);
lightbox.querySelector("[data-close]").addEventListener("click", closeLightbox);
document.addEventListener("keydown", function (e) {
if (!lbOpen) return;
if (e.key === "Escape") closeLightbox();
else if (e.key === "ArrowLeft") step(-1);
else if (e.key === "ArrowRight") step(1);
});
/* ---------- Topbar actions ---------- */
var saveBtn = $("saveBtn");
var saved = false;
saveBtn.addEventListener("click", function () {
saved = !saved;
saveBtn.classList.toggle("is-on", saved);
saveBtn.innerHTML = (saved ? "★" : "☆") + " " + (saved ? "Saved" : "Save");
toast(saved ? "Saved to your board" : "Removed from board", saved ? "★" : "☆");
});
$("shareBtn").addEventListener("click", function () {
var url = "https://vossfilm.example/work/halcyon-drift";
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(
function () { toast("Share link copied to clipboard", "↗"); },
function () { toast("Share: " + url, "↗"); }
);
} else {
toast("Share: " + url, "↗");
}
});
$("reelBtn").addEventListener("click", function () {
toast("Press kit requested — check your inbox", "✉");
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Halcyon Drift — Project Detail</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="grain" aria-hidden="true"></div>
<header class="topbar">
<a class="brand" href="#" aria-label="Voss Film — home">
<span class="brand-mark" aria-hidden="true">▲</span>
<span class="brand-name">VOSS<span class="brand-dim">FILM</span></span>
</a>
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Work</a>
<span aria-hidden="true">/</span>
<a href="#">Short Films</a>
<span aria-hidden="true">/</span>
<span aria-current="page">Halcyon Drift</span>
</nav>
<div class="topbar-actions">
<button class="btn ghost" id="saveBtn" type="button">
<span aria-hidden="true">☆</span> Save
</button>
<button class="btn ghost" id="shareBtn" type="button">
<span aria-hidden="true">↗</span> Share
</button>
</div>
</header>
<main class="wrap">
<!-- HERO PLAYER -->
<section class="player-shell" aria-label="Film player">
<div class="letterbox" id="player" data-playing="false">
<div class="poster" style="background-image:linear-gradient(120deg, rgba(255,176,32,.28), rgba(255,77,77,.20)), url('https://images.unsplash.com/photo-1478720568477-152d9b164e26?auto=format&fit=crop&w=1400&q=60');" role="img" aria-label="Still from Halcyon Drift"></div>
<div class="bars top" aria-hidden="true"></div>
<div class="bars bottom" aria-hidden="true"></div>
<span class="runtime-badge"><span class="rec-dot" aria-hidden="true"></span> 14:22 · 4K DCP</span>
<button class="play-btn" id="playBtn" type="button" aria-label="Play film">
<svg class="ic-play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
<svg class="ic-pause" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg>
</button>
<div class="transport">
<button class="mini-play" id="miniPlay" type="button" aria-label="Play or pause">▶</button>
<span class="tc" id="tcCurrent">00:00:00</span>
<div class="scrub" id="scrub" role="slider" tabindex="0" aria-label="Seek" aria-valuemin="0" aria-valuemax="862" aria-valuenow="0" aria-valuetext="0 seconds">
<div class="scrub-fill" id="scrubFill"></div>
<div class="scrub-thumb" id="scrubThumb"></div>
</div>
<span class="tc dim" id="tcTotal">00:14:22</span>
</div>
</div>
</section>
<div class="cols">
<!-- LEFT COLUMN -->
<div class="col-main">
<div class="title-block">
<div class="eyebrow">Short Film · 2025</div>
<h1>Halcyon Drift</h1>
<p class="tagline">A lighthouse keeper measures the last calm night before the tide takes the coast.</p>
<div class="award-row">
<span class="award">◈ Official Selection — Meridian Film Fest</span>
<span class="award">◈ Best Cinematography — Northlight Awards</span>
</div>
</div>
<section class="panel synopsis" aria-labelledby="synTitle">
<h2 id="synTitle" class="panel-h">Synopsis</h2>
<div class="syn-body" id="synBody">
<p>On a windless coast, keeper Oren Sable runs his nightly routine one final time before the automated beacon replaces him. As fog rolls the horizon flat, a drifting signal light appears offshore — steady, deliberate, answering his own.</p>
<div class="syn-more" id="synMore" hidden>
<p>Shot on 16mm and finished in a warm amber grade, <em>Halcyon Drift</em> trades dialogue for texture: the hiss of the lamp, the metronome of the foghorn, the patience of a man counting down his own obsolescence. The film premiered at Meridian and screened across eleven festivals in its first year.</p>
<p class="dir-note"><span class="dir-label">Director's note</span> — We wanted the sea to feel like a held breath. Every cut lands on the horn.</p>
</div>
</div>
<button class="link-btn" id="synToggle" type="button" aria-expanded="false" aria-controls="synMore">Read director's notes <span aria-hidden="true">↓</span></button>
</section>
<!-- STILLS GALLERY -->
<section class="panel" aria-labelledby="stillsTitle">
<div class="panel-head">
<h2 id="stillsTitle" class="panel-h">Stills</h2>
<span class="count-chip" id="stillCount">6 frames</span>
</div>
<div class="filmstrip" id="filmstrip" role="list"></div>
</section>
<!-- CREDITS -->
<section class="panel" aria-labelledby="credTitle">
<div class="panel-head">
<h2 id="credTitle" class="panel-h">Credits</h2>
</div>
<div class="filter-pills" id="credFilters" role="tablist" aria-label="Filter credits by department"></div>
<ul class="credits" id="creditList"></ul>
</section>
</div>
<!-- RIGHT COLUMN -->
<aside class="col-side">
<section class="panel meta" aria-labelledby="metaTitle">
<h2 id="metaTitle" class="panel-h">Project</h2>
<dl class="meta-list">
<div class="meta-row"><dt>Client</dt><dd>Northlight Studios</dd></div>
<div class="meta-row"><dt>Role</dt><dd>Director / Editor</dd></div>
<div class="meta-row"><dt>Year</dt><dd>2025</dd></div>
<div class="meta-row"><dt>Runtime</dt><dd class="mono">14:22</dd></div>
<div class="meta-row"><dt>Format</dt><dd>16mm → 4K DCP</dd></div>
<div class="meta-row"><dt>Aspect</dt><dd class="mono">2.39:1</dd></div>
</dl>
<button class="btn solid full" id="reelBtn" type="button">Request press kit</button>
</section>
<section class="panel specs" aria-labelledby="specTitle">
<h2 id="specTitle" class="panel-h">Tech specs</h2>
<ul class="spec-list">
<li><span>Camera</span><b class="mono">Arri 416</b></li>
<li><span>Lenses</span><b class="mono">Zeiss Super Speed</b></li>
<li><span>Stock</span><b class="mono">Kodak 500T</b></li>
<li><span>Frame rate</span><b class="mono">24 fps</b></li>
<li><span>Sound</span><b class="mono">5.1 surround</b></li>
</ul>
</section>
</aside>
</div>
<!-- RELATED FILMS -->
<section class="related" aria-labelledby="relTitle">
<div class="rel-head">
<h2 id="relTitle">Related films</h2>
<a class="link-btn" href="#">View full slate <span aria-hidden="true">→</span></a>
</div>
<div class="rel-rail" id="relRail"></div>
</section>
</main>
<footer class="foot">
<span>© 2025 Voss Film — fictional studio.</span>
<span class="mono">Illustrative UI only</span>
</footer>
<!-- LIGHTBOX -->
<div class="lightbox" id="lightbox" hidden>
<div class="lb-backdrop" data-close></div>
<figure class="lb-frame" role="dialog" aria-modal="true" aria-label="Still viewer">
<button class="lb-nav prev" id="lbPrev" type="button" aria-label="Previous still">‹</button>
<div class="lb-img" id="lbImg"></div>
<button class="lb-nav next" id="lbNext" type="button" aria-label="Next still">›</button>
<figcaption class="lb-cap"><span class="tc" id="lbIndex">01 / 06</span><span id="lbCaption"></span></figcaption>
<button class="lb-close" id="lbClose" type="button" aria-label="Close viewer">✕</button>
</figure>
</div>
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Project Detail
A film-forward project page for Halcyon Drift, a fictional 14-minute short by the imaginary director Mara Voss. The layout opens with a letterboxed hero player: a full-bleed poster framed by cinematic black bars, an amber play button, a runtime badge, and a scrubbable timeline that reports its position in a JetBrains Mono timecode. Below it a meta panel lays out client, role, year, format, and runtime as labelled rows, while an expandable synopsis reveals director’s notes on demand.
Interaction is entirely vanilla JavaScript. Pressing play (or the spacebar) toggles a simulated transport that advances the timecode and progress bar; the timeline is keyboard-accessible and click-to-seek. A filmstrip of production stills opens into a lightbox with arrow-key and button navigation and a caption per frame. The credits list can be filtered by department via a pill row, a toast() helper confirms the share and save actions, and the related-films rail offers hover-lifted cards. Every control has a visible focus ring, aria labelling, and AA-contrast text.
The whole thing is self-contained — no frameworks, no build step — so the markup, styles, and one small script drop into any project and re-skin from the :root palette. A prefers-reduced-motion block calms the transitions, and the grid reflows from a two-column desktop layout down to a single stacked column at roughly 360px.
Illustrative UI only — fictional film, not a real product.