From 580e798e16346eb894eb899edac82d2efdf40adc Mon Sep 17 00:00:00 2001
From: Christian Cleberg <hello@cleberg.net>
Date: Sat, 18 Apr 2026 22:32:53 -0500
Subject: initial commit

---
 src/components/BrandDoc.tsx | 518 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 518 insertions(+)
 create mode 100644 src/components/BrandDoc.tsx

(limited to 'src/components/BrandDoc.tsx')

diff --git a/src/components/BrandDoc.tsx b/src/components/BrandDoc.tsx
new file mode 100644
index 0000000..fffea97
--- /dev/null
+++ b/src/components/BrandDoc.tsx
@@ -0,0 +1,518 @@
+import { useRef, useState } from 'react';
+import type { BrandInputs, BrandOutputs, ColorSwatch, LockedSections, Typography } from '../types';
+import { CopyButton } from './CopyButton';
+
+// ── Editable text ─────────────────────────────────────────────────────────────
+
+interface EditableProps {
+  value: string;
+  onChange: (v: string) => void;
+  multiline?: boolean;
+}
+
+function Editable({ value, onChange, multiline = false }: EditableProps) {
+  const [editing, setEditing] = useState(false);
+  const [draft, setDraft] = useState(value);
+  const ref = useRef<HTMLTextAreaElement | HTMLInputElement>(null);
+
+  const commit = () => {
+    setEditing(false);
+    if (draft !== value) onChange(draft);
+  };
+
+  if (!editing) {
+    return (
+      <div
+        className="editable-text"
+        onClick={() => { setDraft(value); setEditing(true); }}
+        title="Click to edit"
+        style={{ whiteSpace: multiline ? 'pre-wrap' : 'normal', padding: '2px 4px', margin: '-2px -4px' }}
+      >
+        {value}
+      </div>
+    );
+  }
+
+  if (multiline) {
+    return (
+      <textarea
+        ref={ref as React.RefObject<HTMLTextAreaElement>}
+        autoFocus
+        className="editable-text editing"
+        value={draft}
+        onChange={e => setDraft(e.target.value)}
+        onBlur={commit}
+        onKeyDown={e => { if (e.key === 'Escape') { setEditing(false); setDraft(value); } }}
+        style={{
+          width: '100%',
+          resize: 'vertical',
+          minHeight: 80,
+          padding: '4px 6px',
+          fontFamily: 'inherit',
+          fontSize: 'inherit',
+          lineHeight: 1.65,
+          background: 'var(--bg-2)',
+          border: '1px solid var(--border-3)',
+          borderRadius: 3,
+          color: 'var(--text)',
+          outline: 'none',
+        }}
+      />
+    );
+  }
+
+  return (
+    <input
+      ref={ref as React.RefObject<HTMLInputElement>}
+      autoFocus
+      className="editable-text editing"
+      value={draft}
+      onChange={e => setDraft(e.target.value)}
+      onBlur={commit}
+      onKeyDown={e => {
+        if (e.key === 'Enter') commit();
+        if (e.key === 'Escape') { setEditing(false); setDraft(value); }
+      }}
+      style={{
+        width: '100%',
+        padding: '2px 6px',
+        fontFamily: 'inherit',
+        fontSize: 'inherit',
+        background: 'var(--bg-2)',
+        border: '1px solid var(--border-3)',
+        borderRadius: 3,
+        color: 'var(--text)',
+        outline: 'none',
+      }}
+    />
+  );
+}
+
+// ── Section wrapper ───────────────────────────────────────────────────────────
+
+interface SectionProps {
+  title: string;
+  locked: boolean;
+  onToggleLock: () => void;
+  copyText?: string;
+  children: React.ReactNode;
+}
+
+function Section({ title, locked, onToggleLock, copyText, children }: SectionProps) {
+  return (
+    <div className={`doc-section${locked ? ' is-locked' : ''}`}>
+      <div className="doc-section-header">
+        <div className="doc-section-title">{title}</div>
+        <div className="doc-section-actions">
+          {copyText && <CopyButton text={copyText} label="Copy" />}
+          <button
+            type="button"
+            className={`section-lock${locked ? ' locked' : ''}`}
+            onClick={onToggleLock}
+            title={locked ? 'Locked — click to unlock' : 'Lock to preserve on regenerate'}
+          >
+            {locked ? '● locked' : '○ lock'}
+          </button>
+        </div>
+      </div>
+      <div className="doc-section-body">
+        {children}
+      </div>
+    </div>
+  );
+}
+
+// ── Editable list item ────────────────────────────────────────────────────────
+
+interface EditableListProps {
+  items: string[];
+  onChange: (items: string[]) => void;
+  numbered?: boolean;
+}
+
+function EditableList({ items, onChange, numbered = true }: EditableListProps) {
+  const updateAt = (i: number, v: string) => {
+    const next = [...items];
+    next[i] = v;
+    onChange(next);
+  };
+
+  if (numbered) {
+    return (
+      <div className="numbered-list">
+        {items.map((item, i) => (
+          <div key={i} className="numbered-item">
+            <span className="numbered-item-num">{i + 1}.</span>
+            <div
+              className="numbered-item-text"
+              contentEditable
+              suppressContentEditableWarning
+              onBlur={e => updateAt(i, e.currentTarget.textContent ?? '')}
+              onKeyDown={e => { if (e.key === 'Escape') e.currentTarget.blur(); }}
+            >
+              {item}
+            </div>
+          </div>
+        ))}
+      </div>
+    );
+  }
+
+  return (
+    <div className="bullet-list">
+      {items.map((item, i) => (
+        <div key={i} className="bullet-item">
+          {item}
+        </div>
+      ))}
+    </div>
+  );
+}
+
+// ── Typography section ────────────────────────────────────────────────────────
+
+function TypographySection({ typography }: { typography: Typography }) {
+  return (
+    <div className="type-section">
+      <div className="type-fonts">
+        <div className="type-font-row">
+          <span className="type-font-role">Primary</span>
+          <span className="type-font-name">{typography.primary}</span>
+        </div>
+        {typography.secondary !== typography.primary && (
+          <div className="type-font-row">
+            <span className="type-font-role">Secondary</span>
+            <span className="type-font-name">{typography.secondary}</span>
+          </div>
+        )}
+        <div className="type-font-row">
+          <span className="type-font-role">Monospace</span>
+          <span className="type-font-name" style={{ fontFamily: 'var(--font-mono)' }}>{typography.mono}</span>
+        </div>
+        <div className="type-pair-note">{typography.pairNote}</div>
+      </div>
+
+      <div className="type-scale">
+        <div className="type-scale-header">
+          <span>Style</span>
+          <span>Size</span>
+          <span>Weight</span>
+          <span className="type-scale-usage">Usage</span>
+        </div>
+        {typography.scale.map(token => (
+          <div key={token.label} className="type-scale-row">
+            <span className="type-scale-label">{token.label}</span>
+            <span className="type-scale-size">{token.size}</span>
+            <span className="type-scale-weight">{token.weight}</span>
+            <span className="type-scale-usage">{token.usage}</span>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+// ── Color swatch card ─────────────────────────────────────────────────────────
+
+interface ColorSwatchCardProps {
+  swatch: ColorSwatch;
+  onChange: (s: ColorSwatch) => void;
+  onRemove: () => void;
+}
+
+function ColorSwatchCard({ swatch, onChange, onRemove }: ColorSwatchCardProps) {
+  const [nameEditing, setNameEditing] = useState(false);
+  const [nameDraft, setNameDraft] = useState(swatch.name);
+  const [hexEditing, setHexEditing] = useState(false);
+  const [hexDraft, setHexDraft] = useState(swatch.hex);
+
+  const commitName = () => {
+    setNameEditing(false);
+    const v = nameDraft.trim();
+    if (v && v !== swatch.name) onChange({ ...swatch, name: v });
+  };
+
+  const commitHex = () => {
+    setHexEditing(false);
+    const v = hexDraft.trim().toLowerCase();
+    const normalized = v.startsWith('#') ? v : `#${v}`;
+    if (/^#[0-9a-f]{6}$/.test(normalized)) {
+      onChange({ ...swatch, hex: normalized });
+    } else {
+      setHexDraft(swatch.hex);
+    }
+  };
+
+  return (
+    <div className="color-swatch-card">
+      <div className="color-swatch-preview" style={{ background: swatch.hex }}>
+        <input
+          type="color"
+          className="color-swatch-picker"
+          value={swatch.hex}
+          onChange={e => onChange({ ...swatch, hex: e.target.value })}
+          title="Pick color"
+        />
+        <button className="color-swatch-remove" onClick={onRemove} title="Remove">×</button>
+      </div>
+      <div className="color-swatch-info">
+        {nameEditing ? (
+          <input
+            autoFocus
+            className="color-swatch-field-input"
+            value={nameDraft}
+            onChange={e => setNameDraft(e.target.value)}
+            onBlur={commitName}
+            onKeyDown={e => {
+              if (e.key === 'Enter') commitName();
+              if (e.key === 'Escape') { setNameEditing(false); setNameDraft(swatch.name); }
+            }}
+          />
+        ) : (
+          <div className="color-swatch-name" onClick={() => { setNameDraft(swatch.name); setNameEditing(true); }} title="Click to edit">
+            {swatch.name}
+          </div>
+        )}
+        {hexEditing ? (
+          <input
+            autoFocus
+            className="color-swatch-field-input color-swatch-hex-input"
+            value={hexDraft}
+            onChange={e => setHexDraft(e.target.value)}
+            onBlur={commitHex}
+            onKeyDown={e => {
+              if (e.key === 'Enter') commitHex();
+              if (e.key === 'Escape') { setHexEditing(false); setHexDraft(swatch.hex); }
+            }}
+          />
+        ) : (
+          <div className="color-swatch-hex" onClick={() => { setHexDraft(swatch.hex); setHexEditing(true); }} title="Click to edit">
+            {swatch.hex}
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+// ── Main BrandDoc ─────────────────────────────────────────────────────────────
+
+interface Props {
+  inputs: BrandInputs;
+  outputs: BrandOutputs;
+  locked: LockedSections;
+  onToggleLock: (section: keyof LockedSections) => void;
+  onEdit: <K extends keyof BrandOutputs>(key: K, value: BrandOutputs[K]) => void;
+}
+
+export function BrandDoc({ inputs, outputs, locked, onToggleLock, onEdit }: Props) {
+  const paletteCopy = outputs.palette.swatches
+    .map(s => `${s.name}: ${s.hex}`)
+    .join('\n');
+
+  const updateSwatch = (index: number, updated: ColorSwatch) => {
+    const swatches = [...outputs.palette.swatches];
+    swatches[index] = updated;
+    onEdit('palette', { swatches });
+  };
+
+  const removeSwatch = (index: number) => {
+    const swatches = outputs.palette.swatches.filter((_, i) => i !== index);
+    onEdit('palette', { swatches });
+  };
+
+  const addSwatch = () => {
+    const id = `s${Date.now()}`;
+    onEdit('palette', {
+      swatches: [...outputs.palette.swatches, { id, name: 'New Color', hex: '#888888', role: 'accent' }],
+    });
+  };
+
+  const messagingCopy = [
+    'Titles:',
+    ...outputs.titles.map((t, i) => `${i + 1}. ${t}`),
+    '',
+    'Taglines:',
+    ...outputs.taglines.map((t, i) => `${i + 1}. ${t}`),
+  ].join('\n');
+
+  return (
+    <div className="brand-doc">
+      <div className="brand-doc-header">
+        <div className="brand-doc-title">{inputs.name || 'Brand Package'}</div>
+        <div className="brand-doc-meta">
+          {inputs.category && (
+            <span className="brand-doc-meta-item">
+              <span className="brand-doc-meta-label">category</span>
+              {inputs.category}
+            </span>
+          )}
+          {inputs.audience && (
+            <span className="brand-doc-meta-item">
+              <span className="brand-doc-meta-label">audience</span>
+              {inputs.audience}
+            </span>
+          )}
+        </div>
+      </div>
+
+      {/* Overview */}
+      <Section title="Overview" locked={locked.overview} onToggleLock={() => onToggleLock('overview')} copyText={outputs.overview}>
+        <Editable value={outputs.overview} onChange={v => onEdit('overview', v)} multiline />
+      </Section>
+
+      {/* Positioning */}
+      <Section title="Positioning" locked={locked.positioning} onToggleLock={() => onToggleLock('positioning')} copyText={outputs.positioning}>
+        <Editable value={outputs.positioning} onChange={v => onEdit('positioning', v)} multiline />
+      </Section>
+
+      {/* Tone */}
+      <Section title="Tone & Voice" locked={locked.tone} onToggleLock={() => onToggleLock('tone')}>
+        <div className="tone-grid">
+          <div className="tone-row">
+            <div className="tone-row-label">Attributes</div>
+            <div className="tone-tags">
+              {outputs.tone.attributes.map(a => (
+                <span key={a} className="tone-tag">{a}</span>
+              ))}
+            </div>
+          </div>
+          <div className="tone-row">
+            <div className="tone-row-label">Voice</div>
+            <div className="tone-row-value">{outputs.tone.voiceNotes}</div>
+          </div>
+          {outputs.tone.avoidList.length > 0 && (
+            <div className="tone-row">
+              <div className="tone-row-label">Avoid</div>
+              <div className="tone-row-value">{outputs.tone.avoidList.join(', ')}</div>
+            </div>
+          )}
+          {outputs.tone.examplePhrases.length > 0 && (
+            <div className="tone-row">
+              <div className="tone-row-label">Example phrases</div>
+              <div className="tone-phrases">
+                {outputs.tone.examplePhrases.map((p, i) => (
+                  <div key={i} className="tone-phrase">{p}</div>
+                ))}
+              </div>
+            </div>
+          )}
+        </div>
+      </Section>
+
+      {/* Messaging */}
+      <Section title="Messaging" locked={locked.messaging} onToggleLock={() => onToggleLock('messaging')} copyText={messagingCopy}>
+        <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
+          <div>
+            <div style={{ fontSize: 11, color: 'var(--text-3)', fontWeight: 500, marginBottom: 10 }}>Titles</div>
+            <EditableList items={outputs.titles} onChange={v => onEdit('titles', v)} />
+          </div>
+          <div>
+            <div style={{ fontSize: 11, color: 'var(--text-3)', fontWeight: 500, marginBottom: 10 }}>Subtitles</div>
+            <EditableList items={outputs.subtitles} onChange={v => onEdit('subtitles', v)} />
+          </div>
+          <div>
+            <div style={{ fontSize: 11, color: 'var(--text-3)', fontWeight: 500, marginBottom: 10 }}>Taglines</div>
+            <EditableList items={outputs.taglines} onChange={v => onEdit('taglines', v)} />
+          </div>
+        </div>
+      </Section>
+
+      {/* Visual Directions */}
+      <Section title="Visual Direction" locked={locked.visual} onToggleLock={() => onToggleLock('visual')}>
+        <div className="directions-grid">
+          {outputs.visualDirections.map(dir => (
+            <div key={dir.id} className="direction-card">
+              <div className="direction-name">{dir.name}</div>
+              <div className="direction-desc">{dir.description}</div>
+              <div className="direction-attrs">
+                <div className="direction-attr">
+                  <span className="direction-attr-label">Palette</span>
+                  <span className="direction-attr-value">{dir.palette}</span>
+                </div>
+                <div className="direction-attr">
+                  <span className="direction-attr-label">Typography</span>
+                  <span className="direction-attr-value">{dir.typography}</span>
+                </div>
+                <div className="direction-attr">
+                  <span className="direction-attr-label">References</span>
+                  <span className="direction-attr-value">{dir.references}</span>
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </Section>
+
+      {/* Color Palette */}
+      <Section title="Color Palette" locked={locked.palette} onToggleLock={() => onToggleLock('palette')} copyText={paletteCopy}>
+        <div className="color-palette">
+          {outputs.palette.swatches.map((swatch, i) => (
+            <ColorSwatchCard
+              key={swatch.id}
+              swatch={swatch}
+              onChange={updated => updateSwatch(i, updated)}
+              onRemove={() => removeSwatch(i)}
+            />
+          ))}
+          <button className="color-swatch-add" onClick={addSwatch} title="Add color">
+            +
+          </button>
+        </div>
+      </Section>
+
+      {/* Typography */}
+      <Section title="Typography" locked={locked.typography} onToggleLock={() => onToggleLock('typography')}>
+        <TypographySection typography={outputs.typography} />
+      </Section>
+
+      {/* Logo Concepts */}
+      <Section title="Logo Concepts" locked={locked.logo} onToggleLock={() => onToggleLock('logo')}>
+        <div className="logo-grid">
+          {outputs.logoConcepts.map(lc => (
+            <div key={lc.id} className="logo-card">
+              <div className="logo-card-title">{lc.title}</div>
+              <div className="logo-card-concept">{lc.concept}</div>
+              <div className="logo-card-attrs">
+                <div className="logo-card-attr">
+                  <span className="logo-card-attr-label">Mark</span>
+                  <span className="logo-card-attr-value">{lc.mark}</span>
+                </div>
+                <div className="logo-card-attr">
+                  <span className="logo-card-attr-label">Execution</span>
+                  <span className="logo-card-attr-value">{lc.execution}</span>
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </Section>
+
+      {/* Usage Examples */}
+      <Section title="Usage Examples" locked={locked.usage} onToggleLock={() => onToggleLock('usage')}>
+        <div className="usage-grid">
+          {outputs.usageExamples.map((ex, i) => (
+            <div key={i} className="usage-item">
+              <div className="usage-context">{ex.context}</div>
+              <div className="usage-text" style={{ position: 'relative' }}>
+                {ex.text}
+                <div className="usage-copy">
+                  <CopyButton text={ex.text} label="Copy" />
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </Section>
+
+      {/* Constraints */}
+      <Section title="Constraints" locked={locked.constraints} onToggleLock={() => onToggleLock('constraints')}>
+        <div className="constraints-list">
+          {outputs.constraints.map((c, i) => (
+            <div key={i} className="constraint-item">{c}</div>
+          ))}
+        </div>
+      </Section>
+    </div>
+  );
+}
-- 
cgit v1.2.3

