diff --git a/.gitignore b/.gitignore index d881035..7723ee4 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ __screenshots__/ # System files .DS_Store Thumbs.db +.claude/settings.json diff --git a/changelog/v0.5.0.md b/changelog/v0.5.0.md new file mode 100644 index 0000000..ca55e5c --- /dev/null +++ b/changelog/v0.5.0.md @@ -0,0 +1,12 @@ +# v0.5.0 + +## New Features +- Document viewer URL now includes document number (e.g. /filings/document/xxx/1) +- Clicking documents in sidebar updates the URL for sharing and bookmarking +- Navigate directly to a specific document via URL + +## Improvements +- Homepage redesigned with dark uniform card layout and teal accent theme +- New stats bar highlighting platform features +- Updated screenshots throughout the homepage +- Google Tag Manager integration with custom events for search and document views diff --git a/public/screenshots/document-viewer.png b/public/screenshots/document-viewer.png new file mode 100644 index 0000000..0d5c09e Binary files /dev/null and b/public/screenshots/document-viewer.png differ diff --git a/public/screenshots/documents-mobile.jpeg b/public/screenshots/documents-mobile.jpeg new file mode 100644 index 0000000..6e66289 Binary files /dev/null and b/public/screenshots/documents-mobile.jpeg differ diff --git a/public/screenshots/filings-desktop-results.jpeg b/public/screenshots/filings-desktop-results.jpeg new file mode 100644 index 0000000..d5fbefe Binary files /dev/null and b/public/screenshots/filings-desktop-results.jpeg differ diff --git a/public/screenshots/filings-desktop-results.png b/public/screenshots/filings-desktop-results.png deleted file mode 100644 index 453ecda..0000000 Binary files a/public/screenshots/filings-desktop-results.png and /dev/null differ diff --git a/public/screenshots/filings-presentations.jpeg b/public/screenshots/filings-presentations.jpeg new file mode 100644 index 0000000..bc37f97 Binary files /dev/null and b/public/screenshots/filings-presentations.jpeg differ diff --git a/public/screenshots/filings-presentations.png b/public/screenshots/filings-presentations.png deleted file mode 100644 index 39f888b..0000000 Binary files a/public/screenshots/filings-presentations.png and /dev/null differ diff --git a/public/screenshots/fillings-queires.jpeg b/public/screenshots/fillings-queires.jpeg new file mode 100644 index 0000000..8b36e22 Binary files /dev/null and b/public/screenshots/fillings-queires.jpeg differ diff --git a/public/screenshots/queries-mobile-panel.jpeg b/public/screenshots/queries-mobile-panel.jpeg new file mode 100644 index 0000000..92a7f8b Binary files /dev/null and b/public/screenshots/queries-mobile-panel.jpeg differ diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c67a57d..5cf4bd4 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -22,6 +22,12 @@ export const routes: Routes = [ loadComponent: () => import('./features/filings/filing-search.component').then((m) => m.FilingSearchComponent), }, + { + path: 'filings/document/:accessionNumber/:documentNumber', + canActivate: [authGuard], + loadComponent: () => + import('./features/document/document-viewer.component').then((m) => m.DocumentViewerComponent), + }, { path: 'filings/document/:accessionNumber', canActivate: [authGuard], diff --git a/src/app/components/welcome-modal/changelog.ts b/src/app/components/welcome-modal/changelog.ts index bdf09e0..daccd2d 100644 --- a/src/app/components/welcome-modal/changelog.ts +++ b/src/app/components/welcome-modal/changelog.ts @@ -1,4 +1,4 @@ -export const APP_VERSION = '0.4.0'; +export const APP_VERSION = '0.5.0'; export interface ChangelogEntry { version: string; @@ -6,6 +6,28 @@ export interface ChangelogEntry { } export const CHANGELOG: ChangelogEntry[] = [ + { + version: '0.5.0', + sections: [ + { + title: 'New Features', + items: [ + 'Document viewer URL now includes document number (e.g. /filings/document/xxx/1)', + 'Clicking documents in sidebar updates the URL for sharing and bookmarking', + 'Navigate directly to a specific document via URL', + ], + }, + { + title: 'Improvements', + items: [ + 'Homepage redesigned with dark uniform card layout and teal accent theme', + 'New stats bar highlighting platform features', + 'Updated screenshots throughout the homepage', + 'Google Tag Manager integration with custom events for search and document views', + ], + }, + ], + }, { version: '0.4.0', sections: [ diff --git a/src/app/core/services/analytics.service.ts b/src/app/core/services/analytics.service.ts new file mode 100644 index 0000000..430fa14 --- /dev/null +++ b/src/app/core/services/analytics.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class AnalyticsService { + private readonly enabled = typeof window !== 'undefined' && window.location.hostname === 'nullabroke.com'; + + private push(event: Record) { + if (!this.enabled) return; + const w = window as unknown as { dataLayer: Record[] }; + w.dataLayer = w.dataLayer || []; + w.dataLayer.push(event); + } + + trackSearch(queryName: string, resultCount: number) { + this.push({ + event: 'query_search', + query_name: queryName, + result_count: resultCount, + }); + } + + trackNewQuery() { + this.push({ event: 'query_create' }); + } + + trackSelectQuery(queryName: string) { + this.push({ + event: 'query_select', + query_name: queryName, + }); + } + + trackDocumentView(accessionNumber: string, documentNumber: string) { + this.push({ + event: 'document_view', + accession_number: accessionNumber, + document_number: documentNumber, + }); + } +} diff --git a/src/app/features/document/document-viewer.component.ts b/src/app/features/document/document-viewer.component.ts index 0295a5c..3b190da 100644 --- a/src/app/features/document/document-viewer.component.ts +++ b/src/app/features/document/document-viewer.component.ts @@ -8,6 +8,7 @@ import { HostListener, } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router'; +import { Location } from '@angular/common'; import { DomSanitizer, SafeHtml, SafeResourceUrl } from '@angular/platform-browser'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { map } from 'rxjs'; @@ -29,6 +30,7 @@ import { lucideImage, } from '@ng-icons/lucide'; +import { AnalyticsService } from '../../core/services/analytics.service'; import { AppChromeService } from '../../core/services/app-chrome.service'; import { FilingService } from '../../core/services/filing.service'; import { DocumentService } from '../../core/services/document.service'; @@ -87,19 +89,25 @@ interface IframeMessage { styleUrl: './document-viewer.component.css', }) export class DocumentViewerComponent { + private readonly analytics = inject(AnalyticsService); private readonly appChrome = inject(AppChromeService); private readonly route = inject(ActivatedRoute); + private readonly location = inject(Location); private readonly sanitizer = inject(DomSanitizer); private readonly filingService = inject(FilingService); private readonly documentService = inject(DocumentService); private readonly xbrlParser = inject(XbrlParserService); private readonly destroyRef = inject(DestroyRef); - // Convert route params to signal + // Convert route params to signals accessionNumber = toSignal( this.route.paramMap.pipe(map((params) => params.get('accessionNumber'))), { initialValue: null } ); + documentNumber = toSignal( + this.route.paramMap.pipe(map((params) => params.get('documentNumber'))), + { initialValue: null } + ); loading = signal(true); error = signal(null); @@ -223,9 +231,15 @@ export class DocumentViewerComponent { }); this.documents.set(viewableDocs); - // Auto-select the first document + // Select document based on URL param, or default to first if (viewableDocs.length > 0) { - this.selectDocument(viewableDocs[0]); + const docNum = this.documentNumber(); + let selectedDoc = viewableDocs[0]; + if (docNum) { + const match = viewableDocs.find(d => d.sequence?.toString() === docNum); + if (match) selectedDoc = match; + } + this.selectDocument(selectedDoc, true); } } this.loading.set(false); @@ -238,9 +252,22 @@ export class DocumentViewerComponent { }); } - selectDocument(doc: DocumentFile) { + selectDocument(doc: DocumentFile, replaceUrl = false) { this.selectedDocument.set(doc); + // Update URL to reflect selected document number + const accNum = this.accessionNumber(); + if (accNum && doc.sequence != null) { + const seq = doc.sequence.toString(); + const path = `/filings/document/${accNum}/${seq}`; + if (replaceUrl) { + this.location.replaceState(path); + } else { + this.location.go(path); + } + this.analytics.trackDocumentView(accNum, seq); + } + if (this.isXbrlDocument(doc)) { this.enterXbrlMode(); } else { diff --git a/src/app/features/filings/components/filing-result-row.component.spec.ts b/src/app/features/filings/components/filing-result-row.component.spec.ts index 01e933d..19560c9 100644 --- a/src/app/features/filings/components/filing-result-row.component.spec.ts +++ b/src/app/features/filings/components/filing-result-row.component.spec.ts @@ -133,7 +133,7 @@ describe('FilingResultRowComponent', () => { it('should have routerLink to correct document URL', async () => { await setupTest(); const link = fixture.nativeElement.querySelector('a'); - expect(link.getAttribute('href')).toBe('/filings/document/0001234567-24-000001'); + expect(link.getAttribute('href')).toBe('/filings/document/0001234567-24-000001/1'); }); it('should use grid layout with 3 columns', async () => { diff --git a/src/app/features/filings/components/filing-result-row.component.ts b/src/app/features/filings/components/filing-result-row.component.ts index 6fe9524..f9b5f99 100644 --- a/src/app/features/filings/components/filing-result-row.component.ts +++ b/src/app/features/filings/components/filing-result-row.component.ts @@ -11,7 +11,7 @@ import { Filing } from '../../../core/models/filing.model'; imports: [RouterLink, DatePipe], template: ` diff --git a/src/app/features/filings/filing-search.component.ts b/src/app/features/filings/filing-search.component.ts index 2ac2e62..b90ee49 100644 --- a/src/app/features/filings/filing-search.component.ts +++ b/src/app/features/filings/filing-search.component.ts @@ -26,6 +26,7 @@ import { FilingService } from '../../core/services/filing.service'; import { SavedQueriesService } from '../../core/services/saved-queries.service'; import { Filing } from '../../core/models/filing.model'; import { SavedQuery } from '../../core/models/query-parameter.model'; +import { AnalyticsService } from '../../core/services/analytics.service'; import { QueryParametersComponent } from '../../components/query-builder'; import { FilingStatusBarComponent, @@ -71,6 +72,7 @@ export class FilingSearchComponent { private readonly appChrome = inject(AppChromeService); private readonly filingService = inject(FilingService); private readonly savedQueriesService = inject(SavedQueriesService); + private readonly analytics = inject(AnalyticsService); private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly destroyRef = inject(DestroyRef); @@ -234,6 +236,7 @@ export class FilingSearchComponent { this.results.set(filings ?? []); this.loading.set(false); this.stopLogoAfterAnimation(animStart, ANIM_DURATION); + this.analytics.trackSearch(this.titleText(), (filings ?? []).length); }, error: () => { this.results.set([]); @@ -247,6 +250,7 @@ export class FilingSearchComponent { newQuery() { this.savedQueriesService.newQuery(); this.queryControl.setValue('', { emitEvent: false }); + this.analytics.trackNewQuery(); } selectSavedQuery(guid: string) { diff --git a/src/app/features/home/home.component.css b/src/app/features/home/home.component.css index 300f3c2..39055cf 100644 --- a/src/app/features/home/home.component.css +++ b/src/app/features/home/home.component.css @@ -8,22 +8,21 @@ .hero-glow { position: absolute; - top: -40%; + top: -60%; left: 50%; transform: translateX(-50%); - width: 140%; + width: 120%; height: 80%; - background: radial-gradient(ellipse at center, rgba(14, 99, 156, 0.25) 0%, rgba(78, 201, 176, 0.08) 40%, transparent 70%); - filter: blur(60px); + background: radial-gradient(ellipse at center, rgba(78, 201, 176, 0.08) 0%, rgba(78, 201, 176, 0.03) 30%, transparent 70%); + filter: blur(80px); } -.grid-overlay { - position: absolute; - inset: 0; - background-image: - linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px), - linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px); - background-size: 60px 60px; +/* Hero gradient text */ +.hero-gradient-text { + background: linear-gradient(135deg, #4ec9b0, #7ee8d0); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } /* Hero screenshot */ @@ -32,11 +31,17 @@ position: relative; } +.hero-screenshot img { + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.06), + 0 24px 80px rgba(0, 0, 0, 0.5); +} + .hero-screenshot::before { content: ''; position: absolute; - inset: -20%; - background: radial-gradient(ellipse at center, rgba(14, 99, 156, 0.2) 0%, transparent 70%); + inset: -30%; + background: radial-gradient(ellipse at center, rgba(78, 201, 176, 0.06) 0%, transparent 70%); pointer-events: none; z-index: -1; } @@ -44,7 +49,7 @@ @keyframes fadeUp { from { opacity: 0; - transform: translateY(40px); + transform: translateY(30px); } to { opacity: 1; @@ -54,69 +59,44 @@ /* Feature cards */ .feature-card { - position: relative; - border-radius: 1.25rem; + background: #0c0c10; border: 1px solid rgba(255, 255, 255, 0.06); - overflow: hidden; - padding: 3rem; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 2rem; - align-items: center; - min-height: 360px; + border-radius: 1rem; + padding: 1.75rem; + transition: border-color 0.2s ease; } -.feature-card-blue { - background: linear-gradient(135deg, #0c2d48 0%, #0e4a7a 50%, #1a3a5c 100%); -} - -.feature-card-teal { - background: linear-gradient(135deg, #0a2f2f 0%, #0f4040 50%, #1a4a4a 100%); -} - -.feature-card-purple { - background: linear-gradient(135deg, #1a1a3e 0%, #252060 50%, #2d2456 100%); -} - -.feature-card-warm { - background: linear-gradient(135deg, #2d1a0a 0%, #3d2a15 50%, #3d2a1a 100%); -} - -.feature-card-content { - position: relative; - z-index: 1; +.feature-card:hover { + border-color: rgba(255, 255, 255, 0.12); } -.feature-card-images { - position: relative; - z-index: 1; +.feature-icon { display: flex; align-items: center; justify-content: center; - gap: 1rem; + width: 2.5rem; + height: 2.5rem; + border-radius: 0.625rem; + background: rgba(78, 201, 176, 0.08); + color: #4ec9b0; + margin-bottom: 1rem; } -/* Feature images */ -.feature-img { - transition: transform 0.3s ease; - border-radius: 0.75rem; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); +/* Showcase images */ +.showcase-image-wrap { + animation: fadeUp 0.8s ease-out both; } -.feature-img:hover { - transform: scale(1.02); +.showcase-img { + border-radius: 0.75rem; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.06), + 0 20px 60px rgba(0, 0, 0, 0.4); + transition: transform 0.3s ease; } -/* Card inner glow */ -.feature-card::after { - content: ''; - position: absolute; - top: -50%; - right: -20%; - width: 60%; - height: 100%; - background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.03) 0%, transparent 70%); - pointer-events: none; +.showcase-img:hover { + transform: scale(1.02); } /* CTA card */ @@ -125,35 +105,18 @@ border-radius: 1.25rem; border: 1px solid rgba(255, 255, 255, 0.06); overflow: hidden; - background: linear-gradient(135deg, #0c2d48 0%, #0a2f2f 50%, #1a1a3e 100%); + background: #0c0c10; padding: 4rem 2rem; } /* CTA glow */ .cta-glow { position: absolute; - width: 600px; - height: 600px; - background: radial-gradient(circle, rgba(14, 99, 156, 0.2) 0%, transparent 70%); + width: 500px; + height: 500px; + background: radial-gradient(circle, rgba(78, 201, 176, 0.06) 0%, transparent 70%); top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none; } - -/* Responsive */ -@media (max-width: 768px) { - .feature-card { - grid-template-columns: 1fr; - padding: 2rem 1.5rem; - min-height: auto; - } - - .feature-card-images { - order: 2; - } - - .feature-card-content { - order: 1; - } -} diff --git a/src/app/features/home/home.component.html b/src/app/features/home/home.component.html index 2ff8be6..04149c0 100644 --- a/src/app/features/home/home.component.html +++ b/src/app/features/home/home.component.html @@ -1,16 +1,16 @@ -
+
-