fix(tier2-g): warmer Gate copy, generic example, plain-language advanced controls

Round-three pass on the Gate card based on the latest UX feedback.

- Tagline: "Keeps your guest list yours" -> "Only your guests get in"
- Walk-through example now uses a neutral placeholder name (Sam)
  instead of "Aunty Patience". GuestGuard's audience is wedding
  couples, corporate planners, and party hosts as much as family
  organisers, and Sam is at home in any of those contexts.
- The four-step "How does the Gate work?" expander was lightly
  rewritten for warmth and to remove em-dashes; the mechanism it
  describes is unchanged.
- Advanced strictness controls now open with a real explanation
  rather than a one-liner about "band thresholds". The intro maps
  the three sliders to the three reactions (watch / flag / refuse),
  explains what the 0-100 numbers mean, and reassures hosts that
  the presets above are still fine for almost everyone. Each
  slider also gets a one-line caption tying its level dot (green
  / amber / red) to the user-visible effect.
- The trusted-networks "What this is and isn't" panel had its quotes
  unwound into natural sentences; the empty-state copy was rewritten
  in the second person; "No decisions to review yet" reworded.
- Pass through every user-visible string and replaced em-dashes
  with periods, commas, or natural rephrasing. Em-dashes only remain
  in code comments now (developer-facing, not on screen).

No behaviour changes; no backend changes; no API changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kwaku Danso
2026-05-19 23:00:11 +01:00
parent dee3d738ac
commit b34715f152
+87 -48
View File
@@ -53,7 +53,7 @@ const PRESETS: Preset[] = [
{
id: 'relaxed',
label: 'Relaxed',
description: "Casual party fine if friends share their link with each other.",
description: "Casual party. It's fine if a few friends share their link with each other.",
medium: 50, high: 80, block: 95,
},
{
@@ -65,7 +65,7 @@ const PRESETS: Preset[] = [
{
id: 'strict',
label: 'Strict',
description: "Wedding or private event — uninvited plus-ones are a problem and the guest list matters.",
description: "Wedding or private event. Uninvited plus-ones are a problem and the guest list really matters.",
medium: 25, high: 50, block: 70,
},
{
@@ -270,30 +270,30 @@ function verdictLabel(v: string) {
<header class="mb-3 flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold">Gate</h2>
<p class="text-xs text-zinc-500">Keeps your guest list yours.</p>
<p class="text-xs text-zinc-500">Only your guests get in.</p>
</div>
</header>
<!-- Value-prop paragraph. This is the *point* of the feature in plain
words. The user named the actual pain ("preventing multiple sharing
of events to uninvited guests") that phrase lives here verbatim. -->
<!-- Value-prop paragraph. Plain language, no em-dashes, no example
names. Hits the actual pain (forwarded / shared invitation
links) head-on. -->
<div class="mb-4 rounded-lg border border-brand-700/40 bg-brand-500/[0.04] p-4 text-sm leading-relaxed">
<p class="text-zinc-200">
Every guest gets their own personal invitation link only meant for them.
The <strong class="text-brand-300">Gate</strong> watches each link in the
background and stops <strong>forwarded or shared invitations</strong>
from being used by people who weren't on your list.
Every guest gets their own personal invitation link. Only one person was meant to use it.
The <strong class="text-brand-300">Gate</strong> quietly watches each link, so when
someone forwards or shares an invitation, the people it ends up with can't actually use it.
</p>
<p class="mt-2 text-xs text-zinc-400">
Real invited guests don't notice it. You don't need to set anything up
for it to work the Gate is on by default with sensible settings.
Your invited guests won't notice a thing. There's nothing you need to set up for this
to work; the Gate runs in the background with sensible defaults from the moment your
event is live.
</p>
</div>
<!-- "How does this work?" — collapsed by default so the page stays
scannable. The expander walks through the actual mechanism in
plain language so curious / sceptical hosts can see what we're
doing without reading docs. -->
scannable. Plain-language walkthrough; uses a generic placeholder
name (Sam) rather than family vocabulary so the example fits
wedding couples, party hosts, and corporate planners equally. -->
<details class="group mb-6 rounded-lg border border-zinc-800 bg-zinc-950">
<summary class="flex cursor-pointer items-center justify-between p-3 text-sm font-medium text-zinc-200">
<span class="flex items-center gap-2">
@@ -307,46 +307,49 @@ function verdictLabel(v: string) {
</svg>
</summary>
<div class="space-y-3 border-t border-zinc-900 p-4 text-sm text-zinc-300">
<p class="text-zinc-400">
Here's the short version, using "Sam" as a stand-in for any one of your invited guests.
</p>
<ol class="space-y-3">
<li class="flex gap-3">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-500/15 text-xs font-semibold text-brand-300">1</span>
<p>
When you send Aunty Patience her invitation, she gets her own personal link.
That link only belongs to her.
When you send Sam an invitation, the link inside it is just for Sam. Nobody else
has the same link.
</p>
</li>
<li class="flex gap-3">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-500/15 text-xs font-semibold text-brand-300">2</span>
<p>
The first time Aunty opens her link, the Gate quietly remembers a few details:
her phone or browser, the general area she connected from, and the network shape.
That becomes her "I'm me" signature.
The first time Sam opens it, the Gate quietly notes a few details about the visit:
the phone or browser Sam is on, roughly where Sam is in the world, and the kind of
network Sam is connected to. Together those become Sam's "this is really me" picture.
</p>
</li>
<li class="flex gap-3">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-500/15 text-xs font-semibold text-brand-300">3</span>
<p>
Every time someone clicks Aunty's link after that — including Aunty herself —
the Gate compares them to her signature. Same phone, similar location?
They sail through. No signature yet? They become Aunty's signature on the first visit.
Every later click on Sam's link, including Sam coming back to update their reply,
gets compared to that first visit. If it looks like the same person, they go straight
through with no friction at all.
</p>
</li>
<li class="flex gap-3">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-500/15 text-xs font-semibold text-brand-300">4</span>
<p>
If someone with a totally different phone on a totally different network tries to
use Aunty's link — like a friend Aunty forwarded it to the Gate notices. Depending
on your strictness setting it'll either flag them for your review, or refuse them
outright.
If a completely different device on a completely different network tries to use Sam's
link, say because Sam forwarded it to a friend, the Gate notices. Depending on how
strict you've set it, the Gate will either flag that click for you to review, or stop
the RSVP from going through at all.
</p>
</li>
</ol>
<p class="rounded-md border border-zinc-800 bg-zinc-900/60 p-3 text-xs text-zinc-400">
<strong class="text-zinc-300">Worth knowing:</strong> guests can switch from mobile data
to Wi-Fi or change rooms without being flagged the Gate only cares about
<em>meaningful</em> differences from the original visit, not normal day-to-day variation.
If a real guest does ever get flagged (it happens), you can clear them with one click
in <em>Recent reviews</em> below.
<strong class="text-zinc-300">Worth knowing:</strong> guests can switch between Wi-Fi
and mobile data, change rooms, or open their link a few days later without being
flagged. The Gate only cares about <em>meaningful</em> differences from that first
visit, not normal day-to-day variation. And if a real guest of yours ever does end up
flagged, you can clear them with one click under <em>Recent reviews</em> below.
</p>
</div>
</details>
@@ -393,7 +396,11 @@ function verdictLabel(v: string) {
You're running with custom strictness settings (set under Advanced).
</p>
<!-- Advanced sliders for power users who want to dial individual bands. -->
<!-- Advanced three sliders that map directly to the engine's
band thresholds. Heavy intro copy is intentional: most
hosts who open this are curious rather than expert, and a
short plain-language explanation here keeps them from
feeling lost. -->
<details class="group mt-4 rounded-lg border border-zinc-800 bg-zinc-950">
<summary class="flex cursor-pointer items-center justify-between p-3 text-sm text-zinc-300">
<span>Advanced strictness controls</span>
@@ -402,40 +409,70 @@ function verdictLabel(v: string) {
</svg>
</summary>
<div class="space-y-4 border-t border-zinc-900 p-4">
<p class="text-xs text-zinc-500">
These directly drive the band thresholds (0100). The presets above just write
sensible triples for you.
</p>
<div class="space-y-2 text-xs text-zinc-400">
<p class="text-zinc-300">
For most hosts, the four presets above are all you need. These sliders are
here if you want to fine-tune exactly when each reaction kicks in.
</p>
<p>
As a click on a guest's link looks more and more different from their first
visit, the Gate moves through three reactions: watch silently, flag for your
review, or refuse the RSVP. The numbers below (from 0 to 100) are how much
difference is enough to trigger each one. Lower numbers make the Gate more
sensitive; higher numbers make it more forgiving.
</p>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<label class="block text-sm">
<span class="mb-1 flex items-center justify-between text-xs text-zinc-400">
<span>Watch from</span>
<span class="flex items-center gap-1.5">
<span class="inline-block h-2 w-2 rounded-full bg-brand-500"></span>
Watch from
</span>
<span class="font-mono tabular-nums text-zinc-300">{{ thresholds.medium }}</span>
</span>
<input v-model.number="thresholds.medium" type="range" min="0" max="100"
:disabled="!canEdit" class="w-full accent-brand-500"
@change="saveThresholds()"
@input="clampOrder('medium')" />
<p class="mt-1 text-[11px] leading-snug text-zinc-500">
When the Gate starts paying closer attention. Quiet for guests; visible only
to you in activity logs.
</p>
</label>
<label class="block text-sm">
<span class="mb-1 flex items-center justify-between text-xs text-zinc-400">
<span>Flag from</span>
<span class="flex items-center gap-1.5">
<span class="inline-block h-2 w-2 rounded-full bg-amber-500"></span>
Flag from
</span>
<span class="font-mono tabular-nums text-zinc-300">{{ thresholds.high }}</span>
</span>
<input v-model.number="thresholds.high" type="range" min="0" max="100"
:disabled="!canEdit" class="w-full accent-amber-500"
@change="saveThresholds()"
@input="clampOrder('high')" />
<p class="mt-1 text-[11px] leading-snug text-zinc-500">
When the Gate marks the click for your review. The RSVP still goes through;
you decide whether to clear it or flag the guest.
</p>
</label>
<label class="block text-sm">
<span class="mb-1 flex items-center justify-between text-xs text-zinc-400">
<span>Refuse from</span>
<span class="flex items-center gap-1.5">
<span class="inline-block h-2 w-2 rounded-full bg-red-500"></span>
Refuse from
</span>
<span class="font-mono tabular-nums text-zinc-300">{{ thresholds.block }}</span>
</span>
<input v-model.number="thresholds.block" type="range" min="0" max="100"
:disabled="!canEdit" class="w-full accent-red-500"
@change="saveThresholds()"
@input="clampOrder('block')" />
<p class="mt-1 text-[11px] leading-snug text-zinc-500">
Where the Gate refuses the RSVP outright. The visitor sees a polite "this
invitation can't be used" and you get a notification.
</p>
</label>
</div>
</div>
@@ -459,15 +496,17 @@ function verdictLabel(v: string) {
<strong>What this is and isn't:</strong>
</p>
<p class="mb-2">
Adding a network here tells the Gate "always wave through clicks coming from
this Wi-Fi — they're already with me." It's useful when you're hosting at home
or the office and your guests will connect through your network.
Adding a network here tells the Gate to always wave through clicks coming from
that Wi-Fi, because the people on it are already with you. It's useful when
you're hosting at home or in the office and your guests will be on your network
when they reply.
</p>
<p>
<strong class="text-zinc-300">It doesn't change how anyone else is treated.</strong>
Guests connecting from their own homes, mobile data, or anywhere else still get
the regular check — they're not suspected of anything by default. The Gate works
perfectly well with this list empty.
Guests connecting from their own homes, from mobile data, or from anywhere else
still get the regular check. They aren't suspected of anything just because they
aren't on your network. The Gate works perfectly well with this list empty, and
most events stay that way.
</p>
</div>
@@ -547,7 +586,7 @@ function verdictLabel(v: string) {
</li>
</ul>
<p v-else class="text-sm text-zinc-500">
No trusted networks and that's fine. The Gate is doing its job.
You haven't added any trusted networks, and that's fine. The Gate is still doing its job.
</p>
</div>
@@ -577,7 +616,7 @@ function verdictLabel(v: string) {
and {{ feedback.length - 10 }} more.
</li>
</ul>
<p v-else class="text-sm text-zinc-500">No decisions to review yet — the gate hasn't flagged anyone.</p>
<p v-else class="text-sm text-zinc-500">Nothing to review yet. The Gate hasn't flagged anyone.</p>
</div>
</div>