From 5c79480aa418030a91958acf0c9dba4ed8db6912 Mon Sep 17 00:00:00 2001 From: Jaume Garriga Maestre Date: Sun, 12 Jul 2026 22:06:03 +0200 Subject: [PATCH] =?UTF-8?q?feat(home):=20estado=20vac=C3=ADo=20amable=20y?= =?UTF-8?q?=20vibraci=C3=B3n=20h=C3=A1ptica=20al=20ganar=20monedas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- frontend/src/app/core/i18n.service.ts | 3 ++ frontend/src/app/core/sound.service.ts | 13 ++++- .../app/features/home/board-view.component.ts | 8 ++- .../src/app/features/home/home.component.html | 51 +++++++++++-------- .../src/app/features/home/home.component.scss | 32 ++++++++++++ .../src/app/features/home/home.component.ts | 4 +- 6 files changed, 85 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/core/i18n.service.ts b/frontend/src/app/core/i18n.service.ts index c29a8c8..f272eea 100644 --- a/frontend/src/app/core/i18n.service.ts +++ b/frontend/src/app/core/i18n.service.ts @@ -21,6 +21,9 @@ export class I18nService { afternoon: { ES: 'TARDE', CA: 'TARDA' }, ready: { ES: 'listo', CA: 'fet' }, noSchool: { ES: 'HOY NO HAY COLE', CA: 'AVUI NO HI HA COLE' }, + freeAfternoon: { ES: 'TARDE LIBRE', CA: 'TARDA LLIURE' }, + emptyDayTitle: { ES: '¡Hoy no hay tareas!', CA: 'Avui no hi ha tasques!' }, + emptyDaySub: { ES: 'Disfruta del día', CA: 'Gaudeix del dia' }, allDone: { ES: '¡TODO LISTO!', CA: 'TOT FET!' }, great: { ES: '¡GENIAL!', CA: 'GENIAL!' }, done: { ES: '¡HECHO!', CA: 'FET!' }, diff --git a/frontend/src/app/core/sound.service.ts b/frontend/src/app/core/sound.service.ts index 76a1576..5e33e96 100644 --- a/frontend/src/app/core/sound.service.ts +++ b/frontend/src/app/core/sound.service.ts @@ -1,8 +1,10 @@ import { Injectable } from '@angular/core'; /** - * Sonido de recompensa sintetizado con WebAudio (sin ficheros de audio). Un par - * de notas alegres al ganar monedas. Silencioso si WebAudio no está disponible. + * Feedback de recompensa: sonido sintetizado con WebAudio (sin ficheros de + * audio) y vibración háptica sutil en dispositivos que la soportan (tablets). + * Ambos se disparan sobre el MISMO evento (causalidad) para que se perciban + * como una sola respuesta. Silencioso/inerte si el navegador no lo soporta. */ @Injectable({ providedIn: 'root' }) export class SoundService { @@ -47,4 +49,11 @@ export class SoundService { osc.stop(start + 0.28); }); } + + /** Vibración corta al ganar monedas (no-op si el dispositivo no vibra). */ + buzz(): void { + if (typeof navigator !== 'undefined' && 'vibrate' in navigator) { + navigator.vibrate(15); + } + } } diff --git a/frontend/src/app/features/home/board-view.component.ts b/frontend/src/app/features/home/board-view.component.ts index e2020fd..82a95d3 100644 --- a/frontend/src/app/features/home/board-view.component.ts +++ b/frontend/src/app/features/home/board-view.component.ts @@ -22,8 +22,12 @@ import { TaskCardComponent, ToggleEvent } from './task-card.component';

🌙 {{ i18n.t('afternoon') }}

- @for (task of afternoon; track task.id) { - + @if (afternoon.length) { + @for (task of afternoon; track task.id) { + + } + } @else { +
🌈 {{ i18n.t('freeAfternoon') }}
}
diff --git a/frontend/src/app/features/home/home.component.html b/frontend/src/app/features/home/home.component.html index 00b9326..f2cbb50 100644 --- a/frontend/src/app/features/home/home.component.html +++ b/frontend/src/app/features/home/home.component.html @@ -17,32 +17,41 @@ - -
- - -
- - - + @if (t.progress.total > 0) { + +
+ + +
- - @if (mode() === 'BOARD') { - + + + + + @if (mode() === 'BOARD') { + + } @else { + + } } @else { - + +
+ {{ t.child.mascot }} +

{{ i18n.t('emptyDayTitle') }}

+

{{ i18n.t('emptyDaySub') }} 🎉

+
} diff --git a/frontend/src/app/features/home/home.component.scss b/frontend/src/app/features/home/home.component.scss index c4bc3be..84f636a 100644 --- a/frontend/src/app/features/home/home.component.scss +++ b/frontend/src/app/features/home/home.component.scss @@ -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; diff --git a/frontend/src/app/features/home/home.component.ts b/frontend/src/app/features/home/home.component.ts index 4615ccd..5fc06a6 100644 --- a/frontend/src/app/features/home/home.component.ts +++ b/frontend/src/app/features/home/home.component.ts @@ -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(); } }