/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Component, OnInit, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { GamesService } from '../../services/games.service'; import { BooksService } from '../../services/books.service'; import { MusicService } from '../../services/music.service'; import { MangaService } from '../../services/manga.service'; import { ShowsService } from '../../services/shows.service'; import { ArtService } from '../../services/art.service'; import { Game, GameStatus, Book, BookStatus, Music, MusicType, Manga, MangaStatus, Show, ShowStatus, ShowType, Art } from '@library/shared-types'; @Component({ selector: 'app-home', standalone: true, imports: [CommonModule, RouterModule], template: `

Welcome to Naomi's Library

A personal collection of games, books, music, manga, shows, and art

🎮

Games

{{ gamesCount() }}

{{ currentlyPlayingCount() }} currently playing

📚

Books

{{ booksCount() }}

{{ currentlyReadingCount() }} currently reading

🎵

Music

{{ musicCount() }}

{{ albumsCount() }} albums, {{ singlesCount() }} singles

📖

Manga

{{ mangaCount() }}

{{ currentlyReadingMangaCount() }} currently reading

📺

Shows

{{ showsCount() }}

{{ animeCount() }} anime, {{ filmsCount() }} films

🎨

Art

{{ artCount() }}

commissioned pieces

Recent Additions

@if (recentGames().length > 0) {

🎮 Latest Games

    @for (game of recentGames(); track game.id) {
  • {{ game.title }} @if (game.platform) { ({{ game.platform }}) }
  • }
} @if (recentBooks().length > 0) {

📚 Latest Books

    @for (book of recentBooks(); track book.id) {
  • {{ book.title }} by {{ book.author }}
  • }
} @if (recentMusic().length > 0) {

🎵 Latest Music

    @for (music of recentMusic(); track music.id) {
  • {{ music.title }} by {{ music.artist }}
  • }
} @if (recentManga().length > 0) {

📖 Latest Manga

    @for (manga of recentManga(); track manga.id) {
  • {{ manga.title }} by {{ manga.author }}
  • }
} @if (recentShows().length > 0) {

📺 Latest Shows

    @for (show of recentShows(); track show.id) {
  • {{ show.title }} {{ formatShowType(show.type) }}
  • }
} @if (recentArt().length > 0) {

🎨 Latest Art

    @for (art of recentArt(); track art.id) {
  • {{ art.title }} by {{ art.artist }}
  • }
}
`, styles: [` .container { max-width: 1200px; margin: 0 auto; padding: 2rem; } .hero { text-align: center; padding: 3rem 0; margin-bottom: 3rem; } .hero h1 { font-size: 2.5rem; margin-bottom: 0.5rem; color: var(--witch-purple); } .tagline { font-size: 1.2rem; color: var(--witch-plum); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 3rem; } .stat-card { display: flex; align-items: center; gap: 1.5rem; padding: 1.5rem; background: rgba(255, 255, 255, 0.95); border: 2px solid var(--witch-mauve); border-radius: 12px; text-decoration: none; color: inherit; transition: all 0.3s; backdrop-filter: blur(10px); } .stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 16px var(--witch-shadow); background: var(--witch-moon); } .games-card:hover { border-color: #ff6b6b; } .books-card:hover { border-color: #8b6f47; } .music-card:hover { border-color: #74b9ff; } .manga-card:hover { border-color: #00b894; } .shows-card:hover { border-color: #e84393; } .art-card:hover { border-color: #fdcb6e; } .icon { font-size: 3rem; flex-shrink: 0; } .stat-info h3 { margin: 0 0 0.5rem 0; font-size: 1.1rem; color: var(--witch-plum); } .count { font-size: 2.5rem; font-weight: bold; line-height: 1; margin-bottom: 0.5rem; } .games-card .count { color: #ff6b6b; } .books-card .count { color: #8b6f47; } .music-card .count { color: #74b9ff; } .manga-card .count { color: #00b894; } .shows-card .count { color: #e84393; } .art-card .count { color: #fdcb6e; } .stat-info p { margin: 0; color: var(--witch-plum); font-size: 0.9rem; } .recent-section { background: rgba(255, 255, 255, 0.95); padding: 2rem; border-radius: 12px; border: 2px solid var(--witch-lavender); backdrop-filter: blur(10px); } .recent-section h2 { margin: 0 0 1.5rem 0; font-size: 1.5rem; } .recent-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; } .recent-category h3 { margin: 0 0 1rem 0; font-size: 1.1rem; color: var(--witch-purple); } .recent-list { list-style: none; padding: 0; margin: 0; } .recent-list li { padding: 0.5rem 0; border-bottom: 1px solid var(--witch-lavender); } .recent-list li:last-child { border-bottom: none; } .recent-list a { color: var(--witch-rose); text-decoration: none; font-weight: 500; } .recent-list a:hover { color: var(--witch-plum); text-decoration: underline; } .platform, .author, .artist, .show-type { display: block; font-size: 0.85rem; color: var(--witch-plum); margin-top: 0.25rem; } `] }) export class HomeComponent implements OnInit { gamesService = inject(GamesService); booksService = inject(BooksService); musicService = inject(MusicService); mangaService = inject(MangaService); showsService = inject(ShowsService); artService = inject(ArtService); games = signal([]); books = signal([]); music = signal([]); manga = signal([]); shows = signal([]); art = signal([]); ngOnInit() { // Load all data this.gamesService.getAllGames().subscribe(games => this.games.set(games)); this.booksService.getAllBooks().subscribe(books => this.books.set(books)); this.musicService.getAllMusic().subscribe(music => this.music.set(music)); this.mangaService.getAllManga().subscribe(manga => this.manga.set(manga)); this.showsService.getAllShows().subscribe(shows => this.shows.set(shows)); this.artService.getAllArt().subscribe(art => this.art.set(art)); } // Games stats gamesCount() { return this.games().length; } currentlyPlayingCount() { return this.games().filter(g => g.status === GameStatus.playing).length; } recentGames() { return this.games().slice(0, 5); } // Books stats booksCount() { return this.books().length; } currentlyReadingCount() { return this.books().filter(b => b.status === BookStatus.reading).length; } recentBooks() { return this.books().slice(0, 5); } // Music stats musicCount() { return this.music().length; } albumsCount() { return this.music().filter(m => m.type === MusicType.album).length; } singlesCount() { return this.music().filter(m => m.type === MusicType.single).length; } recentMusic() { return this.music().slice(0, 5); } // Manga stats mangaCount() { return this.manga().length; } currentlyReadingMangaCount() { return this.manga().filter(m => m.status === MangaStatus.reading).length; } recentManga() { return this.manga().slice(0, 5); } // Shows stats showsCount() { return this.shows().length; } animeCount() { return this.shows().filter(s => s.type === ShowType.anime).length; } filmsCount() { return this.shows().filter(s => s.type === ShowType.film).length; } recentShows() { return this.shows().slice(0, 5); } formatShowType(type: ShowType): string { switch (type) { case ShowType.tvSeries: return 'TV Series'; case ShowType.anime: return 'Anime'; case ShowType.film: return 'Film'; case ShowType.documentary: return 'Documentary'; default: return type; } } // Art stats artCount() { return this.art().length; } recentArt() { return this.art().slice(0, 5); } }