<?xml version="1.0" encoding="UTF-8"?>
<!--
  Ten URLs: the two static pages, and the eight routes the decision gate in
  docs/routing-plan.md is measuring.

  /bot is a third static page and is deliberately NOT listed here. It is the
  crawler disclosure that CreaseDealsBot's User-Agent points at — unlisted,
  reachable by anyone with the URL, and carrying its own noindex tag. Adding it
  to this file would undo that. It is also not disallowed in robots.txt, on
  purpose: see the comment in bot.html.

  THE EIGHT ROUTES ARE AN EXPERIMENT, NOT A FORMALITY. They are client-rendered
  by App.jsx — Google executes JavaScript and can index such routes, less
  reliably and more slowly than static HTML. Whether it actually does is the
  one open question the whole of Stage B is gated on. Four to six weeks after
  this ships, Search Console answers it: indexed means the search surface
  exists for a fraction of the work and Stage B never happens; not indexed
  means prerendering is demonstrably required rather than assumed.

  Why these eight and not all 136 routable combinations. They are the head,
  measured against the live feed of 2026-08-12 (768 deals, post-dedupe):
  apparel 317, nike 116, new-balance 103, warrior 95, heads 93, pads 92,
  sticks 89, cleats 83. Every one sits an order of magnitude clear of any
  sensible threshold, so this file is effectively static and nothing here
  oscillates in and out. The long tail is where the complexity lives and it
  holds the least value.

  Do not add the tail to make the list look thorough. A sitemap of near-empty
  near-identical pages is how a site gets demoted for thin content, and it
  would also wreck the measurement above — a route failing to index because it
  is thin looks identical to one failing because Google could not render it,
  and those have opposite answers.

  Every URL here is also reachable by a real <a href> on the site: categories
  from the chip row in App.jsx, brands from the brand line on each deal card.
  That is deliberate and load-bearing. A sitemap-only URL is an orphan, and an
  orphan that goes unindexed tells you nothing about whether rendering worked.

  Retailer, sort, price and search stay out permanently. They are reorderings
  and continuous ranges — routing them builds a crawl trap of near-identical
  pages, which is the failure this list is shaped to avoid.

  No <lastmod>. The deals on / change daily, and a hand-maintained date in a
  static file would be wrong within a day of being written — Google discounts
  lastmod it finds unreliable, so a stale one is worse than none. If this ever
  needs to be accurate, generate the file at build time or serve it from a
  function the way /feed.json is served. <changefreq> and <priority> are
  omitted for a simpler reason: Google ignores both.
-->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://creasedeals.com/</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/about</loc>
  </url>

  <!-- Categories, largest first. -->
  <url>
    <loc>https://creasedeals.com/apparel</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/heads</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/pads</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/sticks</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/cleats</loc>
  </url>

  <!-- Brands. Slugs are pinned in web/src/routes.js and must never be edited
       to match a changed display name — that is the whole reason they are
       stored rather than derived. If one has to change, keep a redirect. -->
  <url>
    <loc>https://creasedeals.com/brand/nike</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/brand/new-balance</loc>
  </url>
  <url>
    <loc>https://creasedeals.com/brand/warrior</loc>
  </url>
</urlset>
