true], $data)); exit; } function err(string $msg, int $code = 400): void { http_response_code($code); echo json_encode(['error' => $msg]); exit; } function isAdmin(): bool { return !empty($_SESSION['tykkaafi_admin']); } // ─── Oletusdata ──────────────────────────────────────────────── // Kategoriat jotka on poistettu — poistetaan automaattisesti live-serveriltä const REMOVED_CATEGORIES = ['knitting', 'kasvit', 'matkustus', 'tips']; // Vanhojen postausten kategoria-migraatiot (null = poista postaus) const POST_CATEGORY_MAP = [ 'knitting' => 'kasityot', 'kasvit' => 'puutarha', 'tips' => 'hyvinvointi', 'matkustus' => null, ]; function defaultCategories(): array { return [ ['id' => 'recipes', 'fi' => 'Reseptit', 'en' => 'Recipes', 'emoji' => '🍳', 'subcategories' => [ ['id' => 'kasvis', 'fi' => 'Kasvis'], ['id' => 'vegaaniset', 'fi' => 'Vegaaniset'], ['id' => 'jalkiruuat', 'fi' => 'Jälkiruuat'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'kasityot', 'fi' => 'Käsityöt', 'en' => 'Crafts', 'emoji' => '✂️', 'subcategories' => [ ['id' => 'neulominen', 'fi' => 'Neulominen'], ['id' => 'virkkaus', 'fi' => 'Virkkaus'], ['id' => 'ommelu', 'fi' => 'Ommelu'], ['id' => 'askartelu', 'fi' => 'Askartelu'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'puutarha', 'fi' => 'Puutarha & Kasvit', 'en' => 'Garden & Plants', 'emoji' => '🌱', 'subcategories' => [ ['id' => 'huonekasvit', 'fi' => 'Huonekasvit'], ['id' => 'parvekeviljeily', 'fi' => 'Parvekeviljeily'], ['id' => 'vihannekset', 'fi' => 'Vihannekset'], ['id' => 'kukat', 'fi' => 'Kukat'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'sisustus', 'fi' => 'Sisustus', 'en' => 'Interior', 'emoji' => '🏠', 'subcategories' => [ ['id' => 'diy', 'fi' => 'DIY'], ['id' => 'valaistus', 'fi' => 'Valaistus'], ['id' => 'tekstiilit', 'fi' => 'Tekstiilit'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'kirjat', 'fi' => 'Kirjat', 'en' => 'Books', 'emoji' => '📚', 'subcategories' => [ ['id' => 'romaanit', 'fi' => 'Romaanit'], ['id' => 'tietokirjat', 'fi' => 'Tietokirjat'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'hyvinvointi', 'fi' => 'Hyvinvointi', 'en' => 'Wellbeing', 'emoji' => '🧘', 'subcategories' => [ ['id' => 'liikunta', 'fi' => 'Liikunta'], ['id' => 'uni', 'fi' => 'Uni'], ['id' => 'mieli', 'fi' => 'Mieli'], ['id' => 'arki', 'fi' => 'Arki'], ['id' => 'muut', 'fi' => 'Muut'], ]], ['id' => 'lemmikit', 'fi' => 'Lemmikit', 'en' => 'Pets', 'emoji' => '🐾', 'subcategories' => [ ['id' => 'koirat', 'fi' => 'Koirat'], ['id' => 'kissat', 'fi' => 'Kissat'], ['id' => 'muut', 'fi' => 'Muut'], ]], ]; } function defaultPosts(): array { return [ // ── RESEPTIT ────────────────────────────────────────────── [ 'id' => 'pancakes', 'emoji' => '🥞', 'title' => 'Kuohkeat letut', 'category' => 'recipes', 'author' => 'Maija-Liisa', 'time' => '20 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Kultaisia, voisia lettuja, jotka sulavat suuhun. Täydellisiä laiskaan sunnuntaiaamuun.', 'ingredients' => ['1½ dl vehnäjauhoja','3½ tl leivinjauhetta','1 tl suolaa','1 rkl sokeria','3 dl maitoa','1 muna','3 rkl sulatettua voita','Voita tai öljyä paistamiseen'], 'steps' => ['Sekoita kulhossa jauhot, leivinjauhe, suola ja sokeri.','Tee keskelle kuoppa ja kaada sekaan maito, muna ja sulatettu voi.','Sekoita tasaiseksi — pienet paakut ovat ok.','Kuumenna pannua keskilämmöllä.','Kaada noin ¼ dl taikinaa per lettu.','Paista kunnes pintaan nousee kuplia, käännä ja paista kullanruskeaksi.','Tarjoile vaahterasiirapilla ja marjoilla.'], ], [ 'id' => 'bolognese', 'emoji' => '🍝', 'title' => 'Klassinen spagetti bolognese', 'category' => 'recipes', 'author' => 'Tiina K.', 'time' => '1 t 20 min', 'servings' => '6 annosta', 'type' => 'recipe', 'desc' => 'Runsas, hitaasti haudutettu lihamauste al dente -spagetin päällä. Ajaton italialainen klassikko.', 'ingredients' => ['500 g jauhelihaa','400 g spagettia','1 sipuli, hienonnettuna','3 valkosipulinkynttä','800 g murskattuja tomaatteja','2 dl punaviiniä','2 rkl tomaattipyrettä','Suolaa, pippuria, basilikaa, oreganoa','Parmesaanijuustoa tarjoiluun'], 'steps' => ['Kuullota sipuli ja valkosipuli oliiviöljyssä.','Ruskista jauheliha.','Lisää viini ja anna pelkistyä (5 min).','Lisää tomaatit ja mausteet.','Hauduta miedolla lämmöllä 1 tunti.','Keitä spagetti al denteksi.','Tarjoile parmesaanin kera.'], ], [ 'id' => 'cookies', 'emoji' => '🍪', 'title' => 'Suklaahippukeksit', 'category' => 'recipes', 'author' => 'Riitta H.', 'time' => '30 min', 'servings' => '24 keksiä', 'type' => 'recipe', 'desc' => 'Sitkeä sisältä, rapea reunoilta — täydellinen kotitekoinen keksi.', 'ingredients' => ['5½ dl vehnäjauhoja','1 tl ruokasoodaa','1 tl suolaa','225 g pehmeää voita','1½ dl sokeria','1½ dl ruskeaa sokeria','2 munaa','2 tl vaniljauutetta','4 dl suklaahippuja'], 'steps' => ['Kuumenna uuni 190°C.','Sekoita jauhot, sooda ja suola.','Vatkaa voi ja sokerit kuohkeaksi.','Lisää munat ja vanilja.','Yhdistä aineet ja lisää suklaa.','Lusikoi pellille.','Paista 9–11 min kullanruskeaksi.'], ], [ 'id' => 'soup', 'emoji' => '🍲', 'title' => 'Täyttävä kasviskeitto', 'category' => 'recipes', 'subcategory' => 'kasvis', 'author' => 'Sinikka', 'time' => '45 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Lämmittävä kulhollinen paksuja kasviksia rikkaassa yrttiliemessä.', 'ingredients' => ['2 rkl oliiviöljyä','1 sipuli','3 valkosipulia','3 porkkanaa, siivuina','2 perunaa, kuutioina','1 kesäkurpitsa','400 g tomaattimurskaa','1½ l kasvislientä','Timjami, rosmariini, suola, pippuri'], 'steps' => ['Kuullota sipuli ja valkosipuli.','Lisää kasvikset ja sekoittele 5 min.','Kaada liemi ja tomaatit, lisää yrtit.','Hauduta 25 min.','Lisää kesäkurpitsa, keitä 10 min.','Mausta ja tarjoile.'], ], [ 'id' => 'banana_bread', 'emoji' => '🍌', 'title' => 'Mehevä banaanileipä', 'category' => 'recipes', 'author' => 'Tiina K.', 'time' => '1 t 10 min', 'servings' => '10 siivua', 'type' => 'recipe', 'desc' => 'Täydellinen tapa käyttää ylikypsät banaanit. Kosteaa, makeaa ja ihanan aromaattista.', 'ingredients' => ['3 ylikypsää banaania','2 dl vehnäjauhoja','1 tl ruokasoodaa','½ tl suolaa','75 g sulatettua voita','1½ dl sokeria','1 muna','1 tl vaniljauutetta','½ dl smetanaa tai piimää'], 'steps' => ['Kuumenna uuni 175°C ja voitele leipävuoka.','Soseuta banaanit haarukalla.','Sekoita jauhot, sooda ja suola kulhossa.','Yhdistä banaanisose, voi, sokeri, muna ja vanilja toisessa kulhossa.','Sekoita kuivat ja märät ainekset, lisää smetana.','Kaada taikina vuokaan.','Paista 60–65 min, kunnes tikku tulee puhtaana ulos.','Anna jäähtyä ennen leikkaamista.'], ], [ 'id' => 'salmon_soup', 'emoji' => '🐟', 'title' => 'Perinteinen lohikeitto', 'category' => 'recipes', 'author' => 'Sinikka', 'time' => '40 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Kermainen ja täyteläinen lohikeitto — suomalainen sieluruoka parhaimmillaan.', 'ingredients' => ['500 g lohifileetä, kuutioina','4 perunaa, kuutioina','2 porkkanaa, siivuina','1 purjo, siivuina','1 l kalaliemi tai vesi','2 dl kuohukermaa','Tilliä, suolaa, valkopippuria','Voita paistamiseen'], 'steps' => ['Kuullota purjo voissa kattilassa.','Lisää porkkanat ja perunat, paistele 3 min.','Kaada liemi ja keitä kasvikset kypsiksi (15 min).','Lisää lohikuutiot ja keitä 5–7 min.','Kaada kerma ja kuumenna (ei kiehauta).','Mausta tillillä, suolalla ja pippurilla.','Tarjoile ruisleivän kera.'], ], [ 'id' => 'cinnamon_rolls', 'emoji' => '🌀', 'title' => 'Korvapuustit', 'category' => 'recipes', 'author' => 'Riitta H.', 'time' => '2 t 30 min', 'servings' => '20 kpl', 'type' => 'recipe', 'desc' => 'Pehmeät, kanelilla ja kardemummalla maustetut korvapuustit — suomalaiseen kahvipöytään kuuluvat klassikoista klassisin.', 'ingredients' => ['5 dl maitoa','50 g hiivaa','1½ dl sokeria','1 tl suolaa','2 tl jauhettua kardemummaa','noin 13 dl vehnäjauhoja','150 g pehmeää voita','Täyte: 100 g pehmeää voita, 1 dl sokeria, 2 rkl kanelia','Munaa voiteluun, karkeaa sokeria päälle'], 'steps' => ['Lämmitä maito 37°C ja liuota siihen hiiva.','Lisää sokeri, suola, kardemumma ja puolet jauhoista, sekoita.','Lisää voi ja loput jauhot, vaivaa pehmeäksi taikinaksi.','Anna kohota liinan alla 45 min.','Kauli taikina suorakaiteeksi, levitä täyte.','Kääri rullaksi ja leikkaa viipaleiksi, asettele pellille.','Anna kohota 30 min, voitele munalla, ripottele sokeria.','Paista 200°C 12–15 min kullanruskeaksi.'], ], [ 'id' => 'strawberry_quark', 'emoji' => '🍓', 'title' => 'Mansikkarahka', 'category' => 'recipes', 'subcategory' => 'jalkiruuat', 'author' => 'Tiina K.', 'time' => '15 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Helppo ja raikas jälkiruoka kesän makeimmista mansikoista. Valmistuu vartin kymmenessä minuutissa!', 'ingredients' => ['500 g tuoreita mansikoita','400 g rahkaa tai maitorahkaa','2 dl kuohukermaa','3 rkl sokeria tai hunajaa','1 tl vaniljasokeria','Mintunlehtiä koristeluun'], 'steps' => ['Soseuta puolet mansikoista haarukalla tai sauvasekoittimella.','Vatkaa kerma löysäksi vaahdoksi.','Sekoita rahka, mansikasose, sokeri ja vaniljasokeri.','Nostele kermavaahto joukkoon.','Pilko loput mansikat paloiksi ja sekoita tai laita päälle.','Jäähdytä tunnin verran ennen tarjoilua.'], ], [ 'id' => 'carrot_ginger_soup', 'emoji' => '🥕', 'title' => 'Porkkana-inkiväärikeitto', 'category' => 'recipes', 'subcategory' => 'vegaaniset', 'author' => 'Maija-Liisa', 'time' => '35 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Kirpeä inkivääri ja makea porkkana luovat täydellisen liiton tässä lämmittävässä keitossa.', 'ingredients' => ['700 g porkkanoita, siivuina','1 sipuli','3 valkosipulinkynttä','4 cm tuoretta inkivääriä, raastettuna','1 l kasvislientä','2 dl kookoskermaa','2 rkl oliiviöljyä','Suolaa, pippuria','Korianteria tai persiljaa tarjoiluun'], 'steps' => ['Kuullota sipuli ja valkosipuli öljyssä.','Lisää inkivääri, paistele 1 min.','Lisää porkkanat ja liemi.','Keitä 20 min kunnes porkkanat ovat pehmeitä.','Soseuta keitto sauvasekoittimella.','Lisää kookoskerma ja kuumenna.','Mausta ja tarjoile yrteillä koristeltuna.'], ], [ 'id' => 'homemade_pizza', 'emoji' => '🍕', 'title' => 'Kotitekoinen pizza', 'category' => 'recipes', 'author' => 'Sinikka', 'time' => '1 t 30 min', 'servings' => '2 pizzaa', 'type' => 'recipe', 'desc' => 'Rapea, ohut pohja ja täyteläinen tomaattikastike. Kotipizza voittaa aina.', 'ingredients' => ['3 dl lämmintä vettä','1 tl kuivahiivaa','1 tl sokeria','1 tl suolaa','5 dl vehnäjauhoja','2 rkl oliiviöljyä','Kastike: 400 g tomaattimurskaa, 2 valkosipulinkynttä, oreganoa','Täytteet: mozzarella, mitä haluat'], 'steps' => ['Liuota hiiva lämpimään veteen sokerin kera, odota 5 min.','Lisää suola, öljy ja jauhot, vaivaa 8 min.','Anna kohota 1 tunti.','Keitä tomaattikastike valkosipulilla ja oreganolla 10 min.','Kuumenna uuni 250°C.','Kauli taikina ohueksi, levitä kastike, lisää täytteet.','Paista 10–12 min kunnes pohja on rapea.'], ], [ 'id' => 'oat_blueberry_porridge', 'emoji' => '🫐', 'title' => 'Kaura-mustikkapuuro', 'category' => 'recipes', 'subcategory' => 'vegaaniset', 'author' => 'Maija-Liisa', 'time' => '15 min', 'servings' => '2 annosta', 'type' => 'recipe', 'desc' => 'Täyttävä aamiainen, joka antaa energiaa koko aamulle. Mustikka tekee siitä erityisen.', 'ingredients' => ['2 dl kaurahiutaleita','5 dl maitoa tai kasvijuomaa','½ tl suolaa','1 rkl voita','2 dl mustikkaa (tuore tai pakaste)','Hunajaa tai vaahterasiirappia','Mantelilastuja päälle'], 'steps' => ['Mittaa kaura ja maito kattilaan.','Kuumenna sekoittaen keskilämmöllä.','Kun puuro sakenee (n. 8 min), lisää suola ja voi.','Nosta liedeltä, lisää mustikka.','Sekoita kevyesti — mustikka saa jäädä osittain ehjäksi.','Tarjoile hunajalla ja mantelilastuilla.'], ], [ 'id' => 'chicken_risotto', 'emoji' => '🍚', 'title' => 'Kermainen kanarisotto', 'category' => 'recipes', 'author' => 'Tiina K.', 'time' => '45 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Italialaisittain valmistettu risotto, joka muuttuu kermaisen täydelliseksi kärsivällisyydellä.', 'ingredients' => ['300 g risottoriisiä (arborio)','2 kananrintaa, paloina','1 sipuli','3 valkosipulinkynttä','1 l kuumaa kanaliemi','1 dl valkoviiniä','50 g parmesaania, raastettuna','2 rkl voita','Oliiviöljyä, suolaa, pippuria, timjamia'], 'steps' => ['Ruskista kanapalat öljyssä, ota sivuun.','Kuullota sipuli ja valkosipuli samassa pannussa.','Lisää riisi, paistele 2 min.','Kaada viini, sekoita kunnes imeytyy.','Lisää kuumaa lientä kauhallinen kerrallaan, sekoita jatkuvasti.','Jatka 20–25 min kunnes riisi on al dente.','Lisää kana, parmesaani ja voi. Mausta. Tarjoile.'], ], [ 'id' => 'blueberry_pie', 'emoji' => '🫐', 'title' => 'Mustikkapiirakka', 'category' => 'recipes', 'subcategory' => 'jalkiruuat', 'author' => 'Riitta H.', 'time' => '1 t 10 min', 'servings' => '8 palaa', 'type' => 'recipe', 'desc' => 'Mummolan klassikko — murea taikina ja täyteläinen mustikkatäyte. Parasta vaniljakastikkeen kera.', 'ingredients' => ['Pohja: 150 g voita, 1 dl sokeria, 1 muna, 3½ dl vehnäjauhoja, 1 tl leivinjauhetta','Täyte: 4 dl mustikkaa, ½ dl sokeria, 1 rkl perunajauhoja','Vaniljakastiketta tarjoiluun'], 'steps' => ['Kuumenna uuni 200°C.','Vatkaa voi ja sokeri kuohkeaksi, lisää muna.','Sekoita jauhot ja leivinjauhe joukkoon.','Painele ¾ taikinasta piirasvuoan pohjalle ja reunoille.','Sekoita mustikka, sokeri ja perunajauho, levitä täyte.','Murustele loppu taikina päälle.','Paista 35–40 min kullanruskeaksi.'], ], [ 'id' => 'avocado_pasta', 'emoji' => '🥑', 'title' => 'Kermainen avokadopasta', 'category' => 'recipes', 'subcategory' => 'kasvis', 'author' => 'Sinikka', 'time' => '20 min', 'servings' => '2 annosta', 'type' => 'recipe', 'desc' => 'Nopea arkipäivän pasta, jonka kastike valmistuu siinä ajassa kuin pasta kiehuu.', 'ingredients' => ['200 g pastaa','2 kypsää avokadoa','1 valkosipulinkynsi','1 sitruunan mehu','2 rkl oliiviöljyä','Suolaa, pippuria','Kirsikkatomaatteja, basilikanlehtiä tarjoiluun','Parmesaania päälle'], 'steps' => ['Keitä pasta al denteksi, säästä 1 dl keitinvettä.','Soseuta avokado, valkosipuli, sitruunanmehu ja öljy tehosekoittimessa.','Lisää keitinvettä tarpeen mukaan tasaisen kastikkeen saamiseksi.','Mausta suolalla ja pippurilla.','Sekoita pasta ja kastike.','Koristele tomaateilla, basilikaalla ja parmesaanilla.'], ], [ 'id' => 'meatballs', 'emoji' => '🍖', 'title' => 'Lihapullat ruskeassa kastikkeessa', 'category' => 'recipes', 'author' => 'Pekka', 'time' => '1 t', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Perinteiset kotimaiset lihapullat kermaisen ruskean kastikkeen kera. Tarjoile muusiperunan ja puolukan kera.', 'ingredients' => ['500 g jauhelihaa (naudan ja sian sekoitus)','1 dl korppujauhoja','1 dl maitoa','1 muna','1 sipuli, hienonnettuna','Suolaa, pippuria, maustepippuria','Kastike: 3 dl lihalientä, 1½ dl kermaa, 1 rkl vehnäjauhoja'], 'steps' => ['Liota korppujauhot maidossa.','Sekoita kaikki lihapullan aineet tasaiseksi massaksi.','Pyörittele märillä käsillä tasaisia palloja.','Ruskista lihapullat voissa pannulla.','Siirrä uunivuokaan ja paista 175°C 15 min.','Tee kastike: sekoita jauhot kermaan, lisää liemi, keitä paksuksi.','Tarjoile lihapullat kastikkeessa muusiperunan kera.'], ], [ 'id' => 'cauliflower_soup', 'emoji' => '🥦', 'title' => 'Samettinen kukkakaalikeitto', 'category' => 'recipes', 'subcategory' => 'kasvis', 'author' => 'Sinikka', 'time' => '30 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Silky-sileä kukkakaalikeitto — yllättävän täyteläinen ja herkullinen arki-illalliseksi.', 'ingredients' => ['1 kukkakaali, paloina','1 sipuli','2 valkosipulinkynttä','1 l kasvislientä','2 dl kermaa','2 rkl oliiviöljyä','Suolaa, valkopippuria','Paahdettuja pähkinöitä tai yrttejä tarjoiluun'], 'steps' => ['Kuullota sipuli ja valkosipuli öljyssä.','Lisää kukkakaalipalat ja liemi.','Keitä 20 min kunnes kukkakaali on pehmeää.','Soseuta keitto silkinsileäksi.','Lisää kerma ja kuumenna.','Mausta suolalla ja valkopippurilla.','Tarjoile paahdettujen pähkinöiden kera.'], ], [ 'id' => 'karjalanpaisti', 'emoji' => '🥘', 'title' => 'Karjalanpaisti', 'category' => 'recipes', 'author' => 'Pekka', 'time' => '3–4 t', 'servings' => '6 annosta', 'type' => 'recipe', 'desc' => 'Perinteinen suomalainen pataruoka, joka hautuu uunissa itsekseen. Tuoksu täyttää koko kodin.', 'ingredients' => ['600 g naudanlihaa (esim. lapa), kuutioina','400 g sianlihaa (esim. niska), kuutioina','2 sipulia, lohkottuna','2 porkkanaa, siivuina','2 laakerinlehteä','10 maustepippuria','Suolaa','Vettä tai lihalientä'], 'steps' => ['Kuumenna uuni 175°C.','Laita lihat, sipulit ja porkkanat uunipastaan tai pataan.','Lisää laakerinlehdet ja maustepippurit.','Kaada vettä tai lientä juuri lihan verran.','Mausta suolalla.','Peitä kannella tai foliolla.','Hauduta uunissa 3–4 tuntia kunnes liha on ylikypsää ja irtoaa.','Tarjoile perunoiden ja puolukkahillon kera.'], ], // ── KÄSITYÖT - Neulominen ──────────────────────────────── [ 'id' => 'knitting_scarf', 'emoji' => '🧶', 'title' => 'Helppo huivi aloittelijalle', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Neulo kaunis huivi muutamassa tunnissa — täydellinen ensimmäinen projekti!', 'body' => '
Tarvitset:
Ohje: Luo 20 silmukkaa. Neulo suoraan (edestakaisin oikein silmukoin) kunnes huivi on noin 150 cm pitkä. Päättele silmukat ja viimeistele päät. Valmis! 🎉
Vinkki: Paksuilla langoilla ja suurilla neuloilla saat huivista nopeasti valmiin, vaikka olisit aloittelija.
', ], [ 'id' => 'knitting_mittens', 'emoji' => '🧤', 'title' => 'Peukalolapaset aloittelijalle', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Yksinkertaiset lapaset jotka valmistuvat viikonloppuna — lämpimät ja persoonalliset.', 'body' => 'Tarvitset:
Ohje:
Vinkki: Käytä värillistä lankaa tekemään kirjoneuleraita, niin lapasista tulee yksilölliset! 🎨
', ], [ 'id' => 'knitting_hat', 'emoji' => '🎩', 'title' => 'Yksinkertainen pipo', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Pipo syntyy parissa illassa — kiva lahja tai itselle talven varalle.', 'body' => 'Pipo on helppo ja nopea projekti, täydellinen aloittelijalle!
Tarvitset:
Ohje (koko S/M):
Villasukat ovat yksi neulomisen perinteisimmistä ja palkitsevimmista projekteista.
Tarvikeet:
Rakenne:
Ensimmäinen sukka näyttää hassulta — älä luovuta! Toinen onnistuu jo paremmin. 😄
', ], [ 'id' => 'knitting_basics', 'emoji' => '📚', 'title' => 'Neulomisen perusteet — näin aloitat', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Oletko aina haaveillut neulomisesta mutta et tiedä mistä aloittaa? Tässä kaikki mitä tarvitset tietää.', 'body' => 'Neulominen on rentouttava harrastus, jonka voi aloittaa hyvin pienellä välineistöllä.
Harjoittele ensin pienellä neliöllä ennen kuin alat varsinaiseen projektiin. Pian kädet muistavat liikkeet itsestään! 🧶
', ], [ 'id' => 'knitting_baby_hat', 'emoji' => '👶', 'title' => 'Vauvan neulottu pipo', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Söpö ja pehmeä pipo vauvalle — syntyy muutamassa tunnissa ja on heti käyttövalmis.', 'body' => 'Vauvan pipo on ihanteellinen lahjaprojekti — pieni, nopea ja aina tarpeellinen. Sopii noin 0–6 kk ikäiselle.
Tarvikeet:
Ohje:
💡 Valitse aina pehmeä, ihoa ärsyttämätön lanka vauvoille!
', ], [ 'id' => 'knitting_dishcloth', 'emoji' => '🫧', 'title' => 'Neulottu tiskiliina', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Ekologinen vaihtoehto kertakäyttöisille tiskisienille. Valmistuu tunnissa ja kestää vuosia.', 'body' => 'Neulottu tiskiliina on täydellinen ensiprojekti — yksinkertainen, nopea ja hyödyllinen!
Tarvikeet:
Ohje:
Helmineule tarkoittaa, että molemmat puolet ovat samannäköisiä. Tee useampia eri väreissä! 🎨
', ], [ 'id' => 'knitting_cowl', 'emoji' => '🌀', 'title' => 'Kauluri talveen', 'category' => 'kasityot', 'subcategory' => ['neulominen'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Muhkea kauluri pitää lämpimänä ja on käytännöllisempi kuin huivi — ei liiku paikaltaan!', 'body' => 'Kauluri pysyy paikallaan eikä tipu, toisin kuin huivi. Tämä kauluri on nopea neuloa.
Tarvikeet:
Ohje:
Joustinpäättely antaa kauniin ja joustavan reunuksen. ✨
', ], [ 'id' => 'crochet_basket', 'emoji' => '🧺', 'title' => 'Virkkaa kori langantähteistä', 'category' => 'kasityot', 'subcategory' => ['virkkaus'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Käytä lankatähteet hyödyksi! Kori syntyy muutamassa tunnissa ja sopii kaikkeen säilyttämiseen.', 'body' => 'Tässä pääset eroon lankatähteistä luovasti. Yhdistä eri värejä raidalliseen koriin!
Tarvikeet:
Ohje:
💡 Paksuilla langoilla kori kasvaa nopeasti. Kokeile tehdä eri kokoisia koreja sarjana!
', ], // ── HYVINVOINTI - vinkkejä ──────────────────────────────── [ 'id' => 'tip_morning', 'emoji' => '💡', 'title' => 'Rauhallinen aamurutiini', 'category' => 'hyvinvointi', 'subcategory' => ['mieli'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Pienet muutokset aamurutiiniin tekevät koko päivästä paremman.', 'body' => 'Kokeile näitä vinkkejä parempaan aamuun:
Pienet muutokset, iso vaikutus!
', ], [ 'id' => 'tip_digital_detox', 'emoji' => '📵', 'title' => 'Digipaasto — kokeile viikon ajan', 'category' => 'hyvinvointi', 'subcategory' => ['mieli'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Vähennä ruutuaikaa ja huomaa miten elämä muuttuu. Ei tarvitse lopettaa kokonaan!', 'body' => 'Kokeilin viime kuussa viikon digipaastoa ja muutos oli hämmästyttävä. Tässä omat vinkkini:
Nukuin paremmin jo ensimmäisestä yöstä alkaen. Ja parasta: tajusin, miten paljon aikaa minulla oikeasti on!
', ], [ 'id' => 'tip_budgeting', 'emoji' => '💰', 'title' => 'Säästövinkit arkeen', 'category' => 'hyvinvointi', 'subcategory' => ['arki'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Pienet muutokset arjessa voivat säästää satoja euroja vuodessa — ilman että elämänlaatu kärsii.', 'body' => 'Olen oppinut säästämään arjessa ilman tinkimistä mieluisista asioista. Tässä parhaat vinkkini:
Ja muista: paras kauppa on se johon et mene lainkaan! 😄
', ], [ 'id' => 'tip_balcony_garden', 'emoji' => '🌱', 'title' => 'Kasvimaa parvekkeella', 'category' => 'puutarha', 'subcategory' => ['parvekeviljeily'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Oma tomaatti tai yrtit parvekkeen laatikoista — helpompaa kuin luulet!', 'body' => 'Parvekeviljelyä voi harrastaa vaikka asuisi kerrostalossa. Tässä helppo aloitus:
Oman sadon maku on aivan eri kuin kaupan — kannattaa ehdottomasti kokeilla! 🌞
', ], [ 'id' => 'tip_eco_cleaning', 'emoji' => '♻️', 'title' => 'Ekologinen siivous', 'category' => 'hyvinvointi', 'subcategory' => ['arki'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Puhdista koti turvallisilla aineilla — parempi sinulle, parempi ympäristölle ja halvempaa!', 'body' => 'Tehokkain siivousaine löytyy usein kaapistasi: etikka, ruokasooda ja sitruuna tekevät ihmeitä.
Klooripohjaiset aineet ovat tehokkaita mutta raskaita ympäristölle ja hengitysteille. Useimmissa kotitöissä pärjät ilman niitä. Säilytä ne vain tosi tarpeelliseen.
', ], // ── KÄSITYÖT ───────────────────────────────────────────── [ 'id' => 'craft_crochet_heart', 'emoji' => '🧶', 'title' => 'Virkattu sydän — helppo lahja', 'category' => 'kasityot', 'subcategory' => ['virkkaus'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Pieni virkattu sydän syntyy tunnissa ja on täydellinen lahja tai koriste.', 'body' => 'Tämä söpö sydän on helppo myös aloittelijalle!
Tarvikeet:
Ohje:
💡 Kaksivärinen lanka tekee sydämestä erityisen kauniin!
', ], [ 'id' => 'craft_tote_bag', 'emoji' => '👜', 'title' => 'Ompele oma kangaskassi', 'category' => 'kasityot', 'subcategory' => ['ommelu'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Ekologinen ja persoonallinen kangaskassi onnistuu jopa ilman ompelukonetta — mutta koneella nopeammin!', 'body' => 'Kangaskassi on helppo ensimmäinen ompelutyö. Tarvitset vain suorat saumat!
Tarvikeet:
Ohje:
Vinkki: Pese kangas ennen ompelemista, niin se ei enää kutistu käytössä. 🧺
', ], [ 'id' => 'craft_macrame', 'emoji' => '🎨', 'title' => 'Makramee-seinäkoriste', 'category' => 'kasityot', 'subcategory' => ['askartelu'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Trendikäs makramee-koriste syntyy muutamassa tunnissa ja koristaa seinää vuosia.', 'body' => 'Makramee on solmimistaidetta joka näyttää vaikeammalta kuin onkaan!
Tarvikeet:
Perussolmut:
Ohje:
Tomaatit viihtyvät mainiosti ruukussa, kunhan valitset parvekelajikkeen.
Valitse parveketomaatti tai kirsikkatomaatti — ne kasvavat pieniksi ja sopivat ruukkuun. Isot beef-tomaatit tarvitsevat enemmän tilaa.
Ensimmäinen oma tomaatti on kuin pieni ihme! 🍅
', ], [ 'id' => 'garden_herbs', 'emoji' => '🌿', 'title' => 'Yrttipuutarha ikkunalaudalle', 'category' => 'puutarha', 'subcategory' => ['parvekeviljeily'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Tuoreita yrttejä suoraan ikkunalaudalta — basilika, persilja ja ruohosipuli onnistuvat helposti.', 'body' => 'Yrttipuutarha on helpoin tapa aloittaa viljely. Pienikin ikkunalauta riittää!
Tuore basilika pastaan tai ruohosipuli munakkaaseen — yksinkertaiset ilot ovat parhaita! 🌞
', ], [ 'id' => 'garden_strawberries', 'emoji' => '🍓', 'title' => 'Mansikat parvekkeen laatikoissa', 'category' => 'puutarha', 'subcategory' => ['parvekeviljeily', 'vihannekset'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Mansikat viihtyvät hyvin parvekkeen laatikoissa — kesä on makulomaa kun omat mansikat kypsyvät.', 'body' => 'Mansikka on yksi parhaista parvekekasveista — helppohoitoinen ja tuottava!
Ensimmäinen oma mansikka suusta — sen makua ei unohda! 🍓
', ], [ 'id' => 'garden_sunflowers', 'emoji' => '🌻', 'title' => 'Auringonkukat — ilon kasvattajia', 'category' => 'puutarha', 'subcategory' => ['kukat'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Auringonkukka on helpoin kukkakasvi — siemenet maan ja kesän lopussa on metrin taimi.', 'body' => 'Auringonkukka piristää koko naapuruston ja kasvaa lähes itsekseen!
Elokuussa voit korjata siemenet — ne maistuvat paahdettuna! 🌻
', ], // ── SISUSTUS ───────────────────────────────────────────── [ 'id' => 'deco_flea_market', 'emoji' => '🏺', 'title' => 'Kirpputorilöydöt sisustukseen', 'category' => 'sisustus', 'subcategory' => ['muut'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Parhaat sisustushelmet löytyvät kirpputoreilta — vinkit mitä etsiä ja miten hyödyntää.', 'body' => 'Kirpputorit ovat aarreaittoja sisustajalle. Tässä vinkkini parhaiden löytöjen tekemiseen!
Lähes mikä tahansa kirpparilöytö paranee maalaamalla. Kreidemaali tarttuu hyvin ilman pohjamaaliakin ja luo kauniin mattapinnan. Suosikkivärejäni ovat valkoinen, savia ja tummanvihreä.
Parhaat löydöt odottavat sinua! 🔍
', ], [ 'id' => 'deco_candles', 'emoji' => '🕯️', 'title' => 'Kynttilät tunnelman luojina', 'category' => 'sisustus', 'subcategory' => ['valaistus'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Kynttilät ovat halvin ja tehokkain tapa muuttaa huoneen tunnelma — vinkit niiden käyttöön.', 'body' => 'Kynttilät ovat sisustuksen salaisin ase. Tässä miten käytän niitä kotonani:
Älä laita yksittäisiä kynttilöitä ympäri huonetta — ryhmittele niitä kolmen tai viiden ryhmiin. Eri korkuiset kynttilät samalla tasolla luovat kauniin asetelman.
Hämärässä kynttilänvalossa kaikki tuntuu paremmalta. 🕯️
', ], [ 'id' => 'deco_gallery_wall', 'emoji' => '🖼️', 'title' => 'Gallerioseinä kotiin — näin se onnistuu', 'category' => 'sisustus', 'subcategory' => ['diy'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Gallerioseinä on tehokkain tapa tuoda persoonallisuutta kotiin. Tässä ohjeet suunnitteluun ja toteutukseen.', 'body' => 'Gallerioseinä kuulostaa hankalalta mutta onnistuu helposti kun suunnittelee ensin!
Oma gallerioseinä on kuin kotimuseo parhaista muistoistasi! 🖼️
', ], // ── KIRJAT ─────────────────────────────────────────────── [ 'id' => 'book_summer_reads', 'emoji' => '📖', 'title' => 'Kesän lukuvinkit — kirjoja jotka upposi', 'category' => 'kirjat', 'subcategory' => ['romaanit'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Kolme kirjaa joihin upposi tänä kesänä — vaihtelevaa luettavaa eri mielentiloihin.', 'body' => 'Kesä on parasta lukuaikaa. Tässä kolme kirjaa jotka pysäyttivät minut:
Pohjoismainen jännityskirjallisuus ei petä koskaan. Tunnelma, kylmä ilmasto ja monimutkaiset hahmot tekevät siitä koukuttavaa. Etsi kirjastosta suomalaisia tai ruotsalaisia rikosromaaneja.
Välillä tarvitaan kirja jossa on hyvä loppu. Romantiikka tai humoristinen kotimainen proosa sopii täydellisesti mökkilaiturin tunnelmaan.
Paras kesäkirja on se johon palaa ajatuksissaan vielä syksyllä. Sellainen löytyy usein yllättäen — anna kirjaston vinkata!
Hyvää lukuhetkeä! 📚
', ], [ 'id' => 'book_audiobooks', 'emoji' => '🎧', 'title' => 'Äänikirjat — luen nyt enemmän kuin koskaan', 'category' => 'kirjat', 'subcategory' => ['muut'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Äänikirjat muuttivat tapani "lukea" — kuuntelen nykyään keittiössä, lenkillä ja käsitöitä tehdessä.', 'body' => 'Äänikirjat ovat mullistaneet lukuharrastukseni. Tässä miksi suosittelen:
Vinkki: Aloita hitaammalla puhenopeudella (0.9×) jos et ole tottunut. Sopeutut nopeasti ja voit nostaa tahdin! 🎧
', ], // ── HYVINVOINTI ────────────────────────────────────────── [ 'id' => 'wellness_morning', 'emoji' => '🌅', 'title' => 'Aamurutiini joka oikeasti toimii', 'category' => 'hyvinvointi', 'subcategory' => ['mieli'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Kolme pientä muutosta aamuun jotka muuttivat koko päivän laadun.', 'body' => 'Olen kokeillut monia aamurutiinejä. Tässä se mikä oikeasti toimii minulle:
En katso puhelinta ennen kuin olen juonut kahvin. Tuntuu aluksi vaikealta, mutta mieli on paljon rauhallisempi koko aamun.
Ennen kahvia. Keho on kuivunut yön aikana ja vesi käynnistää aineenvaihdunnan.
Minulle se on rauhallinen kahvihetki ikkunan ääressä. Sinulle se voi olla lenkki, meditaatio tai hyvä podcast. Viisi minuuttia riittää.
Et tarvitse täydellistä aamurutiinia — tarvitset vain kolme pientä asiaa. 🌅
', ], [ 'id' => 'wellness_yoga', 'emoji' => '🧘', 'title' => 'Kotijoogaa aloittelijalle — 15 min päivässä', 'category' => 'hyvinvointi', 'subcategory' => ['liikunta'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Et tarvitse joogastudiota tai erityisiä vaatteita — tässä helppo aloitus kotona.', 'body' => 'Jooga tuntuu pelottavalta aloittaa mutta on itse asiassa hyvin helppo! Tässä oma 15 min aloittelijan ohjelma:
Polvillaan, istuen kantapäillä, kädet edessä lattialla. Hengitä syvään 5 kertaa. Rentouttaa selkää ja rauhoittaa mielen.
Kädet ja jalat lattialla, lantio ylös — muodostuu kolmio. Kantapäät kohti lattiaa (ei haittaa vaikka eivät yletä). 5 hengitystä.
Hyvä asento alaraajoihin. Yksi jalka eteen, toinen taakse, kädet ylös. Pidä 5 hengitystä, vaihda puoli.
YouTube on täynnä ilmaisia aloittelijaohjeita — hae "jooga aloittelijoille suomi"! 🧘
', ], [ 'id' => 'wellness_sleep', 'emoji' => '😴', 'title' => 'Parempi uni — vinkit jotka toimivat', 'category' => 'hyvinvointi', 'subcategory' => ['uni'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Nukuin huonosti vuosia ennen kuin opin nämä yksinkertaiset asiat.', 'body' => 'Uni on tärkein hyvinvoinnin pilari, mutta niin monelle se on haasteellista. Tässä mitä minulle toimi:
Nukkuminen ei parane yhdessä yössä, mutta jo viikon rutiinilla huomaa eron. 😴
', ], [ 'id' => 'wellness_forest', 'emoji' => '🌲', 'title' => 'Metsäkävelyjen taika', 'category' => 'hyvinvointi', 'subcategory' => ['liikunta', 'mieli'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Tutkimukset vahvistavat sen minkä jokainen suomalainen jo tietää: metsässä oleminen tekee hyvää.', 'body' => 'Olen alkanut käydä metsässä vähintään kerran viikossa, ja muutos hyvinvoinnissani on selvä.
En lataa podcastia. En kuuntele musiikkia. Vain minä ja metsä. Aluksi hiljaisuus tuntuu oudolta — pian se tuntuu parhaimmalta asialta maailmassa.
Jopa 20 minuuttia metsässä laskee verenpainetta. Kokeile! 🌲
', ], // ── LEMMIKIT ───────────────────────────────────────────── [ 'id' => 'pet_dog_training', 'emoji' => '🐕', 'title' => 'Koiran peruskoulutus kotona', 'category' => 'lemmikit', 'subcategory' => ['koirat'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Istu, maahan ja paikka onnistuvat kotona ilman kouluttajaa — kärsivällisyys on avain.', 'body' => 'Kouluttanut koiriani itse jo 15 vuotta. Tässä parhaat peruskäskyt ja vinkit:
Koiran kouluttaminen on mukavaa yhdessäoloa. Nauti siitä! 🐕
', ], [ 'id' => 'pet_cat_enrichment', 'emoji' => '🐈', 'title' => 'Kissan elämää rikastuttavat aktiviteetit', 'category' => 'lemmikit', 'subcategory' => ['kissat'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Sisäkissa tarvitsee virikkeita — nämä helpot ideat pitävät kissan virkeänä ja onnellisena.', 'body' => 'Sisäkissa elää pitkän ja terveen elämän, mutta tarvitsee virikkeita. Tässä mitä meillä toimii:
15 minuuttia leikkiä päivässä tekee isoja ihmeitä! 🐈
', ], [ 'id' => 'pet_winter', 'emoji' => '❄️', 'title' => 'Lemmikki talvella — vinkit kylmään aikaan', 'category' => 'lemmikit', 'subcategory' => ['muut'], 'author' => 'Tiina K.', 'type' => 'post', 'desc' => 'Kylmä talvi tuo omat haasteensa lemmikkien hoidossa — nämä vinkit auttavat.', 'body' => 'Suomen talvi on rankka sekä meille että lemmikeillämme. Tässä mitä olen oppinut:
Lemmikki ei valita — sinun tehtäväsi on huomata! ❄️
', ], // ── PUUTARHA - Huonekasvit ─────────────────────────────── [ 'id' => 'plant_beginners', 'emoji' => '🪴', 'title' => 'Helpoimmat huonekasvit — ei voi tappaa', 'category' => 'puutarha', 'subcategory' => ['huonekasvit'], 'author' => 'Sinikka', 'type' => 'post', 'desc' => 'Jos olet tappanu jokaisen kasvin jonka olet ostanut, nämä lajikkeet ovat sinulle.', 'body' => 'Kaikki voivat kasvattaa huonekasveja! Avain on valita oikea kasvi oikeaan paikkaan.
Liikakastelu tappaa enemmän kasveja kuin kuivuus. Epäillessäsi — älä kastele. Enemmän on harvoin parempi. 💧
', ], [ 'id' => 'plant_monstera', 'emoji' => '🌿', 'title' => 'Monstera — kuningattaren hoito-opas', 'category' => 'puutarha', 'subcategory' => ['huonekasvit'], 'author' => 'Maija-Liisa', 'type' => 'post', 'desc' => 'Monstera deliciosa on tämän hetken suosituin huonekasvi — ja hyvästä syystä. Tässä kaikki mitä sen hoidosta pitää tietää.', 'body' => 'Monstera on upea kasvi johon rakastuin heti. Tässä mitä olen oppinut sen hoidosta:
Epäsuora kirkas valo on paras. Suora aurinko polttaa lehdet. Sopii hyvin ikkunan viereen etelä- tai länsiseinustalle.
Kastele kun ylimmäinen 5 cm multaa on kuivaa. Talvella harvemmin kuin kesällä. Käytä huoneenlämpöistä vettä.
Kasvukaudella (maalis–elokuu) kerran kuussa nesteylannoitteella. Talvella ei tarvita.
Monstera haluaa kiivetä. Sammalpatsas (moss pole) pitää kasvun pystysuorana ja lehdet kasvavat isommiksi!
Palkitsevin kasvi jonka minulla on. 🌿
', ], [ 'id' => 'plant_seeds', 'emoji' => '🌱', 'title' => 'Kasvien kasvatus siemenistä — kevätprojekti', 'category' => 'puutarha', 'subcategory' => ['vihannekset'], 'author' => 'Riitta H.', 'type' => 'post', 'desc' => 'Oman kasvin kasvattaminen siemenestä on tyydyttävintä mitä puutarhassa voi tehdä.', 'body' => 'Kun ensimmäinen oma taimi nousee mullasta, tuntuu kuin olisi luonut jotain ihmeellistä.
Helmikuu–maaliskuu on hyvä aika aloittaa sisäkylvöt. Taimet siirretään ulos toukokuussa hallojen jälkeen.
Aloita pienestä, nauti prosessista! 🌱
', ], ]; } function getOrInitPosts(): array { if (!file_exists(DATA_DIR . 'posts.json')) { $posts = defaultPosts(); writeData('posts.json', $posts); return $posts; } $posts = readData('posts.json', []); $changed = false; $result = []; foreach ($posts as $post) { $cat = $post['category'] ?? ''; if (array_key_exists($cat, POST_CATEGORY_MAP)) { $newCat = POST_CATEGORY_MAP[$cat]; if ($newCat === null) { $changed = true; continue; } // poista postaus $post['category'] = $newCat; $changed = true; } $result[] = $post; } if ($changed) writeData('posts.json', $result); return $result; } function getOrInitCategories(): array { if (!file_exists(DATA_DIR . 'categories.json')) { $cats = defaultCategories(); writeData('categories.json', $cats); return $cats; } $cats = readData('categories.json', []); // Merge in subcategories (and new ones) from defaults if missing $defaults = defaultCategories(); $defaultMap = []; foreach ($defaults as $d) { $defaultMap[$d['id']] = $d; } $changed = false; foreach ($cats as &$cat) { $defSubs = $defaultMap[$cat['id']]['subcategories'] ?? []; if (!isset($cat['subcategories'])) { $cat['subcategories'] = $defSubs; $changed = true; } else { // Add any new subcategory ids from defaults that don't exist yet $existingIds = array_column($cat['subcategories'], 'id'); foreach ($defSubs as $ds) { if (!in_array($ds['id'], $existingIds)) { $cat['subcategories'][] = $ds; $changed = true; } } } } unset($cat); // Remove deprecated/merged categories $before = count($cats); $cats = array_values(array_filter($cats, fn($c) => !in_array($c['id'], REMOVED_CATEGORIES))); if (count($cats) !== $before) $changed = true; // Append brand-new default categories that don't exist in file yet $existingCatIds = array_column($cats, 'id'); foreach ($defaults as $def) { if (!in_array($def['id'], $existingCatIds)) { $cats[] = $def; $changed = true; } } if ($changed) writeData('categories.json', $cats); return $cats; } // ─── Routing ─────────────────────────────────────────────────── switch ($action) { case 'posts': ok(['posts' => getOrInitPosts()]); case 'categories': ok(['categories' => getOrInitCategories()]); case 'likes': $likes = readData('likes.json', new stdClass()); $userLikes = $_SESSION['user_likes'] ?? []; ok(['likes' => $likes, 'userLikes' => $userLikes]); case 'toggle_like': $postId = $body['postId'] ?? ''; if (!$postId) err('Missing postId'); $likes = readData('likes.json', []); $userLikes = $_SESSION['user_likes'] ?? []; $idx = array_search($postId, $userLikes, true); if ($idx === false) { $likes[$postId] = ($likes[$postId] ?? 0) + 1; $userLikes[] = $postId; $liked = true; } else { $likes[$postId] = max(0, ($likes[$postId] ?? 1) - 1); array_splice($userLikes, $idx, 1); $liked = false; } $_SESSION['user_likes'] = array_values($userLikes); writeData('likes.json', $likes); ok(['liked' => $liked, 'count' => $likes[$postId] ?? 0]); case 'comments': $postId = $_GET['postId'] ?? ''; $comments = readData('comments.json', []); ok(['comments' => $comments[$postId] ?? []]); case 'add_comment': $postId = $body['postId'] ?? ''; $name = trim($body['name'] ?? ''); $text = trim($body['text'] ?? ''); if (!$postId || !$name || !$text) err('Missing fields'); $now = time(); $win = 10 * 60; $times = array_values(array_filter($_SESSION['comment_times'] ?? [], fn($t) => $now - $t < $win)); if (count($times) >= 3) err('Liian monta kommenttia. Odota hetki.'); $times[] = $now; $_SESSION['comment_times'] = $times; $comments = readData('comments.json', []); if (!isset($comments[$postId])) $comments[$postId] = []; $comment = [ 'name' => htmlspecialchars($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'text' => htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'time' => date('d.m.Y'), ]; $comments[$postId][] = $comment; writeData('comments.json', $comments); ok(['comment' => $comment]); case 'add_post': $post = $body['post'] ?? []; if (empty($post['title'])) err('Missing title'); $post['id'] = preg_replace('/[^a-z0-9_]/', '', strtolower($post['id'] ?? '')) ?: 'post_' . time(); $posts = getOrInitPosts(); $posts[] = $post; writeData('posts.json', $posts); ok(); case 'update_post': if (!isAdmin()) err('Unauthorized', 403); $post = $body['post'] ?? []; if (empty($post['id'])) err('Missing id'); $posts = getOrInitPosts(); foreach ($posts as &$p) { if ($p['id'] === $post['id']) { $p = $post; break; } } unset($p); writeData('posts.json', $posts); ok(); case 'delete_post': if (!isAdmin()) err('Unauthorized', 403); $id = $body['id'] ?? ''; if (!$id) err('Missing id'); $posts = array_values(array_filter(getOrInitPosts(), fn($p) => ($p['id'] ?? '') !== $id)); writeData('posts.json', $posts); ok(); case 'save_categories': if (!isAdmin()) err('Unauthorized', 403); writeData('categories.json', $body['categories'] ?? []); ok(); case 'seed_posts': if (!isAdmin()) err('Unauthorized', 403); $posts = defaultPosts(); writeData('posts.json', $posts); ok(['count' => count($posts)]); case 'seed_categories': if (!isAdmin()) err('Unauthorized', 403); $cats = defaultCategories(); writeData('categories.json', $cats); ok(['count' => count($cats)]); case 'admin_login': if (($body['password'] ?? '') === ADMIN_PASSWORD) { $_SESSION['tykkaafi_admin'] = true; ok(); } err('Väärä salasana'); case 'admin_logout': $_SESSION['tykkaafi_admin'] = false; ok(); case 'admin_check': ok(['loggedIn' => isAdmin()]); default: err('Unknown action'); }