{"id":5723,"date":"2025-08-31T19:44:55","date_gmt":"2025-09-01T00:44:55","guid":{"rendered":"https:\/\/madlysane.com\/?page_id=5723"},"modified":"2025-08-31T20:27:03","modified_gmt":"2025-09-01T01:27:03","slug":"live-self-care","status":"publish","type":"page","link":"https:\/\/madlysane.site\/en\/live-self-care\/","title":{"rendered":"Visitor"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n<title>Mood\u2011Fit Device<\/title>\n<style>\n  :root{\n    --ink:#0b1020;\n    --bg:#0f172a;\n    --text:#e5e7eb;\n    --muted:#94a3b8;\n    --brand:#7A6EE6;\n    --glass:rgba(255,255,255,0.07);\n  }\n  html,body{height:100%;margin:0;font-family:Inter,Segoe UI,Roboto,system-ui,Arial,sans-serif;background:radial-gradient(1200px 800px at 50% 20%, #121936, #0b1020 60%);color:var(--text);}\n  *{box-sizing:border-box}\n\n  header{padding:16px 20px;text-align:center}\n  header h1{margin:0;font-weight:800;font-size:1.35rem;letter-spacing:.02em}\n  header p{margin:6px auto 0;max-width:720px;color:var(--muted)}\n\n  .portal{max-width:1200px;margin:18px auto;padding:16px;display:grid;grid-template-columns:1.1fr .9fr;gap:16px;}\n  @media (max-width:980px){ .portal{grid-template-columns:1fr;gap:12px} }\n\n  \/* Mirror *\/\n  .mirror{\n    position:relative;border-radius:18px;overflow:hidden;\n    background:linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.01));\n    box-shadow:0 20px 60px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.06);\n    aspect-ratio:16\/9;display:flex;align-items:center;justify-content:center;\n  }\n  video{width:100%;height:100%;object-fit:cover;filter:contrast(1.05) saturate(1.02) brightness(1.02)}\n  .overlay-tint{\n    position:absolute;inset:0;background:radial-gradient(60% 80% at 50% 20%, transparent, rgba(0,0,0,0.35));\n    mix-blend:multiply;opacity:.75;pointer-events:none;transition:opacity .25s ease;\n  }\n  .tint-color{position:absolute;inset:0;opacity:.22;transition:opacity .25s ease, background .25s ease;background:#7A6EE6;mix-blend:soft-light;pointer-events:none}\n  .hud{position:absolute;left:14px;bottom:14px;display:flex;gap:8px;flex-wrap:wrap;z-index:2}\n  .chip{background:rgba(255,255,255,0.1);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,0.15);padding:8px 10px;border-radius:999px;font-weight:700;font-size:.85rem;color:#e5e7eb}\n\n  \/* Camera help overlay *\/\n  #cameraHelp{\n    position:absolute;inset:0;display:none;align-items:center;justify-content:center;\n    background:rgba(0,0,0,0.7);color:white;padding:20px;text-align:left;font-size:0.95rem;line-height:1.4;z-index:3;\n  }\n  #cameraHelp strong{color:#fff}\n  #cameraHelp u{color:#c7d2fe}\n\n  \/* Emotion orbs *\/\n  .orbs{position:absolute;inset:0;pointer-events:none;min-height:200px}\n  .orb{\n    position:absolute;width:84px;height:84px;border-radius:50%;backdrop-filter:blur(6px);\n    border:1px solid rgba(255,255,255,0.24);box-shadow:0 10px 30px rgba(0,0,0,0.25);\n    display:flex;align-items:center;justify-content:center;color:#0b1020;font-weight:900;letter-spacing:.02em;\n    text-shadow:0 1px 0 rgba(255,255,255,0.5);cursor:pointer;pointer-events:auto;user-select:none;\n    transition:transform .2s ease, box-shadow .2s ease, filter .2s ease, opacity .35s ease;\n  }\n  .orb:hover{transform:scale(1.06);box-shadow:0 16px 48px rgba(0,0,0,0.35)}\n  .orb .label{font-size:.78rem;text-align:center;line-height:1.1}\n\n  \/* Right column *\/\n  .panel{background:var(--glass);backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,0.12);\n    border-radius:18px;padding:16px;box-shadow:0 20px 60px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.06)}\n  .panel h2{margin:0 0 6px;font-size:1.1rem}\n  .panel p{margin:6px 0;color:var(--muted)}\n  .meta-bar{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 10px}\n  .actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\n  .btn{border:1px solid rgba(255,255,255,0.2);background:rgba(255,255,255,0.06);color:#fff;border-radius:10px;padding:10px 12px;font-weight:800;cursor:pointer;}\n  .btn.primary{background:var(--brand);border-color:var(--brand)}\n  .btn.ghost{background:transparent}\n  .btn:disabled{opacity:.5;cursor:not-allowed}\n\n  \/* Map *\/\n  .map-wrap{position:relative;min-height:380px;border-radius:14px;overflow:hidden;border:1px dashed rgba(255,255,255,0.18);margin-top:10px}\n  svg{display:block;width:100%;height:420px;background:linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))}\n  .node{filter:drop-shadow(0 4px 14px rgba(0,0,0,0.4))}\n  .node circle{stroke:rgba(255,255,255,0.4);stroke-width:2}\n  .node text{font-weight:800;fill:#0b1020}\n  .center text{font-size:14px}\n  .branch text{font-size:12px;pointer-events:none}\n  .locked .branch{filter:blur(6px) saturate(0.7) opacity(0.8)}\n  .lock-veil{position:absolute;inset:0;background:linear-gradient(180deg, rgba(11,16,32,0.0), rgba(11,16,32,0.55));display:flex;align-items:flex-end;justify-content:stretch;padding:0;pointer-events:none;}\n  .cta{width:100%;background:rgba(11,16,32,0.7);backdrop-filter:blur(12px);border-top:1px solid rgba(255,255,255,0.15);padding:12px;display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;pointer-events:auto;}\n  .cta p{margin:0;color:#d1d5db}\n\n  \/* Recommendations & History *\/\n  .recommendations, .history{margin-top:14px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);border-radius:12px;padding:12px}\n  .recommendations h3, .history h3{margin:0 0 8px;font-size:1rem}\n  .recommendations ul, .history ul{list-style:none;margin:0;padding:0;display:grid;gap:8px}\n  .recommendations a{color:#c7d2fe;text-decoration:none}\n  .recommendations a:hover{text-decoration:underline}\n  .hidden{display:none}\n\n  \/* Toast *\/\n  .toast{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:10px 12px;border-radius:10px;backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,0.12);opacity:0;transition:opacity .25s ease;z-index:50}\n  .toast.show{opacity:1}\n<\/style>\n<\/head>\n<body>\n\n<header>\n  <h1>Mood\u2011Fit Device<\/h1>\n  <p>See yourself change as you choose your path. Hover an orb to feel its tone. Click one to generate your Live Self\u2011Care Map. Join to unlock everything.<\/p>\n<\/header>\n\n<section class=\"portal\">\n  <!-- Left: Mirror -->\n  <div class=\"mirror\" id=\"mirror\">\n    <video id=\"video\" autoplay muted playsinline><\/video>\n    <div class=\"overlay-tint\"><\/div>\n    <div class=\"tint-color\" id=\"tint\"><\/div>\n\n    <!-- Camera help overlay -->\n    <div id=\"cameraHelp\">\n      <div>\n        <strong>Camera access is blocked or unavailable.<\/strong><br><br>\n        <u>How to enable it:<\/u><br>\n        \u2022 Make sure you\u2019re on <strong>HTTPS<\/strong> (padlock in address bar).<br>\n        \u2022 When prompted, click <strong>Allow<\/strong>.<br>\n        \u2022 If you missed the prompt:<br>\n          \u2013 <em>Chrome\/Edge:<\/em> Click the camera icon in the address bar \u2192 Allow.<br>\n          \u2013 <em>Firefox:<\/em> Look for the camera prompt at the top \u2192 Allow.<br>\n          \u2013 <em>Safari (Mac):<\/em> Safari \u2192 Settings for This Website \u2192 Camera \u2192 Allow.<br>\n          \u2013 <em>iOS\/Android:<\/em> Allow camera in browser settings.<br><br>\n        Refresh the page after changing the setting.\n      <\/div>\n    <\/div>\n\n    <!-- Floating orbs -->\n    <div class=\"orbs\" id=\"orbs\"><\/div>\n\n    <!-- HUD -->\n    <div class=\"hud\">\n      <div class=\"chip\" id=\"hudStatus\">Allow camera access for the live mirror<\/div>\n      <div class=\"chip\" id=\"hudHint\">Hover orbs \u2022 Click to select<\/div>\n    <\/div>\n  <\/div>\n\n  <!-- Right: Map + CTA + Panels -->\n  <div class=\"panel\">\n    <h2>Your Live Self\u2011Care Map<\/h2>\n    <p id=\"mapIntro\">Pick an emotion to generate a personalized map. Save it to return anytime.<\/p>\n\n    <div class=\"meta-bar\">\n      <div class=\"chip\" id=\"metaMood\">Mood: \u2014<\/div>\n      <div class=\"chip\" id=\"metaColor\">Color: \u2014<\/div>\n      <div class=\"chip\" id=\"streakChip\" title=\"Consecutive days you\u2019ve checked in\">Mood Streak: 0<\/div>\n    <\/div>\n\n    <div class=\"actions\">\n      <button class=\"btn\" id=\"recenterBtn\" disabled>Re\u2011center Map<\/button>\n      <button class=\"btn ghost\" id=\"muteBtn\">Mute<\/button>\n      <button class=\"btn ghost\" id=\"copyLinkBtn\">Copy Portal Link<\/button>\n    <\/div>\n\n    <div class=\"map-wrap locked\" id=\"mapWrap\">\n      <svg id=\"mapSvg\" viewBox=\"0 0 680 420\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" aria-labelledby=\"mapTitle\">\n        <title id=\"mapTitle\">Live Self\u2011Care Map<\/title>\n      <\/svg>\n\n      <!-- Lock + CTA -->\n      <div class=\"lock-veil\" id=\"lockVeil\" aria-hidden=\"true\">\n        <div class=\"cta\">\n          <div>\n            <p><strong>Unlock your map<\/strong> to reveal guided steps, live links, and save this device to your account.<\/p>\n          <\/div>\n          <div class=\"actions\">\n            <button class=\"btn primary\" id=\"unlockBtn\">Join &#038; Unlock<\/button>\n            <button class=\"btn\" id=\"changeBtn\">Change Emotion<\/button>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <!-- Recommendations -->\n    <div id=\"recommendations\" class=\"recommendations hidden\">\n      <h3>Recommended for You<\/h3>\n      <ul id=\"recList\"><\/ul>\n    <\/div>\n\n    <!-- History -->\n    <div id=\"moodHistory\" class=\"history hidden\">\n      <h3>Your Mood Journey<\/h3>\n      <ul id=\"historyList\"><\/ul>\n    <\/div>\n  <\/div>\n<\/section>\n\n<div class=\"toast\" id=\"toast\">Copied<\/div>\n\n<script>\n\/* -----------------------------------------\n   Config\n----------------------------------------- *\/\nconst EMOTIONS = [\n  { key:'Calm',     color:'#88d8b0', tips:['Savor a warm sip for 60 seconds','Scan your body from toes to scalp','Protect this calm: set a 20\u2011minute focus block'] },\n  { key:'Curious',  color:'#56ccf2', tips:['Pick one page to explore for 3 minutes','Ask one better question','Bookmark a surprise you find'] },\n  { key:'Overwhelmed', color:'#f67280', tips:['Try 4\u20114\u20114 breathing (one minute)','Name 3 things you can drop today','Do a 2\u2011minute brain dump'] },\n  { key:'Hopeful',  color:'#ffd166', tips:['Write a one\u2011line intention','Send one message of appreciation','Take a 5\u2011minute future walk (imagine it)'] },\n  { key:'Tender',   color:'#f8c1d4', tips:['Place a hand over your heart','Speak to yourself like a friend','Choose soft light for 10 minutes'] },\n  { key:'Steady',   color:'#a3e635', tips:['Stack one tiny habit now','Tidy one square foot','Schedule a 15\u2011minute focus sprint'] },\n  { key:'Restless', color:'#ef4444', tips:['Stand, stretch, and shake it out','Walk to the nearest window','Change the soundtrack for 3 minutes'] },\n  { key:'Focused',  color:'#a78bfa', tips:['Close 2 tabs you don\u2019t need','Set a 12\u2011minute timer','Pick the \u201cnext visible step\u201d only'] }\n];\n\nconst ROUTES = {\n  tools: \"https:\/\/madlysane.site\/en\/psychology-tools\/\",\n  psychquery: \"https:\/\/madlysane.site\/en\/search\/#gsc.tab=0\/\",\n  pulse: \"https:\/\/madlysane.site\/en\/psychology-pulse-in-depth-reviews\/\",\n  iep: \"https:\/\/madlysane.site\/en\/ard-iep-prep-lab\/\",\n  cipher: \"https:\/\/madlysane.site\/en\/cipher-chase\/\",\n  screener: \"https:\/\/madlysane.site\/en\/cognitive-health-screener\/\",\n  safety: \"https:\/\/madlysane.site\/en\/safe-guide-and-safety-plan\/\",\n  volunteer: \"https:\/\/madlysane.site\/en\/madlysane-volunteer-support-hub\/\",\n  anxiety: \"https:\/\/madlysane.site\/anxiety-techniques\/\",\n  resilience: \"https:\/\/madlysane.site\/building-resilience\/\",\n  challenge: \"https:\/\/madlysane.site\/en\/daily-challenge\/\",\n  mood: \"https:\/\/madlysane.site\/en\/mood-tracker\/\",\n  mindful: \"https:\/\/madlysane.site\/en\/mindfulness-journey\/\",\n  nutrition: \"https:\/\/madlysane.site\/nutrition-and-mental-health-tool\/\"\n};\n\nconst RECS = {\n  Calm: [\n    { text: \"Mindfulness Journey\", url: ROUTES.mindful },\n    { text: \"Daily Challenge\", url: ROUTES.challenge },\n    { text: \"Mood Tracker\", url: ROUTES.mood }\n  ],\n  Curious: [\n    { text: \"PsychQuery\", url: ROUTES.psychquery },\n    { text: \"Psychology Pulse\", url: ROUTES.pulse },\n    { text: \"Cipher Chase\", url: ROUTES.cipher }\n  ],\n  Overwhelmed: [\n    { text: \"Anxiety Techniques (breathing)\", url: ROUTES.anxiety },\n    { text: \"Safe Guide & Safety Plan\", url: ROUTES.safety },\n    { text: \"Cognitive Health Screener\", url: ROUTES.screener }\n  ],\n  Hopeful: [\n    { text: \"Building Resilience\", url: ROUTES.resilience },\n    { text: \"Volunteer Support Hub\", url: ROUTES.volunteer },\n    { text: \"Daily Challenge\", url: ROUTES.challenge }\n  ],\n  Tender: [\n    { text: \"Mindfulness Journey\", url: ROUTES.mindful },\n    { text: \"Safe Guide & Safety Plan\", url: ROUTES.safety },\n    { text: \"Daily Challenge\", url: ROUTES.challenge }\n  ],\n  Steady: [\n    { text: \"Psychological Tools\", url: ROUTES.tools },\n    { text: \"Daily Challenge\", url: ROUTES.challenge },\n    { text: \"Building Resilience\", url: ROUTES.resilience }\n  ],\n  Restless: [\n    { text: \"Anxiety Techniques\", url: ROUTES.anxiety },\n    { text: \"Mood Tracker\", url: ROUTES.mood },\n    { text: \"Daily Challenge\", url: ROUTES.challenge }\n  ],\n  Focused: [\n    { text: \"Psychological Tools\", url: ROUTES.tools },\n    { text: \"PsychQuery\", url: ROUTES.psychquery },\n    { text: \"Mindfulness Journey\", url: ROUTES.mindful }\n  ]\n};\n\nconst ORB_AREA_PADDING = 40; \nconst LOCAL_STATE_KEY = 'ms_live_portal_state_v2';\nconst LOCAL_HISTORY_KEY = 'ms_moodfit_history_v1';\n\n\/* -----------------------------------------\n   Elements\n----------------------------------------- *\/\nconst video = document.getElementById('video');\nconst orbsEl = document.getElementById('orbs');\nconst tintEl = document.getElementById('tint');\nconst hudStatus = document.getElementById('hudStatus');\n\nconst mapWrap = document.getElementById('mapWrap');\nconst mapSvg = document.getElementById('mapSvg');\nconst lockVeil = document.getElementById('lockVeil');\n\nconst metaMood = document.getElementById('metaMood');\nconst metaColor = document.getElementById('metaColor');\nconst streakChip = document.getElementById('streakChip');\n\nconst unlockBtn = document.getElementById('unlockBtn');\nconst changeBtn = document.getElementById('changeBtn');\nconst recenterBtn = document.getElementById('recenterBtn');\nconst muteBtn = document.getElementById('muteBtn');\nconst copyLinkBtn = document.getElementById('copyLinkBtn');\n\nconst recWrap = document.getElementById('recommendations');\nconst recList = document.getElementById('recList');\nconst histWrap = document.getElementById('moodHistory');\nconst historyList = document.getElementById('historyList');\n\nconst toast = document.getElementById('toast');\n\nlet selected = null;  \/\/ { key, color }\nlet muted = false;\n\n\/* -----------------------------------------\n   Webcam init (with help overlay fallback)\n----------------------------------------- *\/\nnavigator.mediaDevices?.getUserMedia?.({ video: true })\n  .then(stream => {\n    video.srcObject = stream;\n    hudStatus.textContent = 'Mirror active';\n  })\n  .catch(() => {\n    hudStatus.textContent = 'Camera blocked \u2014 showing help';\n    document.getElementById('mirror').style.background =\n      'radial-gradient(800px 420px at 50% 30%, rgba(255,255,255,0.06), rgba(255,255,255,0.01))';\n    document.getElementById('cameraHelp').style.display = 'flex';\n  });\n\n\/* -----------------------------------------\n   Emotion orbs\n----------------------------------------- *\/\nfunction spawnOrbs(){\n  orbsEl.innerHTML = '';\n  const rect = orbsEl.getBoundingClientRect();\n  const width = Math.max(rect.width, 600);\n  const height = Math.max(rect.height, 320);\n\n  EMOTIONS.forEach((e) => {\n    const d = document.createElement('div');\n    d.className = 'orb';\n    d.style.background = e.color;\n    d.style.color = getTextColor(e.color);\n    d.style.left = rand(ORB_AREA_PADDING, width - ORB_AREA_PADDING - 84) + 'px';\n    d.style.top  = rand(ORB_AREA_PADDING, height - ORB_AREA_PADDING - 84) + 'px';\n    d.style.opacity = 0;\n    d.innerHTML = `<div class=\"label\">${e.key}<\/div>`;\n    d.addEventListener('mouseenter', () => setTint(e.color, 0.35));\n    d.addEventListener('mouseleave', () => setTint(selected ? selected.color : '#7A6EE6', selected ? 0.22 : 0.12));\n    d.addEventListener('click', () => chooseEmotion(e));\n    orbsEl.appendChild(d);\n    requestAnimationFrame(()=>{ d.style.opacity = 1; });\n    driftOrb(d);\n  });\n}\nfunction driftOrb(el){\n  const sX = 0.3 + Math.random()*0.4;\n  const sY = 0.2 + Math.random()*0.4;\n  let t = Math.random()*1000;\n  function step(){\n    t += 0.016;\n    el.style.transform = `translate(${Math.cos(t*sX)*2}px, ${Math.sin(t*sY)*2}px)`;\n    requestAnimationFrame(step);\n  }\n  step();\n}\n\n\/* -----------------------------------------\n   Tint \/ utilities\n----------------------------------------- *\/\nfunction setTint(hex, strength=0.22){\n  tintEl.style.background = hex;\n  tintEl.style.opacity = strength;\n}\nfunction getTextColor(hex){\n  const m = hex.replace('#','');\n  const r = parseInt(m.substring(0,2),16);\n  const g = parseInt(m.substring(2,4),16);\n  const b = parseInt(m.substring(4,6),16);\n  const lum = 0.299*r + 0.587*g + 0.114*b;\n  return lum < 128 ? '#ffffff' : '#0b1020';\n}\nfunction rand(min,max){ return Math.floor(Math.random()*(max-min+1))+min; }\n\n\/* -----------------------------------------\n   Choose emotion \u2192 map + panels\n----------------------------------------- *\/\nfunction chooseEmotion(e){\n  selected = e;\n  setTint(e.color, 0.28);\n  metaMood.textContent = `Mood: ${e.key}`;\n  metaColor.textContent = `Color: ${e.color}`;\n  recenterBtn.disabled = false;\n\n  \/\/ Render map (locked until unlock)\n  mapWrap.classList.add('locked');\n  lockVeil.style.display = '';\n  renderMap(e);\n\n  \/\/ Save local state + update URL hash\n  const state = { emotion:e.key, color:e.color, t:Date.now() };\n  persist(state);\n  writeHash(state, isUnlockedFromHash());\n\n  \/\/ Recommendations + history\n  showRecommendations(e);\n  updateHistory(e);\n\n  \/\/ If already unlocked, keep it unlocked\n  if (isUnlockedFromHash()) unlockNow();\n}\n\n\/* -----------------------------------------\n   Map rendering (adds a Surprise node)\n----------------------------------------- *\/\nfunction renderMap(e){\n  mapSvg.innerHTML = '';\n  const W = 680, H = 420;\n  const cx = W\/2, cy = H\/2;\n\n  \/\/ Branches: tips + 1 Surprise\n  const base = e.tips.slice(0,3);\n  const branches = [...base, 'Surprise'];\n\n  \/\/ links\n  const ANG = (Math.PI*2) \/ branches.length;\n  branches.forEach((t, i) => {\n    const a = -Math.PI\/2 + i*ANG;\n    const x = cx + Math.cos(a)*200;\n    const y = cy + Math.sin(a)*130;\n    mapSvg.appendChild(line(cx, cy, x, y, e.color));\n  });\n\n  \/\/ center node\n  const center = node(cx, cy, 70, e.color, e.key, true);\n  mapSvg.appendChild(center);\n  center.addEventListener('click', () => pulse(center));\n\n  \/\/ branch nodes (clickable when unlocked)\n  branches.forEach((t, i) => {\n    const a = -Math.PI\/2 + i*ANG;\n    const x = cx + Math.cos(a)*200;\n    const y = cy + Math.sin(a)*130;\n    const n = node(x, y, 54, e.color, t, false);\n    n.classList.add('branch');\n\n    n.addEventListener('click', () => {\n      if (mapWrap.classList.contains('locked')) return;\n      if (t === 'Surprise') {\n        const options = RECS[selected.key] || [];\n        if (options.length) window.open(options[Math.floor(Math.random()*options.length)].url, '_blank', 'noopener');\n        return;\n      }\n      const url = getResourceForTip(t, selected.key);\n      window.open(url, '_blank', 'noopener');\n    });\n\n    mapSvg.appendChild(n);\n  });\n}\n\nfunction node(x,y,r,color,label,isCenter){\n  const g = document.createElementNS('http:\/\/www.w3.org\/2000\/svg','g');\n  g.setAttribute('class', 'node ' + (isCenter?'center':''));\n  const c = document.createElementNS('http:\/\/www.w3.org\/2000\/svg','circle');\n  c.setAttribute('cx', x); c.setAttribute('cy', y); c.setAttribute('r', r);\n  c.setAttribute('fill', color); c.setAttribute('stroke', 'rgba(255,255,255,0.5)');\n  const t = document.createElementNS('http:\/\/www.w3.org\/2000\/svg','text');\n  t.setAttribute('x', x); t.setAttribute('y', y);\n  t.setAttribute('text-anchor','middle'); t.setAttribute('dominant-baseline','middle');\n  t.textContent = label;\n  g.appendChild(c); g.appendChild(t);\n  return g;\n}\nfunction line(x1,y1,x2,y2,color){\n  const l = document.createElementNS('http:\/\/www.w3.org\/2000\/svg','line');\n  l.setAttribute('x1',x1); l.setAttribute('y1',y1);\n  l.setAttribute('x2',x2); l.setAttribute('y2',y2);\n  l.setAttribute('stroke', color); l.setAttribute('stroke-opacity','0.6'); l.setAttribute('stroke-width','4');\n  return l;\n}\nfunction pulse(g){\n  const c = g.querySelector('circle');\n  c.style.transition = 'transform .25s ease';\n  c.style.transformOrigin = 'center';\n  c.style.transform = 'scale(1.05)';\n  setTimeout(()=>{ c.style.transform = 'scale(1)'; }, 220);\n}\n\n\/* -----------------------------------------\n   Recommendations + History + Streak\n----------------------------------------- *\/\nfunction showRecommendations(emotion){\n  const recs = RECS[emotion.key] || [];\n  recList.innerHTML = '';\n  recs.forEach(r => {\n    const li = document.createElement('li');\n    li.innerHTML = `<a href=\"${r.url}\" target=\"_blank\" rel=\"noopener\">${r.text}<\/a>`;\n    recList.appendChild(li);\n  });\n  recWrap.classList.toggle('hidden', recs.length === 0);\n}\n\nfunction updateHistory(emotion){\n  let history = readHistory();\n  const today = new Date(); today.setHours(0,0,0,0);\n  if (!history.length || new Date(history[0].date).getTime() !== today.getTime()) {\n    history.unshift({ mood: emotion.key, color: emotion.color, date: today.toISOString() });\n    history = history.slice(0, 30);\n    localStorage.setItem(LOCAL_HISTORY_KEY, JSON.stringify(history));\n  }\n  renderHistory(history);\n  updateStreak(history);\n}\nfunction readHistory(){\n  try{\n    const raw = localStorage.getItem(LOCAL_HISTORY_KEY);\n    return raw ? JSON.parse(raw) : [];\n  }catch(e){ return []; }\n}\nfunction renderHistory(history){\n  historyList.innerHTML = '';\n  history.forEach(h=>{\n    const d = new Date(h.date);\n    const li = document.createElement('li');\n    li.textContent = `${d.toLocaleDateString()} \u2014 ${h.mood}`;\n    historyList.appendChild(li);\n  });\n  histWrap.classList.toggle('hidden', history.length === 0);\n}\nfunction updateStreak(history){\n  if (!history.length){ streakChip.textContent = 'Mood Streak: 0'; return; }\n  let streak = 1;\n  for(let i=0;i<history.length-1;i++){\n    const d1 = new Date(history[i].date); d1.setHours(0,0,0,0);\n    const d2 = new Date(history[i+1].date); d2.setHours(0,0,0,0);\n    const diff = (d1 - d2) \/ (1000*60*60*24);\n    if (diff === 1) streak++; else break;\n  }\n  streakChip.textContent = `Mood Streak: ${streak}`;\n}\n\n\/* Tip \u2192 URL mapper by keyword *\/\nfunction getResourceForTip(tip, emotionKey){\n  const s = tip.toLowerCase();\n  if (s.includes('breath')) return ROUTES.anxiety;\n  if (s.includes('mindful')) return ROUTES.mindful;\n  if (s.includes('dump')) return ROUTES.challenge;\n  if (s.includes('habit') || s.includes('focus')) return ROUTES.tools;\n  if (s.includes('window') || s.includes('walk')) return ROUTES.anxiety;\n  if (s.includes('question') || s.includes('explore')) return ROUTES.psychquery;\n  if (s.includes('intention') || s.includes('appreciation')) return ROUTES.resilience;\n  if (s.includes('soft light') || s.includes('friend')) return ROUTES.mindful;\n  const rec = (RECS[emotionKey] || [])[0];\n  return rec ? rec.url : ROUTES.tools;\n}\n\n\/* -----------------------------------------\n   Lock\/Unlock + hash helpers\n----------------------------------------- *\/\nfunction parseHash(){\n  const raw = (location.hash || '').replace(\/^#\/, '');\n  const params = {};\n  raw.split('&#038;').forEach(part=>{\n    if(!part) return;\n    const [k,v] = part.split('=');\n    params[decodeURIComponent(k||'')] = decodeURIComponent(v||'');\n  });\n  return params;\n}\nfunction isUnlockedFromHash(){\n  const p = parseHash();\n  return p.unlocked === '1';\n}\nfunction writeHash(state, unlocked){\n  try{\n    const enc = btoa(unescape(encodeURIComponent(JSON.stringify(state)))).replace(\/=+$\/,'');\n    const parts = ['portal='+enc];\n    if(unlocked) parts.push('unlocked=1');\n    location.hash = parts.join('&');\n  }catch(e){}\n}\nfunction unlockNow(){\n  mapWrap.classList.remove('locked');\n  lockVeil.style.display = 'none';\n  toastMsg('Map unlocked');\n}\n\n\/* Join & Unlock: redirect to WP login with return hash = #portal=\u2026&unlocked=1 *\/\nunlockBtn.addEventListener('click', ()=>{\n  const state = readState();\n  let hash = 'unlocked=1';\n  if(state){\n    try{\n      const enc = btoa(unescape(encodeURIComponent(JSON.stringify(state)))).replace(\/=+$\/,'');\n      hash = 'portal='+enc+'&unlocked=1';\n    }catch(e){}\n  }\n  const here = location.origin + location.pathname + location.search + '#' + hash;\n  const loginUrl = '\/wp-login.php?redirect_to=' + encodeURIComponent(here);\n  location.href = loginUrl;\n});\n\nchangeBtn.addEventListener('click', ()=>{\n  selected = null;\n  mapSvg.innerHTML = '';\n  mapWrap.classList.add('locked');\n  lockVeil.style.display = '';\n  setTint('#7A6EE6', 0.12);\n  toastMsg('Choose a new emotion');\n});\n\n\/* -----------------------------------------\n   Controls\n----------------------------------------- *\/\nrecenterBtn.addEventListener('click', ()=>{\n  const center = mapSvg.querySelector('.center');\n  if(center) pulse(center);\n});\nmuteBtn.addEventListener('click', ()=>{\n  muted = !muted;\n  document.querySelector('.overlay-tint').style.opacity = muted ? 0.35 : 0.75;\n  muteBtn.textContent = muted ? 'Unmute' : 'Mute';\n});\ncopyLinkBtn.addEventListener('click', ()=>{\n  writeHash(readState(), isUnlockedFromHash());\n  copyToClipboard(location.href);\n  toastMsg('Portal link copied');\n});\n\n\/* -----------------------------------------\n   Persistence (localStorage)\n----------------------------------------- *\/\nfunction persist(state){\n  try{ localStorage.setItem(LOCAL_STATE_KEY, JSON.stringify(state)); }catch(e){}\n}\nfunction readState(){\n  try{\n    const raw = localStorage.getItem(LOCAL_STATE_KEY);\n    if(raw) return JSON.parse(raw);\n  }catch(e){}\n  return null;\n}\n\n\/* -----------------------------------------\n   Clipboard + Toast\n----------------------------------------- *\/\nasync function copyToClipboard(text){\n  try{ await navigator.clipboard.writeText(text); }catch(e){}\n}\nfunction toastMsg(msg){\n  toast.textContent = msg;\n  toast.classList.add('show');\n  setTimeout(()=>toast.classList.remove('show'), 1400);\n}\n\n\/* -----------------------------------------\n   Init\n----------------------------------------- *\/\n\/\/ Wait until the mirror has a size before spawning orbs\nfunction initOrbsWhenReady() {\n  const rect = orbsEl.getBoundingClientRect();\n  if (rect.width > 0 && rect.height > 0) {\n    spawnOrbs();\n  } else {\n    requestAnimationFrame(initOrbsWhenReady);\n  }\n}\ninitOrbsWhenReady();\n\n\/\/ Re\u2011spawn on resize to keep positions correct\nwindow.addEventListener('resize', () => {\n  orbsEl.innerHTML = '';\n  initOrbsWhenReady();\n});\n\n\/\/ Restore from hash or local\n(function(){\n  const params = parseHash();\n  let base = null;\n  if(params.portal){\n    try{ base = JSON.parse(decodeURIComponent(escape(atob(params.portal)))); }catch(e){}\n  }\n  if(!base){ base = readState(); }\n  if(base){\n    const e = EMOTIONS.find(x=>x.key===base.emotion) || EMOTIONS[0];\n    chooseEmotion(e);\n    if(base.color){ selected.color = base.color; setTint(base.color, 0.28); }\n  }\n  if(isUnlockedFromHash()){\n    unlockNow();\n  }\n  const hist = readHistory();\n  if (hist.length){ renderHistory(hist); updateStreak(hist); histWrap.classList.remove('hidden'); }\n})();\/\/ Remove #portal=... from the address bar after load\nif (location.hash.includes('portal=')) {\n  history.replaceState(null, '', location.pathname + location.search);\n}\n\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Mood\u2011Fit Device Mood\u2011Fit Device See yourself change as you choose your path. Hover an orb to feel its tone. Click one to generate your Live Self\u2011Care Map. Join to unlock everything. Camera access is blocked or unavailable. How to enable it: \u2022 Make sure you\u2019re on HTTPS (padlock in address bar). \u2022 When prompted, click [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"footnotes":""},"class_list":["post-5723","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/pages\/5723","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/comments?post=5723"}],"version-history":[{"count":5,"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/pages\/5723\/revisions"}],"predecessor-version":[{"id":5738,"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/pages\/5723\/revisions\/5738"}],"wp:attachment":[{"href":"https:\/\/madlysane.site\/en\/wp-json\/wp\/v2\/media?parent=5723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}