Part 1 final build: Los Santos RP written on camera by the agent

rp_core (sessions, scrypt auth, self-written MySQL-wire driver rp_db),
county-records NUI: residency-file auth, identity-record character
intake, survey-grid spawn, procedural night-city loading screen.
Secrets replaced with .example files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 19:27:48 +00:00
co-authored by Claude Fable 5
parent cb0649016c
commit 80c1d75d2f
41 changed files with 6801 additions and 0 deletions
@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Los Santos RP</title>
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="app.css">
</head>
<body class="hidden">
<div class="edge" aria-hidden="true"></div>
<div class="grain" aria-hidden="true"></div>
<!-- ======================= persistent chrome ======================= -->
<header class="topbar">
<div class="mark">
<span class="mark-name" id="markName">LOS SANTOS</span>
<span class="mark-tag" id="markTag">ROLEPLAY</span>
</div>
<nav class="stages" aria-label="Application progress">
<button class="stagechip" data-stage="auth" type="button" disabled>
<em>01</em><span>Credentials</span>
</button>
<i class="stagelink" aria-hidden="true"></i>
<button class="stagechip" data-stage="characters" type="button" disabled>
<em>02</em><span>Identity</span>
</button>
<i class="stagelink" aria-hidden="true"></i>
<button class="stagechip" data-stage="spawn" type="button" disabled>
<em>03</em><span>Placement</span>
</button>
</nav>
<div class="filetag data">
FILE <span id="fileSerial">0000-000000</span>
</div>
</header>
<!-- ======================= 02 / auth ======================= -->
<section class="screen" id="screen-auth" hidden>
<div class="rail">
<div class="rail-scrim" aria-hidden="true"></div>
<div class="rail-inner">
<div class="kicker">
<span class="eyebrow">Office of Records</span>
<span class="hair" aria-hidden="true"></span>
</div>
<h1 class="head" id="authTitle">Open a file</h1>
<p class="sub" id="authSub">
Everything you do in this city is recorded against this file. Choose a name you will
remember and a password you will not share.
</p>
<form class="form" id="authForm" novalidate>
<div class="field" data-field="username">
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username"
spellcheck="false" maxlength="20" placeholder="e.g. dlarue">
<p class="hint" id="hint-username">3&ndash;20 characters. Letters, numbers and underscore.</p>
</div>
<div class="field" data-field="password">
<label for="password">Password</label>
<div class="withbtn">
<input id="password" name="password" type="password" autocomplete="current-password"
maxlength="72" placeholder="At least 8 characters">
<button class="reveal-btn" id="revealPw" type="button" aria-label="Show password">Show</button>
</div>
<p class="hint" id="hint-password">At least 8 characters, with a letter and a number.</p>
</div>
<!-- only present while registering; height animates, nothing reloads -->
<div class="collapse" id="confirmWrap" aria-hidden="true">
<div class="collapse-inner">
<div class="field" data-field="confirm">
<label for="confirm">Repeat password</label>
<input id="confirm" name="confirm" type="password" autocomplete="new-password"
maxlength="72" placeholder="Type it once more">
<p class="hint" id="hint-confirm">Both entries must match.</p>
</div>
</div>
</div>
<p class="formerror" id="authError" role="alert" aria-live="assertive" hidden></p>
<button class="btn primary" id="authSubmit" type="submit">
<span class="btn-label">Open the file</span>
<span class="btn-spin" aria-hidden="true"></span>
</button>
</form>
<p class="switch">
<span id="switchText">Already have a file?</span>
<button class="linkbtn" id="switchMode" type="button">Sign in instead</button>
</p>
</div>
</div>
</section>
<!-- ======================= 03 / characters ======================= -->
<section class="screen" id="screen-characters" hidden>
<div class="rail wide">
<div class="rail-scrim" aria-hidden="true"></div>
<div class="rail-inner">
<div class="kicker">
<span class="eyebrow">Registered identities</span>
<span class="hair" aria-hidden="true"></span>
<span class="data" id="charCount">0 / 3</span>
</div>
<h1 class="head">Who are you today?</h1>
<ul class="charlist" id="charList"></ul>
<div class="charactions">
<button class="btn primary" id="charEnter" type="button" disabled>
<span class="btn-label">Enter the city</span>
<span class="btn-spin" aria-hidden="true"></span>
</button>
<button class="btn ghost" id="charDelete" type="button" disabled>Delete</button>
</div>
<p class="formerror" id="charError" role="alert" aria-live="assertive" hidden></p>
</div>
</div>
</section>
<!-- ======================= 04 / creator ======================= -->
<section class="screen" id="screen-creator" hidden>
<div class="rail wide">
<div class="rail-scrim" aria-hidden="true"></div>
<div class="rail-inner creator">
<div class="kicker">
<span class="eyebrow">New identity</span>
<span class="hair" aria-hidden="true"></span>
<button class="linkbtn" id="creatorCancel" type="button">Discard</button>
</div>
<nav class="tabs" id="creatorTabs" aria-label="Editor sections">
<button class="tab is-on" data-section="heritage" type="button">Heritage</button>
<button class="tab" data-section="face" type="button">Face</button>
<button class="tab" data-section="hair" type="button">Hair</button>
<button class="tab" data-section="body" type="button">Body</button>
<button class="tab" data-section="clothing" type="button">Clothing</button>
<button class="tab" data-section="identity" type="button">Identity</button>
</nav>
<div class="panes" id="creatorPanes">
<div class="pane is-on" data-section="heritage"></div>
<div class="pane" data-section="face"></div>
<div class="pane" data-section="hair"></div>
<div class="pane" data-section="body"></div>
<div class="pane" data-section="clothing"></div>
<div class="pane" data-section="identity"></div>
</div>
<div class="creator-foot">
<button class="btn ghost" id="creatorPrev" type="button">Back</button>
<button class="btn primary" id="creatorNext" type="button">
<span class="btn-label">Next</span>
<span class="btn-spin" aria-hidden="true"></span>
</button>
</div>
<p class="formerror" id="creatorError" role="alert" aria-live="assertive" hidden></p>
</div>
</div>
<div class="turntable" id="turntable" title="Drag to turn">
<div class="turnhint data"><span class="turnicon" aria-hidden="true"></span>Drag to turn</div>
</div>
</section>
<!-- ======================= 05 / spawn ======================= -->
<section class="screen" id="screen-spawn" hidden>
<div class="rail">
<div class="rail-scrim" aria-hidden="true"></div>
<div class="rail-inner">
<div class="kicker">
<span class="eyebrow">Placement</span>
<span class="hair" aria-hidden="true"></span>
</div>
<h1 class="head">Where does the day start?</h1>
<p class="sub" id="spawnSub">Pick a district. You can move once you are on the ground.</p>
<ul class="spawnlist" id="spawnList"></ul>
<button class="btn primary" id="spawnConfirm" type="button" disabled>
<span class="btn-label">Place me here</span>
<span class="btn-spin" aria-hidden="true"></span>
</button>
<p class="formerror" id="spawnError" role="alert" aria-live="assertive" hidden></p>
</div>
</div>
<!-- survey chart: a simplified plan of the county, not a screenshot -->
<div class="chart" id="chart">
<svg viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
<defs>
<pattern id="grid" width="50" height="50" patternUnits="userSpaceOnUse">
<path d="M50 0 L0 0 0 50" fill="none" stroke="rgba(232,228,220,.055)" stroke-width="1"/>
</pattern>
</defs>
<rect width="1000" height="1000" fill="url(#grid)"/>
<!-- chart furniture: this is a survey sheet, not a screenshot -->
<rect class="chart-frame" x="34" y="34" width="932" height="932"/>
<path class="chart-ticks"
d="M34 96 L34 34 L96 34 M904 34 L966 34 L966 96
M966 904 L966 966 L904 966 M96 966 L34 966 L34 904"/>
<text class="chart-label" x="34" y="1000">Los Santos County</text>
<path id="landmass"
d="M318 62 L470 44 L604 74 L712 66 L806 128 L858 248 L872 372
L842 468 L868 556 L826 664 L742 742 L648 812 L560 872
L462 918 L392 902 L342 836 L296 742 L262 640 L240 528
L206 424 L196 320 L232 196 L272 108 Z"
fill="rgba(255,159,69,.04)" stroke="rgba(255,159,69,.30)" stroke-width="1.5"/>
<!-- the western coastline is the one edge worth drawing emphatically -->
<path id="coastline"
d="M262 640 L240 528 L206 424 L196 320 L232 196 L272 108 L318 62"
fill="none" stroke="rgba(255,159,69,.58)" stroke-width="2.5"/>
</svg>
<div class="pins" id="pins"></div>
</div>
</section>
<!-- the one bold moment: the city stamps your file -->
<div class="stamp" id="stamp" aria-hidden="true"><span id="stampText">FILED</span></div>
<script src="app.js"></script>
</body>
</html>