Created
June 7, 2026 00:01
-
-
Save TechTeller/561e5c39bfe40f68eb81620417d191be to your computer and use it in GitHub Desktop.
Marcus 30-day User Profiles recommendation engine walkthrough
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Marcus's 30 Days — User Profiles Recommendation Engine</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f7f6f2; padding: 20px; } | |
| h2 { font-size: 1rem; font-weight: 700; color: #1c1b18; margin-bottom: 6px; } | |
| .intro { font-size: 0.82rem; color: #6b6860; margin-bottom: 18px; line-height: 1.5; } | |
| .j30 { --j-conn:#3a6a52; --j-pur:#3f7a96; --j-amber:#c9962b; --j-hi:#b4452f; --j-ink:#1c1b18; --j-muted:#6b6860; --j-line:#e0ddd3; } | |
| .j30 * { box-sizing: border-box; } | |
| .j30 .j-phaserow { display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-bottom:6px; font-size:0.7rem; color:var(--j-muted); } | |
| .j30 .j-phaserow b { color:var(--j-ink); } | |
| .j30 .j-timeline { display:flex; gap:5px; flex-wrap:wrap; margin-bottom:14px; } | |
| .j30 .j-chip { cursor:pointer; border:1px solid var(--j-line); background:#fff; border-radius:7px; padding:6px 9px; font-size:0.74rem; font-weight:600; color:var(--j-muted); display:flex; flex-direction:column; align-items:center; gap:3px; min-width:38px; transition:all .12s; user-select:none; } | |
| .j30 .j-chip:hover { border-color:var(--j-conn); color:var(--j-ink); } | |
| .j30 .j-chip .j-dot { width:7px; height:7px; border-radius:50%; } | |
| .j30 .j-chip.conn .j-dot { background:var(--j-conn); } | |
| .j30 .j-chip.pur .j-dot { background:var(--j-pur); } | |
| .j30 .j-chip.away { background:#f4f3ee; color:var(--j-muted); font-style:italic; min-width:auto; } | |
| .j30 .j-chip.away .j-dot { background:#b9b5a8; } | |
| .j30 .j-chip.active { background:var(--j-ink); color:#fff; border-color:var(--j-ink); } | |
| .j30 .j-chip.active.conn { background:var(--j-conn); border-color:var(--j-conn); } | |
| .j30 .j-chip.active.pur { background:var(--j-pur); border-color:var(--j-pur); } | |
| .j30 .j-chip.active.away { background:#7c786c; border-color:#7c786c; color:#fff; } | |
| .j30 .j-stage { display:grid; grid-template-columns: 300px 1fr; gap:16px; align-items:start; } | |
| @media (max-width: 720px){ .j30 .j-stage { grid-template-columns:1fr; } } | |
| .j30 .j-phone { background:#0b1f33; border-radius:14px; padding:14px; } | |
| .j30 .j-phead { color:#fff; } | |
| .j30 .j-phead .j-area { font-weight:700; font-size:1rem; display:flex; align-items:center; gap:7px; } | |
| .j30 .j-area .j-layer { font-size:0.62rem; font-weight:700; padding:2px 7px; border-radius:20px; letter-spacing:.03em; } | |
| .j30 .j-layer.free { background:#71869C; color:#0b1f33; } | |
| .j30 .j-layer.paid { background:var(--j-amber); color:#0b1f33; } | |
| .j30 .j-phead .j-sub { color:#8fa3b8; font-size:0.72rem; margin:3px 0 12px; } | |
| .j30 .j-prompt { background:#13314f; border:1px dashed #5b86ad; border-radius:9px; padding:10px; margin-bottom:11px; } | |
| .j30 .j-prompt .j-q { color:#fff; font-size:0.8rem; font-weight:600; margin-bottom:7px; } | |
| .j30 .j-prompt .j-opts { display:flex; gap:6px; } | |
| .j30 .j-prompt .j-opt { font-size:0.68rem; padding:4px 10px; border-radius:6px; background:#0b1f33; color:#8fa3b8; border:1px solid #2c4c6b; } | |
| .j30 .j-prompt .j-opt.pick { background:var(--j-pur); color:#fff; border-color:var(--j-pur); } | |
| .j30 .j-prompt .j-note { color:#8fa3b8; font-size:0.68rem; margin-top:7px; font-style:italic; } | |
| .j30 .j-card { background:#fff; border-radius:9px; padding:10px 11px; margin-bottom:9px; border-left:3px solid var(--j-conn); } | |
| .j30 .j-card.pur { border-left-color:var(--j-pur); } | |
| .j30 .j-card.locked { opacity:.72; border-left-style:dashed; border-left-color:var(--j-amber); } | |
| .j30 .j-card.review { border-left-color:var(--j-amber); } | |
| .j30 .j-card.stuck { border-left-color:var(--j-hi); } | |
| .j30 .j-ctag { font-size:0.6rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--j-muted); display:flex; align-items:center; gap:6px; } | |
| .j30 .j-badge { font-size:0.56rem; font-weight:700; padding:1px 6px; border-radius:20px; text-transform:uppercase; letter-spacing:.03em; } | |
| .j30 .j-badge.lock { background:var(--j-amber); color:#fff; } | |
| .j30 .j-badge.rev { background:#fbf2da; color:#8a6411; } | |
| .j30 .j-badge.new { background:#13314f; color:#8fa3b8; } | |
| .j30 .j-ctitle { font-weight:700; font-size:0.86rem; margin:4px 0 2px; color:var(--j-ink); line-height:1.25; } | |
| .j30 .j-ctitle .j-strike { color:#b0aca0; text-decoration:line-through; font-weight:500; } | |
| .j30 .j-crelev { color:var(--j-muted); font-size:0.72rem; } | |
| .j30 .j-status { display:inline-flex; align-items:center; gap:5px; margin-top:7px; font-size:0.7rem; font-weight:600; padding:3px 8px; border-radius:6px; } | |
| .j30 .j-status.done { background:#e8f0ea; color:#2c5640; } | |
| .j30 .j-status.ignored { background:#efeee9; color:#86827a; } | |
| .j30 .j-status.locked { background:#fbf2da; color:#8a6411; } | |
| .j30 .j-status.swap { background:#f6e3df; color:#8e2f1d; } | |
| .j30 .j-status.event { background:#e7eef3; color:#2d566b; } | |
| .j30 .j-extra { background:#13314f; border-radius:9px; padding:9px 11px; margin-top:3px; } | |
| .j30 .j-extra .j-ctag { color:#8fa3b8; } | |
| .j30 .j-extra .j-ctitle { color:#fff; font-size:0.82rem; } | |
| .j30 .j-extra .j-crelev { color:#8fa3b8; } | |
| .j30 .j-extra-label { color:#5b86ad; font-size:0.6rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; margin-bottom:6px; } | |
| .j30 .j-away { background:#0b1f33; border-radius:14px; padding:34px 18px; text-align:center; color:#8fa3b8; } | |
| .j30 .j-away .j-moon { font-size:1.8rem; } | |
| .j30 .j-away .j-awt { color:#fff; font-weight:700; margin:8px 0 5px; } | |
| .j30 .j-side { } | |
| .j30 .j-side .j-daylabel { font-size:0.72rem; font-weight:700; color:var(--j-muted); text-transform:uppercase; letter-spacing:.05em; } | |
| .j30 .j-side .j-change { font-size:0.96rem; font-weight:600; color:var(--j-ink); margin:5px 0 12px; line-height:1.4; } | |
| .j30 .j-side .j-trans-h { font-size:0.66rem; font-weight:700; color:var(--j-muted); text-transform:uppercase; letter-spacing:.05em; margin-bottom:7px; } | |
| .j30 .j-trans { display:flex; flex-direction:column; gap:6px; } | |
| .j30 .j-tpill { display:flex; align-items:center; gap:8px; font-size:0.78rem; color:var(--j-ink); background:#fff; border:1px solid var(--j-line); border-radius:7px; padding:7px 10px; } | |
| .j30 .j-tpill::before { content:''; width:8px; height:8px; border-radius:50%; background:var(--j-conn); flex:none; } | |
| .j30 .j-tpill.hot::before { background:var(--j-hi); } | |
| .j30 .j-trans.empty { color:var(--j-muted); font-size:0.78rem; font-style:italic; } | |
| .j30 .j-nav { display:flex; gap:8px; margin-top:14px; } | |
| .j30 .j-nav button { font-size:0.74rem; font-weight:600; padding:6px 13px; border-radius:7px; border:1px solid var(--j-line); background:#fff; color:var(--j-ink); cursor:pointer; } | |
| .j30 .j-nav button:hover { border-color:var(--j-conn); } | |
| .j30 .j-nav button:disabled { opacity:.4; cursor:default; } | |
| .j30 .j-prose { margin-top:16px; background:#fff; border:1px solid var(--j-line); border-left:3px solid var(--j-conn); border-radius:9px; padding:13px 16px; font-size:0.9rem; line-height:1.55; color:var(--j-ink); } | |
| .j30 .j-prose.pur { border-left-color:var(--j-pur); } | |
| .j30 .j-prose.away { border-left-color:#b9b5a8; font-style:italic; color:var(--j-muted); } | |
| .j30 .j-prose .j-prose-day { display:inline-block; font-size:0.66rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--j-muted); margin-right:8px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Marcus's 30 Days</h2> | |
| <p class="intro">Click any day chip to see what the recommendation engine showed Marcus, what he did, and which rule fired. <span style="color:#3a6a52">● Connection</span> <span style="color:#3f7a96">● Purpose</span> <span>☽ away</span></p> | |
| <div class="j30" id="j30"> | |
| <div class="j-phaserow"><b>Marcus's 30 days</b> · click any day — <span style="color:var(--j-conn)">● Connection</span> <span style="color:var(--j-pur)">● Purpose</span> <span>☽ away</span></div> | |
| <div class="j-timeline" id="j30-timeline"></div> | |
| <div class="j-stage"> | |
| <div id="j30-phone"></div> | |
| <div class="j-side"> | |
| <div class="j-daylabel" id="j30-daylabel"></div> | |
| <div class="j-change" id="j30-change"></div> | |
| <div class="j-trans-h">State changes</div> | |
| <div class="j-trans" id="j30-trans"></div> | |
| <div class="j-nav"> | |
| <button id="j30-prev">← Prev</button> | |
| <button id="j30-next">Next →</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="j-prose" id="j30-prose"></div> | |
| </div> | |
| <script> | |
| (function(){ | |
| var DATA = [ | |
| {d:1, area:'Connection', layer:'free', sub:'Working on: Loneliness · Day 1', | |
| cards:[ | |
| {type:'Video', title:'Why you feel alone', relev:'Free main-channel video', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Building Connection', relev:'Unlock with membership', cls:'locked', badge:['lock','Locked'], status:'locked', stxt:'Locked'}, | |
| {type:'Meditation', title:'Self-compassion sit', relev:'Unlock with membership', cls:'locked', badge:['lock','Locked'], status:'locked', stxt:'Locked'}], | |
| change:'Free layer: the slots loosen and locked items still show with an unlock note — but he always has at least one thing he can open.', | |
| trans:['Onboarding done','Area set — Connection','Problem — Loneliness (worst-first)']}, | |
| {d:2, area:'Connection', layer:'free', sub:'Working on: Loneliness · Day 2', | |
| cards:[ | |
| {type:'Video', title:'Making the first move', relev:'Free main-channel video', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Building Connection', relev:'Unlock with membership', cls:'locked', badge:['lock','Locked'], status:'swap', stxt:'Tapped → sees unlock path'}, | |
| {type:'Meditation', title:'Self-compassion sit', relev:'Unlock with membership', cls:'locked', badge:['lock','Locked'], status:'locked', stxt:'Locked'}], | |
| change:'He taps the locked quest, sees the unlock path, and converts to a membership that evening.', | |
| trans:['Tapped a locked item','Converting → Paid']}, | |
| {d:3, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 3', | |
| cards:[ | |
| {type:'Video', title:'The loneliness loop', relev:'On Loneliness', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Building Connection · exercise 1', relev:'Just started', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Self-compassion · sit 1', relev:'Track started', status:'done', stxt:'Done'}], | |
| change:'Now paid → the slots snap to fixed types (a video, a quest, a meditation). The quest is started, so it\'s held from here on.', | |
| trans:['Free → Paid','Slots now fixed types','Quest started (held)']}, | |
| {d:4, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 4', | |
| cards:[ | |
| {type:'Video', title:'Reaching out without cringing', relev:'On Loneliness', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Building Connection · exercise 2', relev:'In progress', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Self-compassion · sit 2', relev:'Track · session 2', status:'done', stxt:'Done'}], | |
| change:'The in-progress quest sets the day\'s problem — Loneliness — and the other slots line up behind it.', | |
| trans:['In-progress quest sets the theme']}, | |
| {d:5, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 5', | |
| cards:[ | |
| {type:'Video', title:'Why you feel alone', relev:'Watch again — does it land differently?', cls:'review', badge:['rev','Review'], status:'done', stxt:'Re-watched'}, | |
| {type:'Quest', title:'Building Connection · exercise 3', relev:'In progress', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Self-compassion · sit 3', relev:'Track · session 3', status:'done', stxt:'Done'}], | |
| change:'A review day. Day 1\'s video is now "due" (a few days passed, and he finished it), so it comes back. At most one review per day.', | |
| trans:['Review surfaced (3-day spacing)']}, | |
| {d:6, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 6', | |
| cards:[ | |
| {type:'Video', title:'<span class="j-strike">Small talk that isn\'t fake</span> → Make a weekly ritual', relev:'Replaced after "not for me"', status:'swap', stxt:'Rejected → next watched'}, | |
| {type:'Quest', title:'Building Connection · exercise 4 (final)', relev:'Last exercise', status:'done', stxt:'Done — quest finished'}, | |
| {type:'Meditation', title:'Self-compassion · sit 4', relev:'Track · session 4', status:'done', stxt:'Done'}], | |
| change:'He taps "not for me" on the video — it\'s dropped and the next one slides in. The quest reaches its last exercise and finishes.', | |
| trans:['Rejected → swapped','Quest complete']}, | |
| {d:7, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 7', | |
| cards:[ | |
| {type:'Video', title:'When friendships fade', relev:'On Loneliness', status:'ignored', stxt:'Shown, ignored'}, | |
| {type:'Quest', title:'Reaching Out · exercise 1', relev:'New quest seeded', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Self-compassion · sit 5 (final)', relev:'Track finished', status:'done', stxt:'Done — track finished'}], | |
| extra:{label:'Optional slot', type:'Research check-in', title:'2-minute check-in', relev:'A questionnaire is due', status:'done', stxt:'Completed'}, | |
| change:'Quest finished → a new one is seeded. The optional slot appears because a research check-in is due.', | |
| trans:['New quest seeded','4th slot — research check-in']}, | |
| {d:8, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 8', | |
| cards:[ | |
| {type:'Video', title:'When friendships fade', relev:'Shown a 2nd time', status:'ignored', stxt:'Ignored ×2'}, | |
| {type:'Quest', title:'Reaching Out · exercise 2', relev:'In progress', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Loneliness track · sit 1', relev:'New track started', status:'done', stxt:'Done'}], | |
| change:'The same video is ignored a second time. A fresh meditation track begins now that the last one is finished.', | |
| trans:['Ignored ×2','New meditation track']}, | |
| {d:9, area:'Connection', layer:'paid', sub:'Working on: Loneliness · Day 9', | |
| cards:[ | |
| {type:'Video', title:'When friendships fade', relev:'Still want this one, or swap?', cls:'stuck', badge:['new','Swap?'], status:'swap', stxt:'Ignored ×3 → swapped'}, | |
| {type:'Quest', title:'Reaching Out · exercise 3', relev:'In progress', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Loneliness track · sit 2', relev:'Track · session 2', status:'done', stxt:'Done'}], | |
| change:'Three shows with no action → the card asks "Still want this one, or swap?" He swaps and watches the replacement.', | |
| trans:['Shown ×3, ignored → swap prompt']}, | |
| {d:10, area:'Connection', layer:'paid', sub:'Working on: Social Anxiety · Day 10', hot:true, | |
| cards:[ | |
| {type:'Video', title:'How social anxiety kills your personality', relev:'On Social Anxiety', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Facing the room · exercise 1', relev:'New problem', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Calm before contact · sit 1', relev:'Track started', status:'done', stxt:'Done'}], | |
| change:'He\'s done enough Loneliness content → the day\'s problem advances to the next on his list, Social Anxiety (still inside Connection).', | |
| trans:[['hot','Loneliness → Social Anxiety (enough done)']]}, | |
| {d:'11–24', away:true, area:'Connection', | |
| change:'Marcus gets busy and doesn\'t open the app for two weeks. Nothing runs while he\'s gone — but the clock counts toward the come-back check-in.', | |
| trans:['No sessions','Counting toward the 2-week check-in']}, | |
| {d:25, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 25 (back after 14 days)', hot:true, | |
| prompt:{q:'Is Connection still what you want to work on?', opts:[['','Keep Connection'],['pick','Switch → Purpose']], note:'He realizes he\'d rather work on Purpose, and switches.'}, | |
| cards:[ | |
| {type:'Video', title:'What do you actually want?', relev:'On Direction', cls:'pur', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Name your direction · exercise 1', relev:'Just started', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Sit with the question · sit 1', relev:'Track started', cls:'pur', status:'done', stxt:'Done'}], | |
| change:'Back after ~two weeks → before showing anything, the app asks if his area is still right. He switches from Connection to Purpose, and the day re-centers on Direction.', | |
| trans:[['hot','Lapse → focus re-confirm'],['hot','Connection → Purpose (user\'s choice)']]}, | |
| {d:26, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 26', | |
| cards:[ | |
| {type:'Video', title:'Goals vs values', relev:'On Direction', cls:'pur', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Name your direction · exercise 2', relev:'In progress', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Sit with the question · sit 2', relev:'Track · session 2', cls:'pur', status:'done', stxt:'Done'}], | |
| extra:{label:'Optional slot', type:'Live workshop', title:'Finding Direction — live today', relev:'Happening now — it expires', status:'event', stxt:'Registered'}, | |
| change:'The purpose quest holds the day on Direction. The optional slot carries a live workshop — it wins ahead of everything because it expires.', | |
| trans:[['','4th slot — live event (top priority)']]}, | |
| {d:27, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 27', | |
| cards:[ | |
| {type:'Video', title:'The dip after a goal', relev:'On Direction', cls:'pur', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Name your direction · exercise 3', relev:'In progress', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Sit with the question · sit 3', relev:'Track · session 3', cls:'pur', status:'done', stxt:'Done'}], | |
| change:'A routine purpose day — three fresh things, all completed.', | |
| trans:[]}, | |
| {d:28, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 28', | |
| cards:[ | |
| {type:'Video', title:'Borrowed purpose', relev:'On Direction', cls:'pur', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Name your direction · exercise 4', relev:'In progress', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Sit with the question (earlier)', relev:'Brought back — nothing new left', cls:'review', badge:['rev','Review'], status:'done', stxt:'Re-done'}], | |
| change:'Purpose has thinner content. The meditation slot runs out of anything new, so it brings back an earlier sit rather than sit empty.', | |
| trans:['Ran out → review fallback']}, | |
| {d:29, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 29', | |
| cards:[ | |
| {type:'Video', title:'Direction in small steps', relev:'On Direction', cls:'pur', status:'done', stxt:'Watched'}, | |
| {type:'Quest', title:'Name your direction (new) · exercise 1', relev:'New quest seeded', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Grounding · sit 1', relev:'New track', cls:'pur', status:'done', stxt:'Done'}], | |
| change:'Routine day — three fresh things, all completed.', | |
| trans:[]}, | |
| {d:30, area:'Purpose', layer:'paid', sub:'Working on: Direction · Day 30', | |
| cards:[ | |
| {type:'Video', title:'What do you actually want?', relev:'Review — from Day 25', cls:'review', badge:['rev','Review'], status:'done', stxt:'Re-watched'}, | |
| {type:'Quest', title:'Name your direction · exercise 2', relev:'In progress', cls:'pur', status:'done', stxt:'Done'}, | |
| {type:'Meditation', title:'Grounding · sit 2', relev:'Track · session 2', cls:'pur', status:'done', stxt:'Done'}], | |
| change:'Another review day surfaces a Day-25 video he\'d finished; the quest and meditation continue fresh.', | |
| trans:['Review surfaced']}, | |
| ]; | |
| var PROSE = [ | |
| 'Marcus signs up free. In onboarding he takes the Connection assessments — Loneliness comes back severe, Social Anxiety moderate — and picks Connection as the area he wants to work on. On the free layer the slots loosen and locked items still show with an unlock note, but he always has at least one thing he can actually open (today, the video).', | |
| 'Another free day with a fresh video on top. He taps the locked “Building Connection” quest, sees “unlock with membership,” and converts to a membership that evening.', | |
| 'Now that he’s paid, the slots snap to fixed types — a video, a quest exercise, a meditation. He starts the quest, so from here on it’s held in place rather than swapped out.', | |
| 'The in-progress quest sets the day’s problem (Loneliness), and the video and meditation line up behind it so the whole day points the same direction.', | |
| 'A review day. Day 1’s video is now “due” — a few days have passed and he finished it — so it comes back with “watch again, does it land differently?” At most one review surfaces per day; the other slots stay new.', | |
| 'He taps “not for me” on the new video, so it’s dropped and the next one slides in. The quest reaches its final exercise and finishes.', | |
| 'With the quest done, a new one (“Reaching Out”) is seeded, and the meditation track also finishes. Because a research check-in is due, the optional slot appears with a quick 2-minute questionnaire.', | |
| 'The same loneliness video is shown again and ignored a second time. A fresh meditation track begins now that the last one is complete.', | |
| 'Shown a third time with no action, the video escalates to “Still want this one, or swap?” He swaps it and watches the replacement.', | |
| 'He’s now finished enough Loneliness content, so the day’s problem advances to the next one on his list — Social Anxiety — still inside Connection. The “why it’s there” line updates to match.', | |
| 'Marcus gets busy and doesn’t open the app for two weeks. Nothing runs while he’s away — but the clock is counting toward the come-back check-in.', | |
| 'Back after about two weeks, the app asks whether Connection is still what he wants to work on before showing anything. He realizes he’d rather work on Purpose and switches; the day re-centers on Direction.', | |
| 'The purpose quest holds the day on Direction. A live workshop is running today, so it takes the optional slot ahead of everything else — it expires if missed — and he registers.', | |
| 'A routine purpose day: three fresh things, all completed.', | |
| 'Purpose has thinner content. The meditation slot runs out of anything new, so instead of sitting empty it brings back an earlier sit as a review.', | |
| 'Another routine day — three fresh items, all done.', | |
| 'A review day surfaces the Day-25 video he’d finished, while the quest and meditation continue fresh.', | |
| ]; | |
| var root = document.getElementById('j30'); | |
| if (!root) return; | |
| var tl = document.getElementById('j30-timeline'); | |
| var phone = document.getElementById('j30-phone'); | |
| var cur = 0; | |
| function areaCls(a){ return a === 'Purpose' ? 'pur' : 'conn'; } | |
| function chipEl(day, i){ | |
| var c = document.createElement('div'); | |
| c.className = 'j-chip ' + (day.away ? 'away' : areaCls(day.area)) + (i===cur?' active':''); | |
| var dot = '<span class="j-dot"></span>'; | |
| var label = day.away ? ('Away ' + day.d) : ('Day ' + day.d); | |
| c.innerHTML = dot + '<span>' + label + '</span>'; | |
| c.onclick = function(){ cur = i; render(); }; | |
| return c; | |
| } | |
| function cardHTML(c){ | |
| var cls = 'j-card' + (c.cls ? ' ' + c.cls : ''); | |
| var badge = c.badge ? '<span class="j-badge ' + c.badge[0] + '">' + c.badge[1] + '</span>' : ''; | |
| return '<div class="' + cls + '">' + | |
| '<div class="j-ctag">' + c.type + badge + '</div>' + | |
| '<div class="j-ctitle">' + c.title + '</div>' + | |
| '<div class="j-crelev">' + c.relev + '</div>' + | |
| '<div class="j-status ' + c.status + '">' + c.stxt + '</div>' + | |
| '</div>'; | |
| } | |
| function render(){ | |
| var day = DATA[cur]; | |
| [].forEach.call(tl.children, function(ch, i){ ch.classList.toggle('active', i===cur); }); | |
| if (day.away){ | |
| phone.innerHTML = '<div class="j-away"><div class="j-moon">☽</div>' + | |
| '<div class="j-awt">Marcus is away</div>' + | |
| '<div>Days 11–24 — no sessions</div></div>'; | |
| } else { | |
| var prompt = ''; | |
| if (day.prompt){ | |
| var opts = day.prompt.opts.map(function(o){ return '<span class="j-opt ' + o[0] + '">' + o[1] + '</span>'; }).join(''); | |
| prompt = '<div class="j-prompt"><div class="j-q">' + day.prompt.q + '</div>' + | |
| '<div class="j-opts">' + opts + '</div>' + | |
| '<div class="j-note">' + day.prompt.note + '</div></div>'; | |
| } | |
| var extra = ''; | |
| if (day.extra){ | |
| var e = day.extra; | |
| extra = '<div class="j-extra"><div class="j-extra-label">' + e.label + '</div>' + | |
| '<div class="j-ctag">' + e.type + '</div>' + | |
| '<div class="j-ctitle">' + e.title + '</div>' + | |
| '<div class="j-crelev">' + e.relev + '</div>' + | |
| '<div class="j-status ' + e.status + '">' + e.stxt + '</div></div>'; | |
| } | |
| phone.innerHTML = '<div class="j-phone"><div class="j-phead">' + | |
| '<div class="j-area">' + day.area + ' <span class="j-layer ' + day.layer + '">' + day.layer.toUpperCase() + '</span></div>' + | |
| '<div class="j-sub">' + day.sub + '</div></div>' + | |
| prompt + | |
| day.cards.map(cardHTML).join('') + | |
| extra + '</div>'; | |
| } | |
| document.getElementById('j30-daylabel').textContent = day.away ? ('Days ' + day.d) : ('Day ' + day.d + ' · ' + day.area); | |
| document.getElementById('j30-change').textContent = day.change; | |
| var pr = document.getElementById('j30-prose'); | |
| pr.className = 'j-prose' + (day.away ? ' away' : (day.area === 'Purpose' ? ' pur' : '')); | |
| pr.innerHTML = '<span class="j-prose-day">' + (day.away ? ('Days ' + day.d) : ('Day ' + day.d)) + '</span>' + PROSE[cur]; | |
| var tw = document.getElementById('j30-trans'); | |
| if (!day.trans || !day.trans.length){ | |
| tw.className = 'j-trans empty'; tw.textContent = 'No major change — routine day.'; | |
| } else { | |
| tw.className = 'j-trans'; | |
| tw.innerHTML = day.trans.map(function(t){ | |
| var hot = Array.isArray(t) && t[0]==='hot'; | |
| var txt = Array.isArray(t) ? t[1] : t; | |
| return '<div class="j-tpill' + (hot?' hot':'') + '">' + txt + '</div>'; | |
| }).join(''); | |
| } | |
| document.getElementById('j30-prev').disabled = (cur===0); | |
| document.getElementById('j30-next').disabled = (cur===DATA.length-1); | |
| } | |
| DATA.forEach(function(day, i){ tl.appendChild(chipEl(day, i)); }); | |
| document.getElementById('j30-prev').onclick = function(){ if(cur>0){cur--;render();} }; | |
| document.getElementById('j30-next').onclick = function(){ if(cur<DATA.length-1){cur++;render();} }; | |
| render(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment