1006 lines
32 KiB
Python
1006 lines
32 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk, filedialog, messagebox
|
|
import os
|
|
import json
|
|
import shutil
|
|
|
|
class Web3Builder:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("Web3 Site Builder")
|
|
self.root.geometry("1200x700")
|
|
|
|
self.components = []
|
|
self.selected_component = None
|
|
self.drag_data = {"x": 0, "y": 0, "item": None}
|
|
|
|
self.bg_color = "#1a1a2e"
|
|
self.sidebar_color = "#16213e"
|
|
self.text_color = "#ffffff"
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
style = ttk.Style()
|
|
style.theme_use('clam')
|
|
|
|
main_container = tk.Frame(self.root, bg=self.bg_color)
|
|
main_container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
sidebar = tk.Frame(main_container, width=200, bg=self.sidebar_color)
|
|
sidebar.pack(side=tk.LEFT, fill=tk.Y)
|
|
sidebar.pack_propagate(False)
|
|
|
|
tk.Label(sidebar, text="Web3 Components", bg=self.sidebar_color,
|
|
fg=self.text_color, font=('Arial', 12, 'bold')).pack(pady=20)
|
|
|
|
components = [
|
|
("Wallet Connect", "🦊", "wallet_connect"),
|
|
("NFT Gallery", "🖼️", "nft_gallery"),
|
|
("Token Balance", "💰", "token_balance"),
|
|
("Swap Interface", "🔄", "swap_interface"),
|
|
("DAO Voting", "🗳️", "dao_voting"),
|
|
("Header", "📝", "header"),
|
|
("Paragraph", "📄", "paragraph"),
|
|
("Button", "🔘", "button"),
|
|
("Image", "🖼️", "image"),
|
|
("Footer", "👇", "footer")
|
|
]
|
|
|
|
for name, icon, tag in components:
|
|
btn = tk.Button(sidebar, text=f"{icon} {name}",
|
|
bg="#0f3460", fg=self.text_color,
|
|
relief=tk.FLAT, font=('Arial', 10),
|
|
command=lambda t=tag: self.add_component(t))
|
|
btn.pack(pady=5, padx=10, fill=tk.X)
|
|
|
|
self.properties_panel = tk.Frame(main_container, width=300, bg=self.sidebar_color)
|
|
self.properties_panel.pack(side=tk.RIGHT, fill=tk.Y)
|
|
self.properties_panel.pack_propagate(False)
|
|
|
|
tk.Label(self.properties_panel, text="Properties", bg=self.sidebar_color,
|
|
fg=self.text_color, font=('Arial', 12, 'bold')).pack(pady=20)
|
|
|
|
self.canvas = tk.Canvas(main_container, bg=self.bg_color, highlightthickness=0)
|
|
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
self.canvas.bind("<Button-1>", self.start_drag)
|
|
self.canvas.bind("<B1-Motion>", self.drag)
|
|
self.canvas.bind("<ButtonRelease-1>", self.stop_drag)
|
|
self.canvas.bind("<Button-3>", self.select_component)
|
|
|
|
export_btn = tk.Button(sidebar, text="🚀 Export Website",
|
|
bg="#4CAF50", fg="white",
|
|
font=('Arial', 11, 'bold'),
|
|
command=self.export_site)
|
|
export_btn.pack(side=tk.BOTTOM, pady=20, padx=10, fill=tk.X)
|
|
|
|
self.init_properties_panel()
|
|
self.add_default_components()
|
|
|
|
def add_default_components(self):
|
|
header = {
|
|
'id': 'header_default',
|
|
'type': 'header',
|
|
'x': 50, 'y': 30,
|
|
'width': 800, 'height': 80,
|
|
'text': 'My Web3 Website',
|
|
'color': '#ffffff',
|
|
'bg_color': '#0f3460',
|
|
'font_size': 36
|
|
}
|
|
self.components.append(header)
|
|
self.draw_component(header)
|
|
|
|
def add_component(self, component_type):
|
|
component_id = f"{component_type}_{len(self.components) + 1}"
|
|
|
|
defaults = {
|
|
'header': {
|
|
'type': 'header',
|
|
'text': 'New Header',
|
|
'color': '#ffffff',
|
|
'bg_color': '#0f3460',
|
|
'font_size': 24,
|
|
'width': 400, 'height': 60
|
|
},
|
|
'paragraph': {
|
|
'type': 'paragraph',
|
|
'text': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
'color': '#cccccc',
|
|
'font_size': 14,
|
|
'width': 400, 'height': 100
|
|
},
|
|
'button': {
|
|
'type': 'button',
|
|
'text': 'Click Me',
|
|
'color': '#ffffff',
|
|
'bg_color': '#4CAF50',
|
|
'width': 120, 'height': 40
|
|
},
|
|
'wallet_connect': {
|
|
'type': 'wallet_connect',
|
|
'text': 'Connect Wallet',
|
|
'bg_color': '#f6851b',
|
|
'width': 200, 'height': 50
|
|
},
|
|
'nft_gallery': {
|
|
'type': 'nft_gallery',
|
|
'title': 'NFT Gallery',
|
|
'width': 600, 'height': 400
|
|
},
|
|
'token_balance': {
|
|
'type': 'token_balance',
|
|
'token': 'ETH',
|
|
'width': 300, 'height': 150
|
|
},
|
|
'swap_interface': {
|
|
'type': 'swap_interface',
|
|
'width': 400, 'height': 300
|
|
},
|
|
'dao_voting': {
|
|
'type': 'dao_voting',
|
|
'title': 'DAO Proposal',
|
|
'width': 500, 'height': 200
|
|
},
|
|
'image': {
|
|
'type': 'image',
|
|
'src': 'https://placehold.co/400x300',
|
|
'width': 400, 'height': 300
|
|
},
|
|
'footer': {
|
|
'type': 'footer',
|
|
'text': '© 2024 Web3 Website',
|
|
'bg_color': '#0f3460',
|
|
'width': 800, 'height': 60
|
|
}
|
|
}
|
|
|
|
component = defaults.get(component_type, defaults['header']).copy()
|
|
component['id'] = component_id
|
|
component['x'] = 100
|
|
component['y'] = 150 + len(self.components) * 20
|
|
|
|
self.components.append(component)
|
|
self.draw_component(component)
|
|
|
|
def draw_component(self, component):
|
|
x, y = component['x'], component['y']
|
|
width, height = component['width'], component['height']
|
|
comp_id = component['id']
|
|
|
|
rect = self.canvas.create_rectangle(
|
|
x, y, x + width, y + height,
|
|
fill=component.get('bg_color', '#2d4059'),
|
|
outline='#4a69bd',
|
|
width=2,
|
|
tags=('component', comp_id)
|
|
)
|
|
|
|
label_y = y + height + 15
|
|
self.canvas.create_text(
|
|
x + width/2, label_y,
|
|
text=component['type'].replace('_', ' ').title(),
|
|
fill='#cccccc',
|
|
font=('Arial', 9),
|
|
tags=('label', comp_id)
|
|
)
|
|
|
|
if component['type'] in ['header', 'button', 'wallet_connect']:
|
|
self.canvas.create_text(
|
|
x + width/2, y + height/2,
|
|
text=component.get('text', ''),
|
|
fill=component.get('color', '#ffffff'),
|
|
font=('Arial', component.get('font_size', 14)),
|
|
tags=('text', comp_id)
|
|
)
|
|
elif component['type'] == 'paragraph':
|
|
self.canvas.create_text(
|
|
x + width/2, y + height/2,
|
|
text=component.get('text', ''),
|
|
fill=component.get('color', '#cccccc'),
|
|
font=('Arial', component.get('font_size', 14)),
|
|
width=width - 20,
|
|
tags=('text', comp_id)
|
|
)
|
|
|
|
icons = {
|
|
'wallet_connect': '🦊',
|
|
'nft_gallery': '🖼️',
|
|
'token_balance': '💰',
|
|
'swap_interface': '🔄',
|
|
'dao_voting': '🗳️'
|
|
}
|
|
|
|
if component['type'] in icons:
|
|
self.canvas.create_text(
|
|
x + 30, y + 25,
|
|
text=icons[component['type']],
|
|
font=('Arial', 24),
|
|
tags=('icon', comp_id)
|
|
)
|
|
|
|
self.canvas.create_text(
|
|
x + width/2, y + height/2,
|
|
text=component.get('title', component['type'].replace('_', ' ').title()),
|
|
fill='#ffffff',
|
|
font=('Arial', 16, 'bold'),
|
|
tags=('title', comp_id)
|
|
)
|
|
|
|
def start_drag(self, event):
|
|
items = self.canvas.find_overlapping(event.x-1, event.y-1, event.x+1, event.y+1)
|
|
for item in items:
|
|
tags = self.canvas.gettags(item)
|
|
for tag in tags:
|
|
if tag in [comp['id'] for comp in self.components]:
|
|
self.drag_data["item"] = tag
|
|
self.drag_data["x"] = event.x
|
|
self.drag_data["y"] = event.y
|
|
self.selected_component = tag
|
|
self.show_properties(tag)
|
|
self.highlight_component(tag)
|
|
return
|
|
|
|
def drag(self, event):
|
|
if self.drag_data["item"]:
|
|
dx = event.x - self.drag_data["x"]
|
|
dy = event.y - self.drag_data["y"]
|
|
|
|
items = self.canvas.find_withtag(self.drag_data["item"])
|
|
for item in items:
|
|
self.canvas.move(item, dx, dy)
|
|
|
|
self.drag_data["x"] = event.x
|
|
self.drag_data["y"] = event.y
|
|
|
|
def stop_drag(self, event):
|
|
if self.drag_data["item"]:
|
|
for comp in self.components:
|
|
if comp['id'] == self.drag_data["item"]:
|
|
items = self.canvas.find_withtag(self.drag_data["item"])
|
|
for item in items:
|
|
if self.canvas.type(item) == 'rectangle':
|
|
coords = self.canvas.coords(item)
|
|
comp['x'] = coords[0]
|
|
comp['y'] = coords[1]
|
|
comp['width'] = coords[2] - coords[0]
|
|
comp['height'] = coords[3] - coords[1]
|
|
break
|
|
self.drag_data["item"] = None
|
|
|
|
def select_component(self, event):
|
|
items = self.canvas.find_overlapping(event.x-1, event.y-1, event.x+1, event.y+1)
|
|
for item in items:
|
|
tags = self.canvas.gettags(item)
|
|
for tag in tags:
|
|
if tag in [comp['id'] for comp in self.components]:
|
|
self.selected_component = tag
|
|
self.show_properties(tag)
|
|
self.highlight_component(tag)
|
|
return
|
|
|
|
self.selected_component = None
|
|
self.clear_highlights()
|
|
|
|
def highlight_component(self, component_id):
|
|
self.clear_highlights()
|
|
items = self.canvas.find_withtag(component_id)
|
|
for item in items:
|
|
if self.canvas.type(item) == 'rectangle':
|
|
self.canvas.itemconfig(item, outline='#00ff00', width=3)
|
|
|
|
def clear_highlights(self):
|
|
for item in self.canvas.find_withtag('component'):
|
|
self.canvas.itemconfig(item, outline='#4a69bd', width=2)
|
|
|
|
def init_properties_panel(self):
|
|
self.property_widgets = {}
|
|
|
|
properties_frame = tk.Frame(self.properties_panel, bg=self.sidebar_color)
|
|
properties_frame.pack(fill=tk.X, padx=10, pady=5)
|
|
|
|
tk.Label(properties_frame, text="Text:", bg=self.sidebar_color,
|
|
fg=self.text_color).grid(row=0, column=0, sticky='w', pady=5)
|
|
self.property_widgets['text'] = tk.Entry(properties_frame, width=25)
|
|
self.property_widgets['text'].grid(row=0, column=1, pady=5)
|
|
|
|
tk.Label(properties_frame, text="Width:", bg=self.sidebar_color,
|
|
fg=self.text_color).grid(row=1, column=0, sticky='w', pady=5)
|
|
self.property_widgets['width'] = tk.Scale(properties_frame, from_=50, to=800,
|
|
orient=tk.HORIZONTAL, length=180)
|
|
self.property_widgets['width'].grid(row=1, column=1, pady=5)
|
|
|
|
tk.Label(properties_frame, text="Height:", bg=self.sidebar_color,
|
|
fg=self.text_color).grid(row=2, column=0, sticky='w', pady=5)
|
|
self.property_widgets['height'] = tk.Scale(properties_frame, from_=30, to=600,
|
|
orient=tk.HORIZONTAL, length=180)
|
|
self.property_widgets['height'].grid(row=2, column=1, pady=5)
|
|
|
|
tk.Label(properties_frame, text="Text Color:", bg=self.sidebar_color,
|
|
fg=self.text_color).grid(row=3, column=0, sticky='w', pady=5)
|
|
self.property_widgets['color'] = tk.Entry(properties_frame, width=25)
|
|
self.property_widgets['color'].grid(row=3, column=1, pady=5)
|
|
self.property_widgets['color'].insert(0, "#ffffff")
|
|
|
|
tk.Label(properties_frame, text="BG Color:", bg=self.sidebar_color,
|
|
fg=self.text_color).grid(row=4, column=0, sticky='w', pady=5)
|
|
self.property_widgets['bg_color'] = tk.Entry(properties_frame, width=25)
|
|
self.property_widgets['bg_color'].grid(row=4, column=1, pady=5)
|
|
self.property_widgets['bg_color'].insert(0, "#0f3460")
|
|
|
|
update_btn = tk.Button(properties_frame, text="Update Properties",
|
|
command=self.update_properties,
|
|
bg="#4CAF50", fg="white")
|
|
update_btn.grid(row=5, column=0, columnspan=2, pady=20)
|
|
|
|
delete_btn = tk.Button(properties_frame, text="Delete Component",
|
|
command=self.delete_component,
|
|
bg="#f44336", fg="white")
|
|
delete_btn.grid(row=6, column=0, columnspan=2, pady=10)
|
|
|
|
def show_properties(self, component_id):
|
|
component = next((c for c in self.components if c['id'] == component_id), None)
|
|
if not component:
|
|
return
|
|
|
|
self.property_widgets['text'].delete(0, tk.END)
|
|
self.property_widgets['text'].insert(0, component.get('text', ''))
|
|
|
|
self.property_widgets['width'].set(component.get('width', 200))
|
|
self.property_widgets['height'].set(component.get('height', 50))
|
|
|
|
self.property_widgets['color'].delete(0, tk.END)
|
|
self.property_widgets['color'].insert(0, component.get('color', '#ffffff'))
|
|
|
|
self.property_widgets['bg_color'].delete(0, tk.END)
|
|
self.property_widgets['bg_color'].insert(0, component.get('bg_color', '#0f3460'))
|
|
|
|
def update_properties(self):
|
|
if not self.selected_component:
|
|
return
|
|
|
|
for comp in self.components:
|
|
if comp['id'] == self.selected_component:
|
|
comp['text'] = self.property_widgets['text'].get()
|
|
comp['width'] = self.property_widgets['width'].get()
|
|
comp['height'] = self.property_widgets['height'].get()
|
|
comp['color'] = self.property_widgets['color'].get()
|
|
comp['bg_color'] = self.property_widgets['bg_color'].get()
|
|
|
|
self.redraw_component(comp)
|
|
break
|
|
|
|
def delete_component(self):
|
|
if not self.selected_component:
|
|
return
|
|
|
|
items = self.canvas.find_withtag(self.selected_component)
|
|
for item in items:
|
|
self.canvas.delete(item)
|
|
|
|
self.components = [c for c in self.components if c['id'] != self.selected_component]
|
|
self.selected_component = None
|
|
|
|
def redraw_component(self, component):
|
|
items = self.canvas.find_withtag(component['id'])
|
|
for item in items:
|
|
self.canvas.delete(item)
|
|
|
|
self.draw_component(component)
|
|
|
|
def export_site(self):
|
|
export_dir = filedialog.askdirectory(title="Select Export Directory")
|
|
if not export_dir:
|
|
return
|
|
|
|
site_dir = os.path.join(export_dir, "web3_site")
|
|
os.makedirs(site_dir, exist_ok=True)
|
|
|
|
html_content = self.generate_html()
|
|
css_content = self.generate_css()
|
|
js_content = self.generate_js()
|
|
|
|
with open(os.path.join(site_dir, "index.html"), "w", encoding="utf-8") as f:
|
|
f.write(html_content)
|
|
|
|
with open(os.path.join(site_dir, "style.css"), "w", encoding="utf-8") as f:
|
|
f.write(css_content)
|
|
|
|
with open(os.path.join(site_dir, "script.js"), "w", encoding="utf-8") as f:
|
|
f.write(js_content)
|
|
|
|
self.copy_assets(site_dir)
|
|
|
|
messagebox.showinfo("Export Complete",
|
|
f"Website exported to:\n{site_dir}\n\nOpen index.html in your browser!")
|
|
|
|
def generate_html(self):
|
|
html = '''<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Web3 Website</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/web3@1.7.0/dist/web3.min.js"></script>
|
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
'''
|
|
|
|
for comp in sorted(self.components, key=lambda x: (x['y'], x['x'])):
|
|
if comp['type'] == 'header':
|
|
html += f'''
|
|
<header class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<h1>{comp.get('text', 'Header')}</h1>
|
|
</header>'''
|
|
|
|
elif comp['type'] == 'paragraph':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<p>{comp.get('text', 'Paragraph text')}</p>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'button':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<button class="custom-btn">{comp.get('text', 'Button')}</button>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'wallet_connect':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<button class="wallet-btn" onclick="connectWallet()">
|
|
<i class="fab fa-ethereum"></i> {comp.get('text', 'Connect Wallet')}
|
|
</button>
|
|
<div id="walletInfo" class="wallet-info"></div>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'nft_gallery':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<h2>{comp.get('title', 'NFT Gallery')}</h2>
|
|
<div class="nft-grid" id="nftGrid">
|
|
</div>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'token_balance':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<div class="token-card">
|
|
<h3><i class="fab fa-ethereum"></i> Token Balance</h3>
|
|
<div class="balance" id="ethBalance">0.00 ETH</div>
|
|
<div class="balance-usd" id="ethUsd">$0.00</div>
|
|
</div>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'swap_interface':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<div class="swap-container">
|
|
<h3><i class="fas fa-exchange-alt"></i> Swap Tokens</h3>
|
|
<div class="swap-input">
|
|
<input type="number" id="fromAmount" placeholder="0.0" value="0.1">
|
|
<select id="fromToken">
|
|
<option value="ETH">ETH</option>
|
|
<option value="DAI">DAI</option>
|
|
</select>
|
|
</div>
|
|
<div class="swap-arrow"><i class="fas fa-arrow-down"></i></div>
|
|
<div class="swap-input">
|
|
<input type="number" id="toAmount" placeholder="0.0" readonly>
|
|
<select id="toToken">
|
|
<option value="DAI">DAI</option>
|
|
<option value="ETH">ETH</option>
|
|
</select>
|
|
</div>
|
|
<button class="swap-btn" onclick="simulateSwap()">Swap</button>
|
|
</div>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'dao_voting':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<div class="dao-proposal">
|
|
<h3><i class="fas fa-vote-yea"></i> {comp.get('title', 'DAO Proposal')}</h3>
|
|
<p>Proposal #123: Upgrade protocol to v2.0</p>
|
|
<div class="vote-buttons">
|
|
<button class="vote-btn yes" onclick="vote('yes')">Yes (68%)</button>
|
|
<button class="vote-btn no" onclick="vote('no')">No (32%)</button>
|
|
</div>
|
|
<div class="vote-info">Connected wallets can vote</div>
|
|
</div>
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'image':
|
|
html += f'''
|
|
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<img src="{comp.get('src', 'https://placehold.co/400x300')}" alt="Image" style="width: 100%; height: 100%; object-fit: cover;">
|
|
</div>'''
|
|
|
|
elif comp['type'] == 'footer':
|
|
html += f'''
|
|
<footer class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
|
|
<p>{comp.get('text', '© 2024 Web3 Website')}</p>
|
|
</footer>'''
|
|
|
|
html += '''
|
|
</div>
|
|
|
|
<script src="script.js"></script>
|
|
</body>
|
|
</html>'''
|
|
|
|
return html
|
|
|
|
def generate_css(self):
|
|
return '''* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
color: #ffffff;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
position: relative;
|
|
min-height: 800px;
|
|
margin: 0 auto;
|
|
max-width: 1200px;
|
|
}
|
|
|
|
.component {
|
|
position: absolute;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.component:hover {
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
header {
|
|
background: linear-gradient(90deg, #0f3460 0%, #1a5f9c 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 15px;
|
|
}
|
|
|
|
header h1 {
|
|
font-size: 2.5rem;
|
|
background: linear-gradient(45deg, #00dbde, #fc00ff);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.custom-btn {
|
|
background: linear-gradient(45deg, #4CAF50, #2E7D32);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 25px;
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.custom-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
|
|
}
|
|
|
|
.wallet-btn {
|
|
background: linear-gradient(45deg, #f6851b, #e2761b);
|
|
color: white;
|
|
border: none;
|
|
padding: 15px 30px;
|
|
border-radius: 25px;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
width: 100%;
|
|
height: 100%;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.wallet-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 20px rgba(246, 133, 27, 0.4);
|
|
}
|
|
|
|
.wallet-info {
|
|
margin-top: 15px;
|
|
padding: 15px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 10px;
|
|
display: none;
|
|
}
|
|
|
|
.nft-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.nft-item {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 15px;
|
|
padding: 15px;
|
|
text-align: center;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.nft-item:hover {
|
|
transform: translateY(-5px);
|
|
background: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.nft-item img {
|
|
width: 100%;
|
|
border-radius: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.token-card {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 20px;
|
|
padding: 25px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.balance {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.balance-usd {
|
|
font-size: 1.2rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.swap-container {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 20px;
|
|
padding: 25px;
|
|
height: 100%;
|
|
}
|
|
|
|
.swap-input {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 15px;
|
|
padding: 15px;
|
|
margin: 15px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.swap-input input {
|
|
background: transparent;
|
|
border: none;
|
|
color: white;
|
|
font-size: 1.5rem;
|
|
width: 70%;
|
|
outline: none;
|
|
}
|
|
|
|
.swap-input select {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
border: none;
|
|
padding: 10px;
|
|
border-radius: 10px;
|
|
width: 30%;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.swap-arrow {
|
|
text-align: center;
|
|
margin: 10px 0;
|
|
color: #4CAF50;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.swap-btn {
|
|
background: linear-gradient(45deg, #2196F3, #1976D2);
|
|
color: white;
|
|
border: none;
|
|
padding: 15px;
|
|
border-radius: 15px;
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
margin-top: 20px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.swap-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(33, 150, 243, 0.4);
|
|
}
|
|
|
|
.dao-proposal {
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
border-radius: 20px;
|
|
padding: 25px;
|
|
height: 100%;
|
|
}
|
|
|
|
.vote-buttons {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.vote-btn {
|
|
flex: 1;
|
|
padding: 15px;
|
|
border: none;
|
|
border-radius: 15px;
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.vote-btn.yes {
|
|
background: linear-gradient(45deg, #4CAF50, #2E7D32);
|
|
color: white;
|
|
}
|
|
|
|
.vote-btn.no {
|
|
background: linear-gradient(45deg, #f44336, #c62828);
|
|
color: white;
|
|
}
|
|
|
|
.vote-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.vote-info {
|
|
font-size: 0.9rem;
|
|
opacity: 0.8;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
footer {
|
|
background: linear-gradient(90deg, #0f3460 0%, #1a5f9c 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 15px;
|
|
}
|
|
|
|
footer p {
|
|
font-size: 1.1rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.component {
|
|
position: relative !important;
|
|
left: 0 !important;
|
|
top: 0 !important;
|
|
width: 100% !important;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.container {
|
|
min-height: auto;
|
|
}
|
|
}
|
|
'''
|
|
|
|
def generate_js(self):
|
|
return '''let web3;
|
|
let connectedAccount = null;
|
|
|
|
async function connectWallet() {
|
|
try {
|
|
if (window.ethereum) {
|
|
web3 = new Web3(window.ethereum);
|
|
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
const accounts = await web3.eth.getAccounts();
|
|
connectedAccount = accounts[0];
|
|
|
|
const walletInfo = document.getElementById('walletInfo');
|
|
const shortAddress = connectedAccount.substring(0, 6) + '...' + connectedAccount.substring(38);
|
|
walletInfo.innerHTML = `
|
|
<p><strong>Connected:</strong> ${shortAddress}</p>
|
|
<button onclick="disconnectWallet()" style="background: #f44336; color: white; border: none; padding: 5px 10px; border-radius: 5px; margin-top: 5px;">Disconnect</button>
|
|
`;
|
|
walletInfo.style.display = 'block';
|
|
|
|
updateBalance();
|
|
alert('Wallet connected successfully!');
|
|
} else {
|
|
alert('Please install MetaMask!');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error connecting wallet:', error);
|
|
alert('Error connecting wallet');
|
|
}
|
|
}
|
|
|
|
function disconnectWallet() {
|
|
connectedAccount = null;
|
|
document.getElementById('walletInfo').style.display = 'none';
|
|
document.getElementById('ethBalance').textContent = '0.00 ETH';
|
|
document.getElementById('ethUsd').textContent = '$0.00';
|
|
}
|
|
|
|
async function updateBalance() {
|
|
if (!connectedAccount || !web3) return;
|
|
|
|
try {
|
|
const balanceWei = await web3.eth.getBalance(connectedAccount);
|
|
const balanceEth = web3.utils.fromWei(balanceWei, 'ether');
|
|
const formattedBalance = parseFloat(balanceEth).toFixed(4);
|
|
|
|
const ethPrice = 3500;
|
|
const usdValue = (parseFloat(formattedBalance) * ethPrice).toFixed(2);
|
|
|
|
document.getElementById('ethBalance').textContent = `${formattedBalance} ETH`;
|
|
document.getElementById('ethUsd').textContent = `$${usdValue} USD`;
|
|
|
|
loadMockNFTs();
|
|
} catch (error) {
|
|
console.error('Error updating balance:', error);
|
|
}
|
|
}
|
|
|
|
function loadMockNFTs() {
|
|
const nftGrid = document.getElementById('nftGrid');
|
|
if (!nftGrid) return;
|
|
|
|
const mockNFTs = [
|
|
{ name: 'CryptoPunk #1234', image: 'https://placehold.co/150x150/00ffaa/000000?text=CryptoPunk' },
|
|
{ name: 'Bored Ape #5678', image: 'https://placehold.co/150x150/ffaa00/000000?text=Bored+Ape' },
|
|
{ name: 'Art Blocks #9012', image: 'https://placehold.co/150x150/aa00ff/000000?text=Art+Blocks' },
|
|
{ name: 'Doodle #3456', image: 'https://placehold.co/150x150/00aaff/000000?text=Doodle' }
|
|
];
|
|
|
|
nftGrid.innerHTML = mockNFTs.map(nft => `
|
|
<div class="nft-item">
|
|
<img src="${nft.image}" alt="${nft.name}">
|
|
<p>${nft.name}</p>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function simulateSwap() {
|
|
const fromAmount = document.getElementById('fromAmount').value;
|
|
const fromToken = document.getElementById('fromToken').value;
|
|
const toToken = document.getElementById('toToken').value;
|
|
|
|
if (!fromAmount || parseFloat(fromAmount) <= 0) {
|
|
alert('Please enter a valid amount');
|
|
return;
|
|
}
|
|
|
|
const rates = {
|
|
'ETH_DAI': 3500,
|
|
'DAI_ETH': 0.0002857
|
|
};
|
|
|
|
const rateKey = `${fromToken}_${toToken}`;
|
|
const rate = rates[rateKey] || 1;
|
|
const toAmount = (parseFloat(fromAmount) * rate).toFixed(4);
|
|
|
|
document.getElementById('toAmount').value = toAmount;
|
|
alert(`Swap successful! ${fromAmount} ${fromToken} = ${toAmount} ${toToken}`);
|
|
}
|
|
|
|
function vote(choice) {
|
|
if (!connectedAccount) {
|
|
alert('Please connect your wallet to vote');
|
|
return;
|
|
}
|
|
|
|
const votes = {
|
|
'yes': Math.floor(Math.random() * 1000) + 700,
|
|
'no': Math.floor(Math.random() * 500) + 300
|
|
};
|
|
|
|
const totalVotes = votes.yes + votes.no;
|
|
const yesPercent = Math.round((votes.yes / totalVotes) * 100);
|
|
const noPercent = 100 - yesPercent;
|
|
|
|
document.querySelector('.vote-btn.yes').textContent = `Yes (${yesPercent}%)`;
|
|
document.querySelector('.vote-btn.no').textContent = `No (${noPercent}%)`;
|
|
|
|
alert(`Vote recorded: ${choice.toUpperCase()}`);
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
if (window.ethereum) {
|
|
web3 = new Web3(window.ethereum);
|
|
|
|
window.ethereum.on('accountsChanged', (accounts) => {
|
|
if (accounts.length > 0) {
|
|
connectedAccount = accounts[0];
|
|
updateBalance();
|
|
} else {
|
|
disconnectWallet();
|
|
}
|
|
});
|
|
|
|
window.ethereum.on('chainChanged', () => {
|
|
window.location.reload();
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.custom-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
alert('Button clicked!');
|
|
});
|
|
});
|
|
|
|
loadMockNFTs();
|
|
});
|
|
'''
|
|
|
|
def copy_assets(self, site_dir):
|
|
assets_dir = os.path.join(site_dir, "assets")
|
|
os.makedirs(assets_dir, exist_ok=True)
|
|
|
|
favicon_path = os.path.join(assets_dir, "favicon.ico")
|
|
|
|
readme = """# Web3 Website
|
|
|
|
This website was generated using Web3 Site Builder.
|
|
|
|
## Features:
|
|
- Wallet connection (MetaMask)
|
|
- NFT gallery display
|
|
- Token balance tracking
|
|
- Token swap interface
|
|
- DAO voting simulation
|
|
|
|
## How to Use:
|
|
1. Open index.html in your browser
|
|
2. Connect your wallet (MetaMask recommended)
|
|
3. Interact with Web3 components
|
|
|
|
## Note:
|
|
This is a demo site. For production use, implement proper security and connect to real blockchain networks.
|
|
"""
|
|
|
|
with open(os.path.join(site_dir, "README.md"), "w", encoding="utf-8") as f:
|
|
f.write(readme)
|
|
|
|
def main():
|
|
root = tk.Tk()
|
|
app = Web3Builder(root)
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|