${project.name}
${project.status}
${project.description}
3D-disain alates ${formatCurrency(project.designPrice)}
/* MTM Project Studio – browser-native sales application. Works without API keys or a backend. Leads are prepared as email drafts and project drafts/favorites are stored only in the visitor's browser. */ const MTM_EMAIL = "info@mtmehitus.ee"; const MTM_PHONE = "3725071042"; const storageKeys = { favorites: "mtm-project-favorites-v1", draft: "mtm-project-draft-v1", leads: "mtm-project-leads-v1" }; const projects = [ { id: "nordic-140", name: "Nordic 140", type: "home", typeLabel: "Eramu", area: 140, rooms: 5, floors: 1, style: "Põhjamaine", material: "Puit ja krohv", designPrice: 1850, status: "Kliendi valitud suund", package: "3D-disain + kohandamine", icon: "fa-house-chimney-window", gradient: "linear-gradient(135deg,#78909c,#263238)", description: "Valgusküllane ühekorruseline lahendus, kus köök ja elutuba avanevad terrassile. Selge ruumiloogika sobib nii linnalähedasele kui maapiirkonna krundile.", customize: "Pindala 115–165 m², 3–4 magamistuba, fassaadimaterjalid, katusekalle, terrassi asukoht ja tehnosüsteemid." }, { id: "compact-92", name: "Compact 92", type: "home", typeLabel: "Eramu", area: 92, rooms: 4, floors: 1, style: "Ajatu", material: "Puitkarkass", designPrice: 1450, status: "Projektina valitud", package: "3D-disain + ehitusprojekt", icon: "fa-house", gradient: "linear-gradient(135deg,#90a4ae,#455a64)", description: "Kompaktne viilkatusega kodu, mis annab väikese ehitusaluse pinna juures hea ruumikasutuse ja kontrollitava ehituseelarve.", customize: "Pindala 78–110 m², 2–3 magamistuba, varjualune, terrass, katusematerjal ja siseplaneering." }, { id: "courtyard-168", name: "Courtyard 168", type: "home", typeLabel: "Eramu", area: 168, rooms: 6, floors: 1, style: "Modernne", material: "Kergplokk ja puit", designPrice: 2450, status: "Täislahenduse lähteprojekt", package: "Disainist teostuseni", icon: "fa-building", gradient: "linear-gradient(135deg,#8d6e63,#3e2723)", description: "L-kujuline sisehooviga lahendus eraldab privaatse magamistubade tiiva ja avatud eluala. Hoov loob tuulevarjulise väliruumi.", customize: "Pindala 145–195 m², garaaž või varjualune, sisehoovi suurus, fassaadikombinatsioon ja ruumide arv." }, { id: "forest-76", name: "Forest 76", type: "summer", typeLabel: "Puhkemaja", area: 76, rooms: 3, floors: 1, style: "Looduslähedane", material: "Termopuit", designPrice: 1350, status: "Kohandatav näidislahendus", package: "3D-kontseptsioon", icon: "fa-tree-city", gradient: "linear-gradient(135deg,#7cb342,#1b5e20)", description: "Kompaktne puhkemaja suure klaasfassaadi, saunaploki ja kaetud terrassiga. Sobib metsa, mere või järve lähedale.", customize: "Pindala 55–95 m², saun, magamiskohtade arv, terrass, vai- või plaatvundament ja aastaringne kasutus." }, { id: "studio-48", name: "Studio 48", type: "outbuilding", typeLabel: "Stuudio / abihoone", area: 48, rooms: 2, floors: 1, style: "Minimalistlik", material: "Puit ja plekk", designPrice: 980, status: "Kohandatav näidislahendus", package: "3D-disain", icon: "fa-warehouse", gradient: "linear-gradient(135deg,#607d8b,#102027)", description: "Paindlik väikehoone kodukontoriks, stuudioks, külalistemajaks või rendipinnaks. Tehnosüsteemid saab lahendada põhiehitisega koos või eraldi.", customize: "Pindala 30–60 m², pesuruum, kööginurk, eraldi sissepääsud, fassaad ja tehniline valmidus." }, { id: "workhub-210", name: "WorkHub 210", type: "office", typeLabel: "Ärihoone", area: 210, rooms: 8, floors: 2, style: "Kaasaegne", material: "Sandwich-paneel ja klaas", designPrice: 2900, status: "Ärilahenduse kontseptsioon", package: "3D-disain + põhiprojekt", icon: "fa-city", gradient: "linear-gradient(135deg,#5c6bc0,#1a237e)", description: "Kahekorruseline paindlik töö- ja teenindushoone, mille ruume saab jagada ühe või mitme kasutaja vahel.", customize: "Pindala 160–320 m², töökohtade arv, lao- või teenindusala, parkimine, reklaampinnad ja energialahendus." } ]; const packageMeta = { concept: { name: "3D-kontseptsioon", duration: "3–6 nädalat", stages: ["Lähteülesanne", "Eskiissuund", "3D-mudel", "Visualiseeringud", "Üleandmine"], descriptions: [ "Vajadused, krundiinfo, ruumiprogramm ja eesmärgid.", "Kaks ruumilist suunda ja eelistatud variandi valik.", "Mahud, fassaadid, avad ja põhikompositsioon.", "Materjalid, valgus ja esitlusvaated.", "Failipakett ning soovitused järgmise etapi jaoks." ], shares: [0.15, 0.2, 0.3, 0.25, 0.1], deliverables: ["Lähteülesanne ja ruumiprogramm", "2 eskiissuunda", "3D-mudel ja 4 vaadet", "Materjali- ja värvikaart"] }, project: { name: "Ehitusprojekt", duration: "8–16 nädalat", stages: ["Lähteülesanne", "3D-kontseptsioon", "Põhijoonised", "Tehniline kooskõla", "Üleandmine"], descriptions: [ "Krundiinfo, ruumiprogramm ja tehnilised eesmärgid.", "Mahud, plaaniloogika, välisilme ja materjalid.", "Plaanid, vaated, lõiked ja põhikonstruktsioonid.", "Konstruktsioonide ja tehnosüsteemide lähteinfo.", "Koondfailid, konsultatsioon ja järgmiste sammude plaan." ], shares: [0.12, 0.22, 0.3, 0.24, 0.12], deliverables: ["Lähteülesanne ja ruumiprogramm", "3D-kontseptsioon ja materjalid", "Plaanid, vaated ja lõiked", "Tehniliste osade lähteinfo"] }, turnkey: { name: "Täislahendus", duration: "9–16 kuud", stages: ["Projekteerimine", "Vundament", "Konstruktsioonid", "Tehnosüsteemid", "Viimistlus ja üleandmine"], descriptions: [ "Disain, projekt, kooskõlastused ja detailne eelarve.", "Pinnasetööd, vundament, soojustus ja aluspõrand.", "Kandekonstruktsioonid, katus, avatäited ja fassaad.", "Elekter, vesi, kanalisatsioon, küte ja ventilatsioon.", "Siseviimistlus, seadistus, kontroll ja dokumentatsioon." ], shares: [0.08, 0.14, 0.3, 0.21, 0.27], deliverables: ["Disain ja ehitusprojekt", "Detailne eelarve ja ajakava", "Ehitusjuhtimine ja kvaliteedikontroll", "Valmis objekt ja üleandmisdokumendid"] } }; let appState = { package: "project", selectedProjectId: null, favoritesOnly: false, estimate: null }; let configState = { area: 120, floors: 1, foundation: "plate", wall: "block", roof: "gable-tin", heating: "geo", finish: "turnkey", region: "harju", use: "home" }; function safeJsonRead(key, fallback) { try { const parsed = JSON.parse(localStorage.getItem(key)); return parsed ?? fallback; } catch { return fallback; } } function saveJson(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } function formatCurrency(value) { return Math.round(value).toLocaleString("et-EE") + " €"; } function packageValueToForm(value) { return packageMeta[value]?.name || "Ehitusprojekt"; } function showToast(message) { const toast = document.getElementById("toast"); toast.textContent = message; toast.classList.add("show"); clearTimeout(showToast.timer); showToast.timer = setTimeout(() => toast.classList.remove("show"), 3200); } window.selectOption = function(type, value, element) { configState[type] = value; element.parentElement.querySelectorAll(".option-card").forEach(card => card.classList.remove("selected")); element.classList.add("selected"); updateCalculator(); }; window.switchTab = function(tabName) { document.querySelectorAll(".tab-btn").forEach(btn => btn.classList.remove("active")); document.querySelectorAll(".tab-content").forEach(content => content.classList.remove("active")); const custom = tabName === "customizer"; document.querySelectorAll(".tab-btn")[custom ? 0 : 1].classList.add("active"); document.getElementById(custom ? "tab-customizer" : "tab-ai-prompt").classList.add("active"); }; window.selectPackage = function(packageId, element) { appState.package = packageId; document.querySelectorAll(".package-option").forEach(card => card.classList.remove("selected")); const target = element || document.querySelector(`[data-package="${packageId}"]`); if (target) target.classList.add("selected"); document.getElementById("form-package").value = packageValueToForm(packageId); updateCalculator(); }; window.choosePackageAndScroll = function(packageId) { selectPackage(packageId); document.querySelector("#kalkulaator").scrollIntoView({behavior: "smooth"}); }; window.updateCalculator = function() { const area = Number(document.getElementById("house-area").value); configState.area = area; configState.foundation = document.getElementById("foundation-type").value; configState.wall = document.getElementById("wall-type").value; configState.roof = document.getElementById("roof-type").value; configState.heating = document.getElementById("heating-type").value; configState.region = document.getElementById("project-region").value; configState.use = document.getElementById("building-use").value; document.getElementById("area-val").textContent = area; let baseRate = 1200; if (configState.floors === 1.5) baseRate += 100; if (configState.floors === 2) baseRate += 180; if (configState.foundation === "plate") baseRate += 60; if (configState.foundation === "pile") baseRate += 90; if (configState.wall === "clt") baseRate += 220; if (configState.wall === "wood") baseRate += 50; if (configState.heating === "geo") baseRate += 80; if (configState.heating === "air-water") baseRate += 50; if (configState.finish === "shell") baseRate -= 350; if (configState.finish === "turnkey") baseRate += 250; if (configState.region === "harju") baseRate *= 1.04; let midpoint; let lowFactor; let highFactor; if (appState.package === "concept") { midpoint = 850 + area * 8.5 + (configState.floors > 1 ? 350 : 0); lowFactor = 0.88; highFactor = 1.18; } else if (appState.package === "project") { midpoint = 2600 + area * 31 + (configState.floors > 1 ? 800 : 0) + (configState.wall === "clt" ? 500 : 0); lowFactor = 0.86; highFactor = 1.22; } else { midpoint = area * baseRate + 6200; lowFactor = 0.9; highFactor = 1.13; } const low = Math.round(midpoint * lowFactor / 50) * 50; const high = Math.round(midpoint * highFactor / 50) * 50; const meta = packageMeta[appState.package]; const selectedProject = projects.find(p => p.id === appState.selectedProjectId); const readiness = Math.min(95, 45 + (selectedProject ? 15 : 0) + (document.getElementById("ai-description").value.trim() ? 15 : 0) + (configState.region ? 10 : 0)); appState.estimate = { midpoint, low, high, baseRate, readiness }; document.getElementById("total-price").textContent = `${formatCurrency(low)} – ${formatCurrency(high)}`; document.getElementById("price-per-m2").textContent = appState.package === "turnkey" ? `hinnanguliselt ${Math.round(baseRate)} € / m²` : `${meta.name.toLowerCase()} valitud mahu põhjal`; meta.shares.forEach((share, index) => { document.getElementById(`stage-label-${index + 1}`).textContent = `${index + 1}. ${meta.stages[index]}`; document.getElementById(`stage-desc-${index + 1}`).textContent = meta.descriptions[index]; document.getElementById(["cost-foundation","cost-walls","cost-roof","cost-utility","cost-finish"][index]).textContent = formatCurrency(midpoint * share); }); meta.deliverables.forEach((item, index) => { document.getElementById(`deliverable-${index + 1}`).textContent = item; }); const useLabels = {home:"eramu", summer:"puhkemaja", office:"ärihoone", outbuilding:"abihoone"}; document.getElementById("brief-title").textContent = `${selectedProject ? selectedProject.name + " põhjal" : "Uus"} ${area} m² ${useLabels[configState.use]} – ${meta.name.toLowerCase()}`; document.getElementById("brief-package").textContent = meta.name; document.getElementById("brief-range").textContent = `${formatCurrency(low)}–${formatCurrency(high)}`; document.getElementById("brief-duration").textContent = meta.duration; document.getElementById("brief-readiness").textContent = `${readiness}%`; }; function getBriefText() { const selected = projects.find(p => p.id === appState.selectedProjectId); const meta = packageMeta[appState.package]; const regionLabels = {harju:"Harjumaa / Tallinn", tartu:"Tartumaa", parnu:"Pärnumaa", rapla:"Raplamaa", other:"Muu Eesti piirkond"}; const useLabels = {home:"Eramu", summer:"Suvila / puhkemaja", office:"Äri- või büroohoone", outbuilding:"Abihoone / stuudio"}; const estimate = appState.estimate || {}; return `MTM EHITUS – PROJEKTI LÄHTEÜLESANNE Koostatud: ${new Date().toLocaleDateString("et-EE")} VALITUD LAHENDUS Pakett: ${meta.name} Lähteprojekt: ${selected ? selected.name : "Personaalne uus lahendus"} Hoone: ${useLabels[configState.use]} Piirkond: ${regionLabels[configState.region]} Pindala: ${configState.area} m² Korruseid: ${configState.floors} TEHNILISED EELISTUSED Vundament: ${configState.foundation} Konstruktsioon: ${configState.wall} Katus: ${configState.roof} Küte: ${configState.heating} Viimistlus: ${configState.finish} IDEE JA ERISOOVID ${document.getElementById("ai-description").value.trim() || "Täpsustatakse konsultatsioonil."} ESIALGNE PLANEERING Hinnavahemik: ${formatCurrency(estimate.low || 0)} – ${formatCurrency(estimate.high || 0)} Ajakava: ${meta.duration} Lähteandmete valmidus: ${estimate.readiness || 0}% Märkus: hinnavahemik on planeerimise lähtehinnang, mitte siduv pakkumine.`; } window.downloadBrief = function() { const blob = new Blob([getBriefText()], {type: "text/plain;charset=utf-8"}); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `MTM-projekti-lahteulesanne-${new Date().toISOString().slice(0,10)}.txt`; link.click(); URL.revokeObjectURL(url); showToast("Lähteülesanne laaditi alla."); }; window.copyBrief = async function() { try { await navigator.clipboard.writeText(getBriefText()); showToast("Projektikokkuvõte kopeeriti."); } catch { showToast("Kopeerimine ei õnnestunud. Laadi fail alla."); } }; window.shareByEmail = function() { const subject = encodeURIComponent(`MTM projektipäring – ${packageMeta[appState.package].name}`); const body = encodeURIComponent(getBriefText()); window.location.href = `mailto:${MTM_EMAIL}?subject=${subject}&body=${body}`; }; window.shareByWhatsApp = function() { const text = encodeURIComponent(getBriefText()); window.open(`https://wa.me/${MTM_PHONE}?text=${text}`, "_blank", "noopener"); }; window.saveDraft = function() { const draft = { config: {...configState}, app: {package: appState.package, selectedProjectId: appState.selectedProjectId}, description: document.getElementById("ai-description").value, savedAt: new Date().toISOString() }; if (saveJson(storageKeys.draft, draft)) showToast("Projekt salvestati selles brauseris."); else showToast("Brauser ei lubanud mustandit salvestada."); }; function restoreDraft() { const draft = safeJsonRead(storageKeys.draft, null); if (!draft?.config) return; Object.assign(configState, draft.config); appState.package = draft.app?.package || "project"; appState.selectedProjectId = draft.app?.selectedProjectId || null; document.getElementById("house-area").value = configState.area || 120; ["foundation","wall","roof","heating"].forEach(key => { const ids = {foundation:"foundation-type",wall:"wall-type",roof:"roof-type",heating:"heating-type"}; if (configState[key]) document.getElementById(ids[key]).value = configState[key]; }); if (configState.region) document.getElementById("project-region").value = configState.region; if (configState.use) document.getElementById("building-use").value = configState.use; document.getElementById("ai-description").value = draft.description || ""; selectPackage(appState.package); if (appState.selectedProjectId) updateStickyProject(); showToast("Varasem projektimustand taastati."); } window.analyzeProjectIdea = function() { const desc = document.getElementById("ai-description").value.trim(); const file = document.getElementById("ai-image-upload").files[0]; if (!desc && !file) { showToast("Lisa kirjeldus või inspiratsioonipilt."); return; } const lower = desc.toLowerCase(); const recommendations = []; if (/modern|minimal|lamekatus/.test(lower)) recommendations.push("Kontseptsioonis tasub kontrollida suure klaaspinna, varjestuse ja lamekatuse tehnilist sobivust."); if (/puit|clt|loodus/.test(lower)) recommendations.push("Puitlahenduse puhul lisa lähteülesandesse fassaadi hooldusklass, tulepüsivus ja niiskusrežiim."); if (/saun|spaa|bassein/.test(lower)) recommendations.push("Märgruumid vajavad varases etapis ventilatsiooni, äravoolude ja niiskustehnilise lahenduse koordineerimist."); if (/äri|büroo|klient|teenindus/.test(lower)) recommendations.push("Ärihoone puhul tuleb kohe määrata kasutajate arv, ligipääsetavus, evakuatsioon ja parkimisvajadus."); if (/odav|eelarve|soodne/.test(lower)) recommendations.push("Eelarve hoidmiseks eelista kompaktset mahtu, korduvaid avatäiteid ja lihtsat katusegeomeetriat."); if (!recommendations.length) recommendations.push("Järgmine mõistlik samm on ruumiprogrammi, krundi piirangute ja soovitud eelarve ühine kinnitamine."); if (file) recommendations.push(`Inspiratsioonipilt „${file.name}” lisatakse konsultatsiooni lähteinfo hulka.`); const selected = projects.find(p => p.id === appState.selectedProjectId); const intro = selected ? `Valitud ${selected.name} annab hea lähtekoha.` : "Kirjelduse põhjal soovitame alustada ruumilise kontseptsiooniga."; document.getElementById("ai-text-output").textContent = `${intro} ${recommendations.join(" ")}`; document.getElementById("ai-analysis-result").style.display = "block"; updateCalculator(); }; function getFavorites() { return safeJsonRead(storageKeys.favorites, []); } window.toggleFavorite = function(id, event) { event?.stopPropagation(); const favorites = getFavorites(); const next = favorites.includes(id) ? favorites.filter(item => item !== id) : [...favorites, id]; saveJson(storageKeys.favorites, next); renderProjects(); showToast(next.includes(id) ? "Projekt lisati lemmikutesse." : "Projekt eemaldati lemmikutest."); }; window.toggleFavoritesOnly = function() { appState.favoritesOnly = !appState.favoritesOnly; renderProjects(); }; window.renderProjects = function() { const query = document.getElementById("catalog-search").value.trim().toLowerCase(); const type = document.getElementById("catalog-type").value; const area = document.getElementById("catalog-area").value; const sort = document.getElementById("catalog-sort").value; const favorites = getFavorites(); let filtered = projects.filter(project => { const searchable = `${project.name} ${project.typeLabel} ${project.style} ${project.material} ${project.description}`.toLowerCase(); const areaMatch = area === "all" || (area === "small" && project.area <= 100) || (area === "medium" && project.area > 100 && project.area <= 160) || (area === "large" && project.area > 160); return (!query || searchable.includes(query)) && (type === "all" || project.type === type) && areaMatch && (!appState.favoritesOnly || favorites.includes(project.id)); }); if (sort === "area-asc") filtered.sort((a,b) => a.area - b.area); if (sort === "area-desc") filtered.sort((a,b) => b.area - a.area); if (sort === "price-asc") filtered.sort((a,b) => a.designPrice - b.designPrice); document.getElementById("catalog-count").textContent = `${filtered.length} lahendust`; document.getElementById("favorite-count").textContent = favorites.length; const grid = document.getElementById("project-grid"); if (!filtered.length) { grid.innerHTML = `
Muuda filtreid või koosta planeerijas personaalne projekt.
${project.status}
${project.description}
3D-disain alates ${formatCurrency(project.designPrice)}