diff --git a/noteUZ-frontend/src/components/ThemeToggle.tsx b/noteUZ-frontend/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..d6e0ab1 --- /dev/null +++ b/noteUZ-frontend/src/components/ThemeToggle.tsx @@ -0,0 +1,60 @@ +// components/ThemeToggle.tsx +import { useEffect, useState } from 'react'; +import { getTheme, setTheme, applyTheme, watchSystemTheme, getEffectiveTheme, type ThemeMode } from '../lib/theme'; + +export default function ThemeToggle() { + const [theme, setThemeState] = useState('system'); + const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light'); + + // Inicjalizacja przy montowaniu komponentu + useEffect(() => { + const current = getTheme(); + setThemeState(current); + setEffectiveTheme(getEffectiveTheme()); + + // Jeśli system, nasłuchuj zmian + if (current === 'system') { + const unwatch = watchSystemTheme(() => { + setEffectiveTheme(getEffectiveTheme()); + }); + return unwatch; + } + }, []); + + // Obsługi zmiany motywu + const handleToggle = () => { + const newTheme: ThemeMode = theme === 'light' ? 'dark' : 'light'; + setTheme(newTheme); + setThemeState(newTheme); + setEffectiveTheme(getEffectiveTheme()); + }; + + return ( + + ); +} diff --git a/noteUZ-frontend/src/lib/theme.ts b/noteUZ-frontend/src/lib/theme.ts new file mode 100644 index 0000000..6178b9f --- /dev/null +++ b/noteUZ-frontend/src/lib/theme.ts @@ -0,0 +1,59 @@ +// lib/theme.ts +export type ThemeMode = 'light' | 'dark' | 'system'; + +const STORAGE_KEY = 'theme-preference'; + +export function getTheme(): ThemeMode { + if (typeof window === 'undefined') return 'system'; + + const stored = localStorage.getItem(STORAGE_KEY) as ThemeMode | null; + if (stored && ['light', 'dark', 'system'].includes(stored)) { + return stored; + } + return 'system'; +} + +export function setTheme(theme: ThemeMode): void { + if (typeof window === 'undefined') return; + + localStorage.setItem(STORAGE_KEY, theme); + applyTheme(theme); +} + +export function applyTheme(theme: ThemeMode): void { + if (typeof document === 'undefined') return; + + const html = document.documentElement; + const isDark = theme === 'dark' || + (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); + + if (isDark) { + html.setAttribute('data-theme', 'dark'); + } else { + html.setAttribute('data-theme', 'light'); + } +} + +export function watchSystemTheme(callback: (isDark: boolean) => void): () => void { + if (typeof window === 'undefined') return () => {}; + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = (e: MediaQueryListEvent) => { + callback(e.matches); + }; + + mediaQuery.addEventListener('change', handler); + return () => mediaQuery.removeEventListener('change', handler); +} + +export function getEffectiveTheme(): 'light' | 'dark' { + const theme = getTheme(); + if (theme === 'dark') return 'dark'; + if (theme === 'light') return 'light'; + + if (typeof window !== 'undefined') { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + return 'light'; +} diff --git a/noteUZ-frontend/src/pages/_app.tsx b/noteUZ-frontend/src/pages/_app.tsx new file mode 100644 index 0000000..cdc2e19 --- /dev/null +++ b/noteUZ-frontend/src/pages/_app.tsx @@ -0,0 +1,15 @@ +// pages/_app.tsx +import type { AppProps } from 'next/app'; +import { useEffect } from 'react'; +import { applyTheme, getTheme } from '../lib/theme'; +import '../styles/globals.css'; + +export default function App({ Component, pageProps }: AppProps) { + // Zastosuj motyw przy załadowaniu aplikacji + useEffect(() => { + const theme = getTheme(); + applyTheme(theme); + }, []); + + return ; +} diff --git a/noteUZ-frontend/src/pages/index.tsx b/noteUZ-frontend/src/pages/index.tsx index 81e81c6..d317c37 100644 --- a/noteUZ-frontend/src/pages/index.tsx +++ b/noteUZ-frontend/src/pages/index.tsx @@ -1,6 +1,7 @@ import Head from 'next/head'; import Link from 'next/link'; import React, { useEffect, useState } from 'react'; +import ThemeToggle from '../components/ThemeToggle'; import s from '../styles/Home.module.css'; const API = process.env.NEXT_PUBLIC_API_URL ?? ''; @@ -79,6 +80,7 @@ export default function Home() {
+ {isLoggedIn ? ( diff --git a/noteUZ-frontend/src/styles/Home.module.css b/noteUZ-frontend/src/styles/Home.module.css index 140d668..8a42b6c 100644 --- a/noteUZ-frontend/src/styles/Home.module.css +++ b/noteUZ-frontend/src/styles/Home.module.css @@ -1,3 +1,4 @@ +/* styles/Home.module.css */ .page { min-height: 100vh; display: flex; @@ -7,6 +8,7 @@ color: var(--foreground, #171717); font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding: 0; + transition: background-color 0.3s ease, color 0.3s ease; } /* Header */ @@ -18,6 +20,7 @@ background: var(--background); border-bottom: 1px solid rgba(15,23,42,0.04); backdrop-filter: blur(6px); + transition: background-color 0.3s ease; } .navInner { @@ -56,6 +59,12 @@ padding: 8px; border-radius: 8px; cursor: pointer; + color: var(--foreground); + transition: all 0.3s ease; +} + +.iconButton:hover { + background: var(--card-bg); } /* Search */ @@ -70,8 +79,15 @@ border-radius: 12px; border: 1px solid rgba(15,23,42,0.06); background: var(--background); + color: var(--foreground); box-shadow: 0 6px 16px rgba(2,6,23,0.04); outline: none; + transition: all 0.3s ease; +} + +.searchInput:focus { + border-color: #4f46e5; + box-shadow: 0 6px 20px rgba(79, 70, 229, 0.15); } /* Container */ @@ -117,6 +133,11 @@ border-radius: 12px; background: linear-gradient(180deg, #fbfdff 0%, #eef6ff 100%); box-shadow: 0 8px 30px rgba(2,6,23,0.04); + transition: background 0.3s ease; +} + +[data-theme='dark'] .hero { + background: linear-gradient(180deg, #1a1a1a 0%, #1a1a1a 100%); } .heroLeft { @@ -147,6 +168,12 @@ font-weight: 600; text-decoration: none; box-shadow: 0 8px 22px rgba(255,122,24,0.16); + transition: all 0.3s ease; +} + +.ctaLink:hover { + transform: translateY(-2px); + box-shadow: 0 10px 28px rgba(255,122,24,0.24); } .ctaLinkInline { @@ -157,9 +184,21 @@ color: white; font-weight: 600; text-decoration: none; + border: none; + cursor: pointer; + transition: all 0.3s ease; } -/* Lang/Secondary link (zachowane pod klasą .secondary jeśli potrzebne później) */ +.ctaLinkInline:hover { + transform: translateY(-1px); +} + +.ctaLinkInline:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Secondary link */ .secondary { padding: 8px 10px; border-radius: 10px; @@ -167,6 +206,13 @@ color: #334155; text-decoration: none; border: 1px solid rgba(15,23,42,0.06); + transition: all 0.3s ease; +} + +.secondary:hover { + background: var(--card-bg); + border-color: #4f46e5; + color: #4f46e5; } /* Text */ @@ -192,6 +238,12 @@ box-shadow: 0 10px 30px rgba(2,6,23,0.06); border: 1px solid #eef6ff; background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%); + transition: all 0.3s ease; +} + +[data-theme='dark'] .device { + border: 1px solid #333333; + background: linear-gradient(180deg, #1a1a1a 0%, #0a0a0a 100%); } .deviceHeader { @@ -201,12 +253,18 @@ .deviceBody { padding: 18px; + background: var(--background); } .noteMock { background: #fbfdff; border-radius: 8px; padding: 12px; + transition: background-color 0.3s ease; +} + +[data-theme='dark'] .noteMock { + background: var(--card-bg); } .noteLine { @@ -244,6 +302,16 @@ border-radius: 10px; padding: 16px; box-shadow: 0 8px 22px rgba(2,6,23,0.04); + transition: all 0.3s ease; +} + +[data-theme='dark'] .feature { + background: var(--card-bg); +} + +.feature:hover { + transform: translateY(-2px); + box-shadow: 0 10px 28px rgba(2,6,23,0.08); } .featureIcon { @@ -255,6 +323,7 @@ margin: 0; font-size: 14px; font-weight: 600; + color: var(--foreground); } .featureText { diff --git a/noteUZ-frontend/src/styles/Login.module.css b/noteUZ-frontend/src/styles/Login.module.css index 211828c..912339c 100644 --- a/noteUZ-frontend/src/styles/Login.module.css +++ b/noteUZ-frontend/src/styles/Login.module.css @@ -1,3 +1,4 @@ +/* styles/Login.module.css */ .page { min-height: 100vh; display: flex; @@ -5,17 +6,20 @@ justify-content: center; padding: 24px; font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; - background: linear-gradient(180deg, #fbfdff 0%, #eef6ff 100%); - color: var(--foreground, #0f172a); + background: var(--background); + color: var(--foreground); + transition: background-color 0.3s ease, color 0.3s ease; } .card { width: 100%; max-width: 420px; - background: white; + background: var(--card-bg); border-radius: 14px; - box-shadow: 0 10px 30px rgba(2,6,23,0.08); + box-shadow: 0 10px 30px rgba(2, 6, 23, 0.08); + border: 1px solid var(--border); padding: 28px; + transition: all 0.3s ease; } .header { @@ -34,7 +38,7 @@ display: flex; align-items: center; justify-content: center; - box-shadow: 0 8px 20px rgba(79,70,229,0.12); + box-shadow: 0 8px 20px rgba(79, 70, 229, 0.12); flex-shrink: 0; } @@ -42,7 +46,7 @@ margin: 0; font-size: 20px; font-weight: 700; - color: #0f172a; + color: var(--foreground); } .form { @@ -58,18 +62,26 @@ display: block; font-size: 13px; margin-bottom: 6px; - color: #334155; + color: var(--text-secondary); + font-weight: 500; } .input { width: 100%; padding: 10px 12px; border-radius: 10px; - border: 1px solid #e6eef8; + border: 1px solid var(--border); font-size: 14px; outline: none; box-sizing: border-box; - background: #fbfdff; + background: var(--background); + color: var(--foreground); + transition: all 0.3s ease; +} + +.input:focus { + border-color: #4f46e5; + box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); } .button { @@ -82,12 +94,22 @@ font-weight: 600; cursor: pointer; font-size: 15px; - box-shadow: 0 8px 22px rgba(255,122,24,0.16); + box-shadow: 0 8px 22px rgba(255, 122, 24, 0.16); + transition: all 0.3s ease; +} + +.button:hover { + transform: translateY(-2px); + box-shadow: 0 10px 28px rgba(255, 122, 24, 0.24); +} + +.button:active { + transform: translateY(0); } .buttonDisabled { opacity: 0.6; - cursor: default; + cursor: not-allowed; } .error { diff --git a/noteUZ-frontend/src/styles/globals.css b/noteUZ-frontend/src/styles/globals.css index a2dc41e..1ff02b8 100644 --- a/noteUZ-frontend/src/styles/globals.css +++ b/noteUZ-frontend/src/styles/globals.css @@ -1,26 +1,87 @@ +/* styles/globals.css */ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #ffffff; + --foreground: #171717; + --card-bg: #f9f9f9; + --border: #e5e5e5; + --text-secondary: #666666; } -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); +/* Dark mode - prefers-color-scheme */ +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + --card-bg: #1a1a1a; + --border: #333333; + --text-secondary: #999999; + } } -@media (prefers-color-scheme: dark) { - :root { +/* Dark mode - data-theme attribute (dla localStorage) */ +[data-theme='dark'] { --background: #0a0a0a; --foreground: #ededed; - } + --card-bg: #1a1a1a; + --border: #333333; + --text-secondary: #999999; +} + +[data-theme='light'] { + --background: #ffffff; + --foreground: #171717; + --card-bg: #f9f9f9; + --border: #e5e5e5; + --text-secondary: #666666; +} + +* { + box-sizing: border-box; +} + +html { + transition: background-color 0.3s ease, color 0.3s ease; } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; + margin: 0; + padding: 0; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Theme Toggle Button */ +.theme-toggle { + background: transparent; + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--foreground); + transition: all 0.3s ease; + width: 40px; + height: 40px; +} + +.theme-toggle:hover { + background: var(--card-bg); + border-color: var(--text-secondary); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); }