( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ HEX
HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux mail.thebrand.ai 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/tmpr/../tmpr/../tmpr/../tmpr/../tmpr/../fcanvasbric/design.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DesignAI — Text-to-Canvas Studio</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;1,300&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>

  <style>
    *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

    :root {
      --bg:            #080a0f;
      --surface:       #0d1017;
      --surface-2:     #111520;
      --surface-3:     #161b28;
      --border:        rgba(255,255,255,0.055);
      --border-strong: rgba(255,255,255,0.1);
      --accent:        #4361EE;
      --accent-dim:    rgba(67,97,238,0.14);
      --accent-glow:   rgba(67,97,238,0.35);
      --accent-hover:  #3451d6;
      --violet:        #7c5cfc;
      --text:          #dde2f0;
      --text-2:        #7e8aaa;
      --text-3:        #3d4460;
      --danger:        #e8404c;
      --danger-dim:    rgba(232,64,76,0.12);
      --success:       #2dd784;
      --success-dim:   rgba(45,215,132,0.12);
      --warning:       #f5a623;
      --radius-sm:     6px;
      --radius:        9px;
      --radius-lg:     13px;
      --shadow-sm:     0 1px 3px rgba(0,0,0,0.4);
      --shadow:        0 4px 20px rgba(0,0,0,0.5);
      --shadow-lg:     0 16px 60px rgba(0,0,0,0.6), 0 4px 16px rgba(0,0,0,0.4);
      --font-ui:       'IBM Plex Sans', system-ui, sans-serif;
      --font-display:  'Syne', system-ui, sans-serif;
      --font-mono:     'IBM Plex Mono', monospace;
    }

    html, body {
      height: 100%;
      background: var(--bg);
      color: var(--text);
      font-family: var(--font-ui);
      font-size: 13px;
      line-height: 1.5;
      -webkit-font-smoothing: antialiased;
    }

    body {
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    /* ─── SCROLLBAR ─────────────────────────────────── */
    ::-webkit-scrollbar { width: 5px; height: 5px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 99px; }
    ::-webkit-scrollbar-thumb:hover { background: var(--text-3); }

    /* ─── HEADER ────────────────────────────────────── */
    .header {
      height: 52px;
      min-height: 52px;
      background: var(--surface);
      border-bottom: 1px solid var(--border);
      display: flex;
      align-items: center;
      padding: 0 16px 0 18px;
      gap: 14px;
      position: relative;
      z-index: 20;
    }

    .logo {
      display: flex;
      align-items: center;
      gap: 9px;
      text-decoration: none;
      flex-shrink: 0;
    }

    .logo-mark {
      width: 30px; height: 30px;
      background: var(--accent);
      border-radius: 8px;
      display: flex; align-items: center; justify-content: center;
      position: relative;
      overflow: hidden;
    }

    .logo-mark::before {
      content: '';
      position: absolute;
      width: 18px; height: 18px;
      background: rgba(255,255,255,0.15);
      border-radius: 50%;
      top: -6px; left: -6px;
    }

    .logo-mark svg { position: relative; z-index: 1; }

    .logo-name {
      font-family: var(--font-display);
      font-weight: 700;
      font-size: 15px;
      letter-spacing: -0.4px;
      color: var(--text);
    }

    .logo-tag {
      font-family: var(--font-mono);
      font-size: 9px;
      font-weight: 500;
      color: var(--accent);
      background: var(--accent-dim);
      border: 1px solid rgba(67,97,238,0.25);
      padding: 2px 6px;
      border-radius: 4px;
      letter-spacing: 0.6px;
      text-transform: uppercase;
    }

    .sep { width: 1px; height: 22px; background: var(--border); flex-shrink: 0; }

    .api-row {
      display: flex; align-items: center; gap: 8px;
      flex: 1; max-width: 360px;
    }

    .api-label {
      font-size: 11px;
      color: var(--text-2);
      white-space: nowrap;
      font-family: var(--font-mono);
    }

    .api-input {
      flex: 1;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 6px 10px;
      border-radius: var(--radius-sm);
      font-family: var(--font-mono);
      font-size: 11.5px;
      transition: border-color 0.15s;
    }
    .api-input:focus { outline: none; border-color: var(--accent); }
    .api-input::placeholder { color: var(--text-3); }

    .hdr-spacer { flex: 1; }

    .hdr-btns { display: flex; align-items: center; gap: 6px; }

    /* ─── GENERIC BUTTON ─────────────────────────────── */
    .btn {
      display: inline-flex; align-items: center; gap: 6px;
      padding: 6px 12px;
      background: var(--surface-3);
      border: 1px solid var(--border-strong);
      color: var(--text-2);
      border-radius: var(--radius-sm);
      font-family: var(--font-ui);
      font-size: 12px;
      font-weight: 400;
      cursor: pointer;
      transition: background 0.14s, color 0.14s, border-color 0.14s, transform 0.1s;
      user-select: none;
      white-space: nowrap;
    }
    .btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
    .btn:active { transform: scale(0.97); }
    .btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
    .btn svg { flex-shrink: 0; }

    .btn-primary {
      background: var(--accent);
      border-color: var(--accent);
      color: #fff;
      font-weight: 500;
    }
    .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
    .btn-primary:disabled { opacity: 0.5; }

    /* ─── MAIN LAYOUT ────────────────────────────────── */
    .workspace {
      flex: 1;
      display: grid;
      grid-template-columns: 192px 1fr 252px;
      overflow: hidden;
      min-height: 0;
    }

    /* ─── LEFT SIDEBAR ───────────────────────────────── */
    .sidebar {
      background: var(--surface);
      border-right: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      padding: 12px 10px;
      gap: 2px;
      overflow-y: auto;
    }

    .sb-section {
      font-family: var(--font-display);
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: 1.2px;
      text-transform: uppercase;
      color: var(--text-3);
      padding: 8px 8px 5px;
      margin-top: 4px;
    }
    .sb-section:first-child { margin-top: 0; }

    .sb-divider { height: 1px; background: var(--border); margin: 6px 4px; }

    .tool-btn {
      display: flex; align-items: center; gap: 9px;
      padding: 8px 9px;
      background: transparent;
      border: 1px solid transparent;
      border-radius: var(--radius-sm);
      color: var(--text-2);
      font-family: var(--font-ui);
      font-size: 12.5px;
      font-weight: 400;
      cursor: pointer;
      transition: all 0.13s;
      text-align: left;
      width: 100%;
      user-select: none;
    }
    .tool-btn:hover {
      background: var(--surface-2);
      border-color: var(--border);
      color: var(--text);
    }
    .tool-btn:active { transform: scale(0.97); }

    .tool-btn.danger:hover {
      background: var(--danger-dim);
      border-color: rgba(232,64,76,0.2);
      color: var(--danger);
    }

    .tb-icon {
      width: 22px; height: 22px;
      display: flex; align-items: center; justify-content: center;
      flex-shrink: 0;
      border-radius: 5px;
      background: var(--surface-3);
      border: 1px solid var(--border);
      color: inherit;
      transition: background 0.13s;
    }
    .tool-btn:hover .tb-icon { background: var(--surface-2); border-color: var(--border-strong); }

    /* ─── CANVAS COLUMN ──────────────────────────────── */
    .canvas-col {
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    /* Prompt bar */
    .prompt-bar {
      padding: 12px 14px;
      background: var(--surface);
      border-bottom: 1px solid var(--border);
      display: flex;
      gap: 8px;
      align-items: flex-start;
      flex-shrink: 0;
    }

    .prompt-wrap {
      flex: 1;
      position: relative;
    }

    .prompt-sparkle {
      position: absolute;
      left: 10px; top: 50%;
      transform: translateY(-50%);
      color: var(--accent);
      pointer-events: none;
      transition: opacity 0.2s;
    }

    .prompt-input {
      width: 100%;
      background: var(--surface-2);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 9px 12px 9px 34px;
      border-radius: var(--radius);
      font-family: var(--font-ui);
      font-size: 13px;
      font-weight: 300;
      resize: none;
      min-height: 40px;
      max-height: 100px;
      line-height: 1.5;
      transition: border-color 0.15s, box-shadow 0.15s;
    }
    .prompt-input::placeholder { color: var(--text-3); }
    .prompt-input:focus {
      outline: none;
      border-color: var(--accent);
      box-shadow: 0 0 0 3px var(--accent-dim);
    }

    .generate-btn {
      display: flex; align-items: center; gap: 7px;
      padding: 9px 18px;
      background: var(--accent);
      border: 1px solid var(--accent);
      color: #fff;
      border-radius: var(--radius);
      font-family: var(--font-display);
      font-size: 13px;
      font-weight: 600;
      cursor: pointer;
      white-space: nowrap;
      flex-shrink: 0;
      transition: background 0.14s, box-shadow 0.14s, transform 0.1s;
      user-select: none;
    }
    .generate-btn:hover {
      background: var(--accent-hover);
      box-shadow: 0 4px 16px var(--accent-glow);
      transform: translateY(-1px);
    }
    .generate-btn:active { transform: translateY(0) scale(0.98); box-shadow: none; }
    .generate-btn:disabled {
      opacity: 0.55; cursor: not-allowed;
      transform: none; box-shadow: none;
    }

    .prompt-hint {
      font-size: 10px;
      color: var(--text-3);
      padding: 3px 0 0 2px;
      font-family: var(--font-mono);
    }

    /* Viewport */
    .canvas-viewport {
      flex: 1;
      overflow: auto;
      display: flex;
      align-items: flex-start;
      justify-content: center;
      padding: 32px;
      background:
        radial-gradient(circle at 25% 70%, rgba(67,97,238,0.05) 0%, transparent 55%),
        radial-gradient(circle at 75% 30%, rgba(124,92,252,0.04) 0%, transparent 55%);
      background-color: var(--bg);
      background-image:
        radial-gradient(circle, rgba(255,255,255,0.028) 1px, transparent 1px),
        radial-gradient(circle at 25% 70%, rgba(67,97,238,0.05) 0%, transparent 55%),
        radial-gradient(circle at 75% 30%, rgba(124,92,252,0.04) 0%, transparent 55%);
      background-size: 22px 22px, auto, auto;
      position: relative;
    }

    /* Canvas frame */
    .canvas-frame {
      position: relative;
      flex-shrink: 0;
    }

    /* Corner brackets */
    .canvas-frame::before,
    .canvas-frame::after,
    .cf-br::before,
    .cf-br::after {
      content: '';
      position: absolute;
      width: 14px; height: 14px;
      border-color: var(--accent);
      border-style: solid;
      z-index: 2;
    }

    .canvas-frame::before { top: -6px; left: -6px; border-width: 2px 0 0 2px; }
    .canvas-frame::after  { top: -6px; right: -6px; border-width: 2px 2px 0 0; }
    .cf-br::before { bottom: -6px; left: -6px; border-width: 0 0 2px 2px; }
    .cf-br::after  { bottom: -6px; right: -6px; border-width: 0 2px 2px 0; }

    .canvas-shadow {
      box-shadow:
        0 0 0 1px rgba(255,255,255,0.04),
        0 24px 80px rgba(0,0,0,0.65),
        0 6px 20px rgba(0,0,0,0.4);
      border-radius: 1px;
    }

    /* Canvas size label */
    .canvas-label {
      position: absolute;
      bottom: -24px; right: 0;
      font-family: var(--font-mono);
      font-size: 10px;
      color: var(--text-3);
      letter-spacing: 0.5px;
    }

    /* Loading overlay */
    .loading-overlay {
      position: absolute;
      inset: 0;
      background: rgba(8,10,15,0.82);
      backdrop-filter: blur(6px);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 14px;
      z-index: 50;
      border-radius: 1px;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s ease;
    }
    .loading-overlay.active {
      opacity: 1;
      pointer-events: all;
    }

    .spinner {
      width: 32px; height: 32px;
      position: relative;
    }
    .spinner::before, .spinner::after {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: 50%;
      border: 2.5px solid transparent;
    }
    .spinner::before {
      border-top-color: var(--accent);
      animation: spin 0.75s linear infinite;
    }
    .spinner::after {
      border-bottom-color: var(--violet);
      animation: spin 1.2s linear infinite reverse;
    }

    @keyframes spin { to { transform: rotate(360deg); } }

    .loading-text {
      font-family: var(--font-display);
      font-size: 13px;
      font-weight: 500;
      color: var(--text-2);
      letter-spacing: 0.3px;
    }

    /* ─── PROPERTIES PANEL ───────────────────────────── */
    .props-panel {
      background: var(--surface);
      border-left: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    .pp-header {
      padding: 0 14px;
      height: 44px;
      min-height: 44px;
      border-bottom: 1px solid var(--border);
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .pp-title {
      font-family: var(--font-display);
      font-size: 11px;
      font-weight: 600;
      letter-spacing: 1px;
      text-transform: uppercase;
      color: var(--text-3);
    }

    .obj-badge {
      font-family: var(--font-mono);
      font-size: 9.5px;
      padding: 2px 7px;
      border-radius: 4px;
      background: var(--accent-dim);
      border: 1px solid rgba(67,97,238,0.2);
      color: var(--accent);
      letter-spacing: 0.3px;
      text-transform: lowercase;
    }

    .pp-empty {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 12px;
      padding: 24px;
      text-align: center;
    }

    .pp-empty-icon {
      width: 44px; height: 44px;
      border: 1.5px dashed var(--border-strong);
      border-radius: var(--radius);
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--text-3);
    }

    .pp-empty-text {
      font-size: 12px;
      color: var(--text-3);
      line-height: 1.6;
      max-width: 160px;
    }

    .pp-scroll {
      flex: 1;
      overflow-y: auto;
      padding: 14px;
      display: flex;
      flex-direction: column;
      gap: 18px;
    }

    .pp-scroll.hidden { display: none; }

    /* Property group */
    .pg { display: flex; flex-direction: column; gap: 7px; }

    .pg-label {
      font-family: var(--font-display);
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: 1px;
      text-transform: uppercase;
      color: var(--text-3);
    }

    .pg-row { display: flex; align-items: center; gap: 7px; }

    .pg-subtext { font-size: 11px; color: var(--text-2); min-width: 48px; }

    /* Inputs */
    .p-input {
      flex: 1;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 6px 9px;
      border-radius: var(--radius-sm);
      font-family: var(--font-ui);
      font-size: 12.5px;
      transition: border-color 0.14s;
    }
    .p-input:focus { outline: none; border-color: var(--accent); }

    .p-input-sm {
      width: 68px;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 6px 8px;
      border-radius: var(--radius-sm);
      font-family: var(--font-ui);
      font-size: 12.5px;
    }
    .p-input-sm:focus { outline: none; border-color: var(--accent); }

    .p-select {
      flex: 1;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 6px 9px;
      border-radius: var(--radius-sm);
      font-family: var(--font-ui);
      font-size: 12.5px;
      cursor: pointer;
    }
    .p-select:focus { outline: none; border-color: var(--accent); }

    /* Color swatch */
    .color-chip {
      width: 30px; height: 30px;
      border-radius: var(--radius-sm);
      border: 1px solid var(--border-strong);
      overflow: hidden;
      flex-shrink: 0;
      cursor: pointer;
      position: relative;
    }
    .color-chip input[type="color"] {
      position: absolute;
      width: 160%; height: 160%;
      top: -30%; left: -30%;
      border: none;
      cursor: pointer;
      padding: 0;
    }

    /* Range */
    .p-range {
      width: 100%;
      height: 3px;
      accent-color: var(--accent);
      cursor: pointer;
    }

    .range-row { display: flex; align-items: center; gap: 8px; }
    .range-val {
      font-family: var(--font-mono);
      font-size: 11px;
      color: var(--text-2);
      min-width: 32px;
      text-align: right;
    }

    /* Z-order buttons */
    .z-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
    .z-btn {
      display: flex; align-items: center; justify-content: center; gap: 5px;
      padding: 7px 6px;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text-2);
      border-radius: var(--radius-sm);
      font-family: var(--font-ui);
      font-size: 11.5px;
      cursor: pointer;
      transition: all 0.13s;
    }
    .z-btn:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }
    .z-btn:active { transform: scale(0.96); }

    .pp-divider { height: 1px; background: var(--border); }

    /* ─── TOAST ──────────────────────────────────────── */
    .toast {
      position: fixed;
      bottom: 24px; left: 50%;
      transform: translateX(-50%) translateY(calc(100% + 24px));
      background: var(--surface-3);
      border: 1px solid var(--border-strong);
      border-radius: var(--radius-lg);
      padding: 11px 18px;
      display: flex; align-items: center; gap: 10px;
      font-size: 13px;
      box-shadow: var(--shadow-lg);
      z-index: 9999;
      transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
      max-width: 400px;
      pointer-events: none;
    }
    .toast.show { transform: translateX(-50%) translateY(0); }
    .toast.error  { border-color: rgba(232,64,76,0.3); }
    .toast.success { border-color: rgba(45,215,132,0.3); }
    .toast-icon { flex-shrink: 0; }
    .toast.error  .toast-icon { color: var(--danger); }
    .toast.success .toast-icon { color: var(--success); }
    .toast-msg { color: var(--text); }

    /* ─── FABRIC OVERRIDES ───────────────────────────── */
    .canvas-container { display: block !important; }
  </style>
</head>
<body>

<!-- ═══════════════════════════ HEADER ══════════════════════════════ -->
<header class="header">
  <a class="logo" href="#">
    <div class="logo-mark">
      <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
        <path d="M3 3h4v4H3zM9 3h4v4H9zM3 9h4v4H3z" fill="white" opacity="0.9"/>
        <rect x="10" y="10" width="3" height="3" rx="1.5" fill="white" opacity="0.9"/>
      </svg>
    </div>
    <span class="logo-name">DesignAI</span>
    <span class="logo-tag">Studio</span>
  </a>

  <div class="sep"></div>

  <div class="api-row">
    <span class="api-label">API</span>
    <input type="password" class="api-input" id="apiKeyInput" placeholder="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" autocomplete="off">
  </div>

  <div class="hdr-spacer"></div>

  <div class="hdr-btns">
    <button class="btn" id="undoBtn" title="Undo (Ctrl+Z)">
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="9 14 4 9 9 4"/><path d="M20 20v-7a4 4 0 0 0-4-4H4"/>
      </svg>
      Undo
    </button>
    <button class="btn btn-primary" id="downloadBtn" title="Export canvas as PNG">
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
      </svg>
      Export PNG
    </button>
  </div>
</header>

<!-- ═══════════════════════════ WORKSPACE ════════════════════════════ -->
<div class="workspace">

  <!-- ─── LEFT SIDEBAR ─────────────────────────────────── -->
  <aside class="sidebar">
    <div class="sb-section">Insert</div>

    <button class="tool-btn" id="addTextBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
          <polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/>
        </svg>
      </span>
      Text
    </button>

    <button class="tool-btn" id="addRectBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <rect x="3" y="3" width="18" height="18" rx="2"/>
        </svg>
      </span>
      Rectangle
    </button>

    <button class="tool-btn" id="addCircleBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <circle cx="12" cy="12" r="9"/>
        </svg>
      </span>
      Circle
    </button>

    <button class="tool-btn" id="addImageBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
        </svg>
      </span>
      Image URL
    </button>

    <div class="sb-divider"></div>
    <div class="sb-section">Actions</div>

    <button class="tool-btn danger" id="deleteBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
          <path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
        </svg>
      </span>
      Delete Selected
    </button>

    <button class="tool-btn danger" id="clearBtn">
      <span class="tb-icon">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/>
          <line x1="18" y1="9" x2="12" y2="15"/><line x1="12" y1="9" x2="18" y2="15"/>
        </svg>
      </span>
      Clear Canvas
    </button>
  </aside>

  <!-- ─── CANVAS COLUMN ──────────────────────────────── -->
  <section class="canvas-col">
    <div class="prompt-bar">
      <div style="flex:1; display:flex; flex-direction:column; gap:4px;">
        <div class="prompt-wrap">
          <span class="prompt-sparkle">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 17l-6.2 4.3 2.4-7.4L2 9.4h7.6z"/>
            </svg>
          </span>
          <textarea
            class="prompt-input"
            id="promptInput"
            rows="1"
            placeholder="Describe your design… e.g. 'bold event poster, dark background, gold headline, centered logo circle'"
          ></textarea>
        </div>
        <span class="prompt-hint">Ctrl+Enter to generate</span>
      </div>
      <button class="generate-btn" id="generateBtn">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
          <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
        </svg>
        Generate
      </button>
    </div>

    <div class="canvas-viewport" id="canvasViewport">
      <div class="canvas-frame cf-br" id="canvasFrame">
        <canvas class="canvas-shadow" id="design-canvas"></canvas>
        <div class="loading-overlay" id="loadingOverlay">
          <div class="spinner"></div>
          <span class="loading-text">Generating your design…</span>
        </div>
        <span class="canvas-label">800 × 600</span>
      </div>
    </div>
  </section>

  <!-- ─── PROPERTIES PANEL ─────────────────────────── -->
  <aside class="props-panel">
    <div class="pp-header">
      <span class="pp-title">Properties</span>
      <span class="obj-badge" id="objBadge" style="display:none"></span>
    </div>

    <div class="pp-empty" id="ppEmpty">
      <div class="pp-empty-icon">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
          <rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
          <rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
        </svg>
      </div>
      <p class="pp-empty-text">Select an object on the canvas to edit its properties</p>
    </div>

    <div class="pp-scroll hidden" id="ppContent">

      <!-- Fill -->
      <div class="pg">
        <span class="pg-label">Fill</span>
        <div class="pg-row">
          <div class="color-chip"><input type="color" id="fillColor" value="#4361EE"></div>
          <input type="text" class="p-input" id="fillColorText" value="#4361EE" maxlength="9" spellcheck="false">
        </div>
      </div>

      <div class="pp-divider"></div>

      <!-- Stroke -->
      <div class="pg">
        <span class="pg-label">Stroke</span>
        <div class="pg-row">
          <div class="color-chip"><input type="color" id="strokeColor" value="#000000"></div>
          <input type="text" class="p-input" id="strokeColorText" value="#000000" maxlength="9" spellcheck="false">
        </div>
        <div class="pg-row">
          <span class="pg-subtext">Width</span>
          <input type="number" class="p-input-sm" id="strokeWidth" value="0" min="0" max="50" step="1">
        </div>
      </div>

      <div class="pp-divider"></div>

      <!-- Opacity -->
      <div class="pg">
        <span class="pg-label">Opacity</span>
        <div class="range-row">
          <input type="range" class="p-range" id="opacityRange" min="0" max="100" value="100">
          <span class="range-val" id="opacityVal">100%</span>
        </div>
      </div>

      <div class="pp-divider"></div>

      <!-- Typography (text only) -->
      <div class="pg" id="typographyGroup" style="display:none">
        <span class="pg-label">Typography</span>
        <div class="pg-row">
          <span class="pg-subtext">Size</span>
          <input type="number" class="p-input-sm" id="fontSize" value="24" min="4" max="400">
        </div>
        <div class="pg-row" style="flex-direction:column; align-items:stretch; gap:5px;">
          <span class="pg-subtext" style="min-width:unset">Family</span>
          <select class="p-select" id="fontFamily">
            <option value="Syne">Syne</option>
            <option value="IBM Plex Sans">IBM Plex Sans</option>
            <option value="IBM Plex Mono">IBM Plex Mono</option>
            <option value="Georgia">Georgia</option>
            <option value="Times New Roman">Times New Roman</option>
            <option value="Courier New">Courier New</option>
            <option value="Impact">Impact</option>
            <option value="Verdana">Verdana</option>
            <option value="Trebuchet MS">Trebuchet MS</option>
            <option value="Arial">Arial</option>
            <option value="Helvetica">Helvetica</option>
          </select>
        </div>
      </div>

      <div class="pp-divider" id="typoDivider" style="display:none"></div>

      <!-- Layer order -->
      <div class="pg">
        <span class="pg-label">Layer</span>
        <div class="z-btns">
          <button class="z-btn" id="bringFwdBtn">
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="18 15 12 9 6 15"/></svg>
            Forward
          </button>
          <button class="z-btn" id="sendBkBtn">
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="6 9 12 15 18 9"/></svg>
            Backward
          </button>
        </div>
      </div>

    </div>
  </aside>
</div><!-- /workspace -->

<!-- ─── TOAST ──────────────────────────────────────── -->
<div class="toast" id="toast">
  <span class="toast-icon" id="toastIcon"></span>
  <span class="toast-msg" id="toastMsg"></span>
</div>

<!-- ═══════════════════════════ SCRIPT ═══════════════════════════════ -->
<script>
"use strict";

/* ──────────────── CANVAS INIT ──────────────── */
const canvas = new fabric.Canvas('design-canvas', {
  width: 800,
  height: 600,
  backgroundColor: '#ffffff',
  preserveObjectStacking: true,
  selection: true
});

/* ──────────────── UNDO SYSTEM ──────────────── */
const undoStack = [];

function saveState() {
  undoStack.push(JSON.stringify(canvas.toJSON()));
  if (undoStack.length > 50) undoStack.shift();
}

function undo() {
  if (!undoStack.length) { showToast('Nothing to undo', 'error'); return; }
  canvas.discardActiveObject();
  const prev = undoStack.pop();
  canvas.loadFromJSON(JSON.parse(prev), () => {
    canvas.renderAll();
    deselectAll();
  });
}

/* ──────────────── TOAST ────────────────────── */
let toastTimer = null;
function showToast(msg, type = 'error') {
  const el   = document.getElementById('toast');
  const icon = document.getElementById('toastIcon');
  const txt  = document.getElementById('toastMsg');
  el.className = `toast ${type}`;
  txt.textContent = msg;
  icon.innerHTML = type === 'error'
    ? `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><circle cx="12" cy="16" r="0.5" fill="currentColor"/></svg>`
    : `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>`;
  el.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => el.classList.remove('show'), 3500);
}

/* ──────────────── LOADING ──────────────────── */
function setLoading(on) {
  document.getElementById('loadingOverlay').classList.toggle('active', on);
  document.getElementById('generateBtn').disabled = on;
}

/* ──────────────── PROPERTY PANEL ──────────── */
let activeObj = null;
let _suppressSync = false;

function deselectAll() {
  activeObj = null;
  document.getElementById('ppEmpty').style.display   = '';
  document.getElementById('ppContent').classList.add('hidden');
  document.getElementById('objBadge').style.display  = 'none';
}

function isTextType(obj) {
  return obj && ['i-text','textbox','text'].includes(obj.type);
}

function hexFromColor(c) {
  if (!c || c === 'transparent') return '#000000';
  if (/^#[0-9a-fA-F]{3,6}$/.test(c)) {
    if (c.length === 4) {
      return '#' + [1,2,3].map(i=>c[i]+c[i]).join('');
    }
    return c;
  }
  // Attempt to parse rgb/rgba
  const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
  if (m) {
    return '#' + [m[1],m[2],m[3]].map(n=>parseInt(n).toString(16).padStart(2,'0')).join('');
  }
  return '#000000';
}

function setColorInput(pickerId, textId, color) {
  const hex = hexFromColor(color);
  try { document.getElementById(pickerId).value = hex; } catch(_) {}
  document.getElementById(textId).value = color || 'transparent';
}

function syncPanel(obj) {
  if (!obj) { deselectAll(); return; }
  activeObj = obj;
  document.getElementById('ppEmpty').style.display   = 'none';
  document.getElementById('ppContent').classList.remove('hidden');

  const badge = document.getElementById('objBadge');
  badge.style.display = '';
  badge.textContent = obj.type;

  _suppressSync = true;

  setColorInput('fillColor',   'fillColorText',   obj.fill   || '#000000');
  setColorInput('strokeColor', 'strokeColorText', obj.stroke || '#000000');

  document.getElementById('strokeWidth').value  = obj.strokeWidth || 0;

  const op = Math.round(((obj.opacity !== undefined ? obj.opacity : 1)) * 100);
  document.getElementById('opacityRange').value = op;
  document.getElementById('opacityVal').textContent = op + '%';

  const isText = isTextType(obj);
  document.getElementById('typographyGroup').style.display = isText ? '' : 'none';
  document.getElementById('typoDivider').style.display     = isText ? '' : 'none';
  if (isText) {
    document.getElementById('fontSize').value   = obj.fontSize   || 24;
    document.getElementById('fontFamily').value = obj.fontFamily || 'IBM Plex Sans';
  }

  _suppressSync = false;
}

/* Canvas events */
canvas.on('selection:created', e => syncPanel(e.selected?.[0]));
canvas.on('selection:updated', e => syncPanel(e.selected?.[0]));
canvas.on('selection:cleared', () => deselectAll());
canvas.on('object:modified',   () => { if (activeObj) syncPanel(activeObj); });

/* Applying changes */
function applyProp(props) {
  if (!activeObj || _suppressSync) return;
  saveState();
  activeObj.set(props);
  canvas.renderAll();
}

/* Fill */
document.getElementById('fillColor').addEventListener('input', e => {
  document.getElementById('fillColorText').value = e.target.value;
  applyProp({ fill: e.target.value });
});
document.getElementById('fillColorText').addEventListener('change', e => {
  let v = e.target.value.trim();
  if (v && !v.startsWith('#') && v !== 'transparent') v = '#' + v;
  try { document.getElementById('fillColor').value = hexFromColor(v); } catch(_) {}
  applyProp({ fill: v });
});

/* Stroke */
document.getElementById('strokeColor').addEventListener('input', e => {
  document.getElementById('strokeColorText').value = e.target.value;
  applyProp({ stroke: e.target.value });
});
document.getElementById('strokeColorText').addEventListener('change', e => {
  let v = e.target.value.trim();
  if (v && !v.startsWith('#') && v !== 'transparent') v = '#' + v;
  try { document.getElementById('strokeColor').value = hexFromColor(v); } catch(_) {}
  applyProp({ stroke: v });
});
document.getElementById('strokeWidth').addEventListener('change', e => {
  applyProp({ strokeWidth: Math.max(0, parseFloat(e.target.value) || 0) });
});

/* Opacity */
document.getElementById('opacityRange').addEventListener('input', e => {
  const pct = parseInt(e.target.value, 10);
  document.getElementById('opacityVal').textContent = pct + '%';
  if (!_suppressSync && activeObj) {
    activeObj.set({ opacity: pct / 100 });
    canvas.renderAll();
  }
});
document.getElementById('opacityRange').addEventListener('change', () => {
  if (activeObj) saveState();
});

/* Font */
document.getElementById('fontSize').addEventListener('change', e => {
  applyProp({ fontSize: Math.max(1, parseInt(e.target.value, 10) || 24) });
});
document.getElementById('fontFamily').addEventListener('change', e => {
  applyProp({ fontFamily: e.target.value });
});

/* Layer order */
document.getElementById('bringFwdBtn').addEventListener('click', () => {
  if (!activeObj) return;
  saveState();
  canvas.bringForward(activeObj);
  canvas.renderAll();
});
document.getElementById('sendBkBtn').addEventListener('click', () => {
  if (!activeObj) return;
  saveState();
  canvas.sendBackwards(activeObj);
  canvas.renderAll();
});

/* ──────────────── TOOLBAR ──────────────────── */
document.getElementById('addTextBtn').addEventListener('click', () => {
  saveState();
  const t = new fabric.IText('Edit this text', {
    left: 200, top: 200,
    fontFamily: 'Syne', fontSize: 32,
    fill: '#1a1a2e', fontWeight: '700'
  });
  canvas.add(t);
  canvas.setActiveObject(t);
  canvas.renderAll();
});

document.getElementById('addRectBtn').addEventListener('click', () => {
  saveState();
  const r = new fabric.Rect({
    left: 150, top: 150, width: 220, height: 130,
    fill: '#4361EE', rx: 10, ry: 10,
    stroke: 'transparent', strokeWidth: 0
  });
  canvas.add(r);
  canvas.setActiveObject(r);
  canvas.renderAll();
});

document.getElementById('addCircleBtn').addEventListener('click', () => {
  saveState();
  const c = new fabric.Circle({
    left: 200, top: 180, radius: 70,
    fill: '#4361EE', stroke: 'transparent', strokeWidth: 0
  });
  canvas.add(c);
  canvas.setActiveObject(c);
  canvas.renderAll();
});

document.getElementById('addImageBtn').addEventListener('click', () => {
  const url = prompt('Image URL:');
  if (!url || !url.trim()) return;
  saveState();
  fabric.Image.fromURL(url.trim(), img => {
    img.scaleToWidth(220);
    img.set({ left: 150, top: 150 });
    canvas.add(img);
    canvas.setActiveObject(img);
    canvas.renderAll();
  }, { crossOrigin: 'anonymous' });
});

document.getElementById('deleteBtn').addEventListener('click', () => {
  const selected = canvas.getActiveObjects();
  if (!selected.length) { showToast('No object selected', 'error'); return; }
  saveState();
  canvas.remove(...selected);
  canvas.discardActiveObject();
  canvas.renderAll();
  deselectAll();
});

document.getElementById('clearBtn').addEventListener('click', () => {
  if (!confirm('Clear all objects from the canvas?')) return;
  saveState();
  canvas.clear();
  canvas.backgroundColor = '#ffffff';
  canvas.renderAll();
  deselectAll();
});

/* Keyboard shortcuts */
document.addEventListener('keydown', e => {
  const tag = document.activeElement?.tagName;
  if (['INPUT','TEXTAREA','SELECT'].includes(tag)) return;
  if (e.key === 'Delete' || e.key === 'Backspace') {
    const sel = canvas.getActiveObjects();
    if (sel.length) {
      saveState();
      canvas.remove(...sel);
      canvas.discardActiveObject();
      canvas.renderAll();
      deselectAll();
    }
  }
  if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
    e.preventDefault();
    undo();
  }
});

/* ──────────────── UNDO / EXPORT ────────────── */
document.getElementById('undoBtn').addEventListener('click', undo);

document.getElementById('downloadBtn').addEventListener('click', () => {
  canvas.discardActiveObject();
  canvas.renderAll();
  const url = canvas.toDataURL({ format: 'png', multiplier: 2 });
  const a = document.createElement('a');
  a.href = url;
  a.download = 'designai-export.png';
  a.click();
  showToast('Exported as PNG (2×)', 'success');
});

/* ──────────────── AI GENERATION ────────────── */
const SYSTEM_PROMPT = `You are an expert graphic designer AI. When given a design description, respond with ONLY a valid JSON array of Fabric.js v5 object configurations. No markdown, no code blocks, no explanation — output raw JSON only, starting with [ and ending with ].

Each object has a required "type" field: "rect", "circle", or "textbox".

Fields common to ALL objects:
- left: number (0–800, x position)
- top: number (0–600, y position)
- opacity: number (0–1, default 1)
- angle: number (rotation in degrees, default 0)
- fill: string (CSS color: hex, rgb, rgba, named, or "transparent")
- stroke: string (CSS color or "transparent")
- strokeWidth: number (default 0)

For "rect" also include:
- width: number
- height: number
- rx: number (border radius, default 0)
- ry: number (same as rx)

For "circle" also include:
- radius: number

For "textbox" also include:
- text: string (the text content)
- fontSize: number (pixels)
- fontFamily: string ("Syne", "Georgia", "Impact", "Helvetica", "Arial", "Times New Roman", "Courier New", "Verdana", or any web-safe font)
- fontWeight: string ("normal", "bold", or numeric like "700")
- fontStyle: string ("normal" or "italic")
- width: number (textbox width)
- textAlign: string ("left", "center", or "right")
- charSpacing: number (optional, letter-spacing in Fabric units)
- lineHeight: number (optional, default 1.16)

Canvas dimensions: 800×600. Design with strong visual hierarchy, thoughtful composition, and appropriate use of color and typography. Use the full canvas space. Create professional, polished designs. Return ONLY the JSON array with no other text.`;

function tryParseJSON(raw) {
  const trimmed = raw.trim();
  try { return JSON.parse(trimmed); } catch(_) {}
  // Try to extract array from markdown code block or surrounding text
  const m = trimmed.match(/\[[\s\S]*\]/);
  if (m) { try { return JSON.parse(m[0]); } catch(_) {} }
  throw new Error('Could not parse design JSON from AI response');
}

function addGeneratedObjects(objects) {
  objects.forEach(cfg => {
    try {
      const { type, text, ...rest } = cfg;
      let obj;
      switch ((type || '').toLowerCase()) {
        case 'rect':    obj = new fabric.Rect(rest); break;
        case 'circle':  obj = new fabric.Circle(rest); break;
        case 'textbox':
        case 'i-text':
        case 'text':    obj = new fabric.Textbox(text || '', rest); break;
        default:
          console.warn('[DesignAI] Unknown type:', type);
          return;
      }
      canvas.add(obj);
    } catch (err) {
      console.warn('[DesignAI] Failed to add object:', err, cfg);
    }
  });
  canvas.renderAll();
}

async function generateDesign() {
  const prompt = document.getElementById('promptInput').value.trim();
  if (!prompt) { showToast('Please enter a design description', 'error'); return; }

  const key = document.getElementById('apiKeyInput').value.trim()
    || (typeof window.ANTHROPIC_API_KEY !== 'undefined' ? window.ANTHROPIC_API_KEY : '');
  if (!key) { showToast('Please enter your Anthropic API key', 'error'); return; }

  setLoading(true);
  saveState();

  try {
    const res = await fetch('https://api.anthropic.com/v1/messages', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
        'x-api-key': key,
        'anthropic-version': '2023-06-01',
        'anthropic-dangerous-direct-browser-access': 'true'
      },
      body: JSON.stringify({
        model: 'claude-sonnet-4-20250514',
        max_tokens: 4096,
        system: SYSTEM_PROMPT,
        messages: [{ role: 'user', content: prompt }]
      })
    });

    if (!res.ok) {
      let errMsg = `API error ${res.status}`;
      try { const d = await res.json(); errMsg = d?.error?.message || errMsg; } catch(_) {}
      throw new Error(errMsg);
    }

    const data = await res.json();
    const rawText = data?.content?.[0]?.text || '';
    const objects = tryParseJSON(rawText);

    if (!Array.isArray(objects)) throw new Error('Expected a JSON array from the AI');
    if (!objects.length) throw new Error('AI returned an empty design');

    canvas.clear();
    canvas.backgroundColor = '#ffffff';
    addGeneratedObjects(objects);
    showToast(`${objects.length} element${objects.length !== 1 ? 's' : ''} generated`, 'success');

  } catch (err) {
    console.error('[DesignAI]', err);
    showToast(err.message || 'Generation failed', 'error');
  } finally {
    setLoading(false);
  }
}

document.getElementById('generateBtn').addEventListener('click', generateDesign);

document.getElementById('promptInput').addEventListener('keydown', e => {
  if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
    e.preventDefault();
    generateDesign();
  }
});

/* ──────────────── INIT ─────────────────────── */
// Injected key support
if (typeof window.ANTHROPIC_API_KEY !== 'undefined' && window.ANTHROPIC_API_KEY) {
  document.getElementById('apiKeyInput').value = window.ANTHROPIC_API_KEY;
}

// Save initial clean state
saveState();

// Auto-resize textarea
document.getElementById('promptInput').addEventListener('input', function() {
  this.style.height = 'auto';
  this.style.height = Math.min(this.scrollHeight, 96) + 'px';
});
</script>
</body>
</html>