diff --git a/scope/index.html b/scope/index.html
new file mode 100644
index 0000000..b8b7793
--- /dev/null
+++ b/scope/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+Scope
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scope/scope.js b/scope/scope.js
new file mode 100644
index 0000000..0910baa
--- /dev/null
+++ b/scope/scope.js
@@ -0,0 +1,126 @@
+const sync_max = 512;
+
+const textarea = document.getElementById ("JNG5U2EX");
+const debug_text = document.getElementById ("XDHSIT76");
+
+textarea.innerText = "Loading...";
+
+let page = "";
+let offset = 0;
+
+function line_start_before (page, offset) {
+ const sync_start = Math.max (sync_max, offset) - sync_max;
+
+ for (let i = offset; i >= sync_start; i--) {
+ const c = page.charAt (i);
+ if (c == '\n') {
+ return i + 1;
+ }
+ }
+
+ return null;
+}
+
+function line_start_after (page, offset)
+{
+ for (let i = offset; i < offset + sync_max; i++) {
+ const c = page.charAt (i);
+ if (c == '\n') {
+ return i + 1;
+ }
+ }
+
+ return null;
+}
+
+function layout_text (page, offset) {
+ const width = 120;
+ const height = 24;
+
+ const line_start = line_start_before (page, offset) || 0;
+
+ // Compute layout
+
+ let lines = Array ();
+ let line = "";
+
+ for (let i = line_start; i < page.length; i++) {
+ if (lines.length >= height) {
+ break;
+ }
+ if (line.length >= width) {
+ lines.push (line);
+ line = "";
+ }
+
+ const c = page.charAt (i);
+
+ if (c == '\n') {
+ lines.push (line);
+ line = "";
+ }
+ else {
+ line = line + page.charAt (i);
+ }
+ }
+
+ return lines.join ('\n');
+}
+
+function repaint (offset)
+{
+ textarea.innerText = layout_text (page, offset);
+ debug_text.innerText = "Offset: " + offset + "\n";
+}
+
+async function async_main ()
+{
+ let file_length = null;
+
+ {
+ const resp = await fetch ("log.txt", {
+ "method": "HEAD",
+ });
+
+ file_length = resp.headers.get ("content-length");
+ }
+
+ const resp = await fetch ("log.txt", {
+ "headers": {
+ "range": "bytes=0-65535",
+ },
+ });
+
+ page = await resp.text ();
+
+ repaint (offset);
+}
+
+async_main ().then (
+ function (val) {},
+ function (err) {}
+);
+
+function cursor_left ()
+{
+ offset = Math.max (1, offset) - 1;
+ repaint (offset);
+}
+
+function cursor_right ()
+{
+ offset += 1;
+ repaint (offset);
+}
+
+function cursor_up ()
+{
+ offset = line_start_before (page, offset - 2) || 0;
+ repaint (offset);
+}
+
+function cursor_down ()
+{
+ offset = line_start_after (page, offset);
+ repaint (offset);
+}