import { useRef, useState } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import { Card } from '@/components/card'; import { CursorEffect } from '@/components/cursor'; import Mask from '@/assets/images/cities-star-mask.svg'; import LinkIcon from '@/assets/icons/link.svg'; import type { City } from '@/lib/ridebus'; import useLocale from '@/utils/useLocale'; import useFrame from '@/utils/useFrame'; import useMediaQuery from '@/utils/useMediaQuery'; import { useResource } from '../departuresCard/useResource'; import { locales } from './locales'; import styles from './citiesCard.module.scss'; function CityBadge({ city, hidden = false }: { city: City; hidden?: boolean }) { return (
  • {city.name}
  • ); } export function CitiesCard() { const t = useLocale(locales); const { locale } = useRouter(); const [revision, setRevision] = useState(0); const cities = useResource( '/api/departures?resource=cities', revision ); const backdrop = useRef(null); const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)'); const cityList = cities.data ?? []; const count = cityList.length; // Each half fills the card even with a short city list, so the loop has no gaps. const ribbonCities = Array.from( { length: Math.max(1, Math.ceil(8 / Math.max(1, count))) }, () => cityList ).flat(); const plural = new Intl.PluralRules(locale === 'ru' ? 'ru' : 'en').select( count ); useFrame((delta, time) => { if (backdrop.current && !reducedMotion) backdrop.current.style.transform = `rotate(${(time * 0.005) % 360}deg)`; }); return (
    {cities.status === 'ready' && count > 0 ? ( <>

    {count} {t(`cities_${plural}`)}
    {t('inRideBus')}

      {ribbonCities.map((city, index) => (
    ) : (

    {t( cities.status === 'error' ? 'unavailable' : cities.status === 'ready' ? 'empty' : 'loading' )}

    {cities.status === 'error' && ( )}
    )} {t('helpCity')}
    ); }