🪻 distributed transcription service thistle.dunkirk.sh

chore: fix biome lint issues and format

dunkirk.sh b30c6519 d024a754

verified
+285 -259
+27 -16
src/index.ts
··· 135 135 console.warn( 136 136 "[Startup] ORIGIN not set, defaulting to http://localhost:3000", 137 137 ); 138 - console.warn( 139 - "[Startup] Set ORIGIN in production for correct email links", 140 - ); 138 + console.warn("[Startup] Set ORIGIN in production for correct email links"); 141 139 } 142 140 143 141 console.log("[Startup] Environment variable validation passed"); ··· 641 639 }), 642 640 }); 643 641 644 - return Response.json({ success: true, message: "Verification email sent" }); 642 + return Response.json({ 643 + success: true, 644 + message: "Verification email sent", 645 + }); 645 646 } catch (error) { 646 647 return handleError(error); 647 648 } ··· 829 830 await updateUserPassword(userId, password); 830 831 consumePasswordResetToken(token); 831 832 832 - return Response.json({ success: true, message: "Password reset successfully" }); 833 + return Response.json({ 834 + success: true, 835 + message: "Password reset successfully", 836 + }); 833 837 } catch (error) { 834 838 console.error("[Email] Reset password error:", error); 835 839 return Response.json( ··· 898 902 try { 899 903 const user = requireAuth(req); 900 904 901 - const rateLimitError = enforceRateLimit(req, "passkey-register-options", { 902 - ip: { max: 10, windowSeconds: 5 * 60 }, 903 - }); 905 + const rateLimitError = enforceRateLimit( 906 + req, 907 + "passkey-register-options", 908 + { 909 + ip: { max: 10, windowSeconds: 5 * 60 }, 910 + }, 911 + ); 904 912 if (rateLimitError) return rateLimitError; 905 913 906 914 const options = await createRegistrationOptions(user); ··· 915 923 try { 916 924 const _user = requireAuth(req); 917 925 918 - const rateLimitError = enforceRateLimit(req, "passkey-register-verify", { 919 - ip: { max: 10, windowSeconds: 5 * 60 }, 920 - }); 926 + const rateLimitError = enforceRateLimit( 927 + req, 928 + "passkey-register-verify", 929 + { 930 + ip: { max: 10, windowSeconds: 5 * 60 }, 931 + }, 932 + ); 921 933 if (rateLimitError) return rateLimitError; 922 934 923 935 const body = await req.json(); ··· 2195 2207 const { encodeCursor } = await import("./lib/cursor"); 2196 2208 const last = transcriptions[transcriptions.length - 1]; 2197 2209 if (last) { 2198 - nextCursor = encodeCursor([ 2199 - last.created_at.toString(), 2200 - last.id, 2201 - ]); 2210 + nextCursor = encodeCursor([last.created_at.toString(), last.id]); 2202 2211 } 2203 2212 } 2204 2213 ··· 3440 3449 server.stop(); 3441 3450 3442 3451 // 2. Close all active SSE streams (safe to kill - sync will handle reconnection) 3443 - console.log(`[Shutdown] Closing ${activeSSEStreams.size} active SSE streams...`); 3452 + console.log( 3453 + `[Shutdown] Closing ${activeSSEStreams.size} active SSE streams...`, 3454 + ); 3444 3455 for (const controller of activeSSEStreams) { 3445 3456 try { 3446 3457 controller.close();
+1 -1
src/lib/api-response-format.test.ts
··· 2 2 3 3 /** 4 4 * API Response Format Standards 5 - * 5 + * 6 6 * This test documents the standardized response formats across the API. 7 7 * All endpoints should follow these patterns for consistency. 8 8 */
+4 -1
src/lib/auth.ts
··· 759 759 const { encodeCursor } = require("./cursor"); 760 760 const last = users[users.length - 1]; 761 761 if (last) { 762 - nextCursor = encodeCursor([last.created_at.toString(), last.id.toString()]); 762 + nextCursor = encodeCursor([ 763 + last.created_at.toString(), 764 + last.id.toString(), 765 + ]); 763 766 } 764 767 } 765 768
+4 -1
src/lib/classes.ts
··· 99 99 const { year, semester, courseCode, id } = decodeClassCursor(cursor); 100 100 101 101 classes = db 102 - .query<ClassWithStats, [number, number, string, string, string, number]>( 102 + .query< 103 + ClassWithStats, 104 + [number, number, string, string, string, number] 105 + >( 103 106 `SELECT c.* FROM classes c 104 107 INNER JOIN class_members cm ON c.id = cm.class_id 105 108 WHERE cm.user_id = ? AND
+3 -3
src/lib/cursor.test.ts
··· 1 1 import { describe, expect, test } from "bun:test"; 2 2 import { 3 - encodeCursor, 3 + decodeClassCursor, 4 4 decodeCursor, 5 - encodeSimpleCursor, 6 5 decodeSimpleCursor, 7 6 encodeClassCursor, 8 - decodeClassCursor, 7 + encodeCursor, 8 + encodeSimpleCursor, 9 9 } from "./cursor"; 10 10 11 11 describe("Cursor encoding/decoding", () => {
+3 -13
src/lib/pagination.test.ts
··· 1 - import { describe, expect, test, beforeAll, afterAll } from "bun:test"; 2 1 import { Database } from "bun:sqlite"; 2 + import { afterAll, beforeAll, describe, expect, test } from "bun:test"; 3 3 4 4 let testDb: Database; 5 5 ··· 171 171 // Create test users 172 172 testDb.run( 173 173 "INSERT INTO users (email, password_hash, email_verified, created_at, role) VALUES (?, ?, 1, ?, ?)", 174 - [ 175 - "user1@test.com", 176 - "hash1", 177 - Math.floor(Date.now() / 1000) - 100, 178 - "user", 179 - ], 174 + ["user1@test.com", "hash1", Math.floor(Date.now() / 1000) - 100, "user"], 180 175 ); 181 176 testDb.run( 182 177 "INSERT INTO users (email, password_hash, email_verified, created_at, role) VALUES (?, ?, 1, ?, ?)", 183 - [ 184 - "user2@test.com", 185 - "hash2", 186 - Math.floor(Date.now() / 1000) - 50, 187 - "user", 188 - ], 178 + ["user2@test.com", "hash2", Math.floor(Date.now() / 1000) - 50, "user"], 189 179 ); 190 180 testDb.run( 191 181 "INSERT INTO users (email, password_hash, email_verified, created_at, role) VALUES (?, ?, 1, ?, ?)",
+104 -89
src/pages/admin.ts
··· 1 - const transcriptionsComponent = document.getElementById('transcriptions-component') as any; 2 - const usersComponent = document.getElementById('users-component') as any; 3 - const userModal = document.getElementById('user-modal') as any; 4 - const transcriptModal = document.getElementById('transcript-modal') as any; 5 - const errorMessage = document.getElementById('error-message') as HTMLElement; 6 - const loading = document.getElementById('loading') as HTMLElement; 7 - const content = document.getElementById('content') as HTMLElement; 1 + const transcriptionsComponent = document.getElementById( 2 + "transcriptions-component", 3 + ) as HTMLElement | null; 4 + const usersComponent = document.getElementById( 5 + "users-component", 6 + ) as HTMLElement | null; 7 + const userModal = document.getElementById("user-modal") as HTMLElement | null; 8 + const transcriptModal = document.getElementById( 9 + "transcript-modal", 10 + ) as HTMLElement | null; 11 + const errorMessage = document.getElementById("error-message") as HTMLElement; 12 + const loading = document.getElementById("loading") as HTMLElement; 13 + const content = document.getElementById("content") as HTMLElement; 8 14 9 15 // Modal functions 10 16 function openUserModal(userId: string) { 11 - userModal.setAttribute('open', ''); 12 - userModal.userId = userId; 17 + userModal.setAttribute("open", ""); 18 + userModal.userId = userId; 13 19 } 14 20 15 21 function closeUserModal() { 16 - userModal.removeAttribute('open'); 17 - userModal.userId = null; 22 + userModal.removeAttribute("open"); 23 + userModal.userId = null; 18 24 } 19 25 20 26 function openTranscriptModal(transcriptId: string) { 21 - transcriptModal.setAttribute('open', ''); 22 - transcriptModal.transcriptId = transcriptId; 27 + transcriptModal.setAttribute("open", ""); 28 + transcriptModal.transcriptId = transcriptId; 23 29 } 24 30 25 31 function closeTranscriptModal() { 26 - transcriptModal.removeAttribute('open'); 27 - transcriptModal.transcriptId = null; 32 + transcriptModal.removeAttribute("open"); 33 + transcriptModal.transcriptId = null; 28 34 } 29 35 30 36 // Listen for component events 31 - transcriptionsComponent?.addEventListener('open-transcription', (e: CustomEvent) => { 32 - openTranscriptModal(e.detail.id); 33 - }); 37 + transcriptionsComponent?.addEventListener( 38 + "open-transcription", 39 + (e: CustomEvent) => { 40 + openTranscriptModal(e.detail.id); 41 + }, 42 + ); 34 43 35 - usersComponent?.addEventListener('open-user', (e: CustomEvent) => { 36 - openUserModal(e.detail.id); 44 + usersComponent?.addEventListener("open-user", (e: CustomEvent) => { 45 + openUserModal(e.detail.id); 37 46 }); 38 47 39 48 // Listen for modal close events 40 - userModal?.addEventListener('close', closeUserModal); 41 - userModal?.addEventListener('user-updated', async () => { 42 - await loadStats(); 49 + userModal?.addEventListener("close", closeUserModal); 50 + userModal?.addEventListener("user-updated", async () => { 51 + await loadStats(); 43 52 }); 44 - userModal?.addEventListener('click', (e: MouseEvent) => { 45 - if (e.target === userModal) closeUserModal(); 53 + userModal?.addEventListener("click", (e: MouseEvent) => { 54 + if (e.target === userModal) closeUserModal(); 46 55 }); 47 56 48 - transcriptModal?.addEventListener('close', closeTranscriptModal); 49 - transcriptModal?.addEventListener('transcript-deleted', async () => { 50 - await loadStats(); 57 + transcriptModal?.addEventListener("close", closeTranscriptModal); 58 + transcriptModal?.addEventListener("transcript-deleted", async () => { 59 + await loadStats(); 51 60 }); 52 - transcriptModal?.addEventListener('click', (e: MouseEvent) => { 53 - if (e.target === transcriptModal) closeTranscriptModal(); 61 + transcriptModal?.addEventListener("click", (e: MouseEvent) => { 62 + if (e.target === transcriptModal) closeTranscriptModal(); 54 63 }); 55 64 56 65 async function loadStats() { 57 - try { 58 - const [transcriptionsRes, usersRes] = await Promise.all([ 59 - fetch('/api/admin/transcriptions'), 60 - fetch('/api/admin/users') 61 - ]); 66 + try { 67 + const [transcriptionsRes, usersRes] = await Promise.all([ 68 + fetch("/api/admin/transcriptions"), 69 + fetch("/api/admin/users"), 70 + ]); 62 71 63 - if (!transcriptionsRes.ok || !usersRes.ok) { 64 - if (transcriptionsRes.status === 403 || usersRes.status === 403) { 65 - window.location.href = '/'; 66 - return; 67 - } 68 - throw new Error('Failed to load admin data'); 69 - } 72 + if (!transcriptionsRes.ok || !usersRes.ok) { 73 + if (transcriptionsRes.status === 403 || usersRes.status === 403) { 74 + window.location.href = "/"; 75 + return; 76 + } 77 + throw new Error("Failed to load admin data"); 78 + } 79 + 80 + const transcriptions = await transcriptionsRes.json(); 81 + const users = await usersRes.json(); 82 + 83 + const totalUsers = document.getElementById("total-users"); 84 + const totalTranscriptions = document.getElementById("total-transcriptions"); 85 + const failedTranscriptions = document.getElementById( 86 + "failed-transcriptions", 87 + ); 70 88 71 - const transcriptions = await transcriptionsRes.json(); 72 - const users = await usersRes.json(); 89 + if (totalUsers) totalUsers.textContent = users.length.toString(); 90 + if (totalTranscriptions) 91 + totalTranscriptions.textContent = transcriptions.length.toString(); 73 92 74 - const totalUsers = document.getElementById('total-users'); 75 - const totalTranscriptions = document.getElementById('total-transcriptions'); 76 - const failedTranscriptions = document.getElementById('failed-transcriptions'); 77 - 78 - if (totalUsers) totalUsers.textContent = users.length.toString(); 79 - if (totalTranscriptions) totalTranscriptions.textContent = transcriptions.length.toString(); 80 - 81 - const failed = transcriptions.filter((t: any) => t.status === 'failed'); 82 - if (failedTranscriptions) failedTranscriptions.textContent = failed.length.toString(); 93 + const failed = transcriptions.filter( 94 + (t: { status: string }) => t.status === "failed", 95 + ); 96 + if (failedTranscriptions) 97 + failedTranscriptions.textContent = failed.length.toString(); 83 98 84 - loading.classList.add('hidden'); 85 - content.classList.remove('hidden'); 86 - } catch (error) { 87 - errorMessage.textContent = (error as Error).message; 88 - errorMessage.classList.remove('hidden'); 89 - loading.classList.add('hidden'); 90 - } 99 + loading.classList.add("hidden"); 100 + content.classList.remove("hidden"); 101 + } catch (error) { 102 + errorMessage.textContent = (error as Error).message; 103 + errorMessage.classList.remove("hidden"); 104 + loading.classList.add("hidden"); 105 + } 91 106 } 92 107 93 108 // Tab switching 94 109 function switchTab(tabName: string) { 95 - document.querySelectorAll('.tab').forEach(t => { 96 - t.classList.remove('active'); 97 - }); 98 - document.querySelectorAll('.tab-content').forEach(c => { 99 - c.classList.remove('active'); 100 - }); 110 + document.querySelectorAll(".tab").forEach((t) => { 111 + t.classList.remove("active"); 112 + }); 113 + document.querySelectorAll(".tab-content").forEach((c) => { 114 + c.classList.remove("active"); 115 + }); 101 116 102 - const tabButton = document.querySelector(`[data-tab="${tabName}"]`); 103 - const tabContent = document.getElementById(`${tabName}-tab`); 104 - 105 - if (tabButton && tabContent) { 106 - tabButton.classList.add('active'); 107 - tabContent.classList.add('active'); 108 - 109 - // Update URL without reloading 110 - const url = new URL(window.location.href); 111 - url.searchParams.set('tab', tabName); 112 - // Remove subtab param when leaving classes tab 113 - if (tabName !== 'classes') { 114 - url.searchParams.delete('subtab'); 115 - } 116 - window.history.pushState({}, '', url); 117 - } 117 + const tabButton = document.querySelector(`[data-tab="${tabName}"]`); 118 + const tabContent = document.getElementById(`${tabName}-tab`); 119 + 120 + if (tabButton && tabContent) { 121 + tabButton.classList.add("active"); 122 + tabContent.classList.add("active"); 123 + 124 + // Update URL without reloading 125 + const url = new URL(window.location.href); 126 + url.searchParams.set("tab", tabName); 127 + // Remove subtab param when leaving classes tab 128 + if (tabName !== "classes") { 129 + url.searchParams.delete("subtab"); 130 + } 131 + window.history.pushState({}, "", url); 132 + } 118 133 } 119 134 120 - document.querySelectorAll('.tab').forEach(tab => { 121 - tab.addEventListener('click', () => { 122 - switchTab((tab as HTMLElement).dataset.tab || ''); 123 - }); 135 + document.querySelectorAll(".tab").forEach((tab) => { 136 + tab.addEventListener("click", () => { 137 + switchTab((tab as HTMLElement).dataset.tab || ""); 138 + }); 124 139 }); 125 140 126 141 // Check for tab query parameter on load 127 142 const params = new URLSearchParams(window.location.search); 128 - const initialTab = params.get('tab'); 129 - const validTabs = ['pending', 'transcriptions', 'users', 'classes']; 143 + const initialTab = params.get("tab"); 144 + const validTabs = ["pending", "transcriptions", "users", "classes"]; 130 145 131 146 if (initialTab && validTabs.includes(initialTab)) { 132 - switchTab(initialTab); 147 + switchTab(initialTab); 133 148 } 134 149 135 150 // Initialize
+12 -8
src/pages/index.ts
··· 1 - document.getElementById('start-btn')?.addEventListener('click', async () => { 2 - const authComponent = document.querySelector('auth-component') as any; 3 - const isLoggedIn = await authComponent.isAuthenticated(); 1 + document.getElementById("start-btn")?.addEventListener("click", async () => { 2 + const authComponent = document.querySelector("auth-component"); 3 + if (!authComponent) return; 4 4 5 - if (isLoggedIn) { 6 - window.location.href = '/classes'; 7 - } else { 8 - authComponent.openAuthModal(); 9 - } 5 + const isLoggedIn = await ( 6 + authComponent as { isAuthenticated: () => Promise<boolean> } 7 + ).isAuthenticated(); 8 + 9 + if (isLoggedIn) { 10 + window.location.href = "/classes"; 11 + } else { 12 + (authComponent as { openAuthModal: () => void }).openAuthModal(); 13 + } 10 14 });
+4 -4
src/pages/reset-password.ts
··· 1 1 // Wait for component to be defined before setting token 2 - await customElements.whenDefined('reset-password-form'); 2 + await customElements.whenDefined("reset-password-form"); 3 3 4 4 // Get token from URL and pass to component 5 5 const urlParams = new URLSearchParams(window.location.search); 6 - const token = urlParams.get('token'); 7 - const resetForm = document.getElementById('reset-form') as any; 6 + const token = urlParams.get("token"); 7 + const resetForm = document.getElementById("reset-form"); 8 8 if (resetForm) { 9 - resetForm.token = token; 9 + (resetForm as { token: string | null }).token = token; 10 10 }
+64 -64
src/styles/admin.css
··· 1 1 main { 2 - max-width: 80rem; 3 - margin: 0 auto; 4 - padding: 2rem; 2 + max-width: 80rem; 3 + margin: 0 auto; 4 + padding: 2rem; 5 5 } 6 6 7 7 h1 { 8 - margin-bottom: 2rem; 9 - color: var(--text); 8 + margin-bottom: 2rem; 9 + color: var(--text); 10 10 } 11 11 12 12 .section { 13 - margin-bottom: 3rem; 13 + margin-bottom: 3rem; 14 14 } 15 15 16 16 .section-title { 17 - font-size: 1.5rem; 18 - font-weight: 600; 19 - color: var(--text); 20 - margin-bottom: 1rem; 21 - display: flex; 22 - align-items: center; 23 - gap: 0.5rem; 17 + font-size: 1.5rem; 18 + font-weight: 600; 19 + color: var(--text); 20 + margin-bottom: 1rem; 21 + display: flex; 22 + align-items: center; 23 + gap: 0.5rem; 24 24 } 25 25 26 26 .tabs { 27 - display: flex; 28 - gap: 1rem; 29 - border-bottom: 2px solid var(--secondary); 30 - margin-bottom: 2rem; 27 + display: flex; 28 + gap: 1rem; 29 + border-bottom: 2px solid var(--secondary); 30 + margin-bottom: 2rem; 31 31 } 32 32 33 33 .tab { 34 - padding: 0.75rem 1.5rem; 35 - border: none; 36 - background: transparent; 37 - color: var(--text); 38 - cursor: pointer; 39 - font-size: 1rem; 40 - font-weight: 500; 41 - font-family: inherit; 42 - border-bottom: 2px solid transparent; 43 - margin-bottom: -2px; 44 - transition: all 0.2s; 34 + padding: 0.75rem 1.5rem; 35 + border: none; 36 + background: transparent; 37 + color: var(--text); 38 + cursor: pointer; 39 + font-size: 1rem; 40 + font-weight: 500; 41 + font-family: inherit; 42 + border-bottom: 2px solid transparent; 43 + margin-bottom: -2px; 44 + transition: all 0.2s; 45 45 } 46 46 47 47 .tab:hover { 48 - color: var(--primary); 48 + color: var(--primary); 49 49 } 50 50 51 51 .tab.active { 52 - color: var(--primary); 53 - border-bottom-color: var(--primary); 52 + color: var(--primary); 53 + border-bottom-color: var(--primary); 54 54 } 55 55 56 56 .tab-content { 57 - display: none; 57 + display: none; 58 58 } 59 59 60 60 .tab-content.active { 61 - display: block; 61 + display: block; 62 62 } 63 63 64 64 .empty-state { 65 - text-align: center; 66 - padding: 3rem; 67 - color: var(--text); 68 - opacity: 0.6; 65 + text-align: center; 66 + padding: 3rem; 67 + color: var(--text); 68 + opacity: 0.6; 69 69 } 70 70 71 71 .loading { 72 - text-align: center; 73 - padding: 3rem; 74 - color: var(--text); 72 + text-align: center; 73 + padding: 3rem; 74 + color: var(--text); 75 75 } 76 76 77 77 .error { 78 - background: #fee2e2; 79 - color: #991b1b; 80 - padding: 1rem; 81 - border-radius: 6px; 82 - margin-bottom: 1rem; 78 + background: #fee2e2; 79 + color: #991b1b; 80 + padding: 1rem; 81 + border-radius: 6px; 82 + margin-bottom: 1rem; 83 83 } 84 84 85 85 .stats { 86 - display: grid; 87 - grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); 88 - gap: 1rem; 89 - margin-bottom: 2rem; 86 + display: grid; 87 + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); 88 + gap: 1rem; 89 + margin-bottom: 2rem; 90 90 } 91 91 92 92 .stat-card { 93 - background: var(--background); 94 - border: 2px solid var(--secondary); 95 - border-radius: 8px; 96 - padding: 1.5rem; 93 + background: var(--background); 94 + border: 2px solid var(--secondary); 95 + border-radius: 8px; 96 + padding: 1.5rem; 97 97 } 98 98 99 99 .stat-value { 100 - font-size: 2rem; 101 - font-weight: 700; 102 - color: var(--primary); 103 - margin-bottom: 0.25rem; 100 + font-size: 2rem; 101 + font-weight: 700; 102 + color: var(--primary); 103 + margin-bottom: 0.25rem; 104 104 } 105 105 106 106 .stat-label { 107 - color: var(--text); 108 - opacity: 0.7; 109 - font-size: 0.875rem; 107 + color: var(--text); 108 + opacity: 0.7; 109 + font-size: 0.875rem; 110 110 } 111 111 112 112 .timestamp { 113 - color: var(--text); 114 - opacity: 0.6; 115 - font-size: 0.875rem; 113 + color: var(--text); 114 + opacity: 0.6; 115 + font-size: 0.875rem; 116 116 } 117 117 118 118 .hidden { 119 - display: none; 119 + display: none; 120 120 }
+41 -41
src/styles/index.css
··· 1 1 .hero-title { 2 - font-size: 3rem; 3 - font-weight: 700; 4 - color: var(--text); 5 - margin-bottom: 1rem; 2 + font-size: 3rem; 3 + font-weight: 700; 4 + color: var(--text); 5 + margin-bottom: 1rem; 6 6 } 7 7 8 8 .hero-subtitle { 9 - font-size: 1.25rem; 10 - color: var(--text); 11 - opacity: 0.8; 12 - margin-bottom: 2rem; 9 + font-size: 1.25rem; 10 + color: var(--text); 11 + opacity: 0.8; 12 + margin-bottom: 2rem; 13 13 } 14 14 15 15 main { 16 - text-align: center; 17 - padding: 4rem 2rem; 16 + text-align: center; 17 + padding: 4rem 2rem; 18 18 } 19 19 20 20 .cta-buttons { 21 - display: flex; 22 - gap: 1rem; 23 - justify-content: center; 24 - margin-top: 2rem; 21 + display: flex; 22 + gap: 1rem; 23 + justify-content: center; 24 + margin-top: 2rem; 25 25 } 26 26 27 27 .btn { 28 - padding: 0.75rem 1.5rem; 29 - border-radius: 6px; 30 - font-size: 1rem; 31 - font-weight: 500; 32 - cursor: pointer; 33 - transition: all 0.2s; 34 - font-family: inherit; 35 - border: 2px solid; 36 - text-decoration: none; 37 - display: inline-block; 28 + padding: 0.75rem 1.5rem; 29 + border-radius: 6px; 30 + font-size: 1rem; 31 + font-weight: 500; 32 + cursor: pointer; 33 + transition: all 0.2s; 34 + font-family: inherit; 35 + border: 2px solid; 36 + text-decoration: none; 37 + display: inline-block; 38 38 } 39 39 40 40 .btn-primary { 41 - background: var(--primary); 42 - color: white; 43 - border-color: var(--primary); 41 + background: var(--primary); 42 + color: white; 43 + border-color: var(--primary); 44 44 } 45 45 46 46 .btn-primary:hover { 47 - background: transparent; 48 - color: var(--primary); 47 + background: transparent; 48 + color: var(--primary); 49 49 } 50 50 51 51 .btn-secondary { 52 - background: transparent; 53 - color: var(--text); 54 - border-color: var(--secondary); 52 + background: transparent; 53 + color: var(--text); 54 + border-color: var(--secondary); 55 55 } 56 56 57 57 .btn-secondary:hover { 58 - border-color: var(--primary); 59 - color: var(--primary); 58 + border-color: var(--primary); 59 + color: var(--primary); 60 60 } 61 61 62 62 @media (max-width: 640px) { 63 - .hero-title { 64 - font-size: 2.5rem; 65 - } 63 + .hero-title { 64 + font-size: 2.5rem; 65 + } 66 66 67 - .cta-buttons { 68 - flex-direction: column; 69 - align-items: center; 70 - } 67 + .cta-buttons { 68 + flex-direction: column; 69 + align-items: center; 70 + } 71 71 }
+4 -4
src/styles/reset-password.css
··· 1 1 main { 2 - display: flex; 3 - align-items: center; 4 - justify-content: center; 5 - padding: 4rem 1rem; 2 + display: flex; 3 + align-items: center; 4 + justify-content: center; 5 + padding: 4rem 1rem; 6 6 }
+1 -1
src/styles/settings.css
··· 1 1 main { 2 - max-width: 64rem; 2 + max-width: 64rem; 3 3 }
+13 -13
src/styles/transcribe.css
··· 1 1 .page-header { 2 - text-align: center; 3 - margin-bottom: 3rem; 2 + text-align: center; 3 + margin-bottom: 3rem; 4 4 } 5 5 6 6 .page-title { 7 - font-size: 2.5rem; 8 - font-weight: 700; 9 - color: var(--text); 10 - margin-bottom: 0.5rem; 7 + font-size: 2.5rem; 8 + font-weight: 700; 9 + color: var(--text); 10 + margin-bottom: 0.5rem; 11 11 } 12 12 13 13 .page-subtitle { 14 - font-size: 1.125rem; 15 - color: var(--text); 16 - opacity: 0.8; 14 + font-size: 1.125rem; 15 + color: var(--text); 16 + opacity: 0.8; 17 17 } 18 18 19 19 .back-link { 20 - color: var(--paynes-gray); 21 - text-decoration: none; 22 - font-size: 0.875rem; 20 + color: var(--paynes-gray); 21 + text-decoration: none; 22 + font-size: 0.875rem; 23 23 } 24 24 25 25 .mb-1 { 26 - margin-bottom: 1rem; 26 + margin-bottom: 1rem; 27 27 }