diff --git a/README.md b/README.md
index 0a26525..6f17d24 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,13 @@ Perfect for **Cursor users** who want to automate their local testing and develo
- **"Monitor this webpage and notify me when the content changes"**
- **"Automatically bookmark interesting articles I'm reading"**
+### šØ Visual Customization & Fun
+- **"Apply a cyberpunk theme to this documentation site to make it more engaging"**
+- **"Make this page dark mode with green text for late-night reading"**
+- **"Add rainbow party effects to celebrate finishing this project"**
+- **"Transform this boring form with a retro 80s theme while I fill it out"**
+- **"Use high contrast styling so I can read this better"**
+
## ā” Quick Start
### 1. Install the Browser Extension
@@ -152,7 +159,7 @@ ngrok config add-authtoken YOUR_TOKEN_HERE
## š ļø Capabilities
-OpenDia gives AI models **17 powerful browser tools**:
+OpenDia gives AI models **18 powerful browser tools**:
### šÆ Smart Page Understanding
- **Analyze any webpage** - AI automatically finds buttons, forms, and interactive elements
@@ -180,6 +187,13 @@ OpenDia gives AI models **17 powerful browser tools**:
- **Natural interactions** - Mimics human behavior to avoid triggering security measures
- **Reliable automation** - Works consistently even on sites that block typical automation tools
+### šØ Page Styling & Customization
+- **Transform any website** - Apply fun themes, custom colors, and visual effects
+- **Preset themes** - Dark hacker, retro 80s, rainbow party, minimalist zen, and more
+- **AI mood styling** - Describe a mood and get matching visual design
+- **Interactive effects** - Matrix rain, floating particles, neon glow, and cursor trails
+- **Accessibility themes** - High contrast and readable designs for better visibility
+
## š¬ Example Prompts to Try
Once everything is set up, try asking your AI:
@@ -202,6 +216,15 @@ Once everything is set up, try asking your AI:
**Personal Assistant:**
> *"Find that GitHub repo I was looking at yesterday about React components and bookmark it for later"*
+**Page Styling & Fun:**
+> *"Apply a dark hacker theme to this page to make it look more interesting"*
+
+> *"Make this boring documentation page feel like a cozy coffee shop"*
+
+> *"Add some matrix rain effects to this page for 30 seconds for a cool screenshot"*
+
+> *"Transform this page with a high contrast theme for better readability"*
+
## šļø How It Works
```mermaid
diff --git a/opendia-extension/background.js b/opendia-extension/background.js
index 807933d..eac2541 100644
--- a/opendia-extension/background.js
+++ b/opendia-extension/background.js
@@ -766,6 +766,71 @@ function getAvailableTools() {
}
}
},
+ {
+ name: "page_style",
+ description: "šØ Transform page appearance with themes, colors, fonts, and fun effects! Apply preset themes like 'dark_hacker', 'retro_80s', or create custom styles. Perfect for making boring pages fun or improving readability.",
+ inputSchema: {
+ type: "object",
+ examples: [
+ { mode: "preset", theme: "dark_hacker" },
+ { mode: "custom", background: "#000", text_color: "#00ff00", font: "monospace" },
+ { mode: "ai_mood", mood: "cozy coffee shop vibes", intensity: "strong" },
+ { mode: "effect", effect: "matrix_rain", duration: 30 }
+ ],
+ properties: {
+ mode: {
+ type: "string",
+ enum: ["preset", "custom", "ai_mood", "effect", "reset"],
+ description: "Styling mode to use"
+ },
+ theme: {
+ type: "string",
+ enum: ["dark_hacker", "retro_80s", "rainbow_party", "minimalist_zen", "high_contrast", "cyberpunk", "pastel_dream", "newspaper"],
+ description: "Preset theme name (when mode=preset)"
+ },
+ background: {
+ type: "string",
+ description: "Background color/gradient"
+ },
+ text_color: {
+ type: "string",
+ description: "Text color"
+ },
+ font: {
+ type: "string",
+ description: "Font family"
+ },
+ font_size: {
+ type: "string",
+ description: "Font size (e.g., '1.2em', '16px')"
+ },
+ mood: {
+ type: "string",
+ description: "Describe desired mood/feeling (when mode=ai_mood)"
+ },
+ intensity: {
+ type: "string",
+ enum: ["subtle", "medium", "strong"],
+ default: "medium"
+ },
+ effect: {
+ type: "string",
+ enum: ["matrix_rain", "floating_particles", "cursor_trail", "neon_glow", "typing_effect"]
+ },
+ duration: {
+ type: "number",
+ description: "Effect duration in seconds",
+ default: 10
+ },
+ remember: {
+ type: "boolean",
+ description: "Remember this style for this website",
+ default: false
+ }
+ },
+ required: ["mode"]
+ }
+ },
];
}
@@ -840,6 +905,9 @@ async function handleMCPRequest(message) {
case "get_page_links":
result = await sendToContentScript('get_page_links', params, params.tab_id);
break;
+ case "page_style":
+ result = await sendToContentScript('page_style', params, params.tab_id);
+ break;
default:
throw new Error(`Unknown method: ${method}`);
}
diff --git a/opendia-extension/content.js b/opendia-extension/content.js
index 6496d5e..9a98cd4 100644
--- a/opendia-extension/content.js
+++ b/opendia-extension/content.js
@@ -283,6 +283,9 @@ class BrowserAutomation {
case "page_scroll":
result = await this.scrollPage(data);
break;
+ case "page_style":
+ result = await this.handlePageStyle(data);
+ break;
case "ping":
// Health check for background tab content script readiness
result = { status: "ready", timestamp: Date.now(), url: window.location.href };
@@ -2507,7 +2510,375 @@ class BrowserAutomation {
};
}
}
+
+ // šØ Page Styling System
+ async handlePageStyle(data) {
+ const { mode, theme, background, text_color, font, font_size, mood, intensity, effect, duration, remember } = data;
+
+ // Remove existing custom styles
+ const existingStyle = document.getElementById('opendia-custom-style');
+ if (existingStyle) existingStyle.remove();
+
+ let css = '';
+ let description = '';
+
+ try {
+ switch (mode) {
+ case 'preset':
+ const themeData = THEME_PRESETS[theme];
+ if (!themeData) throw new Error(`Unknown theme: ${theme}`);
+ css = themeData.css;
+ description = `Applied ${themeData.name} theme`;
+ break;
+
+ case 'custom':
+ css = this.buildCustomCSS({ background, text_color, font, font_size });
+ description = 'Applied custom styling';
+ break;
+
+ case 'ai_mood':
+ css = this.generateMoodCSS(mood, intensity);
+ description = `Applied AI-generated style for mood: "${mood}"`;
+ break;
+
+ case 'effect':
+ css = this.applyEffect(effect, duration);
+ description = `Applied ${effect} effect for ${duration}s`;
+ break;
+
+ case 'reset':
+ // CSS already removed above
+ description = 'Reset page to original styling';
+ break;
+
+ default:
+ throw new Error(`Unknown styling mode: ${mode}`);
+ }
+
+ if (css) {
+ const styleElement = document.createElement('style');
+ styleElement.id = 'opendia-custom-style';
+ styleElement.textContent = css;
+ document.head.appendChild(styleElement);
+ }
+
+ // Remember preference if requested
+ if (remember && mode !== 'reset') {
+ const domain = window.location.hostname;
+ chrome.storage.local.set({ [`style_${domain}`]: { mode, theme, css } });
+ }
+
+ return {
+ success: true,
+ description,
+ applied_css: css.length,
+ mode,
+ theme: theme || 'custom',
+ remember_enabled: remember,
+ effect_duration: duration,
+ mood,
+ intensity
+ };
+
+ } catch (error) {
+ return {
+ success: false,
+ error: error.message,
+ mode,
+ theme,
+ applied_css: 0
+ };
+ }
+ }
+
+ buildCustomCSS({ background, text_color, font, font_size }) {
+ let css = '';
+
+ if (background || text_color || font || font_size) {
+ css += '* { ';
+ if (background) css += `background: ${background} !important; `;
+ if (text_color) css += `color: ${text_color} !important; `;
+ if (font) css += `font-family: ${font} !important; `;
+ if (font_size) css += `font-size: ${font_size} !important; `;
+ css += '}';
+ }
+
+ return css;
+ }
+
+ generateMoodCSS(mood, intensity) {
+ const moodMap = {
+ 'cozy coffee shop': {
+ background: '#2c1810',
+ text: '#f4e4bc',
+ accent: '#d4af37',
+ font: 'Georgia, serif'
+ },
+ 'energetic': {
+ background: 'linear-gradient(45deg, #ff6b35, #f7931e)',
+ text: '#ffffff',
+ effects: 'animation: energyPulse 1s infinite;'
+ },
+ 'calm ocean': {
+ background: 'linear-gradient(to bottom, #87ceeb, #4682b4)',
+ text: '#ffffff',
+ effects: 'animation: gentleWave 4s ease-in-out infinite;'
+ },
+ 'dark professional': {
+ background: '#1a1a1a',
+ text: '#e0e0e0',
+ accent: '#0066cc'
+ },
+ 'warm sunset': {
+ background: 'linear-gradient(to bottom, #ff7e5f, #feb47b)',
+ text: '#ffffff'
+ }
+ };
+
+ const style = moodMap[mood.toLowerCase()] || moodMap['cozy coffee shop'];
+ return this.buildMoodCSS(style, intensity);
+ }
+
+ buildMoodCSS(style, intensity) {
+ const opacity = intensity === 'subtle' ? '0.3' : intensity === 'medium' ? '0.6' : '0.9';
+
+ let css = `
+ body {
+ background: ${style.background} !important;
+ color: ${style.text} !important;
+ ${style.font ? `font-family: ${style.font} !important;` : ''}
+ }
+
+ * {
+ color: ${style.text} !important;
+ }
+
+ a {
+ color: ${style.accent || style.text} !important;
+ }
+ `;
+
+ if (style.effects) {
+ css += style.effects;
+ }
+
+ // Add animation keyframes if needed
+ if (style.effects && style.effects.includes('energyPulse')) {
+ css += `
+ @keyframes energyPulse {
+ 0%, 100% { filter: hue-rotate(0deg); }
+ 50% { filter: hue-rotate(180deg); }
+ }
+ `;
+ }
+
+ if (style.effects && style.effects.includes('gentleWave')) {
+ css += `
+ @keyframes gentleWave {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ }
+ `;
+ }
+
+ return css;
+ }
+
+ applyEffect(effect, duration) {
+ const effects = {
+ matrix_rain: `
+ body::after {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url('data:image/svg+xml;utf8,');
+ animation: matrixFall 2s linear infinite;
+ pointer-events: none;
+ z-index: 9999;
+ opacity: 0.7;
+ }
+ @keyframes matrixFall {
+ from { transform: translateY(-100px); }
+ to { transform: translateY(100vh); }
+ }
+ `,
+ floating_particles: `
+ body::before {
+ content: '⨠š ā š«';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ animation: floatParticles 6s ease-in-out infinite;
+ pointer-events: none;
+ z-index: 9999;
+ font-size: 20px;
+ }
+ @keyframes floatParticles {
+ 0%, 100% { transform: translateY(100vh) rotate(0deg); }
+ 50% { transform: translateY(-100px) rotate(180deg); }
+ }
+ `,
+ cursor_trail: `
+ body {
+ cursor: url('data:image/svg+xml;utf8,'), auto !important;
+ }
+ `,
+ neon_glow: `
+ * {
+ text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 30px #00ffff !important;
+ }
+
+ a, button {
+ box-shadow: 0 0 15px #ff00ff !important;
+ }
+ `,
+ typing_effect: `
+ * {
+ animation: typewriter 2s steps(40, end) infinite !important;
+ }
+ @keyframes typewriter {
+ from { width: 0; }
+ to { width: 100%; }
+ }
+ `
+ };
+
+ const css = effects[effect] || '';
+
+ // Auto-remove effect after duration
+ if (duration && duration > 0) {
+ setTimeout(() => {
+ const effectStyle = document.getElementById('opendia-custom-style');
+ if (effectStyle) effectStyle.remove();
+ }, duration * 1000);
+ }
+
+ return css;
+ }
}
+// Theme Presets Database
+const THEME_PRESETS = {
+ "dark_hacker": {
+ name: "š¤ Dark Hacker",
+ css: `
+ * {
+ background: #0a0a0a !important;
+ color: #00ff00 !important;
+ font-family: 'Courier New', monospace !important;
+ }
+ a { color: #00ffff !important; }
+ body::before {
+ content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+ background: url('data:image/svg+xml;utf8,');
+ opacity: 0.1; pointer-events: none; z-index: -1;
+ }
+ `
+ },
+ "retro_80s": {
+ name: "š¼ Retro 80s",
+ css: `
+ * {
+ background: linear-gradient(45deg, #ff0080, #8000ff) !important;
+ color: #ffffff !important;
+ font-family: 'Arial Black', sans-serif !important;
+ text-shadow: 2px 2px 4px #000000 !important;
+ }
+ body { animation: retroPulse 2s infinite; }
+ @keyframes retroPulse { 0%, 100% { filter: hue-rotate(0deg); } 50% { filter: hue-rotate(180deg); } }
+ `
+ },
+ "rainbow_party": {
+ name: "š Rainbow Party",
+ css: `
+ body {
+ background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet) !important;
+ background-size: 400% 400% !important;
+ animation: rainbowShift 3s ease infinite !important;
+ }
+ @keyframes rainbowShift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ }
+ * { color: white !important; text-shadow: 1px 1px 2px black !important; }
+ `
+ },
+ "minimalist_zen": {
+ name: "š§ Minimalist Zen",
+ css: `
+ * {
+ background: #f8f8f8 !important;
+ color: #333333 !important;
+ font-family: 'Georgia', serif !important;
+ line-height: 1.6 !important;
+ }
+ body { max-width: 800px; margin: 0 auto; padding: 20px; }
+ `
+ },
+ "high_contrast": {
+ name: "š High Contrast",
+ css: `
+ * {
+ background: #000000 !important;
+ color: #ffffff !important;
+ font-family: Arial, sans-serif !important;
+ font-weight: bold !important;
+ }
+ a { color: #ffff00 !important; }
+ `
+ },
+ "cyberpunk": {
+ name: "š¤ Cyberpunk",
+ css: `
+ * {
+ background: #0d1117 !important;
+ color: #ff006e !important;
+ font-family: 'Courier New', monospace !important;
+ }
+ a { color: #00ffff !important; }
+ body {
+ background-image:
+ linear-gradient(90deg, transparent 79px, #abced4 79px, #abced4 81px, transparent 81px),
+ linear-gradient(#eee .1em, transparent .1em);
+ background-size: 81px 1.2em;
+ }
+ `
+ },
+ "pastel_dream": {
+ name: "šø Pastel Dream",
+ css: `
+ * {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+ color: #2c3e50 !important;
+ font-family: 'Comic Sans MS', cursive !important;
+ }
+ body { filter: sepia(20%) saturate(80%); }
+ `
+ },
+ "newspaper": {
+ name: "š° Newspaper",
+ css: `
+ * {
+ background: #ffffff !important;
+ color: #000000 !important;
+ font-family: 'Times New Roman', serif !important;
+ line-height: 1.4 !important;
+ }
+ body {
+ column-count: 2;
+ column-gap: 2em;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+ `
+ }
+};
+
// Initialize the automation system
const browserAutomation = new BrowserAutomation();
diff --git a/opendia-mcp/server.js b/opendia-mcp/server.js
index 2c89cd6..f4edbab 100755
--- a/opendia-mcp/server.js
+++ b/opendia-mcp/server.js
@@ -495,6 +495,9 @@ function formatToolResult(toolName, result) {
case "element_get_state":
return formatElementStateResult(result, metadata);
+ case "page_style":
+ return formatPageStyleResult(result, metadata);
+
default:
// Legacy tools or unknown tools
return JSON.stringify(result, null, 2);
@@ -949,6 +952,56 @@ function formatElementStateResult(result, metadata) {
${JSON.stringify(metadata, null, 2)}`;
}
+function formatPageStyleResult(result, metadata) {
+ const successIcon = result.success ? 'ā
' : 'ā';
+ const statusText = result.success ? 'successfully applied' : 'failed to apply';
+
+ let summary = `šØ Page styling ${statusText}\n\n`;
+
+ summary += `š **Operation Details:**\n`;
+ summary += `⢠**Mode:** ${result.mode || 'Unknown'}\n`;
+
+ if (result.theme) {
+ summary += `⢠**Theme:** ${result.theme}\n`;
+ }
+
+ if (result.applied_css !== undefined) {
+ summary += `⢠**CSS Applied:** ${result.applied_css} characters\n`;
+ }
+
+ if (result.description) {
+ summary += `⢠**Result:** ${result.description}\n`;
+ }
+
+ if (result.remember_enabled) {
+ summary += `⢠**Saved:** Style preferences saved for this domain\n`;
+ }
+
+ if (result.effect_duration) {
+ summary += `⢠**Effect Duration:** ${result.effect_duration} seconds\n`;
+ }
+
+ if (result.mood) {
+ summary += `⢠**Mood Applied:** "${result.mood}"\n`;
+ }
+
+ if (result.intensity) {
+ summary += `⢠**Intensity:** ${result.intensity}\n`;
+ }
+
+ if (!result.success && result.error) {
+ summary += `\nā **Error:** ${result.error}\n`;
+ }
+
+ if (result.warning) {
+ summary += `\nā ļø **Warning:** ${result.warning}\n`;
+ }
+
+ summary += `\nš” **Tip:** Use mode="reset" to restore original page styling`;
+
+ return `${summary}\n\n${JSON.stringify(metadata, null, 2)}`;
+}
+
// Enhanced fallback tools when extension is not connected
function getFallbackTools() {
return [
@@ -1394,6 +1447,71 @@ function getFallbackTools() {
}
}
},
+ {
+ name: "page_style",
+ description: "šØ Transform page appearance with themes, colors, fonts, and fun effects! Apply preset themes like 'dark_hacker', 'retro_80s', or create custom styles. Perfect for making boring pages fun or improving readability.",
+ inputSchema: {
+ type: "object",
+ examples: [
+ { mode: "preset", theme: "dark_hacker" },
+ { mode: "custom", background: "#000", text_color: "#00ff00", font: "monospace" },
+ { mode: "ai_mood", mood: "cozy coffee shop vibes", intensity: "strong" },
+ { mode: "effect", effect: "matrix_rain", duration: 30 }
+ ],
+ properties: {
+ mode: {
+ type: "string",
+ enum: ["preset", "custom", "ai_mood", "effect", "reset"],
+ description: "Styling mode to use"
+ },
+ theme: {
+ type: "string",
+ enum: ["dark_hacker", "retro_80s", "rainbow_party", "minimalist_zen", "high_contrast", "cyberpunk", "pastel_dream", "newspaper"],
+ description: "Preset theme name (when mode=preset)"
+ },
+ background: {
+ type: "string",
+ description: "Background color/gradient"
+ },
+ text_color: {
+ type: "string",
+ description: "Text color"
+ },
+ font: {
+ type: "string",
+ description: "Font family"
+ },
+ font_size: {
+ type: "string",
+ description: "Font size (e.g., '1.2em', '16px')"
+ },
+ mood: {
+ type: "string",
+ description: "Describe desired mood/feeling (when mode=ai_mood)"
+ },
+ intensity: {
+ type: "string",
+ enum: ["subtle", "medium", "strong"],
+ default: "medium"
+ },
+ effect: {
+ type: "string",
+ enum: ["matrix_rain", "floating_particles", "cursor_trail", "neon_glow", "typing_effect"]
+ },
+ duration: {
+ type: "number",
+ description: "Effect duration in seconds",
+ default: 10
+ },
+ remember: {
+ type: "boolean",
+ description: "Remember this style for this website",
+ default: false
+ }
+ },
+ required: ["mode"]
+ }
+ },
];
}