Add the Academy: narrated, animated roasting & brewing lessons; cupping link on roast detail
Test and deploy / test-and-deploy (push) Successful in 53s
Test and deploy / test-and-deploy (push) Successful in 53s
Academy (/academy, 'Learn' nav group): - 10 lessons / 45 slides across two tracks. Roasting: seed anatomy and composition, drying & the turning point, Maillard, first crack & development (DTR), curve/RoR reading (crash, flick, stall), defects → one-change discipline. Brewing: extraction physics (dissolving order, yield, grind, temp/time/ratio), then technique per brewer family — immersion (French press, AeroPress, Clever/Hario Switch, cold brew, cupping), percolation (bloom, V60, Chemex/Kalita, batch, percolator), espresso & pressure (puck prep, shot reading, dialing in, moka) - One cohesive scene system: 25 parameterized animated SVG scenes drawn from the app's palette (beans, curves, phase bars, brewers reusing the silhouette library), CSS keyframe animations, reduced-motion support - Narration: Azure TTS (en-US-Andrew HD, eastus) pre-generated to 45 committed MP3s by scripts/generate-academy-audio.mjs; slides speak phonetic respellings (my-YARD, KEM-ex, MOH-kah…) while captions show normal spelling; hands-free autoplay advances after each clip - Player: slide dots, keyboard arrows/space, per-slide captions Roast detail: 'Open cupping session' button next to cup notes (opens or creates the linked plan's session) — its score and flavors flow into the updated .alog download. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0b4857c225
commit
64c1b3605b
+147
@@ -3089,3 +3089,150 @@ select.f {
|
||||
margin: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Academy ──────────────────────────────────────────────────────────── */
|
||||
.academy-lessons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.academy-lesson {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-raised);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
transition: border-color 0.12s, background 0.12s;
|
||||
}
|
||||
.academy-lesson:hover {
|
||||
border-color: var(--ember);
|
||||
background: var(--ember-soft);
|
||||
}
|
||||
.academy-lesson-num {
|
||||
flex: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-sunken);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
color: var(--ember);
|
||||
}
|
||||
.academy-lesson-label {
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
}
|
||||
.academy-lesson-meta {
|
||||
font-size: 11.5px;
|
||||
color: var(--ink-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.academy-stage {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 8px;
|
||||
}
|
||||
.academy-stage svg {
|
||||
width: 100%;
|
||||
max-width: 760px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.academy-caption {
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
max-width: 720px;
|
||||
margin: 14px auto 10px;
|
||||
min-height: 3.2em;
|
||||
}
|
||||
.academy-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.academy-dots {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.academy-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--line-strong);
|
||||
background: var(--surface-raised);
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.academy-dot.active {
|
||||
background: var(--ember);
|
||||
border-color: var(--ember);
|
||||
}
|
||||
|
||||
/* 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; }
|
||||
@keyframes ac-pulse-kf { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
|
||||
.ac-pulse { animation: ac-pulse-kf 1.6s ease-in-out infinite; }
|
||||
@keyframes ac-breathe-kf { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.045); } }
|
||||
.ac-breathe { animation: ac-breathe-kf 3s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
@keyframes ac-steam-kf { 0% { transform: translateY(6px); opacity: 0; } 30% { opacity: 0.55; } 100% { transform: translateY(-16px); opacity: 0; } }
|
||||
.ac-steam { animation: ac-steam-kf 2.4s ease-out infinite; }
|
||||
@keyframes ac-grow-w-kf { from { width: 0; } to { width: var(--w); } }
|
||||
.ac-grow-w { animation: ac-grow-w-kf 1.4s ease-out forwards; }
|
||||
@keyframes ac-grow-h-kf { from { height: 0; } to { height: 100px; } }
|
||||
.ac-grow-h { animation: ac-grow-h-kf 1.6s ease-out forwards; }
|
||||
@keyframes ac-grow-h-down-kf { from { height: 0; } to { height: 20px; } }
|
||||
.ac-grow-h-down { animation: ac-grow-h-down-kf 2.4s ease-out forwards; }
|
||||
@keyframes ac-spin-kf { to { transform: rotate(360deg); } }
|
||||
.ac-spin { animation: ac-spin-kf 9s linear infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.ac-spin-slow { animation: ac-spin-kf 8s linear infinite; }
|
||||
@keyframes ac-sparkle-kf { 0%, 100% { opacity: 0.2; transform: scale(0.7); } 50% { opacity: 1; transform: scale(1.25); } }
|
||||
.ac-sparkle { animation: ac-sparkle-kf 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
@keyframes ac-drift-kf { 0% { transform: translate(0, 0); opacity: 0.9; } 100% { transform: translate(26px, -20px); opacity: 0; } }
|
||||
.ac-drift { animation: ac-drift-kf 2.6s ease-out infinite; }
|
||||
@keyframes ac-shake-kf { 0%, 100% { transform: translate(0); } 25% { transform: translate(-1.5px, 1px); } 75% { transform: translate(1.5px, -1px); } }
|
||||
.ac-shake { animation: ac-shake-kf 0.28s linear infinite; }
|
||||
@keyframes ac-ring-kf { from { transform: scale(0.6); opacity: 0.9; } to { transform: scale(1.5); opacity: 0; } }
|
||||
.ac-ring { animation: ac-ring-kf 1.7s ease-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
@keyframes ac-crack-kf { from { stroke-dashoffset: 120; } to { stroke-dashoffset: 0; } }
|
||||
.ac-crackline { stroke-dasharray: 120; animation: ac-crack-kf 1.4s ease-in forwards; }
|
||||
@keyframes ac-dissolve-kf { 0% { transform: translate(0, 0); opacity: 1; } 100% { transform: translate(56px, -34px); opacity: 0; } }
|
||||
.ac-dissolve { animation: ac-dissolve-kf 2.4s ease-out infinite; }
|
||||
@keyframes ac-float-kf { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
|
||||
.ac-float { animation: ac-float-kf 2.6s ease-in-out infinite; }
|
||||
@keyframes ac-rise-kf { 0% { transform: translateY(8px); opacity: 0; } 40% { opacity: 1; } 100% { transform: translateY(-22px); opacity: 0; } }
|
||||
.ac-rise { animation: ac-rise-kf 2s ease-out infinite; }
|
||||
@keyframes ac-drip-kf { 0% { transform: translateY(0); opacity: 1; } 100% { transform: translateY(14px); opacity: 0; } }
|
||||
.ac-drip { animation: ac-drip-kf 1.1s ease-in infinite; }
|
||||
@keyframes ac-tamp-kf { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(9px); } }
|
||||
.ac-tamp { animation: ac-tamp-kf 2s ease-in-out infinite; }
|
||||
@keyframes ac-slide-x-kf { 0%, 100% { transform: translateX(-46px); } 50% { transform: translateX(46px); } }
|
||||
.ac-slide-x { animation: ac-slide-x-kf 5s ease-in-out infinite; }
|
||||
@keyframes ac-dial-kf { 0%, 100% { transform: rotate(-38deg); } 50% { transform: rotate(38deg); } }
|
||||
.ac-dial { animation: ac-dial-kf 6s ease-in-out infinite; }
|
||||
@keyframes ac-pour-kf { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
|
||||
.ac-pourline { animation: ac-pour-kf 1.6s ease-in-out infinite; }
|
||||
@keyframes ac-maillard-kf { 0% { fill: #d9ba5a; } 100% { fill: #8a5a30; } }
|
||||
.ac-maillard-shift ellipse:first-child { animation: ac-maillard-kf 5s ease-in-out infinite alternate; }
|
||||
@keyframes ac-tumble-kf { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-8px) rotate(6deg); } }
|
||||
.ac-tumble { animation: ac-tumble-kf 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
@keyframes ac-flame-kf { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(1.12); } }
|
||||
.ac-flame { animation: ac-flame-kf 0.9s ease-in-out infinite; transform-box: fill-box; transform-origin: bottom center; }
|
||||
.ac-delay-0 { animation-delay: 0s; }
|
||||
.ac-delay-1 { animation-delay: 0.55s; }
|
||||
.ac-delay-2 { animation-delay: 1.1s; }
|
||||
.ac-delay-3 { animation-delay: 1.65s; }
|
||||
.ac-delay-4 { animation-delay: 2.2s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.academy-stage svg * { animation: none !important; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user