APIMashupHub
Generative & Satirical

Generating Fake Startups with Real APIs: The Fake Startup Generator

Combine CorporateBS, Colormind, Agify, Genderize, and Nationalize APIs to create an absurdly realistic startup profile generator that skewers Silicon Valley culture.

Published May 2026 • 36 min read • Generative Content & Satire

1. Introduction: The Art of Corporate Parody

Every startup has a story. It usually begins with a visionary founder who saw a gap in the market, assembled a world-class team, and built a product that disrupts the industry through innovative synergies in the cloud-native space. The language of startups has become so formulaic, so divorced from actual meaning, that it reads like it was generated by an algorithm. Mission statements blend together. Pitch decks echo each other. Everyone is leveraging next-generation paradigms to empower stakeholders.

What if it actually was generated by an algorithm? What if we could press a button and produce a complete, eerily realistic startup profile, complete with a company name, mission statement, brand colors, a founding team with names from around the world, and enough corporate buzzwords to fill a pitch deck? That is exactly what we are going to build.

The Fake Startup Generator combines five APIs into a single creative engine. CorporateBS Generator produces business jargon that is indistinguishable from real corporate communications. Colormind generates harmonious color palettes for the brand identity. Agify predicts ages from first names. Genderize predicts gender from first names. Nationalize predicts nationalities from first names. Together, they produce startup profiles that are simultaneously realistic and absurd, highlighting the hollow patterns of startup culture through the very act of algorithmic generation.

This is not just a joke project, though it is certainly funny. It is a masterclass in combining multiple small, focused APIs into something greater than the sum of its parts. The technical patterns, including parallel API orchestration, generative content pipelines, client-side data enrichment, and dynamic CSS generation, are directly applicable to serious applications. The satire just makes the learning more enjoyable.

2. The APIs: Our Toolkit of Absurdity

2.1 CorporateBS Generator

The CorporateBS Generator is a delightfully simple API that produces random corporate buzzword phrases. Each call returns a string like "proactively leverage mission-critical e-commerce" or "seamlessly facilitate best-of-breed interfaces." The phrases are syntactically correct, use real business vocabulary, and sound exactly like the kind of thing you might read in a CEO's LinkedIn post. That is the joke: you cannot tell the difference.

// CorporateBS Generator API
// GET https://corporatebs-generator.samerator.com/api/v1/corporate-bs

// Example response
{
  "phrase": "synergistically orchestrate cross-platform paradigms"
}

The API requires no authentication, has no rate limiting documentation (though it is wise to be respectful), and returns a single phrase per call. For our generator, we will make multiple calls to get a mission statement, a tagline, several product features, and a CEO quote. Each call returns a different random phrase, so every startup profile is unique.

2.2 Colormind API

Colormind uses machine learning trained on thousands of design examples to generate aesthetically harmonious color palettes. You can request a completely random palette or provide one or more "seed" colors and let the algorithm fill in the rest. The response is an array of five RGB color values that work well together.

// Colormind API
// POST http://colormind.io/api/

// Request body for random palette
{ "model": "default" }

// Request body with seed colors
{
  "input": [[44, 43, 44], [90, 83, 82], "N", "N", "N"],
  "model": "default"
}

// Response
{
  "result": [
    [30, 30, 46],
    [50, 74, 115],
    [80, 120, 160],
    [170, 190, 205],
    [230, 235, 240]
  ]
}

Each result is an array of five colors, ordered from dark to light. The "N" values in the input indicate slots that should be algorithmically generated. This is incredibly powerful because we can lock in one brand color and let Colormind build a complete palette around it. The result is a professional-looking color scheme that we will use for the startup's brand identity card.

2.3 Agify API

Agify predicts the age of a person based on their first name. It uses a dataset of millions of name-age records to return the most statistically likely age for a given name. This is useful for our generator because it adds a layer of demographic realism to the fake founding team.

// Agify API
// GET https://api.agify.io/?name=michael

// Response
{
  "count": 233482,
  "name": "michael",
  "age": 61
}

// Batch request (up to 10 names)
// GET https://api.agify.io/?name[]=michael&name[]=sarah&name[]=omar

The count field tells you how many data points were used for the prediction. Higher counts mean more confidence. The batch endpoint is essential for efficiency: instead of making separate API calls for each team member's name, we can send up to ten names in a single request.

2.4 Genderize API

Genderize predicts the gender associated with a first name, along with a probability score. Like Agify, it supports batch requests for up to ten names at once.

// Genderize API
// GET https://api.genderize.io/?name=sarah

// Response
{
  "count": 250122,
  "name": "sarah",
  "gender": "female",
  "probability": 0.98
}

// Batch request
// GET https://api.genderize.io/?name[]=sarah&name[]=james&name[]=alex

The probability field is important for names that are gender-neutral or used across genders in different cultures. A name like "Alex" might return a probability of 0.55 for male, indicating that the API is not very confident. We will use this probability to add nuance to our team profiles rather than making hard binary assignments.

2.5 Nationalize API

Nationalize predicts the most likely nationalities associated with a first name. It returns an array of country codes with probability scores, reflecting the name's global usage patterns.

// Nationalize API
// GET https://api.nationalize.io/?name=omar

// Response
{
  "count": 48291,
  "name": "omar",
  "country": [
    { "country_id": "EG", "probability": 0.157 },
    { "country_id": "SA", "probability": 0.121 },
    { "country_id": "MA", "probability": 0.089 },
    { "country_id": "SD", "probability": 0.075 },
    { "country_id": "DZ", "probability": 0.064 }
  ]
}

The multi-country response is particularly interesting because it reflects the global distribution of names. "Omar" is common in Egypt, Saudi Arabia, Morocco, and many other countries. For our generator, we will use the top-probability country code and map it to a country name and flag emoji, adding visual diversity and a sense of global cosmopolitanism to our fake founding team.

2.6 The Combined Power

Individually, each of these APIs does something trivial. CorporateBS generates a sentence. Colormind picks colors. Agify guesses an age. But combined, they produce something that feels almost alive: a complete brand identity with a color-coded logo area, a mission statement that sounds like every YC pitch deck you have ever read, and a founding team whose members have ages, implied genders, and inferred nationalities. The whole is dramatically greater than the sum of its parts.

3. Architecture: Assembling the Generator

3.1 The Generation Pipeline

Our architecture follows a pipeline pattern. First, we generate the raw materials (names, colors, phrases). Then we enrich the raw materials with demographic data. Finally, we assemble everything into a cohesive startup profile and render it as a polished card.

// Generation pipeline
//
// Phase 1: Raw Material Generation (parallel)
//   - CorporateBS x 6 calls (mission, tagline, 3 features, CEO quote)
//   - Colormind x 1 call (brand palette)
//   - Random name selection from name pool
//
// Phase 2: Name Enrichment (parallel, batched)
//   - Agify batch (all team names)
//   - Genderize batch (all team names)
//   - Nationalize batch (all team names)
//
// Phase 3: Assembly
//   - Merge all data into startup profile object
//   - Generate company name from templates
//   - Calculate fake valuation, employee count, founding year
//   - Build brand identity CSS from Colormind palette
//
// Phase 4: Render
//   - Startup card with dynamic colors
//   - Team member profiles
//   - Feature list
//   - "Investor pitch" section

3.2 The Name Pool

We need a diverse pool of first names to draw from. Rather than hardcoding a list, we curate a pool that represents global diversity. The names are chosen because they work well with the demographic APIs and produce interesting, varied results.

const namePool = {
  common: [
    'James', 'Sarah', 'Michael', 'Emma', 'David', 'Sofia',
    'Robert', 'Maria', 'William', 'Anna', 'Thomas', 'Laura',
    'Daniel', 'Jessica', 'Matthew', 'Jennifer', 'Andrew', 'Lisa'
  ],
  global: [
    'Omar', 'Yuki', 'Priya', 'Wei', 'Fatima', 'Kenji',
    'Aisha', 'Raj', 'Olga', 'Carlos', 'Mei', 'Hassan',
    'Ingrid', 'Kofi', 'Anya', 'Diego', 'Suki', 'Dmitri',
    'Leila', 'Chen', 'Amara', 'Sven', 'Nadia', 'Tariq'
  ],
  hipster: [
    'Sage', 'River', 'Phoenix', 'Kai', 'Indigo', 'Ash',
    'Raven', 'Skyler', 'Quinn', 'Riley', 'Jordan', 'Taylor'
  ]
};

function selectTeamNames(count = 4) {
  const allNames = [...namePool.common, ...namePool.global, ...namePool.hipster];
  const shuffled = allNames.sort(() => Math.random() - 0.5);
  return shuffled.slice(0, count);
}

// Also need last names for full names
const lastNames = [
  'Chen', 'Patel', 'O\'Brien', 'Nakamura', 'Silva', 'Mueller',
  'Johansson', 'Kim', 'Okafor', 'Gonzalez', 'Dubois', 'Yamamoto',
  'Andersen', 'Kapoor', 'Fitzgerald', 'Van der Berg', 'Rossi', 'Park',
  'Nguyen', 'Costa', 'Schmidt', 'Ali', 'Petrov', 'Tanaka'
];

function selectLastName() {
  return lastNames[Math.floor(Math.random() * lastNames.length)];
}

3.3 State Management

The generator maintains a simple state object that holds the current startup profile, a gallery of previously generated startups, and loading/error states.

const state = {
  generating: false,
  error: null,
  currentStartup: null,
  gallery: [],
  favorites: []
};

function updateState(partial) {
  Object.assign(state, partial);
  render();
}

4. Code Walkthrough: Building the Generator

4.1 Fetching Corporate Buzzwords

We need multiple buzzword phrases for different parts of the startup profile. Since each API call returns only one phrase, we make several calls in parallel.

async function fetchCorporateBS(count = 6) {
  const promises = Array.from({ length: count }, () =>
    fetch('https://corporatebs-generator.samerator.com/api/v1/corporate-bs')
      .then(res => {
        if (!res.ok) throw new Error('CorporateBS API failed');
        return res.json();
      })
      .then(data => data.phrase)
  );

  const results = await Promise.allSettled(promises);
  const phrases = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);

  // If we got fewer than needed, pad with fallback phrases
  const fallbacks = [
    'revolutionize cutting-edge paradigms',
    'synergize next-generation deliverables',
    'monetize mission-critical ecosystems',
    'disrupt world-class methodologies',
    'incentivize holistic architectures',
    'orchestrate bleeding-edge solutions'
  ];

  while (phrases.length < count) {
    phrases.push(fallbacks[phrases.length % fallbacks.length]);
  }

  return phrases;
}

Note the fallback mechanism. If the API is having a bad day and some requests fail, we fill in the gaps with pre-written phrases. This ensures the generator always produces a complete profile, even under degraded conditions. The user never sees an error; they just get slightly less random content.

4.2 Fetching the Brand Palette

Colormind uses a POST request, which is unusual for a color API. We call it with the default model to get a completely random but harmonious palette.

async function fetchColorPalette() {
  try {
    const response = await fetch('http://colormind.io/api/', {
      method: 'POST',
      body: JSON.stringify({ model: 'default' })
    });

    if (!response.ok) throw new Error('Colormind API failed');

    const data = await response.json();
    return data.result.map(rgb => ({
      rgb: rgb,
      hex: rgbToHex(rgb[0], rgb[1], rgb[2]),
      css: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
    }));
  } catch (err) {
    // Fallback: generate a random but decent palette
    return generateFallbackPalette();
  }
}

function rgbToHex(r, g, b) {
  return '#' + [r, g, b].map(x =>
    x.toString(16).padStart(2, '0')
  ).join('');
}

function generateFallbackPalette() {
  // Generate a palette based on a random hue
  const hue = Math.floor(Math.random() * 360);
  const palette = [
    hslToRgb(hue, 80, 15),
    hslToRgb(hue, 70, 30),
    hslToRgb(hue, 60, 50),
    hslToRgb(hue, 40, 70),
    hslToRgb(hue, 20, 90)
  ];

  return palette.map(rgb => ({
    rgb: rgb,
    hex: rgbToHex(rgb[0], rgb[1], rgb[2]),
    css: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
  }));
}

function hslToRgb(h, s, l) {
  s /= 100;
  l /= 100;
  const a = s * Math.min(l, 1 - l);
  const f = (n, k = (n + h / 30) % 12) =>
    l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  return [
    Math.round(f(0) * 255),
    Math.round(f(8) * 255),
    Math.round(f(4) * 255)
  ];
}

4.3 Enriching Names with Demographics

All three demographic APIs support batch requests, so we can enrich all team member names in just three parallel API calls instead of twelve sequential ones. This is a huge performance win.

async function enrichNames(firstNames) {
  const nameParams = firstNames.map(n => `name[]=${encodeURIComponent(n)}`).join('&');

  const [agifyRes, genderizeRes, nationalizeRes] = await Promise.allSettled([
    fetch(`https://api.agify.io/?${nameParams}`).then(r => r.json()),
    fetch(`https://api.genderize.io/?${nameParams}`).then(r => r.json()),
    fetch(`https://api.nationalize.io/?${nameParams}`).then(r => r.json())
  ]);

  const agifyData = agifyRes.status === 'fulfilled' ? agifyRes.value : [];
  const genderizeData = genderizeRes.status === 'fulfilled' ? genderizeRes.value : [];
  const nationalizeData = nationalizeRes.status === 'fulfilled' ? nationalizeRes.value : [];

  return firstNames.map((name, i) => {
    const agify = Array.isArray(agifyData)
      ? agifyData.find(d => d.name.toLowerCase() === name.toLowerCase())
      : null;
    const genderize = Array.isArray(genderizeData)
      ? genderizeData.find(d => d.name.toLowerCase() === name.toLowerCase())
      : null;
    const nationalize = Array.isArray(nationalizeData)
      ? nationalizeData.find(d => d.name.toLowerCase() === name.toLowerCase())
      : null;

    return {
      firstName: name,
      lastName: selectLastName(),
      age: agify ? agify.age : randomAge(),
      gender: genderize ? genderize.gender : null,
      genderProbability: genderize ? genderize.probability : null,
      country: nationalize && nationalize.country.length > 0
        ? nationalize.country[0]
        : null,
      dataPoints: agify ? agify.count : 0
    };
  });
}

function randomAge() {
  return Math.floor(Math.random() * 25) + 25; // 25-49
}

// Country code to flag emoji
function countryFlag(countryCode) {
  if (!countryCode) return '';
  const offset = 127397;
  return [...countryCode.toUpperCase()]
    .map(c => String.fromCodePoint(c.charCodeAt(0) + offset))
    .join('');
}

// Country code to name
const countryNames = {
  US: 'United States', GB: 'United Kingdom', CA: 'Canada',
  AU: 'Australia', DE: 'Germany', FR: 'France', JP: 'Japan',
  IN: 'India', BR: 'Brazil', MX: 'Mexico', KR: 'South Korea',
  CN: 'China', RU: 'Russia', EG: 'Egypt', NG: 'Nigeria',
  SA: 'Saudi Arabia', SE: 'Sweden', NO: 'Norway', IT: 'Italy',
  ES: 'Spain', PK: 'Pakistan', BD: 'Bangladesh', TR: 'Turkey',
  MA: 'Morocco', KE: 'Kenya', GH: 'Ghana', PH: 'Philippines',
  VN: 'Vietnam', TH: 'Thailand', IL: 'Israel', NL: 'Netherlands',
  IE: 'Ireland', PL: 'Poland', DZ: 'Algeria', SD: 'Sudan'
};

4.4 Generating the Company Name

Real startups follow naming conventions that are remarkably predictable. We encode these patterns into templates and fill them with random components.

function generateCompanyName() {
  const patterns = [
    () => `${pick(prefixes)}${pick(suffixes)}`,
    () => `${pick(adjectives)}${pick(nouns)}`,
    () => `${pick(nouns)}.${pick(tlds)}`,
    () => `${pick(prefixes)}${pick(nouns)}`,
    () => `${pick(verbs)}ly`,
    () => `${pick(nouns)}ify`,
    () => `go${pick(nouns)}`,
    () => `${pick(nouns)}stack`,
    () => `${pick(adjectives)}${pick(suffixes)}`,
    () => `${pick(prefixes)}${pick(suffixes)}.ai`
  ];

  return patterns[Math.floor(Math.random() * patterns.length)]();
}

const prefixes = [
  'Neo', 'Hyper', 'Quantum', 'Meta', 'Sync', 'Flux',
  'Zen', 'Nova', 'Apex', 'Velo', 'Omni', 'Ultra',
  'Dyna', 'Aero', 'Byte', 'Data', 'Cloud', 'Stack'
];

const suffixes = [
  'hub', 'lab', 'flow', 'scape', 'base', 'grid',
  'forge', 'shift', 'wave', 'link', 'spark', 'pulse',
  'mind', 'craft', 'wise', 'bridge', 'dock', 'vault'
];

const adjectives = [
  'Bright', 'Swift', 'Bold', 'Clear', 'True', 'Pure',
  'Open', 'Smart', 'Deep', 'Fast', 'Next', 'Blue'
];

const nouns = [
  'pixel', 'metric', 'signal', 'orbit', 'prism', 'helix',
  'vector', 'cipher', 'nexus', 'atlas', 'bloom', 'ember'
];

const verbs = [
  'amplif', 'simplif', 'clarif', 'unif', 'notif', 'magnif',
  'gamif', 'stratif', 'diversif', 'intensif', 'solidif', 'quantif'
];

const tlds = ['io', 'ai', 'co', 'so', 'app', 'dev'];

function pick(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

4.5 Assembling the Full Startup Profile

This is where everything comes together. We run all the API calls in parallel, then assemble the results into a comprehensive startup profile object.

async function generateStartup() {
  updateState({ generating: true, error: null });

  try {
    // Phase 1: Raw materials (parallel)
    const teamNames = selectTeamNames(4);
    const [phrases, palette, enrichedTeam] = await Promise.all([
      fetchCorporateBS(6),
      fetchColorPalette(),
      enrichNames(teamNames)
    ]);

    // Phase 2: Assembly
    const companyName = generateCompanyName();
    const foundedYear = 2018 + Math.floor(Math.random() * 8);
    const employeeCount = pick([3, 5, 8, 12, 23, 47, 89, 156, 342]);
    const fundingRound = pick([
      'Pre-Seed', 'Seed', 'Series A', 'Series B', 'Series C'
    ]);
    const valuation = generateValuation(fundingRound);

    // Assign roles to team members
    const roles = ['CEO & Co-founder', 'CTO & Co-founder', 'VP of Product', 'Head of Growth'];
    const team = enrichedTeam.map((member, i) => ({
      ...member,
      role: roles[i],
      fullName: `${member.firstName} ${member.lastName}`,
      flagEmoji: member.country ? countryFlag(member.country.country_id) : '',
      countryName: member.country
        ? (countryNames[member.country.country_id] || member.country.country_id)
        : 'Unknown'
    }));

    // Build the startup profile
    const startup = {
      id: `startup-${Date.now()}`,
      name: companyName,
      tagline: capitalizeFirst(phrases[0]),
      mission: `Our mission is to ${phrases[1]} while we ${phrases[2]}.`,
      founded: foundedYear,
      employees: employeeCount,
      funding: fundingRound,
      valuation: valuation,
      features: [
        capitalizeFirst(phrases[3]),
        capitalizeFirst(phrases[4]),
        capitalizeFirst(phrases[5])
      ],
      team: team,
      palette: palette,
      industry: pick([
        'Enterprise SaaS', 'FinTech', 'EdTech', 'HealthTech',
        'PropTech', 'GreenTech', 'DevTools', 'AI/ML Platform',
        'Blockchain Infrastructure', 'No-Code/Low-Code'
      ]),
      location: pick([
        'San Francisco, CA', 'Austin, TX', 'New York, NY',
        'London, UK', 'Berlin, DE', 'Singapore', 'Tel Aviv, IL',
        'Bangalore, IN', 'Toronto, CA', 'Remote-first'
      ]),
      ceoQuote: generateCEOQuote(companyName, team[0].firstName),
      timestamp: new Date().toISOString()
    };

    // Save to gallery
    const gallery = [startup, ...state.gallery].slice(0, 20);
    localStorage.setItem('fakeStartup_gallery', JSON.stringify(gallery));

    updateState({
      generating: false,
      currentStartup: startup,
      gallery: gallery
    });

  } catch (err) {
    updateState({
      generating: false,
      error: `Generation failed: ${err.message}. Please try again.`
    });
  }
}

function capitalizeFirst(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function generateValuation(round) {
  const ranges = {
    'Pre-Seed': [0.5, 3],
    'Seed': [3, 15],
    'Series A': [15, 80],
    'Series B': [80, 400],
    'Series C': [400, 2000]
  };
  const [min, max] = ranges[round] || [1, 10];
  const val = min + Math.random() * (max - min);
  return val >= 1000
    ? `$${(val / 1000).toFixed(1)}B`
    : `$${val.toFixed(0)}M`;
}

function generateCEOQuote(companyName, ceoName) {
  const templates = [
    `"At ${companyName}, we're not just building a product. We're building the future of how humans interact with technology. And that future is now." - ${ceoName}`,
    `"When I started ${companyName}, everyone said it was impossible. But impossible is just a word that small-minded people use to feel comfortable." - ${ceoName}`,
    `"We don't have competitors. We have companies that will eventually realize they should have been building what we're building." - ${ceoName}`,
    `"${companyName} isn't a company. It's a movement. A paradigm shift. A cultural revolution disguised as a SaaS platform." - ${ceoName}`,
    `"I didn't go to Stanford just to build another app. I went to Stanford to change the world. ${companyName} is how I'm doing it." - ${ceoName}`,
    `"Our Series ${pick(['A', 'B', 'C'])} wasn't just funding. It was validation that the market is ready for what ${companyName} has always known." - ${ceoName}`
  ];
  return pick(templates);
}

4.6 Rendering the Startup Card

The rendering function creates a visually polished startup profile card using the Colormind palette for dynamic styling. Each startup looks like a legitimate landing page hero section.

function renderStartupCard(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;

  const cardHtml = `
    <div class="startup-card" style="
      background: linear-gradient(135deg, ${dark.css}, ${darkMid.css});
      border: 1px solid ${primary.hex}33;
    ">

      <div class="brand-header" style="border-bottom: 2px solid ${primary.css}">
        <div class="logo-placeholder" style="
          background: linear-gradient(135deg, ${primary.css}, ${lightMid.css});
          color: ${dark.css};
        ">
          ${startup.name.charAt(0)}
        </div>
        <h2 style="color: ${light.css}">${startup.name}</h2>
        <span class="industry-tag" style="
          background: ${primary.hex}22;
          color: ${primary.css};
          border: 1px solid ${primary.hex}44;
        ">${startup.industry}</span>
      </div>

      <p class="tagline" style="color: ${lightMid.css}">
        ${startup.tagline}
      </p>

      <p class="mission" style="color: ${light.hex}bb">
        ${startup.mission}
      </p>

      <div class="stats-row">
        <div class="stat">
          <span class="stat-value" style="color: ${primary.css}">
            ${startup.founded}
          </span>
          <span class="stat-label">Founded</span>
        </div>
        <div class="stat">
          <span class="stat-value" style="color: ${primary.css}">
            ${startup.employees}
          </span>
          <span class="stat-label">Employees</span>
        </div>
        <div class="stat">
          <span class="stat-value" style="color: ${primary.css}">
            ${startup.funding}
          </span>
          <span class="stat-label">Stage</span>
        </div>
        <div class="stat">
          <span class="stat-value" style="color: ${primary.css}">
            ${startup.valuation}
          </span>
          <span class="stat-label">Valuation</span>
        </div>
      </div>

      <h3 style="color: ${lightMid.css}">The Team</h3>
      <div class="team-grid">
        ${startup.team.map(member => `
          <div class="team-member">
            <div class="avatar" style="
              background: ${primary.hex}33;
              color: ${primary.css};
            ">${member.firstName.charAt(0)}${member.lastName.charAt(0)}</div>
            <strong style="color: ${light.css}">${member.fullName}</strong>
            <span class="role">${member.role}</span>
            <span class="demographics">
              ${member.flagEmoji} ${member.age ? `Age ${member.age}` : ''}
              ${member.countryName !== 'Unknown' ? ` from ${member.countryName}` : ''}
            </span>
          </div>
        `).join('')}
      </div>

      <h3 style="color: ${lightMid.css}">Key Features</h3>
      <ul class="features-list">
        ${startup.features.map(f => `
          <li style="color: ${light.hex}cc">
            <span style="color: ${primary.css}">✓</span> ${f}
          </li>
        `).join('')}
      </ul>

      <blockquote class="ceo-quote" style="
        border-left-color: ${primary.css};
        color: ${lightMid.hex}cc;
      ">
        ${startup.ceoQuote}
      </blockquote>

      <div class="palette-strip">
        ${startup.palette.map(color => `
          <div class="palette-swatch"
               style="background: ${color.css}"
               title="${color.hex}">
            <span>${color.hex}</span>
          </div>
        `).join('')}
      </div>

      <p class="location" style="color: ${light.hex}88">
        ${startup.location}
      </p>
    </div>
  `;

  document.getElementById('startupDisplay').innerHTML = cardHtml;
}

5. Advanced Features: Deepening the Satire

5.1 Generating a Fake Pitch Deck Narrative

Every startup needs a pitch deck, and every pitch deck follows the same structure: problem, solution, market size, traction, team, and ask. We can generate a complete narrative for each fake startup by combining CorporateBS phrases with template-driven storytelling. The result is a pitch that reads like every YC application you have ever seen, which is both the joke and the point.

function generatePitchDeck(startup, phrases) {
  const marketSizes = ['$4.2B', '$12.8B', '$27.5B', '$89.3B', '$156B', '$342B'];
  const growthRates = ['127%', '340%', '892%', '1,247%'];
  const userCounts = ['10,000', '47,000', '125,000', '890,000'];
  const askAmounts = {
    'Pre-Seed': '$500K',
    'Seed': '$2.5M',
    'Series A': '$15M',
    'Series B': '$45M',
    'Series C': '$120M'
  };

  return {
    problem: {
      title: 'The Problem',
      content: `Every year, enterprises waste billions of dollars trying to ` +
        `${phrases[0] || 'manage outdated systems'}. The current solutions are ` +
        `fragmented, expensive, and fundamentally broken. ` +
        `${pick(['83', '71', '92', '67'])}% of Fortune 500 companies report ` +
        `that their existing tools fail to ${phrases[1] || 'deliver meaningful results'}.`
    },
    solution: {
      title: 'Our Solution',
      content: `${startup.name} is the first platform to ${startup.tagline.toLowerCase()}. ` +
        `We use proprietary ${pick(['AI', 'machine learning', 'blockchain', 'quantum computing'])} ` +
        `algorithms to ${phrases[2] || 'transform how teams collaborate'}. ` +
        `Our platform integrates seamlessly with existing workflows while providing ` +
        `${pick(['10x', '100x', '1000x'])} the value of traditional approaches.`
    },
    market: {
      title: 'Market Opportunity',
      tam: pick(marketSizes),
      sam: `$${(parseFloat(pick(marketSizes)) * 0.15).toFixed(1)}B`,
      som: `$${(parseFloat(pick(marketSizes)) * 0.02).toFixed(1)}B`,
      content: `The total addressable market for ${startup.industry} is ` +
        `${pick(marketSizes)}, growing at ${pick(['18', '24', '31', '42'])}% CAGR. ` +
        `We are targeting the ${pick(marketSizes)} segment initially.`
    },
    traction: {
      title: 'Traction',
      mrr: `$${pick(['23', '89', '156', '340', '720'])}K MRR`,
      users: pick(userCounts),
      growth: pick(growthRates) + ' QoQ',
      nps: pick(['72', '81', '87', '93']),
      content: `Since launching ${pick(['6', '9', '12', '18'])} months ago, ` +
        `we have achieved ${pick(growthRates)} quarter-over-quarter growth with ` +
        `${pick(userCounts)} active users and an NPS of ${pick(['72', '81', '87', '93'])}.`
    },
    team: {
      title: 'The Team',
      content: startup.team.map(m =>
        `${m.fullName} (${m.role}) - ${pick([
          'Ex-Google, Stanford CS',
          'Former McKinsey, Harvard MBA',
          'Ex-Meta, MIT EECS',
          'Serial entrepreneur, 2 exits',
          'Ex-Stripe, YC alum',
          'Former Goldman Sachs, Wharton',
          'Ex-Amazon, 15 patents',
          'Former Palantir, Carnegie Mellon PhD'
        ])}`
      ).join('\n')
    },
    ask: {
      title: 'The Ask',
      amount: askAmounts[startup.funding] || '$10M',
      content: `We are raising ${askAmounts[startup.funding] || '$10M'} to ` +
        `${phrases[3] || 'scale our go-to-market engine'}, ` +
        `expand into ${pick(['APAC', 'EMEA', 'LATAM', 'the enterprise segment'])}, ` +
        `and hire ${pick(['25', '40', '60', '100'])} world-class engineers.`
    }
  };
}

function renderPitchDeck(pitch) {
  return Object.values(pitch).map(slide => `
    <div class="pitch-slide">
      <h4>${slide.title}</h4>
      <p>${slide.content}</p>
      ${slide.tam ? `
        <div class="market-sizes">
          <div><strong>TAM:</strong> ${slide.tam}</div>
          <div><strong>SAM:</strong> ${slide.sam}</div>
          <div><strong>SOM:</strong> ${slide.som}</div>
        </div>
      ` : ''}
      ${slide.mrr ? `
        <div class="traction-metrics">
          <div><strong>MRR:</strong> ${slide.mrr}</div>
          <div><strong>Users:</strong> ${slide.users}</div>
          <div><strong>Growth:</strong> ${slide.growth}</div>
        </div>
      ` : ''}
    </div>
  `).join('');
}

5.2 Generating Fake Product Screenshots

Every startup pitch includes product screenshots, and we can generate convincing mockups using nothing but HTML, CSS, and the Colormind palette. By creating a dynamic SVG dashboard "screenshot," we give each startup a visual representation of its supposed product. The SVG uses the brand palette, includes fake chart elements, and renders text that incorporates the CorporateBS phrases.

function generateProductMockup(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;

  return `
    <svg viewBox="0 0 800 500" xmlns="http://www.w3.org/2000/svg"
         style="width: 100%; border-radius: 8px; border: 1px solid ${primary.hex}33;">

      <!-- Dashboard background -->
      <rect width="800" height="500" fill="${dark.hex}" />

      <!-- Sidebar -->
      <rect width="180" height="500" fill="${darkMid.hex}" />
      <text x="20" y="35" fill="${primary.hex}" font-size="16"
            font-weight="bold" font-family="Inter, sans-serif">
        ${startup.name}
      </text>

      <!-- Sidebar menu items -->
      ${['Dashboard', 'Analytics', 'Users', 'Settings', 'Reports'].map((item, i) => `
        <rect x="10" y="${60 + i * 40}" width="160" height="30" rx="6"
              fill="${i === 0 ? primary.hex + '33' : 'transparent'}" />
        <text x="20" y="${80 + i * 40}" fill="${i === 0 ? primary.hex : lightMid.hex}"
              font-size="12" font-family="Inter, sans-serif">${item}</text>
      `).join('')}

      <!-- Main content area -->
      <text x="200" y="40" fill="${light.hex}" font-size="18"
            font-weight="bold" font-family="Inter, sans-serif">Dashboard</text>

      <!-- Metric cards -->
      ${[
        { label: 'Total Users', value: '24,847', change: '+12.3%' },
        { label: 'Revenue', value: '$156K', change: '+34.1%' },
        { label: 'NPS Score', value: '87', change: '+5.2%' }
      ].map((card, i) => `
        <rect x="${200 + i * 200}" y="55" width="185" height="70" rx="8"
              fill="${darkMid.hex}" />
        <text x="${215 + i * 200}" y="78" fill="${lightMid.hex}"
              font-size="10" font-family="Inter, sans-serif">${card.label}</text>
        <text x="${215 + i * 200}" y="102" fill="${light.hex}"
              font-size="20" font-weight="bold"
              font-family="Inter, sans-serif">${card.value}</text>
        <text x="${320 + i * 200}" y="102" fill="#26de81"
              font-size="11" font-family="Inter, sans-serif">${card.change}</text>
      `).join('')}

      <!-- Fake chart -->
      <rect x="200" y="140" width="385" height="200" rx="8"
            fill="${darkMid.hex}" />
      <text x="215" y="165" fill="${lightMid.hex}"
            font-size="12" font-family="Inter, sans-serif">Revenue Growth</text>

      <!-- Chart line -->
      <polyline points="220,310 260,290 300,300 340,270 380,250 420,260 460,220 500,200 540,180 560,160"
              fill="none" stroke="${primary.hex}" stroke-width="2.5" />
      <!-- Chart area fill -->
      <polygon points="220,310 260,290 300,300 340,270 380,250 420,260 460,220 500,200 540,180 560,160 560,320 220,320"
              fill="${primary.hex}" opacity="0.15" />

      <!-- Activity list -->
      <rect x="600" y="140" width="185" height="200" rx="8"
            fill="${darkMid.hex}" />
      <text x="615" y="165" fill="${lightMid.hex}"
            font-size="12" font-family="Inter, sans-serif">Recent Activity</text>

      ${['New signup', 'Plan upgrade', 'Feature used', 'Invoice paid', 'API call'].map((item, i) => `
        <circle cx="622" cy="${190 + i * 28}" r="3" fill="${primary.hex}" />
        <text x="632" y="${194 + i * 28}" fill="${lightMid.hex}"
              font-size="10" font-family="Inter, sans-serif">${item}</text>
      `).join('')}
    </svg>
  `;
}

5.3 Generating Fake Testimonials and Press Quotes

No startup profile is complete without glowing testimonials from fictional customers and breathless press quotes from imaginary publications. We generate these by combining real publication names with CorporateBS phrases and attaching them to fictional reviewers with demographically enriched names.

function generateTestimonials(startup, team) {
  const publications = [
    'TechCrunch', 'The Verge', 'Wired', 'Fast Company',
    'Forbes', 'Bloomberg', 'Harvard Business Review', 'MIT Technology Review'
  ];

  const titles = [
    'VP of Engineering', 'Head of Product', 'CTO', 'Director of Innovation',
    'Chief Digital Officer', 'SVP of Operations', 'Principal Architect'
  ];

  const companies = [
    'Stripe', 'Notion', 'Figma', 'Linear', 'Vercel',
    'Shopify', 'Twilio', 'Datadog', 'Snowflake', 'MongoDB'
  ];

  const testimonialTemplates = [
    `${startup.name} completely transformed how our team operates. We saw a ${pick(['3x', '5x', '10x'])} improvement in our workflow within the first month.`,
    `After evaluating ${pick(['15', '20', '30'])} vendors, we chose ${startup.name}. It was the only platform that truly understood our needs.`,
    `I cannot imagine going back to how we worked before ${startup.name}. It is genuinely that transformative.`,
    `${startup.name} is not just a tool; it is a competitive advantage. Our competitors do not know what hit them.`
  ];

  const pressTemplates = [
    `"${startup.name} is leading the charge in ${startup.industry}..." - ${pick(publications)}`,
    `"One of the most promising startups we have seen this year" - ${pick(publications)}`,
    `"${startup.name} has raised ${startup.valuation} at a valuation that reflects the enormous market opportunity" - ${pick(publications)}`,
    `"If you are not paying attention to ${startup.name}, you should be" - ${pick(publications)}`
  ];

  return {
    testimonials: Array.from({ length: 3 }, () => ({
      quote: pick(testimonialTemplates),
      name: `${pick(namePool.common)} ${pick(lastNames)}`,
      title: pick(titles),
      company: pick(companies)
    })),
    pressQuotes: Array.from({ length: 2 }, () => pick(pressTemplates))
  };
}

5.4 Generating a Fake Landing Page

The ultimate extension of the generator is to produce a complete, downloadable landing page for each fake startup. This HTML file uses the Colormind palette, incorporates all the generated content, and looks indistinguishable from a real startup's landing page. Users can download it as a standalone HTML file, share it as a joke, or use it as a design template.

function generateLandingPageHTML(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;

  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${startup.name} - ${startup.tagline}</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: ${dark.hex}; color: ${light.hex}; }
    .hero { text-align: center; padding: 6rem 2rem; }
    .hero h1 { font-size: 3.5rem; margin-bottom: 1rem; }
    .hero p { font-size: 1.3rem; color: ${lightMid.hex}; max-width: 600px; margin: 0 auto 2rem; }
    .cta { background: ${primary.hex}; color: ${dark.hex}; padding: 1rem 2.5rem;
           border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer;
           font-weight: 700; text-decoration: none; display: inline-block; }
    .features { display: grid; grid-template-columns: repeat(3, 1fr);
                gap: 2rem; padding: 4rem 2rem; max-width: 1000px; margin: 0 auto; }
    .feature { background: ${darkMid.hex}; padding: 2rem; border-radius: 12px;
               border: 1px solid ${primary.hex}22; }
    .feature h3 { color: ${primary.hex}; margin-bottom: 0.5rem; }
    .feature p { color: ${lightMid.hex}; font-size: 0.95rem; line-height: 1.6; }
    .stats { display: flex; justify-content: center; gap: 4rem; padding: 3rem;
             background: ${darkMid.hex}; }
    .stat-num { font-size: 2.5rem; font-weight: 800; color: ${primary.hex}; }
    .stat-label { color: ${lightMid.hex}; font-size: 0.9rem; }
    footer { text-align: center; padding: 2rem; color: ${lightMid.hex}88; font-size: 0.8rem; }
  </style>
</head>
<body>
  <div class="hero">
    <h1>${startup.name}</h1>
    <p>${startup.mission}</p>
    <a href="#" class="cta">Get Early Access</a>
  </div>
  <div class="stats">
    <div><div class="stat-num">${startup.employees}+</div><div class="stat-label">Team Members</div></div>
    <div><div class="stat-num">${startup.valuation}</div><div class="stat-label">Valuation</div></div>
    <div><div class="stat-num">${startup.funding}</div><div class="stat-label">Latest Round</div></div>
  </div>
  <div class="features">
    ${startup.features.map(f => `
      <div class="feature">
        <h3>✓ Key Feature</h3>
        <p>${f}</p>
      </div>
    `).join('')}
  </div>
  <footer>
    <p>This is a fake startup generated by the Fake Startup Generator. Not a real company.</p>
  </footer>
</body>
</html>`;
}

6. Challenges and Gotchas

6.1 Colormind Uses HTTP, Not HTTPS

This is the single most frustrating gotcha in the project. The Colormind API is served over HTTP only, not HTTPS. If your application is served over HTTPS (as it should be), the browser will block the Colormind request as "mixed content." There are several workarounds. You can use a CORS proxy that adds HTTPS. You can set up a tiny server-side proxy. Or you can use our fallback palette generator, which is entirely client-side and produces decent results. For a production application, the proxy approach is the most robust.

// Option 1: Try Colormind, fall back to local generation
async function fetchColorPaletteSafe() {
  try {
    // Try the direct API call (works if page is HTTP or mixed content is allowed)
    return await fetchColorPalette();
  } catch (err) {
    console.warn('Colormind unavailable (likely mixed content), using fallback');
    return generateFallbackPalette();
  }
}

5.2 Rate Limits on Demographic APIs

Agify, Genderize, and Nationalize all impose a daily rate limit of 1,000 requests for free-tier users (counted by IP address). Since we batch up to four names per call, each startup generation uses three API calls (one per service). That gives us roughly 333 startup generations per day before hitting the limit. For personal use this is plenty, but if you deploy this publicly, you will need to either purchase API keys, implement aggressive caching, or run the demographic lookups through a server that caches results globally.

// Cache demographic data to avoid hitting rate limits
const demographicCache = new Map();

async function enrichNamesWithCache(firstNames) {
  const uncachedNames = firstNames.filter(n => !demographicCache.has(n.toLowerCase()));

  if (uncachedNames.length > 0) {
    const freshData = await enrichNames(uncachedNames);
    freshData.forEach(d => {
      demographicCache.set(d.firstName.toLowerCase(), d);
    });
  }

  return firstNames.map(name => {
    const cached = demographicCache.get(name.toLowerCase());
    return cached || { firstName: name, lastName: selectLastName(), age: randomAge() };
  });
}

5.3 The Gender Problem

Using Genderize to assign gender based on first names is statistically useful but ethically fraught. Gender is not binary, names do not determine gender, and the API's training data reflects historical naming patterns that may not represent how individuals actually identify. For a satirical generator, this is less problematic (the entire output is fake), but if you adapt this pattern for any real-world application, you should treat the gender prediction as a statistical tendency, never as an assertion about any individual. In our generator, we use it only to add demographic flavor to the fictional team, and we include the probability score to signal uncertainty.

5.4 Name-Nationality Stereotyping

Similarly, Nationalize predicts nationalities based on names, which can reinforce stereotypes. "Omar" is predicted to be Egyptian, "Yuki" to be Japanese. While these predictions are statistically accurate (they reflect actual name distributions), they can feel reductive. In our satirical context, this is part of the commentary: startups love to tout their "global, diverse teams" while reducing diversity to flag emojis and country labels. The generator mirrors this superficiality intentionally.

5.5 CorporateBS Repetition

The CorporateBS API has a finite vocabulary pool, so after many generations, you will start seeing repeated phrases. We mitigate this by making six calls but only using each phrase once, and by applying light post-processing (shuffling words within phrases, combining fragments from two phrases) to create more variation.

6. Real-World Use Cases

6.1 Design System Prototyping

Designers often need placeholder content when building design systems. The Fake Startup Generator produces realistic brand identities with harmonious color palettes, making it far more useful than "Lorem ipsum" for prototyping landing pages, dashboards, and marketing materials. Each generation produces a new palette, name, and team, giving designers a rich set of test data.

6.2 Pitch Deck Workshops

Business schools and accelerators can use the generator as a teaching tool. Generate a random startup and challenge teams to build a pitch deck around it in 30 minutes. The absurd but plausible starting material forces participants to practice storytelling, market analysis, and critical thinking. It also highlights how easy it is to make anything sound impressive with the right vocabulary.

6.3 Content for Demo Applications

If you are building a project management tool, CRM, or any B2B SaaS product, you need demo data that looks realistic. The Fake Startup Generator can produce hundreds of unique company profiles with teams, industries, and branding, all perfect for populating demo environments. This is far more convincing than "Test Company 1, Test Company 2."

6.4 Social Media Content

A Twitter/X bot that posts a new fake startup every hour would attract a following among the tech-savvy crowd. Each post could include the company name, tagline, valuation, and team composition. The satirical format is inherently shareable, and the variability ensures fresh content indefinitely.

6.5 Diversity and Inclusion Training

The generator's use of global names and demographic prediction can spark conversations about how we perceive diversity in the workplace. When a generated startup team includes members from four different countries, it raises questions about what diversity really means versus how it is performed. This makes it a useful conversation starter for D&I workshops in tech companies.

7. Performance Tips

7.1 Batch Everything

The demographic APIs all support batch requests. Never make individual calls when you can batch. Four individual calls to Agify take four round trips (~800ms). One batched call takes one round trip (~200ms). This pattern applies universally: any time you need to make the same type of request with different parameters, check if the API supports batching.

7.2 Precompute Name Pools

Instead of randomly selecting names and then enriching them, consider pre-enriching your entire name pool on first load and caching the results. This moves the API calls to a one-time startup cost, making subsequent generations instant. With 54 names in our pool, the batch requests require 6 calls each to Agify, Genderize, and Nationalize (10 names per batch), for a total of 18 calls on first load.

7.3 Generate Ahead

While the user is admiring the current startup, generate the next one in the background. By the time they click "Generate" again, the result is already waiting. This makes the tool feel instantaneous even though the API calls take 500-1000ms.

let nextStartup = null;

async function prefetchNextStartup() {
  try {
    nextStartup = await buildStartupProfile();
  } catch {
    nextStartup = null;
  }
}

async function handleGenerate() {
  if (nextStartup) {
    displayStartup(nextStartup);
    nextStartup = null;
    prefetchNextStartup(); // Start preparing the next one
  } else {
    const startup = await buildStartupProfile();
    displayStartup(startup);
    prefetchNextStartup();
  }
}

7.4 Minimize DOM Writes

Building the startup card HTML as a single string and setting innerHTML once is significantly faster than creating elements individually with createElement and appendChild. For a template this complex, the difference is noticeable. Just be sure to escape any user-generated content to prevent XSS (though in our case, all content is either API-generated or from our own pools).

8. Extension Ideas

8.1 AI-Generated Logos

Replace the letter-based logo placeholder with an AI-generated logo. Services like Stable Diffusion or DALL-E can generate logo concepts from a text description. Combine the company name, industry, and primary brand color into a prompt like "minimal vector logo for a FinTech company called QuantumForge, primary color #6c5ce7" and generate a unique logo for each startup.

8.2 Fake Press Releases

Generate complete press releases for each startup using a template system. Announce their Series A funding, their partnership with a Fortune 500 company, or their pivot from B2B to B2C. Each press release uses CorporateBS phrases to describe the company's vision while following the standard PR format that journalists expect.

8.3 Startup Battle Mode

Generate two startups simultaneously and let users vote on which one they would invest in. Track the votes and build a leaderboard of the most "investable" fake startups. This gamification would drive engagement and produce entertaining data about what makes a startup sound credible (hint: higher valuation and more buzzwords always win).

8.4 LinkedIn Profile Generator

For each team member, generate a complete LinkedIn-style profile with a professional headline (combining their role with a CorporateBS phrase), a summary section, and a list of previous fake companies. This extends the satire into the realm of personal branding and professional social media.

8.5 Export to Pitch Deck

Allow users to export the startup profile as a downloadable PDF pitch deck. Use a library like jsPDF to generate slides that include the brand colors, team photos (using randomly selected stock photos from an avatar API), features, and the obligatory hockey-stick growth chart. The result would be indistinguishable from a real seed-stage pitch deck.

9. Advanced Rendering: SVG Business Charts

Every fake startup needs fake metrics, and fake metrics need fake charts. We can generate convincing business charts purely with SVG, no libraries needed. The charts use the Colormind palette for brand consistency and include the kind of hockey-stick growth curves that investors love to see. The key is to make the numbers internally consistent: if the startup was founded in 2022 and raised a Series B, the revenue curve should show the trajectory that justifies that valuation.

function generateRevenueChart(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;
  const yearsActive = new Date().getFullYear() - startup.founded;

  // Generate plausible revenue trajectory
  const months = yearsActive * 12;
  const dataPoints = [];
  let revenue = 1000 + Math.random() * 5000; // Starting MRR

  for (let i = 0; i <= months; i++) {
    const growthRate = 1.05 + Math.random() * 0.1; // 5-15% monthly growth
    const seasonality = 1 + 0.1 * Math.sin(i * Math.PI / 6); // Seasonal variation
    revenue *= growthRate * seasonality;
    dataPoints.push({ month: i, revenue: Math.round(revenue) });
  }

  // Normalize for SVG coordinates
  const maxRevenue = Math.max(...dataPoints.map(d => d.revenue));
  const chartWidth = 600;
  const chartHeight = 250;
  const padding = { top: 30, right: 20, bottom: 40, left: 60 };
  const plotWidth = chartWidth - padding.left - padding.right;
  const plotHeight = chartHeight - padding.top - padding.bottom;

  const points = dataPoints.map((d, i) => {
    const x = padding.left + (i / dataPoints.length) * plotWidth;
    const y = padding.top + plotHeight - (d.revenue / maxRevenue) * plotHeight;
    return `${x},${y}`;
  });

  const areaPoints = [
    `${padding.left},${padding.top + plotHeight}`,
    ...points,
    `${padding.left + plotWidth},${padding.top + plotHeight}`
  ];

  // Format revenue for Y-axis labels
  const formatRevenue = (val) => {
    if (val >= 1000000) return `$${(val / 1000000).toFixed(1)}M`;
    if (val >= 1000) return `$${(val / 1000).toFixed(0)}K`;
    return `$${val}`;
  };

  // Generate Y-axis labels
  const yLabels = Array.from({ length: 5 }, (_, i) => {
    const value = (maxRevenue / 4) * i;
    const y = padding.top + plotHeight - (i / 4) * plotHeight;
    return { value, y, label: formatRevenue(value) };
  });

  // Generate X-axis labels (years)
  const xLabels = Array.from({ length: yearsActive + 1 }, (_, i) => {
    const x = padding.left + (i * 12 / dataPoints.length) * plotWidth;
    return { x, label: String(startup.founded + i) };
  });

  return `
    <svg viewBox="0 0 ${chartWidth} ${chartHeight}" xmlns="http://www.w3.org/2000/svg"
         style="width: 100%; background: ${darkMid.hex}; border-radius: 8px;">

      <!-- Title -->
      <text x="${chartWidth / 2}" y="18" fill="${lightMid.hex}" font-size="12"
            text-anchor="middle" font-family="Inter, sans-serif">
        Monthly Recurring Revenue
      </text>

      <!-- Grid lines -->
      ${yLabels.map(l => `
        <line x1="${padding.left}" y1="${l.y}" x2="${padding.left + plotWidth}" y2="${l.y}"
              stroke="${dark.hex}" stroke-width="0.5" />
        <text x="${padding.left - 5}" y="${l.y + 4}" fill="${lightMid.hex}88"
              font-size="9" text-anchor="end" font-family="Inter, sans-serif">
          ${l.label}
        </text>
      `).join('')}

      <!-- X-axis labels -->
      ${xLabels.map(l => `
        <text x="${l.x}" y="${padding.top + plotHeight + 20}" fill="${lightMid.hex}88"
              font-size="9" text-anchor="middle" font-family="Inter, sans-serif">
          ${l.label}
        </text>
      `).join('')}

      <!-- Area fill -->
      <polygon points="${areaPoints.join(' ')}" fill="${primary.hex}" opacity="0.15" />

      <!-- Line -->
      <polyline points="${points.join(' ')}" fill="none"
                stroke="${primary.hex}" stroke-width="2" />

      <!-- Current MRR annotation -->
      <text x="${padding.left + plotWidth}" y="${padding.top + 10}"
            fill="${primary.hex}" font-size="14" font-weight="bold"
            text-anchor="end" font-family="Inter, sans-serif">
        ${formatRevenue(dataPoints[dataPoints.length - 1].revenue)} MRR
      </text>
    </svg>
  `;
}

function generateTeamGrowthChart(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;
  const quarters = Math.max(4, (new Date().getFullYear() - startup.founded) * 4);

  // Generate plausible headcount growth
  let headcount = 2; // Founders
  const data = [{ q: 0, count: headcount }];

  for (let q = 1; q <= quarters; q++) {
    const growth = Math.floor(Math.random() * 4) + 1;
    headcount += growth;
    data.push({ q, count: headcount });
  }

  const maxCount = Math.max(...data.map(d => d.count));
  const barWidth = 500 / data.length - 2;

  return `
    <svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg"
         style="width: 100%; background: ${darkMid.hex}; border-radius: 8px; margin-top: 1rem;">
      <text x="300" y="18" fill="${lightMid.hex}" font-size="12"
            text-anchor="middle" font-family="Inter, sans-serif">
        Team Growth
      </text>
      ${data.map((d, i) => {
        const barHeight = (d.count / maxCount) * 140;
        const x = 50 + i * (500 / data.length);
        const y = 170 - barHeight;
        return `
          <rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}"
                fill="${primary.hex}" opacity="${0.5 + (i / data.length) * 0.5}"
                rx="2" />
          ${i === data.length - 1 ? `
            <text x="${x + barWidth / 2}" y="${y - 5}" fill="${primary.hex}"
                  font-size="11" font-weight="bold" text-anchor="middle"
                  font-family="Inter, sans-serif">${d.count}</text>
          ` : ''}
        `;
      }).join('')}
      <line x1="50" y1="170" x2="550" y2="170" stroke="${dark.hex}" stroke-width="1" />
    </svg>
  `;
}

These SVG charts are lightweight (no canvas rendering, no library overhead), responsive (they scale with their container), and print beautifully. The revenue chart intentionally has the hockey-stick shape that is expected in startup pitch decks, reinforcing the satire. The team growth chart shows the relentless hiring that characterizes venture-backed companies. Together with the Colormind palette, they make each generated startup feel like a real company with a real track record.

10. Sharing and Social Features

10.1 Generating Shareable Social Cards

Each generated startup should be shareable on social media with a visually compelling preview card. We generate an Open Graph-compatible image using SVG that includes the startup name, tagline, team composition, and brand colors. This SVG can be converted to a PNG using a canvas element for downloading or sharing.

function generateSocialCard(startup) {
  const [dark, darkMid, primary, lightMid, light] = startup.palette;

  const svg = `
    <svg viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
      <!-- Background -->
      <rect width="1200" height="630" fill="${dark.hex}" />
      <rect x="0" y="0" width="1200" height="4" fill="${primary.hex}" />

      <!-- Logo circle -->
      <circle cx="100" cy="200" r="50" fill="${primary.hex}" opacity="0.2" />
      <text x="100" y="215" text-anchor="middle" fill="${primary.hex}"
            font-size="36" font-weight="bold" font-family="Inter, sans-serif">
        ${startup.name.charAt(0)}
      </text>

      <!-- Company name -->
      <text x="170" y="195" fill="${light.hex}" font-size="42" font-weight="bold"
            font-family="Inter, sans-serif">${startup.name}</text>
      <text x="170" y="225" fill="${lightMid.hex}" font-size="16"
            font-family="Inter, sans-serif">${startup.industry} | ${startup.location}</text>

      <!-- Tagline -->
      <text x="60" y="310" fill="${lightMid.hex}" font-size="20"
            font-family="Inter, sans-serif">
        "${startup.tagline}"
      </text>

      <!-- Stats bar -->
      <rect x="60" y="370" width="1080" height="80" rx="10"
            fill="${darkMid.hex}" />
      ${[
        { label: 'Founded', value: startup.founded },
        { label: 'Team', value: `${startup.employees} people` },
        { label: 'Stage', value: startup.funding },
        { label: 'Valuation', value: startup.valuation }
      ].map((stat, i) => `
        <text x="${130 + i * 270}" y="405" text-anchor="middle"
              fill="${primary.hex}" font-size="22" font-weight="bold"
              font-family="Inter, sans-serif">${stat.value}</text>
        <text x="${130 + i * 270}" y="430" text-anchor="middle"
              fill="${lightMid.hex}88" font-size="12"
              font-family="Inter, sans-serif">${stat.label}</text>
      `).join('')}

      <!-- Team avatars -->
      ${startup.team.map((member, i) => `
        <circle cx="${130 + i * 90}" cy="510" r="25" fill="${primary.hex}33" />
        <text x="${130 + i * 90}" y="516" text-anchor="middle"
              fill="${primary.hex}" font-size="14" font-weight="bold"
              font-family="Inter, sans-serif">
          ${member.firstName.charAt(0)}${member.lastName.charAt(0)}
        </text>
        <text x="${130 + i * 90}" y="555" text-anchor="middle"
              fill="${lightMid.hex}88" font-size="10"
              font-family="Inter, sans-serif">${member.role.split(' ')[0]}</text>
      `).join('')}

      <!-- Palette strip -->
      ${startup.palette.map((color, i) => `
        <rect x="${900 + i * 50}" y="490" width="40" height="40" rx="6"
              fill="${color.css}" />
      `).join('')}

      <!-- Footer -->
      <text x="60" y="610" fill="${lightMid.hex}44" font-size="12"
            font-family="Inter, sans-serif">
        Generated by Fake Startup Generator | Not a real company
      </text>
    </svg>
  `;

  return svg;
}

async function downloadSocialCard(startup) {
  const svg = generateSocialCard(startup);

  // Create a canvas and draw the SVG
  const canvas = document.createElement('canvas');
  canvas.width = 1200;
  canvas.height = 630;
  const ctx = canvas.getContext('2d');

  const img = new Image();
  const svgBlob = new Blob([svg], { type: 'image/svg+xml' });
  const url = URL.createObjectURL(svgBlob);

  return new Promise((resolve, reject) => {
    img.onload = () => {
      ctx.drawImage(img, 0, 0);
      URL.revokeObjectURL(url);

      canvas.toBlob(blob => {
        const downloadUrl = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = downloadUrl;
        link.download = `${startup.name.toLowerCase().replace(/\s+/g, '-')}-card.png`;
        link.click();
        URL.revokeObjectURL(downloadUrl);
        resolve();
      }, 'image/png');
    };
    img.onerror = reject;
    img.src = url;
  });
}

10.2 Building a Startup Gallery

Over time, users will generate dozens of startups. We store them in a gallery that allows browsing, sorting, filtering, and comparison. The gallery persists in localStorage and can be exported as a JSON file for backup or sharing. Each gallery entry includes the full startup profile, making it possible to recreate the card view for any previously generated startup.

function renderGallery(gallery) {
  if (gallery.length === 0) {
    return '<p>No startups generated yet. Click Generate to create your first one.</p>';
  }

  // Sort options
  const sortOptions = {
    newest: (a, b) => new Date(b.timestamp) - new Date(a.timestamp),
    valuation: (a, b) => parseValuation(b.valuation) - parseValuation(a.valuation),
    name: (a, b) => a.name.localeCompare(b.name),
    team: (a, b) => b.employees - a.employees
  };

  const sorted = [...gallery].sort(sortOptions.newest);

  return `
    <div class="gallery-header">
      <h3>Generated Startups (${gallery.length})</h3>
      <div class="gallery-controls">
        <select onchange="sortGallery(this.value)">
          <option value="newest">Newest First</option>
          <option value="valuation">Highest Valuation</option>
          <option value="name">Alphabetical</option>
          <option value="team">Largest Team</option>
        </select>
        <button onclick="exportGallery()">Export JSON</button>
      </div>
    </div>
    <div class="gallery-grid">
      ${sorted.map(startup => {
        const [dark, darkMid, primary] = startup.palette;
        return `
          <div class="gallery-card" onclick="viewStartup('${startup.id}')"
               style="border-top: 3px solid ${primary.css}">
            <div class="gallery-logo" style="background: ${primary.hex}22; color: ${primary.css}">
              ${startup.name.charAt(0)}
            </div>
            <h4>${startup.name}</h4>
            <span class="gallery-industry">${startup.industry}</span>
            <span class="gallery-valuation">${startup.valuation}</span>
            <span class="gallery-date">${new Date(startup.timestamp).toLocaleDateString()}</span>
          </div>
        `;
      }).join('')}
    </div>
  `;
}

function parseValuation(valStr) {
  const num = parseFloat(valStr.replace(/[^0-9.]/g, ''));
  if (valStr.includes('B')) return num * 1000;
  return num;
}

function exportGallery() {
  const data = JSON.stringify(state.gallery, null, 2);
  const blob = new Blob([data], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = 'fake-startups-gallery.json';
  link.click();
  URL.revokeObjectURL(url);
}

11. The Satire Behind the Code

Building the Fake Startup Generator is funny, but it also makes a serious point. The fact that an algorithm can produce startup profiles that are indistinguishable from real ones says something uncomfortable about the state of startup culture. When mission statements are interchangeable, when brand identities are algorithmically generated, and when founding team bios follow templates, the individuality that supposedly defines entrepreneurship starts to look performative.

This is not to say that all startups are hollow. Many are building genuinely transformative products with deeply original thinking. But the language they use to describe themselves has become so standardized that it no longer communicates anything meaningful. When every startup "leverages cutting-edge AI to disrupt legacy systems," the phrase becomes noise. Our generator makes this noise explicit by producing it on demand, stripping away the pretense that these phrases carry real meaning.

The demographic APIs add another layer to the commentary. In Silicon Valley, "diversity" is often reduced to headcounts and flag emojis. Our generator faithfully reproduces this reduction: each team member gets an age, a predicted gender, and a country flag, and somehow this is supposed to demonstrate that the startup values inclusivity. The superficiality is the point. Real diversity is about perspective, experience, and power, none of which can be captured by an API call to Nationalize.

Finally, the Colormind palette generator reveals how much of "brand identity" is aesthetic rather than substantive. A harmonious five-color palette applied consistently creates the appearance of a thoughtful brand, but the colors were generated by an algorithm in a fraction of a second. The design looks professional because good color harmony is mathematical, not creative. This does not diminish the work of real designers, but it does suggest that the visual veneer of professionalism is easier to manufacture than the substance behind it.

10. Conclusion

The Fake Startup Generator demonstrates that creative, generative applications can be built from small, simple APIs. CorporateBS generates text. Colormind generates colors. Agify, Genderize, and Nationalize add demographic texture. None of these APIs is impressive on its own. Combined, they produce an output that is simultaneously realistic, absurd, and thought-provoking.

The technical patterns are widely applicable. Parallel API orchestration with Promise.all, batch requests for efficiency, fallback mechanisms for unreliable APIs, dynamic CSS generation from API data, caching for rate-limited services, and prefetching for perceived performance are all patterns you will use in production applications. The satirical framing just makes the learning process more memorable.

The project also demonstrates an underappreciated aspect of API mashups: the whole can be qualitatively different from the parts. A color palette is just colors. A buzzword phrase is just words. A predicted nationality is just a statistic. But assembled into a startup profile, they create something that feels like a narrative, a story about a company that could plausibly exist. This emergent narrative quality is what separates a great mashup from a mere data aggregation. It is not about combining data; it is about combining meaning.

Press the button. Generate a startup. Laugh at the absurdity. Then ask yourself: could you tell the difference between this fake startup and a real one? If the answer is no, that might be the most important takeaway of all.