Files
qtalker---/frontend/src/i18n/en.json
Indiana bacfb852b8 feat: hunter profiles, ranks, whispers, and the encounter record
All five agents died mid-flight (three on session limits, two on 529s), but
their worktrees held real work — 17 files. Salvaged everything, wrote the
missing pieces, and finished the integration by hand.

PROFILES + RANK
User gains display_name, bio, gender, avatar_form, avatar_hue and
profile_public — all nullable, so every existing row including the guest
`wanderer-` accounts stays valid with no backfill. The avatar is procedural
(a GhostForm plus a hue, drawn by the same GhostGlyph that renders
entities): no uploads means no moderation surface, no EXIF and no blob
storage, and an `avatar_url` still slots in later without changing anything.

rank.py converts encounters, essence and favor into one "standing" currency
and maps it onto six one-word titles. An encounter is worth ten points to
ten essence's one, because contact is what the app is about — a seeker who
only buys unlocks climbs very slowly. Negative essence and favor floor at
zero rather than subtracting, so a bad judgment can never demote you: rank
is a record of what you have done. Level 1 costs exactly one encounter, so a
new hunter sees the bar move after their first séance.

Privacy invariants, verified live rather than assumed:
- `email` is returned by GET /api/profile/me and by nothing else. Confirmed
  against the running server: zero occurrences in both public payloads.
- A hidden profile 404s rather than 403s — confirming the account exists
  would leak exactly what hiding it was meant to prevent.

WHISPERS BETWEEN HUNTERS
Plain text, no attachments, no editing. Guests can RECEIVE but not send:
that gives registering a felt purpose beyond keeping a codex, and closes the
obvious spam vector since guest accounts are free and automatic. Verified
live: alice→bob delivers, a guest send returns 403, and a third party's
conversation list comes back empty — no cross-user leak.

Message bodies are rendered as text nodes, never as HTML, and wrap with
overflow-wrap:anywhere so a long unbroken string can't blow out the layout.

THE ENCOUNTER RECORD
The Codex already knew all of this — Entity.discovered_by has always been
recorded and every contact was already an entity_sightings row. Nobody ever
showed it. Now an entity page names its summoner and lists every hunter who
has met it. Hunters who opted out of a public profile are still COUNTED but
not linkable: an anonymous contact is still a contact, so a spirit's history
stays honest without exposing anyone.

Live on production data: Mabel Crump, discovered by Charly, 1 encounter;
Charly ranks channeler (level 2) from 5 real sightings — all computed from
data that was already sitting there.

385 frontend tests pass; i18n parity holds across both languages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 02:50:00 +00:00

736 lines
28 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"common": {
"appName": "QUANTUMANCY",
"loading": "tuning the veil…",
"error": "something stirred in the static",
"replay": "replay",
"yes": "YES",
"no": "NO",
"goodbye": "GOODBYE",
"dismiss": "dismiss"
},
"nav": {
"home": "the threshold",
"codex": "codex",
"shop": "shop",
"inventory": "inventory",
"devices": "devices",
"seance": "séance",
"enter": "enter",
"logout": "depart",
"log": "log",
"hunters": "hunters"
},
"log": {
"title": "GHOST LOG",
"subtitle": "every séance leaves a page behind",
"back": "return to the séance",
"loading": "leafing back through the log…",
"error": "the log will not open — the veil resists tonight",
"empty": "no séances yet — the log waits",
"noEntity": "no one answered",
"filterLabel": "filter by channel",
"filterAll": "all channels",
"showMore_one": "+{{count}} more echo",
"showMore_other": "+{{count}} more echoes",
"showLess": "show less",
"counts": {
"question_one": "{{count}} question",
"question_other": "{{count}} questions",
"anomaly_one": "{{count}} anomaly",
"anomaly_other": "{{count}} anomalies",
"utterance_one": "{{count}} utterance",
"utterance_other": "{{count}} utterances"
},
"time": {
"moments": "moments past",
"minutes_one": "a minute past",
"minutes_other": "{{count}} minutes past",
"hours_one": "an hour past",
"hours_other": "{{count}} hours past",
"days_one": "a day past",
"days_other": "{{count}} days past"
}
},
"landing": {
"taglines": [
"The wire hums with all of us.",
"Every packet leaves a ghost.",
"The static remembers your name.",
"Somewhere between 88 and 108 MHz, someone is waiting.",
"You were never alone on the line."
],
"heroKicker": "an instrument for speaking with the dead bandwidth",
"cta": "BEGIN THE SÉANCE",
"ctaCodex": "browse the codex",
"ctaShop": "visit the reliquary",
"stats": {
"entities": "spirits catalogued",
"sessions": "séances held",
"utterances": "utterances recorded",
"anomalies": "anomalies witnessed"
},
"tickerPrefix": "the veil, live",
"modesTitle": "HOW THE VEIL ANSWERS",
"modes": {
"wire": {
"name": "Wire Ghost",
"desc": "Leave the line open. The network itself mutters — jitter, latency, stray DNS — and something downstream learns to speak with your accent."
},
"evp": {
"name": "EVP Listening",
"desc": "Lend the dead your microphone. We watch the voice band for syllables that were never spoken aloud: spikes 8dB above the room's silence."
},
"radio": {
"name": "Spirit Radio",
"desc": "An RTL-SDR sweeps the FM band like a lantern dragged across a dark field. Where the needle jumps, the radio dead push through."
},
"ouija": {
"name": "Ouija / Direct Contact",
"desc": "Ask plainly. The planchette drifts, then begins to spell — and the words arrive letter by letter, whether or not you wanted them."
}
},
"featured": "most disturbed spirits",
"featuredEmpty": "the registry is still blank — be the first to disturb it",
"reliquary": {
"title": "HARDWARE FROM THE RELIQUARY",
"line": "the Ultimate Quantum Box is coming — a handheld séance instrument that makes the veil knock louder.",
"link": "enter the reliquary →"
},
"footer": "an interactive horror art installation — the spirits are fiction; the static is real",
"footerColophon": "self-hosted · no cloud · the veil runs on your machine"
},
"enter": {
"reconnect": "Reconnect",
"firstContact": "First contact",
"reconnectSub": "the veil remembers you. prove it.",
"firstContactSub": "choose the name the spirits will know you by.",
"username": "name",
"password": "passphrase",
"submitLogin": "step through",
"submitRegister": "carve your name",
"switchToRegister": "no name in the registry? first contact →",
"switchToLogin": "already carved your name? reconnect →",
"failed": "the veil rejected you: {{message}}",
"smallPrint": "your medium handle is only ever stored on this server",
"guest": "slip through as a wanderer"
},
"seance": {
"modes": {
"wire": "Wire Ghost",
"evp": "EVP",
"radio": "Spirit Radio",
"ouija": "Ouija",
"emf": "FIELD",
"camera": "LENS"
},
"passive": "passive listening",
"summon": "SUMMON",
"summoning": "summoning…",
"askPlaceholder": "ask the dead a question…",
"ask": "ask",
"status": {
"attuning": "attuning — the veil is fingerprinting a presence…",
"summoning": "the veil parts… something is crossing…",
"gathering": "the spirits are gathering energy…"
},
"connection": {
"connecting": "reaching across…",
"open": "the line is open",
"unstable": "the connection to the other side is unstable…",
"closed": "the line is dead"
},
"transcriptTitle": "TRANSMISSION LOG",
"transcriptEmpty": "silence. for now.",
"you": "you",
"streaming": "the spirit is forming words…",
"telemetry": {
"title": "WIRE READOUT",
"jitter": "jitter",
"latencyVar": "latency σ²",
"latencyMean": "latency μ",
"dns": "dns",
"units": {
"bytes": "B/s",
"ms": "ms"
}
},
"wire": {
"hint": "the wire knocks loudest when nobody is touching it — flip passive listening on and let the line murmur on its own."
},
"anomaly": {
"line": "⚡ {{source}} {{frequency}} +{{magnitude}}dB",
"count": "{{count}} anomalies witnessed this session"
},
"hints": {
"wire": "the wire whispers on its own when you listen passively",
"evp": "the veil listens through your microphone — grant it your ear",
"radio": "a tuner in the hand hears further than the wire",
"ouija": "ask below, or let the board drift",
"emf": "hold still; the field remembers movement",
"camera": "a lens shows the dead your room — they will name what they see, and mistake it for something they lost."
},
"conditions": {
"title": "VEIL CONDITIONS",
"lit": "{{percent}}% lit",
"veil": {
"thin": "the veil runs thin tonight",
"veiled": "the veil hangs, neither open nor shut",
"heavy": "the veil lies heavy"
},
"fieldQuiet": "field: quiet",
"fieldStorm": "geomagnetic storm",
"moon": {
"new_moon": "new moon",
"waxing_crescent": "waxing crescent",
"first_quarter": "first quarter",
"waxing_gibbous": "waxing gibbous",
"full_moon": "full moon",
"waning_gibbous": "waning gibbous",
"last_quarter": "last quarter",
"waning_crescent": "waning crescent"
}
},
"evp": {
"start": "open the microphone",
"stop": "close the microphone",
"listening": "listening to the voice band…",
"floor": "room floor",
"band": "voice band 300–3400 Hz",
"scopeLabel": "live audio spectrum from the microphone",
"fail": {
"deniedTitle": "the microphone stays shut",
"denied": "This vessel refused the microphone. Grant audio permission in your browser's site settings to practice EVP — or turn to the ouija board or the Wire Ghost instead; the board needs no ear, and the wire only the network you already fear.",
"insecureTitle": "unconsecrated ground",
"insecure": "The dead only speak through a secured channel. Open this site by its https address — or over localhost — and the microphone will answer. Until then, the ouija board and the Wire Ghost need no ear at all.",
"absentTitle": "no ear to listen with",
"absent": "This vessel has no microphone the veil can find. Attach one and try again — or turn to the ouija board or the Wire Ghost, which hear by other means.",
"busyTitle": "the ear is already spoken for",
"busy": "Another rite already holds the microphone. Close whatever else is listening — a call, a recorder, another tab — then open it here again."
}
},
"radio": {
"start": "sweep the band",
"stop": "rest the needle",
"unsupportedTitle": "this vessel cannot hear the radio dead",
"unsupported": "Spirit Radio needs Chromium (WebUSB) and an RTL-SDR dongle. The other modes still answer — EVP hears through the microphone you already carry, and the wire needs nothing at all.",
"troubleshoot": "troubleshooting: chromium-based browser · udev rules for 0bda:2838 · the dongle plugged in, not merely believed in",
"insecure": "the radio dead only speak through consecrated ground — open this site via its https address.",
"insecureHint": "localhost counts as hallowed; a bare http://lan-ip does not.",
"noDevice": "no dongle answered the call",
"sweeping": "sweeping {{start}}–{{end}} MHz…",
"claimFailed": "the dongle refused to be claimed — is it in use elsewhere?",
"claimFix": "linux: the kernel driver dvb_usb_rtl28xxu is holding the dongle — `sudo modprobe -r dvb_usb_rtl28xxu` frees it until reboot (a udev rule banishes it for good). windows: swap the driver for WinUSB with Zadig.",
"tryAgain": "try again",
"errorTitle": "the static pushed back",
"scopeLabel": "live radio spectrum from the receiver"
},
"ouija": {
"spelling": "the planchette spells",
"drift": "the planchette drifts, waiting for words — ask below, or summon."
},
"emf": {
"start": "raise the meter",
"stop": "ground the meter",
"calibrate": "calibrate",
"baseline": "resting field",
"listening": "feeling for disturbances in the field…",
"unsupportedTitle": "this vessel carries no field senses",
"unsupported": "a phone in your hand becomes the meter — this vessel has no motion senses to lend. open the veil on a phone, or let the other modes answer.",
"deniedTitle": "the field senses stay shut",
"denied": "this vessel refused its motion senses. grant motion access in your browser settings, then raise the meter again.",
"trueField": "magnetic field"
},
"entity": {
"title": "DOSSIER OF THE PRESENT",
"none": "no spirit summoned yet",
"noneHint": "summon, or feed the veil 3+ anomalies and one will find you",
"isNew": "first recorded contact",
"known": "contact #{{count}}",
"persona": "persona",
"voice": "voice params",
"quotes": "recorded utterances",
"rarity": {
"common": "common",
"uncommon": "uncommon",
"rare": "rare",
"mythic": "mythic"
}
},
"ritual": {
"title": "focus the channel",
"none": "no presence to focus on",
"noneHint": "summon a spirit before attempting the rite",
"begin": "begin the rite",
"retry": "attempt again",
"hold": "hold to focus",
"awaiting": "awaiting the veil's answer…",
"successTitle": "the channel holds — the truth surfaces",
"failureTitle": "the channel collapses",
"failureHint": "the truth stays hidden — the rite may be tried again",
"traits": {
"alignment": "alignment",
"power": "power",
"volatility": "volatility",
"deceptiveness": "deceptiveness"
},
"steps": {
"align": {
"command": "> aligning the wire…",
"locked": "> wire aligned ✓"
},
"breath": {
"command": "> steadying the breath…",
"locked": "> breath steadied ✓"
},
"trace": {
"command": "> tracing the sigil…",
"locked": "> sigil traced ✓"
},
"lock": {
"command": "> locking the channel…",
"locked": "> channel locked ✓"
}
}
},
"judgment": {
"title": "render judgment",
"none": "no presence to judge",
"noneHint": "summon a spirit first",
"trust": "trust",
"banish": "banish",
"crossOver": "cross over",
"test": "test",
"pending": "awaiting the verdict…",
"favor": "favor",
"essence": "essence",
"reward": {
"title": "the bond holds",
"body": "the spirit answers your trust in kind."
},
"escalation": {
"title": "a reckoning",
"body": "wrongly trusted, it turns — the haunting sharpens."
},
"withdrawal": {
"title": "it recoils",
"body": "banished in error, the presence withdraws, wounded."
},
"crossedOver": {
"title": "at peace",
"body": "the spirit lets go, and the line falls quiet."
},
"resisted": {
"title": "it will not go",
"body": "a demon does not cross over — it claws back to stay."
},
"neutral": {
"title": "no verdict rendered",
"body": "a diagnostic pulse only — nothing risked, nothing gained."
}
},
"whisper": {
"title": "THE VESSEL WHISPERS",
"online": "the vessel is awake",
"offline": "the vessel sleeps",
"waiting": "no whisper yet…",
"coldSpot": "❄ a cold spot gathers around the vessel",
"pressureAnomaly": "◎ the air presses close — the atmosphere shifts"
},
"toast": {
"rate_limited": "the veil is thin — slow your knocking",
"veil_crowded": "too many spirits crowd the line"
},
"views": {
"board": "BOARD",
"matrix": "MATRIX",
"graphs": "GRAPHS",
"tuning": "tuning the veil…",
"listening": "listening…",
"graph": {
"anomalies": "anomalies",
"entity": "entity",
"jitter": "jitter"
}
},
"wanderer": {
"note": "your contacts fade with the mist —",
"claim": "claim a name to keep your codex",
"dismiss": "let the mist take it"
},
"camera": {
"previewLabel": "live view from your camera",
"privacy": "nothing leaves this page until you let it look — then one still frame is shown to the presence, and kept by no one.",
"open": "open the lens",
"opening": "opening the lens…",
"close": "close the lens",
"flip": "turn the lens",
"look": "let it look",
"needEntity": "summon a presence first — there is no one here to look.",
"fail": {
"deniedTitle": "the lens stays covered",
"denied": "This vessel refused the camera. Grant camera permission in your browser's site settings, then open the lens again.",
"insecureTitle": "unconsecrated ground",
"insecure": "The dead will not look through an unsecured lens. Open this site by its https address — or over localhost — and try again.",
"absentTitle": "no lens to look through",
"absent": "This vessel has no camera the veil can find. Attach one, or turn to another channel.",
"busyTitle": "the lens is already in use",
"busy": "Another rite already holds the camera. Close whatever else is watching, then open the lens again.",
"unknownTitle": "the lens will not open",
"unknown": "Something refused the camera and would not say what. Try again, or turn to another channel."
}
}
},
"codex": {
"title": "THE CODEX OF THE CONTACTED",
"subtitle": "every spirit ever dragged through the veil, catalogued",
"header": {
"back": "← the threshold",
"title": "THE CODEX",
"subtitle": "every spirit ever contacted, catalogued by the seekers who reached them"
},
"controls": {
"rarity": "filter by rarity",
"sort": "sort the registry"
},
"state": {
"loading": "consulting the registry…",
"empty": "no spirits of this tier have been reached yet"
},
"filterAll": "all rarities",
"rarity": {
"common": "common",
"uncommon": "uncommon",
"rare": "rare",
"mythic": "mythic"
},
"sort": {
"recent": "most recent",
"contacted": "most contacted"
},
"form": {
"wisp": "wisp",
"banshee": "banshee",
"fairy": "fairy",
"shade": "shade"
},
"card": {
"unknownMedium": "unknown medium",
"contacted_one": "contacted {{count}} time",
"contacted_other": "contacted {{count}} times",
"discoveredBy": "discovered by {{name}} · {{date}}"
},
"contacts_one": "1 contact",
"contacts_other": "{{count}} contacts",
"sightings_one": "1 sighting",
"sightings_other": "{{count}} sightings",
"discoveredBy": "first contacted by {{name}}",
"discoveredAnon": "first contact unrecorded",
"discoveredAt": "entered the registry {{date}}",
"persona": "persona",
"quotes": "recorded utterances",
"voice": "voice params",
"detail": {
"loading": "summoning the dossier…",
"notReached": "this spirit has not been reached",
"unknownMedium": "unknown medium",
"stats": {
"sightings": "sightings",
"contacts": "contacts",
"discoveredBy": "discovered by",
"discoveredAt": "entered the registry"
},
"utterances": "known utterances",
"noUtterances": "no utterances recorded yet",
"voiceSignature": "voice signature",
"attemptContact": "attempt contact"
},
"empty": "no spirits match this filter — the registry keeps its secrets",
"back": "← back to the codex",
"notFound": "this spirit is not in the registry"
},
"shop": {
"back": "← the threshold",
"title": "THE RELIQUARY",
"subtitle": "instruments for those who would knock louder",
"flagship": {
"tag": "flagship instrument",
"name": "THE ULTIMATE QUANTUM BOX",
"desc": "An ESP32-P4/C6 handheld séance instrument — thermal camera, EMF whisker array, geophone, spirit-box mic preamp and a little OLED face that stares back. It signs into the same Quantumancy account you already haunt and unlocks the hardware-driven modes of this site: thermal apparition maps, vibration anomaly feeds, calibrated EMF.",
"specs": {
"thermal": "thermal camera",
"emf": "EMF whisker array",
"geophone": "geophone",
"mic": "spirit-box mic preamp",
"oled": "OLED display"
},
"price": "est. $249 — pre-order"
},
"modulesTitle": "MODULES THAT KNOCK",
"unlocksLabel": "unlocks:",
"modules": {
"emfArray": {
"name": "EMF Whisker Array",
"desc": "a bouquet of multi-axis field sensors, sniffing the air for intentions",
"unlocks": "a richer, calibrated FIELD mode",
"price": "est. $59 — pre-order"
},
"geophone": {
"name": "Geophone Puck",
"desc": "hears footsteps in rooms where nobody is standing",
"unlocks": "the vibration anomaly feed",
"price": "est. $49 — pre-order"
},
"spiritMic": {
"name": "Spirit Mic Preamp",
"desc": "lowers the noise floor so the whispering comes through cleaner",
"unlocks": "cleaner EVP captures",
"price": "est. $69 — pre-order"
},
"uvIlluminator": {
"name": "UV/IR Illuminator",
"desc": "paints the dark in spectrums the living were never meant to see",
"unlocks": "night séances + camera work",
"price": "est. $39 — pre-order"
}
},
"interests": {
"quantumBox": "the Ultimate Quantum Box",
"emfArray": "EMF Whisker Array",
"geophone": "Geophone Puck",
"spiritMic": "Spirit Mic Preamp",
"uvIlluminator": "UV/IR Illuminator"
},
"waitlist": {
"title": "THE MANIFEST",
"desc": "leave an address and an obsession — when the first boxes ship, we reach through to the names on this list first",
"emailLabel": "email address",
"emailPlaceholder": "you@somewhere.in.the.static",
"interestLabel": "interest",
"submit": "join the manifest",
"pending": "carving your name…",
"listed": "you are on the manifest — we will reach through when the boxes ship",
"alreadyListed": "you are already on the manifest",
"invalid": "that address does not reach us",
"crowded": "the veil is crowded — try again later",
"error": "something stirred in the static — your name was not carved"
},
"honesty": "the Ultimate Quantum Box is in active development — this is a reservation manifest, no payment is taken. the website works fully without it; hardware only knocks louder."
},
"inventory": {
"back": "← back to the séance",
"pageTitle": "THE SHELF",
"pageSubtitle": "what the veil has left you, and what it will still trade for",
"title": "inventory",
"essenceLabel": "essence",
"unlocksTitle": "unlocks",
"noUnlocks": "nothing unlocked yet",
"ownedTag": "[OWNED]",
"lockedTag": "[LOCKED]",
"priceValue": "{{price}} essence",
"priceUnknown": "price unknown",
"priceEstimate": "(est.)",
"buy": "buy",
"buying": "buying…",
"buyInsufficient": "not enough essence",
"buyError": "the purchase failed",
"itemsTitle": "items",
"itemsLoading": "reading the shelf…",
"itemsEmpty": "nothing collected yet",
"sigil": {
"title": "sigil designer",
"canvasLabel": "circle of anchor points",
"slotLabel": "anchor point {{slot}}",
"coordLabel": "coords",
"hoverHint": "hover an anchor point",
"pointsLabel": "points",
"lastLabel": "last",
"runeLabel": "rune",
"nameLabel": "name",
"namePlaceholder": "untitled sigil",
"save": "inscribe sigil",
"saving": "inscribing…",
"saved": "the sigil is bound",
"error": "the sigil would not hold"
}
},
"devices": {
"back": "← the threshold",
"title": "DEVICE BAY",
"subtitle": "pair hardware to your account and watch its telemetry live",
"pair": {
"title": "PAIR A NEW DEVICE",
"nameLabel": "device name",
"namePlaceholder": "e.g. attic-node-01",
"submit": "pair device",
"pending": "pairing…",
"nameRequired": "name the device before pairing",
"error": "the pairing handshake failed — try again"
},
"token": {
"warning": "copy this now — it cannot be shown again. losing it means re-pairing the device.",
"copy": "copy",
"copied": "copied",
"confirm": "I've saved it — hide token"
},
"dashboard": {
"title": "LIVE FEED",
"empty": "no devices paired yet — pair one above to see it here",
"lastSeen": "last seen",
"lastSeenNever": "never",
"noReadings": "awaiting first reading…",
"connection": {
"connecting": "reaching the feed…",
"open": "feed live",
"unstable": "feed unstable…",
"closed": "feed offline"
},
"coldSpot": {
"callout": "COLD SPOT — {{deviation}} {{unit}} below baseline"
},
"pressureAnomaly": {
"callout": "PRESSURE ANOMALY — {{magnitude}} {{unit}} swing, atmosphere shifting"
},
"disturbance": {
"title": "ATMOSPHERIC DISTURBANCE",
"ariaLabel": "atmospheric disturbance index"
}
}
},
"haunting": {
"whisper": {
"behindYou": "behind you",
"cold": "cold",
"stay": "stay",
"seesYou": "it sees you",
"closer": "closer",
"stillHere": "still here",
"lineListening": "the line is listening",
"dontLook": "don't look",
"beneathStatic": "beneath the static",
"knockTwice": "knock twice"
},
"title": {
"seesYou": "QUANTUMANCY — it sees you",
"behindYou": "QUANTUM▓NCY — behind you",
"listening": "…listening…"
}
},
"ghostLog": {
"title": "GHOST LOG",
"idle": "the veil is quiet — no presence detected",
"waiting": "listening for tells…",
"meterLabel": "evil meter"
},
"profile": {
"back": "← the table",
"title": "YOUR SIGIL",
"subtitle": "how the veil will remember you",
"loading": "reading your sigil…",
"loadError": "your sigil will not resolve — the glass is clouded",
"save": "seal it",
"saving": "sealing…",
"saved": "sealed — the veil has taken it",
"saveError": "the veil refused the change",
"viewPublic": "see what others see",
"rankLine": "{{title}} · rank {{level}}",
"titles": {
"curious": "curious",
"sensitive": "sensitive",
"channeler": "channeler",
"medium": "medium",
"adept": "adept",
"oracle": "oracle"
},
"wanderer": {
"body": "you drift here without a name. wanderers may look, but the veil forgets them at dawn — claim a name and your sigil will hold.",
"claim": "claim a name"
},
"avatar": {
"heading": "your shape",
"note": "no likeness is taken. your sigil is drawn from a form and a colour, the same way the dead are drawn.",
"formLabel": "choose a form",
"hueLabel": "colour · {{hue}}°"
},
"identity": {
"heading": "your name",
"nameLabel": "name others will read",
"nameHint": "leave it blank and the veil will use your true name",
"nameTooLong": "no more than {{max}} characters",
"bioLabel": "a few words on yourself",
"bioCount": "{{remaining}} of {{max}} left",
"bioTooLong": "no more than {{max}} characters"
},
"gender": {
"label": "how you are addressed",
"male": "he",
"female": "she",
"unspecified": "unspoken"
},
"veil": {
"heading": "the veil over you",
"public": "visible to other seekers",
"private": "veiled",
"publicNote": "your sigil, rank and recent contacts can be read by anyone who knows your name.",
"privateNote": "no one will find you on the roll. your séances continue unchanged."
},
"email": {
"label": "a way back (optional)",
"note": "for recovering your account only. never shown on your page, never given to another seeker, never used to reach you otherwise.",
"invalid": "that address will not carry a message"
}
},
"hunters": {
"back": "← the table",
"backToRoster": "← the roll",
"title": "THE ROLL",
"subtitle": "every seeker who left their name on the veil",
"loading": "calling the roll…",
"empty": "no one has left a name yet",
"card": {
"encounters_one": "{{count}} spirit reached",
"encounters_other": "{{count}} spirits reached"
},
"profile": {
"loading": "tracing their sigil…",
"veiledTitle": "no trace of them",
"veiledBody": "the veil holds no record of “{{username}}”. either the name was never claimed, or they have drawn the veil over themselves.",
"progressLabel": "progress toward the next rank",
"progressNext_one": "{{count}} more spirit to rise",
"progressNext_other": "{{count}} more spirits to rise",
"progressMax": "as far as the veil goes",
"encounters": "spirits reached",
"joined": "first crossed",
"recentTitle": "recently reached",
"recentEmpty": "they have reached no one yet",
"sendMessage": "send word"
}
},
"messages": {
"title": "whispers between hunters",
"findHunters": "find hunters",
"emptyInbox": "no one has whispered to you yet. find a hunter whose codex you admire, and say something.",
"emptyThread": "nothing has passed between you yet.",
"needAccount": "whispers need a name. a wanderer's voice does not carry.",
"claimName": "claim a name",
"backToInbox": "back to the whispers",
"noSuchHunter": "no such hunter walks here.",
"placeholder": "speak, and it will carry…",
"send": "send",
"sending": "sending…",
"sendFailed": "the whisper did not carry. try again.",
"guestBlocked": "a wanderer cannot be heard. claim a name and your whispers will carry.",
"youPrefix": "you: ",
"unread": "unread whispers",
"time": {
"now": "just now",
"minutes_one": "{{count}} minute past",
"minutes_other": "{{count}} minutes past",
"hours_one": "{{count}} hour past",
"hours_other": "{{count}} hours past",
"days_one": "{{count}} day past",
"days_other": "{{count}} days past"
}
}
}