( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ
<script>
$(document).ready(function() {
// --- Resize Logic (Standard App Pattern) ---
let isResizing = false;
let initialHeight;
let initialY;
function startResizing(event) {
isResizing = true;
const $button = $(this);
const $div = $('#brand-brand');
initialHeight = $div.height();
initialY = event.clientY;
event.preventDefault();
}
function resizeDiv(event) {
if (isResizing) {
const $div = $('#brand-brand');
const newHeight = initialHeight - (event.clientY - initialY);
if (newHeight > 50) {
$div.height(newHeight);
}
}
}
function stopResizing() {
isResizing = false;
}
// Mouse events
$('#btn-resize-brand').on('mousedown', startResizing);
$(document).on('mousemove', resizeDiv);
$(document).on('mouseup', stopResizing);
// Touch events for mobile
$('#btn-resize-brand').on('touchstart', startResizing);
$(document).on('touchmove', resizeDiv);
$(document).on('touchend', stopResizing);
});
</script>
<style>
/* Search Section Styling */
.brand-search-wrapper {
position: relative;
margin-bottom: 15px;
}
.brand-search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
font-size: 20px;
pointer-events: none;
}
#template-search {
padding-left: 40px;
height: 40px;
border-radius: 8px;
border: 1px solid #e2e8f0;
width: 100% !important;
font-size: 14px;
background: #fff;
transition: all 0.2s ease;
}
#template-search:focus {
border-color: #6366f1;
outline: none;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
/* Template Grid Styling */
.template-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 13px;
padding-bottom: 20px;
}
/* Template Card Styling */
.brand-template-card {
cursor: pointer;
position: relative;
background: #fff;
border-radius: 8px;
border: 1px solid #e2e8f0;
overflow: hidden;
transition: all 0.2s ease;
}
.brand-template-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border-color: #cbd5e1;
}
.brand-template-thumb {
aspect-ratio: 1;
background: #f8fafc;
overflow: hidden;
position: relative;
}
.brand-template-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.brand-template-card:hover .brand-template-thumb img {
transform: scale(1.05);
}
.brand-template-info {
padding: 10px;
border-top: 1px solid #f1f5f9;
background: #fff;
}
.brand-template-title {
font-size: 12px;
font-weight: 500;
color: #334155;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Hover Overlay */
.brand-template-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s;
}
.brand-template-card:hover .brand-template-overlay {
opacity: 1;
}
.brand-use-btn {
background: #fff;
color: #0f172a;
font-size: 12px;
font-weight: 600;
padding: 6px 12px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transform: translateY(10px);
transition: transform 0.2s;
}
.brand-template-card:hover .brand-use-btn {
transform: translateY(0);
}
#my-categories{overflow:visible}
#my-categories .mycats-head{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}
#my-categories .mycats-title{font-family:'Inter',sans-serif;font-weight:700;letter-spacing:-.02em;color:#383a3f;font-size:1.05rem;line-height:1}
#my-categories .mycats-subtitle{font-family:'Inter',sans-serif;font-weight:500;color:rgba(56,58,63,.65);font-size:.85rem;line-height:1}
#my-categories .mycats-tabs{display:flex;gap:6px;align-items:center;flex-wrap:nowrap;overflow:auto;padding:4px;border-radius:999px;background:rgba(56,58,63,.05);border:1px solid rgba(56,58,63,.08);margin-bottom:8px}
#my-categories .mycats-tab{flex:0 0 auto;border:0;cursor:pointer;border-radius:999px;padding:5px 10px;font-family:'Inter',sans-serif;font-weight:500;font-size:.75rem;letter-spacing:0;color:rgba(56,58,63,.78);background:transparent;transition:background .16s ease, box-shadow .16s ease, transform .16s ease}
#my-categories .mycats-tab:hover{background:rgba(255,255,255,.85);box-shadow:0 10px 25px rgba(10, 20, 30, .10);transform:translateY(-1px)}
#my-categories .mycats-tab[aria-selected="true"]{background:#fff;color:rgba(56,58,63,.92);box-shadow:0 14px 35px rgba(10, 20, 30, .12);font-weight:600}
#my-categories .mycats-masonry{display:flex;flex-wrap:wrap;gap:5px;width:100%}
#my-categories .mycats-btn{display:inline-block;padding:4px 10px;background:#fff;border:1px solid rgba(56,58,63,.15);border-radius:999px;color:#383a3f;font-family:'Inter',sans-serif;font-weight:500;font-size:0.75rem;cursor:pointer;transition:all 0.15s ease;text-align:center;width:auto}
#my-categories .mycats-btn:hover{background:#f1f5f9;border-color:rgba(56,58,63,.3);transform:translateY(-1px)}
#my-categories .mycats-item{break-inside:avoid;margin:0}
</style>
<div id="brand-brand" class="brand-icon-panel-content panel-hide" >
<div class="brand-tabs">
<ul class="brand-tabs-menu">
<li class="active" data-target="#brand-tab-my-brand">My Brand</li>
<li data-target="#brand-tab-templates">Templates</li>
<li data-target="#brand-tab-shared">Team</li>
</ul>
<!-- Tab: My Brand -->
<div id="brand-tab-my-brand" class="brand-tab active">
<div class="brand-templates-menu-wrap" style="margin-top: 10px;margin-bottom:0px!important">
<select id="brand-select" class="brand-select brand-form-field">
<option value="" disabled selected>Select a Brand...</option>
</select>
</div>
<div id="brand-assets-container" class="brand-templates-content" style="padding: 10px;">
<!-- Loading State -->
<div class="brand-pagination light-theme simple-pagination">Loading Assets...</div>
</div>
<div id="brand-texts-setting" style="padding: 10px;" >
<h6 style="margin: 0 0 10px 0; font-size: 12px; text-transform: uppercase; color: #888;">Brand Details</h6>
<ul class="brand-accordion" id="brand_identity_list">
</ul>
</div>
</div>
<!-- Tab: Templates -->
<div id="brand-tab-templates" class="brand-tab">
<div class="brand-templates-menu-wrap" style="margin-top: 10px;margin-bottom:0px!important">
<div class="brand-search-wrapper">
<span class="material-icons brand-search-icon">search</span>
<input type="text" id="template-search" placeholder="Search templates..." class="brand-form-field">
</div>
</div>
<div id="my-categories" style="margin-bottom:10px">
<!-- <div class="mycats-head">
<div class="mycats-title">Relevant Categories</div>
<div class="mycats-subtitle">Curated for you</div>
</div> -->
<div id="mycats-tabs" class="mycats-tabs" role="tablist" aria-label="My categories"></div>
<div id="mycats-masonry" class="mycats-masonry"></div>
</div>
<div class="brand-templates-content">
<div class="brand-grid-wrap">
<div id="brand-templates-container" class="template-grid">
<div class="brand-pagination light-theme simple-pagination">Loading Templates...</div>
</div>
</div>
</div>
</div>
<!-- Tab: Shared / Team -->
<div id="brand-tab-shared" class="brand-tab">
<div class="brand-templates-menu-wrap" style="margin-top: 10px;margin-bottom:0px!important">
<div id="shared-breadcrumb" style="font-size: 13px; color: #666; padding: 5px;">
<span data-index="0" style="cursor: pointer; font-weight: bold;">Home</span>
</div>
</div>
<div class="brand-templates-content">
<div class="brand-grid-wrap">
<div id="brand-shared-container" class="brand-grid brand-elements-grid four-column">
<div class="brand-pagination light-theme simple-pagination">Loading Shared Content...</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const API_URL = 'brandDetails.php';
const ASSETS_BASE_URL = 'https://www.thebrand.ai/wowX/teams/';
const TASWIRA_BASE_URL = 'https://www.thebrand.ai/taswira.php';
// State
const state = {
brands: [],
currentBrandId: null,
templates: [],
shared: {},
currentPath: [{id: 'root', name: 'Home'}],
isLoading: false,
context: {
creator: 'Gamer',
type: '',
interests: ''
}
};
var myCategoriesData = [];
// DOM Elements
const els = {
tabs: document.querySelectorAll('.brand-tabs-menu li'),
panes: document.querySelectorAll('.brand-tab'),
brandSelect: document.getElementById('brand-select'),
assetsContainer: document.getElementById('brand-assets-container'),
templatesContainer: document.getElementById('brand-templates-container'),
sharedContainer: document.getElementById('brand-shared-container'),
sharedBreadcrumb: document.getElementById('shared-breadcrumb'),
templateSearch: document.getElementById('template-search')
};
// Init
function init() {
bindEvents();
loadBrands();
if (window.editorContext) {
updateContext(window.editorContext);
}
window.addEventListener('editor-context-change', (e) => {
if (e.detail) updateContext(e.detail);
});
}
function updateContext(newContext) {
state.context = { ...state.context, ...newContext };
const activeTab = document.querySelector('.brand-tabs-menu li.active');
if (activeTab && activeTab.dataset.target === '#brand-tab-templates') {
loadTemplates();
}
}
// Events
function bindEvents() {
// Tab Switching
els.tabs.forEach(tab => {
tab.addEventListener('click', () => {
els.tabs.forEach(t => t.classList.remove('active'));
els.panes.forEach(p => {
p.classList.remove('active');
p.style.display = 'none';
});
tab.classList.add('active');
const targetId = tab.dataset.target.replace('#', '');
const targetPane = document.getElementById(targetId);
if (targetPane) {
targetPane.classList.add('active');
targetPane.style.display = 'block';
if (targetId === 'brand-tab-templates' && els.templatesContainer.innerHTML.includes('Loading')) {
loadTemplates();
} else if (targetId === 'brand-tab-shared' && els.sharedContainer.innerHTML.includes('Loading')) {
loadShared();
}
}
});
});
// Brand Selection
els.brandSelect.addEventListener('change', (e) => {
state.currentBrandId = e.target.value;
renderBrandAssets(state.currentBrandId);
});
// Search
let debounceTimer;
els.templateSearch.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
loadTemplates(e.target.value);
}, 500);
});
// Breadcrumb
els.sharedBreadcrumb.addEventListener('click', (e) => {
if (e.target.tagName === 'SPAN' && e.target.dataset.index) {
const index = parseInt(e.target.dataset.index);
navigateToPathIndex(index);
}
});
var mycatsTabsEl = document.getElementById('mycats-tabs');
if (mycatsTabsEl) {
mycatsTabsEl.addEventListener('click', function(e){
var btn = e.target.closest('.mycats-tab');
if(!btn) return;
var cat = btn.getAttribute('data-cat') || '';
setMyCatsActive(cat);
});
}
}
// API Helpers
function getAssetUrl(path) {
if (!path) return '';
if (path.startsWith('http')) return path;
let normalized = path.replace(/^\//, '');
// Prevent double prefixing if path already includes the base structure
if (normalized.startsWith('wowX/teams/')) {
return 'https://www.thebrand.ai/' + normalized;
}
return ASSETS_BASE_URL + normalized;
}
function getTaswiraUrl(imagePath) {
if (!imagePath) return '';
let normalizedPath = imagePath;
// Check for specific prefixes that indicate absolute paths we support
if (imagePath.startsWith('/wowX/teams/') || imagePath.startsWith('/v/uploads/gallery/')) {
normalizedPath = imagePath;
} else {
// Default legacy behavior
normalizedPath = '/v/uploads/gallery/' + imagePath.replace(/^(\/v\/uploads\/gallery\/)/, '');
}
return `${TASWIRA_BASE_URL}?width=560&height=560&quality=70&cropratio=1:1&image=${normalizedPath}`;
}
async function fetchData(params) {
try {
const query = new URLSearchParams(params).toString();
const res = await fetch(`${API_URL}?${query}`);
if (!res.ok) throw new Error('Network response was not ok');
return await res.json();
} catch (error) {
console.error('Brand API Error:', error);
return null;
}
}
// --- My Brand Logic ---
async function loadBrands() {
const data = await fetchData({ brands: 'yes' });
if (data && Array.isArray(data)) {
state.brands = data;
renderBrandSelect();
if (state.brands.length > 0) {
state.currentBrandId = state.brands[0].id;
els.brandSelect.value = state.currentBrandId;
renderBrandAssets(state.currentBrandId);
}
} else {
state.brands = [];
renderBrandSelect();
}
}
function renderBrandSelect() {
els.brandSelect.innerHTML = '<option value="" disabled>Select a Brand...</option>';
state.brands.forEach(brand => {
const opt = document.createElement('option');
opt.value = brand.id;
opt.textContent = brand.name + (brand.is_owner ? '' : ' (Shared)');
els.brandSelect.appendChild(opt);
});
}
function renderBrandAssets(brandId) {
const brand = state.brands.find(b => b.id == brandId);
if (!brand) return;
fetchBrandDetails(brandId);
}
async function fetchBrandDetails(brandId) {
els.assetsContainer.innerHTML = '<div class="brand-pagination light-theme simple-pagination">Loading Assets...</div>';
const data = await fetchData({ brandid: brandId });
// Ensure brand_id is preserved even if API returns partial data
const finalData = data || {};
if (!finalData.brand_id) finalData.brand_id = brandId;
renderAssetsUI(finalData);
}
function renderAssetsUI(brandData) {
let html = '';
const brandId = brandData.brand_id || 'shared';
const fontSuffix = `-${brandId}`;
// 1. Load Font Faces
const fonts = brandData.fonts || (brandData.brandkits ? brandData.brandkits.flatMap(k => k.fonts || []) : []);
const fontSelections = brandData.font_selections || {};
// Helper to normalize font names for comparison (remove quotes, trim, lowercase)
const normalizeFontName = (name) => (name || '').toLowerCase().replace(/['"]/g, '').trim();
// Normalize selections to lowercase for case-insensitive lookup
const normalizedSelections = {};
Object.keys(fontSelections).forEach(k => {
normalizedSelections[normalizeFontName(k)] = fontSelections[k];
});
const styleId = 'brand-custom-fonts';
let styleEl = document.getElementById(styleId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
if (fonts.length) {
let css = '';
fonts.forEach(f => {
let url = f.file_path;
if (!url.startsWith('http') && !url.startsWith('/')) {
url = getAssetUrl(url);
}
const safeName = f.font_name.replace(/'/g, "\\'");
const uniqueName = `${safeName}${fontSuffix}`;
css += `
@font-face {
font-family: '${uniqueName}';
src: url('${url}') format('truetype');
font-display: swap;
}
`;
});
styleEl.textContent = css;
} else {
styleEl.textContent = '';
}
// Helper for sections
const renderSection = (title, content) => `
<div style="margin-bottom: 20px;">
<h6 style="margin: 0 0 10px 0; font-size: 12px; text-transform: uppercase; color: #888;">${title}</h6>
<div class="brand-grid brand-elements-grid four-column" style="gap: 10px;">
${content}
</div>
</div>
`;
// Colors
const colors = brandData.colors || (brandData.brandkits ? brandData.brandkits.flatMap(k => k.colors || []) : []);
if (colors.length) {
html += `
<div style="margin-bottom: 20px;">
<h6 style="margin: 0 0 10px 0; font-size: 12px; text-transform: uppercase; color: #888;">Colors</h6>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
${colors.map(c => {
const code = c.color_code || c;
return `<div style="width: 30px; height: 30px; background-color: ${code}; border-radius: 4px; border: 1px solid #ddd; cursor: pointer;" title="${code}" onclick="copyToClipboard('${code}')"></div>`;
}).join('')}
</div>
</div>`;
}
// Font Styles
const fontStyles = brandData.font_styles || (brandData.brandkits ? brandData.brandkits.flatMap(k => k.font_styles || []) : []);
if (fontStyles.length) {
html += `
<div style="margin-bottom: 20px;">
<h6 style="margin: 0 0 10px 0; font-size: 12px; text-transform: uppercase; color: #888;">Font Styles</h6>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
${fontStyles.map(s => {
// Resolve font family from selections if available
let fontFamily = s.font_family || s.font_name || 'inherit';
let uniqueFontFamily = fontFamily;
const lookupType = normalizeFontName(s.font_type);
if (lookupType && normalizedSelections[lookupType]) {
const fontId = normalizedSelections[lookupType];
const linkedFont = fonts.find(f => f.id == fontId);
if (linkedFont) {
fontFamily = linkedFont.font_name;
const safeName = fontFamily.replace(/'/g, "\\'");
uniqueFontFamily = `${safeName}${fontSuffix}`;
}
} else {
// Try to match with loaded fonts to apply suffix
const cleanFamily = normalizeFontName(fontFamily);
const found = fonts.find(f => normalizeFontName(f.font_name) === cleanFamily);
if (found) {
const safeName = found.font_name.replace(/'/g, "\\'");
uniqueFontFamily = `${safeName}${fontSuffix}`;
}
}
return `
<div style="border: 1px solid #eee; padding: 15px; border-radius: 4px; background: #fff; display: flex; flex-direction: column;">
<div style="font-family: '${uniqueFontFamily}', sans-serif; font-size: 10px; color: #aaa; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px;">${s.font_type || 'Style'}</div>
<div style="
font-family: '${uniqueFontFamily}', sans-serif;
font-size: ${s.font_size || '16px'};
color: ${s.text_color || s.color || s.color_code || '#333'};
font-weight: ${s.font_weight || 'normal'};
line-height: ${s.line_height || '1.4'};
letter-spacing: ${s.letter_spacing || 'normal'};
text-transform: ${s.text_transform || 'none'};
">
${s.font_type || 'Sample Text'}
</div>
</div>
`}).join('')}
</div>
</div>`;
}
// Logos
const logos = brandData.logos || (brandData.brandkits ? brandData.brandkits.flatMap(k => k.logos || []) : []);
if (logos.length) {
html += renderSection('Logos', logos.map(l => {
const fullUrl = getAssetUrl(l.file_path || l.url);
return `
<div style="border: 1px solid #eee; border-radius: 4px; padding: 5px; cursor: grab; background: #fff; display: flex; align-items: center; justify-content: center; height: 80px;" draggable="true" ondragstart="handleDragStart(event, '${fullUrl}', 'image')">
<img src="${fullUrl}" style="max-width: 100%; max-height: 100%; object-fit: contain;">
</div>`;
}).join(''));
}
// Other Assets (Photos, Graphics, Icons)
['photos', 'graphics', 'icons'].forEach(type => {
const assets = brandData[type] || [];
if (assets.length) {
html += renderSection(type.charAt(0).toUpperCase() + type.slice(1), assets.map(a => {
const fullUrl = getAssetUrl(a.file_path || a.url);
return `
<div style="border: 1px solid #eee; border-radius: 4px; padding: 5px; cursor: grab; background: #fff; display: flex; align-items: center; justify-content: center; height: 80px;" draggable="true" ondragstart="handleDragStart(event, '${fullUrl}', 'image')">
<img src="${fullUrl}" style="max-width: 100%; max-height: 100%; object-fit: contain;">
</div>`;
}).join(''));
}
});
// Fonts (Typography) - Last Section
if (fonts.length) {
html += `
<div style="margin-bottom: 20px;">
<h6 style="margin: 0 0 10px 0; font-size: 12px; text-transform: uppercase; color: #888;">Typography</h6>
<div style="display: flex; flex-direction: column; gap: 5px;">
${fonts.slice(0, 10).map(f => {
const safeName = f.font_name.replace(/'/g, "\\'");
const uniqueName = `${safeName}${fontSuffix}`;
return `
<div style="font-family: '${uniqueName}', sans-serif; padding: 8px; border: 1px solid #eee; background: #fff; cursor: pointer;">
${f.font_name}
</div>
`}).join('')}
</div>
</div>`;
}
els.assetsContainer.innerHTML = html || '<div class="notice notice-info">No assets found for this brand.</div>';
}
// --- Templates Logic ---
async function loadTemplates(search = '') {
els.templatesContainer.innerHTML = '<div class="brand-pagination light-theme simple-pagination">Loading Templates...</div>';
const params = { templates: 'yes' };
if (search) params.query = search;
// Add context params
if (state.context.type) params.industry = state.context.type;
if (state.context.interests) params.interests = state.context.interests;
const data = await fetchData(params);
let templates = [];
if (data) {
if (Array.isArray(data)) {
templates = data;
} else if (data.curated_templates && Array.isArray(data.curated_templates)) {
templates = data.curated_templates;
} else if (data.templates && Array.isArray(data.templates)) {
templates = data.templates;
}
if (data.my_categories && Array.isArray(data.my_categories)) {
myCategoriesData = data.my_categories;
renderMyCategories();
}
}
renderTemplates(templates);
}
function renderTemplates(templates) {
if (!templates || templates.length === 0) {
els.templatesContainer.innerHTML = '<div class="notice notice-info">No templates found.</div>';
return;
}
// Group by category if needed, or just flat list.
// The previous implementation had categories. Let's do a simple flat grid for now as it matches standard "search results" view.
// Or if we want "Recommended", we can separate them.
let html = '';
// Filter recommended vs others if context is active?
// For simplicity and matching "app style", let's just dump them in the grid.
html = templates.map(t => {
const poster = t.post_poster || t.poster || t.url || t.image_path;
const posterUrl = getTaswiraUrl(poster);
return `
<div class="brand-template-card" onclick="loadTemplate(${t.id || t.post_id})">
<div class="brand-template-thumb">
<img src="${posterUrl}" loading="lazy">
<div class="brand-template-overlay">
<span class="brand-use-btn">Use Template</span>
</div>
</div>
<div class="brand-template-info">
<div class="brand-template-title">${t.title || t.post_title || 'Untitled'}</div>
</div>
</div>
`;
}).join('');
els.templatesContainer.innerHTML = html;
}
var mycatsState = { groups: [], active: '' };
function normKey(s){ return (s || '').toString().toLowerCase().replace(/\s+/g,' ').trim(); }
function pickRatioBadgeAndAR(label){
var nk = normKey(label);
if(nk.indexOf('story') !== -1 || nk.indexOf('tiktok') !== -1){ return {badge:'9:16', ar:'9/16'}; }
if(nk.indexOf('thumbnail') !== -1 || nk.indexOf('youtube') !== -1){ return {badge:'16:9', ar:'16/9'}; }
return {badge:'', ar:'1/1'};
}
function buildMyCategoryGroups(data){
var groups = [];
if(!data || !data.length) return groups;
for(var i=0;i<data.length;i++){
var cat = data[i];
if(!cat || !cat.children || !cat.children.length) continue;
var parent = (cat.category || '').toString().trim();
if(!parent) continue;
var seen = {};
var children = [];
for(var j=0;j<cat.children.length;j++){
var label = (cat.children[j] || '').toString().trim();
var k = normKey(label);
if(!k || seen[k]) continue;
seen[k] = true;
children.push({label: label, parent: parent});
}
if(children.length){ groups.push({category: parent, children: children}); }
}
return groups;
}
function defaultPreviewDataUrl(label, parent){
var t1 = (label || '').toString().slice(0, 34);
var t2 = (parent || '').toString().slice(0, 34);
var svg =
'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"1200\" viewBox=\"0 0 1200 1200\">' +
'<defs>' +
'<linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">' +
'<stop offset=\"0\" stop-color=\"#0f172a\" stop-opacity=\".22\"/>' +
'<stop offset=\".55\" stop-color=\"#475569\" stop-opacity=\".10\"/>' +
'<stop offset=\"1\" stop-color=\"#94a3b8\" stop-opacity=\".06\"/>' +
'</linearGradient>' +
'<linearGradient id=\"h\" x1=\"0\" y1=\"1\" x2=\"1\" y2=\"0\">' +
'<stop offset=\"0\" stop-color=\"#111827\" stop-opacity=\".70\"/>' +
'<stop offset=\"1\" stop-color=\"#111827\" stop-opacity=\"0\"/>' +
'</linearGradient>' +
'</defs>' +
'<rect width=\"1200\" height=\"1200\" fill=\"url(#g)\"/>' +
'<rect y=\"720\" width=\"1200\" height=\"480\" fill=\"url(#h)\"/>' +
'<g fill=\"#ffffff\" font-family=\"Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial\" text-rendering=\"geometricPrecision\">' +
'<text x=\"70\" y=\"940\" font-size=\"62\" font-weight=\"800\" opacity=\".98\">' + escapeHtml(t1) + '</text>' +
'<text x=\"70\" y=\"1025\" font-size=\"40\" font-weight=\"700\" opacity=\".75\">' + escapeHtml(t2) + '</text>' +
'</g>' +
'</svg>';
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
}
function escapeHtml(s){
return (s || '').toString()
.replace(/&/g,'&')
.replace(/</g,'<')
.replace(/>/g,'>')
.replace(/\"/g,'"')
.replace(/'/g,''');
}
function escapeHtmlAttr(s){ return escapeHtml(s); }
async function fetchPreviewForQuery(query){
var payload = null;
var res = await fetchData({ templates: 'yes', query: query || '' });
if(res && res.curated_templates && res.curated_templates.length){
var item = res.curated_templates[0];
payload = { title: item.post_title || item.title || '', poster: item.post_poster || item.poster || '' };
}
return payload;
}
function renderMyCatsTabs(){
var tabs = document.getElementById('mycats-tabs');
if(!tabs) return;
if(!mycatsState.groups.length){
tabs.innerHTML = '';
return;
}
var html = '';
for(var i=0;i<mycatsState.groups.length;i++){
var g = mycatsState.groups[i];
var selected = (g.category === mycatsState.active);
html += '<button type="button" class="mycats-tab" role="tab" aria-selected="'+(selected?'true':'false')+'" data-cat="'+escapeHtmlAttr(g.category)+'">';
html += escapeHtml(g.category);
html += '</button>';
}
tabs.innerHTML = html;
}
function setMyCatsActive(cat){
mycatsState.active = (cat || '').toString();
renderMyCatsTabs();
renderMyCatsGrid();
}
function renderMyCatsGrid(){
var root = document.getElementById('mycats-masonry');
if(!root) return;
var g = null;
for(var i=0;i<mycatsState.groups.length;i++){
if(mycatsState.groups[i].category === mycatsState.active){ g = mycatsState.groups[i]; break; }
}
var items = g && g.children ? g.children : [];
if(!items.length){
root.innerHTML = '<div style="padding:10px 6px;color:rgba(56,58,63,.6);font-weight:700">No categories found.</div>';
return;
}
var html = '';
for(var j=0;j<items.length;j++){
var it = items[j];
html += '<button type="button" class="mycats-btn" data-label="'+escapeHtmlAttr(it.label)+'" data-parent="'+escapeHtmlAttr(it.parent || '')+'" aria-label="'+escapeHtmlAttr(it.label)+'">';
html += escapeHtml(it.label);
html += '</button>';
}
root.innerHTML = html;
}
// Event Listener for My Categories Labels
(function(){
var grid = document.getElementById('mycats-masonry');
if(grid){
grid.addEventListener('click', function(e){
var btn = e.target.closest('.mycats-btn');
if(!btn) return;
var q = btn.getAttribute('data-label') || '';
if(!q) return;
var inp = document.getElementById('template-search');
if(inp){ inp.value = q; }
loadTemplates(q);
});
}
})();
function renderMyCategories(){
mycatsState.groups = buildMyCategoryGroups(myCategoriesData || []);
mycatsState.active = (mycatsState.groups[0] && mycatsState.groups[0].category) ? mycatsState.groups[0].category : '';
renderMyCatsTabs();
renderMyCatsGrid();
}
// --- Shared / Team Logic ---
async function loadShared() {
renderSharedContent();
}
async function navigateToPathIndex(index) {
state.currentPath = state.currentPath.slice(0, index + 1);
renderBreadcrumb();
renderSharedContent();
}
async function openFolder(folderId, folderName) {
state.currentPath.push({ id: folderId, name: folderName });
renderBreadcrumb();
renderSharedContent();
}
function renderBreadcrumb() {
els.sharedBreadcrumb.innerHTML = state.currentPath.map((item, index) => `
<span data-index="${index}" style="${index === state.currentPath.length - 1 ? 'font-weight: bold; color: #333; cursor: default;' : 'cursor: pointer; color: #666;'}">${item.name}</span>
${index < state.currentPath.length - 1 ? '<span style="margin: 0 5px; color: #ccc;">/</span>' : ''}
`).join('');
}
async function renderSharedContent() {
els.sharedContainer.innerHTML = '<div class="brand-pagination light-theme simple-pagination">Loading...</div>';
const currentFolderId = state.currentPath[state.currentPath.length - 1].id;
let params = {};
if (currentFolderId === 'root') {
params = { shared: 'yes' };
} else {
params = { folder: currentFolderId };
}
const data = await fetchData(params);
if (!data) {
els.sharedContainer.innerHTML = '<div class="notice notice-info">Failed to load content.</div>';
return;
}
let html = '';
// Handle Root (Shared) View
if (params.shared) {
console.log('Shared Data:', data);
// Folders
if (data.folders && data.folders.length) {
html += data.folders.map(f => {
const id = f.folder_id || f.id;
const name = f.folder_name || f.name || 'Untitled Folder';
const safeName = name.replace(/'/g, "\\'");
return `
<div onclick="openFolder('${id}', '${safeName}')" style="cursor: pointer; position: relative; aspect-ratio: 1; background: #fdfdfd; border: 1px solid #eee; border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<span class="material-icons" style="font-size: 48px; color: #fbbf24;">folder</span>
<div style="font-size: 11px; margin-top: 5px; max-width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #555;">${name}</div>
</div>
`}).join('');
}
// Templates (Shared Files at root)
if (data.templates && data.templates.length) {
html += data.templates.map(f => {
const thumb = getTaswiraUrl(f.poster || f.url);
return `
<div style="cursor: grab; position: relative; aspect-ratio: 1; background: #f9f9f9; border-radius: 4px; overflow: hidden;" draggable="true" ondragstart="handleDragStart(event, '${thumb}', 'image')">
<img src="${thumb}" style="width: 100%; height: 100%; object-fit: cover;">
</div>
`;
}).join('');
}
}
// Handle Folder View
else if (params.folder) {
// Combine designs and stock images for uniform rendering
const items = [
...(data.designs || []).map(d => ({ ...d, type: 'design' })),
...(data.stock_images || []).map(s => ({ ...s, type: 'stock' })),
// Also handle 'photo' type if present in the response
...(data.photos || []).map(p => ({ ...p, type: 'photo' }))
];
if (items.length) {
html += items.map(f => {
// Prioritize the correct property for each type to ensure valid thumbnail
let thumbPath = f.full_path || f.file_path || f.poster || f.url;
// If it's a design/template, it might have 'poster' or 'image_path'
if (f.type === 'design') {
thumbPath = f.full_path || f.poster || f.post_poster || f.image_path || f.file_path;
}
// If it's a stock/photo, it usually has 'file_path' or 'url'
else if (f.type === 'stock' || f.type === 'photo') {
thumbPath = f.full_path || f.file_path || f.url;
}
const thumb = getTaswiraUrl(thumbPath);
const fullUrl = getAssetUrl(thumbPath);
return `
<div class="folder-share-pick" style="cursor: pointer; position: relative; aspect-ratio: 1; background: #f9f9f9; border-radius: 4px; overflow: hidden;" draggable="true" ondragstart="handleDragStart(event, '${fullUrl}', 'image')">
<img src="${thumb}" data-full="${fullUrl}" style="width: 100%; height: 100%; object-fit: cover;">
</div>
`;
}).join('');
}
}
if (!html) {
html = '<div class="notice notice-info" style="grid-column: 1/-1;">This folder is empty.</div>';
}
els.sharedContainer.innerHTML = html;
// Expose openFolder to global scope for onclick attributes
window.openFolder = openFolder;
}
// Global Helpers
window.handleDragStart = function(e, url, type) {
e.dataTransfer.setData('text/plain', url);
e.dataTransfer.setData('type', type);
};
window.copyToClipboard = function(text) {
navigator.clipboard.writeText(text);
// Toast notification could go here
};
window.loadTemplate = function(id) {
const event = new CustomEvent('template-load-request', { detail: { id } });
window.dispatchEvent(event);
};
// Run
init();
})();
</script>