Academy films: locally generated physics videos of the roast and the brew
Test and deploy / test-and-deploy (push) Successful in 1m1s
Test and deploy / test-and-deploy (push) Successful in 1m1s
Two short macro films generated on the local ComfyUI instance (Wan 2.2 14B t2v + lightx2v 4-step LoRAs, 9 clips at 832x480) and stitched with crossfades and burned-in phase labels: roast-physics.mp4 (charge, drying, Maillard, first crack, drop) and brew-physics.mp4 (grind, bloom, immersion, espresso). A "Films — the physics in motion" card on the Academy menu plays them with posters; opening a lesson hides and pauses them. The service worker now bypasses /academy-video/ and /academy-audio/ (Range/206 responses the Cache API rejects) and only caches full 200s. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
@@ -64,6 +64,48 @@
|
|||||||
<main class="page-content">
|
<main class="page-content">
|
||||||
<div id="academy-menu"></div>
|
<div id="academy-menu"></div>
|
||||||
|
|
||||||
|
<section class="panel-card" id="academy-films">
|
||||||
|
<div class="panel-head">
|
||||||
|
<h2>Films — the physics in motion</h2>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p class="muted" style="margin-top: 0">
|
||||||
|
Two short macro films of what the lessons teach — every shot
|
||||||
|
generated on the local roaster's GPU.
|
||||||
|
</p>
|
||||||
|
<div class="academy-films-grid">
|
||||||
|
<figure class="academy-film">
|
||||||
|
<video
|
||||||
|
src="/academy-video/roast-physics.mp4?v=__ASSET_VERSION__"
|
||||||
|
poster="/academy-video/roast-physics-poster.jpg?v=__ASSET_VERSION__"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
playsinline
|
||||||
|
preload="metadata"
|
||||||
|
></video>
|
||||||
|
<figcaption>
|
||||||
|
The roast — charge · drying · Maillard · first crack · drop
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="academy-film">
|
||||||
|
<video
|
||||||
|
src="/academy-video/brew-physics.mp4?v=__ASSET_VERSION__"
|
||||||
|
poster="/academy-video/brew-physics-poster.jpg?v=__ASSET_VERSION__"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
playsinline
|
||||||
|
preload="metadata"
|
||||||
|
></video>
|
||||||
|
<figcaption>
|
||||||
|
The brew — grind · bloom · immersion · espresso
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="panel-card hidden" id="academy-player">
|
<section class="panel-card hidden" id="academy-player">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -3190,6 +3190,28 @@ select.f {
|
|||||||
border-color: var(--ember);
|
border-color: var(--ember);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.academy-films-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.academy-film {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.academy-film video {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: #1b1614;
|
||||||
|
}
|
||||||
|
.academy-film figcaption {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Scene animations — restarted per slide because the SVG is rebuilt. */
|
/* Scene animations — restarted per slide because the SVG is rebuilt. */
|
||||||
@keyframes ac-draw-kf { from { stroke-dashoffset: 900; } to { stroke-dashoffset: 0; } }
|
@keyframes ac-draw-kf { from { stroke-dashoffset: 900; } to { stroke-dashoffset: 0; } }
|
||||||
.ac-draw { stroke-dasharray: 900; animation: ac-draw-kf 2.6s ease-out forwards; }
|
.ac-draw { stroke-dasharray: 900; animation: ac-draw-kf 2.6s ease-out forwards; }
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ function renderMenu() {
|
|||||||
function openLesson(track, lesson, index) {
|
function openLesson(track, lesson, index) {
|
||||||
current = { track, lesson, index };
|
current = { track, lesson, index };
|
||||||
$("academy-menu").classList.add("hidden");
|
$("academy-menu").classList.add("hidden");
|
||||||
|
$("academy-films")?.classList.add("hidden");
|
||||||
|
// Pause any playing film so it doesn't run (and download) behind the lesson.
|
||||||
|
for (const video of document.querySelectorAll("#academy-films video")) video.pause();
|
||||||
$("academy-player").classList.remove("hidden");
|
$("academy-player").classList.remove("hidden");
|
||||||
renderSlide();
|
renderSlide();
|
||||||
}
|
}
|
||||||
@@ -106,6 +109,7 @@ function closeLesson() {
|
|||||||
current = null;
|
current = null;
|
||||||
$("academy-player").classList.add("hidden");
|
$("academy-player").classList.add("hidden");
|
||||||
$("academy-menu").classList.remove("hidden");
|
$("academy-menu").classList.remove("hidden");
|
||||||
|
$("academy-films")?.classList.remove("hidden");
|
||||||
renderMenu(); // reflect fresh progress
|
renderMenu(); // reflect fresh progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-3
@@ -64,7 +64,11 @@ self.addEventListener("fetch", (event) => {
|
|||||||
if (
|
if (
|
||||||
request.method !== "GET" ||
|
request.method !== "GET" ||
|
||||||
url.origin !== self.location.origin ||
|
url.origin !== self.location.origin ||
|
||||||
url.pathname.startsWith("/api/")
|
url.pathname.startsWith("/api/") ||
|
||||||
|
// Media streams use Range requests (206 responses the Cache API rejects) and are far
|
||||||
|
// too large to belong in the static cache — let the browser handle them natively.
|
||||||
|
url.pathname.startsWith("/academy-video/") ||
|
||||||
|
url.pathname.startsWith("/academy-audio/")
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -86,10 +90,12 @@ self.addEventListener("fetch", (event) => {
|
|||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(request)
|
fetch(request)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok)
|
// Only full 200 responses: cache.put() throws on 206 partials.
|
||||||
|
if (response.status === 200)
|
||||||
caches
|
caches
|
||||||
.open(CACHE_NAME)
|
.open(CACHE_NAME)
|
||||||
.then((cache) => cache.put(request, response.clone()));
|
.then((cache) => cache.put(request, response.clone()))
|
||||||
|
.catch(() => {});
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.catch(() => caches.match(request)),
|
.catch(() => caches.match(request)),
|
||||||
|
|||||||
Reference in New Issue
Block a user