/* =========================================================
লিংক ভাণ্ডার — script.js
এখানে SEED_LINKS-এ তোমার লিংকগুলো লেখা থাকে। পেজে "নতুন লিংক
যোগ করো" দিয়ে যা যোগ করবে তা শুধু ব্রাউজারের localStorage-এ
থাকবে। স্থায়ীভাবে সবার জন্য প্রকাশ করতে চাইলে পেজের নিচে
"কোড কপি করো" বাটনে চাপ দিয়ে সেই কোড এখানে SEED_LINKS-এ
বসিয়ে দিও।
========================================================= */
const SEED_LINKS = [
{
id: "seed-1",
title: "Figma",
url: "https://figma.com",
category: "ডিজাইন",
description: "UI ডিজাইন আর প্রোটোটাইপ বানানোর জন্য ব্যবহার করি — টিমের সাথে একসাথে কাজ করাও সহজ।"
},
{
id: "seed-2",
title: "MDN Web Docs",
url: "https://developer.mozilla.org",
category: "ডেভেলপমেন্ট",
description: "HTML, CSS, JS নিয়ে কোনো সন্দেহ হলে সবার আগে এখানেই খুঁজি — সবচেয়ে নির্ভরযোগ্য রেফারেন্স।"
},
{
id: "seed-3",
title: "Google Fonts",
url: "https://fonts.google.com",
category: "ডিজাইন",
description: "ফ্রি ফন্ট খোঁজার জায়গা, ওয়েবসাইটে সরাসরি বসানো যায় সহজে।"
},
{
id: "seed-4",
title: "Unsplash",
url: "https://unsplash.com",
category: "টুলস",
description: "ব্লগ পোস্টের জন্য ফ্রি, ভালো মানের ছবি এখান থেকেই নিই।"
},
{
id: "seed-5",
title: "freeCodeCamp",
url: "https://freecodecamp.org",
category: "শেখা ও কোর্স",
description: "প্রোগ্রামিং শেখার জন্য ফ্রি, হাতে-কলমে কোর্স — নতুনদের জন্য দারুণ শুরু।"
},
{
id: "seed-6",
title: "Notion",
url: "https://notion.so",
category: "টুলস",
description: "লেখার খসড়া, প্ল্যানিং, আর ব্লগের আইডিয়াগুলো এখানে গুছিয়ে রাখি।"
},
{
id: "seed-7",
title: "Behance",
url: "https://behance.net",
category: "ইন্সপিরেশন",
description: "ডিজাইনের আইডিয়া লাগলে বা মন ভালো করার মতো কাজ দেখতে চাইলে এখানে ঢুঁ মারি।"
},
{
id: "seed-8",
title: "Grammarly",
url: "https://grammarly.com",
category: "লেখালেখি",
description: "ইংরেজিতে লেখার সময় বানান আর গ্রামার ঠিক করে দেয়।"
}
];
const TAG_COLORS = ["var(--tag-1)","var(--tag-2)","var(--tag-3)","var(--tag-4)","var(--tag-5)"];
const STORAGE_KEY = "linkVaultData";
let state = {
links: [],
activeCategory: "সব",
query: ""
};
/* ---------- persistence ---------- */
function loadData(){
try{
const raw = localStorage.getItem(STORAGE_KEY);
if(raw){
state.links = JSON.parse(raw);
return;
}
}catch(e){ /* fall through to seed */ }
state.links = SEED_LINKS.slice();
saveData();
}
function saveData(){
try{
localStorage.setItem(STORAGE_KEY, JSON.stringify(state.links));
}catch(e){
showToast("সেভ করা যায়নি — ব্রাউজার স্টোরেজ পূর্ণ হতে পারে।");
}
}
/* ---------- helpers ---------- */
function getCategories(){
const set = new Set(state.links.map(l => l.category.trim()).filter(Boolean));
return ["সব", ...Array.from(set)];
}
function colorForCategory(cat, categories){
const idx = categories.indexOf(cat);
return TAG_COLORS[Math.max(idx,0) % TAG_COLORS.length];
}
function escapeHtml(str){
return str.replace(/[&<>"']/g, ch => ({
"&":"&", "<":"<", ">":">", '"':""", "'":"'"
}[ch]));
}
function shortUrl(url){
try{
const u = new URL(url);
return u.hostname.replace(/^www\./,"") + (u.pathname !== "/" ? u.pathname : "");
}catch(e){
return url;
}
}
/* ---------- rendering ---------- */
function renderChips(){
const categories = getCategories();
const chipWrap = document.getElementById("categoryChips");
chipWrap.innerHTML = "";
categories.forEach(cat => {
const chip = document.createElement("button");
chip.className = "chip";
chip.type = "button";
chip.textContent = cat;
chip.setAttribute("role","tab");
chip.setAttribute("aria-selected", state.activeCategory === cat ? "true" : "false");
chip.addEventListener("click", () => {
state.activeCategory = cat;
renderChips();
renderCards();
});
chipWrap.appendChild(chip);
});
}
function filteredLinks(){
const q = state.query.trim().toLowerCase();
return state.links.filter(l => {
const inCategory = state.activeCategory === "সব" || l.category === state.activeCategory;
if(!inCategory) return false;
if(!q) return true;
return (
l.title.toLowerCase().includes(q) ||
l.description.toLowerCase().includes(q) ||
l.url.toLowerCase().includes(q) ||
l.category.toLowerCase().includes(q)
);
});
}
function renderCards(){
const grid = document.getElementById("cardGrid");
const empty = document.getElementById("emptyState");
const count = document.getElementById("resultCount");
const categories = getCategories();
const items = filteredLinks();
count.textContent = `${items.length} টি লিংক দেখানো হচ্ছে`;
grid.innerHTML = "";
if(items.length === 0){
empty.hidden = false;
return;
}
empty.hidden = true;
items.forEach(link => {
const card = document.createElement("article");
card.className = "card";
const tabColor = colorForCategory(link.category, categories);
card.innerHTML = `
${escapeHtml(link.category)}
${escapeHtml(shortUrl(link.url))}
${escapeHtml(link.description)}
`;
card.querySelector(".card-del").addEventListener("click", () => deleteLink(link.id));
grid.appendChild(card);
});
}
function renderAll(){
renderChips();
renderCards();
refreshCategoryDatalist();
}
function refreshCategoryDatalist(){
const list = document.getElementById("categoryList");
list.innerHTML = "";
getCategories().filter(c => c !== "সব").forEach(cat => {
const opt = document.createElement("option");
opt.value = cat;
list.appendChild(opt);
});
}
/* ---------- CRUD ---------- */
function addLink({title, url, category, description}){
state.links.unshift({
id: "l-" + Date.now(),
title: title.trim(),
url: url.trim(),
category: category.trim() || "সাধারণ",
description: description.trim()
});
saveData();
renderAll();
showToast("লিংক যোগ হয়েছে।");
}
function deleteLink(id){
const ok = window.confirm("এই লিংকটা মুছে ফেলতে চাও?");
if(!ok) return;
state.links = state.links.filter(l => l.id !== id);
saveData();
renderAll();
showToast("লিংক মুছে ফেলা হয়েছে।");
}
/* ---------- toast ---------- */
let toastTimer = null;
function showToast(msg){
const el = document.getElementById("toast");
el.textContent = msg;
el.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => el.classList.remove("show"), 2400);
}
/* ---------- modal ---------- */
const modalBackdrop = document.getElementById("modalBackdrop");
const addForm = document.getElementById("addForm");
function openModal(){
modalBackdrop.hidden = false;
document.getElementById("fieldTitle").focus();
document.addEventListener("keydown", handleEsc);
}
function closeModal(){
modalBackdrop.hidden = true;
addForm.reset();
document.removeEventListener("keydown", handleEsc);
}
function handleEsc(e){
if(e.key === "Escape") closeModal();
}
document.getElementById("openAddBtn").addEventListener("click", openModal);
document.getElementById("cancelBtn").addEventListener("click", closeModal);
modalBackdrop.addEventListener("click", e => {
if(e.target === modalBackdrop) closeModal();
});
addForm.addEventListener("submit", e => {
e.preventDefault();
const title = document.getElementById("fieldTitle").value;
let url = document.getElementById("fieldUrl").value.trim();
const category = document.getElementById("fieldCategory").value;
const description = document.getElementById("fieldDesc").value;
if(!/^https?:\/\//i.test(url)){
url = "https://" + url;
}
addLink({title, url, category, description});
closeModal();
});
/* ---------- search ---------- */
document.getElementById("searchInput").addEventListener("input", e => {
state.query = e.target.value;
renderCards();
});
/* ---------- backup / export ---------- */
document.getElementById("downloadBtn").addEventListener("click", () => {
const blob = new Blob([JSON.stringify(state.links, null, 2)], {type:"application/json"});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "link-vault-backup.json";
a.click();
URL.revokeObjectURL(a.href);
showToast("ব্যাকআপ ডাউনলোড হয়েছে।");
});
document.getElementById("copyCodeBtn").addEventListener("click", async () => {
const code = "const SEED_LINKS = " + JSON.stringify(state.links, null, 2) + ";";
try{
await navigator.clipboard.writeText(code);
showToast("কোড কপি হয়েছে — script.js-এ পেস্ট করো।");
}catch(e){
showToast("কপি করা যায়নি, ব্রাউজার অনুমতি দেয়নি।");
}
});
/* ---------- init ---------- */
loadData();
renderAll();