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">
|
||||
<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">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
|
||||
@@ -3190,6 +3190,28 @@ select.f {
|
||||
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. */
|
||||
@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; }
|
||||
|
||||
@@ -97,6 +97,9 @@ function renderMenu() {
|
||||
function openLesson(track, lesson, index) {
|
||||
current = { track, lesson, index };
|
||||
$("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");
|
||||
renderSlide();
|
||||
}
|
||||
@@ -106,6 +109,7 @@ function closeLesson() {
|
||||
current = null;
|
||||
$("academy-player").classList.add("hidden");
|
||||
$("academy-menu").classList.remove("hidden");
|
||||
$("academy-films")?.classList.remove("hidden");
|
||||
renderMenu(); // reflect fresh progress
|
||||
}
|
||||
|
||||
|
||||
+9
-3
@@ -64,7 +64,11 @@ self.addEventListener("fetch", (event) => {
|
||||
if (
|
||||
request.method !== "GET" ||
|
||||
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;
|
||||
|
||||
@@ -86,10 +90,12 @@ self.addEventListener("fetch", (event) => {
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then((response) => {
|
||||
if (response.ok)
|
||||
// Only full 200 responses: cache.put() throws on 206 partials.
|
||||
if (response.status === 200)
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => cache.put(request, response.clone()));
|
||||
.then((cache) => cache.put(request, response.clone()))
|
||||
.catch(() => {});
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match(request)),
|
||||
|
||||
Reference in New Issue
Block a user