ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:54:35 +10:00
commit 344b1701dd
505 changed files with 56231 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
import Link from "next/link";
import { Band, CtaBand, SiteNav, body, h2, h3, kicker, wrap } from "@/components/site";
import { plansLive } from "@/lib/plans-live";
export const metadata = {
title: "Who built it",
description: "ThreadCount was written by a hospital uniform coordinator for their own linen room, because the workbook stopped being enough.",
alternates: { canonical: "/about" },
};
const STORY = [
"A hospital linen room runs on memory. Who has how many sets, which sizes are short, what was ordered three weeks ago and never arrived, which ward should be paying for it. Most of that lives in a workbook, a notepad, and the coordinators head.",
"That works until it doesnt. A staff member insists they were never issued a jacket. Finance asks why one wards spend doubled. A size runs out on a Monday morning and nobody knew it was low on Friday.",
"ThreadCount was built to answer those questions from a record rather than from recollection. Every garment is scanned to a named person, every issue starts its own replacement order, and every dollar lands on the ward that wore it. The features exist because each one solved a real morning at a real counter.",
"Its free. No licence, no per-device charge, no sales process. If its useful to your room, use it.",
];
// The last paragraph and the last principle once plans are live: the software is still free, and
// the sentence now says what is and isn't. Everything above them is unchanged — see lib/plans-live.ts.
const STORY_LAST_LIVE = "The software is free — run it yourself and it costs nothing. Hosting it for you is what costs money, and a small room is hosted free. If its useful to your room, use it.";
const PRINCIPLE_LAST_LIVE = { t: "Free to run, and portable", b: "The code is published, there is no lock-in, and paying for hosting never locks anything else. Every report and register exports as CSV, and one backup file takes the whole facility. Leaving is as easy as arriving." };
const PRINCIPLES = [
{ t: "Built at the counter", b: "Nothing in the product exists because it demonstrates well. Every screen was written to survive a queue of nurses at eight in the morning." },
{ t: "No paperwork tax", b: "The finance outputs are assembled from data already being entered. Theres no second system to keep fed." },
{ t: "Free, and portable", b: "No licence and no lock-in. Every report and register exports as CSV, and one backup file takes the whole facility. Leaving is as easy as arriving." },
];
export default async function About() {
const live = await plansLive();
const story = live ? [...STORY.slice(0, -1), STORY_LAST_LIVE] : STORY;
const principles = live ? [...PRINCIPLES.slice(0, -1), PRINCIPLE_LAST_LIVE] : PRINCIPLES;
return (
<>
<SiteNav />
{/* Half-and-half hero: type left, photograph right behind an ink border. */}
<section style={{ borderBottom: "2px solid var(--color-text)" }}>
<div className="tcm-split" style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<div style={{ padding: "clamp(56px,7vw,88px) 40px clamp(52px,6vw,76px)", maxWidth: 760, marginLeft: "auto", width: "100%" }}>
<div style={kicker}>Who built it</div>
<h1 style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: "clamp(34px,4.4vw,64px)", lineHeight: 1.0, letterSpacing: "-0.035em", margin: "18px 0 0" }}>One coordinator, one linen room, one spreadsheet too many.</h1>
<p style={{ ...body, fontSize: 17, maxWidth: "46ch", margin: "22px 0 0" }}>ThreadCount was written by the person doing the job, for the job. It exists because the workbook stopped being enough.</p>
</div>
<div style={{ borderLeft: "2px solid var(--color-text)", minHeight: "clamp(280px,32vw,460px)", overflow: "hidden" }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className="grayscale" src="/photos/tc-photo-kitchen.jpg" alt="" aria-hidden="true" style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: "center 40%", display: "block" }} />
</div>
</div>
</section>
<Band>
<div className="tcm-rowgrid" style={{ display: "grid", gridTemplateColumns: "200px 1fr", gap: 40 }}>
<div style={kicker}>The story</div>
<div style={{ maxWidth: "64ch" }}>
{story.map((p, i) => <p key={i} style={{ ...body, margin: i ? "18px 0 0" : 0 }}>{p}</p>)}
</div>
</div>
</Band>
<Band tone="ink" pad="clamp(48px,6vw,76px) 40px">
<div style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: "clamp(24px,3.2vw,44px)", lineHeight: 1.15, letterSpacing: "-0.03em", maxWidth: "26ch", color: "#fff" }}>Every feature here started as an annoyance, not a roadmap item.</div>
</Band>
<Band tone="surface">
<div className="tcm-3col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 0, border: "2px solid var(--color-text)", background: "var(--color-bg)" }}>
{principles.map((p, i) => (
<div key={p.t} style={{ padding: "26px 28px 32px", borderRight: i < 2 ? "1px solid var(--color-divider)" : undefined }}>
<h3 style={{ ...h3, fontSize: 20 }}>{p.t}</h3>
<p style={{ fontSize: 14.5, lineHeight: 1.65, color: "var(--color-neutral-800)", marginTop: 10 }}>{p.b}</p>
</div>
))}
</div>
</Band>
<Band>
<div className="tcm-split" style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 32, alignItems: "center" }}>
<h2 style={{ ...h2, fontSize: "clamp(22px,2.6vw,34px)" }}>If your linen room, aged-care home or clinic has the same problem, come and say so.</h2>
<Link href="/contact" className="btn btn-primary">Get in touch</Link>
</div>
</Band>
<CtaBand />
</>
);
}