diff --git a/app/layout.tsx b/app/layout.tsx
index 908bb2f..4d442d8 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -5,26 +5,99 @@ import Navbar from "@/components/ui/Navbar";
import Footer from "@/components/ui/Footer";
import { Toaster } from 'sonner';
import { RibbonsBg } from '@/components/Ribbons';
-import OrphansFixer from "@/components/utils/OrphansFixer"; // Import automatu
+import OrphansFixer from "@/components/utils/OrphansFixer";
+import { GoogleAnalytics } from '@next/third-parties/google';
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const fredoka = Fredoka({ subsets: ["latin"], variable: "--font-fredoka" });
+// --- 🚀 ROZBUDOWANE METADANE SEO 🚀 ---
export const metadata: Metadata = {
- title: "Sklep Urwis | Zabawki, Balony i Artykuły Szkolne Białobrzegi",
- description: "Największy wybór zabawek, gier i artykułów imprezowych w Białobrzegach. Prawdziwy sklep stacjonarny dla dzieci!",
+ metadataBase: new URL('https://www.sklep-urwis.pl'),
+ title: "Sklep z zabawkami Białobrzegi | Urwis - Art. Szkolne i Biurowe",
+ description: "Najlepszy sklep z zabawkami w Białobrzegach! Największy wybór zabawek, gier planszowych, LEGO, balonów z helem i artykułów szkolnych. Zapraszamy na ul. Reymonta 38A.",
+ keywords: [
+ "sklep z zabawkami Białobrzegi",
+ "zabawki Białobrzegi",
+ "balony z helem Białobrzegi",
+ "artykuły szkolne Białobrzegi",
+ "gry planszowe Białobrzegi",
+ "sklep Urwis"
+ ],
+ alternates: {
+ canonical: '/',
+ },
+ openGraph: {
+ title: "Sklep z zabawkami Białobrzegi | Urwis",
+ description: "Największy wybór zabawek, gier planszowych, balonów z helem i artykułów szkolnych w Białobrzegach. Sprawdź naszą ofertę stacjonarnie!",
+ url: 'https://www.sklep-urwis.pl',
+ siteName: 'Sklep Urwis Białobrzegi',
+ locale: 'pl_PL',
+ type: 'website',
+ },
+ robots: {
+ index: true,
+ follow: true,
+ }
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
+
+ // --- 🤖 DANE STRUKTURALNE JSON-LD DLA GOOGLE LOKALNEGO 🤖 ---
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@type": "ToyStore",
+ "name": "Sklep Urwis",
+ "image": "https://www.sklep-urwis.pl/logo.png",
+ "@id": "https://www.sklep-urwis.pl",
+ "url": "https://www.sklep-urwis.pl",
+ "telephone": "+48604208183",
+ "address": {
+ "@type": "PostalAddress",
+ "streetAddress": "ul. Reymonta 38A",
+ "addressLocality": "Białobrzegi",
+ "postalCode": "26-800",
+ "addressCountry": "PL"
+ },
+ "geo": {
+ "@type": "GeoCoordinates",
+ "latitude": 51.644767922296516,
+ "longitude": 20.950334289637663
+ },
+ "openingHoursSpecification": [
+ {
+ "@type": "OpeningHoursSpecification",
+ "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
+ "opens": "08:00",
+ "closes": "18:00"
+ },
+ {
+ "@type": "OpeningHoursSpecification",
+ "dayOfWeek": "Saturday",
+ "opens": "08:00",
+ "closes": "15:00"
+ }
+ ],
+ "sameAs": [
+ "https://facebook.com/sklepurwis.bialobrzegi",
+ "https://instagram.com/sklepurwis.bialobrzegi"
+ ]
+ };
+
return (
+
+ {/* Tu wstrzykujemy kod JSON-LD zdefiniowany wyżej */}
+
+
-
+
{/* AUTOMATYCZNA NAPRAWA SPÓJNIKÓW */}
-
-
{/* WARSTWA 1: SZKŁO (Musi być pod treścią, ale nad tłem) */}
+
{children}
@@ -43,6 +117,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
+
+ {/* 👇 GOOGLE ANALYTICS */}
+
+
);
diff --git a/app/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 0000000..0156538
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1,36 @@
+import Link from 'next/link'
+import Image from 'next/image'
+
+export default function NotFound() {
+ return (
+
+
+
+
+
+
+ 404
+
+
+
+ Ojej! Urwis gdzieś schował tę stronę...
+
+
+
+ Wygląda na to, że link, którego szukasz, nie istnieje.
+
+
+
+ Wróć do bazy
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/oferta/gry/page.tsx b/app/oferta/gry/page.tsx
index 5988927..ab15021 100644
--- a/app/oferta/gry/page.tsx
+++ b/app/oferta/gry/page.tsx
@@ -150,7 +150,7 @@ export default function GryPage() {
Zadzwoń i zapytaj
diff --git a/app/oferta/imprezy/page.tsx b/app/oferta/imprezy/page.tsx
index c943518..af3263d 100644
--- a/app/oferta/imprezy/page.tsx
+++ b/app/oferta/imprezy/page.tsx
@@ -155,7 +155,7 @@ export default function ImprezyPage() {
Zadzwoń i zapytaj
diff --git a/app/oferta/szkola/page.tsx b/app/oferta/szkola/page.tsx
index 16e0c2f..f8b09f3 100644
--- a/app/oferta/szkola/page.tsx
+++ b/app/oferta/szkola/page.tsx
@@ -151,7 +151,7 @@ export default function SzkolaPage() {
Zadzwoń do Sklepu
diff --git a/app/oferta/zabawki/page.tsx b/app/oferta/zabawki/page.tsx
index 91cfc40..714e7f2 100644
--- a/app/oferta/zabawki/page.tsx
+++ b/app/oferta/zabawki/page.tsx
@@ -149,7 +149,7 @@ export default function ZabawkiPage() {
Zadzwoń teraz
diff --git a/app/page.tsx b/app/page.tsx
index c82f22c..754464c 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -28,21 +28,12 @@ export default function StoreFrontPage() {
{/* Sekcja powitalna */}
-
{/* Banner kierujący do nowej domeny akademiaurwisa.pl */}
-
- {/* Sekcja informacyjna o sali zabaw i sklepie */}
-
-
- ŚWIAT PEŁEN PRZYGÓD
-
-
- Zapraszamy do naszej sali zabaw w Białobrzegach. Sprawdź co przygotowaliśmy dla Twojego Urwisa!
-
-
+
+
diff --git a/app/sitemap.ts b/app/sitemap.ts
new file mode 100644
index 0000000..e4790bf
--- /dev/null
+++ b/app/sitemap.ts
@@ -0,0 +1,25 @@
+import { MetadataRoute } from 'next'
+
+export default function sitemap(): MetadataRoute.Sitemap {
+ const baseUrl = 'https://sklep-urwis.pl'
+
+ // Definiujemy główne ścieżki
+ const routes = [
+ '',
+ '/o-nas',
+ '/oferta',
+ '/oferta/zabawki',
+ '/oferta/szkola',
+ '/oferta/imprezy',
+ '/oferta/gry',
+ '/kontakt',
+ '/salazabaw',
+ ]
+
+ return routes.map((route) => ({
+ url: `${baseUrl}${route}`,
+ lastModified: new Date(),
+ changeFrequency: route === '' ? 'weekly' : 'monthly',
+ priority: route === '' ? 1.0 : route.startsWith('/oferta/') ? 0.8 : 0.5,
+ }))
+}
\ No newline at end of file
diff --git a/components/AboutSection.tsx b/components/AboutSection.tsx
index aded1df..f114f6f 100644
--- a/components/AboutSection.tsx
+++ b/components/AboutSection.tsx
@@ -181,7 +181,7 @@ export default function AboutSection() {
,
title: 'Zadzwoń do nas',
- value: '604 208 193',
- link: 'tel:+48604208193',
+ value: '604 208 183',
+ link: 'tel:+48604208183',
glowColor: '59, 130, 246',
gridClass: 'md:col-span-1 md:row-span-1'
},
diff --git a/components/Hero.tsx b/components/Hero.tsx
index 7b069cc..01d18af 100644
--- a/components/Hero.tsx
+++ b/components/Hero.tsx
@@ -48,48 +48,41 @@ export default function HeroSection() {
))}
+ {/* ZMIANA: opacity jest nadal animowane przy scrollowaniu (useTransform),
+ ale bazowy kontener NIE MA 'opacity: 0' na start.
+ */}
- {/* NAGŁÓWEK W JEDNEJ LINII - Poprawka ucinania litery S */}
-
-
- SKLEP
-
- {/* ZMIANA:
- - dodany pr-[0.05em] (padding-right) w jednostce em, aby skalował się z fontem
- - dodany inline-block i overflow-visible
- */}
-
- URWIS
-
-
-
-
- Nie tylko dla grzecznych dzieci
-
-
+ {/* NAGŁÓWEK W JEDNEJ LINII - Poprawka ucinania litery S */}
+
+ {/* 🚀 FIX LCP (Largest Contentful Paint):
+ Zmieniono na czyste
z CSS'em.
+ Dzięki temu tekst renderuje się błyskawicznie bez czekania na JavaScript.
+ Używamy klasy 'animate-fade-in-up' (jeśli masz ją w tailwind.config) lub po prostu pojawia się od razu.
+ */}
+
+ SKLEP
+
+
+ URWIS
+
+
+
+ {/* Usunięto framer-motion z tego tekstu dla LCP */}
+
+ Nie tylko dla grzecznych dzieci
+
+
- {/* NOWY, TRAFNY OPIS - Z FIXEM NA SPÓJNIKI */}
-
- Największy wybór gier i{"\u00A0"}zabawek, akcesoriów imprezowych,
- art. szkolnych i{"\u00A0"}biurowych przy{"\u00A0"}ul. Reymonta 38A.
-
- Prawdziwy sklep stacjonarny, w{"\u00A0"}którym rządzisz Ty i{"\u00A0"}Twoja wyobraźnia!
-
+ {/* NOWY, TRAFNY OPIS - Z FIXEM NA SPÓJNIKI
+ Usunięto framer-motion, bo ten element LCP zajmował w Lighthouse 26 sekund!
+ */}
+
+ Największy wybór gier i{"\u00A0"}zabawek, akcesoriów imprezowych,
+ art. szkolnych i{"\u00A0"}biurowych przy{"\u00A0"}ul. Reymonta 38A.
+
+ Prawdziwy sklep stacjonarny, w{"\u00A0"}którym rządzisz Ty i{"\u00A0"}Twoja wyobraźnia!
+
{/* PRZYCISKI CTA */}
@@ -146,7 +139,10 @@ export default function HeroSection() {
-
+ {/* WAŻNE DLA WYDAJNOŚCI: iframe też bywa ciężki, ale jest w modalu, więc ładuje się po kliknięciu. */}
+ {isMapOpen && (
+
+ )}
diff --git a/components/Intro.tsx b/components/Intro.tsx
index a5200b2..ab9827d 100644
--- a/components/Intro.tsx
+++ b/components/Intro.tsx
@@ -6,11 +6,29 @@ import LoadingScreen from '@/components/LoadingScreen';
export default function UrwisIntro({ children }: { children: React.ReactNode }) {
const [shouldShowIntro, setShouldShowIntro] = useState(false);
const [loadingComplete, setLoadingComplete] = useState(false);
- // Zredukowane stany: tylko loading -> video -> done
const [step, setStep] = useState<'loading' | 'video' | 'done'>('loading');
const videoRef = useRef(null);
- // Obsługa blokowania scrolla podczas intro
+ // 1. SILNIEJSZA OBSŁUGA AUTOPLAY NA MOBILE
+ useEffect(() => {
+ if (step === 'video' && videoRef.current) {
+ const playVideo = async () => {
+ try {
+ // Force'ujemy wyciszenie (warunek konieczny dla autoplay na mobile)
+ videoRef.current!.muted = true;
+ // Próbujemy odpalić wideo
+ await videoRef.current!.play();
+ } catch (error) {
+ console.error("Autoplay failed, user interaction might be needed or power save mode is on:", error);
+ // Opcjonalnie: jeśli wideo nie ruszy w ciągu 2 sekund, skipujemy intro
+ // żeby użytkownik nie patrzył na czarny ekran
+ // setTimeout(finishIntro, 2000);
+ }
+ };
+ playVideo();
+ }
+ }, [step]);
+
useEffect(() => {
if (shouldShowIntro && step !== 'done') {
document.body.style.overflow = 'hidden';
@@ -19,14 +37,12 @@ export default function UrwisIntro({ children }: { children: React.ReactNode })
document.body.style.overflow = 'unset';
document.body.style.height = 'unset';
}
- // Cleanup przy odmontowaniu
return () => {
document.body.style.overflow = 'unset';
document.body.style.height = 'unset';
};
}, [shouldShowIntro, step]);
- // Sprawdzenie sesji (czy intro już było grane)
useEffect(() => {
const introShown = sessionStorage.getItem('urwis_intro_shown');
if (!introShown) {
@@ -37,7 +53,6 @@ export default function UrwisIntro({ children }: { children: React.ReactNode })
}
}, []);
- // Funkcja kończąca intro i zapisująca stan
const finishIntro = () => {
setStep('done');
sessionStorage.setItem('urwis_intro_shown', 'true');
@@ -53,13 +68,11 @@ export default function UrwisIntro({ children }: { children: React.ReactNode })
finishIntro();
};
- // Jeśli intro nie ma być grane, zwracamy od razu dzieci (stronę)
if (!shouldShowIntro) return <>{children}>;
return (
<>
- {/* KROK 1: Ładowanie zasobów */}
{step === 'loading' && (
{
setLoadingComplete(true);
@@ -67,13 +80,11 @@ export default function UrwisIntro({ children }: { children: React.ReactNode })
}} />
)}
- {/* KROK 2: Wideo na pełen ekran */}
{step === 'video' && (
- {/* Przycisk Pomiń */}
Pomiń animację
@@ -109,17 +121,13 @@ export default function UrwisIntro({ children }: { children: React.ReactNode })
)}
- {/* KROK 3: Główna zawartość (Hero.tsx i reszta) */}
diff --git a/components/ModelViewer.tsx b/components/ModelViewer.tsx
index ef52147..739b65c 100644
--- a/components/ModelViewer.tsx
+++ b/components/ModelViewer.tsx
@@ -1,10 +1,13 @@
+'use client';
+
import { FC, Suspense, useRef, useLayoutEffect, useEffect, useMemo, useState } from 'react';
-import { Canvas, useFrame, useLoader, useThree, invalidate } from '@react-three/fiber';
-import { OrbitControls, useGLTF, useFBX, useProgress, Html, Environment, ContactShadows } from '@react-three/drei';
+import { Canvas, useFrame, useThree, invalidate } from '@react-three/fiber';
+import { OrbitControls, useGLTF, useProgress, Html, Environment, ContactShadows } from '@react-three/drei';
import * as THREE from 'three';
-const isTouch = typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0);
+const isTouchDevice = () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0);
+// --- TYPY ---
export interface ViewerProps {
url: string;
width?: number | string;
@@ -34,9 +37,9 @@ export interface ViewerProps {
onModelLoaded?: () => void;
}
-// Check if device supports touch input - evaluated at runtime only inside hooks
+// --- STAŁE FIZYKI RUCHU ---
const deg2rad = (d: number) => (d * Math.PI) / 180;
-const DECIDE = 8; // px before we decide horizontal vs vertical
+const DECIDE = 8;
const ROTATE_SPEED = 0.005;
const INERTIA = 0.925;
const PARALLAX_MAG = 0.05;
@@ -44,15 +47,23 @@ const PARALLAX_EASE = 0.12;
const HOVER_MAG = deg2rad(6);
const HOVER_EASE = 0.15;
-const Loader: FC<{ placeholderSrc?: string }> = ({ placeholderSrc }) => {
+// --- KOMPONENTY POMOCNICZE ---
+
+const LoadingOverlay: FC<{ placeholderSrc?: string }> = ({ placeholderSrc }) => {
const { progress, active } = useProgress();
- if (!active && placeholderSrc) return null;
+
+ // 🟢 NAPRAWA BŁĘDU:
+ // Jeśli ładowanie nie jest aktywne (!active) LUB postęp wynosi 100%, po prostu zniknij.
+ if (!active || progress === 100) return null;
+
return (
{placeholderSrc ? (
-
+
) : (
- `${Math.round(progress)} %`
+