Files
Carlos Gutierrez a63a758cc5 feat: initial release of Strata framework v0.1.0
- Static compiler with STRC pattern (Static Template Resolution with
   Compartmentalized Layers)
   - Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
    }
   - File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
   .scss
   - CLI tools: strata-compile dev, strata-compile build, strata-compile
    g (generators)
   - create-strata-compile scaffolding CLI with Pokemon API example
   - Dev server with WebSocket HMR (Hot Module Replacement)
   - Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
   LICENSE
2026-01-16 09:13:14 -05:00

923 lines
27 KiB
Go

package server
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/CarGDev/strata-compile/internal/ast"
"github.com/CarGDev/strata-compile/internal/compiler"
"github.com/CarGDev/strata-compile/internal/parser"
"github.com/gorilla/websocket"
)
// DevServer handles development server with HMR
type DevServer struct {
port int
projectDir string
distDir string
clients map[*websocket.Conn]bool
mu sync.RWMutex
upgrader websocket.Upgrader
compiler *compiler.StaticCompiler
}
// NewDevServer creates a new dev server
func NewDevServer(port int, projectDir string) *DevServer {
return &DevServer{
port: port,
projectDir: projectDir,
distDir: filepath.Join(projectDir, ".strata", "dev"),
clients: make(map[*websocket.Conn]bool),
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Allow all origins in dev
},
},
compiler: compiler.NewStaticCompiler(projectDir),
}
}
// Start starts the dev server
func (s *DevServer) Start() error {
// Create dev output directory
if err := os.MkdirAll(s.distDir, 0755); err != nil {
return err
}
// Generate initial files
if err := s.generateDevFiles(); err != nil {
return err
}
// Set up HTTP handlers
mux := http.NewServeMux()
// WebSocket for HMR
mux.HandleFunc("/__strata_hmr", s.handleHMR)
// API proxy (if configured)
mux.HandleFunc("/api/", s.handleAPIProxy)
// Static files from .strata/dev
mux.HandleFunc("/", s.handleStatic)
server := &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
fmt.Printf("\n Strata Dev Server\n")
fmt.Printf(" ─────────────────────────────\n")
fmt.Printf(" Local: http://localhost:%d\n", s.port)
fmt.Printf(" Network: http://%s:%d\n", getLocalIP(), s.port)
fmt.Printf("\n Watching for changes...\n\n")
return server.ListenAndServe()
}
// handleStatic serves static files
func (s *DevServer) handleStatic(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if path == "/" {
path = "/index.html"
}
// Try .strata/dev first
filePath := filepath.Join(s.distDir, path)
if _, err := os.Stat(filePath); err == nil {
http.ServeFile(w, r, filePath)
return
}
// Try public folder
publicPath := filepath.Join(s.projectDir, "public", path)
if _, err := os.Stat(publicPath); err == nil {
http.ServeFile(w, r, publicPath)
return
}
// Try src/assets
assetPath := filepath.Join(s.projectDir, "src", "assets", path)
if _, err := os.Stat(assetPath); err == nil {
http.ServeFile(w, r, assetPath)
return
}
// SPA fallback - serve index.html
indexPath := filepath.Join(s.distDir, "index.html")
if _, err := os.Stat(indexPath); err == nil {
http.ServeFile(w, r, indexPath)
return
}
http.NotFound(w, r)
}
// handleHMR handles WebSocket connections for HMR
func (s *DevServer) handleHMR(w http.ResponseWriter, r *http.Request) {
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("WebSocket upgrade error: %v", err)
return
}
s.mu.Lock()
s.clients[conn] = true
s.mu.Unlock()
defer func() {
s.mu.Lock()
delete(s.clients, conn)
s.mu.Unlock()
conn.Close()
}()
// Keep connection alive
for {
_, _, err := conn.ReadMessage()
if err != nil {
break
}
}
}
// handleAPIProxy proxies API requests
func (s *DevServer) handleAPIProxy(w http.ResponseWriter, r *http.Request) {
// TODO: Read API base URL from strataconfig.ts
// For now, return 501
w.WriteHeader(http.StatusNotImplemented)
w.Write([]byte(`{"error": "API proxy not configured"}`))
}
// Rebuild regenerates all dev files (called on file changes)
func (s *DevServer) Rebuild() error {
return s.generateDevFiles()
}
// NotifyChange sends HMR update to all clients
func (s *DevServer) NotifyChange(changeType string, path string) {
message := map[string]string{
"type": changeType,
"path": path,
}
data, _ := json.Marshal(message)
s.mu.RLock()
defer s.mu.RUnlock()
for client := range s.clients {
err := client.WriteMessage(websocket.TextMessage, data)
if err != nil {
client.Close()
delete(s.clients, client)
}
}
}
// generateDevFiles generates initial dev files
func (s *DevServer) generateDevFiles() error {
// Generate index.html with HMR client
html := s.generateDevHTML()
indexPath := filepath.Join(s.distDir, "index.html")
if err := os.WriteFile(indexPath, []byte(html), 0644); err != nil {
return err
}
// Generate runtime.js
runtime := s.generateDevRuntime()
runtimePath := filepath.Join(s.distDir, "assets", "js", "runtime.js")
if err := os.MkdirAll(filepath.Dir(runtimePath), 0755); err != nil {
return err
}
if err := os.WriteFile(runtimePath, []byte(runtime), 0644); err != nil {
return err
}
// Copy and process source files
return s.processSourceFiles()
}
func (s *DevServer) generateDevHTML() string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Strata | Code Faster</title>
<link rel="stylesheet" href="/assets/css/app.css">
<script src="https://cdn.tailwindcss.com"></script>
<script>
window.__STRATA_CONFIG__ = {
devMode: true,
apiBaseUrl: 'http://localhost:8080'
};
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
body {
background-color: #0a0a0a;
color: #ffffff;
font-family: 'JetBrains Mono', monospace;
overflow: hidden;
}
.ascii-art {
white-space: pre;
line-height: 1.2;
font-size: clamp(0.5rem, 1.5vw, 1rem);
color: #3b82f6;
text-shadow: 0 0 20px rgba(59, 130, 246, 0.5);
}
.heart-btn {
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.heart-btn:active {
transform: scale(0.9);
}
.floating-heart {
position: absolute;
pointer-events: none;
animation: floatUp 1s ease-out forwards;
}
@keyframes floatUp {
0% { transform: translateY(0) scale(1); opacity: 1; }
100% { transform: translateY(-100px) scale(1.5); opacity: 0; }
}
.gradient-text {
background: linear-gradient(to right, #3b82f6, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.strata-error {
background: #fee;
border: 1px solid #fcc;
padding: 1rem;
margin: 1rem;
border-radius: 8px;
font-family: monospace;
white-space: pre-wrap;
color: #c00;
}
</style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen p-4">
<div id="app"></div>
<script type="module" src="/assets/js/runtime.js"></script>
<script type="module" src="/assets/js/app.js"></script>
<!-- HMR Client -->
<script>
(function() {
const ws = new WebSocket('ws://' + location.host + '/__strata_hmr');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('[Strata HMR]', data.type, data.path);
if (data.type === 'reload') {
location.reload();
} else if (data.type === 'css') {
const links = document.querySelectorAll('link[rel="stylesheet"]');
links.forEach(link => {
const url = new URL(link.href);
url.searchParams.set('t', Date.now());
link.href = url.toString();
});
} else if (data.type === 'component') {
location.reload();
}
};
ws.onclose = function() {
console.log('[Strata HMR] Disconnected. Attempting reconnect...');
setTimeout(() => location.reload(), 1000);
};
})();
</script>
</body>
</html>`
}
func (s *DevServer) generateDevRuntime() string {
return `// Strata Runtime (Dev Mode)
console.log('[Strata] Runtime loaded');
class Strata {
constructor() {
this._tabId = 'tab_' + Math.random().toString(36).slice(2, 10);
this._stores = new Map();
this._cache = new Map();
}
get tabId() {
return this._tabId;
}
async init() {
console.log('[Strata] Initialized with tabId:', this._tabId);
window.__STRATA__ = this;
}
// Simple fetch with caching
async fetch(url, options = {}) {
const cacheKey = url + JSON.stringify(options);
if (options.cache !== 'none' && this._cache.has(cacheKey)) {
const cached = this._cache.get(cacheKey);
if (Date.now() - cached.timestamp < 300000) { // 5 min
return cached.data;
}
}
const response = await fetch(url, options);
const data = await response.json();
this._cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}
// Broadcast to other tabs (simplified for dev)
broadcast(event, data) {
console.log('[Strata] Broadcast:', event, data);
window.dispatchEvent(new CustomEvent('strata:' + event, { detail: data }));
}
onBroadcast(event, handler) {
window.addEventListener('strata:' + event, (e) => handler(e.detail));
}
}
// Initialize
const strata = new Strata();
strata.init();
export { strata };
export default strata;
`
}
func (s *DevServer) processSourceFiles() error {
// Process .strata files in src/pages
pagesDir := filepath.Join(s.projectDir, "src", "pages")
if _, err := os.Stat(pagesDir); err != nil {
return nil // No pages directory
}
// Find all pages - both directory structure (pages/name/name.strata) and flat (pages/name.strata)
var pages []PageInfo
processedDirs := make(map[string]bool)
err := filepath.Walk(pagesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check for directory-based pages first (pages/name/name.strata)
if info.IsDir() && path != pagesDir {
dirName := filepath.Base(path)
strataFile := filepath.Join(path, dirName+".strata")
compilerFile := filepath.Join(path, dirName+".compiler.sts")
// If this is a page directory with .strata file
if _, err := os.Stat(strataFile); err == nil {
processedDirs[path] = true
// Check if it has a .compiler.sts file (new structure)
if _, err := os.Stat(compilerFile); err == nil {
// Use static compiler for new structure
pageInfo, err := s.compilePageDirectory(path)
if err != nil {
log.Printf("Warning: failed to compile %s: %v", path, err)
return nil
}
pages = append(pages, pageInfo)
} else {
// Fallback to old compilation for legacy structure
pageInfo, err := s.compilePage(strataFile)
if err != nil {
log.Printf("Warning: failed to compile %s: %v", strataFile, err)
return nil
}
pages = append(pages, pageInfo)
}
return filepath.SkipDir // Don't descend further
}
}
// Handle flat structure (pages/name.strata)
if !info.IsDir() && strings.HasSuffix(path, ".strata") {
// Skip if parent dir was already processed as a page directory
if processedDirs[filepath.Dir(path)] {
return nil
}
pageInfo, err := s.compilePage(path)
if err != nil {
log.Printf("Warning: failed to compile %s: %v", path, err)
return nil
}
pages = append(pages, pageInfo)
}
return nil
})
if err != nil {
return err
}
// Generate app.js from compiled pages
appJS := s.generateAppJSFromPages(pages)
appPath := filepath.Join(s.distDir, "assets", "js", "app.js")
if err := os.WriteFile(appPath, []byte(appJS), 0644); err != nil {
return err
}
// Generate combined CSS
cssPath := filepath.Join(s.distDir, "assets", "css", "app.css")
if err := os.MkdirAll(filepath.Dir(cssPath), 0755); err != nil {
return err
}
css := s.generateCSSFromPages(pages)
if err := os.WriteFile(cssPath, []byte(css), 0644); err != nil {
return err
}
return nil
}
// compilePageDirectory compiles a page from directory structure using static compiler
func (s *DevServer) compilePageDirectory(pageDir string) (PageInfo, error) {
pageName := filepath.Base(pageDir)
// Use the static compiler
module, err := s.compiler.CompilePage(pageDir)
if err != nil {
return PageInfo{}, err
}
// Read service file for runtime logic (if exists)
serviceContent := ""
servicePath := filepath.Join(pageDir, pageName+".service.sts")
if content, err := os.ReadFile(servicePath); err == nil {
serviceContent = string(content)
}
// Calculate route from directory path
relPath, _ := filepath.Rel(filepath.Join(s.projectDir, "src", "pages"), pageDir)
route := "/" + relPath
if pageName == "index" || relPath == "index" {
route = "/"
}
return PageInfo{
Name: pageName,
Path: pageDir,
HTML: module.HTML,
Style: module.CSS,
Route: route,
Compiler: "", // Already resolved in HTML
Service: serviceContent,
Imports: []string{},
}, nil
}
// PageInfo holds compiled page information
type PageInfo struct {
Name string
Path string
HTML string
Compiler string // .compiler.sts - variable definitions
Service string // .service.sts - business logic
Script string // Legacy .sts support
Style string
Route string
Imports []string // Imported components
}
// compilePage compiles a single .strata file and its associated files
// File structure:
// - page.strata - HTML template
// - page.compiler.sts - variable definitions (like Angular component)
// - page.service.sts - business logic (like Angular service)
// - page.scss - styles
func (s *DevServer) compilePage(strataPath string) (PageInfo, error) {
info := PageInfo{
Path: strataPath,
Imports: make([]string, 0),
}
// Get base name without extension
baseName := strings.TrimSuffix(filepath.Base(strataPath), ".strata")
info.Name = baseName
// Calculate route from file path
relPath, _ := filepath.Rel(filepath.Join(s.projectDir, "src", "pages"), strataPath)
route := "/" + strings.TrimSuffix(relPath, ".strata")
if strings.HasSuffix(route, "/index") {
route = strings.TrimSuffix(route, "/index")
if route == "" {
route = "/"
}
}
info.Route = route
// Read and parse .strata file
strataContent, err := os.ReadFile(strataPath)
if err != nil {
return info, err
}
p := parser.NewStrataParser(string(strataContent))
file, err := p.Parse()
if err != nil {
return info, err
}
// Generate HTML from template
if file.Template != nil {
info.HTML = s.generateHTMLFromTemplate(file.Template)
// Extract imports from parsed template
info.Imports = s.extractImports(file.Template)
}
// Check for .compiler.sts file (variable definitions - Angular-like component)
compilerPath := strings.TrimSuffix(strataPath, ".strata") + ".compiler.sts"
if compilerContent, err := os.ReadFile(compilerPath); err == nil {
info.Compiler = string(compilerContent)
}
// Check for .service.sts file (business logic - Angular-like service)
servicePath := strings.TrimSuffix(strataPath, ".strata") + ".service.sts"
if serviceContent, err := os.ReadFile(servicePath); err == nil {
info.Service = string(serviceContent)
}
// Legacy: Check for .sts file (combined script)
stsPath := strings.TrimSuffix(strataPath, ".strata") + ".sts"
if stsContent, err := os.ReadFile(stsPath); err == nil {
info.Script = string(stsContent)
} else if file.Script != nil {
info.Script = file.Script.Content
}
// Check for associated .scss file (styles)
scssPath := strings.TrimSuffix(strataPath, ".strata") + ".scss"
if scssContent, err := os.ReadFile(scssPath); err == nil {
info.Style = string(scssContent)
} else if file.Style != nil {
info.Style = file.Style.Content
}
return info, nil
}
// extractImports extracts import paths from a template's ImportNodes
func (s *DevServer) extractImports(template interface{}) []string {
imports := make([]string, 0)
if t, ok := template.(*ast.TemplateNode); ok {
for _, child := range t.Children {
if imp, ok := child.(*ast.ImportNode); ok {
imports = append(imports, imp.Path)
}
}
}
return imports
}
// generateHTMLFromTemplate converts AST template to HTML string
func (s *DevServer) generateHTMLFromTemplate(template interface{}) string {
if t, ok := template.(interface{ ToHTML() string }); ok {
return t.ToHTML()
}
return ""
}
// generateAppJSFromPages creates the app.js from compiled pages
func (s *DevServer) generateAppJSFromPages(pages []PageInfo) string {
if len(pages) == 0 {
return s.generateAppJS() // Fallback to default
}
var js strings.Builder
js.WriteString(`// Strata App - Static Compilation Output
// All templates resolved at build time - no Strata syntax in runtime HTML
import { strata } from './runtime.js';
`)
// Generate page modules
for i, page := range pages {
// Escape the HTML for JavaScript
escapedHTML := strings.ReplaceAll(page.HTML, "`", "\\`")
escapedHTML = strings.ReplaceAll(escapedHTML, "${", "\\${")
// Check if this is a statically compiled page (Compiler is empty means already resolved)
// Pages can still have Service for runtime interactivity
isStaticCompiled := page.Compiler == ""
if isStaticCompiled {
// Statically compiled page - HTML is already resolved
// May have service file for runtime interactivity
if page.Service != "" {
// Extract the mount function from service file
escapedService := strings.ReplaceAll(page.Service, "`", "\\`")
escapedService = strings.ReplaceAll(escapedService, "${", "\\${")
js.WriteString(fmt.Sprintf(`// Page: %s (statically compiled with runtime service)
const page%dService = (function() {
%s
})();
const page%d = {
name: "%s",
route: "%s",
render() {
return `+"`%s`"+`;
},
mount() {
console.log('[Strata] Static page mounted:', this.name);
// Execute service mount if defined
if (page%dService && page%dService.mount) {
page%dService.mount();
} else if (page%dService && page%dService.default && page%dService.default.mount) {
page%dService.default.mount();
}
}
};
`, page.Name, i, escapedService, i, page.Name, page.Route, escapedHTML, i, i, i, i, i, i, i))
} else {
// Pure static page - no runtime code
js.WriteString(fmt.Sprintf(`// Page: %s (statically compiled)
const page%d = {
name: "%s",
route: "%s",
render() {
return `+"`%s`"+`;
},
mount() {
console.log('[Strata] Static page mounted:', this.name);
}
};
`, page.Name, i, page.Name, page.Route, escapedHTML))
}
} else {
// Legacy page with runtime compilation
compilerContent := page.Compiler
if compilerContent == "" && page.Script != "" {
compilerContent = page.Script
}
if compilerContent == "" {
compilerContent = "// No compiler defined\nreturn {};"
}
serviceContent := page.Service
if serviceContent == "" {
serviceContent = "// No service defined\nreturn {};"
}
js.WriteString(fmt.Sprintf(`// Page: %s (runtime)
const page%dCompiler = (function() {
%s
})();
const page%dService = (function() {
%s
})();
const page%d = {
name: "%s",
route: "%s",
compiler: page%dCompiler,
service: page%dService,
render() {
return `+"`%s`"+`;
},
mount() {
const state = this.compiler.state || this.compiler.default || {};
const methods = this.service.methods || this.service.default || {};
// Bind event handlers
document.querySelectorAll('[data-strata-\\@click]').forEach(el => {
const handler = el.getAttribute('data-strata-@click');
if (methods[handler]) {
el.addEventListener('click', (e) => methods[handler].call({ state, methods }, e));
}
});
// Bind interpolation placeholders
document.querySelectorAll('[data-strata-bind]').forEach(el => {
const expr = el.getAttribute('data-strata-bind');
const value = expr.split('.').reduce((o, k) => o && o[k], state);
if (value !== undefined) el.textContent = value;
});
console.log('[Strata] Page mounted:', this.name);
}
};
`, page.Name, i, compilerContent, i, serviceContent,
i, page.Name, page.Route, i, i, escapedHTML))
}
}
// Generate pages map
js.WriteString(`// Pages registry
const pages = {
`)
for i, page := range pages {
js.WriteString(fmt.Sprintf(` "%s": page%d,
`, page.Route, i))
}
js.WriteString(`};
// Router
function getRoute() {
const path = window.location.pathname || '/';
return path === '' ? '/' : path;
}
// Mount application
async function mount() {
const app = document.getElementById('app');
const route = getRoute();
const page = pages[route] || pages['/'];
if (!page) {
app.innerHTML = '<div class="strata-error">Page not found: ' + route + '</div>';
return;
}
try {
// Render HTML
app.innerHTML = page.render({});
// Mount page (bind events, etc)
page.mount();
console.log('[Strata] Mounted:', page.name, 'at', route);
} catch (error) {
app.innerHTML = '<div class="strata-error">' + error.message + '</div>';
console.error('[Strata] Mount error:', error);
}
}
// Initialize
mount();
// Handle navigation
window.addEventListener('popstate', mount);
`)
return js.String()
}
// generateCSSFromPages combines all page styles
func (s *DevServer) generateCSSFromPages(pages []PageInfo) string {
var css strings.Builder
// Add global styles first
globalPath := filepath.Join(s.projectDir, "src", "assets", "styles", "global.scss")
if globalContent, err := os.ReadFile(globalPath); err == nil {
css.WriteString("/* Global styles */\n")
css.WriteString(string(globalContent))
css.WriteString("\n\n")
}
// Add variables
varsPath := filepath.Join(s.projectDir, "src", "assets", "styles", "_variables.scss")
if varsContent, err := os.ReadFile(varsPath); err == nil {
css.WriteString("/* Variables */\n")
css.WriteString(string(varsContent))
css.WriteString("\n\n")
}
// Add page styles
for _, page := range pages {
if page.Style != "" {
css.WriteString(fmt.Sprintf("/* Page: %s */\n", page.Name))
css.WriteString(page.Style)
css.WriteString("\n\n")
}
}
return css.String()
}
func (s *DevServer) generateAppJS() string {
return `// Strata App (Dev Mode)
import { strata } from './runtime.js';
async function mount() {
const app = document.getElementById('app');
try {
app.innerHTML = ` + "`" + `
<!-- Header / Branding -->
<div class="mb-8 text-center animate-pulse">
<div class="ascii-art mb-4">
╔═══════════════════════════════════════════════════════╗
║ ║
║ ███████╗████████╗██████╗ █████╗ ████████╗ █████╗ ║
║ ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗ ║
║ ███████╗ ██║ ██████╔╝███████║ ██║ ███████║ ║
║ ╚════██║ ██║ ██╔══██╗██╔══██║ ██║ ██╔══██║ ║
║ ███████║ ██║ ██║ ██║██║ ██║ ██║ ██║ ██║ ║
║ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ║
║ ║
║ Static Template Rendering Architecture ║
╚═══════════════════════════════════════════════════════╝
</div>
<h1 class="text-2xl md:text-4xl font-bold mb-2">Welcome to <span class="gradient-text">Strata</span></h1>
<p class="text-gray-400 text-sm md:text-base">Code Faster. Load Quick. Deploy ASAP.</p>
</div>
<!-- Main Interaction -->
<div class="relative flex flex-col items-center">
<button id="heartBtn" class="heart-btn bg-white/5 hover:bg-white/10 border border-white/10 rounded-full p-8 mb-4 backdrop-blur-sm shadow-2xl group">
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 text-red-500 group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
</button>
<div class="text-center">
<span id="counter" class="text-5xl font-bold tabular-nums">0</span>
<p class="text-gray-500 mt-2 uppercase tracking-widest text-xs">Hearts Captured</p>
</div>
</div>
<!-- Tab ID Badge -->
<div class="fixed top-4 right-4 bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-xs text-gray-400">
Tab: <code class="text-blue-400">${strata.tabId}</code>
</div>
<!-- Background Decoration -->
<div class="fixed bottom-4 text-gray-700 text-[10px] uppercase tracking-tighter opacity-50">
STRATA_ENGINE_V1.0 // FAST_PATH_ENABLED // HMR_ACTIVE
</div>
` + "`" + `;
// Initialize heart button interaction
const btn = document.getElementById('heartBtn');
const counterEl = document.getElementById('counter');
let count = 0;
function createHeart(x, y) {
const heart = document.createElement('div');
heart.className = 'floating-heart text-red-500';
heart.innerHTML = '<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>';
heart.style.left = x + 'px';
heart.style.top = y + 'px';
document.body.appendChild(heart);
setTimeout(() => heart.remove(), 1000);
}
btn.addEventListener('click', (e) => {
count++;
counterEl.innerText = count;
createHeart(e.clientX - 12, e.clientY - 12);
counterEl.style.transform = 'scale(1.1)';
setTimeout(() => counterEl.style.transform = 'scale(1)', 100);
});
window.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.code === 'Enter') btn.click();
});
console.log('[Strata] App mounted with tabId:', strata.tabId);
} catch (error) {
app.innerHTML = '<div class="strata-error">' + error.message + '</div>';
console.error('[Strata] Mount error:', error);
}
}
mount();
`
}
func getLocalIP() string {
// Simplified - return localhost for now
return "localhost"
}