commit 7758bce2a0d184b500bd53756db66724f06b010c
Author: root <root@hub.scroll.pub>
Date: 2025-01-15 23:52:13 +0000
Subject: Initial commit
diff --git a/index.scroll b/index.scroll
new file mode 100644
index 0000000..b5a98b9
--- /dev/null
+++ b/index.scroll
@@ -0,0 +1,78 @@
+buildHtml
+baseUrl https://viz.scroll.pub
+metaTags
+editButton /edit.html
+title Graph Visualizer
+style.css
+header
+ class hero
+ nav
+ div GraphViz
+ class logo
+ div
+ class nav-links
+ a Create
+ href #canvas
+ a Examples
+ href #examples
+ a Help
+ href #help
+main
+ section
+ id canvas
+ class graph-workspace
+ div
+ class controls
+ button New Node
+ id addNode
+ button New Edge
+ id addEdge
+ button Clear
+ id clear
+ canvas
+ id graphCanvas
+ section
+ id examples
+ class example-section
+ h2 Example Graphs
+ div
+ class example-grid
+ div
+ class example-card
+ h3 Binary Tree
+ canvas
+ class mini-canvas
+ data-type binary
+ div
+ class example-card
+ h3 Network
+ canvas
+ class mini-canvas
+ data-type network
+ div
+ class example-card
+ h3 Circle
+ canvas
+ class mini-canvas
+ data-type circle
+ section
+ id help
+ class help-section
+ h2 How to Use
+ div
+ class instructions
+ p Click "New Node" to add vertices
+ p Click "New Edge" then select two nodes to connect
+ p Drag nodes to reposition
+ p Double-click to delete
+footer
+ div
+ class footer-content
+ p Built with Scroll
+ div
+ class footer-links
+ a Tutorial
+ href tutorial.html
+ a API
+ href api.html
+script.js
\ No newline at end of file
diff --git a/prompt.json b/prompt.json
new file mode 100644
index 0000000..fa65f17
--- /dev/null
+++ b/prompt.json
@@ -0,0 +1,20 @@
+{
+ "id": "msg_01LpW7qwkBZE1F8vs2zWjQHq",
+ "type": "message",
+ "role": "assistant",
+ "model": "claude-3-5-sonnet-20241022",
+ "content": [
+ {
+ "type": "text",
+ "text": "For a graph visualization website, I suggest the domain \"viz.scroll.pub\" - it's short, memorable, and clearly indicates the purpose.\n\n---domain---\nviz.scroll.pub\n---index.scroll---\nbuildHtml\nbaseUrl https://viz.scroll.pub\nmetaTags\neditButton /edit.html\ntitle Graph Visualizer\nstyle.css\nheader\n class hero\n nav\n div GraphViz\n class logo\n div\n class nav-links\n a Create\n href #canvas\n a Examples\n href #examples\n a Help\n href #help\nmain\n section\n id canvas\n class graph-workspace\n div\n class controls\n button New Node\n id addNode\n button New Edge\n id addEdge\n button Clear\n id clear\n canvas\n id graphCanvas\n section\n id examples\n class example-section\n h2 Example Graphs\n div\n class example-grid\n div\n class example-card\n h3 Binary Tree\n canvas\n class mini-canvas\n data-type binary\n div\n class example-card\n h3 Network\n canvas\n class mini-canvas\n data-type network\n div\n class example-card\n h3 Circle\n canvas\n class mini-canvas\n data-type circle\n section\n id help\n class help-section\n h2 How to Use\n div\n class instructions\n p Click \"New Node\" to add vertices\n p Click \"New Edge\" then select two nodes to connect\n p Drag nodes to reposition\n p Double-click to delete\nfooter\n div\n class footer-content\n p Built with Scroll\n div\n class footer-links\n a Tutorial\n href tutorial.html\n a API\n href api.html\nscript.js\n---style.css---\n:root {\n --primary-color: #2a6b9c;\n --secondary-color: #3498db;\n --accent-color: #e74c3c;\n --background-color: #f5f6fa;\n --text-color: #2c3e50;\n --shadow: 0 4px 6px rgba(0,0,0,0.1);\n}\n\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n font-family: 'Segoe UI', system-ui, sans-serif;\n line-height: 1.6;\n color: var(--text-color);\n background: var(--background-color);\n}\n\n.hero {\n background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n padding: 1rem;\n color: white;\n}\n\nnav {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1rem;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.logo {\n font-size: 1.5rem;\n font-weight: bold;\n text-shadow: 2px 2px 4px rgba(0,0,0,0.2);\n}\n\n.nav-links {\n display: flex;\n gap: 2rem;\n}\n\n.nav-links a {\n color: white;\n text-decoration: none;\n transition: all 0.3s ease;\n}\n\n.nav-links a:hover {\n transform: translateY(-2px);\n text-shadow: 0 0 8px rgba(255,255,255,0.5);\n}\n\n.graph-workspace {\n padding: 2rem;\n background: white;\n border-radius: 8px;\n box-shadow: var(--shadow);\n margin: 2rem auto;\n max-width: 1200px;\n}\n\n#graphCanvas {\n width: 100%;\n height: 600px;\n border: 2px solid var(--secondary-color);\n border-radius: 4px;\n margin-top: 1rem;\n}\n\n.controls {\n display: flex;\n gap: 1rem;\n margin-bottom: 1rem;\n}\n\nbutton {\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n background: var(--secondary-color);\n color: white;\n cursor: pointer;\n transition: all 0.3s ease;\n}\n\nbutton:hover {\n background: var(--primary-color);\n transform: translateY(-2px);\n}\n\n.example-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 2rem;\n padding: 2rem;\n}\n\n.example-card {\n background: white;\n padding: 1rem;\n border-radius: 8px;\n box-shadow: var(--shadow);\n transition: all 0.3s ease;\n}\n\n.example-card:hover {\n transform: translateY(-4px);\n box-shadow: 0 6px 12px rgba(0,0,0,0.15);\n}\n\n.mini-canvas {\n width: 100%;\n height: 200px;\n border: 1px solid var(--secondary-color);\n border-radius: 4px;\n}\n\n.help-section {\n background: white;\n padding: 2rem;\n margin: 2rem auto;\n max-width: 1200px;\n border-radius: 8px;\n box-shadow: var(--shadow);\n}\n\nfooter {\n background: var(--primary-color);\n color: white;\n padding: 2rem;\n margin-top: 4rem;\n}\n\n.footer-content {\n max-width: 1200px;\n margin: 0 auto;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.footer-links a {\n color: white;\n text-decoration: none;\n margin-left: 2rem;\n}\n\n@media (max-width: 768px) {\n .nav-links {\n gap: 1rem;\n }\n \n .controls {\n flex-direction: column;\n }\n \n .example-grid {\n grid-template-columns: 1fr;\n }\n \n .footer-content {\n flex-direction: column;\n gap: 1rem;\n text-align: center;\n }\n}\n---script.js---\nclass GraphVisualizer {\n constructor() {\n this.canvas = document.getElementById('graphCanvas');\n this.ctx = this.canvas.getContext('2d');\n this.nodes = [];\n this.edges = [];\n this.isDragging = false;\n this.selectedNode = null;\n this.mode = 'node';\n \n this.initializeCanvas();\n this.addEventListeners();\n this.animate();\n }\n\n initializeCanvas() {\n this.resizeCanvas();\n window.addEventListener('resize', () => this.resizeCanvas());\n }\n\n resizeCanvas() {\n this.canvas.width = this.canvas.offsetWidth;\n this.canvas.height = this.canvas.offsetHeight;\n }\n\n addEventListeners() {\n document.getElementById('addNode').addEventListener('click', () => this.mode = 'node');\n document.getElementById('addEdge').addEventListener('click', () => this.mode = 'edge');\n document.getElementById('clear').addEventListener('click', () => this.clear());\n\n this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));\n this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));\n this.canvas.addEventListener('mouseup', () => this.handleMouseUp());\n this.canvas.addEventListener('dblclick', (e) => this.handleDoubleClick(e));\n }\n\n getMousePos(e) {\n const rect = this.canvas.getBoundingClientRect();\n return {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top\n };\n }\n\n handleMouseDown(e) {\n const pos = this.getMousePos(e);\n const clickedNode = this.findNodeAtPosition(pos);\n\n if (clickedNode) {\n if (this.mode === 'edge' && this.selectedNode) {\n this.addEdge(this.selectedNode, clickedNode);\n this.selectedNode = null;\n } else if (this.mode === 'edge') {\n this.selectedNode = clickedNode;\n } else {\n this.isDragging = true;\n this.selectedNode = clickedNode;\n }\n } else if (this.mode === 'node') {\n this.addNode(pos.x, pos.y);\n }\n }\n\n handleMouseMove(e) {\n if (this.isDragging && this.selectedNode) {\n const pos = this.getMousePos(e);\n this.selectedNode.x = pos.x;\n this.selectedNode.y = pos.y;\n }\n }\n\n handleMouseUp() {\n this.isDragging = false;\n }\n\n handleDoubleClick(e) {\n const pos = this.getMousePos(e);\n const node = this.findNodeAtPosition(pos);\n if (node) {\n this.removeNode(node);\n }\n }\n\n addNode(x, y) {\n this.nodes.push({\n x,\n y,\n radius: 20,\n color: this.getRandomColor()\n });\n }\n\n addEdge(node1, node2) {\n if (node1 !== node2) {\n this.edges.push({\n from: node1,\n to: node2\n });\n }\n }\n\n removeNode(node) {\n const index = this.nodes.indexOf(node);\n if (index > -1) {\n this.nodes.splice(index, 1);\n this.edges = this.edges.filter(edge => \n edge.from !== node && edge.to !== node\n );\n }\n }\n\n clear() {\n this.nodes = [];\n this.edges = [];\n this.selectedNode = null;\n }\n\n findNodeAtPosition(pos) {\n return this.nodes.find(node => \n Math.hypot(node.x - pos.x, node.y - pos.y) < node.radius\n );\n }\n\n getRandomColor() {\n const colors = ['#2ecc71', '#3498db', '#e74c3c', '#f1c40f', '#9b59b6'];\n return colors[Math.floor(Math.random() * colors.length)];\n }\n\n draw() {\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n // Draw edges\n this.edges.forEach(edge => {\n this.ctx.beginPath();\n this.ctx.moveTo(edge.from.x, edge.from.y);\n this.ctx.lineTo(edge.to.x, edge.to.y);\n this.ctx.strokeStyle = '#95a5a6';\n this.ctx.lineWidth = 2;\n this.ctx.stroke();\n });\n\n // Draw nodes\n this.nodes.forEach(node => {\n this.ctx.beginPath();\n this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);\n this.ctx.fillStyle = node === this.selectedNode ? '#e74c3c' : node.color;\n this.ctx.fill();\n this.ctx.strokeStyle = '#2c3e50';\n this.ctx.lineWidth = 2;\n this.ctx.stroke();\n });\n }\n\n animate() {\n this.draw();\n requestAnimationFrame(() => this.animate());\n }\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n new GraphVisualizer();\n});\n---end---\n\nThis implementation creates an interactive graph visualization tool with the following features:\n- Add nodes by clicking the \"New Node\" button and clicking on the canvas\n- Add edges by clicking the \"New Edge\" button and selecting two nodes\n- Drag nodes to reposition them\n- Double-click nodes to delete them\n- Clear the entire graph with the \"Clear\" button\n- Responsive design that works on both desktop and mobile\n- Smooth animations and transitions\n- Professional color scheme and modern UI\n- Accessible controls and semantic HTML\n- Canvas-based rendering for optimal performance\n\nThe CSS includes modern features like CSS variables, fluid layouts, and smooth transitions. The JavaScript implementation uses classes and modern ES6+ features while maintaining broad browser compatibility."
+ }
+ ],
+ "stop_reason": "end_turn",
+ "stop_sequence": null,
+ "usage": {
+ "input_tokens": 818,
+ "cache_creation_input_tokens": 0,
+ "cache_read_input_tokens": 0,
+ "output_tokens": 3210
+ }
+}
\ No newline at end of file
diff --git a/readme.scroll b/readme.scroll
new file mode 100644
index 0000000..5828ea3
--- /dev/null
+++ b/readme.scroll
@@ -0,0 +1,3 @@
+# viz.scroll.pub
+
+website generated by Claude from prompt: graph viz
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..304b4cd
--- /dev/null
+++ b/script.js
@@ -0,0 +1,162 @@
+class GraphVisualizer {
+ constructor() {
+ this.canvas = document.getElementById('graphCanvas');
+ this.ctx = this.canvas.getContext('2d');
+ this.nodes = [];
+ this.edges = [];
+ this.isDragging = false;
+ this.selectedNode = null;
+ this.mode = 'node';
+
+ this.initializeCanvas();
+ this.addEventListeners();
+ this.animate();
+ }
+
+ initializeCanvas() {
+ this.resizeCanvas();
+ window.addEventListener('resize', () => this.resizeCanvas());
+ }
+
+ resizeCanvas() {
+ this.canvas.width = this.canvas.offsetWidth;
+ this.canvas.height = this.canvas.offsetHeight;
+ }
+
+ addEventListeners() {
+ document.getElementById('addNode').addEventListener('click', () => this.mode = 'node');
+ document.getElementById('addEdge').addEventListener('click', () => this.mode = 'edge');
+ document.getElementById('clear').addEventListener('click', () => this.clear());
+
+ this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));
+ this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
+ this.canvas.addEventListener('mouseup', () => this.handleMouseUp());
+ this.canvas.addEventListener('dblclick', (e) => this.handleDoubleClick(e));
+ }
+
+ getMousePos(e) {
+ const rect = this.canvas.getBoundingClientRect();
+ return {
+ x: e.clientX - rect.left,
+ y: e.clientY - rect.top
+ };
+ }
+
+ handleMouseDown(e) {
+ const pos = this.getMousePos(e);
+ const clickedNode = this.findNodeAtPosition(pos);
+
+ if (clickedNode) {
+ if (this.mode === 'edge' && this.selectedNode) {
+ this.addEdge(this.selectedNode, clickedNode);
+ this.selectedNode = null;
+ } else if (this.mode === 'edge') {
+ this.selectedNode = clickedNode;
+ } else {
+ this.isDragging = true;
+ this.selectedNode = clickedNode;
+ }
+ } else if (this.mode === 'node') {
+ this.addNode(pos.x, pos.y);
+ }
+ }
+
+ handleMouseMove(e) {
+ if (this.isDragging && this.selectedNode) {
+ const pos = this.getMousePos(e);
+ this.selectedNode.x = pos.x;
+ this.selectedNode.y = pos.y;
+ }
+ }
+
+ handleMouseUp() {
+ this.isDragging = false;
+ }
+
+ handleDoubleClick(e) {
+ const pos = this.getMousePos(e);
+ const node = this.findNodeAtPosition(pos);
+ if (node) {
+ this.removeNode(node);
+ }
+ }
+
+ addNode(x, y) {
+ this.nodes.push({
+ x,
+ y,
+ radius: 20,
+ color: this.getRandomColor()
+ });
+ }
+
+ addEdge(node1, node2) {
+ if (node1 !== node2) {
+ this.edges.push({
+ from: node1,
+ to: node2
+ });
+ }
+ }
+
+ removeNode(node) {
+ const index = this.nodes.indexOf(node);
+ if (index > -1) {
+ this.nodes.splice(index, 1);
+ this.edges = this.edges.filter(edge =>
+ edge.from !== node && edge.to !== node
+ );
+ }
+ }
+
+ clear() {
+ this.nodes = [];
+ this.edges = [];
+ this.selectedNode = null;
+ }
+
+ findNodeAtPosition(pos) {
+ return this.nodes.find(node =>
+ Math.hypot(node.x - pos.x, node.y - pos.y) < node.radius
+ );
+ }
+
+ getRandomColor() {
+ const colors = ['#2ecc71', '#3498db', '#e74c3c', '#f1c40f', '#9b59b6'];
+ return colors[Math.floor(Math.random() * colors.length)];
+ }
+
+ draw() {
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+
+ // Draw edges
+ this.edges.forEach(edge => {
+ this.ctx.beginPath();
+ this.ctx.moveTo(edge.from.x, edge.from.y);
+ this.ctx.lineTo(edge.to.x, edge.to.y);
+ this.ctx.strokeStyle = '#95a5a6';
+ this.ctx.lineWidth = 2;
+ this.ctx.stroke();
+ });
+
+ // Draw nodes
+ this.nodes.forEach(node => {
+ this.ctx.beginPath();
+ this.ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
+ this.ctx.fillStyle = node === this.selectedNode ? '#e74c3c' : node.color;
+ this.ctx.fill();
+ this.ctx.strokeStyle = '#2c3e50';
+ this.ctx.lineWidth = 2;
+ this.ctx.stroke();
+ });
+ }
+
+ animate() {
+ this.draw();
+ requestAnimationFrame(() => this.animate());
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ new GraphVisualizer();
+});
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..5055d5a
--- /dev/null
+++ b/style.css
@@ -0,0 +1,173 @@
+:root {
+ --primary-color: #2a6b9c;
+ --secondary-color: #3498db;
+ --accent-color: #e74c3c;
+ --background-color: #f5f6fa;
+ --text-color: #2c3e50;
+ --shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', system-ui, sans-serif;
+ line-height: 1.6;
+ color: var(--text-color);
+ background: var(--background-color);
+}
+
+.hero {
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
+ padding: 1rem;
+ color: white;
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.logo {
+ font-size: 1.5rem;
+ font-weight: bold;
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
+}
+
+.nav-links {
+ display: flex;
+ gap: 2rem;
+}
+
+.nav-links a {
+ color: white;
+ text-decoration: none;
+ transition: all 0.3s ease;
+}
+
+.nav-links a:hover {
+ transform: translateY(-2px);
+ text-shadow: 0 0 8px rgba(255,255,255,0.5);
+}
+
+.graph-workspace {
+ padding: 2rem;
+ background: white;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ margin: 2rem auto;
+ max-width: 1200px;
+}
+
+#graphCanvas {
+ width: 100%;
+ height: 600px;
+ border: 2px solid var(--secondary-color);
+ border-radius: 4px;
+ margin-top: 1rem;
+}
+
+.controls {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+button {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ background: var(--secondary-color);
+ color: white;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+button:hover {
+ background: var(--primary-color);
+ transform: translateY(-2px);
+}
+
+.example-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 2rem;
+ padding: 2rem;
+}
+
+.example-card {
+ background: white;
+ padding: 1rem;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ transition: all 0.3s ease;
+}
+
+.example-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 6px 12px rgba(0,0,0,0.15);
+}
+
+.mini-canvas {
+ width: 100%;
+ height: 200px;
+ border: 1px solid var(--secondary-color);
+ border-radius: 4px;
+}
+
+.help-section {
+ background: white;
+ padding: 2rem;
+ margin: 2rem auto;
+ max-width: 1200px;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+}
+
+footer {
+ background: var(--primary-color);
+ color: white;
+ padding: 2rem;
+ margin-top: 4rem;
+}
+
+.footer-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.footer-links a {
+ color: white;
+ text-decoration: none;
+ margin-left: 2rem;
+}
+
+@media (max-width: 768px) {
+ .nav-links {
+ gap: 1rem;
+ }
+
+ .controls {
+ flex-direction: column;
+ }
+
+ .example-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .footer-content {
+ flex-direction: column;
+ gap: 1rem;
+ text-align: center;
+ }
+}
\ No newline at end of file