Lay the party logo over the background

Centred, spanning 60% of the screen width, on the poster's layer so the
video frame and the stills still pass in front of it. The file lives in
assets/ with the posters, so it has to be kept out of the background
rotation — it is the one image there that is never a background.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9v2Xoa6eoeGYpK6gp95k9
This commit is contained in:
simon
2026-08-10 23:46:19 +02:00
co-authored by Claude Opus 5
parent 0683acd69e
commit 7706fe46c6
6 changed files with 47 additions and 2 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+10 -1
View File
@@ -115,6 +115,15 @@ export const DEFAULT_ANIMATION = AUTO_ANIMATION;
const ASSET_DIR = join(dirname(fileURLToPath(import.meta.url)), 'assets');
const IMAGE_TYPES = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif']);
// The party's logo, laid over every background. Lives in assets/ with the
// posters, so it has to be kept out of the rotation below — it is the one
// image there that is never a background of its own.
const LOGO_FILE = 'logo.png';
export const LOGO_SRC = `/assets/${LOGO_FILE}`;
// How much of the screen's width the logo spans, centred.
export const LOGO_WIDTH = 0.6;
function readBackgrounds() {
let files;
try {
@@ -125,7 +134,7 @@ function readBackgrounds() {
return Object.fromEntries(
files
.filter((file) => IMAGE_TYPES.has(extname(file).toLowerCase()))
.filter((file) => file !== LOGO_FILE && IMAGE_TYPES.has(extname(file).toLowerCase()))
.sort()
.map((file) => {
const key = file.slice(0, -extname(file).length);
+13
View File
@@ -17,11 +17,24 @@ import {
BACKGROUND_MAX_ZOOM,
BACKGROUND_FOCUS_SPREAD,
BACKGROUND_MAX_OVERSAMPLE,
LOGO_SRC,
LOGO_WIDTH,
} from '/config.js';
import { createPicker } from './motion.js';
const KEYS = Object.keys(BACKGROUNDS);
/**
* Point the logo overlay at the configured file and size it against the
* screen. The element is in the page from the start — only the two values that
* belong to the config are set here.
*/
export function showLogo(element) {
if (!element) return;
element.src = LOGO_SRC;
element.style.width = `${LOGO_WIDTH * 100}vw`;
}
/**
* A zoom level and the point of the image the crop closes in on, in percent.
* The focal point is pulled toward the middle by BACKGROUND_FOCUS_SPREAD, so a
+4
View File
@@ -9,6 +9,10 @@
<body>
<div id="backgrounds"></div>
<!-- The party's logo, centred over the poster. Its src and width come from
the shared config, so the file can be swapped without touching this. -->
<img id="logo" alt="" />
<!-- Frozen screenshots of the frame. A sibling, not a child, of #frameMover:
these are positioned against the screen, not the moving frame. -->
<div id="stills"></div>
+17
View File
@@ -42,6 +42,23 @@ body {
opacity: 1;
}
/* The logo sits on the poster, not on the video: it shares the backdrop's
layer so the frame and the stills still pass in front of it. Its width comes
from LOGO_WIDTH, written onto the element by backdrop.js. */
#logo {
position: fixed;
top: 50%;
left: 50%;
translate: -50% -50%;
height: auto;
/* Only bites on a screen too short for the width above — on the projector
the width is what decides. */
max-height: 70vh;
object-fit: contain;
pointer-events: none;
z-index: 0;
}
/* Built and removed by the wash animation. Sits between the poster and the
stills, and blends rather than covers, so the poster stays legible through
whatever colour is passing over it. The gradient itself is written per frame
+3 -1
View File
@@ -2,7 +2,7 @@ import { connect } from '/shared/socket.js';
import { OVERLAP_SECONDS, HARD_CUT_SECONDS, AUTO_BACKGROUND, AUTO_ANIMATION } from '/config.js';
import { createPlayer, loadApi, STATE } from './yt.js';
import { startAnimationCycle } from './motion.js';
import { startBackgroundCycle } from './backdrop.js';
import { startBackgroundCycle, showLogo } from './backdrop.js';
import { requestCapture } from './capture.js';
const TICK_MS = 250;
@@ -25,6 +25,8 @@ const backgrounds = startBackgroundCycle(
state.background,
);
showLogo(document.getElementById('logo'));
// --- start gate ---------------------------------------------------------
const gate = document.getElementById('gate');