From e17ee5c22f10759e7d0b1ee9742c8a53167a2cfb Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Sat, 21 Mar 2026 16:58:07 -0700 Subject: [PATCH 01/10] add og:img dims --- docs/index.html | 2 ++ index.html | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/index.html b/docs/index.html index 8c94a1b..bf02120 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,6 +11,8 @@ + + diff --git a/index.html b/index.html index 730d5c9..da47bde 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,8 @@ + + From c470c240bc0eb85d0f60229fa5fd18261290a2ec Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Fri, 10 Apr 2026 11:32:57 -0700 Subject: [PATCH 02/10] WIP notes to self --- HELLO.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 HELLO.md diff --git a/HELLO.md b/HELLO.md new file mode 100644 index 0000000..ca9cdbd --- /dev/null +++ b/HELLO.md @@ -0,0 +1,31 @@ +# Notes: Wild Ducks Quiz Situation + +**Date:** April 2026 + +**Context:** +- Found that Melinda at Wild Ducks released a quiz at https://wearewildducks.com/quiz +- Uses "creative flight path" metaphor with 4 patterns: Zig Zag, Spiral, Horizon Line, Braided +- Outputs 2 patterns: Dominant Path + Seasonal Path + +**Applied Designer Quiz:** +- 10+ archetypes (Orchestrator, Researcher, Experimentalist, etc.) +- Dimension-based scoring +- Primary + secondary archetype output + +**The problem:** +- "Ducks" is Melinda's entire brand +- Both quizzes solve a similar problem (career/creative identity) +- Both use dual-result format +- Haven't posted about Applied Designer quiz yet, so could look like copying + +**Current status:** +- NOT URGENT +- Feeling uncertain, don't know what to do +- Not sure if should compete, pivot, collaborate, or let it be + +**Key questions unanswered:** +- Did you enjoy building it? +- What's the goal for Applied Designer? +- Do you still want to do this? + +**Decision:** Park it. Let uncertainty settle. Revisit later. \ No newline at end of file From 36e431eb12e1aa4909574b47ac18900997dd067f Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Thu, 7 May 2026 14:57:03 -0700 Subject: [PATCH 03/10] jumbo refactor with quiz v2 - untested --- .gitignore | 3 +- .../{index-DpcNTgIm.js => index-DD83wrc6.js} | 571 ++++++++++-------- docs/assets/quizData_v1-Dn8aNCMe.js | 137 +++++ docs/index.html | 2 +- scripts/Makefile | 12 +- ...ons.cpp => score-quiz-combinations_v1.cpp} | 25 +- scripts/score-quiz-combinations_v2.cpp | 338 +++++++++++ src/data/{quizData.js => quizData_v1.js} | 18 +- src/data/quizData_v2.js | 134 ++++ src/data/scoringUtils.js | 2 +- src/pages/Quiz.jsx | 224 +++---- src/pages/Results.jsx | 29 +- src/test/dataValidation.test.js | 2 +- src/test/fixtures.test.js | 4 +- src/test/quizAnalytics.test.jsx | 23 +- src/test/quizResultsFlow.test.jsx | 2 +- src/test/resultsUrlVersioning.test.jsx | 116 ++++ src/test/scoringProperties.test.js | 2 +- src/test/scoringUtils.test.js | 8 +- src/test/utils/archetypeTestUtils.js | 2 +- 20 files changed, 1255 insertions(+), 399 deletions(-) rename docs/assets/{index-DpcNTgIm.js => index-DD83wrc6.js} (99%) create mode 100644 docs/assets/quizData_v1-Dn8aNCMe.js rename scripts/{score-quiz-combinations.cpp => score-quiz-combinations_v1.cpp} (94%) create mode 100644 scripts/score-quiz-combinations_v2.cpp rename src/data/{quizData.js => quizData_v1.js} (93%) create mode 100644 src/data/quizData_v2.js create mode 100644 src/test/resultsUrlVersioning.test.jsx diff --git a/.gitignore b/.gitignore index e343cf5..25a799d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,9 @@ src/test/fixtures test-results/ playwright-report/ -# Omit non-js files from scripts +# Omit non-code files from scripts scripts/* +!scripts/*.cpp !scripts/*.js .DS_Store diff --git a/docs/assets/index-DpcNTgIm.js b/docs/assets/index-DD83wrc6.js similarity index 99% rename from docs/assets/index-DpcNTgIm.js rename to docs/assets/index-DD83wrc6.js index a22b5aa..249996b 100644 --- a/docs/assets/index-DpcNTgIm.js +++ b/docs/assets/index-DD83wrc6.js @@ -11249,7 +11249,7 @@ function requireReactDomClient_production() { r: requestFormReset, D: prefetchDNS, C: preconnect, - L: preload, + L: preload2, m: preloadModule, X: preinitScript, S: preinitStyle, @@ -11281,7 +11281,7 @@ function requireReactDomClient_production() { previousDispatcher.C(href, crossOrigin); preconnectAs("preconnect", href, crossOrigin); } - function preload(href, as, options3) { + function preload2(href, as, options3) { previousDispatcher.L(href, as, options3); var ownerDocument = globalDocument; if (ownerDocument && href && as) { @@ -12482,6 +12482,61 @@ function requireClient() { } var clientExports = requireClient(); const ReactDOM = /* @__PURE__ */ getDefaultExportFromCjs(clientExports); +const scriptRel = "modulepreload"; +const assetsURL = function(dep) { + return "/" + dep; +}; +const seen = {}; +const __vitePreload = function preload(baseModule, deps, importerUrl) { + let promise = Promise.resolve(); + if (deps && deps.length > 0) { + let allSettled2 = function(promises$2) { + return Promise.all(promises$2.map((p2) => Promise.resolve(p2).then((value$1) => ({ + status: "fulfilled", + value: value$1 + }), (reason) => ({ + status: "rejected", + reason + })))); + }; + var allSettled = allSettled2; + document.getElementsByTagName("link"); + const cspNonceMeta = document.querySelector("meta[property=csp-nonce]"); + const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce"); + promise = allSettled2(deps.map((dep) => { + dep = assetsURL(dep); + if (dep in seen) return; + seen[dep] = true; + const isCss = dep.endsWith(".css"); + const cssSelector = isCss ? '[rel="stylesheet"]' : ""; + if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) return; + const link = document.createElement("link"); + link.rel = isCss ? "stylesheet" : scriptRel; + if (!isCss) link.as = "script"; + link.crossOrigin = ""; + link.href = dep; + if (cspNonce) link.setAttribute("nonce", cspNonce); + document.head.appendChild(link); + if (isCss) return new Promise((res, rej) => { + link.addEventListener("load", res); + link.addEventListener("error", () => rej(/* @__PURE__ */ new Error(`Unable to preload CSS for ${dep}`))); + }); + })); + } + function handlePreloadError(err$2) { + const e$12 = new Event("vite:preloadError", { cancelable: true }); + e$12.payload = err$2; + window.dispatchEvent(e$12); + if (!e$12.defaultPrevented) throw err$2; + } + return promise.then((res) => { + for (const item of res || []) { + if (item.status !== "rejected") continue; + handlePreloadError(item.reason); + } + return baseModule().catch(handlePreloadError); + }); +}; var reactExports = requireReact(); const React = /* @__PURE__ */ getDefaultExportFromCjs(reactExports); var PopStateEventType = "popstate"; @@ -136276,6 +136331,104 @@ ReactElementFactory.Instance.registerElement(LocalizableString.editableRenderer, return reactExports.createElement(SurveyLocStringEditor, props); }); checkLibraryVersion(`${"2.5.15"}`, "survey-react-ui"); +const archetypeData = { + "The Orchestrator": { + emoji: "🧠", + description: "Builds frameworks and connects dots across teams, systems, and ideas.", + mostAliveWhen: "Building systems and connecting dots across complex challenges", + mantra: "Structure creates clarity", + designers: ["Min Lew"], + dimensions: { strategy: 5, adaptability: 3, collaboration: 4, experimentation: 2, impact: 3 } + }, + "The Researcher": { + emoji: "πŸ”", + description: "Finds insight in history, data, and culture to deepen every decision.", + mostAliveWhen: "Digging into research and uncovering hidden patterns", + mantra: "Context is everything", + designers: ["Ruben Pater"], + dimensions: { strategy: 5, adaptability: 2, collaboration: 3, experimentation: 2, impact: 5 } + }, + "The Multidisciplinary": { + emoji: "🎨", + description: "Blends tools and disciplines fluidly β€” always evolving, never boxed in.", + mostAliveWhen: "Working across different media and breaking traditional boundaries", + mantra: "Everything is connected", + designers: ["Irma Boom"], + dimensions: { strategy: 3, adaptability: 5, collaboration: 3, experimentation: 4, impact: 2 } + }, + "The Generalist": { + emoji: "🧭", + description: "Adapts across roles and industries, thriving on variety and versatility.", + mostAliveWhen: "Moving between different challenges and learning new skills", + mantra: "Versatility is strength", + designers: ["Zak Kyes"], + dimensions: { strategy: 3, adaptability: 5, collaboration: 3, experimentation: 3, impact: 2 } + }, + "The Director": { + emoji: "🎬", + description: "Leads creative vision and execution β€” aligning teams to deliver clarity.", + mostAliveWhen: "Guiding teams toward a shared creative vision", + mantra: "Vision requires alignment", + designers: ["Tom Hingston"], + dimensions: { strategy: 4, adaptability: 2, collaboration: 5, experimentation: 2, impact: 3 } + }, + "The Advocate": { + emoji: "πŸ“’", + description: "Uses design as a tool for impact, equity, and industry change.", + mostAliveWhen: "Creating work that drives social and cultural change", + mantra: "Design can change the world", + designers: ["Dori Tunstall"], + dimensions: { strategy: 3, adaptability: 3, collaboration: 4, experimentation: 2, impact: 5 } + }, + "The Experimentalist": { + emoji: "πŸ§ͺ", + description: "Pushes form, function, and tools to expand what design can be.", + mostAliveWhen: "Experimenting with new materials, processes, and ideas", + mantra: "Process matters as much as outcome", + designers: ["Martine Syms"], + dimensions: { strategy: 2, adaptability: 4, collaboration: 2, experimentation: 5, impact: 3 } + }, + "The Disruptor": { + emoji: "⚑️", + description: "Challenges norms and reinvents creative paths from the inside out.", + mostAliveWhen: "Challenging established systems and creating new paradigms", + mantra: "Break things to make them better", + designers: ["Samuel Ross"], + dimensions: { strategy: 3, adaptability: 4, collaboration: 2, experimentation: 5, impact: 4 } + }, + "The Connector": { + emoji: "🀝", + description: "Bridges gaps between people, disciplines, and ways of thinking.", + mostAliveWhen: "Building bridges between different communities and ideas", + mantra: "Connection creates possibility", + designers: ["Juliette Cezzar"], + dimensions: { strategy: 3, adaptability: 3, collaboration: 5, experimentation: 2, impact: 4 } + }, + "The Idealist": { + emoji: "πŸ’‘", + description: "Imagines a better design world β€” ethical, accessible, and sustainable.", + mostAliveWhen: "Working toward more ethical and sustainable design futures", + mantra: "Design should serve humanity", + designers: ["Eike KΓΆnig"], + dimensions: { strategy: 4, adaptability: 3, collaboration: 4, experimentation: 2, impact: 5 } + }, + "The Improviser": { + emoji: "🎲", + description: "Works fast and intuitively, solving problems on the fly with creative instinct.", + mostAliveWhen: "Solving problems quickly and intuitively under pressure", + mantra: "Trust the creative flow", + designers: ["Julian Glander"], + dimensions: { strategy: 2, adaptability: 5, collaboration: 3, experimentation: 4, impact: 3 } + }, + "The Educator": { + emoji: "πŸͺ„", + description: "Shares knowledge and mentors others to grow the field of design.", + mostAliveWhen: "Teaching and mentoring others to expand their creative potential", + mantra: "Knowledge grows when shared", + designers: ["Silas Munro"], + dimensions: { strategy: 4, adaptability: 2, collaboration: 5, experimentation: 2, impact: 5 } + } +}; const quizQuestions = [ { id: "Q1", @@ -136294,17 +136447,17 @@ const quizQuestions = [ choices: [ { text: "Bridging gaps between people or roles", archetype: "The Connector" }, { text: "Taking the lead and shaping the process", archetype: "The Director" }, - { text: "Filling in wherever I’m needed", archetype: "The Improviser" }, + { text: "Filling in wherever I'm needed", archetype: "The Improviser" }, { text: "Supporting others as they grow", archetype: "The Educator" }, - { text: "Keeping things small and focused", archetype: "The Generalist" } + { text: "Building tools that help the team", archetype: "The Multidisciplinary" } ] }, { id: "Q3", - text: "What’s your creative strength?", + text: "What's your creative strength?", choices: [ { text: "Connecting unexpected ideas", archetype: "The Multidisciplinary" }, - { text: "Making sense of messy information", archetype: "The Orchestrator" }, + { text: "Imagining better futures", archetype: "The Idealist" }, { text: "Asking bold questions", archetype: "The Disruptor" }, { text: "Turning plans into action", archetype: "The Advocate" }, { text: "Explaining ideas clearly", archetype: "The Educator" } @@ -136316,14 +136469,14 @@ const quizQuestions = [ choices: [ { text: "Scaling an idea across different contexts", archetype: "The Orchestrator" }, { text: "Working without a clear path", archetype: "The Improviser" }, - { text: "Digging into deeper meaning", archetype: "The Researcher" }, + { text: "Bridging disciplines and communities", archetype: "The Connector" }, { text: "Changing how people think or act", archetype: "The Advocate" }, { text: "Reinventing how things are done", archetype: "The Disruptor" } ] }, { id: "Q5", - text: "When I’m stuck, I…", + text: "When I'm stuck, I...", choices: [ { text: "Jump into making something", archetype: "The Experimentalist" }, { text: "Look back at my research", archetype: "The Researcher" }, @@ -136345,12 +136498,12 @@ const quizQuestions = [ }, { id: "Q7", - text: "In an ideal world, my work would…", + text: "In an ideal world, my work would...", choices: [ { text: "Teach or inspire someone else", archetype: "The Educator" }, { text: "Spark new possibilities", archetype: "The Experimentalist" }, { text: "Shift a system or community", archetype: "The Advocate" }, - { text: "Work across different platforms", archetype: "The Orchestrator" }, + { text: "Imagine better futures", archetype: "The Idealist" }, { text: "Stay fluid and adaptable", archetype: "The Generalist" } ] }, @@ -136358,7 +136511,7 @@ const quizQuestions = [ id: "Q8", text: "What do I value most when designing?", choices: [ - { text: "Structure and cohesion", archetype: "The Orchestrator" }, + { text: "Systems that scale and adapt", archetype: "The Orchestrator" }, { text: "Context and clarity", archetype: "The Researcher" }, { text: "Play and experimentation", archetype: "The Experimentalist" }, { text: "Connection and collaboration", archetype: "The Connector" }, @@ -136367,7 +136520,7 @@ const quizQuestions = [ }, { id: "Q9", - text: "My dream project would be…", + text: "My dream project would be...", choices: [ { text: "Designing a system or workflow", archetype: "The Orchestrator" }, { text: "A speculative exhibition or provocation", archetype: "The Experimentalist" }, @@ -136395,14 +136548,14 @@ const quizQuestions = [ { text: "Insight that clarifies meaning", archetype: "The Educator" }, { text: "Fast reactions that shift the work", archetype: "The Improviser" }, { text: "Honest conversations", archetype: "The Director" }, - { text: "Seeing it work in the real world", archetype: "The Advocate" } + { text: "Makes the world better", archetype: "The Idealist" } ] }, { id: "Q12", - text: "I wish more people understood that design…", + text: "I wish more people understood that design...", choices: [ - { text: "Visuals can be strategic", archetype: "The Orchestrator" }, + { text: "Visuals can be strategic", archetype: "The Director" }, { text: "Can change how things work", archetype: "The Advocate" }, { text: "Is a way of thinking", archetype: "The Multidisciplinary" }, { text: "Thrives in complexity", archetype: "The Researcher" }, @@ -136410,104 +136563,10 @@ const quizQuestions = [ ] } ]; -const archetypeData = { - "The Orchestrator": { - emoji: "🧠", - description: "Builds frameworks and connects dots across teams, systems, and ideas.", - mostAliveWhen: "Building systems and connecting dots across complex challenges", - mantra: "Structure creates clarity", - designers: ["Min Lew"], - dimensions: { strategy: 5, adaptability: 3, collaboration: 4, experimentation: 2, impact: 3 } - }, - "The Researcher": { - emoji: "πŸ”", - description: "Finds insight in history, data, and culture to deepen every decision.", - mostAliveWhen: "Digging into research and uncovering hidden patterns", - mantra: "Context is everything", - designers: ["Ruben Pater"], - dimensions: { strategy: 5, adaptability: 2, collaboration: 3, experimentation: 2, impact: 5 } - }, - "The Multidisciplinary": { - emoji: "🎨", - description: "Blends tools and disciplines fluidly β€” always evolving, never boxed in.", - mostAliveWhen: "Working across different media and breaking traditional boundaries", - mantra: "Everything is connected", - designers: ["Irma Boom"], - dimensions: { strategy: 3, adaptability: 5, collaboration: 3, experimentation: 4, impact: 2 } - }, - "The Generalist": { - emoji: "🧭", - description: "Adapts across roles and industries, thriving on variety and versatility.", - mostAliveWhen: "Moving between different challenges and learning new skills", - mantra: "Versatility is strength", - designers: ["Zak Kyes"], - dimensions: { strategy: 3, adaptability: 5, collaboration: 3, experimentation: 3, impact: 2 } - }, - "The Director": { - emoji: "🎬", - description: "Leads creative vision and execution β€” aligning teams to deliver clarity.", - mostAliveWhen: "Guiding teams toward a shared creative vision", - mantra: "Vision requires alignment", - designers: ["Tom Hingston"], - dimensions: { strategy: 4, adaptability: 2, collaboration: 5, experimentation: 2, impact: 3 } - }, - "The Advocate": { - emoji: "πŸ“’", - description: "Uses design as a tool for impact, equity, and industry change.", - mostAliveWhen: "Creating work that drives social and cultural change", - mantra: "Design can change the world", - designers: ["Dori Tunstall"], - dimensions: { strategy: 3, adaptability: 3, collaboration: 4, experimentation: 2, impact: 5 } - }, - "The Experimentalist": { - emoji: "πŸ§ͺ", - description: "Pushes form, function, and tools to expand what design can be.", - mostAliveWhen: "Experimenting with new materials, processes, and ideas", - mantra: "Process matters as much as outcome", - designers: ["Martine Syms"], - dimensions: { strategy: 2, adaptability: 4, collaboration: 2, experimentation: 5, impact: 3 } - }, - "The Disruptor": { - emoji: "⚑️", - description: "Challenges norms and reinvents creative paths from the inside out.", - mostAliveWhen: "Challenging established systems and creating new paradigms", - mantra: "Break things to make them better", - designers: ["Samuel Ross"], - dimensions: { strategy: 3, adaptability: 4, collaboration: 2, experimentation: 5, impact: 4 } - }, - "The Connector": { - emoji: "🀝", - description: "Bridges gaps between people, disciplines, and ways of thinking.", - mostAliveWhen: "Building bridges between different communities and ideas", - mantra: "Connection creates possibility", - designers: ["Juliette Cezzar"], - dimensions: { strategy: 3, adaptability: 3, collaboration: 5, experimentation: 2, impact: 4 } - }, - "The Idealist": { - emoji: "πŸ’‘", - description: "Imagines a better design world β€” ethical, accessible, and sustainable.", - mostAliveWhen: "Working toward more ethical and sustainable design futures", - mantra: "Design should serve humanity", - designers: ["Eike KΓΆnig"], - dimensions: { strategy: 4, adaptability: 3, collaboration: 4, experimentation: 2, impact: 5 } - }, - "The Improviser": { - emoji: "🎲", - description: "Works fast and intuitively, solving problems on the fly with creative instinct.", - mostAliveWhen: "Solving problems quickly and intuitively under pressure", - mantra: "Trust the creative flow", - designers: ["Julian Glander"], - dimensions: { strategy: 2, adaptability: 5, collaboration: 3, experimentation: 4, impact: 3 } - }, - "The Educator": { - emoji: "πŸͺ„", - description: "Shares knowledge and mentors others to grow the field of design.", - mostAliveWhen: "Teaching and mentoring others to expand their creative potential", - mantra: "Knowledge grows when shared", - designers: ["Silas Munro"], - dimensions: { strategy: 4, adaptability: 2, collaboration: 5, experimentation: 2, impact: 5 } - } -}; +const quizData_v2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + quizQuestions +}, Symbol.toStringTag, { value: "Module" })); const ARCHETYPE_ORDER = Object.keys(archetypeData); const DIMENSION_KEYS = Object.keys(archetypeData[ARCHETYPE_ORDER[0]].dimensions); function createEmptyScores() { @@ -136668,88 +136727,98 @@ function getClosestArchetypes(userDims) { function QuizPage() { const [survey, setSurvey] = reactExports.useState(null); const [isComplete, setIsComplete] = reactExports.useState(false); + const [quizQuestions2, setQuizQuestions] = reactExports.useState([]); const navigate = useNavigate(); - const checkCompletion = (surveyData) => { - const answeredCount = Object.keys(surveyData || {}).filter( - (key) => surveyData[key] !== void 0 && surveyData[key] !== null - ).length; - const allAnswered = answeredCount === quizQuestions.length; - console.log("Completion check:", answeredCount, "of", quizQuestions.length, "=>", allAnswered); - return allAnswered; - }; reactExports.useEffect(() => { - const panelClassOrder = [ - "quiz-panel-blue", - "quiz-panel-brown", - "quiz-panel-green", - "quiz-panel-yellow", - "quiz-panel-purple" - ]; - const surveyModel = new SurveyModel({ - questions: quizQuestions.map((q, index) => ({ - type: "radiogroup", - name: q.id, - title: q.text, - description: `${index + 1} of ${quizQuestions.length}`, - choices: q.choices.map((c4) => c4.text), - isRequired: true, - showNoneItem: false - })), - showNavigationButtons: false, - showProgressBar: false, - completedHtml: "
" - }); - const applyPanelStyle = (questionName) => { - const questionIndex = quizQuestions.findIndex((question) => question.id === questionName); - if (questionIndex === -1) return; - const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; - const colors = PANEL_COLORS_HEX[panelClass]; - if (!colors) return; - const questionElements = document.querySelectorAll(".sd-question"); - questionElements.forEach((el) => { - const titleEl = el.querySelector(".sd-question__title"); - if (titleEl && titleEl.textContent.includes(quizQuestions[questionIndex].text.substring(0, 20))) { - el.style.backgroundColor = colors.bg; - el.style.color = colors.fg; - el.style.setProperty("--panel-fg", colors.fg); - el.classList.add("quiz-panel", panelClass); + const checkCompletion = (surveyData, questions) => { + const answeredCount = Object.keys(surveyData || {}).filter( + (key) => surveyData[key] !== void 0 && surveyData[key] !== null + ).length; + const allAnswered = answeredCount === questions.length; + console.log("Completion check:", answeredCount, "of", questions.length, "=>", allAnswered); + return allAnswered; + }; + const loadQuizData = async () => { + const params = new URLSearchParams(window.location.search); + const useV1 = params.get("v") === "1"; + const module = await (useV1 ? __vitePreload(() => import("./quizData_v1-Dn8aNCMe.js"), true ? [] : void 0) : __vitePreload(() => Promise.resolve().then(() => quizData_v2), true ? void 0 : void 0)); + return module.quizQuestions; + }; + loadQuizData().then((questions) => { + setQuizQuestions(questions); + const panelClassOrder = [ + "quiz-panel-blue", + "quiz-panel-brown", + "quiz-panel-green", + "quiz-panel-yellow", + "quiz-panel-purple" + ]; + const surveyModel = new SurveyModel({ + questions: questions.map((q, index) => ({ + type: "radiogroup", + name: q.id, + title: q.text, + description: `${index + 1} of ${questions.length}`, + choices: q.choices.map((c4) => c4.text), + isRequired: true, + showNoneItem: false + })), + showNavigationButtons: false, + showProgressBar: false, + completedHtml: "
" + }); + const applyPanelStyle = (questionName) => { + const questionIndex = questions.findIndex((question) => question.id === questionName); + if (questionIndex === -1) return; + const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; + const colors = PANEL_COLORS_HEX[panelClass]; + if (!colors) return; + const questionElements = document.querySelectorAll(".sd-question"); + questionElements.forEach((el) => { + const titleEl = el.querySelector(".sd-question__title"); + if (titleEl && titleEl.textContent.includes(questions[questionIndex].text.substring(0, 20))) { + el.style.backgroundColor = colors.bg; + el.style.color = colors.fg; + el.style.setProperty("--panel-fg", colors.fg); + el.classList.add("quiz-panel", panelClass); + } + }); + }; + surveyModel.onValueChanged.add((sender, options2) => { + const allAnswered = checkCompletion(sender.data, questions); + setIsComplete(allAnswered); + if (options2.question) { + setTimeout(() => applyPanelStyle(options2.question.name), 0); } }); - }; - surveyModel.onValueChanged.add((sender, options2) => { - const allAnswered = checkCompletion(sender.data); - setIsComplete(allAnswered); - if (options2.question) { - setTimeout(() => applyPanelStyle(options2.question.name), 0); - } - }); - surveyModel.onCurrentPageChanged.add((sender, _options) => { - const allAnswered = checkCompletion(sender.data); - setIsComplete(allAnswered); - }); - surveyModel.onAfterRenderQuestion.add((sender, options2) => { - const questionIndex = quizQuestions.findIndex((question) => question.id === options2.question.name); - if (questionIndex === -1) return; - const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; - const colors = PANEL_COLORS_HEX[panelClass]; - if (!colors) return; - options2.htmlElement.style.backgroundColor = colors.bg; - options2.htmlElement.style.color = colors.fg; - options2.htmlElement.style.setProperty("--panel-fg", colors.fg); - options2.htmlElement.classList.add("quiz-panel", panelClass); - const parentEl = options2.htmlElement.closest(".sd-row__question, .sd-question"); - if (parentEl && parentEl !== options2.htmlElement) { - parentEl.classList.add("quiz-panel", panelClass); - } - }); - const initiallyComplete = checkCompletion(surveyModel.data); - setIsComplete(initiallyComplete); - setSurvey(surveyModel); - setTimeout(() => { - quizQuestions.forEach((q) => { - applyPanelStyle(q.id); + surveyModel.onCurrentPageChanged.add((sender, _options) => { + const allAnswered = checkCompletion(sender.data, questions); + setIsComplete(allAnswered); }); - }, 100); + surveyModel.onAfterRenderQuestion.add((sender, options2) => { + const questionIndex = questions.findIndex((question) => question.id === options2.question.name); + if (questionIndex === -1) return; + const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; + const colors = PANEL_COLORS_HEX[panelClass]; + if (!colors) return; + options2.htmlElement.style.backgroundColor = colors.bg; + options2.htmlElement.style.color = colors.fg; + options2.htmlElement.style.setProperty("--panel-fg", colors.fg); + options2.htmlElement.classList.add("quiz-panel", panelClass); + const parentEl = options2.htmlElement.closest(".sd-row__question, .sd-question"); + if (parentEl && parentEl !== options2.htmlElement) { + parentEl.classList.add("quiz-panel", panelClass); + } + }); + const initiallyComplete = checkCompletion(surveyModel.data, questions); + setIsComplete(initiallyComplete); + setSurvey(surveyModel); + setTimeout(() => { + questions.forEach((q) => { + applyPanelStyle(q.id); + }); + }, 100); + }); }, []); const handleSubmit = () => { if (!survey || !isComplete) return; @@ -136765,17 +136834,22 @@ function QuizPage() { const dimsB64 = btoa(dimsRaw); window.gtag?.("event", "quiz_complete", { dims_raw: dimsRaw, - version: 1, + version: 2, strategy: dims.strategy, adaptability: dims.adaptability, collaboration: dims.collaboration, experimentation: dims.experimentation, impact: dims.impact, + primary: scores.primary.archetype, + secondary: scores.secondary?.archetype, question_answers: JSON.stringify(survey.data) }); - navigate(`/results?dims=${encodeURIComponent(dimsB64)}`); + const params = new URLSearchParams({ v: "2", dims: dimsB64 }); + if (scores.primary?.archetype) params.set("p", scores.primary.archetype); + if (scores.secondary?.archetype) params.set("s", scores.secondary.archetype); + navigate(`/results?${params.toString()}`); }; - if (!survey) { + if (!survey || quizQuestions2.length === 0) { return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "quiz-container", children: "Loading..." }); } return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "quiz-container", children: [ @@ -182030,14 +182104,14 @@ function initializeContext(params) { function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) { var _a2; const def = schema._zod.def; - const seen = ctx.seen.get(schema); - if (seen) { - seen.count++; + const seen2 = ctx.seen.get(schema); + if (seen2) { + seen2.count++; const isCycle = _params.schemaPath.includes(schema); if (isCycle) { - seen.cycle = _params.path; + seen2.cycle = _params.path; } - return seen.schema; + return seen2.schema; } const result = { schema: {}, count: 1, cycle: void 0, path: _params.path }; ctx.seen.set(schema, result); @@ -182120,12 +182194,12 @@ function extractDefs(ctx, schema) { if (entry[1].schema.$ref) { return; } - const seen = entry[1]; + const seen2 = entry[1]; const { ref: ref2, defId } = makeURI(entry); - seen.def = { ...seen.schema }; + seen2.def = { ...seen2.schema }; if (defId) - seen.defId = defId; - const schema2 = seen.schema; + seen2.defId = defId; + const schema2 = seen2.schema; for (const key in schema2) { delete schema2[key]; } @@ -182133,16 +182207,16 @@ function extractDefs(ctx, schema) { }; if (ctx.cycles === "throw") { for (const entry of ctx.seen.entries()) { - const seen = entry[1]; - if (seen.cycle) { - throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/ + const seen2 = entry[1]; + if (seen2.cycle) { + throw new Error(`Cycle detected: #/${seen2.cycle?.join("/")}/ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`); } } } for (const entry of ctx.seen.entries()) { - const seen = entry[1]; + const seen2 = entry[1]; if (schema === entry[0]) { extractToDef(entry); continue; @@ -182159,11 +182233,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. extractToDef(entry); continue; } - if (seen.cycle) { + if (seen2.cycle) { extractToDef(entry); continue; } - if (seen.count > 1) { + if (seen2.count > 1) { if (ctx.reused === "ref") { extractToDef(entry); continue; @@ -182176,13 +182250,13 @@ function finalize(ctx, schema) { if (!root2) throw new Error("Unprocessed schema. This is a bug in Zod."); const flattenRef = (zodSchema) => { - const seen = ctx.seen.get(zodSchema); - if (seen.ref === null) + const seen2 = ctx.seen.get(zodSchema); + if (seen2.ref === null) return; - const schema2 = seen.def ?? seen.schema; + const schema2 = seen2.def ?? seen2.schema; const _cached = { ...schema2 }; - const ref2 = seen.ref; - seen.ref = null; + const ref2 = seen2.ref; + seen2.ref = null; if (ref2) { flattenRef(ref2); const refSeen = ctx.seen.get(ref2); @@ -182234,7 +182308,7 @@ function finalize(ctx, schema) { ctx.override({ zodSchema, jsonSchema: schema2, - path: seen.path ?? [] + path: seen2.path ?? [] }); }; for (const entry of [...ctx.seen.entries()].reverse()) { @@ -182258,9 +182332,9 @@ function finalize(ctx, schema) { Object.assign(result, root2.def ?? root2.schema); const defs = ctx.external?.defs ?? {}; for (const entry of ctx.seen.entries()) { - const seen = entry[1]; - if (seen.def && seen.defId) { - defs[seen.defId] = seen.def; + const seen2 = entry[1]; + if (seen2.def && seen2.defId) { + defs[seen2.defId] = seen2.def; } } if (ctx.external) ; @@ -182634,9 +182708,9 @@ const tupleProcessor = (schema, ctx, _json, params) => { const nullableProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; const inner = process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); + const seen2 = ctx.seen.get(schema); if (ctx.target === "openapi-3.0") { - seen.ref = def.innerType; + seen2.ref = def.innerType; json.nullable = true; } else { json.anyOf = [inner, { type: "null" }]; @@ -182645,29 +182719,29 @@ const nullableProcessor = (schema, ctx, json, params) => { const nonoptionalProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; }; const defaultProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; json.default = JSON.parse(JSON.stringify(def.defaultValue)); }; const prefaultProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue)); }; const catchProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; let catchValue; try { catchValue = def.catchValue(void 0); @@ -182680,21 +182754,21 @@ const pipeProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out; process$1(innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = innerType; }; const readonlyProcessor = (schema, ctx, json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; json.readOnly = true; }; const optionalProcessor = (schema, ctx, _json, params) => { const def = schema._zod.def; process$1(def.innerType, ctx, params); - const seen = ctx.seen.get(schema); - seen.ref = def.innerType; + const seen2 = ctx.seen.get(schema); + seen2.ref = def.innerType; }; const ZodISODateTime = /* @__PURE__ */ $constructor("ZodISODateTime", (inst, def) => { $ZodISODateTime.init(inst, def); @@ -206261,20 +206335,20 @@ class Font { newVertexIndex.push(vertexIndices[id]); } const newFaces = geom.faces.map((f) => f.map((i) => newVertexIndex[i])); - const seen = {}; + const seen2 = {}; for (const face of newFaces) { for (let off = 0; off < face.length; off++) { const a2 = face[off]; const b2 = face[(off + 1) % face.length]; const id = `${Math.min(a2, b2)}-${Math.max(a2, b2)}`; - if (!seen[id]) seen[id] = []; - seen[id].push([a2, b2]); + if (!seen2[id]) seen2[id] = []; + seen2[id].push([a2, b2]); } } const validEdges = []; - for (const key in seen) { - if (seen[key].length === 1) { - validEdges.push(seen[key][0]); + for (const key in seen2) { + if (seen2[key].length === 1) { + validEdges.push(seen2[key][0]); } } const extruded = this._pInst.buildGeometry(() => { @@ -216246,13 +216320,30 @@ function ResultsPage() { const navigate = useNavigate(); const [copyText, setCopyText] = reactExports.useState("Copy Link"); const params = new URLSearchParams(location2.search); + const version2 = params.get("v"); const dimsB64 = params.get("dims"); const dims = dimsB64 ? decodeDims(dimsB64) : null; if (!dims) { navigate("/quiz", { replace: true }); return null; } - const { primary, secondary } = getClosestArchetypes(dims); + if (version2 !== null && version2 !== "2") { + navigate("/quiz", { replace: true }); + return null; + } + let primary, secondary; + if (version2 === "2") { + const votePrimary = params.get("p"); + const voteSecondary = params.get("s"); + if (votePrimary && archetypeData[votePrimary]) { + primary = votePrimary; + secondary = voteSecondary && archetypeData[voteSecondary] ? voteSecondary : null; + } else { + ({ primary, secondary } = getClosestArchetypes(dims)); + } + } else { + ({ primary, secondary } = getClosestArchetypes(dims)); + } const primaryData = archetypeData[primary] || {}; const secondaryData = archetypeData[secondary] || {}; const radarValues = dimsToArray(dims); diff --git a/docs/assets/quizData_v1-Dn8aNCMe.js b/docs/assets/quizData_v1-Dn8aNCMe.js new file mode 100644 index 0000000..0da3c76 --- /dev/null +++ b/docs/assets/quizData_v1-Dn8aNCMe.js @@ -0,0 +1,137 @@ +const quizQuestions = [ + { + id: "Q1", + text: "What excites you most when starting a new project?", + choices: [ + { text: "Finding patterns and designing a system", archetype: "The Orchestrator" }, + { text: "Researching the context", archetype: "The Researcher" }, + { text: "Trying a lot of different ideas", archetype: "The Experimentalist" }, + { text: "Getting the team to work toward the same vision", archetype: "The Director" }, + { text: "Discovery along the way", archetype: "The Generalist" } + ] + }, + { + id: "Q2", + text: "How do you approach working with others?", + choices: [ + { text: "Bridging gaps between people or roles", archetype: "The Connector" }, + { text: "Taking the lead and shaping the process", archetype: "The Director" }, + { text: "Filling in wherever I'm needed", archetype: "The Improviser" }, + { text: "Supporting others as they grow", archetype: "The Educator" }, + { text: "Keeping things small and focused", archetype: "The Generalist" } + ] + }, + { + id: "Q3", + text: "What's your creative strength?", + choices: [ + { text: "Connecting unexpected ideas", archetype: "The Multidisciplinary" }, + { text: "Making sense of messy information", archetype: "The Orchestrator" }, + { text: "Asking bold questions", archetype: "The Disruptor" }, + { text: "Turning plans into action", archetype: "The Advocate" }, + { text: "Explaining ideas clearly", archetype: "The Educator" } + ] + }, + { + id: "Q4", + text: "What kind of challenge energizes you?", + choices: [ + { text: "Scaling an idea across different contexts", archetype: "The Orchestrator" }, + { text: "Working without a clear path", archetype: "The Improviser" }, + { text: "Digging into deeper meaning", archetype: "The Researcher" }, + { text: "Changing how people think or act", archetype: "The Advocate" }, + { text: "Reinventing how things are done", archetype: "The Disruptor" } + ] + }, + { + id: "Q5", + text: "When I'm stuck, I...", + choices: [ + { text: "Jump into making something", archetype: "The Experimentalist" }, + { text: "Look back at my research", archetype: "The Researcher" }, + { text: "Zoom out and reframe the problem", archetype: "The Generalist" }, + { text: "Talk it through with someone", archetype: "The Connector" }, + { text: "Try something totally unexpected", archetype: "The Disruptor" } + ] + }, + { + id: "Q6", + text: "How would I describe my creative path so far?", + choices: [ + { text: "Nonlinear and cross-disciplinary", archetype: "The Multidisciplinary" }, + { text: "Driven by purpose or values", archetype: "The Idealist" }, + { text: "Full of pivots and changes", archetype: "The Improviser" }, + { text: "Focused on shaping ideas with others", archetype: "The Director" }, + { text: "Still in motion β€” and open", archetype: "The Generalist" } + ] + }, + { + id: "Q7", + text: "In an ideal world, my work would...", + choices: [ + { text: "Teach or inspire someone else", archetype: "The Educator" }, + { text: "Spark new possibilities", archetype: "The Experimentalist" }, + { text: "Shift a system or community", archetype: "The Advocate" }, + { text: "Work across different platforms", archetype: "The Orchestrator" }, + { text: "Stay fluid and adaptable", archetype: "The Generalist" } + ] + }, + { + id: "Q8", + text: "What do I value most when designing?", + choices: [ + { text: "Structure and cohesion", archetype: "The Orchestrator" }, + { text: "Context and clarity", archetype: "The Researcher" }, + { text: "Play and experimentation", archetype: "The Experimentalist" }, + { text: "Connection and collaboration", archetype: "The Connector" }, + { text: "Speed and flow", archetype: "The Improviser" } + ] + }, + { + id: "Q9", + text: "My dream project would be...", + choices: [ + { text: "Designing a system or workflow", archetype: "The Orchestrator" }, + { text: "A speculative exhibition or provocation", archetype: "The Experimentalist" }, + { text: "A mentorship or learning experience", archetype: "The Educator" }, + { text: "A tool that reshapes how we work", archetype: "The Disruptor" }, + { text: "Something that spans industries", archetype: "The Multidisciplinary" } + ] + }, + { + id: "Q10", + text: "I relate to design tools as...", + choices: [ + { text: "Extensions of my thinking", archetype: "The Researcher" }, + { text: "Things to question or remix", archetype: "The Disruptor" }, + { text: "Flexible and ever-changing", archetype: "The Generalist" }, + { text: "Something I share or teach", archetype: "The Educator" }, + { text: "A way to visualize deeper ideas", archetype: "The Idealist" } + ] + }, + { + id: "Q11", + text: "The kind of feedback that fuels me is...", + choices: [ + { text: "Open-ended questions", archetype: "The Researcher" }, + { text: "Insight that clarifies meaning", archetype: "The Educator" }, + { text: "Fast reactions that shift the work", archetype: "The Improviser" }, + { text: "Honest conversations", archetype: "The Director" }, + { text: "Seeing it work in the real world", archetype: "The Advocate" } + ] + }, + { + id: "Q12", + text: "I wish more people understood that design...", + choices: [ + { text: "Visuals can be strategic", archetype: "The Orchestrator" }, + { text: "Can change how things work", archetype: "The Advocate" }, + { text: "Is a way of thinking", archetype: "The Multidisciplinary" }, + { text: "Thrives in complexity", archetype: "The Researcher" }, + { text: "Belongs everywhere", archetype: "The Connector" } + ] + } +]; +export { + quizQuestions +}; diff --git a/docs/index.html b/docs/index.html index bf02120..e203ea2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -44,7 +44,7 @@ Learn how to configure a non-root public URL by running `npm run build`. --> - + diff --git a/scripts/Makefile b/scripts/Makefile index e5a43a0..dbea4c7 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -26,13 +26,13 @@ compress: gzip -f ../src/test/fixtures/quiz_answer_combinations.csv @echo "βœ… Compression complete: ../src/test/fixtures/quiz_answer_combinations.csv.gz" -score: score-quiz-combinations - @echo "πŸš€ Scoring all combinations..." - ./score-quiz-combinations +score: score-quiz-combinations_v2 + @echo "πŸš€ Scoring all combinations (v2)..." + ./score-quiz-combinations_v2 -score-quiz-combinations: score-quiz-combinations.cpp - @echo "πŸ”¨ Compiling score-quiz-combinations.cpp..." - $(CXX) $(CXXFLAGS) score-quiz-combinations.cpp -o score-quiz-combinations -lz +score-quiz-combinations_v2: score-quiz-combinations_v2.cpp + @echo "πŸ”¨ Compiling score-quiz-combinations_v2.cpp..." + $(CXX) $(CXXFLAGS) score-quiz-combinations_v2.cpp -o score-quiz-combinations_v2 -lz analyze: analyze-archetype-distribution @echo "πŸš€ Analyzing archetype distribution..." diff --git a/scripts/score-quiz-combinations.cpp b/scripts/score-quiz-combinations_v1.cpp similarity index 94% rename from scripts/score-quiz-combinations.cpp rename to scripts/score-quiz-combinations_v1.cpp index 8fdb7e3..487a105 100644 --- a/scripts/score-quiz-combinations.cpp +++ b/scripts/score-quiz-combinations_v1.cpp @@ -19,7 +19,7 @@ const int GZIP_BUFFER_SIZE = 4 * 1024 * 1024; // 4MB gzip read buffer const int WRITE_BUFFER_SIZE = 100000; // Batch write every 100k rows const int LINE_BUFFER_SIZE = 256; -// Archetype dimension profiles from archetypeData.js +// Archetype dimension profiles from archetypeData.js (v1 - ORIGINAL) struct Archetype { std::string id; std::string name; @@ -42,7 +42,7 @@ std::vector archetypes = { {"idealist", "The Idealist", {2, 2, 4, 3, 5}, 0.0}, }; -// Quiz answer to archetype mapping (from quizData.js) +// Quiz answer to archetype mapping (v1 - ORIGINAL) std::vector quizAnswerToArchetype = { 0, // Q1: A1 -> Orchestrator 1, // Q1: A2 -> Generalist @@ -79,11 +79,10 @@ std::vector quizAnswerToArchetype = { 8, // Q7: A3 -> Improviser 9, // Q7: A4 -> Advocate 10, // Q7: A5 -> Multidisciplinary - 11, // Q8: A1 -> Idealist - 0, // Q8: A2 -> Orchestrator - 1, // Q8: A3 -> Generalist - 2, // Q8: A4 -> Researcher - 3, // Q8: A5 -> Experimentalist + 0, // Q8: A1 -> Orchestrator + 1, // Q8: A2 -> Generalist + 2, // Q8: A3 -> Researcher + 3, // Q8: A4 -> Experimentalist 4, // Q9: A1 -> Director 5, // Q9: A2 -> Disruptor 6, // Q9: A3 -> Educator @@ -165,9 +164,9 @@ TieResult getClosestArchetypesWithTies(const std::vector& dims) { int main() { auto start_time = std::chrono::high_resolution_clock::now(); - std::cout << "🎯 Scoring " << TOTAL_COMBINATIONS << " quiz answer combinations..." << std::endl; + std::cout << "🎯 Scoring " << TOTAL_COMBINATIONS << " quiz answer combinations (v1 - cosine)..." << std::endl; std::cout << "πŸ“ Input: src/test/fixtures/quiz_answer_combinations.csv.gz" << std::endl; - std::cout << "πŸ“ Output: src/test/fixtures/quiz_results.csv" << std::endl << std::endl; + std::cout << "πŸ“ Output: src/test/fixtures/quiz_results_v1.csv" << std::endl << std::endl; // Precompute archetype norms (optimization #1) for (auto& arch : archetypes) { @@ -186,7 +185,7 @@ int main() { } gzbuffer(infile, GZIP_BUFFER_SIZE); // Set 4MB buffer - std::ofstream outfile("../src/test/fixtures/quiz_results.csv"); + std::ofstream outfile("../src/test/fixtures/quiz_results_v1.csv"); if (!outfile.is_open()) { std::cerr << "❌ Error: Could not open output file" << std::endl; gzclose(infile); @@ -303,11 +302,11 @@ int main() { double elapsed_seconds = elapsed / 1000.0; // Write stats to JSON - std::ofstream statsfile("../src/test/fixtures/quiz_results_stats.json"); + std::ofstream statsfile("../src/test/fixtures/quiz_results_v1_stats.json"); statsfile << "{\n"; statsfile << " \"total_combinations\": " << lineNum << ",\n"; statsfile << " \"runtime_seconds\": " << std::fixed << std::setprecision(1) << elapsed_seconds << ",\n"; - statsfile << " \"combinations_per_second\": " << std::fixed << std::setprecision(0) << (lineNum / elapsed_seconds) << ",\n"; + statsfile << " \"combos_per_second\": " << std::fixed << std::setprecision(0) << (lineNum / elapsed_seconds) << ",\n"; statsfile << " \"tie_statistics\": {\n"; statsfile << " \"no_ties\": " << noTies << " (" << std::fixed << std::setprecision(2) << (100.0 * noTies / lineNum) << "%),\n"; @@ -336,7 +335,7 @@ int main() { std::cout << "πŸ“Š 4+ way primary ties: " << (lineNum - noTies - twoWayPrimary - threeWayPrimary) << " (" << std::fixed << std::setprecision(2) << (100.0 * (lineNum - noTies - twoWayPrimary - threeWayPrimary) / lineNum) << "%)" << std::endl; - std::cout << "\nDetailed tie breakdown in: ../src/test/fixtures/quiz_results_stats.json" << std::endl; + std::cout << "\nDetailed tie breakdown in: ../src/test/fixtures/quiz_results_v1_stats.json" << std::endl; return 0; } diff --git a/scripts/score-quiz-combinations_v2.cpp b/scripts/score-quiz-combinations_v2.cpp new file mode 100644 index 0000000..01cc40f --- /dev/null +++ b/scripts/score-quiz-combinations_v2.cpp @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const long long TOTAL_COMBINATIONS = 244140625LL; +const int QUESTIONS = 12; +const int CHOICES = 5; +const int NUM_ARCHETYPES = 12; +const int NUM_DIMENSIONS = 5; +const long long PROGRESS_INTERVAL = 10000000; +const int GZIP_BUFFER_SIZE = 4 * 1024 * 1024; // 4MB gzip read buffer +const int WRITE_BUFFER_SIZE = 100000; // Batch write every 100k rows +const int LINE_BUFFER_SIZE = 256; + +// Archetype dimension profiles from archetypeData.js (MUST match JS exactly) +struct Archetype { + std::string id; + std::string name; + std::vector dimensions; // [strategy, adaptability, collaboration, experimentation, impact] + double norm; // Precomputed L2 norm +}; + +std::vector archetypes = { + {"orchestrator", "The Orchestrator", {5, 3, 4, 2, 3}, 0.0}, + {"generalist", "The Generalist", {3, 5, 3, 3, 2}, 0.0}, + {"researcher", "The Researcher", {5, 2, 3, 2, 5}, 0.0}, + {"experimentalist", "The Experimentalist", {2, 4, 2, 5, 3}, 0.0}, + {"director", "The Director", {4, 2, 5, 2, 3}, 0.0}, + {"disruptor", "The Disruptor", {3, 4, 2, 5, 4}, 0.0}, + {"educator", "The Educator", {4, 2, 5, 2, 5}, 0.0}, + {"connector", "The Connector", {3, 3, 5, 2, 4}, 0.0}, + {"improviser", "The Improviser", {2, 5, 3, 4, 3}, 0.0}, + {"advocate", "The Advocate", {3, 3, 4, 2, 5}, 0.0}, + {"multidisciplinary", "The Multidisciplinary", {3, 5, 3, 4, 2}, 0.0}, + {"idealist", "The Idealist", {4, 3, 4, 2, 5}, 0.0}, +}; + +// Quiz answer to archetype mapping (from quizData.js) +// Archetype indices: 0=orchestrator,1=generalist,2=researcher,3=experimentalist,4=director, +// 5=disruptor,6=educator,7=connector,8=improviser,9=advocate,10=multidisciplinary,11=idealist +std::vector quizAnswerToArchetype = { + 0, // Q1: A1 -> The Orchestrator + 2, // Q1: A2 -> The Researcher + 3, // Q1: A3 -> The Experimentalist + 4, // Q1: A4 -> The Director + 1, // Q1: A5 -> The Generalist + 7, // Q2: A1 -> The Connector + 4, // Q2: A2 -> The Director + 8, // Q2: A3 -> The Improviser + 6, // Q2: A4 -> The Educator + 10, // Q2: A5 -> The Multidisciplinary + 10, // Q3: A1 -> The Multidisciplinary + 11, // Q3: A2 -> The Idealist + 5, // Q3: A3 -> The Disruptor + 9, // Q3: A4 -> The Advocate + 6, // Q3: A5 -> The Educator + 0, // Q4: A1 -> The Orchestrator + 8, // Q4: A2 -> The Improviser + 7, // Q4: A3 -> The Connector + 9, // Q4: A4 -> The Advocate + 5, // Q4: A5 -> The Disruptor + 3, // Q5: A1 -> The Experimentalist + 2, // Q5: A2 -> The Researcher + 1, // Q5: A3 -> The Generalist + 7, // Q5: A4 -> The Connector + 5, // Q5: A5 -> The Disruptor + 10, // Q6: A1 -> The Multidisciplinary + 11, // Q6: A2 -> The Idealist + 8, // Q6: A3 -> The Improviser + 4, // Q6: A4 -> The Director + 1, // Q6: A5 -> The Generalist + 6, // Q7: A1 -> The Educator + 3, // Q7: A2 -> The Experimentalist + 9, // Q7: A3 -> The Advocate + 11, // Q7: A4 -> The Idealist + 1, // Q7: A5 -> The Generalist + 0, // Q8: A1 -> The Orchestrator + 2, // Q8: A2 -> The Researcher + 3, // Q8: A3 -> The Experimentalist + 7, // Q8: A4 -> The Connector + 8, // Q8: A5 -> The Improviser + 0, // Q9: A1 -> The Orchestrator + 3, // Q9: A2 -> The Experimentalist + 6, // Q9: A3 -> The Educator + 5, // Q9: A4 -> The Disruptor + 10, // Q9: A5 -> The Multidisciplinary + 2, // Q10: A1 -> The Researcher + 5, // Q10: A2 -> The Disruptor + 1, // Q10: A3 -> The Generalist + 6, // Q10: A4 -> The Educator + 11, // Q10: A5 -> The Idealist + 2, // Q11: A1 -> The Researcher + 6, // Q11: A2 -> The Educator + 8, // Q11: A3 -> The Improviser + 4, // Q11: A4 -> The Director + 11, // Q11: A5 -> The Idealist + 4, // Q12: A1 -> The Director + 9, // Q12: A2 -> The Advocate + 10, // Q12: A3 -> The Multidisciplinary + 2, // Q12: A4 -> The Researcher + 7, // Q12: A5 -> The Connector +}; + +struct TieResult { + int primaryCount; + int secondaryCount; + int primaryArchetype; + int secondaryArchetype; +}; + +TieResult getArchetypesFromVotes(const std::vector& voteCounts) { + // Find primary (highest vote count) + int primaryIdx = 0; + for (int i = 1; i < NUM_ARCHETYPES; ++i) { + if (voteCounts[i] > voteCounts[primaryIdx]) { + primaryIdx = i; + } + } + + // Count primary ties + int primaryCount = 0; + for (int i = 0; i < NUM_ARCHETYPES; ++i) { + if (voteCounts[i] == voteCounts[primaryIdx]) { + primaryCount++; + } + } + + // Find secondary (highest vote count excluding primary) + int secondaryIdx = -1; + for (int i = 0; i < NUM_ARCHETYPES; ++i) { + if (i == primaryIdx) continue; + if (secondaryIdx == -1 || voteCounts[i] > voteCounts[secondaryIdx]) { + secondaryIdx = i; + } + } + + // Count secondary ties + int secondaryCount = 0; + if (secondaryIdx >= 0) { + for (int i = 0; i < NUM_ARCHETYPES; ++i) { + if (i == primaryIdx) continue; + if (voteCounts[i] == voteCounts[secondaryIdx]) { + secondaryCount++; + } + } + } + + return { + primaryCount, + secondaryCount, + primaryIdx, + secondaryIdx + }; +} + +int main() { + auto start_time = std::chrono::high_resolution_clock::now(); + + std::cout << "🎯 Scoring " << TOTAL_COMBINATIONS << " quiz answer combinations..." << std::endl; + std::cout << "πŸ“ Input: src/test/fixtures/quiz_answer_combinations.csv.gz" << std::endl; + std::cout << "πŸ“ Output: src/test/fixtures/quiz_results.csv" << std::endl << std::endl; + + // Precompute archetype norms (optimization #1) + for (auto& arch : archetypes) { + double norm = 0; + for (auto d : arch.dimensions) { + norm += d * d; + } + arch.norm = std::sqrt(norm); + } + + // Open gzip file with larger buffer (optimization #4) + gzFile infile = gzopen("../src/test/fixtures/quiz_answer_combinations.csv.gz", "rb"); + if (!infile) { + std::cerr << "❌ Error: Could not open input file" << std::endl; + return 1; + } + gzbuffer(infile, GZIP_BUFFER_SIZE); // Set 4MB buffer + + std::ofstream outfile("../src/test/fixtures/quiz_results.csv"); + if (!outfile.is_open()) { + std::cerr << "❌ Error: Could not open output file" << std::endl; + gzclose(infile); + return 1; + } + + // Write header + outfile << "combo_index,primary_archetype,secondary_archetype\n"; + + // Tie tracking + long long noTies = 0; + long long twoWayPrimary = 0; + long long threeWayPrimary = 0; + std::map wayTieCount; + + long long lineNum = 0; + char gzip_buffer[GZIP_BUFFER_SIZE]; + char line_buffer[LINE_BUFFER_SIZE]; + std::string write_buffer; // Batch write buffer (optimization #2) + write_buffer.reserve(WRITE_BUFFER_SIZE * 100); // Pre-allocate + + std::vector answers; // Pre-allocate (optimization #3) + answers.reserve(QUESTIONS); + std::vector dims(NUM_DIMENSIONS, 0); + + // Skip header line + gzgets(infile, line_buffer, sizeof(line_buffer)); + + while (gzgets(infile, line_buffer, sizeof(line_buffer)) != nullptr) { + lineNum++; + + // Fast CSV parsing (optimization #3 - manual character parsing) + answers.clear(); + int value = 0; + for (char* p = line_buffer; *p; ++p) { + if (*p == ',') { + answers.push_back(value); + value = 0; + } else if (*p >= '0' && *p <= '4') { + value = *p - '0'; + } + } + if (value >= 0 && value <= 4) { + answers.push_back(value); + } + + if (answers.size() != QUESTIONS) continue; + + // Count votes for each archetype + std::vector voteCounts(NUM_ARCHETYPES, 0); + for (int q = 0; q < QUESTIONS; ++q) { + int archetypeIdx = quizAnswerToArchetype[q * CHOICES + answers[q]]; + voteCounts[archetypeIdx]++; + } + + // Find archetypes from vote counts + TieResult result = getArchetypesFromVotes(voteCounts); + + // Track tie statistics + wayTieCount[result.primaryCount]++; + if (result.primaryCount == 1) { + noTies++; + } else if (result.primaryCount == 2) { + twoWayPrimary++; + } else if (result.primaryCount == 3) { + threeWayPrimary++; + } + + // Build result line + char result_line[256]; + snprintf(result_line, sizeof(result_line), "%lld,%s,%s\n", + lineNum - 1, + archetypes[result.primaryArchetype].id.c_str(), + result.secondaryArchetype >= 0 ? archetypes[result.secondaryArchetype].id.c_str() : "N/A"); + write_buffer += result_line; + + // Batch write (optimization #2) + if (lineNum % WRITE_BUFFER_SIZE == 0) { + outfile << write_buffer; + write_buffer.clear(); + } + + // Progress logging + if (lineNum % PROGRESS_INTERVAL == 0) { + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start_time).count(); + double elapsed_seconds = elapsed / 1000.0; + double rate = lineNum / elapsed_seconds; + long long remaining_total = TOTAL_COMBINATIONS - lineNum; + double eta_seconds = remaining_total / rate; + double eta_minutes = eta_seconds / 60.0; + + std::cout << "βœ“ Scored " << lineNum << " combinations | " + << std::fixed << std::setprecision(0) + << rate << " combos/sec | " + << "ETA: " << std::fixed << std::setprecision(1) + << eta_minutes << " minutes remaining" << std::endl; + } + } + + // Flush remaining buffer + if (!write_buffer.empty()) { + outfile << write_buffer; + } + + gzclose(infile); + outfile.close(); + + // Calculate final stats + auto end_time = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end_time - start_time).count(); + double elapsed_seconds = elapsed / 1000.0; + + // Write stats to JSON + std::ofstream statsfile("../src/test/fixtures/quiz_results_stats.json"); + statsfile << "{\n"; + statsfile << " \"total_combinations\": " << lineNum << ",\n"; + statsfile << " \"runtime_seconds\": " << std::fixed << std::setprecision(1) << elapsed_seconds << ",\n"; + statsfile << " \"combinations_per_second\": " << std::fixed << std::setprecision(0) << (lineNum / elapsed_seconds) << ",\n"; + statsfile << " \"tie_statistics\": {\n"; + statsfile << " \"no_ties\": " << noTies << " (" << std::fixed << std::setprecision(2) << (100.0 * noTies / lineNum) << "%),\n"; + + // Output all N-way tie counts + bool first = true; + for (auto& p : wayTieCount) { + if (!first) statsfile << ",\n"; + statsfile << " \"" << p.first << "_way_tie\": " << p.second << " (" + << std::fixed << std::setprecision(2) << (100.0 * p.second / lineNum) << "%)"; + first = false; + } + + statsfile << "\n }\n"; + statsfile << "}\n"; + statsfile.close(); + + std::cout << std::endl; + std::cout << "βœ… Done! Scored " << lineNum << " combinations in " + << std::fixed << std::setprecision(1) << elapsed_seconds << "s" << std::endl; + std::cout << "πŸ“Š No ties: " << noTies << " (" << std::fixed << std::setprecision(2) + << (100.0 * noTies / lineNum) << "%)" << std::endl; + std::cout << "πŸ“Š 2-way primary ties: " << twoWayPrimary << " (" << std::fixed << std::setprecision(2) + << (100.0 * twoWayPrimary / lineNum) << "%)" << std::endl; + std::cout << "πŸ“Š 3-way primary ties: " << threeWayPrimary << " (" << std::fixed << std::setprecision(2) + << (100.0 * threeWayPrimary / lineNum) << "%)" << std::endl; + std::cout << "πŸ“Š 4+ way primary ties: " << (lineNum - noTies - twoWayPrimary - threeWayPrimary) + << " (" << std::fixed << std::setprecision(2) + << (100.0 * (lineNum - noTies - twoWayPrimary - threeWayPrimary) / lineNum) << "%)" << std::endl; + std::cout << "\nDetailed tie breakdown in: ../src/test/fixtures/quiz_results_stats.json" << std::endl; + + return 0; +} diff --git a/src/data/quizData.js b/src/data/quizData_v1.js similarity index 93% rename from src/data/quizData.js rename to src/data/quizData_v1.js index 31eab27..8676543 100644 --- a/src/data/quizData.js +++ b/src/data/quizData_v1.js @@ -16,14 +16,14 @@ export const quizQuestions = [ choices: [ { text: 'Bridging gaps between people or roles', archetype: 'The Connector' }, { text: 'Taking the lead and shaping the process', archetype: 'The Director' }, - { text: 'Filling in wherever I’m needed', archetype: 'The Improviser' }, + { text: 'Filling in wherever I\'m needed', archetype: 'The Improviser' }, { text: 'Supporting others as they grow', archetype: 'The Educator' }, { text: 'Keeping things small and focused', archetype: 'The Generalist' } ] }, { id: 'Q3', - text: 'What’s your creative strength?', + text: 'What\'s your creative strength?', choices: [ { text: 'Connecting unexpected ideas', archetype: 'The Multidisciplinary' }, { text: 'Making sense of messy information', archetype: 'The Orchestrator' }, @@ -45,7 +45,7 @@ export const quizQuestions = [ }, { id: 'Q5', - text: 'When I’m stuck, I…', + text: 'When I\'m stuck, I...', choices: [ { text: 'Jump into making something', archetype: 'The Experimentalist' }, { text: 'Look back at my research', archetype: 'The Researcher' }, @@ -67,7 +67,7 @@ export const quizQuestions = [ }, { id: 'Q7', - text: 'In an ideal world, my work would…', + text: 'In an ideal world, my work would...', choices: [ { text: 'Teach or inspire someone else', archetype: 'The Educator' }, { text: 'Spark new possibilities', archetype: 'The Experimentalist' }, @@ -89,7 +89,7 @@ export const quizQuestions = [ }, { id: 'Q9', - text: 'My dream project would be…', + text: 'My dream project would be...', choices: [ { text: 'Designing a system or workflow', archetype: 'The Orchestrator' }, { text: 'A speculative exhibition or provocation', archetype: 'The Experimentalist' }, @@ -100,7 +100,7 @@ export const quizQuestions = [ }, { id: 'Q10', - text: 'I relate to design tools as…', + text: 'I relate to design tools as...', choices: [ { text: 'Extensions of my thinking', archetype: 'The Researcher' }, { text: 'Things to question or remix', archetype: 'The Disruptor' }, @@ -111,7 +111,7 @@ export const quizQuestions = [ }, { id: 'Q11', - text: 'The kind of feedback that fuels me is…', + text: 'The kind of feedback that fuels me is...', choices: [ { text: 'Open-ended questions', archetype: 'The Researcher' }, { text: 'Insight that clarifies meaning', archetype: 'The Educator' }, @@ -122,7 +122,7 @@ export const quizQuestions = [ }, { id: 'Q12', - text: 'I wish more people understood that design…', + text: 'I wish more people understood that design...', choices: [ { text: 'Visuals can be strategic', archetype: 'The Orchestrator' }, { text: 'Can change how things work', archetype: 'The Advocate' }, @@ -131,4 +131,4 @@ export const quizQuestions = [ { text: 'Belongs everywhere', archetype: 'The Connector' } ] } -]; \ No newline at end of file +]; diff --git a/src/data/quizData_v2.js b/src/data/quizData_v2.js new file mode 100644 index 0000000..1e27542 --- /dev/null +++ b/src/data/quizData_v2.js @@ -0,0 +1,134 @@ +export const quizQuestions = [ + { + id: 'Q1', + text: 'What excites you most when starting a new project?', + choices: [ + { text: 'Finding patterns and designing a system', archetype: 'The Orchestrator' }, + { text: 'Researching the context', archetype: 'The Researcher' }, + { text: 'Trying a lot of different ideas', archetype: 'The Experimentalist' }, + { text: 'Getting the team to work toward the same vision', archetype: 'The Director' }, + { text: 'Discovery along the way', archetype: 'The Generalist' } + ] + }, + { + id: 'Q2', + text: 'How do you approach working with others?', + choices: [ + { text: 'Bridging gaps between people or roles', archetype: 'The Connector' }, + { text: 'Taking the lead and shaping the process', archetype: 'The Director' }, + { text: 'Filling in wherever I\'m needed', archetype: 'The Improviser' }, + { text: 'Supporting others as they grow', archetype: 'The Educator' }, + { text: 'Building tools that help the team', archetype: 'The Multidisciplinary' } + ] + }, + { + id: 'Q3', + text: 'What\'s your creative strength?', + choices: [ + { text: 'Connecting unexpected ideas', archetype: 'The Multidisciplinary' }, + { text: 'Imagining better futures', archetype: 'The Idealist' }, + { text: 'Asking bold questions', archetype: 'The Disruptor' }, + { text: 'Turning plans into action', archetype: 'The Advocate' }, + { text: 'Explaining ideas clearly', archetype: 'The Educator' } + ] + }, + { + id: 'Q4', + text: 'What kind of challenge energizes you?', + choices: [ + { text: 'Scaling an idea across different contexts', archetype: 'The Orchestrator' }, + { text: 'Working without a clear path', archetype: 'The Improviser' }, + { text: 'Bridging disciplines and communities', archetype: 'The Connector' }, + { text: 'Changing how people think or act', archetype: 'The Advocate' }, + { text: 'Reinventing how things are done', archetype: 'The Disruptor' } + ] + }, + { + id: 'Q5', + text: "When I'm stuck, I...", + choices: [ + { text: 'Jump into making something', archetype: 'The Experimentalist' }, + { text: 'Look back at my research', archetype: 'The Researcher' }, + { text: 'Zoom out and reframe the problem', archetype: 'The Generalist' }, + { text: 'Talk it through with someone', archetype: 'The Connector' }, + { text: 'Try something totally unexpected', archetype: 'The Disruptor' } + ] + }, + { + id: 'Q6', + text: 'How would I describe my creative path so far?', + choices: [ + { text: 'Nonlinear and cross-disciplinary', archetype: 'The Multidisciplinary' }, + { text: 'Driven by purpose or values', archetype: 'The Idealist' }, + { text: 'Full of pivots and changes', archetype: 'The Improviser' }, + { text: 'Focused on shaping ideas with others', archetype: 'The Director' }, + { text: 'Still in motion β€” and open', archetype: 'The Generalist' } + ] + }, + { + id: 'Q7', + text: 'In an ideal world, my work would...', + choices: [ + { text: 'Teach or inspire someone else', archetype: 'The Educator' }, + { text: 'Spark new possibilities', archetype: 'The Experimentalist' }, + { text: 'Shift a system or community', archetype: 'The Advocate' }, + { text: 'Imagine better futures', archetype: 'The Idealist' }, + { text: 'Stay fluid and adaptable', archetype: 'The Generalist' } + ] + }, + { + id: 'Q8', + text: 'What do I value most when designing?', + choices: [ + { text: 'Systems that scale and adapt', archetype: 'The Orchestrator' }, + { text: 'Context and clarity', archetype: 'The Researcher' }, + { text: 'Play and experimentation', archetype: 'The Experimentalist' }, + { text: 'Connection and collaboration', archetype: 'The Connector' }, + { text: 'Speed and flow', archetype: 'The Improviser' } + ] + }, + { + id: 'Q9', + text: 'My dream project would be...', + choices: [ + { text: 'Designing a system or workflow', archetype: 'The Orchestrator' }, + { text: 'A speculative exhibition or provocation', archetype: 'The Experimentalist' }, + { text: 'A mentorship or learning experience', archetype: 'The Educator' }, + { text: 'A tool that reshapes how we work', archetype: 'The Disruptor' }, + { text: 'Something that spans industries', archetype: 'The Multidisciplinary' } + ] + }, + { + id: 'Q10', + text: 'I relate to design tools as…', + choices: [ + { text: 'Extensions of my thinking', archetype: 'The Researcher' }, + { text: 'Things to question or remix', archetype: 'The Disruptor' }, + { text: 'Flexible and ever-changing', archetype: 'The Generalist' }, + { text: 'Something I share or teach', archetype: 'The Educator' }, + { text: 'A way to visualize deeper ideas', archetype: 'The Idealist' } + ] + }, + { + id: 'Q11', + text: 'The kind of feedback that fuels me is…', + choices: [ + { text: 'Open-ended questions', archetype: 'The Researcher' }, + { text: 'Insight that clarifies meaning', archetype: 'The Educator' }, + { text: 'Fast reactions that shift the work', archetype: 'The Improviser' }, + { text: 'Honest conversations', archetype: 'The Director' }, + { text: 'Makes the world better', archetype: 'The Idealist' } + ] + }, + { + id: 'Q12', + text: 'I wish more people understood that design...', + choices: [ + { text: 'Visuals can be strategic', archetype: 'The Director' }, + { text: 'Can change how things work', archetype: 'The Advocate' }, + { text: 'Is a way of thinking', archetype: 'The Multidisciplinary' }, + { text: 'Thrives in complexity', archetype: 'The Researcher' }, + { text: 'Belongs everywhere', archetype: 'The Connector' } + ] + } +]; \ No newline at end of file diff --git a/src/data/scoringUtils.js b/src/data/scoringUtils.js index 0190c58..8ff6a83 100644 --- a/src/data/scoringUtils.js +++ b/src/data/scoringUtils.js @@ -1,5 +1,5 @@ import { archetypeData } from './archetypeData'; -import { quizQuestions } from './quizData'; +import { quizQuestions } from './quizData_v2'; const ARCHETYPE_ORDER = Object.keys(archetypeData); const DIMENSION_KEYS = Object.keys(archetypeData[ARCHETYPE_ORDER[0]].dimensions); diff --git a/src/pages/Quiz.jsx b/src/pages/Quiz.jsx index 2fb9e19..bfea24e 100644 --- a/src/pages/Quiz.jsx +++ b/src/pages/Quiz.jsx @@ -1,7 +1,6 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Model, Survey } from 'survey-react-ui'; -import { quizQuestions } from '../data/quizData'; import { calculateScores } from '../data/scoringUtils'; import { encodeDimsV1, DIM_KEYS } from '../data/quizUtils'; import { PANEL_COLORS_HEX } from '../data/colors'; @@ -10,160 +9,165 @@ import 'survey-core/survey-core.css'; export default function QuizPage() { const [survey, setSurvey] = useState(null); const [isComplete, setIsComplete] = useState(false); + const [quizQuestions, setQuizQuestions] = useState([]); const navigate = useNavigate(); - - const checkCompletion = (surveyData) => { - const answeredCount = Object.keys(surveyData || {}).filter(key => - surveyData[key] !== undefined && surveyData[key] !== null - ).length; - const allAnswered = answeredCount === quizQuestions.length; - console.log('Completion check:', answeredCount, 'of', quizQuestions.length, '=>', allAnswered); - return allAnswered; - }; - + useEffect(() => { - const panelClassOrder = [ - 'quiz-panel-blue', - 'quiz-panel-brown', - 'quiz-panel-green', - 'quiz-panel-yellow', - 'quiz-panel-purple' - ]; - - const surveyModel = new Model({ - questions: quizQuestions.map((q, index) => ({ - type: 'radiogroup', - name: q.id, - title: q.text, - description: `${index + 1} of ${quizQuestions.length}`, - choices: q.choices.map(c => c.text), - isRequired: true, - showNoneItem: false - })), - showNavigationButtons: false, - showProgressBar: false, - completedHtml: '
' - }); + const checkCompletion = (surveyData, questions) => { + const answeredCount = Object.keys(surveyData || {}).filter(key => + surveyData[key] !== undefined && surveyData[key] !== null + ).length; + const allAnswered = answeredCount === questions.length; + console.log('Completion check:', answeredCount, 'of', questions.length, '=>', allAnswered); + return allAnswered; + }; + + // Load quiz data: default v2, v=1 loads v1 for testing + const loadQuizData = async () => { + const params = new URLSearchParams(window.location.search); + const useV1 = params.get('v') === '1'; + const module = await import(useV1 ? '../data/quizData_v1.js' : '../data/quizData_v2.js'); + return module.quizQuestions; + }; + + loadQuizData().then(questions => { + setQuizQuestions(questions); + + const panelClassOrder = [ + 'quiz-panel-blue', + 'quiz-panel-brown', + 'quiz-panel-green', + 'quiz-panel-yellow', + 'quiz-panel-purple' + ]; - const applyPanelStyle = (questionName) => { - const questionIndex = quizQuestions.findIndex(question => question.id === questionName); - if (questionIndex === -1) return; - - const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; - const colors = PANEL_COLORS_HEX[panelClass]; - if (!colors) return; - - // Find question element - SurveyJS renders to .sd-question elements - const questionElements = document.querySelectorAll('.sd-question'); - questionElements.forEach(el => { - const titleEl = el.querySelector('.sd-question__title'); - if (titleEl && titleEl.textContent.includes(quizQuestions[questionIndex].text.substring(0, 20))) { - // Apply inline styles to ensure they stick through re-renders - el.style.backgroundColor = colors.bg; - el.style.color = colors.fg; - el.style.setProperty('--panel-fg', colors.fg); - el.classList.add('quiz-panel', panelClass); + const surveyModel = new Model({ + questions: questions.map((q, index) => ({ + type: 'radiogroup', + name: q.id, + title: q.text, + description: `${index + 1} of ${questions.length}`, + choices: q.choices.map(c => c.text), + isRequired: true, + showNoneItem: false + })), + showNavigationButtons: false, + showProgressBar: false, + completedHtml: '
' + }); + + const applyPanelStyle = (questionName) => { + const questionIndex = questions.findIndex(question => question.id === questionName); + if (questionIndex === -1) return; + + const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; + const colors = PANEL_COLORS_HEX[panelClass]; + if (!colors) return; + + const questionElements = document.querySelectorAll('.sd-question'); + questionElements.forEach(el => { + const titleEl = el.querySelector('.sd-question__title'); + if (titleEl && titleEl.textContent.includes(questions[questionIndex].text.substring(0, 20))) { + el.style.backgroundColor = colors.bg; + el.style.color = colors.fg; + el.style.setProperty('--panel-fg', colors.fg); + el.classList.add('quiz-panel', panelClass); + } + }); + }; + + surveyModel.onValueChanged.add((sender, options) => { + const allAnswered = checkCompletion(sender.data, questions); + setIsComplete(allAnswered); + if (options.question) { + setTimeout(() => applyPanelStyle(options.question.name), 0); } }); - }; - surveyModel.onValueChanged.add((sender, options) => { - const allAnswered = checkCompletion(sender.data); - setIsComplete(allAnswered); - // Reapply styles on value change - if (options.question) { - setTimeout(() => applyPanelStyle(options.question.name), 0); - } - }); - - surveyModel.onCurrentPageChanged.add((sender, _options) => { - const allAnswered = checkCompletion(sender.data); - setIsComplete(allAnswered); - }); + surveyModel.onCurrentPageChanged.add((sender, _options) => { + const allAnswered = checkCompletion(sender.data, questions); + setIsComplete(allAnswered); + }); - surveyModel.onAfterRenderQuestion.add((sender, options) => { - const questionIndex = quizQuestions.findIndex(question => question.id === options.question.name); - if (questionIndex === -1) return; - - const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; - const colors = PANEL_COLORS_HEX[panelClass]; - if (!colors) return; - - // Apply inline styles - options.htmlElement.style.backgroundColor = colors.bg; - options.htmlElement.style.color = colors.fg; - options.htmlElement.style.setProperty('--panel-fg', colors.fg); - options.htmlElement.classList.add('quiz-panel', panelClass); - - // Also apply to parent row/container on mobile (sd-question--mobile) - const parentEl = options.htmlElement.closest('.sd-row__question, .sd-question'); - if (parentEl && parentEl !== options.htmlElement) { - parentEl.classList.add('quiz-panel', panelClass); - } - }); - - // Set initial state - const initiallyComplete = checkCompletion(surveyModel.data); - setIsComplete(initiallyComplete); - - setSurvey(surveyModel); - - // Apply panel styles after initial render (handles mobile where onAfterRenderQuestion may not fire) - setTimeout(() => { - quizQuestions.forEach(q => { - applyPanelStyle(q.id); + surveyModel.onAfterRenderQuestion.add((sender, options) => { + const questionIndex = questions.findIndex(question => question.id === options.question.name); + if (questionIndex === -1) return; + + const panelClass = panelClassOrder[questionIndex % panelClassOrder.length]; + const colors = PANEL_COLORS_HEX[panelClass]; + if (!colors) return; + + options.htmlElement.style.backgroundColor = colors.bg; + options.htmlElement.style.color = colors.fg; + options.htmlElement.style.setProperty('--panel-fg', colors.fg); + options.htmlElement.classList.add('quiz-panel', panelClass); + + const parentEl = options.htmlElement.closest('.sd-row__question, .sd-question'); + if (parentEl && parentEl !== options.htmlElement) { + parentEl.classList.add('quiz-panel', panelClass); + } }); - }, 100); + + const initiallyComplete = checkCompletion(surveyModel.data, questions); + setIsComplete(initiallyComplete); + setSurvey(surveyModel); + + setTimeout(() => { + questions.forEach(q => { + applyPanelStyle(q.id); + }); + }, 100); + }); }, []); - + const handleSubmit = () => { if (!survey || !isComplete) return; - // Calculate dimension scores from responses const responses = Object.entries(survey.data).map(([questionId, answer]) => ({ questionId, answer })); const scores = calculateScores(responses); - // Build dims object in canonical order const dims = Object.fromEntries( (scores.dimensionScores || []).map(([k, v]) => [k, v]) ); - // Fill missing keys with 0 DIM_KEYS.forEach(k => { if (!(k in dims)) dims[k] = 0; }); const dimsRaw = encodeDimsV1(dims); const dimsB64 = btoa(dimsRaw); - // Log analytics window.gtag?.('event', 'quiz_complete', { dims_raw: dimsRaw, - version: 1, + version: 2, strategy: dims.strategy, adaptability: dims.adaptability, collaboration: dims.collaboration, experimentation: dims.experimentation, impact: dims.impact, + primary: scores.primary.archetype, + secondary: scores.secondary?.archetype, question_answers: JSON.stringify(survey.data) }); - navigate(`/results?dims=${encodeURIComponent(dimsB64)}`); + const params = new URLSearchParams({ v: '2', dims: dimsB64 }); + if (scores.primary?.archetype) params.set('p', scores.primary.archetype); + if (scores.secondary?.archetype) params.set('s', scores.secondary.archetype); + navigate(`/results?${params.toString()}`); }; - - if (!survey) { + + if (!survey || quizQuestions.length === 0) { return
Loading...
; } - + return (

Applied Designer Quiz

- {/* TODO: fix padding properly */}

Take the quiz to find out which archetype of Applied Designer you are!

- +
-