{
  "information": {
    "Title": "ejss_model_sunGame2 Strategy Math Sun Game",
    "Author": [
      "shaun",
      "lookang",
      "based on idea by theresa"
    ],
    "AuthorLogo": [
      "",
      "",
      ""
    ],
    "Password": "",
    "Keywords": "",
    "Abstract": "",
    "Copyright": "",
    "Level": "",
    "Language": "",
    "Logo": [
      "./sunGame/Screenshot 2021-04-13 at 9.31.37 PM (2).png"
    ],
    "RunAlways": "true",
    "ModelTab": "",
    "ModelTabTitle": "",
    "ModelName": "",
    "FixedNavigationBar": "false",
    "CSSFile": "",
    "DetectedFiles": [
      "./sunGame/Screenshot 2021-04-13 at 9.31.37 PM (2).png"
    ],
    "AuxiliaryFiles": [
      "./sunGame/"
    ],
    "HTMLHead": "<script async=\"true\" src=\"https://www.googletagmanager.com/gtag/js?id=UA-3326007-19\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n  gtag('config', 'UA-3326007-19');\n</script>\n<script data-ad-client=\"ca-pub-0121577198857509\" async=\"true\" src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>",
    "SaveInXMLFormat": "false",
    "IncludeSource": "true",
    "IncludeLibrary": "true",
    "UglifyJS": "false",
    "PreviewFullModel": "false",
    "UseInterpreter": "true",
    "UseDeltaForODE": "false"
  },
  "description": {
    "pages": [
      {
        "Name": "Intro Page",
        "Active": "true",
        "Internal": "false",
        "Type": "DESCRIPTION_EDITOR",
        "Locale": "_default_",
        "Title": "Intro Page",
        "External": "false",
        "Code": "<html>\n<head></head>\n<body>\n<p>Sun Game <strong>Objective</strong>: Activate the correct edges so that the number of active edges connected to each node corresponds to the number on that node. <strong>Controls</strong>: Click an edge once to activate it, a second time to flag it as impossible, and a third time to return it to undecided.</p><p><strong>Teacher tools</strong>: Use Hint for the next logical observation, Next Step to demonstrate it, Play Thinking to walk through the deduction, and Solve Now when you want the computer to finish the board.</p>\n</body>\n</html>"
      }
    ]
  },
  "model": {
    "variables": {
      "pages": [
        {
          "Name": "Var Table",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "numOfRows",
              "Value": "5",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numOfCols",
              "Value": "5",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numOfNodes",
              "Value": "numOfRows * numOfCols",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "grid",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "nodeVal",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "initClickEvent",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "font",
              "Value": "\"normal normal 2.5vw\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numCorrect",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "gameOver",
              "Value": "false",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "lines",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "linesLength",
              "Value": "1",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesColor",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesColorInactive",
              "Value": "\"#90a4ae\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesColorActive",
              "Value": "\"black\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesColorFlagged",
              "Value": "\"#eceff1\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesXSize",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesYSize",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesActive",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesInteract",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesAttribute",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "edgeList",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "circles",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "circleLength",
              "Value": "0.5",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "circleX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "circleY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "circleColor",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "colorValid",
              "Value": "\"#c5e1a5\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "colorInvalid",
              "Value": "\"#ef9a9a\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "colorNormal",
              "Value": "\"white\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "text",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "textX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "lookang",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "textdebug",
              "Value": "\"\"",
              "Type": "String",
              "Dimension": "[numOfRows*numOfCols]",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "n",
              "Value": "numOfRows*numOfCols",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesXoffset1",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesYoffset1",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesXoffset2",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "linesYoffset2",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "offset",
              "Value": "0.3",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "t",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "dt",
              "Value": "0.05",
              "Type": "double",
              "Dimension": "",
              "Comment": "",
              "Domain": ""
            },
            {
              "Name": "playing",
              "Value": "false",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "",
              "Domain": ""
            },
            {
              "Name": "showAutoSolve",
              "Value": "false",
              "Type": "double",
              "Dimension": "",
              "Comment": "",
              "Domain": ""
            },
            {
              "Name": "currentPuzzleName",
              "Value": "\"puzzle 1\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "teacherMode",
              "Value": "true",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "teacherStatusHtml",
              "Value": "\"\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "teacherFocusNodes",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "teacherFocusColor",
              "Value": "\"#ffe082\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "autoPlayTimer",
              "Value": "null",
              "Type": "double",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "autoPlayRunning",
              "Value": "false",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "",
              "Domain": ""
            }
          ]
        }
      ]
    },
    "initialization": {
      "pages": [
        {
          "Name": "Init Page",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "if (_isMobile){\n  //do nothing\n}\nelse{\n    // copy this into the initialization\n    // make the font bigger\n  _view.plottingPanel.getMessageDecoration(\"TL\").getFont().setFontSize(\"2vw\");\n  _view.plottingPanel.getMessageDecoration(\"TR\").getFont().setFontSize(\"2vw\");\n  _view.plottingPanel.getMessageDecoration(\"BL\").getFont().setFontSize(\"2vw\");\n  _view.plottingPanel.getMessageDecoration(\"BR\").getFont().setFontSize(\"2vw\");\n}\n\n// initialising the grid (bottom row up)\ncurrentPuzzleName = currentPuzzleName || \"puzzle 1\";\ngrid = getPuzzleGrid(currentPuzzleName);\n\n// creating all the arrays\nnodeVal = [];\n\nlinesX = [];\nlinesY = [];\nlinesXSize = [];\nlinesYSize = [];\nlinesColor = [];\nlinesActive = [];\nlinesAttribute = [];\nedgeList = [];\n\n//start lookang trying to create hotdots for clicking lines\nlinesXoffset1 = []\nlinesYoffset1 = []\nlinesXoffset2 = []\nlinesYoffset2 = []\n\n\ncircleX = [];\ncircleY = [];\ncircleColor = [];\n\ntextX = [];\ntextY = [];\n\n// generate lines\nfor(var i = 0; i < numOfRows; i++) {\n  for(var j = 0; j < numOfCols; j++) {   \n    var curX = j * linesLength; \n    var curY = i * linesLength;\n    \n    // horizontal lines\n    if(j < numOfCols - 1) {\n      linesX.push(curX); linesXSize.push(linesLength);\n      linesY.push(curY); linesYSize.push(0);\n      \n      edgeList.push([i * numOfRows + j, i * numOfRows + j + 1]);\n      \n      //start lookang trying to create hotdots for clicking lines\n      // hotdots position\n      linesXoffset1.push(curX + linesLength * offset);\n      linesYoffset1.push(curY);\n      \n      linesXoffset2.push(curX + linesLength * (1 -offset));\n      linesYoffset2.push(curY);\n    }\n    // vertical lines\n    if(i < numOfRows - 1) {\n      linesX.push(curX); linesXSize.push(0);\n      linesY.push(curY); linesYSize.push(linesLength);\n      \n      edgeList.push([i * numOfRows + j, (i + 1) * numOfRows + j]);\n      \n      // hotdots position\n      linesXoffset1.push(curX);\n      linesYoffset1.push(curY + linesLength * offset);\n      \n      linesXoffset2.push(curX);\n      linesYoffset2.push(curY + linesLength * (1 - offset));\n    }\n    \n    // diagonally left\n    if(j > 0 && i < numOfRows - 1){\n      linesX.push(curX); linesXSize.push(-linesLength);\n      linesY.push(curY); linesYSize.push(linesLength);\n \n      edgeList.push([i * numOfRows + j, (i + 1) * numOfRows + j - 1]);\n      \n      // hotdots position\n      linesXoffset1.push(curX - linesLength * offset);\n      linesYoffset1.push(curY + linesLength * offset);\n      \n      linesXoffset2.push(curX - linesLength * (1 - offset));\n      linesYoffset2.push(curY + linesLength * (1 - offset));\n    }\n    \n    // diagonally right\n    if(j < numOfCols - 1 && i < numOfRows - 1) {\n      linesX.push(curX); linesXSize.push(linesLength);\n      linesY.push(curY); linesYSize.push(linesLength);\n      \n      edgeList.push([i * numOfRows + j, (i + 1) * numOfRows + j + 1]);\n      \n      // hotdots position\n      linesXoffset1.push(curX + linesLength * offset);\n      linesYoffset1.push(curY + linesLength * offset);\n      \n      linesXoffset2.push(curX + linesLength * (1 - offset));\n      linesYoffset2.push(curY + linesLength * (1 - offset));\n    }\n    \n    nodeVal.push(0);\n    // circle position (the vertices)\n    circleX.push(curX);\n    circleY.push(curY);\n    circleColor.push(\"white\");\n    // number text\n    textX.push(curX);\n    textY.push(curY);\n  }\n}\n\n// initialising linesAttribute, linesActive and linesColor\nfor(var i = 0; i < linesX.length; i++){\n  linesAttribute.push(\n    {\n      \"stroke-dasharray\": \"10 5\"\n    }\n  );\n  linesColor.push(linesColorInactive);\n  linesActive.push(0);  \n}\n\nconsole.log(edgeList);\nsetTeacherStatus(\"Ready\", \"Choose edges so that each number matches the count of active edges touching it.\", \"Teacher Mode can explain the next local deduction for \" + currentPuzzleName + \".\");\n"
        },
        {
          "Name": "debug",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "for (var i=0; i<n  ; i++) {\n  textdebug[i]= 1+i;\n}\n"
        },
        {
          "Name": "esc",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": ""
        }
      ]
    },
    "evolution": {
      "information": {
        "FPS": "20",
        "SPD": "1",
        "RealTimeVariable": "",
        "Autoplay": "false"
      },
      "pages": []
    },
    "fixed_relations": {
      "pages": [
        {
          "Name": "FixRel Page 1",
          "Active": "true",
          "Internal": "false",
          "Type": "undefined",
          "Comment": "",
          "Code": "document.onkeydown = function(evt) {\n    evt = evt || window.event;\n    var isEscape = false;\n    if (\"key\" in evt) {\n        isEscape = (evt.key === \"Escape\" || evt.key === \"Esc\");\n    } else {\n        isEscape = (evt.keyCode === 27);\n    }\n    if (isEscape) {\n        toggleTeacherMode();\n    }\n};"
        }
      ]
    },
    "custom": {
      "pages": [
        {
          "Name": "toggleEdge",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "function getPuzzleGrid(puzzleName) {\n    if (puzzleName === \"puzzle 2\") return [\n      2, 3, 3, 3, 1,\n      3, 6, 7, 5, 1,\n      4, 4, 6, 6, 5,\n      5, 5, 7, 4, 2,\n      2, 5, 2, 4, 1\n    ];\n    if (puzzleName === \"puzzle 3\") return [\n      1, 2, 2, 4, 2,\n      1, 5, 6, 3, 3,\n      4, 3, 5, 2, 3,\n      3, 7, 7, 6, 2,\n      2, 1, 3, 3, 2\n    ];\n    if (puzzleName === \"puzzle 4\") return [\n      2, 2, 3, 4, 2,\n      1, 7, 6, 4, 4,\n      2, 5, 1, 6, 3,\n      4, 5, 7, 5, 1,\n      2, 3, 4, 1, 2\n    ];\n    return [\n      2, 4, 1, 3, 2,\n      2, 3, 6, 1, 3,\n      2, 4, 6, 8, 1,\n      2, 2, 6, 4, 4,\n      1, 2, 2, 4, 1\n    ];\n  }\n\n  function buildAdjacencyList() {\n    var adjEdges = [];\n    for (var i = 0; i < numOfNodes; i++) adjEdges.push([]);\n    for (var e = 0; e < edgeList.length; e++) {\n      var a = edgeList[e][0];\n      var b = edgeList[e][1];\n      adjEdges[a].push(e);\n      adjEdges[b].push(e);\n    }\n    return adjEdges;\n  }\n\n  function formatNodeName(nodeIdx) {\n    var row = Math.floor(nodeIdx / numOfCols) + 1;\n    var col = (nodeIdx % numOfCols) + 1;\n    return \"row \" + row + \", col \" + col;\n  }\n\n  function buildTeacherStatus(title, message, detail) {\n    var modeLine = teacherMode ? \"Teacher mode is on.\" : \"Teacher mode is off.\";\n    var html = \"<div style='padding:0.35rem 0'><b>\" + title + \"</b><br/>\" + message + \"</div>\";\n    html += \"<div style='padding:0.15rem 0'>\" + modeLine + \" Current puzzle: \" + currentPuzzleName + \".</div>\";\n    if (detail) html += \"<div style='padding:0.15rem 0'>\" + detail + \"</div>\";\n    return html;\n  }\n\n  function refreshTeacherControls() {\n    if (!_view) return;\n    if (_view.comboBox && currentPuzzleName) _view.comboBox.setProperty(\"SelectedOptions\", [currentPuzzleName]);\n    if (_view.teacherModeBtn) _view.teacherModeBtn.setProperty(\"Text\", teacherMode ? \"Teacher: On\" : \"Teacher: Off\");\n    if (_view.hintBtn) _view.hintBtn.setProperty(\"Display\", teacherMode ? \"inline\" : \"none\");\n    if (_view.stepBtn) _view.stepBtn.setProperty(\"Display\", teacherMode ? \"inline\" : \"none\");\n    if (_view.playBtn) {\n      _view.playBtn.setProperty(\"Display\", teacherMode ? \"inline\" : \"none\");\n      _view.playBtn.setProperty(\"Text\", autoPlayRunning ? \"Stop Play\" : \"Play Thinking\");\n    }\n    if (_view.autoSolveBtn) _view.autoSolveBtn.setProperty(\"Text\", teacherMode ? \"Solve Now\" : \"Auto-solve\");\n    if (_view.teacherStatus) _view.teacherStatus.setProperty(\"Text\", teacherStatusHtml || \"\");\n  }\n\n  function setTeacherStatus(title, message, detail) {\n    teacherStatusHtml = buildTeacherStatus(title, message, detail);\n    refreshTeacherControls();\n  }\n\n  function stopTeacherAutoPlay() {\n    if (autoPlayTimer) {\n      window.clearTimeout(autoPlayTimer);\n      autoPlayTimer = null;\n    }\n    autoPlayRunning = false;\n    refreshTeacherControls();\n  }\n\n  function setEdgeVisualState(edgeIdx, state) {\n    linesActive[edgeIdx] = state;\n    if (state === 1) {\n      linesAttribute[edgeIdx][\"stroke-dasharray\"] = 0;\n      linesColor[edgeIdx] = linesColorActive;\n      return;\n    }\n    if (state === 2) {\n      linesAttribute[edgeIdx][\"stroke-dasharray\"] = \"10 30\";\n      linesColor[edgeIdx] = linesColorFlagged;\n      return;\n    }\n    linesAttribute[edgeIdx][\"stroke-dasharray\"] = \"10 5\";\n    linesColor[edgeIdx] = linesColorInactive;\n  }\n\n  function recomputeNodeValsAndColors() {\n    for (var i = 0; i < numOfNodes; i++) nodeVal[i] = 0;\n    for (var e = 0; e < edgeList.length; e++) {\n      if (linesActive[e] === 1) {\n        var a = edgeList[e][0];\n        var b = edgeList[e][1];\n        nodeVal[a]++;\n        nodeVal[b]++;\n      }\n    }\n    for (var n = 0; n < numOfNodes; n++) {\n      if (nodeVal[n] === grid[n]) circleColor[n] = colorValid;\n      else if (nodeVal[n] > grid[n]) circleColor[n] = colorInvalid;\n      else circleColor[n] = colorNormal;\n    }\n    if (teacherFocusNodes && teacherFocusNodes.length) {\n      for (var j = 0; j < teacherFocusNodes.length; j++) {\n        circleColor[teacherFocusNodes[j]] = teacherFocusColor;\n      }\n    }\n  }\n\n  function highlightNodes(nodeList) {\n    teacherFocusNodes = nodeList ? nodeList.slice(0) : [];\n    recomputeNodeValsAndColors();\n  }\n\n  function clearTeacherHighlight() {\n    teacherFocusNodes = [];\n    recomputeNodeValsAndColors();\n  }\n\n  function isBoardSolved() {\n    for (var i = 0; i < numOfNodes; i++) {\n      if (nodeVal[i] !== grid[i]) return false;\n    }\n    return true;\n  }\n\n  function finishIfSolved(dialogText) {\n    if (!isBoardSolved()) return false;\n    gameOver = true;\n    if (dialogText) _tools.showOkDialog(dialogText);\n    return true;\n  }\n\n  function getBoardSolverData() {\n    var solverData = [];\n    for (var e = 0; e < edgeList.length; e++) {\n      if (linesActive[e] === 1) solverData[e] = 1;\n      else if (linesActive[e] === 2) solverData[e] = 0;\n      else solverData[e] = -1;\n    }\n    return solverData;\n  }\n\n  function buildReasoningStepFromState(solverData, adjEdges) {\n    var bestStep = null;\n    for (var nodeIdx = 0; nodeIdx < numOfNodes; nodeIdx++) {\n      var edges = adjEdges[nodeIdx];\n      var req = grid[nodeIdx];\n      var curActive = 0;\n      var unknownEdges = [];\n      for (var k = 0; k < edges.length; k++) {\n        var edgeIdx = edges[k];\n        if (solverData[edgeIdx] === 1) curActive++;\n        else if (solverData[edgeIdx] === -1) unknownEdges.push(edgeIdx);\n      }\n      if (curActive > req) {\n        return {\n          kind: \"contradiction\",\n          nodes: [nodeIdx],\n          explanation: \"At \" + formatNodeName(nodeIdx) + \", there are already \" + curActive + \" active edges, but the clue is only \" + req + \". Undo at least one active edge near this node.\"\n        };\n      }\n      if (curActive + unknownEdges.length < req) {\n        return {\n          kind: \"contradiction\",\n          nodes: [nodeIdx],\n          explanation: \"At \" + formatNodeName(nodeIdx) + \", even turning on every undecided edge would only give \" + (curActive + unknownEdges.length) + \", which is still less than the clue \" + req + \".\"\n        };\n      }\n      if (unknownEdges.length === 0) continue;\n      var step = null;\n      if (curActive === req) {\n        step = {\n          kind: \"step\",\n          rule: \"all-filled\",\n          targetValue: 0,\n          nodes: [nodeIdx],\n          edges: unknownEdges.slice(0),\n          explanation: \"At \" + formatNodeName(nodeIdx) + \", the clue \" + req + \" is already satisfied, so every remaining undecided edge around this node must stay inactive.\"\n        };\n      } else if (curActive + unknownEdges.length === req) {\n        step = {\n          kind: \"step\",\n          rule: \"all-needed\",\n          targetValue: 1,\n          nodes: [nodeIdx],\n          edges: unknownEdges.slice(0),\n          explanation: \"At \" + formatNodeName(nodeIdx) + \", the clue still needs \" + (req - curActive) + \" more active edges, and exactly \" + unknownEdges.length + \" undecided edge(s) remain, so all of them must be active.\"\n        };\n      }\n      if (step && (!bestStep || step.edges.length < bestStep.edges.length)) bestStep = step;\n    }\n    return bestStep;\n  }\n\n  function validateSolverData(solverData, adjEdges) {\n    for (var nodeIdx = 0; nodeIdx < numOfNodes; nodeIdx++) {\n      var req = grid[nodeIdx];\n      var curActive = 0;\n      var unknown = 0;\n      var edges = adjEdges[nodeIdx];\n      for (var k = 0; k < edges.length; k++) {\n        var edgeIdx = edges[k];\n        if (solverData[edgeIdx] === 1) curActive++;\n        else if (solverData[edgeIdx] === -1) unknown++;\n      }\n      if (curActive > req) return false;\n      if (curActive + unknown < req) return false;\n    }\n    return true;\n  }\n\n  function chooseEdgeMRV(assignments, adjEdges) {\n    var bestEdge = null;\n    var bestScore = Infinity;\n    for (var edgeIdx = 0; edgeIdx < assignments.length; edgeIdx++) {\n      if (assignments[edgeIdx] !== -1) continue;\n      var a = edgeList[edgeIdx][0];\n      var b = edgeList[edgeIdx][1];\n      var curActiveA = 0;\n      var curActiveB = 0;\n      var unknownA = 0;\n      var unknownB = 0;\n      for (var i = 0; i < adjEdges[a].length; i++) {\n        var edgeA = adjEdges[a][i];\n        if (assignments[edgeA] === 1) curActiveA++;\n        else if (assignments[edgeA] === -1) unknownA++;\n      }\n      for (var j = 0; j < adjEdges[b].length; j++) {\n        var edgeB = adjEdges[b][j];\n        if (assignments[edgeB] === 1) curActiveB++;\n        else if (assignments[edgeB] === -1) unknownB++;\n      }\n      var score = Math.min(grid[a] - curActiveA, grid[b] - curActiveB) + 0.01 * Math.min(unknownA, unknownB);\n      if (score < bestScore) {\n        bestScore = score;\n        bestEdge = edgeIdx;\n      }\n    }\n    return bestEdge;\n  }\n\n  function solveWithSearch(assignments, adjEdges) {\n    function backtrack() {\n      if (!validateSolverData(assignments, adjEdges)) return false;\n      var choice = chooseEdgeMRV(assignments, adjEdges);\n      if (choice === null) return true;\n      var aNode = edgeList[choice][0];\n      var bNode = edgeList[choice][1];\n      var curActiveA = 0;\n      var curActiveB = 0;\n      for (var i = 0; i < adjEdges[aNode].length; i++) if (assignments[adjEdges[aNode][i]] === 1) curActiveA++;\n      for (var j = 0; j < adjEdges[bNode].length; j++) if (assignments[adjEdges[bNode][j]] === 1) curActiveB++;\n      var tries = (Math.min(grid[aNode] - curActiveA, grid[bNode] - curActiveB) <= 1) ? [1, 0] : [0, 1];\n      for (var tIdx = 0; tIdx < tries.length; tIdx++) {\n        assignments[choice] = tries[tIdx];\n        if (backtrack()) return true;\n        assignments[choice] = -1;\n      }\n      return false;\n    }\n    return backtrack();\n  }\n\n  function solveFromCurrentBoard(allowSearch) {\n    var adjEdges = buildAdjacencyList();\n    var solverData = getBoardSolverData();\n    var logicalSteps = 0;\n    while (true) {\n      var step = buildReasoningStepFromState(solverData, adjEdges);\n      if (!step) break;\n      if (step.kind === \"contradiction\") {\n        return { success: false, contradiction: step, usedSearch: false, logicalSteps: logicalSteps };\n      }\n      for (var i = 0; i < step.edges.length; i++) solverData[step.edges[i]] = step.targetValue;\n      logicalSteps++;\n    }\n    if (!validateSolverData(solverData, adjEdges)) {\n      return { success: false, contradiction: { kind: \"contradiction\", nodes: [], explanation: \"The current partial board cannot be completed without undoing some moves.\" }, usedSearch: false, logicalSteps: logicalSteps };\n    }\n    var solved = true;\n    for (var e = 0; e < solverData.length; e++) {\n      if (solverData[e] === -1) {\n        solved = false;\n        break;\n      }\n    }\n    if (solved) return { success: true, solverData: solverData, usedSearch: false, logicalSteps: logicalSteps };\n    if (!allowSearch) return { success: false, needsSearch: true, solverData: solverData, usedSearch: false, logicalSteps: logicalSteps };\n    var finalData = solverData.slice(0);\n    var success = solveWithSearch(finalData, adjEdges);\n    return { success: success, solverData: finalData, usedSearch: true, logicalSteps: logicalSteps };\n  }\n\n  function applyTeacherStep(step, titlePrefix) {\n    teacherFocusNodes = step.nodes ? step.nodes.slice(0) : [];\n    for (var i = 0; i < step.edges.length; i++) {\n      setEdgeVisualState(step.edges[i], step.targetValue === 1 ? 1 : 2);\n    }\n    recomputeNodeValsAndColors();\n    var detail = step.targetValue === 1\n      ? \"Those undecided edges were turned on because they are all needed.\"\n      : \"Those undecided edges were flagged because this clue is already satisfied.\";\n    if (finishIfSolved(\"Completed! Well done!\")) {\n      setTeacherStatus(\"Puzzle solved\", step.explanation, detail + \" The puzzle is now complete.\");\n      return true;\n    }\n    setTeacherStatus(titlePrefix, step.explanation, detail);\n    return false;\n  }\n\n  function applySolvedBoard(result, title) {\n    teacherFocusNodes = [];\n    for (var e = 0; e < result.solverData.length; e++) {\n      setEdgeVisualState(e, result.solverData[e] === 1 ? 1 : 2);\n    }\n    recomputeNodeValsAndColors();\n    var detail = result.usedSearch\n      ? \"Logical deduction found \" + result.logicalSteps + \" forced step(s). When no forced move remained, computer search completed the rest.\"\n      : \"This position could be completed using visible deduction only.\";\n    finishIfSolved(\"Solved by Auto-solver!\");\n    setTeacherStatus(title, \"The board has been completed.\", detail);\n  }\n\n  function showHint() {\n    stopTeacherAutoPlay();\n    clearTeacherHighlight();\n    if (gameOver) {\n      setTeacherStatus(\"Puzzle solved\", \"This puzzle is already complete.\", \"Choose another puzzle or press Reset to explore again.\");\n      return;\n    }\n    var nextStep = buildReasoningStepFromState(getBoardSolverData(), buildAdjacencyList());\n    if (!nextStep) {\n      setTeacherStatus(\"No forced move\", \"There is no single-node deduction available from this position.\", \"A human solver may need a stronger pattern, or you can use Solve Now to let computer search finish.\");\n      return;\n    }\n    highlightNodes(nextStep.nodes);\n    if (nextStep.kind === \"contradiction\") {\n      setTeacherStatus(\"Contradiction found\", nextStep.explanation, \"Undo something near the highlighted node, then ask for a new hint.\");\n      return;\n    }\n    setTeacherStatus(\"Hint\", nextStep.explanation, \"Press Next Step to let the sim demonstrate this move.\");\n  }\n\n  function stepTeacherReasoning() {\n    stopTeacherAutoPlay();\n    clearTeacherHighlight();\n    if (gameOver) {\n      setTeacherStatus(\"Puzzle solved\", \"This puzzle is already complete.\", \"Choose another puzzle or press Reset to explore again.\");\n      return false;\n    }\n    var nextStep = buildReasoningStepFromState(getBoardSolverData(), buildAdjacencyList());\n    if (!nextStep) {\n      setTeacherStatus(\"No forced move\", \"The current position does not have a forced move that can be explained with the basic local rule set.\", \"Try a different idea yourself, or use Solve Now to let computer search finish.\");\n      return false;\n    }\n    if (nextStep.kind === \"contradiction\") {\n      highlightNodes(nextStep.nodes);\n      setTeacherStatus(\"Contradiction found\", nextStep.explanation, \"Undo something near the highlighted node before continuing.\");\n      return false;\n    }\n    applyTeacherStep(nextStep, \"Next step\");\n    return true;\n  }\n\n  function toggleTeacherPlayback() {\n    if (autoPlayRunning) {\n      stopTeacherAutoPlay();\n      setTeacherStatus(\"Playback stopped\", \"Teacher playback has paused.\", \"You can continue with Hint, Next Step, or Solve Now.\");\n      return;\n    }\n    if (gameOver) {\n      setTeacherStatus(\"Puzzle solved\", \"This puzzle is already complete.\", \"Choose another puzzle or press Reset to explore again.\");\n      return;\n    }\n    autoPlayRunning = true;\n    refreshTeacherControls();\n    function advancePlayback() {\n      if (!autoPlayRunning) return;\n      var nextStep = buildReasoningStepFromState(getBoardSolverData(), buildAdjacencyList());\n      if (nextStep && nextStep.kind === \"step\") {\n        applyTeacherStep(nextStep, \"Thinking aloud\");\n        autoPlayTimer = window.setTimeout(advancePlayback, 900);\n        return;\n      }\n      if (nextStep && nextStep.kind === \"contradiction\") {\n        highlightNodes(nextStep.nodes);\n        setTeacherStatus(\"Contradiction found\", nextStep.explanation, \"Playback stopped so that the contradiction can be repaired.\");\n        stopTeacherAutoPlay();\n        return;\n      }\n      var result = solveFromCurrentBoard(false);\n      if (result.success) {\n        applySolvedBoard(result, \"Auto-play finished\");\n        stopTeacherAutoPlay();\n        return;\n      }\n      if (result.contradiction) {\n        if (result.contradiction.nodes && result.contradiction.nodes.length) highlightNodes(result.contradiction.nodes);\n        setTeacherStatus(\"Contradiction found\", result.contradiction.explanation, \"Playback stopped so that you can repair the highlighted area before continuing.\");\n        stopTeacherAutoPlay();\n        return;\n      }\n      setTeacherStatus(\"Search boundary\", \"No more forced teacher steps are available from this position.\", \"Play Thinking now stops instead of freezing. Use Solve Now if you want the computer to continue with search, or keep exploring manually.\");\n      stopTeacherAutoPlay();\n    }\n    advancePlayback();\n  }\n\n  function toggleTeacherMode() {\n    stopTeacherAutoPlay();\n    clearTeacherHighlight();\n    teacherMode = !teacherMode;\n    setTeacherStatus(\n      teacherMode ? \"Teacher mode enabled\" : \"Teacher mode hidden\",\n      teacherMode ? \"Hints, step-by-step reasoning, and teacher playback are now available.\" : \"Only the puzzle controls remain visible.\",\n      teacherMode ? \"Use Hint for a suggestion, Next Step for a demonstrated move, and Play Thinking for a narrated walkthrough.\" : \"You can turn teacher mode back on at any time.\"\n    );\n  }\n\n  function autoSolve() {\n    stopTeacherAutoPlay();\n    clearTeacherHighlight();\n    if (gameOver) {\n      setTeacherStatus(\"Puzzle solved\", \"This puzzle is already complete.\", \"Choose another puzzle or press Reset to explore again.\");\n      return;\n    }\n    var result = solveFromCurrentBoard(true);\n    if (!result.success) {\n      if (result.contradiction && result.contradiction.nodes && result.contradiction.nodes.length) highlightNodes(result.contradiction.nodes);\n      setTeacherStatus(\"Cannot solve from here\", result.contradiction ? result.contradiction.explanation : \"This board state is inconsistent.\", \"Undo a few moves and try again.\");\n      return;\n    }\n    applySolvedBoard(result, \"Auto-solver\");\n  }\n\n  function toggleEdge(edgeIdx) {\n    if (gameOver) return;\n    stopTeacherAutoPlay();\n    teacherFocusNodes = [];\n    var nextState = (linesActive[edgeIdx] + 1) % 3;\n    setEdgeVisualState(edgeIdx, nextState);\n    recomputeNodeValsAndColors();\n    if (finishIfSolved(\"Completed! Well done!\")) {\n      setTeacherStatus(\"Puzzle solved\", \"You satisfied every clue on the board.\", \"Use another puzzle, or reset this one and compare your strategy with Teacher Mode.\");\n      return;\n    }\n    setTeacherStatus(\"Board updated\", \"You changed one edge.\", \"Use Hint or Next Step to see what a solver would examine from this position.\");\n  }"
        }
      ]
    },
    "elements": {
      "list": []
    }
  },
  "view": {
    "Tree": [
      {
        "Name": "topPanel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [],
        "Children": [
          {
            "Name": "comboBox",
            "Type": "ComboBox",
            "Properties": [
              {
                "name": "Options",
                "value": "[\"puzzle 1\",\"puzzle 2\",\"puzzle 3\",\"puzzle 4\"]"
              },
              {
                "name": "OnChange",
                "value": "var opts = _view.comboBox.getProperty(\"SelectedOptions\");\nvar option = (opts.length > 0) ? opts[0] : currentPuzzleName;\nstopTeacherAutoPlay();\ncurrentPuzzleName = option || \"puzzle 1\";\n_reset();"
              },
              {
                "name": "Font",
                "value": "font"
              },
              {
                "name": "Disabled",
                "value": "false"
              }
            ]
          },
          {
            "Name": "reset",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"Reset\""
              },
              {
                "name": "OnClick",
                "value": "stopTeacherAutoPlay();\n_reset();"
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          },
          {
            "Name": "teacherModeBtn",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "teacherMode?\"Teacher: On\":\"Teacher: Off\""
              },
              {
                "name": "OnClick",
                "value": "toggleTeacherMode();"
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          },
          {
            "Name": "hintBtn",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"Hint\""
              },
              {
                "name": "OnClick",
                "value": "showHint();"
              },
              {
                "name": "Font",
                "value": "font"
              },
              {
                "name": "Display",
                "value": "teacherMode?\"inline\":\"none\""
              }
            ]
          },
          {
            "Name": "stepBtn",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"Next Step\""
              },
              {
                "name": "OnClick",
                "value": "stepTeacherReasoning();"
              },
              {
                "name": "Font",
                "value": "font"
              },
              {
                "name": "Display",
                "value": "teacherMode?\"inline\":\"none\""
              }
            ]
          },
          {
            "Name": "playBtn",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "autoPlayRunning?\"Stop Play\":\"Play Thinking\""
              },
              {
                "name": "OnClick",
                "value": "toggleTeacherPlayback();"
              },
              {
                "name": "Font",
                "value": "font"
              },
              {
                "name": "Display",
                "value": "teacherMode?\"inline\":\"none\""
              }
            ]
          },
          {
            "Name": "autoSolveBtn",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "teacherMode?\"Solve Now\":\"Auto-solve\""
              },
              {
                "name": "OnClick",
                "value": "autoSolve();"
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          }
        ]
      },
      {
        "Name": "mainPanel",
        "Type": "Panel",
        "Expanded": "false",
        "Properties": [],
        "Children": [
          {
            "Name": "plottingPanel",
            "Type": "PlottingPanel",
            "Expanded": "true",
            "Properties": [
              {
                "name": "Gutters",
                "value": "[0,0,0,0]"
              },
              {
                "name": "YScalePrecision",
                "value": "0"
              },
              {
                "name": "XFixedTick",
                "value": "0"
              },
              {
                "name": "Enabled",
                "value": "true"
              },
              {
                "name": "SquareAspect",
                "value": "true"
              },
              {
                "name": "YFixedTick",
                "value": "0"
              },
              {
                "name": "YAutoTicks",
                "value": "false"
              },
              {
                "name": "XTickStep",
                "value": "1"
              },
              {
                "name": "YTickStep",
                "value": "1"
              },
              {
                "name": "AutoScaleY",
                "value": "true"
              },
              {
                "name": "AutoScaleX",
                "value": "true"
              },
              {
                "name": "CursorTypeForMove",
                "value": "\"pointer\""
              },
              {
                "name": "XAutoTicks",
                "value": "false"
              },
              {
                "name": "Height",
                "value": "\"90vh\""
              },
              {
                "name": "XScalePrecision",
                "value": "0"
              },
              {
                "name": "Width",
                "value": "\"100%\""
              },
              {
                "name": "OnMove",
                "value": "playing = false;"
              }
            ],
            "Children": [
              {
                "Name": "segmentSet",
                "Type": "SegmentSet2D",
                "Properties": [
                  {
                    "name": "SizeX",
                    "value": "linesXSize"
                  },
                  {
                    "name": "X",
                    "value": "linesX"
                  },
                  {
                    "name": "Attributes",
                    "value": "linesAttribute"
                  },
                  {
                    "name": "LineColor",
                    "value": "linesColor"
                  },
                  {
                    "name": "Y",
                    "value": "linesY"
                  },
                  {
                    "name": "OnPress",
                    "value": "toggleEdge(linesInteract);"
                  },
                  {
                    "name": "SizeY",
                    "value": "linesYSize"
                  },
                  {
                    "name": "LineWidth",
                    "value": "4"
                  },
                  {
                    "name": "EnabledPosition",
                    "value": "\"ENABLED_NO_MOVE\""
                  },
                  {
                    "name": "ElementInteracted",
                    "value": "linesInteract"
                  }
                ]
              },
              {
                "Name": "shapeSet",
                "Type": "ShapeSet2D",
                "Properties": [
                  {
                    "name": "FillColor",
                    "value": "circleColor"
                  },
                  {
                    "name": "SizeX",
                    "value": "circleLength"
                  },
                  {
                    "name": "X",
                    "value": "circleX"
                  },
                  {
                    "name": "Y",
                    "value": "circleY"
                  },
                  {
                    "name": "SizeY",
                    "value": "circleLength"
                  }
                ]
              },
              {
                "Name": "textSet",
                "Type": "TextSet2D",
                "Properties": [
                  {
                    "name": "X",
                    "value": "textX"
                  },
                  {
                    "name": "Y",
                    "value": "textY"
                  },
                  {
                    "name": "Text",
                    "value": "%grid%"
                  },
                  {
                    "name": "Font",
                    "value": "font"
                  }
                ]
              },
              {
                "Name": "debug",
                "Type": "Group2D",
                "Expanded": "true",
                "Properties": [],
                "Children": [
                  {
                    "Name": "hotclickpoints",
                    "Type": "ShapeSet2D",
                    "Properties": [
                      {
                        "name": "FillColor",
                        "value": "linesColor"
                      },
                      {
                        "name": "SizeX",
                        "value": "0.1"
                      },
                      {
                        "name": "ShapeType",
                        "value": "\"ELLIPSE\""
                      },
                      {
                        "name": "X",
                        "value": "linesXoffset1"
                      },
                      {
                        "name": "Y",
                        "value": "linesYoffset1"
                      },
                      {
                        "name": "OnPress",
                        "value": "toggleEdge(linesInteract);"
                      },
                      {
                        "name": "SizeY",
                        "value": "0.1"
                      },
                      {
                        "name": "DrawLines",
                        "value": "false"
                      },
                      {
                        "name": "EnabledPosition",
                        "value": "\"ENABLED_NO_MOVE\""
                      },
                      {
                        "name": "ElementInteracted",
                        "value": "linesInteract"
                      }
                    ]
                  },
                  {
                    "Name": "hotclickpoints2",
                    "Type": "ShapeSet2D",
                    "Properties": [
                      {
                        "name": "FillColor",
                        "value": "linesColor"
                      },
                      {
                        "name": "SizeX",
                        "value": "0.1"
                      },
                      {
                        "name": "ShapeType",
                        "value": "\"ELLIPSE\""
                      },
                      {
                        "name": "X",
                        "value": "linesXoffset2"
                      },
                      {
                        "name": "Y",
                        "value": "linesYoffset2"
                      },
                      {
                        "name": "OnPress",
                        "value": "toggleEdge(linesInteract);"
                      },
                      {
                        "name": "SizeY",
                        "value": "0.1"
                      },
                      {
                        "name": "DrawLines",
                        "value": "false"
                      },
                      {
                        "name": "EnabledPosition",
                        "value": "\"ENABLED_NO_MOVE\""
                      },
                      {
                        "name": "ElementInteracted",
                        "value": "linesInteract"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "Name": "bottomPanel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [
          {
            "name": "Html",
            "value": "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/z0Ofm-U6_C4?si=6wHFE52P9He0vZ5Y\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>"
          }
        ],
        "Children": [
          {
            "Name": "instructions",
            "Type": "Label",
            "Properties": [
              {
                "name": "TextAlign",
                "value": "\"left\""
              },
              {
                "name": "Text",
                "value": "\"<h1>Sun Game</h1> <b>Objective</b>: Activate the correct edges so that the number of active edges touching each node matches the clue on that node.<br/><br/><b>Controls</b>: Click an edge once to activate it, a second time to flag it as impossible, and a third time to return it to undecided.<br/><br/><b>Teacher tools</b>: Use Hint for the next logical observation, Next Step to demonstrate it, Play Thinking to walk through the deduction, and Solve Now when you want the computer to finish.\""
              }
            ]
          },
          {
            "Name": "teacherStatus",
            "Type": "Label",
            "Properties": [
              {
                "name": "TextAlign",
                "value": "\"left\""
              },
              {
                "name": "Text",
                "value": "teacherStatusHtml"
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          }
        ]
      }
    ],
    "RootProperties": []
  },
  "metadata": {
    "APP": "WebEJS",
    "CreatedWith": "WebEJS : The web version of Easy JavaScript Simulations",
    "MoreInfo": "WebEJS 1.1",
    "version": "https://www.um.es/fem/wikis/webejs/"
  }
}