7.3 KiB · text History 1ce9641
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
  <section class="py-20 sm:py-28 text-center">
    <p class="font-mono text-xs uppercase tracking-[0.25em] text-accent mb-4">DMS</p>
    <h1 class="text-4xl sm:text-6xl font-bold tracking-tight">
      Stash images. Find them with a phrase.
    </h1>
    <p class="text-base-content/70 text-lg sm:text-xl mt-6 max-w-2xl mx-auto leading-relaxed">
      OCR every screenshot, describe every photo, tag everything as it lands.
      Search later by anything a categorizer noticed — "McMaster catalog cover"
      finds the photo of the McMaster catalog cover.
    </p>
    <div class="flex justify-center gap-3 mt-8">
      {% if user %}
        <a href="/uploads" class="btn btn-primary">
          <i class="fas fa-fw fa-images"></i> Open uploads
        </a>
        <a href="/invites" class="btn btn-ghost">
          <i class="fas fa-fw fa-envelope"></i> Invites
        </a>
      {% else %}
        {% if registration_open %}
          <a href="/register" class="btn btn-primary">Get started</a>
        {% else %}
          <span class="btn btn-disabled" title="Registration is invite-only">
            Get started · invite-only
          </span>
        {% endif %}
        <a href="/login" class="btn btn-ghost">Log in</a>
      {% endif %}
    </div>
  </section>

  <section class="py-12 sm:py-16">
    <h2 class="text-2xl sm:text-3xl font-bold mb-2">What you get</h2>
    <p class="text-base-content/70 mb-8">
      A document-management system that does the boring parts — categorizing,
      tagging, indexing — so a year from now you can still find that one
      screenshot.
    </p>

    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-bolt size-5 text-accent"></i>
          <h3 class="font-semibold">Auto-categorizers</h3>
        </div>
        <p class="text-sm text-base-content/80">
          Every upload is fanned out to a queue: OCR, image describe (VLM),
          similar-image detection, tag extraction. Each pipeline can be re-run
          on demand from the detail page.
        </p>
      </div>

      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-tags size-5 text-accent"></i>
          <h3 class="font-semibold">Namespaced tags</h3>
        </div>
        <p class="text-sm text-base-content/80">
          <code>desc:dog</code>, <code>Series:Friends</code>, <code>ocr:mcmaster</code>.
          Tags carry their origin so categorizer output never collides with
          your own labels — and you can filter by namespace.
        </p>
      </div>

      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-magnifying-glass size-5 text-accent"></i>
          <h3 class="font-semibold">Phrase search</h3>
        </div>
        <p class="text-sm text-base-content/80">
          Search by file name, item description, or any tag from any pipeline.
          OCR text becomes <code>ocr:*</code> tags, descriptions become
          <code>desc:*</code> — the same query box hits all of them.
        </p>
      </div>

      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-envelope-open-text size-5 text-accent"></i>
          <h3 class="font-semibold">Invite-only by default</h3>
        </div>
        <p class="text-sm text-base-content/80">
          Public registration is off until you flip <code>REGISTRATION_ENABLED</code>.
          Generate scoped invite links from <a href="/invites" class="link">/invites</a>
          — optional email pin, optional expiry, one-click revoke.
        </p>
      </div>

      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-layer-group size-5 text-accent"></i>
          <h3 class="font-semibold">Workers scale horizontally</h3>
        </div>
        <p class="text-sm text-base-content/80">
          RabbitMQ competing-consumer queues per categorizer. Run <code>just worker
          ocr</code> N times across N machines — work is distributed; failures
          dead-letter; idempotency is per-job.
        </p>
      </div>

      <div class="border border-base-300 rounded-box p-5 bg-base-200">
        <div class="flex items-center gap-2 mb-2">
          <i class="fas fa-fw fa-fingerprint size-5 text-accent"></i>
          <h3 class="font-semibold">Content-addressed storage</h3>
        </div>
        <p class="text-sm text-base-content/80">
          Files land at <code>sha256/aa/bb/&lt;hash&gt;</code>. Re-uploading the
          same image short-circuits to the existing record — no duplicate
          bytes, no duplicate categorizer runs.
        </p>
      </div>
    </div>
  </section>

  <section class="py-12 sm:py-16">
    <h2 class="text-2xl sm:text-3xl font-bold mb-2">Three steps to searchable</h2>
    <p class="text-base-content/70 mb-8">
      From "I have an image" to "I can find that image again" — the only
      manual step is the upload.
    </p>

    <ol class="grid grid-cols-1 sm:grid-cols-3 gap-4">
      <li class="border border-base-300 rounded-box p-5 bg-base-200 flex flex-col gap-2">
        <span class="font-mono text-xs text-accent">01</span>
        <h3 class="font-semibold">Get in</h3>
        <p class="text-sm text-base-content/80">
          {% if registration_open %}
            Sign up directly — registration is open.
          {% else %}
            DMS is invite-only. Ask an existing user to generate a link from <a href="/invites" class="link">/invites</a>, or run <code>just invite</code> on the server.
          {% endif %}
        </p>
      </li>
      <li class="border border-base-300 rounded-box p-5 bg-base-200 flex flex-col gap-2">
        <span class="font-mono text-xs text-accent">02</span>
        <h3 class="font-semibold">Drop something in</h3>
        <p class="text-sm text-base-content/80">
          Visit <a href="/uploads" class="link">Uploads</a>, pick a file, add
          optional tags. Thumbnails, OCR, and describe jobs fire off in the
          background — watch the status grid on the detail page.
        </p>
      </li>
      <li class="border border-base-300 rounded-box p-5 bg-base-200 flex flex-col gap-2">
        <span class="font-mono text-xs text-accent">03</span>
        <h3 class="font-semibold">Find it again</h3>
        <p class="text-sm text-base-content/80">
          Filter by free-text, tag, or namespace. Click any tag on a detail
          page to pivot to other uploads sharing it. Re-run a pipeline anytime
          if you swap in a smarter model.
        </p>
      </li>
    </ol>
  </section>

  <section class="py-12 sm:py-16 text-center">
    <h2 class="text-2xl sm:text-3xl font-bold mb-2">Built on Rust</h2>
    <p class="text-base-content/70 max-w-2xl mx-auto">
      axum + sea-orm on Postgres, RabbitMQ for the categorizer queues,
      <a href="https://github.com/robertknight/ocrs" class="link">ocrs</a>
      for OCR, minijinja + Tailwind + DaisyUI on the front. Three binaries:
      webservice, queue-worker, registration-token.
    </p>
  </section>
</div>