feat: rebuild mintel.me as a single-page freelance site
The site now presents one freelance developer on one page: hero, introduction, four typical cases (developer for a product, make an app reliable, automate a process, a website) with what you get, how it runs and what it costs, a process automation section, technology and project fit, how I work, two live references, questions and contact. - Hero and closing headline are drawn from running binary digits. - Each case explains its four steps with an animated drawing; the automation section shows example workflows with work travelling between systems; a pipeline run shows what a test catches before a release. - Contact is one email address, kept in parts and assembled in the browser so it never appears in the page source (src/domain/email.ts, spec first). - Imprint and privacy pages added. - The old site (blog, case studies, contact form, fixed-price pages, video rendering and related scripts) is removed. Its URLs redirect to the homepage. - The deploy smoke test checks the legal pages and the redirects instead of the removed case study. - Dev scripts use docker compose v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Example workflows for the automation section. They are illustrations of typical
|
||||
* cases, not descriptions of specific client projects.
|
||||
*/
|
||||
|
||||
export type NodeKind = "trigger" | "rule" | "ai" | "action" | "human";
|
||||
|
||||
export type FlowNode = {
|
||||
readonly id: string;
|
||||
readonly kind: NodeKind;
|
||||
/** Position of the node centre on the wide 920 x 400 canvas. */
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
/** Position of the node centre on the tall 400 x 540 canvas for phones. */
|
||||
readonly nx: number;
|
||||
readonly ny: number;
|
||||
readonly system: string;
|
||||
readonly label: string;
|
||||
/** What appears in the log when a run reaches this node. */
|
||||
readonly log: string;
|
||||
};
|
||||
|
||||
/** One step of a run: the connections travelled at the same time. */
|
||||
export type FlowStep = readonly (readonly [from: string, to: string])[];
|
||||
|
||||
export type Workflow = {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly summary: string;
|
||||
readonly byHand: string;
|
||||
readonly automated: string;
|
||||
readonly nodes: readonly FlowNode[];
|
||||
/** Every connection that exists, for drawing. */
|
||||
readonly edges: readonly (readonly [from: string, to: string])[];
|
||||
/** The runs that play in turn. Each run is a list of steps. */
|
||||
readonly runs: readonly (readonly FlowStep[])[];
|
||||
};
|
||||
|
||||
export const WORKFLOWS: readonly Workflow[] = [
|
||||
{
|
||||
id: "order",
|
||||
name: "Order to invoice",
|
||||
summary:
|
||||
"A new order arrives in the shop. The invoice, the customer record and the confirmation follow without anyone typing.",
|
||||
byHand:
|
||||
"Someone opens the order, writes the invoice, updates the customer record and sends an email.",
|
||||
automated:
|
||||
"The order triggers everything. People only see the finished result.",
|
||||
nodes: [
|
||||
{
|
||||
id: "shop",
|
||||
kind: "trigger",
|
||||
x: 110,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 60,
|
||||
system: "Shop",
|
||||
label: "New order",
|
||||
log: "order received",
|
||||
},
|
||||
{
|
||||
id: "stock",
|
||||
kind: "rule",
|
||||
x: 340,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 190,
|
||||
system: "Rule",
|
||||
label: "In stock?",
|
||||
log: "stock checked: available",
|
||||
},
|
||||
{
|
||||
id: "invoice",
|
||||
kind: "action",
|
||||
x: 580,
|
||||
y: 110,
|
||||
nx: 105,
|
||||
ny: 330,
|
||||
system: "Accounting",
|
||||
label: "Create invoice",
|
||||
log: "invoice created",
|
||||
},
|
||||
{
|
||||
id: "crm",
|
||||
kind: "action",
|
||||
x: 580,
|
||||
y: 290,
|
||||
nx: 295,
|
||||
ny: 330,
|
||||
system: "CRM",
|
||||
label: "Update customer",
|
||||
log: "customer record updated",
|
||||
},
|
||||
{
|
||||
id: "mail",
|
||||
kind: "action",
|
||||
x: 810,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 470,
|
||||
system: "Email",
|
||||
label: "Send confirmation",
|
||||
log: "confirmation sent",
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
["shop", "stock"],
|
||||
["stock", "invoice"],
|
||||
["stock", "crm"],
|
||||
["invoice", "mail"],
|
||||
["crm", "mail"],
|
||||
],
|
||||
runs: [
|
||||
[
|
||||
[["shop", "stock"]],
|
||||
[
|
||||
["stock", "invoice"],
|
||||
["stock", "crm"],
|
||||
],
|
||||
[
|
||||
["invoice", "mail"],
|
||||
["crm", "mail"],
|
||||
],
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "documents",
|
||||
name: "Incoming documents",
|
||||
summary:
|
||||
"Invoices arrive as PDF by email. An AI step reads them, a rule checks the result, and a person only sees the unclear ones.",
|
||||
byHand:
|
||||
"Someone opens every PDF, reads supplier, amount and date, and types them into the accounting system.",
|
||||
automated:
|
||||
"Clear cases are booked directly. Unclear ones go to a person with the document attached.",
|
||||
nodes: [
|
||||
{
|
||||
id: "inbox",
|
||||
kind: "trigger",
|
||||
x: 110,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 60,
|
||||
system: "Inbox",
|
||||
label: "Email with PDF",
|
||||
log: "document received",
|
||||
},
|
||||
{
|
||||
id: "read",
|
||||
kind: "ai",
|
||||
x: 340,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 190,
|
||||
system: "AI step",
|
||||
label: "Read the document",
|
||||
log: "supplier, amount and date extracted",
|
||||
},
|
||||
{
|
||||
id: "check",
|
||||
kind: "rule",
|
||||
x: 570,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 320,
|
||||
system: "Rule",
|
||||
label: "Plausible?",
|
||||
log: "values checked",
|
||||
},
|
||||
{
|
||||
id: "book",
|
||||
kind: "action",
|
||||
x: 810,
|
||||
y: 110,
|
||||
nx: 105,
|
||||
ny: 460,
|
||||
system: "Accounting",
|
||||
label: "Book it",
|
||||
log: "booked automatically",
|
||||
},
|
||||
{
|
||||
id: "ask",
|
||||
kind: "human",
|
||||
x: 810,
|
||||
y: 290,
|
||||
nx: 295,
|
||||
ny: 460,
|
||||
system: "A person",
|
||||
label: "Review and decide",
|
||||
log: "unclear: sent to a person for review",
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
["inbox", "read"],
|
||||
["read", "check"],
|
||||
["check", "book"],
|
||||
["check", "ask"],
|
||||
],
|
||||
runs: [
|
||||
[[["inbox", "read"]], [["read", "check"]], [["check", "book"]]],
|
||||
[[["inbox", "read"]], [["read", "check"]], [["check", "book"]]],
|
||||
[[["inbox", "read"]], [["read", "check"]], [["check", "ask"]]],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "lead",
|
||||
name: "Enquiry to follow-up",
|
||||
summary:
|
||||
"Someone fills in the contact form. The contact is created, the right person is told, and a reminder makes sure nobody forgets.",
|
||||
byHand:
|
||||
"Someone copies the enquiry into the CRM, forwards it by email and hopes the follow-up is not forgotten.",
|
||||
automated: "The enquiry is filed, assigned and followed up on schedule.",
|
||||
nodes: [
|
||||
{
|
||||
id: "form",
|
||||
kind: "trigger",
|
||||
x: 110,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 60,
|
||||
system: "Website",
|
||||
label: "Contact form",
|
||||
log: "enquiry received",
|
||||
},
|
||||
{
|
||||
id: "contact",
|
||||
kind: "action",
|
||||
x: 340,
|
||||
y: 200,
|
||||
nx: 200,
|
||||
ny: 190,
|
||||
system: "CRM",
|
||||
label: "Create contact",
|
||||
log: "contact created",
|
||||
},
|
||||
{
|
||||
id: "notify",
|
||||
kind: "action",
|
||||
x: 580,
|
||||
y: 110,
|
||||
nx: 105,
|
||||
ny: 330,
|
||||
system: "Team chat",
|
||||
label: "Notify sales",
|
||||
log: "sales notified",
|
||||
},
|
||||
{
|
||||
id: "wait",
|
||||
kind: "rule",
|
||||
x: 580,
|
||||
y: 290,
|
||||
nx: 295,
|
||||
ny: 330,
|
||||
system: "Rule",
|
||||
label: "No reply in 3 days?",
|
||||
log: "follow-up scheduled",
|
||||
},
|
||||
{
|
||||
id: "remind",
|
||||
kind: "action",
|
||||
x: 810,
|
||||
y: 290,
|
||||
nx: 295,
|
||||
ny: 470,
|
||||
system: "Tasks",
|
||||
label: "Create reminder",
|
||||
log: "reminder created",
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
["form", "contact"],
|
||||
["contact", "notify"],
|
||||
["contact", "wait"],
|
||||
["wait", "remind"],
|
||||
],
|
||||
runs: [
|
||||
[
|
||||
[["form", "contact"]],
|
||||
[
|
||||
["contact", "notify"],
|
||||
["contact", "wait"],
|
||||
],
|
||||
[["wait", "remind"]],
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const KIND_LABEL: Readonly<Record<NodeKind, string>> = {
|
||||
trigger: "Trigger: something happens",
|
||||
rule: "Rule: a fixed check",
|
||||
ai: "AI step: reads or sorts",
|
||||
action: "Action: a system does something",
|
||||
human: "Person: decides the unclear cases",
|
||||
};
|
||||
|
||||
export const AUTOMATION_NOTES = [
|
||||
{
|
||||
title: "Worth automating when",
|
||||
items: [
|
||||
"The same steps repeat every day or every week",
|
||||
"Data is typed from one tool into another",
|
||||
"Mistakes happen because the work is monotonous",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "What stays with people",
|
||||
items: [
|
||||
"Decisions and exceptions",
|
||||
"Anything a customer should hear from a person",
|
||||
"The final say when the automation is unsure",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "What I need from you",
|
||||
items: [
|
||||
"Access to the tools and their interfaces (APIs)",
|
||||
"Someone who knows the process as it is done today",
|
||||
"Real examples to test with",
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
Reference in New Issue
Block a user