diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index e8f289d7..27cf74d6 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -1,3 +1,9 @@
{
+ "permissions": {
+ "allow": [
+ "Bash(cd:*)",
+ "Bash(python3:*)"
+ ]
+ },
"enableAllProjectMcpServers": false
}
\ No newline at end of file
diff --git a/crawl4ai/script/c4ai_script.py b/crawl4ai/script/c4ai_script.py
index f0691538..dfc9ba1c 100644
--- a/crawl4ai/script/c4ai_script.py
+++ b/crawl4ai/script/c4ai_script.py
@@ -133,8 +133,8 @@ start : line*
?line : command | proc_def | include | comment
command : wait | nav | click_cmd | double_click | right_click | move | drag | scroll
- | type | press | key_down | key_up
- | eval_cmd | set_var | proc_call | if_cmd | repeat_cmd
+ | type | clear | set_input | press | key_down | key_up
+ | eval_cmd | setvar | proc_call | if_cmd | repeat_cmd
wait : "WAIT" (ESCAPED_STRING|BACKTICK_STRING|NUMBER) NUMBER? -> wait_cmd
nav : "GO" URL -> go
@@ -151,12 +151,14 @@ drag : "DRAG" coords coords -> drag
scroll : "SCROLL" DIR NUMBER? -> scroll
type : "TYPE" (ESCAPED_STRING | NAME) -> type
+clear : "CLEAR" BACKTICK_STRING -> clear
+set_input : "SET" BACKTICK_STRING (ESCAPED_STRING | BACKTICK_STRING | NAME) -> set_input
press : "PRESS" WORD -> press
key_down : "KEY_DOWN" WORD -> key_down
key_up : "KEY_UP" WORD -> key_up
eval_cmd : "EVAL" BACKTICK_STRING -> eval_cmd
-set_var : "SET" NAME "=" value -> set_var
+setvar : "SETVAR" NAME "=" value -> setvar
proc_call : NAME -> proc_call
proc_def : "PROC" NAME line* "ENDPROC" -> proc_def
include : "USE" ESCAPED_STRING -> include
@@ -165,14 +167,15 @@ comment : /#.*/ -> comment
if_cmd : "IF" "(" condition ")" "THEN" command ("ELSE" command)? -> if_cmd
repeat_cmd : "REPEAT" "(" command "," repeat_count ")" -> repeat_cmd
-condition : exists_cond | js_cond
+condition : not_cond | exists_cond | js_cond
+not_cond : "NOT" condition -> not_cond
exists_cond : "EXISTS" BACKTICK_STRING -> exists_cond
js_cond : BACKTICK_STRING -> js_cond
repeat_count : NUMBER | BACKTICK_STRING
coords : NUMBER NUMBER
-value : ESCAPED_STRING | NUMBER
+value : ESCAPED_STRING | BACKTICK_STRING | NUMBER
DIR : /(UP|DOWN|LEFT|RIGHT)/i
REST : /[^\n]+/
@@ -270,13 +273,15 @@ class ASTBuilder(Transformer):
# KEYS
def type(self,tok): return Cmd("TYPE",[self._strip(str(tok))])
+ def clear(self,sel): return Cmd("CLEAR",[self._strip(str(sel))])
+ def set_input(self,sel,val): return Cmd("SET",[self._strip(str(sel)), self._strip(str(val))])
def press(self,w): return Cmd("PRESS",[str(w)])
def key_down(self,w): return Cmd("KEYDOWN",[str(w)])
def key_up(self,w): return Cmd("KEYUP",[str(w)])
# FLOW
def eval_cmd(self,txt): return Cmd("EVAL",[self._strip(str(txt))])
- def set_var(self,n,v):
+ def setvar(self,n,v):
# v might be a Token or a Tree, extract value properly
if hasattr(v, 'value'):
value = v.value
@@ -284,7 +289,7 @@ class ASTBuilder(Transformer):
value = v.children[0].value
else:
value = str(v)
- return Cmd("SET",[str(n), self._strip(value)])
+ return Cmd("SETVAR",[str(n), self._strip(value)])
def proc_call(self,n): return Cmd("CALL",[str(n)])
def proc_def(self,n,*body): return Proc(str(n),[b for b in body if isinstance(b,Cmd)])
def include(self,p): return Cmd("INCLUDE",[self._strip(p)])
@@ -297,6 +302,9 @@ class ASTBuilder(Transformer):
def condition(self, cond):
return cond
+ def not_cond(self, cond):
+ return ("NOT", cond)
+
def exists_cond(self, selector):
return ("EXISTS", self._strip(str(selector)))
@@ -366,19 +374,22 @@ class Compiler:
out=[]
for c in ir:
if isinstance(c,Cmd):
- if c.op=="SET": self.vars[c.args[0].lstrip('$')]=c.args[1]
+ if c.op=="SETVAR":
+ # Store variable
+ self.vars[c.args[0].lstrip('$')]=c.args[1]
else:
- if c.op in("TYPE","EVAL"): c.args=[sub(a) for a in c.args]
+ # Apply variable substitution to commands that use them
+ if c.op in("TYPE","EVAL","SET"): c.args=[sub(a) for a in c.args]
out.append(c)
return out
# JS emitter
def _emit_js(self, cmd: Cmd) -> str:
op, a = cmd.op, cmd.args
- if op == "GO": return f"location.href = '{a[0]}';"
- if op == "RELOAD": return "location.reload();"
- if op == "BACK": return "history.back();"
- if op == "FORWARD": return "history.forward();"
+ if op == "GO": return f"window.location.href = '{a[0]}';"
+ if op == "RELOAD": return "window.location.reload();"
+ if op == "BACK": return "window.history.back();"
+ if op == "FORWARD": return "window.history.forward();"
if op == "WAIT":
arg, kind = a[0]
@@ -411,13 +422,24 @@ class Compiler:
# click-style helpers
def _js_click(sel, evt="click", button=0, detail=1):
sel = sel.replace("'", "\\'")
- return f"document.querySelector('{sel}')?.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}}));"
+ return textwrap.dedent(f"""
+ (()=>{{
+ const el=document.querySelector('{sel}');
+ if(el){{
+ el.focus&&el.focus();
+ el.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}}));
+ }}
+ }})();
+ """).strip()
def _js_click_xy(x, y, evt="click", button=0, detail=1):
return textwrap.dedent(f"""
(()=>{{
const el=document.elementFromPoint({x},{y});
- el&&el.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}}));
+ if(el){{
+ el.focus&&el.focus();
+ el.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}}));
+ }}
}})();
""").strip()
@@ -463,23 +485,57 @@ class Compiler:
}})();
""").strip()
+ if op == "CLEAR":
+ sel = a[0].replace("'", "\\'")
+ return textwrap.dedent(f"""
+ (()=>{{
+ const el=document.querySelector('{sel}');
+ if(el && 'value' in el){{
+ el.value = '';
+ el.dispatchEvent(new Event('input',{{bubbles:true}}));
+ el.dispatchEvent(new Event('change',{{bubbles:true}}));
+ }}
+ }})();
+ """).strip()
+
+ if op == "SET" and len(a) == 2:
+ # This is SET for input fields (SET `#field` "value")
+ sel = a[0].replace("'", "\\'")
+ val = a[1].replace("'", "\\'")
+ return textwrap.dedent(f"""
+ (()=>{{
+ const el=document.querySelector('{sel}');
+ if(el && 'value' in el){{
+ el.value = '';
+ el.focus&&el.focus();
+ el.value = '{val}';
+ el.dispatchEvent(new Event('input',{{bubbles:true}}));
+ el.dispatchEvent(new Event('change',{{bubbles:true}}));
+ }}
+ }})();
+ """).strip()
+
if op in ("PRESS","KEYDOWN","KEYUP"):
key = a[0]
evs = {"PRESS":("keydown","keyup"),"KEYDOWN":("keydown",),"KEYUP":("keyup",)}[op]
return ";".join([f"document.dispatchEvent(new KeyboardEvent('{e}',{{key:'{key}',bubbles:true}}))" for e in evs]) + ";"
if op == "EVAL":
- return a[0]
+ return textwrap.dedent(f"""
+ (()=>{{
+ try {{
+ {a[0]};
+ }} catch (e) {{
+ console.error('C4A-Script EVAL error:', e);
+ }}
+ }})();
+ """).strip()
if op == "IF":
condition, then_cmd, else_cmd = a
- cond_type, cond_value = condition
# Generate condition JavaScript
- if cond_type == "EXISTS":
- js_condition = f"!!document.querySelector('{cond_value}')"
- else: # JS condition
- js_condition = cond_value
+ js_condition = self._emit_condition(condition)
# Generate commands - handle both regular commands and procedure calls
then_js = self._handle_cmd_or_proc(then_cmd)
@@ -531,6 +587,19 @@ class Compiler:
raise ValueError(f"Unhandled op {op}")
+ def _emit_condition(self, condition):
+ """Convert a condition tuple to JavaScript"""
+ cond_type = condition[0]
+
+ if cond_type == "EXISTS":
+ return f"!!document.querySelector('{condition[1]}')"
+ elif cond_type == "NOT":
+ # Recursively handle the negated condition
+ inner_condition = self._emit_condition(condition[1])
+ return f"!({inner_condition})"
+ else: # JS condition
+ return condition[1]
+
def _handle_cmd_or_proc(self, cmd):
"""Handle a command that might be a regular command or a procedure call"""
if not cmd:
@@ -596,15 +665,13 @@ def compile_lines(lines: List[str], *, root: Union[pathlib.Path, None] = None) -
DEMO = """
# quick sanity demo
PROC login
- CLICK `input[name="username"]`
- TYPE $user
- PRESS Tab
- TYPE $pass
+ SET `input[name="username"]` $user
+ SET `input[name="password"]` $pass
CLICK `button.submit`
ENDPROC
-SET user = "tom@crawl4ai.com"
-SET pass = "hunter2"
+SETVAR user = "tom@crawl4ai.com"
+SETVAR pass = "hunter2"
GO https://example.com/login
WAIT `input[name="username"]` 10
diff --git a/docs/examples/c4a_script/tutorial/README.md b/docs/examples/c4a_script/tutorial/README.md
new file mode 100644
index 00000000..088435af
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/README.md
@@ -0,0 +1,246 @@
+# C4A-Script Interactive Tutorial
+
+Welcome to the C4A-Script Interactive Tutorial! This hands-on tutorial teaches you how to write web automation scripts using C4A-Script, a domain-specific language for Crawl4AI.
+
+## 🚀 Quick Start
+
+### 1. Start the Tutorial Server
+
+```bash
+cd docs/examples/c4a_script/tutorial
+python server.py
+```
+
+Then open your browser to: http://localhost:8080
+
+### 2. Try Your First Script
+
+```c4a
+# Basic interaction
+GO playground/
+WAIT `body` 2
+IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+CLICK `#start-tutorial`
+```
+
+## 📚 What You'll Learn
+
+### Basic Commands
+- **Navigation**: `GO url`
+- **Waiting**: `WAIT selector timeout` or `WAIT seconds`
+- **Clicking**: `CLICK selector`
+- **Typing**: `TYPE "text"`
+- **Scrolling**: `SCROLL DOWN/UP amount`
+
+### Control Flow
+- **Conditionals**: `IF (condition) THEN action`
+- **Loops**: `REPEAT (action, condition)`
+- **Procedures**: Define reusable command sequences
+
+### Advanced Features
+- **JavaScript evaluation**: `EVAL code`
+- **Variables**: `SET name = "value"`
+- **Complex selectors**: CSS selectors in backticks
+
+## 🎮 Interactive Playground Features
+
+The tutorial includes a fully interactive web app with:
+
+### 1. **Authentication System**
+- Login form with validation
+- Session management
+- Protected content
+
+### 2. **Dynamic Content**
+- Infinite scroll products
+- Pagination controls
+- Load more buttons
+
+### 3. **Complex Forms**
+- Multi-step wizards
+- Dynamic field visibility
+- Form validation
+
+### 4. **Interactive Elements**
+- Tabs and accordions
+- Modals and popups
+- Expandable content
+
+### 5. **Data Tables**
+- Sortable columns
+- Search functionality
+- Export options
+
+## 🛠️ Tutorial Features
+
+### Live Code Editor
+- Syntax highlighting
+- Real-time compilation
+- Error messages with suggestions
+
+### JavaScript Output Viewer
+- See generated JavaScript code
+- Edit and test JS directly
+- Understand the compilation
+
+### Visual Execution
+- Step-by-step progress
+- Element highlighting
+- Console output
+
+### Example Scripts
+Load pre-written examples demonstrating:
+- Cookie banner handling
+- Login workflows
+- Infinite scroll automation
+- Multi-step form completion
+- Complex interaction sequences
+
+## 📖 Tutorial Sections
+
+### 1. Getting Started
+Learn basic commands and syntax:
+```c4a
+GO https://example.com
+WAIT `.content` 5
+CLICK `.button`
+```
+
+### 2. Handling Dynamic Content
+Master waiting strategies and conditionals:
+```c4a
+IF (EXISTS `.popup`) THEN CLICK `.close`
+WAIT `.results` 10
+```
+
+### 3. Form Automation
+Fill and submit forms:
+```c4a
+CLICK `#email`
+TYPE "user@example.com"
+CLICK `button[type="submit"]`
+```
+
+### 4. Advanced Workflows
+Build complex automation flows:
+```c4a
+PROC login
+ CLICK `#username`
+ TYPE $username
+ CLICK `#password`
+ TYPE $password
+ CLICK `#login-btn`
+ENDPROC
+
+SET username = "demo"
+SET password = "pass123"
+login
+```
+
+## 🎯 Practice Challenges
+
+### Challenge 1: Cookie & Popups
+Handle the cookie banner and newsletter popup that appear on page load.
+
+### Challenge 2: Complete Login
+Successfully log into the application using the demo credentials.
+
+### Challenge 3: Load All Products
+Use infinite scroll to load all 100 products in the catalog.
+
+### Challenge 4: Multi-step Survey
+Complete the entire multi-step survey form.
+
+### Challenge 5: Full Workflow
+Create a script that logs in, browses products, and exports data.
+
+## 💡 Tips & Tricks
+
+### 1. Use Specific Selectors
+```c4a
+# Good - specific
+CLICK `button.submit-order`
+
+# Bad - too generic
+CLICK `button`
+```
+
+### 2. Always Handle Popups
+```c4a
+# Check for common popups
+IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+IF (EXISTS `.newsletter-modal`) THEN CLICK `.close`
+```
+
+### 3. Add Appropriate Waits
+```c4a
+# Wait for elements before interacting
+WAIT `.form` 5
+CLICK `#submit`
+```
+
+### 4. Use Procedures for Reusability
+```c4a
+PROC handle_popups
+ IF (EXISTS `.popup`) THEN CLICK `.close`
+ IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+ENDPROC
+
+# Use anywhere
+handle_popups
+```
+
+## 🔧 Troubleshooting
+
+### Common Issues
+
+1. **"Element not found"**
+ - Add a WAIT before clicking
+ - Check selector specificity
+ - Verify element exists with IF
+
+2. **"Timeout waiting for selector"**
+ - Increase timeout value
+ - Check if element is dynamically loaded
+ - Verify selector is correct
+
+3. **"Missing THEN keyword"**
+ - All IF statements need THEN
+ - Format: `IF (condition) THEN action`
+
+## 🚀 Using with Crawl4AI
+
+Once you've mastered C4A-Script in the tutorial, use it with Crawl4AI:
+
+```python
+from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
+
+config = CrawlerRunConfig(
+ url="https://example.com",
+ c4a_script="""
+ WAIT `.content` 5
+ IF (EXISTS `.load-more`) THEN CLICK `.load-more`
+ WAIT `.new-content` 3
+ """
+)
+
+async with AsyncWebCrawler() as crawler:
+ result = await crawler.arun(config=config)
+```
+
+## 📝 Example Scripts
+
+Check the `scripts/` folder for complete examples:
+- `01-basic-interaction.c4a` - Getting started
+- `02-login-flow.c4a` - Authentication
+- `03-infinite-scroll.c4a` - Dynamic content
+- `04-multi-step-form.c4a` - Complex forms
+- `05-complex-workflow.c4a` - Full automation
+
+## 🤝 Contributing
+
+Found a bug or have a suggestion? Please open an issue on GitHub!
+
+---
+
+Happy automating with C4A-Script! 🎉
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/assets/app.css b/docs/examples/c4a_script/tutorial/assets/app.css
new file mode 100644
index 00000000..22d7589e
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/assets/app.css
@@ -0,0 +1,667 @@
+/* ================================================================
+ C4A-Script Tutorial - App Layout CSS
+ Terminal theme with Dank Mono font
+ ================================================================ */
+
+/* CSS Variables */
+:root {
+ --bg-primary: #070708;
+ --bg-secondary: #0e0e10;
+ --bg-tertiary: #1a1a1b;
+ --border-color: #2a2a2c;
+ --border-hover: #3a3a3c;
+ --text-primary: #e0e0e0;
+ --text-secondary: #8b8b8d;
+ --text-muted: #606065;
+ --primary-color: #0fbbaa;
+ --primary-hover: #0da89a;
+ --primary-dim: #0a8577;
+ --error-color: #ff5555;
+ --warning-color: #ffb86c;
+ --success-color: #50fa7b;
+ --info-color: #8be9fd;
+ --code-bg: #1e1e20;
+ --modal-overlay: rgba(0, 0, 0, 0.8);
+}
+
+/* Base Reset */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+/* Fonts */
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+}
+
+/* Body & App Container */
+body {
+ font-family: 'Dank Mono', 'Monaco', 'Consolas', monospace;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+}
+
+.app-container {
+ display: flex;
+ height: 100vh;
+ width: 100vw;
+ overflow: hidden;
+}
+
+/* Panels */
+.editor-panel,
+.playground-panel {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: hidden;
+}
+
+.editor-panel {
+ flex: 1;
+ background: var(--bg-secondary);
+ border-right: 1px solid var(--border-color);
+ min-width: 400px;
+}
+
+.playground-panel {
+ flex: 1;
+ background: var(--bg-primary);
+ min-width: 400px;
+}
+
+/* Panel Headers */
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+.panel-header h2 {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--primary-color);
+ margin: 0;
+}
+
+.header-actions {
+ display: flex;
+ gap: 8px;
+}
+
+/* Action Buttons */
+.action-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-family: inherit;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.action-btn:hover {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+ border-color: var(--border-hover);
+}
+
+.action-btn.primary {
+ background: var(--primary-color);
+ color: var(--bg-primary);
+ border-color: var(--primary-color);
+}
+
+.action-btn.primary:hover {
+ background: var(--primary-hover);
+ border-color: var(--primary-hover);
+}
+
+.action-btn .icon {
+ font-size: 16px;
+}
+
+/* Editor Wrapper */
+.editor-wrapper {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+ position: relative;
+ z-index: 1; /* Ensure it's above any potential overlays */
+}
+
+.editor-wrapper .CodeMirror {
+ flex: 1;
+ height: 100%;
+ font-family: 'Dank Mono', monospace;
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+/* Ensure CodeMirror is interactive */
+.CodeMirror {
+ background: var(--bg-primary) !important;
+}
+
+.CodeMirror-scroll {
+ overflow: auto !important;
+}
+
+/* Make cursor more visible */
+.CodeMirror-cursor {
+ border-left: 2px solid var(--primary-color) !important;
+ border-left-width: 2px !important;
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
+/* Ensure cursor is visible when focused */
+.CodeMirror-focused .CodeMirror-cursor {
+ visibility: visible !important;
+}
+
+/* Fix for CodeMirror in flex container */
+.CodeMirror-sizer {
+ min-height: auto !important;
+}
+
+/* Remove aggressive pointer-events override */
+.CodeMirror-code {
+ cursor: text;
+}
+
+.editor-wrapper textarea {
+ display: none;
+}
+
+/* Output Section (Bottom of Editor) */
+.output-section {
+ height: 250px;
+ border-top: 1px solid var(--border-color);
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+}
+
+/* Tabs */
+.tabs {
+ display: flex;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+ flex-shrink: 0;
+}
+
+.tab {
+ padding: 8px 20px;
+ background: transparent;
+ color: var(--text-secondary);
+ border: none;
+ border-bottom: 2px solid transparent;
+ font-family: inherit;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.tab:hover {
+ color: var(--text-primary);
+ background: var(--bg-secondary);
+}
+
+.tab.active {
+ color: var(--primary-color);
+ border-bottom-color: var(--primary-color);
+}
+
+/* Tab Content */
+.tab-content {
+ flex: 1;
+ overflow: hidden;
+}
+
+.tab-pane {
+ display: none;
+ height: 100%;
+ overflow-y: auto;
+}
+
+.tab-pane.active {
+ display: block;
+}
+
+/* Console */
+.console {
+ padding: 12px;
+ background: var(--bg-primary);
+ font-size: 13px;
+ min-height: 100%;
+}
+
+.console-line {
+ margin-bottom: 8px;
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.console-prompt {
+ color: var(--primary-color);
+ flex-shrink: 0;
+}
+
+.console-text {
+ color: var(--text-primary);
+}
+
+.console-error {
+ color: var(--error-color);
+}
+
+.console-warning {
+ color: var(--warning-color);
+}
+
+.console-success {
+ color: var(--success-color);
+}
+
+/* JavaScript Output */
+.js-output-header {
+ display: flex;
+ justify-content: flex-end;
+ padding: 8px 12px;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.js-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.mini-btn {
+ padding: 4px 8px;
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 3px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.mini-btn:hover {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+}
+
+.js-output {
+ padding: 12px;
+ background: var(--code-bg);
+ color: var(--text-primary);
+ font-family: 'Dank Mono', monospace;
+ font-size: 13px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ margin: 0;
+ min-height: calc(100% - 44px);
+}
+
+/* Execution Progress */
+.execution-progress {
+ padding: 12px;
+ background: var(--bg-primary);
+}
+
+.progress-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ font-size: 13px;
+}
+
+.progress-icon {
+ color: var(--text-muted);
+}
+
+.progress-item.active .progress-icon {
+ color: var(--info-color);
+ animation: pulse 1s infinite;
+}
+
+.progress-item.completed .progress-icon {
+ color: var(--success-color);
+}
+
+.progress-item.error .progress-icon {
+ color: var(--error-color);
+}
+
+/* Playground */
+.playground-wrapper {
+ flex: 1;
+ overflow: hidden;
+}
+
+#playground-frame {
+ width: 100%;
+ height: 100%;
+ border: none;
+ background: var(--bg-secondary);
+}
+
+/* Tutorial Intro Modal */
+.tutorial-intro-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: var(--modal-overlay);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 2000;
+ transition: opacity 0.3s;
+}
+
+.tutorial-intro-modal.hidden {
+ display: none;
+}
+
+.intro-content {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 32px;
+ max-width: 500px;
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
+}
+
+.intro-content h2 {
+ color: var(--primary-color);
+ margin-bottom: 16px;
+ font-size: 24px;
+}
+
+.intro-content p {
+ color: var(--text-primary);
+ margin-bottom: 16px;
+ line-height: 1.6;
+}
+
+.intro-content ul {
+ list-style: none;
+ margin-bottom: 24px;
+}
+
+.intro-content li {
+ color: var(--text-secondary);
+ margin-bottom: 8px;
+ padding-left: 20px;
+ position: relative;
+}
+
+.intro-content li:before {
+ content: "▸";
+ position: absolute;
+ left: 0;
+ color: var(--primary-color);
+}
+
+.intro-actions {
+ display: flex;
+ gap: 12px;
+ justify-content: flex-end;
+}
+
+.intro-btn {
+ padding: 10px 24px;
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-family: inherit;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.intro-btn:hover {
+ background: var(--bg-primary);
+ border-color: var(--border-hover);
+}
+
+.intro-btn.primary {
+ background: var(--primary-color);
+ color: var(--bg-primary);
+ border-color: var(--primary-color);
+}
+
+.intro-btn.primary:hover {
+ background: var(--primary-hover);
+ border-color: var(--primary-hover);
+}
+
+/* Tutorial Navigation Bar */
+.tutorial-nav {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--primary-color);
+ z-index: 1000;
+ transition: transform 0.3s;
+}
+
+.tutorial-nav.hidden {
+ transform: translateY(-100%);
+}
+
+.tutorial-nav-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+}
+
+.tutorial-left {
+ flex: 1;
+}
+
+.tutorial-step-title {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 8px;
+}
+
+.tutorial-step-title span:first-child {
+ color: var(--text-secondary);
+ font-size: 12px;
+ text-transform: uppercase;
+}
+
+.tutorial-step-title span:last-child {
+ color: var(--primary-color);
+ font-weight: 600;
+ font-size: 16px;
+}
+
+.tutorial-description {
+ color: var(--text-primary);
+ margin: 0;
+ font-size: 14px;
+ max-width: 600px;
+}
+
+.tutorial-right {
+ display: flex;
+ align-items: center;
+}
+
+.tutorial-progress-bar {
+ height: 3px;
+ background: var(--bg-secondary);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+
+.tutorial-progress-bar .progress-fill {
+ height: 100%;
+ background: var(--primary-color);
+ transition: width 0.3s;
+}
+
+/* Adjust app container when tutorial is active */
+.app-container.tutorial-active {
+ padding-top: 80px;
+}
+
+.tutorial-controls {
+ display: flex;
+ gap: 12px;
+}
+
+.nav-btn {
+ padding: 8px 16px;
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-family: inherit;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.nav-btn:hover:not(:disabled) {
+ background: var(--bg-primary);
+ border-color: var(--border-hover);
+}
+
+.nav-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.nav-btn.primary {
+ background: var(--primary-color);
+ color: var(--bg-primary);
+ border-color: var(--primary-color);
+}
+
+.nav-btn.primary:hover {
+ background: var(--primary-hover);
+ border-color: var(--primary-hover);
+}
+
+.exit-btn {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ color: var(--text-secondary);
+ border: none;
+ font-size: 20px;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s;
+ margin-left: 16px;
+}
+
+.exit-btn:hover {
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+}
+
+/* Fullscreen Mode */
+.playground-panel.fullscreen {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1500;
+}
+
+/* Animations */
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+/* Scrollbar Styling */
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--bg-secondary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border-color);
+ border-radius: 5px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--border-hover);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .app-container {
+ flex-direction: column;
+ }
+
+ .editor-panel,
+ .playground-panel {
+ min-width: auto;
+ width: 100%;
+ }
+
+ .editor-panel {
+ border-right: none;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .output-section {
+ height: 200px;
+ }
+}
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/assets/app.js b/docs/examples/c4a_script/tutorial/assets/app.js
new file mode 100644
index 00000000..a54f3342
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/assets/app.js
@@ -0,0 +1,625 @@
+// C4A-Script Tutorial App Controller
+class TutorialApp {
+ constructor() {
+ this.editor = null;
+ this.currentScript = '';
+ this.currentJS = [];
+ this.isEditingJS = false;
+ this.tutorialMode = false;
+ this.currentStep = 0;
+ this.tutorialSteps = [];
+
+ this.init();
+ }
+
+ init() {
+ this.setupEditors();
+ this.setupButtons();
+ this.setupTabs();
+ this.setupTutorial();
+ this.checkFirstVisit();
+ }
+
+ setupEditors() {
+ // C4A Script Editor
+ const c4aTextarea = document.getElementById('c4a-editor');
+ this.editor = CodeMirror.fromTextArea(c4aTextarea, {
+ mode: 'javascript', // Use JS mode for now
+ theme: 'material-darker',
+ lineNumbers: true,
+ lineWrapping: true,
+ autoCloseBrackets: true,
+ matchBrackets: true,
+ readOnly: false,
+ cursorBlinkRate: 530,
+ inputStyle: 'contenteditable' // Changed from 'textarea' to prevent cursor issues
+ });
+
+ // Save script on change
+ this.editor.on('change', () => {
+ this.currentScript = this.editor.getValue();
+ localStorage.setItem('c4a-script', this.currentScript);
+ });
+
+ // Load saved script
+ const saved = localStorage.getItem('c4a-script');
+ if (saved && !this.tutorialMode) {
+ this.editor.setValue(saved);
+ }
+
+ // Ensure editor is properly sized and interactive
+ setTimeout(() => {
+ this.editor.refresh();
+ // Set cursor position instead of just focusing
+ const doc = this.editor.getDoc();
+ doc.setCursor(doc.lineCount() - 1, 0);
+ this.editor.focus();
+ }, 100);
+
+ // Single unified click handler for focus
+ const editorElement = this.editor.getWrapperElement();
+ editorElement.addEventListener('mousedown', (e) => {
+ // Use mousedown instead of click for immediate response
+ e.stopPropagation();
+ // Ensure editor gets focus on next tick
+ setTimeout(() => {
+ if (!this.editor.hasFocus()) {
+ this.editor.focus();
+ }
+ }, 0);
+ });
+ }
+
+ setupButtons() {
+ // Run button
+ document.getElementById('run-btn').addEventListener('click', () => {
+ this.runScript();
+ });
+
+ // Clear button
+ document.getElementById('clear-btn').addEventListener('click', () => {
+ this.editor.setValue('');
+ this.clearConsole();
+ });
+
+ // Examples button
+ document.getElementById('examples-btn').addEventListener('click', () => {
+ this.showExamples();
+ });
+
+ // Tutorial button
+ document.getElementById('tutorial-btn').addEventListener('click', () => {
+ this.showIntroModal();
+ });
+
+ // Copy JS button
+ document.getElementById('copy-js-btn').addEventListener('click', () => {
+ this.copyJS();
+ });
+
+ // Edit JS button
+ document.getElementById('edit-js-btn').addEventListener('click', () => {
+ this.toggleJSEdit();
+ });
+
+ // Reset playground
+ document.getElementById('reset-playground').addEventListener('click', () => {
+ this.resetPlayground();
+ });
+
+ // Fullscreen
+ document.getElementById('fullscreen-btn').addEventListener('click', () => {
+ this.toggleFullscreen();
+ });
+
+ // Intro modal buttons
+ document.getElementById('start-tutorial-btn').addEventListener('click', () => {
+ this.hideIntroModal();
+ this.startTutorial();
+ });
+
+ document.getElementById('skip-tutorial-btn').addEventListener('click', () => {
+ this.hideIntroModal();
+ });
+
+ // Tutorial navigation
+ document.getElementById('tutorial-prev').addEventListener('click', () => {
+ this.prevStep();
+ });
+
+ document.getElementById('tutorial-next').addEventListener('click', () => {
+ this.nextStep();
+ });
+
+ document.getElementById('tutorial-exit').addEventListener('click', () => {
+ this.exitTutorial();
+ });
+ }
+
+ setupTabs() {
+ const tabs = document.querySelectorAll('.tab');
+ tabs.forEach(tab => {
+ tab.addEventListener('click', () => {
+ const tabName = tab.getAttribute('data-tab');
+ this.switchTab(tabName);
+ });
+ });
+
+ // Remove execution tab since we're removing it
+ const progressTab = document.querySelector('[data-tab="progress"]');
+ if (progressTab) progressTab.remove();
+ }
+
+ switchTab(tabName) {
+ // Update tab buttons
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
+
+ // Update tab panes
+ document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
+ document.getElementById(`${tabName}-tab`).classList.add('active');
+ }
+
+ checkFirstVisit() {
+ const hasVisited = localStorage.getItem('c4a-tutorial-visited');
+ if (!hasVisited) {
+ setTimeout(() => this.showIntroModal(), 500);
+ }
+ }
+
+ showIntroModal() {
+ document.getElementById('tutorial-intro').classList.remove('hidden');
+ }
+
+ hideIntroModal() {
+ document.getElementById('tutorial-intro').classList.add('hidden');
+ localStorage.setItem('c4a-tutorial-visited', 'true');
+ }
+
+ setupTutorial() {
+ this.tutorialSteps = [
+ {
+ title: "Welcome to C4A-Script!",
+ description: "C4A-Script is a simple language for web automation. Let's start by handling popups that appear on websites.",
+ script: "# Welcome to C4A-Script Tutorial!\n# Let's start by waiting for the page to load\n\nWAIT `body` 2",
+ validate: () => true
+ },
+ {
+ title: "Handle Cookie Banner",
+ description: "Check if cookie banner exists and accept it.",
+ script: "# Wait for page and handle cookie banner\nWAIT `body` 2\n\n# Check if cookie banner exists, then click accept\nIF (EXISTS `.cookie-banner`) THEN CLICK `.accept`",
+ validate: () => {
+ const iframe = document.getElementById('playground-frame');
+ return !iframe.contentDocument?.querySelector('.cookie-banner');
+ }
+ },
+ {
+ title: "Handle Newsletter Popup",
+ description: "Now let's handle the newsletter popup that appears after 3 seconds.",
+ script: "# Wait for newsletter popup to appear\nWAIT 3\n\n# Close the newsletter popup\nIF (EXISTS `#newsletter-popup`) THEN CLICK `.close`",
+ validate: () => {
+ const iframe = document.getElementById('playground-frame');
+ return !iframe.contentDocument?.querySelector('#newsletter-popup');
+ }
+ },
+ {
+ title: "Start Interactive Elements",
+ description: "Click the start button to reveal more interactive elements.",
+ script: "# Click the start tutorial button\nCLICK `#start-tutorial`",
+ validate: () => true
+ },
+ {
+ title: "Login Process",
+ description: "Now let's complete the login form with email and password using the new SET command.",
+ script: "# Login process\nCLICK `#login-btn`\nWAIT `.login-form` 2\n\n# Fill form fields using SET\nSET `#email` \"demo@example.com\"\nSET `#password` \"demo123\"\n\n# Submit the form\nCLICK `button[type=\"submit\"]`\nWAIT `.success` 2",
+ validate: () => {
+ const iframe = document.getElementById('playground-frame');
+ return iframe.contentDocument?.querySelector('.user-info')?.style.display === 'flex';
+ }
+ },
+ {
+ title: "Navigate and Scroll",
+ description: "Navigate to the catalog and use scrolling to load more products.",
+ script: "# Navigate to catalog\nCLICK `#catalog-link`\nWAIT `.product-grid` 3\n\n# Scroll to load more products\nSCROLL DOWN 500\nWAIT 1\nSCROLL DOWN 500\nWAIT 1\n\n# Apply a filter\nCLICK `.filter-group input[type=\"checkbox\"]`",
+ validate: () => true
+ },
+ {
+ title: "Advanced - Procedures",
+ description: "Create reusable command groups with PROC for common tasks.",
+ script: "# Define a procedure to check login status\nPROC check_login\n IF (NOT EXISTS `.user-info`) THEN CLICK `#login-btn`\n IF (NOT EXISTS `.user-info`) THEN WAIT `.login-form` 2\n IF (NOT EXISTS `.user-info`) THEN SET `#email` \"demo@example.com\"\n IF (NOT EXISTS `.user-info`) THEN SET `#password` \"demo123\"\n IF (NOT EXISTS `.user-info`) THEN CLICK `button[type=\"submit\"]`\nENDPROC\n\n# Example: Navigate between sections\nCLICK `a[href=\"#tabs\"]`\nWAIT 1\nCLICK `a[href=\"#forms\"]`\nWAIT 1\n\n# If we get logged out, use our procedure\ncheck_login",
+ validate: () => true
+ },
+ {
+ title: "More Commands",
+ description: "Explore additional C4A commands with the Forms section.",
+ script: "# First, let's fill the contact form with variables\n\n# Set variables\nSETVAR name = \"Alice Smith\"\nSETVAR msg = \"I'd like to know more about your Premium plan!\"\n\n# Fill contact form\nSET `#contact-name` $name\nSET `#contact-email` \"alice@example.com\"\nSET `#contact-message` $msg\n\n# Select dropdown option\nCLICK `#contact-subject`\nCLICK `option[value=\"support\"]`\nWAIT 0.5\n\n# Submit contact form\nCLICK `.btn-primary`\nWAIT 1\n\n# Now let's do the multi-step survey\nSCROLL DOWN 400\nWAIT 0.5\n\n# Step 1: Personal info\nSET `#full-name` \"Bob Johnson\"\nSET `#survey-email` \"bob@example.com\"\nCLICK `.next-step`\nWAIT 0.5\n\n# Step 2: Select interests (multi-select)\nCLICK `#interests`\nCLICK `option[value=\"technology\"]`\nCLICK `option[value=\"science\"]`\nCLICK `.next-step`\nWAIT 0.5\n\n# Step 3: Submit survey\nCLICK `#submit-survey`\n\n# Check results with JavaScript\nEVAL `console.log('Forms completed successfully!')`",
+ validate: () => true
+ },
+ {
+ title: "Congratulations!",
+ description: "You've mastered C4A-Script basics! You can now automate complex web interactions. Try the examples or create your own scripts.",
+ script: "# 🎉 You've completed the tutorial!\n\n# You learned:\n# ✓ WAIT - Wait for elements or time\n# ✓ IF/THEN - Conditional actions\n# ✓ NOT - Negate conditions\n# ✓ CLICK - Click elements\n# ✓ SET - Set input field values\n# ✓ SETVAR - Create variables\n# ✓ SCROLL - Scroll the page\n# ✓ PROC - Create procedures\n# ✓ And much more!\n\n# Try the Examples button for more scripts\n# Happy automating with C4A-Script!",
+ validate: () => true
+ }
+ ];
+ }
+
+ startTutorial() {
+ this.tutorialMode = true;
+ this.currentStep = 0;
+ document.getElementById('tutorial-nav').classList.remove('hidden');
+ document.querySelector('.app-container').classList.add('tutorial-active');
+ this.showStep(0);
+ }
+
+ exitTutorial() {
+ this.tutorialMode = false;
+ document.getElementById('tutorial-nav').classList.add('hidden');
+ document.querySelector('.app-container').classList.remove('tutorial-active');
+ }
+
+ showStep(index) {
+ const step = this.tutorialSteps[index];
+ if (!step) return;
+
+ // Update navigation UI
+ document.getElementById('tutorial-step-info').textContent = `Step ${index + 1} of ${this.tutorialSteps.length}`;
+ document.getElementById('tutorial-title').textContent = step.title;
+
+ // Update progress bar
+ const progress = ((index + 1) / this.tutorialSteps.length) * 100;
+ document.getElementById('tutorial-progress-fill').style.width = `${progress}%`;
+
+ // Update buttons
+ document.getElementById('tutorial-prev').disabled = index === 0;
+ const nextBtn = document.getElementById('tutorial-next');
+ if (index === this.tutorialSteps.length - 1) {
+ nextBtn.textContent = 'Finish';
+ } else {
+ nextBtn.textContent = 'Next →';
+ }
+
+ // Set script
+ this.editor.setValue(step.script);
+
+ // Update description in nav bar
+ document.getElementById('tutorial-description').textContent = step.description;
+
+ // Focus editor after setting content and ensure it's editable
+ // Use requestAnimationFrame for better timing
+ requestAnimationFrame(() => {
+ this.editor.setOption('readOnly', false);
+ this.editor.refresh();
+ // Place cursor at end first, then focus
+ const doc = this.editor.getDoc();
+ const lastLine = doc.lineCount() - 1;
+ const lastCh = doc.getLine(lastLine).length;
+ doc.setCursor(lastLine, lastCh);
+ this.editor.focus();
+ });
+ }
+
+ // Removed showTooltip method - no longer needed
+
+ nextStep() {
+ if (this.currentStep < this.tutorialSteps.length - 1) {
+ this.currentStep++;
+ this.showStep(this.currentStep);
+ } else {
+ this.exitTutorial();
+ }
+ }
+
+ prevStep() {
+ if (this.currentStep > 0) {
+ this.currentStep--;
+ this.showStep(this.currentStep);
+ }
+ }
+
+ async runScript() {
+ const script = this.editor.getValue();
+ if (!script.trim()) {
+ this.addConsoleMessage('No script to run', 'error');
+ return;
+ }
+
+ this.clearConsole();
+ this.switchTab('console');
+ this.addConsoleMessage('Compiling C4A script...');
+
+ try {
+ // If in JS edit mode, use the edited JS directly
+ if (this.isEditingJS) {
+ const jsCode = document.getElementById('js-output').textContent.split('\n').filter(line => line.trim());
+ await this.executeJS(jsCode);
+ return;
+ }
+
+ // Compile C4A to JS
+ const compiled = await this.compileScript(script);
+
+ if (compiled.success) {
+ this.currentJS = compiled.jsCode;
+ this.displayJS(compiled.jsCode);
+ this.addConsoleMessage(`✓ Compiled successfully (${compiled.jsCode.length} statements)`, 'success');
+
+ // Execute the JS
+ await this.executeJS(compiled.jsCode);
+ } else {
+ // Show compilation error
+ const error = compiled.error;
+ this.addConsoleMessage(`✗ Compilation error at line ${error.line}:${error.column}`, 'error');
+ this.addConsoleMessage(` ${error.message}`, 'error');
+ if (error.suggestion) {
+ this.addConsoleMessage(` 💡 ${error.suggestion}`, 'warning');
+ }
+ }
+ } catch (error) {
+ this.addConsoleMessage(`Error: ${error.message}`, 'error');
+ }
+ }
+
+ async compileScript(script) {
+ try {
+ const response = await fetch('/api/compile', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ script })
+ });
+
+ if (!response.ok) {
+ throw new Error('Compilation service unavailable');
+ }
+
+ return await response.json();
+ } catch (error) {
+ // Return error if compilation service is unavailable
+ return {
+ success: false,
+ error: {
+ line: 1,
+ column: 1,
+ message: "C4A compilation service unavailable. Please ensure the server is running.",
+ suggestion: "Start the C4A server or check your connection"
+ }
+ };
+ }
+ }
+
+ displayJS(jsCode) {
+ const formatted = jsCode.map((line, i) =>
+ `${(i + 1).toString().padStart(2, ' ')}. ${line}`
+ ).join('\n');
+
+ document.getElementById('js-output').textContent = formatted;
+ }
+
+ async executeJS(jsCode) {
+ this.addConsoleMessage('Executing JavaScript...');
+
+ // Send all code to iframe to execute at once
+ await this.executeInIframe(jsCode);
+ }
+
+ async executeInIframe(jsCode) {
+ const iframe = document.getElementById('playground-frame');
+ const iframeWindow = iframe.contentWindow;
+
+ // Create a unique ID for this execution
+ const executionId = 'exec_' + Date.now();
+
+ // Create the full script to execute in iframe
+ const fullScript = `
+ (async () => {
+ const results = [];
+ try {
+ ${jsCode.map((code, i) => `
+ try {
+ ${code}
+ results.push({ index: ${i}, success: true, code: ${JSON.stringify(code)} });
+ } catch (error) {
+ results.push({ index: ${i}, success: false, error: error.message, code: ${JSON.stringify(code)} });
+ throw error; // Stop execution on first error
+ }
+ `).join('\n')}
+
+ // Send success message
+ window.parent.postMessage({
+ type: 'c4a-execution-complete',
+ id: '${executionId}',
+ success: true,
+ results: results
+ }, '*');
+ } catch (error) {
+ // Send error message
+ window.parent.postMessage({
+ type: 'c4a-execution-complete',
+ id: '${executionId}',
+ success: false,
+ error: error.message,
+ results: results
+ }, '*');
+ }
+ })();
+ `;
+
+ // Wait for execution result
+ return new Promise((resolve, reject) => {
+ const messageHandler = (event) => {
+ if (event.data.type === 'c4a-execution-complete' && event.data.id === executionId) {
+ window.removeEventListener('message', messageHandler);
+
+ // Log results
+ if (event.data.results) {
+ event.data.results.forEach(result => {
+ if (result.success) {
+ this.addConsoleMessage(`✓ ${result.code}`, 'success');
+ } else {
+ this.addConsoleMessage(`✗ ${result.code}`, 'error');
+ this.addConsoleMessage(` Error: ${result.error}`, 'error');
+ }
+ });
+ }
+
+ if (event.data.success) {
+ this.addConsoleMessage('Execution completed successfully', 'success');
+ resolve();
+ } else {
+ this.addConsoleMessage('Execution stopped due to error', 'error');
+ resolve(); // Still resolve to not break the flow
+ }
+ }
+ };
+
+ window.addEventListener('message', messageHandler);
+
+ // Inject and execute the script
+ const script = iframe.contentDocument.createElement('script');
+ script.textContent = fullScript;
+ iframe.contentDocument.body.appendChild(script);
+ script.remove();
+
+ // Timeout after 10 seconds
+ setTimeout(() => {
+ window.removeEventListener('message', messageHandler);
+ this.addConsoleMessage('Execution timeout', 'warning');
+ resolve();
+ }, 10000);
+ });
+ }
+
+ highlightElement(element) {
+ const originalBorder = element.style.border;
+ const originalBackground = element.style.backgroundColor;
+
+ element.style.border = '2px solid #0fbbaa';
+ element.style.backgroundColor = 'rgba(15, 187, 170, 0.1)';
+
+ setTimeout(() => {
+ element.style.border = originalBorder;
+ element.style.backgroundColor = originalBackground;
+ }, 1000);
+ }
+
+ // Removed progress methods - everything goes to console now
+
+ addConsoleMessage(message, type = 'text') {
+ const consoleEl = document.getElementById('console-output');
+ const line = document.createElement('div');
+ line.className = 'console-line';
+ line.innerHTML = `
+ $
+ ${message}
+ `;
+ consoleEl.appendChild(line);
+
+ // Scroll the console container (parent of console-output)
+ const consoleContainer = consoleEl.parentElement;
+ if (consoleContainer) {
+ // Use requestAnimationFrame to ensure DOM has updated
+ requestAnimationFrame(() => {
+ consoleContainer.scrollTop = consoleContainer.scrollHeight;
+ });
+ }
+ }
+
+ clearConsole() {
+ document.getElementById('console-output').innerHTML = `
+
+ $
+ Ready to run C4A scripts...
+
+ `;
+ }
+
+ copyJS() {
+ const text = this.currentJS.join('\n');
+ navigator.clipboard.writeText(text).then(() => {
+ this.addConsoleMessage('JavaScript copied to clipboard', 'success');
+ });
+ }
+
+ toggleJSEdit() {
+ this.isEditingJS = !this.isEditingJS;
+ const editBtn = document.getElementById('edit-js-btn');
+ const jsOutput = document.getElementById('js-output');
+
+ if (this.isEditingJS) {
+ editBtn.innerHTML = '💾';
+ jsOutput.contentEditable = true;
+ jsOutput.style.outline = '1px solid #0fbbaa';
+ this.addConsoleMessage('JS edit mode enabled - modify and run', 'warning');
+ } else {
+ editBtn.innerHTML = '✏️';
+ jsOutput.contentEditable = false;
+ jsOutput.style.outline = 'none';
+ this.addConsoleMessage('JS edit mode disabled', 'text');
+ }
+ }
+
+ resetPlayground() {
+ const iframe = document.getElementById('playground-frame');
+ iframe.src = iframe.src;
+ this.addConsoleMessage('Playground reset', 'success');
+ }
+
+ toggleFullscreen() {
+ const playgroundPanel = document.querySelector('.playground-panel');
+ playgroundPanel.classList.toggle('fullscreen');
+ }
+
+ showExamples() {
+ const examples = [
+ {
+ name: 'Quick Start - Handle Popups',
+ script: `# Handle popups quickly\nWAIT \`body\` 2\nIF (EXISTS \`.cookie-banner\`) THEN CLICK \`.accept\`\nWAIT 3\nIF (EXISTS \`#newsletter-popup\`) THEN CLICK \`.close\``
+ },
+ {
+ name: 'Complete Login Flow',
+ script: `# Full login process\nCLICK \`#login-btn\`\nWAIT \`.login-form\` 2\n\n# Set credentials using the new SET command\nSET \`#email\` "demo@example.com"\nSET \`#password\` "demo123"\n\n# Submit\nCLICK \`button[type="submit"]\`\nWAIT \`.success\` 2`
+ },
+ {
+ name: 'Product Browsing',
+ script: `# Browse products with filters\nCLICK \`#catalog-link\`\nWAIT \`.product-grid\` 3\n\n# Apply filters\nCLICK \`.filter-button\`\nWAIT 0.5\nCLICK \`input[value="electronics"]\`\n\n# Load more products\nREPEAT (SCROLL DOWN 800, 3)\nWAIT 1`
+ },
+ {
+ name: 'Advanced Form Wizard',
+ script: `# Multi-step form with validation\nCLICK \`a[href="#forms"]\`\nWAIT \`#survey-form\` 2\n\n# Step 1: Personal Info\nSET \`#full-name\` "John Doe"\nSET \`#survey-email\` "john@example.com"\nCLICK \`.next-step\`\nWAIT 1\n\n# Step 2: Preferences\nCLICK \`#interests\`\nCLICK \`option[value="tech"]\`\nCLICK \`option[value="music"]\`\nCLICK \`.next-step\`\nWAIT 1\n\n# Step 3: Submit\nIF (EXISTS \`#comments\`) THEN SET \`#comments\` "Great experience!"\nCLICK \`#submit-survey\`\nWAIT \`.success-message\` 3`
+ }
+ ];
+
+ const currentIndex = parseInt(localStorage.getItem('exampleIndex') || '0');
+ const example = examples[currentIndex % examples.length];
+
+ this.editor.setValue(example.script);
+ this.addConsoleMessage(`Loaded example: ${example.name}`, 'success');
+
+ localStorage.setItem('exampleIndex', ((currentIndex + 1) % examples.length).toString());
+ }
+
+ wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+}
+
+// Add animations CSS
+const style = document.createElement('style');
+style.textContent = `
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(-10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes fadeOut {
+ from { opacity: 1; transform: translateY(0); }
+ to { opacity: 0; transform: translateY(-10px); }
+}
+`;
+document.head.appendChild(style);
+
+// Initialize when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ window.tutorialApp = new TutorialApp();
+ console.log('🎓 C4A-Script Tutorial initialized!');
+});
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/assets/styles.css b/docs/examples/c4a_script/tutorial/assets/styles.css
new file mode 100644
index 00000000..fac03a63
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/assets/styles.css
@@ -0,0 +1,531 @@
+/* DankMono Font Faces */
+@font-face {
+ font-family: 'DankMono';
+ src: url('DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DankMono';
+ src: url('DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DankMono';
+ src: url('DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+}
+
+/* Root Variables - Matching docs theme */
+:root {
+ --global-font-size: 14px;
+ --global-code-font-size: 13px;
+ --global-line-height: 1.5em;
+ --global-space: 10px;
+ --font-stack: DankMono, Monaco, Courier New, monospace;
+ --mono-font-stack: DankMono, Monaco, Courier New, monospace;
+
+ --background-color: #070708;
+ --font-color: #e8e9ed;
+ --invert-font-color: #222225;
+ --secondary-color: #d5cec0;
+ --tertiary-color: #a3abba;
+ --primary-color: #0fbbaa;
+ --error-color: #ff3c74;
+ --progress-bar-background: #3f3f44;
+ --progress-bar-fill: #09b5a5;
+ --code-bg-color: #3f3f44;
+ --block-background-color: #202020;
+
+ --header-height: 55px;
+}
+
+/* Base Styles */
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: var(--font-stack);
+ font-size: var(--global-font-size);
+ line-height: var(--global-line-height);
+ color: var(--font-color);
+ background-color: var(--background-color);
+}
+
+/* Terminal Framework */
+.terminal {
+ min-height: 100vh;
+}
+
+.container {
+ width: 100%;
+ margin: 0 auto;
+}
+
+/* Header */
+.header-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: var(--header-height);
+ background-color: var(--background-color);
+ border-bottom: 1px solid var(--progress-bar-background);
+ z-index: 1000;
+ padding: 0 calc(var(--global-space) * 2);
+}
+
+.terminal-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 100%;
+}
+
+.terminal-logo h1 {
+ margin: 0;
+ font-size: 1.2em;
+ color: var(--primary-color);
+ font-weight: 400;
+}
+
+.terminal-menu ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ gap: 2em;
+}
+
+.terminal-menu a {
+ color: var(--secondary-color);
+ text-decoration: none;
+ transition: color 0.2s;
+}
+
+.terminal-menu a:hover,
+.terminal-menu a.active {
+ color: var(--primary-color);
+}
+
+/* Main Container */
+.main-container {
+ padding-top: calc(var(--header-height) + 2em);
+ padding-left: 2em;
+ padding-right: 2em;
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+/* Tutorial Grid */
+.tutorial-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2em;
+ align-items: start;
+}
+
+/* Terminal Cards */
+.terminal-card {
+ background-color: var(--block-background-color);
+ border: 1px solid var(--progress-bar-background);
+ margin-bottom: 1.5em;
+}
+
+.terminal-card header {
+ background-color: var(--progress-bar-background);
+ padding: 0.8em 1em;
+ font-weight: 700;
+ color: var(--font-color);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.terminal-card > div {
+ padding: 1.5em;
+}
+
+/* Editor Section */
+.editor-controls {
+ display: flex;
+ gap: 0.5em;
+}
+
+.editor-container {
+ height: 300px;
+ overflow: hidden;
+}
+
+#c4a-editor {
+ width: 100%;
+ height: 100%;
+ font-family: var(--mono-font-stack);
+ font-size: var(--global-code-font-size);
+ background-color: var(--code-bg-color);
+ color: var(--font-color);
+ border: none;
+ padding: 1em;
+ resize: none;
+}
+
+/* JS Output */
+.js-output-container {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.js-output-container pre {
+ margin: 0;
+ padding: 1em;
+ background-color: var(--code-bg-color);
+}
+
+.js-output-container code {
+ font-family: var(--mono-font-stack);
+ font-size: var(--global-code-font-size);
+ color: var(--font-color);
+ white-space: pre-wrap;
+}
+
+/* Console Output */
+.console-output {
+ font-family: var(--mono-font-stack);
+ font-size: var(--global-code-font-size);
+ max-height: 200px;
+ overflow-y: auto;
+ padding: 1em;
+}
+
+.console-line {
+ margin-bottom: 0.5em;
+}
+
+.console-prompt {
+ color: var(--primary-color);
+ margin-right: 0.5em;
+}
+
+.console-text {
+ color: var(--font-color);
+}
+
+.console-error {
+ color: var(--error-color);
+}
+
+.console-success {
+ color: var(--primary-color);
+}
+
+/* Playground */
+.playground-container {
+ height: 600px;
+ background-color: #fff;
+ border: 1px solid var(--progress-bar-background);
+}
+
+#playground-frame {
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
+/* Execution Progress */
+.execution-progress {
+ padding: 1em;
+}
+
+.progress-item {
+ display: flex;
+ align-items: center;
+ gap: 0.8em;
+ margin-bottom: 0.8em;
+ color: var(--secondary-color);
+}
+
+.progress-item.active {
+ color: var(--primary-color);
+}
+
+.progress-item.completed {
+ color: var(--tertiary-color);
+}
+
+.progress-item.error {
+ color: var(--error-color);
+}
+
+.progress-icon {
+ font-size: 1.2em;
+}
+
+/* Buttons */
+.btn {
+ background-color: var(--primary-color);
+ color: var(--background-color);
+ border: none;
+ padding: 0.5em 1em;
+ font-family: var(--font-stack);
+ font-size: 0.9em;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn:hover {
+ background-color: var(--progress-bar-fill);
+}
+
+.btn-sm {
+ padding: 0.3em 0.8em;
+ font-size: 0.85em;
+}
+
+.btn-ghost {
+ background-color: transparent;
+ color: var(--secondary-color);
+ border: 1px solid var(--progress-bar-background);
+}
+
+.btn-ghost:hover {
+ background-color: var(--progress-bar-background);
+ color: var(--font-color);
+}
+
+/* Scrollbars */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--block-background-color);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--progress-bar-background);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--secondary-color);
+}
+
+/* CodeMirror Theme Override */
+.CodeMirror {
+ font-family: var(--mono-font-stack) !important;
+ font-size: var(--global-code-font-size) !important;
+ background-color: var(--code-bg-color) !important;
+ color: var(--font-color) !important;
+ height: 100% !important;
+}
+
+.CodeMirror-gutters {
+ background-color: var(--progress-bar-background) !important;
+ border-right: 1px solid var(--progress-bar-background) !important;
+}
+
+/* Responsive */
+@media (max-width: 1200px) {
+ .tutorial-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .playground-section {
+ order: -1;
+ }
+}
+
+/* Links */
+a {
+ color: var(--primary-color);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* Lists */
+ul, ol {
+ padding-left: 2em;
+}
+
+li {
+ margin-bottom: 0.5em;
+}
+
+/* Code */
+code {
+ background-color: var(--code-bg-color);
+ padding: 0.2em 0.4em;
+ font-family: var(--mono-font-stack);
+ font-size: 0.9em;
+}
+
+/* Headings */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 700;
+ margin-top: 1.5em;
+ margin-bottom: 0.8em;
+}
+
+h3 {
+ color: var(--primary-color);
+ font-size: 1.1em;
+}
+
+/* Tutorial Panel */
+.tutorial-panel {
+ position: absolute;
+ top: 60px;
+ right: 20px;
+ width: 380px;
+ background: #1a1a1b;
+ border: 1px solid #2a2a2c;
+ border-radius: 8px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ transition: all 0.3s ease;
+}
+
+.tutorial-panel.hidden {
+ display: none;
+}
+
+.tutorial-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #2a2a2c;
+}
+
+.tutorial-header h3 {
+ margin: 0;
+ color: #0fbbaa;
+ font-size: 18px;
+}
+
+.close-btn {
+ background: none;
+ border: none;
+ color: #8b8b8d;
+ font-size: 24px;
+ cursor: pointer;
+ padding: 0;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+}
+
+.close-btn:hover {
+ background: #2a2a2c;
+ color: #e0e0e0;
+}
+
+.tutorial-content {
+ padding: 20px;
+}
+
+.tutorial-content p {
+ margin: 0 0 16px 0;
+ color: #e0e0e0;
+ line-height: 1.6;
+}
+
+.tutorial-progress {
+ margin-top: 16px;
+}
+
+.tutorial-progress span {
+ display: block;
+ margin-bottom: 8px;
+ color: #8b8b8d;
+ font-size: 12px;
+ text-transform: uppercase;
+}
+
+.progress-bar {
+ height: 4px;
+ background: #2a2a2c;
+ border-radius: 2px;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: #0fbbaa;
+ transition: width 0.3s ease;
+}
+
+.tutorial-actions {
+ display: flex;
+ gap: 12px;
+ padding: 0 20px 20px;
+}
+
+.tutorial-btn {
+ flex: 1;
+ padding: 10px 16px;
+ background: #2a2a2c;
+ color: #e0e0e0;
+ border: 1px solid #3a3a3c;
+ border-radius: 6px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.tutorial-btn:hover:not(:disabled) {
+ background: #3a3a3c;
+ transform: translateY(-1px);
+}
+
+.tutorial-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.tutorial-btn.primary {
+ background: #0fbbaa;
+ color: #070708;
+ border-color: #0fbbaa;
+}
+
+.tutorial-btn.primary:hover {
+ background: #0da89a;
+ border-color: #0da89a;
+}
+
+/* Tutorial Highlights */
+.tutorial-highlight {
+ position: relative;
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(15, 187, 170, 0.4);
+ }
+ 50% {
+ box-shadow: 0 0 0 10px rgba(15, 187, 170, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(15, 187, 170, 0);
+ }
+}
+
+.editor-card {
+ position: relative;
+}
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/index.html b/docs/examples/c4a_script/tutorial/index.html
new file mode 100644
index 00000000..c22d41cc
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/index.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+ C4A-Script Interactive Tutorial | Crawl4AI
+
+
+
+
+
+
+
+
+
Welcome to C4A-Script Tutorial!
+
C4A-Script is a simple language for web automation. This interactive tutorial will teach you:
+
+ - How to handle popups and banners
+ - Form filling and navigation
+ - Advanced automation techniques
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $
+ Ready to run C4A scripts...
+
+
+
+
+
+
// JavaScript will appear here...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 1 of 9
+ Welcome
+
+
Let's start by waiting for the page to load.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/playground/app.js b/docs/examples/c4a_script/tutorial/playground/app.js
new file mode 100644
index 00000000..e931a050
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/playground/app.js
@@ -0,0 +1,604 @@
+// Playground App JavaScript
+class PlaygroundApp {
+ constructor() {
+ this.isLoggedIn = false;
+ this.currentSection = 'home';
+ this.productsLoaded = 0;
+ this.maxProducts = 100;
+ this.tableRowsLoaded = 10;
+ this.inspectorMode = false;
+ this.tooltip = null;
+
+ this.init();
+ }
+
+ init() {
+ this.setupCookieBanner();
+ this.setupNewsletterPopup();
+ this.setupNavigation();
+ this.setupAuth();
+ this.setupProductCatalog();
+ this.setupForms();
+ this.setupTabs();
+ this.setupDataTable();
+ this.setupInspector();
+ this.loadInitialData();
+ }
+
+ // Cookie Banner
+ setupCookieBanner() {
+ const banner = document.getElementById('cookie-banner');
+ const acceptBtn = banner.querySelector('.accept');
+ const declineBtn = banner.querySelector('.decline');
+
+ acceptBtn.addEventListener('click', () => {
+ banner.style.display = 'none';
+ console.log('✅ Cookies accepted');
+ });
+
+ declineBtn.addEventListener('click', () => {
+ banner.style.display = 'none';
+ console.log('❌ Cookies declined');
+ });
+ }
+
+ // Newsletter Popup
+ setupNewsletterPopup() {
+ const popup = document.getElementById('newsletter-popup');
+ const closeBtn = popup.querySelector('.close');
+ const subscribeBtn = popup.querySelector('.subscribe');
+
+ // Show popup after 3 seconds
+ setTimeout(() => {
+ popup.style.display = 'flex';
+ }, 3000);
+
+ closeBtn.addEventListener('click', () => {
+ popup.style.display = 'none';
+ });
+
+ subscribeBtn.addEventListener('click', () => {
+ const email = popup.querySelector('input').value;
+ if (email) {
+ console.log(`📧 Subscribed: ${email}`);
+ popup.style.display = 'none';
+ }
+ });
+
+ // Close on outside click
+ popup.addEventListener('click', (e) => {
+ if (e.target === popup) {
+ popup.style.display = 'none';
+ }
+ });
+ }
+
+ // Navigation
+ setupNavigation() {
+ const navLinks = document.querySelectorAll('.nav-link');
+ const sections = document.querySelectorAll('.section');
+
+ navLinks.forEach(link => {
+ link.addEventListener('click', (e) => {
+ e.preventDefault();
+ const targetId = link.getAttribute('href').substring(1);
+
+ // Update active states
+ navLinks.forEach(l => l.classList.remove('active'));
+ link.classList.add('active');
+
+ // Show target section
+ sections.forEach(s => s.classList.remove('active'));
+ const targetSection = document.getElementById(targetId);
+ if (targetSection) {
+ targetSection.classList.add('active');
+ this.currentSection = targetId;
+
+ // Load content for specific sections
+ this.loadSectionContent(targetId);
+ }
+ });
+ });
+
+ // Start tutorial button
+ const startBtn = document.getElementById('start-tutorial');
+ if (startBtn) {
+ startBtn.addEventListener('click', () => {
+ console.log('🚀 Tutorial started!');
+ alert('Tutorial started! Check the console for progress.');
+ });
+ }
+ }
+
+ // Authentication
+ setupAuth() {
+ const loginBtn = document.getElementById('login-btn');
+ const logoutBtn = document.getElementById('logout-btn');
+ const loginModal = document.getElementById('login-modal');
+ const loginForm = document.getElementById('login-form');
+ const closeBtn = loginModal.querySelector('.close');
+
+ loginBtn.addEventListener('click', () => {
+ loginModal.style.display = 'flex';
+ });
+
+ closeBtn.addEventListener('click', () => {
+ loginModal.style.display = 'none';
+ });
+
+ loginForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const email = document.getElementById('email').value;
+ const password = document.getElementById('password').value;
+ const rememberMe = document.getElementById('remember-me').checked;
+ const messageEl = document.getElementById('login-message');
+
+ // Simple validation
+ if (email === 'demo@example.com' && password === 'demo123') {
+ this.isLoggedIn = true;
+ messageEl.textContent = '✅ Login successful!';
+ messageEl.className = 'form-message success';
+
+ setTimeout(() => {
+ loginModal.style.display = 'none';
+ document.getElementById('login-btn').style.display = 'none';
+ document.getElementById('user-info').style.display = 'flex';
+ document.getElementById('username-display').textContent = 'Demo User';
+ console.log(`✅ Logged in${rememberMe ? ' (remembered)' : ''}`);
+ }, 1000);
+ } else {
+ messageEl.textContent = '❌ Invalid credentials. Try demo@example.com / demo123';
+ messageEl.className = 'form-message error';
+ }
+ });
+
+ logoutBtn.addEventListener('click', () => {
+ this.isLoggedIn = false;
+ document.getElementById('login-btn').style.display = 'block';
+ document.getElementById('user-info').style.display = 'none';
+ console.log('👋 Logged out');
+ });
+
+ // Close modal on outside click
+ loginModal.addEventListener('click', (e) => {
+ if (e.target === loginModal) {
+ loginModal.style.display = 'none';
+ }
+ });
+ }
+
+ // Product Catalog
+ setupProductCatalog() {
+ // View toggle
+ const infiniteBtn = document.getElementById('infinite-scroll-btn');
+ const paginationBtn = document.getElementById('pagination-btn');
+ const infiniteView = document.getElementById('infinite-scroll-view');
+ const paginationView = document.getElementById('pagination-view');
+
+ infiniteBtn.addEventListener('click', () => {
+ infiniteBtn.classList.add('active');
+ paginationBtn.classList.remove('active');
+ infiniteView.style.display = 'block';
+ paginationView.style.display = 'none';
+ this.setupInfiniteScroll();
+ });
+
+ paginationBtn.addEventListener('click', () => {
+ paginationBtn.classList.add('active');
+ infiniteBtn.classList.remove('active');
+ paginationView.style.display = 'block';
+ infiniteView.style.display = 'none';
+ });
+
+ // Load more button
+ const loadMoreBtn = paginationView.querySelector('.load-more');
+ loadMoreBtn.addEventListener('click', () => {
+ this.loadMoreProducts();
+ });
+
+ // Collapsible filters
+ const collapsibles = document.querySelectorAll('.collapsible');
+ collapsibles.forEach(header => {
+ header.addEventListener('click', () => {
+ const content = header.nextElementSibling;
+ const toggle = header.querySelector('.toggle');
+ content.style.display = content.style.display === 'none' ? 'block' : 'none';
+ toggle.textContent = content.style.display === 'none' ? '▶' : '▼';
+ });
+ });
+ }
+
+ setupInfiniteScroll() {
+ const container = document.querySelector('.products-container');
+ const loadingIndicator = document.getElementById('loading-indicator');
+
+ container.addEventListener('scroll', () => {
+ if (container.scrollTop + container.clientHeight >= container.scrollHeight - 100) {
+ if (this.productsLoaded < this.maxProducts) {
+ loadingIndicator.style.display = 'block';
+ setTimeout(() => {
+ this.loadMoreProducts();
+ loadingIndicator.style.display = 'none';
+ }, 1000);
+ }
+ }
+ });
+ }
+
+ loadMoreProducts() {
+ const grid = document.getElementById('product-grid');
+ const batch = 10;
+
+ for (let i = 0; i < batch && this.productsLoaded < this.maxProducts; i++) {
+ const product = this.createProductCard(this.productsLoaded + 1);
+ grid.appendChild(product);
+ this.productsLoaded++;
+ }
+
+ console.log(`📦 Loaded ${batch} more products. Total: ${this.productsLoaded}`);
+ }
+
+ createProductCard(id) {
+ const card = document.createElement('div');
+ card.className = 'product-card';
+ card.innerHTML = `
+ 📦
+ Product ${id}
+ $${(Math.random() * 100 + 10).toFixed(2)}
+
+ `;
+
+ // Quick view functionality
+ const quickViewBtn = card.querySelector('button');
+ quickViewBtn.addEventListener('click', () => {
+ alert(`Quick view for Product ${id}`);
+ });
+
+ return card;
+ }
+
+ // Forms
+ setupForms() {
+ // Contact Form
+ const contactForm = document.getElementById('contact-form');
+ const subjectSelect = document.getElementById('contact-subject');
+ const departmentGroup = document.getElementById('department-group');
+ const departmentSelect = document.getElementById('department');
+
+ subjectSelect.addEventListener('change', () => {
+ if (subjectSelect.value === 'support') {
+ departmentGroup.style.display = 'block';
+ departmentSelect.innerHTML = `
+
+
+
+
+ `;
+ } else {
+ departmentGroup.style.display = 'none';
+ }
+ });
+
+ contactForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ const messageDisplay = document.getElementById('contact-message-display');
+ messageDisplay.textContent = '✅ Message sent successfully!';
+ messageDisplay.className = 'form-message success';
+ console.log('📧 Contact form submitted');
+ });
+
+ // Multi-step Form
+ const surveyForm = document.getElementById('survey-form');
+ const steps = surveyForm.querySelectorAll('.form-step');
+ const progressFill = document.getElementById('progress-fill');
+ let currentStep = 1;
+
+ surveyForm.addEventListener('click', (e) => {
+ if (e.target.classList.contains('next-step')) {
+ if (currentStep < 3) {
+ steps[currentStep - 1].style.display = 'none';
+ currentStep++;
+ steps[currentStep - 1].style.display = 'block';
+ progressFill.style.width = `${(currentStep / 3) * 100}%`;
+ }
+ } else if (e.target.classList.contains('prev-step')) {
+ if (currentStep > 1) {
+ steps[currentStep - 1].style.display = 'none';
+ currentStep--;
+ steps[currentStep - 1].style.display = 'block';
+ progressFill.style.width = `${(currentStep / 3) * 100}%`;
+ }
+ }
+ });
+
+ surveyForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ document.getElementById('survey-success').style.display = 'block';
+ console.log('📋 Survey submitted successfully!');
+ });
+ }
+
+ // Tabs
+ setupTabs() {
+ const tabBtns = document.querySelectorAll('.tab-btn');
+ const tabPanes = document.querySelectorAll('.tab-pane');
+
+ tabBtns.forEach(btn => {
+ btn.addEventListener('click', () => {
+ const targetTab = btn.getAttribute('data-tab');
+
+ // Update active states
+ tabBtns.forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+
+ // Show target pane
+ tabPanes.forEach(pane => {
+ pane.style.display = pane.id === targetTab ? 'block' : 'none';
+ });
+ });
+ });
+
+ // Show more functionality
+ const showMoreBtn = document.querySelector('.show-more');
+ const hiddenText = document.querySelector('.hidden-text');
+
+ if (showMoreBtn) {
+ showMoreBtn.addEventListener('click', () => {
+ if (hiddenText.style.display === 'none') {
+ hiddenText.style.display = 'block';
+ showMoreBtn.textContent = 'Show Less';
+ } else {
+ hiddenText.style.display = 'none';
+ showMoreBtn.textContent = 'Show More';
+ }
+ });
+ }
+
+ // Load comments
+ const loadCommentsBtn = document.querySelector('.load-comments');
+ const commentsSection = document.querySelector('.comments-section');
+
+ if (loadCommentsBtn) {
+ loadCommentsBtn.addEventListener('click', () => {
+ commentsSection.style.display = 'block';
+ commentsSection.innerHTML = `
+
+
+ `;
+ loadCommentsBtn.style.display = 'none';
+ console.log('💬 Comments loaded');
+ });
+ }
+ }
+
+ // Data Table
+ setupDataTable() {
+ const loadMoreBtn = document.querySelector('.load-more-rows');
+ const searchInput = document.querySelector('.search-input');
+ const exportBtn = document.getElementById('export-btn');
+ const sortableHeaders = document.querySelectorAll('.sortable');
+
+ // Load more rows
+ loadMoreBtn.addEventListener('click', () => {
+ this.loadMoreTableRows();
+ });
+
+ // Search functionality
+ searchInput.addEventListener('input', (e) => {
+ const searchTerm = e.target.value.toLowerCase();
+ const rows = document.querySelectorAll('#table-body tr');
+
+ rows.forEach(row => {
+ const text = row.textContent.toLowerCase();
+ row.style.display = text.includes(searchTerm) ? '' : 'none';
+ });
+ });
+
+ // Export functionality
+ exportBtn.addEventListener('click', () => {
+ console.log('📊 Exporting table data...');
+ alert('Table data exported! (Check console)');
+ });
+
+ // Sorting
+ sortableHeaders.forEach(header => {
+ header.addEventListener('click', () => {
+ console.log(`🔄 Sorting by ${header.getAttribute('data-sort')}`);
+ });
+ });
+ }
+
+ loadMoreTableRows() {
+ const tbody = document.getElementById('table-body');
+ const batch = 10;
+
+ for (let i = 0; i < batch; i++) {
+ const row = document.createElement('tr');
+ const id = this.tableRowsLoaded + i + 1;
+ row.innerHTML = `
+ User ${id} |
+ user${id}@example.com |
+ ${new Date().toLocaleDateString()} |
+ |
+ `;
+ tbody.appendChild(row);
+ }
+
+ this.tableRowsLoaded += batch;
+ console.log(`📄 Loaded ${batch} more rows. Total: ${this.tableRowsLoaded}`);
+ }
+
+ // Load initial data
+ loadInitialData() {
+ // Load initial products
+ this.loadMoreProducts();
+
+ // Load initial table rows
+ this.loadMoreTableRows();
+ }
+
+ // Load content when navigating to sections
+ loadSectionContent(sectionId) {
+ switch(sectionId) {
+ case 'catalog':
+ // Ensure products are loaded in catalog
+ if (this.productsLoaded === 0) {
+ this.loadMoreProducts();
+ }
+ break;
+ case 'data-tables':
+ // Ensure table rows are loaded
+ if (this.tableRowsLoaded === 0) {
+ this.loadMoreTableRows();
+ }
+ break;
+ case 'forms':
+ // Forms are already set up
+ break;
+ case 'tabs':
+ // Tabs content is static
+ break;
+ }
+ }
+
+ // Inspector Mode
+ setupInspector() {
+ const inspectorBtn = document.getElementById('inspector-btn');
+
+ // Create tooltip element
+ this.tooltip = document.createElement('div');
+ this.tooltip.className = 'inspector-tooltip';
+ this.tooltip.style.cssText = `
+ position: fixed;
+ background: rgba(0, 0, 0, 0.9);
+ color: white;
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-family: monospace;
+ pointer-events: none;
+ z-index: 10000;
+ display: none;
+ max-width: 300px;
+ `;
+ document.body.appendChild(this.tooltip);
+
+ inspectorBtn.addEventListener('click', () => {
+ this.toggleInspector();
+ });
+
+ // Add mouse event listeners
+ document.addEventListener('mousemove', this.handleMouseMove.bind(this));
+ document.addEventListener('mouseout', this.handleMouseOut.bind(this));
+ }
+
+ toggleInspector() {
+ this.inspectorMode = !this.inspectorMode;
+ const inspectorBtn = document.getElementById('inspector-btn');
+
+ if (this.inspectorMode) {
+ inspectorBtn.classList.add('active');
+ inspectorBtn.style.background = '#0fbbaa';
+ document.body.style.cursor = 'crosshair';
+ } else {
+ inspectorBtn.classList.remove('active');
+ inspectorBtn.style.background = '';
+ document.body.style.cursor = '';
+ this.tooltip.style.display = 'none';
+ this.removeHighlight();
+ }
+ }
+
+ handleMouseMove(e) {
+ if (!this.inspectorMode) return;
+
+ const element = e.target;
+ if (element === this.tooltip) return;
+
+ // Highlight element
+ this.highlightElement(element);
+
+ // Show tooltip with element info
+ const info = this.getElementInfo(element);
+ this.tooltip.innerHTML = info;
+ this.tooltip.style.display = 'block';
+
+ // Position tooltip
+ const x = e.clientX + 15;
+ const y = e.clientY + 15;
+
+ // Adjust position if tooltip would go off screen
+ const rect = this.tooltip.getBoundingClientRect();
+ const adjustedX = x + rect.width > window.innerWidth ? x - rect.width - 30 : x;
+ const adjustedY = y + rect.height > window.innerHeight ? y - rect.height - 30 : y;
+
+ this.tooltip.style.left = adjustedX + 'px';
+ this.tooltip.style.top = adjustedY + 'px';
+ }
+
+ handleMouseOut(e) {
+ if (!this.inspectorMode) return;
+ if (e.target === document.body) {
+ this.removeHighlight();
+ this.tooltip.style.display = 'none';
+ }
+ }
+
+ highlightElement(element) {
+ this.removeHighlight();
+ element.style.outline = '2px solid #0fbbaa';
+ element.style.outlineOffset = '1px';
+ element.setAttribute('data-inspector-highlighted', 'true');
+ }
+
+ removeHighlight() {
+ const highlighted = document.querySelector('[data-inspector-highlighted]');
+ if (highlighted) {
+ highlighted.style.outline = '';
+ highlighted.style.outlineOffset = '';
+ highlighted.removeAttribute('data-inspector-highlighted');
+ }
+ }
+
+ getElementInfo(element) {
+ const tagName = element.tagName.toLowerCase();
+ const id = element.id ? `#${element.id}` : '';
+ const classes = element.className ?
+ `.${element.className.split(' ').filter(c => c).join('.')}` : '';
+
+ let selector = tagName;
+ if (id) {
+ selector = id;
+ } else if (classes) {
+ selector = `${tagName}${classes}`;
+ }
+
+ // Build info HTML
+ let info = `${selector}`;
+
+ // Add additional attributes
+ const attrs = [];
+ if (element.name) attrs.push(`name="${element.name}"`);
+ if (element.type) attrs.push(`type="${element.type}"`);
+ if (element.href) attrs.push(`href="${element.href}"`);
+ if (element.value && element.tagName === 'INPUT') attrs.push(`value="${element.value}"`);
+
+ if (attrs.length > 0) {
+ info += `
${attrs.join(' ')}`;
+ }
+
+ return info;
+ }
+}
+
+// Initialize app when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ window.playgroundApp = new PlaygroundApp();
+ console.log('🎮 Playground app initialized!');
+});
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/playground/index.html b/docs/examples/c4a_script/tutorial/playground/index.html
new file mode 100644
index 00000000..6ae8efd5
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/playground/index.html
@@ -0,0 +1,328 @@
+
+
+
+
+
+ C4A-Script Playground
+
+
+
+
+
+
+
🍪 We use cookies to enhance your experience. By continuing, you agree to our cookie policy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to C4A-Script Playground
+ This is an interactive demo for testing C4A-Script commands. Each section contains different challenges for web automation.
+
+
+
+
+
+
🔐 Authentication
+
Test login forms and user sessions
+
+
+
📜 Dynamic Content
+
Infinite scroll and pagination
+
+
+
📝 Forms
+
Complex form interactions
+
+
+
📊 Data Tables
+
Sortable and filterable data
+
+
+
+
+
+
+
+
+
+ Product Catalog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tabs Demo
+
+
+
+
+
Product Description
+
This is a detailed description of the product...
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
+
+
+
This is the hidden text that appears when you click "Show More". It contains additional details about the product that weren't visible initially.
+
+
+
+
+
Customer Reviews
+
+
+
+
+
Technical Specifications
+
+ | Model | XYZ-2000 |
+ | Weight | 2.5 kg |
+ | Dimensions | 30 x 20 x 10 cm |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/playground/styles.css b/docs/examples/c4a_script/tutorial/playground/styles.css
new file mode 100644
index 00000000..bfb2c071
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/playground/styles.css
@@ -0,0 +1,627 @@
+/* Playground Styles - Modern Web App Theme */
+:root {
+ --primary-color: #0fbbaa;
+ --secondary-color: #3f3f44;
+ --background-color: #ffffff;
+ --text-color: #333333;
+ --border-color: #e0e0e0;
+ --error-color: #ff3c74;
+ --success-color: #0fbbaa;
+ --warning-color: #ffa500;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-size: 16px;
+ line-height: 1.6;
+ color: var(--text-color);
+ background-color: var(--background-color);
+}
+
+/* Cookie Banner */
+.cookie-banner {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #2c3e50;
+ color: white;
+ padding: 1rem;
+ z-index: 1000;
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
+}
+
+.cookie-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.cookie-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+/* Header */
+.site-header {
+ background-color: #fff;
+ border-bottom: 1px solid var(--border-color);
+ padding: 1rem 2rem;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.nav-menu {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+}
+
+.nav-link {
+ text-decoration: none;
+ color: var(--text-color);
+ font-weight: 500;
+ transition: color 0.2s;
+}
+
+.nav-link:hover,
+.nav-link.active {
+ color: var(--primary-color);
+}
+
+/* Dropdown */
+.dropdown {
+ position: relative;
+}
+
+.dropdown-content {
+ display: none;
+ position: absolute;
+ background-color: white;
+ min-width: 160px;
+ box-shadow: 0 8px 16px rgba(0,0,0,0.1);
+ z-index: 1;
+ border-radius: 4px;
+ top: 100%;
+ margin-top: 0.5rem;
+}
+
+.dropdown:hover .dropdown-content {
+ display: block;
+}
+
+.dropdown-content a {
+ color: var(--text-color);
+ padding: 0.75rem 1rem;
+ text-decoration: none;
+ display: block;
+}
+
+.dropdown-content a:hover {
+ background-color: #f5f5f5;
+}
+
+/* Auth Section */
+.auth-section {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.user-info {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.user-avatar {
+ font-size: 1.5rem;
+}
+
+/* Main Content */
+.main-content {
+ padding: 2rem;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.section {
+ display: none;
+}
+
+.section.active {
+ display: block;
+}
+
+/* Buttons */
+.btn {
+ background-color: var(--primary-color);
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ transition: all 0.2s;
+}
+
+.btn:hover {
+ background-color: #0aa599;
+ transform: translateY(-1px);
+}
+
+.btn-sm {
+ padding: 0.25rem 0.75rem;
+ font-size: 0.875rem;
+}
+
+.btn-secondary {
+ background-color: var(--secondary-color);
+}
+
+.btn-secondary:hover {
+ background-color: #333;
+}
+
+.btn-primary {
+ background-color: var(--primary-color);
+}
+
+/* Feature Grid */
+.feature-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 1.5rem;
+ margin-top: 2rem;
+}
+
+.feature-card {
+ background-color: #f8f9fa;
+ padding: 1.5rem;
+ border-radius: 8px;
+ text-align: center;
+ transition: transform 0.2s;
+}
+
+.feature-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+}
+
+.feature-card h3 {
+ margin-top: 0;
+}
+
+/* Modal */
+.modal {
+ position: fixed;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-content {
+ background-color: white;
+ padding: 2rem;
+ border-radius: 8px;
+ max-width: 500px;
+ width: 90%;
+ position: relative;
+ animation: modalFadeIn 0.3s;
+}
+
+@keyframes modalFadeIn {
+ from { opacity: 0; transform: translateY(-20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.close {
+ position: absolute;
+ right: 1rem;
+ top: 1rem;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: #999;
+}
+
+.close:hover {
+ color: #333;
+}
+
+/* Forms */
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+}
+
+.input {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-size: 1rem;
+}
+
+.input:focus {
+ outline: none;
+ border-color: var(--primary-color);
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.form-message {
+ margin-top: 1rem;
+ padding: 0.75rem;
+ border-radius: 4px;
+ display: none;
+}
+
+.form-message.error {
+ background-color: #ffe6e6;
+ color: var(--error-color);
+ display: block;
+}
+
+.form-message.success {
+ background-color: #e6fff6;
+ color: var(--success-color);
+ display: block;
+}
+
+/* Product Catalog */
+.view-toggle {
+ margin-bottom: 1rem;
+}
+
+.catalog-layout {
+ display: grid;
+ grid-template-columns: 250px 1fr;
+ gap: 2rem;
+}
+
+.filters-sidebar {
+ background-color: #f8f9fa;
+ padding: 1rem;
+ border-radius: 8px;
+}
+
+.filter-group {
+ margin-bottom: 1.5rem;
+}
+
+.collapsible {
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.filter-content {
+ margin-top: 0.5rem;
+}
+
+.filter-content label {
+ display: block;
+ margin-bottom: 0.5rem;
+}
+
+/* Product Grid */
+.product-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 1.5rem;
+}
+
+.product-card {
+ background-color: white;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 1rem;
+ text-align: center;
+ transition: transform 0.2s;
+}
+
+.product-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+}
+
+.product-image {
+ width: 100%;
+ height: 150px;
+ background-color: #f0f0f0;
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 3rem;
+}
+
+.product-name {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+}
+
+.product-price {
+ color: var(--primary-color);
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+
+/* Loading Indicator */
+.loading-indicator {
+ text-align: center;
+ padding: 2rem;
+}
+
+.spinner {
+ border: 3px solid #f3f3f3;
+ border-top: 3px solid var(--primary-color);
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+ margin: 0 auto;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Pagination */
+.pagination {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: center;
+ margin-top: 2rem;
+}
+
+.page-btn {
+ padding: 0.5rem 1rem;
+ border: 1px solid var(--border-color);
+ background-color: white;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.page-btn:hover,
+.page-btn.active {
+ background-color: var(--primary-color);
+ color: white;
+}
+
+/* Multi-step Form */
+.progress-bar {
+ width: 100%;
+ height: 8px;
+ background-color: #e0e0e0;
+ border-radius: 4px;
+ margin-bottom: 2rem;
+}
+
+.progress-fill {
+ height: 100%;
+ background-color: var(--primary-color);
+ border-radius: 4px;
+ transition: width 0.3s;
+}
+
+.form-step {
+ display: none;
+}
+
+.form-step.active {
+ display: block;
+}
+
+/* Tabs */
+.tabs-container {
+ margin-top: 2rem;
+}
+
+.tabs-header {
+ display: flex;
+ border-bottom: 2px solid var(--border-color);
+}
+
+.tab-btn {
+ background: none;
+ border: none;
+ padding: 1rem 2rem;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ color: var(--text-color);
+ position: relative;
+}
+
+.tab-btn:hover {
+ color: var(--primary-color);
+}
+
+.tab-btn.active {
+ color: var(--primary-color);
+}
+
+.tab-btn.active::after {
+ content: '';
+ position: absolute;
+ bottom: -2px;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background-color: var(--primary-color);
+}
+
+.tabs-content {
+ padding: 2rem 0;
+}
+
+.tab-pane {
+ display: none;
+}
+
+.tab-pane.active {
+ display: block;
+}
+
+/* Expandable Text */
+.expandable-text {
+ margin-top: 1rem;
+}
+
+.text-preview {
+ margin-bottom: 0.5rem;
+}
+
+.show-more {
+ margin-top: 0.5rem;
+}
+
+/* Comments Section */
+.comments-section {
+ margin-top: 1rem;
+}
+
+.comment {
+ background-color: #f8f9fa;
+ padding: 1rem;
+ border-radius: 4px;
+ margin-bottom: 1rem;
+}
+
+.comment-author {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+}
+
+/* Data Table */
+.table-controls {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.search-input {
+ flex: 1;
+ max-width: 300px;
+}
+
+.data-table {
+ width: 100%;
+ border-collapse: collapse;
+ background-color: white;
+}
+
+.data-table th,
+.data-table td {
+ padding: 0.75rem;
+ text-align: left;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.data-table th {
+ background-color: #f8f9fa;
+ font-weight: 600;
+}
+
+.sortable {
+ cursor: pointer;
+}
+
+.sortable:hover {
+ color: var(--primary-color);
+}
+
+/* Form Cards */
+.form-card {
+ background-color: white;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 2rem;
+ margin-bottom: 2rem;
+}
+
+.form-card h2 {
+ margin-top: 0;
+}
+
+/* Success Message */
+.success-message {
+ background-color: #e6fff6;
+ color: var(--success-color);
+ padding: 1rem;
+ border-radius: 4px;
+ text-align: center;
+ font-weight: 500;
+}
+
+/* Load More Button */
+.load-more,
+.load-more-rows {
+ display: block;
+ margin: 2rem auto;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .catalog-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .feature-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .nav-menu {
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+ .cookie-content {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+
+/* Inspector Mode */
+#inspector-btn.active {
+ background: var(--primary-color) !important;
+ color: var(--bg-primary) !important;
+}
+
+.inspector-tooltip {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
diff --git a/docs/examples/c4a_script/tutorial/requirements.txt b/docs/examples/c4a_script/tutorial/requirements.txt
new file mode 100644
index 00000000..2a6bcb37
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/requirements.txt
@@ -0,0 +1,2 @@
+flask>=2.3.0
+flask-cors>=4.0.0
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/scripts/01-basic-interaction.c4a b/docs/examples/c4a_script/tutorial/scripts/01-basic-interaction.c4a
new file mode 100644
index 00000000..d42bd001
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/scripts/01-basic-interaction.c4a
@@ -0,0 +1,18 @@
+# Basic Page Interaction
+# This script demonstrates basic C4A commands
+
+# Navigate to the playground
+GO http://127.0.0.1:8080/playground/
+
+# Wait for page to load
+WAIT `body` 2
+
+# Handle cookie banner if present
+IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+
+# Close newsletter popup if it appears
+WAIT 3
+IF (EXISTS `.newsletter-popup`) THEN CLICK `.close`
+
+# Click the start tutorial button
+CLICK `#start-tutorial`
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/scripts/02-login-flow.c4a b/docs/examples/c4a_script/tutorial/scripts/02-login-flow.c4a
new file mode 100644
index 00000000..ebff2de2
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/scripts/02-login-flow.c4a
@@ -0,0 +1,27 @@
+# Complete Login Flow
+# Demonstrates form interaction and authentication
+
+# Click login button
+CLICK `#login-btn`
+
+# Wait for login modal
+WAIT `.login-form` 3
+
+# Fill in credentials
+CLICK `#email`
+TYPE "demo@example.com"
+
+CLICK `#password`
+TYPE "demo123"
+
+# Check remember me
+IF (EXISTS `#remember-me`) THEN CLICK `#remember-me`
+
+# Submit form
+CLICK `button[type="submit"]`
+
+# Wait for success
+WAIT `.welcome-message` 5
+
+# Verify login succeeded
+IF (EXISTS `.user-info`) THEN EVAL `console.log('✅ Login successful!')`
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/scripts/03-infinite-scroll.c4a b/docs/examples/c4a_script/tutorial/scripts/03-infinite-scroll.c4a
new file mode 100644
index 00000000..fa6f5e14
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/scripts/03-infinite-scroll.c4a
@@ -0,0 +1,32 @@
+# Infinite Scroll Product Loading
+# Load all products using scroll automation
+
+# Navigate to catalog
+CLICK `#catalog-link`
+WAIT `.product-grid` 3
+
+# Switch to infinite scroll mode
+CLICK `#infinite-scroll-btn`
+
+# Define scroll procedure
+PROC load_more_products
+ # Get current product count
+ EVAL `window.initialCount = document.querySelectorAll('.product-card').length`
+
+ # Scroll down
+ SCROLL DOWN 1000
+ WAIT 2
+
+ # Check if more products loaded
+ EVAL `
+ const newCount = document.querySelectorAll('.product-card').length;
+ console.log('Products loaded: ' + newCount);
+ window.moreLoaded = newCount > window.initialCount;
+ `
+ENDPROC
+
+# Load products until no more
+REPEAT (load_more_products, `window.moreLoaded !== false`)
+
+# Final count
+EVAL `console.log('✅ Total products: ' + document.querySelectorAll('.product-card').length)`
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/scripts/04-multi-step-form.c4a b/docs/examples/c4a_script/tutorial/scripts/04-multi-step-form.c4a
new file mode 100644
index 00000000..01b710b0
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/scripts/04-multi-step-form.c4a
@@ -0,0 +1,41 @@
+# Multi-step Form Wizard
+# Complete a complex form with multiple steps
+
+# Navigate to forms section
+CLICK `a[href="#forms"]`
+WAIT `#survey-form` 2
+
+# Step 1: Basic Information
+CLICK `#full-name`
+TYPE "John Doe"
+
+CLICK `#survey-email`
+TYPE "john.doe@example.com"
+
+# Go to next step
+CLICK `.next-step`
+WAIT 1
+
+# Step 2: Select Interests
+# Select multiple options
+CLICK `#interests`
+CLICK `option[value="tech"]`
+CLICK `option[value="music"]`
+CLICK `option[value="travel"]`
+
+# Continue to final step
+CLICK `.next-step`
+WAIT 1
+
+# Step 3: Review and Submit
+# Verify we're on the last step
+IF (EXISTS `#submit-survey`) THEN EVAL `console.log('📋 On final step')`
+
+# Submit the form
+CLICK `#submit-survey`
+
+# Wait for success message
+WAIT `.success-message` 5
+
+# Verify submission
+IF (EXISTS `.success-message`) THEN EVAL `console.log('✅ Survey submitted successfully!')`
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/scripts/05-complex-workflow.c4a b/docs/examples/c4a_script/tutorial/scripts/05-complex-workflow.c4a
new file mode 100644
index 00000000..0d71a26c
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/scripts/05-complex-workflow.c4a
@@ -0,0 +1,82 @@
+# Complete E-commerce Workflow
+# Login, browse products, and interact with various elements
+
+# Define reusable procedures
+PROC handle_popups
+ IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+ IF (EXISTS `.newsletter-popup`) THEN CLICK `.close`
+ENDPROC
+
+PROC login_user
+ CLICK `#login-btn`
+ WAIT `.login-form` 2
+ CLICK `#email`
+ TYPE "demo@example.com"
+ CLICK `#password`
+ TYPE "demo123"
+ CLICK `button[type="submit"]`
+ WAIT `.welcome-message` 5
+ENDPROC
+
+PROC browse_products
+ # Go to catalog
+ CLICK `#catalog-link`
+ WAIT `.product-grid` 3
+
+ # Apply filters
+ CLICK `.collapsible`
+ WAIT 0.5
+ CLICK `input[type="checkbox"]`
+
+ # Load some products
+ SCROLL DOWN 500
+ WAIT 1
+ SCROLL DOWN 500
+ WAIT 1
+ENDPROC
+
+# Main workflow
+GO http://127.0.0.1:8080/playground/
+WAIT `body` 2
+
+# Handle initial popups
+handle_popups
+
+# Login if not already
+IF (NOT EXISTS `.user-info`) THEN login_user
+
+# Browse products
+browse_products
+
+# Navigate to tabs demo
+CLICK `a[href="#tabs"]`
+WAIT `.tabs-container` 2
+
+# Interact with tabs
+CLICK `button[data-tab="reviews"]`
+WAIT 1
+
+# Load comments
+IF (EXISTS `.load-comments`) THEN CLICK `.load-comments`
+WAIT `.comments-section` 2
+
+# Check specifications
+CLICK `button[data-tab="specs"]`
+WAIT 1
+
+# Final navigation to data tables
+CLICK `a[href="#data"]`
+WAIT `.data-table` 2
+
+# Search in table
+CLICK `.search-input`
+TYPE "User"
+
+# Load more rows
+CLICK `.load-more-rows`
+WAIT 1
+
+# Export data
+CLICK `#export-btn`
+
+EVAL `console.log('✅ Workflow completed successfully!')`
\ No newline at end of file
diff --git a/docs/examples/c4a_script/tutorial/server.py b/docs/examples/c4a_script/tutorial/server.py
new file mode 100644
index 00000000..6242789d
--- /dev/null
+++ b/docs/examples/c4a_script/tutorial/server.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python3
+"""
+C4A-Script Tutorial Server
+Serves the tutorial app and provides C4A compilation API
+"""
+
+import sys
+import os
+from pathlib import Path
+from flask import Flask, render_template_string, request, jsonify, send_from_directory
+from flask_cors import CORS
+
+# Add parent directories to path to import crawl4ai
+sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent))
+
+try:
+ from crawl4ai.script import compile as c4a_compile
+ C4A_AVAILABLE = True
+except ImportError:
+ print("⚠️ C4A compiler not available. Using mock compiler.")
+ C4A_AVAILABLE = False
+
+app = Flask(__name__)
+CORS(app)
+
+# Serve static files
+@app.route('/')
+def index():
+ return send_from_directory('.', 'index.html')
+
+@app.route('/assets/')
+def serve_assets(path):
+ return send_from_directory('assets', path)
+
+@app.route('/playground/')
+def playground():
+ return send_from_directory('playground', 'index.html')
+
+@app.route('/playground/')
+def serve_playground(path):
+ return send_from_directory('playground', path)
+
+# API endpoint for C4A compilation
+@app.route('/api/compile', methods=['POST'])
+def compile_endpoint():
+ try:
+ data = request.get_json()
+ script = data.get('script', '')
+
+ if not script:
+ return jsonify({
+ 'success': False,
+ 'error': {
+ 'line': 1,
+ 'column': 1,
+ 'message': 'No script provided',
+ 'suggestion': 'Write some C4A commands'
+ }
+ })
+
+ if C4A_AVAILABLE:
+ # Use real C4A compiler
+ result = c4a_compile(script)
+
+ if result.success:
+ return jsonify({
+ 'success': True,
+ 'jsCode': result.js_code,
+ 'metadata': {
+ 'lineCount': len(result.js_code),
+ 'sourceLines': len(script.split('\n'))
+ }
+ })
+ else:
+ error = result.first_error
+ return jsonify({
+ 'success': False,
+ 'error': {
+ 'line': error.line,
+ 'column': error.column,
+ 'message': error.message,
+ 'suggestion': error.suggestions[0].message if error.suggestions else None,
+ 'code': error.code,
+ 'sourceLine': error.source_line
+ }
+ })
+ else:
+ # Use mock compiler for demo
+ result = mock_compile(script)
+ return jsonify(result)
+
+ except Exception as e:
+ return jsonify({
+ 'success': False,
+ 'error': {
+ 'line': 1,
+ 'column': 1,
+ 'message': f'Server error: {str(e)}',
+ 'suggestion': 'Check server logs'
+ }
+ }), 500
+
+def mock_compile(script):
+ """Simple mock compiler for demo when C4A is not available"""
+ lines = [line for line in script.split('\n') if line.strip() and not line.strip().startswith('#')]
+ js_code = []
+
+ for i, line in enumerate(lines):
+ line = line.strip()
+
+ try:
+ if line.startswith('GO '):
+ url = line[3:].strip()
+ # Handle relative URLs
+ if not url.startswith(('http://', 'https://')):
+ url = '/' + url.lstrip('/')
+ js_code.append(f"await page.goto('{url}');")
+
+ elif line.startswith('WAIT '):
+ parts = line[5:].strip().split(' ')
+ if parts[0].startswith('`'):
+ selector = parts[0].strip('`')
+ timeout = parts[1] if len(parts) > 1 else '5'
+ js_code.append(f"await page.waitForSelector('{selector}', {{ timeout: {timeout}000 }});")
+ else:
+ seconds = parts[0]
+ js_code.append(f"await page.waitForTimeout({seconds}000);")
+
+ elif line.startswith('CLICK '):
+ selector = line[6:].strip().strip('`')
+ js_code.append(f"await page.click('{selector}');")
+
+ elif line.startswith('TYPE '):
+ text = line[5:].strip().strip('"')
+ js_code.append(f"await page.keyboard.type('{text}');")
+
+ elif line.startswith('SCROLL '):
+ parts = line[7:].strip().split(' ')
+ direction = parts[0]
+ amount = parts[1] if len(parts) > 1 else '500'
+ if direction == 'DOWN':
+ js_code.append(f"await page.evaluate(() => window.scrollBy(0, {amount}));")
+ elif direction == 'UP':
+ js_code.append(f"await page.evaluate(() => window.scrollBy(0, -{amount}));")
+
+ elif line.startswith('IF '):
+ if 'THEN' not in line:
+ return {
+ 'success': False,
+ 'error': {
+ 'line': i + 1,
+ 'column': len(line),
+ 'message': "Missing 'THEN' keyword after IF condition",
+ 'suggestion': "Add 'THEN' after the condition",
+ 'sourceLine': line
+ }
+ }
+
+ condition = line[3:line.index('THEN')].strip()
+ action = line[line.index('THEN') + 4:].strip()
+
+ if 'EXISTS' in condition:
+ selector_match = condition.split('`')
+ if len(selector_match) >= 2:
+ selector = selector_match[1]
+ action_selector = action.split('`')[1] if '`' in action else ''
+ js_code.append(
+ f"if (await page.$$('{selector}').length > 0) {{ "
+ f"await page.click('{action_selector}'); }}"
+ )
+
+ elif line.startswith('PRESS '):
+ key = line[6:].strip()
+ js_code.append(f"await page.keyboard.press('{key}');")
+
+ else:
+ # Unknown command
+ return {
+ 'success': False,
+ 'error': {
+ 'line': i + 1,
+ 'column': 1,
+ 'message': f"Unknown command: {line.split()[0]}",
+ 'suggestion': "Check command syntax",
+ 'sourceLine': line
+ }
+ }
+
+ except Exception as e:
+ return {
+ 'success': False,
+ 'error': {
+ 'line': i + 1,
+ 'column': 1,
+ 'message': f"Failed to parse: {str(e)}",
+ 'suggestion': "Check syntax",
+ 'sourceLine': line
+ }
+ }
+
+ return {
+ 'success': True,
+ 'jsCode': js_code,
+ 'metadata': {
+ 'lineCount': len(js_code),
+ 'sourceLines': len(lines)
+ }
+ }
+
+# Example scripts endpoint
+@app.route('/api/examples')
+def get_examples():
+ examples = [
+ {
+ 'id': 'cookie-banner',
+ 'name': 'Handle Cookie Banner',
+ 'description': 'Accept cookies and close newsletter popup',
+ 'script': '''# Handle cookie banner and newsletter
+GO http://127.0.0.1:8080/playground/
+WAIT `body` 2
+IF (EXISTS `.cookie-banner`) THEN CLICK `.accept`
+IF (EXISTS `.newsletter-popup`) THEN CLICK `.close`'''
+ },
+ {
+ 'id': 'login',
+ 'name': 'Login Flow',
+ 'description': 'Complete login with credentials',
+ 'script': '''# Login to the site
+CLICK `#login-btn`
+WAIT `.login-form` 2
+CLICK `#email`
+TYPE "demo@example.com"
+CLICK `#password`
+TYPE "demo123"
+IF (EXISTS `#remember-me`) THEN CLICK `#remember-me`
+CLICK `button[type="submit"]`
+WAIT `.welcome-message` 5'''
+ },
+ {
+ 'id': 'infinite-scroll',
+ 'name': 'Infinite Scroll',
+ 'description': 'Load products with scrolling',
+ 'script': '''# Navigate to catalog and scroll
+CLICK `#catalog-link`
+WAIT `.product-grid` 3
+
+# Scroll multiple times to load products
+SCROLL DOWN 1000
+WAIT 1
+SCROLL DOWN 1000
+WAIT 1
+SCROLL DOWN 1000'''
+ },
+ {
+ 'id': 'form-wizard',
+ 'name': 'Multi-step Form',
+ 'description': 'Complete a multi-step survey',
+ 'script': '''# Navigate to forms
+CLICK `a[href="#forms"]`
+WAIT `#survey-form` 2
+
+# Step 1: Basic info
+CLICK `#full-name`
+TYPE "John Doe"
+CLICK `#survey-email`
+TYPE "john@example.com"
+CLICK `.next-step`
+WAIT 1
+
+# Step 2: Preferences
+CLICK `#interests`
+CLICK `option[value="tech"]`
+CLICK `option[value="music"]`
+CLICK `.next-step`
+WAIT 1
+
+# Step 3: Submit
+CLICK `#submit-survey`
+WAIT `.success-message` 5'''
+ }
+ ]
+
+ return jsonify(examples)
+
+if __name__ == '__main__':
+ port = int(os.environ.get('PORT', 8080))
+ print(f"""
+╔══════════════════════════════════════════════════════════╗
+║ C4A-Script Interactive Tutorial Server ║
+╠══════════════════════════════════════════════════════════╣
+║ ║
+║ Server running at: http://localhost:{port:<6} ║
+║ ║
+║ Features: ║
+║ • C4A-Script compilation API ║
+║ • Interactive playground ║
+║ • Real-time execution visualization ║
+║ ║
+║ C4A Compiler: {'✓ Available' if C4A_AVAILABLE else '✗ Using mock compiler':<30} ║
+║ ║
+╚══════════════════════════════════════════════════════════╝
+ """)
+
+ app.run(host='0.0.0.0', port=port, debug=True)
\ No newline at end of file