feat(home): estado vacío amable y vibración háptica al ganar monedas
Un día sin tareas muestra una tarjeta con la mascota del niño en vez de una pantalla en blanco; la columna de tarde vacía tiene su propio mensaje. El feedback de recompensa suma una vibración corta (Vibration API) que se dispara junto al sonido sobre el mismo evento.
This commit is contained in:
@@ -22,8 +22,12 @@ import { TaskCardComponent, ToggleEvent } from './task-card.component';
|
||||
|
||||
<section class="col">
|
||||
<h2 class="col__head">🌙 {{ i18n.t('afternoon') }}</h2>
|
||||
@for (task of afternoon; track task.id) {
|
||||
<app-task-card [task]="task" [ttsEnabled]="ttsEnabled" (toggle)="toggle.emit($event)" />
|
||||
@if (afternoon.length) {
|
||||
@for (task of afternoon; track task.id) {
|
||||
<app-task-card [task]="task" [ttsEnabled]="ttsEnabled" (toggle)="toggle.emit($event)" />
|
||||
}
|
||||
} @else {
|
||||
<div class="empty">🌈 {{ i18n.t('freeAfternoon') }}</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -17,32 +17,41 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Conmutador de modo -->
|
||||
<div class="modes">
|
||||
<button type="button" class="modes__btn" [class.modes__btn--on]="mode() === 'BOARD'" (click)="setMode('BOARD')">
|
||||
🗂️ {{ i18n.t('board') }}
|
||||
</button>
|
||||
<button type="button" class="modes__btn" [class.modes__btn--on]="mode() === 'FOCUS'" (click)="setMode('FOCUS')">
|
||||
🎯 {{ i18n.t('focus') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Eventos del día -->
|
||||
<app-event-banner [events]="t.specialEvents" [ttsEnabled]="t.child.ttsEnabled" />
|
||||
|
||||
<!-- Progreso global -->
|
||||
<app-progress-bar [done]="t.progress.totalDone" [total]="t.progress.total" />
|
||||
@if (t.progress.total > 0) {
|
||||
<!-- Conmutador de modo -->
|
||||
<div class="modes">
|
||||
<button type="button" class="modes__btn" [class.modes__btn--on]="mode() === 'BOARD'" (click)="setMode('BOARD')">
|
||||
🗂️ {{ i18n.t('board') }}
|
||||
</button>
|
||||
<button type="button" class="modes__btn" [class.modes__btn--on]="mode() === 'FOCUS'" (click)="setMode('FOCUS')">
|
||||
🎯 {{ i18n.t('focus') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tareas: tablero o foco -->
|
||||
@if (mode() === 'BOARD') {
|
||||
<app-board-view
|
||||
[morning]="t.morning"
|
||||
[afternoon]="t.afternoon"
|
||||
[ttsEnabled]="t.child.ttsEnabled"
|
||||
(toggle)="onToggle($event)"
|
||||
/>
|
||||
<!-- Progreso global -->
|
||||
<app-progress-bar [done]="t.progress.totalDone" [total]="t.progress.total" />
|
||||
|
||||
<!-- Tareas: tablero o foco -->
|
||||
@if (mode() === 'BOARD') {
|
||||
<app-board-view
|
||||
[morning]="t.morning"
|
||||
[afternoon]="t.afternoon"
|
||||
[ttsEnabled]="t.child.ttsEnabled"
|
||||
(toggle)="onToggle($event)"
|
||||
/>
|
||||
} @else {
|
||||
<app-focus-view [tasksInput]="allTasks()" [ttsEnabled]="t.child.ttsEnabled" (toggle)="onToggle($event)" />
|
||||
}
|
||||
} @else {
|
||||
<app-focus-view [tasksInput]="allTasks()" [ttsEnabled]="t.child.ttsEnabled" (toggle)="onToggle($event)" />
|
||||
<!-- Día sin tareas: mensaje amable, nunca una pantalla en blanco. -->
|
||||
<div class="freeday">
|
||||
<span class="freeday__mascot">{{ t.child.mascot }}</span>
|
||||
<p class="freeday__title">{{ i18n.t('emptyDayTitle') }}</p>
|
||||
<p class="freeday__sub">{{ i18n.t('emptyDaySub') }} 🎉</p>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
|
||||
|
||||
@@ -89,6 +89,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Día sin tareas: tarjeta amable con la mascota, nunca una pantalla vacía.
|
||||
.freeday {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-6) var(--space-4);
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: var(--shadow-card);
|
||||
text-align: center;
|
||||
animation: slideUp 0.4s ease both;
|
||||
|
||||
&__mascot {
|
||||
font-size: 96px;
|
||||
line-height: 1;
|
||||
animation: floatY 3s ease-in-out infinite;
|
||||
}
|
||||
&__title {
|
||||
margin: var(--space-3) 0 0;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 1.8rem;
|
||||
color: var(--text-strong);
|
||||
}
|
||||
&__sub {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Moneda voladora: parte de la tarea y viaja al monedero.
|
||||
.flycoin {
|
||||
position: fixed;
|
||||
|
||||
@@ -127,12 +127,14 @@ export class HomeComponent implements OnInit {
|
||||
};
|
||||
this.today.set(updated);
|
||||
|
||||
// Feedback positivo solo al ganar monedas (marcar, no desmarcar).
|
||||
// Feedback positivo solo al ganar monedas (marcar, no desmarcar). Sonido y
|
||||
// vibración se disparan juntos sobre el mismo evento (armonía multimodal).
|
||||
if (result.coinsEarned > 0) {
|
||||
this.flyCoins(ev.x, ev.y, result.coinsEarned);
|
||||
this.wallet?.bump();
|
||||
if (current.child.soundEnabled) {
|
||||
this.sound.playReward();
|
||||
this.sound.buzz();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user