Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ export function App() {
};
};

// Accordion state for quick access in Studio sidebar
const [openStudioPanel, setOpenStudioPanel] = useState<'mockup' | 'ai' | 'gradient' | 'font' | 'logo' | 'image' | 'konva' | 'chart' | null>(null);

// Logo Config State
const [logoConfig, setLogoConfig] = useState<ZhaketLogoConfig>(DEFAULT_LOGO_CONFIG);

Expand Down Expand Up @@ -271,7 +268,7 @@ export function App() {
const [exportProgressText, setExportProgressText] = useState<string>('');
const [isFullscreenPreview, setIsFullscreenPreview] = useState(false);
const [isLivePreviewOpen, setIsLivePreviewOpen] = useState(false);
const [sidebarActiveTab, setSidebarActiveTab] = useState<'layers' | 'badges' | 'logo' | 'gradient' | 'typography' | 'image' | 'ai' | 'konva' | 'chart'>('layers');
const [sidebarActiveTab, setSidebarActiveTab] = useState<'layers' | 'mockup' | 'badges' | 'logo' | 'gradient' | 'typography' | 'image' | 'ai' | 'konva' | 'chart'>('layers');

// Precision Dragging & Snapping Settings
const [enableMagneticSnap, setEnableMagneticSnap] = useState(true);
Expand Down Expand Up @@ -566,6 +563,16 @@ export function App() {

const handleAddMockupLayer = (mockupConfig: Partial<CanvasLayerItem>) => {
const newId = `mockup-${Date.now()}`;
const mockupType = mockupConfig.data?.mockupType || 'mobile';
const mockupSize = {
mobile: { width: 128, height: 256 },
tablet: { width: 192, height: 256 },
laptop: { width: 256, height: 154 },
desktop: { width: 256, height: 216 },
browser: { width: 240, height: 176 },
box3d: { width: 144, height: 192 },
}[mockupType];
const { displayWidth, displayHeight } = getWorkspaceDisplaySize(targetPlatform, activeAsset);
const newLayer: CanvasLayerItem = {
id: newId,
name: mockupConfig.name || 'موکاپ جدید',
Expand All @@ -576,8 +583,8 @@ export function App() {
scale: mockupConfig.scale || 0.9,
rotation: mockupConfig.rotation || 0,
opacity: mockupConfig.opacity || 100,
x: 140,
y: 70,
x: Math.max(0, Math.round((displayWidth - mockupSize.width) / 2)),
y: Math.max(0, Math.round((displayHeight - mockupSize.height) / 2)),
Comment on lines +586 to +587
data: mockupConfig.data || {},
};

Expand Down Expand Up @@ -2658,9 +2665,10 @@ export function App() {
<div className="space-y-3">

{/* Photoshop Studio Dock Tab Navigation Bar */}
<div className="bg-slate-900 border border-slate-800 p-1.5 rounded-2xl flex flex-wrap items-center gap-1 shadow-xl" role="tablist" aria-label="پنل‌های استودیو">
<div className="bg-slate-900 border border-slate-800 p-1.5 rounded-2xl flex flex-nowrap items-center gap-1 overflow-x-auto custom-scrollbar shadow-xl" role="group" aria-label="پنل‌های استودیو">
{[
{ id: 'layers', label: 'لایه‌ها', icon: Layers, color: 'text-amber-400' },
{ id: 'mockup', label: 'موکاپ‌ها', icon: Smartphone, color: 'text-indigo-400' },
{ id: 'badges', label: 'بج‌های ژاکت', icon: ShieldCheck, color: 'text-emerald-400' },
{ id: 'logo', label: 'لوگو و کادر', icon: ShoppingBag, color: 'text-orange-400' },
{ id: 'gradient', label: 'گرادیانت', icon: Palette, color: 'text-purple-400' },
Expand All @@ -2677,7 +2685,8 @@ export function App() {
key={tab.id}
type="button"
onClick={() => setSidebarActiveTab(tab.id as any)}
className={`px-2.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 whitespace-nowrap shrink-0 ${
aria-pressed={isActive}
className={`min-h-10 px-3 rounded-xl text-xs font-bold transition-colors flex items-center gap-1.5 whitespace-nowrap shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 ${
isActive
? 'bg-amber-500 text-slate-950 font-black shadow-md'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/80'
Expand Down Expand Up @@ -2742,6 +2751,12 @@ export function App() {
</div>
)}

{sidebarActiveTab === 'mockup' && (
<div className="animate-in fade-in duration-150">
<MockupComposer onAddMockup={handleAddMockupLayer} />
Comment on lines +2754 to +2756
Comment on lines +2754 to +2756
Comment on lines +2754 to +2756

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent mockups from being added to infographic assets

When the active asset is infographic, this unconditionally exposes the composer and handleAddMockupLayer appends the new layer to canvasLayers.infographic, but the infographic workspace and its export stage render only the infographic rows and stickers—not canvasLayers.infographic through DynamicLayerRenderer. The user receives a success toast, yet the mockup is absent from both preview and exported infographic; disable this panel for that asset or render the added layers there.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the selected 3D tilt to new mockups

The restored composer persists data.tilt3D, but the rendering path wired here is DynamicLayerRenderer, which never reads that property and always uses its ordinary rotate/scale transform. Consequently toggling between isometric and flat before adding any mockup produces identical output; pass this flag into the rendered transform or use the dedicated mockup renderer that implements it.

Useful? React with 👍 / 👎.

</div>
)}

{/* TAB 2: ZHAKET OFFICIAL BADGES DIRECT CATALOG */}
{sidebarActiveTab === 'badges' && (
<div className="p-4 bg-slate-900 border border-emerald-500/30 rounded-3xl space-y-3.5 shadow-xl animate-in fade-in duration-150">
Expand Down
34 changes: 34 additions & 0 deletions src/components/DynamicLayerRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,40 @@ export const DynamicLayerRenderer: React.FC<DynamicLayerRendererProps> = ({
);
}

if (mockupType === 'desktop') {
return (
<div className="flex flex-col items-center w-64 select-none">
<div className={`w-full h-44 rounded-2xl p-2 border-4 shadow-2xl backdrop-blur-md ${frameBg}`}>
<div className="w-full h-full rounded-xl overflow-hidden bg-slate-950">
{renderScreen()}
</div>
</div>
<div className="w-6 h-8 bg-slate-700 border-x border-slate-600" />
<div className="w-24 h-2 bg-slate-600 rounded-full shadow-md" />
</div>
);
}

if (mockupType === 'browser') {
return (
<div className="w-60 h-44 rounded-2xl border-2 border-slate-700 bg-slate-900 shadow-2xl overflow-hidden flex flex-col select-none">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor frame colors on browser mockups

For browser mockups, selecting silver, gold, or midnight in the composer has no visual effect: this new frame hardcodes bg-slate-900 border-slate-700 and does not apply the computed frameBg classes. Apply frameBg to the browser shell so its persisted frame-color setting is reflected.

Useful? React with 👍 / 👎.

<div className="bg-slate-800 px-3 py-1.5 flex items-center justify-between border-b border-slate-700">
<div className="flex items-center gap-1.5" aria-hidden="true">
<span className="w-2 h-2 rounded-full bg-rose-500" />
<span className="w-2 h-2 rounded-full bg-amber-500" />
<span className="w-2 h-2 rounded-full bg-emerald-500" />
</div>
<span className="bg-slate-950 px-3 py-0.5 rounded text-[8px] text-slate-400 font-mono">
https://yourstore.com
</span>
</div>
<div className="flex-1 min-h-0 bg-slate-950">
{renderScreen()}
</div>
</div>
);
}

return (
<div className="w-36 h-48 rounded-xl bg-gradient-to-br from-amber-600 via-amber-700 to-amber-900 border-2 border-amber-400 p-3 flex flex-col justify-between shadow-2xl text-white">
<Box className="w-5 h-5 text-amber-300" />
Expand Down
27 changes: 16 additions & 11 deletions src/components/MockupComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
</div>
<div>
<h4 className="font-bold text-white text-xs">کامپوزر موکاپ‌های سه‌بعدی</h4>
<p className="text-[10px] text-slate-400">افزودن انواع فریم گوشی، تبلت، لپ‌تاپ و باکس محصول به بوم</p>
<p className="text-[11px] leading-5 text-slate-300">افزودن فریم گوشی، تبلت، لپ‌تاپ، دسکتاپ، مرورگر و باکس سه‌بعدی به کاور</p>
</div>
</div>

Expand All @@ -97,11 +97,13 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<button
key={m.id}
type="button"
aria-pressed={isSelected}
title={m.title}
onClick={() => {
setSelectedType(m.id);
setScale(m.defaultScale);
}}
className={`p-2 rounded-xl border text-right transition-all flex flex-col gap-1 ${
className={`min-h-14 p-2.5 rounded-xl border text-right transition-colors flex flex-col justify-between gap-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 ${
isSelected
? 'bg-indigo-600 text-white border-indigo-400 shadow-md font-bold'
: 'bg-slate-900 text-slate-300 border-slate-800 hover:bg-slate-800/80 hover:text-white'
Expand All @@ -111,7 +113,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<Icon className={`w-4 h-4 ${isSelected ? 'text-white' : 'text-indigo-400'}`} />
{isSelected && <Check className="w-3 h-3 text-white" />}
</div>
<span className="text-[10px] truncate">{m.title.split(' ')[0]} {m.title.split(' ')[1] || ''}</span>
<span className="text-[11px] leading-4">{m.title.split(' ')[0]} {m.title.split(' ')[1] || ''}</span>
</button>
);
})}
Expand All @@ -134,7 +136,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<button
type="button"
onClick={() => setScreenPreview(null)}
className="text-[10px] text-rose-400 hover:text-rose-300 flex items-center gap-1"
className="min-h-10 px-2 text-[11px] text-rose-300 hover:text-rose-200 flex items-center gap-1 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-400"
>
<Trash2 className="w-3 h-3" />
<span>حذف تصویر</span>
Expand All @@ -160,7 +162,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="w-full py-2.5 px-3 rounded-xl border border-dashed border-indigo-500/40 hover:border-indigo-400 bg-indigo-500/5 hover:bg-indigo-500/10 text-indigo-300 text-[11px] font-bold flex items-center justify-center gap-1.5 transition-all"
className="w-full min-h-11 py-2.5 px-3 rounded-xl border border-dashed border-indigo-500/40 hover:border-indigo-400 bg-indigo-500/5 hover:bg-indigo-500/10 text-indigo-200 text-[11px] font-bold flex items-center justify-center gap-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300"
>
<Upload className="w-3.5 h-3.5" />
<span>آپلود اسکرین‌شات / عکس دلخواه برای صفحه موکاپ</span>
Expand All @@ -171,7 +173,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
{/* 3. Frame Style & Perspective */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[10px] text-slate-400 mb-1 block">رنگ فریم:</label>
<label className="text-[10px] text-slate-300 mb-1 block">رنگ فریم:</label>
<div className="grid grid-cols-4 gap-1">
{[
{ id: 'dark', label: 'تیره', color: '#0f172a' },
Expand All @@ -182,8 +184,10 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<button
key={c.id}
type="button"
aria-label={`رنگ فریم ${c.label}`}
aria-pressed={frameColor === c.id}
onClick={() => setFrameColor(c.id as any)}
className={`h-7 rounded-lg border flex items-center justify-center transition-all ${
className={`min-h-11 rounded-lg border flex items-center justify-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-300 ${
frameColor === c.id ? 'border-amber-400 ring-2 ring-amber-500/50' : 'border-slate-800'
}`}
style={{ backgroundColor: c.color }}
Expand All @@ -194,14 +198,15 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
</div>

<div>
<label className="text-[10px] text-slate-400 mb-1 block">زاویه سه‌بعدی:</label>
<label className="text-[10px] text-slate-300 mb-1 block">زاویه سه‌بعدی:</label>
<button
type="button"
aria-pressed={tilt3D}
onClick={() => setTilt3D(!tilt3D)}
className={`w-full h-7 rounded-lg text-[10px] font-bold border transition-all flex items-center justify-center gap-1 ${
className={`w-full min-h-11 rounded-lg text-[11px] font-bold border transition-colors flex items-center justify-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-300 ${
tilt3D
? 'bg-amber-500 text-slate-950 border-amber-400 font-black'
: 'bg-slate-900 text-slate-400 border-slate-800'
: 'bg-slate-900 text-slate-300 border-slate-800'
}`}
>
<span>{tilt3D ? 'پرسپکتیو ایزومتریک' : 'تخت ۲ بعدی (Flat)'}</span>
Expand All @@ -213,7 +218,7 @@ export const MockupComposer: React.FC<MockupComposerProps> = ({ onAddMockup }) =
<button
type="button"
onClick={handleCreateAndAdd}
className="w-full py-2.5 px-4 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 text-white font-black text-xs shadow-lg shadow-indigo-600/20 flex items-center justify-center gap-2 transition-all transform active:scale-95"
className="w-full min-h-11 py-2.5 px-4 rounded-xl bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-400 hover:to-orange-400 text-slate-950 font-black text-xs shadow-lg shadow-amber-600/20 flex items-center justify-center gap-2 transition-colors active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-300 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950"
>
<Plus className="w-4 h-4" />
<span>افزودن این موکاپ به لایه‌های بوم</span>
Expand Down
Loading