Spaces:
Running
Running
Download src/app.js from SoSa123456/greenroot: direct link, hf CLI and curl.
- Browser
- Download file 31 kB
-
https://huggingface.co/spaces/SoSa123456/greenroot/resolve/main/src/app.js
- Command line
-
hf download hf://spaces/SoSa123456/greenroot/src/app.js
-
curl -L -o app.js https://huggingface.co/spaces/SoSa123456/greenroot/resolve/main/src/app.js
31 kB
| /* GreenRoot app: i18n engine, nav, counters, reveal, chat, career, mood, charts, form */ | |
| (function(){ | |
| ; | |
| /* ---------- safe storage ---------- */ | |
| var store = (function(){ try{ var s=window.localStorage; s.setItem("__gr","1"); s.removeItem("__gr"); return s; }catch(e){ return null; } })(); | |
| /* ---------- language ---------- */ | |
| var lang = "en"; | |
| var saved = store ? store.getItem("gr-lang") : null; | |
| if (saved && ["en","fa","tr"].indexOf(saved)>-1) lang = saved; | |
| else { | |
| var nav3 = (navigator.language||""); | |
| if (nav3.indexOf("fa")===0) lang = "fa"; | |
| else if (nav3.indexOf("tr")===0) lang = "tr"; | |
| } | |
| function t(k){ | |
| var d = I18N[lang] || I18N.en; | |
| if (d[k]!==undefined) return d[k]; | |
| if (I18N.en[k]!==undefined) return I18N.en[k]; | |
| return ""; | |
| } | |
| function fmt(n){ return n.toLocaleString("en-US"); } | |
| var raf = (window.requestAnimationFrame | |
| ? function(f){ return window.requestAnimationFrame(f); } | |
| : function(f){ return setTimeout(function(){ f(Date.now()); }, 16); }); | |
| function safe(fn){ try{ fn(); }catch(e){ if (window.console) console.warn("GreenRoot init:", e); } } | |
| function mergeTr(){ | |
| var X = window.GR_EXTRA_TR; | |
| if (!X) return; | |
| CHAT_GREETING.tr = X.greeting; | |
| CHAT_FALLBACK.tr = X.fallback; | |
| X.chips.forEach(function(c){ | |
| var dst = CHAT_CHIPS.find(function(x){ return x.id===c.id; }); | |
| if (dst) dst.tr = c.tr; | |
| }); | |
| Object.keys(X.intents).forEach(function(id){ | |
| var dst = CHAT_INTENTS.find(function(x){ return x.id===id; }); | |
| if (dst) dst.r.tr = X.intents[id]; | |
| }); | |
| Object.keys(X.career||{}).forEach(function(job){ | |
| var s = X.career[job], d = CAREER_PLANS[job]; | |
| if (d){ d.title.tr=s.title; d.s1.tr=s.s1; d.s2.tr=s.s2; d.s3.tr=s.s3; d.story.tr=s.story; } | |
| }); | |
| } | |
| function applyLang(){ | |
| document.documentElement.lang = lang; | |
| document.documentElement.dir = (lang==="fa") ? "rtl" : "ltr"; | |
| document.querySelectorAll("[data-i18n]").forEach(function(el){ | |
| el.innerHTML = t(el.getAttribute("data-i18n")); | |
| }); | |
| document.querySelectorAll("[data-i18n-ph]").forEach(function(el){ | |
| el.setAttribute("placeholder", t(el.getAttribute("data-i18n-ph"))); | |
| }); | |
| document.querySelectorAll(".lang-sw .lang").forEach(function(b){ | |
| b.classList.toggle("on", b.getAttribute("data-lang")===lang); | |
| }); | |
| /* COP31 countdown */ | |
| var days = Math.max(0, Math.ceil((new Date(2026,10,9) - new Date())/86400000)); | |
| document.querySelectorAll("[data-cop-days]").forEach(function(elm){ | |
| elm.textContent = t("cop.countdown").replace("{d}", days); | |
| }); | |
| var page = document.body.getAttribute("data-page"); | |
| if (page && t("title."+page)) document.title = t("title."+page); | |
| var cs = document.getElementById("chatShellDir"); | |
| if (page==="ai") renderChips(); | |
| applyChatDir(); | |
| if (page==="dashboard" && document.getElementById("chartMonthly")) initDashboard(); | |
| var file = (page === "home" ? "index" : page) + ".html"; | |
| document.querySelectorAll(".links a").forEach(function(a){ | |
| a.classList.toggle("active", a.getAttribute("href") === file); | |
| }); | |
| } | |
| function setLang(l){ | |
| lang = l; | |
| if (store) try{ store.setItem("gr-lang", l); }catch(e){} | |
| safe(applyLang); | |
| } | |
| document.addEventListener("DOMContentLoaded", function(){ | |
| safe(mergeTr); | |
| safe(applyLang); | |
| document.querySelectorAll(".lang-sw .lang").forEach(function(b){ | |
| b.addEventListener("click", function(){ setLang(b.getAttribute("data-lang")); }); | |
| }); | |
| /* burger */ | |
| var nav = document.getElementById("nav"); | |
| var burger = document.getElementById("burger"); | |
| if (burger && nav){ | |
| burger.addEventListener("click", function(){ nav.classList.toggle("open"); }); | |
| nav.querySelectorAll(".links a").forEach(function(a){ | |
| a.addEventListener("click", function(){ nav.classList.remove("open"); }); | |
| }); | |
| } | |
| /* footer year */ | |
| document.querySelectorAll("#year").forEach(function(el){ el.textContent = new Date().getFullYear(); }); | |
| /* reveal on scroll */ | |
| var rvs = document.querySelectorAll(".rv"); | |
| if ("IntersectionObserver" in window){ | |
| var io = new IntersectionObserver(function(entries){ | |
| entries.forEach(function(e){ if (e.isIntersecting){ e.target.classList.add("in"); io.unobserve(e.target); } }); | |
| }, {threshold:.12}); | |
| rvs.forEach(function(el){ io.observe(el); }); | |
| } else { rvs.forEach(function(el){ el.classList.add("in"); }); } | |
| /* animated counters */ | |
| function runCounter(el){ | |
| var target = parseFloat(el.getAttribute("data-count"))||0; | |
| var pre = el.getAttribute("data-prefix")||"", suf = el.getAttribute("data-suffix")||""; | |
| var dur = 1500, t0 = null; | |
| function step(ts){ | |
| if(!t0) t0 = ts; | |
| var p = Math.min((ts-t0)/dur, 1); | |
| var eased = 1 - Math.pow(1-p, 3); | |
| el.textContent = pre + fmt(Math.round(target*eased)) + suf; | |
| if (p<1) raf(step); | |
| } | |
| raf(step); | |
| } | |
| var counters = document.querySelectorAll("[data-count]"); | |
| if ("IntersectionObserver" in window){ | |
| var cio = new IntersectionObserver(function(entries){ | |
| entries.forEach(function(e){ if (e.isIntersecting){ safe(function(){ runCounter(e.target); }); cio.unobserve(e.target); } }); | |
| }, {threshold:.4}); | |
| counters.forEach(function(el){ cio.observe(el); }); | |
| } else { counters.forEach(function(el){ safe(function(){ runCounter(el); }); }); } | |
| /* page inits */ | |
| var page = document.body.getAttribute("data-page"); | |
| safe(function(){ | |
| if (page==="ai") initAI(); | |
| if (page==="dashboard") initDashboard(); | |
| if (page==="join") initJoin(); | |
| }); | |
| }); | |
| /* ============================================================ | |
| Raha chat (AI page) | |
| ============================================================ */ | |
| var CHAT_INTENTS = [ | |
| { id:"crisis", | |
| kw:["suicide","kill myself","overdose","end it","خودکشی","اوردوز","مردن","تموم کنم","نابود","intihar","aşırı doz","bitirmek istiyorum"], | |
| r:{ en:"I'm really glad you told me. Please reach a human right now:\n• Your local emergency number (DE: 112 · IR: 123)\n• Iran addiction hotline: 09628\n• Your GreenRoot counselor — number is on your card.\nYou are not alone, and this moment will pass. Stay with me — then call.", | |
| fa:"خیلی خوشحالم که گفتی. لطفاً همین حالا به یک انسان وصل شو:\n• شمارهٔ اورژانس محلی (آلمان: ۱۱۲ · ایران: ۱۲۳)\n• خط اعتیاد ایران: ۰۹۶۲۸\n• شمارهٔ مشاور گرینروت روی کارتت هست.\nتنها نیستی و این لحظه رد میشود. با من بمون — بعد زنگ بزن." } }, | |
| { id:"craving", | |
| kw:["craving","crave","urge","relapse now","using again","هوس","کشش","میل","لغزش","مصرف","kriz","canım çekti","kullanmak istiyorum"], | |
| r:{ en:"Craving noticed — that's already a win. Let's ride the wave:\n1. Put the thought on a 10-minute timer.\n2. 4-7-8 breathing ×3 with me: in 4… hold 7… out 8…\n3. Walk toward a crew member or call your buddy.\nCravings peak and pass — usually under 20 minutes. I'm staying right here.", | |
| fa:"هوس رو شنیدم — همین که گفتی یعنی یک برد. موج رو سوار بشیم:\n۱. فکر رو روی تایمر ۱۰ دقیقهای بذار.\n۲. سه بار تنفس ۴-۷-۸ با من: دم ۴… نگه ۷… بازدم ۸…\n۳. برو سمت یکی از اعضای تیم یا به همراهت زنگ بزن.\nهوس اوج میگیره و رد میشه — معمولاً زیر ۲۰ دقیقه. من همینجام." } }, | |
| { id:"mood", | |
| kw:["sad","depress","low","lonely","tired","empty","anxious","غم","افسر","تنها","خسته","حالم","اضطراب","نگران","mutsuz","yalnızım","kötüyüm","yorgunum","kaygılı"], | |
| r:{ en:"Thanks for telling me. Low days are data, not failure.\n• Today's shift is optional — rest is treatment too.\n• Want me to draft a message to your counselor?\n• One small thing: water, window, 5 slow breaths. That's enough for now.", | |
| fa:"ممنون که گفتی. روزهای پایین، دادهان نه شکست.\n• شیفت امروز اختیاریه — استراحت هم بخشی از درمانه.\n• برات یک پیام برای مشاورت بنویسم؟\n• یک کار کوچک: آب، پنجره، ۵ نفس آروم. فعلاً همین کافیه." } }, | |
| { id:"career", | |
| kw:["job","career","work again","cv","resume","interview","شغل","کار","رزومه","مصاحبه","حرفه","kariyer","meslek","mülakat","özgeçmiş"], | |
| r:{ en:"Career rewind is my favourite topic 💼 Your plan has 3 steps: skill map → paid refresh role inside the program → placement with a partner employer.\nTell me your old job — driver, nurse, accountant, mechanic, teacher, cook — or try the Career rewind demo below the chat.", | |
| fa:"بازگشت شغلی موضوع موردعلاقهام هست 💼 طرحت ۳ قدم داره: نقشهٔ مهارت → نقشِ تمرینیِ حقوقدار در برنامه → قرارگیری در یک کارفرمای شریک.\nشغل قبلیت رو بگو — راننده، پرستار، حسابدار، مکانیک، معلم، آشپز — یا دموی «بازگشت شغلی» زیر چت رو امتحان کن." } }, | |
| { id:"balance", | |
| kw:["token","balance","reward","point","wallet","توکن","موجودی","پاداش","امتیاز","کیف","jeton","bakiye","cüzdan"], | |
| r:{ en:"Token balance: 340 🪙\n• This week: +60 (3 shifts)\n• Available rewards: bus pass (40) · phone credit (25) · welding course (220)\nTip: set a goal in the wallet — goals roughly double follow-through.", | |
| fa:"موجودی توکن: 340 🪙\n• این هفته: +60 (۳ شیفت)\n• پاداشهای موجود: کارت اتوبوس (40) · شارژ (25) · دورهٔ جوشکاری (220)\nپیشنهاد: در کیف، یک هدف بذار — هدفگذاری پیگیری رو تقریباً دوبرابر میکنه." } }, | |
| { id:"schedule", | |
| kw:["schedule","shift","time","plan","when","برنامه","شیفت","ساعت","کی","وقت","vardiya","bu hafta"], | |
| r:{ en:"This week's plan:\n• Mon / Wed / Fri 08:00 — cleanup shift (meet at the depot)\n• Tue 17:00 — group therapy\n• Thu 16:00 — Raha session + skills class\nRain plan: indoor sorting line. Missed a day? It happens — tell your mentor, your place is safe.", | |
| fa:"برنامهٔ این هفته:\n• دوشنبه / چهارشنبه / جمعه 08:00 — شیفت پاکسازی (قرار مقابل انبار)\n• سهشنبه 17:00 — گروهدرمانی\n• پنجشنبه 16:00 — جلسهٔ رها + کلاس مهارت\nبرنامهٔ بارونی: خط جداسازی سرپوشیده. یه روز جا موندی؟ پیش میاد — به مربیت بگو، جایگاهت محفوظه." } }, | |
| { id:"trash", | |
| kw:["trash","waste","recycl","collect","زباله","بازیافت","جمع","çöp","atık"], | |
| r:{ en:"Today's crews collected 214 kg: 96 kg PET · 41 kg metal · 52 kg paper · 25 kg glass.\nThat's about €118 to the treatment fund — and enough flake for 3 face shields heading to the workshop. You make that happen.", | |
| fa:"تیمهای امروز ۲۱۴ کیلو جمع کردند: ۹۶ کیلو پت · ۴۱ فلز · ۵۲ کاغذ · ۲۵ شیشه.\nیعنی حدود €118 به صندوق درمان — و خردِ کافی برای ۳ شیلد صورت به سمت کارگاه. این تو اتفاق انداختی." } }, | |
| { id:"about", | |
| kw:["who are you","what are you","raha","رها کیست","تو کی هستی","چی هستی"], | |
| r:{ en:"I'm Raha — \"رهاء\" means freedom. I'm the program's AI companion: craving SOS, mood check-ins, token wallet and career-rewind coach. I'm a tool with human oversight — your counselor and doctors make the clinical calls. My data policy is simple: your story belongs to you.", | |
| fa:"من رها هستم؛ «رهاء» یعنی آزادی. همیار هوش مصنوعی برنامهام: SOS هوس، چک-این خلق، کیف توکن و مربی بازگشت شغلی. من یک ابزار با نظارت انسانیام — تصمیمهای بالینی با مشاور و پزشکت. سیاست دادهام سادهست: داستان زندگیات مال خودته." } }, | |
| { id:"thanks", | |
| kw:["thank","thanks","merci","مرسی","ممنون","متشکر","سپاس","teşekkür","sağol"], | |
| r:{ en:"Anytime. That's what I'm here for — see you at check-in tomorrow? 🌱", | |
| fa:"همیشه در خدمتم. برای همین اینجام — فردا سر چک-این میبینمت؟ 🌱" } } | |
| ]; | |
| var CHAT_FALLBACK = { | |
| en:"I hear you. I'm the demo version — I know cravings, mood, tokens, the weekly plan and career rewind. Try a chip below, or type \"craving\", \"job\", \"balance\"…", | |
| fa:"شنیدمت. من نسخهٔ دمو هستم — از هوس، خلق، توکن، برنامهٔ هفتگی و بازگشت شغلی سر درمیارم. یکی از دکمهها رو بزن، یا بنویس «هوس»، «شغل»، «موجودی»…" | |
| }; | |
| var CHAT_GREETING = { | |
| en:"Hi, I'm Raha 🌱 GreenRoot's recovery companion. How are you feeling today? Tap a chip below or just type — English or فارسی, both work.", | |
| fa:"سلام، من رها هستم 🌱 همیار بهبودی گرینروت. امروز حالت چطوره؟ از دکمههای پایین استفاده کن یا آزادانه بنویس — فارسی یا English، هر دو جواب میده." | |
| }; | |
| var CHAT_CHIPS = [ | |
| { id:"craving", en:"I have a craving", fa:"هوس دارم" }, | |
| { id:"mood", en:"I feel low", fa:"حالم پایینه" }, | |
| { id:"career", en:"Career rewind", fa:"بازگشت شغلی" }, | |
| { id:"balance", en:"Token balance", fa:"موجودی توکن" }, | |
| { id:"schedule",en:"This week's plan",fa:"برنامهٔ این هفته" } | |
| ]; | |
| var chatBody, chatInput; | |
| function el(id){ return document.getElementById(id); } | |
| function chatMsg(text, who){ | |
| var m = document.createElement("div"); | |
| m.className = "msg " + who; | |
| m.textContent = text; | |
| chatBody.appendChild(m); | |
| chatBody.scrollTop = chatBody.scrollHeight; | |
| return m; | |
| } | |
| function botReply(intentId, userText){ | |
| var typing = document.createElement("div"); | |
| typing.className = "msg bot typing"; | |
| typing.innerHTML = "<span></span><span></span><span></span>"; | |
| chatBody.appendChild(typing); | |
| chatBody.scrollTop = chatBody.scrollHeight; | |
| var resp, d = I18N[lang]||I18N.en; | |
| if (intentId){ resp = CHAT_INTENTS.find(function(i){return i.id===intentId;}).r[lang] || CHAT_INTENTS.find(function(i){return i.id===intentId;}).r.en; } | |
| else { | |
| var lower = (userText||"").toLowerCase(); | |
| var hit = CHAT_INTENTS.find(function(i){ return i.kw.some(function(k){ return lower.indexOf(k.toLowerCase())>-1; }); }); | |
| resp = hit ? (hit.r[lang]||hit.r.en) : (CHAT_FALLBACK[lang]||CHAT_FALLBACK.en); | |
| } | |
| setTimeout(function(){ typing.remove(); chatMsg(resp,"bot"); }, 800 + Math.random()*500); | |
| } | |
| function routeIntent(intentId){ | |
| var chip = CHAT_CHIPS.find(function(c){return c.id===intentId;}); | |
| var label = chip ? (chip[lang]||chip.en) : intentId; | |
| chatMsg(label,"user"); | |
| botReply(intentId,null); | |
| } | |
| function renderChips(){ | |
| var wrap = el("chatChips"); | |
| if (!wrap) return; | |
| wrap.innerHTML = ""; | |
| CHAT_CHIPS.forEach(function(c){ | |
| var b = document.createElement("button"); | |
| b.type = "button"; | |
| b.textContent = c[lang]||c.en; | |
| b.addEventListener("click", function(){ routeIntent(c.id); }); | |
| wrap.appendChild(b); | |
| }); | |
| } | |
| function sendChat(){ | |
| var v = (chatInput.value||"").trim(); | |
| if (!v) return; | |
| chatMsg(v,"user"); | |
| chatInput.value = ""; | |
| botReply(null,v); | |
| } | |
| function initAI(){ | |
| chatBody = el("chatBody"); chatInput = el("chatInput"); | |
| var shell = document.querySelector(".chat-shell"); | |
| if (shell){ shell.removeAttribute("dir"); } | |
| applyChatDir(); | |
| chatMsg(CHAT_GREETING[lang]||CHAT_GREETING.en,"bot"); | |
| renderChips(); | |
| var send = el("chatSend"); | |
| if (send) send.addEventListener("click", sendChat); | |
| if (chatInput) chatInput.addEventListener("keydown", function(e){ if (e.key==="Enter") sendChat(); }); | |
| /* career rewind */ | |
| var plans = CAREER_PLANS; | |
| var picker = el("careerPicker"), planBox = el("careerPlan"); | |
| if (picker && planBox){ | |
| picker.querySelectorAll("button").forEach(function(b){ | |
| b.addEventListener("click", function(){ | |
| picker.querySelectorAll("button").forEach(function(x){ x.classList.remove("on"); }); | |
| b.classList.add("on"); | |
| var job = b.getAttribute("data-job"); | |
| renderPlan(job); | |
| }); | |
| }); | |
| } | |
| function renderPlan(job){ | |
| var p = plans[job]; | |
| planBox.style.display = "block"; | |
| planBox.innerHTML = | |
| "<h4>"+ (p.title[lang]||p.title.en) +"</h4>" + | |
| "<ol><li>"+ (p.s1[lang]||p.s1.en) +"</li><li>"+ (p.s2[lang]||p.s2.en) +"</li><li>"+ (p.s3[lang]||p.s3.en) +"</li></ol>" + | |
| "<p class='small' style='margin-top:12px;color:var(--teal);font-weight:700'>💡 "+ (p.story[lang]||p.story.en) +"</p>"; | |
| } | |
| /* mood tracker */ | |
| var moodLog = []; | |
| var moodRow = el("moodRow"), moodMsg = el("moodMsg"); | |
| if (moodRow && moodMsg){ | |
| moodRow.querySelectorAll("button").forEach(function(b){ | |
| b.addEventListener("click", function(){ | |
| moodLog.push(parseInt(b.getAttribute("data-mood"),10)); | |
| if (moodLog.length>12) moodLog.shift(); | |
| renderMood(); | |
| }); | |
| }); | |
| } | |
| function renderMood(){ | |
| if (!moodLog.length) return; | |
| var MOOD_TXT = { | |
| 1:{en:"Very low — you're not alone. Tap \"I have a craving\" / SOS or call your counselor. I've flagged a check-in.",fa:"خیلی پایین — تنها نیستی. «هوس دارم»/SOS رو بزن یا به مشاورت زنگ بزن. یک چک-این علامت خوردم."}, | |
| 2:{en:"Low — thanks for logging. Consider an SOS session or an early night.",fa:"پایین — ممنون که ثبت کردی. یک جلسهٔ SOS یا زودخوابیدن رو در نظر بگیر."}, | |
| 3:{en:"Okay days count too. Logged.",fa:"روزهای معمولی هم میشمارند. ثبت شد."}, | |
| 4:{en:"Good — noted. Keep the rhythm.",fa:"خوب — ثبت شد. ریتم رو نگه دار."}, | |
| 5:{en:"Great day — logged. Streaks are how recovery is built. 🌱",fa:"روزِ خوبی بود — ثبت شد. رشتهها، بهبودی را میسازند. 🌱"} | |
| }; | |
| var last = moodLog[moodLog.length-1]; | |
| var txt = (lang==="tr" && window.GR_EXTRA_TR && window.GR_EXTRA_TR.mood[last]) || MOOD_TXT[last][lang] || MOOD_TXT[last].en; | |
| var trend = moodLog.length>1 ? (moodLog[moodLog.length-1]>=moodLog[moodLog.length-2] ? " ↗" : " ↘") : ""; | |
| /* sparkline */ | |
| var w=220,h=44,pts=""; | |
| moodLog.forEach(function(v,i){ | |
| var x = moodLog.length===1 ? w/2 : (i/(moodLog.length-1))*(w-8)+4; | |
| var y = h-6-((v-1)/4)*(h-12); | |
| pts += x.toFixed(1)+","+y.toFixed(1)+" "; | |
| }); | |
| var svg = "<svg width='"+w+"' height='"+h+"' viewBox='0 0 "+w+" "+h+"'><polyline points='"+pts+"' fill='none' stroke='#16A34A' stroke-width='2.5' stroke-linecap='round'/></svg>"; | |
| moodMsg.innerHTML = txt + trend + "<br>" + svg + "<br><span class='muted'>" + moodLog.length + " × " + (lang==="fa"?"چک-این":"check-ins") + "</span>"; | |
| } | |
| } | |
| var CAREER_PLANS = { | |
| driver:{ title:{en:"🚚 Driver — rewind plan",fa:"🚚 راننده — طرح بازگشت"}, | |
| s1:{en:"Licence & record check: we verify your licence status and the skills fund covers any refresher course.",fa:"بررسی گواهینامه و سوابق: وضعیت گواهینامهات را بررسی میکنیم و صندوق مهارت هزینهٔ دورهٔ تجدید را میپوشاند."}, | |
| s2:{en:"6 paid weeks as relief driver for the cleanup crews' logistics — with a clean-shift certificate.",fa:"۶ هفتهٔ حقوقدار بهعنوان رانندهٔ جانشین در حملونقل تیمهای پاکسازی — با گواهی شیفتهای تمیز."}, | |
| s3:{en:"Raha rebuilds your CV around 12+ months of documented reliability, then places you with partner logistics firms.",fa:"رها رزومهات را حول ۱۲+ ماه اتکاپذیریِ مستند بازمیسازد و بعد با شرکتهای حملونقل شریک قرارت میدهد."}, | |
| story:{en:"Your time on the crew isn't a gap — it's proof of reliability.",fa:"بودنت در تیم، یک «وقفه» نیست — مدرک اتکاپذیری است."}}, | |
| nurse:{ title:{en:"🧑⚕️ Nurse / health worker — rewind plan",fa:"🧑⚕️ پرستار / کارمند سلامت — طرح بازگشت"}, | |
| s1:{en:"Re-enter clinical life through lower-intensity roles: hygiene and health-promotion rounds within the crews.",fa:"بازگشت به فضای بالینی از نقشهای کمفشار: گشتیهای بهداشت و ارتقای سلامت درون تیمها."}, | |
| s2:{en:"Skills revalidation: BLS refresher, licence paperwork — supported by the skills fund.",fa:"بازاعتبارسنجی مهارت: دورهٔ BLS، امور پروانه — با پشتیبانی صندوق مهارت."}, | |
| s3:{en:"Supported shifts with partner clinics while Raha builds your gradual full-return plan.",fa:"شیفتهای حمایتی در کلینیکهای شریک، در حالی که رها طرح بازگشت کامل تدریجیات را میسازد."}, | |
| story:{en:"The care you learned to give yourself is now your credential.",fa:"مراقبتی که یاد گرفتی به خودت بدهی، حالا اعتبارت است."}}, | |
| accountant:{ title:{en:"🧾 Accountant — rewind plan",fa:"🧾 حسابدار — طرح بازگشت"}, | |
| s1:{en:"Skills audit with free assessments (Excel, bookkeeping) — a gap map within one week.",fa:"ارزیابی مهارت با تستهای رایگان (اکسل، اصول حسابداری) — نقشهٔ شکافها در یک هفته."}, | |
| s2:{en:"Six weeks of software refresh + a real role: counting and reporting the crews' material revenue.",fa:"شش هفته بهروزرسانی نرمافزار + یک نقش واقعی: شمارش و گزارش درآمد مواد تیمها."}, | |
| s3:{en:"Raha frames your CV around \"12 months of clean data\", then runs interview practice with partner firms.",fa:"رها رزومه را حول «۱۲ ماه دادهٔ تمیز» قاب میگیرد و بعد با شرکتهای شریک مصاحبه تمرینی میگذارد."}, | |
| story:{en:"The discipline you built in recovery is exactly what accounting wants.",fa:"انضباطی که در بهبودی ساختی، دقیقاً همان چیزی است که حسابداری میخواهد."}}, | |
| mechanic:{ title:{en:"🔧 Mechanic — rewind plan",fa:"🔧 مکانیک — طرح بازگشت"}, | |
| s1:{en:"Workshop assessment: tools, safety, current skills — with a technical mentor.",fa:"ارزیابی کارگاهی: ابزار، ایمنی، مهارتهای فعلی — با یک مربی فنی."}, | |
| s2:{en:"Documented experience maintaining the recycling line's machines and vehicles, on site.",fa:"تجربهٔ مستند نگهداشت ماشینآلات و خودروهای خط بازیافت — در همان سایت."}, | |
| s3:{en:"Specialist certificate (e.g. electromechanics) from the skills fund + placement at partner workshops.",fa:"گواهی تخصصی (مثلاً الکترومکانیک) از صندوق مهارت + قرارگیری در کارگاههای شریک."}, | |
| story:{en:"The machines you brought back to life are your best résumé.",fa:"ماشینهایی که به آنها زندگی دوباره دادی، بهترین رزومهٔ تو هستند."}}, | |
| teacher:{ title:{en:"🧑🏫 Teacher — rewind plan",fa:"🧑🏫 معلم — طرح بازگشت"}, | |
| s1:{en:"The honest-narrative plan: how to tell the gap in interviews, practised with Raha until it's strength.",fa:"طرحِ روایت صادقانه: چطور وقفه را در مصاحبه بگویی — با رها تمرین میکنی تا به نقطهقوت تبدیل شود."}, | |
| s2:{en:"Real classroom: environmental-literacy lessons for school groups in the parks your crew cleaned.",fa:"کلاس واقعی: درسهای سواد محیطزیست برای گروههای مدرسهای در پارکهایی که تیمت تمیز کرده."}, | |
| s3:{en:"Teaching-certificate refresh + assistant placement at partner schools, stepping up to full-time.",fa:"روزآمدسازی گواهی تدریس + جایگاه دستیار در مدارس شریک، تا رسیدن به تماموقت."}, | |
| story:{en:"Your recovery is the deepest lesson your future classroom will ever get.",fa:"بهبودیات، عمیقترین درسی است که کلاس آیندهات میگیرد."}}, | |
| cook:{ title:{en:"🍳 Cook — rewind plan",fa:"🍳 آشپز — طرح بازگشت"}, | |
| s1:{en:"Food safety & hygiene certificate — official, funded by the skills fund.",fa:"گواهی ایمنی و بهداشت غذا — رسمی، هزینهاش با صندوق مهارت."}, | |
| s2:{en:"Real volume experience: cooking crew lunches for 50+ people every day.",fa:"تجربهٔ حجم واقعی: پخت ناهار تیمها برای ۵۰+ نفر هر روز."}, | |
| s3:{en:"Supported internship in a partner kitchen with a mentor, then part-time or full-time placement.",fa:"کارآموزی حمایتی در آشپزخانهٔ شریک با مربی، بعد جایگاه پارهوقت یا تماموقت."}, | |
| story:{en:"The meals you cook for the crew are the first order of your comeback.",fa:"غذاهایی که برای تیم میپزی، اولین سفارشِ بازگشت تو هستند."}} | |
| }; | |
| function applyChatDir(){ | |
| var shell = document.querySelector(".chat-shell"); | |
| if (shell) shell.setAttribute("dir", lang==="fa" ? "rtl" : "ltr"); | |
| if (chatBody) chatBody.setAttribute("dir", lang==="fa" ? "rtl" : "ltr"); | |
| } | |
| /* ============================================================ | |
| Dashboard charts | |
| ============================================================ */ | |
| var MONTHLY_KG = [1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2400]; | |
| var WASTE_MIX = [ | |
| {label:"PET", v:42, color:"#16A34A"}, | |
| {label:"Paper", v:22, color:"#0D9488"}, | |
| {label:"Metal", v:18, color:"#F59E0B"}, | |
| {label:"Glass", v:11, color:"#3B82F6"}, | |
| {label:"Other", v:7, color:"#94A3B8"} | |
| ]; | |
| var FUND = [ | |
| {v:1900, color:"#16A34A"}, | |
| {v:1250, color:"#F59E0B"}, | |
| {v:600, color:"#0D9488"} | |
| ]; | |
| var REPORT = [ | |
| {m:7, a:98, kg:1600, eur:3200}, | |
| {m:8, a:104, kg:1700, eur:3400}, | |
| {m:9, a:109, kg:1800, eur:3600}, | |
| {m:10,a:113, kg:1900, eur:3800}, | |
| {m:11,a:117, kg:2000, eur:4100}, | |
| {m:12,a:120, kg:2400, eur:4600} | |
| ]; | |
| function renderReportTable(){ | |
| var body = el("reportBody"); | |
| if (!body) return; | |
| body.innerHTML = REPORT.map(function(r){ | |
| return "<tr><td>"+r.m+"</td><td>"+r.a+"</td><td>"+fmt(r.kg)+"</td><td>€"+fmt(r.eur)+"</td></tr>"; | |
| }).join(""); | |
| } | |
| function initDashboard(){ | |
| drawBars(el("chartMonthly"), MONTHLY_KG); | |
| drawDonut(el("chartMix"), WASTE_MIX); | |
| drawHBars(el("chartFund"), FUND); | |
| renderReportTable(); | |
| } | |
| function drawBars(container, values){ | |
| if (!container) return; | |
| var W=460,H=230,pad=34,bw=(W-pad-10)/values.length; | |
| var max = Math.max.apply(null,values)*1.12; | |
| var s = "<svg viewBox='0 0 "+W+" "+H+"' style='width:100%;height:auto'>"; | |
| s += "<line x1='"+pad+"' y1='"+(H-26)+"' x2='"+(W-4)+"' y2='"+(H-26)+"' stroke='#E2E9DD' stroke-width='1.5'/>"; | |
| values.forEach(function(v,i){ | |
| var h = Math.round((v/max)*(H-58)); | |
| var x = pad + i*bw + 3; | |
| var y = H-26-h; | |
| s += "<rect x='"+x+"' y='"+y+"' width='"+(bw-7)+"' height='"+h+"' rx='4' fill='url(#gbars)'><title>"+fmt(v)+" kg</title></rect>"; | |
| if (i%2===0) s += "<text x='"+(x+(bw-7)/2)+"' y='"+(H-10)+"' font-size='10' fill='#8AA395' text-anchor='middle'>"+(i+1)+"</text>"; | |
| if (i===values.length-1) s += "<text x='"+(x+(bw-7)/2)+"' y='"+(y-6)+"' font-size='11' font-weight='700' fill='#166534' text-anchor='middle'>"+fmt(v)+"</text>"; | |
| }); | |
| s += "<defs><linearGradient id='gbars' x1='0' y1='0' x2='0' y2='1'><stop offset='0' stop-color='#22C55E'/><stop offset='1' stop-color='#15803D'/></linearGradient></defs>"; | |
| s += "</svg>"; | |
| container.innerHTML = s; | |
| } | |
| function drawDonut(container, segs){ | |
| if (!container) return; | |
| var total = segs.reduce(function(a,b){return a+b.v;},0); | |
| var R=76,C=2*Math.PI*R, off=0; | |
| var s = "<svg viewBox='0 0 200 200' style='width:min(220px,100%);margin:auto;display:block'>"; | |
| s += "<g transform='rotate(-90 100 100)'>"; | |
| segs.forEach(function(seg){ | |
| var len = seg.v/total*C; | |
| s += "<circle cx='100' cy='100' r='"+R+"' fill='none' stroke='"+seg.color+"' stroke-width='34' stroke-dasharray='"+len.toFixed(1)+" "+(C-len).toFixed(1)+"' stroke-dashoffset='"+(-off).toFixed(1)+"'><title>"+seg.label+" "+seg.v+"%</title></circle>"; | |
| off += len; | |
| }); | |
| s += "</g><text x='100' y='96' text-anchor='middle' font-size='24' font-weight='800' fill='#0C2318'>"+t("dash.ch2.t1")+"</text>"; | |
| s += "<text x='100' y='116' text-anchor='middle' font-size='11' fill='#54685C'>"+t("dash.ch2.t2")+"</text></svg>"; | |
| container.innerHTML = s; | |
| var lg = el("mixLegend"); | |
| if (lg){ | |
| lg.innerHTML = segs.map(function(seg){ | |
| return "<span><i style='background:"+seg.color+"'></i>"+seg.label+" "+seg.v+"%</span>"; | |
| }).join(""); | |
| } | |
| } | |
| function drawHBars(container, items){ | |
| if (!container) return; | |
| var labels = [ t("dash.ch3.l1"), t("dash.ch3.l2"), t("dash.ch3.l3") ]; | |
| var max = Math.max.apply(null, items.map(function(i){return i.v;}))*1.1; | |
| var s = ""; | |
| items.forEach(function(it,i){ | |
| var pct = Math.round(it.v/max*100); | |
| s += "<div style='margin-bottom:14px'>" + | |
| "<div style='display:flex;justify-content:space-between;font-size:13.5px;margin-bottom:5px'><b>"+labels[i]+"</b><span class='muted' dir='ltr'>€"+fmt(it.v)+"/"+(lang==="fa"?"ماه":"mo")+"</span></div>" + | |
| "<div style='background:#EEF3EA;border-radius:99px;height:14px;overflow:hidden' dir='ltr'><div style='width:"+pct+"%;height:100%;border-radius:99px;background:"+it.color+"'></div></div></div>"; | |
| }); | |
| container.innerHTML = s; | |
| } | |
| /* ============================================================ | |
| Join form | |
| ============================================================ */ | |
| function initJoin(){ | |
| var f = el("joinForm"); | |
| if (!f) return; | |
| f.addEventListener("submit", function(e){ | |
| e.preventDefault(); | |
| el("formOk").style.display = "block"; | |
| f.querySelector("button[type=submit]").disabled = true; | |
| }); | |
| } | |
| })(); | |