Canvas Quiz Answer Identifier (Subtle Shadow + Edge Hover)

Stealthily highlights correct Canvas quiz answers with a faint right-edge shadow on hover only. Won’t steal focus or trigger LMS cursor alerts. API-based & unobtrusive UX by design. Fully self-contained & auto-loaded on quiz pages only.

  1. // ==UserScript==
  2. // @name Canvas Quiz Answer Identifier (Subtle Shadow + Edge Hover)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description Stealthily highlights correct Canvas quiz answers with a faint right-edge shadow on hover only. Won’t steal focus or trigger LMS cursor alerts. API-based & unobtrusive UX by design. Fully self-contained & auto-loaded on quiz pages only.
  6. // @author You
  7. // @match *://*.instructure.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. window.addEventListener('load', function () {
  15. const texts = document.getElementsByClassName("text");
  16.  
  17. if (!texts.length) return;
  18.  
  19. let questions = [];
  20. let optionsList = [];
  21.  
  22. // Step 1: Extract questions and options
  23. for (let i = 0; i < texts.length; i++) {
  24. const questionDiv = texts[i].querySelector("div:nth-of-type(2)");
  25. const questionText = questionDiv?.innerText?.trim() || "";
  26.  
  27. const optionLabels = texts[i].getElementsByClassName("answer_label");
  28. let optionsText = [];
  29. for (let j = 0; j < optionLabels.length; j++) {
  30. optionsText.push(optionLabels[j].innerText.trim());
  31. }
  32.  
  33. questions.push(questionText);
  34. optionsList.push(optionsText);
  35. }
  36.  
  37. // Step 2: Send questions to the API and get answers
  38. for (let i = 0; i < questions.length; i++) {
  39. const payload = {
  40. id: i,
  41. question: questions[i],
  42. options: optionsList[i].join(", "),
  43. };
  44.  
  45. fetch("https://canvasquiz-new.uc.r.appspot.com/generate", {
  46. method: "POST",
  47. headers: { "Content-Type": "application/json" },
  48. body: JSON.stringify(payload),
  49. })
  50. .then((res) => {
  51. if (!res.ok) throw new Error("Network response error");
  52. return res.json();
  53. })
  54. .then((data) => {
  55. const correctAnswer = data.answer?.trim();
  56. if (!correctAnswer) return;
  57.  
  58. const answerLabels = texts[i].getElementsByClassName("answer_label");
  59. for (let j = 0; j < answerLabels.length; j++) {
  60. const label = answerLabels[j];
  61. const labelText = label.innerText.trim();
  62.  
  63. if (labelText === correctAnswer) {
  64. label.style.position = "relative";
  65.  
  66. label.addEventListener("mouseenter", () => {
  67. let shadowMarker = document.createElement("div");
  68. shadowMarker.className = "hover-shadow-marker";
  69. shadowMarker.style.position = "absolute";
  70. shadowMarker.style.top = "50%";
  71. shadowMarker.style.left = "100%"; // outside the label
  72. shadowMarker.style.transform = "translateY(-50%) translateX(2px)";
  73. shadowMarker.style.width = "8px";
  74. shadowMarker.style.height = "20px";
  75. shadowMarker.style.boxShadow = "0 0 6px rgba(0, 0, 0, 0.1)";
  76. shadowMarker.style.borderRadius = "3px";
  77. shadowMarker.style.pointerEvents = "none"; // don't interfere
  78. label.appendChild(shadowMarker);
  79. });
  80.  
  81. label.addEventListener("mouseleave", () => {
  82. const marker = label.querySelector(".hover-shadow-marker");
  83. if (marker) marker.remove();
  84. });
  85. }
  86. }
  87. })
  88. .catch((err) => {
  89. console.warn("Answer API error:", err);
  90. });
  91. }
  92. });
  93. })();