Compare Small AI Model Prices

Build an interactive page to compare AI model costs by prompt and output size.

Introduction

30 Second Summary

An AI model that looks affordable for a short prompt can become the expensive option when the prompt grows. That switch is easy to miss when each price lives in a separate table.

In this project, you will build a browser-based calculator that compares the cost of one call across GPT-6 Luna, Muse Ember 1.0, and Claude Haiku 5.5. You will use a stepped chart to explain every change in the cheapest-model result.

What You'll Build

You will drag prompt-size and output-size sliders to watch the cheapest-model label change while three stepped lines show exactly where the ranking flips.

By the end of this project, you'll have:

  • A live cost comparison that responds to two sliders, so you can test prompt sizes and output sizes without doing the arithmetic yourself.
  • An interactive price chart that traces costs from 1k to 1M input tokens with visible jumps at 100k, 200k, and 272k.
  • An editable JSON price table that redraws the calculator after you change a published rate.
  • Secret Mission: An optional challenge to push your skills further.

Are there any prerequisites?

You need basic HTML plus JavaScript experience. You also need a browser with access to any free static host.

No model is called, so the project has no API cost.

Before We Start

Your price comparison needs one predictable home on your computer. A clear workspace keeps the pricing data separate from unrelated files.

In this step, you'll confirm that a modern browser is ready. You'll also open an empty local folder in your code editor.

In this step, get ready to:
  • Confirm your browser is ready to test the page.
  • Create an empty project folder on your Desktop.
  • Open the project folder in your code editor.
Check your browser

The finished calculator runs as a web page. Your browser provides the feedback loop for every change you make later.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type the name of the browser you normally use.
  • Press Enter to open it.

First check complete. You'll see a browser window with a tab ready for the page you build later.

Create your project folder

A dedicated folder keeps the page files together. Storing it on your Desktop also gives every later instruction a clear starting point.

macOS

  • Press Cmd+Space to open Spotlight Search.
  • Type Finder.
  • Press Enter to open Finder.
  • Click Desktop in the left sidebar.
  • Click File in the top menu bar.
  • Click New Folder.
  • Type ai-model-price-comparison as the folder name.
  • Press Enter to create the folder.

Windows

  • Press the Windows key to open system search.
  • Type File Explorer.
  • Press Enter to open File Explorer.
  • Click Desktop in the left sidebar.
  • Click New in the toolbar.
  • Click Folder.
  • Type ai-model-price-comparison as the folder name.
  • Press Enter to create the folder.

Good, your project now has a clear home. You'll see an empty folder named ai-model-price-comparison on your Desktop.

Open the folder in your editor

Opening the whole folder gives your code editor one workspace for the project. Its file sidebar becomes the map for everything you add later.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type the name of your code editor.
  • Press Enter to open the editor.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select Desktop in the folder picker.
  • Select the ai-model-price-comparison folder.
  • Click Open on macOS or Select Folder on Windows.

Before you check, predict what the editor sidebar will show for a brand-new empty folder.

  • Check the file sidebar in your code editor.

You'll see ai-model-price-comparison as the open folder. You won't see any project files beneath it yet.

Don't see the empty folder?

  • Confirm that the folder picker is showing your Desktop.
  • Use the editor's File menu again if the welcome screen remains visible.
  • Confirm that you selected ai-model-price-comparison instead of the Desktop itself.

Still stuck? Help me open my empty project folder in my code editor.

Your browser is ready. Your empty workspace is open too. Next up, you'll bring the published model rates into this project.

Create the Model Price Table

A price comparison is only as reliable as the numbers behind it. Cached-input prices can look like standard prices at a glance, which would make every later calculation misleading.

You will verify the published standard rates before storing them in a JSON file. Opening that file in your browser proves the structure is valid before the calculator starts using it.

In this step, get ready to:
  • Confirm the standard input and output rates on the vendor pricing pages.
  • Store the short-prompt rates, long-prompt rates, and thresholds in prices.json.
  • Open the completed price table in your browser and check every record.
Check the published rates

Each vendor separates standard token rates from discounted cache rates. This project uses standard input and output pricing because the calculator estimates a fresh request.

Which Rates Should You Confirm?

  • All three models list short-prompt rates of $0.10 per million input tokens and $0.50 per million output tokens.
  • GPT-6 Luna lists long-prompt rates of $0.20 per million input tokens and $0.75 per million output tokens.
  • Muse Ember 1.0 lists long-prompt rates of $0.30 per million input tokens and $1.50 per million output tokens.
  • Claude Haiku 5.5 lists long-prompt rates of $0.50 per million input tokens and $2.50 per million output tokens.

Why Exclude Cached Input?

Cached input has its own discounted price column. Mixing that value with standard input pricing would make the calculator compare different request types.

Build the price table

Each model needs the same six fields so later code can compare records without special cases. The threshold identifies the input size where that record switches to its long-prompt rates.

  • Use the new-file control in the code editor from earlier to create prices.json inside the project folder already open.
  • Fill prices.json with the complete price table by copying this code:
[
  {
    "name": "GPT-6 Luna",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.20,
    "longPromptOutputRate": 0.75,
    "inputTokenThreshold": 100000
  },
  {
    "name": "Muse Ember 1.0",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.30,
    "longPromptOutputRate": 1.50,
    "inputTokenThreshold": 200000
  },
  {
    "name": "Claude Haiku 5.5",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.50,
    "longPromptOutputRate": 2.50,
    "inputTokenThreshold": 272000
  }
]

What Does This Data Capture?

  • Each object represents one model using the same field structure. A consistent shape lets the calculator process every model with one cost function.
  • The four rate fields store prices in US dollars per million tokens. Input and output stay separate because their published prices differ.
  • The inputTokenThreshold field stores the breakpoint for that model. Later code uses it to choose the short-prompt or long-prompt rates.
  • Save prices.json.
  • Confirm prices.json is listed in the code editor's file sidebar.

Seeing a JSON Warning?

  • Check that every property except the last property in each record ends with a comma.
  • Check that every property name and model name uses straight double quotation marks.
  • Check that the file begins with [ and ends with ].

Still stuck? Help me find the JSON syntax issue in my prices.json file.

✔️ My file matches

Great. Keep prices.json saved in your project folder.

ⓧ I'd like to double check the full code

Compare your entire prices.json file with this reference.

[
  {
    "name": "GPT-6 Luna",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.20,
    "longPromptOutputRate": 0.75,
    "inputTokenThreshold": 100000
  },
  {
    "name": "Muse Ember 1.0",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.30,
    "longPromptOutputRate": 1.50,
    "inputTokenThreshold": 200000
  },
  {
    "name": "Claude Haiku 5.5",
    "shortPromptInputRate": 0.10,
    "shortPromptOutputRate": 0.50,
    "longPromptInputRate": 0.50,
    "longPromptOutputRate": 2.50,
    "inputTokenThreshold": 272000
  }
]
Open and verify the JSON

Before you open the file, predict whether one missing comma would let the browser parse all three records.

  • Switch back to the browser from earlier.
  • Press Cmd+O on macOS or Ctrl+O on Windows to open the browser's file picker.
  • Select prices.json from your local project folder.
  • Confirm the selection in the file picker.

You should see three complete model records as plain text or a formatted JSON tree. The page should load without a parsing error.

  • Check the GPT-6 Luna record against 0.10, 0.50, 0.20, 0.75, and 100000.
  • Check the Muse Ember 1.0 record against 0.10, 0.50, 0.30, 1.50, and 200000.
  • Check the Claude Haiku 5.5 record against 0.10, 0.50, 0.50, 2.50, and 272000.

That source check is complete. Your browser can read all three pricing records, so the calculator now has a dependable data layer.

Browser Won't Display the File?

  • Confirm you selected the saved prices.json file from the project folder.
  • Return to the code editor if the browser mentions invalid JSON. Compare the punctuation with the full-file reference above.
  • Save the corrected file before opening it in the browser again.

Need another pair of eyes? Help me work out why my browser cannot parse prices.json.

Your model prices are saved and readable. Next, you will turn these records into a cost function that chooses the correct rate at each threshold.

Calculate Call Costs

Your checked price table gives you trustworthy rates. A list of per-million prices still leaves the cost of a real call hidden.

In this step, you'll use JavaScript to select each model's rate tier from its input-token threshold. You'll turn the result into a four-row comparison table for one fixed output size.

In this step, get ready to:
  • Define calculateCost(model, inputTokens, outputTokens) for threshold-aware totals.
  • Build the comparison table shell in index.html.
  • Render four prompt sizes in a browser comparison table.
Define the cost calculation

A call combines input cost with output cost. The prompt size decides whether the function uses the short-prompt pair or the long-prompt pair from prices.json.

  • Create app.js inside the project folder from earlier by copying this code into a new file:
// Compare the same prompt sizes across every model
const INPUT_TOKEN_SIZES = [50_000, 150_000, 250_000, 500_000]
const OUTPUT_TOKENS = 10_000

function calculateCost(model, inputTokens, outputTokens) {
  // Crossing the input threshold selects the model's long-prompt tier
  const useLongRates = inputTokens > model.inputTokenThreshold
  const inputRate = useLongRates ? model.longInputRate : model.shortInputRate
  const outputRate = useLongRates ? model.longOutputRate : model.shortOutputRate

  // Published rates are prices per one million tokens
  const inputCost = (inputTokens / 1_000_000) * inputRate
  const outputCost = (outputTokens / 1_000_000) * outputRate

  return { inputCost, outputCost, totalCost: inputCost + outputCost }
}

What does this code do?

  • The INPUT_TOKEN_SIZES array stores the four prompt sizes that appear in the comparison.
  • The OUTPUT_TOKENS constant keeps every comparison at 10,000 output tokens.
  • The useLongRates check becomes true only when the input size is above model.inputTokenThreshold.
  • The return value keeps the input cost, output cost, and combined total available for later features.
  • Save app.js.
  • Confirm the editor sidebar lists app.js beside prices.json.

Can't find app.js?

  • Check that app.js is inside the same project folder as prices.json.
  • Compare the filename's capitalization with app.js.

Still stuck? Help me check where app.js should be saved in my browser pricing project.

Build the comparison table

The HTML page provides one row for each test prompt. Its model columns give the rendering code a fixed place for every total.

  • Create index.html inside the same project folder by copying this code into a new file:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI Model Call Cost Comparison</title>
</head>
<body>
  <!-- Introduce the comparison and its fixed output size -->
  <main>
    <h1>AI Model Call Cost Comparison</h1>
    <p>Output tokens per call: <strong>10,000</strong></p>

    <!-- JavaScript replaces the loading row with calculated costs -->
    <table>
      <thead>
        <tr>
          <th scope="col">Input tokens</th>
          <th scope="col">GPT-6 Luna</th>
          <th scope="col">Muse Ember 1.0</th>
          <th scope="col">Claude Haiku 5.5</th>
        </tr>
      </thead>
      <tbody id="cost-table-body">
        <tr><td colspan="4">Loading costs...</td></tr>
      </tbody>
    </table>
  </main>
  <script src="app.js" defer></script>
</body>
</html>

What does this code do?

  • The table header fixes the model order used by the records in prices.json.
  • The cost-table-body element gives app.js a target for the calculated rows.
  • The defer attribute waits for the page structure to load before running app.js.
  • Save index.html.
  • Start your code editor's local web preview for index.html.

Good progress: your browser now shows the comparison heading, the 10,000-token output size, and a table row that says Loading costs....

Don't see the table shell?

  • Confirm the preview is displaying index.html from the same folder as app.js.
  • Check that the closing </table> tag appears before the script tag.
  • Use a local web preview instead of a file: address so the next chunk can load prices.json.

Need a second pair of eyes? Help me debug why my index.html comparison table is not visible.

Render the four cost comparisons

The page now has a destination for the results. The final function loads the checked price records before calculating each model's total.

  • Append the rendering logic to app.js by copying this code below calculateCost():
async function renderCosts() {
  // Load the checked pricing records from the local JSON file
  const response = await fetch("./prices.json")
  const priceData = await response.json()
  const models = Array.isArray(priceData) ? priceData : priceData.models
  const tableBody = document.querySelector("#cost-table-body")

  // Replace the loading row with one calculated row per prompt size
  tableBody.innerHTML = ""

  INPUT_TOKEN_SIZES.forEach((inputTokens) => {
    const row = document.createElement("tr")
    const modelCosts = models.map((model) => {
      const { totalCost } = calculateCost(model, inputTokens, OUTPUT_TOKENS)
      return `<td>$${totalCost.toFixed(6)}</td>`
    }).join("")

    row.innerHTML = `<th scope="row">${inputTokens.toLocaleString()}</th>${modelCosts}`
    tableBody.append(row)
  })
}

// Show loading problems inside the table instead of hiding them
renderCosts().catch((error) => {
  document.querySelector("#cost-table-body").innerHTML = `<tr><td colspan="4">Could not load prices: ${error.message}</td></tr>`
})

What does this code do?

  • The fetch() call loads prices.json from the same local preview.
  • The Array.isArray() check supports a direct array or an object containing a models array.
  • Each input size passes through calculateCost() with the same output size.
  • The error handler places loading problems inside the table where you can see them.
  • Save app.js.

Before you refresh, pause to predict which rows will mix short-prompt rates with long-prompt rates.

  • Refresh the browser preview.

You'll see one row each for 50,000, 150,000, 250,000, and 500,000 input tokens. Every model cell shows the total cost of a call with 10,000 output tokens.

What should the table prove?

  • At 50k input tokens, all three models use their short-prompt rates.
  • At 150k input tokens, GPT-6 Luna uses its long-prompt rates. Muse Ember 1.0 uses its short-prompt rates. Claude Haiku 5.5 uses its short-prompt rates.
  • At 250k input tokens, GPT-6 Luna uses its long-prompt rates. Muse Ember 1.0 uses its long-prompt rates. Claude Haiku 5.5 uses its short-prompt rates.
  • At 500k input tokens, all three models use their long-prompt rates.

For the 150k row, divide each token count by one million before multiplying it by the selected rate. Add the input cost to the output cost. The result should match the corresponding table cell.

You have the key behavior working: one table now exposes how different thresholds change the cost ranking.

Still seeing the loading row?

  • Confirm the browser preview address starts with http instead of file:.
  • Check that prices.json sits beside index.html.
  • Compare the rate field names in prices.json with shortInputRate, shortOutputRate, longInputRate, and longOutputRate.

Still blocked? Help me debug why my pricing table cannot load or calculate the model costs.

✔️ Awesome, I've got everything!

Your comparison table is rendering. Save app.js and index.html before continuing.

ⓧ I'd like to double check the full code

Compare your two files with these complete versions. Keep the checked prices.json file unchanged.

// Compare the same prompt sizes across every model
const INPUT_TOKEN_SIZES = [50_000, 150_000, 250_000, 500_000]
const OUTPUT_TOKENS = 10_000

function calculateCost(model, inputTokens, outputTokens) {
  // Crossing the input threshold selects the model's long-prompt tier
  const useLongRates = inputTokens > model.inputTokenThreshold
  const inputRate = useLongRates ? model.longInputRate : model.shortInputRate
  const outputRate = useLongRates ? model.longOutputRate : model.shortOutputRate

  // Published rates are prices per one million tokens
  const inputCost = (inputTokens / 1_000_000) * inputRate
  const outputCost = (outputTokens / 1_000_000) * outputRate

  return { inputCost, outputCost, totalCost: inputCost + outputCost }
}

async function renderCosts() {
  // Load the checked pricing records from the local JSON file
  const response = await fetch("./prices.json")
  const priceData = await response.json()
  const models = Array.isArray(priceData) ? priceData : priceData.models
  const tableBody = document.querySelector("#cost-table-body")

  // Replace the loading row with one calculated row per prompt size
  tableBody.innerHTML = ""

  INPUT_TOKEN_SIZES.forEach((inputTokens) => {
    const row = document.createElement("tr")
    const modelCosts = models.map((model) => {
      const { totalCost } = calculateCost(model, inputTokens, OUTPUT_TOKENS)
      return `<td>$${totalCost.toFixed(6)}</td>`
    }).join("")

    row.innerHTML = `<th scope="row">${inputTokens.toLocaleString()}</th>${modelCosts}`
    tableBody.append(row)
  })
}

// Show loading problems inside the table instead of hiding them
renderCosts().catch((error) => {
  document.querySelector("#cost-table-body").innerHTML = `<tr><td colspan="4">Could not load prices: ${error.message}</td></tr>`
})
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI Model Call Cost Comparison</title>
</head>
<body>
  <!-- Introduce the comparison and its fixed output size -->
  <main>
    <h1>AI Model Call Cost Comparison</h1>
    <p>Output tokens per call: <strong>10,000</strong></p>

    <!-- JavaScript replaces the loading row with calculated costs -->
    <table>
      <thead>
        <tr>
          <th scope="col">Input tokens</th>
          <th scope="col">GPT-6 Luna</th>
          <th scope="col">Muse Ember 1.0</th>
          <th scope="col">Claude Haiku 5.5</th>
        </tr>
      </thead>
      <tbody id="cost-table-body">
        <tr><td colspan="4">Loading costs...</td></tr>
      </tbody>
    </table>
  </main>
  <script src="app.js" defer></script>
</body>
</html>

Your page now converts published rates into call-level costs across four prompt sizes. Next up, you'll turn those rows into a chart that makes every price jump visible.

Chart Costs by Prompt Size

Your comparison table proves the cost calculation at four prompt sizes. A four-row snapshot still hides where each model's price changes across the full range.

This step turns the existing JSON pricing data into a line chart covering 1000 through 1000000 input tokens. A fixed output-token size isolates the effect of each input threshold.

In this step, get ready to:
  • Add a chart container beneath the comparison table.
  • Build full-range cost lines from the existing pricing data.
  • Verify the three threshold jumps on the completed chart.
Add the chart area

The page needs a dedicated chart region beneath the table. This gives app.js a stable place to draw the full cost range.

  • Use your code editor's file sidebar to select index.html.
  • Find the comparison table area already on the page.
  • Add a chart container with a visible heading immediately below the table area.
  • Save index.html.
  • Refresh the browser preview to confirm the chart region sits beneath the comparison table.

Good progress. Your comparison page now has a visible home for the full cost chart.

Chart area missing?

  • Confirm the chart container is inside the page body in index.html.
  • Check that the chart container appears after the comparison table's closing markup.
  • Save index.html before refreshing the browser again.

Still stuck? Help me find why the chart area I added to index.html is missing from my browser preview.

Build the full-range cost lines

JavaScript can reuse calculateCost() for every point on the chart. Threshold-adjacent points preserve each sudden price jump instead of smoothing it away.

How the chart preserves jumps

  • The input range begins at 1000 input tokens.
  • The input range ends at 1000000 input tokens.
  • Each model series calls calculateCost() for every plotted point.
  • Each series includes a point at model.inputTokenThreshold.
  • Each series includes another point one token above model.inputTokenThreshold.
  • The horizontal axis represents input-token size.
  • The vertical axis represents the total call cost returned by calculateCost().
  • The output-token size stays fixed while the input-token size changes.
  • Switch to app.js in your code editor.
  • Find the code that finishes rendering the four-prompt comparison table.
  • Add the complete chart-building section below that code using the chart requirements above.
  • Give every model its own labelled cost line inside the chart container.
  • Save app.js.
  • Refresh the browser preview to render the completed chart.

You should see three labelled cost lines spanning the full input-token range. The lines overlap before their individual thresholds because the short-prompt rates are equal.

Chart empty or missing steps?

  • Confirm the chart logic selects the container you added to index.html.
  • Confirm every plotted cost comes from the existing calculateCost() function.
  • Check that each series includes a point at model.inputTokenThreshold.
  • Check that each series includes another point one token above the threshold.

Need another pair of eyes? Help me debug why my JavaScript cost chart is empty or does not show the threshold jumps.

Inspect the pricing steps

Before you inspect the finished chart, which model line do you expect to jump first as prompt size rises?

  • Refresh the browser page.
  • Follow every model line from left to right.
  • Compare each visible jump with its model's input-token threshold.

You should see GPT-6 Luna jump at 100000 input tokens.

You should see Muse Ember 1.0 jump at 200000 input tokens.

You should see Claude Haiku 5.5 jump at 272000 input tokens.

That is the complete pricing pattern. Your page now exposes every point where the model ranking can change.

✔️ Awesome, I've got everything!

Your comparison table remains available above the chart. The chart now shows three separate cost lines across the complete prompt-size range.

ⓧ I'd like to double check the full code

Your project should now match these checkpoints.

  • The index.html file still loads app.js.
  • The index.html file contains the comparison table area.
  • The index.html file contains a chart container beneath the table area.
  • The app.js file still loads prices.json.
  • The app.js file still defines calculateCost(model, inputTokens, outputTokens).
  • The comparison table still covers 50000, 150000, 250000, and 500000 input tokens.
  • The chart data begins at 1000 input tokens.
  • The chart data ends at 1000000 input tokens.
  • The chart renders a separate cost line for GPT-6 Luna.
  • The chart renders a separate cost line for Muse Ember 1.0.
  • The chart renders a separate cost line for Claude Haiku 5.5.
  • The three visible jumps occur at 100000, 200000, and 272000 input tokens.
  • The prices.json file keeps the three published pricing records unchanged.

Your cost curve now reveals the three places where pricing changes. Next up, you will make the comparison interactive with prompt-size controls.

Add Interactive Cost Controls

Your chart already reveals the three pricing steps across the full prompt range. Now you will turn that overview into a cost calculator someone can control.

JavaScript range inputs report a value whenever someone drags a slider. Each movement needs to trigger the same pricing logic.

A single update path keeps the winner label in sync with the comparison table. It also redraws the chart for the selected output size.

In this step, get ready to:
  • Add range controls for both token sizes.
  • Find the lowest current cost for the winner label.
  • Refresh every cost view from one update path.
Add the range controls

An HTML range input gives someone a bounded way to choose a token count. A nearby value display makes the current selection clear before the costs refresh.

  • Switch back to index.html from earlier.
  • Add a prompt-size range control spanning 1000 through 1000000 input tokens with a visible current value.
  • Save index.html.
  • Refresh the browser preview.

You should see the prompt-size slider above the existing comparison table. Its displayed value should match the slider position.

  • Return to index.html in your code editor.
  • Add an output-size range control with a visible current value. Use the output-token value from the existing chart calculations as its initial value.
  • Save index.html.
  • Refresh the browser preview.

You should now see one slider for prompt size. You should also see one slider for output size.

  • Return to index.html in your code editor.
  • Add a cheapest-model label above the comparison table.
  • Save index.html.
  • Refresh the browser preview.

You should see the new label beside the two controls. The existing comparison table plus chart should remain visible.

Controls missing from the page?

  • Confirm that the new controls sit inside the visible page content in index.html.
  • Check the surrounding HTML for a missing closing tag.
  • Confirm that your browser is showing the same index.html file you just saved.

Still stuck? Help me find why my new range controls are missing from index.html.

Connect the pricing updates

The controls need one shared route into your existing pricing logic. The helper findCheapestModel(costs) selects the lowest result while updateUI() refreshes every visible result.

  • Switch back to app.js from earlier.
  • Implement the interaction layer with findCheapestModel(costs) plus updateUI(). Make the helper return the model with the lowest current call cost. Make the update function read both selected token counts before recalculating all three models.
  • Save app.js.
  • Refresh the browser preview.

You should see three recalculated costs for the sliders' initial values. The cheapest-model label should name the lowest result.

How does the update path work?

  • The prompt-size control supplies the current input-token count.
  • The output-size control supplies the current output-token count.
  • The existing calculateCost(model, inputTokens, outputTokens) function applies each model's threshold pricing.
  • The findCheapestModel(costs) helper selects the lowest calculated cost.
  • The updateUI() function refreshes the displayed values.
  • The same update function redraws or updates the chart for the selected output size.

Seeing blank or invalid costs?

  • Confirm that updateUI() reads numeric values from both range controls.
  • Check that every model still passes through calculateCost(model, inputTokens, outputTokens).
  • Confirm that findCheapestModel(costs) receives all three calculated results.

Need another pair of eyes? Help me debug blank costs in my interactive model calculator.

Verify both sliders

An event listener runs a function when a control changes. Both sliders need to call the same update path so the page cannot show mixed token values.

  • Return to app.js in your code editor.
  • Finish the update wiring so updateUI() runs after prices.json loads plus whenever either slider moves.
  • Save app.js.
  • Switch back to the browser preview.
  • Refresh the page.

Before moving the prompt-size slider, where do you expect the cheapest-model label to change as the selection crosses the three pricing thresholds?

  • Drag the prompt-size slider slowly through 100000, 200000, plus 272000 input tokens.

You should see the displayed prompt size plus all three call costs update immediately. The cheapest-model label changes when a pricing transition changes the ranking.

Before moving the output-size slider, which displayed results do you expect to respond to the new output-token count?

  • Drag the output-size slider across several values.

You should see the displayed output size plus the three table costs update. The chart lines should redraw or update for the selected output size.

That is the calculator working as one system. Every control now feeds the same pricing comparison.

Page not responding to a slider?

  • Confirm that the unresponsive slider has a movement listener that calls updateUI().
  • Check that the slider reference matches the corresponding control in index.html.
  • Confirm that the initial updateUI() call runs only after the pricing data is available.

Still not updating? Help me trace why one of my cost sliders does not refresh the page.

✔️ Awesome, I've got everything!

Great. Save index.html plus app.js. Keep the interactive preview available for the next step.

ⓧ I'd like to double check the interaction

Compare your current files against this checklist.

  • Confirm that index.html loads app.js.
  • Confirm that index.html contains prompt-size plus output-size range sliders with visible values.
  • Confirm that index.html contains a cheapest-model label.
  • Confirm that the comparison table plus chart container remain present.
  • Confirm that app.js still loads prices.json.
  • Confirm that app.js defines calculateCost(model, inputTokens, outputTokens), findCheapestModel(costs), plus updateUI().
  • Confirm that both sliders refresh the selected costs plus cheapest-model label.
  • Confirm that the chart still covers 1000 through 1000000 input tokens.
  • Confirm that the chart updates for the selected output size.
  • Leave the published pricing records in prices.json unchanged.

Your calculator now responds to real prompt plus output sizes. Next, you will mark the crossover points. Then you will publish the page as a shareable link.

Mark Crossovers and Publish

The interactive calculator already responds to both sliders. A local preview still hides the result from anyone outside your computer.

This final step marks every calculated crossover point alongside the three price jumps. You will then publish the completed page through static hosting so anyone with the link can test it.

In this step, get ready to:
  • Mark each calculated model-ranking crossover on the chart.
  • Show the three fixed price jumps across the chart.
  • Publish the completed calculator at a shareable URL.
Mark calculated ranking crossovers

A crossover happens when the model with the lowest call cost changes between two neighbouring prompt sizes. Calculating these points from the existing cost functions keeps the markers accurate when the output-size slider moves.

How does a crossover point work?

  • Each existing chart sample represents one input size from 1000 through 1000000.
  • At each sample, calculateCost(model, inputTokens, outputTokens) provides the three costs for the currently selected output size.
  • The existing findCheapestModel(costs) function identifies the lowest-cost model at that sample.
  • A change from the previous cheapest model identifies a ranking crossover. Its input size and winning cost become the chart marker coordinates.
  • Return to app.js in the code editor from earlier.
  • Locate the chart-generation logic inside updateUI().
  • Add the crossover-point calculation beside the chart-generation logic using the four rules above.
  • Save app.js.
  • Refresh the browser preview.

You should see a marker wherever the cheapest model changes for the selected output size. Good progress. The chart now explains where the ranking flips.

No crossover markers?

  • Confirm the crossover calculation uses the same input samples as the three chart lines.
  • Confirm every sample passes the selected output size into calculateCost(model, inputTokens, outputTokens).
  • Confirm the calculated marker coordinates are included in the chart configuration.

Still stuck? Help me debug why my calculated model-ranking crossover markers are missing from the chart.

Mark the three price jumps

A model-ranking crossover depends on the selected output size. A price-jump marker stays fixed at the input threshold where a model switches to its long-prompt rate.

  • Switch back to app.js.
  • Add vertical chart markers at 100000, 200000, and 272000 input tokens.
  • Save app.js.

Before the refresh, the question is where the three fixed price jumps should appear along the x-axis.

  • Refresh the browser preview.

You should see vertical markers at 100000, 200000, and 272000 input tokens. The calculated ranking markers should remain visible alongside them.

The final local check connects the chart annotations to the interactive result. A marker earns its place when it matches the ranking change shown above the table.

  • Return to the browser preview.
  • Drag the prompt-size slider slowly from its minimum to its maximum.

You should see the cheapest-model label change near the ranking crossovers shown on the chart.

  • Move the output-size slider to a different value.
  • Sweep the prompt-size slider across the chart again.

You should see the costs recalculate for the new output size. The chart should redraw its calculated crossover markers while preserving the three fixed price-jump markers.

Markers do not match the label?

  • Confirm the chart lines and crossover calculation use the same selected output-token value.
  • Confirm updateUI() recalculates the crossover points whenever either slider changes.
  • Confirm the fixed threshold markers use input-token values on the chart's x-axis.

Need another pair of eyes? Help me find why my chart markers and cheapest-model label disagree.

✔️ Awesome, I've got everything!

The finished local page recalculates costs across the full input range. Its chart shows the calculated ranking crossovers plus all three fixed price jumps.

ⓧ I'd like to double check the full code

The cumulative project checkpoint has the following state.

  • The app.js file loads prices.json.
  • The app.js file defines calculateCost(model, inputTokens, outputTokens).
  • The app.js file defines findCheapestModel(costs).
  • The app.js file defines updateUI().
  • The crossover-point logic detects changes in the cheapest model across the chart samples.
  • The three model lines cover input sizes from 1000 through 1000000.
  • The chart marks calculated ranking crossovers.
  • The chart marks price jumps at 100000, 200000, and 272000 input tokens.
  • Either slider causes the selected costs and chart to update.
  • The index.html file contains both sliders and their displayed values.
  • The index.html file contains the cheapest-model label and comparison table.
  • The index.html file contains the chart container and loads app.js.
  • The prices.json file contains valid pricing records for all three models.
Publish the shareable page

Static hosting serves your HTML and browser JavaScript directly from a public URL. The host must receive index.html, app.js, and prices.json from the same folder so the deployed page can load its pricing data.

The account sign-in is the only credentials moment in this step. Your project files contain no API keys or model credentials.

  • Choose a free static hosting service that accepts a local project folder.
  • Create an account if you do not already have one.
  • Sign in to the static hosting service.
  • Complete any account verification the service requests.

What gets published?

The publishing root is the folder the host treats as the top level of your website. Keeping all three project files there preserves the relative request from app.js to prices.json.

This page runs entirely in the browser. The deployment needs no model API call or server process.

  • Start a new static-site deployment from a local folder.
  • Select the local project folder from earlier.
  • Set the selected folder as the publishing root.
  • Confirm index.html, app.js, and prices.json are listed at that root.
  • Publish the site.

You should see a completed deployment with a public address for the page.

  • Copy the generated deployment address into your shareable deployment URL.

Before the final test, the question is whether the calculator will load outside your signed-in browser session with all three local files available.

  • Open a private browsing window from your browser menu.
  • Paste your shareable deployment URL into the address bar.
  • Press Enter.

You should see the complete calculator with both sliders and the comparison table. The chart should show all three cost lines plus the crossover and price-jump markers.

  • Move the prompt-size slider across one marked crossover.

You should see the cheapest-model label change near the marked ranking crossover.

  • Move the output-size slider to another value.

You should see the table recalculate. The chart should redraw for the selected output size.

That is the hard part done. Your calculator is published and responding through a shareable link.

Published page not working?

  • Confirm the deployment root contains index.html, app.js, and prices.json.
  • Confirm the deployed filenames use the same capitalization as the local files.
  • Confirm the deployment is publicly accessible if the page asks visitors to sign in.

Still blocked? Help me troubleshoot why my published static cost calculator is missing data or chart markers.

Your finished calculator turns changing price thresholds into a decision tool that anyone can test from a browser.

Secret mission

Run a Pricing Boundary Audit

Test both sides of the 100000, 200000, and 272000 input-token thresholds to catch boundary mistakes. Then vary output size near a crossover to see whether the cheapest-model recommendation holds.

Clean Up Your Resources

Clean Up Your Resources

This project costs $0 because it uses published prices without making model calls. Decide whether to keep the calculator available, pause your local work, or remove the project entirely.

Resources you used:

  • Local project files index.html, app.js, and prices.json in the project folder from earlier.
  • Published static site on the signed-in static hosting service from earlier.
  • Tested shareable URL that serves the interactive cost comparison page.

Keep everything running

No action needed. Choose this if you want to keep sharing the calculator or update its prices later.

  • Keep prices.json so you can update the published rates when they change.
  • Keep index.html and app.js so the sliders, comparison table, cheapest-model label, and chart remain available.
  • Leave the static site published so the tested shareable URL continues to serve the calculator.

Pause - I'll come back to this later

Close your local workspace while keeping every file and the published page available for later.

  • Close the browser tab that displays the calculator.
  • Close the project workspace in your code editor.
  • Leave the signed-in static hosting service configured so the shareable URL stays available.

Delete - I don't want to use this again

A full reset can feel risky. Your tested calculator remains available until you confirm the hosting deletion.

  • Return to the signed-in static hosting service from earlier.
  • Select the published site connected to your tested shareable URL.
  • Follow the hosting service's documented site-deletion flow.
  • Confirm the site deletion when the service asks.
  • Visit the tested shareable URL to verify that it no longer serves the calculator.

That clears the public copy. Your local project files remain available until you remove them.

  • Return to the project workspace in your code editor from earlier.
  • Select index.html, app.js, and prices.json in the file sidebar.
  • Delete the selected files through the editor's file menu.
  • Confirm the deletion if your editor asks.

You should no longer see index.html, app.js, or prices.json in the file sidebar. Your calculator files and published page are now removed.

Nice Work!

Nice Work!

You did it! You built a published cost comparison page for three small AI models. Moving either slider now reveals the cheapest option for the selected prompt size plus output size.

You've learned how to:

  • Converted the published rate table into a reusable JSON data source. Built a JavaScript cost engine that switches rates at each model's exact threshold.
  • Drew a line chart covering 1,000 to 1,000,000 input tokens. Marked the price jumps at 100,000, 200,000, and 272,000 tokens. Added the calculated crossover points where the model ranking changes.
  • Added live prompt-size and output-size sliders that refresh the full comparison instantly. Published the finished page through static hosting as a tested shareable URL.
  • Completed an optional Secret Mission to extend the project beyond the core build.

Ready to quiz yourself?