import { useIdentity } from '@deplixo/sdk'; import { useMemo, useState, useRef } from 'react'; import { PikeIcon, PikeBadge } from './components/PikeLogo.jsx'; import { MapIcon, FishIcon, LureIcon, WeatherIcon, PermitIcon, CabinIcon, SearchIcon, HomeNavIcon, CatchIcon, ProfileIcon, SparkIcon, } from './components/Icons.jsx'; // Main action tiles on home — now 8 cards per spec. const NAV_TILES = [ { id: 'kalat', label: 'KALAT', Icon: FishIcon, hint: 'Suomen kalalajit', view: 'kalat' }, { id: 'kartta', label: 'KARTTA', Icon: MapIcon, hint: 'Vesistöt & paikat', view: 'kartta' }, { id: 'saaliit', label: 'SAALISPÄIVÄKIRJA', Icon: CatchIcon, hint: 'Tallenna saalis', view: 'saaliit' }, { id: 'viehet', label: 'VIEHEET', Icon: LureIcon, hint: 'Galleria', view: 'viehet' }, { id: 'saa', label: 'SÄÄ & KALASTUS', Icon: WeatherIcon, hint: 'Kalastussää', view: 'saa' }, { id: 'laavut', label: 'LAAVUT & NUOTIOPAIKAT', Icon: PermitIcon, hint: 'Taukopaikat', view: 'laavut' }, { id: 'mokit', label: 'VUOKRAMÖKIT', Icon: CabinIcon, hint: 'Majoitus', view: 'mokit' }, { id: 'feed', label: 'KALASTAJIEN SAALIIT', Icon: ProfileIcon, hint: 'Yhteisön feed', view: 'feed' }, ]; const BOTTOM_TABS = [ { id: 'koti', label: 'Koti', Icon: HomeNavIcon, view: 'koti' }, { id: 'kartta', label: 'Kartta', Icon: MapIcon, view: 'kartta' }, { id: 'kalat', label: 'Kalat', Icon: FishIcon, view: 'kalat' }, { id: 'saaliit', label: 'Saaliit', Icon: CatchIcon, view: 'saaliit' }, { id: 'profiili', label: 'Profiili', Icon: ProfileIcon, view: 'profiili' }, ]; const LOCATIONS = [ { id: 'saimaa', label: 'Saimaa', base: 72 }, { id: 'paijanne', label: 'Päijänne', base: 68 }, { id: 'inari', label: 'Inarijärvi', base: 65 }, { id: 'rannikko', label: 'Suomenlahti', base: 60 }, { id: 'pikkujarvi',label: 'Lähijärvi', base: 55 }, ]; const WEATHERS = [ { id: 'pilvinen', label: 'Pilvipouta', mod: 12 }, { id: 'sade', label: 'Tihkusade', mod: 8 }, { id: 'tyyni', label: 'Tyyni', mod: -5 }, { id: 'aurinko', label: 'Helteinen', mod: -15 }, { id: 'myrsky', label: 'Myrsky', mod: -20 }, ]; const SPECIES = [ { id: 'hauki', label: 'Hauki', loves: ['pilvinen', 'sade'], hates: ['aurinko'] }, { id: 'kuha', label: 'Kuha', loves: ['pilvinen', 'tyyni'], hates: ['myrsky'] }, { id: 'ahven', label: 'Ahven', loves: ['tyyni', 'aurinko'], hates: ['myrsky'] }, { id: 'taimen', label: 'Taimen', loves: ['sade', 'pilvinen'], hates: ['aurinko'] }, { id: 'siika', label: 'Siika', loves: ['tyyni', 'pilvinen'], hates: ['myrsky'] }, ]; const AREA_SPECIES = { saimaa: [ { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', size: '2–8 kg', note: 'Yleisin peto — kaislikoista' }, { id: 'kuha', name: 'Kuha', latin: 'Sander lucioperca', size: '1–4 kg', note: 'Syvänteet, hämärän aika' }, { id: 'ahven', name: 'Ahven', latin: 'Perca fluviatilis', size: '200–800 g', note: 'Parvissa luodoilla' }, { id: 'jarvilohi',name: 'Järvilohi', latin: 'Salmo salar m. sebago',size: '3–7 kg', note: 'Uhanalainen — vapautus!' }, { id: 'nieria', name: 'Saimaannieriä', latin: 'Salvelinus alpinus', size: '1–3 kg', note: 'Harvinainen, syvät vedet' }, { id: 'made', name: 'Made', latin: 'Lota lota', size: '500 g–2 kg',note: 'Talvikalastus, pohjalla' }, ], paijanne: [ { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', size: '2–10 kg', note: 'Kookkaita yksilöitä' }, { id: 'kuha', name: 'Kuha', latin: 'Sander lucioperca', size: '1–5 kg', note: 'Päijänteen vahvin laji' }, { id: 'ahven', name: 'Ahven', latin: 'Perca fluviatilis', size: '200 g–1 kg',note: 'Isokokoisia ahvenia' }, { id: 'taimen', name: 'Järvitaimen', latin: 'Salmo trutta m. lacustris', size: '2–6 kg', note: 'Istutuskanta, vapautus' }, { id: 'muikku', name: 'Muikku', latin: 'Coregonus albula', size: '50–150 g', note: 'Nuottakalastus' }, { id: 'siika', name: 'Siika', latin: 'Coregonus lavaretus', size: '500 g–2 kg', note: 'Verkoilla ja uistimella' }, ], inari: [ { id: 'nieria', name: 'Nieriä (rautu)', latin: 'Salvelinus alpinus', size: '1–5 kg', note: 'Inarin tunnuskala' }, { id: 'taimen', name: 'Taimen', latin: 'Salmo trutta', size: '1–4 kg', note: 'Virtapaikat, saaristo' }, { id: 'siika', name: 'Siika', latin: 'Coregonus lavaretus',size: '500 g–2 kg', note: 'Useita muotoja' }, { id: 'harjus', name: 'Harjus', latin: 'Thymallus thymallus',size: '300 g–1 kg', note: 'Perhokalastus kesällä' }, { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', size: '2–7 kg', note: 'Matalat lahdet' }, { id: 'siikapojat',name: 'Pohjasiika', latin: 'Coregonus lavaretus pidschian', size: '400 g–1,5 kg', note: 'Syvien vesien laji' }, ], rannikko: [ { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', size: '2–12 kg', note: 'Saaristo, kevät huippu' }, { id: 'ahven', name: 'Ahven', latin: 'Perca fluviatilis', size: '300 g–1,2 kg', note: 'Isot raidat luodoilta' }, { id: 'kuha', name: 'Kuha', latin: 'Sander lucioperca', size: '1–6 kg', note: 'Sameat lahdet kesällä' }, { id: 'merita', name: 'Meritaimen', latin: 'Salmo trutta m. trutta', size: '1–5 kg', note: 'Rantavieheellä keväällä' }, { id: 'silakka',name: 'Silakka', latin: 'Clupea harengus membras', size: '20–80 g', note: 'Lippokalastus satamissa' }, { id: 'turska', name: 'Turska', latin: 'Gadus morhua', size: '1–3 kg', note: 'Harvinaistunut Itämeressä' }, ], pikkujarvi: [ { id: 'ahven', name: 'Ahven', latin: 'Perca fluviatilis', size: '100–500 g', note: 'Runsain laji' }, { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', size: '1–5 kg', note: 'Rantakaislikot' }, { id: 'sarki', name: 'Särki', latin: 'Rutilus rutilus', size: '100–400 g', note: 'Mato-onkiminen' }, { id: 'lahna', name: 'Lahna', latin: 'Abramis brama', size: '500 g–3 kg',note: 'Syvemmät kuopat' }, ], }; const RECENT_CATCHES = [ { id: 'c-mikko-hauki', user: 'Mikko R.', initials: 'MR', avatarTone: 'tone-a', species: 'Hauki', weight: '7,2 kg', length: '98 cm', location: 'Saimaa · Puumala', lure: 'Rapala Super Shad Rap', time: '2 h sitten', note: 'Napsi kaislikon reunasta aamuhämärissä. Vapautettu takaisin! 🎣', likes: 42, comments: 8, }, { id: 'c-anni-kuha', user: 'Anni L.', initials: 'AL', avatarTone: 'tone-b', species: 'Kuha', weight: '3,8 kg', length: '71 cm', location: 'Päijänne · Sysmä', lure: 'Jigi 12 cm, puna-valkoinen', time: '5 h sitten', note: 'Syvänteestä 14 m, pilvistä ja tyyntä. Täydellinen ilta.', likes: 67, comments: 14, }, { id: 'c-jukka-ahven', user: 'Jukka P.', initials: 'JP', avatarTone: 'tone-c', species: 'Ahven', weight: '820 g', length: '38 cm', location: 'Suomenlahti · Porvoon saaristo', lure: 'Mepps Aglia #3', time: '1 pv sitten', note: 'Luodon kärjestä kolmas heitto. Komea raita!', likes: 29, comments: 5, }, { id: 'c-sanna-taimen', user: 'Sanna K.', initials: 'SK', avatarTone: 'tone-d', species: 'Järvitaimen', weight: '4,5 kg', length: '76 cm', location: 'Inarijärvi · Nellim', lure: 'Perho: Black Zonker', time: '2 pv sitten', note: 'Elämän kala! Istukas, rasvaevä leikattu — talteen.', likes: 118, comments: 23, }, ]; // Fish species gallery — real photographic images served via a dedicated // image proxy (images.weserv.nl, the "deplixo.proxy" style image proxy) so // the browser can reliably fetch Wikimedia Commons photos without CORS or // hotlink issues. Each entry includes full species info for the detail view // (identification, habitat, best times, lures, weather). const proxied = (url) => { // Strip the scheme — weserv expects "host/path" with no leading "https://". const stripped = url.replace(/^https?:\/\//, ''); return `https://images.weserv.nl/?url=${encodeURIComponent(stripped)}&w=800&output=jpg`; }; const FISH_GALLERY = [ { id: 'ahven', name: 'Ahven', latin: 'Perca fluviatilis', note: 'Yleisin peto, maistuu loistavasti', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Perca_fluviatilis_Prague_Vltava_3.jpg/1280px-Perca_fluviatilis_Prague_Vltava_3.jpg'), identification: 'Tummanvihreä selkä ja 5–9 pystyraitaa kyljissä. Oranssit alaevät ja piikikäs selkäevä, jossa musta täplä takaosassa.', habitat: 'Kaikki Suomen järvet ja rannikko. Viihtyy luodoilla, kivikoissa, laitureilla ja kaislikoiden liepeillä parvissa.', bestTimes: 'Kesä–syksy valoisaan aikaan, erityisesti aamu ja iltapäivä. Syyskuulla isot ahvenet syvemmälle.', lures: 'Pienet lippa- ja lusikkavieheet (Mepps #2–3), tasapainopilkki, jigi 5–7 cm, mato-onki.', weather: 'Tyyni tai hieman tuulinen, aurinkoinen tai puolipilvinen päivä. Vältä myrskyä.', }, { id: 'hauki', name: 'Hauki', latin: 'Esox lucius', note: 'Kaislikoiden kuningas', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Esox_lucius1.jpg/1280px-Esox_lucius1.jpg'), identification: 'Pitkä, torpedomainen vartalo, litteä kuonopää ja terävät hampaat. Vihertävä kyljissä vaaleita täpliä ja raitoja.', habitat: 'Matalat kasvikasvustot, kaislikot, lahdet ja saaristot koko Suomessa. Suuret yksilöt syvempien reunoilla.', bestTimes: 'Kevät heti kudun jälkeen (touko–kesäkuu) ja syksy (syys–lokakuu). Hämärä aamu ja ilta parhaita.', lures: 'Jerkbaitit (Rapala Super Shad Rap, X-Rap), isot lusikat, kumikalat 15–25 cm, spinnerbaitit.', weather: 'Pilvinen, lämmin ja hieman tuulinen päivä. Tihkusade aktivoi — vältä kirkasta helteistä päivää.', }, { id: 'kuha', name: 'Kuha', latin: 'Sander lucioperca', note: 'Syvänteiden hopea', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Zander.jpg/1280px-Zander.jpg'), identification: 'Hopeanharmaa, pitkulainen peto. Selässä kaksi erillistä selkäevää, hampaat terävät kuten haukella. Suuret, lasimaiset silmät.', habitat: 'Syvät, rehevät järvet ja saaristo. Oleskelee syvänteiden reunoilla 5–15 metrissä, nousee öisin matalammalle.', bestTimes: 'Kesä–loppusyksy. Hämärä ja yö parhaita — auringonlaskusta puoleenyöhön. Myös pilvisinä päivinä.', lures: 'Jigit 10–14 cm (puna-valkoinen, kirjo), syvälle sukeltavat vaaput, tasapainopilkki talvella.', weather: 'Pilvinen tai tyyni, matalapaine. Sameat vedet suosivat kuhaa. Vältä myrskyä ja kirkasta helteistä päivää.', }, { id: 'siika', name: 'Siika', latin: 'Coregonus lavaretus', note: 'Hopeinen pohjoisen helmi', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Coregonus_lavaretus1.jpg/1280px-Coregonus_lavaretus1.jpg'), identification: 'Hopeanvärinen, sivuilta litistynyt lohikala. Rasvaevä, pieni suu alahuulella, hopeiset suomut.', habitat: 'Puhtaat, viileät järvet ja rannikko. Useita muotoja (järvi-, vaellus-, pohjasiika). Viihtyy avovesillä ja rannoilla.', bestTimes: 'Alkukesä (touko–kesäkuu) rannoilla sulamisen jälkeen, syksy kutuaikaan loka–marraskuussa.', lures: 'Pienet lusikat, siikaperhot (nymfit), heittoperhokalastus, verkot. Kuivaperhoja pintasyötöllä.', weather: 'Tyyni, viileä ja pilvinen päivä. Alkukesän auringonlasku on taika-aikaa.', }, { id: 'taimen', name: 'Taimen', latin: 'Salmo trutta', note: 'Virran vikkeliä aristokraatteja', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/Salmo_trutta.jpg/1280px-Salmo_trutta.jpg'), identification: 'Pyöreämpi vartalo kuin lohella, mustia täpliä myös pyrstön alapuolella. Punaisia pisteitä kyljissä, rasvaevä.', habitat: 'Virtaavat vedet, kosket ja järvien kylmät alueet. Vaelluskantoja merellä ja järvillä (meritaimen, järvitaimen).', bestTimes: 'Kesä sateisina päivinä ja syksy kutuaikaan. Virtavesillä aamu ja ilta — talvi avovesialueilla.', lures: 'Perhot (Black Zonker, Mikkeli, nymfit), spinnerit (Mepps #2–3), vaapu 7–11 cm, uistin.', weather: 'Pilvinen, viileä sadepäivä on paras. Vältä kirkasta helteistä päivää — kala menee syvälle.', }, { id: 'lohi', name: 'Lohi', latin: 'Salmo salar', note: 'Vaelluskalojen ylpeys', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Salmo_salar-Atlantic_Salmon-Atlanterhavsparken_Norway.JPG/1280px-Salmo_salar-Atlantic_Salmon-Atlanterhavsparken_Norway.JPG'), identification: 'Hopeanhohtoinen, voimakas vartalo. Mustia täpliä selässä ja kyljissä pyrstöviivan yläpuolella. Rasvaevä ja syvälovinen pyrstö.', habitat: 'Kutuvaellukset Tornion- ja Simojokeen sekä Itämereen. Avomerellä syö silakkaa, nousee jokeen kesällä.', bestTimes: 'Kesä–alkusyksy nousujoilla (kesä–elokuu). Vetouistelu Itämerellä kevät–kesä.', lures: 'Lohiperhot (tubiperhot, Templedog), isot vaaput, uistimet, tasapainot (Rapala Downdeep, Nils Master).', weather: 'Pilvipouta ja heikko sade jokivedessä. Veden lämpötila 10–16 °C paras. Vältä kuivaa helteistä kautta.', }, { id: 'made', name: 'Made', latin: 'Lota lota', note: 'Talven herkku, pohjalla', photo: proxied('https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Lota_lota_Oulanka.jpg/1280px-Lota_lota_Oulanka.jpg'), identification: 'Ainoa makean veden turskakala. Pitkä vartalo, leveä pää, viiksi leuan alla. Marmoroitu ruskeankeltainen väritys.', habitat: 'Viileät, happirikkaat järvet ja virrat koko Suomessa. Pohjakala — kivikot, hiekkapohjat 3–20 metrissä.', bestTimes: 'Talvi (joulu–helmikuu) kutuaikaan — paras pilkkikausi. Myös syyskuulla pimeällä rannoilta.', lures: 'Pilkkivieheet painolla (madepilkki) silakkasyötöllä, iskukoukut, pohjaonki muikun palalla.', weather: 'Kylmä talvipakkanen (-5…-20 °C) ja kiinteä jää. Pimeä yö parhaita — made on yöeläjä.', }, ]; // Lure gallery const LURES = [ { id: 'rapala-ssr', name: 'Super Shad Rap', type: 'Jerkbait', for: 'Hauki, kuha', conditions: 'Matalat kaislikot, pilvinen', color: '#c0392b' }, { id: 'jigi-12', name: 'Jigi 12 cm', type: 'Pehmoviehe', for: 'Kuha, hauki', conditions: 'Syvänteet, hämärä', color: '#8e44ad' }, { id: 'mepps-3', name: 'Mepps Aglia #3', type: 'Lusikkaviehe', for: 'Ahven, taimen', conditions: 'Kirkas vesi, aurinko', color: '#f39c12' }, { id: 'perho-zonker', name: 'Black Zonker', type: 'Perho', for: 'Taimen, lohi', conditions: 'Virtavedet, sade', color: '#2c3e50' }, { id: 'rapala-xrap', name: 'Rapala X-Rap', type: 'Jerkbait', for: 'Hauki, kuha', conditions: 'Rannat, tuulinen', color: '#27ae60' }, { id: 'tasapaino', name: 'Tasapainopilkki', type: 'Pilkki', for: 'Ahven, kuha', conditions: 'Talvikalastus, jää', color: '#2980b9' }, ]; // Map points (lat/lng-ish percentages used in the stylized map) const MAP_POINTS = [ { id: 'p-saimaa', kind: 'fish', label: 'Saimaa — kuha-apaja', x: 62, y: 48 }, { id: 'p-paijanne', kind: 'fish', label: 'Päijänne — hauki', x: 48, y: 55 }, { id: 'p-inari', kind: 'fish', label: 'Inari — nieriä', x: 55, y: 12 }, { id: 'p-laavu-1', kind: 'laavu', label: 'Laavu: Puumala', x: 65, y: 52 }, { id: 'p-laavu-2', kind: 'laavu', label: 'Laavu: Nellim', x: 58, y: 18 }, { id: 'p-nuotio-1', kind: 'nuotio', label: 'Nuotiopaikka: Sysmä', x: 46, y: 58 }, { id: 'p-wc-1', kind: 'wc', label: 'WC: Puumalan satama', x: 68, y: 50 }, { id: 'p-vene-1', kind: 'vene', label: 'Venepaikka: Lahti', x: 44, y: 62 }, { id: 'p-mokki-1', kind: 'mokki', label: 'Vuokramökki: Puumala', x: 70, y: 54 }, { id: 'p-mokki-2', kind: 'mokki', label: 'Vuokramökki: Nellim', x: 56, y: 20 }, { id: 'p-me', kind: 'me', label: 'Sinä olet tässä', x: 50, y: 70 }, ]; const LAAVUT = [ { id: 'l-puumala', name: 'Puumalan laavu', kind: 'Laavu', dist: '1,2 km', note: 'Puut, kuivakäymälä, laituri' }, { id: 'l-nellim', name: 'Nellimin kota', kind: 'Laavu', dist: '14 km', note: 'Avoinna ympäri vuoden' }, { id: 'n-sysma', name: 'Sysmän nuotiopaikka', kind: 'Nuotiopaikka', dist: '3,4 km', note: 'Grillikatos, puut' }, { id: 'wc-puumala', name: 'Puumalan satama WC', kind: 'WC', dist: '0,8 km', note: 'Esteetön, auki 24/7' }, { id: 'l-koli', name: 'Kolin laavu', kind: 'Laavu', dist: '42 km', note: 'Näköalapaikka' }, ]; const CABINS = [ { id: 'mk-puumala', name: 'Puumalan rantamökki', beds: '4+2', dist: '0,5 km järvestä', price: '120 €/vrk', note: 'Sauna, vene, laituri' }, { id: 'mk-nellim', name: 'Nellimin huvila', beds: '6', dist: '0,2 km järvestä', price: '180 €/vrk', note: 'Takka, savusauna' }, { id: 'mk-sysma', name: 'Sysmän aittamökki', beds: '2', dist: '1,0 km järvestä', price: '85 €/vrk', note: 'Soutuvene mukana' }, { id: 'mk-koli', name: 'Kolin lomamökki', beds: '8', dist: '2,0 km järvestä', price: '220 €/vrk', note: 'Palju, näköala' }, ]; function computeForecast(locId, weatherId, speciesId) { const loc = LOCATIONS.find(l => l.id === locId); const weather = WEATHERS.find(w => w.id === weatherId); const species = SPECIES.find(s => s.id === speciesId); if (!loc || !weather || !species) return null; let score = loc.base + weather.mod; if (species.loves.includes(weather.id)) score += 15; if (species.hates.includes(weather.id)) score -= 15; score = Math.max(0, Math.min(100, score)); let verdict, tone; if (score >= 80) { verdict = 'Erinomainen — lähde heti!'; tone = 'excellent'; } else if (score >= 60) { verdict = 'Hyvä — kannattaa kokeilla.'; tone = 'good'; } else if (score >= 40) { verdict = 'Kohtalainen — voi napata.'; tone = 'ok'; } else { verdict = 'Heikko — ehkä toinen päivä.'; tone = 'weak'; } return { score, verdict, tone, summary: `${species.label} · ${loc.label} · ${weather.label}` }; } // ---------- Header ---------- function Header() { return (

NAPPAAKO?

Suomalainen kalastuskaveri

); } // ---------- Hero banner ---------- function HeroBanner() { return (
🎣 NAPPAAKO?

Lähde kalaan tänään

Sää, paikat, lajit ja luvat — kaikki yhdessä sovelluksessa.

); } // ---------- Search ---------- function SearchBar() { return (
e.preventDefault()}>
); } // ---------- Nav grid ---------- function NavGrid({ onOpen }) { return ( ); } // ---------- Nappaako Nyt card ---------- function NappaakoNytCard({ locationId, setLocationId }) { const [weatherId, setWeatherId] = useState(''); const [speciesId, setSpeciesId] = useState(''); const [result, setResult] = useState(null); const ready = locationId && weatherId && speciesId; const preview = useMemo( () => (ready ? computeForecast(locationId, weatherId, speciesId) : null), [ready, locationId, weatherId, speciesId] ); const display = result ?? preview; const fillPct = display ? display.score : 0; const handleCheck = () => { if (!ready) return; setResult(computeForecast(locationId, weatherId, speciesId)); }; return (
ENNUSTE

NAPPAAKO NYT?

Kalastusennuste yhdistää sijainnin, sään ja kalalajien aktiivisuuden — näet yhdellä silmäyksellä, kannattaako lähteä järvelle.

HeikkoKohtalainenHyväErinomainen
{display && (
{display.score}/100

{display.verdict}

{display.summary}

)}

Yhdistetään oikeaan sää- ja sijaintidataan

); } // ---------- Mitä täällä nappaa section ---------- function MitaNappaaSection({ locationId }) { const location = LOCATIONS.find(l => l.id === locationId); const species = locationId ? (AREA_SPECIES[locationId] ?? []) : []; return (

Mitä täällä nappaa?

{location ? `Alueen kalalajit — ${location.label}` : 'Alueen kalalajit — valitse ensin sijainti'}

{!locationId && (
{[0, 1, 2, 3].map((i) => (
); } // ---------- Tuoreimmat saaliit feed ---------- function TuoreimmatSaaliitSection({ extra, onShare }) { const all = extra.concat(RECENT_CATCHES); return (

Kalastajien saaliit

Yhteisön tuoreet saalisraportit

); } // ---------- Interactive map ---------- function MapView({ title = 'Kartta', subtitle = 'Kalastuspaikat, laavut, WC:t, venepaikat & mökit' }) { const [selected, setSelected] = useState(null); const kindLabel = { fish: 'Kalastuspaikka', laavu: 'Laavu', nuotio: 'Nuotiopaikka', wc: 'WC', vene: 'Venepaikka', mokki: 'Vuokramökki', me: 'Oma sijainti' }; const kindEmoji = { fish: '🐟', laavu: '⛺', nuotio: '🔥', wc: '🚻', vene: '⛵', mokki: '🏡', me: '📍' }; return (

{title}

{subtitle}

{MAP_POINTS.map(p => ( ))} {selected && (() => { const p = MAP_POINTS.find(mp => mp.id === selected); return (
{kindEmoji[p.kind]} {kindLabel[p.kind]} {p.label}
); })()}
Kalastus Laavut Nuotio WC Vene Mökit Sinä
); } // ---------- Fish detail view ---------- function FishDetailView({ fish, onBack }) { return (
{`${fish.name}

{fish.name}

{fish.latin}

{fish.note}

🔎 Tunnistustiedot
{fish.identification}
🌿 Tyypillinen elinympäristö
{fish.habitat}
⏰ Parhaat kalastusajat
{fish.bestTimes}
🎣 Sopivat vieheet
{fish.lures}
☁️ Sopivat sääolosuhteet
{fish.weather}
); } // ---------- Fish gallery view (card layout with photos) ---------- function FishGalleryView({ onSelect }) { return (

Kalalajit

Suomen yleisimmät kalat

    {FISH_GALLERY.map(f => (
  • {`${f.name}

    {f.name}

    {f.latin}

    {f.note}

  • ))}
); } // ---------- Lure gallery view ---------- function LureGalleryView() { return (

Vieheet

Mille kalalle ja millaisissa olosuhteissa

    {LURES.map(l => (
  • {l.name}

    {l.type}

    Kalalle: {l.for}

    Olot: {l.conditions}

  • ))}
); } // ---------- Catch log form ---------- function CatchLogView({ onSaved }) { const [form, setForm] = useState({ species: '', weight: '', length: '', date: '', location: '', lure: '', weather: '', note: '', }); const [saved, setSaved] = useState(false); const update = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value })); const handleSubmit = (e) => { e.preventDefault(); if (!form.species) return; const entry = { id: `my-${Date.now()}`, user: 'Sinä', initials: 'SI', avatarTone: 'tone-c', species: form.species || 'Kala', weight: form.weight || '—', length: form.length || '—', location: form.location || '—', lure: form.lure || '—', time: 'juuri nyt', note: form.note || 'Oma saalis.', likes: 0, comments: 0, }; onSaved?.(entry); setSaved(true); setForm({ species: '', weight: '', length: '', date: '', location: '', lure: '', weather: '', note: '' }); }; return (

Saalispäiväkirja

Tallenna oma saalis