diff --git a/apps/frontend/src/app/app.html b/apps/frontend/src/app/app.html
index b6a4dae..4bdc0fb 100644
--- a/apps/frontend/src/app/app.html
+++ b/apps/frontend/src/app/app.html
@@ -2,3 +2,4 @@
+
diff --git a/apps/frontend/src/app/app.ts b/apps/frontend/src/app/app.ts
index 8af4875..88e48d6 100644
--- a/apps/frontend/src/app/app.ts
+++ b/apps/frontend/src/app/app.ts
@@ -1,13 +1,20 @@
-import { Component } from '@angular/core';
+import { Component, inject, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
+import { FooterComponent } from './components/footer/footer.component';
+import { AnalyticsService } from './services/analytics.service';
@Component({
- imports: [RouterModule, HeaderComponent],
+ imports: [RouterModule, HeaderComponent, FooterComponent],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.scss',
})
-export class App {
+export class App implements OnInit {
protected title = 'Naomi\'s Library';
+ private analytics = inject(AnalyticsService);
+
+ ngOnInit(): void {
+ this.analytics.initialise();
+ }
}
diff --git a/apps/frontend/src/app/components/footer/footer.component.ts b/apps/frontend/src/app/components/footer/footer.component.ts
new file mode 100644
index 0000000..478fb26
--- /dev/null
+++ b/apps/frontend/src/app/components/footer/footer.component.ts
@@ -0,0 +1,138 @@
+/**
+ * @copyright 2026 NHCarrigan
+ * @license Naomi's Public License
+ * @author Naomi Carrigan
+ */
+
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+ selector: 'app-footer',
+ standalone: true,
+ imports: [CommonModule],
+ template: `
+
+ `,
+ styles: [`
+ .footer {
+ background: linear-gradient(135deg, var(--witch-purple) 0%, var(--witch-plum) 100%);
+ color: var(--witch-moon);
+ padding: 3rem 2rem 1.5rem;
+ margin-top: 4rem;
+ }
+
+ .footer-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+
+ .cta-section {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 2rem;
+ margin-bottom: 2rem;
+ }
+
+ .cta-card {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(10px);
+ border-radius: 12px;
+ padding: 1.5rem;
+ text-align: center;
+ border: 2px solid rgba(255, 255, 255, 0.2);
+ transition: transform 0.3s, box-shadow 0.3s;
+ }
+
+ .cta-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+ }
+
+ .cta-card h3 {
+ margin: 0 0 0.75rem 0;
+ font-size: 1.25rem;
+ color: var(--witch-moon);
+ }
+
+ .cta-card p {
+ margin: 0 0 1.25rem 0;
+ font-size: 0.95rem;
+ line-height: 1.5;
+ color: var(--witch-lavender);
+ }
+
+ .btn {
+ display: inline-block;
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 1rem;
+ transition: all 0.3s;
+ }
+
+ .btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+ }
+
+ .btn-discord {
+ background: #5865F2;
+ color: white;
+ }
+
+ .btn-discord:hover {
+ background: #4752C4;
+ }
+
+ .btn-donate {
+ background: var(--witch-rose);
+ color: var(--witch-moon);
+ }
+
+ .btn-donate:hover {
+ background: var(--witch-mauve);
+ color: var(--witch-purple);
+ }
+
+ .footer-bottom {
+ text-align: center;
+ padding-top: 1.5rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ }
+
+ .footer-bottom p {
+ margin: 0;
+ font-size: 0.9rem;
+ color: var(--witch-lavender);
+ }
+ `]
+})
+export class FooterComponent {
+ currentYear = new Date().getFullYear();
+}
diff --git a/apps/frontend/src/app/services/analytics.service.ts b/apps/frontend/src/app/services/analytics.service.ts
new file mode 100644
index 0000000..b0f8adb
--- /dev/null
+++ b/apps/frontend/src/app/services/analytics.service.ts
@@ -0,0 +1,42 @@
+/**
+ * @copyright 2026 NHCarrigan
+ * @license Naomi's Public License
+ * @author Naomi Carrigan
+ */
+
+import { Injectable, inject } from '@angular/core';
+import { Router, NavigationEnd } from '@angular/router';
+import { filter } from 'rxjs/operators';
+
+declare global {
+ interface Window {
+ plausible?: (event: string, options?: { props?: Record }) => void;
+ }
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AnalyticsService {
+ private router = inject(Router);
+
+ initialise(): void {
+ this.router.events.pipe(
+ filter((event): event is NavigationEnd => event instanceof NavigationEnd)
+ ).subscribe((event) => {
+ this.trackPageView(event.urlAfterRedirects);
+ });
+ }
+
+ private trackPageView(path: string): void {
+ if (window.plausible) {
+ window.plausible('pageview', {
+ props: {
+ domain: 'library.nhcarrigan.com',
+ page: 'Naomi\'s Library',
+ path: path || '/'
+ }
+ });
+ }
+ }
+}
diff --git a/apps/frontend/src/index.html b/apps/frontend/src/index.html
index a415161..41008e3 100644
--- a/apps/frontend/src/index.html
+++ b/apps/frontend/src/index.html
@@ -2,10 +2,22 @@
- frontend
+ Naomi's Library
+
+
+