Compare commits

...

2 commits

Author SHA1 Message Date
Dariusz Niemczyk 220d7e0733
Cebula. 2025-02-08 16:08:44 +01:00
Dariusz Niemczyk e65adb54a2
feat: update css 2025-02-08 15:20:47 +01:00
62 changed files with 1246 additions and 731 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
public/videos/** filter=lfs diff=lfs merge=lfs -text
public/*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.ogv filter=lfs diff=lfs merge=lfs -text
*.webm filter=lfs diff=lfs merge=lfs -text

49
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,49 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug client-side (Firefox)",
"type": "firefox",
"request": "launch",
"url": "http://localhost:3000",
"reAttach": true,
"pathMappings": [
{
"url": "webpack://_N_E",
"path": "${workspaceFolder}"
}
]
},
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/next",
"runtimeArgs": [
"--inspect"
],
"skipFiles": [
"<node_internals>/**"
],
"serverReadyAction": {
"action": "debugWithEdge",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
}
}
]
}

1058
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@
"@lingui/loader": "^5.1.2",
"@lingui/swc-plugin": "^5.0.2",
"@lingui/vite-plugin": "^5.1.2",
"@tailwindcss/postcss": "^4.0.0",
"@types/negotiator": "^0.6.3",
"@types/node": "^20",
"@types/react": "^19",
@ -43,7 +44,7 @@
"eslint": "^9",
"eslint-config-next": "15.1.6",
"postcss": "^8",
"tailwindcss": "^3.4.17",
"tailwindcss": "^4.0.0",
"typescript": "^5"
}
}

View file

@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
'@tailwindcss/postcss': {},
},
};

Binary file not shown.

View file

@ -1,6 +1,6 @@
{
"name": "MyWebSite",
"short_name": "MySite",
"name": "CebulaCamp",
"short_name": "Cebula",
"icons": [
{
"src": "/web-app-manifest-192x192.png",

BIN
public/videos/ceboola_gradient-white_full.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_full.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_full.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_hd.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_hd.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_hd.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_mobile.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_mobile.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_mobile.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_tablet.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_tablet.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_tablet.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_twok.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_twok.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_twok.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_uhd.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_uhd.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient-white_uhd.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_full.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_full.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_full.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_hd.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_hd.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_hd.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_mobile.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_mobile.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_mobile.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_tablet.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_tablet.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_tablet.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_twok.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_twok.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_twok.webm (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_uhd.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_uhd.ogv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/videos/ceboola_gradient_uhd.webm (Stored with Git LFS) Normal file

Binary file not shown.

90
scripts/reencode-videos.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/bash
# Define directories
SOURCE_DIR="./source-videos"
DEST_DIR="./public/videos"
# Define sizes and names
SIZES=(480 720 1080 1440 2160)
NAMES=("mobile" "tablet" "hd" "twok" "uhd")
# Create destination directory if it doesn't exist
mkdir -p "$DEST_DIR"
for video in "$SOURCE_DIR"/*.mp4; do
if [ -f "$video" ]; then
filename=$(basename "$video" .mp4)
echo "Processing: $filename"
# Get video dimensions
width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$video")
height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$video")
# Process each size
for i in "${!SIZES[@]}"; do
size="${SIZES[$i]}"
name="${NAMES[$i]}"
echo "Debug: Processing $name with height=$size"
if [ "$size" -le "$height" ]; then
echo "Processing size $name (${size}p)"
# MP4
ffmpeg -n -i "$video" \
-c:v libx264 \
-vf "scale=-1:${size}" \
-preset slow \
-crf 23 \
-an \
"${DEST_DIR}/${filename}_${name}.mp4"
# WebM
ffmpeg -n -i "$video" \
-c:v libvpx-vp9 \
-deadline good \
-cpu-used 2 \
-row-mt 1 \
-threads 8 \
-vf "scale=-1:${size}" \
-quality good \
-an \
-crf 20 \
"${DEST_DIR}/${filename}_${name}.webm"
# Ogg
ffmpeg -n -i "$video" \
-c:v libtheora \
-q:v 5 \
-vf "scale=-1:${size}" \
-an \
"${DEST_DIR}/${filename}_${name}.ogv"
fi
done
# Create original resolution version
echo "Creating original resolution version"
cp "$video" "${DEST_DIR}/${filename}_full.mp4"
ffmpeg -n -i "$video" \
-c:v libvpx-vp9 \
-deadline good \
-cpu-used 2 \
-row-mt 1 \
-threads 8 \
-quality good \
-an \
"${DEST_DIR}/${filename}_full.webm"
ffmpeg -n -i "$video" \
-c:v libtheora \
-q:v 5 \
-an \
"${DEST_DIR}/${filename}_full.ogv"
echo "Completed processing: $filename"
echo "----------------------------"
fi
done
echo "All videos have been processed."

BIN
source-videos/ceboola_gradient-white.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
source-videos/ceboola_gradient.mp4 (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,37 @@
'use client'
import { useEffect } from "react"
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div className="flex h-full w-full items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold tracking-tight text-black sm:text-8xl">
{error.name}
</h1>
<p className="mt-4 text-lg text-gray-500">{error.message}</p>
<div className="mt-6">
<button
type="button"
className="inline-flex items-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-base font-medium text-white shadow-xs hover:bg-red-700 focus:outline-hidden focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:text-sm"
onClick={reset}
>
Try again
</button>
</div>
</div>
</div>
)
}

View file

@ -2,10 +2,12 @@ import "../../globals.css";
import type React from "react";
import { Lang, locales } from "@/i18n/locales";
import { getLocale, Lang } from "@/i18n/locales";
import Head from 'next/head';
import { ThemeProvider } from "@/components/providers";
import { Oxanium } from "next/font/google";
import { headers } from "next/headers";
const oxanium = Oxanium({ subsets: ["latin-ext"] })
@ -16,20 +18,34 @@ export default async function RootLayout({
children: React.ReactNode
params: Promise<{ locale: Lang }>
}) {
const { locale } = await params
const currentLang = locales.includes(locale) ? locale : "en"
const [{ locale }, head] = await Promise.all([
params,
headers(),
]);
const preferedTheme = head.get("Sec-CH-Prefers-Color-Scheme")?.toLowerCase();
const supportedThemes = ["dark", "light"];
const isDarkOrLight =
preferedTheme && supportedThemes.includes(preferedTheme);
const currentLang = getLocale(locale);
const defaultTheme = isDarkOrLight ? preferedTheme : "dark";
return (
<html lang={currentLang} className={`${oxanium.className} dark`}>
<html lang={currentLang} className={`${oxanium.className} ${defaultTheme}`}>
<Head>
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="MyWebSite" />
<meta name="apple-mobile-web-app-title" content="CebulaCamp" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
<body className="bg-background text:foreground antialiased">
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)

View file

@ -0,0 +1,4 @@
export default function Loading() {
return <div>Loading...</div>
}

View file

@ -0,0 +1,5 @@
export default function NotFound() {
return <div>Not Found</div>
}

View file

@ -1,130 +1,17 @@
"use client"
import { Nav } from "@/components/nav"
import { translations } from "@/i18n/translations"
import { useEffect, useRef } from "react"
import LandingPage from "@/components/landing-page";
import { getLocale, Lang } from "@/i18n/locales";
import { translations } from "@/i18n/translations";
export default function Home() {
const videoRef = useRef<HTMLVideoElement>(null)
const t = translations.pl // For now using Polish, could be made dynamic
export default async function Home(
{ params }
:
{ params: Promise<{ locale: Lang }> }
) {
const { locale } = await params
const currentLocale = getLocale(locale)
const t = translations[currentLocale];
useEffect(() => {
const handleScroll = () => {
if (videoRef.current) {
const scrolled = window.scrollY;
videoRef.current.style.willChange = "transform";
videoRef.current.style.transform = `translateY(${scrolled * 0.5}px)`;
videoRef.current.style.willChange = "unset";
}
};
const throttledHandleScroll = () => {
requestAnimationFrame(handleScroll);
};
window.addEventListener('scroll', throttledHandleScroll);
return () => window.removeEventListener('scroll', throttledHandleScroll);
}, []);
return (
<div>
<Nav t={t} />
<main className="flex flex-col min-h-screen">
<section className="h-screen relative overflow-hidden dark:bg-black">
<div className="absolute inset-0 bg-black opacity-80">
<video ref={videoRef} autoPlay muted loop playsInline className="w-full h-full object-cover parallax-video ">
<source src="/cebula.mp4" type="video/mp4" />
</video>
</div>
<div className="relative z-10 container mx-auto px-4 h-full flex items-center justify-center">
<div className="text-center font-[JGS7]">
<h1 className="text-5xl sm:text-6xl md:text-8xl font-bold tracking-tighter mb-6 light:text-background">{t.hero.title}</h1>
<p className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl-text:7xl 2xl:text-8xl text-primary">{t.hero.subtitle}</p>
</div>
</div>
</section>
<section id="about" className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.about.title}</h2>
<div className="text-lg text-muted-foreground max-w-3xl mx-auto whitespace-pre-line">
{t.about.description}
</div>
</div>
</section>
<section id="where" className="py-24 bg-background/90">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.where.title}</h2>
<div className="text-lg text-muted-foreground">
<p>{t.where.location}</p>
</div>
</div>
</section>
<section id="when" className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.when.title}</h2>
<div className="text-lg text-muted-foreground">
<p className="text-primary text-3xl font-[JGS7]">{t.when.date}</p>
<p className="mt-4">{t.when.extra}</p>
</div>
</div>
</section>
<section id="tickets" className="py-24 bg-background/90">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.tickets.title}</h2>
<div className="text-lg text-muted-foreground">
<p>{t.tickets.status}</p>
</div>
</div>
</section>
<section id="accommodation" className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.accommodation.title}</h2>
<div className="text-lg text-muted-foreground max-w-3xl mx-auto">
<p>{t.accommodation.description}</p>
</div>
</div>
</section>
<section id="food" className="py-24 bg-background/90">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.food.title}</h2>
<div className="text-lg text-muted-foreground max-w-3xl mx-auto">
<p>{t.food.description}</p>
</div>
</div>
</section>
<section id="contact" className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.contact.title}</h2>
<div className="text-lg text-muted-foreground">
<a href={`mailto:${t.contact.email}`} className="text-primary hover:underline">
{t.contact.email}
</a>
</div>
</div>
</section>
<section id="credits" className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter">{t.credits.title}</h2>
<div className="text-lg text-muted-foreground">
<p>{t.credits.usedFonts}</p>
<p><a className="hover:underline" href="https://velvetyne.fr/fonts/jgs-font/">{t.credits.jgs7}</a> {` ${t.credits.jgs7RemovedGlyphs}`}</p>
<p><a className="hover:underline" href="https://fonts.google.com/specimen/Oxanium">{t.credits.oxanium}</a></p>
</div>
</div>
</section>
</main>
</div>
)
return <LandingPage t={t} />
}

View file

@ -0,0 +1,173 @@
"use client"
import { Nav } from "@/components/nav";
import { Translations } from "@/i18n/translations";
import { cn } from "@/lib/utils";
import { ReactElement, useEffect, useRef } from "react";
import { useTheme } from "./providers";
function Section({
id,
title,
paragraphs
}: {
id: string
title: string;
paragraphs: ReactElement;
}) {
return (<section id={id} className="py-24 bg-background">
<div className="container mx-auto px-4">
<h2 className="text-4xl font-bold mb-8 tracking-tighter max-w-3xl mx-auto">{title}</h2>
<div className="text-lg text-muted-foreground max-w-3xl mx-auto whitespace-pre-line">
{paragraphs}
</div>
</div>
</section>)
}
function getSource({
src,
type
}: {
src: string;
type: 'mp4' | 'webm' | 'ogv'
}) {
const sourceType = `video/${type}`
return [
<source
key={`mobile-${type}`}
media="(max-width: 640px)"
src={src.replace('.mp4', `_mobile.${type}`)}
type={sourceType}
/>,
<source
key={`tablet-${type}`}
media="(max-width: 1024px)"
src={src.replace('.mp4', `_tablet.${type}`)}
type={sourceType}
/>,
<source
key={`hd-${type}`}
media="(max-width: 1920px)"
src={src.replace('.mp4', `_hd.${type}`)}
type={sourceType}
/>,
<source
key={`twok-${type}`}
media="(max-width: 2560px)"
src={src.replace('.mp4', `_2k.${type}`)}
type={sourceType}
/>,
<source
key={`uhd-${type}`}
media="(max-width: 3840px)"
src={src.replace('.mp4', `_uhd.${type}`)}
type={sourceType}
/>,
<source
key={`original-${type}`}
media="(min-width: 3841px)"
src={src.replace('.mp4', `_original.${type}`)}
type={sourceType}
/>,
];
}
function Video({ sourceBase, hidden }: {
sourceBase: string;
hidden: boolean;
}) {
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
const handleScroll = () => {
if (!videoRef.current || hidden) return;
videoRef.current.play();
const scrolled = window.scrollY;
videoRef.current.style.willChange = "transform";
videoRef.current.style.transform = `translateY(${scrolled * 0.5}px)`;
videoRef.current.style.willChange = "unset";
};
const throttledHandleScroll = () => {
requestAnimationFrame(handleScroll);
};
window.addEventListener("scroll", throttledHandleScroll);
handleScroll();
return () => window.removeEventListener("scroll", throttledHandleScroll);
}, [hidden]);
const sources = [...getSource({ src: sourceBase, type: 'mp4' }), ...getSource({ src: sourceBase, type: 'ogv' }), ...getSource({ src: sourceBase, type: 'webm' })]
return (
<video
ref={videoRef}
preload="auto"
autoPlay
muted
loop
playsInline
webkit-playsinline="true"
x5-playsinline="true"
className={cn("w-full h-full object-cover parallax-video", {
hidden,
})}
>
{sources.map(x => x)}
</video>
);
}
export default function LandingPage(
{ t }: { t: Translations }
) {
const { theme } = useTheme()
return (
<div>
<Nav t={t} />
<main className="flex flex-col min-h-screen">
<section id="hero" className="h-screen relative overflow-hidden dark:bg-black light:bg-white ">
<div className="absolute inset-0 opacity-80">
<Video sourceBase="/videos/ceboola_gradient.mp4" hidden={theme === 'light'} />
<Video sourceBase="/videos/ceboola_gradient-white.mp4" hidden={theme === "dark"} />
</div>
<div className="relative z-10 container mx-auto px-4 h-full flex items-center justify-center">
<div className="text-center font-[JGS7]">
<h1 className="text-5xl sm:text-6xl md:text-8xl font-bold tracking-tighter light:text-background">{t.hero.title}</h1>
<p className="mt-2 text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl-text:7xl 2xl:text-8xl text-primary">{t.hero.subtitle}</p>
<p className="mt-2 text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl-text:7xl 2xl:text-8xl text-primary ">{t.when.date}</p>
</div>
</div>
</section>
<Section id="about" title={t.about.title} paragraphs={<p>{t.about.description}</p>} />
<Section id="where" title={t.where.title} paragraphs={<p>{t.where.location}</p>} />
<Section id="when" title={t.where.title} paragraphs={<>
<p className="text-primary text-3xl font-[JGS7]">{t.when.date}</p>
<p className="mt-4">{t.when.extra}</p></>}
/>
<Section id="tickets" title={t.tickets.title} paragraphs={<p>{t.tickets.status}</p>} />
<Section id="accommodation" title={t.accommodation.title} paragraphs={<p>{t.accommodation.description}</p>} />
<Section id="food" title={t.food.title} paragraphs={<p>{t.food.description}</p>} />
<Section id="contact" title={t.contact.title} paragraphs={<p>{t.contact.email}</p>} />
<Section id="credits" title={t.credits.title} paragraphs={<>
<p>{t.credits.usedFonts}</p>
<p><a className="hover:underline" href="https://velvetyne.fr/fonts/jgs-font/">{t.credits.jgs7}</a></p>
<p><a className="hover:underline" href="https://fonts.google.com/specimen/Oxanium">{t.credits.oxanium}</a></p>
</>}
/>
</main>
</div>
)
}

View file

@ -3,10 +3,12 @@ import { Button } from "@/components/ui/button"
import { Sections, type translations } from "@/i18n/translations"
import { cn } from "@/lib/utils"
import { MoonIcon, SunIcon } from "lucide-react"
import { useCallback, useEffect, useState } from "react"
import { useEffect, useState } from "react"
import { MobileNav } from "./mobile-nav"
import { useTheme } from "./providers"
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
"hero",
"about",
"where",
"when",
@ -16,41 +18,12 @@ const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
"contact",
]
function useTheme(): [theme: "light" | "dark", setTheme: (theme: "light" | "dark") => void] {
/* eslint-disable react-hooks/rules-of-hooks */
if (typeof window === "undefined") return ["dark", () => { }]
const [theme, setTheme] = useState<"light" | "dark">("dark")
const root = window.document.documentElement
const changeTheme = useCallback((theme: "light" | "dark") => {
root.classList.remove("light", "dark")
root.classList.add(theme)
}, [root])
useEffect(() => {
// Determine the user's preferred color scheme
const preferredTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
setTheme(preferredTheme)
changeTheme(preferredTheme)
}, [changeTheme])
const updateTheme = useCallback((theme: "light" | "dark") => {
setTheme(theme)
changeTheme(theme)
}, [changeTheme, setTheme])
return [theme, updateTheme]
/* eslint-enable react-hooks/rules-of-hooks */
}
export function Nav({
t,
}: {
t: typeof translations.pl
}) {
const [theme, setTheme] = useTheme()
const { theme, setTheme } = useTheme()
const [activeSection, setActiveSection] = useState<Sections>("about")
@ -90,7 +63,7 @@ export function Nav({
}, []);
return (
<nav className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-sm border-b">
<nav className="fixed top-0 left-0 right-0 z-50 backdrop-blur-sm bg-background/80 border-b">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between h-16">
<a href="#" className="text-xl font-bold tracking-tighter hover:text-primary transition-colors">

View file

@ -0,0 +1,43 @@
'use client';
import React, { createContext, useCallback, useContext, useState } from "react";
type Theme = 'dark' | 'light';
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<Theme>("dark")
const changeTheme = useCallback((theme: Theme) => {
const root = window.document.documentElement
root.classList.remove("light", "dark")
root.classList.add(theme)
}, [])
const updateTheme = useCallback((theme: Theme) => {
setTheme(theme)
changeTheme(theme)
}, [changeTheme, setTheme])
return (
<ThemeContext.Provider value={{ theme, setTheme: updateTheme }}>
{children}
</ThemeContext.Provider>
);
};
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}

View file

@ -5,18 +5,18 @@ import * as React from "react"
import { cn } from '../../lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
"bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},

View file

@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@ -64,7 +64,7 @@ const SheetContent = React.forwardRef<
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>

View file

@ -1,7 +1,70 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *));
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@layer utilities {
@font-face {
font-family: "JGS7";
src: url("/fonts/jgs7.woff2") format("woff2"),
@ -16,8 +79,7 @@ html {
scroll-behavior: smooth;
}
}
@custom-variant dark (&:where(.dark, .dark *));
}
@layer base {
:root {

View file

@ -2,3 +2,9 @@
export const locales = ["en", "pl"] as const;
export type Lang = (typeof locales)[number];
export function getLocale(lang: Lang): Lang {
return locales.includes(lang as Lang) ? lang : "en";
}
export const defaultLocale = locales[0];

View file

@ -1,11 +1,12 @@
const common = {
jgs7: "JGS7",
jgs7: "JGS font Jgs font by Adel Faure. Distributed by velvetyne.fr",
oxanium: "Oxanium",
};
const pl = {
siteTitle: "CEBULACAMP",
nav: {
hero: "Cebula",
about: "O nas",
when: "Kiedy",
where: "Gdzie",
@ -35,7 +36,7 @@ const pl = {
title: "Kiedy",
date: "28-31.08.2025",
extra:
"chętnych do pomocy w przygotowaniach zapraszamy już na *Day 0* 27 sierpnia",
"chętnych do pomocy w przygotowaniach zapraszamy już na Day 0, 27 sierpnia",
},
tickets: {
title: "Bilety",
@ -60,7 +61,6 @@ const pl = {
usedFonts: "Użyte fonty:",
oxanium: common.oxanium,
jgs7: common.jgs7,
jgs7RemovedGlyphs: "z usuniętymi znakami",
},
};
@ -75,4 +75,6 @@ export const translations: {
en: en,
};
export type Translations = typeof pl;
export type Sections = keyof (typeof pl)["nav"];

View file

@ -27,5 +27,7 @@ export function middleware(request: NextRequest) {
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
matcher: "/((?!api|static|.*\\..*|_next).*)",
// matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

View file

@ -1,62 +0,0 @@
import type { Config } from "tailwindcss";
export default {
darkMode: ["class"],
content: [
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
},
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
},
border: "var(--border)",
input: "var(--input)",
ring: "var(--ring)",
chart: {
"1": "var(--chart-1)",
"2": "var(--chart-2)",
"3": "var(--chart-3)",
"4": "var(--chart-4)",
"5": "var(--chart-5)",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
// eslint-disable-next-line @typescript-eslint/no-require-imports
plugins: [require("tailwindcss-animate")],
} satisfies Config;

View file

@ -22,6 +22,6 @@
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "tailwind.config.ts"],
"exclude": ["node_modules"]
}