Примеры Alpine.js

Примеры Alpine.js, включая директивы, компоненты, реактивные данные и современные паттерны Alpine

💻 Hello World на Alpine.js html

🟢 simple

Базовая настройка Alpine.js и примеры Hello World

<!-- Alpine.js Hello World Examples -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alpine.js Hello World</title>
    <!-- Alpine.js CDN -->
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .demo { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
        .demo h3 { margin-top: 0; color: #333; }
        button { margin: 5px; padding: 8px 16px; cursor: pointer; }
        input, select { margin: 5px; padding: 5px; }
        .hidden { display: none; }
    </style>
</head>
<body>
    <h1>Alpine.js Hello World Examples</h1>

    <!-- 1. Basic reactive text -->
    <div class="demo">
        <h3>1. Basic Reactive Text</h3>
        <div x-data="{ message: 'Hello, Alpine.js!' }">
            <p x-text="message"></p>
            <input
                type="text"
                x-model="message"
                placeholder="Type something..."
            >
        </div>
    </div>

    <!-- 2. Simple counter -->
    <div class="demo">
        <h3>2. Simple Counter</h3>
        <div x-data="{ count: 0 }">
            <button x-on:click="count--">Decrement</button>
            <span x-text="count"></span>
            <button x-on:click="count++">Increment</button>
            <br><br>
            <button x-on:click="count = 0">Reset</button>
        </div>
    </div>

    <!-- 3. Toggle visibility -->
    <div class="demo">
        <h3>3. Toggle Visibility</h3>
        <div x-data="{ open: false }">
            <button x-on:click="open = !open">
                <span x-show="!open">Show Content</span>
                <span x-show="open">Hide Content</span>
            </button>

            <div x-show="open" x-transition>
                <p style="margin-top: 10px; padding: 10px; background: #f0f0f0; border-radius: 3px;">
                    This content can be toggled on and off!
                </p>
            </div>
        </div>
    </div>

    <!-- 4. User greeting -->
    <div class="demo">
        <h3>4. User Greeting</h3>
        <div x-data="{
            name: 'Guest',
            greeting: 'Welcome'
        }">
            <input
                type="text"
                x-model="name"
                placeholder="Enter your name"
                x-on:input="greeting = 'Hello, ' + name + '!'"
            >
            <p x-text="greeting" x-show="name !== 'Guest'"></p>
        </div>
    </div>

    <!-- 5. Multiple data properties -->
    <div class="demo">
        <h3>5. Multiple Data Properties</h3>
        <div x-data="{
            firstName: 'John',
            lastName: 'Doe',
            age: 25,
            city: 'New York',
            get fullName() {
                return this.firstName + ' ' + this.lastName;
            },
            get info() {
                return this.fullName + ' (' + this.age + ', ' + this.city + ')';
            }
        }">
            <input type="text" x-model="firstName" placeholder="First Name">
            <input type="text" x-model="lastName" placeholder="Last Name">
            <input type="number" x-model="age" placeholder="Age">
            <input type="text" x-model="city" placeholder="City">

            <div style="margin-top: 10px;">
                <strong>Full Name:</strong> <span x-text="fullName"></span><br>
                <strong>Info:</strong> <span x-text="info"></span>
            </div>
        </div>
    </div>

    <!-- 6. Array manipulation -->
    <div class="demo">
        <h3>6. Array Manipulation</h3>
        <div x-data="{
            items: ['Apple', 'Banana', 'Orange'],
            newItem: '',
            addItem() {
                if (this.newItem.trim()) {
                    this.items.push(this.newItem.trim());
                    this.newItem = '';
                }
            },
            removeItem(index) {
                this.items.splice(index, 1);
            }
        }">
            <div style="margin-bottom: 10px;">
                <input
                    type="text"
                    x-model="newItem"
                    placeholder="Add new item"
                    x-on:keyup.enter="addItem()"
                >
                <button x-on:click="addItem()">Add</button>
            </div>

            <ul>
                <template x-for="(item, index) in items">
                    <li>
                        <span x-text="item"></span>
                        <button
                            x-on:click="removeItem(index)"
                            style="margin-left: 10px;"
                        >
                            Remove
                        </button>
                    </li>
                </template>
            </ul>

            <p x-show="items.length === 0">No items in the list</p>
            <p x-show="items.length > 0">
                Total items: <span x-text="items.length"></span>
            </p>
        </div>
    </div>

    <!-- 7. Todo list -->
    <div class="demo">
        <h3>7. Todo List</h3>
        <div x-data="{
            todos: [
                { text: 'Learn Alpine.js', completed: false },
                { text: 'Build something cool', completed: false }
            ],
            newTodo: '',
            addTodo() {
                if (this.newTodo.trim()) {
                    this.todos.push({
                        text: this.newTodo.trim(),
                        completed: false
                    });
                    this.newTodo = '';
                }
            },
            toggleTodo(index) {
                this.todos[index].completed = !this.todos[index].completed;
            },
            clearCompleted() {
                this.todos = this.todos.filter(todo => !todo.completed);
            },
            get completedCount() {
                return this.todos.filter(todo => todo.completed).length;
            },
            get activeCount() {
                return this.todos.filter(todo => !todo.completed).length;
            }
        }">
            <form x-on:submit.prevent="addTodo()" style="margin-bottom: 15px;">
                <input
                    type="text"
                    x-model="newTodo"
                    placeholder="What needs to be done?"
                    style="width: 300px;"
                >
                <button type="submit">Add</button>
            </form>

            <ul style="list-style: none; padding: 0;">
                <template x-for="(todo, index) in todos">
                    <li style="margin: 5px 0; padding: 8px; background: #f9f9f9; border-radius: 3px;">
                        <input
                            type="checkbox"
                            x-model="todo.completed"
                            style="margin-right: 8px;"
                        >
                        <span
                            x-text="todo.text"
                            :style="todo.completed ? 'text-decoration: line-through; opacity: 0.6;' : ''"
                        ></span>
                    </li>
                </template>
            </ul>

            <div style="margin-top: 15px;">
                <span x-text="activeCount"></span> active,
                <span x-text="completedCount"></span> completed
                <button
                    x-on:click="clearCompleted()"
                    x-show="completedCount > 0"
                    style="margin-left: 10px;"
                >
                    Clear completed
                </button>
            </div>
        </div>
    </div>

    <!-- 8. Form with validation -->
    <div class="demo">
        <h3>8. Form with Validation</h3>
        <div x-data="{
            name: '',
            email: '',
            message: '',
            errors: {},
            validate() {
                this.errors = {};

                if (!this.name.trim()) {
                    this.errors.name = 'Name is required';
                }

                if (!this.email.trim()) {
                    this.errors.email = 'Email is required';
                } else if (!this.email.includes('@')) {
                    this.errors.email = 'Email is invalid';
                }

                if (!this.message.trim()) {
                    this.errors.message = 'Message is required';
                }

                return Object.keys(this.errors).length === 0;
            },
            submit() {
                if (this.validate()) {
                    alert('Form submitted successfully!\n' +
                          'Name: ' + this.name + '\n' +
                          'Email: ' + this.email + '\n' +
                          'Message: ' + this.message);
                }
            }
        }">
            <form x-on:submit.prevent="submit()">
                <div style="margin-bottom: 10px;">
                    <label>Name:</label><br>
                    <input
                        type="text"
                        x-model="name"
                        style="width: 250px;"
                    >
                    <span x-show="errors.name" style="color: red; display: block;">
                        <span x-text="errors.name"></span>
                    </span>
                </div>

                <div style="margin-bottom: 10px;">
                    <label>Email:</label><br>
                    <input
                        type="email"
                        x-model="email"
                        style="width: 250px;"
                    >
                    <span x-show="errors.email" style="color: red; display: block;">
                        <span x-text="errors.email"></span>
                    </span>
                </div>

                <div style="margin-bottom: 10px;">
                    <label>Message:</label><br>
                    <textarea
                        x-model="message"
                        rows="4"
                        style="width: 250px;"
                    ></textarea>
                    <span x-show="errors.message" style="color: red; display: block;">
                        <span x-text="errors.message"></span>
                    </span>
                </div>

                <button type="submit">Submit</button>
            </form>
        </div>
    </div>

    <!-- 9. Color picker -->
    <div class="demo">
        <h3>9. Color Picker</h3>
        <div x-data="{
            color: '#3B82F6',
            presetColors: ['#EF4444', '#F59E0B', '#10B981', '#3B82F6', '#8B5CF6', '#EC4899'],
            setColor(newColor) {
                this.color = newColor;
            }
        }">
            <div style="display: flex; align-items: center; gap: 15px;">
                <label>Choose Color:</label>
                <input
                    type="color"
                    x-model="color"
                    style="width: 50px; height: 40px;"
                >
                <div
                    style="width: 100px; height: 40px; background-color: white; border: 2px solid #ddd; border-radius: 5px;"
                    :style="'background-color: ' + color"
                ></div>
                <span x-text="color"></span>
            </div>

            <div style="margin-top: 15px;">
                <p>Quick Colors:</p>
                <template x-for="presetColor in presetColors">
                    <button
                        x-on:click="setColor(presetColor)"
                        :style="'background-color: ' + presetColor + '; color: white; margin: 2px; padding: 8px; border: none; border-radius: 3px; cursor: pointer;'"
                        x-text="presetColor"
                    ></button>
                </template>
            </div>
        </div>
    </div>

    <!-- 10. Shopping cart -->
    <div class="demo">
        <h3>10. Shopping Cart</h3>
        <div x-data="{
            products: [
                { id: 1, name: 'Laptop', price: 999 },
                { id: 2, name: 'Mouse', price: 25 },
                { id: 3, name: 'Keyboard', price: 75 }
            ],
            cart: [],
            addToCart(product) {
                const existingItem = this.cart.find(item => item.id === product.id);
                if (existingItem) {
                    existingItem.quantity++;
                } else {
                    this.cart.push({ ...product, quantity: 1 });
                }
            },
            removeFromCart(productId) {
                this.cart = this.cart.filter(item => item.id !== productId);
            },
            updateQuantity(productId, quantity) {
                const item = this.cart.find(item => item.id === productId);
                if (item) {
                    if (quantity <= 0) {
                        this.removeFromCart(productId);
                    } else {
                        item.quantity = quantity;
                    }
                }
            },
            get total() {
                return this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
            },
            get itemCount() {
                return this.cart.reduce((sum, item) => sum + item.quantity, 0);
            }
        }">
            <div style="display: flex; gap: 30px;">
                <!-- Products -->
                <div style="flex: 1;">
                    <h4>Products</h4>
                    <template x-for="product in products">
                        <div style="border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; border-radius: 5px;">
                            <h5 x-text="product.name"></h5>
                            <p>$<span x-text="product.price"></span></p>
                            <button x-on:click="addToCart(product)">Add to Cart</button>
                        </div>
                    </template>
                </div>

                <!-- Cart -->
                <div style="flex: 1;">
                    <h4>Cart (<span x-text="itemCount"></span> items)</h4>
                    <div x-show="cart.length === 0">
                        <p>Your cart is empty</p>
                    </div>

                    <template x-for="item in cart">
                        <div style="border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; border-radius: 5px;">
                            <h5 x-text="item.name"></h5>
                            <p>$<span x-text="item.price"></span> x
                               <input
                                   type="number"
                                   x-model.number="item.quantity"
                                   min="1"
                                   style="width: 60px;"
                               > = $<span x-text="item.price * item.quantity"></span>
                            </p>
                            <button x-on:click="removeFromCart(item.id)">Remove</button>
                        </div>
                    </template>

                    <div x-show="cart.length > 0" style="margin-top: 15px; padding-top: 15px; border-top: 2px solid #333;">
                        <strong>Total: $<span x-text="total"></span></strong>
                    </div>
                </div>
            </div>
        </div>
    </div>

</body>
</html>

💻 Директивы Alpine.js html

🟡 intermediate

Полное руководство по директивам Alpine.js и их использованию

<!-- Alpine.js Directives Guide -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alpine.js Directives</title>
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .demo { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
        .demo h3 { margin-top: 0; color: #333; }
        button { margin: 5px; padding: 8px 16px; cursor: pointer; }
        input, select, textarea { margin: 5px; padding: 5px; }
        .highlight { background: yellow; }
        .error { color: red; }
        .success { color: green; }
        .fade-enter { opacity: 0; }
        .fade-enter-active { transition: opacity 0.5s; }
        .slide-enter { transform: translateX(-100%); }
        .slide-enter-active { transition: transform 0.3s; }
        .box { padding: 20px; margin: 10px 0; background: #f0f0f0; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>Alpine.js Directives Guide</h1>

    <!-- 1. x-data - Component Initialization -->
    <div class="demo">
        <h3>1. x-data - Component Initialization</h3>
        <div x-data="{ message: 'Hello World!' }">
            <p x-text="message"></p>
        </div>

        <div x-data="{
            count: 0,
            increment() { this.count++ },
            decrement() { this.count-- }
        }">
            <button x-on:click="decrement()">-</button>
            <span x-text="count"></span>
            <button x-on:click="increment()">+</button>
        </div>
    </div>

    <!-- 2. x-init - Initialization Hook -->
    <div class="demo">
        <h3>2. x-init - Initialization Hook</h3>
        <div x-data="{
            time: new Date().toLocaleTimeString(),
            startClock() {
                setInterval(() => {
                    this.time = new Date().toLocaleTimeString();
                }, 1000);
            }
        }"
             x-init="startClock()">
            <p>Current time: <span x-text="time"></span></p>
        </div>

        <div x-data="{ data: null }"
             x-init="fetch('https://jsonplaceholder.typicode.com/posts/1')
                     .then(response => response.json())
                     .then(json => data = json)">
            <div x-show="data">
                <p><strong>Title:</strong> <span x-text="data.title"></span></p>
                <p><strong>Body:</strong> <span x-text="data.body"></span></p>
            </div>
            <div x-show="!data">
                <p>Loading...</p>
            </div>
        </div>
    </div>

    <!-- 3. x-show / x-hide - Conditional Rendering -->
    <div class="demo">
        <h3>3. x-show / x-hide - Conditional Rendering</h3>
        <div x-data="{
            isVisible: true,
            hasError: false,
            isLoading: false
        }">
            <button x-on:click="isVisible = !isVisible">Toggle Visibility</button>
            <button x-on:click="hasError = !hasError">Toggle Error</button>
            <button x-on:click="isLoading = !isLoading">Toggle Loading</button>

            <div class="box" x-show="isVisible">
                This content is conditionally visible
            </div>

            <div class="box error" x-show="hasError">
                Error message goes here
            </div>

            <div class="box" x-show="isLoading">
                Loading... Please wait
            </div>
        </div>
    </div>

    <!-- 4. x-if - True/False Block Rendering -->
    <div class="demo">
        <h3>4. x-if - True/False Block Rendering</h3>
        <div x-data="{ user: null }">
            <button x-on:click="user = { name: 'John', role: 'admin' }">
                Login as Admin
            </button>
            <button x-on:click="user = { name: 'Guest', role: 'guest' }">
                Login as Guest
            </button>
            <button x-on:click="user = null">
                Logout
            </button>

            <template x-if="user">
                <div class="box">
                    <p>Welcome, <span x-text="user.name"></span>!</p>
                    <template x-if="user.role === 'admin'">
                        <p>You have admin privileges.</p>
                    </template>
                    <template x-if="user.role === 'guest'">
                        <p>You have guest access.</p>
                    </template>
                </div>
            </template>

            <template x-if="!user">
                <div class="box">
                    <p>Please log in to continue.</p>
                </div>
            </template>
        </div>
    </div>

    <!-- 5. x-for - Looping -->
    <div class="demo">
        <h3>5. x-for - Looping</h3>
        <div x-data="{
            users: [
                { id: 1, name: 'Alice', age: 25, role: 'admin' },
                { id: 2, name: 'Bob', age: 30, role: 'user' },
                { id: 3, name: 'Charlie', age: 35, role: 'user' }
            ],
            addUser() {
                this.users.push({
                    id: this.users.length + 1,
                    name: 'User ' + (this.users.length + 1),
                    age: 20,
                    role: 'user'
                });
            }
        }">
            <button x-on:click="addUser()">Add User</button>

            <h4>Users List:</h4>
            <template x-for="user in users">
                <div class="box">
                    <p><strong>Name:</strong> <span x-text="user.name"></span></p>
                    <p><strong>Age:</strong> <span x-text="user.age"></span></p>
                    <p><strong>Role:</strong> <span x-text="user.role"></span></p>
                </div>
            </template>

            <h4>With Index:</h4>
            <template x-for="(user, index) in users">
                <div class="box">
                    <p><span x-text="index + 1"></span>. <span x-text="user.name"></span></p>
                </div>
            </template>

            <h4>Admins Only:</h4>
            <template x-for="user in users.filter(u => u.role === 'admin')">
                <div class="box">
                    <p>Admin: <span x-text="user.name"></span></p>
                </div>
            </template>
        </div>
    </div>

    <!-- 6. x-text - Text Content -->
    <div class="demo">
        <h3>6. x-text - Text Content</h3>
        <div x-data="{
            message: 'Hello Alpine.js',
            htmlContent: '<strong>Bold Text</strong>',
            computeGreeting() {
                return 'Welcome to ' + this.message;
            }
        }">
            <p>Direct text: <span x-text="message"></span></p>
            <p>Computed text: <span x-text="computeGreeting()"></span></p>
            <p>Escaped HTML: <span x-text="htmlContent"></span></p>
            <p>String manipulation: <span x-text="message.toUpperCase()"></span></p>
            <p>Template literal: <span x-text="`Length: ${message.length}`"></span></p>
        </div>
    </div>

    <!-- 7. x-html - HTML Content -->
    <div class="demo">
        <h3>7. x-html - HTML Content (Use with caution)</h3>
        <div x-data="{
            safeHtml: '<p>This is <strong>safe</strong> HTML</p>',
            userInput: '',
            renderedInput: ''
        }">
            <div>
                <label>Enter HTML (will be rendered):</label>
                <input
                    type="text"
                    x-model="userInput"
                    placeholder="Enter some HTML"
                >
                <button x-on:click="renderedInput = userInput">Render HTML</button>
            </div>

            <h4>Safe HTML:</h4>
            <div x-html="safeHtml" class="box"></div>

            <h4>User Input (warning - this can be dangerous!):</h4>
            <div x-show="renderedInput" x-html="renderedInput" class="box"></div>

            <p style="color: orange; font-weight: bold;">
                ⚠️ Warning: x-html can expose you to XSS attacks. Only use with trusted content!
            </p>
        </div>
    </div>

    <!-- 8. x-model - Two-way Data Binding -->
    <div class="demo">
        <h3>8. x-model - Two-way Data Binding</h3>
        <div x-data="{
            text: '',
            number: 0,
            checkbox: false,
            radio: 'option1',
            select: 'apple',
            textarea: '',
            multiSelect: [],
            terms: false
        }">
            <h4>Text Input:</h4>
            <input type="text" x-model="text" placeholder="Type something...">
            <p>You typed: <span x-text="text"></span></p>

            <h4>Number Input:</h4>
            <input type="number" x-model.number="number">
            <p>Number doubled: <span x-text="number * 2"></span></p>

            <h4>Checkbox:</h4>
            <label>
                <input type="checkbox" x-model="checkbox">
                I agree to terms (<span x-text="checkbox ? 'Yes' : 'No'"></span>)
            </label>

            <h4>Radio Buttons:</h4>
            <label><input type="radio" x-model="radio" value="option1"> Option 1</label>
            <label><input type="radio" x-model="radio" value="option2"> Option 2</label>
            <p>Selected: <span x-text="radio"></span></p>

            <h4>Select Dropdown:</h4>
            <select x-model="select">
                <option value="apple">Apple</option>
                <option value="banana">Banana</option>
                <option value="orange">Orange</option>
            </select>
            <p>Selected fruit: <span x-text="select"></span></p>

            <h4>Textarea:</h4>
            <textarea x-model="textarea" rows="3" style="width: 100%;"></textarea>
            <p>Character count: <span x-text="textarea.length"></span></p>

            <h4>Multi-select:</h4>
            <select x-model="multiSelect" multiple>
                <option value="red">Red</option>
                <option value="blue">Blue</option>
                <option value="green">Green</option>
            </select>
            <p>Selected colors: <span x-text="multiSelect.join(', ')"></span></p>
        </div>
    </div>

    <!-- 9. x-on - Event Handling -->
    <div class="demo">
        <h3>9. x-on - Event Handling</h3>
        <div x-data="{
            message: 'Click me!',
            mousePosition: { x: 0, y: 0 },
            keyPress: '',
            formInput: '',
            handleClick() {
                this.message = 'Clicked!';
                setTimeout(() => {
                    this.message = 'Click me!';
                }, 1000);
            },
            handleMouseMove(event) {
                this.mousePosition.x = event.offsetX;
                this.mousePosition.y = event.offsetY;
            },
            handleKeydown(event) {
                this.keyPress = event.key;
            },
            handleSubmit(event) {
                event.preventDefault();
                alert('Form submitted: ' + this.formInput);
                this.formInput = '';
            }
        }">
            <!-- Click events -->
            <button x-on:click="handleClick()" x-text="message"></button>
            <button x-on:click="message = 'Directly changed!'">Direct Change</button>
            <button x-on:click.once="message = 'This only works once!'">Once Only</button>

            <!-- Mouse events -->
            <div
                class="box"
                x-on:mousemove="handleMouseMove($event)"
                style="height: 100px; position: relative;"
            >
                Mouse position: (<span x-text="mousePosition.x"></span>, <span x-text="mousePosition.y"></span>)
            </div>

            <!-- Keyboard events -->
            <input
                type="text"
                x-on:keydown="handleKeydown($event)"
                placeholder="Type to see key pressed"
            >
            <p>Last key: <span x-text="keyPress"></span></p>

            <!-- Form events -->
            <form x-on:submit="handleSubmit($event)">
                <input
                    type="text"
                    x-model="formInput"
                    placeholder="Enter text to submit"
                    required
                >
                <button type="submit">Submit</button>
            </form>

            <!-- Modifier examples -->
            <div style="margin-top: 15px;">
                <p>With modifiers:</p>
                <input
                    type="text"
                    placeholder="Enter and press Enter"
                    x-on:keydown.enter="alert('Enter pressed!')"
                >
                <button
                    x-on:click.shift="alert('Shift+Click!')"
                    x-on:click.ctrl="alert('Ctrl+Click!')"
                    x-on:click.alt="alert('Alt+Click!')"
                >
                    Click with modifier keys
                </button>
            </div>
        </div>
    </div>

    <!-- 10. x-bind - Attribute Binding -->
    <div class="demo">
        <h3>10. x-bind - Attribute Binding</h3>
        <div x-data="{
            color: '#3B82F6',
            width: 100,
            isDisabled: false,
            href: 'https://example.com',
            alt: 'Sample image',
            sizes: ['small', 'medium', 'large'],
            selectedSize: 'medium'
        }">
            <!-- Style binding -->
            <div
                class="box"
                :style="`background-color: ${color}; width: ${width}px; height: 50px;`"
            ></div>

            <!-- Class binding -->
            <div
                class="box"
                :class="{
                    'highlight': color === '#3B82F6',
                    'error': color === '#EF4444',
                    'success': color === '#10B981'
                }"
            >
                Dynamic styling based on color
            </div>

            <!-- Attribute binding -->
            <a :href="href" :target="'_blank'">External Link</a><br>
            <img :src="'https://picsum.photos/100/100'" :alt="alt" style="margin-top: 10px;"><br>

            <!-- Multiple attributes -->
            <button
                :disabled="isDisabled"
                :style="`background-color: ${isDisabled ? '#ccc' : '#3B82F6'};`"
            >
                <span x-text="isDisabled ? 'Disabled' : 'Enabled'"></span>
            </button>
            <button x-on:click="isDisabled = !isDisabled">
                Toggle Disabled
            </button>

            <!-- Select binding -->
            <select x-model="selectedSize">
                <template x-for="size in sizes">
                    <option :value="size" x-text="size"></option>
                </template>
            </select>
            <span>Selected: <span x-text="selectedSize"></span></span>

            <!-- Dynamic attributes -->
            <div
                x-data="dynamicAttrs = { id: 'dynamic-id', 'data-value': '123' }"
                v-bind="dynamicAttrs"
            >
                This div has dynamic attributes
            </div>
        </div>
    </div>

    <!-- 11. x-transition - Transitions -->
    <div class="demo">
        <h3>11. x-transition - Transitions</h3>
        <div x-data="{
            show: false,
            showSlide: false,
            showFade: false,
            items: ['Item 1', 'Item 2', 'Item 3'],
            currentItemIndex: 0,
            nextItem() {
                this.currentItemIndex = (this.currentItemIndex + 1) % this.items.length;
            }
        }">
            <!-- Basic transition -->
            <button x-on:click="show = !show">Toggle Basic Transition</button>
            <div
                x-show="show"
                x-transition
                class="box"
            >
                This content fades in and out
            </div>

            <!-- Fade transition -->
            <button x-on:click="showFade = !showFade">Toggle Fade Transition</button>
            <div
                x-show="showFade"
                x-transition:enter="transition ease-out duration-300"
                x-transition:enter-start="opacity-0"
                x-transition:enter-end="opacity-100"
                x-transition:leave="transition ease-in duration-300"
                x-transition:leave-start="opacity-100"
                x-transition:leave-end="opacity-0"
                class="box"
            >
                This content has a custom fade transition
            </div>

            <!-- Slide transition -->
            <button x-on:click="showSlide = !showSlide">Toggle Slide Transition</button>
            <div
                x-show="showSlide"
                x-transition:enter="transition ease-out duration-500"
                x-transition:enter-start="opacity-0 transform -translate-x-full"
                x-transition:enter-end="opacity-100 transform translate-x-0"
                x-transition:leave="transition ease-in duration-500"
                x-transition:leave-start="opacity-100 transform translate-x-0"
                x-transition:leave-end="opacity-0 transform -translate-x-full"
                class="box"
            >
                This content slides in and out
            </div>

            <!-- Carousel with transitions -->
            <div style="position: relative; height: 100px;">
                <button x-on:click="nextItem()">Next Item</button>
                <template x-for="(item, index) in items">
                    <div
                        x-show="currentItemIndex === index"
                        x-transition:enter="transition ease-out duration-300"
                        x-transition:enter-start="opacity-0"
                        x-transition:enter-end="opacity-100"
                        x-transition:leave="transition ease-in duration-300"
                        x-transition:leave-start="opacity-100"
                        x-transition:leave-end="opacity-0"
                        style="position: absolute; width: 100%;"
                    >
                        <div class="box">
                            <h4 x-text="item"></h4>
                            <p>Item <span x-text="index + 1"></span> of <span x-text="items.length"></span></p>
                        </div>
                    </div>
                </template>
            </div>
        </div>
    </div>

    <!-- 12. x-ref - Element References -->
    <div class="demo">
        <h3>12. x-ref - Element References</h3>
        <div x-data="{
            message: '',
            inputRef: null,
            init() {
                // Auto-focus input after 2 seconds
                setTimeout(() => {
                    if (this.$refs.inputRef) {
                        this.$refs.inputRef.focus();
                    }
                }, 2000);
            },
            focusInput() {
                this.$refs.inputRef.focus();
            },
            scrollToTop() {
                this.$refs.containerRef.scrollTo({ top: 0, behavior: 'smooth' });
            },
            getInputValue() {
                this.message = 'Input value: ' + this.$refs.inputRef.value;
            },
            changeBackgroundColor() {
                this.$refs.boxRef.style.backgroundColor =
                    '#' + Math.floor(Math.random()*16777215).toString(16);
            }
        }"
             x-init="init()">
            <!-- Reference to input -->
            <div>
                <input
                    type="text"
                    x-ref="inputRef"
                    placeholder="This input will auto-focus after 2 seconds"
                >
                <button x-on:click="focusInput()">Focus Input</button>
                <button x-on:click="getInputValue()">Get Value</button>
            </div>

            <!-- Reference to div -->
            <div
                x-ref="boxRef"
                class="box"
                style="margin-top: 10px;"
            >
                Click the button to change my background color
            </div>
            <button x-on:click="changeBackgroundColor()">Change Background</button>

            <!-- Reference to container -->
            <div
                x-ref="containerRef"
                style="height: 100px; overflow-y: auto; border: 1px solid #ccc; margin-top: 10px;"
            >
                <p style="height: 200px;">Scrollable content</p>
                <p>More content...</p>
            </div>
            <button x-on:click="scrollToTop()">Scroll to Top</button>

            <!-- Show message -->
            <p x-show="message" x-text="message" style="margin-top: 10px; color: green;"></p>
        </div>
    </div>

    <!-- 13. x-cloak - Prevent Flash -->
    <div class="demo">
        <h3>13. x-cloak - Prevent Flash</h3>
        <style>
            [x-cloak] { display: none !important; }
        </style>

        <div x-data="{ message: 'This content was hidden initially' }">
            <div x-cloak>
                <h4>This content is hidden until Alpine.js loads:</h4>
                <p x-text="message"></p>
            </div>

            <div>
                <h4>This content is visible immediately:</h4>
                <p>This will show even before Alpine.js loads</p>
            </div>
        </div>

        <p style="color: blue;">
            The content above with x-cloak is hidden initially to prevent the "flash" of unstyled content before Alpine.js initializes.
        </p>
    </div>

</body>
</html>

💻 Магические свойства Alpine.js html

🟡 intermediate

Работа с магическими свойствами и встроенными помощниками Alpine.js

<!-- Alpine.js Magic Properties -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alpine.js Magic Properties</title>
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .demo { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
        .demo h3 { margin-top: 0; color: #333; }
        .box { padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border: 1px solid #dee2e6; }
        .highlight { background: #fff3cd; border-color: #ffeaa7; }
        .error { background: #f8d7da; border-color: #f5c6cb; color: #721c24; }
        .success { background: #d4edda; border-color: #c3e6cb; color: #155724; }
        button { margin: 5px; padding: 8px 16px; cursor: pointer; }
        input, select { margin: 5px; padding: 5px; }
        .scroll-container { height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; }
        .modal {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center;
            z-index: 1000;
        }
        .modal-content { background: white; padding: 20px; border-radius: 8px; max-width: 500px; }
    </style>
</head>
<body>
    <h1>Alpine.js Magic Properties</h1>

    <!-- 1. $el - Current Element -->
    <div class="demo">
        <h3>1. $el - Current Element</h3>
        <div x-data="{ message: 'Click me!' }">
            <button
                x-on:click="$el.textContent = 'Clicked!'"
                style="padding: 10px 20px; font-size: 16px;"
                x-text="message"
            ></button>

            <div
                class="box"
                x-on:mouseover="$el.style.backgroundColor = '#e3f2fd'"
                x-on:mouseout="$el.style.backgroundColor = '#f8f9fa'"
            >
                Hover over this box to change its background
            </div>

            <input
                type="text"
                x-on:focus="$el.style.borderColor = '#2196f3'"
                x-on:blur="$el.style.borderColor = '#ddd'"
                placeholder="Focus me to see border change"
            >
        </div>
    </div>

    <!-- 2. $el - Element Manipulation -->
    <div class="demo">
        <h3>2. $el - Advanced Element Manipulation</h3>
        <div x-data="{
            counter: 0,
            increment() {
                this.counter++;
                // Add highlight effect
                $el.classList.add('highlight');
                setTimeout(() => {
                    $el.classList.remove('highlight');
                }, 500);
            },
            changeColor() {
                const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#6c5ce7'];
                const randomColor = colors[Math.floor(Math.random() * colors.length)];
                $el.style.backgroundColor = randomColor;
                $el.style.color = 'white';
            }
        }">
            <div
                class="box"
                x-on:click="increment()"
                style="cursor: pointer;"
            >
                <p>Click count: <span x-text="counter"></span></p>
                <small>Click this box to increment and see highlight effect</small>
            </div>

            <button x-on:click="changeColor()">Change My Color</button>
        </div>
    </div>

    <!-- 3. $refs - Element References -->
    <div class="demo">
        <h3>3. $refs - Element References</h3>
        <div x-data="{
            message: '',
            focusInput() {
                this.$refs.myInput.focus();
                this.$refs.myInput.select();
            },
            getInputValue() {
                this.message = this.$refs.myInput.value;
            },
            scrollToTop() {
                this.$refs.scrollContainer.scrollTop = 0;
            },
            scrollToBottom() {
                this.$refs.scrollContainer.scrollTop = this.$refs.scrollContainer.scrollHeight;
            },
            toggleClass() {
                this.$refs.targetBox.classList.toggle('highlight');
            },
            getElementInfo() {
                const element = this.$refs.infoElement;
                this.message = `Tag: ${element.tagName}, ID: ${element.id}, Classes: ${element.className}`;
            }
        }">
            <input
                type="text"
                x-ref="myInput"
                placeholder="This is the referenced input"
                value="Hello World!"
            >
            <button x-on:click="focusInput()">Focus & Select</button>
            <button x-on:click="getInputValue()">Get Value</button>

            <div
                x-ref="scrollContainer"
                class="scroll-container"
            >
                <p>This is scrollable content...</p>
                <p>Scroll down and use the buttons to navigate</p>
                <div style="height: 100px; background: #f0f0f0; margin: 10px 0;">More content 1</div>
                <div style="height: 100px; background: #e0e0e0; margin: 10px 0;">More content 2</div>
                <div style="height: 100px; background: #d0d0d0; margin: 10px 0;">More content 3</div>
                <div style="height: 100px; background: #c0c0c0; margin: 10px 0;">End of content</div>
            </div>
            <button x-on:click="scrollToTop()">Scroll to Top</button>
            <button x-on:click="scrollToBottom()">Scroll to Bottom</button>

            <div
                x-ref="targetBox"
                class="box"
            >
                <button x-on:click="toggleClass()">Toggle My Class</button>
            </div>

            <div
                x-ref="infoElement"
                id="info-div"
                class="box highlight"
            >
                Element with ID and multiple classes
            </div>
            <button x-on:click="getElementInfo()">Get Element Info</button>

            <p x-show="message" x-text="message" class="success"></p>
        </div>
    </div>

    <!-- 4. $store - Global Store -->
    <div class="demo">
        <h3>4. $store - Global Store</h3>
        <div x-data="{
            theme: 'light',
            user: { name: 'John', role: 'admin' },
            notifications: [],
            addNotification(message) {
                this.notifications.push({
                    id: Date.now(),
                    message,
                    timestamp: new Date().toLocaleTimeString()
                });
                // Keep only last 5 notifications
                if (this.notifications.length > 5) {
                    this.notifications.shift();
                }
            },
            clearNotifications() {
                this.notifications = [];
            }
        }"
             x-init="$store.theme = theme; $store.user = user; $store.notifications = notifications;">

            <!-- Theme switcher -->
            <div class="box">
                <h4>Theme: <span x-text="$store.theme"></span></h4>
                <button x-on:click="$store.theme = 'light'">Light Theme</button>
                <button x-on:click="$store.theme = 'dark'">Dark Theme</button>
                <p>Current theme stored in $store.theme: <span x-text="$store.theme"></span></p>
            </div>

            <!-- User info -->
            <div class="box">
                <h4>User Info (from $store):</h4>
                <p>Name: <span x-text="$store.user.name"></span></p>
                <p>Role: <span x-text="$store.user.role"></span></p>
                <button x-on:click="$store.user.name = 'Jane'">Change Name to Jane</button>
            </div>

            <!-- Notifications -->
            <div class="box">
                <h4>Notifications:</h4>
                <button x-on:click="addNotification('New notification!')">
                    Add Notification
                </button>
                <button x-on:click="clearNotifications()">
                    Clear All
                </button>

                <div style="margin-top: 10px;">
                    <template x-for="notification in $store.notifications">
                        <div class="highlight" style="margin-bottom: 5px; padding: 8px;">
                            <small x-text="notification.timestamp"></small>:
                            <span x-text="notification.message"></span>
                        </div>
                    </template>
                </div>

                <p x-show="$store.notifications.length === 0">No notifications</p>
            </div>
        </div>
    </div>

    <!-- 5. $event - Event Object -->
    <div class="demo">
        <h3>5. $event - Event Object</h3>
        <div x-data="{
            mousePosition: { x: 0, y: 0 },
            keyInfo: '',
            clickInfo: '',
            submitInfo: '',
            handleMouseMove(e) {
                this.mousePosition.x = e.offsetX;
                this.mousePosition.y = e.offsetY;
            },
            handleKeydown(e) {
                this.keyInfo = `Key: ${e.key}, Code: ${e.code}, Ctrl: ${e.ctrlKey}, Shift: ${e.shiftKey}`;
            },
            handleClick(e) {
                this.clickInfo = `Clicked at (${e.clientX}, ${e.clientY}) on element: ${e.target.tagName}`;
            },
            handleSubmit(e) {
                e.preventDefault();
                this.submitInfo = `Form submitted with ${new FormData(e.target).get('username')}`;
            }
        }">
            <!-- Mouse events -->
            <div
                class="box"
                style="height: 100px; cursor: crosshair; position: relative;"
                x-on:mousemove="handleMouseMove($event)"
                x-on:click="handleClick($event)"
            >
                <p>Move mouse here: (<span x-text="mousePosition.x"></span>, <span x-text="mousePosition.y"></span>)</p>
                <p x-show="clickInfo" x-text="clickInfo"></p>
            </div>

            <!-- Keyboard events -->
            <input
                type="text"
                x-on:keydown="handleKeydown($event)"
                placeholder="Type to see event info"
                style="width: 300px;"
            >
            <p x-show="keyInfo" x-text="keyInfo"></p>

            <!-- Form events -->
            <form x-on:submit="handleSubmit($event)">
                <input type="text" name="username" placeholder="Username" required>
                <button type="submit">Submit</button>
            </form>
            <p x-show="submitInfo" x-text="submitInfo"></p>

            <!-- Event prevention and propagation -->
            <div
                class="box"
                x-on:click="alert('Outer div clicked!')"
                style="position: relative; padding: 20px;"
            >
                <p>Outer div (clickable)</p>
                <button
                    x-on:click.stop="alert('Button clicked without propagating')"
                >
                    Stop Propagation
                </button>
                <button
                    x-on:click.prevent="alert('Default prevented')"
                >
                    Prevent Default
                </button>
                <button
                    x-on:click.self="alert('Only when clicking the div itself')"
                >
                    Self Only
                </button>
            </div>
        </div>
    </div>

    <!-- 6. $dispatch - Custom Events -->
    <div class="demo">
        <h3>6. $dispatch - Custom Events</h3>
        <div x-data="{
            customMessage: '',
            eventData: null,
            lastEvent: '',
            handleMessage(event) {
                this.customMessage = event.detail.message;
                this.eventData = event.detail;
                this.lastEvent = 'Received: ' + event.type;
            },
            handleNotification(event) {
                alert('Notification: ' + event.detail.message);
            }
        }"
             x-on:custom-message="handleMessage($event)"
             x-on:show-notification="handleNotification($event)">

            <!-- Event dispatcher -->
            <div class="box">
                <h4>Event Dispatchers:</h4>
                <button x-on:click="$dispatch('custom-message', { message: 'Hello from dispatcher!', timestamp: Date.now() })">
                    Dispatch Custom Message
                </button>
                <button x-on:click="$dispatch('show-notification', { message: 'This is a notification!' })">
                    Show Notification
                </button>
                <button x-on:click="$dispatch('user-action', { action: 'click', element: 'button' })">
                    Dispatch User Action
                </button>
            </div>

            <!-- Event listener -->
            <div class="box">
                <h4>Event Receiver:</h4>
                <p>Custom Message: <span x-text="customMessage"></span></p>
                <p>Last Event: <span x-text="lastEvent"></span></p>
                <div x-show="eventData">
                    <h5>Event Details:</h5>
                    <template x-for="(value, key) in eventData">
                        <p><small><strong x-text="key"></strong>: <span x-text="value"></span></small></p>
                    </template>
                </div>
            </div>

            <!-- Cross-component communication -->
            <div class="box">
                <h4>Cross-Component Communication:</h4>
                <button x-on:click="$dispatch('theme-change', { theme: 'dark' })">
                    Change Theme to Dark
                </button>
                <button x-on:click="$dispatch('theme-change', { theme: 'light' })">
                    Change Theme to Light
                </button>
            </div>

            <div
                x-data="{ currentTheme: 'light' }"
                x-on:theme-change="currentTheme = $event.detail.theme"
                class="box"
            >
                <p>Current Theme: <span x-text="currentTheme"></span></p>
                <div :style="'background: ' + (currentTheme === 'dark' ? '#333' : '#fff') + '; color: ' + (currentTheme === 'dark' ? '#fff' : '#333') + '; padding: 10px;'">
                    Themed content based on dispatched events
                </div>
            </div>
        </div>
    </div>

    <!-- 7. $watch - Reactive Watching -->
    <div class="demo">
        <h3>7. $watch - Reactive Watching</h3>
        <div x-data="{
            counter: 0,
            message: '',
            searchQuery: '',
            results: [],
            user: { name: '', email: '' },
            watchCount: 0,
            init() {
                // Watch for counter changes
                this.$watch('counter', (value) => {
                    this.message = `Counter changed to: ${value}`;
                    this.watchCount++;
                });

                // Watch for search query
                this.$watch('searchQuery', (value) => {
                    if (value.length > 2) {
                        // Simulate search
                        setTimeout(() => {
                            this.results = [`Result for "${value}"`, `Another result for "${value}"`];
                        }, 500);
                    } else {
                        this.results = [];
                    }
                });

                // Deep watch user object
                this.$watch('user', (value) => {
                    console.log('User changed:', value);
                }, { deep: true });
            }
        }">
            <div class="box">
                <h4>Counter Watcher:</h4>
                <button x-on:click="counter++">Increment</button>
                <button x-on:click="counter--">Decrement</button>
                <button x-on:click="counter = 0">Reset</button>
                <p>Counter: <span x-text="counter"></span></p>
                <p>Watch triggered: <span x-text="watchCount"></span> times</p>
                <p x-show="message" x-text="message"></p>
            </div>

            <div class="box">
                <h4>Search Watcher:</h4>
                <input
                    type="text"
                    x-model="searchQuery"
                    placeholder="Search for something (type 3+ characters)"
                    style="width: 300px;"
                >
                <div x-show="results.length > 0">
                    <h5>Results:</h5>
                    <template x-for="result in results">
                        <div class="highlight" style="padding: 5px; margin: 2px 0;">
                            <span x-text="result"></span>
                        </div>
                    </template>
                </div>
            </div>

            <div class="box">
                <h4>Deep Watcher:</h4>
                <input
                    type="text"
                    x-model="user.name"
                    placeholder="Name"
                >
                <input
                    type="email"
                    x-model="user.email"
                    placeholder="Email"
                >
                <p>User object: <span x-text="JSON.stringify(user)"></span></p>
                <small>Check console to see deep watcher in action</small>
            </div>
        </div>
    </div>

    <!-- 8. $nextTick - DOM Update Timing -->
    <div class="demo">
        <h3>8. $nextTick - DOM Update Timing</h3>
        <div x-data="{
            items: ['Item 1', 'Item 2'],
            newItem: '',
            message: '',
            addItem() {
                this.items.push(this.newItem);
                this.newItem = '';

                // Wait for DOM to update
                this.$nextTick(() => {
                    // Scroll to the new item
                    const allItems = document.querySelectorAll('.list-item');
                    const lastItem = allItems[allItems.length - 1];
                    if (lastItem) {
                        lastItem.scrollIntoView({ behavior: 'smooth' });
                    }
                    this.message = 'New item added and scrolled into view!';
                    setTimeout(() => {
                        this.message = '';
                    }, 2000);
                });
            },
            highlightLastItem() {
                this.$nextTick(() => {
                    const items = document.querySelectorAll('.list-item');
                    if (items.length > 0) {
                        items[items.length - 1].classList.add('highlight');
                        setTimeout(() => {
                            items[items.length - 1].classList.remove('highlight');
                        }, 1000);
                    }
                });
            }
        }">
            <div class="box">
                <h4>Add Items with Auto-Scroll:</h4>
                <input
                    type="text"
                    x-model="newItem"
                    placeholder="Enter item name"
                    x-on:keyup.enter="addItem()"
                >
                <button x-on:click="addItem()">Add Item</button>
                <button x-on:click="highlightLastItem()">Highlight Last Item</button>

                <div class="scroll-container" style="max-height: 200px; overflow-y: auto; margin-top: 10px;">
                    <template x-for="(item, index) in items">
                        <div
                            class="list-item"
                            style="padding: 8px; margin: 2px 0; background: #f8f9fa; border-left: 3px solid #007bff;"
                        >
                            <span x-text="index + 1"></span>. <span x-text="item"></span>
                        </div>
                    </template>
                </div>

                <p x-show="message" x-text="message" class="success"></p>
            </div>
        </div>
    </div>

</body>
</html>

💻 Плагины и утилиты Alpine.js html

🔴 complex

Плагины, расширения и утилитарные функции Alpine.js

<!-- Alpine.js Plugins and Utilities -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alpine.js Plugins and Utilities</title>
    <!-- Alpine.js Core -->
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <!-- Alpine.js Plugins -->
    <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>

    <style>
        body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
        .demo { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background: #fafafa; }
        .demo h3 { margin-top: 0; color: #333; border-bottom: 2px solid #007bff; padding-bottom: 5px; }
        .box { padding: 15px; margin: 10px 0; background: white; border-radius: 5px; border: 1px solid #e0e0e0; }
        .highlight { background: #fff3cd; border-color: #ffeaa7; }
        .success { background: #d4edda; border-color: #c3e6cb; color: #155724; }
        .error { background: #f8d7da; border-color: #f5c6cb; color: #721c24; }
        button {
            margin: 5px; padding: 10px 15px; cursor: pointer;
            border: 1px solid #007bff; background: #007bff; color: white;
            border-radius: 4px; transition: all 0.2s;
        }
        button:hover { background: #0056b3; }
        button.secondary { background: #6c757d; border-color: #6c757d; }
        button.danger { background: #dc3545; border-color: #dc3545; }
        input, select, textarea { margin: 5px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
        .tab-content { padding: 15px; border: 1px solid #ddd; border-top: none; }
        .scroll-spy-nav {
            position: sticky; top: 20px; background: white; padding: 10px;
            border: 1px solid #ddd; border-radius: 5px; max-width: 200px;
        }
        .modal-overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.5); display: flex; align-items: center;
            justify-content: center; z-index: 1000;
        }
        .modal-content {
            background: white; padding: 30px; border-radius: 8px;
            max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto;
        }
    </style>
</head>
<body>
    <h1>Alpine.js Plugins and Utilities</h1>

    <!-- 1. Mask Plugin -->
    <div class="demo">
        <h3>1. Mask Plugin</h3>
        <div x-data="{
            phone: '',
            date: '',
            creditCard: '',
            currency: '',
            time: '',
            postalCode: ''
        }">
            <div class="grid">
                <div class="box">
                    <h4>Phone Number</h4>
                    <input
                        type="text"
                        x-model="phone"
                        x-mask="(999) 999-9999"
                        placeholder="(123) 456-7890"
                    >
                    <p>Formatted: <strong x-text="phone"></strong></p>
                </div>

                <div class="box">
                    <h4>Date</h4>
                    <input
                        type="text"
                        x-model="date"
                        x-mask="99/99/9999"
                        placeholder="12/25/2023"
                    >
                    <p>Formatted: <strong x-text="date"></strong></p>
                </div>

                <div class="box">
                    <h4>Credit Card</h4>
                    <input
                        type="text"
                        x-model="creditCard"
                        x-mask="9999 9999 9999 9999"
                        placeholder="1234 5678 9012 3456"
                    >
                    <p>Formatted: <strong x-text="creditCard"></strong></p>
                </div>

                <div class="box">
                    <h4>Currency</h4>
                    <input
                        type="text"
                        x-model="currency"
                        x-mask="$9,999.99"
                        placeholder="$1,234.56"
                    >
                    <p>Formatted: <strong x-text="currency"></strong></p>
                </div>

                <div class="box">
                    <h4>Time</h4>
                    <input
                        type="text"
                        x-model="time"
                        x-mask="99:99"
                        placeholder="14:30"
                    >
                    <p>Formatted: <strong x-text="time"></strong></p>
                </div>

                <div class="box">
                    <h4>Postal Code</h4>
                    <input
                        type="text"
                        x-model="postalCode"
                        x-mask="99999"
                        placeholder="12345"
                    >
                    <p>Formatted: <strong x-text="postalCode"></strong></p>
                </div>
            </div>
        </div>
    </div>

    <!-- 2. Anchor Plugin (Smooth Scroll) -->
    <div class="demo">
        <h3>2. Anchor Plugin (Smooth Scroll)</h3>
        <div x-data="{ activeSection: '' }">
            <nav class="scroll-spy-nav">
                <h4>Navigation:</h4>
                <a href="#section1" x-anchor>Section 1</a><br>
                <a href="#section2" x-anchor>Section 2</a><br>
                <a href="#section3" x-anchor>Section 3</a><br>
                <a href="#section4" x-anchor>Section 4</a><br>
                <p>Active: <strong x-text="activeSection"></strong></p>
            </nav>

            <div style="margin-left: 220px;">
                <section id="section1" class="box" style="min-height: 200px; margin-bottom: 20px;">
                    <h3>Section 1</h3>
                    <p>This is the first section. Click the navigation links to see smooth scrolling in action.</p>
                </section>

                <section id="section2" class="box" style="min-height: 200px; margin-bottom: 20px;">
                    <h3>Section 2</h3>
                    <p>This is the second section. The anchor plugin provides smooth scrolling between sections.</p>
                </section>

                <section id="section3" class="box" style="min-height: 200px; margin-bottom: 20px;">
                    <h3>Section 3</h3>
                    <p>This is the third section. Scroll spy functionality highlights the active section.</p>
                </section>

                <section id="section4" class="box" style="min-height: 200px; margin-bottom: 20px;">
                    <h3>Section 4</h3>
                    <p>This is the fourth section. The scroll behavior is smooth and automatic.</p>
                </section>
            </div>
        </div>
    </div>

    <!-- 3. Focus Plugin -->
    <div class="demo">
        <h3>3. Focus Plugin (Trap Focus)</h3>
        <div x-data="{
            showModal: false,
            formData: {
                name: '',
                email: '',
                message: ''
            }
        }">
            <button x-on:click="showModal = true">Open Modal with Focus Trap</button>

            <div x-show="showModal" x-trap="showModal" class="modal-overlay">
                <div class="modal-content">
                    <h3>Contact Form</h3>
                    <p>Focus is trapped within this modal. Press Tab to navigate.</p>

                    <form>
                        <div style="margin-bottom: 15px;">
                            <label>Name:</label><br>
                            <input
                                type="text"
                                x-model="formData.name"
                                placeholder="Your name"
                                style="width: 100%;"
                            >
                        </div>

                        <div style="margin-bottom: 15px;">
                            <label>Email:</label><br>
                            <input
                                type="email"
                                x-model="formData.email"
                                placeholder="[email protected]"
                                style="width: 100%;"
                            >
                        </div>

                        <div style="margin-bottom: 15px;">
                            <label>Message:</label><br>
                            <textarea
                                x-model="formData.message"
                                placeholder="Your message"
                                rows="4"
                                style="width: 100%;"
                            ></textarea>
                        </div>

                        <div style="margin-bottom: 15px;">
                            <button
                                type="button"
                                x-on:click="alert('Form submitted: ' + JSON.stringify(formData))"
                            >
                                Submit
                            </button>
                            <button
                                type="button"
                                class="secondary"
                                x-on:click="showModal = false"
                            >
                                Cancel
                            </button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <!-- 4. Intersect Plugin -->
    <div class="demo">
        <h3>4. Intersect Plugin (Scroll Animations)</h3>
        <div>
            <div class="box"
                 x-intersect:enter.opacity-100.transition.duration-500ms
                 x-intersect:leave.opacity-0.transition.duration-500ms>
                <h3>Fade In/Out</h3>
                <p>This box fades in when it enters the viewport and fades out when it leaves.</p>
            </div>

            <div class="box"
                 x-intersect:enter.translate-y-0.transition.duration-700ms
                 x-intersect:leave.-translate-y-8.transition.duration-700ms>
                <h3>Slide Animation</h3>
                <p>This box slides up when it enters the viewport and slides down when it leaves.</p>
            </div>

            <div class="box"
                 x-data="{ hasIntersected: false }"
                 x-intersect.once="hasIntersected = true">
                <h3>Once Only</h3>
                <p>This animation only triggers once when first visible:
                   <span x-text="hasIntersected ? '✓ Triggered' : '○ Not triggered'"></span></p>
            </div>

            <div class="box"
                 x-data="{
                     visibleCount: 0,
                     countIntersect(event) {
                         if (event.detail.isIntersecting) {
                             this.visibleCount++;
                         }
                     }
                 }"
                 x-intersect.margin.-100px="countIntersect($event)">
                <h3>With Margin</h3>
                <p>Triggers when 100px from viewport edge. Visible count:
                   <strong x-text="visibleCount"></strong></p>
            </div>

            <div class="box"
                 x-data="{ threshold: 0.5 }"
                 x-intersect.threshold.50="console.log('50% visible!')">
                <h3>Custom Threshold</h3>
                <p>Triggers when 50% of this element is visible (check console).</p>
                <label>Threshold:
                    <input type="number" x-model.number="threshold" min="0" max="1" step="0.1">
                    <span x-text="threshold"></span>
                </label>
            </div>

            <!-- Add some spacing to allow scrolling -->
            <div style="height: 300px;"></div>
        </div>
    </div>

    <!-- 5. Persist Plugin -->
    <div class="demo">
        <h3>5. Persist Plugin (Local Storage)</h3>
        <div x-data="{
            preferences: {
                theme: $persist('light').as('theme'),
                language: $persist('en').as('language'),
                notifications: $persist(true).as('notifications')
            },
            user: {
                name: $persist('').as('userName'),
                visits: $persist(0).as('visitCount')
            },
            cart: $persist([]).as('shoppingCart'),
            todoList: $persist(['Learn Alpine.js', 'Build cool projects']).as('todos'),
            init() {
                this.user.visits++;
                console.log('Welcome back! Visit count:', this.user.visits);
            },
            addItem() {
                this.cart.push({
                    id: Date.now(),
                    name: 'Item ' + (this.cart.length + 1),
                    price: Math.floor(Math.random() * 100) + 1
                });
            },
            clearCart() {
                this.cart = [];
            },
            addTodo(event) {
                if (event.target.value.trim()) {
                    this.todoList.push(event.target.value.trim());
                    event.target.value = '';
                }
            },
            removeTodo(index) {
                this.todoList.splice(index, 1);
            }
        }">
            <div class="grid">
                <div class="box">
                    <h4>User Preferences</h4>
                    <div style="margin-bottom: 10px;">
                        <label>Theme:</label>
                        <select x-model="preferences.theme">
                            <option value="light">Light</option>
                            <option value="dark">Dark</option>
                            <option value="auto">Auto</option>
                        </select>
                    </div>
                    <div style="margin-bottom: 10px;">
                        <label>Language:</label>
                        <select x-model="preferences.language">
                            <option value="en">English</option>
                            <option value="es">Spanish</option>
                            <option value="fr">French</option>
                        </select>
                    </div>
                    <div style="margin-bottom: 10px;">
                        <label>
                            <input type="checkbox" x-model="preferences.notifications">
                            Enable notifications
                        </label>
                    </div>
                    <p><small>All preferences are saved to localStorage!</small></p>
                </div>

                <div class="box">
                    <h4>User Info</h4>
                    <div style="margin-bottom: 10px;">
                        <label>Name:</label>
                        <input
                            type="text"
                            x-model="user.name"
                            placeholder="Your name"
                        >
                    </div>
                    <p>Visit count: <strong x-text="user.visits"></strong></p>
                    <button class="danger" x-on:click="user.visits = 0">Reset Visits</button>
                </div>

                <div class="box">
                    <h4>Shopping Cart</h4>
                    <button x-on:click="addItem()">Add Random Item</button>
                    <button class="secondary" x-on:click="clearCart()">Clear Cart</button>
                    <div style="margin-top: 10px;">
                        <template x-for="item in cart">
                            <div style="padding: 5px; background: #f8f9fa; margin: 2px 0; border-radius: 3px;">
                                <span x-text="item.name"></span> - $<span x-text="item.price"></span>
                            </div>
                        </template>
                        <p x-show="cart.length === 0">Cart is empty</p>
                        <p x-show="cart.length > 0">
                            Total: $<strong x-text="cart.reduce((sum, item) => sum + item.price, 0)"></strong>
                        </p>
                    </div>
                </div>

                <div class="box">
                    <h4>Todo List</h4>
                    <div style="margin-bottom: 10px;">
                        <input
                            type="text"
                            x-on:keyup.enter="addTodo($event)"
                            placeholder="Add todo and press Enter"
                        >
                    </div>
                    <ul style="list-style: none; padding: 0;">
                        <template x-for="(todo, index) in todoList">
                            <li style="margin: 5px 0; padding: 8px; background: #e9ecef; border-radius: 3px;">
                                <span x-text="todo"></span>
                                <button
                                    class="danger"
                                    style="float: right; padding: 2px 8px;"
                                    x-on:click="removeTodo(index)"
                                >
                                    ×
                                </button>
                            </li>
                        </template>
                    </ul>
                    <p x-show="todoList.length === 0">No todos yet</p>
                </div>
            </div>
        </div>
    </div>

    <!-- 6. Custom Magic Properties -->
    <div class="demo">
        <h3>6. Custom Magic Properties</h3>
        <div>
            <p>Custom magic properties allow you to extend Alpine.js functionality:</p>

            <div x-data="{
                message: 'Hello Custom Magic!',
                init() {
                    // Add custom magic property
                    Alpine.magic('now', () => new Date().toLocaleTimeString());

                    // Custom magic with parameter
                    Alpine.magic('format', () => (value, type) => {
                        switch(type) {
                            case 'currency': return '$' + parseFloat(value).toFixed(2);
                            case 'percent': return value + '%';
                            case 'uppercase': return value.toUpperCase();
                            default: return value;
                        }
                    });

                    // Custom magic for API calls
                    Alpine.magic('api', () => ({
                        get: async (endpoint) => {
                            const response = await fetch(endpoint);
                            return response.json();
                        },
                        post: async (endpoint, data) => {
                            const response = await fetch(endpoint, {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify(data)
                            });
                            return response.json();
                        }
                    }));
                },
                price: 123.45,
                percentage: 75,
                text: 'hello world',
                data: null,
                async loadData() {
                    try {
                        this.data = await this.$api.get('https://jsonplaceholder.typicode.com/posts/1');
                    } catch (error) {
                        console.error('API call failed:', error);
                    }
                }
            }">
                <div class="box">
                    <h4>Custom Magic Properties in Action</h4>
                    <p>Current time: <strong x-text="$now"></strong></p>
                    <p>Price formatted: <strong x-text="$format(price, 'currency')"></strong></p>
                    <p>Percentage: <strong x-text="$format(percentage, 'percent')"></strong></p>
                    <p>Uppercase: <strong x-text="$format(text, 'uppercase')"></strong></p>

                    <button x-on:click="loadData()">Load API Data</button>
                    <div x-show="data" class="success" style="margin-top: 10px; padding: 10px;">
                        <h5>API Response:</h5>
                        <p><strong>Title:</strong> <span x-text="data.title"></span></p>
                        <p><strong>Body:</strong> <span x-text="data.body"></span></p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 7. Alpine Utilities -->
    <div class="demo">
        <h3>7. Alpine Utilities</h3>
        <div x-data="{
            componentData: { count: 0, message: 'Hello' },
            methods: {
                increment() { this.componentData.count++; },
                reset() {
                    this.componentData.count = 0;
                    this.componentData.message = 'Reset';
                }
            },
            init() {
                // Alpine utilities
                console.log('Alpine version:', Alpine.version);
                console.log('Current component data:', this.$data);

                // Store some data
                Alpine.store('app', {
                    version: '1.0.0',
                    user: { name: 'Demo User' }
                });
            },
            inspectData() {
                alert('Component data: ' + JSON.stringify(this.$data, null, 2));
            },
            inspectStore() {
                alert('App store: ' + JSON.stringify(Alpine.store('app'), null, 2));
            }
        }">
            <div class="box">
                <h4>Component Utilities</h4>
                <p>Count: <strong x-text="componentData.count"></strong></p>
                <p>Message: <strong x-text="componentData.message"></strong></p>
                <button x-on:click="methods.increment()">Increment</button>
                <button x-on:click="methods.reset()">Reset</button>
                <button class="secondary" x-on:click="inspectData()">Inspect Data</button>
                <button class="secondary" x-on:click="inspectStore()">Inspect Store</button>
            </div>

            <div class="box">
                <h4>Alpine Store</h4>
                <p>App Version: <strong x-text="$store.app.version"></strong></p>
                <p>User: <strong x-text="$store.app.user.name"></strong></p>
                <button x-on:click="$store.app.user.name = 'Updated User'">Update User</button>
            </div>
        </div>
    </div>

</body>
</html>