Spaces:
Running
Running
steps
Browse files- index.html +27 -37
index.html
CHANGED
|
@@ -6,6 +6,17 @@
|
|
| 6 |
<title>Secondary Use of Health Records Navigator</title>
|
| 7 |
<link rel="stylesheet" href="style.css" />
|
| 8 |
<script src="app.js" defer></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</head>
|
| 10 |
<body>
|
| 11 |
<header>
|
|
@@ -108,7 +119,6 @@
|
|
| 108 |
refs: [],
|
| 109 |
},
|
| 110 |
];
|
| 111 |
-
|
| 112 |
const STEPS = [
|
| 113 |
{
|
| 114 |
num: 1,
|
|
@@ -681,8 +691,6 @@
|
|
| 681 |
],
|
| 682 |
},
|
| 683 |
];
|
| 684 |
-
|
| 685 |
-
|
| 686 |
/* Flow questions derived from the flowchart */
|
| 687 |
const FLOW = {
|
| 688 |
1: {
|
|
@@ -712,7 +720,6 @@
|
|
| 712 |
8: { q: "Any gaps found?", onYes: 4, onNo: 9 },
|
| 713 |
9: { q: "Will you retain or reuse data beyond approved plan?", onYes: 1, onNo: "end" },
|
| 714 |
};
|
| 715 |
-
|
| 716 |
/* ------------ State ------------ */
|
| 717 |
const STORAGE = "suhr_flow_v1";
|
| 718 |
const $ = (s) => document.querySelector(s);
|
|
@@ -726,7 +733,6 @@
|
|
| 726 |
subQ: 0,
|
| 727 |
reachedEnd: false, // track if user ever reached the end screen
|
| 728 |
};
|
| 729 |
-
|
| 730 |
/* ------------ Purpose UI ------------ */
|
| 731 |
function renderPurposes() {
|
| 732 |
const host = $("#purposeList");
|
|
@@ -780,7 +786,6 @@
|
|
| 780 |
$("#anonMsg").classList.toggle("hidden", state.purpose !== "anon");
|
| 781 |
}
|
| 782 |
}
|
| 783 |
-
|
| 784 |
/* ------------ Timeline (accordion) ------------ */
|
| 785 |
function renderTimeline() {
|
| 786 |
const host = $("#timeline");
|
|
@@ -790,7 +795,6 @@
|
|
| 790 |
for (let i = 0; i < v; i++) addStep(host, STEPS[i], i + 1);
|
| 791 |
expandDetailsForStep(state.step);
|
| 792 |
}
|
| 793 |
-
|
| 794 |
function addStep(host, step, idx) {
|
| 795 |
const done = isStepComplete(step);
|
| 796 |
const sec = document.createElement("section");
|
|
@@ -819,13 +823,27 @@
|
|
| 819 |
const li = document.createElement("li");
|
| 820 |
li.className = "li";
|
| 821 |
const id = `${step.key}_${it.k}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 822 |
li.innerHTML = `
|
| 823 |
<div class="letters">${it.k}</div>
|
| 824 |
<label for="${id}">${it.label}${
|
| 825 |
it.req
|
| 826 |
? ` <span class='req'>★</span>`
|
| 827 |
: " <span class='opt'>(opt)</span>"
|
| 828 |
-
}</label>
|
| 829 |
<div style="display:flex;align-items:center;gap:8px">
|
| 830 |
<input class="chk" type="checkbox" id="${id}" ${
|
| 831 |
checked ? "checked" : ""
|
|
@@ -835,7 +853,6 @@
|
|
| 835 |
li.querySelector(".chk").addEventListener("change", (e) => {
|
| 836 |
(state.checks[step.key] ??= {})[it.k] = e.target.checked;
|
| 837 |
save(true);
|
| 838 |
-
|
| 839 |
const nowDone = isStepComplete(step);
|
| 840 |
$("#badge_" + step.key).textContent = nowDone
|
| 841 |
? "Complete ✓"
|
|
@@ -846,10 +863,8 @@
|
|
| 846 |
} else if (!nowDone) {
|
| 847 |
sec.classList.remove("done");
|
| 848 |
}
|
| 849 |
-
|
| 850 |
renderDots(state.visible); // refresh top dots' done state
|
| 851 |
updateProgress();
|
| 852 |
-
|
| 853 |
// If the end screen is visible (or was reached before), update/show its message when all steps are complete.
|
| 854 |
if ($("#finish").classList.contains("show")) {
|
| 855 |
refreshFinishBanner();
|
|
@@ -859,16 +874,13 @@
|
|
| 859 |
});
|
| 860 |
ul.appendChild(li);
|
| 861 |
});
|
| 862 |
-
|
| 863 |
// "Details" toggle
|
| 864 |
sec.querySelector(".toggle").addEventListener("click", () => {
|
| 865 |
const pane = document.getElementById("fold_" + step.key);
|
| 866 |
pane.classList.toggle("fold");
|
| 867 |
});
|
| 868 |
-
|
| 869 |
host.appendChild(sec);
|
| 870 |
}
|
| 871 |
-
|
| 872 |
/* Expand current step details and collapse others while asking each question */
|
| 873 |
function expandDetailsForStep(stepNum) {
|
| 874 |
if (!stepNum) return;
|
|
@@ -879,7 +891,6 @@
|
|
| 879 |
const pane = document.getElementById("fold_" + current.key);
|
| 880 |
if (pane) pane.classList.remove("fold");
|
| 881 |
}
|
| 882 |
-
|
| 883 |
/* ------------ Dots / progress ------------ */
|
| 884 |
function renderDots(visible) {
|
| 885 |
const d = $("#dots");
|
|
@@ -930,32 +941,26 @@
|
|
| 930 |
{ duration: 800 }
|
| 931 |
);
|
| 932 |
}
|
| 933 |
-
|
| 934 |
/* ------------ Flow controller (Yes/No) ------------ */
|
| 935 |
const flow = $("#flow"),
|
| 936 |
qtxt = $("#qtxt"),
|
| 937 |
yesBtn = $("#yesBtn"),
|
| 938 |
noBtn = $("#noBtn");
|
| 939 |
-
|
| 940 |
function showFlow() {
|
| 941 |
flow.classList.remove("hidden");
|
| 942 |
}
|
| 943 |
function hideFlow() {
|
| 944 |
flow.classList.add("hidden");
|
| 945 |
}
|
| 946 |
-
|
| 947 |
function askCurrent() {
|
| 948 |
// Reset finish visibility text live region while navigating
|
| 949 |
$("#finish").classList.remove("show");
|
| 950 |
-
|
| 951 |
if (state.purpose === "anon" || !state.step) {
|
| 952 |
hideFlow();
|
| 953 |
return;
|
| 954 |
}
|
| 955 |
-
|
| 956 |
// Ensure the current step's checklist is expanded (and others collapsed)
|
| 957 |
expandDetailsForStep(state.step);
|
| 958 |
-
|
| 959 |
// Step 7 special sequence
|
| 960 |
if (state.step === 7) {
|
| 961 |
const seq = FLOW[7];
|
|
@@ -987,7 +992,6 @@
|
|
| 987 |
};
|
| 988 |
return;
|
| 989 |
}
|
| 990 |
-
|
| 991 |
// Regular step question
|
| 992 |
const node = FLOW[state.step];
|
| 993 |
if (!node) {
|
|
@@ -998,7 +1002,6 @@
|
|
| 998 |
yesBtn.onclick = () => goto(node.onYes);
|
| 999 |
noBtn.onclick = () => goto(node.onNo);
|
| 1000 |
}
|
| 1001 |
-
|
| 1002 |
function goto(dest) {
|
| 1003 |
if (dest === "end") {
|
| 1004 |
finish();
|
|
@@ -1014,7 +1017,6 @@
|
|
| 1014 |
scrollToStep(state.step);
|
| 1015 |
save(true);
|
| 1016 |
}
|
| 1017 |
-
|
| 1018 |
/* Called when step 1 completes to auto reveal step 2 */
|
| 1019 |
function advanceToStep(n) {
|
| 1020 |
if (state.visible < n) {
|
|
@@ -1028,7 +1030,6 @@
|
|
| 1028 |
askCurrent();
|
| 1029 |
save(true);
|
| 1030 |
}
|
| 1031 |
-
|
| 1032 |
/* ------------ Helpers ------------ */
|
| 1033 |
function isStepComplete(step) {
|
| 1034 |
const d = state.checks[step.key] || {};
|
|
@@ -1057,19 +1058,16 @@
|
|
| 1057 |
const pct = total ? Math.round((100 * done) / total) : 0;
|
| 1058 |
$("#pbar").style.width = pct + "%";
|
| 1059 |
}
|
| 1060 |
-
|
| 1061 |
/* ------------ Finish ------------ */
|
| 1062 |
function finish() {
|
| 1063 |
hideFlow();
|
| 1064 |
state.reachedEnd = true;
|
| 1065 |
save(true);
|
| 1066 |
-
|
| 1067 |
const ok = allStepsComplete();
|
| 1068 |
const finishEl = $("#finish");
|
| 1069 |
const titleEl = $("#finishTitle");
|
| 1070 |
const subEl = $("#finishSub");
|
| 1071 |
const confettiHost = $("#confetti");
|
| 1072 |
-
|
| 1073 |
if (ok) {
|
| 1074 |
titleEl.textContent = "End 🎉";
|
| 1075 |
subEl.textContent = "All flow checks passed.";
|
|
@@ -1082,14 +1080,12 @@
|
|
| 1082 |
}
|
| 1083 |
finishEl.classList.add("show");
|
| 1084 |
}
|
| 1085 |
-
|
| 1086 |
function refreshFinishBanner() {
|
| 1087 |
// Update finish banner live without toggling visibility
|
| 1088 |
const ok = allStepsComplete();
|
| 1089 |
const titleEl = $("#finishTitle");
|
| 1090 |
const subEl = $("#finishSub");
|
| 1091 |
const confettiHost = $("#confetti");
|
| 1092 |
-
|
| 1093 |
if (ok) {
|
| 1094 |
titleEl.textContent = "End 🎉";
|
| 1095 |
subEl.textContent = "All flow checks passed.";
|
|
@@ -1101,7 +1097,6 @@
|
|
| 1101 |
confettiHost.innerHTML = "";
|
| 1102 |
}
|
| 1103 |
}
|
| 1104 |
-
|
| 1105 |
function confetti(n) {
|
| 1106 |
const host = $("#confetti");
|
| 1107 |
host.innerHTML = "";
|
|
@@ -1114,7 +1109,6 @@
|
|
| 1114 |
host.appendChild(piece);
|
| 1115 |
}
|
| 1116 |
}
|
| 1117 |
-
|
| 1118 |
/* ------------ Save/Load ------------ */
|
| 1119 |
function save(quiet = false) {
|
| 1120 |
state.savedAt = Date.now();
|
|
@@ -1127,7 +1121,6 @@
|
|
| 1127 |
if (raw) Object.assign(state, JSON.parse(raw));
|
| 1128 |
} catch (e) {}
|
| 1129 |
}
|
| 1130 |
-
|
| 1131 |
/* ------------ Clear / Reset ------------ */
|
| 1132 |
function clearAll() {
|
| 1133 |
try {
|
|
@@ -1140,7 +1133,6 @@
|
|
| 1140 |
state.step = 0;
|
| 1141 |
state.subQ = 0;
|
| 1142 |
state.reachedEnd = false;
|
| 1143 |
-
|
| 1144 |
renderPurposes();
|
| 1145 |
renderDots(0);
|
| 1146 |
$("#timeline").innerHTML = "";
|
|
@@ -1149,7 +1141,6 @@
|
|
| 1149 |
hideFlow();
|
| 1150 |
updateProgress();
|
| 1151 |
}
|
| 1152 |
-
|
| 1153 |
/* ------------ Boot ------------ */
|
| 1154 |
load();
|
| 1155 |
renderPurposes();
|
|
@@ -1164,7 +1155,6 @@
|
|
| 1164 |
hideFlow();
|
| 1165 |
}
|
| 1166 |
updateProgress();
|
| 1167 |
-
|
| 1168 |
// Clear button handler
|
| 1169 |
document.getElementById("clearBtn").addEventListener("click", () => {
|
| 1170 |
const ok = confirm(
|
|
@@ -1174,4 +1164,4 @@
|
|
| 1174 |
});
|
| 1175 |
</script>
|
| 1176 |
</body>
|
| 1177 |
-
</html>
|
|
|
|
| 6 |
<title>Secondary Use of Health Records Navigator</title>
|
| 7 |
<link rel="stylesheet" href="style.css" />
|
| 8 |
<script src="app.js" defer></script>
|
| 9 |
+
|
| 10 |
+
<!-- Added tiny style for inline refs -->
|
| 11 |
+
<style>
|
| 12 |
+
.refs {
|
| 13 |
+
font-size: 0.85em;
|
| 14 |
+
opacity: 0.9;
|
| 15 |
+
margin-left: 4px;
|
| 16 |
+
display: inline;
|
| 17 |
+
}
|
| 18 |
+
.refs a { text-decoration: underline; }
|
| 19 |
+
</style>
|
| 20 |
</head>
|
| 21 |
<body>
|
| 22 |
<header>
|
|
|
|
| 119 |
refs: [],
|
| 120 |
},
|
| 121 |
];
|
|
|
|
| 122 |
const STEPS = [
|
| 123 |
{
|
| 124 |
num: 1,
|
|
|
|
| 691 |
],
|
| 692 |
},
|
| 693 |
];
|
|
|
|
|
|
|
| 694 |
/* Flow questions derived from the flowchart */
|
| 695 |
const FLOW = {
|
| 696 |
1: {
|
|
|
|
| 720 |
8: { q: "Any gaps found?", onYes: 4, onNo: 9 },
|
| 721 |
9: { q: "Will you retain or reuse data beyond approved plan?", onYes: 1, onNo: "end" },
|
| 722 |
};
|
|
|
|
| 723 |
/* ------------ State ------------ */
|
| 724 |
const STORAGE = "suhr_flow_v1";
|
| 725 |
const $ = (s) => document.querySelector(s);
|
|
|
|
| 733 |
subQ: 0,
|
| 734 |
reachedEnd: false, // track if user ever reached the end screen
|
| 735 |
};
|
|
|
|
| 736 |
/* ------------ Purpose UI ------------ */
|
| 737 |
function renderPurposes() {
|
| 738 |
const host = $("#purposeList");
|
|
|
|
| 786 |
$("#anonMsg").classList.toggle("hidden", state.purpose !== "anon");
|
| 787 |
}
|
| 788 |
}
|
|
|
|
| 789 |
/* ------------ Timeline (accordion) ------------ */
|
| 790 |
function renderTimeline() {
|
| 791 |
const host = $("#timeline");
|
|
|
|
| 795 |
for (let i = 0; i < v; i++) addStep(host, STEPS[i], i + 1);
|
| 796 |
expandDetailsForStep(state.step);
|
| 797 |
}
|
|
|
|
| 798 |
function addStep(host, step, idx) {
|
| 799 |
const done = isStepComplete(step);
|
| 800 |
const sec = document.createElement("section");
|
|
|
|
| 823 |
const li = document.createElement("li");
|
| 824 |
li.className = "li";
|
| 825 |
const id = `${step.key}_${it.k}`;
|
| 826 |
+
|
| 827 |
+
/* Build inline, lowercase, clickable refs */
|
| 828 |
+
const refsHtml =
|
| 829 |
+
it.refs && it.refs.length
|
| 830 |
+
? ` <small class="refs">` +
|
| 831 |
+
it.refs
|
| 832 |
+
.map(
|
| 833 |
+
(r) =>
|
| 834 |
+
`<a href="${r.url}" target="_blank" rel="noreferrer noopener">${(r.title || r.url).toLowerCase()}</a>`
|
| 835 |
+
)
|
| 836 |
+
.join(" • ") +
|
| 837 |
+
`</small>`
|
| 838 |
+
: "";
|
| 839 |
+
|
| 840 |
li.innerHTML = `
|
| 841 |
<div class="letters">${it.k}</div>
|
| 842 |
<label for="${id}">${it.label}${
|
| 843 |
it.req
|
| 844 |
? ` <span class='req'>★</span>`
|
| 845 |
: " <span class='opt'>(opt)</span>"
|
| 846 |
+
}${refsHtml}</label>
|
| 847 |
<div style="display:flex;align-items:center;gap:8px">
|
| 848 |
<input class="chk" type="checkbox" id="${id}" ${
|
| 849 |
checked ? "checked" : ""
|
|
|
|
| 853 |
li.querySelector(".chk").addEventListener("change", (e) => {
|
| 854 |
(state.checks[step.key] ??= {})[it.k] = e.target.checked;
|
| 855 |
save(true);
|
|
|
|
| 856 |
const nowDone = isStepComplete(step);
|
| 857 |
$("#badge_" + step.key).textContent = nowDone
|
| 858 |
? "Complete ✓"
|
|
|
|
| 863 |
} else if (!nowDone) {
|
| 864 |
sec.classList.remove("done");
|
| 865 |
}
|
|
|
|
| 866 |
renderDots(state.visible); // refresh top dots' done state
|
| 867 |
updateProgress();
|
|
|
|
| 868 |
// If the end screen is visible (or was reached before), update/show its message when all steps are complete.
|
| 869 |
if ($("#finish").classList.contains("show")) {
|
| 870 |
refreshFinishBanner();
|
|
|
|
| 874 |
});
|
| 875 |
ul.appendChild(li);
|
| 876 |
});
|
|
|
|
| 877 |
// "Details" toggle
|
| 878 |
sec.querySelector(".toggle").addEventListener("click", () => {
|
| 879 |
const pane = document.getElementById("fold_" + step.key);
|
| 880 |
pane.classList.toggle("fold");
|
| 881 |
});
|
|
|
|
| 882 |
host.appendChild(sec);
|
| 883 |
}
|
|
|
|
| 884 |
/* Expand current step details and collapse others while asking each question */
|
| 885 |
function expandDetailsForStep(stepNum) {
|
| 886 |
if (!stepNum) return;
|
|
|
|
| 891 |
const pane = document.getElementById("fold_" + current.key);
|
| 892 |
if (pane) pane.classList.remove("fold");
|
| 893 |
}
|
|
|
|
| 894 |
/* ------------ Dots / progress ------------ */
|
| 895 |
function renderDots(visible) {
|
| 896 |
const d = $("#dots");
|
|
|
|
| 941 |
{ duration: 800 }
|
| 942 |
);
|
| 943 |
}
|
|
|
|
| 944 |
/* ------------ Flow controller (Yes/No) ------------ */
|
| 945 |
const flow = $("#flow"),
|
| 946 |
qtxt = $("#qtxt"),
|
| 947 |
yesBtn = $("#yesBtn"),
|
| 948 |
noBtn = $("#noBtn");
|
|
|
|
| 949 |
function showFlow() {
|
| 950 |
flow.classList.remove("hidden");
|
| 951 |
}
|
| 952 |
function hideFlow() {
|
| 953 |
flow.classList.add("hidden");
|
| 954 |
}
|
|
|
|
| 955 |
function askCurrent() {
|
| 956 |
// Reset finish visibility text live region while navigating
|
| 957 |
$("#finish").classList.remove("show");
|
|
|
|
| 958 |
if (state.purpose === "anon" || !state.step) {
|
| 959 |
hideFlow();
|
| 960 |
return;
|
| 961 |
}
|
|
|
|
| 962 |
// Ensure the current step's checklist is expanded (and others collapsed)
|
| 963 |
expandDetailsForStep(state.step);
|
|
|
|
| 964 |
// Step 7 special sequence
|
| 965 |
if (state.step === 7) {
|
| 966 |
const seq = FLOW[7];
|
|
|
|
| 992 |
};
|
| 993 |
return;
|
| 994 |
}
|
|
|
|
| 995 |
// Regular step question
|
| 996 |
const node = FLOW[state.step];
|
| 997 |
if (!node) {
|
|
|
|
| 1002 |
yesBtn.onclick = () => goto(node.onYes);
|
| 1003 |
noBtn.onclick = () => goto(node.onNo);
|
| 1004 |
}
|
|
|
|
| 1005 |
function goto(dest) {
|
| 1006 |
if (dest === "end") {
|
| 1007 |
finish();
|
|
|
|
| 1017 |
scrollToStep(state.step);
|
| 1018 |
save(true);
|
| 1019 |
}
|
|
|
|
| 1020 |
/* Called when step 1 completes to auto reveal step 2 */
|
| 1021 |
function advanceToStep(n) {
|
| 1022 |
if (state.visible < n) {
|
|
|
|
| 1030 |
askCurrent();
|
| 1031 |
save(true);
|
| 1032 |
}
|
|
|
|
| 1033 |
/* ------------ Helpers ------------ */
|
| 1034 |
function isStepComplete(step) {
|
| 1035 |
const d = state.checks[step.key] || {};
|
|
|
|
| 1058 |
const pct = total ? Math.round((100 * done) / total) : 0;
|
| 1059 |
$("#pbar").style.width = pct + "%";
|
| 1060 |
}
|
|
|
|
| 1061 |
/* ------------ Finish ------------ */
|
| 1062 |
function finish() {
|
| 1063 |
hideFlow();
|
| 1064 |
state.reachedEnd = true;
|
| 1065 |
save(true);
|
|
|
|
| 1066 |
const ok = allStepsComplete();
|
| 1067 |
const finishEl = $("#finish");
|
| 1068 |
const titleEl = $("#finishTitle");
|
| 1069 |
const subEl = $("#finishSub");
|
| 1070 |
const confettiHost = $("#confetti");
|
|
|
|
| 1071 |
if (ok) {
|
| 1072 |
titleEl.textContent = "End 🎉";
|
| 1073 |
subEl.textContent = "All flow checks passed.";
|
|
|
|
| 1080 |
}
|
| 1081 |
finishEl.classList.add("show");
|
| 1082 |
}
|
|
|
|
| 1083 |
function refreshFinishBanner() {
|
| 1084 |
// Update finish banner live without toggling visibility
|
| 1085 |
const ok = allStepsComplete();
|
| 1086 |
const titleEl = $("#finishTitle");
|
| 1087 |
const subEl = $("#finishSub");
|
| 1088 |
const confettiHost = $("#confetti");
|
|
|
|
| 1089 |
if (ok) {
|
| 1090 |
titleEl.textContent = "End 🎉";
|
| 1091 |
subEl.textContent = "All flow checks passed.";
|
|
|
|
| 1097 |
confettiHost.innerHTML = "";
|
| 1098 |
}
|
| 1099 |
}
|
|
|
|
| 1100 |
function confetti(n) {
|
| 1101 |
const host = $("#confetti");
|
| 1102 |
host.innerHTML = "";
|
|
|
|
| 1109 |
host.appendChild(piece);
|
| 1110 |
}
|
| 1111 |
}
|
|
|
|
| 1112 |
/* ------------ Save/Load ------------ */
|
| 1113 |
function save(quiet = false) {
|
| 1114 |
state.savedAt = Date.now();
|
|
|
|
| 1121 |
if (raw) Object.assign(state, JSON.parse(raw));
|
| 1122 |
} catch (e) {}
|
| 1123 |
}
|
|
|
|
| 1124 |
/* ------------ Clear / Reset ------------ */
|
| 1125 |
function clearAll() {
|
| 1126 |
try {
|
|
|
|
| 1133 |
state.step = 0;
|
| 1134 |
state.subQ = 0;
|
| 1135 |
state.reachedEnd = false;
|
|
|
|
| 1136 |
renderPurposes();
|
| 1137 |
renderDots(0);
|
| 1138 |
$("#timeline").innerHTML = "";
|
|
|
|
| 1141 |
hideFlow();
|
| 1142 |
updateProgress();
|
| 1143 |
}
|
|
|
|
| 1144 |
/* ------------ Boot ------------ */
|
| 1145 |
load();
|
| 1146 |
renderPurposes();
|
|
|
|
| 1155 |
hideFlow();
|
| 1156 |
}
|
| 1157 |
updateProgress();
|
|
|
|
| 1158 |
// Clear button handler
|
| 1159 |
document.getElementById("clearBtn").addEventListener("click", () => {
|
| 1160 |
const ok = confirm(
|
|
|
|
| 1164 |
});
|
| 1165 |
</script>
|
| 1166 |
</body>
|
| 1167 |
+
</html>
|