diff --git a/src/SignsOfAI.UI/Components/TaskEntry.razor b/src/SignsOfAI.UI/Components/TaskEntry.razor
new file mode 100644
index 0000000..40579b2
--- /dev/null
+++ b/src/SignsOfAI.UI/Components/TaskEntry.razor
@@ -0,0 +1,72 @@
+@* The front door, organised by what the reader came to do rather than by which engine runs.
+
+ The pages behind this already speak the right language — the baseline page's own heading is
+ literally "Does this look like the person who wrote the others?" — but the navigation hid every
+ one of them behind the name of its machinery: Analyse, Originality, Baseline, Catalogue, Folder.
+ A teacher does not arrive wanting a baseline. They arrive with thirty essays and one that felt
+ wrong, and nothing on the page told them which of the five things was theirs.
+
+ Deliberately not a wizard and deliberately not a filter. It routes and it explains; the editor is
+ still directly below, so the person who came to paste a paragraph loses one short band of page
+ and nothing else. The current task is shown rather than hidden, because a chooser that omits
+ where you already are makes the reader wonder what they missed.
+
+ The folder card always links, including in a browser where scanning a folder is impossible: that
+ page states why and points at the download. Hiding the card would leave the browser visitor
+ believing the product cannot do it at all, which is the lowest-common-denominator answer
+ HostCapabilities exists to avoid. *@
+@inherits LocalizedComponent
+@inject IFolderBatch Folders
+
+
+ @L["task.question"]
+
+
+
+@code {
+ /// Which of the four the reader is already on. Empty on a page that is none of them.
+ [Parameter] public string Current { get; set; } = "";
+
+ private sealed record Task(string Key, string Href);
+
+ // Ordered by how often a teacher's week starts there, not by how the code is arranged: the stack
+ // of submissions comes before the single text, because the single text is usually the second
+ // step of the same afternoon.
+ private static readonly Task[] Tasks =
+ [
+ new("text", ""),
+ new("folder", "batch"),
+ new("person", "baseline"),
+ new("overlap", "originality"),
+ ];
+}
diff --git a/src/SignsOfAI.UI/Layout/MainLayout.razor b/src/SignsOfAI.UI/Layout/MainLayout.razor
index ac0c856..8c72654 100644
--- a/src/SignsOfAI.UI/Layout/MainLayout.razor
+++ b/src/SignsOfAI.UI/Layout/MainLayout.razor
@@ -10,15 +10,16 @@
@L["nav.builtwith"]
diff --git a/src/SignsOfAI.UI/wwwroot/css/app.css b/src/SignsOfAI.UI/wwwroot/css/app.css
index 21bb6f0..e663678 100644
--- a/src/SignsOfAI.UI/wwwroot/css/app.css
+++ b/src/SignsOfAI.UI/wwwroot/css/app.css
@@ -74,6 +74,69 @@ a { color: var(--brand); }
.tagline strong { color: var(--text); }
.privacy { font-size: .85rem; color: var(--text-muted); margin: 0; }
+/* ---- task entry ----
+ Four routes by what the reader came to do. Quiet on purpose: it sits above the editor, so it must
+ read as signposting and never compete with the result panels below it. auto-fit rather than a
+ fixed count, so it becomes two rows on a laptop and one column on a phone without a breakpoint. */
+.task-entry { max-width: 900px; margin: 0 auto 1.75rem; }
+.task-q {
+ text-align: center;
+ font-size: .82rem;
+ font-weight: 600;
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ margin: 0 0 .7rem;
+}
+.task-cards {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
+ gap: .7rem;
+}
+.task-card {
+ display: flex;
+ flex-direction: column;
+ gap: .3rem;
+ height: 100%;
+ padding: .85rem 1rem;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ text-decoration: none;
+ color: inherit;
+ transition: border-color .15s, transform .15s;
+}
+a.task-card:hover, a.task-card:focus-visible {
+ border-color: var(--brand);
+ transform: translateY(-1px);
+}
+.task-name { font-weight: 650; font-size: .96rem; color: var(--text); line-height: 1.3; }
+.task-what { font-size: .82rem; color: var(--text-muted); line-height: 1.45; }
+/* Where the thing actually lives, when it is not here. Same slot as "you are here", muted. */
+.task-where {
+ margin-top: auto;
+ padding-top: .3rem;
+ font-size: .72rem;
+ font-weight: 600;
+ letter-spacing: .05em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+}
+/* Where you already are: still readable, visibly not a destination. */
+.task-card.here { background: var(--surface-2); border-style: dashed; }
+.task-here {
+ margin-top: auto;
+ padding-top: .3rem;
+ font-size: .72rem;
+ font-weight: 600;
+ letter-spacing: .05em;
+ text-transform: uppercase;
+ color: var(--brand);
+}
+
/* ---- cards ---- */
.card {
background: var(--surface);
diff --git a/src/SignsOfAI.UI/wwwroot/i18n/en.json b/src/SignsOfAI.UI/wwwroot/i18n/en.json
index 5109a21..f201fdc 100644
--- a/src/SignsOfAI.UI/wwwroot/i18n/en.json
+++ b/src/SignsOfAI.UI/wwwroot/i18n/en.json
@@ -1,7 +1,19 @@
{
- "nav.analyze": "Analyze",
- "nav.originality": "Originality",
- "nav.batch": "Folder",
+ "task.aria": "What do you need to do",
+ "task.question": "What do you need to do?",
+ "task.here": "you are here",
+ "task.text.name": "Look at one text",
+ "task.text.what": "Paste it below. Signals, hidden characters and what it says about its own sources.",
+ "task.folder.name": "Work through a stack of submissions",
+ "task.folder.what": "Reads a whole folder and says where to look first. A reading order, not a ranking.",
+ "task.folder.desktop": "in the desktop app",
+ "task.person.name": "Ask whether the same person wrote it",
+ "task.person.what": "Measured against their own earlier work, on their own scale — not against a machine.",
+ "task.overlap.name": "Ask whether two of them overlap",
+ "task.overlap.what": "Shows the passages two documents share, highlighted as evidence. You judge.",
+ "nav.analyze": "One text",
+ "nav.originality": "Overlap",
+ "nav.batch": "A folder",
"common.cancel": "Cancel",
"batch.pagetitle": "Scan a folder — Signs of AI Writing",
"batch.h1": "Scan a whole folder",
@@ -49,7 +61,7 @@
"verdict.signs": "Signs of AI writing",
"verdict.none": "No signs above the measured boundary",
"home.pagetitle": "Signs of AI Writing — detect & de-AI-ify your text",
- "home.tagline": "Paste, upload, or just start typing. We flag the tells of AI writing — overused words, rhetorical crutches, robotic rhythm — live, as you write , and show you how to fix each one.",
+ "home.tagline": "We mark the signs of AI writing and show you the evidence, not just a percentage.",
"home.privacy": "Analysis runs entirely in your browser. Your text never leaves your device.",
"home.textlang": "Text language",
"home.textlang.auto": "Auto-detect",
@@ -331,7 +343,7 @@
"citations.explain.limits": "What it cannot tell you is whether a well-formed, consistent reference is a real paper. That takes looking it up, which means sending the citation to a service — a separate step, and one that would ask you first. And it only runs at all when the document announces a reference list; guessing where a bibliography starts would produce complaints out of formatting.",
"citations.explain.notproof": "A missing reference is usually a slip, not dishonesty, and it is always the writer’s to explain. Ask them for the source itself.",
"citations.explain.noscore": "None of this affects the score. A score is a judgement you can argue with; whether a name appears in a list is something you can check by looking.",
- "nav.baseline": "Baseline",
+ "nav.baseline": "The same person",
"base.pagetitle": "Compare with earlier work — Signs of AI Writing",
"base.h1": "Does this look like the person who wrote the others?",
"base.tagline": "Every AI detector asks whether a text looks like a machine , and that question punishes anyone whose ordinary register is formal. This one asks whether it looks like the writer — measured against their own earlier work, on their own scale.",
diff --git a/src/SignsOfAI.UI/wwwroot/i18n/es.json b/src/SignsOfAI.UI/wwwroot/i18n/es.json
index 74ef2b2..add2f4a 100644
--- a/src/SignsOfAI.UI/wwwroot/i18n/es.json
+++ b/src/SignsOfAI.UI/wwwroot/i18n/es.json
@@ -1,7 +1,19 @@
{
- "nav.analyze": "Analizar",
- "nav.originality": "Originalidad",
- "nav.batch": "Carpeta",
+ "task.aria": "Qué necesitas hacer",
+ "task.question": "¿Qué necesitas hacer?",
+ "task.here": "estás aquí",
+ "task.text.name": "Mirar un texto",
+ "task.text.what": "Pégalo abajo. Señales, caracteres ocultos y qué dice sobre sus propias fuentes.",
+ "task.folder.name": "Sacar adelante un montón de entregas",
+ "task.folder.what": "Lee una carpeta entera y dice por dónde empezar. Un orden de lectura, no un ranking.",
+ "task.folder.desktop": "en la aplicación de escritorio",
+ "task.person.name": "Preguntar si lo escribió la misma persona",
+ "task.person.what": "Medido contra sus propios trabajos anteriores, en su propia escala — no contra una máquina.",
+ "task.overlap.name": "Preguntar si dos se parecen demasiado",
+ "task.overlap.what": "Enseña los pasajes que dos documentos comparten, resaltados como evidencia. Tú juzgas.",
+ "nav.analyze": "Un texto",
+ "nav.originality": "Coincidencias",
+ "nav.batch": "Una carpeta",
"common.cancel": "Cancelar",
"batch.pagetitle": "Analizar una carpeta — Señales de escritura IA",
"batch.h1": "Analiza una carpeta entera",
@@ -49,7 +61,7 @@
"verdict.signs": "Señales de escritura con IA",
"verdict.none": "Sin señales por encima del umbral medido",
"home.pagetitle": "Señales de escritura IA — detecta y humaniza tu texto",
- "home.tagline": "Pega, sube un archivo o simplemente empieza a escribir. Marcamos las señales de escritura IA —palabras sobreusadas, muletillas retóricas, ritmo robótico— en vivo, mientras escribes , y te mostramos cómo corregir cada una.",
+ "home.tagline": "Marcamos las señales de escritura con IA y te enseñamos la evidencia, no solo un porcentaje.",
"home.privacy": "El análisis se ejecuta por completo en tu navegador. Tu texto nunca sale de tu dispositivo.",
"home.textlang": "Idioma del texto",
"home.textlang.auto": "Detectar automáticamente",
@@ -331,7 +343,7 @@
"citations.explain.limits": "Lo que no puede decirle es si una referencia bien formada y coherente corresponde a un artículo real. Eso exige buscarla, o sea enviar la cita a un servicio — un paso aparte, y que le preguntaría antes. Y solo se ejecuta cuando el documento anuncia una lista de referencias; adivinar dónde empieza una bibliografía produciría quejas sacadas del formato.",
"citations.explain.notproof": "Una referencia que falta suele ser un descuido, no deshonestidad, y siempre le toca explicarla a quien escribió. Pídale la fuente en sí.",
"citations.explain.noscore": "Nada de esto afecta a la puntuación. Una puntuación es un juicio discutible; que un apellido esté o no en una lista se comprueba mirando.",
- "nav.baseline": "Referencia",
+ "nav.baseline": "La misma persona",
"base.pagetitle": "Comparar con trabajos anteriores — Señales de escritura con IA",
"base.h1": "¿Se parece a quien escribió los otros?",
"base.tagline": "Todo detector de IA pregunta si un texto se parece a una máquina , y esa pregunta castiga a quien escribe formal por costumbre. Esta pregunta si se parece a la persona — medido contra sus propios trabajos anteriores, en su propia escala.",
diff --git a/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs b/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs
index 8cc32cf..d2b69e6 100644
--- a/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs
+++ b/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs
@@ -30,6 +30,11 @@ .. Enum.GetValues().Select(c => "cat." + c.ToString().ToLowerInvar
.. Enum.GetValues().Select(s => "sev." + s.ToString().ToLowerInvariant()),
"verdict.signs", "verdict.none",
"lang.english", "lang.spanish",
+ // TaskEntry builds these from its own list, so a plain text search cannot see them and a
+ // deleted one would render as the raw key on the front door.
+ .. new[] { "text", "folder", "person", "overlap" }
+ .SelectMany(t => new[] { $"task.{t}.name", $"task.{t}.what" }),
+ "task.folder.desktop",
];
[Fact]
@@ -100,11 +105,12 @@ public void Fallback_locale_defines_every_key_the_app_asks_for()
/// Wordings this project decided it may not use, in any locale, because they are claims about the
/// writer rather than about the tool.
///
- /// A blacklist is a blunt instrument and this one is deliberate. The decision was taken in #32 and
- /// applied to the report and to the score card; it survived for months in the empty state of the
- /// home page — "This reads mostly human — nice work" — and in the rhythm caption, because the test
- /// that guards it reads AnalysisResult.Verdict and these are locale data. Translations
- /// arrive as community pull requests, so the guard has to live where the data does.
+ /// A blacklist is a blunt instrument and this one is deliberate. The decision was taken in #32 on
+ /// 2026-08-05 and applied to the report and the score card; the wording survived it by five days
+ /// in the empty state of the home page — "This reads mostly human — nice work" — and in the
+ /// rhythm caption, because the test that guards the decision reads AnalysisResult.Verdict
+ /// and these are locale data. Translations arrive as community pull requests, so the guard has to
+ /// live where the data does.
///
private static readonly string[] RetiredClaims =
[