diff --git a/src/pages/admin/AdminLogin.module.css b/src/pages/admin/AdminLogin.module.css
new file mode 100644
index 0000000..7d21443
--- /dev/null
+++ b/src/pages/admin/AdminLogin.module.css
@@ -0,0 +1,49 @@
+.page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--color-cream);
+ padding: var(--space-lg);
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+ width: 100%;
+ max-width: 320px;
+}
+
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.field label {
+ color: var(--color-charcoal);
+}
+
+.field input {
+ padding: var(--space-sm);
+ border: 1px solid var(--color-gold);
+ background: white;
+}
+
+.error {
+ color: #a33;
+}
+
+.form button {
+ background: var(--color-charcoal);
+ color: var(--color-cream);
+ border: none;
+ padding: var(--space-sm) var(--space-md);
+ cursor: pointer;
+}
+
+.form button:disabled {
+ opacity: 0.6;
+ cursor: default;
+}
diff --git a/src/pages/admin/AdminLogin.test.tsx b/src/pages/admin/AdminLogin.test.tsx
new file mode 100644
index 0000000..8a2b558
--- /dev/null
+++ b/src/pages/admin/AdminLogin.test.tsx
@@ -0,0 +1,49 @@
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter } from 'react-router-dom'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+
+const signInMock = vi.fn()
+const navigateMock = vi.fn()
+
+vi.mock('../../lib/supabase', () => ({
+ supabase: { auth: { signInWithPassword: (...args: unknown[]) => signInMock(...args) } },
+}))
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom')
+ return { ...actual, useNavigate: () => navigateMock }
+})
+
+import AdminLogin from './AdminLogin'
+
+describe('AdminLogin', () => {
+ beforeEach(() => {
+ signInMock.mockReset()
+ navigateMock.mockReset()
+ })
+
+ it('signs in and navigates to the admin dashboard on success', async () => {
+ signInMock.mockResolvedValue({ error: null })
+ render(