Create a marketing campaign from a product sketch of a Jet Backpack

This notebook contains a code example of using the Gemini API to analyze a a product sketch (in this case, a drawing of a Jet Backpack), create a marketing campaign for it, and output taglines in JSON format.

Setup

Install the Google GenAI SDK

Install the Google GenAI SDK from npm.

$ npm install @google/genai

Setup your API key

You can create your API key using Google AI Studio with a single click.

Remember to treat your API key like a password. Don’t accidentally save it in a notebook or source file you later commit to GitHub. In this notebook we will be storing the API key in a .env file. You can also set it as an environment variable or use a secret manager.

Here’s how to set it up in a .env file:

$ touch .env
$ echo "GEMINI_API_KEY=<YOUR_API_KEY>" >> .env
Tip

Another option is to set the API key as an environment variable. You can do this in your terminal with the following command:

$ export GEMINI_API_KEY="<YOUR_API_KEY>"

Load the API key

To load the API key from the .env file, we will use the dotenv package. This package loads environment variables from a .env file into process.env.

$ npm install dotenv

Then, we can load the API key in our code:

const dotenv = require("dotenv") as typeof import("dotenv");

dotenv.config({
  path: "../.env",
});

const GEMINI_API_KEY = process.env.GEMINI_API_KEY ?? "";
if (!GEMINI_API_KEY) {
  throw new Error("GEMINI_API_KEY is not set in the environment variables");
}
console.log("GEMINI_API_KEY is set in the environment variables");
GEMINI_API_KEY is set in the environment variables
Note

In our particular case the .env is is one directory up from the notebook, hence we need to use ../ to go up one directory. If the .env file is in the same directory as the notebook, you can omit it altogether.

│
├── .env
└── examples
    └── Market_a_Jet_Backpack.ipynb

Initialize SDK Client

With the new SDK, now you only need to initialize a client with you API key (or OAuth if using Vertex AI). The model is now set in each call.

const google = require("@google/genai") as typeof import("@google/genai");

const ai = new google.GoogleGenAI({ apiKey: GEMINI_API_KEY });

Select a model

Now select the model you want to use in this guide, either by selecting one in the list or writing it down. Keep in mind that some models, like the 2.5 ones are thinking models and thus take slightly more time to respond (cf. thinking notebook for more details and in particular learn how to switch the thiking off).

const tslab = require("tslab") as typeof import("tslab");

const MODEL_ID = "gemini-2.5-flash-preview-05-20";

Marketing Campaign

  • Product Name
  • Description
  • Feature List / Descriptions
  • H1
  • H2

Analyze Product Sketch

First you will download a sample image to be used:

const fs = require("fs") as typeof import("fs");
const path = require("path") as typeof import("path");

const downloadFile = async (url: string, filePath: string) => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Failed to download file: ${response.statusText}`);
  }
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  const buffer = await response.blob();
  const bufferData = Buffer.from(await buffer.arrayBuffer());
  fs.writeFileSync(filePath, bufferData);
};
const IMAGE_URL = "https://storage.googleapis.com/generativeai-downloads/images/jetpack.jpg";

const jetpackImagePath = path.join("../assets/examples", "jetpack.jpg");
await downloadFile(IMAGE_URL, jetpackImagePath);

You can view the sample image to understand the prompts you are going to work with:

tslab.display.jpeg(fs.readFileSync(jetpackImagePath));

Now define a prompt to analyze the sample image:

const analyzePrompt = `
  This image contains a sketch of a potential product along with some notes.
  Given the product sketch, describe the product as thoroughly
  as possible based on what you see in the image, making sure to note
  all of the product features.

  Return output in json format.
`;
  • Set the model to return JSON by setting responseMimeType="application/json".
  • Describe the schema for the response using a zod schema.
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const responseSchema = z.object({
  description: z.string().describe("A detailed description of the product based on the image"),
  features: z.array(z.string()).describe("A list of features of the product based on the image"),
});
const analyzeResponse = await ai.models.generateContent({
  model: MODEL_ID,
  contents: [
    analyzePrompt,
    google.createPartFromBase64(fs.readFileSync(jetpackImagePath).toString("base64"), "image/jpeg"),
  ],
  config: {
    responseMimeType: "application/json",
    responseJsonSchema: zodToJsonSchema(responseSchema) as Record<string, unknown>,
  },
});
const productInfo = JSON.parse(analyzeResponse.text ?? "{}") as z.infer<typeof responseSchema>;
console.log(JSON.stringify(productInfo, null, 2));
{
  "description": "The product is a \"Jetpack Backpack\" designed to appear like a normal backpack while offering personal flight capabilities. It is described as lightweight and operates on steam power, making it a green and clean energy solution.",
  "features": [
    "Padded strap support",
    "Fits an 18-inch laptop",
    "Lightweight design",
    "Looks like a normal backpack",
    "USB-C charging",
    "15-minute battery life",
    "Retractable boosters",
    "Steam-powered propulsion",
    "Green/clean energy source"
  ]
}
Note

Here the model is just following text instructions for how the output json should be formatted. The API also supports a strict JSON mode where you specify a schema, and the API uses “Controlled Generation” (aka “Constrained Decoding”) to ensure the model follows the schema, see the JSON mode quickstart for details.

Generate marketing ideas

Now using the image you can use Gemini API to generate marketing names ideas:

const namePrompt = `
  You are a marketing whiz and writer trying to come up
  with a name for the product shown in the image.
  Come up with ten varied, interesting possible names.
`;

const nameResponse = await ai.models.generateContent({
  model: MODEL_ID,
  contents: [
    namePrompt,
    google.createPartFromBase64(fs.readFileSync(jetpackImagePath).toString("base64"), "image/jpeg"),
  ],
  config: {
    responseMimeType: "application/json",
    responseJsonSchema: zodToJsonSchema(
      z.array(z.string()).describe("A list of ten possible names for the product")
    ) as Record<string, unknown>,
  },
});
const nameSuggestions = JSON.parse(nameResponse.text ?? "[]") as string[];
console.log(JSON.stringify(nameSuggestions, null, 2));
[
  "AeroPack",
  "VaporGlide",
  "SkyCommute",
  "NimbusCarry",
  "EcoBoost Bag",
  "AscendPack",
  "StratoLift",
  "JetStream Pack",
  "DiscreetFly",
  "ZenithPack"
]

Finally you can work on generating a page for your product campaign:

const productName = nameSuggestions[Math.floor(Math.random() * nameSuggestions.length)];

const websiteCopyPrompt = `
  You're a marketing whiz and expert copywriter. You're writing
  website copy for a product named {name}. Your first job is to come
  up with H1 H2 copy. These are brief, pithy sentences or phrases that
  are the first and second things the customer sees when they land on the
  splash page. Here are some examples:
  [{{
    "h1": "A feeling is canned",
    "h2": "drinks and powders to help you feel calm cool and collected\
    despite the stressful world around you"
  }},
  {{
    "h1": "Design. Publish. Done.",
    "h2": "Stop rebuilding your designs from scratch. In Framer, everything\
    you put on the canvas is ready to be published to the web."
  }}]

  Create the same json output for a product named "${productName}".
  "${productInfo.description}".
  Output ten different options as json in an array.
`;
const headingSchema = z
  .object({
    h1: z.string().describe("The main headline for the product"),
    h2: z.string().describe("The subheadline for the product"),
  })
  .describe("A headline and subheadline for the product");
const websiteCopyResponse = await ai.models.generateContent({
  model: MODEL_ID,
  contents: [
    websiteCopyPrompt,
    google.createPartFromBase64(fs.readFileSync(jetpackImagePath).toString("base64"), "image/jpeg"),
  ],
  config: {
    responseMimeType: "application/json",
    responseJsonSchema: zodToJsonSchema(
      z.array(headingSchema).describe("An array of ten different H1 and H2 copy options for the product")
    ) as Record<string, unknown>,
  },
});
const websiteCopy = JSON.parse(websiteCopyResponse.text ?? "[]") as z.infer<typeof headingSchema>[];
console.log(JSON.stringify(websiteCopy, null, 2));
[
  {
    "h1": "Your Commute, Redefined.",
    "h2": "Soar above traffic with SkyCommute, the discreet personal flight system that looks just like your everyday backpack."
  },
  {
    "h1": "Backpack by Day, Jetpack by Choice.",
    "h2": "SkyCommute discreetly integrates personal flight into your daily routine, powered by clean steam energy."
  },
  {
    "h1": "Green Flight Takes Off.",
    "h2": "Experience the future of eco-conscious travel with SkyCommute, your lightweight, steam-powered personal jetpack."
  },
  {
    "h1": "Elevate Your Every Day.",
    "h2": "Break free from the ground. SkyCommute offers effortless, clean, personal flight in a backpack you'll love."
  },
  {
    "h1": "The Backpack That Flies.",
    "h2": "SkyCommute hides cutting-edge personal flight technology inside a lightweight, ordinary-looking backpack."
  },
  {
    "h1": "Fly From Your Backpack.",
    "h2": "SkyCommute: Unobtrusive personal flight, powered by clean steam, ready when you are."
  },
  {
    "h1": "Skip the Traffic, Soar to Your Destination.",
    "h2": "With SkyCommute, your lightweight, steam-powered backpack is all you need to beat the daily grind."
  },
  {
    "h1": "Your Sky Awaits.",
    "h2": "Embrace personal flight with SkyCommute, the innovative and eco-friendly jetpack that fits right into your life."
  },
  {
    "h1": "Simple Commute, Soaring Heights.",
    "h2": "SkyCommute delivers powerful, clean, personal flight, disguised as an everyday lightweight backpack."
  },
  {
    "h1": "The Secret to Flying Green.",
    "h2": "SkyCommute is the revolutionary, steam-powered jetpack disguised as a normal backpack for clean, discreet personal travel."
  }
]
const hIndex = Math.floor(Math.random() * websiteCopy.length);
const { h1, h2 } = websiteCopy[hIndex];
const htmlPrompt = `
 Generate HTML and CSS for a splash page for a new product called ${productName}.
    Output only HTML and CSS and do not link to any external resources.
    Include the top level title: "${h1}" with the subtitle: "${h2}".

    Return the HTML directly, do not wrap it in triple-back-ticks (\`\`\`).
`;
const htmlResponse = await ai.models.generateContent({
  model: MODEL_ID,
  contents: [htmlPrompt],
});
console.log(htmlResponse.text);
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SkyCommute - Simple Commute, Soaring Heights</title>
    <style>
        body {
            margin: 0;
            font-family: Arial, sans-serif; /* A common sans-serif font */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh; /* Full viewport height */
            background: linear-gradient(to bottom right, #4CAF50, #2196F3); /* A pleasant green to blue gradient */
            color: white; /* White text for contrast */
            text-align: center;
            overflow: hidden; /* Prevent scrollbar if content slightly larger than viewport */
            box-sizing: border-box; /* Include padding and border in the element's total width and height */
        }

        .splash-container {
            padding: 30px;
            max-width: 900px; /* Increased max-width for better display of long titles */
            margin: 20px; /* Add some margin for smaller screens */
            background-color: rgba(0, 0, 0, 0.4); /* Slightly transparent dark background for content box */
            border-radius: 12px;
            box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); /* More pronounced shadow */
            backdrop-filter: blur(5px); /* Add a subtle blur effect behind the container */
            border: 1px solid rgba(255, 255, 255, 0.2); /* Light border for definition */
        }

        .product-name {
            font-size: 3.5em; /* Larger for product name */
            margin-bottom: 15px;
            font-weight: bold;
            letter-spacing: 3px;
            text-transform: uppercase;
            color: #FFD700; /* Gold color for the product name */
            text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5); /* Shadow for product name */
        }

        .main-title {
            font-size: 4em; /* Very large for main title */
            margin-top: 0;
            margin-bottom: 25px;
            line-height: 1.2;
            text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.4); /* Add stronger shadow for depth */
        }

        .subtitle {
            font-size: 1.6em; /* Good size for subtitle */
            line-height: 1.6;
            max-width: 700px;
            margin: 0 auto; /* Center the subtitle within its container */
            opacity: 0.95; /* Slightly less prominent than titles */
        }

        /* Responsive adjustments */
        @media (max-width: 800px) {
            .product-name {
                font-size: 2.8em;
            }
            .main-title {
                font-size: 3.2em;
            }
            .subtitle {
                font-size: 1.4em;
            }
            .splash-container {
                padding: 25px;
                margin: 15px;
            }
        }

        @media (max-width: 600px) {
            .product-name {
                font-size: 2.2em;
                letter-spacing: 2px;
            }
            .main-title {
                font-size: 2.5em;
                margin-bottom: 20px;
            }
            .subtitle {
                font-size: 1.2em;
                max-width: 90%; /* Allow subtitle to take more width */
            }
            .splash-container {
                padding: 20px;
                margin: 10px;
            }
        }

        @media (max-width: 400px) {
            .product-name {
                font-size: 1.8em;
                letter-spacing: 1px;
            }
            .main-title {
                font-size: 2em;
                line-height: 1.3;
            }
            .subtitle {
                font-size: 1em;
            }
            .splash-container {
                padding: 15px;
                margin: 5px;
            }
        }
    </style>
</head>
<body>
    <div class="splash-container">
        <h2 class="product-name">SkyCommute</h2>
        <h1 class="main-title">Simple Commute, Soaring Heights.</h1>
        <p class="subtitle">SkyCommute delivers powerful, clean, personal flight, disguised as an everyday lightweight backpack.</p>
    </div>
</body>
</html>
const containerId = `shadow-container-${Math.random().toString(36).slice(2)}`;

const containerScript = `
<div id="${containerId}"></div>
<script>
  const htmlContent = \`${htmlResponse.text.replace(/`/g, "\\`")}\`;

  const container = document.getElementById('${containerId}');
  if (container && !container.shadowRoot) {
    const shadow = container.attachShadow({ mode: 'open' });
    shadow.innerHTML = htmlContent;
  }
</script>
`;

tslab.display.html(containerScript);