feat: responsive desing #3

Merged
Nom4ne merged 1 commits from RWD into master 2025-12-31 16:28:28 +01:00
7 changed files with 168 additions and 104 deletions
Showing only changes of commit de9486bece - Show all commits

View File

@@ -3,4 +3,5 @@ PUBLIC_URL=https://example.com # Publicly accessible website root, used for rewr
USE_SUBDOMAINS=true # Whether backend allows for use of subdomains in URL generation. USE_SUBDOMAINS=true # Whether backend allows for use of subdomains in URL generation.
DEBUG=false DEBUG=false
# Frontend specific # Frontend specific
<miejsce na twoje zmienne> <miejsce na twoje zmienne>
VITE_API_TARGET=kitkat.example.com # Target backend for API requests.

View File

@@ -218,9 +218,7 @@ export const FlappyCat: React.FC<{ onBack: () => void }> = ({ onBack }) => {
</div> </div>
)} )}
</div> </div>
<p className="mt-4 text-slate-400 text-sm font-bold uppercase tracking-widest">
Physics: {isPlaying ? "Frame-Independent" : "Ready"}
</p>
</div> </div>
); );
}; };

View File

@@ -8,41 +8,52 @@ interface GeneratorProps {
} }
export const Generator: React.FC<GeneratorProps> = ({ url, setUrl, onGenerate }) => ( export const Generator: React.FC<GeneratorProps> = ({ url, setUrl, onGenerate }) => (
<div className="max-w-[800px] mx-auto pt-16 px-4 flex flex-col items-center"> <div className="max-w-[800px] mx-auto pt-10 sm:pt-16 px-4 flex flex-col items-center">
<header className="text-center mb-12"> {/* Header - Skalowanie tekstu i ikony */}
<h1 className="text-7xl font-black text-pink-500 mb-2 tracking-tighter flex items-center gap-4"> <header className="text-center mb-8 sm:mb-12">
KittyURL <PawPrint size={50} fill="currentColor" /> <h1 className="text-4xl sm:text-7xl font-black text-pink-500 mb-2 tracking-tighter flex items-center justify-center gap-2 sm:gap-4">
KittyURL <PawPrint className="w-8 h-8 sm:w-12 sm:h-12" fill="currentColor" />
</h1> </h1>
<p className="text-pink-300 text-xl font-medium">Shorten your links with a purr!</p> <p className="text-pink-300 text-lg sm:text-xl font-medium px-4">
Shorten your links with a purr!
</p>
</header> </header>
<div className="w-full bg-white rounded-[3rem] shadow-2xl shadow-pink-200/50 p-10 border-4 border-white relative overflow-hidden"> {/* Główna karta - mniejsze paddingi i zaokrąglenia na mobile */}
<div className="w-full bg-white rounded-[2rem] sm:rounded-[3rem] shadow-2xl shadow-pink-200/50 p-6 sm:p-10 border-4 border-white relative overflow-hidden">
<div className="mb-6"> <div className="mb-6">
<label className="block text-xs font-black uppercase tracking-widest text-pink-300 mb-3 ml-2">Long URL to shorten</label> <label className="block text-[10px] sm:text-xs font-black uppercase tracking-widest text-pink-300 mb-3 ml-2">
Long URL to shorten
</label>
<div className="relative"> <div className="relative">
<input <input
type="url" type="url"
placeholder="https://example.com/very-long-url" placeholder="https://example.com/very-long-url"
className="w-full p-5 bg-pink-50/30 border-2 border-pink-100 rounded-2xl outline-none focus:border-pink-400 focus:bg-white transition-all text-lg shadow-inner" className="w-full p-4 sm:p-5 bg-pink-50/30 border-2 border-pink-100 rounded-xl sm:rounded-2xl outline-none focus:border-pink-400 focus:bg-white transition-all text-base sm:text-lg shadow-inner pr-12"
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
/> />
<Heart className="absolute right-5 top-1/2 -translate-y-1/2 text-pink-200" size={24} /> <Heart className="absolute right-4 top-1/2 -translate-y-1/2 text-pink-200 w-5 h-5 sm:w-6 sm:h-6" />
</div> </div>
</div> </div>
<button <button
onClick={onGenerate} onClick={onGenerate}
className="w-full bg-pink-500 hover:bg-pink-600 text-white font-black py-5 rounded-[1.5rem] transition-all shadow-xl shadow-pink-100 active:scale-[0.98] text-xl flex items-center justify-center gap-3 cursor-pointer" className="w-full bg-pink-500 hover:bg-pink-600 text-white font-black py-4 sm:py-5 rounded-xl sm:rounded-[1.5rem] transition-all shadow-xl shadow-pink-100 active:scale-[0.98] text-lg sm:text-xl flex items-center justify-center gap-3 cursor-pointer"
> >
Generate Kitty Link <Sparkles size={24} /> <span className="hidden xs:inline">Generate Kitty Link</span>
<span className="xs:hidden">Generate</span>
<Sparkles className="w-5 h-5 sm:w-6 sm:h-6" />
</button> </button>
</div> </div>
<div className="w-full mt-16 text-center"> {/* Sekcja "No links yet" - Skalowanie paddingu i ikony */}
<div className="bg-pink-100/50 rounded-[2.5rem] border-4 border-dashed border-pink-200 p-12"> <div className="w-full mt-10 sm:mt-16 text-center">
<Cat size={60} className="mx-auto text-pink-200 mb-4" /> <div className="bg-pink-100/50 rounded-[2rem] sm:rounded-[2.5rem] border-4 border-dashed border-pink-200 p-8 sm:p-12">
<p className="text-pink-300 font-bold">No links generated yet. Feed me a URL! 🐾</p> <Cat className="mx-auto text-pink-200 mb-4 w-12 h-12 sm:w-16 sm:h-16" />
<p className="text-pink-300 font-bold text-sm sm:text-base px-2">
No links generated yet. Feed me a URL! 🐾
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,15 +1,23 @@
import { PawPrint, ExternalLink } from 'lucide-react';
import { PawPrint, ExternalLink } from 'lucide-react';
export const HistoryView = ({ onBack }: { onBack: () => void }) => ( export const HistoryView = ({ onBack }: { onBack: () => void }) => (
<div className="max-w-4xl mx-auto p-6 pt-10"> <div className="max-w-4xl mx-auto px-4 py-6 sm:p-6 sm:pt-10">
<button onClick={onBack} className="mb-8 flex items-center gap-2 text-pink-500 font-bold hover:scale-105 transition-transform"> {/* Przycisk powrotu - mniejszy margines na mobile */}
<PawPrint size={20} /> Back to KittyURL <button
onClick={onBack}
className="mb-6 sm:mb-8 flex items-center gap-2 text-pink-500 font-bold hover:scale-105 transition-transform text-sm sm:text-base"
>
<PawPrint size={18} /> Back to KittyURL
</button> </button>
<div className="bg-white/80 backdrop-blur-md rounded-[2.5rem] p-8 shadow-xl border-2 border-pink-50">
<h2 className="text-3xl font-black text-slate-800 mb-8">Recent Paws 🐾</h2> <div className="bg-white/80 backdrop-blur-md rounded-[2rem] sm:rounded-[2.5rem] p-5 sm:p-8 shadow-xl border-2 border-pink-50">
<h2 className="text-2xl sm:text-3xl font-black text-slate-800 mb-6 sm:mb-8 flex items-center gap-2">
Recent Paws <span className="text-xl sm:text-2xl">🐾</span>
</h2>
<div className="overflow-hidden rounded-2xl border border-pink-100"> <div className="overflow-hidden rounded-2xl border border-pink-100">
<table className="w-full text-left bg-white"> {/* Widok Tabeli (widoczny od ekranów 'sm') */}
<table className="w-full text-left bg-white hidden sm:table">
<thead className="bg-pink-50 text-pink-600"> <thead className="bg-pink-50 text-pink-600">
<tr> <tr>
<th className="px-6 py-4 font-bold uppercase text-xs">Long URL</th> <th className="px-6 py-4 font-bold uppercase text-xs">Long URL</th>
@@ -18,11 +26,34 @@ export const HistoryView = ({ onBack }: { onBack: () => void }) => (
</thead> </thead>
<tbody className="divide-y divide-pink-50"> <tbody className="divide-y divide-pink-50">
<tr className="hover:bg-pink-50/50 transition-colors"> <tr className="hover:bg-pink-50/50 transition-colors">
<td className="px-6 py-4 text-slate-400 truncate max-w-[200px]">https://very-long-link.com/cats</td> <td className="px-6 py-4 text-slate-400 truncate max-w-[300px]">
<td className="px-6 py-4 font-bold text-pink-500 flex items-center gap-2">kitty.url/meow <ExternalLink size={14} /></td> https://very-long-link.com/cats/are/the/best/animals/in/the/world
</td>
<td className="px-6 py-4">
<a href="#" className="font-bold text-pink-500 flex items-center gap-2 hover:underline">
kitty.url/meow <ExternalLink size={14} />
</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
{/* Widok Mobilny (Lista kart zamiast tabeli - widoczny tylko na małych ekranach) */}
<div className="sm:hidden divide-y divide-pink-50 bg-white">
<div className="p-4 space-y-2">
<div className="text-[10px] font-bold text-pink-400 uppercase tracking-wider">Long URL</div>
<div className="text-slate-500 text-sm truncate">
https://very-long-link.com/cats/are/the/best/animals/in/the/world
</div>
<div className="pt-2">
<div className="text-[10px] font-bold text-pink-400 uppercase tracking-wider mb-1">Kitty URL</div>
<a href="#" className="font-bold text-pink-500 flex items-center gap-1 text-sm">
kitty.url/meow <ExternalLink size={14} />
</a>
</div>
</div>
{/* Tutaj możesz mapować kolejne wpisy historii tak samo jak wyżej */}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -243,10 +243,7 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => {
</div> </div>
)} )}
</div> </div>
<div className="mt-6 flex gap-6 text-slate-300 font-bold uppercase text-xs tracking-[0.2em]">
<span>Speed: {Math.round(INITIAL_SPEED + score * SPEED_INCREMENT)} px/s</span>
<span>Physics: Delta-Time Based</span>
</div>
</div> </div>
); );
}; };

View File

@@ -1,5 +1,4 @@
import { History as HistoryIcon, LogIn, LogOut, Gamepad2, Wind } from 'lucide-react'; import { History as HistoryIcon, LogIn, LogOut, Gamepad2, Wind, Cat } from 'lucide-react';
// Używamy 'import type' dla zadowolenia verbatimModuleSyntax
import type { View } from '../App'; import type { View } from '../App';
interface NavbarProps { interface NavbarProps {
@@ -9,49 +8,66 @@ interface NavbarProps {
} }
export const Navbar = ({ onNavigate, isAuthenticated, onLogout }: NavbarProps) => ( export const Navbar = ({ onNavigate, isAuthenticated, onLogout }: NavbarProps) => (
<nav className="max-w-5xl mx-auto py-6 px-6 flex justify-end gap-3"> <nav className="max-w-5xl mx-auto py-4 px-4 flex items-center justify-between gap-4">
{/* Przycisk Jump Game */} {/* LEWA STRONA: Przycisk Home w stylu Kitty */}
<button <button
onClick={() => onNavigate('jump-game')} onClick={() => onNavigate('home')}
className="flex items-center gap-2 px-4 py-2 bg-pink-100 hover:bg-pink-200 rounded-full font-bold text-pink-600 transition-all active:scale-95" className="flex items-center gap-2 px-4 py-2 bg-pink-500 hover:bg-pink-600 rounded-full font-black text-white transition-all active:scale-95 shadow-lg shadow-pink-200"
> >
<Gamepad2 size={18} /> Jump <Cat size={20} fill="currentColor" />
<span className="text-lg tracking-tighter">KittyURL</span>
</button> </button>
{/* Przycisk Flappy Cat */} {/* PRAWA STRONA: Pozostałe przyciski zgrupowane w divie */}
<button <div className="flex flex-wrap justify-end gap-2 sm:gap-3">
onClick={() => onNavigate('flappy-game')} {/* Przycisk Jump Game */}
className="flex items-center gap-2 px-4 py-2 bg-blue-100 hover:bg-blue-200 rounded-full font-bold text-blue-600 transition-all active:scale-95"
>
<Wind size={18} /> Flappy
</button>
{/* Przycisk History */}
<button
onClick={() => onNavigate('history')}
className="flex items-center gap-2 px-4 py-2 bg-white rounded-full font-bold text-pink-500 border border-pink-100 transition-all active:scale-95"
>
<HistoryIcon size={18} /> History
</button>
{/* Dynamiczny przycisk Logowania / Wylogowania */}
{isAuthenticated ? (
<button <button
onClick={() => { onClick={() => onNavigate('jump-game')}
onLogout(); className="flex items-center gap-2 px-3 py-2 sm:px-4 bg-pink-100 hover:bg-pink-200 rounded-full font-bold text-pink-600 transition-all active:scale-95 text-sm sm:text-base"
onNavigate('home');
}}
className="flex items-center gap-2 px-4 py-2 bg-pink-50 hover:bg-pink-100 rounded-full font-bold text-pink-500 border-2 border-pink-200 transition-all active:scale-95"
> >
<LogOut size={18} /> Logout <Gamepad2 size={18} />
<span className="hidden md:inline">Jump</span>
</button> </button>
) : (
{/* Przycisk Flappy Cat */}
<button <button
onClick={() => onNavigate('login')} onClick={() => onNavigate('flappy-game')}
className="flex items-center gap-2 px-4 py-2 bg-pink-500 hover:bg-pink-600 rounded-full font-bold text-white shadow-lg shadow-pink-200 transition-all active:scale-95" className="flex items-center gap-2 px-3 py-2 sm:px-4 bg-blue-100 hover:bg-blue-200 rounded-full font-bold text-blue-600 transition-all active:scale-95 text-sm sm:text-base"
> >
<LogIn size={18} /> Sign In <Wind size={18} />
<span className="hidden md:inline">Flappy</span>
</button> </button>
)}
{/* Przycisk History */}
<button
onClick={() => onNavigate('history')}
className="flex items-center gap-2 px-3 py-2 sm:px-4 bg-white rounded-full font-bold text-pink-500 border border-pink-100 transition-all active:scale-95 text-sm sm:text-base"
>
<HistoryIcon size={18} />
<span className="hidden md:inline">History</span>
</button>
{/* Logowanie / Wylogowanie */}
{isAuthenticated ? (
<button
onClick={() => {
onLogout();
onNavigate('home');
}}
className="flex items-center gap-2 px-3 py-2 sm:px-4 bg-pink-50 hover:bg-pink-100 rounded-full font-bold text-pink-500 border-2 border-pink-200 transition-all active:scale-95 text-sm sm:text-base"
>
<LogOut size={18} />
<span className="hidden md:inline">Logout</span>
</button>
) : (
<button
onClick={() => onNavigate('login')}
className="flex items-center gap-2 px-4 py-2 bg-pink-500 hover:bg-pink-600 rounded-full font-bold text-white transition-all active:scale-95 text-sm sm:text-base shadow-lg shadow-pink-200"
>
<LogIn size={18} />
<span className="hidden sm:inline">Sign In</span>
</button>
)}
</div>
</nav> </nav>
); );

View File

@@ -1,40 +1,50 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import path from 'path' // Importuj moduł path
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [ // Ustawiamy ścieżkę do folderu, w którym faktycznie znajduje się plik .env
react(), // path.resolve(__dirname, '..') oznacza: "wyjdź jeden poziom wyżej względem tego pliku"
tailwindcss(), const envDirectory = path.resolve(__dirname, '..');
],
server: { // Ładujemy env z określonej lokalizacji
port: 6568, const env = loadEnv(mode, envDirectory, '');
proxy: {
'/api': { const apiTarget = env.VITE_API_TARGET;
target: 'https://ktty.is',
changeOrigin: true, return {
secure: false, plugins: [
// Dodatkowe nagłówki pomagają oszukać zabezpieczenia serwera (np. Cloudflare) react(),
headers: { tailwindcss(),
'Origin': 'https://ktty.is', ],
'Referer': 'https://ktty.is/' server: {
}, port: 6568,
configure: (proxy) => { proxy: {
proxy.on('error', (err) => { '/api': {
console.log('[Proxy Error]:', err.message); target: apiTarget,
}); changeOrigin: true,
proxy.on('proxyReq', (_, req) => { secure: false,
console.log(`[Proxy] Wysyłam do zdalnego serwera: ${req.method} ${req.url}`); headers: {
}); 'Origin': apiTarget,
proxy.on('proxyRes', (proxyRes, req) => { 'Referer': `${apiTarget}/`
console.log(`[Proxy] Odpowiedź z ktty.is: ${proxyRes.statusCode} ${req.url}`); },
}); configure: (proxy) => {
}, proxy.on('error', (err) => {
console.log('[Proxy Error]:', err.message);
});
proxy.on('proxyReq', (_, req) => {
console.log(`[Proxy] Wysyłam do: ${apiTarget}${req.url}`);
});
proxy.on('proxyRes', (proxyRes, req) => {
console.log(`[Proxy] Odpowiedź: ${proxyRes.statusCode} ${req.url}`);
});
},
}
} }
} },
}, preview: {
preview: { port: 6568,
port: 6568, },
}
},
}) })