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.
DEBUG=false
# 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>
<p className="mt-4 text-slate-400 text-sm font-bold uppercase tracking-widest">
Physics: {isPlaying ? "Frame-Independent" : "Ready"}
</p>
</div>
);
};

View File

@@ -8,41 +8,52 @@ interface GeneratorProps {
}
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">
<header className="text-center mb-12">
<h1 className="text-7xl font-black text-pink-500 mb-2 tracking-tighter flex items-center gap-4">
KittyURL <PawPrint size={50} fill="currentColor" />
<div className="max-w-[800px] mx-auto pt-10 sm:pt-16 px-4 flex flex-col items-center">
{/* Header - Skalowanie tekstu i ikony */}
<header className="text-center mb-8 sm:mb-12">
<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>
<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>
<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">
<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">
<input
type="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}
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>
<button
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>
</div>
<div className="w-full mt-16 text-center">
<div className="bg-pink-100/50 rounded-[2.5rem] border-4 border-dashed border-pink-200 p-12">
<Cat size={60} className="mx-auto text-pink-200 mb-4" />
<p className="text-pink-300 font-bold">No links generated yet. Feed me a URL! 🐾</p>
{/* Sekcja "No links yet" - Skalowanie paddingu i ikony */}
<div className="w-full mt-10 sm:mt-16 text-center">
<div className="bg-pink-100/50 rounded-[2rem] sm:rounded-[2.5rem] border-4 border-dashed border-pink-200 p-8 sm:p-12">
<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>

View File

@@ -1,15 +1,23 @@
import { PawPrint, ExternalLink } from 'lucide-react';
import { PawPrint, ExternalLink } from 'lucide-react';
export const HistoryView = ({ onBack }: { onBack: () => void }) => (
<div className="max-w-4xl mx-auto p-6 pt-10">
<button onClick={onBack} className="mb-8 flex items-center gap-2 text-pink-500 font-bold hover:scale-105 transition-transform">
<PawPrint size={20} /> Back to KittyURL
<div className="max-w-4xl mx-auto px-4 py-6 sm:p-6 sm:pt-10">
{/* Przycisk powrotu - mniejszy margines na mobile */}
<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>
<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">
<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">
<tr>
<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>
<tbody className="divide-y divide-pink-50">
<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 font-bold text-pink-500 flex items-center gap-2">kitty.url/meow <ExternalLink size={14} /></td>
<td className="px-6 py-4 text-slate-400 truncate max-w-[300px]">
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>
</tbody>
</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>

View File

@@ -243,10 +243,7 @@ export const KittyGame: React.FC<{ onBack: () => void }> = ({ onBack }) => {
</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>
);
};

View File

@@ -1,5 +1,4 @@
import { History as HistoryIcon, LogIn, LogOut, Gamepad2, Wind } from 'lucide-react';
// Używamy 'import type' dla zadowolenia verbatimModuleSyntax
import { History as HistoryIcon, LogIn, LogOut, Gamepad2, Wind, Cat } from 'lucide-react';
import type { View } from '../App';
interface NavbarProps {
@@ -9,49 +8,66 @@ interface NavbarProps {
}
export const Navbar = ({ onNavigate, isAuthenticated, onLogout }: NavbarProps) => (
<nav className="max-w-5xl mx-auto py-6 px-6 flex justify-end gap-3">
{/* Przycisk Jump Game */}
<nav className="max-w-5xl mx-auto py-4 px-4 flex items-center justify-between gap-4">
{/* LEWA STRONA: Przycisk Home w stylu Kitty */}
<button
onClick={() => onNavigate('jump-game')}
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"
onClick={() => onNavigate('home')}
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>
{/* Przycisk Flappy Cat */}
<button
onClick={() => onNavigate('flappy-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 ? (
{/* PRAWA STRONA: Pozostałe przyciski zgrupowane w divie */}
<div className="flex flex-wrap justify-end gap-2 sm:gap-3">
{/* Przycisk Jump Game */}
<button
onClick={() => {
onLogout();
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"
onClick={() => onNavigate('jump-game')}
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"
>
<LogOut size={18} /> Logout
<Gamepad2 size={18} />
<span className="hidden md:inline">Jump</span>
</button>
) : (
{/* Przycisk Flappy Cat */}
<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 shadow-lg shadow-pink-200 transition-all active:scale-95"
onClick={() => onNavigate('flappy-game')}
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>
)}
{/* 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>
);

View File

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