Building a Resilience Self-Test Web App

The team next to mine at work is doing research on ‘AI citizenship’ — what kind of education teenagers need in the age of AI. Today they gave a short presentation on their research, and one concept that came up was the ‘collaborative navigator.’ The idea is that students shouldn’t just be filling themselves with knowledge; they need to become agents who shape their own futures. So rather than planting a flag alone, they should become people who mediate conflicts, fulfill their social responsibilities, and navigate together with others. Among the various capacities needed for this, metacognition and resilience were highlighted, with resilience taking priority. How do you actually build resilience? What if you fail? That conversation reminded me of a self-assessment file from a book on resilience I’d read five years ago.
It was an Excel file I’d picked up at some event in late 2020 — a 53-item self-assessment adapted for Korea by Professor Kim Joo-hwan. Fifty-three questions across nine subscales (emotion regulation, impulse control, causal analysis, communication, empathy, self-expansion, self-optimism, life satisfaction, and gratitude). I took it twice, six months apart, and kept a private record on my Naver blog — that was June 2021. I remember writing that my self-expansion score dropping from 29 to 27 in six months meant ‘yeah, this was definitely a period when I saw fewer people.’
Thinking it would be nice to do this with my coworkers, I opened that Excel file — and after staring at the formulas for a while, my enthusiasm cooled. The cells moved sluggishly inside the sheet, the tables broke on mobile, and the scoring was packed with SUMIFs. If a tool that requires answering all 53 questions before showing any results is this heavy, people won’t finish it. While mulling over how to make it lighter, I asked Claude Code, and the answer came right back — ‘why not just build it as a web app instead of Excel?’ I happened to have an experiments corner called /lab on byminseok.com, so I decided to build it there.
What went up at /lab/krq53/ is a single static HTML page. It lives inside the Jekyll site but leaves the layout empty and works as a standalone page. The flow has three stages — intro (introduction + source) → section-by-section progress screens → results screen (total score + grade + bar chart + 9-sided radar + answer review) — and all state is managed purely in memory. I deliberately avoided localStorage. It’s a take-it-once-and-move-on kind of tool, and if you want to retake it, starting fresh every time felt more honest. I also intentionally left out any messaging that just compares your result to the Korean average, because I think this tool is far more meaningful for tracking yourself against who you were six months ago than for absolute values.
The data structure is simple. The nine subscales sit in an array, and each subscale bundles the indices of the questions that belong to it.
const SUBSCALES = [
{ group: '자기조절능력', name: '감정조절력', items: [0,1,2,3,4,5] },
{ group: '자기조절능력', name: '충동통제력', items: [6,7,8,9,10,11] },
// ...
{ group: '긍정성', name: '감사하기', items: [47,48,49,50,51,52] }
];
const REVERSE = new Set(
[4,5,6,10,11,12,16,17,18,22,23,24,28,29,30,
34,35,36,40,41,42,51,52,53].map(n => n - 1)
);
Questions whose indices are in the REVERSE Set get flipped to (6 - response value) when scoring. This corrects for items like “I get easily swept up in my emotions,” where a lower score means higher resilience. The scoring formula is one line.
function score(idx) {
const v = answers[idx];
if (v === null) return 0;
return REVERSE.has(idx) ? (6 - v) : v;
}
There was one problem with the first version. In the original sheet, the first 3 questions of each subscale were forward-scored and the last 3 were reverse-scored, grouped together. If you port that as-is, someone working through a section will catch on — ‘ah, from here on I should answer in reverse.’ The scariest response bias in self-report scales is exactly this kind of straight-lining, and keeping forward and reverse items separated is practically an anti-pattern that encourages it. So I made one more pass. The question order gets shuffled once within each section, and that order stays fixed for the session. The order of the sections themselves (emotion regulation → impulse control → …) is left untouched.
function shuffle(arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function reshuffleSections() {
SUBSCALES.forEach(s => { s.displayItems = shuffle(s.items); });
}
Scoring still works off the original indices, so the shuffle doesn’t affect scores. displayItems is used only for rendering, and the ‘answer review’ on the results screen sorts everything back into the original 1–53 order. The reshuffle is wired to two places — the start button and the retake button.
The results screen shows the nine subscale scores in two ways, a bar chart and a radar chart. The radar is visually the most powerful — it overlays your scores and the average scores as two layered areas on a nonagon. Where one area caves in, that’s your weak dimension, and if you take the same test six months later and compare the two radars, that comparison itself becomes tracking. Five years ago, I only saw my self-expansion drop from 29 to 27 through a single bar chart — I suspect the change would have hit home sooner if I’d seen it on a radar.
After building it, I took the test again myself, five years later.

