<!doctype html>
<!--
  ===================================================================
  STILLS — the teaser
  ===================================================================

  Deploy this FOLDER, not this file. See docs/holding-page.md, which is
  kept OUT of here because the folder is the document root — anything
  sitting in it at upload time is published at its own filename.

  FOUR ELEMENTS, AND THERE IS NOT A FIFTH: the wordmark, the headline
  with the word that turns, the email row, and the legal line. That is
  the entire brief. If something new wants to be added here, it almost
  certainly belongs on the launch site instead.

  IT SAYS NOTHING ABOUT WHETHER THE APP IS AVAILABLE. No App Store
  link, no `apple-itunes-app` Smart App Banner tag, no release date.
  The headline says what the thing is; the email row says you will be
  told when there is something to see. If a future edit adds a download
  button back, that is a decision about what this page is allowed to
  announce, not a detail — make it deliberately.

  `<meta name="robots" content="noindex">` is not decoration. A teaser
  that gets indexed replaces the real site in search results, and the
  pages it replaces took real work to rank. Leaving that tag on is how
  those results survive. It also means THIS PAGE MUST NOT STAY UP FOR
  WEEKS: past roughly a fortnight Google starts dropping noindexed URLs
  rather than holding them.

  404.html is a byte-for-byte copy of this file, so any unknown URL on
  the domain lands on the teaser rather than on a dead end. Edit one,
  copy it to the other.

  There is no analytics and no third-party anything. It is this file,
  two stylesheets, two fonts, one script and the wordmark.
  ===================================================================
-->
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">

  <title>Stills — a memory camera</title>
  <meta name="description" content="A disposable camera for your iPhone. 12 or 27 frames, no preview, no takebacks.">
  <meta name="robots" content="noindex, follow">
  <meta name="theme-color" content="#0D0D0D">
  <meta name="color-scheme" content="dark">

  <meta property="og:site_name" content="Stills">
  <meta property="og:title" content="Stills — a memory camera">
  <meta property="og:description" content="A disposable camera for your iPhone. 12 or 27 frames, no preview, no takebacks.">
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://stills.cam/">

  <link rel="icon" href="/assets/brand/stills-icon-light.png">
  <link rel="apple-touch-icon" href="/assets/brand/stills-icon-light.png">
  <link rel="preload" as="font" type="font/ttf" href="/assets/fonts/YoungSerif-Light.ttf?v=3" crossorigin>
  <link rel="preload" as="font" type="font/ttf" href="/assets/fonts/Heming-Variable.ttf?v=3" crossorigin>

  <link rel="stylesheet" href="/assets/fonts.css?v=8">
  <link rel="stylesheet" href="/assets/teaser.css?v=8">
  <!-- Heming draws the capital D 160 units too wide, and the uppercase
       micro-copy here has two of them: LANDS and MADE. Without this the
       status line renders as "IT LAND S." with a hole after the D. -->
  <script src="/assets/kern.js?v=8" defer></script>
  <script src="/assets/teaser.js?v=8" defer></script>
</head>
<body>

  <a class="mark" href="/" aria-label="Stills">
    <img src="/assets/brand/stills-logo-white.png" alt="Stills" width="1894" height="474">
  </a>

  <main class="mid">

    <!--
      THE ACCESSIBLE NAME IS AN aria-label, NOT A DUPLICATE SPAN. A live
      region that rewrites itself every few seconds is announced every
      few seconds, which turns the headline into a stuck announcement for
      anyone using a screen reader — so the turning word is aria-hidden
      and the heading names itself instead.

      It used to carry a visually-hidden twin of the word alongside the
      visible one. That worked for screen readers and broke everything
      that reads text: h1.textContent came out "a memorymemory camera",
      with no space, which is what a preview generator, a scraper or a
      title-extractor would have taken away from the page. An aria-label
      says the same thing to assistive tech and leaves the text clean.

      `.tail` exists so "camera" can drop to its own line on a phone.
      The point is that the line COUNT never changes as the word turns —
      fifteen words of different lengths would otherwise reflow the
      headline every few seconds on a page that cannot scroll.
    -->
    <h1 aria-label="a memory camera">a <span class="cycle" aria-hidden="true">memory</span> <span class="tail">camera</span></h1>

    <!--
      method AND action ARE THE NO-JS FALLBACK, and without them this
      form was a privacy bug rather than a broken feature. A <form> with
      neither submits GET to the CURRENT URL, so if teaser.js fails to
      load — CSP mistake, blocked script, flaky network — pressing the
      button reloads the page with ?email=someone@example.com in the
      address bar, where it lands in history, in the referrer and in any
      access log. Posting to the endpoint instead keeps the address in a
      request body. The worker already accepts form-encoded input, so
      the degraded path genuinely subscribes; it just answers with JSON
      instead of a nice line of type.
    -->
    <form class="join" method="post" action="/api/subscribe" novalidate>
      <label class="sr-only" for="join-email">Email address</label>
      <div class="join-row">
        <input id="join-email" name="email" type="email"
               placeholder="your email"
               autocomplete="email" inputmode="email"
               spellcheck="false" autocapitalize="off"
               maxlength="254" required>
        <button type="submit">keep me posted</button>
      </div>
      <!--
        This line is the microcopy AND the status. It starts as the
        promise and the script overwrites it with sending / done /
        error, which is how the page gets a fourth state without a
        fifth element. aria-live announces the change but not the
        initial text, which is exactly the behaviour wanted.
      -->
      <!--
        tabindex="-1" so the script can move focus here when the row is
        removed on success. Without it the focused submit button is
        display:none'd out from under the keyboard and focus falls back
        to <body>, which on a page with no landmarks means tabbing starts
        again from the top with no announcement of what just happened.
      -->
      <p class="join-say" role="status" aria-live="polite" tabindex="-1">One email when it lands. Nothing else, ever.</p>
    </form>

  </main>

  <!--
    THE PRIVACY LINK GOES TO THE SITE POLICY, NOT THE APP ONE. This page
    collects an email address, and the document that governs that address
    is /privacy. It used to point at /app-privacy — which describes what
    the iPhone app does with photographs and says nothing about this box —
    leaving the only policy that actually covers the form unreachable from
    the page holding the form.

    /app-privacy is still deployed and still resolves, because it is the
    App Store listing's required Privacy Policy URL and a store listing
    whose privacy link 404s is a compliance problem. It does not need a
    link here; the store links it directly, and /privacy opens by pointing
    at it for anyone who arrived looking for the app.
  -->
  <p class="foot">
    <span>2026 Stills</span>
    <span>Made in California</span>
    <a href="/privacy">Privacy</a>
    <a href="/terms">Terms</a>
  </p>

</body>
</html>
