diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..ebc5c8f Binary files /dev/null and b/assets/logo.png differ diff --git a/config.js b/config.js index f301229..394608c 100644 --- a/config.js +++ b/config.js @@ -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); diff --git a/public/player/backdrop.js b/public/player/backdrop.js index f045d17..2fb9e3c 100644 --- a/public/player/backdrop.js +++ b/public/player/backdrop.js @@ -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 diff --git a/public/player/index.html b/public/player/index.html index 0cb8afb..be10148 100644 --- a/public/player/index.html +++ b/public/player/index.html @@ -9,6 +9,10 @@
+ + +
diff --git a/public/player/player.css b/public/player/player.css index 097f8be..485426d 100644 --- a/public/player/player.css +++ b/public/player/player.css @@ -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 diff --git a/public/player/player.js b/public/player/player.js index 6ea6ed0..4fe9089 100644 --- a/public/player/player.js +++ b/public/player/player.js @@ -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');