:root { --accent: #ff4fc3; --ink: #14060f; } * { box-sizing: border-box; } html, body { margin: 0; height: 100%; overflow: hidden; background: var(--ink); font-family: system-ui, -apple-system, "Segoe UI", sans-serif; cursor: none; } /* --- background ------------------------------------------------------- */ #backgrounds { position: fixed; inset: 0; } #backgrounds img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 900ms ease; /* Knock the poster back so the video frame stays the focal point — the posters are pale and read far brighter on a projector than on a desk. */ filter: brightness(0.45) saturate(1.15); } #backgrounds img.active { opacity: 1; } /* --- frozen screenshots ------------------------------------------------ */ #stills { position: fixed; inset: 0; pointer-events: none; z-index: 1; } #stills img { position: absolute; top: 0; left: 0; object-fit: cover; opacity: 0; /* Matches #frameSkin, so a still reads as a frozen copy of the frame. */ border-radius: 10px; border: 2px solid rgba(255, 255, 255, 0.85); box-shadow: 0 0 0 4px rgba(255, 79, 195, 0.55), 0 24px 70px rgba(0, 0, 0, 0.7); transition: opacity 500ms ease; will-change: transform; } #stills img.visible { opacity: 1; } /* The trail ramps opacity frame-by-frame as a still ages, so the CSS transition above would only fight it. */ #stills img.trail { transition: none; } /* --- the moving frame -------------------------------------------------- */ #frameMover { position: fixed; top: 0; left: 0; /* Above the stills, so the live video always wins if anything ever overlaps. */ z-index: 2; will-change: transform; } #frameSkin { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: 10px; border: 2px solid rgba(255, 255, 255, 0.85); box-shadow: 0 0 0 4px rgba(255, 79, 195, 0.55), 0 24px 70px rgba(0, 0, 0, 0.7); background: #000; } /* Lift the frame while the drag animation is holding it, the way a dragged window separates from the desktop. */ #frameMover.dragging #frameSkin { box-shadow: 0 0 0 4px rgba(255, 79, 195, 0.75), 0 40px 100px rgba(0, 0, 0, 0.85); } /* --- the drawn pointer -------------------------------------------------- */ /* Built and removed by the drag animation; nothing else on the page has one. */ #pointer { position: fixed; top: 0; left: 0; z-index: 3; pointer-events: none; will-change: transform; filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.6)); } #pointer svg { display: block; transition: transform 90ms ease; } /* The click itself: the arrow dips, so a grab is visible even mid-pause. */ #pointer.down svg { transform: scale(0.82); } .deck { position: absolute; inset: 0; opacity: 0; /* Opacity is driven frame-by-frame during a crossfade, so no CSS transition here — it would fight the JS ramp. */ } .deck iframe { width: 100%; height: 100%; border: 0; /* The iframe would otherwise swallow nothing useful, but keeping it inert avoids stray clicks pausing playback. */ pointer-events: none; } /* The screen-capture stream's sink. Has to be in the document to keep getting frames (see capture.js), but must not be visible or catch clicks. */ #captureSink { position: fixed; left: 0; top: 0; width: 1px; height: 1px; opacity: 0.01; pointer-events: none; z-index: -1; } #probe { position: fixed; left: -400px; top: -400px; width: 320px; height: 180px; opacity: 0; pointer-events: none; } /* --- start gate -------------------------------------------------------- */ #gate { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(20, 6, 15, 0.92); backdrop-filter: blur(6px); z-index: 10; cursor: pointer; transition: opacity 400ms ease; } #gate.hidden { opacity: 0; pointer-events: none; } .gate-inner { text-align: center; color: #fff; } .gate-inner h1 { font-size: clamp(2rem, 7vw, 5rem); margin: 0 0 0.3em; letter-spacing: -0.03em; color: var(--accent); } .gate-inner p { margin: 0 0 2em; opacity: 0.7; font-size: 1.05rem; } #start { font: inherit; font-size: 1.3rem; padding: 0.7em 3em; border: 0; border-radius: 999px; background: var(--accent); color: var(--ink); font-weight: 700; cursor: pointer; } #start:disabled { opacity: 0.35; cursor: default; } #status { position: fixed; bottom: 12px; right: 16px; z-index: 20; color: var(--accent); font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; }