commit 49689c8225dbb0ecfc25ce787561c1024b8f5aa5
Author: byte <byteturtle@byteturtle.eu>
Date:   Sun Mar 9 11:57:49 2025 +0100

    initial commit, added caret-read-progress.js

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b281815
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+* `caret-read-progress.js` - highlights the line/position while reading a text - move back/forth with left/right arrows.
diff --git a/caret-read-progress.js b/caret-read-progress.js
new file mode 100644
index 0000000..fe6a556
--- /dev/null
+++ b/caret-read-progress.js
@@ -0,0 +1,123 @@
+// ==UserScript==
+// @name         Caret Read Progress
+// @namespace    http://tampermonkey.net/
+// @version      1.5
+// @description  Moves caret with arrow keys, highlights read text, works across paragraphs and divs
+// @author       You
+// @match        *://*/*
+// @grant        none
+// ==/UserScript==
+
+(function () {
+    'use strict';
+
+    let highlightDiv = document.createElement("div");
+    highlightDiv.style.position = "absolute";
+    highlightDiv.style.background = "rgba(255, 255, 0, 0.3)"; // Yellow highlight
+    highlightDiv.style.height = "1.5em";
+    highlightDiv.style.pointerEvents = "none";
+    highlightDiv.style.zIndex = "9999";
+    highlightDiv.style.width = "0px"; // Start with no highlight
+    document.body.appendChild(highlightDiv);
+
+    document.addEventListener("keydown", function (event) {
+        if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
+
+        let sel = window.getSelection();
+        if (!sel.rangeCount) return;
+
+        let range = sel.getRangeAt(0);
+        let moved = moveCaret(event.key === "ArrowRight" ? 1 : -1);
+
+        if (moved) updateHighlight();
+    });
+
+    function moveCaret(direction) {
+        let sel = window.getSelection();
+        if (!sel.rangeCount) return false;
+
+        let range = sel.getRangeAt(0);
+        let node = range.startContainer;
+        let offset = range.startOffset;
+
+        // Move within the same text node
+        if (direction === 1 && offset < node.length) {
+            offset++;
+        } else if (direction === -1 && offset > 0) {
+            offset--;
+        }
+        // If at the end of a node, move to the next text node
+        else if (direction === 1) {
+            let nextNode = findNextTextNode(node);
+            if (nextNode) {
+                node = nextNode;
+                offset = 0;
+            } else {
+                return false;
+            }
+        }
+        // If at the start of a node, move to the previous text node
+        else if (direction === -1) {
+            let prevNode = findPreviousTextNode(node);
+            if (prevNode) {
+                node = prevNode;
+                offset = prevNode.length;
+            } else {
+                return false;
+            }
+        }
+
+        // Update selection range
+        let newRange = document.createRange();
+        newRange.setStart(node, offset);
+        newRange.collapse(true);
+        sel.removeAllRanges();
+        sel.addRange(newRange);
+
+        return true;
+    }
+
+    function findNextTextNode(node) {
+        while (node) {
+            if (node.nextSibling) {
+                node = node.nextSibling;
+                while (node && node.nodeType !== Node.TEXT_NODE) {
+                    node = node.firstChild || node.nextSibling;
+                }
+                return node;
+            }
+            node = node.parentNode;
+        }
+        return null;
+    }
+
+    function findPreviousTextNode(node) {
+        while (node) {
+            if (node.previousSibling) {
+                node = node.previousSibling;
+                while (node && node.nodeType !== Node.TEXT_NODE) {
+                    node = node.lastChild || node.previousSibling;
+                }
+                return node;
+            }
+            node = node.parentNode;
+        }
+        return null;
+    }
+
+    function updateHighlight() {
+        let sel = window.getSelection();
+        if (!sel.rangeCount) return;
+
+        let range = sel.getRangeAt(0);
+        let rect = range.getBoundingClientRect();
+
+        if (rect.width === 0 && rect.height === 0) {
+            return; // Ignore empty selections
+        }
+
+        highlightDiv.style.top = `${rect.top + window.scrollY}px`;
+        highlightDiv.style.left = `0px`; // Always start from the left
+        highlightDiv.style.width = `${rect.right}px`; // Expand highlight only to where you've read
+    }
+})();