<?xml version="1.0" encoding="UTF-8"?>
<!--
  robots.txt has advertised this file for a long time and it did not exist. The
  request was answered by the Amplify SPA fallback with index.html at HTTP 200
  and Content-Type text/html, so a crawler asking for XML got HTML and treated
  the sitemap as invalid. That is worse than a 404, which is at least honest.

  A real file under public/ is emitted to dist/ and served directly, and the SPA
  fallback only applies to paths with no matching file, so shipping this fixes it
  with no console change.

  ONLY GENUINELY PUBLIC, INDEXABLE ROUTES BELONG HERE. Listing a URL that
  robots.txt disallows, or that is served noindex, is a self-contradicting signal
  and devalues the whole file. So this omits every Disallow-ed route
  (/results, /upload, /post, /quiz, /dashboard, /projects, /settings, /vote/),
  omits /go because it is served X-Robots-Tag: noindex, and omits the account and
  flow routes (/login, /reset-password, /verify-email, /unsubscribe, /team,
  /admin/*) which are functional rather than content.

  <lastmod> ONLY ON THE PRERENDERED CONTENT PAGES, and only because something
  now knows the answer: each one equals that page's dateModified (or
  datePublished) in tests/guidePages.mjs, and guides-prerender.test.mjs fails if
  they disagree. A fabricated or blanket-updated date is a signal Google learns
  to distrust, so the homepage and /business still carry none: nothing tracks
  their real change dates.

  Add a route here when it becomes public content, and keep it consistent with
  frontend/public/robots.txt. The two files disagreeing is the failure mode.
-->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://mytopit.com/</loc>
  </url>
  <!--
    /contact, /terms AND /privacy WERE LISTED HERE AND WERE REMOVED ON
    2026-09-15. They are still public, still linked from the footer, and still
    perfectly reachable. They are not listed because of what they SERVE.

    All three are React routes, so Amplify's SPA catch-all answers them with
    index.html: the homepage's <title>, the homepage's description, and
    og:url pointing at the homepage. Home.tsx rewrites the head at runtime, but
    a crawler that does not execute JavaScript never sees that, which is the
    same reason index.html carries no canonical.

    So listing them submitted three URLs that a crawler reads as three more
    copies of the homepage. That is the self-contradicting signal the note above
    describes for Disallow-ed routes, arriving by a different route: not a URL
    that says do not index me, but a URL whose content is not the content the
    sitemap is claiming.

    THEY COME BACK THE DAY THEY GET A PER-ROUTE HEAD IN THE DELIVERED BYTES,
    which means prerendering, which is a separate decision with its own Console
    cost. Re-add them in the same change that ships it, and not before: a
    listing is a claim about what the URL serves, and nothing here changes what
    these three serve.
  -->
  <!--
    /vote/<token> IS DELIBERATELY STILL ABSENT and must stay absent. dist/vote.html
    (scripts/voteHead.mjs) gives it a real head for link-preview bots, but the
    page renders another person's photographs, robots.txt disallows the prefix,
    and the generated head says noindex. Serving a correct preview and listing a
    URL for indexing are different things; this file only does the second.
  -->

  <!--
    /business ("Topit for Marketplaces", the B2B landing page) IS LISTED. It is
    public, robots.txt does not disallow it, RouteRobots does not noindex it, and
    it has real content. It is a React route, so it follows the same two-halves
    rule as the guides: scripts/businessHead.mjs emits dist/business.html with
    its own title, description, canonical and JSON-LD, and that file is only
    served once an Amplify Console rewrite exists, placed ABOVE the catch-all:

      /business  ->  /business.html  200 (Rewrite)

    Until that rule is added, /business is served the SPA shell with the
    homepage's head. The listing is still a valid URL either way, because the SPA
    renders the page for a person at HTTP 200; the rule is what makes the
    DELIVERED head match the claim. Add the rule after the deploy, never before
    (docs/marketplaces/business-seo.md). If the rule is ever removed, this entry
    goes back to being a URL whose bytes are the homepage's: remove it with the rule.

    /business/contact, /business/terms, /business/privacy AND /business/dpa ARE
    NOT LISTED, for the reason /contact, /terms and /privacy were de-listed above.
    They are public and indexable, but each is served the SPA shell with the
    homepage's head, and the three legal pages are chromeless documents rather
    than content a search should land on. List one in the same change that gives
    it a per-route head. (The legal text is separately served as /business/*.txt.)
  -->
  <url>
    <loc>https://mytopit.com/business</loc>
  </url>

  <!--
    THE URLs BELOW ARE PRERENDERED STATIC FILES (frontend/public/guides.html,
    ai-photo-selector.html, guides/*.html, compare/*.html), not React routes.
    Each needs TWO independent halves to be reachable, and only one of them
    lives in this repo.

    HALF ONE, in the repo: the .html file, shipped to dist/ by the build.
    HALF TWO, NOT in the repo: one Amplify Console rewrite row per page, placed
    ABOVE the SPA catch-all, source /<path> -> target /<path>.html, type 200.

    THERE IS NO WILDCARD. The 2026-09-15 decision record deferred a /guides/<*>
    row to wave 4 as a test; on 2026-10-15 /guides/<anything-without-a-row>
    still returned the homepage, so every page needs its own row, including the
    Hinge guide (which has one). The catch-all's extension allowlist has no
    `html` entry and its first branch is `^[^.]+$`, so BOTH the extensionless
    path and the .html path fall through to it without a row.

    Rows live as of 2026-10-15: dating-profile-photos, linkedin-profile-photo,
    instagram-photo, which-photo-for-hinge, compare/photofeeler-alternative.
    Rows the 2026-10-15 content PR needs, added in the Console AFTER the deploy
    that ships the files (a row pointing at a file the live build lacks breaks
    the URL): /guides, /ai-photo-selector, /guides/instagram-carousel-order,
    /guides/instagram-photo-picker, /compare/photoselector-app-alternative,
    /compare/rankmyphotos-alternative.

    WHY THE TWO HALVES ARE WORTH SPELLING OUT: a Console rule is invisible from
    this repository. Nothing in a diff, a review or CI can tell you it is gone,
    and its removal produces no error, just a page quietly serving the homepage
    at HTTP 200 while this file keeps advertising it. So if a Console rule is
    ever removed, remove its entry here in the same change. Check with curl,
    not a browser:
      curl -s https://mytopit.com/guides/dating-profile-photos | grep -o '<title>[^<]*'
    The homepage title means the rewrite is gone.
  -->
  <url>
    <loc>https://mytopit.com/guides</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/ai-photo-selector</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/dating-profile-photos</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/which-photo-for-hinge</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/linkedin-profile-photo</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/instagram-photo</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/instagram-carousel-order</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/guides/instagram-photo-picker</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/compare/photofeeler-alternative</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/compare/photoselector-app-alternative</loc>
    <lastmod>2026-10-15</lastmod>
  </url>
  <url>
    <loc>https://mytopit.com/compare/rankmyphotos-alternative</loc>
    <lastmod>2026-10-15</lastmod>
  </url>

  <!--
    /photography AND /products ARE REACT ROUTES WITH A PRERENDERED TWIN, not
    static files under public/. scripts/pageHeads.mjs writes
    dist/photography.html and dist/products.html at build time: the page's own
    head (title, description, canonical, og tags, JSON-LD) and the page itself
    rendered inside #root. Like the guides, they need TWO halves, and only one
    lives in this repo. HALF TWO is two Amplify Console rewrite rules, placed
    ABOVE the SPA catch-all, exact paths only (/photography/terms must keep
    falling through to index.html):

      /photography  ->  /photography.html  200
      /products     ->  /products.html     200

    Rules go in AFTER the build that contains the files is deployed, never
    before (scripts/pageHeads.mjs explains why). Until they exist, a crawler
    reads the homepage's head at these URLs, which is the self-contradicting
    signal the /contact note above describes. That window is accepted because
    it closes the day the rules land; if the rules are ever removed, remove
    these two entries in the same change. Check with curl, not a browser:
      curl -s https://mytopit.com/photography | grep -o '<title>[^<]*'
    A homepage title means the rewrite is missing.
  -->
  <url>
    <loc>https://mytopit.com/photography</loc>
  </url>
  <url>
    <loc>https://mytopit.com/products</loc>
  </url>

  <!--
    /business/taste-api: the same prerendered twin as /photography and
    /products (scripts/pageHeads.mjs writes dist/business/taste-api.html). Its Amplify
    Console rule, above the catch-all and after the deploy:
      /business/taste-api  ->  /business/taste-api.html  200
    If the rule is ever removed, remove this entry in the same change.
  -->
  <url>
    <loc>https://mytopit.com/business/taste-api</loc>
  </url>
</urlset>
