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>
821 lines
28 KiB
CSS
821 lines
28 KiB
CSS
/* ---------------------------------------------------------------------------
|
|
Los Santos RP - the pre-spawn screens.
|
|
|
|
Every screen is a sheet of paper from the Office of Vital Records, lying on a
|
|
dark desk with the city running behind it. The body stays transparent: the
|
|
game is the room, and the paper is the only thing we draw.
|
|
--------------------------------------------------------------------------- */
|
|
|
|
body {
|
|
background: transparent;
|
|
transition: opacity 420ms var(--ease);
|
|
}
|
|
body.hidden { opacity: 0; pointer-events: none; }
|
|
|
|
/* --- the room -------------------------------------------------------------
|
|
A lamp over the desk on the left, and the dark closing in everywhere else.
|
|
This is what makes an opaque sheet of paper look lit rather than pasted on. */
|
|
.edge {
|
|
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
background:
|
|
radial-gradient(58% 74% at 22% 46%, var(--lamp) 0%, transparent 62%),
|
|
radial-gradient(120% 96% at 26% 50%, transparent 38%, rgba(0,0,0,.58) 100%);
|
|
}
|
|
.grain {
|
|
position: fixed; inset: 0; z-index: 60; pointer-events: none;
|
|
opacity: .10; mix-blend-mode: overlay;
|
|
background-image: var(--fiber);
|
|
background-size: 160px 160px;
|
|
}
|
|
|
|
/* =========================================================================
|
|
persistent chrome: the edge of the desk
|
|
========================================================================= */
|
|
|
|
.topbar {
|
|
position: fixed; top: 0; left: 0; right: 0; z-index: 40;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
gap: 26px;
|
|
padding: 20px var(--gut-x) 26px;
|
|
background: linear-gradient(to bottom, rgba(6,7,5,.80), rgba(6,7,5,0));
|
|
pointer-events: none;
|
|
}
|
|
|
|
.mark { display: flex; align-items: center; gap: 12px; }
|
|
|
|
.seal { width: 32px; height: 32px; flex: none; opacity: .92; }
|
|
.seal-ring { fill: none; stroke: var(--stock-2); stroke-width: 3; }
|
|
.seal-ring.thin { stroke-width: 1.4; opacity: .6; }
|
|
.seal-ticks {
|
|
fill: none; stroke: var(--stock-2); stroke-width: 5; opacity: .5;
|
|
stroke-dasharray: 2 9;
|
|
}
|
|
.seal-star { fill: var(--canary); }
|
|
|
|
.mark-lines { display: flex; flex-direction: column; line-height: 1.1; }
|
|
.mark-name {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 118, 'wght' 800;
|
|
font-size: 15px; letter-spacing: .04em; text-transform: uppercase;
|
|
color: var(--stock-hi);
|
|
}
|
|
.mark-tag {
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 9px; letter-spacing: .3em; text-transform: uppercase;
|
|
color: var(--canary);
|
|
}
|
|
|
|
/* the routing box: three stations, in the order the file moves through them */
|
|
.stages { display: flex; align-items: center; gap: 10px; }
|
|
.stagechip {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
background: none; border: 0; padding: 0; cursor: default;
|
|
}
|
|
.stagechip em {
|
|
font-style: normal; font-size: 9px; line-height: 1;
|
|
width: 21px; height: 19px; display: grid; place-items: center;
|
|
border: 1px solid rgba(222,216,198,.26);
|
|
color: rgba(222,216,198,.45);
|
|
transition: all 320ms var(--ease);
|
|
}
|
|
.stagechip span {
|
|
font-size: 9.5px; letter-spacing: .17em;
|
|
color: rgba(222,216,198,.42);
|
|
transition: color 320ms var(--ease);
|
|
}
|
|
.stagechip.on em {
|
|
background: var(--canary); border-color: var(--canary); color: var(--ink);
|
|
}
|
|
.stagechip.on span { color: var(--stock-hi); }
|
|
.stagechip.done em { color: transparent; border-color: rgba(222,216,198,.4); position: relative; }
|
|
.stagechip.done em::after {
|
|
content: '\00D7';
|
|
position: absolute; inset: 0; display: grid; place-items: center;
|
|
font-size: 13px; color: var(--canary);
|
|
}
|
|
.stagelink { width: 26px; height: 1px; background: rgba(222,216,198,.2); }
|
|
|
|
.filetag { display: flex; align-items: baseline; gap: 8px; font-size: 11px; color: rgba(222,216,198,.55); }
|
|
.filetag-k {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 104, 'wght' 600;
|
|
font-size: 9px; letter-spacing: .19em; text-transform: uppercase;
|
|
color: rgba(222,216,198,.35);
|
|
}
|
|
|
|
/* =========================================================================
|
|
the sheet
|
|
========================================================================= */
|
|
|
|
.screen {
|
|
position: fixed; inset: 0; z-index: 10;
|
|
display: flex; align-items: center;
|
|
padding: 96px var(--gut-x) var(--gut-y);
|
|
}
|
|
.screen[hidden] { display: none; }
|
|
|
|
.rail {
|
|
position: relative;
|
|
width: var(--sheet);
|
|
max-height: 100%;
|
|
display: flex; flex-direction: column;
|
|
background: var(--stock);
|
|
box-shadow: var(--lift-2);
|
|
z-index: 2;
|
|
}
|
|
.rail.wide { width: var(--sheet-w); }
|
|
|
|
/* paper: fibre, a warmer lit edge on the lamp side, and a shaded far edge */
|
|
.rail-scrim {
|
|
position: absolute; inset: 0; z-index: 0; pointer-events: none;
|
|
background-image:
|
|
linear-gradient(to right, transparent 70%, rgba(25,28,24,.10) 100%),
|
|
var(--fiber);
|
|
background-size: auto, auto, 160px 160px;
|
|
opacity: 1;
|
|
mix-blend-mode: normal;
|
|
}
|
|
.rail-scrim::after {
|
|
content: ''; position: absolute; inset: 0;
|
|
background-image: var(--fiber);
|
|
background-size: 150px 150px;
|
|
opacity: .05;
|
|
mix-blend-mode: multiply;
|
|
}
|
|
|
|
.rail::after {
|
|
content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
|
|
background: radial-gradient(78% 62% at 16% 6%, rgba(255,240,196,.55) 0%, rgba(255,240,196,0) 64%);
|
|
mix-blend-mode: soft-light;
|
|
}
|
|
|
|
.rail-inner {
|
|
position: relative; z-index: 1;
|
|
padding: 22px var(--gut-x) var(--gut-y);
|
|
overflow-y: auto; overflow-x: hidden;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--stock-3) transparent;
|
|
}
|
|
.rail-inner::-webkit-scrollbar { width: 4px; }
|
|
.rail-inner::-webkit-scrollbar-thumb { background: var(--stock-3); }
|
|
.rail-inner::-webkit-scrollbar-track { background: transparent; }
|
|
|
|
/* --- letterhead ----------------------------------------------------------- */
|
|
.letterhead {
|
|
position: relative; z-index: 1; flex: none;
|
|
display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
|
|
padding: 20px var(--gut-x) 14px;
|
|
border-bottom: 2.5px solid var(--ink);
|
|
}
|
|
.letterhead::after {
|
|
content: ''; position: absolute; left: var(--gut-x); right: var(--gut-x); bottom: -5px;
|
|
height: 1px; background: var(--ink); opacity: .45;
|
|
}
|
|
.lh-dept { display: flex; flex-direction: column; gap: 2px; }
|
|
.lh-city {
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 9.5px; letter-spacing: .22em; text-transform: uppercase;
|
|
color: var(--ink-3);
|
|
}
|
|
.lh-office {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 116, 'wght' 800;
|
|
font-size: 15px; letter-spacing: .015em; text-transform: uppercase;
|
|
color: var(--ink); line-height: 1.15;
|
|
}
|
|
.lh-form {
|
|
font-size: 10px; letter-spacing: .1em; color: var(--ink-2);
|
|
border: 1px solid var(--rule); padding: 3px 7px; white-space: nowrap;
|
|
}
|
|
|
|
/* --- headings ------------------------------------------------------------- */
|
|
.head {
|
|
font-size: clamp(23px, 2.05vw, 30px);
|
|
font-variation-settings: 'wdth' 108, 'wght' 800;
|
|
letter-spacing: .005em; line-height: 1.06;
|
|
color: var(--ink);
|
|
margin: 12px 0 10px;
|
|
text-wrap: balance;
|
|
}
|
|
.sub {
|
|
font-size: 13.5px; line-height: 1.6; color: var(--ink-2);
|
|
max-width: 46ch; margin-bottom: 20px;
|
|
}
|
|
|
|
.kicker { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
|
|
.hair { flex: 1; height: 1px; background: var(--rule); }
|
|
|
|
/* =========================================================================
|
|
form fields - a printed box with its caption punched through the rule
|
|
========================================================================= */
|
|
|
|
.form { display: block; }
|
|
|
|
.field { position: relative; margin-top: 24px; }
|
|
.field:first-child { margin-top: 8px; }
|
|
|
|
.field label {
|
|
position: absolute; top: -7px; left: 11px; z-index: 2;
|
|
font-size: 9px; letter-spacing: .17em;
|
|
color: var(--ink-2);
|
|
background: var(--stock);
|
|
padding: 0 6px;
|
|
}
|
|
|
|
.field input,
|
|
.field textarea {
|
|
width: 100%; display: block;
|
|
border: 1.4px solid var(--rule-firm);
|
|
background: rgba(255,255,255,.34);
|
|
color: var(--ink);
|
|
font-size: 14px; line-height: 1.4;
|
|
padding: 14px 13px 11px;
|
|
transition: border-color 200ms var(--ease), background 200ms var(--ease);
|
|
}
|
|
.field textarea { min-height: 108px; resize: none; padding-top: 15px; }
|
|
.field input::placeholder,
|
|
.field textarea::placeholder { color: var(--ink-3); opacity: .65; }
|
|
.field input:focus,
|
|
.field textarea:focus {
|
|
outline: none;
|
|
border-color: var(--ink);
|
|
background: rgba(255,255,255,.6);
|
|
}
|
|
|
|
/* the clerk's tick in the margin of the box */
|
|
.field::after {
|
|
content: ''; position: absolute; top: 15px; right: 13px;
|
|
font-size: 14px; line-height: 1; pointer-events: none;
|
|
opacity: 0; transition: opacity 180ms var(--ease);
|
|
}
|
|
.field.good::after {
|
|
content: ''; opacity: 1;
|
|
width: 6px; height: 11px; top: 15px; right: 16px;
|
|
border: 2px solid var(--verdi); border-top: 0; border-left: 0;
|
|
transform: rotate(42deg);
|
|
}
|
|
.field.bad::after { content: '\00D7'; color: var(--stamp); opacity: 1; }
|
|
.field.good input { border-color: var(--verdi); }
|
|
.field.bad input { border-color: var(--stamp); }
|
|
.field.good .hint { color: var(--verdi); }
|
|
.field.bad .hint { color: var(--stamp); }
|
|
|
|
.hint {
|
|
font-size: 11.5px; line-height: 1.45; color: var(--ink-3);
|
|
margin-top: 7px;
|
|
}
|
|
.counter {
|
|
font-size: 10.5px; color: var(--ink-3); margin-top: 7px; text-align: right;
|
|
}
|
|
|
|
.withbtn { position: relative; }
|
|
.withbtn input { padding-right: 68px; }
|
|
.reveal-btn {
|
|
position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
|
|
background: none; border: 0; cursor: pointer;
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 104, 'wght' 600;
|
|
font-size: 9.5px; letter-spacing: .15em; text-transform: uppercase;
|
|
color: var(--ink-2);
|
|
border-bottom: 1px solid var(--rule-firm);
|
|
padding-bottom: 1px;
|
|
}
|
|
.reveal-btn:hover { color: var(--ink); border-bottom-color: var(--ink); }
|
|
|
|
.collapse { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 380ms var(--ease); }
|
|
.collapse.open { grid-template-rows: 1fr; }
|
|
.collapse-inner { overflow: hidden; min-height: 0; }
|
|
|
|
.formerror {
|
|
margin-top: 16px;
|
|
font-family: 'Plex Sans', sans-serif;
|
|
font-size: 12.5px; line-height: 1.45;
|
|
color: var(--stamp);
|
|
border-left: 3px solid var(--stamp);
|
|
background: rgba(126,43,38,.07);
|
|
padding: 9px 12px;
|
|
animation: nudge 380ms var(--ease);
|
|
}
|
|
|
|
/* =========================================================================
|
|
buttons
|
|
========================================================================= */
|
|
|
|
.btn {
|
|
position: relative; overflow: hidden;
|
|
display: inline-flex; align-items: center; justify-content: center; gap: 10px;
|
|
font-size: 11px; letter-spacing: .15em;
|
|
padding: 15px 24px;
|
|
border: 1.4px solid var(--ink);
|
|
background: none; color: var(--ink);
|
|
cursor: pointer;
|
|
transition: color 260ms var(--ease), border-color 260ms var(--ease), opacity 200ms var(--ease);
|
|
}
|
|
.btn::before {
|
|
content: ''; position: absolute; inset: 0; z-index: 0;
|
|
background: var(--canary);
|
|
transform: scaleX(0); transform-origin: left center;
|
|
transition: transform 340ms var(--ease-out);
|
|
}
|
|
.btn:hover:not(:disabled)::before { transform: scaleX(1); }
|
|
.btn-label, .btn-spin { position: relative; z-index: 1; }
|
|
|
|
.btn.primary { background: var(--ink); color: var(--stock-hi); width: 100%; margin-top: 22px; }
|
|
.btn.primary:hover:not(:disabled) { color: var(--ink); }
|
|
|
|
.btn.ghost { background: rgba(255,255,255,.2); }
|
|
.btn.ghost:hover:not(:disabled) { color: var(--ink); }
|
|
|
|
.btn.danger { border-color: var(--stamp); color: var(--stamp); }
|
|
.btn.danger::before { background: var(--stamp); }
|
|
.btn.danger:hover:not(:disabled) { color: var(--stock-hi); }
|
|
|
|
.btn:disabled { opacity: .32; cursor: default; }
|
|
|
|
.btn-spin { display: none; width: 13px; height: 13px; }
|
|
.btn.busy .btn-label { opacity: .35; }
|
|
.btn.busy .btn-spin {
|
|
display: block;
|
|
border: 1.6px solid currentColor; border-top-color: transparent;
|
|
border-radius: 50%;
|
|
animation: spin 720ms linear infinite;
|
|
}
|
|
|
|
.switch { margin-top: 18px; font-size: 12.5px; color: var(--ink-2); }
|
|
.linkbtn {
|
|
background: none; border: 0; cursor: pointer; padding: 0;
|
|
font-family: inherit; font-size: inherit; color: var(--ink);
|
|
border-bottom: 1px solid var(--rule-firm);
|
|
}
|
|
.linkbtn:hover { border-bottom-color: var(--ink); }
|
|
.linkbtn.danger {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 104, 'wght' 600;
|
|
font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
|
|
color: var(--stamp); border-bottom-color: rgba(126,43,38,.4);
|
|
}
|
|
.linkbtn.danger:hover { border-bottom-color: var(--stamp); }
|
|
|
|
/* =========================================================================
|
|
02 / the register of persons - ruled index cards in a drawer
|
|
========================================================================= */
|
|
|
|
.charlist { list-style: none; }
|
|
|
|
.charcard {
|
|
position: relative;
|
|
padding: 15px 18px 14px 52px;
|
|
margin-bottom: 9px;
|
|
background: var(--stock-hi);
|
|
border: 1px solid var(--rule);
|
|
cursor: pointer;
|
|
display: flex; align-items: center; justify-content: space-between; gap: 14px;
|
|
transition: transform 200ms var(--ease), box-shadow 200ms var(--ease), background 200ms var(--ease);
|
|
/* the faint ruling of a real index card */
|
|
background-image: repeating-linear-gradient(
|
|
to bottom, transparent 0 22px, rgba(46,97,85,.075) 22px 23px);
|
|
}
|
|
.charcard:hover { transform: translateY(-1px); box-shadow: 0 6px 14px -8px rgba(0,0,0,.55); }
|
|
|
|
.charslot {
|
|
position: absolute; left: 0; top: 0; bottom: 0; width: 36px;
|
|
display: grid; place-items: center;
|
|
background: var(--stock-2);
|
|
border-right: 1px solid var(--rule);
|
|
font-size: 10.5px; color: var(--ink-2);
|
|
transition: background 200ms var(--ease), color 200ms var(--ease);
|
|
}
|
|
|
|
.charname {
|
|
display: block; font-size: 14.5px; letter-spacing: .045em;
|
|
color: var(--ink); line-height: 1.2;
|
|
}
|
|
.charmeta { display: block; font-size: 10.5px; color: var(--ink-3); margin-top: 4px; }
|
|
.charmoney { font-size: 13px; color: var(--ink); white-space: nowrap; }
|
|
|
|
.charcard.on {
|
|
background-color: #f4f0e4;
|
|
border-color: var(--ink);
|
|
box-shadow: inset 4px 0 0 var(--canary), 0 8px 18px -8px rgba(0,0,0,.6);
|
|
}
|
|
.charcard.on .charslot { background: var(--canary); color: var(--ink); }
|
|
|
|
.charcard.empty {
|
|
background-color: transparent; background-image: none;
|
|
border: 1.4px dashed var(--rule-firm);
|
|
color: var(--ink-2);
|
|
justify-content: flex-start;
|
|
}
|
|
.charcard.empty .charslot { background: transparent; border-right-color: var(--rule-soft); font-size: 15px; }
|
|
.charcard.empty .charname { font-size: 11px; color: var(--ink-2); }
|
|
.charcard.empty:hover { background-color: rgba(255,255,255,.24); }
|
|
|
|
.charactions { display: flex; gap: 10px; align-items: stretch; margin-top: 20px; }
|
|
.charactions .btn.primary { margin-top: 0; }
|
|
.charactions .btn.ghost { width: auto; flex: none; margin-top: 0; }
|
|
|
|
/* =========================================================================
|
|
03 / application for identity
|
|
========================================================================= */
|
|
|
|
.tabs {
|
|
position: relative; z-index: 1; flex: none;
|
|
display: flex; gap: 2px;
|
|
padding: 14px var(--gut-x) 0;
|
|
border-bottom: 1.4px solid var(--rule-firm);
|
|
}
|
|
.tab {
|
|
padding: 8px 11px 7px;
|
|
font-size: 9.5px; letter-spacing: .12em;
|
|
background: var(--stock-2);
|
|
border: 1px solid var(--rule); border-bottom: 0;
|
|
color: var(--ink-2); cursor: pointer;
|
|
margin-bottom: -1.4px;
|
|
transition: background 200ms var(--ease), color 200ms var(--ease);
|
|
}
|
|
.tab:hover { background: var(--stock); color: var(--ink); }
|
|
.tab.is-on {
|
|
background: var(--stock-hi); color: var(--ink);
|
|
border-bottom: 1.4px solid var(--stock-hi);
|
|
}
|
|
|
|
.rail-inner.creator { padding-top: 20px; }
|
|
|
|
.pane { display: none; }
|
|
.pane.is-on {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 18px 20px;
|
|
align-content: start;
|
|
}
|
|
.pane .sub { grid-column: 1 / -1; margin-bottom: 0; }
|
|
.pane .field { grid-column: 1 / -1; }
|
|
|
|
.ctrl { min-width: 0; }
|
|
.ctrl.span2 { grid-column: 1 / -1; }
|
|
.ctrl-head {
|
|
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
|
|
margin-bottom: 7px;
|
|
}
|
|
.ctrl-label { font-size: 9.5px; letter-spacing: .14em; color: var(--ink-2); }
|
|
.ctrl-val { font-size: 10.5px; color: var(--ink-3); white-space: nowrap; }
|
|
|
|
/* a measuring scale with a surveyor's marker riding on it */
|
|
.slider {
|
|
-webkit-appearance: none; appearance: none;
|
|
width: 100%; height: 24px; background: transparent; cursor: pointer;
|
|
}
|
|
.slider::-webkit-slider-runnable-track {
|
|
height: 24px;
|
|
background-image:
|
|
linear-gradient(var(--rule-firm), var(--rule-firm)),
|
|
repeating-linear-gradient(90deg, var(--rule) 0 1px, transparent 1px 10%);
|
|
background-size: 100% 1.4px, 100% 7px;
|
|
background-position: 0 12px, 0 13px;
|
|
background-repeat: no-repeat, repeat-x;
|
|
}
|
|
.slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none; appearance: none;
|
|
width: 11px; height: 18px; margin-top: 3px;
|
|
background: var(--ink); border: 0;
|
|
clip-path: polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%);
|
|
transition: background 160ms var(--ease);
|
|
}
|
|
.slider:hover::-webkit-slider-thumb { background: var(--canary-dp); }
|
|
|
|
.stepper {
|
|
display: grid; grid-template-columns: 32px 1fr 32px;
|
|
border: 1.4px solid var(--rule-firm);
|
|
background: rgba(255,255,255,.34);
|
|
}
|
|
.stepper button {
|
|
border: 0; background: none; height: 32px; cursor: pointer;
|
|
color: var(--ink-2); font-size: 14px; line-height: 1;
|
|
transition: background 160ms var(--ease), color 160ms var(--ease);
|
|
}
|
|
.stepper button:hover { background: var(--ink); color: var(--stock-hi); }
|
|
.stepval {
|
|
display: grid; place-items: center;
|
|
font-size: 11.5px; color: var(--ink);
|
|
border-left: 1px solid var(--rule); border-right: 1px solid var(--rule);
|
|
overflow: hidden; white-space: nowrap;
|
|
}
|
|
|
|
/* a printed colour chart: a fixed grid, so it can never wrap into orphans */
|
|
.swatches {
|
|
display: grid; grid-template-columns: repeat(16, 1fr);
|
|
grid-auto-rows: 17px;
|
|
gap: 1px; padding: 1px;
|
|
background: var(--rule-firm);
|
|
border: 1px solid var(--rule-firm);
|
|
}
|
|
.swatch {
|
|
border: 0; padding: 0; cursor: pointer;
|
|
position: relative; min-width: 0;
|
|
transition: transform 140ms var(--ease);
|
|
}
|
|
.swatch:hover { transform: scale(1.18); z-index: 3; box-shadow: 0 0 0 1px var(--ink); }
|
|
.swatch.on {
|
|
z-index: 4;
|
|
box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px var(--canary);
|
|
}
|
|
|
|
/* the sex field, ticked the way a form is ticked */
|
|
.seg { display: flex; gap: 24px; grid-column: 1 / -1; margin-bottom: 2px; }
|
|
.seg button {
|
|
display: inline-flex; align-items: center; gap: 9px;
|
|
background: none; border: 0; cursor: pointer;
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 108, 'wght' 700;
|
|
font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
|
|
color: var(--ink-2);
|
|
}
|
|
.seg button::before {
|
|
content: ''; width: 15px; height: 15px; flex: none;
|
|
display: grid; place-items: center;
|
|
border: 1.4px solid var(--rule-firm);
|
|
background: rgba(255,255,255,.4);
|
|
font-size: 13px; line-height: 1; color: var(--stamp);
|
|
}
|
|
.seg button.on { color: var(--ink); }
|
|
.seg button.on::before { content: '\00D7'; border-color: var(--ink); }
|
|
|
|
.creator-foot {
|
|
position: relative; z-index: 1; flex: none;
|
|
display: flex; gap: 10px;
|
|
padding: 14px var(--gut-x) 18px;
|
|
border-top: 1px solid var(--rule);
|
|
}
|
|
.creator-foot .btn { margin-top: 0; }
|
|
.creator-foot .btn.ghost { flex: none; width: auto; }
|
|
.creator-foot .btn.primary { flex: 1; }
|
|
|
|
#screen-creator .formerror { margin: 0 var(--gut-x); }
|
|
|
|
/* --- the photograph plate ------------------------------------------------
|
|
The subject stands inside this frame, and the frame is also the drag
|
|
surface, so the thing you aim at is the thing that turns. It is drawn wide
|
|
on purpose: the ped is framed generously rather than pinned to a cut-out. */
|
|
.turntable {
|
|
position: absolute; left: 36%; right: 16%; top: 16%; bottom: 17%;
|
|
z-index: 5; cursor: grab;
|
|
}
|
|
.turntable.dragging { cursor: grabbing; }
|
|
|
|
.plate-corner {
|
|
position: absolute; width: 30px; height: 30px;
|
|
border: 2px solid rgba(233,226,204,.72);
|
|
transition: border-color 260ms var(--ease);
|
|
}
|
|
.plate-corner.tl { left: 0; top: 0; border-right: 0; border-bottom: 0; }
|
|
.plate-corner.tr { right: 0; top: 0; border-left: 0; border-bottom: 0; }
|
|
.plate-corner.bl { left: 0; bottom: 0; border-right: 0; border-top: 0; }
|
|
.plate-corner.br { right: 0; bottom: 0; border-left: 0; border-top: 0; }
|
|
.turntable.dragging .plate-corner { border-color: var(--canary); }
|
|
|
|
.plate-cap {
|
|
position: absolute; left: 0; right: 0; bottom: -28px;
|
|
display: flex; align-items: center; justify-content: space-between; gap: 14px;
|
|
}
|
|
.plate-cap-k {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 104, 'wght' 600;
|
|
font-size: 9px; letter-spacing: .22em; text-transform: uppercase;
|
|
color: rgba(222,216,198,.5);
|
|
}
|
|
.turnhint {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 10px; letter-spacing: .1em;
|
|
color: rgba(222,216,198,.45);
|
|
transition: opacity 200ms var(--ease);
|
|
}
|
|
.turntable.dragging .turnhint { opacity: 0; }
|
|
.turnicon {
|
|
width: 14px; height: 14px; display: block; flex: none;
|
|
border: 1px solid currentColor; border-radius: 50%;
|
|
border-right-color: transparent;
|
|
transform: rotate(-30deg);
|
|
}
|
|
|
|
/* =========================================================================
|
|
04 / notice of placement
|
|
========================================================================= */
|
|
|
|
.spawnlist { list-style: none; border-top: 1px solid var(--rule); }
|
|
|
|
.spawncard {
|
|
position: relative;
|
|
padding: 13px 14px 13px 44px;
|
|
border-bottom: 1px solid var(--rule);
|
|
cursor: pointer;
|
|
transition: background 180ms var(--ease);
|
|
}
|
|
.spawncard:hover { background: rgba(255,255,255,.28); }
|
|
|
|
.spawncard::before {
|
|
content: ''; position: absolute; left: 14px; top: 15px;
|
|
width: 15px; height: 15px;
|
|
display: grid; place-items: center;
|
|
border: 1.4px solid var(--rule-firm);
|
|
background: rgba(255,255,255,.45);
|
|
font-family: 'Plex Sans', sans-serif;
|
|
font-size: 13px; line-height: 1; color: var(--stamp);
|
|
}
|
|
.spawncard.on::before { content: '\00D7'; border-color: var(--ink); }
|
|
.spawncard.on { background: rgba(217,178,60,.13); }
|
|
|
|
.spawnhead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
|
.spawnname { font-size: 12.5px; letter-spacing: .07em; color: var(--ink); }
|
|
.spawnarea {
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 9.5px; letter-spacing: .11em; text-transform: uppercase;
|
|
color: var(--ink-3); white-space: nowrap;
|
|
}
|
|
.spawnblurb { font-size: 12px; line-height: 1.5; color: var(--ink-2); margin-top: 4px; }
|
|
|
|
/* --- the plan sheet ------------------------------------------------------- */
|
|
.chart {
|
|
position: absolute; right: 6%; top: 50%;
|
|
transform: translateY(-50%) rotate(.5deg);
|
|
width: min(33vw, 58vh); aspect-ratio: 1 / 1;
|
|
z-index: 1;
|
|
background: var(--stock);
|
|
box-shadow: var(--lift-2);
|
|
padding: 3%;
|
|
}
|
|
.chart-paper {
|
|
position: absolute; inset: 0; pointer-events: none;
|
|
background-image:
|
|
radial-gradient(80% 66% at 14% 8%, rgba(255,244,206,.26) 0%, rgba(255,244,206,0) 62%),
|
|
var(--fiber);
|
|
background-size: auto, 150px 150px;
|
|
opacity: .9;
|
|
}
|
|
.chart svg { position: relative; width: 100%; height: 100%; display: block; }
|
|
|
|
.chart-frame { fill: none; stroke: var(--rule); stroke-width: 1; }
|
|
.chart-ticks { fill: none; stroke: var(--ink); stroke-width: 2.5; opacity: .55; }
|
|
#landmass { fill: rgba(46,97,85,.07); stroke: var(--ink-2); stroke-width: 1.4; }
|
|
#coastline { fill: none; stroke: var(--ink); stroke-width: 2.6; }
|
|
|
|
.pins { position: absolute; inset: 3%; pointer-events: none; }
|
|
.pin {
|
|
position: absolute; transform: translate(-50%, -50%);
|
|
display: flex; align-items: center; gap: 7px;
|
|
}
|
|
.pin.flip { flex-direction: row-reverse; }
|
|
.pin-mark {
|
|
width: 9px; height: 9px; flex: none;
|
|
background: var(--stock);
|
|
border: 1.6px solid var(--ink);
|
|
transform: rotate(45deg);
|
|
transition: all 220ms var(--ease);
|
|
}
|
|
.pin-label {
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 8.5px; letter-spacing: .12em; text-transform: uppercase;
|
|
color: var(--ink-2); white-space: nowrap;
|
|
}
|
|
.pin.on .pin-mark {
|
|
background: var(--canary); border-color: var(--ink);
|
|
width: 13px; height: 13px;
|
|
box-shadow: 0 0 0 3px rgba(217,178,60,.3);
|
|
}
|
|
.pin.on .pin-label { color: var(--ink); }
|
|
|
|
.titleblock {
|
|
position: absolute; right: 3%; bottom: 3%;
|
|
border: 1.4px solid var(--ink);
|
|
background: var(--stock-hi);
|
|
min-width: 44%;
|
|
}
|
|
.tb-row { display: flex; }
|
|
.tb-row + .tb-row { border-top: 1px solid var(--rule); }
|
|
.tb-k, .tb-v { padding: 4px 7px; font-size: 8px; line-height: 1.3; }
|
|
.tb-k {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 100, 'wght' 600;
|
|
letter-spacing: .14em; text-transform: uppercase;
|
|
color: var(--ink-3);
|
|
border-right: 1px solid var(--rule);
|
|
flex: none;
|
|
}
|
|
.tb-v {
|
|
font-family: 'Plex Mono', monospace;
|
|
color: var(--ink); flex: 1;
|
|
letter-spacing: .04em;
|
|
}
|
|
.tb-row + .tb-row .tb-k { border-left: 1px solid var(--rule); }
|
|
.tb-row + .tb-row .tb-k:first-child { border-left: 0; }
|
|
.tb-main .tb-v {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 112, 'wght' 700;
|
|
font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
|
|
}
|
|
|
|
/* =========================================================================
|
|
the stamp - the one bold moment, and the only thing that moves loudly
|
|
========================================================================= */
|
|
|
|
.stamp {
|
|
position: fixed; left: 17%; top: 53%; z-index: 50;
|
|
transform: translate(-50%, -50%) rotate(-8deg) scale(1);
|
|
display: none;
|
|
flex-direction: column; align-items: center; gap: 3px;
|
|
padding: 13px 30px 11px;
|
|
border: 4px solid var(--stamp);
|
|
color: var(--stamp);
|
|
pointer-events: none;
|
|
/* real rubber never inks evenly */
|
|
-webkit-mask-image: var(--inkmask);
|
|
mask-image: var(--inkmask);
|
|
-webkit-mask-size: 100% 100%;
|
|
mask-size: 100% 100%;
|
|
}
|
|
.stamp::before {
|
|
content: ''; position: absolute; inset: 4px;
|
|
border: 1.4px solid var(--stamp);
|
|
}
|
|
.stamp-word {
|
|
font-family: 'Archivo', sans-serif;
|
|
font-variation-settings: 'wdth' 118, 'wght' 800;
|
|
font-size: 37px; letter-spacing: .1em; text-transform: uppercase;
|
|
line-height: 1;
|
|
}
|
|
.stamp-sub {
|
|
font-family: 'Plex Mono', monospace;
|
|
font-size: 8.5px; letter-spacing: .2em; text-transform: uppercase;
|
|
opacity: 1;
|
|
}
|
|
.stamp.show { display: flex; animation: stampdown 1700ms var(--ease-out) forwards; }
|
|
|
|
@keyframes stampdown {
|
|
0% { opacity: 0; transform: translate(-50%,-50%) rotate(-8deg) scale(2.3); filter: blur(4px); }
|
|
22% { opacity: 1; transform: translate(-50%,-50%) rotate(-8deg) scale(.93); filter: blur(0); }
|
|
32% { transform: translate(-50%,-50%) rotate(-8deg) scale(1.03); }
|
|
42% { transform: translate(-50%,-50%) rotate(-8deg) scale(1); }
|
|
78% { opacity: 1; }
|
|
100% { opacity: 0; transform: translate(-50%,-50%) rotate(-8deg) scale(1); }
|
|
}
|
|
|
|
/* the desk takes the hit when the stamp lands */
|
|
body.struck .rail, body.struck .chart { animation: jolt 260ms var(--ease); }
|
|
@keyframes jolt {
|
|
0% { transform: translate(0,0); }
|
|
18% { transform: translate(-1px, 2px); }
|
|
46% { transform: translate(1px, -1px); }
|
|
100% { transform: translate(0,0); }
|
|
}
|
|
/* the plan sheet keeps its rotation while it shakes */
|
|
body.struck .chart { animation-name: joltchart; }
|
|
@keyframes joltchart {
|
|
0% { transform: translateY(-50%) rotate(.5deg); }
|
|
18% { transform: translate(-1px, calc(-50% + 2px)) rotate(.5deg); }
|
|
46% { transform: translate(1px, calc(-50% - 1px)) rotate(.5deg); }
|
|
100% { transform: translateY(-50%) rotate(.5deg); }
|
|
}
|
|
|
|
/* =========================================================================
|
|
entrances - one orchestrated move per screen, not seven scattered ones
|
|
========================================================================= */
|
|
|
|
.screen.is-on .rail { animation: sheetdown 520ms var(--ease-out) both; }
|
|
.screen.is-on .chart { animation: chartin 620ms var(--ease-out) 120ms both; }
|
|
.screen.is-on .turntable { animation: platein 700ms var(--ease-out) 180ms both; }
|
|
.screen.is-on .rail-inner,
|
|
.screen.is-on .creator-foot { animation: settle 520ms var(--ease-out) 160ms both; }
|
|
|
|
@keyframes sheetdown {
|
|
from { opacity: 0; transform: translateY(-14px) scale(.995); }
|
|
to { opacity: 1; transform: none; }
|
|
}
|
|
@keyframes settle {
|
|
from { opacity: 0; transform: translateY(6px); }
|
|
to { opacity: 1; transform: none; }
|
|
}
|
|
@keyframes chartin {
|
|
from { opacity: 0; transform: translateY(-50%) rotate(.5deg) scale(.97); }
|
|
to { opacity: 1; transform: translateY(-50%) rotate(.5deg) scale(1); }
|
|
}
|
|
@keyframes platein {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
@keyframes nudge {
|
|
0% { transform: translateX(0); }
|
|
22% { transform: translateX(-5px); }
|
|
44% { transform: translateX(4px); }
|
|
68% { transform: translateX(-2px); }
|
|
100% { transform: translateX(0); }
|
|
}
|
|
|
|
/* Small screens: the sheet takes the width, the plate and plan sheet stand down. */
|
|
@media (max-width: 1100px) {
|
|
.rail, .rail.wide { width: min(92vw, 560px); }
|
|
.chart { display: none; }
|
|
.turntable { left: auto; right: 4%; width: 34%; }
|
|
}
|