json-object-editor 0.10.665 → 0.10.670

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,479 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Harmonious Wellness — Protocol Report (Mockup)</title>
7
+ <style>
8
+ :root{
9
+ /* Brand-inspired (lightweight) */
10
+ --plum:#3a1430;
11
+ --gold:#c6a44d;
12
+ --olive:#6c7b3b;
13
+ --ink:#0f172a;
14
+ --muted:#475569;
15
+ --bg:#0b0810;
16
+ --paper:#ffffff;
17
+ --soft:#f8fafc;
18
+ --line:#e5e7eb;
19
+ --max: 980px;
20
+ --radius: 16px;
21
+ --font: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
22
+ }
23
+
24
+ html,body{height:100%;}
25
+ body{margin:0; font-family:var(--font); background:var(--bg); color:var(--ink);}
26
+
27
+ /* Topbar */
28
+ .topbar{position:sticky; top:0; z-index:10; background:rgba(11,8,16,.92); border-bottom:1px solid rgba(255,255,255,.10);}
29
+ .topbar .inner{max-width:var(--max); margin:0 auto; padding:10px 14px; display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;}
30
+ .brand{display:flex; align-items:center; gap:10px; color:#f1f5f9;}
31
+ .mark{width:28px; height:28px; border-radius:10px; background:linear-gradient(135deg,var(--olive),var(--gold)); box-shadow:0 0 0 3px rgba(198,164,77,.15);}
32
+ .brand b{letter-spacing:.2px;}
33
+ .actions{display:flex; gap:8px; flex-wrap:wrap;}
34
+ button{border:1px solid rgba(255,255,255,.18); background:rgba(255,255,255,.06); color:#e2e8f0; padding:8px 10px; border-radius:999px; font-weight:700; cursor:pointer;}
35
+ button.primary{border-color:rgba(198,164,77,.55); background:rgba(198,164,77,.18); color:#fff7e6;}
36
+
37
+ /* Page */
38
+ .wrap{max-width:var(--max); margin:18px auto 52px; padding:0 14px;}
39
+ .paper{background:var(--paper); border-radius:22px; overflow:hidden; border:1px solid rgba(255,255,255,.06);}
40
+
41
+ .hero{padding:18px; border-bottom:1px solid var(--line); background:linear-gradient(180deg,#fff,#fbfdff);}
42
+ .hero h1{margin:0; font-size:22px; line-height:1.2;}
43
+ .hero .sub{margin-top:6px; color:var(--muted); font-weight:700;}
44
+ .meta{display:flex; gap:10px; flex-wrap:wrap; margin-top:12px;}
45
+ .pill{display:inline-flex; gap:8px; align-items:center; padding:6px 10px; border:1px solid var(--line); background:var(--soft); border-radius:999px; font-weight:800; font-size:13px;}
46
+ .pill small{color:var(--muted); font-weight:800;}
47
+
48
+ .section{padding:16px 18px; border-bottom:1px solid var(--line);}
49
+ .section:last-child{border-bottom:none;}
50
+ .section h2{margin:0 0 10px; font-size:16px;}
51
+ .grid{display:grid; gap:10px;}
52
+ .two{grid-template-columns: repeat(2, minmax(0,1fr));}
53
+ @media (max-width:780px){.two{grid-template-columns:1fr;}}
54
+
55
+ .card{border:1px solid var(--line); border-radius:var(--radius); padding:12px; background:#fff;}
56
+ .card h3{margin:0 0 8px; font-size:13px; color:var(--plum);}
57
+ .muted{color:var(--muted); font-weight:650; line-height:1.55;}
58
+ ul{margin:0; padding-left:18px; color:var(--muted); font-weight:650;}
59
+ li{margin:6px 0;}
60
+
61
+ /* Phases */
62
+ .phase{border:1px solid var(--line); border-radius:18px; overflow:hidden; margin-bottom:12px;}
63
+ .phase .head{padding:12px; background:linear-gradient(180deg,#fff,#f9fbff); border-bottom:1px solid var(--line);}
64
+ .phase .head b{color:var(--plum);}
65
+ .phase .head p{margin:6px 0 0;}
66
+ .phase .recs{padding:12px; display:grid; gap:10px;}
67
+
68
+ .rec{border:1px solid var(--line); border-radius:14px; padding:10px;}
69
+ .rec-top{display:flex; justify-content:space-between; gap:10px; align-items:flex-start;}
70
+ .rec-name{font-weight:900;}
71
+ .dose{font-size:12px; font-weight:900; color:var(--plum); background:rgba(58,20,48,.06); border:1px solid rgba(58,20,48,.12); padding:6px 10px; border-radius:999px; white-space:nowrap;}
72
+ .more{display:none; margin-top:8px; padding-top:8px; border-top:1px dashed rgba(15,23,42,.18);}
73
+ .toggle{margin-left:8px; border:1px solid rgba(15,23,42,.18); background:#fff; color:var(--ink); padding:3px 8px; border-radius:999px; font-weight:900; font-size:12px;}
74
+
75
+ /* PDF Mode */
76
+ body.pdf-mode .toggle{display:none;}
77
+ body.pdf-mode .more{display:block;}
78
+
79
+ /* Print */
80
+ @media print{
81
+ body{background:#fff;}
82
+ .topbar{display:none;}
83
+ .wrap{max-width:100%; margin:0; padding:0;}
84
+ .paper{border-radius:0;}
85
+ .more{display:block !important;}
86
+ .toggle{display:none !important;}
87
+ .phase{break-inside:avoid; page-break-inside:avoid;}
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="topbar">
93
+ <div class="inner">
94
+ <div class="brand">
95
+ <div class="mark" aria-hidden="true"></div>
96
+ <div>
97
+ <b>Harmonious Wellness</b>
98
+ <div style="font-size:12px; color:#cbd5e1; font-weight:700;">Protocol Report (Mockup)</div>
99
+ </div>
100
+ </div>
101
+ <div class="actions">
102
+ <button id="pdfBtn" aria-pressed="false">📄 PDF Mode</button>
103
+ <button class="primary" onclick="window.print()">🖨️ Print / Save</button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <main class="wrap">
109
+ <article class="paper" id="report"></article>
110
+ </main>
111
+
112
+ <script>
113
+ /*
114
+ PROTOCOL REPORT TEMPLATE
115
+ - Uses data from PROTOCOL (passed via reportbuilder)
116
+ - Resolves client, recommendations, and conditions via $J.get()
117
+ - Lightweight styling + small JS to avoid performance issues
118
+ - Web mode: expandable long descriptions
119
+ - PDF/Print: single-column, expanded
120
+ */
121
+
122
+ // ---------------------------
123
+ // DATA TRANSFORMATION
124
+ // ---------------------------
125
+ const protocol = typeof PROTOCOL !== 'undefined' ? PROTOCOL : null;
126
+
127
+ function getRef(refId) {
128
+ if (!refId) return null;
129
+ try {
130
+ if (typeof $J !== 'undefined' && $J.get) {
131
+ return $J.get(refId);
132
+ }
133
+ } catch(e) {
134
+ console.error('Error getting reference:', refId, e);
135
+ }
136
+ return null;
137
+ }
138
+
139
+ function extractText(rendering) {
140
+ if (!rendering) return '';
141
+ if (typeof rendering === 'string') {
142
+ return rendering.replace(/<[^>]*>/g, '').trim();
143
+ }
144
+ return String(rendering);
145
+ }
146
+
147
+ function buildData() {
148
+ const data = {
149
+ protocol_name: '',
150
+ protocol_headline: '',
151
+ name: '',
152
+ email: '',
153
+ phone_number: '',
154
+ generated_on: '',
155
+ protocol_conditions: [],
156
+ main_concerns: [],
157
+ top_outcomes: [],
158
+ daily_diet_breakfast_lunch_dinner_snacks: '',
159
+ urine_ph: '',
160
+ urine_status: '',
161
+ sleep_quality: '',
162
+ daily_energy_level: '',
163
+ current_supplements: '',
164
+ current_medications: '',
165
+ client_symptoms: '',
166
+ phase_1_headline: '',
167
+ phase_1_description: '',
168
+ phase_1_recommendations: [],
169
+ phase_2_headline: '',
170
+ phase_2_description: '',
171
+ phase_2_recommendations: [],
172
+ phase_3_headline: '',
173
+ phase_3_description: '',
174
+ phase_3_recommendations: [],
175
+ phase_4_headline: '',
176
+ phase_4_description: '',
177
+ phase_4_recommendations: [],
178
+ phase_5_headline: '',
179
+ phase_5_description: '',
180
+ phase_5_recommendations: [],
181
+ blood_pressure_analysis_headline: '',
182
+ urine_analysis_headline: '',
183
+ condition_summaries: []
184
+ };
185
+
186
+ if (!protocol) {
187
+ console.warn('No protocol data available');
188
+ return data;
189
+ }
190
+
191
+ const client = protocol.client ? getRef(protocol.client) : null;
192
+
193
+ data.protocol_name = protocol.name || '';
194
+ data.protocol_headline = protocol.info || '';
195
+
196
+ if (client) {
197
+ data.name = client.name || '';
198
+ data.email = client.email || '';
199
+ data.phone_number = client.phone || '';
200
+
201
+ if (client.main_concerns) {
202
+ const concernsText = extractText(client.main_concerns);
203
+ data.main_concerns = concernsText ? concernsText.split(/\n|•|·|-/).map(s => s.trim()).filter(Boolean) : [];
204
+ }
205
+ if (client.top_outcomes) {
206
+ const outcomesText = extractText(client.top_outcomes);
207
+ data.top_outcomes = outcomesText ? outcomesText.split(/\n|•|·|-/).map(s => s.trim()).filter(Boolean) : [];
208
+ }
209
+ if (client.client_symptoms) {
210
+ data.client_symptoms = extractText(client.client_symptoms);
211
+ }
212
+ }
213
+
214
+ if (protocol.joeUpdated || protocol.created) {
215
+ const date = new Date(protocol.joeUpdated || protocol.created);
216
+ data.generated_on = date.toLocaleDateString('en-US', {
217
+ year: 'numeric',
218
+ month: 'long',
219
+ day: 'numeric'
220
+ });
221
+ }
222
+
223
+ if (protocol.conditions && Array.isArray(protocol.conditions)) {
224
+ data.protocol_conditions = protocol.conditions.map(function(condId) {
225
+ const cond = getRef(condId);
226
+ return cond ? cond.name : null;
227
+ }).filter(Boolean);
228
+ }
229
+
230
+ if (protocol.urine_analysis_ph) {
231
+ data.urine_ph = String(protocol.urine_analysis_ph);
232
+ }
233
+ if (protocol.urine_status) {
234
+ data.urine_status = protocol.urine_status;
235
+ }
236
+ if (protocol.blood_pressure_analysis_headline) {
237
+ data.blood_pressure_analysis_headline = protocol.blood_pressure_analysis_headline;
238
+ }
239
+ if (protocol.urine_analysis_headline) {
240
+ data.urine_analysis_headline = protocol.urine_analysis_headline;
241
+ }
242
+
243
+ for (let i = 1; i <= 5; i++) {
244
+ const phaseHeadline = protocol[`phase_${i}_headline`] || '';
245
+ const phaseDesc = protocol[`phase_${i}_description`] ? extractText(protocol[`phase_${i}_description`]) : '';
246
+ const phaseRecIds = protocol[`phase_${i}_recommendations`] || [];
247
+
248
+ data[`phase_${i}_headline`] = phaseHeadline;
249
+ data[`phase_${i}_description`] = phaseDesc;
250
+
251
+ data[`phase_${i}_recommendations`] = phaseRecIds.map(function(recId) {
252
+ const rec = getRef(recId);
253
+ if (!rec) return null;
254
+ return {
255
+ recommendation_name: rec.name || '',
256
+ short_description: rec.info || '',
257
+ long_description: extractText(rec.description) || '',
258
+ standard_dosage: rec.standard_dosage || ''
259
+ };
260
+ }).filter(Boolean);
261
+ }
262
+
263
+ if (protocol.conditions && Array.isArray(protocol.conditions)) {
264
+ data.condition_summaries = protocol.conditions.map(function(condId) {
265
+ const cond = getRef(condId);
266
+ if (!cond) return null;
267
+ return {
268
+ protocol_conditions: cond.name || '',
269
+ long_description: extractText(cond.long_description || cond.description || '') || ''
270
+ };
271
+ }).filter(Boolean);
272
+ }
273
+
274
+ return data;
275
+ }
276
+
277
+ const data = buildData();
278
+
279
+ // ---------------------------
280
+ // Render helpers
281
+ // ---------------------------
282
+ const el = (tag, attrs={}, children=[]) => {
283
+ const n = document.createElement(tag);
284
+ Object.entries(attrs).forEach(([k,v]) => {
285
+ if(k === 'class') n.className = v;
286
+ else n.setAttribute(k, v);
287
+ });
288
+ (Array.isArray(children) ? children : [children]).forEach(c => {
289
+ if(c === null || c === undefined) return;
290
+ if(typeof c === 'string') n.appendChild(document.createTextNode(c));
291
+ else n.appendChild(c);
292
+ });
293
+ return n;
294
+ };
295
+
296
+ const pill = (label, value) => el('span', {class:'pill'}, [el('small', {}, label), ' ' + value]);
297
+
298
+ function recCard(rec){
299
+ const more = el('div', {class:'more'}, [el('div', {class:'muted'}, rec.long_description || '')]);
300
+ const toggle = el('button', {class:'toggle', type:'button', 'data-toggle':'more', 'aria-expanded':'false'}, '+');
301
+ return el('div', {class:'rec'}, [
302
+ el('div', {class:'rec-top'}, [
303
+ el('div', {}, [
304
+ el('div', {class:'rec-name'}, rec.recommendation_name),
305
+ el('div', {class:'muted', style:'margin-top:6px'}, [rec.short_description, ' ', toggle])
306
+ ]),
307
+ el('div', {class:'dose'}, rec.standard_dosage || '')
308
+ ]),
309
+ more
310
+ ]);
311
+ }
312
+
313
+ function phase(n){
314
+ const headline = data[`phase_${n}_headline`];
315
+ const desc = data[`phase_${n}_description`];
316
+ const recs = data[`phase_${n}_recommendations`] || [];
317
+ return el('div', {class:'phase'}, [
318
+ el('div', {class:'head'}, [
319
+ el('div', {}, [
320
+ el('b', {}, headline),
321
+ el('p', {class:'muted'}, desc)
322
+ ])
323
+ ]),
324
+ el('div', {class:'recs'}, recs.map(recCard))
325
+ ]);
326
+ }
327
+
328
+ // ---------------------------
329
+ // Render page (with DOM ready check)
330
+ // ---------------------------
331
+ function initReport() {
332
+ const report = document.getElementById('report');
333
+ if (!report) {
334
+ console.error('Report element not found');
335
+ return;
336
+ }
337
+
338
+ report.appendChild(el('section', {class:'hero'}, [
339
+ el('h1', {}, data.protocol_name),
340
+ el('div', {class:'sub'}, data.protocol_headline),
341
+ el('div', {class:'meta'}, [
342
+ pill('Client', data.name),
343
+ pill('Generated', data.generated_on),
344
+ pill('Email', data.email),
345
+ pill('Phone', data.phone_number)
346
+ ])
347
+ ]));
348
+
349
+ // Intentions and Goals
350
+ report.appendChild(el('section', {class:'section'}, [
351
+ el('h2', {}, 'Intentions and Goals'),
352
+ el('div', {class:'grid two'}, [
353
+ el('div', {class:'card'}, [
354
+ el('h3', {}, 'Conditions of focus'),
355
+ el('div', {class:'muted'}, data.protocol_conditions.join(', ')),
356
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: protocol_conditions')
357
+ ]),
358
+ el('div', {class:'card'}, [
359
+ el('h3', {}, 'Main Glands, Organs, or Systems Involved'),
360
+ el('div', {class:'muted'}, data.client_symptoms),
361
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: client_symptoms')
362
+ ])
363
+ ]),
364
+
365
+ el('div', {class:'grid two', style:'margin-top:10px'}, [
366
+ el('div', {class:'card'}, [
367
+ el('h3', {}, 'Top Client Concerns'),
368
+ el('ul', {}, data.main_concerns.map(x => el('li', {}, x))),
369
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: main_concerns')
370
+ ]),
371
+ el('div', {class:'card'}, [
372
+ el('h3', {}, 'Other Key Notes from Consult'),
373
+ el('ul', {}, data.top_outcomes.map(x => el('li', {}, x))),
374
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: top_outcomes')
375
+ ])
376
+ ]),
377
+
378
+ el('div', {class:'grid two', style:'margin-top:10px'}, [
379
+ el('div', {class:'card'}, [
380
+ el('h3', {}, 'Current Diet'),
381
+ el('div', {class:'muted'}, data.daily_diet_breakfast_lunch_dinner_snacks || 'Not specified'),
382
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: daily_diet_breakfast_lunch_dinner_snacks')
383
+ ]),
384
+ el('div', {class:'card'}, [
385
+ el('h3', {}, 'Kidney Filtration / Sleep / Energy'),
386
+ el('div', {class:'muted'}, `urine_ph: ${data.urine_ph || 'N/A'} · urine_status: ${data.urine_status || 'N/A'}`),
387
+ el('div', {class:'muted'}, `sleep_quality: ${data.sleep_quality || 'N/A'} · daily_energy_level: ${data.daily_energy_level || 'N/A'}`),
388
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped Fields: urine_ph, urine_status, sleep_quality, daily_energy_level')
389
+ ])
390
+ ]),
391
+
392
+ el('div', {class:'grid two', style:'margin-top:10px'}, [
393
+ el('div', {class:'card'}, [
394
+ el('h3', {}, 'Current Supplements'),
395
+ el('div', {class:'muted'}, data.current_supplements || 'Not specified'),
396
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: current_supplements')
397
+ ]),
398
+ el('div', {class:'card'}, [
399
+ el('h3', {}, 'Current Medications'),
400
+ el('div', {class:'muted'}, data.current_medications || 'Not specified'),
401
+ el('div', {class:'muted', style:'font-size:12px; margin-top:8px'}, 'Mapped System Field: current_medications')
402
+ ])
403
+ ])
404
+ ]));
405
+
406
+ // Phases & Recommendations
407
+ report.appendChild(el('section', {class:'section'}, [
408
+ el('h2', {}, 'Protocol Phases & Recommendations'),
409
+ phase(1), phase(2), phase(3), phase(4), phase(5)
410
+ ]));
411
+
412
+ // Education & Resources
413
+ report.appendChild(el('section', {class:'section'}, [
414
+ el('h2', {}, 'Education & Resources'),
415
+
416
+ el('div', {class:'card'}, [
417
+ el('h3', {}, 'Understanding Your Body Signals'),
418
+ el('div', {class:'muted'}, `blood_pressure_analysis_headline: ${data.blood_pressure_analysis_headline || 'N/A'}`),
419
+ el('div', {class:'muted', style:'margin-top:8px'}, `urine_analysis_headline: ${data.urine_analysis_headline || 'N/A'}`)
420
+ ]),
421
+
422
+ el('div', {class:'card', style:'margin-top:10px'}, [
423
+ el('h3', {}, "Key Conditions We’re Supporting"),
424
+ ...data.condition_summaries.map(c => el('div', {style:'margin-top:10px'}, [
425
+ el('div', {style:'font-weight:900; color:var(--plum)'}, c.protocol_conditions),
426
+ el('div', {class:'muted'}, c.long_description)
427
+ ]))
428
+ ]),
429
+
430
+ el('div', {class:'card', style:'margin-top:10px'}, [
431
+ el('h3', {}, 'Products Referenced in 20-Week Protocol'),
432
+ el('div', {class:'muted'}, 'List and links for products referenced in this protocol will be emailed separately.')
433
+ ])
434
+ ]));
435
+
436
+ // Closing Note (static rendered content)
437
+ report.appendChild(el('section', {class:'section'}, [
438
+ el('h2', {}, 'Closing Note'),
439
+ el('div', {class:'muted'},
440
+ `${data.name ? data.name.split(' ')[0] : 'Client'}, This is a ton of information and oftentimes it takes some digestion and processing. If you would like to book a follow up appointment or just hop on the phone to discuss any of this please let me know. I am here to support you! I also make tailor made tinctures and teas so feel free to let me know if that is of interest. Deep blessings on the next step in your health journey! I am available by email or text if you have questions along the way. Please never hesitate to reach out! I would love to at least connect back in 3 months to hear how things are going and recommend adjustments/modifications as needed. You can book your follow-up appointment online here. Select either a Short Follow up or Long Follow up. There is a small fee for these follow ups. However, accessibility is a value of mine and I will never turn anyone away for lack of funds. Please let me know and I will share my sliding scale range. Thank you ${data.name || 'you'} for trusting and allowing me to support you. The work works. Keep the faith. Live inspired. Blessings on your wellness journey and enhanced vitality! ~ Kelly Shay`
441
+ )
442
+ ]));
443
+
444
+ // ---------------------------
445
+ // Interactions
446
+ // ---------------------------
447
+ const pdfBtn = document.getElementById('pdfBtn');
448
+ if (pdfBtn) {
449
+ pdfBtn.addEventListener('click', () => {
450
+ const enabled = !document.body.classList.contains('pdf-mode');
451
+ document.body.classList.toggle('pdf-mode', enabled);
452
+ pdfBtn.setAttribute('aria-pressed', String(enabled));
453
+ });
454
+ }
455
+
456
+ document.addEventListener('click', (e) => {
457
+ const t = e.target.closest('[data-toggle="more"]');
458
+ if(!t) return;
459
+ if(document.body.classList.contains('pdf-mode')) return;
460
+ const rec = t.closest('.rec');
461
+ if (!rec) return;
462
+ const more = rec.querySelector('.more');
463
+ if (!more) return;
464
+ const expanded = t.getAttribute('aria-expanded') === 'true';
465
+ t.setAttribute('aria-expanded', String(!expanded));
466
+ more.style.display = expanded ? 'none' : 'block';
467
+ });
468
+ }
469
+
470
+ // Wait for DOM to be ready before initializing
471
+ if (document.readyState === 'loading') {
472
+ document.addEventListener('DOMContentLoaded', initReport);
473
+ } else {
474
+ // DOM already ready, but wait a tick to ensure elements exist
475
+ setTimeout(initReport, 0);
476
+ }
477
+ </script>
478
+ </body>
479
+ </html>