优化展示效果

This commit is contained in:
wangli 2025-12-21 18:10:48 +08:00
parent 0f448ec33e
commit 9c274b5b41
5 changed files with 228 additions and 31 deletions

View File

@ -21,10 +21,12 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -153,6 +155,15 @@ public class GalleryController {
File currentFolder = new File(rootPath, path);
String rootAbsolutePath = new File(rootPath).getAbsolutePath();
Set<String> cartItemIds = Collections.emptySet();
if (StpUtil.isLogin() && ((String) StpUtil.getLoginId()).startsWith("user-")) {
String loginId = (String) StpUtil.getLoginId();
String authCode = loginId.substring(5);
cartItemIds = jdbcTemplate.queryForList("SELECT item_id FROM cart_items WHERE auth_code = ?", String.class, authCode)
.stream()
.collect(Collectors.toSet());
}
List<Map<String, String>> folders = new ArrayList<>();
Map<String, Map<String, File>> groupedFiles = new HashMap<>();
@ -176,6 +187,7 @@ public class GalleryController {
}
}
Set<String> finalCartItemIds = cartItemIds;
List<ImageGroup> imageGroups = groupedFiles.entrySet().stream()
.map(entry -> {
String uniqueId = entry.getKey();
@ -183,6 +195,7 @@ public class GalleryController {
Map<String, File> typeMap = entry.getValue();
ImageGroup group = new ImageGroup(uniqueId, displayName);
group.setInCart(finalCartItemIds.contains(uniqueId));
java.util.function.Function<File, String> getUrl = (file) -> {
if (file == null) return null;

View File

@ -8,6 +8,7 @@ public class ImageGroup {
private String effectImageUrl;
private String templateThumbnailUrl;
private String templateImageUrl;
private boolean inCart;
public ImageGroup(String id, String name) {
this.id = id;
@ -63,4 +64,12 @@ public class ImageGroup {
public void setTemplateImageUrl(String templateImageUrl) {
this.templateImageUrl = templateImageUrl;
}
public boolean isInCart() {
return inCart;
}
public void setInCart(boolean inCart) {
this.inCart = inCart;
}
}

View File

@ -4,24 +4,66 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理后台</title>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<style>
body { font-family: sans-serif; background-color: #f4f6f8; margin: 0; display: flex; height: 100vh; }
.sidebar { width: 280px; background: #fff; box-shadow: 2px 0 5px rgba(0,0,0,0.1); padding: 20px; overflow-y: auto; }
.main-content { flex-grow: 1; padding: 20px; overflow-y: auto; }
:root {
--primary-color: #007bff; /* Blue shade */
--bg-color: #e9f5ff; /* Light blue background */
--glass-bg: rgba(255, 255, 255, 0.6);
--card-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
body {
font-family: sans-serif;
background-color: var(--bg-color);
margin: 0;
display: flex;
height: 100vh;
}
#particles-js {
position: fixed;
width: 100%;
height: 100%;
z-index: 0;
}
.sidebar {
position: relative;
z-index: 1;
width: 280px;
background: var(--glass-bg);
backdrop-filter: blur(10px);
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
padding: 20px;
overflow-y: auto;
}
.main-content {
position: relative;
z-index: 1;
flex-grow: 1;
padding: 20px;
overflow-y: auto;
}
.code-list-item { padding: 12px; border-bottom: 1px solid #eee; cursor: pointer; border-radius: 6px; }
.code-list-item:hover, .code-list-item.active { background-color: #e9f5ff; }
.code-list-item:hover, .code-list-item.active { background-color: #d1e7fd; }
.code-list-item strong { font-size: 14px; color: #333; }
.code-list-item span { font-size: 12px; color: #777; display: block; margin-top: 4px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; }
.grid-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.05); }
.grid-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: var(--card-shadow); }
.grid-item img { width: 100%; height: auto; display: block; }
.grid-item-info { padding: 10px; font-size: 12px; text-align: center; }
#download-btn { background: #28a745; color: white; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 16px; display: none; margin-bottom: 20px; }
#download-btn { background: var(--primary-color); color: white; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 16px; display: none; margin-bottom: 20px; }
#download-btn:hover { background: #0056b3; }
#download-btn:disabled { background: #999; }
</style>
</head>
<body>
<div id="particles-js"></div>
<div class="sidebar" id="sidebar">
<h3>授权码列表</h3>
<div id="code-list"></div>
@ -126,7 +168,27 @@
}
};
document.addEventListener('DOMContentLoaded', fetchAuthCodes);
document.addEventListener('DOMContentLoaded', () => {
fetchAuthCodes();
particlesJS('particles-js', {
"particles": {
"number": { "value": 80, "density": { "enable": true, "value_area": 800 } },
"color": { "value": "#007bff" },
"shape": { "type": "circle", "stroke": { "width": 0, "color": "#000000" }, "polygon": { "nb_sides": 5 } },
"opacity": { "value": 0.5, "random": false, "anim": { "enable": false, "speed": 1, "opacity_min": 0.1, "sync": false } },
"size": { "value": 3, "random": true, "anim": { "enable": false, "speed": 40, "size_min": 0.1, "sync": false } },
"line_linked": { "enable": true, "distance": 150, "color": "#007bff", "opacity": 0.4, "width": 1 },
"move": { "enable": true, "speed": 6, "direction": "none", "random": false, "straight": false, "out_mode": "out", "bounce": false, "attract": { "enable": false, "rotateX": 600, "rotateY": 1200 } }
},
"interactivity": {
"detect_on": "canvas",
"events": { "onhover": { "enable": true, "mode": "repulse" }, "onclick": { "enable": true, "mode": "push" }, "resize": true },
"modes": { "grab": { "distance": 400, "line_linked": { "opacity": 1 } }, "bubble": { "distance": 400, "size": 40, "duration": 2, "opacity": 8, "speed": 3 }, "repulse": { "distance": 200, "duration": 0.4 }, "push": { "particles_nb": 4 }, "remove": { "particles_nb": 2 } }
},
"retina_detect": true
});
});
</script>
</body>

View File

@ -6,13 +6,42 @@
<title>我的购物车</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.js"></script>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<style>
body { font-family: sans-serif; background-color: #f0f2f5; margin: 0; }
.navbar { background: #fff; padding: 10px 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); display: flex; align-items: center; }
.navbar a { text-decoration: none; color: #007bff; font-weight: bold; margin-right: 20px; }
.main-content { padding: 20px; max-width: 1600px; margin: auto; }
:root {
--primary-color: #28a745; /* Green shade */
--bg-color: #e8f5e9; /* Light green background */
--glass-bg: rgba(255, 255, 255, 0.6);
--card-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
body {
font-family: sans-serif;
background-color: var(--bg-color);
margin: 0;
}
#particles-js {
position: fixed;
width: 100%;
height: 100%;
z-index: 0;
}
.navbar {
position: relative;
z-index: 1;
background: var(--glass-bg);
backdrop-filter: blur(10px);
padding: 10px 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
align-items: center;
}
.navbar a { text-decoration: none; color: var(--primary-color); font-weight: bold; margin-right: 20px; }
.main-content { position: relative; z-index: 1; padding: 20px; max-width: 1600px; margin: auto; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.grid-item { position: relative; background: #fff; border-radius: 15px; overflow: hidden; box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); }
.grid-item { position: relative; background: #fff; border-radius: 15px; overflow: hidden; box-shadow: var(--card-shadow); }
.grid-item img { width: 100%; height: auto; display: block; cursor: zoom-in; }
.image-info { padding: 12px; font-size: 12px; text-align: center; }
.loading-status { text-align: center; padding: 40px; color: #999; }
@ -41,9 +70,11 @@
</head>
<body>
<div id="particles-js"></div>
<div class="navbar">
<a href="/">返回主页</a>
<a id="admin-link" href="/admin" style="display: none;">管理后台</a>
<a id="admin-link" href="/api/admin" style="display: none;">管理后台</a>
</div>
<div class="main-content">
@ -74,7 +105,6 @@
items.forEach(item => {
const gridItem = document.createElement('div');
gridItem.className = 'grid-item';
// **[FIX]** Use a data attribute to reliably store the original ID.
gridItem.dataset.itemId = item.id;
gridItem.innerHTML = `
<button class="delete-btn" data-id="${item.id}" title="从购物车移除">×</button>
@ -105,7 +135,6 @@
});
if (response.ok) {
// **[FIX]** Use a robust attribute selector to find the correct element.
const itemElement = document.querySelector(`.grid-item[data-item-id="${itemId}"]`);
if (itemElement) {
itemElement.style.transition = 'opacity 0.5s, transform 0.5s';
@ -138,7 +167,29 @@
});
async function checkUserRole() { try { const res = await fetch('/api/auth/info'); if (res.ok) { const data = await res.json(); if (data.role === 'admin') { document.getElementById('admin-link').style.display = 'block'; } } } catch (e) { /* Ignore */ } }
document.addEventListener('DOMContentLoaded', () => { checkUserRole(); fetchCartItems(); });
document.addEventListener('DOMContentLoaded', () => {
checkUserRole();
fetchCartItems();
particlesJS('particles-js', {
"particles": {
"number": { "value": 80, "density": { "enable": true, "value_area": 800 } },
"color": { "value": "#28a745" },
"shape": { "type": "circle", "stroke": { "width": 0, "color": "#000000" }, "polygon": { "nb_sides": 5 } },
"opacity": { "value": 0.5, "random": false, "anim": { "enable": false, "speed": 1, "opacity_min": 0.1, "sync": false } },
"size": { "value": 3, "random": true, "anim": { "enable": false, "speed": 40, "size_min": 0.1, "sync": false } },
"line_linked": { "enable": true, "distance": 150, "color": "#28a745", "opacity": 0.4, "width": 1 },
"move": { "enable": true, "speed": 6, "direction": "none", "random": false, "straight": false, "out_mode": "out", "bounce": false, "attract": { "enable": false, "rotateX": 600, "rotateY": 1200 } }
},
"interactivity": {
"detect_on": "canvas",
"events": { "onhover": { "enable": true, "mode": "repulse" }, "onclick": { "enable": true, "mode": "push" }, "resize": true },
"modes": { "grab": { "distance": 400, "line_linked": { "opacity": 1 } }, "bubble": { "distance": 400, "size": 40, "duration": 2, "opacity": 8, "speed": 3 }, "repulse": { "distance": 200, "duration": 0.4 }, "push": { "particles_nb": 4 }, "remove": { "particles_nb": 2 } }
},
"retina_detect": true
});
});
</script>
</body>

View File

@ -7,12 +7,13 @@
<title>云端私密相册 - 模板图片挑选</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.js"></script>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<style>
:root {
--primary-color: #007bff;
--bg-color: #f0f2f5;
--glass-bg: rgba(255, 255, 255, 0.7);
--primary-color: #28a745; /* Green shade */
--bg-color: #e8f5e9; /* Light green background */
--glass-bg: rgba(255, 255, 255, 0.6);
--card-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
@ -25,11 +26,20 @@
overflow-x: hidden;
}
#particles-js {
position: fixed;
width: 100%;
height: 100%;
z-index: 0;
}
.main-content { position: relative; z-index: 1; padding: 30px 5%; max-width: 1600px; margin: 0 auto; }
.breadcrumb { padding: 15px 25px; background: var(--glass-bg); backdrop-filter: blur(10px); margin-bottom: 30px; border-radius: 15px; box-shadow: var(--card-shadow); font-size: 14px; }
.breadcrumb { display: flex; justify-content: space-between; align-items: center; padding: 15px 25px; background: var(--glass-bg); backdrop-filter: blur(10px); margin-bottom: 30px; border-radius: 15px; box-shadow: var(--card-shadow); font-size: 14px; }
.breadcrumb a { color: var(--primary-color); text-decoration: none; font-weight: 500; }
.breadcrumb a:hover { color: #0056b3; }
.breadcrumb a:hover { color: #1e7e34; }
.breadcrumb span { margin: 0 10px; color: #abb2bf; }
.admin-link { background-color: var(--primary-color); color: white !important; padding: 8px 15px; border-radius: 8px; text-decoration: none; font-weight: 500; }
.admin-link:hover { background-color: #1e7e34; }
.folder-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 20px; margin-bottom: 40px; }
.folder-card { background: var(--glass-bg); backdrop-filter: blur(8px); padding: 20px; border-radius: 18px; text-align: center; cursor: pointer; box-shadow: var(--card-shadow); border: 1px solid rgba(255, 255, 255, 0.4); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }
.folder-card:hover { transform: translateY(-5px); box-shadow: 0 12px 40px rgba(0,0,0,0.1); }
@ -40,13 +50,15 @@
.grid-item:hover { transform: scale(1.02); z-index: 10; }
.grid-item img { width: 100%; height: auto; display: block; cursor: zoom-in; }
.image-info { padding: 12px; font-size: 12px; color: #666; text-align: center; background: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.add-to-cart-btn { position: absolute; bottom: 10px; right: 10px; background: rgba(0, 123, 255, 0.8); color: white; border: none; border-radius: 50px; width: 40px; height: 40px; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); opacity: 0; transform: scale(0.8); transition: opacity 0.2s ease, transform 0.2s ease; z-index: 5; display: flex; align-items: center; justify-content: center; }
.add-to-cart-btn { position: absolute; bottom: 10px; right: 10px; background: rgba(40, 167, 69, 0.8); color: white; border: none; border-radius: 50px; width: 40px; height: 40px; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); opacity: 0; transform: scale(0.8); transition: opacity 0.2s ease, transform 0.2s ease; z-index: 5; display: flex; align-items: center; justify-content: center; }
.grid-item:hover .add-to-cart-btn { opacity: 1; transform: scale(1); }
.add-to-cart-btn:hover { background: #0056b3; transform: scale(1.1); }
.add-to-cart-btn:hover { background: #1e7e34; transform: scale(1.1); }
.add-to-cart-btn.in-cart { background: #218838; cursor: not-allowed; }
.in-cart-badge { position: absolute; top: 10px; left: 10px; background: #28a745; color: white; padding: 5px 8px; border-radius: 5px; font-size: 12px; z-index: 5; }
.loading-status { text-align: center; padding: 40px; color: #999; font-size: 14px; }
.fab-cart { position: fixed; bottom: 40px; right: 40px; width: 60px; height: 60px; background-color: var(--primary-color); color: white; border-radius: 50%; border: none; box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4); display: none; align-items: center; justify-content: center; cursor: pointer; z-index: 1000; transition: all 0.3s; }
.fab-cart:hover { transform: scale(1.1); background-color: #0056b3; }
.fab-cart { position: fixed; bottom: 40px; right: 40px; width: 60px; height: 60px; background-color: var(--primary-color); color: white; border-radius: 50%; border: none; box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4); display: none; align-items: center; justify-content: center; cursor: pointer; z-index: 1000; transition: all 0.3s; }
.fab-cart:hover { transform: scale(1.1); background-color: #1e7e34; }
.fab-cart.show { display: flex; }
#toast-container { position: fixed; top: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; align-items: flex-end; }
@ -59,10 +71,14 @@
</head>
<body>
<div id="particles-js"></div>
<div id="toast-container"></div>
<div class="main-content">
<div class="breadcrumb" id="breadcrumb"></div>
<div class="breadcrumb" id="breadcrumb">
<div id="breadcrumb-links"></div>
<a id="admin-link" class="admin-link" href="/api/admin" style="display: none;">后台管理</a>
</div>
<div class="folder-container" id="folder-list"></div>
<div class="grid" id="image-grid"></div>
<div id="loader" class="loading-status">正在探索云端内容...</div>
@ -106,6 +122,8 @@
currentUserRole = data.role;
if (currentUserRole === 'user') {
document.getElementById('fab-cart').classList.add('show');
} else if (currentUserRole === 'admin') {
document.getElementById('admin-link').style.display = 'block';
}
} else {
handleAuthError();
@ -167,7 +185,7 @@
}
function renderBreadcrumb(path) {
const bc = document.getElementById('breadcrumb');
const bc = document.getElementById('breadcrumb-links');
bc.innerHTML = `<a onclick="loadLevel('')">🏠 根目录</a>`;
let parts = path.split('/').filter(p => p);
let fullPath = "";
@ -204,8 +222,20 @@
const effectImageUrl = group.effectImageUrl ? `/raw${group.effectImageUrl}` : '';
const displayName = cleanString(group.name);
const itemId = cleanString(group.id);
const cartButtonHtml = currentUserRole === 'user' ? `<button class="add-to-cart-btn" data-id="${itemId}" title="加入购物车"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M.5 1a.5.5 0 0 0 0 1h1.11l.401 1.607 1.498 7.985A.5.5 0 0 0 4 12h1a2 2 0 1 0 0 4 2 2 0 0 0 0-4h7a2 2 0 1 0 0 4 2 2 0 0 0 0-4h1a.5.5 0 0 0 .491-.408l1.5-8A.5.5 0 0 0 14.5 3H2.89l-.405-1.621A.5.5 0 0 0 2 1zM6 14a1 1 0 1 1-2 0 1 1 0 0 1 2 0m7 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0M9 5.5V7h1.5a.5.5 0 0 1 0 1H9v1.5a.5.5 0 0 1-1 0V8H6.5a.5.5 0 0 1 0-1H8V5.5a.5.5 0 0 1 1 0"/></svg></button>` : '';
item.innerHTML = `<img src="${effectThumbUrl}" data-original="${effectImageUrl}" alt="${displayName}" />${cartButtonHtml}<div class="image-info">${displayName}</div>`;
let cartButtonHtml = '';
let inCartBadgeHtml = '';
if (currentUserRole === 'user') {
if (group.inCart) {
inCartBadgeHtml = '<div class="in-cart-badge">✔️</div>';
cartButtonHtml = `<button class="add-to-cart-btn in-cart" data-id="${itemId}" title="已在购物车" disabled><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg></button>`;
} else {
cartButtonHtml = `<button class="add-to-cart-btn" data-id="${itemId}" title="加入购物车"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M.5 1a.5.5 0 0 0 0 1h1.11l.401 1.607 1.498 7.985A.5.5 0 0 0 4 12h1a2 2 0 1 0 0 4 2 2 0 0 0 0-4h7a2 2 0 1 0 0 4 2 2 0 0 0 0-4h1a.5.5 0 0 0 .491-.408l1.5-8A.5.5 0 0 0 14.5 3H2.89l-.405-1.621A.5.5 0 0 0 2 1zM6 14a1 1 0 1 1-2 0 1 1 0 0 1 2 0m7 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0M9 5.5V7h1.5a.5.5 0 0 1 0 1H9v1.5a.5.5 0 0 1-1 0V8H6.5a.5.5 0 0 1 0-1H8V5.5a.5.5 0 0 1 1 0"/></svg></button>`;
}
}
item.innerHTML = `${inCartBadgeHtml}<img src="${effectThumbUrl}" data-original="${effectImageUrl}" alt="${displayName}" />${cartButtonHtml}<div class="image-info">${displayName}</div>`;
grid.appendChild(item);
});
if (viewer) {
@ -223,7 +253,7 @@
async function handleAddToCart(itemId) {
console.log(`Adding item ${itemId} to cart.`);
try {
const response = await fetch('/api/cart/add', { // **[PATH-FIX]** Reverted to /api/cart/add
const response = await fetch('/api/cart/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itemId: itemId })
@ -234,6 +264,20 @@
showToast(result, 'info');
} else {
showToast(result, 'success');
const button = document.querySelector(`.add-to-cart-btn[data-id="${itemId}"]`);
if(button){
button.classList.add('in-cart');
button.disabled = true;
button.title = "已在购物车";
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg>';
const item = button.closest('.grid-item');
if(item && !item.querySelector('.in-cart-badge')){
const badge = document.createElement('div');
badge.className = 'in-cart-badge';
badge.textContent = '✔️';
item.prepend(badge);
}
}
}
} else if (response.status === 401) {
handleAuthError();
@ -249,12 +293,30 @@
await fetchUserInfo();
document.getElementById('image-grid').addEventListener('click', function(event) {
const cartButton = event.target.closest('.add-to-cart-btn');
if (cartButton) {
if (cartButton && !cartButton.disabled) {
event.stopPropagation();
handleAddToCart(cartButton.dataset.id);
}
});
loadLevel("");
particlesJS('particles-js', {
"particles": {
"number": { "value": 80, "density": { "enable": true, "value_area": 800 } },
"color": { "value": "#28a745" },
"shape": { "type": "circle", "stroke": { "width": 0, "color": "#000000" }, "polygon": { "nb_sides": 5 } },
"opacity": { "value": 0.5, "random": false, "anim": { "enable": false, "speed": 1, "opacity_min": 0.1, "sync": false } },
"size": { "value": 3, "random": true, "anim": { "enable": false, "speed": 40, "size_min": 0.1, "sync": false } },
"line_linked": { "enable": true, "distance": 150, "color": "#28a745", "opacity": 0.4, "width": 1 },
"move": { "enable": true, "speed": 6, "direction": "none", "random": false, "straight": false, "out_mode": "out", "bounce": false, "attract": { "enable": false, "rotateX": 600, "rotateY": 1200 } }
},
"interactivity": {
"detect_on": "canvas",
"events": { "onhover": { "enable": true, "mode": "repulse" }, "onclick": { "enable": true, "mode": "push" }, "resize": true },
"modes": { "grab": { "distance": 400, "line_linked": { "opacity": 1 } }, "bubble": { "distance": 400, "size": 40, "duration": 2, "opacity": 8, "speed": 3 }, "repulse": { "distance": 200, "duration": 0.4 }, "push": { "particles_nb": 4 }, "remove": { "particles_nb": 2 } }
},
"retina_detect": true
});
}
initializeApp();