Skip to content

Commit bbeb7f3

Browse files
committed
demo videos
1 parent 91d91e0 commit bbeb7f3

4 files changed

Lines changed: 169 additions & 1 deletion

File tree

css/videos.css

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,69 @@
5353
opacity: 1;
5454
transform: scale(1.015);
5555
}
56+
/* ── Touch-friendly play button overlay ─────────────────────── */
57+
.featured-card-play {
58+
position: absolute;
59+
inset: 0;
60+
display: flex;
61+
align-items: center;
62+
justify-content: center;
63+
background: rgba(5, 5, 12, 0.25);
64+
border: none;
65+
cursor: pointer;
66+
z-index: 2;
67+
transition:
68+
opacity 0.3s ease,
69+
background 0.3s ease;
70+
-webkit-tap-highlight-color: transparent;
71+
}
72+
.featured-card-play::before {
73+
content: '';
74+
width: 54px;
75+
height: 54px;
76+
border-radius: 50%;
77+
background: rgba(88, 166, 255, 0.85);
78+
box-shadow: 0 4px 20px -2px rgba(88, 166, 255, 0.6);
79+
transition:
80+
transform 0.25s ease,
81+
background 0.25s ease;
82+
}
83+
/* Triangular "play" glyph layered over the disc */
84+
.featured-card-play::after {
85+
content: '';
86+
position: absolute;
87+
width: 0;
88+
height: 0;
89+
border-style: solid;
90+
border-width: 11px 0 11px 18px;
91+
border-color: transparent transparent transparent #05050c;
92+
transform: translateX(2px);
93+
transition: transform 0.25s ease;
94+
}
95+
.featured-card-play:hover::before,
96+
.featured-card-play:focus-visible::before {
97+
transform: scale(1.08);
98+
background: rgba(120, 184, 255, 0.95);
99+
}
100+
/* Hide the play button once the video is actively playing */
101+
.featured-card-media.is-playing .featured-card-play {
102+
opacity: 0;
103+
pointer-events: none;
104+
}
105+
/* Same for the modal media container */
106+
.modal-media.is-playing .featured-card-play {
107+
opacity: 0;
108+
pointer-events: none;
109+
}
110+
/* On non-touch devices that support hover, the card hover already
111+
starts playback — keep the button as a subtle affordance but let
112+
it fade so it doesn't obscure the video. */
113+
@media (hover: hover) and (pointer: fine) {
114+
.featured-card:hover .featured-card-play {
115+
opacity: 0;
116+
pointer-events: none;
117+
}
118+
}
56119

57120
@media (prefers-reduced-motion: reduce) {
58121
.featured-card-video {
@@ -62,4 +125,34 @@
62125
.featured-card:focus-within .featured-card-video {
63126
transform: none;
64127
}
128+
.featured-card-play::before {
129+
transition: none;
130+
}
131+
}
132+
/* ── Video inside the expanded modal ────────────────────────── */
133+
.modal-media {
134+
position: relative;
135+
width: 100%;
136+
margin: 1.5rem 1.75rem 0.5rem;
137+
width: calc(100% - 3.5rem);
138+
border-radius: 12px;
139+
overflow: hidden;
140+
background: #05050c;
141+
border: 1px solid rgba(150, 160, 255, 0.14);
142+
aspect-ratio: 16 / 9;
143+
line-height: 0;
144+
}
145+
.modal-media:empty {
146+
display: none;
147+
}
148+
.modal-media .featured-card-video {
149+
opacity: 1;
150+
}
151+
/* Larger, always-available play control in the modal */
152+
.modal-media .featured-card-play::before {
153+
width: 68px;
154+
height: 68px;
155+
}
156+
.modal-media .featured-card-play::after {
157+
border-width: 14px 0 14px 23px;
65158
}

experiments/spacelike-knots/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ <h1>
593593
>Distance-Entropy Weight
594594
<input type="number" class="value-input" id="val-entropy" value="0.0" step="0.01"
595595
/></label>
596-
<input type="range" id="param-entropy" min="0" max="2.0" step="0.01" value="0.0" />
596+
<input type="range" id="param-entropy" min="-10.0" max="10.0" step="0.01" value="0.0" />
597597
<div style="font-size: 0.6rem; color: var(--text-muted); margin-top: -2px">
598598
Maximize Shannon entropy of pairwise distances (0 = off)
599599
</div>

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ <h2 class="modal-title" id="modalTitle"></h2>
748748
<a class="modal-launch" id="modalLaunch" href="#">Open <span class="arrow"></span></a>
749749
<button class="modal-close" id="modalClose" aria-label="Close"></button>
750750
</div>
751+
<div class="modal-media" id="modalMedia"></div>
751752
<div class="modal-body" id="modalBody"></div>
752753
</div>
753754
</div>

js/home.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ function openModal(card) {
251251
modalIcon.textContent = icon;
252252
modalTitle.textContent = title;
253253
modalLaunch.href = href;
254+
mountModalVideo(card, title);
254255
if (readmeCache.has(path)) {
255256
modalBody.innerHTML = readmeCache.get(path);
256257
renderMathAndDiagrams(modalBody);
@@ -282,6 +283,60 @@ function closeModal() {
282283
overlay.classList.remove('open');
283284
overlay.setAttribute('aria-hidden', 'true');
284285
document.body.classList.remove('modal-open');
286+
clearModalVideo();
287+
}
288+
/* ── Modal video helpers ───────────────────────────────────── */
289+
const modalMedia = document.getElementById('modalMedia');
290+
function clearModalVideo() {
291+
if (!modalMedia) return;
292+
const v = modalMedia.querySelector('video');
293+
if (v) {
294+
v.pause();
295+
}
296+
modalMedia.innerHTML = '';
297+
modalMedia.classList.remove('is-playing');
298+
}
299+
function mountModalVideo(card, title) {
300+
if (!modalMedia) return;
301+
clearModalVideo();
302+
const src = card.dataset.video;
303+
if (!src) return;
304+
const video = document.createElement('video');
305+
video.className = 'featured-card-video';
306+
video.muted = true;
307+
video.loop = true;
308+
video.playsInline = true;
309+
video.controls = true;
310+
video.preload = 'metadata';
311+
video.setAttribute('aria-label', (title || 'demo') + ' — demonstration video');
312+
const source = document.createElement('source');
313+
source.src = src;
314+
source.type = 'video/mp4';
315+
video.appendChild(source);
316+
modalMedia.appendChild(video);
317+
const reduceMotion =
318+
window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
319+
// Touch-friendly play button overlay
320+
const playBtn = document.createElement('button');
321+
playBtn.className = 'featured-card-play';
322+
playBtn.type = 'button';
323+
playBtn.setAttribute('aria-label', 'Play ' + (title || 'demo') + ' video');
324+
modalMedia.appendChild(playBtn);
325+
const togglePlay = () => {
326+
if (video.paused) {
327+
const p = video.play();
328+
if (p && typeof p.catch === 'function') p.catch(() => {});
329+
} else {
330+
video.pause();
331+
}
332+
};
333+
playBtn.addEventListener('click', togglePlay);
334+
video.addEventListener('play', () => modalMedia.classList.add('is-playing'));
335+
video.addEventListener('pause', () => modalMedia.classList.remove('is-playing'));
336+
if (!reduceMotion) {
337+
const p = video.play();
338+
if (p && typeof p.catch === 'function') p.catch(() => {});
339+
}
285340
}
286341

287342
document.querySelectorAll('.featured-card[data-readme]').forEach((card) => {
@@ -334,6 +389,25 @@ document.addEventListener('keydown', (e) => {
334389
source.type = 'video/mp4';
335390
video.appendChild(source);
336391
mount.appendChild(video);
392+
// Track play state so the overlay button can hide itself.
393+
video.addEventListener('play', () => mount.classList.add('is-playing'));
394+
video.addEventListener('pause', () => mount.classList.remove('is-playing'));
395+
// Touch-friendly play button overlay (works without hover).
396+
const playBtn = document.createElement('button');
397+
playBtn.className = 'featured-card-play';
398+
playBtn.type = 'button';
399+
playBtn.setAttribute('aria-label', 'Play ' + title + ' video');
400+
playBtn.addEventListener('click', (e) => {
401+
// Don't trigger the card's modal-open click handler.
402+
e.stopPropagation();
403+
e.preventDefault();
404+
if (video.paused) {
405+
safePlay(video);
406+
} else {
407+
video.pause();
408+
}
409+
});
410+
mount.appendChild(playBtn);
337411
mount.dataset.built = 'true';
338412
return video;
339413
}

0 commit comments

Comments
 (0)