v2.2
MENU ANIMATIONS
UI SURECART
BUTTONS
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Dock Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 450px;
width: 100%;
position: relative;
overflow: visible;
border-radius: var(--card-radius);
background-color: #252525;
border: 1px solid var(--border);
box-shadow: var(--shadow);
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: var(--track);
border-radius: 3px;
outline: none;
margin: 0.8rem 0;
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: var(--thumb);
border: none;
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-moz-range-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
.color-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-row {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem 1.25rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
transition: var(--transition);
}
.color-row:hover {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}
.color-picker-container {
position: relative;
width: 40px;
height: 40px;
border-radius: 8px;
overflow: hidden;
border: 2px solid var(--border);
cursor: pointer;
transition: var(--transition);
flex-shrink: 0;
background: var(--card-bg);
display: flex;
align-items: center;
justify-content: center;
--selected-color: #3b82f6;
}
.color-picker-container:hover {
border-color: var(--accent);
transform: scale(1.05);
box-shadow: 0 0 12px rgba(239, 96, 19, 0.3);
}
.color-picker-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--selected-color, #3b82f6);
border-radius: 6px;
transition: var(--transition);
}
input[type="color"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
cursor: pointer;
background: transparent;
opacity: 0;
z-index: 2;
}
.color-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-label {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.25rem;
}
.color-input {
padding: 0.5rem 0.75rem;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 12px;
transition: var(--transition);
min-width: 0;
}
.color-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
.color-input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}
.hex-input,
.hsl-input {
width: 100%;
}
.color-input-group:nth-child(2) {
flex: 0.3;
}
.color-input-group:nth-child(3) {
flex: 0.7;
}
input[type="text"],
input[type="url"],
input[type="number"],
select {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
color: var(--text-primary);
background-color: var(--card-bg);
margin-bottom: 0.75rem;
outline: none;
transition: var(--transition);
}
input[type="text"]:focus,
input[type="url"]:focus,
input[type="number"]:focus,
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.toggle-switch-wrapper {
display: flex;
align-items: center;
margin: 0.5rem 0;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--card-bg);
border: 1px solid var(--border);
transition: .4s;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 3px;
background-color: var(--text-secondary);
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--accent);
border-color: var(--accent);
}
input:checked + .toggle-slider:before {
transform: translateX(26px);
background-color: white;
}
.toggle-label {
margin-left: 10px;
font-size: var(--text-xs);
color: var(--text-secondary);
}
input:checked ~ .toggle-label {
color: var(--accent);
}
.dock-inputs {
margin-bottom: 1rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
padding: 1rem;
position: relative;
}
.dock-inputs:last-child {
margin-bottom: 0;
}
.dock-count {
position: absolute;
top: -10px;
left: 10px;
background-color: var(--accent);
color: white;
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 600;
}
.dock-action-buttons {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.action-button {
flex: 1;
padding: 0.7rem 1rem;
border: none;
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
cursor: pointer;
transition: var(--transition);
}
.add-btn {
background-color: #2c2c2e;
color: white;
}
.add-btn:hover {
background-color: #3a3a3c;
transform: translateY(-2px);
}
.remove-btn {
background-color: rgba(255, 59, 48, 0.2);
color: #ff3b30;
}
.remove-btn:hover {
background-color: rgba(255, 59, 48, 0.3);
transform: translateY(-2px);
}
.icon-select-btn {
padding: 8px;
border-radius: var(--input-radius);
background-color: var(--card-bg);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: var(--text-secondary);
transition: var(--transition);
font-size: var(--text-xs);
font-weight: 500;
}
.icon-select-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
color: var(--text-primary);
}
.input-with-label {
display: flex;
flex-direction: column;
margin-bottom: 0.75rem;
}
.input-with-label label {
font-size: var(--text-xs);
margin-bottom: 0.25rem;
color: var(--text-secondary);
}
.notification {
position: fixed;
bottom: calc(var(--action-bar-height) + 1rem);
left: 50%;
background-color: var(--success);
color: white;
padding: 0.75rem 1rem;
border-radius: var(--input-radius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1001;
transform: translate(-50%, 200px);
opacity: 0;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
font-size: var(--text-xs);
font-weight: 500;
max-width: 320px;
word-wrap: break-word;
line-height: 1.4;
text-align: center;
}
.notification.show {
transform: translate(-50%, 0);
opacity: 1;
}
.btn {
display: block;
width: 100%;
padding: 0.9rem 1.5rem;
background-color: var(--accent);
color: white;
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
text-align: center;
border: none;
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
}
.btn:hover {
background-color: var(--accent-hover);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(239, 96, 19, 0.3);
}
.btn:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(239, 96, 19, 0.2);
}
.icon-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.icon-modal.active {
visibility: visible;
opacity: 1;
}
.icon-modal-content {
width: 90%;
max-width: 900px;
max-height: 90vh;
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
overflow: hidden;
transform: translateY(20px);
transition: all 0.3s ease;
border: 1px solid var(--border);
}
.icon-modal.active .icon-modal-content {
transform: translateY(0);
}
.icon-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.icon-modal-title {
font-size: var(--text-s);
font-weight: 600;
}
.close-modal {
background: none;
border: none;
font-size: var(--text-s);
cursor: pointer;
color: var(--text-secondary);
transition: var(--transition);
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.close-modal:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.1);
}
.icon-modal-body {
padding: 1.5rem;
overflow-y: auto;
max-height: calc(90vh - 200px);
-webkit-overflow-scrolling: touch;
}
.icon-upload-section {
margin-bottom: 2rem;
padding: 1rem;
border: 2px dashed var(--border);
border-radius: var(--input-radius);
text-align: center;
}
.icon-upload-section h4 {
margin-bottom: 1rem;
color: var(--text-primary);
font-size: 14px;
}
.upload-area {
position: relative;
padding: 2rem;
background-color: rgba(30, 30, 30, 0.5);
border-radius: var(--input-radius);
transition: var(--transition);
cursor: pointer;
}
.upload-area:hover {
background-color: rgba(239, 96, 19, 0.1);
border-color: var(--accent);
}
.upload-area.dragover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.upload-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.upload-icon {
font-size: 2rem;
color: var(--text-secondary);
}
.upload-text {
font-size: var(--text-xs);
color: var(--text-secondary);
}
.upload-subtext {
font-size: 11px;
color: var(--text-secondary);
opacity: 0.7;
}
.file-input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.custom-icons-section {
margin-bottom: 2rem;
}
.custom-icons-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 10px;
margin-top: 1rem;
}
.custom-icon-item {
position: relative;
padding: 12px;
border-radius: var(--input-radius);
background-color: var(--card-bg);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
aspect-ratio: 1;
}
.custom-icon-item:hover {
background-color: rgba(239, 96, 19, 0.1);
border-color: var(--accent);
transform: translateY(-2px);
}
.custom-icon-item.selected {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.custom-icon-item svg {
width: 24px;
height: 24px;
color: var(--text-secondary);
}
.custom-icon-item.selected svg {
color: var(--accent);
}
.delete-custom-icon {
position: absolute;
top: -5px;
right: -5px;
width: 16px;
height: 16px;
background-color: var(--danger);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.delete-custom-icon:hover {
background-color: #c82333;
transform: scale(1.1);
}
.icon-search {
margin-bottom: 1rem;
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
background-color: var(--card-bg);
color: var(--text-primary);
font-size: var(--text-xs);
}
.icon-search:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
outline: none;
}
.icon-categories {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.category-btn {
padding: 0.5rem 0.75rem;
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--input-radius);
color: var(--text-secondary);
font-size: 11px;
cursor: pointer;
transition: var(--transition);
}
.category-btn:hover,
.category-btn.active {
background-color: rgba(239, 96, 19, 0.1);
border-color: var(--accent);
color: var(--accent);
}
.modal-icon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 10px;
max-height: 400px;
overflow-y: auto;
padding: 10px 0;
}
.icon-modal-option {
padding: 12px;
border-radius: var(--input-radius);
background-color: var(--card-bg);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: var(--transition);
aspect-ratio: 1;
position: relative;
}
.icon-modal-option:hover {
background-color: rgba(239, 96, 19, 0.1);
border-color: var(--accent);
transform: translateY(-2px);
}
.icon-modal-option.selected {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.icon-modal-option svg {
width: 24px;
height: 24px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.icon-modal-option.selected svg {
color: var(--accent);
}
.icon-name {
font-size: 9px;
color: var(--text-secondary);
text-align: center;
line-height: 1.2;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.icon-modal-option.selected .icon-name {
color: var(--accent);
}
.icon-modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
/* Advanced Dock Styles */
.dock-container {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: end;
gap: 8px;
padding: 12px;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
}
.dock-item {
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transform-origin: bottom center;
position: relative;
overflow: visible;
box-sizing: border-box;
}
.dock-item svg {
color: white;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
pointer-events: none;
width: 50%;
height: 50%;
}
.dock-label {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(-8px);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 10;
}
.dock-item:hover .dock-label {
opacity: 1;
transform: translateX(-50%) translateY(-12px);
}
.spring-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.spring-control {
display: flex;
flex-direction: column;
}
.spring-control label {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.spring-control input {
margin-bottom: 0;
}
.spring-value {
font-size: 10px;
color: var(--text-secondary);
text-align: center;
margin-top: 0.25rem;
}
@media (max-width: 1200px) {
.content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.preview-section {
position: static;
}
.controls-section {
max-width: 100%;
}
}
@media (max-width: 768px) {
.action-bar {
flex-direction: column;
height: auto;
min-height: var(--action-bar-height);
padding: 0.75rem;
}
.breadcrumb {
order: 1;
width: 100%;
}
.action-buttons {
order: 2;
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
body {
padding-bottom: calc(var(--action-bar-height) + 20px);
}
.notification {
bottom: calc(var(--action-bar-height) + 2rem);
max-width: 280px;
transform: translate(-50%, 250px);
}
.notification.show {
transform: translate(-50%, 0);
opacity: 1;
}
.color-row {
flex-direction: column;
align-items: stretch;
gap: 1rem;
padding: 1rem;
}
.color-picker-container {
align-self: center;
margin-bottom: 0.5rem;
}
.color-input-group {
align-items: stretch;
}
.hex-input,
.hsl-input {
width: 100%;
}
.preview-container {
height: 300px;
}
.data-attribute-display {
font-size: 10px;
padding: 0.4rem 0.6rem;
}
.action-btn {
font-size: 11px;
padding: 0.5rem 0.8rem;
}
.page-title {
font-size: 2rem;
}
.modal-icon-grid {
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
}
.custom-icons-grid {
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
}
.spring-controls {
grid-template-columns: 1fr;
gap: 0.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
button:focus-visible,
input:focus-visible,
.action-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--background);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
</style>
</head>
<body>
<div class="action-bar">
<nav class="breadcrumb">
<a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
<span class="breadcrumb-separator">›</span>
<a href="https://bricksfusion.com/menu-animations/" class="breadcrumb-item">Menu animations</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Interactive Dock</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-interactive-dock
</div>
<button class="action-btn primary" id="download-config" title="Copy JavaScript code (Ctrl+D)" data-protection-animation="true">
<span>📋</span>
Copy JS
</button>
<button class="action-btn" id="copy-full-section" title="Copy complete section JSON for Bricks Builder (Ctrl+S)" data-protection-animation="true">
<span>📦</span>
Copy Full Section
</button>
</div>
</div>
<div class="container">
<div class="page-header">
<h1 class="page-title">Interactive Dock</h1>
<p class="page-subtitle">Apple-style magnification dock for Bricks Builder</p>
</div>
<div class="instructions-toggle">
<div class="instructions-card" id="instructions-card">
<div class="instructions-header" id="instructions-toggle">
<div class="instructions-title">
How to Use & Code Information
</div>
<span class="toggle-icon">▼</span>
</div>
<div class="instructions-content" id="instructions-content">
<div class="instructions-grid">
<div class="how-to-use">
<ol>
<li>Customize your advanced dock using the controls below</li>
<li>Configure magnification effect, distance, and spring physics</li>
<li>Choose from 50+ built-in icons or upload your own SVG icons</li>
<li>Toggle labels on/off for each dock item</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-interactive-dock</code> as attribute name (leave value empty)</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="content">
<section class="preview-section">
<div class="preview-container" id="dock-preview" data-interactive-dock="true">
<div class="dock-container" id="preview-dock-container">
<div class="dock-item">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<div class="dock-label">Home</div>
</div>
<div class="dock-item">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle></svg>
<div class="dock-label">Settings</div>
</div>
<div class="dock-item">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
<div class="dock-label">Profile</div>
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Dock Items
<div class="card-actions">
<button class="card-action-btn" id="reset-items" title="Reset Items">↺</button>
</div>
</div>
<div class="card-content">
<div id="dock-container"></div>
<div class="dock-action-buttons">
<button class="action-button add-btn" id="add-item-btn">Add Item</button>
<button class="action-button remove-btn" id="remove-last-btn">Remove Last</button>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Magnification Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-magnification" title="Reset Magnification">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Distance (Effect Radius)
<span class="help-tooltip" title="How far from cursor the magnification effect reaches">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="magnification-distance-value">150</span>px</span>
<button class="reset-btn" onclick="resetParameter('magnification-distance', 150)">↺</button>
</div>
</div>
<input type="range" id="magnification-distance" min="80" max="300" step="10" value="150">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Magnification Size
<span class="help-tooltip" title="Maximum size when magnified">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="magnification-size-value">80</span>px</span>
<button class="reset-btn" onclick="resetParameter('magnification-size', 80)">↺</button>
</div>
</div>
<input type="range" id="magnification-size" min="60" max="120" step="5" value="80">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Panel Height (Base Size)
<span class="help-tooltip" title="Base size of dock items when not magnified">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="panel-height-value">48</span>px</span>
<button class="reset-btn" onclick="resetParameter('panel-height', 48)">↺</button>
</div>
</div>
<input type="range" id="panel-height" min="32" max="64" step="4" value="48">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Spring Physics
<div class="card-actions">
<button class="card-action-btn" id="reset-spring" title="Reset Spring Physics">↺</button>
</div>
</div>
<div class="card-content">
<div class="spring-controls">
<div class="spring-control">
<label for="spring-mass">Mass</label>
<input type="range" id="spring-mass" min="0.05" max="0.5" step="0.05" value="0.1">
<div class="spring-value" id="spring-mass-value">0.1</div>
</div>
<div class="spring-control">
<label for="spring-stiffness">Stiffness</label>
<input type="range" id="spring-stiffness" min="50" max="300" step="10" value="150">
<div class="spring-value" id="spring-stiffness-value">150</div>
</div>
<div class="spring-control">
<label for="spring-damping">Damping</label>
<input type="range" id="spring-damping" min="5" max="25" step="1" value="12">
<div class="spring-value" id="spring-damping-value">12</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Style Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-style" title="Reset Style">↺</button>
</div>
</div>
<div class="card-content">
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="background-color" value="#ffffff1a">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="background-color-hex" value="#ffffff1a" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="background-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="border-color" value="#ffffff33">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="border-color-hex" value="#ffffff33" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="border-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="icon-color" value="#ffffff">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="icon-color-hex" value="#ffffff" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="icon-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Container Border Radius
<span class="help-tooltip" title="Corner roundness of the dock container">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="container-radius-value">16</span>px</span>
<button class="reset-btn" onclick="resetParameter('container-radius', 16)">↺</button>
</div>
</div>
<input type="range" id="container-radius" min="0" max="32" step="1" value="16">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Item Border Radius
<span class="help-tooltip" title="Corner roundness of individual items">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="item-radius-value">12</span>px</span>
<button class="reset-btn" onclick="resetParameter('item-radius', 12)">↺</button>
</div>
</div>
<input type="range" id="item-radius" min="0" max="24" step="1" value="12">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Items Gap
<span class="help-tooltip" title="Space between dock items">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="items-gap-value">8</span>px</span>
<button class="reset-btn" onclick="resetParameter('items-gap', 8)">↺</button>
</div>
</div>
<input type="range" id="items-gap" min="4" max="24" step="2" value="8">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Labels & Interaction
<div class="card-actions">
<button class="card-action-btn" id="reset-labels" title="Reset Labels">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Show Labels</span>
</div>
<div class="toggle-switch-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="show-labels" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Display tooltips on hover</span>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Container Padding
<span class="help-tooltip" title="Internal padding of the dock container">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="container-padding-value">12</span>px</span>
<button class="reset-btn" onclick="resetParameter('container-padding', 12)">↺</button>
</div>
</div>
<input type="range" id="container-padding" min="8" max="24" step="2" value="12">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Position Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-position" title="Reset Position">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Respect Container Position</span>
</div>
<div class="toggle-switch-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="respect-container-position">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Position dock relative to its container instead of viewport</span>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Fixed Position</span>
</div>
<div class="toggle-switch-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="fixed-position" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Use fixed positioning at bottom of viewport (when not respecting container)</span>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Distance from Bottom
<span class="help-tooltip" title="How far from bottom edge (when not respecting container)">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="bottom-distance-value">2</span>rem</span>
<button class="reset-btn" onclick="resetParameter('bottom-distance', 2)">↺</button>
</div>
</div>
<input type="range" id="bottom-distance" min="1" max="8" step="0.5" value="2">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Z-Index
<span class="help-tooltip" title="Layer stacking order">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="z-index-value">1000</span></span>
<button class="reset-btn" onclick="resetParameter('z-index', 1000)">↺</button>
</div>
</div>
<input type="range" id="z-index" min="1" max="9999" step="1" value="1000">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<div class="icon-modal" id="icon-modal">
<div class="icon-modal-content">
<div class="icon-modal-header">
<h3 class="icon-modal-title">Select an Icon</h3>
<button class="close-modal" id="close-icon-modal">×</button>
</div>
<div class="icon-modal-body">
<!-- Upload Section -->
<div class="icon-upload-section">
<h4>Upload Custom SVG</h4>
<div class="upload-area" id="upload-area">
<input type="file" class="file-input" id="svg-file-input" accept=".svg,image/svg+xml">
<div class="upload-content">
<div class="upload-icon">📄</div>
<div class="upload-text">Click to upload or drag & drop</div>
<div class="upload-subtext">SVG files only</div>
</div>
</div>
</div>
<!-- Custom Icons Section -->
<div class="custom-icons-section" id="custom-icons-section" style="display: none;">
<h4 style="margin-bottom: 1rem; color: var(--text-primary); font-size: 14px;">Your Custom Icons</h4>
<div class="custom-icons-grid" id="custom-icons-grid"></div>
</div>
<!-- Search and Filter -->
<input type="text" class="icon-search" id="icon-search" placeholder="Search icons...">
<div class="icon-categories">
<button class="category-btn active" data-category="all">All</button>
<button class="category-btn" data-category="navigation">Navigation</button>
<button class="category-btn" data-category="ui">UI/UX</button>
<button class="category-btn" data-category="content">Content</button>
<button class="category-btn" data-category="communication">Communication</button>
<button class="category-btn" data-category="ecommerce">E-commerce</button>
<button class="category-btn" data-category="social">Social</button>
<button class="category-btn" data-category="tools">Tools</button>
</div>
<!-- Icon Grid -->
<div class="modal-icon-grid" id="icon-grid"></div>
</div>
<div class="icon-modal-footer">
<button class="btn" id="select-icon-btn">Select Icon</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configuration object with advanced settings
let dockConfig = {
items: [
{ title: "Home", icon: "home", url: "" },
{ title: "Settings", icon: "settings", url: "" },
{ title: "Profile", icon: "user", url: "" }
],
// Visual settings
backgroundColor: "#ffffff1a",
borderColor: "#ffffff33",
iconColor: "#ffffff",
containerRadius: 16,
itemRadius: 12,
itemsGap: 8,
containerPadding: 12,
// Advanced magnification settings
magnificationDistance: 150,
magnificationSize: 80,
panelHeight: 48,
// Spring physics
springMass: 0.2,
springStiffness: 150,
springDamping: 12,
// Labels and interaction
showLabels: true,
// Position - NEW OPTION ADDED
respectContainerPosition: false,
fixedPosition: true,
bottomDistance: 2,
zIndex: 1000
};
const defaultConfig = { ...dockConfig };
let currentDockAnimation = null;
let customIcons = {};
let currentItemIndex = null;
let selectedIconName = null;
let currentCategory = 'all';
let searchTerm = '';
// Mouse tracking for magnification effect
let mouseX = 0;
let isHoveringDock = false;
// Animation frame reference
let animationFrame = null;
// Expanded icon library
const ICONS = {
// Navigation (8 icons)
home: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>', category: 'navigation' },
menu: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="18" y2="18"/></svg>', category: 'navigation' },
search: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg>', category: 'navigation' },
arrowLeft: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>', category: 'navigation' },
arrowRight: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>', category: 'navigation' },
arrowUp: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>', category: 'navigation' },
arrowDown: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>', category: 'navigation' },
compass: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg>', category: 'navigation' },
// UI/UX (12 icons)
settings: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle></svg>', category: 'ui' },
user: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>', category: 'ui' },
users: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="m22 21-3-3 3-3"/><path d="M16 3h6v6"/></svg>', category: 'ui' },
eye: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>', category: 'ui' },
eyeOff: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/></svg>', category: 'ui' },
lock: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>', category: 'ui' },
unlock: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></svg>', category: 'ui' },
shield: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>', category: 'ui' },
check: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>', category: 'ui' },
x: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>', category: 'ui' },
plus: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>', category: 'ui' },
minus: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/></svg>', category: 'ui' },
// Content (10 icons)
file: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>', category: 'content' },
folder: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>', category: 'content' },
image: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>', category: 'content' },
video: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg>', category: 'content' },
music: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>', category: 'content' },
download: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>', category: 'content' },
upload: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>', category: 'content' },
archive: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></svg>', category: 'content' },
book: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>', category: 'content' },
bookmark: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/></svg>', category: 'content' },
// Communication (8 icons)
mail: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>', category: 'communication' },
phone: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>', category: 'communication' },
message: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>', category: 'communication' },
bell: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>', category: 'communication' },
chat: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></svg>', category: 'communication' },
send: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>', category: 'communication' },
inbox: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 21 6 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>', category: 'communication' },
wifi: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h.01"/><path d="M2 8.82a15 15 0 0 1 20 0"/><path d="M5 12.859a10 10 0 0 1 14 0"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/></svg>', category: 'communication' },
// E-commerce (8 icons)
shoppingCart: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>', category: 'ecommerce' },
creditCard: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></svg>', category: 'ecommerce' },
dollarSign: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>', category: 'ecommerce' },
gift: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5A4.9 4.9 0 0 1 12 8a4.9 4.9 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5"/></svg>', category: 'ecommerce' },
tag: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42l-8.704-8.704Z"/><circle cx="7.5" cy="7.5" r=".5"/></svg>', category: 'ecommerce' },
percent: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" x2="5" y1="5" y2="19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/></svg>', category: 'ecommerce' },
truck: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/><path d="M15 18H9"/><path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/><circle cx="17" cy="18" r="2"/><circle cx="7" cy="18" r="2"/></svg>', category: 'ecommerce' },
package: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="M3.3 7 12 12l8.7-5"/><path d="M12 22V12"/></svg>', category: 'ecommerce' },
// Social (6 icons)
heart: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>', category: 'social' },
star: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>', category: 'social' },
share: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" x2="12" y1="2" y2="15"/></svg>', category: 'social' },
thumbsUp: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h3.73a2 2 0 0 1 1.92 2.56z"/></svg>', category: 'social' },
follow: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" x2="19" y1="8" y2="14"/><line x1="22" x2="16" y1="11" y2="11"/></svg>', category: 'social' },
link: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>', category: 'social' },
// Tools (8 icons)
edit: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>', category: 'tools' },
trash: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c-1 0 2 1 2 2v2"/></svg>', category: 'tools' },
save: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>', category: 'tools' },
copy: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>', category: 'tools' },
printer: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect width="12" height="8" x="6" y="14"/></svg>', category: 'tools' },
filter: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>', category: 'tools' },
tool: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>', category: 'tools' },
wrench: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>', category: 'tools' },
// Additional common icons (4 icons)
calendar: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>', category: 'ui' },
clock: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>', category: 'ui' },
globe: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" x2="22" y1="12" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>', category: 'navigation' },
zap: { svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>', category: 'ui' }
};
/**
* Advanced Animation System (Magnification Effect)
*/
// Easing functions for smoother animations
const easing = {
easeOut: (t) => 1 - Math.pow(1 - t, 3),
easeInOut: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
spring: (t, mass = 0.1, stiffness = 150, damping = 12) => {
const w0 = Math.sqrt(stiffness / mass);
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
if (zeta < 1) {
const wd = w0 * Math.sqrt(1 - zeta * zeta);
return 1 - Math.exp(-zeta * w0 * t) * Math.cos(wd * t);
} else {
return 1 - Math.exp(-w0 * t);
}
}
};
// Spring physics simulation for smoother interactions
class SpringValue {
constructor(initialValue = 0, config = {}) {
this.value = initialValue;
this.target = initialValue;
this.velocity = 0;
this.mass = Math.max(config.mass || CONFIG.springMass, 0.2); // Mínimo 0.2
this.stiffness = config.stiffness || CONFIG.springStiffness;
this.damping = config.damping || CONFIG.springDamping;
}
set(targetValue) {
this.target = targetValue;
}
update(deltaTime = 0.016) {
const force = -this.stiffness * (this.value - this.target);
const dampingForce = -this.damping * this.velocity;
const acceleration = (force + dampingForce) / this.mass;
this.velocity += acceleration * deltaTime;
this.value += this.velocity * deltaTime;
// Stop tiny oscillations
if (Math.abs(this.velocity) < 0.01 && Math.abs(this.value - this.target) < 0.01) {
this.value = this.target;
this.velocity = 0;
}
return this.value;
}
}
// Magnification calculation based on distance
function calculateMagnification(distance, maxDistance, minSize, maxSize) {
if (distance >= maxDistance) return minSize;
const normalizedDistance = Math.abs(distance) / maxDistance;
const easedDistance = easing.easeOut(1 - normalizedDistance);
return minSize + (maxSize - minSize) * easedDistance;
}
// Get mouse position relative to dock container
function getMousePosition(event, container) {
const rect = container.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
// Advanced dock animation system
function initializeDockAnimation() {
const dockContainer = document.getElementById('preview-dock-container');
if (!dockContainer) return;
const dockItems = dockContainer.querySelectorAll('.dock-item');
const springValues = [];
// Initialize spring values for each item
dockItems.forEach((item, index) => {
springValues[index] = new SpringValue(dockConfig.panelHeight, {
mass: dockConfig.springMass,
stiffness: dockConfig.springStiffness,
damping: dockConfig.springDamping
});
});
let isAnimating = false;
// Update dock heights with magnification effect
function updateDockMagnification() {
dockItems.forEach((item, index) => {
const rect = item.getBoundingClientRect();
const itemCenter = rect.left + rect.width / 2;
const distance = mouseX - itemCenter;
let targetSize = dockConfig.panelHeight;
if (isHoveringDock && Math.abs(distance) <= dockConfig.magnificationDistance) {
targetSize = calculateMagnification(
distance,
dockConfig.magnificationDistance,
dockConfig.panelHeight,
dockConfig.magnificationSize
);
}
springValues[index].set(targetSize);
const currentSize = springValues[index].update();
// Apply size changes
item.style.width = `${currentSize}px`;
item.style.height = `${currentSize}px`;
});
// Continue animation if any spring is still moving
const stillAnimating = springValues.some(spring =>
Math.abs(spring.value - spring.target) > 0.1 || Math.abs(spring.velocity) > 0.1
);
if (stillAnimating || isHoveringDock) {
animationFrame = requestAnimationFrame(updateDockMagnification);
} else {
isAnimating = false;
}
}
// Start animation loop
function startAnimation() {
if (!isAnimating) {
isAnimating = true;
updateDockMagnification();
}
}
// Mouse move handler
function handleMouseMove(e) {
const containerRect = dockContainer.getBoundingClientRect();
mouseX = e.clientX;
if (!isAnimating) {
startAnimation();
}
}
// Mouse enter handler
function handleMouseEnter() {
isHoveringDock = true;
startAnimation();
}
// Mouse leave handler
function handleMouseLeave() {
isHoveringDock = false;
startAnimation();
}
// Add event listeners
dockContainer.addEventListener('mousemove', handleMouseMove);
dockContainer.addEventListener('mouseenter', handleMouseEnter);
dockContainer.addEventListener('mouseleave', handleMouseLeave);
// Update spring configuration when config changes
window.updateSpringConfig = function() {
springValues.forEach(spring => {
spring.mass = dockConfig.springMass;
spring.stiffness = dockConfig.springStiffness;
spring.damping = dockConfig.springDamping;
});
};
// Store cleanup function
return () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
dockContainer.removeEventListener('mousemove', handleMouseMove);
dockContainer.removeEventListener('mouseenter', handleMouseEnter);
dockContainer.removeEventListener('mouseleave', handleMouseLeave);
};
}
/**
* Custom Icon Management Functions (unchanged)
*/
function loadCustomIcons() {
try {
const saved = localStorage.getItem('bricksfusion-dock-custom-icons');
if (saved) {
customIcons = JSON.parse(saved);
}
} catch (e) {
console.warn('Failed to load custom icons:', e);
}
}
function saveCustomIcons() {
try {
localStorage.setItem('bricksfusion-dock-custom-icons', JSON.stringify(customIcons));
} catch (e) {
console.warn('Failed to save custom icons:', e);
}
}
function isValidSVG(svgString) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(svgString, 'image/svg+xml');
const svg = doc.querySelector('svg');
if (!svg) return false;
if (doc.querySelector('parsererror')) return false;
const dangerousElements = ['script', 'object', 'embed', 'iframe'];
for (const tag of dangerousElements) {
if (doc.querySelector(tag)) return false;
}
return true;
} catch (e) {
return false;
}
}
function sanitizeSVG(svgString) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(svgString, 'image/svg+xml');
const svg = doc.querySelector('svg');
if (!svg) return null;
svg.setAttribute('width', '24');
svg.setAttribute('height', '24');
svg.setAttribute('viewBox', svg.getAttribute('viewBox') || '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
const dangerousElements = ['script', 'object', 'embed', 'iframe'];
dangerousElements.forEach(tag => {
const elements = svg.querySelectorAll(tag);
elements.forEach(el => el.remove());
});
const dangerousAttrs = ['onload', 'onerror', 'onclick', 'onmouseover'];
svg.querySelectorAll('*').forEach(el => {
dangerousAttrs.forEach(attr => {
if (el.hasAttribute(attr)) {
el.removeAttribute(attr);
}
});
});
return svg.outerHTML;
} catch (e) {
return null;
}
}
function generateCustomIconId() {
return 'custom_' + Math.random().toString(36).substring(2, 10) + Date.now().toString(36);
}
function handleFileUpload(file) {
if (!file || file.type !== 'image/svg+xml') {
showNotification('Please select a valid SVG file', 'error');
return;
}
if (file.size > 50000) {
showNotification('SVG file is too large. Please use a file under 50KB', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
if (!isValidSVG(svgContent)) {
showNotification('Invalid SVG file format', 'error');
return;
}
const sanitized = sanitizeSVG(svgContent);
if (!sanitized) {
showNotification('Failed to process SVG file', 'error');
return;
}
const iconId = generateCustomIconId();
const iconName = file.name.replace(/\.(svg)$/i, '').substring(0, 20);
customIcons[iconId] = {
name: iconName,
svg: sanitized,
category: 'custom'
};
saveCustomIcons();
renderCustomIcons();
showNotification(`Custom icon "${iconName}" uploaded successfully!`);
};
reader.onerror = function() {
showNotification('Failed to read SVG file', 'error');
};
reader.readAsText(file);
}
function renderCustomIcons() {
const customIconsSection = document.getElementById('custom-icons-section');
const customIconsGrid = document.getElementById('custom-icons-grid');
const hasCustomIcons = Object.keys(customIcons).length > 0;
customIconsSection.style.display = hasCustomIcons ? 'block' : 'none';
if (!hasCustomIcons) return;
customIconsGrid.innerHTML = '';
Object.keys(customIcons).forEach(iconId => {
const icon = customIcons[iconId];
const iconElement = document.createElement('div');
iconElement.className = 'custom-icon-item';
iconElement.dataset.icon = iconId;
iconElement.innerHTML = `
${icon.svg}
<button class="delete-custom-icon" onclick="deleteCustomIcon('${iconId}')">×</button>
`;
iconElement.addEventListener('click', function(e) {
if (e.target.classList.contains('delete-custom-icon')) return;
document.querySelectorAll('.icon-modal-option, .custom-icon-item').forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
selectedIconName = iconId;
});
customIconsGrid.appendChild(iconElement);
});
}
window.deleteCustomIcon = function(iconId) {
if (confirm('Are you sure you want to delete this custom icon?')) {
delete customIcons[iconId];
saveCustomIcons();
renderCustomIcons();
showNotification('Custom icon deleted');
}
};
function filterIcons() {
const iconGrid = document.getElementById('icon-grid');
iconGrid.innerHTML = '';
const filteredIcons = Object.keys(ICONS).filter(iconName => {
const icon = ICONS[iconName];
const matchesCategory = currentCategory === 'all' || icon.category === currentCategory;
const matchesSearch = !searchTerm || iconName.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
});
filteredIcons.forEach(iconName => {
const icon = ICONS[iconName];
const iconOption = document.createElement('div');
iconOption.className = `icon-modal-option ${iconName === selectedIconName ? 'selected' : ''}`;
iconOption.dataset.icon = iconName;
iconOption.innerHTML = `
${icon.svg}
<div class="icon-name">${iconName}</div>
`;
iconOption.addEventListener('click', function() {
document.querySelectorAll('.icon-modal-option, .custom-icon-item').forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
selectedIconName = this.dataset.icon;
});
iconGrid.appendChild(iconOption);
});
}
function setupIconModal() {
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('svg-file-input');
const iconSearch = document.getElementById('icon-search');
const categoryBtns = document.querySelectorAll('.category-btn');
// File upload handlers
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files);
const svgFile = files.find(file => file.type === 'image/svg+xml');
if (svgFile) {
handleFileUpload(svgFile);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFileUpload(e.target.files[0]);
}
});
// Search handler
iconSearch.addEventListener('input', (e) => {
searchTerm = e.target.value;
filterIcons();
});
// Category handlers
categoryBtns.forEach(btn => {
btn.addEventListener('click', () => {
categoryBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentCategory = btn.dataset.category;
filterIcons();
});
});
loadCustomIcons();
renderCustomIcons();
filterIcons();
}
function openIconModal(itemIndex) {
currentItemIndex = itemIndex;
selectedIconName = dockConfig.items[itemIndex].icon;
// Reset search and category
searchTerm = '';
currentCategory = 'all';
document.getElementById('icon-search').value = '';
document.querySelectorAll('.category-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.category === 'all');
});
renderCustomIcons();
filterIcons();
// Select current icon if it exists
setTimeout(() => {
const currentIconElement = document.querySelector(`[data-icon="${selectedIconName}"]`);
if (currentIconElement) {
currentIconElement.classList.add('selected');
}
}, 100);
document.getElementById('icon-modal').classList.add('active');
}
function closeIconModal() {
document.getElementById('icon-modal').classList.remove('active');
}
function selectIcon() {
if (currentItemIndex !== null && selectedIconName) {
dockConfig.items[currentItemIndex].icon = selectedIconName;
const iconBtn = document.getElementById(`icon-btn-${currentItemIndex}`);
const currentIconSpan = iconBtn.querySelector('.current-icon');
// Get the appropriate icon SVG and name
let iconSvg = '';
let iconDisplayName = selectedIconName;
if (customIcons[selectedIconName]) {
iconSvg = customIcons[selectedIconName].svg;
iconDisplayName = customIcons[selectedIconName].name;
} else if (ICONS[selectedIconName]) {
iconSvg = ICONS[selectedIconName].svg;
} else {
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9 9h6v6"/></svg>';
iconDisplayName = 'unknown';
}
currentIconSpan.innerHTML = iconSvg;
iconBtn.querySelector('span:nth-child(2)').textContent = iconDisplayName;
updatePreview();
saveConfiguration();
closeIconModal();
}
}
/**
* Utility Functions
*/
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
function updateColorInputs(colorKey, value) {
const hexInput = document.getElementById(`${colorKey}-hex`);
const hslInput = document.getElementById(`${colorKey}-hsl`);
const colorInput = document.getElementById(colorKey);
hexInput.value = value;
colorInput.value = value;
const hsl = hexToHsl(value);
hslInput.value = `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', value);
}
}
function setupColorInputs(colorKey) {
const colorInput = document.getElementById(colorKey);
const hexInput = document.getElementById(`${colorKey}-hex`);
const hslInput = document.getElementById(`${colorKey}-hsl`);
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
const configKey = colorKey.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
dockConfig[configKey] = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updatePreview();
saveConfiguration();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
if (hex.length === 7 && hex.startsWith('#')) {
colorInput.value = hex;
hslInput.value = `hsl(${hexToHsl(hex).h}, ${hexToHsl(hex).s}%, ${hexToHsl(hex).l}%)`;
const configKey = colorKey.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
dockConfig[configKey] = hex;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updatePreview();
saveConfiguration();
}
});
hslInput.addEventListener('change', (e) => {
const hsl = e.target.value;
// Simple HSL to hex conversion would be added here
// For now, we'll just sync with hex input changes
});
}
/**
* Configuration Management
*/
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-advanced-dock-config', JSON.stringify(dockConfig));
} catch (e) {
console.warn('Failed to save configuration:', e);
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-advanced-dock-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(dockConfig, savedConfig);
// Update UI elements
updateColorInputs('background-color', savedConfig.backgroundColor);
updateColorInputs('border-color', savedConfig.borderColor);
updateColorInputs('icon-color', savedConfig.iconColor);
// Update range inputs
const ranges = ['container-radius', 'item-radius', 'items-gap', 'container-padding',
'magnification-distance', 'magnification-size', 'panel-height',
'spring-mass', 'spring-stiffness', 'spring-damping',
'bottom-distance', 'z-index'];
ranges.forEach(id => {
const element = document.getElementById(id);
const valueElement = document.getElementById(`${id}-value`);
const configKey = id.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
if (element && savedConfig[configKey] !== undefined) {
const value = savedConfig[configKey];
element.value = value;
if (valueElement) {
valueElement.textContent = value;
}
}
});
// Update toggles
document.getElementById('show-labels').checked = savedConfig.showLabels !== false;
document.getElementById('respect-container-position').checked = savedConfig.respectContainerPosition === true;
document.getElementById('fixed-position').checked = savedConfig.fixedPosition !== false;
renderDockInputs();
}
} catch (e) {
console.warn('Failed to load configuration:', e);
}
}
/**
* UI Rendering Functions
*/
function renderDockInputs() {
const dockContainer = document.getElementById('dock-container');
dockContainer.innerHTML = '';
dockConfig.items.forEach((item, index) => {
const dockInputs = document.createElement('div');
dockInputs.className = 'dock-inputs';
// Get icon SVG - check custom icons first, then built-in icons
let iconSvg = '';
let iconDisplayName = item.icon;
if (customIcons[item.icon]) {
iconSvg = customIcons[item.icon].svg;
iconDisplayName = customIcons[item.icon].name;
} else if (ICONS[item.icon]) {
iconSvg = ICONS[item.icon].svg;
} else {
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9 9h6v6"/></svg>';
iconDisplayName = 'unknown';
}
dockInputs.innerHTML = `
<div class="dock-count">Item ${index + 1}</div>
<div class="input-with-label">
<label for="item-title-${index}">Title</label>
<input
type="text"
id="item-title-${index}"
value="${item.title}"
placeholder="Enter item title..."
data-index="${index}"
class="item-title-input"
>
</div>
<div class="input-with-label">
<label for="item-url-${index}">URL (optional)</label>
<input
type="url"
id="item-url-${index}"
value="${item.url || ''}"
placeholder="https://example.com"
data-index="${index}"
class="item-url-input"
>
</div>
<div class="input-with-label">
<label for="item-icon-${index}">Icon</label>
<button
class="icon-select-btn"
data-index="${index}"
id="icon-btn-${index}"
>
<span class="current-icon">${iconSvg}</span>
<span>${iconDisplayName}</span>
<span class="icon-label">Change</span>
</button>
</div>
`;
dockContainer.appendChild(dockInputs);
});
addDockInputListeners();
}
function addDockInputListeners() {
document.querySelectorAll('.item-title-input').forEach(input => {
input.addEventListener('input', function() {
const index = parseInt(this.dataset.index);
dockConfig.items[index].title = this.value;
updatePreview();
saveConfiguration();
});
});
document.querySelectorAll('.item-url-input').forEach(input => {
input.addEventListener('input', function() {
const index = parseInt(this.dataset.index);
dockConfig.items[index].url = this.value;
saveConfiguration();
});
});
document.querySelectorAll('.icon-select-btn').forEach(button => {
button.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
openIconModal(index);
});
});
}
/**
* Preview Update Function with Advanced Effects
*/
function updatePreview() {
const preview = document.getElementById('dock-preview');
const dockContainer = preview.querySelector('.dock-container');
if (!dockContainer) return;
// Update dock container styles
dockContainer.style.background = dockConfig.backgroundColor;
dockContainer.style.borderColor = dockConfig.borderColor;
dockContainer.style.borderRadius = `${dockConfig.containerRadius}px`;
dockContainer.style.gap = `${dockConfig.itemsGap}px`;
dockContainer.style.padding = `${dockConfig.containerPadding}px`;
// Position within the preview container - ALWAYS FIXED FOR PREVIEW
dockContainer.style.position = 'absolute';
dockContainer.style.bottom = `${dockConfig.bottomDistance}rem`;
dockContainer.style.left = '50%';
dockContainer.style.transform = 'translateX(-50%)';
dockContainer.style.zIndex = '10';
// Clear and rebuild dock items
dockContainer.innerHTML = '';
dockConfig.items.forEach((item, index) => {
const dockItem = document.createElement('div');
dockItem.className = 'dock-item';
dockItem.style.width = `${dockConfig.panelHeight}px`;
dockItem.style.height = `${dockConfig.panelHeight}px`;
dockItem.style.borderRadius = `${dockConfig.itemRadius}px`;
dockItem.style.background = 'rgba(255, 255, 255, 0.1)';
dockItem.style.border = '1px solid rgba(255, 255, 255, 0.2)';
dockItem.style.display = 'flex';
dockItem.style.alignItems = 'center';
dockItem.style.justifyContent = 'center';
dockItem.style.cursor = 'pointer';
dockItem.style.transformOrigin = 'bottom center';
dockItem.style.position = 'relative';
dockItem.style.overflow = 'visible';
dockItem.style.boxSizing = 'border-box';
// Get icon SVG - check custom icons first, then built-in icons
let iconSvg = '';
if (customIcons[item.icon]) {
iconSvg = customIcons[item.icon].svg;
} else if (ICONS[item.icon]) {
iconSvg = ICONS[item.icon].svg;
} else {
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9 9h6v6"/></svg>';
}
dockItem.innerHTML = iconSvg;
// Add label if enabled
if (dockConfig.showLabels) {
const label = document.createElement('div');
label.className = 'dock-label';
label.textContent = item.title;
dockItem.appendChild(label);
}
const svg = dockItem.querySelector('svg');
if (svg) {
svg.style.width = '50%';
svg.style.height = '50%';
svg.style.color = dockConfig.iconColor;
svg.style.filter = 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2))';
svg.style.pointerEvents = 'none';
}
if (item.url && item.url.trim()) {
dockItem.addEventListener('click', function() {
window.open(item.url, '_blank');
});
}
dockContainer.appendChild(dockItem);
});
// Reinitialize advanced animation system
if (currentDockAnimation) {
currentDockAnimation();
}
currentDockAnimation = initializeDockAnimation();
// Update spring configuration
if (window.updateSpringConfig) {
window.updateSpringConfig();
}
}
/**
* Code Generation Functions
*/
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateJavaScriptCode() {
// Combine built-in icons with custom icons for the generated code
const allIcons = { ...ICONS };
// Add custom icons to the ICONS object for the generated code
Object.keys(customIcons).forEach(iconId => {
allIcons[iconId] = { svg: customIcons[iconId].svg };
});
// Convert icons object to the format expected by the JavaScript code
const iconsForCode = {};
Object.keys(allIcons).forEach(key => {
iconsForCode[key] = allIcons[key].svg;
});
return `(function() {
console.log('AdvancedInteractiveDock: Script starting...');
window.AdvancedInteractiveDock = window.AdvancedInteractiveDock || {};
if (window.AdvancedInteractiveDock.initialized) {
console.log('AdvancedInteractiveDock: Already initialized');
return;
}
const ICONS = ${JSON.stringify(iconsForCode, null, 2)};
const DEFAULT_ITEMS = ${JSON.stringify(dockConfig.items, null, 2)};
const CONFIG = {
backgroundColor: "${dockConfig.backgroundColor}",
borderColor: "${dockConfig.borderColor}",
iconColor: "${dockConfig.iconColor}",
containerRadius: ${dockConfig.containerRadius},
itemRadius: ${dockConfig.itemRadius},
itemsGap: ${dockConfig.itemsGap},
containerPadding: ${dockConfig.containerPadding},
magnificationDistance: ${dockConfig.magnificationDistance},
magnificationSize: ${dockConfig.magnificationSize},
panelHeight: ${dockConfig.panelHeight},
springMass: ${dockConfig.springMass},
springStiffness: ${dockConfig.springStiffness},
springDamping: ${dockConfig.springDamping},
showLabels: ${dockConfig.showLabels},
respectContainerPosition: ${dockConfig.respectContainerPosition},
fixedPosition: ${dockConfig.fixedPosition},
bottomDistance: ${dockConfig.bottomDistance},
zIndex: ${dockConfig.zIndex}
};
console.log('AdvancedInteractiveDock: CONFIG loaded', CONFIG);
const initializedContainers = new WeakSet();
const containerAnimations = new Map();
// Spring physics simulation
class SpringValue {
constructor(initialValue = 0, config = {}) {
this.value = initialValue;
this.target = initialValue;
this.velocity = 0;
this.mass = config.mass || CONFIG.springMass;
this.stiffness = config.stiffness || CONFIG.springStiffness;
this.damping = config.damping || CONFIG.springDamping;
}
set(targetValue) {
this.target = targetValue;
}
update(deltaTime = 0.016) {
const force = -this.stiffness * (this.value - this.target);
const dampingForce = -this.damping * this.velocity;
const acceleration = (force + dampingForce) / this.mass;
this.velocity += acceleration * deltaTime;
this.value += this.velocity * deltaTime;
if (Math.abs(this.velocity) < 0.01 && Math.abs(this.value - this.target) < 0.01) {
this.value = this.target;
this.velocity = 0;
}
return this.value;
}
}
// Easing function
const easing = {
easeOut: (t) => 1 - Math.pow(1 - t, 3)
};
// Magnification calculation
function calculateMagnification(distance, maxDistance, minSize, maxSize) {
if (Math.abs(distance) >= maxDistance) return minSize;
const normalizedDistance = Math.abs(distance) / maxDistance;
const easedDistance = easing.easeOut(1 - normalizedDistance);
return minSize + (maxSize - minSize) * easedDistance;
}
function injectCSS() {
if (document.getElementById('advanced-interactive-dock-styles')) {
console.log('AdvancedInteractiveDock: CSS already injected');
return;
}
console.log('AdvancedInteractiveDock: Injecting CSS...');
const style = document.createElement('style');
style.id = 'advanced-interactive-dock-styles';
// Generate positioning CSS based on respectContainerPosition setting
let positioningCSS = '';
if (CONFIG.respectContainerPosition) {
positioningCSS = \`
position: relative;
margin: 0 auto;
display: block;
\`;
} else {
positioningCSS = \`
position: \${CONFIG.fixedPosition ? 'fixed' : 'absolute'};
bottom: \${CONFIG.bottomDistance}rem;
left: 50%;
transform: translateX(-50%);
\`;
}
style.textContent = \`
[data-interactive-dock] {
display: flex;
align-items: end;
gap: \${CONFIG.itemsGap}px;
padding: \${CONFIG.containerPadding}px;
background: \${CONFIG.backgroundColor};
border-radius: \${CONFIG.containerRadius}px;
backdrop-filter: blur(10px);
border: 1px solid \${CONFIG.borderColor};
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
\${positioningCSS}
z-index: \${CONFIG.zIndex};
box-sizing: border-box;
overflow: visible;
transition: none;
width: auto;
min-width: fit-content;
}
[data-interactive-dock] .dock-item {
min-width: \${CONFIG.panelHeight}px;
min-height: \${CONFIG.panelHeight}px;
width: \${CONFIG.panelHeight}px;
height: \${CONFIG.panelHeight}px;
border-radius: \${CONFIG.itemRadius}px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transform-origin: bottom center;
position: relative;
overflow: visible;
box-sizing: border-box;
transition: none;
flex-shrink: 0;
}
[data-interactive-dock] .dock-item svg {
width: 50%;
height: 50%;
color: \${CONFIG.iconColor};
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
pointer-events: none;
}
\${CONFIG.showLabels ? \`
[data-interactive-dock] .dock-label {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(-8px);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 10;
}
[data-interactive-dock] .dock-item:hover .dock-label {
opacity: 1;
transform: translateX(-50%) translateY(-12px);
}
\` : ''}
\`;
document.head.appendChild(style);
console.log('AdvancedInteractiveDock: CSS injected successfully');
}
// Animation system - FIXED VERSION
function initializeDockAnimation(container) {
const dockItems = container.querySelectorAll('.dock-item');
if (dockItems.length === 0) {
console.log('AdvancedInteractiveDock: No dock items found');
return null;
}
console.log('AdvancedInteractiveDock: Initializing animation for', dockItems.length, 'items');
const springValues = [];
const containerWidthSpring = new SpringValue(0, {
mass: CONFIG.springMass,
stiffness: CONFIG.springStiffness,
damping: CONFIG.springDamping
});
const containerHeightSpring = new SpringValue(CONFIG.panelHeight + (CONFIG.containerPadding * 2), {
mass: CONFIG.springMass,
stiffness: CONFIG.springStiffness,
damping: CONFIG.springDamping
});
let mouseX = 0;
let isHoveringDock = false;
let animationFrame = null;
// Initialize spring values for each item
dockItems.forEach((item, index) => {
springValues[index] = new SpringValue(CONFIG.panelHeight, {
mass: CONFIG.springMass,
stiffness: CONFIG.springStiffness,
damping: CONFIG.springDamping
});
});
let isAnimating = false;
// FIXED: Calculate container dimensions dynamically
function updateDockMagnification() {
let maxSize = CONFIG.panelHeight;
let totalWidth = CONFIG.containerPadding * 2;
dockItems.forEach((item, index) => {
if (!springValues[index]) return;
const rect = item.getBoundingClientRect();
const itemCenter = rect.left + rect.width / 2;
const distance = mouseX - itemCenter;
let targetSize = CONFIG.panelHeight;
if (isHoveringDock && Math.abs(distance) <= CONFIG.magnificationDistance) {
targetSize = calculateMagnification(
distance,
CONFIG.magnificationDistance,
CONFIG.panelHeight,
CONFIG.magnificationSize
);
}
maxSize = Math.max(maxSize, targetSize);
springValues[index].set(targetSize);
const currentSize = springValues[index].update();
// Apply size changes
item.style.width = currentSize + 'px';
item.style.height = currentSize + 'px';
// Add to total width calculation
totalWidth += currentSize;
if (index < dockItems.length - 1) {
totalWidth += CONFIG.itemsGap; // Add gap between items
}
});
// FIXED: Update both container height AND width dynamically
const targetContainerHeight = maxSize + (CONFIG.containerPadding * 2) + 4;
const targetContainerWidth = totalWidth + 4; // +4 for border buffer
containerHeightSpring.set(targetContainerHeight);
containerWidthSpring.set(targetContainerWidth);
const currentContainerHeight = containerHeightSpring.update();
const currentContainerWidth = containerWidthSpring.update();
// Apply both dimensions to container
container.style.height = currentContainerHeight + 'px';
container.style.width = currentContainerWidth + 'px';
container.style.minWidth = 'auto';
// Continue animation if any spring is still moving
const stillAnimating = springValues.some(spring =>
spring && (Math.abs(spring.value - spring.target) > 0.1 || Math.abs(spring.velocity) > 0.1)
) || Math.abs(containerHeightSpring.value - containerHeightSpring.target) > 0.1 ||
Math.abs(containerHeightSpring.velocity) > 0.1 ||
Math.abs(containerWidthSpring.value - containerWidthSpring.target) > 0.1 ||
Math.abs(containerWidthSpring.velocity) > 0.1;
if (stillAnimating || isHoveringDock) {
animationFrame = requestAnimationFrame(updateDockMagnification);
} else {
isAnimating = false;
animationFrame = null;
}
}
// Start animation loop
function startAnimation() {
if (!isAnimating && !animationFrame) {
isAnimating = true;
updateDockMagnification();
}
}
// Mouse move handler
function handleMouseMove(e) {
mouseX = e.clientX;
startAnimation();
}
// Mouse enter handler
function handleMouseEnter(e) {
console.log('AdvancedInteractiveDock: Mouse entered dock');
isHoveringDock = true;
mouseX = e.clientX;
startAnimation();
}
// Mouse leave handler
function handleMouseLeave() {
console.log('AdvancedInteractiveDock: Mouse left dock');
isHoveringDock = false;
startAnimation();
}
// Add event listeners
container.addEventListener('mousemove', handleMouseMove, { passive: true });
container.addEventListener('mouseenter', handleMouseEnter, { passive: true });
container.addEventListener('mouseleave', handleMouseLeave, { passive: true });
console.log('AdvancedInteractiveDock: Event listeners attached');
// Calculate initial width
const initialWidth = (CONFIG.panelHeight * dockItems.length) +
(CONFIG.itemsGap * (dockItems.length - 1)) +
(CONFIG.containerPadding * 2) + 4;
containerWidthSpring.set(initialWidth);
containerWidthSpring.value = initialWidth;
container.style.width = initialWidth + 'px';
// Return cleanup function
return () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('mouseenter', handleMouseEnter);
container.removeEventListener('mouseleave', handleMouseLeave);
};
}
function createAdvancedInteractiveDock(container) {
if (initializedContainers.has(container)) {
console.log('AdvancedInteractiveDock: Container already initialized');
return;
}
console.log('AdvancedInteractiveDock: Creating dock for container', container);
initializedContainers.add(container);
let items;
try {
const dataItemsAttr = container.getAttribute('data-items');
if (!dataItemsAttr) {
items = DEFAULT_ITEMS;
container.setAttribute('data-items', JSON.stringify(DEFAULT_ITEMS));
} else {
items = JSON.parse(dataItemsAttr);
}
} catch (e) {
console.warn('AdvancedInteractiveDock: Error parsing items, using defaults', e);
items = DEFAULT_ITEMS;
container.setAttribute('data-items', JSON.stringify(DEFAULT_ITEMS));
}
console.log('AdvancedInteractiveDock: Items loaded', items);
container.innerHTML = '';
items.forEach((item, index) => {
const dockItem = document.createElement('div');
dockItem.className = 'dock-item';
dockItem.setAttribute('data-title', item.title);
if (item.url) {
dockItem.setAttribute('data-url', item.url);
}
const iconName = item.icon;
if (ICONS[iconName]) {
dockItem.innerHTML = ICONS[iconName];
} else {
console.warn('AdvancedInteractiveDock: Icon not found:', iconName);
dockItem.innerHTML = ICONS['home'] || '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/></svg>';
}
// Add label if enabled
if (CONFIG.showLabels) {
const label = document.createElement('div');
label.className = 'dock-label';
label.textContent = item.title;
dockItem.appendChild(label);
}
dockItem.addEventListener('click', (e) => {
e.preventDefault();
const url = dockItem.getAttribute('data-url');
if (url && url.trim() !== '') {
window.open(url, '_blank');
}
});
container.appendChild(dockItem);
});
console.log('AdvancedInteractiveDock: DOM built, initializing animation...');
// Initialize animation with a small delay to ensure DOM is ready
setTimeout(() => {
const cleanup = initializeDockAnimation(container);
if (cleanup) {
containerAnimations.set(container, cleanup);
console.log('AdvancedInteractiveDock: Animation initialized successfully');
} else {
console.error('AdvancedInteractiveDock: Failed to initialize animation');
}
}, 10);
return container;
}
function init() {
console.log('AdvancedInteractiveDock: Initializing...');
injectCSS();
const dockContainers = document.querySelectorAll('[data-interactive-dock]');
console.log('AdvancedInteractiveDock: Found', dockContainers.length, 'dock containers');
if (dockContainers.length === 0) {
console.log('AdvancedInteractiveDock: No dock containers found, will wait for content updates');
}
dockContainers.forEach((container, index) => {
console.log('AdvancedInteractiveDock: Processing container', index + 1);
createAdvancedInteractiveDock(container);
});
}
function safeInit() {
if (window.AdvancedInteractiveDock.initialized) {
console.log('AdvancedInteractiveDock: Already initialized, skipping');
return;
}
console.log('AdvancedInteractiveDock: Safe init starting...');
window.AdvancedInteractiveDock.initialized = true;
init();
}
// Multiple initialization strategies for Bricks Builder
if (document.readyState === 'loading') {
console.log('AdvancedInteractiveDock: Document loading, waiting for DOMContentLoaded');
document.addEventListener('DOMContentLoaded', safeInit);
} else {
console.log('AdvancedInteractiveDock: Document ready, initializing immediately');
safeInit();
}
// Also try with a small delay for Bricks Builder compatibility
setTimeout(safeInit, 100);
// Handle Bricks Builder content updates
document.addEventListener('bricks/content_updated', () => {
console.log('AdvancedInteractiveDock: Bricks content updated, scanning for new docks...');
const dockContainers = document.querySelectorAll('[data-interactive-dock]');
dockContainers.forEach(container => {
if (!initializedContainers.has(container)) {
console.log('AdvancedInteractiveDock: Initializing new dock container');
createAdvancedInteractiveDock(container);
}
});
});
// Global object
window.AdvancedInteractiveDock = {
init: safeInit,
create: createAdvancedInteractiveDock,
initialized: false
};
console.log('AdvancedInteractiveDock: Script setup complete');
})();`;
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const divId = generateUniqueId();
const codeId = generateUniqueId();
// Obtener el JavaScript actual con la configuración del usuario
const jsCode = generateJavaScriptCode();
// Crear el objeto JSON completo de Bricks Builder
const bricksJSON = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId],
"settings": {}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [divId, codeId],
"settings": {"_alignItems": "center"}
},
{
"id": divId,
"name": "div",
"parent": containerId,
"children": [],
"settings": {"_attributes": [{"id": "pwlpls", "name": "data-interactive-dock"}]},
"label": "Dock"
},
{
"id": codeId,
"name": "code",
"parent": containerId,
"children": [],
"settings": {
"executeCode": true,
"javascriptCode": jsCode
},
"label": "Interactive Dock JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('Copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
/**
* Reset Functions
*/
window.resetParameter = function(parameterId, defaultValue) {
const element = document.getElementById(parameterId);
if (element) {
element.value = defaultValue;
const valueElement = document.getElementById(`${parameterId}-value`);
if (valueElement) {
valueElement.textContent = defaultValue;
}
const configKey = parameterId.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
dockConfig[configKey] = defaultValue;
updatePreview();
saveConfiguration();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
/**
* UI Event Handlers
*/
function initializeUI() {
// Instructions toggle
const instructionsToggle = document.getElementById('instructions-toggle');
const instructionsContent = document.getElementById('instructions-content');
const instructionsCard = document.getElementById('instructions-card');
const toggleIcon = instructionsToggle.querySelector('.toggle-icon');
instructionsToggle.addEventListener('click', () => {
const isVisible = instructionsContent.classList.contains('show');
if (isVisible) {
instructionsContent.classList.remove('show');
instructionsCard.classList.remove('expanded');
toggleIcon.classList.remove('expanded');
} else {
instructionsContent.classList.add('show');
instructionsCard.classList.add('expanded');
toggleIcon.classList.add('expanded');
}
});
// Action bar buttons
document.getElementById('quick-attribute').addEventListener('click', () => {
copyToClipboard('data-interactive-dock');
});
document.getElementById('download-config').addEventListener('click', () => {
const jsCode = generateJavaScriptCode();
copyToClipboard(jsCode);
showNotification('Advanced JavaScript code copied to clipboard!');
});
document.getElementById('copy-full-section').addEventListener('click', () => {
const sectionJSON = generateFullSectionJSON();
copyToClipboard(sectionJSON);
showNotification('Full advanced section JSON copied to clipboard!');
});
// Icon modal handlers
document.getElementById('close-icon-modal').addEventListener('click', closeIconModal);
document.getElementById('select-icon-btn').addEventListener('click', selectIcon);
document.getElementById('icon-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeIconModal();
}
});
// Dock item management
document.getElementById('add-item-btn').addEventListener('click', function() {
const newItem = {
title: "New Item",
icon: "star",
url: ""
};
dockConfig.items.push(newItem);
renderDockInputs();
updatePreview();
saveConfiguration();
});
document.getElementById('remove-last-btn').addEventListener('click', function() {
if (dockConfig.items.length > 1) {
dockConfig.items.pop();
renderDockInputs();
updatePreview();
saveConfiguration();
} else {
showNotification('At least one item is required', 'error');
}
});
// Reset buttons
document.getElementById('reset-items').addEventListener('click', () => {
dockConfig.items = [...defaultConfig.items];
renderDockInputs();
updatePreview();
saveConfiguration();
showNotification('Items reset to default');
});
document.getElementById('reset-magnification').addEventListener('click', () => {
dockConfig.magnificationDistance = defaultConfig.magnificationDistance;
dockConfig.magnificationSize = defaultConfig.magnificationSize;
dockConfig.panelHeight = defaultConfig.panelHeight;
document.getElementById('magnification-distance').value = defaultConfig.magnificationDistance;
document.getElementById('magnification-size').value = defaultConfig.magnificationSize;
document.getElementById('panel-height').value = defaultConfig.panelHeight;
document.getElementById('magnification-distance-value').textContent = defaultConfig.magnificationDistance;
document.getElementById('magnification-size-value').textContent = defaultConfig.magnificationSize;
document.getElementById('panel-height-value').textContent = defaultConfig.panelHeight;
updatePreview();
saveConfiguration();
showNotification('Magnification settings reset');
});
document.getElementById('reset-spring').addEventListener('click', () => {
dockConfig.springMass = defaultConfig.springMass;
dockConfig.springStiffness = defaultConfig.springStiffness;
dockConfig.springDamping = defaultConfig.springDamping;
document.getElementById('spring-mass').value = defaultConfig.springMass;
document.getElementById('spring-stiffness').value = defaultConfig.springStiffness;
document.getElementById('spring-damping').value = defaultConfig.springDamping;
document.getElementById('spring-mass-value').textContent = defaultConfig.springMass;
document.getElementById('spring-stiffness-value').textContent = defaultConfig.springStiffness;
document.getElementById('spring-damping-value').textContent = defaultConfig.springDamping;
if (window.updateSpringConfig) {
window.updateSpringConfig();
}
updatePreview();
saveConfiguration();
showNotification('Spring physics reset');
});
document.getElementById('reset-style').addEventListener('click', () => {
dockConfig.backgroundColor = defaultConfig.backgroundColor;
dockConfig.borderColor = defaultConfig.borderColor;
dockConfig.iconColor = defaultConfig.iconColor;
dockConfig.containerRadius = defaultConfig.containerRadius;
dockConfig.itemRadius = defaultConfig.itemRadius;
dockConfig.itemsGap = defaultConfig.itemsGap;
updateColorInputs('background-color', defaultConfig.backgroundColor);
updateColorInputs('border-color', defaultConfig.borderColor);
updateColorInputs('icon-color', defaultConfig.iconColor);
document.getElementById('container-radius').value = defaultConfig.containerRadius;
document.getElementById('item-radius').value = defaultConfig.itemRadius;
document.getElementById('items-gap').value = defaultConfig.itemsGap;
document.getElementById('container-radius-value').textContent = defaultConfig.containerRadius;
document.getElementById('item-radius-value').textContent = defaultConfig.itemRadius;
document.getElementById('items-gap-value').textContent = defaultConfig.itemsGap;
updatePreview();
saveConfiguration();
showNotification('Style settings reset');
});
document.getElementById('reset-labels').addEventListener('click', () => {
dockConfig.showLabels = defaultConfig.showLabels;
dockConfig.containerPadding = defaultConfig.containerPadding;
document.getElementById('show-labels').checked = defaultConfig.showLabels;
document.getElementById('container-padding').value = defaultConfig.containerPadding;
document.getElementById('container-padding-value').textContent = defaultConfig.containerPadding;
updatePreview();
saveConfiguration();
showNotification('Labels & interaction settings reset');
});
document.getElementById('reset-position').addEventListener('click', () => {
dockConfig.respectContainerPosition = defaultConfig.respectContainerPosition;
dockConfig.fixedPosition = defaultConfig.fixedPosition;
dockConfig.bottomDistance = defaultConfig.bottomDistance;
dockConfig.zIndex = defaultConfig.zIndex;
document.getElementById('respect-container-position').checked = defaultConfig.respectContainerPosition;
document.getElementById('fixed-position').checked = defaultConfig.fixedPosition;
document.getElementById('bottom-distance').value = defaultConfig.bottomDistance;
document.getElementById('z-index').value = defaultConfig.zIndex;
document.getElementById('bottom-distance-value').textContent = defaultConfig.bottomDistance;
document.getElementById('z-index-value').textContent = defaultConfig.zIndex;
updatePreview();
saveConfiguration();
showNotification('Position settings reset');
});
// Color inputs
setupColorInputs('background-color');
setupColorInputs('border-color');
setupColorInputs('icon-color');
// Range inputs
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
if (valueElement) {
valueElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
const configKey = input.id.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
dockConfig[configKey] = parseFloat(input.value);
// Update spring configuration immediately for spring controls
if (input.id.startsWith('spring-') && window.updateSpringConfig) {
window.updateSpringConfig();
}
updatePreview();
saveConfiguration();
});
});
// Toggle inputs
document.getElementById('show-labels').addEventListener('change', function() {
dockConfig.showLabels = this.checked;
updatePreview();
saveConfiguration();
});
document.getElementById('respect-container-position').addEventListener('change', function() {
dockConfig.respectContainerPosition = this.checked;
saveConfiguration();
});
document.getElementById('fixed-position').addEventListener('change', function() {
dockConfig.fixedPosition = this.checked;
saveConfiguration();
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case 'd':
e.preventDefault();
document.getElementById('download-config').click();
break;
case 's':
e.preventDefault();
document.getElementById('copy-full-section').click();
break;
}
}
});
}
/**
* Initialize Application
*/
function init() {
setupIconModal();
initializeUI();
loadConfiguration();
renderDockInputs();
updatePreview();
setTimeout(() => {
showNotification('BricksFusion Interactive Dock Configurator loaded!');
}, 500);
}
init();
});
</script>
</body>
</html>
Dock Menu
Creates a macOS-style dock menu with magnification effect. Icons grow smoothly as you hover near them with physics-based spring animations. Perfect for navigation, quick actions, or social media links.
Items
Add items to your dock. Each item needs an icon (HTML/FontAwesome), optional title for labels, and optional URL for clicking. Icons can be colored individually.
Default: Empty array
Appearance
Background color of the dock container. Use rgba for transparency with the blur effect for a glass-like appearance.
Default: rgba(255,255,255,0.1)
Color of the dock container border. Subtle borders work best with the glass effect.
Default: rgba(255,255,255,0.2)
Border radius of the dock container. Higher values create more rounded corners.
Default: 16
Border radius of individual dock items. Match this with container radius for consistency.
Default: 12
Sizing
Size of icons relative to their container. 50% means icons take half the item space.
Default: 50
Base height of dock items when not magnified. The dock grows from this size.
Default: 48
Internal padding of the dock container around items.
Default: 12
Space between dock items. Larger gaps spread items apart more.
Default: 8
Magnification
How far from an item the magnification effect starts. Larger values create smoother, wider effects.
Default: 150
Maximum size items grow to when hovered. Should be larger than Panel Height for the effect to work.
Default: 80
Spring Physics
Weight of the spring animation. Higher values make animations feel heavier and slower.
Default: 0.2
How tight the spring is. Higher values make animations snappier and faster.
Default: 150
How much the spring bounces. Lower values create more bounce, higher values are smoother.
Default: 12
Position
When on, dock stays inside its container. When off, dock can position itself fixed or absolute on the page.
Default: Off
When on and Respect Container is off, dock stays fixed at bottom of viewport when scrolling. Perfect for persistent navigation.
Default: Off
Distance from bottom of viewport when not respecting container. Uses rem units.
Default: 2
Stacking order of the dock. Higher values place it above other elements.
Default: 1000
Labels
Display tooltips with item titles on hover. Helps users understand what each icon does.
Default: Off
Performance
This element uses custom spring physics with requestAnimationFrame for smooth 60fps animations. The magnification effect only animates when hovering, pausing when idle to save resources. Supports FontAwesome icons and custom SVGs.