← back to Brand Selector Viewer

magic-app.html

235 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Magic · BrandSelector — live React</title>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
<style>
  body { margin: 0; background: #0b0b0d; }
  .font-serif { font-family: "Didot", "Bodoni 72", Georgia, serif; }
  .font-sans  { font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; }
</style>
</head>
<body>
<div id="root"></div>

<script type="text/babel" data-presets="env,react">
const FASHION_20 = [
  { name: "Louis Vuitton", country: "FR", founded: 1854, palette: ["#5d3a22", "#d4b886", "#1a1a1a"] },
  { name: "Chanel",        country: "FR", founded: 1910, palette: ["#000000", "#ffffff", "#c9a96e"] },
  { name: "Hermès",        country: "FR", founded: 1837, palette: ["#f37021", "#1a1a1a", "#f4e7d3"] },
  { name: "Gucci",         country: "IT", founded: 1921, palette: ["#006341", "#a01c1f", "#d4af37"] },
  { name: "Prada",         country: "IT", founded: 1913, palette: ["#000000", "#8b8680", "#e8e4dc"] },
  { name: "Dior",          country: "FR", founded: 1946, palette: ["#1a1a1a", "#c8a883", "#ffffff"] },
  { name: "Versace",       country: "IT", founded: 1978, palette: ["#d4af37", "#000000", "#a01c1f"] },
  { name: "Burberry",      country: "UK", founded: 1856, palette: ["#a08661", "#000000", "#c1272d"] },
  { name: "Saint Laurent", country: "FR", founded: 1961, palette: ["#000000", "#d4b886", "#ffffff"] },
  { name: "Balenciaga",    country: "ES", founded: 1919, palette: ["#000000", "#5a5a5a", "#ffffff"] },
  { name: "Bottega Veneta",country: "IT", founded: 1966, palette: ["#3d5a2c", "#c9b27a", "#1a1a1a"] },
  { name: "Fendi",         country: "IT", founded: 1925, palette: ["#5d3a22", "#f0e6d2", "#000000"] },
  { name: "Givenchy",      country: "FR", founded: 1952, palette: ["#000000", "#d4af37", "#a01c1f"] },
  { name: "Valentino",     country: "IT", founded: 1960, palette: ["#a01c1f", "#1a1a1a", "#f0e6d2"] },
  { name: "Armani",        country: "IT", founded: 1975, palette: ["#1a1a2e", "#8b8680", "#000000"] },
  { name: "Ralph Lauren",  country: "US", founded: 1967, palette: ["#1a3a6c", "#a01c1f", "#f0e6d2"] },
  { name: "Tom Ford",      country: "US", founded: 2005, palette: ["#000000", "#5d3a22", "#d4af37"] },
  { name: "Celine",        country: "FR", founded: 1945, palette: ["#000000", "#d4b886", "#ffffff"] },
  { name: "Loewe",         country: "ES", founded: 1846, palette: ["#5d3a22", "#d4b886", "#1a1a1a"] },
  { name: "Alexander McQueen", country: "UK", founded: 1992, palette: ["#000000", "#a01c1f", "#8b8680"] },
];

const WALL_10 = [
  { name: "Phillip Jeffries",  country: "US", founded: 1976, palette: ["#d4b886", "#5d3a22", "#f0e6d2"] },
  { name: "Schumacher",        country: "US", founded: 1889, palette: ["#a01c1f", "#3d5a2c", "#d4af37"] },
  { name: "Cole & Son",        country: "UK", founded: 1875, palette: ["#1a3a6c", "#c9a96e", "#f0e6d2"] },
  { name: "Thibaut",           country: "US", founded: 1886, palette: ["#3d5a2c", "#c9a96e", "#1a1a1a"] },
  { name: "Zoffany",           country: "UK", founded: 1895, palette: ["#5d3a22", "#d4b886", "#3d5a2c"] },
  { name: "Farrow & Ball",     country: "UK", founded: 1946, palette: ["#1a3a3a", "#e8e4dc", "#5d3a22"] },
  { name: "Brunschwig & Fils", country: "US", founded: 1909, palette: ["#a01c1f", "#1a3a6c", "#d4b886"] },
  { name: "de Gournay",        country: "UK", founded: 1986, palette: ["#c9a96e", "#5d3a22", "#3d5a2c"] },
  { name: "Fromental",         country: "UK", founded: 2005, palette: ["#1a3a6c", "#d4af37", "#5d3a22"] },
  { name: "Pierre Frey",       country: "FR", founded: 1935, palette: ["#a01c1f", "#d4b886", "#1a3a6c"] },
];

function Tag({ children }) {
  return (
    <span className="text-[9.5px] tracking-[1.2px] uppercase text-zinc-500 border border-zinc-800 px-2 py-0.5 rounded-full font-sans">
      {children}
    </span>
  );
}

function BrandCard({ brand, selected, onClick }) {
  return (
    <button
      onClick={onClick}
      className={[
        "relative text-left rounded-2xl p-5 min-h-[140px] flex flex-col justify-between transition border",
        selected
          ? "border-amber-400 shadow-[inset_0_0_0_1px_rgba(212,175,55,1)]"
          : "bg-zinc-900 border-zinc-800 hover:border-zinc-600 hover:-translate-y-0.5",
      ].join(" ")}
      style={selected ? { background: "linear-gradient(135deg, rgba(212,175,55,0.10), #1a1a22)" } : {}}
    >
      <span
        className={[
          "absolute top-3 right-3 w-5 h-5 rounded-full border flex items-center justify-center text-[11px]",
          selected ? "bg-amber-400 border-amber-400 text-zinc-900" : "border-zinc-700 text-transparent",
        ].join(" ")}
      >
        ✓
      </span>
      <div>
        <div className="font-serif text-lg leading-tight pr-7 text-zinc-100">{brand.name}</div>
        <div className="flex gap-1.5 mt-2.5">
          <Tag>{brand.country}</Tag>
          <Tag>est. {brand.founded}</Tag>
        </div>
      </div>
      <div className="flex gap-1 mt-3">
        {brand.palette.map((c) => (
          <span key={c} className="w-4 h-4 rounded border border-white/10" style={{ background: c }} />
        ))}
      </div>
    </button>
  );
}

function Stat({ n, label }) {
  return (
    <div>
      <div className="font-serif text-3xl text-amber-400 leading-none">{n}</div>
      <div className="text-[10px] tracking-[2px] uppercase text-zinc-500 mt-1 font-sans">{label}</div>
    </div>
  );
}

function Section({ index, title, sub, brands, picked, onToggle, toolbar }) {
  return (
    <section className="px-10 pt-10">
      <div className="flex items-end justify-between gap-5 mb-5">
        <div>
          <div className="text-[10px] tracking-[3px] uppercase text-amber-400 font-sans">{index}</div>
          <h2 className="font-serif text-3xl mt-1 text-zinc-100">{title}</h2>
          <div className="italic text-zinc-500 text-sm tracking-wide font-serif">{sub}</div>
        </div>
        {toolbar}
      </div>
      <div className="grid gap-3" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))" }}>
        {brands.map((b) => (
          <BrandCard key={b.name} brand={b} selected={!!picked[b.name]} onClick={() => onToggle(b.name)} />
        ))}
      </div>
    </section>
  );
}

function Pill({ children, onClick, primary }) {
  return (
    <button
      onClick={onClick}
      className={[
        "text-xs tracking-[0.5px] px-4 py-2 rounded-full font-sans transition",
        primary
          ? "bg-amber-400 text-zinc-900 hover:bg-amber-300 font-medium"
          : "bg-zinc-900 border border-zinc-800 text-zinc-300 hover:border-zinc-600",
      ].join(" ")}
    >
      {children}
    </button>
  );
}

function App() {
  const [picked, setPicked] = React.useState({ Chanel: true, Versace: true, "Phillip Jeffries": true });
  const [showJson, setShowJson] = React.useState(false);

  const toggle = (name) => setPicked((p) => ({ ...p, [name]: !p[name] }));

  const fashionPicked = FASHION_20.filter((b) => picked[b.name]);
  const wallPicked = WALL_10.filter((b) => picked[b.name]);
  const total = fashionPicked.length + wallPicked.length;
  const all = [...fashionPicked, ...wallPicked];
  const summary = all.length ? all.map((b) => b.name).join(" · ") : "none";

  const buildPayload = () => ({
    schema: "figma-build/brand-selection.v1",
    generated_at: new Date().toISOString(),
    counts: { fashion: fashionPicked.length, wallcovering: wallPicked.length, total },
    fashion: fashionPicked,
    wallcovering: wallPicked,
  });

  const fashionToolbar = (
    <div className="flex gap-2">
      <Pill onClick={() => setPicked((p) => { const n = { ...p }; FASHION_20.forEach((b) => (n[b.name] = true)); return n; })}>Select all 20</Pill>
      <Pill onClick={() => setPicked((p) => { const n = { ...p }; FASHION_20.forEach((b) => (n[b.name] = false)); return n; })}>Clear</Pill>
      <Pill onClick={() => setPicked((p) => { const n = { ...p }; FASHION_20.forEach((b, i) => (n[b.name] = i < 5)); return n; })}>Top 5 only</Pill>
    </div>
  );
  const wallToolbar = (
    <div className="flex gap-2">
      <Pill onClick={() => setPicked((p) => { const n = { ...p }; WALL_10.forEach((b) => (n[b.name] = true)); return n; })}>Select all 10</Pill>
      <Pill onClick={() => setPicked((p) => { const n = { ...p }; WALL_10.forEach((b) => (n[b.name] = false)); return n; })}>Clear</Pill>
    </div>
  );

  return (
    <div className="min-h-screen bg-[#0b0b0d] pb-32 font-sans">
      <header className="px-10 pt-12 pb-6 border-b border-zinc-800 flex flex-wrap items-end justify-between gap-6">
        <div>
          <div className="flex items-center gap-2.5">
            <span className="w-1.5 h-1.5 rounded-full bg-amber-400 inline-block" />
            <span className="text-[10px] tracking-[3px] uppercase text-amber-400/90">Figma Build Source · v1 · live React</span>
          </div>
          <h1 className="font-serif text-5xl tracking-tight mt-2 text-zinc-100">Brand Selector</h1>
          <p className="italic text-zinc-400 mt-1 font-serif">Pick the labels that will source the next Figma build · Top 20 fashion · Top 10 wallcovering</p>
        </div>
        <div className="flex gap-12 text-right">
          <Stat n={fashionPicked.length} label="Fashion" />
          <Stat n={wallPicked.length} label="Wallcovering" />
        </div>
      </header>

      <Section index="I."  title="Fashion"      sub="Top 20 — Luxury & Iconic Houses"  brands={FASHION_20} picked={picked} onToggle={toggle} toolbar={fashionToolbar} />
      <Section index="II." title="Wallcovering" sub="Top 10 — Heritage & Designer Houses" brands={WALL_10}    picked={picked} onToggle={toggle} toolbar={wallToolbar} />

      <div className="fixed bottom-0 inset-x-0 backdrop-blur border-t border-zinc-800 px-10 py-3 flex items-center justify-between gap-4" style={{ background: "rgba(11,11,13,0.92)" }}>
        <div className="flex gap-3 items-center text-sm">
          <span className="bg-zinc-900 px-3 py-1.5 rounded-full text-zinc-400">
            <b className="text-amber-400 font-serif text-base mr-1">{total}</b>selected
          </span>
          <span className="text-zinc-500 truncate max-w-[600px]">{summary}</span>
        </div>
        <div className="flex gap-2">
          <Pill onClick={() => setPicked({})}>Clear all</Pill>
          <Pill onClick={() => setShowJson(true)}>Preview JSON</Pill>
          <Pill primary onClick={() => setShowJson(true)}>Send to Figma Build →</Pill>
        </div>
      </div>

      {showJson && (
        <div className="fixed inset-0 z-50 bg-black/75 flex items-center justify-center p-6" onClick={() => setShowJson(false)}>
          <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-6 max-w-3xl w-full max-h-[85vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
            <h3 className="font-serif text-xl text-zinc-100 mb-3">Figma build payload</h3>
            <pre className="bg-black border border-zinc-800 rounded p-4 overflow-auto text-xs leading-relaxed text-zinc-300 flex-1">{JSON.stringify(buildPayload(), null, 2)}</pre>
            <div className="flex gap-2 justify-end mt-4">
              <Pill onClick={() => navigator.clipboard.writeText(JSON.stringify(buildPayload(), null, 2))}>Copy JSON</Pill>
              <Pill primary onClick={() => setShowJson(false)}>Close</Pill>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
</html>