Files
redl-gamepanel/resources/[rp]/rp_loading/html/app.css
T
Claude Opus 5andClaude Opus 5 71856b15e9 The server itself: repo now mirrors the machine it runs on, plus install.sh
The repository carried two different servers side by side - the early
`justrp` prototype with its Python auth API, and a snapshot of the real
one under `server-code/`. Only the second one is a server anyone should
start from, so the prototype is gone and the real one moved to the root.

Taken from the live box, so the UI is the finished version (the tokens,
the county-records sheets and the variable fonts landed after the last
snapshot was pushed):

  resources/[rp]/   rp_db, rp_core, rp_session, rp_loading, rp_ui,
                    rp_selftest, rp_dbtest
  bin/              supervise.sh (keep-alive + console FIFO), rcon, init script
  etc/schema.sql    accounts, characters, transactions, inventory, vehicles
  assets/fonts/     the bundled subsets
  docs/SERVER.md    how it is put together, resource by resource

install.sh turns a clean Ubuntu/Debian box into this server in one
command: recommended FXServer build, MariaDB with the schema, resources,
server.cfg + a generated database password, boot entry (systemd or
init.d), then it waits for the Cfx registration. --name/--port/--db-name
let a second server live on the same machine. Tested end to end on a
spare install root: build 25770 fetched, schema applied, boot entry
written, FXServer started and stopped exactly where a wrong licence key
should stop it.

No secrets travel with it: the licence key and the database password live
in data/secrets.cfg on the machine, and .gitignore now names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:23:33 +00:00

326 lines
8.7 KiB
CSS

/* ---------------------------------------------------------------------------
Loading screen.
Layout: the city runs full bleed; the record sits in a rail on the left,
held there by a gradient scrim rather than a panel, so the two never feel
like separate windows.
--------------------------------------------------------------------------- */
#sky, .grain, .scrim, .vignette {
position: fixed;
inset: 0;
pointer-events: none;
}
#sky { width: 100%; height: 100%; display: block; }
.scrim {
background: linear-gradient(
90deg,
rgba(8, 9, 11, 0.97) 0%,
rgba(8, 9, 11, 0.93) 34%,
rgba(8, 9, 11, 0.72) 58%,
rgba(8, 9, 11, 0.15) 82%,
rgba(8, 9, 11, 0) 100%
);
}
.vignette {
background: radial-gradient(120% 90% at 62% 45%, transparent 40%, rgba(0, 0, 0, 0.55) 100%);
}
/* film grain, drawn once as an SVG turbulence tile */
.grain {
opacity: 0.16;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
animation: grainshift 640ms steps(2) infinite;
}
@keyframes grainshift {
0% { transform: translate(0, 0); }
50% { transform: translate(-3px, 2px); }
100% { transform: translate(2px, -2px); }
}
/* --- rail ---------------------------------------------------------------- */
.rail {
position: relative;
z-index: 2;
width: var(--sheet);
height: 100%;
padding: var(--gut-y) var(--gut-x);
display: grid;
grid-template-rows: auto 1fr auto auto;
gap: clamp(20px, 2.4vh, 34px);
}
/* staged entrance; each element declares its own delay */
.reveal {
opacity: 0;
transform: translateY(10px);
animation: rise 900ms var(--ease-out) forwards;
animation-delay: var(--d, 0ms);
}
@keyframes rise {
to { opacity: 1; transform: none; }
}
/* --- file header --------------------------------------------------------- */
.file {
border-top: 1px solid rgba(217,178,60,.42);
padding-top: 14px;
}
.file .serial {
margin-top: 7px;
color: rgba(233,226,204,.62);
}
.file .serial span { color: var(--canary); }
/* --- wordmark ------------------------------------------------------------ */
.brand { align-self: end; }
.wordmark {
font-size: clamp(46px, 5.2vw, 82px);
font-weight: 800;
line-height: 0.86;
letter-spacing: 0.3em;
text-indent: 0.3em; /* keeps the wide tracking optically centred */
opacity: 0;
animation: settle 1500ms var(--ease-out) 180ms forwards;
}
/* the name tightens into place, like a stamp being pressed */
@keyframes settle {
0% { opacity: 0; letter-spacing: 0.3em; text-indent: 0.3em; filter: blur(6px); }
60% { opacity: 1; }
100% { opacity: 1; letter-spacing: -0.028em; text-indent: 0; filter: blur(0); }
}
.tagrow {
display: flex;
align-items: center;
gap: 14px;
margin-top: 16px;
}
.tagline {
font-family: 'Plex Mono', monospace;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.42em;
color: var(--canary);
text-transform: uppercase;
}
.dash {
flex: 1;
height: 1px;
background: linear-gradient(90deg, rgba(217,178,60,.42), transparent);
}
/* --- notices ------------------------------------------------------------- */
.notices { align-self: center; }
.notice-head {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-bottom: 10px;
border-bottom: 1px solid rgba(233,226,204,.14);
}
.notice-index { color: rgba(233,226,204,.40); }
.notice { padding-top: 18px; min-height: 132px; }
.notice-title {
font-size: 21px;
font-weight: 600;
letter-spacing: -0.015em;
margin-bottom: 8px;
}
.notice-body {
font-size: 14.5px;
color: rgba(233,226,204,.62);
max-width: 40ch;
}
.notice.swap .notice-title,
.notice.swap .notice-body { animation: none; }
.notice-title, .notice-body {
animation: noticein 620ms var(--ease-out) both;
}
.notice-body { animation-delay: 70ms; }
@keyframes noticein {
from { opacity: 0; transform: translateY(7px); }
to { opacity: 1; transform: none; }
}
/* --- progress ------------------------------------------------------------ */
.load { align-self: end; }
.loadhead {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 11px;
gap: 16px;
}
.step {
color: rgba(233,226,204,.62);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pct {
font-family: 'Plex Mono', monospace;
font-weight: 500;
font-size: 28px;
line-height: 1;
color: var(--stock-hi);
font-variant-numeric: tabular-nums;
}
.pct i {
font-style: normal;
font-size: 13px;
color: rgba(233,226,204,.40);
margin-left: 2px;
}
.track {
position: relative;
height: 2px;
background: rgba(233,226,204,.14);
overflow: hidden;
}
.bar {
display: block;
height: 100%;
width: 0%;
background: var(--canary);
transition: width 420ms var(--ease);
}
/* a light runs ahead of the fill so the bar reads as active even when the
game stalls on a long step */
.bar::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 64px;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 159, 69, 0.85));
animation: crawl 1900ms linear infinite;
}
@keyframes crawl {
0% { transform: translateX(-64px); opacity: 0; }
35% { opacity: 1; }
100% { transform: translateX(0); opacity: 0; }
}
/* --- volume fader -------------------------------------------------------- */
.fader {
display: flex;
align-items: center;
gap: 14px;
margin-top: 26px;
padding-top: 18px;
border-top: 1px solid rgba(233,226,204,.07);
}
.mute {
width: 30px;
height: 30px;
display: grid;
place-items: center;
background: transparent;
border: 1px solid rgba(233,226,204,.14);
color: rgba(233,226,204,.62);
cursor: pointer;
transition: color 180ms var(--ease), border-color 180ms var(--ease), transform 120ms var(--ease);
}
.mute:hover { color: var(--canary); border-color: rgba(217,178,60,.42); }
.mute:active { transform: translateY(1px); }
.mute.off #wave { opacity: 0.18; }
.mute.off { color: rgba(233,226,204,.40); }
input[type='range'] {
flex: 1;
-webkit-appearance: none;
appearance: none;
height: 22px;
background: transparent;
cursor: pointer;
}
input[type='range']::-webkit-slider-runnable-track {
height: 2px;
background: rgba(233,226,204,.14);
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
width: 3px;
height: 15px;
margin-top: -6.5px;
background: var(--canary);
border: 0;
border-radius: 0;
transition: height 160ms var(--ease), margin-top 160ms var(--ease);
}
input[type='range']:hover::-webkit-slider-thumb,
input[type='range']:focus-visible::-webkit-slider-thumb {
height: 21px;
margin-top: -9.5px;
}
.volval { width: 3ch; text-align: right; color: rgba(233,226,204,.40); }
.audiohint {
margin-top: 10px;
color: var(--canary);
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.45; } }
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
/* Once the world is ready the rail steps aside and reveals the live camera.
The page background has to go transparent too, otherwise the fade reveals
nothing but our own backdrop colour. */
body.handover, body.handover .rail { background: transparent !important; }
body.handover .scrim { transition: opacity 1400ms var(--ease); opacity: 0; }
body.handover #sky { transition: opacity 1400ms var(--ease); opacity: 0; }
body.handover .grain { transition: opacity 1400ms var(--ease); opacity: 0; }
body.handover .rail { transition: opacity 900ms var(--ease), transform 1200ms var(--ease); opacity: 0; transform: translateY(-12px); }
/* ---------------------------------------------------------------------------
The loading screen is the only screen with no paper on it, so the three
voices have to be restated in their light-on-dark values.
--------------------------------------------------------------------------- */
.eyebrow { color: rgba(233,226,204,.42); }
.data { color: rgba(233,226,204,.60); }
body { color: var(--stock-hi); }
/* The posted notice is a real notice: a slip of the same stock the rest of the
product is printed on, taped to the window of a city that is still loading. */
.notice {
background: var(--stock);
color: var(--ink);
padding: 15px 17px 16px;
box-shadow: var(--lift-1);
border-left: 3px solid var(--canary);
}
.notice-head { border-bottom: 1px solid var(--rule); padding-bottom: 7px; margin-bottom: 9px; }
.notice-index { color: var(--ink-3); }
.notice-title { color: var(--ink); }
.notice-body { color: var(--ink-2); }