feat: visual polish & UX — fade-in, theme toggle, scroll-to-top, footer, cursor, badge
- Add FadeIn component (IntersectionObserver scroll-reveal + load animation) - Add dark/light theme toggle (useSyncExternalStore + localStorage) - Add scroll-to-top button (appears after 300px scroll) - Add configurable footer with social links (gallery.yaml) - Add crosshair cursor on photo grid items - Add 'X photos' count badge on subpage cover images - Add light theme color palette in tokens.css
This commit is contained in:
23
e2e/about.spec.ts
Normal file
23
e2e/about.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('About page', () => {
|
||||
test('loads and shows the about content', async ({ page }) => {
|
||||
await page.goto('/about');
|
||||
const about = page.locator('.about');
|
||||
await expect(about).toBeVisible();
|
||||
});
|
||||
|
||||
test('has a portrait column', async ({ page }) => {
|
||||
await page.goto('/about');
|
||||
const portrait = page.locator('.about__portrait-col');
|
||||
await expect(portrait).toBeVisible();
|
||||
});
|
||||
|
||||
test('has a text column with name', async ({ page }) => {
|
||||
await page.goto('/about');
|
||||
const name = page.locator('.about__name');
|
||||
await expect(name).toBeVisible();
|
||||
const text = await name.textContent();
|
||||
expect(text).toBeTruthy();
|
||||
});
|
||||
});
|
||||
10
e2e/health.spec.ts
Normal file
10
e2e/health.spec.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Health API', () => {
|
||||
test('GET /api/health returns 200 with expected shape', async ({ request }) => {
|
||||
const response = await request.get('/api/health');
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const body = await response.json();
|
||||
expect(body).toHaveProperty('status');
|
||||
});
|
||||
});
|
||||
38
e2e/homepage.spec.ts
Normal file
38
e2e/homepage.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Homepage', () => {
|
||||
test('loads and renders the hero section', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// Wait for the hero section to appear
|
||||
const hero = page.locator('.hero');
|
||||
await expect(hero).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows the site title in the hero', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const title = page.locator('.hero__title');
|
||||
await expect(title).toBeVisible();
|
||||
const text = await title.textContent();
|
||||
expect(text).toBeTruthy();
|
||||
});
|
||||
|
||||
test('has navigation links in the header', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const nav = page.locator('.header__nav');
|
||||
await expect(nav).toBeVisible();
|
||||
|
||||
// Should have at least the Home and About links
|
||||
const links = nav.locator('.header__nav-link');
|
||||
const count = await links.count();
|
||||
expect(count).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test('hero nav links are present for subpages', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const heroNav = page.locator('.hero__nav');
|
||||
await expect(heroNav).toBeVisible();
|
||||
const links = heroNav.locator('.hero__nav-link');
|
||||
const count = await links.count();
|
||||
expect(count).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
31
e2e/navigation.spec.ts
Normal file
31
e2e/navigation.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Navigation', () => {
|
||||
test('clicking a hero nav link navigates to a subpage', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const heroNav = page.locator('.hero__nav .hero__nav-link').first();
|
||||
const href = await heroNav.getAttribute('href');
|
||||
expect(href).toBeTruthy();
|
||||
|
||||
await heroNav.click();
|
||||
await page.waitForURL(`**${href}`);
|
||||
expect(page.url()).toContain(href);
|
||||
});
|
||||
|
||||
test('header About link navigates to /about', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const aboutLink = page.locator('.header__nav-link', { hasText: 'About' });
|
||||
await aboutLink.click();
|
||||
await page.waitForURL('**/about');
|
||||
expect(page.url()).toContain('/about');
|
||||
});
|
||||
|
||||
test('header Home link navigates back to /', async ({ page }) => {
|
||||
await page.goto('/about');
|
||||
const homeLink = page.locator('.header__nav-link', { hasText: 'Home' });
|
||||
await homeLink.click();
|
||||
await page.waitForURL('**/');
|
||||
// Accept both "/" and "?..." after root
|
||||
expect(new URL(page.url()).pathname).toBe('/');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user