mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-03-31 15:07:47 +01:00
1705 lines
70 KiB
HTML
1705 lines
70 KiB
HTML
<!-- PHPFlasher Interactive Studio - Enhanced 3.0 -->
|
|
<section data-controller="flasher-studio" class="relative overflow-hidden bg-gradient-to-br from-indigo-50 to-slate-50 py-12 my-8 rounded-2xl">
|
|
<!-- Dynamic background elements -->
|
|
<div class="absolute inset-0 z-0">
|
|
<div class="absolute top-0 right-0 w-1/3 h-1/3 bg-gradient-to-br from-indigo-500/10 to-purple-500/10 rounded-full blur-3xl animate-[float_20s_ease-in-out_infinite]"></div>
|
|
<div class="absolute bottom-0 left-0 w-1/3 h-1/3 bg-gradient-to-tr from-emerald-500/10 to-blue-500/10 rounded-full blur-3xl animate-[float_15s_ease-in-out_infinite_reverse]"></div>
|
|
|
|
<!-- Animated floating elements -->
|
|
<div class="absolute top-[15%] left-[10%] w-16 h-16 rounded-full bg-gradient-to-br from-blue-500/10 to-indigo-500/10 animate-[float_8s_ease-in-out_infinite]"></div>
|
|
<div class="absolute top-[40%] left-[80%] w-20 h-20 rounded-full bg-gradient-to-br from-green-500/5 to-emerald-500/5 animate-[float_12s_ease-in-out_infinite]"></div>
|
|
<div class="absolute top-[80%] left-[20%] w-12 h-12 rounded-full bg-gradient-to-br from-amber-500/5 to-orange-500/5 animate-[float_10s_ease-in-out_infinite]"></div>
|
|
</div>
|
|
|
|
<!-- Main content -->
|
|
<div class="container relative z-10 mx-auto px-4 max-w-7xl">
|
|
<!-- Quick Presets -->
|
|
<div class="mb-6 bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="text-sm font-semibold text-slate-700 flex items-center">
|
|
<i class="fas fa-bolt text-amber-500 mr-2"></i>
|
|
Quick Presets
|
|
</h3>
|
|
<span class="text-xs text-slate-500">Common use cases</span>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2">
|
|
<button data-preset="user-created" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-emerald-50 text-emerald-700 border border-emerald-200 hover:bg-emerald-100 transition-all">
|
|
<i class="fas fa-user-plus mr-1"></i> User Created
|
|
</button>
|
|
<button data-preset="payment-failed" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-red-50 text-red-700 border border-red-200 hover:bg-red-100 transition-all">
|
|
<i class="fas fa-credit-card mr-1"></i> Payment Failed
|
|
</button>
|
|
<button data-preset="login-success" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-blue-50 text-blue-700 border border-blue-200 hover:bg-blue-100 transition-all">
|
|
<i class="fas fa-sign-in-alt mr-1"></i> Login Success
|
|
</button>
|
|
<button data-preset="validation-error" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-amber-50 text-amber-700 border border-amber-200 hover:bg-amber-100 transition-all">
|
|
<i class="fas fa-exclamation-triangle mr-1"></i> Validation Error
|
|
</button>
|
|
<button data-preset="order-processing" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-indigo-50 text-indigo-700 border border-indigo-200 hover:bg-indigo-100 transition-all">
|
|
<i class="fas fa-shopping-cart mr-1"></i> Order Processing
|
|
</button>
|
|
<button data-preset="welcome-message" class="preset-btn px-3 py-2 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-all">
|
|
<i class="fas fa-heart mr-1"></i> Welcome
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main interactive area -->
|
|
<div class="rounded-xl shadow-xl bg-white overflow-hidden backdrop-blur-sm border border-slate-100 transition-all hover:shadow-2xl">
|
|
<!-- Toolbar with improved styling -->
|
|
<div class="bg-gradient-to-r from-slate-800 to-slate-900 border-b border-slate-700 px-6 py-3 flex items-center justify-between">
|
|
<div class="flex items-center space-x-2">
|
|
<div class="w-3 h-3 rounded-full bg-red-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-yellow-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
|
</div>
|
|
<div class="text-xs font-medium text-slate-300">PHPFlasher Studio</div>
|
|
<div id="status-indicator" class="flex items-center space-x-2">
|
|
<span id="status-text" class="text-xs text-emerald-400">Ready</span>
|
|
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-[ping_2.5s_cubic-bezier(0,0,0.2,1)_infinite]"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main content area with tabs -->
|
|
<div class="flex flex-col xl:flex-row">
|
|
<!-- Left panel: Options with improved UI -->
|
|
<div class="w-full xl:w-2/5 border-r border-slate-100 max-h-[750px] overflow-y-auto">
|
|
<!-- Options panel -->
|
|
<div class="p-5">
|
|
<div class="mb-1">
|
|
<div class="inline-block px-2 py-1 bg-indigo-50 text-indigo-700 rounded-full text-xs font-medium mb-2">Step 1: Choose Type</div>
|
|
</div>
|
|
|
|
<!-- Notification Type Selection with improved visual appeal -->
|
|
<div class="mb-6">
|
|
<div class="grid grid-cols-4 gap-3 w-full">
|
|
<button data-type="success" class="type-button flex flex-col items-center justify-center p-3 rounded-lg bg-emerald-50 border border-emerald-100 hover:bg-emerald-100 transition-all duration-300 active:scale-95 transform border-2 border-emerald-500 shadow-sm">
|
|
<span class="flex items-center justify-center w-10 h-10 rounded-md bg-emerald-500/90 text-white mb-2">
|
|
<i class="fas fa-check text-lg"></i>
|
|
</span>
|
|
<span class="text-xs font-medium text-slate-700">Success</span>
|
|
</button>
|
|
|
|
<button data-type="error" class="type-button flex flex-col items-center justify-center p-3 rounded-lg bg-red-50 border border-red-100 hover:bg-red-100 transition-all duration-300 active:scale-95 transform shadow-sm">
|
|
<span class="flex items-center justify-center w-10 h-10 rounded-md bg-red-500/90 text-white mb-2">
|
|
<i class="fas fa-times text-lg"></i>
|
|
</span>
|
|
<span class="text-xs font-medium text-slate-700">Error</span>
|
|
</button>
|
|
|
|
<button data-type="info" class="type-button flex flex-col items-center justify-center p-3 rounded-lg bg-blue-50 border border-blue-100 hover:bg-blue-100 transition-all duration-300 active:scale-95 transform shadow-sm">
|
|
<span class="flex items-center justify-center w-10 h-10 rounded-md bg-blue-500/90 text-white mb-2">
|
|
<i class="fas fa-info text-lg"></i>
|
|
</span>
|
|
<span class="text-xs font-medium text-slate-700">Info</span>
|
|
</button>
|
|
|
|
<button data-type="warning" class="type-button flex flex-col items-center justify-center p-3 rounded-lg bg-amber-50 border border-amber-100 hover:bg-amber-100 transition-all duration-300 active:scale-95 transform shadow-sm">
|
|
<span class="flex items-center justify-center w-10 h-10 rounded-md bg-amber-500/90 text-white mb-2">
|
|
<i class="fas fa-exclamation-triangle text-lg"></i>
|
|
</span>
|
|
<span class="text-xs font-medium text-slate-700">Warning</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-1">
|
|
<div class="inline-block px-2 py-1 bg-indigo-50 text-indigo-700 rounded-full text-xs font-medium mb-2">Step 2: Customize Message</div>
|
|
</div>
|
|
|
|
<!-- Content Section with improved form styling -->
|
|
<div class="mb-6 space-y-4">
|
|
<div>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<label for="title-input" class="block text-sm font-medium text-slate-700">Title</label>
|
|
<span class="text-xs text-slate-400 bg-slate-50 px-2 py-0.5 rounded">Optional</span>
|
|
</div>
|
|
<input
|
|
id="title-input"
|
|
type="text"
|
|
class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300"
|
|
placeholder="Enter notification title"
|
|
value="">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="message-input" class="block mb-2 text-sm font-medium text-slate-700">Message</label>
|
|
<input id="message-input"
|
|
type="text"
|
|
class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300"
|
|
placeholder="Enter your notification message"
|
|
value="Your product has been created successfully!">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-1">
|
|
<div class="inline-block px-2 py-1 bg-indigo-50 text-indigo-700 rounded-full text-xs font-medium mb-2">Step 3: Advanced Options</div>
|
|
</div>
|
|
|
|
<!-- Configuration Options -->
|
|
<div class="px-3 py-3 mb-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<div>
|
|
<label for="position-select" class="block mb-1.5 text-sm font-medium text-slate-700">Position</label>
|
|
<div class="relative">
|
|
<select id="position-select" class="w-full appearance-none px-3 py-2.5 bg-white border border-slate-200 rounded-lg pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300">
|
|
<option value="">Default</option>
|
|
<option value="top-right">Top Right</option>
|
|
<option value="top-left">Top Left</option>
|
|
<option value="bottom-right">Bottom Right</option>
|
|
<option value="bottom-left">Bottom Left</option>
|
|
<option value="center-top">Center Top</option>
|
|
<option value="center-bottom">Center Bottom</option>
|
|
</select>
|
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-400">
|
|
<i class="fas fa-chevron-down text-xs"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="timeout-select" class="block mb-1.5 text-sm font-medium text-slate-700">Duration</label>
|
|
<div class="relative">
|
|
<select id="timeout-select" class="w-full appearance-none px-3 py-2.5 bg-white border border-slate-200 rounded-lg pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300">
|
|
<option value="">Default</option>
|
|
<option value="30000">30 seconds</option>
|
|
<option value="10000">10 seconds</option>
|
|
<option value="5000">5 seconds</option>
|
|
<option value="3000">3 seconds</option>
|
|
<option value="false">Sticky</option>
|
|
</select>
|
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-400">
|
|
<i class="fas fa-chevron-down text-xs"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<div>
|
|
<label for="theme-select" class="block mb-1.5 text-sm font-medium text-slate-700">Theme</label>
|
|
<div class="relative">
|
|
<select id="theme-select" class="w-full appearance-none px-3 py-2.5 bg-white border border-slate-200 rounded-lg pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300">
|
|
<option value="">Default</option>
|
|
<option value="material">Material</option>
|
|
<option value="minimal">Minimal</option>
|
|
<option value="neon">Neon</option>
|
|
<option value="ios">iOS</option>
|
|
<option value="slack">Slack</option>
|
|
<option value="facebook">Facebook</option>
|
|
<option value="google">Google</option>
|
|
<option value="amazon">Amazon</option>
|
|
<option value="ruby">Ruby</option>
|
|
<option value="jade">Jade</option>
|
|
<option value="aurora">Aurora</option>
|
|
<option value="sapphire">Sapphire</option>
|
|
<option value="onyx">Onyx</option>
|
|
<option value="amber">Amber</option>
|
|
<option value="emerald">Emerald</option>
|
|
<option value="flasher">Flasher</option>
|
|
<option value="crystal">Crystal</option>
|
|
</select>
|
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-400">
|
|
<i class="fas fa-chevron-down text-xs"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="direction-select" class="block mb-1.5 text-sm font-medium text-slate-700">Direction</label>
|
|
<div class="relative">
|
|
<select id="direction-select" class="w-full appearance-none px-3 py-2.5 bg-white border border-slate-200 rounded-lg pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all duration-300">
|
|
<option value="">Default</option>
|
|
<option value="top">Top</option>
|
|
<option value="bottom">Bottom</option>
|
|
</select>
|
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-400">
|
|
<i class="fas fa-chevron-down text-xs"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 grid grid-cols-2 gap-3">
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="rtl-check" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 transition">
|
|
<label for="rtl-check" class="ml-2 text-sm text-slate-700">RTL Layout</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="escape-html-check" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 transition" checked>
|
|
<label for="escape-html-check" class="ml-2 text-sm text-slate-700">Escape HTML</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Preview & Launch buttons - enhanced with better visual hierarchy -->
|
|
<div class="flex gap-2">
|
|
<button id="show-notification-btn" class="flex-1 group relative overflow-hidden rounded-lg shadow-md">
|
|
<span class="absolute inset-0 bg-gradient-to-r from-indigo-600 to-purple-600 group-hover:from-indigo-700 group-hover:to-purple-700 transition-all duration-300"></span>
|
|
<span class="relative px-5 py-3.5 flex items-center justify-center text-white font-medium">
|
|
<i class="fas fa-bell mr-2"></i>
|
|
<span>Show Notification</span>
|
|
</span>
|
|
<span class="absolute top-0 -inset-full h-full w-1/2 block transform -skew-x-12 bg-gradient-to-r from-transparent to-white opacity-20 group-hover:animate-[shine_1s_forwards]"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right panel: Code Preview -->
|
|
<div class="w-full xl:w-3/5" style="background: #f5f2f0; border: none; box-sizing: unset;">
|
|
<div class="border-b border-slate-200 px-6 py-3 flex flex-col sm:flex-row items-start sm:items-center justify-between bg-slate-50 gap-2">
|
|
<div class="text-sm font-medium text-slate-700">Generated Code</div>
|
|
<div class="flex items-center gap-2">
|
|
<button id="compare-btn" class="text-xs text-slate-600 hover:text-indigo-600 transition-colors flex items-center px-2 py-1 rounded hover:bg-slate-100">
|
|
<i class="fas fa-columns mr-1"></i> Compare
|
|
</button>
|
|
<div class="text-xs text-slate-400">Copy-paste into your project</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Code tabs with improved styling -->
|
|
<div class="flex flex-wrap border-b border-slate-200 bg-slate-50">
|
|
<button id="laravel-tab" data-tab="laravel" class="code-tab-btn px-4 py-3 text-sm font-medium text-indigo-600 border-b-2 border-indigo-600 flex items-center space-x-2">
|
|
<i class="fab fa-laravel text-red-500"></i>
|
|
<span>Laravel</span>
|
|
</button>
|
|
<button id="symfony-tab" data-tab="symfony" class="code-tab-btn px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 flex items-center space-x-2">
|
|
<i class="fab fa-symfony text-black"></i>
|
|
<span>Symfony</span>
|
|
</button>
|
|
<button id="js-tab" data-tab="js" class="code-tab-btn px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 flex items-center space-x-2">
|
|
<i class="fab fa-js text-yellow-500"></i>
|
|
<span>JavaScript</span>
|
|
</button>
|
|
<button id="noty-tab" data-tab="noty" class="code-tab-btn px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 flex items-center space-x-2">
|
|
<span class="w-4 h-4 bg-blue-500 rounded text-white text-xs flex items-center justify-center">N</span>
|
|
<span>Noty</span>
|
|
</button>
|
|
<button id="toastr-tab" data-tab="toastr" class="code-tab-btn px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 flex items-center space-x-2">
|
|
<span class="w-4 h-4 bg-orange-500 rounded text-white text-xs flex items-center justify-center">T</span>
|
|
<span>Toastr</span>
|
|
</button>
|
|
<button id="sweetalert-tab" data-tab="sweetalert" class="code-tab-btn px-4 py-3 text-sm font-medium text-slate-600 hover:text-slate-800 flex items-center space-x-2">
|
|
<span class="w-4 h-4 bg-pink-500 rounded text-white text-xs flex items-center justify-center">S</span>
|
|
<span>SweetAlert</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Code animation wrapper with improved styles -->
|
|
<div id="code-display-wrapper" class="relative">
|
|
<!-- Laravel Code Panel -->
|
|
<div id="laravel-code-panel" class="bg-gray-50 overflow-y-auto code-panel active p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- Symfony Code Panel -->
|
|
<div id="symfony-code-panel" class="bg-gray-50 overflow-y-auto hidden code-panel p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- JavaScript Code Panel -->
|
|
<div id="js-code-panel" class="bg-gray-50 overflow-y-auto hidden code-panel p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- Noty Code Panel (New) -->
|
|
<div id="noty-code-panel" class="bg-gray-50 overflow-y-auto hidden code-panel p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- Toastr Code Panel (New) -->
|
|
<div id="toastr-code-panel" class="bg-gray-50 overflow-y-auto hidden code-panel p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- SweetAlert Code Panel (New) -->
|
|
<div id="sweetalert-code-panel" class="bg-gray-50 overflow-y-auto hidden code-panel p-4">
|
|
<!-- Code will be inserted here by JavaScript -->
|
|
</div>
|
|
|
|
<!-- Animation overlay -->
|
|
<div id="animation-layer" class="absolute top-0 left-0 w-full h-full pointer-events-none"></div>
|
|
</div>
|
|
|
|
<!-- Code Comparison Modal (New) -->
|
|
<div id="comparison-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<div class="bg-white rounded-xl shadow-2xl max-w-6xl w-full max-h-[90vh] overflow-hidden">
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-200">
|
|
<h3 class="text-lg font-semibold text-slate-800">Code Comparison: Before vs After PHPFlasher</h3>
|
|
<button id="close-comparison-btn" class="text-slate-400 hover:text-slate-600 transition-colors">
|
|
<i class="fas fa-times text-xl"></i>
|
|
</button>
|
|
</div>
|
|
<div class="grid grid-cols-2 divide-x divide-slate-200 max-h-[70vh] overflow-y-auto">
|
|
<div class="p-4">
|
|
<h4 class="text-sm font-medium text-red-600 mb-2 flex items-center">
|
|
<i class="fas fa-times-circle mr-2"></i> Before PHPFlasher
|
|
</h4>
|
|
<pre id="before-code" class="bg-red-50 rounded-lg p-4 text-sm overflow-x-auto"><code></code></pre>
|
|
</div>
|
|
<div class="p-4">
|
|
<h4 class="text-sm font-medium text-green-600 mb-2 flex items-center">
|
|
<i class="fas fa-check-circle mr-2"></i> After PHPFlasher
|
|
</h4>
|
|
<pre id="after-code" class="bg-green-50 rounded-lg p-4 text-sm overflow-x-auto"><code></code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer with improved design -->
|
|
<div class="px-6 py-3.5 bg-slate-50 border-t border-slate-200 flex flex-col sm:flex-row items-center justify-between gap-3">
|
|
<div class="text-xs text-slate-500">Customize your perfect notification in seconds</div>
|
|
<div class="flex space-x-4">
|
|
<a href="https://php-flasher.io" class="text-xs text-slate-600 hover:text-indigo-600 transition-colors flex items-center hover:underline">
|
|
<i class="fas fa-book mr-1.5 text-xs"></i> Documentation
|
|
</a>
|
|
<a href="https://github.com/php-flasher/php-flasher" class="text-xs text-slate-600 hover:text-indigo-600 transition-colors flex items-center hover:underline">
|
|
<i class="fab fa-github mr-1.5 text-xs"></i> GitHub
|
|
</a>
|
|
<a href="https://php-flasher.io/symfony/" class="text-xs text-slate-600 hover:text-indigo-600 transition-colors flex items-center hover:underline">
|
|
<i class="fas fa-lightbulb mr-1.5 text-xs"></i> Examples
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
/* Base styles */
|
|
:root {
|
|
--primary-color: #4F46E5;
|
|
--success-color: #10B981;
|
|
--error-color: #EF4444;
|
|
--info-color: #3B82F6;
|
|
--warning-color: #F59E0B;
|
|
--highlight-color: rgba(16, 185, 129, 0.1);
|
|
}
|
|
|
|
body {
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
}
|
|
|
|
/* Custom animations */
|
|
@keyframes shine {
|
|
to {
|
|
left: 100%;
|
|
}
|
|
}
|
|
|
|
@keyframes ping {
|
|
0% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
75%, 100% {
|
|
transform: scale(2);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-15px); }
|
|
}
|
|
|
|
/* Code style improvements */
|
|
pre code {
|
|
font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Preset button styles */
|
|
.preset-btn {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.preset-btn:hover {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Type button styles */
|
|
.type-button {
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.type-button:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.type-button::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 8px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
box-shadow: 0 0 0 2px var(--primary-color);
|
|
}
|
|
|
|
.type-button:focus::after {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Code panel enhancements */
|
|
.code-panel {
|
|
transition: opacity 0.3s ease;
|
|
min-height: 300px;
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.code-panel.hidden {
|
|
opacity: 0;
|
|
}
|
|
|
|
.code-panel.active {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Typing container styles */
|
|
.typing-container {
|
|
line-height: 1.4;
|
|
overflow: visible;
|
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
}
|
|
|
|
#code-display-wrapper {
|
|
background-color: #f5f2f0;
|
|
}
|
|
|
|
/* Cursor animation */
|
|
.cursor {
|
|
display: inline-block;
|
|
width: 3px;
|
|
height: 1em;
|
|
background-color: var(--primary-color);
|
|
margin-left: 4px;
|
|
animation: blink 1s infinite;
|
|
vertical-align: middle;
|
|
position: relative;
|
|
top: -1em;
|
|
}
|
|
|
|
@keyframes blink {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/* Mobile responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.code-panel {
|
|
height: 350px !important;
|
|
}
|
|
|
|
.code-tab-btn {
|
|
padding: 0.75rem 0.5rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.code-tab-btn span:last-child {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Comparison modal animation */
|
|
#comparison-modal {
|
|
animation: fadeIn 0.2s ease-out;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Scrollbar styling for code panels */
|
|
.code-panel::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.code-panel::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.code-panel::-webkit-scrollbar-thumb {
|
|
background: #cbd5e1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.code-panel::-webkit-scrollbar-thumb:hover {
|
|
background: #94a3b8;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
/**
|
|
* PHPFlasher Interactive Studio - Enhanced Edition 3.0
|
|
*
|
|
* Features:
|
|
* - Real-time code generation with typing animation
|
|
* - Context-aware examples for different notification types
|
|
* - Advanced animations and transitions
|
|
* - Clean and concise code examples
|
|
* - Copy to clipboard functionality
|
|
* - Quick presets for common use cases
|
|
* - Multiple adapter support (Laravel, Symfony, JS, Noty, Toastr, SweetAlert)
|
|
* - Code comparison view
|
|
* - Mobile responsive design
|
|
*/
|
|
|
|
// Quick Presets Configuration
|
|
const presets = {
|
|
'user-created': {
|
|
type: 'success',
|
|
title: 'User Created',
|
|
message: 'User account created successfully! You can now log in.'
|
|
},
|
|
'payment-failed': {
|
|
type: 'error',
|
|
title: 'Payment Failed',
|
|
message: 'Your payment could not be processed. Please try again or use a different payment method.'
|
|
},
|
|
'login-success': {
|
|
type: 'success',
|
|
title: 'Welcome Back!',
|
|
message: 'You have successfully logged in.'
|
|
},
|
|
'validation-error': {
|
|
type: 'warning',
|
|
title: 'Validation Error',
|
|
message: 'Please check the form for errors and try again.'
|
|
},
|
|
'order-processing': {
|
|
type: 'info',
|
|
title: 'Order Processing',
|
|
message: 'Your order is being processed. You will receive a confirmation email shortly.'
|
|
},
|
|
'welcome-message': {
|
|
type: 'success',
|
|
title: 'Welcome!',
|
|
message: 'Thank you for joining us. Get started by exploring our features.'
|
|
}
|
|
};
|
|
|
|
// Before/After Code Examples for Comparison
|
|
const beforeAfterExamples = {
|
|
success: {
|
|
before: `// WITHOUT PHPFlasher - Multiple lines needed
|
|
session()->flash('success', 'Product created!');
|
|
return redirect()->back();
|
|
|
|
// In your Blade template:
|
|
@if(session('success'))
|
|
<div class="alert alert-success">
|
|
{{ session('success') }}
|
|
</div>
|
|
@endif`,
|
|
after: `// WITH PHPFlasher - One simple line
|
|
flash()->success('Product created successfully!');
|
|
|
|
// That's it! No template code needed.`
|
|
},
|
|
error: {
|
|
before: `// WITHOUT PHPFlasher
|
|
return redirect()->back()
|
|
->with('error', 'Payment failed!')
|
|
->with('error_code', 'CARD_DECLINED');
|
|
|
|
// Template:
|
|
@if(session('error'))
|
|
<div class="alert alert-danger" data-code="{{ session('error_code') }}">
|
|
{{ session('error') }}
|
|
</div>
|
|
@endif`,
|
|
after: `// WITH PHPFlasher - Clean & simple
|
|
flash()->error('Payment failed!', 'Error');
|
|
flash()->option('timeout', 10000);
|
|
|
|
// Auto-dismisses after 10 seconds`
|
|
},
|
|
info: {
|
|
before: `// WITHOUT PHPFlasher - Manual notification system
|
|
$notification = new Notification();
|
|
$notification->type = 'info';
|
|
$notification->message = 'Order processing...';
|
|
$notification->save();
|
|
|
|
// Complex template rendering...`,
|
|
after: `// WITH PHPFlasher - Instant feedback
|
|
flash()->info('Your order is being processed.', 'Order Status');
|
|
|
|
// Beautiful, animated notification instantly`
|
|
},
|
|
warning: {
|
|
before: `// WITHOUT PHPFlasher
|
|
Session::put('warning', 'Account expiring soon!');
|
|
Session::put('days_left', 3);
|
|
|
|
// Template checks and rendering...`,
|
|
after: `// WITH PHPFlasher - Simple & effective
|
|
flash()->warning('Your account will expire in 3 days.', 'Account Expiring');
|
|
|
|
// Professional warning with minimal code`
|
|
}
|
|
};
|
|
|
|
// State Management
|
|
const createStateProxy = () => {
|
|
const stateTarget = {
|
|
type: "success",
|
|
title: "",
|
|
message: "Your product has been created successfully!",
|
|
timeout: "",
|
|
position: "",
|
|
direction: "",
|
|
rtl: false,
|
|
theme: "",
|
|
escapeHtml: true,
|
|
_previousState: {},
|
|
_animationInProgress: false,
|
|
};
|
|
return new Proxy(stateTarget, {
|
|
set(target, property, value) {
|
|
if (property !== "_previousState" && property !== "_animationInProgress") {
|
|
target._previousState[property] = target[property];
|
|
}
|
|
target[property] = value;
|
|
if (document.readyState === "complete" && property !== "_previousState" && property !== "_animationInProgress") {
|
|
if (property === "type") {
|
|
updateStatus("Changing template...");
|
|
}
|
|
updateCodeDisplay(true);
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
};
|
|
const state = createStateProxy();
|
|
|
|
// Context Templates for Code Generation
|
|
const contextTemplates = {
|
|
success: {
|
|
laravel: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
controller: "ProductController",
|
|
method: "store",
|
|
code: {
|
|
before: `// ProductController.php
|
|
namespace App\\Http\\Controllers;
|
|
|
|
use App\\Models\\Product;
|
|
use App\\Http\\Requests\\ProductRequest;
|
|
|
|
class ProductController extends Controller
|
|
{
|
|
public function store(ProductRequest $request)
|
|
{
|
|
// Create product
|
|
$product = Product::create($request->validated());
|
|
`,
|
|
after: `
|
|
return redirect()->route('products.index');
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
symfony: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
controller: "ProductController",
|
|
method: "create",
|
|
code: {
|
|
before: `// ProductController.php
|
|
namespace App\\Controller;
|
|
|
|
use App\\Entity\\Product;
|
|
use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;
|
|
|
|
class ProductController extends AbstractController
|
|
{
|
|
#[Route('/product/new', name: 'app_product_create')]
|
|
public function create(Request $request): Response
|
|
{
|
|
// Process form & save product
|
|
$product = new Product();
|
|
`,
|
|
after: `
|
|
return $this->redirectToRoute('app_product_index');
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
js: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
file: "product-service.js",
|
|
method: "createProduct",
|
|
code: {
|
|
before: `// product-service.js
|
|
import flasher from '@flasher/flasher';
|
|
|
|
async function createProduct(data) {
|
|
try {
|
|
const response = await api.post('/products', data);
|
|
`,
|
|
after: `
|
|
return response.data;
|
|
} catch (error) {
|
|
flasher.error('Failed to create product');
|
|
throw error;
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
noty: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
code: {
|
|
before: `// Noty adapter
|
|
import Noty from 'noty';
|
|
|
|
`,
|
|
after: `
|
|
new Noty({
|
|
type: 'success',
|
|
text: 'Your product has been created successfully!',
|
|
timeout: 3000
|
|
}).show();`
|
|
}
|
|
},
|
|
toastr: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
code: {
|
|
before: `// Toastr adapter
|
|
import toastr from 'toastr';
|
|
|
|
`,
|
|
after: `
|
|
toastr.success('Your product has been created successfully!');`
|
|
}
|
|
},
|
|
sweetalert: {
|
|
title: "Success",
|
|
message: "Your product has been created successfully!",
|
|
code: {
|
|
before: `// SweetAlert adapter
|
|
import Swal from 'sweetalert2';
|
|
|
|
`,
|
|
after: `
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Success',
|
|
text: 'Your product has been created successfully!',
|
|
timer: 3000
|
|
});`
|
|
}
|
|
}
|
|
},
|
|
error: {
|
|
laravel: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
controller: "PaymentController",
|
|
method: "process",
|
|
code: {
|
|
before: `// PaymentController.php
|
|
namespace App\\Http\\Controllers;
|
|
|
|
use App\\Services\\PaymentGateway;
|
|
|
|
class PaymentController extends Controller
|
|
{
|
|
public function process(Request $request)
|
|
{
|
|
try {
|
|
$result = $this->paymentGateway->charge($request->token);
|
|
} catch (\\Exception $e) {
|
|
`,
|
|
after: `
|
|
return back();
|
|
}
|
|
|
|
return redirect()->route('payment.success');
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
symfony: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
controller: "PaymentController",
|
|
method: "process",
|
|
code: {
|
|
before: `// PaymentController.php
|
|
namespace App\\Controller;
|
|
|
|
use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;
|
|
|
|
class PaymentController extends AbstractController
|
|
{
|
|
public function process(Request $request): Response
|
|
{
|
|
try {
|
|
$payment = $this->paymentService->process($request->get('token'));
|
|
} catch (\\Exception $e) {
|
|
`,
|
|
after: `
|
|
return $this->redirectToRoute('payment_form');
|
|
}
|
|
|
|
return $this->redirectToRoute('payment_success');
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
js: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
file: "payment-service.js",
|
|
method: "processPayment",
|
|
code: {
|
|
before: `// payment-service.js
|
|
import flasher from '@flasher/flasher';
|
|
|
|
async function processPayment(paymentData) {
|
|
try {
|
|
const response = await api.post('/payments', paymentData);
|
|
if (!response.ok) {
|
|
`,
|
|
after: `
|
|
return false;
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error(error);
|
|
return false;
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
noty: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
code: {
|
|
before: `// Noty adapter
|
|
import Noty from 'noty';
|
|
|
|
`,
|
|
after: `
|
|
new Noty({
|
|
type: 'error',
|
|
text: 'An error occurred while processing your request.',
|
|
timeout: 5000
|
|
}).show();`
|
|
}
|
|
},
|
|
toastr: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
code: {
|
|
before: `// Toastr adapter
|
|
import toastr from 'toastr';
|
|
|
|
`,
|
|
after: `
|
|
toastr.error('An error occurred while processing your request.');`
|
|
}
|
|
},
|
|
sweetalert: {
|
|
title: "Error",
|
|
message: "An error occurred while processing your request.",
|
|
code: {
|
|
before: `// SweetAlert adapter
|
|
import Swal from 'sweetalert2';
|
|
|
|
`,
|
|
after: `
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error',
|
|
text: 'An error occurred while processing your request.'
|
|
});`
|
|
}
|
|
}
|
|
},
|
|
info: {
|
|
laravel: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
controller: "OrderController",
|
|
method: "submit",
|
|
code: {
|
|
before: `// OrderController.php
|
|
namespace App\\Http\\Controllers;
|
|
|
|
use App\\Models\\Order;
|
|
|
|
class OrderController extends Controller
|
|
{
|
|
public function submit(Request $request)
|
|
{
|
|
// Create and save order
|
|
$order = Order::create($request->validated());
|
|
`,
|
|
after: `
|
|
// Send to processing queue
|
|
ProcessOrderJob::dispatch($order);
|
|
|
|
return redirect()->route('orders.show', $order);
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
symfony: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
controller: "OrderController",
|
|
method: "create",
|
|
code: {
|
|
before: `// OrderController.php
|
|
namespace App\\Controller;
|
|
|
|
use App\\Entity\\Order;
|
|
use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;
|
|
|
|
class OrderController extends AbstractController
|
|
{
|
|
public function create(Request $request): Response
|
|
{
|
|
// Create and save order
|
|
$order = new Order();
|
|
// ... set order properties
|
|
$this->entityManager->persist($order);
|
|
$this->entityManager->flush();
|
|
`,
|
|
after: `
|
|
// Dispatch order processing
|
|
$this->messageBus->dispatch(new ProcessOrder($order->getId()));
|
|
|
|
return $this->redirectToRoute('app_order_show', [
|
|
'id' => $order->getId()
|
|
]);
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
js: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
file: "order-service.js",
|
|
method: "submitOrder",
|
|
code: {
|
|
before: `// order-service.js
|
|
import flasher from '@flasher/flasher';
|
|
|
|
async function submitOrder(orderData) {
|
|
try {
|
|
const response = await api.post('/orders', orderData);
|
|
const order = response.data;
|
|
`,
|
|
after: `
|
|
// Track order
|
|
analytics.trackOrder(order.id);
|
|
return order;
|
|
} catch (error) {
|
|
flasher.error('Order submission failed');
|
|
throw error;
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
noty: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
code: {
|
|
before: `// Noty adapter
|
|
import Noty from 'noty';
|
|
|
|
`,
|
|
after: `
|
|
new Noty({
|
|
type: 'info',
|
|
text: 'Your order is being processed.',
|
|
timeout: 4000
|
|
}).show();`
|
|
}
|
|
},
|
|
toastr: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
code: {
|
|
before: `// Toastr adapter
|
|
import toastr from 'toastr';
|
|
|
|
`,
|
|
after: `
|
|
toastr.info('Your order is being processed.');`
|
|
}
|
|
},
|
|
sweetalert: {
|
|
title: "Information",
|
|
message: "Your order is being processed.",
|
|
code: {
|
|
before: `// SweetAlert adapter
|
|
import Swal from 'sweetalert2';
|
|
|
|
`,
|
|
after: `
|
|
Swal.fire({
|
|
icon: 'info',
|
|
title: 'Information',
|
|
text: 'Your order is being processed.',
|
|
timer: 4000
|
|
});`
|
|
}
|
|
}
|
|
},
|
|
warning: {
|
|
laravel: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
controller: "AccountController",
|
|
method: "dashboard",
|
|
code: {
|
|
before: `// AccountController.php
|
|
namespace App\\Http\\Controllers;
|
|
|
|
class AccountController extends Controller
|
|
{
|
|
public function dashboard()
|
|
{
|
|
$user = auth()->user();
|
|
$subscription = $user->subscription;
|
|
if ($subscription && $subscription->daysUntilExpiration() <= 3) {
|
|
`,
|
|
after: `
|
|
}
|
|
|
|
return view('account.dashboard', compact('user'));
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
symfony: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
controller: "AccountController",
|
|
method: "dashboard",
|
|
code: {
|
|
before: `// AccountController.php
|
|
namespace App\\Controller;
|
|
|
|
use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;
|
|
|
|
class AccountController extends AbstractController
|
|
{
|
|
public function dashboard(): Response
|
|
{
|
|
$user = $this->getUser();
|
|
$subscription = $user->getSubscription();
|
|
if ($subscription && $subscription->getDaysRemaining() <= 3) {
|
|
`,
|
|
after: `
|
|
}
|
|
|
|
return $this->render('account/dashboard.html.twig', [
|
|
'user' => $user
|
|
]);
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
js: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
file: "account-service.js",
|
|
method: "checkStatus",
|
|
code: {
|
|
before: `// account-service.js
|
|
import flasher from '@flasher/flasher';
|
|
|
|
async function checkStatus() {
|
|
const response = await api.get('/account/status');
|
|
const { subscription } = response.data;
|
|
if (subscription && subscription.daysRemaining <= 3) {
|
|
`,
|
|
after: `
|
|
}
|
|
return subscription;
|
|
}`
|
|
}
|
|
},
|
|
noty: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
code: {
|
|
before: `// Noty adapter
|
|
import Noty from 'noty';
|
|
|
|
`,
|
|
after: `
|
|
new Noty({
|
|
type: 'warning',
|
|
text: 'Your account will expire in 3 days.',
|
|
timeout: 6000
|
|
}).show();`
|
|
}
|
|
},
|
|
toastr: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
code: {
|
|
before: `// Toastr adapter
|
|
import toastr from 'toastr';
|
|
|
|
`,
|
|
after: `
|
|
toastr.warning('Your account will expire in 3 days.');`
|
|
}
|
|
},
|
|
sweetalert: {
|
|
title: "Warning",
|
|
message: "Your account will expire in 3 days.",
|
|
code: {
|
|
before: `// SweetAlert adapter
|
|
import Swal from 'sweetalert2';
|
|
|
|
`,
|
|
after: `
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Warning',
|
|
text: 'Your account will expire in 3 days.',
|
|
timer: 6000
|
|
});`
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const indentMap = {
|
|
js: { success: " ", error: " ", info: " ", warning: " " },
|
|
laravel: { success: " ", error: " ", info: " ", warning: " " },
|
|
symfony: { success: " ", error: " ", info: " ", warning: " " },
|
|
noty: { success: "", error: "", info: "", warning: "" },
|
|
toastr: { success: "", error: "", info: "", warning: "" },
|
|
sweetalert: { success: "", error: "", info: "", warning: "" }
|
|
};
|
|
|
|
// Helper Functions
|
|
function sanitizeForCode(str) {
|
|
if (typeof str !== "string") return str;
|
|
return str
|
|
.replace(/\\/g, "\\\\")
|
|
.replace(/'/g, "\\'")
|
|
.replace(/"/g, "\\\"")
|
|
.replace(/\n/g, "\\n")
|
|
.replace(/\r/g, "\\r")
|
|
.replace(/\t/g, "\\t");
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (typeof text !== "string") return "";
|
|
return text
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
function initializeTypeButtons() {
|
|
document.querySelectorAll(".type-button").forEach(btn => {
|
|
const type = btn.dataset.type;
|
|
btn.classList.remove("border-2");
|
|
btn.classList.remove("border-green-500", "border-red-500", "border-blue-500", "border-amber-500");
|
|
if (type === state.type) {
|
|
btn.classList.add("border-2");
|
|
switch (type) {
|
|
case "success": btn.classList.add("border-green-500"); break;
|
|
case "error": btn.classList.add("border-red-500"); break;
|
|
case "info": btn.classList.add("border-blue-500"); break;
|
|
case "warning": btn.classList.add("border-amber-500"); break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function initializeFormValues() {
|
|
const template = contextTemplates[state.type];
|
|
const currentTab = getCurrentTab();
|
|
|
|
if (!state.title && template && template[currentTab]) {
|
|
state.title = template[currentTab].title;
|
|
}
|
|
|
|
if (state.title && (state.title.toLowerCase() === state.type.toLowerCase() ||
|
|
(state.title.toLowerCase() === "information" && state.type.toLowerCase() === "info"))) {
|
|
state.title = "";
|
|
}
|
|
|
|
document.getElementById("title-input").value = state.title || "";
|
|
document.getElementById("message-input").value = state.message || "";
|
|
document.getElementById("position-select").value = state.position || "";
|
|
document.getElementById("timeout-select").value = state.timeout === false ? "false" : (state.timeout || "");
|
|
document.getElementById("theme-select").value = state.theme || "";
|
|
document.getElementById("direction-select").value = state.direction || "";
|
|
document.getElementById("rtl-check").checked = !!state.rtl;
|
|
document.getElementById("escape-html-check").checked = state.escapeHtml !== false;
|
|
}
|
|
|
|
function updateStatus(message) {
|
|
const statusText = document.getElementById("status-text");
|
|
if (!statusText) return;
|
|
statusText.textContent = message;
|
|
statusText.classList.remove("text-emerald-600");
|
|
statusText.classList.add("text-blue-600");
|
|
const dot = document.querySelector("#status-indicator div");
|
|
if (dot) {
|
|
dot.classList.remove("bg-emerald-500");
|
|
dot.classList.add("bg-blue-500");
|
|
}
|
|
setTimeout(() => {
|
|
statusText.classList.remove("text-blue-600");
|
|
statusText.classList.add("text-emerald-600");
|
|
statusText.textContent = "Ready";
|
|
if (dot) {
|
|
dot.classList.remove("bg-blue-500");
|
|
dot.classList.add("bg-emerald-500");
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
function getCurrentTab() {
|
|
const activeTab = document.querySelector(".code-tab-btn.text-indigo-600, .code-tab-btn.text-blue-600");
|
|
return activeTab ? activeTab.getAttribute("data-tab") : "laravel";
|
|
}
|
|
|
|
function showCodeTab(tab) {
|
|
document.querySelectorAll(".code-panel").forEach(panel => {
|
|
panel.classList.add("hidden");
|
|
panel.classList.remove("active");
|
|
});
|
|
const activePanel = document.getElementById(`${tab}-code-panel`);
|
|
if (activePanel) {
|
|
activePanel.classList.remove("hidden");
|
|
activePanel.classList.add("active");
|
|
}
|
|
document.querySelectorAll(".code-tab-btn").forEach(btn => {
|
|
btn.classList.remove("text-indigo-600", "text-blue-600", "border-b-2", "border-indigo-600", "border-blue-500");
|
|
btn.classList.add("text-slate-600", "text-gray-600");
|
|
});
|
|
const activeTabBtn = document.querySelector(`[data-tab="${tab}"]`);
|
|
if (activeTabBtn) {
|
|
activeTabBtn.classList.remove("text-slate-600", "text-gray-600");
|
|
activeTabBtn.classList.add("text-indigo-600", "border-b-2", "border-indigo-600");
|
|
}
|
|
updateCodeDisplay(state.animationsEnabled);
|
|
}
|
|
|
|
function generateFlashCode(framework, indent = "") {
|
|
const safeMessage = sanitizeForCode(state.message);
|
|
const safeTitle = state.title && state.title.toLowerCase() !== state.type.toLowerCase() ? sanitizeForCode(state.title) : "";
|
|
let lines = [];
|
|
|
|
// Adapter-specific code generation
|
|
if (framework === "noty") {
|
|
return contextTemplates[state.type].noty.code.before +
|
|
`new Noty({
|
|
type: '${state.type}',
|
|
text: '${safeMessage}'${safeTitle ? `,
|
|
title: '${safeTitle}'` : ''}${state.timeout ? `,
|
|
timeout: ${state.timeout}` : ''}
|
|
}).show();`;
|
|
}
|
|
|
|
if (framework === "toastr") {
|
|
const toastrType = state.type === "error" ? "error" : (state.type === "warning" ? "warning" : "success");
|
|
return contextTemplates[state.type].toastr.code.before +
|
|
`toastr.${toastrType}('${safeMessage}'${safeTitle ? `, '${safeTitle}'` : ''});`;
|
|
}
|
|
|
|
if (framework === "sweetalert") {
|
|
const iconMap = { success: "success", error: "error", info: "info", warning: "warning" };
|
|
return contextTemplates[state.type].sweetalert.code.before +
|
|
`Swal.fire({
|
|
icon: '${iconMap[state.type]}',
|
|
${safeTitle ? `title: '${safeTitle}',\n ` : ``}text: '${safeMessage}'${state.timeout ? `,
|
|
timer: ${state.timeout}` : ''}
|
|
});`;
|
|
}
|
|
|
|
// Standard Flasher code (Laravel, Symfony, JS)
|
|
const prefix = framework === "js" ? "flasher" : "flash()";
|
|
|
|
if (framework === "js") {
|
|
const options = {};
|
|
if (state.position) options.position = state.position;
|
|
if (state.timeout !== undefined && state.timeout !== "") options.timeout = state.timeout;
|
|
if (state.theme) options.theme = state.theme;
|
|
if (state.direction) options.direction = state.direction;
|
|
if (state.rtl) options.rtl = true;
|
|
if (state.escapeHtml === false) options.escapeHtml = false;
|
|
|
|
if (Object.keys(options).length === 0) {
|
|
return `${indent}${prefix}.${state.type}('${safeMessage}'${safeTitle ? `, '${safeTitle}'` : ""});`;
|
|
}
|
|
lines.push(`${indent}${prefix}`);
|
|
lines.push(`${indent} .${state.type}('${safeMessage}'${safeTitle ? `, '${safeTitle}'` : ""}, {`);
|
|
Object.entries(options).forEach(([key, value], index, array) => {
|
|
const comma = index < array.length - 1 ? "," : "";
|
|
if (typeof value === "string") {
|
|
lines.push(`${indent} ${key}: '${value}'${comma}`);
|
|
} else {
|
|
lines.push(`${indent} ${key}: ${value}${comma}`);
|
|
}
|
|
});
|
|
lines.push(`${indent} });`);
|
|
} else {
|
|
lines.push(`${indent}${prefix}`);
|
|
if (state.theme) lines.push(`${indent} ->use('theme.${state.theme}')`);
|
|
if (state.position) lines.push(`${indent} ->option('position', '${state.position}')`);
|
|
if (state.timeout !== undefined && state.timeout !== "") {
|
|
const timeoutValue = state.timeout === false ? 'false' : state.timeout;
|
|
lines.push(`${indent} ->option('timeout', ${timeoutValue})`);
|
|
}
|
|
if (state.direction) lines.push(`${indent} ->option('direction', '${state.direction}')`);
|
|
if (state.rtl) lines.push(`${indent} ->option('rtl', true)`);
|
|
if (state.escapeHtml === false) lines.push(`${indent} ->option('escapeHtml', false)`);
|
|
lines.push(`${indent} ->${state.type}('${safeMessage}'${safeTitle ? `, '${safeTitle}'` : ""});`);
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function updateCodeDisplay(animate = true) {
|
|
const currentTab = getCurrentTab();
|
|
const panel = document.getElementById(`${currentTab}-code-panel`);
|
|
if (!panel) return;
|
|
|
|
const template = contextTemplates[state.type][currentTab];
|
|
if (!template) return;
|
|
|
|
const defaultIndent = indentMap[currentTab] && indentMap[currentTab][state.type] ? indentMap[currentTab][state.type] : " ";
|
|
const flashCode = generateFlashCode(currentTab, defaultIndent);
|
|
|
|
if (animate && template.code) {
|
|
panel.innerHTML = `<pre class="language-${currentTab === "js" ? "javascript" : "php"}"><code>${escapeHtml(template.code.before)}</code></pre>`;
|
|
if (typeof Prism !== "undefined") {
|
|
Prism.highlightElement(panel.querySelector("code"));
|
|
}
|
|
const typingContainer = document.createElement("div");
|
|
typingContainer.className = "typing-container border-l-4 mt-4 border-green-500 bg-green-50/20";
|
|
typingContainer.style.fontFamily = "monospace";
|
|
|
|
const panelPre = panel.querySelector("pre");
|
|
panelPre.appendChild(typingContainer);
|
|
panelPre.style.marginBottom = "0";
|
|
panelPre.style.paddingBottom = "0";
|
|
|
|
if (template.code.after) {
|
|
const afterContainer = document.createElement("pre");
|
|
afterContainer.className = `language-${currentTab === "js" ? "javascript" : "php"}`;
|
|
afterContainer.style.marginTop = "0";
|
|
afterContainer.style.paddingTop = "0";
|
|
afterContainer.innerHTML = `<code>${escapeHtml(template.code.after)}</code>`;
|
|
panel.appendChild(afterContainer);
|
|
if (typeof Prism !== "undefined") {
|
|
Prism.highlightElement(afterContainer.querySelector("code"));
|
|
}
|
|
}
|
|
typeCode(flashCode, typingContainer);
|
|
} else {
|
|
let completeCode = "";
|
|
if (template.code && template.code.before) {
|
|
completeCode = template.code.before + flashCode;
|
|
if (template.code.after) {
|
|
completeCode += template.code.after;
|
|
}
|
|
} else {
|
|
completeCode = flashCode;
|
|
}
|
|
panel.innerHTML = `<pre class="language-${currentTab === "js" || currentTab === "sweetalert" ? "javascript" : "php"}"><code>${escapeHtml(completeCode)}</code></pre>`;
|
|
if (typeof Prism !== "undefined") {
|
|
Prism.highlightElement(panel.querySelector("code"));
|
|
}
|
|
}
|
|
}
|
|
|
|
function typeCode(code, container) {
|
|
const cursor = document.createElement("span");
|
|
cursor.className = "cursor";
|
|
container.appendChild(cursor);
|
|
|
|
let i = 0;
|
|
let currentLine = document.createElement("div");
|
|
currentLine.style.minHeight = "1.2em";
|
|
container.insertBefore(currentLine, cursor);
|
|
|
|
const charsPerMinute = 5000;
|
|
const baseDelay = 60000 / charsPerMinute;
|
|
|
|
function typeNextChar() {
|
|
if (i < code.length) {
|
|
const char = code.charAt(i);
|
|
|
|
if (char === "\n") {
|
|
currentLine = document.createElement("div");
|
|
currentLine.style.minHeight = "1.2em";
|
|
container.insertBefore(currentLine, cursor);
|
|
} else {
|
|
const charSpan = document.createElement("span");
|
|
if ("(){}[];".includes(char)) {
|
|
charSpan.style.color = "#a626a4";
|
|
} else if ("'\"`".includes(char)) {
|
|
charSpan.style.color = "#50a14f";
|
|
} else if (char === "-" && i > 0 && code.charAt(i - 1) === "-" && code.charAt(i - 2) === ">") {
|
|
charSpan.style.color = "#e45649";
|
|
} else if (char === ">") {
|
|
charSpan.style.color = "#e45649";
|
|
} else {
|
|
charSpan.style.color = "#383a42";
|
|
}
|
|
charSpan.textContent = char;
|
|
currentLine.appendChild(charSpan);
|
|
}
|
|
|
|
i++;
|
|
let delay = baseDelay;
|
|
|
|
if ([".", ",", ";", ")", "}", ">", "!"].includes(char)) {
|
|
delay *= 2.5;
|
|
} else if (char === "\n") {
|
|
delay *= 4;
|
|
} else {
|
|
delay *= (0.7 + Math.random() * 0.6);
|
|
}
|
|
|
|
if (Math.random() < 0.05) {
|
|
delay += Math.random() * 500;
|
|
}
|
|
|
|
setTimeout(typeNextChar, delay);
|
|
} else {
|
|
setTimeout(() => {
|
|
cursor.remove();
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
setTimeout(typeNextChar, 150);
|
|
}
|
|
|
|
function updateContentForType(type) {
|
|
const currentTab = getCurrentTab();
|
|
const template = contextTemplates[type][currentTab];
|
|
if (!template) return;
|
|
|
|
document.getElementById("message-input").value = template.message;
|
|
state.message = template.message;
|
|
|
|
if (template.title && template.title.toLowerCase() !== type.toLowerCase() &&
|
|
!(template.title.toLowerCase() === "information" && type.toLowerCase() === "info")) {
|
|
document.getElementById("title-input").value = template.title;
|
|
state.title = template.title;
|
|
} else {
|
|
document.getElementById("title-input").value = "";
|
|
state.title = "";
|
|
}
|
|
}
|
|
|
|
function applyPreset(presetKey) {
|
|
const preset = presets[presetKey];
|
|
if (!preset) return;
|
|
|
|
state.type = preset.type;
|
|
state.title = preset.title;
|
|
state.message = preset.message;
|
|
|
|
document.getElementById("title-input").value = preset.title;
|
|
document.getElementById("message-input").value = preset.message;
|
|
|
|
initializeTypeButtons();
|
|
updateStatus(`Preset applied: ${presetKey} ✓`);
|
|
}
|
|
|
|
function showComparison() {
|
|
const modal = document.getElementById("comparison-modal");
|
|
const beforeCode = document.getElementById("before-code").querySelector("code");
|
|
const afterCode = document.getElementById("after-code").querySelector("code");
|
|
|
|
const example = beforeAfterExamples[state.type];
|
|
if (!example) return;
|
|
|
|
beforeCode.textContent = example.before;
|
|
afterCode.textContent = example.after;
|
|
|
|
if (typeof Prism !== "undefined") {
|
|
Prism.highlightElement(beforeCode);
|
|
Prism.highlightElement(afterCode);
|
|
}
|
|
|
|
modal.classList.remove("hidden");
|
|
}
|
|
|
|
function showNotification() {
|
|
const options = {};
|
|
if (state.position) options.position = state.position;
|
|
if (state.timeout !== undefined && state.timeout !== "") options.timeout = state.timeout;
|
|
if (state.direction) options.direction = state.direction;
|
|
if (state.rtl) options.rtl = state.rtl;
|
|
if (state.escapeHtml !== undefined) options.escapeHtml = state.escapeHtml;
|
|
|
|
console.log("Showing notification with options:", options);
|
|
|
|
try {
|
|
if (typeof flasher !== "undefined") {
|
|
if (state.theme) {
|
|
const notificationOptions = {
|
|
type: state.type,
|
|
message: state.message,
|
|
title: state.title || undefined,
|
|
...options
|
|
};
|
|
delete notificationOptions.theme;
|
|
flasher.use(`theme.${state.theme}`).flash(notificationOptions);
|
|
} else {
|
|
flasher[state.type](state.message, state.title || null, options);
|
|
}
|
|
updateStatus("Notification shown ✓");
|
|
} else {
|
|
let optionsText = Object.entries(options)
|
|
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
.join("\n");
|
|
alert(`Notification would display with:\n\n` +
|
|
`Type: ${state.type}\n` +
|
|
`Message: ${state.message}\n` +
|
|
(state.title ? `Title: ${state.title}\n` : "") +
|
|
(optionsText ? `\nOptions:\n${optionsText}` : ""));
|
|
}
|
|
} catch (e) {
|
|
console.error("Error showing notification:", e);
|
|
updateStatus("Error: " + e.message);
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
initializeFormValues();
|
|
initializeTypeButtons();
|
|
|
|
// Type buttons
|
|
document.querySelectorAll(".type-button").forEach(btn => {
|
|
btn.addEventListener("click", () => {
|
|
const type = btn.dataset.type;
|
|
state.type = type;
|
|
updateContentForType(type);
|
|
initializeTypeButtons();
|
|
updateStatus(`Type: ${state.type}`);
|
|
});
|
|
});
|
|
|
|
// Preset buttons
|
|
document.querySelectorAll(".preset-btn").forEach(btn => {
|
|
btn.addEventListener("click", () => {
|
|
const preset = btn.dataset.preset;
|
|
applyPreset(preset);
|
|
});
|
|
});
|
|
|
|
// Form inputs
|
|
document.getElementById("title-input").addEventListener("input", (e) => {
|
|
state.title = e.target.value.trim();
|
|
});
|
|
document.getElementById("message-input").addEventListener("input", (e) => {
|
|
state.message = e.target.value;
|
|
});
|
|
document.getElementById("position-select").addEventListener("change", (e) => {
|
|
state.position = e.target.value;
|
|
});
|
|
document.getElementById("timeout-select").addEventListener("change", (e) => {
|
|
if (e.target.value === "false") {
|
|
state.timeout = false;
|
|
} else if (e.target.value) {
|
|
state.timeout = parseInt(e.target.value);
|
|
} else {
|
|
state.timeout = "";
|
|
}
|
|
});
|
|
document.getElementById("theme-select").addEventListener("change", (e) => {
|
|
state.theme = e.target.value;
|
|
});
|
|
document.getElementById("direction-select").addEventListener("change", (e) => {
|
|
state.direction = e.target.value;
|
|
});
|
|
document.getElementById("rtl-check").addEventListener("change", (e) => {
|
|
state.rtl = e.target.checked;
|
|
});
|
|
document.getElementById("escape-html-check").addEventListener("change", (e) => {
|
|
state.escapeHtml = e.target.checked;
|
|
});
|
|
|
|
// Code tab buttons
|
|
document.querySelectorAll(".code-tab-btn").forEach(button => {
|
|
button.addEventListener("click", () => {
|
|
const tab = button.getAttribute("data-tab");
|
|
showCodeTab(tab);
|
|
});
|
|
});
|
|
|
|
// Compare button
|
|
document.getElementById("compare-btn").addEventListener("click", showComparison);
|
|
document.getElementById("close-comparison-btn").addEventListener("click", () => {
|
|
document.getElementById("comparison-modal").classList.add("hidden");
|
|
});
|
|
|
|
// Show notification button
|
|
const notificationBtn = document.getElementById("show-notification-btn");
|
|
notificationBtn.addEventListener("click", () => {
|
|
notificationBtn.classList.add("scale-95");
|
|
setTimeout(() => notificationBtn.classList.remove("scale-95"), 150);
|
|
updateStatus("Launching notification...");
|
|
showNotification();
|
|
});
|
|
|
|
// Close modal on outside click
|
|
document.getElementById("comparison-modal").addEventListener("click", (e) => {
|
|
if (e.target.id === "comparison-modal") {
|
|
document.getElementById("comparison-modal").classList.add("hidden");
|
|
}
|
|
});
|
|
|
|
// Konami code easter egg
|
|
let konamiCode = [];
|
|
const konamiSequence = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown",
|
|
"ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"];
|
|
document.addEventListener("keydown", function(e) {
|
|
konamiCode.push(e.key);
|
|
konamiCode = konamiCode.slice(-konamiSequence.length);
|
|
if (konamiCode.join(",") === konamiSequence.join(",")) {
|
|
if (typeof flasher !== "undefined") {
|
|
flasher.use("neon").success("You found the Konami code! 🎮", "Secret Unlocked");
|
|
}
|
|
}
|
|
});
|
|
|
|
console.info("%cTry the Konami code for a surprise!", "color: #10B981; font-style: italic;");
|
|
showCodeTab("laravel");
|
|
});
|
|
</script>
|