Compare commits

...

3 commits

Author SHA1 Message Date
Dariusz Niemczyk c1ead36042
fix: scrollspy offset
Some checks failed
/ deploy (push) Failing after 54s
2025-04-13 22:02:21 +02:00
Dariusz Niemczyk 65a8ea8ab8 fix: properly fix colors-sections
Some checks failed
/ deploy (push) Failing after 56s
2025-04-13 19:41:36 +00:00
q3k 29c1252633 landing: do not mention how many tickets there are per space
Some checks failed
/ deploy (push) Failing after 54s
This seems like an internal detail, and is probably likely to change
(ie. we will issue more vouchers as they legitimately run out and if it
won't starve other spaces).
2025-04-13 19:46:57 +02:00
7 changed files with 54 additions and 100 deletions

17
package-lock.json generated
View file

@ -36,6 +36,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-leaflet": "^5.0.0-rc.2", "react-leaflet": "^5.0.0-rc.2",
"react-scrollspy-navigation": "^2.0.6",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1" "zod": "^3.24.1"
@ -9808,6 +9809,22 @@
} }
} }
}, },
"node_modules/react-scrollspy-navigation": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/react-scrollspy-navigation/-/react-scrollspy-navigation-2.0.6.tgz",
"integrity": "sha512-BBnIEI9BsCPIMnp3z/OIEJ6mRSlDgGYf8wty5IjR8nOIhIeRhQp6eDUAKFMyHRoM2RUInA2NDu5DutIFTphoKQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/toviszsolt"
},
{
"type": "paypal",
"url": "https://www.paypal.com/paypalme/toviszsolt"
}
],
"license": "MIT"
},
"node_modules/react-style-singleton": { "node_modules/react-style-singleton": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",

View file

@ -37,6 +37,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-leaflet": "^5.0.0-rc.2", "react-leaflet": "^5.0.0-rc.2",
"react-scrollspy-navigation": "^2.0.6",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1" "zod": "^3.24.1"

View file

@ -2,10 +2,9 @@
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
import { useColorSections } from "@/hooks/color-sections"
import type { Sections, translations } from "@/i18n/translations" import type { Sections, translations } from "@/i18n/translations"
import { Menu } from "lucide-react" import { Menu } from "lucide-react"
import { useRef } from "react" import ScrollSpy from "react-scrollspy-navigation"
import { LanguageSelector } from "./ui/language-selector" import { LanguageSelector } from "./ui/language-selector"
function NavContent({ function NavContent({
@ -15,15 +14,16 @@ function NavContent({
t: typeof translations.pl, t: typeof translations.pl,
linksOrder: Array<Sections> linksOrder: Array<Sections>
}) { }) {
const parent = useRef<HTMLDivElement>(null);
useColorSections(parent);
return ( return (
<nav className="flex flex-col gap-4 mt-8" ref={parent}> <nav className="flex flex-col gap-4 mt-8">
<ScrollSpy activeClass="nav-active" offsetTop={80}>
{linksOrder.map((value) => ( {linksOrder.map((value) => (
<a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors"> <a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors">
{t.nav[value]} {t.nav[value]}
</a> </a>
))} ))}
</ScrollSpy>
<LanguageSelector /> <LanguageSelector />
</nav> </nav>
) )

View file

@ -1,11 +1,11 @@
"use client" "use client"
import { useColorSections } from "@/hooks/color-sections"
import { type translations } from "@/i18n/translations" import { type translations } from "@/i18n/translations"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useRef } from "react" import ScrollSpy from 'react-scrollspy-navigation'
import { MobileNav } from "./mobile-nav" import { MobileNav } from "./mobile-nav"
import { NavContainer } from "./nav-container" import { NavContainer } from "./nav-container"
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [ const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
'about', 'about',
'tickets', 'tickets',
@ -19,13 +19,12 @@ export function MainpageNav({
}: { }: {
t: typeof translations.pl t: typeof translations.pl
}) { }) {
const parent = useRef<HTMLDivElement>(null);
useColorSections(parent);
return ( return (
<NavContainer title={t.nav.title}> <NavContainer title={t.nav.title}>
<> <>
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}> <div className="hidden md:flex md:items-center md:gap-4 lg:gap-8">
<ScrollSpy activeClass="nav-active" offsetTop={80}>
{linksOrder.map((value) => ( {linksOrder.map((value) => (
<a <a
@ -38,6 +37,7 @@ export function MainpageNav({
})} /> })} />
</a> </a>
))} ))}
</ScrollSpy>
</div> </div>
<div className="md:hidden ml-2"> <div className="md:hidden ml-2">
<MobileNav t={t} linksOrder={linksOrder} /> <MobileNav t={t} linksOrder={linksOrder} />

View file

@ -159,3 +159,11 @@
section { section {
scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4); scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4);
} }
.nav-active {
color: var(--color-primary) !important;
& span {
--tw-scale-x: 100%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
}

View file

@ -1,72 +0,0 @@
import { Sections } from "@/i18n/translations";
import { useLayoutEffect, useRef } from "react";
export const linksOrder: Array<Sections> = [
"about",
"tickets",
"cfp",
"details",
"contact"
]
export function useColorSections(parent: React.RefObject<HTMLDivElement | null>) {
const previous = useRef<Sections>(linksOrder[0])
useLayoutEffect(() => {
if (parent.current === null) return;
const options = {
root: null,
rootMargin: "-10px",
threshold: 0.5, // Adjust the visibility threshold as needed
};
const sections = linksOrder.map(value => document.getElementById(value));
const subs = linksOrder.reduce((acc, value) => {
acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!;
return acc;
}, {} as Record<Sections, HTMLAnchorElement>);
const links = linksOrder.reduce((acc, value) => {
acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!;
return acc;
}, {} as Record<Sections, HTMLAnchorElement>);
console.log(links)
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const target = entry.target.id as Sections
if (entry.isIntersecting) {
// FIXME: This seems to be VERY broken on firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1250972
// It basically spikes up CPU usage to some enormous values just to update the hash, like WTF firefox.
// if (history.replaceState) {
// timeout = setTimeout(() => {
// history.replaceState(null, "", `#${target}`)
// }, 150)
// }
subs[previous.current]?.classList.remove('scale-x-100');
links[previous.current]?.classList.remove('text-primary');
previous.current = target;
subs[previous.current]?.classList.add('scale-x-100');
links[previous.current]?.classList.add('text-primary');
break;
}
}
}, options);
sections.forEach(section => {
if (section) {
observer.observe(section);
}
});
return () => {
observer.disconnect()
};
}, [parent]);
}

View file

@ -44,7 +44,7 @@ const pl = {
tickets: { tickets: {
title: "Bilety", title: "Bilety",
status: `Rusza sprzedaż pierwszej puli biletów na Cebula Camp 2025: Reaktywacja! status: `Rusza sprzedaż pierwszej puli biletów na Cebula Camp 2025: Reaktywacja!
Od 20 kwietnia 2025 r. będzie można kupić bilet. Każdy z sześciu polskich Hackerspaceów ma voucher, który pozwala mu kupić 10 biletów. Skontaktuj się więc z najbliższym HS i zarezerwuj swój bilet jak najszybciej! Od 20 kwietnia 2025 r. będzie można kupić bilet. Każdy z sześciu polskich Hackerspaceów ma voucher, który pozwala na zakup biletów. Skontaktuj się więc z najbliższym HS i zarezerwuj swój bilet jak najszybciej!
W miejscu, gdzie będzie nasz Camp, mamy do dyspozycji przestrzeń, którą wykorzystamy jako małe pole namiotowe. Jeżeli chcesz nocować w swoim namiocie, przy zakupie biletu dodaj nocleg na polu namiotowym jako dodatek do biletu. Nie pobieramy za to żadnych dodatkowych opłat, ale liczba miejsc jest ograniczona, a pula wspólna dla wszystkich.`, W miejscu, gdzie będzie nasz Camp, mamy do dyspozycji przestrzeń, którą wykorzystamy jako małe pole namiotowe. Jeżeli chcesz nocować w swoim namiocie, przy zakupie biletu dodaj nocleg na polu namiotowym jako dodatek do biletu. Nie pobieramy za to żadnych dodatkowych opłat, ale liczba miejsc jest ograniczona, a pula wspólna dla wszystkich.`,
link: "Kup bilet tutaj", link: "Kup bilet tutaj",
@ -144,7 +144,7 @@ const en = {
title: "Tickets", title: "Tickets",
status: `The sale of the first batch of tickets for Cebula Camp 2025: Reactivation is starting! status: `The sale of the first batch of tickets for Cebula Camp 2025: Reactivation is starting!
You will be able to buy a ticket from April 20, 2025. Each of the six Polish Hackerspaces has a voucher that allows them to buy 10 tickets. Contact your nearest hackerspace and book your ticket as soon as possible! You will be able to buy a ticket from April 20, 2025. Each of the six Polish Hackerspaces has a voucher that allows them to buy tickets. Contact your nearest hackerspace and book your ticket as soon as possible!
We have a small camping ground next to the venue for our use. If you want to bring a tent and stay in it overnight, when buying a ticket, add "overnight stay at the camping site" as an addition to the ticket. We do not charge any additional fees for this, but the number of places is limited and the pool is shared by everyone.`, We have a small camping ground next to the venue for our use. If you want to bring a tent and stay in it overnight, when buying a ticket, add "overnight stay at the camping site" as an addition to the ticket. We do not charge any additional fees for this, but the number of places is limited and the pool is shared by everyone.`,
link: "Get your ticket here!", link: "Get your ticket here!",