First-timer coaching, an escape from the judge, and a cleaner polaroid

- judge screen: the foot row now wraps (NEXT ORDER was pushed off-viewport
  on narrower windows — the game looked unfinishable), ENTER advances after
  a 0.6s guard, and the button says so
- polaroid: dropped the partly.party URL stamp
- kitchen: first session gets the phase hints at coaching volume (gold,
  pulsing) until the first slice is served

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-09-01 10:43:49 +10:00
parent 02ccf9e800
commit 317c78be71
3 changed files with 36 additions and 4 deletions

View File

@ -20,6 +20,9 @@ export class JudgeView implements View {
private slice: Slice | null = null;
private spin = 0;
private heatmap: 0 | 1 | 2 = 0;
/** Seconds since the scorecard appeared the leftover ENTER that served the
* toast must not also dismiss the verdict before anyone reads it. */
private age = 0;
private panel: Panel;
private cardEl!: HTMLElement;
@ -98,7 +101,7 @@ export class JudgeView implements View {
() => undefined,
);
});
this.nextBtn = el('button', 'btn', btns, 'NEXT ORDER');
this.nextBtn = el('button', 'btn', btns, 'NEXT ORDER');
this.nextBtn.addEventListener('click', () => this.onNext?.());
}
@ -161,9 +164,6 @@ export class JudgeView implements View {
row += w + ' ';
}
if (row.trim() && y <= H - 40) g.fillText((y === 762 ? '“' : '') + row.trim() + '”', 34, y);
g.font = '16px Georgia, serif';
g.fillStyle = '#8a7f72';
g.fillText('partly.party/toastsim', W - 210, H - 22);
// Hand it over.
const a = document.createElement('a');
a.href = out.toDataURL('image/png');
@ -181,6 +181,7 @@ export class JudgeView implements View {
eye.hide(); // the full portrait takes over from the corner glance
this.slice = slice;
this.spin = 0;
this.age = 0;
this.heatmap = 0;
slice.setHeatmap(0);
slice.setPresentation(true);
@ -334,6 +335,8 @@ export class JudgeView implements View {
}
update(dt: number): void {
this.age += dt;
if (this.age > 0.6 && this.app.input.justPressed('Enter')) this.onNext?.();
this.spin += dt * 0.42;
if (this.slice) this.slice.mesh.rotation.y = this.spin;
this.app.camera.position.set(0.15, 2.35, 3.5);

View File

@ -229,6 +229,10 @@ export class KitchenView implements View {
this.cueEl = el('div', 'cue', p, 'smells like bread');
this.hintEl = el('div', 'hint', p, 'SPACE — push the lever down');
// Never-served-a-slice players get the hints at coaching volume.
try {
if (!localStorage.getItem('toastsim-coached')) this.hintEl.classList.add('coach');
} catch { /* storage blocked — quiet hints are fine */ }
this.setSpread(this.spreadId);
}
@ -306,6 +310,10 @@ export class KitchenView implements View {
serve(): void {
if (this.phase !== 'spreading') return;
if (this.hintEl.classList.contains('coach')) {
this.hintEl.classList.remove('coach');
try { localStorage.setItem('toastsim-coached', '1'); } catch { /* fine */ }
}
this.onServed?.();
}

View File

@ -117,6 +117,20 @@ canvas#c {
min-height: 14px;
}
/* First-ever session: same hints, but loud enough to actually teach.
The class comes off for good once the first slice has been served. */
.toast-panel .hint.coach {
font-size: 15px;
font-weight: 700;
color: var(--gold);
animation: coach-pulse 1.6s ease-in-out infinite;
}
@keyframes coach-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
}
/* Pressure gauge: fill = what your wrist is making, gold = what the spread
needs to flow, red = where spreading becomes scraping. */
.toast-panel .gauge {
@ -379,6 +393,11 @@ canvas#c {
display: flex;
align-items: center;
justify-content: space-between;
/* Four buttons plus the stamp outgrow the 380px column on plenty of
windows; without wrap, NEXT ORDER is shoved past the viewport edge
and the game looks unfinishable. */
flex-wrap: wrap;
row-gap: 10px;
margin-top: 16px;
}
@ -416,6 +435,8 @@ canvas#c {
.judge-btns {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.btn {