diff --git a/Assets/Resources/Prefabs/UI/FrameITUI.prefab b/Assets/Resources/Prefabs/UI/FrameITUI.prefab
index 3b1ab2046795ab96df86a52929609c25c436a09c..d726a68734f04ede2a1ac84dea1ce8d5bcacf4e6 100644
--- a/Assets/Resources/Prefabs/UI/FrameITUI.prefab
+++ b/Assets/Resources/Prefabs/UI/FrameITUI.prefab
@@ -406,12 +406,12 @@ PrefabInstance:
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
       propertyPath: m_AnchorMax.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
       propertyPath: m_AnchorMin.y
-      value: 0
+      value: 1
       objectReference: {fileID: 0}
     - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1,
         type: 3}
@@ -561,7 +561,7 @@ PrefabInstance:
     - target: {fileID: 6500467619489830996, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: 604800
+      value: 605880
       objectReference: {fileID: 0}
     - target: {fileID: 7849991042685492731, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
@@ -611,7 +611,7 @@ PrefabInstance:
     - target: {fileID: 7989559431199338490, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
       propertyPath: m_AnchoredPosition.y
-      value: -33600
+      value: -33660
       objectReference: {fileID: 0}
     - target: {fileID: 8004702056544321748, guid: 292834880e6f0e54186b873acc62d3f2,
         type: 3}
diff --git a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
index 846b8783720d879dc70688617a77c180daa49a78..ba7a6d62ac82c3a2206515433c0f5d0aea8f4c2e 100644
--- a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
+++ b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab
@@ -2950,6 +2950,36 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: ae310511ad607a64e891bcb46517277b, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
+  OnButtonClick:
+    m_PersistentCalls:
+      m_Calls: []
+  HintAvailableEvent:
+    m_PersistentCalls:
+      m_Calls: []
+  OnFactAssignmentUpdated:
+    m_PersistentCalls:
+      m_Calls: []
+  OnMMTServerComunicationError:
+    m_PersistentCalls:
+      m_Calls: []
+  OnScrollDynamicInfoError:
+    m_PersistentCalls:
+      m_Calls: []
+  OnScrollApplicationError:
+    m_PersistentCalls:
+      m_Calls: []
+  OnCancelErrorState:
+    m_PersistentCalls:
+      m_Calls: []
+  OnScrollSolved:
+    m_PersistentCalls:
+      m_Calls: []
+  OnScrollChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  OnScrollDynamicInfoUpdated:
+    m_PersistentCalls:
+      m_Calls: []
   DynamicScrollDescriptionsActive: 1
   AutomaticHintGenerationActive: 1
 --- !u!1 &8004702058016740896
@@ -3475,12 +3505,12 @@ PrefabInstance:
     - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866,
         type: 3}
       propertyPath: m_SizeDelta.x
-      value: 600
+      value: 32
       objectReference: {fileID: 0}
     - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866,
         type: 3}
       propertyPath: m_SizeDelta.y
-      value: 600
+      value: 32
       objectReference: {fileID: 0}
     - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866,
         type: 3}
@@ -3573,9 +3603,6 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 534d25977ad35154781c29672ce2bb13, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
-  onFactAssignment:
-    m_PersistentCalls:
-      m_Calls: []
   onHintRequested:
     m_PersistentCalls:
       m_Calls: []
diff --git a/Assets/Scripts/UI/InGame/ScrollDetails.cs b/Assets/Scripts/UI/InGame/ScrollDetails.cs
index 1302849c22589533a0d97e4b61d1f9f77450ec83..c203d6845cfa1b47d929208aee1a064c632337d3 100644
--- a/Assets/Scripts/UI/InGame/ScrollDetails.cs
+++ b/Assets/Scripts/UI/InGame/ScrollDetails.cs
@@ -1,5 +1,6 @@
 using REST_JSON_API;
 using System.Collections.Generic;
+using System.Text.RegularExpressions;
 using TMPro;
 using UnityEngine;
 
@@ -67,7 +68,13 @@ private void ShowScroll(Scroll newScroll)
         //Clear all current ScrollFacts
         originalViewport.GetChild(0).gameObject.DestroyAllChildren();
 
-        originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = newScroll.description;
+        var description = newScroll.description;
+        // if description is a html description, extract the alternative text representation
+        if (newScroll.description.StartsWith("<scroll-description"))
+        {
+            description = Regex.Match(newScroll.description, "<scroll-description [^>]* alt=([\"']?)(.*)\\1 [^>]*>").Groups[2].Value;
+        }
+        originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = description;
 
         //ParameterDisplays.ForEach(gameObj => Destroy(gameObj));
         ParameterDisplays = new();
diff --git a/Assets/Scripts/UI/InGame/WebViewController.cs b/Assets/Scripts/UI/InGame/WebViewController.cs
index d4c24bb08c712c2e2246baa461c94ebe30dae5ec..a2ca32a302357aea06e0269790e5d93461ada57d 100644
--- a/Assets/Scripts/UI/InGame/WebViewController.cs
+++ b/Assets/Scripts/UI/InGame/WebViewController.cs
@@ -4,6 +4,7 @@
 using MoreLinq;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
+using REST_JSON_API;
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -14,14 +15,6 @@
 
 public class WebViewController : ScrollView
 {
-
-
-    // TODO: onFactAssignment event shoud hav a dropzone id and a fact id as parameters
-    public UnityEvent<DomNodeWrapper, string> onFactAssignment;
-    // TODO: implement onHintRequested event
-    public UnityEvent<DomNodeWrapper, string> onHintRequested;
-    // TODO: click events (option for custom buttons for a scroll)
-    public UnityEvent<DomNodeWrapper> onClick;
     private WebViewComponent webViewComponent;
     private DomNodeWrapper[] dropzones;
 
@@ -31,47 +24,129 @@ private void Awake()
         webViewComponent = GetComponent<WebViewComponent>();
         WebViewComponent.serializerSettings.Converters.Add(new FactObjectUIConverter());
         webViewComponent.targetUrl = Path.Join(CommunicationEvents.Get_DataPath(), "scrollView.html");
+        webViewComponent.OnWebViewComponentReady += OnWebViewComponentReady;
 
         // TODO: handle webViewComponent.onDomDocumentUpdated
+    }
+    private void OnWebViewComponentReady()
+    {
+        webViewComponent.onDomDocumentUpdated += DocumentUpdatedHandler;
+        SwitchScrollUI.activeScrollData.OnScrollChanged.AddListener(SetScrollContent);
+        SwitchScrollUI.activeScrollData.OnScrollDynamicInfoUpdated.AddListener(SetScrollContent);
+        webViewComponent.tab.AddJSBinding("applyScroll", ApplyScrollHandler);
+        webViewComponent.tab.AddJSBinding("getHint", GetHintHandler);
+        RegisterBrowserEventhandlers();
+    }
+    private void OnDisable()
+    {
+        webViewComponent.onDomDocumentUpdated -= DocumentUpdatedHandler;
+        SwitchScrollUI.activeScrollData.OnScrollChanged.RemoveListener(SetScrollContent);
+        SwitchScrollUI.activeScrollData.OnScrollDynamicInfoUpdated.RemoveListener(SetScrollContent);
+        webViewComponent.tab.RemoveJSBinding("applyScroll");
+        webViewComponent.tab.RemoveJSBinding("getHint");
+        DeRegisterBrowserEventhandlers();
+    }
 
-        // TODO: remove this debug code
-        onFactAssignment.AddListener((DomNodeWrapper node, string factId) => {
-            Debug.LogWarning($"onFactAssignment: '{string.Join(", ", GetFactAssignments())}'");
-        });
+    private void DocumentUpdatedHandler(documentUpdatedEvent _)
+    {
+        // all old DomNodeWrapper objects are invalid, because the whole document has been updated
+        dropzones = null;
+        RegisterBrowserEventhandlers();
+    }
+
+    private async void RegisterBrowserEventhandlers()
+    {
+        // register js event handlers in the browser
+        _ = webViewComponent.tab.Evaluate("dropZones = document.querySelectorAll('[dropzone=\"copy\"]'); dropZones.forEach( dropZone => dropZone.addEventListener(\"drop\", ev => dropHandler(ev, \"DropEvent\")) )");
+        
+        // register c# event handlers
+        // get the dropzones from the scroll and add the event handler to track when something is dropped
+        DomNodeWrapper document = await webViewComponent.tab.GetDocument();
+        dropzones = await document.querySelectorAllAsync("[dropzone='copy']");
+        dropzones.ForEach(dropzone => dropzone.OnAttributeModified += DropzoneAttributeModifiedHandler);
+    }
+
+    private void DeRegisterBrowserEventhandlers()
+    {
+        dropzones?.ForEach(dropzone => dropzone.OnAttributeModified -= DropzoneAttributeModifiedHandler);
     }
 
     /// <summary>
     /// sets or updates the content of the scroll container dom element
     /// </summary>
     /// <param name="scrollHTML"></param>
-    public async void SetScrollContent(string scrollHTML)
+    private async void SetScrollContent(Scroll scroll)
     {
         // update scroll container content
-        DomNodeWrapper scrollContainer = await DomNodeWrapper.requestNodeAsync(webViewComponent.tab, "scrollContainer");
-        await scrollContainer.setNodeValueAsync(scrollHTML);
+        DomNodeWrapper document = await webViewComponent.tab.GetDocument();
+        DomNodeWrapper scrollContainer = await document.querySelectorAsync("#scrollContainer");
 
-        // get the dropzones from the scroll and add the event handler to track when something is dropped
-        dropzones = await scrollContainer.querySelectorAllAsync("[dropzone='copy']");
-        dropzones.ForEach(dropzone => dropzone.OnAttributeModified += DropzoneAttributeModifiedHandler);
+        var description = scroll.description;
+        // if scroll is a lagacy plain text scroll, generate html with slots for the required facts
+        if (!scroll.description.StartsWith("<scroll-description"))
+        {
+            // generate slots for a plane text scroll description
+            var factSlots = SwitchScrollUI.activeScrollData.Assignments
+                .Where(pair => pair.Value.IsVisible)
+                .Select(pair =>
+                    @$"<span class='lagacySlot' dropzone='copy' data-slot-id='{pair.Key}' {(pair.Value.IsSet ? $"data-fact-id='{pair.Value.fact.Id}'" : "")}>
+                        {(pair.Value.IsSet ?
+                            pair.Value.fact.GetLabel(StageStatic.stage.factState) :
+                            scroll.requiredFacts.Find(fact => fact.@ref.uri == pair.Key).label )}
+                    </span>");
+
+            description = $"<scroll-description><p>{scroll.description}</p><div id='lagacySlots'>{String.Join("", factSlots)}</div></scroll-description>";
+        }
+        // display the scroll description
+        await scrollContainer.setOuterHtmlAsync($"<div id='scrollContainer'>{description}</div>");
+
+        RegisterBrowserEventhandlers();
     }
 
-    private void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent)
+    private async void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent)
     {
         Debug.LogWarning($"dropzoneAtrributeModifiedHandler: '{attributeModifiedEvent.name}'");
 
         // call the onFactAssignment event if the data-fact-id attribute was modified
         if (attributeModifiedEvent.name == "data-fact-id")
         {
-            var node = webViewComponent.tab.domNodes.GetValueOrDefault(attributeModifiedEvent.nodeId);
-            onFactAssignment.Invoke(node, attributeModifiedEvent.value);
+            // get the slot id
+            var node = await webViewComponent.tab.GetNode(attributeModifiedEvent.nodeId);
+            if (! (node.Node.attributes.TryGetValue("data-slot-id", out string slot)
+                || (await node.getAttributesAsync()).TryGetValue("data-slot-id", out slot)))
+            {
+                Debug.LogError($"dropzoneAtrributeModifiedHandler: data-slot-id attribute not found on dropzone");
+                throw new Exception("data-slot-id attribute not found on dropzone");
+            }
+
+            // get the fact from the fact id
+            if (!FactRecorder.AllFacts.TryGetValue(attributeModifiedEvent.value, out Fact fact))
+            {
+                Debug.LogError($"dropzoneAtrributeModifiedHandler: fact with id '{attributeModifiedEvent.value}' not found");
+                throw new Exception($"fact with id '{attributeModifiedEvent.value}' not found");
+            }
+
+            // assign fact to slot
+            SwitchScrollUI.activeScrollData.AssignFact(slot, fact);
         }
     }
 
+    private void ApplyScrollHandler(string button)
+    {
+        SwitchScrollUI.activeScrollData.ButtonClicked(new MagicScrollButton());
+    }
+
+    private void GetHintHandler(string url)
+    {
+        SwitchScrollUI.activeScrollData.ButtonClicked(new HintScrollButton(url));
+    }
+
     public string[] GetFactAssignments()
     {
         return dropzones.Select(dropzone => dropzone.Node.attributes.GetValueOrDefault("data-fact-id", null)).ToArray();
     }
 
+    [Obsolete()]
     private async void GetDropzoneStateAsync()
     {
         // alternative way to get the dropzone state
diff --git a/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html b/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html
index af335f71b1e137b95089031a23e31727715d80d2..9c1302c95e31cd7ba6be52159ab4604dc93e37da 100644
--- a/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html
+++ b/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html
@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-<html lang="en">
 <head>
   <meta charset="utf-8">
   <title>Scroll View</title>
@@ -9,7 +8,13 @@
       font-size: 100px;
     }
     [dropzone] {
-      background-color: yellow;
+      background-color: grey;
+    }
+    .lagacySlot {
+      width: 100px;
+      height: 100px;
+      margin: 50px;
+      border-width: 1px;
     }
     #rectangle {
       width: 10px;
@@ -24,86 +29,51 @@
   </style>
 </head>
 <body>
-  <math>
-    <mi>E</mi>
-    <mo>=</mo>
-    <mfenced>
-    <mtable>
-      <mtr>
-        <mtd>
-          <mi dropzone="copy" data-allowed-types="PointFact,AngleFact" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?A">a</mi>
-        </mtd>
-        <mtd>
-            <mi dropzone="copy" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?B">b</mi>
-        </mtd>
-        <mtd>
-          <mn>0</mn>
-        </mtd>
-      </mtr>
-      <mtr>
-        <mtd>
-          <mn>0</mn>
-        </mtd>
-        <mtd>
-          <mn>1</mn>
-        </mtd>
-        <mtd>
-          <mn>0</mn>
-        </mtd>
-      </mtr>
-      <mtr>
-        <mtd>
-          <mn>0</mn>
-        </mtd>
-        <mtd>
-          <mn>0</mn>
-        </mtd>
-        <mtd>
-          <mn>1</mn>
-        </mtd>
-      </mtr>
-    </mtable>
-  </math>
-  <div class="demoFact">{
-    "s_type": "PointFact",
-    "label": "A",
-    "_CustomLabel": null,
-    "hasCustomLabel": false,
-    "labelId": 0,
-    "point": {"x": -1.66086578, "y": -0.00494432449, "z": -2.17682648},
-    "normal": {"x": 0.1, "y": 1.2, "z": 0.3}
-  }</div>
-  <div class="demoFact">{
-    "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact252",
-    "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact254",
-    "pid3": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256",
-    "s_type": "AngleFact",
-    "label": "∠BDF",
-    "_CustomLabel": null,
-    "is_right_angle": false,
-    "hasCustomLabel": false,
-    "labelId": 0
-  }</div>
-  <div class="demoFact">{
-    "s_type": "LineFact",
-    "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact255",
-    "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256",
-    "dir": {"x": 0.1, "y": 1.2, "z": 0.3},
-    "label": "[EF]",
-    "_CustomLabel": null,
-    "hasCustomLabel": false,
-    "labelId": 0
-  }</div>
-  <div class="demoFact">{
-    "s_type": "RayFact",
-    "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256",
-    "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact252",
-    "dir": {"x": 0.1, "y": 1.2, "z": 0.3},
-    "label": "FB",
-    "_CustomLabel": null,
-    "hasCustomLabel": false,
-    "labelId": 0
-  }</div>
+  <div id="scrollContainer">
+    <div>Default Scroll</div>
+    <math>
+      <mi>E</mi>
+      <mo>=</mo>
+      <mfenced>
+      <mtable>
+        <mtr>
+          <mtd>
+            <mi dropzone="copy" data-allowed-types="PointFact,AngleFact" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?A">a</mi>
+          </mtd>
+          <mtd>
+              <mi dropzone="copy" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?B">b</mi>
+          </mtd>
+          <mtd>
+            <mn>0</mn>
+          </mtd>
+        </mtr>
+        <mtr>
+          <mtd>
+            <mn>0</mn>
+          </mtd>
+          <mtd>
+            <mn>1</mn>
+          </mtd>
+          <mtd>
+            <mn>0</mn>
+          </mtd>
+        </mtr>
+        <mtr>
+          <mtd>
+            <mn>0</mn>
+          </mtd>
+          <mtd>
+            <mn>0</mn>
+          </mtd>
+          <mtd>
+            <mn>1</mn>
+          </mtd>
+        </mtr>
+      </mtable>
+    </math>
+  </div>
+  <button type="button" title="Apply the scroll" onclick="applyScroll()">Magic</button><br>
+  <button type="button" title="Hint" onclick="getHint('http://mathhub.info/FrameIT/frameworld?TriangleProblem_RightAngleAtC?rightAngleC')">Hint</button>
 </body>
   <script>
     /**
diff --git a/Packages/bessw.unity.webview b/Packages/bessw.unity.webview
index c49f768b41a3caf0541fcdbfa0d65b74c7e63a4f..32bbc9be2e099918d10d0e54e8993cae6e54799a 160000
--- a/Packages/bessw.unity.webview
+++ b/Packages/bessw.unity.webview
@@ -1 +1 @@
-Subproject commit c49f768b41a3caf0541fcdbfa0d65b74c7e63a4f
+Subproject commit 32bbc9be2e099918d10d0e54e8993cae6e54799a