progressive-acrylic
Version:
Advanced layered acrylic glass effects for web elements. Create iOS/Windows-style acrylic blur overlays with multiple customizable layers: blur, luminosity, tint, and noise.
1,505 lines (1,338 loc) • 63.9 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Progressive Blur Editor</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* { box-sizing: border-box; }
body {
background: #1a1a1a;
color: #e0e0e0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
min-height: 100vh;
}
i{
margin-right: 5px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2rem;
font-weight: 500;
margin: 0;
color: #ffffff;
}
.editor-container {
display: grid;
grid-template-columns: 700px 1fr;
gap: 30px;
max-width: 1200px;
margin: 0 auto;
align-items: start;
}
.preview-section {
position: sticky;
top: 20px;
}
.container {
position: relative;
width: 700px;
height: 450px;
background-size: cover;
background-position: center;
border-radius: 8px;
overflow: hidden;
border: 1px solid #333;
background-image: url('https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=700&q=80');
}
.blur-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.image-selector {
display: flex;
gap: 8px;
margin: 15px 0;
justify-content: center;
align-items: center;
}
.image-thumb {
width: 60px;
height: 40px;
border-radius: 4px;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.2s;
background-size: cover;
background-position: center;
}
.image-thumb:hover {
border-color: #666;
}
.image-thumb.active {
border-color: #0066cc;
}
.upload-section {
display: flex;
align-items: center;
gap: 10px;
margin-left: 15px;
padding-left: 15px;
border-left: 1px solid #333;
}
.upload-btn {
position: relative;
background: #2a2a2a !important;
border: 1px solid #444 !important;
padding: 8px 16px !important;
font-size: 0.85rem !important;
cursor: pointer;
overflow: hidden;
border-radius: 4px;
}
.upload-btn:hover {
background: #333 !important;
}
.upload-btn input[type="file"] {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.controls-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.control-panel {
background: #222;
border: 1px solid #333;
border-radius: 6px;
padding: 20px;
}
.panel-title {
font-size: 1.1rem;
font-weight: 500;
margin: 0 0 15px 0;
color: #fff;
}
.dual-panel {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.sub-panel {
padding: 0;
}
.sub-title {
font-size: 0.95rem;
font-weight: 500;
margin: 0 0 12px 0;
color: #ccc;
border-bottom: 1px solid #333;
padding-bottom: 6px;
}
.control-group {
margin: 15px 0;
}
.control-group label {
display: block;
margin-bottom: 6px;
font-weight: 400;
font-size: 0.9rem;
color: #ccc;
}
.dropdown-label {
display: flex !important;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
}
.slider-container {
display: flex;
align-items: center;
gap: 12px;
}
input[type="range"] {
flex: 1;
height: 4px;
background: #333;
border-radius: 2px;
outline: none;
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
background: #0066cc;
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
background: #0066cc;
border-radius: 50%;
cursor: pointer;
border: none;
}
.value-display {
min-width: 40px;
text-align: center;
background: #333;
padding: 4px 8px;
border-radius: 3px;
font-size: 0.85rem;
color: #fff;
}
.button-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 8px;
}
.button-row {
display: flex;
gap: 8px;
margin-top: 8px;
}
button {
padding: 8px 16px;
background: #2a2a2a;
color: #e0e0e0;
border: 1px solid #444;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
flex: 1;
}
button:hover {
background: #333;
}
button.active {
background: #0066cc;
border-color: #0066cc;
color: #fff;
}
/* Bezier Editor Styles */
.bezier-editor {
background: #222;
border: 1px solid #333;
border-radius: 6px;
padding: 20px;
}
.bezier-layout {
display: grid;
grid-template-columns: 280px 1fr;
gap: 20px;
align-items: start;
}
.bezier-canvas-section {
display: flex;
justify-content: center;
}
.bezier-controls-section {
display: flex;
flex-direction: column;
gap: 15px;
padding-top: 10px;
}
.bezier-canvas {
width: 280px;
height: 280px;
background: #1a1a1a;
border-radius: 4px;
display: block;
cursor: crosshair;
border: 1px solid #333;
}
.bezier-values {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.bezier-values input {
padding: 6px 8px;
border: 1px solid #444;
border-radius: 3px;
background: #2a2a2a;
color: #e0e0e0;
text-align: left;
font-size: 0.85rem;
width: 76px;
}
.bezier-presets {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.preset-btn {
background: #2a2a2a !important;
font-size: 0.8rem;
padding: 6px 10px !important;
}
.preset-btn:hover {
background: #333 !important;
}
.bezier-output {
text-align: center;
margin-top: 15px;
font-size: 0.8rem;
color: #999;
font-family: monospace;
background: #1a1a1a;
padding: 6px 10px;
border-radius: 3px;
border: 1px solid #333;
}
.export-section {
background: #222;
border: 1px solid #333;
border-radius: 6px;
padding: 20px;
margin-top: 20px;
}
.code-output {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 4px;
padding: 12px;
font-family: 'SF Mono', Monaco, monospace;
font-size: 0.85rem;
color: #4CAF50;
white-space: pre-wrap;
margin-bottom: 15px;
max-height: 200px;
overflow-y: auto;
}
.export-buttons {
display: flex;
gap: 8px;
}
.copy-btn {
background: #2a2a2a ;
font-weight: 500;
}
.copy-btn:hover {
background: #333;
}
.copy-btn:active {
background: #2a2a2a ;
}
/* Info text styling */
.info-text {
font-size: 0.75rem;
color: #888;
margin: 4px 0 0 0;
font-style: italic;
line-height: 1.3;
}
.layers-info {
font-size: 0.75rem;
color: #888;
margin: 4px 0 0 0;
font-style: italic;
line-height: 1.3;
}
.warning-text {
font-size: 0.75rem;
color: #ff9100;
/* margin: 4px 0 0 0; */
font-style: italic;
line-height: 1.3;
background: #ff910030;
padding: 10px;
border-radius: 8px;
border: 1px solid #ff9100;
margin-top: 30px;
}
/* New styles for form elements */
select {
padding: 6px 10px;
background: #2a2a2a;
color: #e0e0e0;
border: 1px solid #444;
border-radius: 4px;
font-size: 0.9rem;
width: 150px;
margin: 4px 0 0 0;
}
select:hover {
border-color: #666;
}
select:focus {
outline: none;
border-color: #0066cc;
}
input[type="checkbox"] {
width: 16px;
height: 16px;
margin-left: 8px;
accent-color: #0066cc;
}
input[type="color"] {
width: 50px;
height: 32px;
padding: 0;
border: 1px solid #444;
border-radius: 4px;
background: none;
cursor: pointer;
margin-left: 8px;
}
input[type="color"]:hover {
border-color: #666;
}
/* Gradient color stop styling */
.tint-gradient-color-stop {
display: flex !important;
align-items: center;
gap: 8px !important;
margin-bottom: 8px !important;
padding: 8px;
background: #1a1a1a;
border-radius: 4px;
border: 1px solid #333;
}
.tint-gradient-color-stop input[type="number"] {
width: 60px !important;
padding: 4px 6px;
border: 1px solid #444;
border-radius: 3px;
background: #2a2a2a;
color: #e0e0e0;
text-align: center;
font-size: 0.85rem;
}
.tint-gradient-color-stop button {
padding: 4px 8px !important;
min-width: 28px;
flex: 0 0 auto !important;
font-size: 0.9rem;
background: #dc3545 !important;
border-color: #dc3545 !important;
}
.tint-gradient-color-stop button:hover {
background: #c82333 !important;
border-color: #c82333 !important;
}
.tint-gradient-color-stop input[type="color"] {
margin-left: 0 !important;
width: 40px;
height: 28px;
}
.gradient-stop-separator {
display: inline-block;
width: 1px;
height: 30px;
background-color: #444;
margin: 0 5px;
vertical-align: middle;
}
/* Add button styling */
button[onclick*="addGradientColorStop"] {
background: #28a745 !important;
border-color: #28a745 !important;
margin-top: 8px;
padding: 8px 12px !important;
}
button[onclick*="addGradientColorStop"]:hover {
background: #218838 !important;
border-color: #218838 !important;
}
/* Improve control group spacing */
.control-group label {
display: flex;
align-items: center;
margin-bottom: 6px;
font-weight: 400;
font-size: 0.9rem;
color: #ccc;
}
.control-group label input[type="checkbox"],
.control-group label input[type="color"] {
margin-left: auto;
}
/* Grid improvements */
.dual-panel .sub-panel:last-child {
border-left: 1px solid #333;
padding-left: 20px;
}
/* Better spacing for nested controls */
#tintGradientControls {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #333;
}
#tintGradientControls .control-group {
margin: 10px 0;
}
#tintGradientColors {
margin-top: 8px;
}
#tintGradientColors > label {
display: block !important;
margin-bottom: 8px;
font-size: 0.85rem;
color: #aaa;
}
/* Modern Toggle Switch Styles */
.toggle-switch {
position: relative;
display: inline-block;
width: 40px;
height: 24px;
margin-left: auto;
}
.toggle-switch input[type="checkbox"] {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
border: 1px solid #555;
transition: all 0.3s ease;
border-radius: 24px;
display: flex;
align-items: center;
padding: 2px;
}
.toggle-slider:before {
content: "";
height: 18px;
width: 18px;
background-color: #666;
transition: all 0.3s ease;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
flex-shrink: 0;
transform: translateX(0);
}
.toggle-switch input:checked + .toggle-slider {
background-color: #0066cc;
border-color: #0066cc;
}
.toggle-switch input:checked + .toggle-slider:before {
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(0,102,204,0.3);
transform: translateX(16px);
}
/* Expand animation when pressed */
.toggle-switch input:active + .toggle-slider:before {
width: 22px;
border-radius: 12px;
transition: all 0.1s ease;
}
.toggle-switch input:checked:active + .toggle-slider:before {
width: 22px;
border-radius: 12px;
transform: translateX(12px);
transition: all 0.1s ease;
}
.toggle-switch:hover .toggle-slider {
background-color: #444;
}
.toggle-switch input:checked:hover + .toggle-slider {
background-color: #0052a3;
}
/* Improve control group labels for toggles */
.control-group label.toggle-label {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
font-weight: 400;
font-size: 0.9rem;
color: #ccc;
cursor: pointer;
}
.control-group label.toggle-label:hover {
color: #fff;
}
</style>
</head>
<body>
<div class="header">
<h1><i class="fas fa-magic"></i> Progressive Blur Editor</h1>
<!-- <p>Create beautiful iOS-style progressive blur effects with real-time preview</p> -->
</div>
<div class="editor-container">
<!-- Left: Preview -->
<div class="preview-section">
<div class="container" id="previewContainer">
<div class="blur-overlay" id="blurBox"></div>
</div>
<div class="image-selector">
<div class="image-thumb active" data-image="https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=700&q=80" style="background-image: url('https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=100&q=80')"></div>
<div class="image-thumb" data-image="https://images.unsplash.com/photo-1519904981063-b0cf448d479e?auto=format&fit=crop&w=700&q=80" style="background-image: url('https://images.unsplash.com/photo-1519904981063-b0cf448d479e?auto=format&fit=crop&w=100&q=80')"></div>
<div class="image-thumb" data-image="https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=700&q=80" style="background-image: url('https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=100&q=80')"></div>
<div class="image-thumb" data-image="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=700&q=80" style="background-image: url('https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=100&q=80')"></div>
<div class="image-thumb" data-image="https://images.unsplash.com/photo-1518837695005-2083093ee35b?auto=format&fit=crop&w=700&q=80" style="background-image: url('https://images.unsplash.com/photo-1518837695005-2083093ee35b?auto=format&fit=crop&w=100&q=80')"></div>
<div class="upload-section">
<div class="upload-btn">
<i class="fas fa-upload"></i> Upload Image
<input type="file" id="imageUpload" accept="image/*">
</div>
</div>
</div>
<!-- Export Code -->
<div class="export-section">
<!-- <h3 class="panel-title">📋 Export Code</h3> -->
<div class="code-output" id="codeOutput">// Your generated code will appear here</div>
<div class="export-buttons">
<button onclick="updateCode()"><i class="fas fa-sync-alt"></i> Refresh Code</button>
<button class="copy-btn" onclick="copyCode()"><i class="fas fa-copy"></i> Copy Code</button>
</div>
</div>
</div>
<!-- Right: Controls -->
<div class="controls-section">
<!-- Blur Settings -->
<div class="control-panel" >
<h3 class="panel-title">Main Settings</h3>
<div class="control-group">
<label class="toggle-label" for="blurEnabled">
Blur Enabled:
<div class="toggle-switch">
<input type="checkbox" id="blurEnabled" checked>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="control-group">
<label><i class="fas fa-adjust"></i> Blur Amount: <span id="blurValue">200</span>px</label>
<div class="slider-container">
<input type="range" id="blurSlider" min="0" max="1000" value="200">
<div class="value-display" id="blurDisplay">200</div>
</div>
<p class="info-text">Recommended: 100-500px</p>
</div>
<div class="control-group">
<label><i class="fas fa-layer-group"></i> Layers: <span id="layersValue">5</span></label>
<div class="slider-container">
<input type="range" id="layersSlider" min="3" max="20" value="5">
<div class="value-display" id="layersDisplay">5</div>
</div>
<p class="info-text">More layers = smoother blur transition = bad performance (3-8 recommended)</p>
</div>
<div class="control-group">
<label><i class="fas fa-arrows-alt-v"></i> Height: <span id="heightValue">50</span>%</label>
<div class="slider-container">
<input type="range" id="heightSlider" min="10" max="100" value="50">
<div class="value-display" id="heightDisplay">50</div>
</div>
<p class="info-text">Size of the blur area relative to container</p>
</div>
<!-- Preset Buttons -->
<div class="control-group">
<label><i class="fas fa-magic"></i> Presets:</label>
<div class="button-grid">
<button class="preset-btn" onclick="loadBlurPreset('ios')">IOS Style</button>
<button class="preset-btn" onclick="loadBlurPreset('windows-acrylic')">Windows Acrylic</button>
<button class="preset-btn" onclick="loadBlurPreset('glass-morphism')">Glass Morphism</button>
<button class="preset-btn" onclick="loadBlurPreset('subtle')">Subtle</button>
</div>
<p class="info-text">Quick presets for common blur configurations</p>
</div>
</div>
<!-- Opacity & Direction Settings -->
<div class="control-panel">
<!-- <h3 class="panel-title">Opacity & Direction</h3> -->
<div class="dual-panel">
<!-- Left: Opacity -->
<div class="sub-panel">
<h3 class="panel-title">Opacity</h3>
<div class="control-group">
<label>Start Opacity: <span id="startOpacityValue">0.0</span></label>
<div class="slider-container">
<input type="range" id="startOpacitySlider" min="0" max="1" step="0.1" value="0">
<div class="value-display" id="startOpacityDisplay">0.0</div>
</div>
<p class="info-text">Blur opacity at the fade start</p>
</div>
<div class="control-group">
<label>End Opacity: <span id="endOpacityValue">1.0</span></label>
<div class="slider-container">
<input type="range" id="endOpacitySlider" min="0" max="1" step="0.1" value="1">
<div class="value-display" id="endOpacityDisplay">1.0</div>
</div>
<p class="info-text">Blur opacity at the fade end</p>
</div>
<p class="warning-text"><i class="fas fa-circle-exclamation"></i> We don't recommend changing the opacity values.</p>
</div>
<!-- Right: Direction & Position -->
<div class="sub-panel" style="border-left: 1px solid #333; padding-left: 20px;">
<h3 class="panel-title">Direction & Position</h3>
<div class="control-group">
<label><i class="fas fa-arrows-alt"></i> Direction:</label>
<div class="button-grid">
<button class="dir-btn" data-dir="top"><i class="fas fa-arrow-up"></i>Top</button>
<button class="dir-btn active" data-dir="bottom"><i class="fas fa-arrow-down"></i> Bottom</button>
<button class="dir-btn" data-dir="left"><i class="fas fa-arrow-left"></i> Left</button>
<button class="dir-btn" data-dir="right"><i class="fas fa-arrow-right"></i> Right</button>
</div>
<p class="info-text">Which direction the blur fades towards</p>
</div>
<div class="control-group">
<label><i class="fas fa-map-marker-alt"></i> Position:</label>
<div class="button-row">
<button class="pos-btn" data-pos="top">Top</button>
<button class="pos-btn active" data-pos="bottom">Bottom</button>
</div>
<p class="info-text">Where to place the blur area (top/bottom edge)</p>
</div>
</div>
</div>
</div>
<!-- Bezier Curve Editor -->
<div class="bezier-editor">
<h3 class="panel-title">Feather Curve</h3>
<p class="info-text" style="margin-bottom: 15px;">Control how blur intensity changes across the gradient. Drag control points or use presets.</p>
<div class="bezier-layout">
<!-- Left: Canvas -->
<div class="bezier-canvas-section">
<canvas class="bezier-canvas" id="bezierCanvas" width="280" height="280"></canvas>
</div>
<!-- Right: Controls -->
<div class="bezier-controls-section">
<div class="bezier-values">
<input type="number" id="x1Input" step="0.01" min="0" max="1" value="0.25" placeholder="x1">
<input type="number" id="y1Input" step="0.01" value="0.46" placeholder="y1">
<input type="number" id="x2Input" step="0.01" min="0" max="1" value="0.45" placeholder="x2">
<input type="number" id="y2Input" step="0.01" value="0.94" placeholder="y2">
</div>
<div class="bezier-presets" style="padding-top: 15px; border-top: 1px solid #333;">
<button class="preset-btn" onclick="loadPreset([0.25, 0.46, 0.45, 0.94])">IOS</button>
<button class="preset-btn" onclick="loadPreset([0.68, -0.55, 0.265, 1.55])">Bounce</button>
<button class="preset-btn" onclick="loadPreset([0.16, 1, 0.3, 1])">Extreme</button>
<button class="preset-btn" onclick="loadPreset([0.7, 0, 0.84, 0])">Sharp</button>
</div>
</div>
</div>
<!-- Bottom: Output -->
<div class="bezier-output">
<span id="bezierOutput">cubic-bezier(0.25, 0.46, 0.45, 0.94)</span>
</div>
</div>
<!-- Luminosity & Tint Settings -->
<div class="control-panel">
<h3 class="panel-title"><i class="fas fa-lightbulb"></i> Luminosity Layer</h3>
<div class="control-group">
<label class="toggle-label" for="luminosityEnabled">
Enabled:
<div class="toggle-switch">
<input type="checkbox" id="luminosityEnabled" checked>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="control-group">
<label>Brightness: <span id="brightnessValue">1.1</span></label>
<div class="slider-container">
<input type="range" id="brightnessSlider" min="0" max="2" step="0.1" value="1.1">
<div class="value-display" id="brightnessDisplay">1.1</div>
</div>
</div>
<div class="control-group">
<label>Contrast: <span id="contrastValue">1.05</span></label>
<div class="slider-container">
<input type="range" id="contrastSlider" min="0" max="2" step="0.05" value="1.05">
<div class="value-display" id="contrastDisplay">1.05</div>
</div>
</div>
<div class="control-group">
<label>Saturate: <span id="saturateValue">1.2</span></label>
<div class="slider-container">
<input type="range" id="saturateSlider" min="0" max="3" step="0.1" value="1.2">
<div class="value-display" id="saturateDisplay">1.2</div>
</div>
</div>
<div class="control-group">
<label>Opacity: <span id="luminosityOpacityValue">0.8</span></label>
<div class="slider-container">
<input type="range" id="luminosityOpacitySlider" min="0" max="1" step="0.1" value="0.8">
<div class="value-display" id="luminosityOpacityDisplay">0.8</div>
</div>
</div>
<div class="control-group">
<label class="dropdown-label">Blend Mode:
<select id="luminosityBlendMode">
<option value="normal">Normal</option>
<option value="overlay">Overlay</option>
<option value="screen">Screen</option>
<option value="multiply">Multiply</option>
<option value="lighten">Lighten</option>
<option value="color-dodge">Color Dodge</option>
</select>
</label>
</div>
</div>
<div class="control-panel">
<h3 class="panel-title"><i class="fas fa-palette"></i> Tint Layer</h3>
<div class="control-group">
<label class="toggle-label" for="tintEnabled">
Enabled:
<div class="toggle-switch">
<input type="checkbox" id="tintEnabled" checked>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="control-group">
<label>Color: <input type="color" id="tintColor" value="#ffffff"></label>
</div>
<div class="control-group">
<label>Opacity: <span id="tintOpacityValue">0.1</span></label>
<div class="slider-container">
<input type="range" id="tintOpacitySlider" min="0" max="1" step="0.05" value="0.1">
<div class="value-display" id="tintOpacityDisplay">0.1</div>
</div>
</div>
<div class="control-group">
<label class="dropdown-label">Blend Mode:
<select id="tintBlendMode">
<option value="normal">Normal</option>
<option value="overlay" selected>Overlay</option>
<option value="screen">Screen</option>
<option value="multiply">Multiply</option>
<option value="color">Color</option>
<option value="hue">Hue</option>
</select>
</label>
</div>
<div class="control-group">
<label class="toggle-label" for="tintGradientEnabled">
Gradient:
<div class="toggle-switch">
<input type="checkbox" id="tintGradientEnabled">
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div id="tintGradientControls" style="display:none;">
<div class="control-group">
<label class="dropdown-label">Direction:
<select id="tintGradientDirection">
<option value="to bottom">To Bottom</option>
<option value="to top">To Top</option>
<option value="to right">To Right</option>
<option value="to left">To Left</option>
<option value="to bottom right">To Bottom Right</option>
<option value="to top left">To Top Left</option>
</select>
</label>
</div>
<div class="control-group" id="tintGradientColors">
<label>Colors (up to 5):</label>
<!-- Color stops will be added here by JS -->
</div>
<button onclick="addGradientColorStop()" type="button"><i class="fas fa-plus"></i> Add Color Stop</button>
</div>
</div>
<div class="control-panel">
<h3 class="panel-title"><i class="fas fa-tv"></i> Noise Layer</h3>
<div class="control-group">
<label class="toggle-label" for="noiseEnabled">
Enabled:
<div class="toggle-switch">
<input type="checkbox" id="noiseEnabled">
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="control-group">
<label>Opacity: <span id="noiseOpacityValue">0.3</span></label>
<div class="slider-container">
<input type="range" id="noiseOpacitySlider" min="0" max="1" step="0.05" value="0.3">
<div class="value-display" id="noiseOpacityDisplay">0.3</div>
</div>
</div>
<div class="control-group">
<label class="dropdown-label">Blend Mode:
<select id="noiseBlendMode">
<option value="normal">Normal</option>
<option value="multiply" selected>Multiply</option>
<option value="overlay">Overlay</option>
<option value="screen">Screen</option>
<option value="darken">Darken</option>
<option value="lighten">Lighten</option>
</select>
</label>
</div>
<p class="info-text">Adds texture and grain to the blur effect for more realistic appearance</p>
</div>
</div>
</div>
<!-- Footer Section -->
<footer style="margin-top: 50px; padding: 30px 20px; background: #1a1a1a; border-top: 1px solid #333; text-align: center;">
<div style="max-width: 1200px; margin: 0 auto;">
<h3 style="color: #fff; margin: 0 0 15px 0; font-size: 1.3rem;">
<i class="fas fa-magic"></i> Progressive Acrylic Interactive Editor
</h3>
<p style="color: #999; margin: 0 0 20px 0; font-size: 0.95rem; line-height: 1.5;">
Real-time editor for creating beautiful layered acrylic glass effects.
Perfect for iOS-style progressive blur, Windows Acrylic material, and custom glass morphism designs.
</p>
<div style="display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; margin: 20px 0;">
<a href="https://github.com/ysfemreAlbyrk/Progressive-Acrylic"
style="color: #0066cc; text-decoration: none; padding: 8px 16px; border: 1px solid #0066cc; border-radius: 4px; transition: all 0.3s ease;"
onmouseover="this.style.background='#0066cc'; this.style.color='#fff'"
onmouseout="this.style.background='transparent'; this.style.color='#0066cc'">
<i class="fab fa-github"></i> GitHub Repository
</a>
<a href="https://www.npmjs.com/package/progressive-acrylic"
style="color: #cb3837; text-decoration: none; padding: 8px 16px; border: 1px solid #cb3837; border-radius: 4px; transition: all 0.3s ease;"
onmouseover="this.style.background='#cb3837'; this.style.color='#fff'"
onmouseout="this.style.background='transparent'; this.style.color='#cb3837'">
<i class="fab fa-npm"></i> NPM Package
</a>
<a href="./docs/README.md"
style="color: #28a745; text-decoration: none; padding: 8px 16px; border: 1px solid #28a745; border-radius: 4px; transition: all 0.3s ease;"
onmouseover="this.style.background='#28a745'; this.style.color='#fff'"
onmouseout="this.style.background='transparent'; this.style.color='#28a745'">
<i class="fas fa-book"></i> Documentation
</a>
</div>
<div style="border-top: 1px solid #333; padding-top: 15px; margin-top: 20px; font-size: 0.8rem; color: #888;">
<p style="margin: 0;">
Created by <strong>Yusuf Emre Albayrak</strong> •
Licensed under <strong>MIT</strong> •
Made with <i class="fas fa-heart" style="color: #ff4757;"></i> for the web community
</p>
</div>
</div>
</footer>
<script src="./progressive-acrylic.js"></script>
<script>
let currentSettings = {
blur: {
enabled: true,
maxBlur: 200,
layers: 5,
height: '50%',
startOpacity: 0,
endOpacity: 1,
direction: 'bottom',
position: 'bottom',
curve: [0.25, 0.46, 0.45, 0.94]
},
luminosity: {
enabled: true,
brightness: 1.1,
contrast: 1.05,
saturate: 1.2,
opacity: 0.8,
blendMode: 'normal'
},
tint: {
enabled: true,
color: '#ffffff',
gradient: null,
opacity: 0.1,
blendMode: 'overlay'
},
noise: {
enabled: false,
opacity: 0.3,
blendMode: 'multiply'
}
};
// Cubic Bezier Editor
const canvas = document.getElementById('bezierCanvas');
const ctx = canvas.getContext('2d');
let isDragging = false;
let dragPoint = null;
let bezierPoints = {
p1: { x: 70, y: 151 }, // x1=0.25, y1=0.46 (280*0.25, 280*(1-0.46))
p2: { x: 126, y: 17 } // x2=0.45, y2=0.94 (280*0.45, 280*(1-0.94))
};
function drawBezierEditor() {
ctx.clearRect(0, 0, 280, 280);
// Grid
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
for (let i = 0; i <= 280; i += 35) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, 280);
ctx.moveTo(0, i);
ctx.lineTo(280, i);
ctx.stroke();
}
// Main diagonal
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 280);
ctx.lineTo(280, 0);
ctx.stroke();
// Control lines
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
ctx.setLineDash([3, 3]);
ctx.beginPath();
ctx.moveTo(0, 280);
ctx.lineTo(bezierPoints.p1.x, bezierPoints.p1.y);
ctx.moveTo(280, 0);
ctx.lineTo(bezierPoints.p2.x, bezierPoints.p2.y);
ctx.stroke();
ctx.setLineDash([]);
// Bezier curve
ctx.strokeStyle = '#0066cc';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, 280);
ctx.bezierCurveTo(
bezierPoints.p1.x, bezierPoints.p1.y,
bezierPoints.p2.x, bezierPoints.p2.y,
280, 0
);
ctx.stroke();
// Control points
ctx.fillStyle = '#0066cc';
ctx.beginPath();
ctx.arc(bezierPoints.p1.x, bezierPoints.p1.y, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(bezierPoints.p2.x, bezierPoints.p2.y, 6, 0, 2 * Math.PI);
ctx.fill();
// Update values
updateBezierValues();
updateBlur();
}
function updateBezierValues() {
const x1 = (bezierPoints.p1.x / 280).toFixed(3);
const y1 = (1 - bezierPoints.p1.y / 280).toFixed(3);
const x2 = (bezierPoints.p2.x / 280).toFixed(3);
const y2 = (1 - bezierPoints.p2.y / 280).toFixed(3);
document.getElementById('x1Input').value = x1;
document.getElementById('y1Input').value = y1;
document.getElementById('x2Input').value = x2;
document.getElementById('y2Input').value = y2;
document.getElementById('bezierOutput').textContent = `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`;
currentSettings.blur.curve = [parseFloat(x1), parseFloat(y1), parseFloat(x2), parseFloat(y2)];
}
// Canvas events
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const dist1 = Math.sqrt((x - bezierPoints.p1.x) ** 2 + (y - bezierPoints.p1.y) ** 2);
const dist2 = Math.sqrt((x - bezierPoints.p2.x) ** 2 + (y - bezierPoints.p2.y) ** 2);
if (dist1 < 20) {
isDragging = true;
dragPoint = 'p1';
} else if (dist2 < 20) {
isDragging = true;
dragPoint = 'p2';
}
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const rect = canvas.getBoundingClientRect();
const x = Math.max(0, Math.min(280, e.clientX - rect.left));
const y = Math.max(0, Math.min(280, e.clientY - rect.top));
bezierPoints[dragPoint] = { x, y };
drawBezierEditor();
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
dragPoint = null;
});
// Input listeners
['x1Input', 'y1Input', 'x2Input', 'y2Input'].forEach(id => {
document.getElementById(id).addEventListener('input', () => {
const x1 = parseFloat(document.getElementById('x1Input').value);
const y1 = parseFloat(document.getElementById('y1Input').value);
const x2 = parseFloat(document.getElementById('x2Input').value);
const y2 = parseFloat(document.getElementById('y2Input').value);
bezierPoints.p1.x = x1 * 280;
bezierPoints.p1.y = (1 - y1) * 280;
bezierPoints.p2.x = x2 * 280;
bezierPoints.p2.y = (1 - y2) * 280;
drawBezierEditor();
});
});
function loadPreset(values) {
const [x1, y1, x2, y2] = values;
bezierPoints.p1.x = x1 * 280;
bezierPoints.p1.y = (1 - y1) * 280;
bezierPoints.p2.x = x2 * 280;
bezierPoints.p2.y = (1 - y2) * 280;
drawBezierEditor();
}
function updateBlur() {
progressiveAcrylic(document.getElementById('blurBox'), currentSettings);
updateCode();
}
function updateCode() {
let code = 'progressiveAcrylic(targetElement, {\n';
code += ' blur: {\n';
code += ` enabled: ${currentSettings.blur.enabled},\n`;
code += ` direction: '${currentSettings.blur.direction}',\n`;
code += ` height: '${currentSettings.blur.height}',\n`;
code += ` layers: ${currentSettings.blur.layers},\n`;
code += ` maxBlur: ${currentSettings.blur.maxBlur},\n`; // Note: API uses maxBlur, editor uses blur for slider id
code += ` startOpacity: ${currentSettings.blur.startOpacity},\n`;
code += ` endOpacity: ${currentSettings.blur.endOpacity},\n`;
code += ` position: '${currentSettings.blur.position}',\n`;
code += ` curve: [${currentSettings.blur.curve.join(', ')}]\n`;
code += ' },\n';
code += ' luminosity: {\n';
code += ` enabled: ${currentSettings.luminosity.enabled},\n`;
code += ` brightness: ${currentSettings.luminosity.brightness},\n`;
code += ` contrast: ${currentSettings.luminosity.contrast},\n`;
code += ` saturate: ${currentSettings.luminosity.saturate},\n`;
code += ` opacity: ${currentSettings.luminosity.opacity},\n`;
code += ` blendMode: '${currentSettings.luminosity.blendMode}'\n`;
code += ' },\n';
code += ' tint: {\n';
code += ` enabled: ${currentSettings.tint.enabled},\n`;
if (currentSettings.tint.gradient) {
code += ' gradient: {\n';
code += ` direction: '${currentSettings.tint.gradient.direction}',\n`;
code += ' colors: [\n';
currentSettings.tint.gradient.colors.forEach(c => {
if (typeof c === 'string') {
code += ` '${c}',\n`;
} else {
code += ` { color: '${c.color}', stop: ${c.stop}, opacity: ${c.opacity}`;
code += ' },\n';
}
});
code += ' ]\n';
code += ' },\n';
} else {
code += ` color: '${currentSettings.tint.color}',\n`;
}
code += ` opacity: ${currentSettings.tint.opacity},\n`;
code += ` blendMode: '${currentSettings.tint.blendMode}'\n`;
code += ' },\n';
code += ' noise: {\n';
code += ` enabled: ${currentSettings.noise.enabled},\n`;
code += ` opacity: ${currentSettings.noise.opacity},\n`;
code += ` blendMode: '${currentSettings.noise.blendMode}'\n`;
code += ' }\n';
code += '});';
document.getElementById('codeOutput').textContent = code;
}
function copyCode() {
const code = document.getElementById('codeOutput').textContent;
navigator.clipboard.writeText(code).then(() => {
const btn = document.querySelector('.copy-btn');
const originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
btn.style.backgroundColor = '#4CAF5030';
btn.style.color = '#4CAF50';
btn.style.borderColor = '#4CAF50';
btn.style.borderStyle = 'dashed';
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.style.backgroundColor = '';
btn.style.color = '';
btn.style.borderColor = '';
btn.style.borderStyle = 'solid';
}, 500);
});
}
// Slider controls
['blur', 'layers', 'height', 'startOpacity', 'endOpacity'].forEach(param => {
const slider = document.getElementById(param + 'Slider');
slider.addEventListener('input', function(e) {
const value = param.includes('Opacity') ?
parseFloat(e.target.value) : parseInt(e.target.value);
// Handle special parameter names
if (param === 'blur') {
currentSettings.blur.maxBlur = value;
} else if (param === 'height') {
currentSettings.blur.height = value + '%';
} else {
currentSettings.blur[param] = value;
}
const display = param.includes('Opacity') ? value.toFixed(1) : value;
document.getElementById(param + 'Display').textContent = display;
document.getElementById(param + 'Value').textContent = display;
updateBlur();
});
});
// Button controls
document.querySelectorAll('.dir-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.dir-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentSettings.blur.direction = this.getAttribute('data-dir');
updateBlur();
});
});
document.querySelectorAll('.pos-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.pos-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentSettings.blur.position = this.getAttribute('data-pos');
updateBlur();
});
});
// Image selector
document.querySelectorAll('.image-thumb').forEach(thumb => {
thumb.addEventListener('click', function() {
document.querySelectorAll('.image-thumb').forEach(t => t.classList.remove('active'));
this.classList.add('active');
document.getElementById('previewContainer').style.backgroundImage = `url('${this.getAttribute('data-image')}')`;
});
});
// Image upload
document.getElementById('imageUpload').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const imageUrl = e.target.result;
// Clear all active states
document.querySelectorAll('.image-thumb').forEach(t => t.classList.remove('active'));
// Set new background
document.getElementById('previewContainer').style.backgroundImage = `url('${imageUrl}')`;
// Optional: Add uploaded image as a new thumbnail
const newThumb = document.createElement('div');
newThumb.className = 'image-thumb active';
newThumb.style.backgroundImage = `url('${imageUrl}')`;
newThumb.setAttribute('data-image', imageUrl);
// Add click event to new thumb
newThumb.addEventListener('click', function() {
document.querySelectorAll('.image-thumb').forEach(t => t.classList.remove('active'));
this.classList.add('active');
document.getElementById('previewContainer').style.backgroundImage = `url('${imageUrl}')`;
});
// Insert before upload section
const uploadSection = document.querySelector('.upload-section');
uploadSection.parentNode.insertBefore(newThumb, uploadSection);
};
reader.readAsDataURL(file);
}
});
// --- LUMINOSITY LAYER CONTROLS ---
document.getElementById('luminosityEnabled').addEventListener('change', function(e) {
currentSettings.luminosity.enabled = e.target.checked;
updateBlur();
});
// --- BLUR LAYER CONTROLS ---
document.getElementById('blurEnabled').addEventListener('change', function(e) {
currentSettings.blur.enabled = e.target.checked;
updateBlur();
});
['brightness', 'contrast', 'saturate'].forEach(param => {
const slider = document.getElementById(param + 'Slider');
slider.addEventListener('input', function(e) {
const value = parseFloat(e.target.value);
currentSettings.luminosity[param] = value;
document.getElementById(param + 'Display').textContent = value.toFixed(param === 'contrast' ? 2 : 1);
document.getElementById(param + 'Value').textContent = value.toFixed(param === 'contrast' ? 2 : 1);
updateBlur();
});
});
document.getElementById('luminosityOpacitySlider').addEventListener('input', function(e) {
const value = parseFloat(e.target.value);
currentSettings.luminosity.opacity = value;
document.getElementById('luminosityOpacityDisplay').textContent = value.toFixed(1);
document.getElementById('luminosityOpacityValue').textContent = value.toFixed(1);
updateBlur();
});
document.getElementById('luminosityBlendMode').addEventListener('change', function(e) {
currentSettings.luminosity.blendMode = e.target.value;
updateBlur();
});
// --- TINT LAYER CONTROLS ---
document.getElementById('tintEnabled').addEventListener('change', function(e) {
currentSettings.tint.enabled = e.target.checked;
updateBlur();
});
document.getElementById('tintColor').addEventListener('input', function(e) {
currentSettings.tint.color = e.target.value;
if (!currentSettings.tint.gradient) { // Only update if not in gradient mode
updateBlur();
}
});
document.getElementById('tintOpacitySlider').addEventListener('input', function(e) {
const value = parseFloat(e.target.value);
currentSettings.tint.opacity = value;
document.getElementById('tintOpacityDisplay').textContent = value.toFixed(2);
document.getElementById('tintOpacityValue').textContent = value.toFixed(2);
updateBlur();
});
document.getElementById('tintBlendMode').addEventListener('change', function(e) {
currentSettings.tint.blendMode = e.target.value;
updateBlur();
});
document.getElementById('tintGradientEnabled').addEventListener('change', function(e) {
const gradientControls = document.getElementById('tintGradientControls');
if (e.target.checked) {
gradientControls.style.display = 'block';
currentSettings.tint.gradient = {
direction: document.getElementById('tintGradientDirection').value,
colors: getGradientColors()
};
if (currentSettings.tint.gradient.colors.length === 0) {
addGradientColorStop('#ffffff', 0, 1.0);
addGradientColorStop('#000000', 100, 1.0);
}
} else {
gradientControls.style.display = 'none';
currentSettings.tint.gradient = null;
}
updateBlur();
});
document.getElementById('tintGradientDirection').addEventListener('change', function(e) {
if (currentSettings.tint.gradient) {
currentSettings.tint.gradient.direction = e.target.value;
updateBlur();
}
});
function getGradientColors() {
const colorStops = [];
const colorElements = document.querySelectorAll('.tint-gradient-color-stop');
colorElements.forEach(el => {
const colorInput = el.querySelector('input[type="color"]');
const numberInputs = el.querySelectorAll('input[type="number"]');
const stopInput = numberInputs[0]; // First number input is stop
const opacityInput = numberInputs[1]; // Second number input is opacity
colorStops.push({
color: colorInput.value,
stop: parseInt(stopInput.value),
opacity: parseFloat(opacityInput.value)
});
});
return colorStops;
}
let colorStopCount = 0;
function addGradientColorStop(defaultColor = '#cccccc', defaultStop = 50, defaultOpacity = 1.0) {
if (colorStopCount >= 5) return; // Max 5 color stops
colorStopCount++;
const container = document.getElementById('tintGradientColors');
const stopDiv = document.createElement('div');
stopDiv.className = 'tint-gradient-color-stop';
stopDiv.style.display = 'flex';
stopDiv.style.gap = '8px';
stopDiv.style.marginBottom = '8px';
const colorInput = document.createElement('i