From 926ed05a8898fabcc5eaef5554f137c24004aea8 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 27 May 2026 08:52:05 +0000 Subject: [PATCH] Initial import --- README.md | 18 + code.js | 1334 +++++++++++++++++++++++++++++++++++++++++++++++++ manifest.json | 8 + ui.html | 199 ++++++++ 4 files changed, 1559 insertions(+) create mode 100644 README.md create mode 100644 code.js create mode 100644 manifest.json create mode 100644 ui.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa781e1 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# BeautyHub Dashboard - Figma Plugin + +## طريقة التشغيل + +### الخطوة 1: فتح Figma +1. افتح ملف Figma على هذا الرابط: + https://www.figma.com/design/HHS8Q5SC44oGxQtyzmmhW4/Beauty-Hub-Design?node-id=185-1652 + +### الخطوة 2: تثبيت البلغ-إن +1. في Figma، اذهب إلى: **Menu (≡) → Plugins → Development → Import plugin from manifest...** +2. اختر ملف `manifest.json` من مجلد `figma-plugin` + +### الخطوة 3: تشغيل البلغ-إن +1. في Figma، اذهب إلى: **Menu → Plugins → Development → BeautyHub Dashboard Generator** +2. اضغط زر **"إنشاء جميع الصفحات"** +3. انتظر حتى تكتمل العملية + +سيتم إنشاء 11 إطار تصميم كامل في صفحة "Dashboard UI" diff --git a/code.js b/code.js new file mode 100644 index 0000000..8412414 --- /dev/null +++ b/code.js @@ -0,0 +1,1334 @@ +// BeautyHub – Figma Plugin v3 (Enhanced Design + Auth + Onboarding) +figma.showUI(__html__, { width: 360, height: 600 }); + +// ── Palette ────────────────────────────────────────────────────── +const C = { + wine: { r: 0.482, g: 0.176, b: 0.294 }, + wineDark: { r: 0.310, g: 0.090, b: 0.180 }, + wineLight: { r: 0.600, g: 0.290, b: 0.420 }, + wine50: { r: 0.976, g: 0.941, b: 0.953 }, + wine100: { r: 0.941, g: 0.839, b: 0.878 }, + gold: { r: 0.831, g: 0.659, b: 0.325 }, + goldDark: { r: 0.690, g: 0.541, b: 0.239 }, + goldLight: { r: 0.960, g: 0.890, b: 0.720 }, + cream: { r: 0.961, g: 0.941, b: 0.922 }, + creamDark: { r: 0.898, g: 0.867, b: 0.839 }, + white: { r: 1, g: 1, b: 1 }, + bg: { r: 0.973, g: 0.961, b: 0.949 }, + textPrimary: { r: 0.133, g: 0.071, b: 0.110 }, + textSecond: { r: 0.310, g: 0.220, b: 0.275 }, + textMuted: { r: 0.520, g: 0.430, b: 0.480 }, + success: { r: 0.200, g: 0.580, b: 0.400 }, + successBg: { r: 0.880, g: 0.960, b: 0.920 }, + warning: { r: 0.820, g: 0.600, b: 0.120 }, + warningBg: { r: 0.990, g: 0.960, b: 0.860 }, + danger: { r: 0.780, g: 0.220, b: 0.220 }, + dangerBg: { r: 0.990, g: 0.890, b: 0.890 }, + border: { r: 0.880, g: 0.850, b: 0.830 }, + borderLight: { r: 0.942, g: 0.922, b: 0.910 }, +}; + +const FW = 1440, FH = 900, SW = 280, TH = 72, CW = FW - SW; + +function safe(s) { + return String(s).replace(/[^\u0000-\uFFFF]/g, '') || ' '; +} + +async function loadFonts() { + for (const style of ["Regular", "Medium", "Semi Bold", "Bold"]) { + await figma.loadFontAsync({ family: "Inter", style }); + } +} + +// ── Primitives ──────────────────────────────────────────────────── + +function R(p, x, y, w, h, col, a, r) { + const n = figma.createRectangle(); + n.x = x; n.y = y; + n.resize(Math.max(w, 1), Math.max(h, 1)); + n.fills = [{ type: "SOLID", color: col || C.white, opacity: a !== undefined ? a : 1 }]; + n.cornerRadius = r || 0; + n.strokes = []; n.strokeWeight = 0; + if (p) p.appendChild(n); + return n; +} + +async function T(p, x, y, w, h, chars, size, col, weight, align) { + const style = weight || "Regular"; + await figma.loadFontAsync({ family: "Inter", style }); + const n = figma.createText(); + n.fontName = { family: "Inter", style }; + n.fontSize = size || 12; + n.characters = safe(chars); + n.fills = [{ type: "SOLID", color: col || C.textPrimary }]; + n.textAlignHorizontal = align || "RIGHT"; + n.textAlignVertical = "CENTER"; + n.textAutoResize = "NONE"; + n.x = x; n.y = y; + n.resize(Math.max(w, 20), Math.max(h, 14)); + if (p) p.appendChild(n); + return n; +} + +function F(p, x, y, w, h, col, a, r) { + const n = figma.createFrame(); + n.x = x; n.y = y; + n.resize(Math.max(w, 1), Math.max(h, 1)); + n.fills = [{ type: "SOLID", color: col || C.white, opacity: a !== undefined ? a : 1 }]; + n.cornerRadius = r || 0; + n.clipsContent = true; + n.strokes = []; n.strokeWeight = 0; + if (p) p.appendChild(n); + return n; +} + +// ── Card (elevated) ─────────────────────────────────────────────── +function card(p, x, y, w, h, r) { + const n = F(p, x, y, w, h, C.white, 1, r || 20); + n.effects = [{ + type: "DROP_SHADOW", + color: { r: 0.200, g: 0.080, b: 0.150, a: 0.08 }, + offset: { x: 0, y: 4 }, radius: 24, spread: -4, + visible: true, blendMode: "NORMAL" + }]; + n.strokes = [{ type: "SOLID", color: C.borderLight }]; + n.strokeWeight = 1; n.strokeAlign = "INSIDE"; + return n; +} + +// Soft card (lighter shadow) +function softCard(p, x, y, w, h, r) { + const n = F(p, x, y, w, h, C.white, 1, r || 16); + n.effects = [{ + type: "DROP_SHADOW", + color: { r: 0.200, g: 0.080, b: 0.150, a: 0.05 }, + offset: { x: 0, y: 2 }, radius: 12, spread: 0, + visible: true, blendMode: "NORMAL" + }]; + n.strokes = [{ type: "SOLID", color: C.borderLight }]; + n.strokeWeight = 1; n.strokeAlign = "INSIDE"; + return n; +} + +// ── Input field ─────────────────────────────────────────────────── +async function inputField(p, x, y, w, label, val) { + await T(p, p.width - x - 180, y - 20, 180, 18, label, 11, C.textMuted, "Medium"); + const b = F(p, x, y, w, 48, C.white, 1, 12); + b.strokes = [{ type: "SOLID", color: C.border }]; + b.strokeWeight = 1.5; b.strokeAlign = "INSIDE"; + await T(b, 16, 0, w - 32, 48, val, 13, C.textPrimary, "Regular", "LEFT"); + return b; +} + +// Active input (focused blue border replaced with wine) +async function activeInput(p, x, y, w, label, val) { + await T(p, p.width - x - 180, y - 20, 180, 18, label, 11, C.wine, "Medium"); + const b = F(p, x, y, w, 48, C.wine50, 1, 12); + b.strokes = [{ type: "SOLID", color: C.wine }]; + b.strokeWeight = 2; b.strokeAlign = "INSIDE"; + await T(b, 16, 0, w - 32, 48, val, 13, C.textPrimary, "Regular", "LEFT"); + return b; +} + +// ── Topbar ──────────────────────────────────────────────────────── +async function topbar(p, title, sub) { + const tb = F(p, 0, 0, CW, TH, C.white); + tb.effects = [{ + type: "DROP_SHADOW", + color: { r: 0.200, g: 0.080, b: 0.150, a: 0.04 }, + offset: { x: 0, y: 2 }, radius: 8, spread: 0, + visible: true, blendMode: "NORMAL" + }]; + // Right: title + sub + await T(tb, CW - 24, 10, 340, 28, title, 20, C.textPrimary, "Bold"); + await T(tb, CW - 24, 40, 280, 18, sub, 12, C.textMuted); + // Left icons area + // Notification bell (circle + dot) + R(tb, 58, 20, 32, 32, C.cream, 1, 16); + R(tb, 62, 24, 24, 24, C.creamDark, 1, 12); + R(tb, 80, 18, 8, 8, C.wine, 1, 4); + // Avatar + R(tb, 16, 16, 36, 36, C.wine100, 1, 18); + await T(tb, 16, 16, 36, 36, "\u0633", 15, C.wine, "Bold", "CENTER"); + // Separator + R(tb, 0, TH - 1, CW, 1, C.borderLight); +} + +// ── Sidebar ─────────────────────────────────────────────────────── +async function sidebar(p, active) { + const labels = [ + "\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629", + "\u0627\u0644\u0645\u0648\u0627\u0639\u064a\u062f", + "\u0627\u0644\u062e\u062f\u0645\u0627\u062a", + "\u0627\u0644\u0639\u0645\u0644\u0627\u0621", + "\u0627\u0644\u0645\u0648\u0638\u0641\u0648\u0646", + "\u0627\u0644\u0645\u062e\u0632\u0648\u0646", + "\u0627\u0644\u0645\u062a\u062c\u0631", + "\u0627\u0644\u062a\u0641\u0627\u0639\u0644", + "\u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0635\u062d\u064a", + "\u0627\u0644\u062a\u0642\u0627\u0631\u064a\u0631", + "\u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", + ]; + const sb = F(p, FW - SW, 0, SW, FH, C.wineDark); + + // Subtle top gradient effect (darker top) + R(sb, 0, 0, SW, 200, C.wineDark, 0.4); + + // Decorative circles + R(sb, SW - 80, -40, 120, 120, C.white, 0.03, 60); + R(sb, -30, FH - 130, 100, 100, C.white, 0.03, 50); + + // Logo area + const logo = F(sb, 20, 20, SW - 40, 64, C.white, 0.05, 16); + R(logo, 16, 12, 40, 40, C.gold, 1, 12); + // "B" in logo + await T(logo, 16, 12, 40, 40, "B", 20, C.wineDark, "Bold", "CENTER"); + await T(logo, 64, 14, SW - 100, 20, "BeautyHub", 15, C.white, "Bold", "LEFT"); + await T(logo, 64, 36, SW - 100, 16, "\u0644\u0648\u062d\u0629 \u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0635\u0627\u0644\u0648\u0646", 10, { r:1,g:1,b:1 }, undefined, undefined, "LEFT"); + + R(sb, 20, 96, SW - 40, 1, C.white, 0.10); + + // Nav items + for (let i = 0; i < labels.length; i++) { + const iy = 108 + i * 58; + const isA = i === active; + if (isA) { + // Active bg + const activeBg = F(sb, 12, iy, SW - 24, 46, C.white, 0.12, 12); + // Gold left indicator + R(sb, SW - 8, iy + 9, 4, 28, C.gold, 1, 2); + } + // Icon block + R(sb, SW - 50, iy + 11, 24, 24, C.white, isA ? 0.20 : 0.07, 8); + // Icon inner dot + R(sb, SW - 44, iy + 17, 12, 12, C.white, isA ? 0.90 : 0.40, 6); + await T(sb, 20, iy + 6, SW - 78, 34, labels[i], 13, C.white, isA ? "Semi Bold" : "Regular", "LEFT"); + } + + // Divider + R(sb, 20, FH - 110, SW - 40, 1, C.white, 0.10); + + // User card + const uc = F(sb, 12, FH - 100, SW - 24, 84, C.white, 0.07, 16); + R(uc, SW - 60, 16, 44, 44, C.gold, 0.20, 22); + await T(uc, SW - 60, 16, 44, 44, "\u0633", 18, C.gold, "Bold", "CENTER"); + await T(uc, 16, 16, SW - 90, 20, "\u0635\u0627\u0644\u0648\u0646 \u0633\u0627\u0631\u0629", 13, C.white, "Semi Bold", "LEFT"); + await T(uc, 16, 38, SW - 90, 16, "\u0645\u0633\u0624\u0648\u0644", 10, { r:1,g:1,b:1 }, undefined, undefined, "LEFT"); + await T(uc, 16, 58, SW - 90, 14, "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062e\u0631\u0648\u062c", 10, { r:1,g:1,b:1 }, undefined, undefined, "LEFT"); +} + +// ── Stat Card ───────────────────────────────────────────────────── +async function statCard(p, x, y, w, lbl, val, chg, accent, positive) { + const c = card(p, x, y, w, 108, 20); + // Top accent line + R(c, 0, 0, w, 4, accent, 1, 0); + // Icon circle (right side) + R(c, w - 56, 20, 44, 44, accent, 0.10, 22); + R(c, w - 48, 28, 28, 28, accent, 0.20, 14); + // Value + await T(c, 20, 14, w - 80, 32, val, 22, C.textPrimary, "Bold", "LEFT"); + // Label + await T(c, 20, 48, w - 80, 20, lbl, 12, C.textMuted, "Regular", "LEFT"); + // Change badge + const bCol = positive === false ? C.dangerBg : C.successBg; + const tCol = positive === false ? C.danger : C.success; + R(c, 20, 74, 130, 22, bCol, 1, 11); + await T(c, 20, 74, 130, 22, chg, 10, tCol, "Medium", "LEFT"); +} + +// ── Card header ─────────────────────────────────────────────────── +async function cardHeader(p, w, title, action) { + await T(p, w - 20, 14, w - 130, 28, title, 14, C.textPrimary, "Bold"); + if (action) { + R(p, 16, 16, 90, 28, C.wine50, 1, 14); + await T(p, 16, 16, 90, 28, action, 11, C.wine, "Medium", "CENTER"); + } + R(p, 16, 56, w - 32, 1, C.borderLight); +} + +// ── Table row ───────────────────────────────────────────────────── +async function tableRow(p, y, cells, rh, isHdr) { + if (isHdr) R(p, 0, y, p.width, rh, C.bg, 1, 0); + else if (y % (rh * 2) === rh) R(p, 0, y, p.width, rh, C.white, 1); + R(p, 16, y + rh - 1, p.width - 32, 1, C.borderLight); + const cw = Math.floor((p.width - 32) / cells.length); + for (let i = 0; i < cells.length; i++) { + const cx = p.width - 16 - (i + 1) * cw; + await T(p, cx, y + 2, cw - 8, rh - 4, cells[i], isHdr ? 11 : 12, + isHdr ? C.textMuted : C.textSecond, isHdr ? "Medium" : "Regular"); + } +} + +// ── Search bar ──────────────────────────────────────────────────── +async function searchBar(p, x, y, w, ph) { + const b = F(p, x, y, w, 44, C.bg, 1, 12); + b.strokes = [{ type: "SOLID", color: C.border }]; + b.strokeWeight = 1; b.strokeAlign = "INSIDE"; + R(b, w - 34, 12, 20, 20, C.textMuted, 0.25, 5); + R(b, w - 30, 16, 12, 12, C.textMuted, 0.20, 6); + await T(b, 12, 0, w - 50, 44, ph, 12, C.textMuted, "Regular", "LEFT"); +} + +// ── Bar chart ───────────────────────────────────────────────────── +function bars(p, x, y, w, h, vals, col) { + const v = vals || [55, 72, 45, 88, 62, 78, 58]; + const bw = Math.floor((w - (v.length - 1) * 6) / v.length); + for (let i = 0; i < v.length; i++) { + const bh = Math.max(Math.floor(v[i] * (h - 28) / 100), 4); + const alpha = 0.45 + i * 0.08; + R(p, x + i * (bw + 6), y + h - 24 - bh, bw, bh, col || C.wine, Math.min(alpha, 0.95), 4); + // Top rounded cap + R(p, x + i * (bw + 6), y + h - 24 - bh, bw, 4, col || C.wine, Math.min(alpha, 0.95), 2); + } +} + +// ── Progress bar ───────────────────────────────────────────────── +async function progBar(p, x, y, w, pct, lbl, cnt) { + await T(p, x + w - 200, y, 200, 20, lbl, 12, C.textPrimary, "Medium"); + const badge = F(p, x, y + 2, 52, 16, C.wine50, 1, 8); + await T(badge, 4, 0, 44, 16, cnt + " \u062d\u062c\u0632", 10, C.wine, "Medium", "LEFT"); + R(p, x, y + 28, w, 6, C.borderLight, 1, 3); + const fw = Math.max(Math.floor(w * pct / 100), 6); + const barF = F(p, x + w - fw, y + 28, fw, 6, C.wine, 1, 3); +} + +// ── Tag / Badge ─────────────────────────────────────────────────── +async function badge(p, x, y, text, type) { + const cols = { success: [C.successBg, C.success], danger: [C.dangerBg, C.danger], warning: [C.warningBg, C.warning], wine: [C.wine50, C.wine], neutral: [C.cream, C.textMuted] }; + const [bg, tx] = cols[type] || cols.neutral; + R(p, x, y, 76, 22, bg, 1, 11); + await T(p, x, y, 76, 22, text, 10, tx, "Medium", "CENTER"); +} + +// ── Dot indicator ───────────────────────────────────────────────── +function dot(p, x, y, active) { + R(p, x, y, active ? 24 : 8, 8, active ? C.wine : C.creamDark, 1, 4); +} + +// ── Page builder ────────────────────────────────────────────────── +async function buildPage(name, xi, navIdx, fn) { + const frame = figma.createFrame(); + frame.name = name; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.bg }]; + frame.clipsContent = true; + const content = F(frame, 0, 0, CW, FH, C.bg); + content.name = "Content"; + content.clipsContent = false; + await fn(content); + await sidebar(frame, navIdx); + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// ONBOARDING 1 — مرحبا +// ═══════════════════════════════════════════════════════════════════ +async function pOnboard1(xi) { + const frame = figma.createFrame(); + frame.name = "\u0646\u0645\u0648\u0630\u062c 01 - \u0645\u0631\u062d\u0628\u0627"; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.bg }]; + frame.clipsContent = true; + + // Left panel — brand + const lp = F(frame, 0, 0, 660, FH, C.wineDark); + // Decorative circles + R(lp, -80, -80, 280, 280, C.white, 0.04, 140); + R(lp, 500, 600, 220, 220, C.white, 0.04, 110); + R(lp, 300, 200, 160, 160, C.white, 0.03, 80); + R(lp, 60, 520, 100, 100, C.gold, 0.08, 50); + // Center content + const logoBox = F(lp, 230, 120, 200, 200, C.white, 0.08, 100); + R(lp, 255, 145, 150, 150, C.gold, 0.15, 75); + await T(lp, 230, 120, 200, 200, "BH", 56, C.white, "Bold", "CENTER"); + await T(lp, 100, 350, 460, 56, "BeautyHub", 48, C.white, "Bold", "CENTER"); + await T(lp, 100, 418, 460, 28, "\u0646\u0638\u0627\u0645 \u0625\u062f\u0627\u0631\u0629 \u0635\u0627\u0644\u0648\u0646\u0627\u062a \u0645\u062a\u0643\u0627\u0645\u0644", 18, { r:1,g:1,b:1 }, "Regular", "CENTER"); + // Bottom dots + dot(lp, 285, FH - 60, true); + dot(lp, 317, FH - 58, false); + dot(lp, 333, FH - 58, false); + + // Right panel — content + const rp = F(frame, 660, 0, 780, FH, C.white); + await T(rp, 80, 160, 620, 52, "\u0645\u0631\u062d\u0628\u0627\u064b \u0628\u0643 \u0641\u064a BeautyHub", 36, C.textPrimary, "Bold", "RIGHT"); + await T(rp, 80, 228, 620, 28, "\u0646\u0638\u0627\u0645 \u0634\u0627\u0645\u0644 \u0644\u0625\u062f\u0627\u0631\u0629 \u0635\u0627\u0644\u0648\u0646\u0643 \u0628\u0627\u062d\u062a\u0631\u0627\u0641\u064a\u0629 \u0648\u0633\u0647\u0648\u0644\u0629", 15, C.textMuted, "Regular", "RIGHT"); + + // Feature bullets + const feats = [ + "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u062d\u062c\u0648\u0632\u0627\u062a \u0648\u0627\u0644\u0639\u0645\u0644\u0627\u0621 \u0628\u0633\u0647\u0648\u0644\u0629", + "\u062a\u0642\u0627\u0631\u064a\u0631 \u0648\u062a\u062d\u0644\u064a\u0644\u0627\u062a \u0645\u0627\u0644\u064a\u0629 \u0645\u062a\u0642\u062f\u0645\u0629", + "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0645\u062e\u0632\u0648\u0646 \u0648\u0627\u0644\u0645\u062a\u062c\u0631", + ]; + for (let i = 0; i < feats.length; i++) { + R(rp, 80, 290 + i * 64, 44, 44, C.wine50, 1, 22); + R(rp, 92, 302, 20, 20, C.wine, 0.3, 10); + await T(rp, 136, 290 + i * 64, 524, 44, feats[i], 14, C.textSecond, "Regular", "LEFT"); + R(rp, 80 + 18, 290 + i * 64 + 17, 8, 8, C.wine, 1, 4); + } + + // CTA Button + R(rp, 80, 496, 340, 56, C.wine, 1, 16); + await T(rp, 80, 496, 340, 56, "\u0627\u0628\u062f\u0623 \u0627\u0644\u0622\u0646", 16, C.white, "Bold", "CENTER"); + R(rp, 436, 496, 200, 56, C.cream, 1, 16); + await T(rp, 436, 496, 200, 56, "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644", 14, C.wine, "Medium", "CENTER"); + + // Bottom note + await T(rp, 80, 576, 620, 20, "\u0647\u0644 \u0644\u062f\u064a\u0643 \u062d\u0633\u0627\u0628 \u0628\u0627\u0644\u0641\u0639\u0644\u061f \u0633\u062c\u0651\u0644 \u0627\u0644\u062f\u062e\u0648\u0644", 13, C.textMuted, "Regular", "RIGHT"); + + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// ONBOARDING 2 — المزايا +// ═══════════════════════════════════════════════════════════════════ +async function pOnboard2(xi) { + const frame = figma.createFrame(); + frame.name = "\u0646\u0645\u0648\u0630\u062c 02 - \u0627\u0644\u0645\u0632\u0627\u064a\u0627"; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.bg }]; + frame.clipsContent = true; + + // Top header + const hdr = F(frame, 0, 0, FW, 180, C.wineDark); + R(hdr, -60, -60, 200, 200, C.white, 0.03, 100); + R(hdr, FW - 100, 80, 160, 160, C.white, 0.03, 80); + await T(hdr, 60, 60, FW - 120, 44, "\u0644\u0645\u0627\u0630\u0627 BeautyHub \u061f", 32, C.white, "Bold", "RIGHT"); + await T(hdr, 60, 116, FW - 120, 24, "\u0643\u0644 \u0645\u0627 \u062a\u062d\u062a\u0627\u062c\u0647 \u0635\u0627\u0644\u0648\u0646\u0643 \u0641\u064a \u0645\u0643\u0627\u0646 \u0648\u0627\u062d\u062f", 15, { r:1,g:1,b:1 }, "Regular", "RIGHT"); + // Dots + dot(hdr, FW/2 - 24, 150, false); + dot(hdr, FW/2 - 8, 148, true); + dot(hdr, FW/2 + 20, 150, false); + + // Feature cards 3x2 + const features = [ + { t: "\u062d\u062c\u0648\u0632\u0627\u062a \u0630\u0643\u064a\u0629", d: "\u0646\u0638\u0627\u0645 \u062d\u062c\u0632 \u0623\u0648\u062a\u0648\u0645\u0627\u062a\u064a\u0643\u064a \u0645\u0639 \u062a\u0630\u0643\u064a\u0631\u0627\u062a SMS", col: C.wine }, + { t: "\u062a\u0642\u0627\u0631\u064a\u0631 \u0645\u062a\u0642\u062f\u0645\u0629", d: "\u062a\u062d\u0644\u064a\u0644\u0627\u062a \u0645\u0627\u0644\u064a\u0629 \u0648\u0625\u062d\u0635\u0627\u0626\u064a\u0629 \u062a\u0641\u0635\u064a\u0644\u064a\u0629", col: C.gold }, + { t: "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0639\u0645\u0644\u0627\u0621", d: "\u062a\u062a\u0628\u0639 \u0633\u062c\u0644\u0627\u062a \u0648\u0628\u064a\u0627\u0646\u0627\u062a \u0639\u0645\u0644\u0627\u0626\u0643 \u0628\u0633\u0647\u0648\u0644\u0629", col: C.success }, + { t: "\u0645\u062e\u0632\u0648\u0646 \u0630\u0643\u064a", d: "\u062a\u062a\u0628\u0639 \u0645\u0648\u0627\u062f\u0643 \u0648\u0627\u062d\u0635\u0644 \u062a\u0646\u0628\u064a\u0647\u0627\u062a \u0646\u0641\u0627\u062f", col: C.wineLight }, + { t: "\u0641\u0631\u064a\u0642 \u0645\u062a\u0643\u0627\u0645\u0644", d: "\u0625\u062f\u0627\u0631\u0629 \u0645\u0648\u0638\u0641\u064a\u0646\u0643 \u0648\u062c\u062f\u0627\u0648\u0644\u0647\u0645", col: C.wine }, + { t: "\u0645\u062a\u062c\u0631 \u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a", d: "\u0628\u064a\u0639 \u0645\u0646\u062a\u062c\u0627\u062a\u0643 \u0648\u062f\u0648\u0631\u0627\u062a \u062a\u062f\u0631\u064a\u0628\u064a\u0629", col: C.goldDark }, + ]; + const cw = Math.floor((FW - 96 - 48) / 3), ch = 190; + for (let i = 0; i < features.length; i++) { + const col = i % 3, row = Math.floor(i / 3); + const fx = 48 + col * (cw + 24), fy = 200 + row * (ch + 20); + const fc = card(frame, fx, fy, cw, ch, 20); + R(fc, 0, 0, cw, 6, features[i].col, 1, 0); + R(fc, cw - 64, 24, 48, 48, features[i].col, 0.10, 24); + R(fc, cw - 56, 32, 32, 32, features[i].col, 0.18, 16); + await T(fc, 24, 24, cw - 84, 32, features[i].t, 15, C.textPrimary, "Bold", "LEFT"); + await T(fc, 24, 64, cw - 48, 48, features[i].d, 12, C.textMuted, "Regular", "LEFT"); + R(fc, 24, ch - 36, 80, 24, features[i].col, 0.10, 12); + await T(fc, 24, ch - 36, 80, 24, "\u0627\u0643\u062a\u0634\u0641 \u0623\u0643\u062b\u0631", 10, features[i].col, "Medium", "CENTER"); + } + + // Bottom CTA + R(frame, FW/2 - 200, FH - 80, 400, 52, C.wine, 1, 16); + await T(frame, FW/2 - 200, FH - 80, 400, 52, "\u0627\u0644\u062a\u0627\u0644\u064a", 15, C.white, "Bold", "CENTER"); + + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// ONBOARDING 3 — ابدأ الآن +// ═══════════════════════════════════════════════════════════════════ +async function pOnboard3(xi) { + const frame = figma.createFrame(); + frame.name = "\u0646\u0645\u0648\u0630\u062c 03 - \u0627\u0628\u062f\u0623 \u0627\u0644\u0622\u0646"; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.white }]; + frame.clipsContent = true; + + // Split: right side wine + const rp = F(frame, FW - 620, 0, 620, FH, C.wineDark); + R(rp, 400, -60, 240, 240, C.white, 0.04, 120); + R(rp, -40, FH - 200, 200, 200, C.white, 0.04, 100); + R(rp, 200, 300, 120, 120, C.gold, 0.07, 60); + await T(rp, 60, 180, 500, 52, "\u062c\u0627\u0647\u0632 \u0644\u0644\u0628\u062f\u0621\u061f", 36, C.white, "Bold", "RIGHT"); + await T(rp, 60, 248, 500, 28, "\u0633\u062c\u0651\u0644 \u0635\u0627\u0644\u0648\u0646\u0643 \u0648\u0627\u0628\u062f\u0623 \u0625\u062f\u0627\u0631\u062a\u0647 \u0628\u0634\u0643\u0644 \u0627\u062d\u062a\u0631\u0627\u0641\u064a", 16, { r:1,g:1,b:1 }, "Regular", "RIGHT"); + // Stats + const stats = [["500+","\u0635\u0627\u0644\u0648\u0646"], ["50K+","\u0639\u0645\u064a\u0644"], ["98%","\u0631\u0636\u0627"]]; + for (let i = 0; i < stats.length; i++) { + R(rp, 60 + i * 186, 320, 160, 80, C.white, 0.07, 16); + await T(rp, 60 + i * 186, 330, 160, 32, stats[i][0], 24, C.gold, "Bold", "CENTER"); + await T(rp, 60 + i * 186, 364, 160, 24, stats[i][1], 13, C.white, "Regular", "CENTER"); + } + dot(rp, 240, FH - 60, false); + dot(rp, 256, FH - 58, false); + dot(rp, 272, FH - 60, true); + + // Left side + await T(frame, 60, 80, 720, 32, "BeautyHub", 28, C.wine, "Bold", "LEFT"); + await T(frame, 60, 200, 760, 44, "\u0627\u0628\u062f\u0623 \u0631\u062d\u0644\u062a\u0643 \u0627\u0644\u064a\u0648\u0645", 32, C.textPrimary, "Bold", "LEFT"); + await T(frame, 60, 258, 700, 24, "\u0623\u0646\u0634\u0626 \u062d\u0633\u0627\u0628\u0643 \u0627\u0644\u0622\u0646 \u0645\u062c\u0627\u0646\u0627\u064b \u0644\u0645\u062f\u0629 30 \u064a\u0648\u0645\u0627\u064b", 15, C.textMuted, "Regular", "LEFT"); + + // Plan cards + const plans = [ + { n: "\u0623\u0633\u0627\u0633\u064a", p: "\u0645\u062c\u0627\u0646\u064a", d: "\u0644\u0644\u0635\u0627\u0644\u0648\u0646\u0627\u062a \u0627\u0644\u0635\u063a\u064a\u0631\u0629" }, + { n: "\u0645\u062a\u0642\u062f\u0645", p: "199 \u0631.\u0633 / \u0634\u0647\u0631", d: "\u0644\u0644\u0635\u0627\u0644\u0648\u0646\u0627\u062a \u0627\u0644\u0645\u062a\u0648\u0633\u0637\u0629" }, + ]; + for (let i = 0; i < plans.length; i++) { + const isRec = i === 1; + const pc = isRec ? card(frame, 60 + i * 320, 310, 290, 160, 16) : softCard(frame, 60 + i * 320, 310, 290, 160, 16); + if (isRec) { + R(pc, 0, 0, 290, 160, C.wine, 0.04, 16); + R(pc, pc.width - 100, 16, 84, 26, C.wine, 1, 13); + await T(pc, pc.width - 100, 16, 84, 26, "\u0645\u0648\u0635\u0649 \u0628\u0647", 10, C.white, "Bold", "CENTER"); + } + await T(pc, 24, 24, 200, 24, plans[i].n, 16, isRec ? C.wine : C.textPrimary, "Bold", "LEFT"); + await T(pc, 24, 52, 200, 28, plans[i].p, 20, isRec ? C.wine : C.textPrimary, "Bold", "LEFT"); + await T(pc, 24, 84, 220, 20, plans[i].d, 11, C.textMuted, "Regular", "LEFT"); + R(pc, 24, 116, 200, 32, isRec ? C.wine : C.cream, 1, 10); + await T(pc, 24, 116, 200, 32, "\u0627\u062e\u062a\u0631 \u0647\u0630\u0627 \u0627\u0644\u0628\u0644\u0627\u0646", 12, isRec ? C.white : C.wine, "Medium", "CENTER"); + } + + // CTA + R(frame, 60, 510, 540, 56, C.wine, 1, 16); + await T(frame, 60, 510, 540, 56, "\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628 \u0645\u062c\u0627\u0646\u064a", 16, C.white, "Bold", "CENTER"); + R(frame, 60, 580, 540, 48, C.cream, 1, 16); + await T(frame, 60, 580, 540, 48, "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644 \u0628\u062d\u0633\u0627\u0628 \u0645\u0648\u062c\u0648\u062f", 14, C.wine, "Medium", "CENTER"); + + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// LOGIN +// ═══════════════════════════════════════════════════════════════════ +async function pLogin(xi) { + const frame = figma.createFrame(); + frame.name = "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644"; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.bg }]; + frame.clipsContent = true; + + // Right panel — brand (wine) + const rp = F(frame, FW - 600, 0, 600, FH, C.wine); + R(rp, 400, -40, 200, 200, C.white, 0.05, 100); + R(rp, -60, FH - 180, 220, 220, C.white, 0.04, 110); + R(rp, 200, 350, 100, 100, C.gold, 0.10, 50); + R(rp, 100, 140, 80, 80, C.white, 0.04, 40); + // Logo + R(rp, 220, 160, 160, 160, C.white, 0.10, 80); + await T(rp, 220, 160, 160, 160, "BH", 52, C.white, "Bold", "CENTER"); + await T(rp, 80, 348, 440, 44, "BeautyHub", 36, C.white, "Bold", "CENTER"); + await T(rp, 80, 404, 440, 28, "\u0645\u0646\u0635\u0629 \u0625\u062f\u0627\u0631\u0629 \u0635\u0627\u0644\u0648\u0646\u0627\u062a \u0645\u062a\u0643\u0627\u0645\u0644\u0629", 16, { r:1,g:1,b:1 }, "Regular", "CENTER"); + // Testimonial + R(rp, 60, FH - 180, 480, 120, C.white, 0.08, 16); + await T(rp, 80, FH - 166, 440, 48, "\u0642\u0645\u062a \u0628\u0632\u064a\u0627\u062f\u0629 \u0625\u064a\u0631\u0627\u062f\u0627\u062a\u064a 40% \u0628\u0639\u062f \u0627\u0633\u062a\u062e\u062f\u0627\u0645 BeautyHub!", 13, C.white, "Regular", "CENTER"); + await T(rp, 80, FH - 108, 440, 20, "\u0633\u0627\u0631\u0629 - \u0645\u062f\u064a\u0631\u0629 \u0635\u0627\u0644\u0648\u0646 \u0633\u0627\u0631\u0629", 12, C.goldLight, "Medium", "CENTER"); + + // Left panel — form + const lp = F(frame, 0, 0, FW - 600, FH, C.white); + // Back link + R(lp, 40, 36, 80, 32, C.cream, 1, 16); + await T(lp, 40, 36, 80, 32, "\u0627\u0644\u0631\u062c\u0648\u0639", 12, C.wine, "Medium", "CENTER"); + + await T(lp, 80, 120, 680, 44, "\u0645\u0631\u062d\u0628\u0627\u064b \u0628\u0639\u0648\u062f\u062a\u0643", 32, C.textPrimary, "Bold", "RIGHT"); + await T(lp, 80, 176, 680, 24, "\u0633\u062c\u0651\u0644 \u062f\u062e\u0648\u0644\u0643 \u0644\u0625\u062f\u0627\u0631\u0629 \u0635\u0627\u0644\u0648\u0646\u0643", 15, C.textMuted, "Regular", "RIGHT"); + + // Form card + const fc = card(lp, 80, 224, 680, 400, 24); + await T(fc, 40, 28, 600, 24, "\u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a", 12, C.textMuted, "Medium"); + await activeInput(fc, 40, 56, 600, "", "salon@example.com"); + await T(fc, 40, 128, 600, 24, "\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631", 12, C.textMuted, "Medium"); + const pi = await inputField(fc, 40, 156, 600, "", "\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631"); + // password dots + for (let i = 0; i < 8; i++) R(fc, 56 + i * 16, 174, 8, 8, C.textMuted, 0.4, 4); + // Show password icon + R(fc, 56, 164, 20, 20, C.textMuted, 0.2, 5); + + // Remember + Forgot + R(fc, 40, 232, 20, 20, C.wine, 0.15, 6); + R(fc, 44, 236, 12, 12, C.wine, 1, 3); + await T(fc, 68, 228, 200, 28, "\u062a\u0630\u0643\u0651\u0631\u0646\u064a", 12, C.textSecond, "Regular", "LEFT"); + R(fc, 480, 232, 160, 28, C.wine50, 1, 14); + await T(fc, 480, 232, 160, 28, "\u0646\u0633\u064a\u062a \u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631\u061f", 12, C.wine, "Medium", "CENTER"); + + // Login button + R(fc, 40, 280, 600, 56, C.wine, 1, 16); + await T(fc, 40, 280, 600, 56, "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644", 16, C.white, "Bold", "CENTER"); + + // Divider + R(fc, 40, 356, 260, 1, C.borderLight); + await T(fc, 290, 344, 60, 24, "\u0623\u0648", 13, C.textMuted, "Regular", "CENTER"); + R(fc, 360, 356, 260, 1, C.borderLight); + + // Google login + R(fc, 40, 368, 600, 20, C.cream, 1, 14); + + await T(lp, 80, 644, 680, 24, "\u0644\u064a\u0633 \u0644\u062f\u064a\u0643 \u062d\u0633\u0627\u0628\u061f \u0633\u062c\u0651\u0644 \u0627\u0644\u0622\u0646", 14, C.textMuted, "Regular", "RIGHT"); + R(lp, 480, 640, 280, 40, C.wine50, 1, 12); + await T(lp, 480, 640, 280, 40, "\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628 \u062c\u062f\u064a\u062f", 13, C.wine, "Medium", "CENTER"); + + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// REGISTER +// ═══════════════════════════════════════════════════════════════════ +async function pRegister(xi) { + const frame = figma.createFrame(); + frame.name = "\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628"; + frame.x = xi; frame.y = 0; + frame.resize(FW, FH); + frame.fills = [{ type: "SOLID", color: C.bg }]; + frame.clipsContent = true; + + // Left panel — brand (gold theme) + const lp = F(frame, 0, 0, 560, FH, C.wineDark); + R(lp, -60, -60, 200, 200, C.white, 0.03, 100); + R(lp, 460, FH - 200, 180, 180, C.white, 0.03, 90); + R(lp, 350, 240, 120, 120, C.gold, 0.08, 60); + // Logo + R(lp, 200, 120, 160, 160, C.white, 0.08, 80); + await T(lp, 200, 120, 160, 160, "BH", 48, C.gold, "Bold", "CENTER"); + await T(lp, 60, 308, 440, 40, "BeautyHub", 32, C.white, "Bold", "CENTER"); + await T(lp, 60, 360, 440, 24, "\u0627\u0646\u0636\u0645 \u0625\u0644\u0649 \u0623\u0643\u062b\u0631 \u0645\u0646 500 \u0635\u0627\u0644\u0648\u0646", 14, { r:1,g:1,b:1 }, "Regular", "CENTER"); + // Benefit list + const bens = ["\u062a\u062c\u0631\u0628\u0629 \u0645\u062c\u0627\u0646\u064a\u0629 30 \u064a\u0648\u0645\u0627\u064b","\u0644\u0627 \u062d\u0627\u062c\u0629 \u0644\u0628\u0637\u0627\u0642\u0629 \u0627\u0626\u062a\u0645\u0627\u0646","\u062f\u0639\u0645 \u0641\u0648\u0631\u064a 24/7"]; + for (let i = 0; i < bens.length; i++) { + R(lp, 80, 420 + i * 52, 20, 20, C.gold, 1, 10); + await T(lp, 110, 418 + i * 52, 380, 24, bens[i], 13, C.white, "Regular", "LEFT"); + } + + // Right panel — form + const rp = F(frame, 560, 0, FW - 560, FH, C.white); + // Back + R(rp, 40, 28, 80, 32, C.cream, 1, 16); + await T(rp, 40, 28, 80, 32, "\u0627\u0644\u0631\u062c\u0648\u0639", 12, C.wine, "Medium", "CENTER"); + + await T(rp, 60, 80, 780, 36, "\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628 \u062c\u062f\u064a\u062f", 28, C.textPrimary, "Bold", "RIGHT"); + await T(rp, 60, 128, 780, 24, "\u0623\u062f\u062e\u0644 \u0628\u064a\u0627\u0646\u0627\u062a\u0643 \u0644\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628\u0643", 14, C.textMuted, "Regular", "RIGHT"); + + // Form two-column + const fw = (FW - 560 - 120 - 20) / 2; + + // Row 1: Name + Salon name + await T(rp, 60, 172, fw, 18, "\u0627\u0644\u0627\u0633\u0645 \u0627\u0644\u0643\u0627\u0645\u0644", 11, C.textMuted, "Medium", "LEFT"); + const n1 = F(rp, 60, 194, fw, 48, C.bg, 1, 12); + n1.strokes = [{ type: "SOLID", color: C.border }]; n1.strokeWeight = 1.5; n1.strokeAlign = "INSIDE"; + await T(n1, 16, 0, fw - 32, 48, "\u0633\u0627\u0631\u0629 \u0623\u062d\u0645\u062f", 13, C.textPrimary, "Regular", "LEFT"); + + await T(rp, 80 + fw, 172, fw, 18, "\u0627\u0633\u0645 \u0627\u0644\u0635\u0627\u0644\u0648\u0646", 11, C.textMuted, "Medium", "LEFT"); + const n2 = F(rp, 80 + fw, 194, fw, 48, C.wine50, 1, 12); + n2.strokes = [{ type: "SOLID", color: C.wine }]; n2.strokeWeight = 2; n2.strokeAlign = "INSIDE"; + await T(n2, 16, 0, fw - 32, 48, "\u0635\u0627\u0644\u0648\u0646 \u0633\u0627\u0631\u0629", 13, C.textPrimary, "Regular", "LEFT"); + + // Row 2: Email + Phone + await T(rp, 60, 262, fw, 18, "\u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a", 11, C.textMuted, "Medium", "LEFT"); + const n3 = F(rp, 60, 284, fw, 48, C.bg, 1, 12); + n3.strokes = [{ type: "SOLID", color: C.border }]; n3.strokeWeight = 1.5; n3.strokeAlign = "INSIDE"; + await T(n3, 16, 0, fw - 32, 48, "salon@example.com", 13, C.textPrimary, "Regular", "LEFT"); + + await T(rp, 80 + fw, 262, fw, 18, "\u0631\u0642\u0645 \u0627\u0644\u062c\u0648\u0627\u0644", 11, C.textMuted, "Medium", "LEFT"); + const n4 = F(rp, 80 + fw, 284, fw, 48, C.bg, 1, 12); + n4.strokes = [{ type: "SOLID", color: C.border }]; n4.strokeWeight = 1.5; n4.strokeAlign = "INSIDE"; + await T(n4, 16, 0, fw - 32, 48, "05x xxx xxxx", 13, C.textPrimary, "Regular", "LEFT"); + + // Row 3: Password + Confirm + await T(rp, 60, 352, fw, 18, "\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631", 11, C.textMuted, "Medium", "LEFT"); + const n5 = F(rp, 60, 374, fw, 48, C.bg, 1, 12); + n5.strokes = [{ type: "SOLID", color: C.border }]; n5.strokeWeight = 1.5; n5.strokeAlign = "INSIDE"; + for (let i = 0; i < 8; i++) R(n5, 20 + i * 14, 20, 8, 8, C.textMuted, 0.4, 4); + + await T(rp, 80 + fw, 352, fw, 18, "\u062a\u0623\u0643\u064a\u062f \u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631", 11, C.textMuted, "Medium", "LEFT"); + const n6 = F(rp, 80 + fw, 374, fw, 48, C.bg, 1, 12); + n6.strokes = [{ type: "SOLID", color: C.border }]; n6.strokeWeight = 1.5; n6.strokeAlign = "INSIDE"; + for (let i = 0; i < 8; i++) R(n6, 20 + i * 14, 20, 8, 8, C.textMuted, 0.4, 4); + + // City + const totalW = fw * 2 + 20; + await T(rp, 60, 442, totalW, 18, "\u0627\u0644\u0645\u062f\u064a\u0646\u0629", 11, C.textMuted, "Medium", "LEFT"); + const n7 = F(rp, 60, 464, totalW, 48, C.bg, 1, 12); + n7.strokes = [{ type: "SOLID", color: C.border }]; n7.strokeWeight = 1.5; n7.strokeAlign = "INSIDE"; + await T(n7, 16, 0, totalW - 32, 48, "\u0627\u0644\u0631\u064a\u0627\u0636", 13, C.textPrimary, "Regular", "LEFT"); + R(n7, totalW - 40, 16, 16, 16, C.textMuted, 0.2, 4); + + // Terms + R(rp, 60, 530, 20, 20, C.wine, 0.15, 6); + R(rp, 64, 534, 12, 12, C.wine, 1, 3); + await T(rp, 88, 526, 660, 28, "\u0623\u0648\u0627\u0641\u0642 \u0639\u0644\u0649 \u0634\u0631\u0648\u0637 \u0627\u0644\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0648\u0633\u064a\u0627\u0633\u0629 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629", 12, C.textSecond, "Regular", "LEFT"); + + // Register button + R(rp, 60, 570, totalW, 56, C.wine, 1, 16); + await T(rp, 60, 570, totalW, 56, "\u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u062d\u0633\u0627\u0628", 16, C.white, "Bold", "CENTER"); + + await T(rp, 60, 644, totalW, 24, "\u0644\u062f\u064a\u0643 \u062d\u0633\u0627\u0628 \u0628\u0627\u0644\u0641\u0639\u0644\u061f \u0633\u062c\u0651\u0644 \u0627\u0644\u062f\u062e\u0648\u0644", 13, C.textMuted, "Regular", "CENTER"); + + figma.currentPage.appendChild(frame); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 01 الرئيسية +// ═══════════════════════════════════════════════════════════════════ +async function p01(xi) { + await buildPage("01 - \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629", xi, 0, async (c) => { + await topbar(c, "\u0644\u0648\u062d\u0629 \u0627\u0644\u062a\u062d\u0643\u0645", "\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621\u060c 26 \u0645\u0627\u064a\u0648 2026"); + const cx = 24, cy = TH + 20; + + // Hero banner + const bn = F(c, cx, cy, CW - 48, 108, C.wine, 1, 20); + R(bn, -40, -40, 180, 180, C.white, 0.04, 90); + R(bn, bn.width - 160, 60, 180, 180, C.white, 0.04, 90); + R(bn, bn.width - 400, -20, 100, 100, C.gold, 0.07, 50); + await T(bn, bn.width - 24, 20, 360, 30, "\u0623\u0647\u0644\u0627\u064b \u0628\u0643\u0650\u060c \u0635\u0627\u0644\u0648\u0646 \u0633\u0627\u0631\u0629", 18, C.white, "Bold"); + await T(bn, bn.width - 24, 54, 360, 20, "\u0644\u062f\u064a\u0643 \u0627\u0644\u064a\u0648\u0645 5 \u062d\u062c\u0648\u0632\u0627\u062a \u0648\u062d\u062c\u0632 \u062c\u062f\u064a\u062f \u0641\u064a \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631", 13, { r:1,g:1,b:1 }); + R(bn, 24, 36, 140, 36, C.white, 0.12, 18); + await T(bn, 24, 36, 140, 36, "\u0627\u0644\u064a\u0648\u0645: 5 \u062d\u062c\u0648\u0632\u0627\u062a", 12, C.white, "Medium", "CENTER"); + R(bn, 176, 36, 156, 36, C.gold, 0.25, 18); + await T(bn, 176, 36, 156, 36, "\u0625\u064a\u0631\u0627\u062f\u0627\u062a: 2,400 \u0631.\u0633", 12, C.white, "Medium", "CENTER"); + + // Stats row + const sdata = [ + ["\u0627\u0644\u0625\u064a\u0631\u0627\u062f\u0627\u062a","48,250 \u0631.\u0633","+ 12% \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.wine, true], + ["\u0627\u0644\u062d\u062c\u0648\u0632\u0627\u062a","284 \u062d\u062c\u0632","+ 8% \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.wineLight, true], + ["\u0639\u0645\u0644\u0627\u0621 \u062c\u062f\u062f","42 \u0639\u0645\u064a\u0644","+ 15% \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.success, true], + ["\u0627\u0644\u062a\u0642\u064a\u064a\u0645","4.8 / 5","\u062b\u0627\u0628\u062a \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.gold, true], + ]; + const sw = Math.floor((CW - 48 - 36) / 4); + for (let i = 0; i < sdata.length; i++) { + await statCard(c, cx + i * (sw + 12), cy + 128, sw, sdata[i][0], sdata[i][1], sdata[i][2], sdata[i][3], sdata[i][4]); + } + + // Two columns + const tY = cy + 260, cw2 = Math.floor((CW - 48 - 20) / 2); + + // Bookings card + const bc = card(c, cx + cw2 + 20, tY, cw2, 294); + await cardHeader(bc, cw2, "\u0623\u062d\u062f\u062b \u0627\u0644\u062d\u062c\u0648\u0632\u0627\u062a", "\u0639\u0631\u0636 \u0627\u0644\u0643\u0644"); + const brows = [ + ["\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a","\u0635\u0628\u063a \u0634\u0639\u0631","350 \u0631.\u0633","\u0645\u0643\u062a\u0645\u0644"], + ["\u0647\u0646\u062f \u0627\u0644\u0634\u0645\u0631\u064a","\u0645\u0627\u0646\u064a\u0643\u064a\u0631","180 \u0631.\u0633","\u0645\u0624\u0643\u062f"], + ["\u0631\u064a\u0645 \u0627\u0644\u0642\u062d\u0637\u0627\u0646\u064a","\u062a\u0646\u0638\u064a\u0641 \u0628\u0634\u0631\u0629","220 \u0631.\u0633","\u0627\u0646\u062a\u0638\u0627\u0631"], + ["\u0633\u0627\u0631\u0629 \u0627\u0644\u063a\u0627\u0645\u062f\u064a","\u0645\u0633\u0627\u062c","300 \u0631.\u0633","\u0645\u0643\u062a\u0645\u0644"], + ["\u0641\u0627\u0637\u0645\u0629 \u0627\u0644\u0632\u0647\u0631\u0627\u0646\u064a","\u0645\u064a\u0643\u0627\u0628","250 \u0631.\u0633","\u0645\u0644\u063a\u064a"], + ]; + const badgeT = {"\u0645\u0643\u062a\u0645\u0644":"success","\u0645\u0624\u0643\u062f":"wine","\u0627\u0646\u062a\u0638\u0627\u0631":"warning","\u0645\u0644\u063a\u064a":"danger"}; + for (let i = 0; i < brows.length; i++) { + const ry = 64 + i * 44; + R(bc, bc.width - 46, ry + 8, 30, 30, C.wine50, 1, 15); + await T(bc, bc.width - 46, ry + 8, 30, 30, brows[i][0].charAt(0), 12, C.wine, "Bold", "CENTER"); + await T(bc, bc.width - 86, ry + 6, 170, 18, brows[i][0], 12, C.textPrimary, "Semi Bold"); + await T(bc, bc.width - 86, ry + 26, 170, 16, brows[i][1], 10, C.textMuted); + await T(bc, 90, ry + 10, 80, 20, brows[i][2], 12, C.goldDark, "Bold", "LEFT"); + await badge(bc, 16, ry + 10, brows[i][3], badgeT[brows[i][3]] || "neutral"); + if (i < brows.length - 1) R(bc, 16, ry + 43, cw2 - 32, 1, C.borderLight); + } + + // Services card + const sc = card(c, cx, tY, cw2, 294); + await cardHeader(sc, cw2, "\u0627\u0644\u062e\u062f\u0645\u0627\u062a \u0627\u0644\u0623\u0643\u062b\u0631 \u0637\u0644\u0628\u0627\u064b", ""); + const srows = [ + ["\u0635\u0628\u063a \u0627\u0644\u0634\u0639\u0631",46,92], + ["\u0642\u0635 \u0627\u0644\u0634\u0639\u0631",38,76], + ["\u062a\u0646\u0638\u064a\u0641 \u0628\u0634\u0631\u0629",33,66], + ["\u0645\u0627\u0646\u064a\u0643\u064a\u0631",28,56], + ["\u0645\u0633\u0627\u062c",22,44], + ]; + for (let i = 0; i < srows.length; i++) { + await progBar(sc, 20, 68 + i * 42, cw2 - 40, srows[i][2], srows[i][0], srows[i][1]); + } + + // Revenue chart + const chY = tY + 310; + const cc = card(c, cx, chY, CW - 48, 200); + await cardHeader(cc, CW - 48, "\u0627\u0644\u0625\u064a\u0631\u0627\u062f\u0627\u062a \u0627\u0644\u0623\u0633\u0628\u0648\u0639\u064a\u0629"); + R(cc, 20, 68, CW - 88, 108, C.bg, 1, 8); + bars(cc, 24, 68, CW - 96, 108); + const days = ["\u0633\u0628\u062a","\u0623\u062d\u062f","\u0625\u062b\u0646","\u062b\u0644\u062b","\u0623\u0631\u0628","\u062e\u0645\u0633","\u062c\u0645\u0639"]; + const dw = Math.floor((CW - 96) / 7); + for (let i = 0; i < days.length; i++) { + await T(cc, 24 + i * dw, 180, dw, 14, days[i], 9, C.textMuted, "Regular", "CENTER"); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 02 المواعيد +// ═══════════════════════════════════════════════════════════════════ +async function p02(xi) { + await buildPage("02 - \u0627\u0644\u0645\u0648\u0627\u0639\u064a\u062f", xi, 1, async (c) => { + await topbar(c, "\u0627\u0644\u0645\u0648\u0627\u0639\u064a\u062f", "\u0625\u062f\u0627\u0631\u0629 \u062d\u062c\u0648\u0632\u0627\u062a \u0627\u0644\u0639\u0645\u0644\u0627\u0621"); + const cx = 24, cy = TH + 20; + + // Filter bar + const fb = softCard(c, cx, cy, CW - 48, 60); + const ftabs = ["\u0627\u0644\u0643\u0644","\u0642\u064a\u062f \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631","\u0645\u0624\u0643\u062f","\u0645\u0643\u062a\u0645\u0644","\u0645\u0644\u063a\u064a"]; + for (let i = 0; i < ftabs.length; i++) { + R(fb, fb.width - 16 - (i + 1) * 100, 12, 92, 36, i === 0 ? C.wine : C.bg, 1, 18); + await T(fb, fb.width - 16 - (i + 1) * 100, 12, 92, 36, ftabs[i], 11, i === 0 ? C.white : C.textMuted, "Medium", "CENTER"); + } + await searchBar(fb, 16, 8, 240, "\u0627\u0644\u0628\u062d\u062b..."); + R(fb, fb.width - 558, 12, 36, 36, C.wine50, 1, 10); + await T(fb, fb.width - 558, 12, 36, 36, "+", 18, C.wine, "Bold", "CENTER"); + + // Table card + const tc = card(c, cx, cy + 76, CW - 48, FH - TH - cy - 104); + const hdrs = ["\u0627\u0644\u062d\u0627\u0644\u0629","\u0627\u0644\u0645\u0628\u0644\u063a","\u0627\u0644\u0648\u0642\u062a","\u0627\u0644\u062a\u0627\u0631\u064a\u062e","\u0627\u0644\u062e\u062f\u0645\u0629","\u0627\u0644\u0639\u0645\u064a\u0644","#"]; + await tableRow(tc, 0, hdrs, 48, true); + const rows = [ + ["\u0645\u0643\u062a\u0645\u0644","350 \u0631.\u0633","10:00","26/5"," \u0635\u0628\u063a \u0634\u0639\u0631","\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a","001"], + ["\u0645\u0624\u0643\u062f","180 \u0631.\u0633","11:30","26/5","\u0645\u0627\u0646\u064a\u0643\u064a\u0631","\u0647\u0646\u062f \u0627\u0644\u0634\u0645\u0631\u064a","002"], + ["\u0627\u0646\u062a\u0638\u0627\u0631","220 \u0631.\u0633","13:00","26/5","\u062a\u0646\u0638\u064a\u0641 \u0628\u0634\u0631\u0629","\u0631\u064a\u0645 \u0627\u0644\u0642\u062d\u0637\u0627\u0646\u064a","003"], + ["\u0645\u0643\u062a\u0645\u0644","300 \u0631.\u0633","14:30","26/5","\u0645\u0633\u0627\u062c","\u0633\u0627\u0631\u0629 \u0627\u0644\u063a\u0627\u0645\u062f\u064a","004"], + ["\u0645\u0644\u063a\u064a","0 \u0631.\u0633","15:00","26/5","\u0645\u064a\u0643\u0627\u0628","\u0641\u0627\u0637\u0645\u0629 \u0627\u0644\u0632\u0647\u0631\u0627\u0646\u064a","005"], + ["\u0645\u0624\u0643\u062f","450 \u0631.\u0633","09:00","27/5","\u0635\u0628\u063a + \u062a\u0633\u0631\u064a\u062d\u0629","\u0645\u0646\u0649 \u0627\u0644\u062d\u0631\u0628\u064a","006"], + ["\u0627\u0646\u062a\u0638\u0627\u0631","150 \u0631.\u0633","10:30","27/5","\u0642\u0635 \u0634\u0639\u0631","\u0623\u0645\u0644 \u0627\u0644\u0639\u0645\u0631\u064a","007"], + ["\u0645\u0643\u062a\u0645\u0644","280 \u0631.\u0633","12:00","27/5","\u0645\u0627\u0646\u064a\u0643\u064a\u0631","\u0644\u0645\u0649 \u0627\u0644\u062d\u0645\u064a\u062f","008"], + ["\u0645\u0624\u0643\u062f","320 \u0631.\u0633","14:00","27/5","\u0635\u0628\u063a \u0634\u0639\u0631","\u062f\u0644\u0627\u0644 \u0627\u0644\u0639\u0645\u0631\u064a","009"], + ]; + for (let i = 0; i < rows.length; i++) { + await tableRow(tc, 48 + i * 48, rows[i], 48, false); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 03 الخدمات +// ═══════════════════════════════════════════════════════════════════ +async function p03(xi) { + await buildPage("03 - \u0627\u0644\u062e\u062f\u0645\u0627\u062a", xi, 2, async (c) => { + await topbar(c, "\u0627\u0644\u062e\u062f\u0645\u0627\u062a", "\u0625\u062f\u0627\u0631\u0629 \u062e\u062f\u0645\u0627\u062a \u0627\u0644\u0635\u0627\u0644\u0648\u0646"); + const cx = 24, cy = TH + 20; + + // Action bar + R(c, cx, cy, 150, 44, C.wine, 1, 12); + await T(c, cx, cy, 150, 44, "+ \u0625\u0636\u0627\u0641\u0629 \u062e\u062f\u0645\u0629", 13, C.white, "Semi Bold", "CENTER"); + R(c, cx + 162, cy, 150, 44, C.cream, 1, 12); + await T(c, cx + 162, cy, 150, 44, "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0641\u0626\u0627\u062a", 13, C.wine, "Medium", "CENTER"); + + const groups = [ + { name:"\u062e\u062f\u0645\u0627\u062a \u0627\u0644\u0634\u0639\u0631", col: C.wine, items:[ + {n:"\u0635\u0628\u063a \u0627\u0644\u0634\u0639\u0631",p:"250-500 \u0631.\u0633",d:"90 \u062f\u0642\u064a\u0642\u0629",active:true}, + {n:"\u0642\u0635 \u0627\u0644\u0634\u0639\u0631",p:"80-150 \u0631.\u0633",d:"45 \u062f\u0642\u064a\u0642\u0629",active:true}, + {n:"\u062a\u0633\u0631\u064a\u062d\u0629",p:"120-200 \u0631.\u0633",d:"60 \u062f\u0642\u064a\u0642\u0629",active:true}, + {n:"\u0643\u064a\u0631\u0627\u062a\u064a\u0646",p:"400-700 \u0631.\u0633",d:"120 \u062f\u0642\u064a\u0642\u0629",active:false}, + ]}, + { name:"\u0627\u0644\u0639\u0646\u0627\u064a\u0629 \u0628\u0627\u0644\u0628\u0634\u0631\u0629", col: C.gold, items:[ + {n:"\u062a\u0646\u0638\u064a\u0641 \u0627\u0644\u0628\u0634\u0631\u0629",p:"180-280 \u0631.\u0633",d:"60 \u062f\u0642\u064a\u0642\u0629",active:true}, + {n:"\u0645\u0633\u0627\u062c \u0648\u062c\u0647",p:"150-250 \u0631.\u0633",d:"45 \u062f\u0642\u064a\u0642\u0629",active:true}, + ]}, + { name:"\u0627\u0644\u0623\u0638\u0627\u0641\u0631", col: C.success, items:[ + {n:"\u0645\u0627\u0646\u064a\u0643\u064a\u0631",p:"80-150 \u0631.\u0633",d:"45 \u062f\u0642\u064a\u0642\u0629",active:true}, + {n:"\u0628\u064a\u062f\u064a\u0643\u064a\u0631",p:"100-180 \u0631.\u0633",d:"60 \u062f\u0642\u064a\u0642\u0629",active:true}, + ]}, + ]; + + let gy = cy + 60; + for (const g of groups) { + const gh = 60 + g.items.length * 60; + const gc = card(c, cx, gy, CW - 48, gh, 20); + // Category header + R(gc, 0, 0, CW - 48, 60, C.bg, 1, 20); + R(gc, 0, 40, CW - 48, 20, C.bg); // remove bottom radius on bg + R(gc, 0, 0, 6, 60, g.col, 1, 0); + await T(gc, gc.width - 20, 10, 240, 28, g.name, 14, C.textPrimary, "Bold"); + R(gc, 16, 18, 60, 24, g.col, 0.10, 12); + await T(gc, 16, 18, 60, 24, g.items.length + " \u062e\u062f\u0645\u0629", 11, g.col, "Medium", "CENTER"); + R(gc, 0, 60, gc.width, 1, C.borderLight); + + for (let i = 0; i < g.items.length; i++) { + const iy = 60 + i * 60; + const item = g.items[i]; + // Active toggle + R(gc, 20, iy + 20, 36, 20, item.active ? C.wine : C.creamDark, 1, 10); + R(gc, item.active ? 40 : 22, iy + 23, 14, 14, C.white, 1, 7); + await T(gc, gc.width - 20, iy + 10, 240, 20, item.n, 13, C.textPrimary, "Semi Bold"); + await T(gc, gc.width - 20, iy + 34, 200, 16, item.d, 11, C.textMuted); + await T(gc, 80, iy + 16, 160, 22, item.p, 13, C.goldDark, "Bold", "LEFT"); + R(gc, 252, iy + 18, 60, 24, C.wine50, 1, 8); + await T(gc, 252, iy + 18, 60, 24, "\u062a\u0639\u062f\u064a\u0644", 11, C.wine, "Medium", "CENTER"); + if (i < g.items.length - 1) R(gc, 16, iy + 59, gc.width - 32, 1, C.borderLight); + } + gy += gh + 16; + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 04 العملاء +// ═══════════════════════════════════════════════════════════════════ +async function p04(xi) { + await buildPage("04 - \u0627\u0644\u0639\u0645\u0644\u0627\u0621", xi, 3, async (c) => { + await topbar(c, "\u0627\u0644\u0639\u0645\u0644\u0627\u0621", "\u0625\u062f\u0627\u0631\u0629 \u0642\u0627\u0639\u062f\u0629 \u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0639\u0645\u0644\u0627\u0621"); + const cx = 24, cy = TH + 20; + const sw = Math.floor((CW - 48 - 24) / 3); + const sdata = [ + ["\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0639\u0645\u0644\u0627\u0621","428","\u0632\u064a\u0627\u062f\u0629 12 \u0639\u0645\u064a\u0644 \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.wine, true], + ["\u0639\u0645\u0644\u0627\u0621 \u0646\u0634\u0637\u0648\u0646","312","\u0646\u0633\u0628\u0629 73% \u0645\u0646 \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a", C.success, true], + ["\u0645\u062a\u0648\u0633\u0637 \u0627\u0644\u0625\u0646\u0641\u0627\u0642","580 \u0631.\u0633","\u0632\u064a\u0627\u062f\u0629 5% \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.gold, true], + ]; + for (let i = 0; i < sdata.length; i++) { + await statCard(c, cx + i * (sw + 12), cy, sw, sdata[i][0], sdata[i][1], sdata[i][2], sdata[i][3], sdata[i][4]); + } + + const tc = card(c, cx, cy + 124, CW - 48, FH - TH - cy - 152); + // Toolbar + await searchBar(tc, tc.width - 340, 12, 300, "\u0628\u062d\u062b \u0639\u0646 \u0639\u0645\u064a\u0644..."); + R(tc, 16, 12, 110, 36, C.wine, 1, 12); + await T(tc, 16, 12, 110, 36, "+ \u0639\u0645\u064a\u0644 \u062c\u062f\u064a\u062f", 11, C.white, "Medium", "CENTER"); + R(tc, 136, 12, 80, 36, C.cream, 1, 12); + await T(tc, 136, 12, 80, 36, "\u062a\u0635\u0641\u064a\u0629", 11, C.wine, "Medium", "CENTER"); + R(tc, 0, 60, tc.width, 1, C.borderLight); + + const hdrs = ["\u0646\u0642\u0627\u0637 \u0627\u0644\u0648\u0644\u0627\u0621","\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0625\u0646\u0641\u0627\u0642","\u0639\u062f\u062f \u0627\u0644\u0632\u064a\u0627\u0631\u0627\u062a","\u0627\u0644\u0647\u0627\u062a\u0641","\u0627\u0644\u0639\u0645\u064a\u0644"]; + await tableRow(tc, 60, hdrs, 48, true); + const rows = [ + ["850 \u0646\u0642\u0637\u0629","4,200 \u0631.\u0633","12 \u0632\u064a\u0627\u0631\u0629","05x xxx xxxx","\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a"], + ["620 \u0646\u0642\u0637\u0629","3,100 \u0631.\u0633","9 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0647\u0646\u062f \u0627\u0644\u0634\u0645\u0631\u064a"], + ["430 \u0646\u0642\u0637\u0629","2,150 \u0631.\u0633","7 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0631\u064a\u0645 \u0627\u0644\u0642\u062d\u0637\u0627\u0646\u064a"], + ["380 \u0646\u0642\u0637\u0629","1,900 \u0631.\u0633","6 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0633\u0627\u0631\u0629 \u0627\u0644\u063a\u0627\u0645\u062f\u064a"], + ["750 \u0646\u0642\u0637\u0629","3,750 \u0631.\u0633","10 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0645\u0646\u0649 \u0627\u0644\u062d\u0631\u0628\u064a"], + ["290 \u0646\u0642\u0637\u0629","1,450 \u0631.\u0633","5 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0641\u0627\u0637\u0645\u0629 \u0627\u0644\u0632\u0647\u0631\u0627\u0646\u064a"], + ["510 \u0646\u0642\u0637\u0629","2,550 \u0631.\u0633","8 \u0632\u064a\u0627\u0631\u0627\u062a","05x xxx xxxx","\u0623\u0645\u0644 \u0627\u0644\u0639\u0645\u0631\u064a"], + ]; + for (let i = 0; i < rows.length; i++) await tableRow(tc, 108 + i * 48, rows[i], 48, false); + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 05 الموظفون +// ═══════════════════════════════════════════════════════════════════ +async function p05(xi) { + await buildPage("05 - \u0627\u0644\u0645\u0648\u0638\u0641\u0648\u0646", xi, 4, async (c) => { + await topbar(c, "\u0627\u0644\u0645\u0648\u0638\u0641\u0648\u0646", "\u0625\u062f\u0627\u0631\u0629 \u0641\u0631\u064a\u0642 \u0627\u0644\u0639\u0645\u0644"); + const cx = 24, cy = TH + 20; + + const tabs = ["\u0623\u0639\u0636\u0627\u0621 \u0627\u0644\u0641\u0631\u064a\u0642","\u0637\u0644\u0628\u0627\u062a \u0627\u0644\u062a\u0648\u0638\u064a\u0641","\u0627\u0644\u062c\u062f\u0627\u0648\u0644"]; + for (let i = 0; i < tabs.length; i++) { + R(c, cx + i * (i === 0 ? 0 : i * 160), cy, 152, 40, i === 0 ? C.wine : C.cream, 1, 12); + await T(c, cx + i * 160, cy, 152, 40, tabs[i], 12, i === 0 ? C.white : C.textMuted, "Medium", "CENTER"); + } + + const staff = [ + ["\u0633\u0627\u0631\u0629 \u0627\u0644\u0645\u0647\u0646\u062f\u0633","\u0645\u062f\u064a\u0631\u0629 \u0627\u0644\u0635\u0627\u0644\u0648\u0646","4.9","92 \u062d\u062c\u0632", C.wine], + ["\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u0644\u064a","\u0623\u062e\u0635\u0627\u0626\u064a\u0629 \u0634\u0639\u0631","4.8","78 \u062d\u062c\u0632", C.wineLight], + ["\u0647\u0646\u062f \u0627\u0644\u062e\u0627\u0644\u062f","\u0623\u062e\u0635\u0627\u0626\u064a\u0629 \u0628\u0634\u0631\u0629","4.7","65 \u062d\u062c\u0632", C.gold], + ["\u0631\u064a\u0645 \u0627\u0644\u0641\u0647\u062f","\u0623\u062e\u0635\u0627\u0626\u064a\u0629 \u0623\u0638\u0627\u0641\u0631","4.9","84 \u062d\u062c\u0632", C.success], + ["\u0623\u0645\u0644 \u0627\u0644\u0631\u0627\u0634\u062f","\u0645\u0633\u0627\u062c\u064a\u0633\u062a","4.6","55 \u062d\u062c\u0632", C.wine], + ["\u0644\u0645\u0649 \u0627\u0644\u0645\u0637\u064a\u0631\u064a","\u0645\u064a\u0643\u0627\u0628 \u0623\u0631\u062a\u064a\u0633\u062a","4.8","70 \u062d\u062c\u0632", C.wineLight], + ]; + const cw = Math.floor((CW - 48 - 40) / 3); + for (let i = 0; i < staff.length; i++) { + const col = i % 3, row = Math.floor(i / 3); + const sx = cx + col * (cw + 20), sy = cy + 56 + row * 168; + const sc = card(c, sx, sy, cw, 152, 20); + // Top accent + R(sc, 0, 0, cw, 5, staff[i][4], 1, 0); + // Avatar + R(sc, sc.width - 60, 20, 48, 48, staff[i][4], 0.12, 24); + R(sc, sc.width - 56, 24, 40, 40, staff[i][4], 0.18, 20); + await T(sc, sc.width - 60, 20, 48, 48, staff[i][0].charAt(0), 18, staff[i][4], "Bold", "CENTER"); + // Info + await T(sc, 20, 20, cw - 88, 22, staff[i][0], 14, C.textPrimary, "Bold", "LEFT"); + await T(sc, 20, 44, cw - 88, 18, staff[i][1], 11, C.textMuted, "Regular", "LEFT"); + // Rating + R(sc, 20, 72, 100, 24, C.goldLight, 1, 12); + await T(sc, 20, 72, 100, 24, staff[i][2] + " / 5", 12, C.goldDark, "Bold", "CENTER"); + // Bookings + await T(sc, 136, 72, 100, 24, staff[i][3], 12, C.textSecond, "Regular", "LEFT"); + // Action buttons + R(sc, 20, 108, 80, 28, C.wine50, 1, 10); + await T(sc, 20, 108, 80, 28, "\u0627\u0644\u062c\u062f\u0648\u0644", 10, C.wine, "Medium", "CENTER"); + R(sc, 110, 108, 80, 28, C.cream, 1, 10); + await T(sc, 110, 108, 80, 28, "\u0627\u0644\u0645\u0644\u0641", 10, C.textSecond, "Medium", "CENTER"); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 06 المخزون +// ═══════════════════════════════════════════════════════════════════ +async function p06(xi) { + await buildPage("06 - \u0627\u0644\u0645\u062e\u0632\u0648\u0646", xi, 5, async (c) => { + await topbar(c, "\u0627\u0644\u0645\u062e\u0632\u0648\u0646", "\u0625\u062f\u0627\u0631\u0629 \u0645\u0648\u0627\u062f \u0648\u0623\u062f\u0648\u0627\u062a \u0627\u0644\u0635\u0627\u0644\u0648\u0646"); + const cx = 24, cy = TH + 20; + const sw = Math.floor((CW - 48 - 24) / 3); + const sdata = [ + ["\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0648\u0627\u062f","124 \u0635\u0646\u0641","\u0645\u062a\u0627\u062d \u0641\u064a \u0627\u0644\u0645\u062e\u0632\u0648\u0646", C.wine, true], + ["\u062a\u062d\u062a \u0627\u0644\u062d\u062f \u0627\u0644\u0623\u062f\u0646\u0649","8 \u0623\u0635\u0646\u0627\u0641","\u062a\u062d\u062a\u0627\u062c \u0625\u0639\u0627\u062f\u0629 \u0637\u0644\u0628", C.danger, false], + ["\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u062e\u0632\u0648\u0646","28,500 \u0631.\u0633","\u0627\u0644\u062a\u0642\u062f\u064a\u0631 \u0627\u0644\u0643\u0644\u064a", C.gold, true], + ]; + for (let i = 0; i < sdata.length; i++) { + await statCard(c, cx + i * (sw + 12), cy, sw, sdata[i][0], sdata[i][1], sdata[i][2], sdata[i][3], sdata[i][4]); + } + + const tc = card(c, cx, cy + 124, CW - 48, FH - TH - cy - 152); + await searchBar(tc, tc.width - 340, 12, 300, "\u0628\u062d\u062b \u0639\u0646 \u0645\u0627\u062f\u0629..."); + R(tc, 16, 12, 110, 36, C.wine, 1, 12); + await T(tc, 16, 12, 110, 36, "+ \u0625\u0636\u0627\u0641\u0629 \u0645\u0627\u062f\u0629", 11, C.white, "Medium", "CENTER"); + R(tc, 0, 60, tc.width, 1, C.borderLight); + + const hdrs = ["\u0627\u0644\u062d\u0627\u0644\u0629","\u0627\u0644\u062d\u062f \u0627\u0644\u0623\u062f\u0646\u0649","\u0627\u0644\u0643\u0645\u064a\u0629","\u0627\u0644\u0648\u062d\u062f\u0629","\u0627\u0633\u0645 \u0627\u0644\u0645\u0627\u062f\u0629"]; + await tableRow(tc, 60, hdrs, 48, true); + const rows = [ + ["\u062c\u064a\u062f","10","45","\u0639\u0644\u0628\u0629","\u0635\u0628\u063a\u0629 \u0627\u0644\u0634\u0639\u0631"], + ["\u062c\u064a\u062f","5","28","\u0643\u064a\u0644\u0648","\u0643\u0631\u064a\u0645 \u0627\u0644\u0634\u0639\u0631"], + ["\u0645\u0646\u062e\u0641\u0636","20","6","\u0642\u0637\u0639\u0629","\u0623\u062f\u0648\u0627\u062a \u0627\u0644\u0642\u0635"], + ["\u062c\u064a\u062f","15","62","\u0632\u062c\u0627\u062c\u0629","\u0634\u0627\u0645\u0628\u0648"], + ["\u062c\u064a\u062f","10","34","\u0639\u0644\u0628\u0629","\u0623\u0635\u0628\u0627\u063a \u0627\u0644\u0623\u0638\u0627\u0641\u0631"], + ["\u0645\u0646\u062e\u0641\u0636","8","2","\u0639\u0644\u0628\u0629","\u0648\u0631\u0642 \u0627\u0644\u062a\u0644\u0648\u064a\u0646"], + ["\u062c\u064a\u062f","5","20","\u0643\u064a\u0644\u0648","\u0642\u0646\u0627\u0639 \u0627\u0644\u0648\u062c\u0647"], + ]; + for (let i = 0; i < rows.length; i++) { + await tableRow(tc, 108 + i * 48, rows[i], 48, false); + if (rows[i][0] === "\u0645\u0646\u062e\u0641\u0636") { + R(tc, 0, 108 + i * 48, tc.width, 47, C.danger, 0.04); + R(tc, 0, 108 + i * 48, 3, 47, C.danger, 0.8, 0); + } + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 07 المتجر +// ═══════════════════════════════════════════════════════════════════ +async function p07(xi) { + await buildPage("07 - \u0627\u0644\u0645\u062a\u062c\u0631", xi, 6, async (c) => { + await topbar(c, "\u0627\u0644\u0645\u062a\u062c\u0631", "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0645\u0646\u062a\u062c\u0627\u062a \u0648\u0627\u0644\u0637\u0644\u0628\u0627\u062a"); + const cx = 24, cy = TH + 20; + + const tabs = ["\u0627\u0644\u0645\u0646\u062a\u062c\u0627\u062a","\u0627\u0644\u0637\u0644\u0628\u0627\u062a","\u0627\u0644\u062f\u0648\u0631\u0627\u062a \u0627\u0644\u062a\u062f\u0631\u064a\u0628\u064a\u0629"]; + for (let i = 0; i < tabs.length; i++) { + R(c, cx + i * 170, cy, 162, 40, i === 0 ? C.wine : C.cream, 1, 12); + await T(c, cx + i * 170, cy, 162, 40, tabs[i], 12, i === 0 ? C.white : C.textMuted, "Medium", "CENTER"); + } + + const prods = [ + {n:"\u0634\u0627\u0645\u0628\u0648 \u0645\u0631\u0637\u0628",p:"85 \u0631.\u0633",q:"45",on:true,col:C.wine}, + {n:"\u0643\u0631\u064a\u0645 \u0627\u0644\u0634\u0639\u0631",p:"120 \u0631.\u0633",q:"32",on:true,col:C.gold}, + {n:"\u0635\u0628\u063a\u0629 \u0637\u0628\u064a\u0639\u064a\u0629",p:"95 \u0631.\u0633",q:"28",on:false,col:C.wineLight}, + {n:"\u0642\u0646\u0627\u0639 \u0627\u0644\u0639\u0633\u0644",p:"150 \u0631.\u0633",q:"18",on:true,col:C.success}, + {n:"\u0632\u064a\u062a \u0627\u0644\u0623\u0631\u063a\u0627\u0646",p:"200 \u0631.\u0633",q:"12",on:true,col:C.wine}, + {n:"\u0643\u0631\u064a\u0645 \u0627\u0644\u0648\u062c\u0647",p:"180 \u0631.\u0633",q:"0",on:false,col:C.textMuted}, + ]; + const pw = Math.floor((CW - 48 - 50) / 3); + for (let i = 0; i < prods.length; i++) { + const col = i % 3, row = Math.floor(i / 3); + const px = cx + col * (pw + 25), py = cy + 60 + row * 196; + const pc = card(c, px, py, pw, 180, 20); + // Product image area + R(pc, 0, 0, pw, 100, prods[i].on ? C.wine50 : C.cream, 1, 20); + R(pc, 0, 80, pw, 20, prods[i].on ? C.wine50 : C.cream, 1); // bottom fill + R(pc, pw/2 - 30, 20, 60, 60, prods[i].col, 0.15, 20); + R(pc, pw/2 - 20, 30, 40, 40, prods[i].col, 0.25, 12); + // Out of stock badge + if (prods[i].q === "0") { + R(pc, pw - 80, 12, 64, 22, C.dangerBg, 1, 11); + await T(pc, pw - 80, 12, 64, 22, "\u0646\u0641\u062f", 9, C.danger, "Bold", "CENTER"); + } else if (prods[i].on) { + R(pc, pw - 72, 12, 56, 22, C.successBg, 1, 11); + await T(pc, pw - 72, 12, 56, 22, "\u0645\u062a\u0627\u062d", 9, C.success, "Bold", "CENTER"); + } + await T(pc, pw - 20, 108, pw - 40, 20, prods[i].n, 13, C.textPrimary, "Semi Bold"); + await T(pc, 20, 108, 100, 20, prods[i].p, 14, C.goldDark, "Bold", "LEFT"); + await T(pc, 20, 132, 120, 16, prods[i].q + " \u0642\u0637\u0639\u0629", 10, C.textMuted, "Regular", "LEFT"); + R(pc, 20, 152, 80, 24, C.wine50, 1, 8); + await T(pc, 20, 152, 80, 24, "\u062a\u0639\u062f\u064a\u0644", 10, C.wine, "Medium", "CENTER"); + // Toggle + R(pc, pw - 66, 152, 44, 24, prods[i].on ? C.wine : C.creamDark, 1, 12); + R(pc, prods[i].on ? pw - 34 : pw - 64, 155, 18, 18, C.white, 1, 9); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 08 التفاعل +// ═══════════════════════════════════════════════════════════════════ +async function p08(xi) { + await buildPage("08 - \u0627\u0644\u062a\u0641\u0627\u0639\u0644", xi, 7, async (c) => { + await topbar(c, "\u0627\u0644\u062a\u0641\u0627\u0639\u0644", "\u0625\u062f\u0627\u0631\u0629 \u0627\u0644\u0645\u062a\u0627\u0628\u0639\u064a\u0646 \u0648\u0627\u0644\u062a\u0642\u064a\u064a\u0645\u0627\u062a"); + const cx = 24, cy = TH + 20; + + const tabs = ["\u0627\u0644\u0645\u062a\u0627\u0628\u0639\u0648\u0646","\u0627\u0644\u062a\u0642\u064a\u064a\u0645\u0627\u062a","\u0627\u0644\u0645\u062d\u062a\u0648\u0649","\u0627\u0644\u0634\u0643\u0627\u0648\u0649"]; + for (let i = 0; i < tabs.length; i++) { + R(c, cx + i * 152, cy, 144, 40, i === 1 ? C.wine : C.cream, 1, 12); + await T(c, cx + i * 152, cy, 144, 40, tabs[i], 12, i === 1 ? C.white : C.textMuted, "Medium", "CENTER"); + } + + const sw = Math.floor((CW - 48 - 24) / 3); + const sdata = [ + ["\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u062a\u0642\u064a\u064a\u0645\u0627\u062a","284 \u062a\u0642\u064a\u064a\u0645","\u0632\u064a\u0627\u062f\u0629 18 \u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.wine, true], + ["\u0627\u0644\u0645\u062a\u0648\u0633\u0637 \u0627\u0644\u0639\u0627\u0645","4.8 / 5","\u062a\u0642\u064a\u064a\u0645 \u0645\u0645\u062a\u0627\u0632", C.gold, true], + ["5 \u0646\u062c\u0648\u0645","68% \u0645\u0646 \u0627\u0644\u062a\u0642\u064a\u064a\u0645\u0627\u062a","\u0627\u0644\u0641\u0626\u0629 \u0627\u0644\u0623\u0639\u0644\u0649", C.success, true], + ]; + for (let i = 0; i < sdata.length; i++) { + await statCard(c, cx + i * (sw + 12), cy + 56, sw, sdata[i][0], sdata[i][1], sdata[i][2], sdata[i][3], sdata[i][4]); + } + + const rc = card(c, cx, cy + 180, CW - 48, FH - TH - cy - 210); + await cardHeader(rc, CW - 48, "\u0623\u062d\u062f\u062b \u0627\u0644\u062a\u0642\u064a\u064a\u0645\u0627\u062a", "\u0631\u062f \u0639\u0644\u0649 \u0627\u0644\u0643\u0644"); + const revs = [ + ["\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a","5 / 5","\u062e\u062f\u0645\u0629 \u0645\u0645\u062a\u0627\u0632\u0629 \u0648\u0641\u0631\u064a\u0642 \u0645\u062d\u062a\u0631\u0641 \u062c\u062f\u0627\u064b","26 \u0645\u0627\u064a\u0648"], + ["\u0647\u0646\u062f \u0627\u0644\u0634\u0645\u0631\u064a","4 / 5","\u0631\u0627\u0636\u064a\u0629 \u0639\u0646 \u0627\u0644\u062e\u062f\u0645\u0629 \u0628\u0634\u0643\u0644 \u0639\u0627\u0645","25 \u0645\u0627\u064a\u0648"], + ["\u0631\u064a\u0645 \u0627\u0644\u0642\u062d\u0637\u0627\u0646\u064a","5 / 5","\u0623\u0641\u0636\u0644 \u0635\u0627\u0644\u0648\u0646 \u062a\u0639\u0627\u0645\u0644\u062a \u0645\u0639\u0647","24 \u0645\u0627\u064a\u0648"], + ["\u0633\u0627\u0631\u0629 \u0627\u0644\u063a\u0627\u0645\u062f\u064a","5 / 5","\u0646\u062a\u064a\u062c\u0629 \u0631\u0627\u0626\u0639\u0629 \u0648\u0633\u0639\u0631 \u0645\u0646\u0627\u0633\u0628","23 \u0645\u0627\u064a\u0648"], + ]; + for (let i = 0; i < revs.length; i++) { + const ry = 68 + i * 82; + R(rc, rc.width - 60, ry + 8, 44, 44, C.wine50, 1, 22); + await T(rc, rc.width - 60, ry + 8, 44, 44, revs[i][0].charAt(0), 16, C.wine, "Bold", "CENTER"); + await T(rc, rc.width - 116, ry + 10, 200, 22, revs[i][0], 13, C.textPrimary, "Bold"); + // Star rating + R(rc, rc.width - 116, ry + 34, 80, 22, C.goldLight, 1, 11); + await T(rc, rc.width - 116, ry + 34, 80, 22, revs[i][1], 11, C.goldDark, "Bold", "CENTER"); + await T(rc, 80, ry + 16, rc.width - 230, 36, revs[i][2], 12, C.textMuted, "Regular", "LEFT"); + await T(rc, 20, ry + 20, 60, 18, revs[i][3], 10, C.textMuted, "Regular", "LEFT"); + R(rc, 20, ry + 44, 70, 22, C.wine50, 1, 11); + await T(rc, 20, ry + 44, 70, 22, "\u0631\u062f", 10, C.wine, "Medium", "CENTER"); + if (i < revs.length - 1) R(rc, 16, ry + 81, rc.width - 32, 1, C.borderLight); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 09 السجل الصحي +// ═══════════════════════════════════════════════════════════════════ +async function p09(xi) { + await buildPage("09 - \u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0635\u062d\u064a", xi, 8, async (c) => { + await topbar(c, "\u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0635\u062d\u064a", "\u0628\u064a\u0627\u0646\u0627\u062a \u0635\u062d\u064a\u0629 \u0633\u0631\u064a\u0629 \u0644\u0644\u0639\u0645\u0644\u0627\u0621"); + const cx = 24, cy = TH + 20; + + // Privacy banner + const pb = F(c, cx, cy, CW - 48, 48, C.warningBg, 1, 12); + R(pb, pb.width - 40, 12, 24, 24, C.warning, 0.3, 12); + await T(pb, 16, 0, pb.width - 60, 48, "\u062a\u0646\u0628\u064a\u0647: \u0647\u0630\u0647 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0633\u0631\u064a\u0629 - \u0644\u0627 \u064a\u062c\u0648\u0632 \u0627\u0644\u0627\u0637\u0644\u0627\u0639 \u0625\u0644\u0627 \u0628\u0645\u0648\u0627\u0641\u0642\u0629 \u0627\u0644\u0639\u0645\u064a\u0644", 12, C.warning, "Medium", "LEFT"); + + const lw = 300, dw = CW - 48 - lw - 16, spY = cy + 64, spH = FH - TH - cy - 92; + + // Client list + const lc = card(c, cx + dw + 16, spY, lw, spH, 16); + await searchBar(lc, 12, 12, lw - 24, "\u0628\u062d\u062b..."); + R(lc, 0, 64, lw, 1, C.borderLight); + const names = ["\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a","\u0647\u0646\u062f \u0627\u0644\u0634\u0645\u0631\u064a","\u0631\u064a\u0645 \u0627\u0644\u0642\u062d\u0637\u0627\u0646\u064a","\u0633\u0627\u0631\u0629 \u0627\u0644\u063a\u0627\u0645\u062f\u064a","\u0641\u0627\u0637\u0645\u0629 \u0627\u0644\u0632\u0647\u0631\u0627\u0646\u064a"]; + for (let i = 0; i < names.length; i++) { + R(lc, 8, 72 + i * 60, lw - 16, 52, i === 0 ? C.wine50 : C.white, 1, 10); + if (i === 0) R(lc, 8, 72, 4, 52, C.wine, 1, 2); + R(lc, lw - 52, 72 + i * 60 + 10, 36, 36, i === 0 ? C.wine100 : C.cream, 1, 18); + await T(lc, lw - 52, 72 + i * 60 + 10, 36, 36, names[i].charAt(0), 14, i === 0 ? C.wine : C.textMuted, "Bold", "CENTER"); + await T(lc, 12, 72 + i * 60 + 8, lw - 76, 20, names[i], 12, i === 0 ? C.wine : C.textPrimary, i === 0 ? "Semi Bold" : "Regular", "LEFT"); + await T(lc, 12, 72 + i * 60 + 30, 100, 14, i < 3 ? "\u0645\u0648\u0627\u0641\u0642" : "\u063a\u064a\u0631 \u0645\u0648\u0627\u0641\u0642", 10, i < 3 ? C.success : C.danger, "Regular", "LEFT"); + } + + // Detail panel + const dc = card(c, cx, spY, dw, spH, 16); + R(dc, 0, 0, dw, 72, C.bg, 1, 16); + R(dc, 0, 52, dw, 20, C.bg, 1); + await T(dc, dc.width - 20, 16, 280, 28, "\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u062a\u064a\u0628\u064a", 18, C.textPrimary, "Bold"); + await T(dc, dc.width - 20, 46, 220, 20, "\u0639\u0645\u064a\u0644\u0629 \u0645\u0646\u0630 \u064a\u0646\u0627\u064a\u0631 2024 - 12 \u0632\u064a\u0627\u0631\u0629", 11, C.textMuted); + R(dc, 0, 72, dc.width, 1, C.borderLight); + + // Sections + let sy = 84; + const sections = [ + ["\u0627\u0644\u062d\u0633\u0627\u0633\u064a\u0627\u062a",["\u062d\u0633\u0627\u0633\u064a\u0629 \u0645\u0646 \u0627\u0644\u0628\u0646\u0633\u0644\u064a\u0646","\u062d\u0633\u0627\u0633\u064a\u0629 \u0645\u0646 \u0627\u0644\u0645\u0643\u0633\u0631\u0627\u062a"]], + ["\u0627\u0644\u0623\u0645\u0631\u0627\u0636 \u0627\u0644\u0645\u0632\u0645\u0646\u0629",["\u0636\u063a\u0637 \u0627\u0644\u062f\u0645 \u0627\u0644\u0645\u0631\u062a\u0641\u0639","\u0644\u0627 \u064a\u0648\u062c\u062f \u0633\u0643\u0631\u064a"]], + ]; + for (const sec of sections) { + R(dc, dc.width - 20, sy, 140, 28, C.wine50, 1, 14); + await T(dc, dc.width - 20, sy, 140, 28, sec[0], 12, C.wine, "Semi Bold"); + for (let i = 0; i < sec[1].length; i++) { + R(dc, 20, sy + 38 + i * 36, 10, 10, C.gold, 1, 5); + await T(dc, 36, sy + 34 + i * 36, dc.width - 60, 20, sec[1][i], 12, C.textPrimary, "Regular", "LEFT"); + } + sy += 40 + sec[1].length * 36 + 20; + } + + // Visit history timeline + R(dc, dc.width - 20, sy, 160, 28, C.wine50, 1, 14); + await T(dc, dc.width - 20, sy, 160, 28, "\u0633\u062c\u0644 \u0627\u0644\u0632\u064a\u0627\u0631\u0627\u062a", 12, C.wine, "Semi Bold"); + const visits = [ + ["26 \u0645\u0627\u064a\u0648 2026","\u0635\u0628\u063a \u0634\u0639\u0631","350 \u0631.\u0633"], + ["10 \u0645\u0627\u064a\u0648 2026","\u0642\u0635 \u0648\u062a\u0633\u0631\u064a\u062d\u0629","180 \u0631.\u0633"], + ["28 \u0623\u0628\u0631\u064a\u0644 2026","\u0643\u064a\u0631\u0627\u062a\u064a\u0646","500 \u0631.\u0633"], + ]; + for (let i = 0; i < visits.length; i++) { + R(dc, dc.width - 22, sy + 46 + i * 48, 12, 12, i === 0 ? C.wine : C.borderLight, 1, 6); + if (i < visits.length - 1) R(dc, dc.width - 17, sy + 58 + i * 48, 2, 36, C.borderLight); + const row = F(dc, 20, sy + 40 + i * 48, dc.width - 50, 40, C.bg, 1, 10); + await T(row, row.width - 16, 0, 180, 20, visits[i][0], 11, C.textMuted, "Regular"); + await T(row, row.width - 16, 20, 180, 16, visits[i][1], 11, C.textPrimary, "Medium"); + await T(row, 16, 10, 100, 20, visits[i][2], 12, C.goldDark, "Bold", "LEFT"); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 10 التقارير +// ═══════════════════════════════════════════════════════════════════ +async function p10(xi) { + await buildPage("10 - \u0627\u0644\u062a\u0642\u0627\u0631\u064a\u0631", xi, 9, async (c) => { + await topbar(c, "\u0627\u0644\u062a\u0642\u0627\u0631\u064a\u0631", "\u062a\u062d\u0644\u064a\u0644\u0627\u062a \u0648\u0625\u062d\u0635\u0627\u0621\u0627\u062a \u0627\u0644\u0623\u062f\u0627\u0621"); + const cx = 24, cy = TH + 20; + const sw = Math.floor((CW - 48 - 36) / 4); + const sdata = [ + ["\u0627\u0644\u0625\u064a\u0631\u0627\u062f\u0627\u062a","48,250 \u0631.\u0633","+ 12%", C.wine, true], + ["\u0627\u0644\u0645\u0635\u0631\u0648\u0641\u0627\u062a","18,800 \u0631.\u0633","- 3%", C.danger, false], + ["\u0635\u0627\u0641\u064a \u0627\u0644\u0631\u0628\u062d","29,450 \u0631.\u0633","+ 18%", C.success, true], + ["\u0645\u0639\u062f\u0644 \u0627\u0644\u0646\u0645\u0648","15.4%","\u0647\u0630\u0627 \u0627\u0644\u0634\u0647\u0631", C.gold, true], + ]; + for (let i = 0; i < sdata.length; i++) { + await statCard(c, cx + i * (sw + 12), cy, sw, sdata[i][0], sdata[i][1], sdata[i][2], sdata[i][3], sdata[i][4]); + } + + const cw = Math.floor((CW - 48 - 20) / 2), chY = cy + 124; + + // Revenue chart + const rcc = card(c, cx + cw + 20, chY, cw, 276, 20); + await cardHeader(rcc, cw, "\u0627\u0644\u0625\u064a\u0631\u0627\u062f\u0627\u062a \u0627\u0644\u0634\u0647\u0631\u064a\u0629"); + R(rcc, 20, 68, cw - 40, 168, C.bg, 1, 8); + bars(rcc, 24, 68, cw - 48, 168, [60,75,45,88,70,82,65]); + const months = ["\u064a\u0646\u0627","\u0641\u0628\u0631","\u0645\u0627\u0631","\u0623\u0628\u0631","\u0645\u0627\u064a","\u064a\u0648\u0646","\u064a\u0648\u0644"]; + for (let i = 0; i < months.length; i++) { + await T(rcc, 24 + i * Math.floor((cw - 48) / 7), 242, 36, 14, months[i], 9, C.textMuted, "Regular", "CENTER"); + } + + // Donut chart + const pcc = card(c, cx, chY, cw, 276, 20); + await cardHeader(pcc, cw, "\u062a\u0648\u0632\u064a\u0639 \u0627\u0644\u062e\u062f\u0645\u0627\u062a"); + const pieC = [C.wine, C.gold, C.success, C.wineLight, { r:0.65,g:0.80,b:0.68 }]; + const pieL = ["\u0634\u0639\u0631 40%","\u0628\u0634\u0631\u0629 25%","\u0623\u0638\u0627\u0641\u0631 20%","\u0645\u0633\u0627\u062c 10%","\u0623\u062e\u0631\u0649 5%"]; + for (let i = 0; i < pieL.length; i++) { + R(pcc, 24, 72 + i * 32, 14, 14, pieC[i], 1, 7); + await T(pcc, 44, 70 + i * 32, 140, 18, pieL[i], 12, C.textPrimary, "Regular", "LEFT"); + } + R(pcc, pcc.width - 176, 72, 144, 144, C.wine, 1, 72); + R(pcc, pcc.width - 140, 108, 72, 72, C.white, 1, 36); + await T(pcc, pcc.width - 140, 108, 72, 72, "284", 16, C.wine, "Bold", "CENTER"); + + // Staff performance + const spc = card(c, cx, chY + 292, CW - 48, 192, 20); + await cardHeader(spc, CW - 48, "\u0623\u062f\u0627\u0621 \u0627\u0644\u0645\u0648\u0638\u0641\u064a\u0646", "\u0639\u0631\u0636 \u0627\u0644\u0643\u0644"); + const ph = ["\u0627\u0644\u062a\u0642\u064a\u064a\u0645","\u0627\u0644\u0625\u064a\u0631\u0627\u062f","\u0639\u062f\u062f \u0627\u0644\u062d\u062c\u0648\u0632\u0627\u062a","\u0627\u0644\u0645\u0648\u0638\u0641"]; + await tableRow(spc, 60, ph, 44, true); + const perf = [ + ["4.9 / 5","18,500 \u0631.\u0633","92 \u062d\u062c\u0632","\u0633\u0627\u0631\u0629 \u0627\u0644\u0645\u0647\u0646\u062f\u0633"], + ["4.8 / 5","15,200 \u0631.\u0633","78 \u062d\u062c\u0632","\u0646\u0648\u0631\u0629 \u0627\u0644\u0639\u0644\u064a"], + ["4.7 / 5","12,800 \u0631.\u0633","65 \u062d\u062c\u0632","\u0647\u0646\u062f \u0627\u0644\u062e\u0627\u0644\u062f"], + ]; + for (let i = 0; i < perf.length; i++) await tableRow(spc, 104 + i * 40, perf[i], 40, false); + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// PAGE 11 الإعدادات +// ═══════════════════════════════════════════════════════════════════ +async function p11(xi) { + await buildPage("11 - \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", xi, 10, async (c) => { + await topbar(c, "\u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", "\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0648\u062a\u062e\u0635\u064a\u0635\u0627\u062a \u0627\u0644\u0635\u0627\u0644\u0648\u0646"); + const cx = 24, cy = TH + 20; + const cw = Math.floor((CW - 48 - 20) / 2); + + // Salon info + const ic = card(c, cx + cw + 20, cy, cw, 316, 20); + R(ic, 0, 0, cw, 52, C.bg, 1, 20); + R(ic, 0, 32, cw, 20, C.bg, 1); + await T(ic, ic.width - 20, 12, 200, 28, "\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0627\u0644\u0635\u0627\u0644\u0648\u0646", 14, C.wine, "Bold"); + R(ic, 0, 52, ic.width, 1, C.borderLight); + const fields = [ + ["\u0627\u0633\u0645 \u0627\u0644\u0635\u0627\u0644\u0648\u0646","\u0635\u0627\u0644\u0648\u0646 \u0633\u0627\u0631\u0629"], + ["\u0631\u0642\u0645 \u0627\u0644\u062c\u0648\u0627\u0644","05x xxx xxxx"], + ["\u0627\u0644\u0639\u0646\u0648\u0627\u0646","\u0627\u0644\u0631\u064a\u0627\u0636\u060c \u062d\u064a \u0627\u0644\u0646\u0632\u0647\u0629"], + ["\u0627\u0644\u0628\u0631\u064a\u062f","sarah@salon.com"], + ]; + for (let i = 0; i < fields.length; i++) { + await T(ic, ic.width - 20, 60 + i * 62, ic.width - 40, 16, fields[i][0], 10, C.textMuted, "Regular"); + const fi = F(ic, 16, 78 + i * 62, ic.width - 32, 40, C.bg, 1, 10); + fi.strokes = [{ type: "SOLID", color: C.borderLight }]; fi.strokeWeight = 1; fi.strokeAlign = "INSIDE"; + await T(fi, 12, 0, fi.width - 24, 40, fields[i][1], 13, C.textPrimary, "Regular", "LEFT"); + } + R(ic, 16, ic.height - 52, ic.width - 32, 40, C.wine, 1, 12); + await T(ic, 16, ic.height - 52, ic.width - 32, 40, "\u062d\u0641\u0638 \u0627\u0644\u0645\u0639\u0644\u0648\u0645\u0627\u062a", 13, C.white, "Medium", "CENTER"); + + // Hours + const hc = card(c, cx, cy, cw, 316, 20); + R(hc, 0, 0, cw, 52, C.bg, 1, 20); + R(hc, 0, 32, cw, 20, C.bg, 1); + await T(hc, hc.width - 20, 12, 200, 28, "\u0633\u0627\u0639\u0627\u062a \u0627\u0644\u0639\u0645\u0644", 14, C.wine, "Bold"); + R(hc, 0, 52, hc.width, 1, C.borderLight); + const wdays = [ + ["\u0627\u0644\u0623\u062d\u062f - \u0627\u0644\u062e\u0645\u064a\u0633","9:00 - 22:00"], + ["\u0627\u0644\u062c\u0645\u0639\u0629","14:00 - 22:00"], + ["\u0627\u0644\u0633\u0628\u062a","9:00 - 23:00"], + ]; + for (let i = 0; i < wdays.length; i++) { + R(hc, 16, 60 + i * 82, hc.width - 32, 68, C.bg, 1, 12); + await T(hc, hc.width - 36, 74 + i * 82, cw - 60, 20, wdays[i][0], 12, C.textPrimary, "Medium"); + await T(hc, 32, 74 + i * 82, 160, 20, wdays[i][1], 13, C.goldDark, "Bold", "LEFT"); + R(hc, hc.width - 40, 68 + i * 82, 24, 24, C.wine50, 1, 8); + await T(hc, hc.width - 40, 68 + i * 82, 24, 24, "\u062a", 10, C.wine, "Bold", "CENTER"); + } + + // Notifications + const nc = card(c, cx, cy + 332, CW - 48, 204, 20); + R(nc, 0, 0, CW - 48, 52, C.bg, 1, 20); + R(nc, 0, 32, CW - 48, 20, C.bg, 1); + await T(nc, nc.width - 20, 12, 240, 28, "\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a", 14, C.wine, "Bold"); + R(nc, 0, 52, nc.width, 1, C.borderLight); + const notifs = [ + ["\u0625\u0634\u0639\u0627\u0631\u0627\u062a \u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a", true], + ["\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a \u0627\u0644\u0641\u0648\u0631\u064a\u0629", true], + ["\u0631\u0633\u0627\u0626\u0644 SMS", false], + ["\u062a\u0642\u0627\u0631\u064a\u0631 \u0623\u0633\u0628\u0648\u0639\u064a\u0629", true], + ]; + for (let i = 0; i < notifs.length; i++) { + const col = i % 2, row = Math.floor(i / 2); + const nw = (nc.width - 48) / 2; + const nx = col === 0 ? nc.width - 24 - nw : 24; + const ny = 60 + row * 68; + await T(nc, nx - nw + 60, ny + 8, nw - 68, 22, notifs[i][0], 13, C.textPrimary); + R(nc, nx - 52, ny + 6, 44, 24, notifs[i][1] ? C.wine : C.creamDark, 1, 12); + R(nc, notifs[i][1] ? nx - 28 : nx - 50, ny + 9, 18, 18, C.white, 1, 9); + } + }); +} + +// ═══════════════════════════════════════════════════════════════════ +// ENTRY POINT +// ═══════════════════════════════════════════════════════════════════ +figma.ui.onmessage = async (msg) => { + if (msg.type !== "generate") return; + + const pages = [ + { fn: pOnboard1, label: "\u0646\u0645\u0648\u0630\u062c 1" }, + { fn: pOnboard2, label: "\u0646\u0645\u0648\u0630\u062c 2" }, + { fn: pOnboard3, label: "\u0646\u0645\u0648\u0630\u062c 3" }, + { fn: pLogin, label: "\u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644" }, + { fn: pRegister, label: "\u0625\u0646\u0634\u0627\u0621 \u062d\u0633\u0627\u0628" }, + { fn: p01, label: "\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629" }, + { fn: p02, label: "\u0627\u0644\u0645\u0648\u0627\u0639\u064a\u062f" }, + { fn: p03, label: "\u0627\u0644\u062e\u062f\u0645\u0627\u062a" }, + { fn: p04, label: "\u0627\u0644\u0639\u0645\u0644\u0627\u0621" }, + { fn: p05, label: "\u0627\u0644\u0645\u0648\u0638\u0641\u0648\u0646" }, + { fn: p06, label: "\u0627\u0644\u0645\u062e\u0632\u0648\u0646" }, + { fn: p07, label: "\u0627\u0644\u0645\u062a\u062c\u0631" }, + { fn: p08, label: "\u0627\u0644\u062a\u0641\u0627\u0639\u0644" }, + { fn: p09, label: "\u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0635\u062d\u064a" }, + { fn: p10, label: "\u0627\u0644\u062a\u0642\u0627\u0631\u064a\u0631" }, + { fn: p11, label: "\u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a" }, + ]; + + try { + await loadFonts(); + + for (let i = 0; i < pages.length; i++) { + figma.ui.postMessage({ + type: "progress", + current: i + 1, + total: pages.length, + label: pages[i].label + }); + await pages[i].fn(i * (FW + 60)); + } + + figma.ui.postMessage({ type: "done", count: pages.length }); + figma.viewport.scrollAndZoomIntoView(figma.currentPage.children); + } catch (e) { + figma.ui.postMessage({ type: "error", message: e && e.message ? e.message : String(e) }); + } +}; diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..badcaa4 --- /dev/null +++ b/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "BeautyHub Dashboard Generator", + "id": "beautyhub-dashboard-gen", + "api": "1.0.0", + "main": "code.js", + "ui": "ui.html", + "editorType": ["figma"] +} diff --git a/ui.html b/ui.html new file mode 100644 index 0000000..e96024f --- /dev/null +++ b/ui.html @@ -0,0 +1,199 @@ + + + + + + + + +
+
BH
+
+
BeautyHub
+
مولّد التصاميم
+
+ 16 شاشة +
+ +

BeautyHub Dashboard Generator

+

سيتم إنشاء 16 شاشة كاملة: شاشات Onboarding تعريفية + تسجيل الدخول + إنشاء حساب + لوحة التحكم كاملة بتصميم محسّن.

+ +
+
شاشات التعريف — Onboarding
+
01مرحبا — Welcome
+
02المزايا — Features
+
03ابدأ الآن — Get Started
+ +
المصادقة — Auth
+
04تسجيل الدخول — Login
+
05إنشاء حساب — Register
+ +
لوحة التحكم — Dashboard (11 شاشة)
+
06الرئيسية
+
07المواعيد
+
08الخدمات
+
09العملاء
+
10الموظفون
+
11المخزون
+
12المتجر
+
13التفاعل
+
14السجل الصحي
+
15التقارير
+
16الإعدادات
+
+ +
+
جاري الإنشاء...
+
+
0 / 16
+
+ +
+ + + + + +