31 Commits
Author SHA1 Message Date
fivemservertest 146c6592aa docs: 0.2 — the release notes and the new screenshots
Thirty commits since 0.1. The README now leads with what this version
adds rather than what the first one was, and carries shots of the panels
that did not exist then: inventory, chat with notifications, the
dealership, a garage, the berry buyer.

The limits section is the honest half. Most of 0.2 has been verified by
rendering the interfaces and reading the logs rather than by playing it
with several people, and placement in the world is the part most likely
to be wrong — which is why /coords shipped alongside it.
2026-09-01 17:05:08 +00:00
fivemservertest 6464bff749 chore: drop the chat's leftover scrollbar.js
Removed from the manifest and the markup when the chat's scrollbar went,
but the file itself stayed in the tree — it was never loaded, so nothing
pointed at it.

It survived because of how these changes reach the repository: a tar
extracted over the clone, which adds and overwrites but never deletes.
A file removed on the server therefore stays in git until something goes
looking for it.
2026-09-01 16:51:14 +00:00
fivemservertest 7367926afe fix: robbery refused people who had never been robbed
"They have nothing left on them" was landing on passers-by nobody had
touched. The per-victim cooldown is keyed on a network id, and the game
recycles those constantly as ambient peds come and go — over a half hour
window a fresh passer-by inherits the id of someone already robbed and
picks up their cooldown with it.

Three minutes now: short enough to outlive the id, long enough to stop
turning out the pockets of the man still standing in front of you. The
message says what it actually means, too.

An unnetworked ped has no id at all, and that case was returning in
silence — the search played out and then nothing happened, with no way to
tell whether it had worked. It now goes through on the player's cooldown
alone, which is the one that matters for whether a gun is an income.
2026-09-01 16:43:28 +00:00
fivemservertest 60d03b91f0 feat: weapons as inventory items
The robbery marker only appears while the character is armed, and there
was no way to be armed: the inventory had no weapons in it, and a gun
handed out by any other means is a gun nobody owns.

fiverp-weapons registers three — pistol, combat pistol, knife — through
the inventory's registerItems, so they belong to this resource rather than
to the registry file.

The bag is the truth and the ped is made to match it. Every inventory
change re-syncs, and the sync clears the ped first: working out the
difference would be cheaper but would also leave behind anything the world
handed over, and a weapon that survives a respawn is a weapon with no row
behind it. Dying strips a ped, so the set is put back once the character
is up again — the bag did not change, and being disarmed by respawning is
not something the player did.

Using one only decides which is in hand; carrying it is what the row does.
2026-09-01 16:31:17 +00:00
fivemservertest 8971a629cd fix: move the dealership outdoors; add /coords
FiveM does not stream the Premium Deluxe interior. The building shell is
there and behind its windows is grey nothing, so a showroom placed inside
it was a showroom nobody could walk into.

It is on the forecourt now, which is real map. The display car, the
delivery spot and the camera are all snapped to the ground they actually
find rather than to the height in the config, so those numbers only have
to be close — the forecourt is not level. The camera is also aimed a beat
after the panel opens, at the car as it stands rather than at where the
config guessed it would be, because until the first car spawns there is
nothing to aim at.

Adds /coords. Placing anything in this world means picking coordinates,
and I have been picking them from screenshots — which is how the seller
ended up in a bank's back office and a dealership inside a wall. It prints
the standing position in the form the config files take, on screen and to
the server console, so a spot that is off by a few metres can be corrected
with the actual number instead of another guess.
2026-09-01 16:27:32 +00:00
fivemservertest 4eb256cec2 feat: vehicle dealership with ownership and garages, and street robbery
Two mechanics.

fiverp-vehicles. Pick a car at Premium Deluxe and it appears on the
turntable in the colour you chose before any money moves — the panel keeps
the middle of the screen clear and takes no scrim, because the car is the
thing being sold and dimming the room to read about it is the wrong way
round. Buying pays from the bank, issues a plate checked for uniqueness
against the table rather than trusted to chance, and writes the row before
the car is handed over: one that exists in the world and not in the table
disappears at the next restart.

The car is driven out of the showroom door rather than delivered through a
wall, and a garage puts one on the nearest road node instead of at the
attendant's feet — a garage that drops a car into a wall is worse than one
that makes you walk five metres.

The price, the paint list and who owns what are all read server-side. A
colour that was never offered is not a colour anyone chose. `stored` is
the whole garage model: a car is in one or it is in the world, and
everything is put back on disconnect and on restart, because nothing
survives either.

fiverp-robbery. Draw a gun and the nearest passer-by wears a marker;
holding it puts their hands up and searches them. The marker is attached
to the entity rather than to a point, so a hold-up on somebody who has
strolled off is not a hold-up, and it re-checks the weapon every frame
through canUse. Police peds are excluded.

The payout and both cooldowns are the server's: ninety seconds per player,
because otherwise a gun is an income rather than a threat, and half an
hour per victim, because a person has one wallet. The ped is claimed
before the money is paid so two requests in one tick cannot both collect,
and the ped table is swept, since net ids are reused within a session.

The request is sent after the animation rather than before it. The search
is what the moment is; a payout that lands first makes it decoration.
2026-09-01 16:20:32 +00:00
fivemservertest 0d2306ffb6 fix: every bush vanished, crate-sized drops, blank drag ghost, wear on click
The berry patches emptied completely. GetGroundZAndNormalFor_3dCoord does
not return the same shape on every build, and reading .z off what came
back threw — inside the spawn loop, which took the thread down and with it
every bush on the server. The slope check is guarded now, and each bush is
placed inside its own pcall so one bad spot cannot cost the other forty
nine. A check that can silently remove the feature it guards is worse than
no check at all.

Dropped items came out as a shipping crate taller than the player. The
fallbacks are reordered to small containers and, more usefully,
size-checked: GetModelDimensions gives the bounding box, so a model
nothing like the size of a dropped object is rejected rather than
discovered in a screenshot. The item's own model is still taken as
authored — that one is content's to get right, the fallback is ours.

The drag ghost was an empty box once items had pictures: it was still
rendering initials as text, and the slot it came from is dimmed, so
nothing on screen said what was in hand. It carries the picture now.

Right-clicking clothing closed the inventory and did nothing. Wearing
something is not the same as using it: clothing now goes on or comes off
where it lies and the panel stays open, since the next thing you do is
usually to the slot beside it. Food still animates and closes. The
destination is derived from the item's own wear slot, so there is nothing
for a client to choose; whatever was already worn swaps into the vacated
bag slot rather than being destroyed.
2026-09-01 15:57:39 +00:00
fivemservertest 83355741c3 feat: artwork for every item currently in the registry
Ten icons, 256×256 RGBA, named after the item ids they belong to. Berries
and shears take one each; the five worn slots share one picture per slot
rather than per garment, because the registry stores which slot a thing
belongs in and not what it looks like — a jacket and a shirt are both
worn_top and therefore both draw the same icon. Giving them separate
artwork would mean keying pictures off the drawable, which is a different
piece of work.

The initials fallback stays for anything added later without a picture.
2026-09-01 15:44:47 +00:00
fivemservertest 67e42f7649 feat: dropped items are visible, eating is one at a time, first item artwork
A dropped item left nothing behind but its interact marker, which tells
you something is there and nothing about what. Items now fall back to a
box when they have no model of their own — and when the model they name
turns out not to exist, which is checked with IsModelInCdimage rather than
discovered by a silent failure to spawn.

Eating a stack ate the whole stack. The clicks came faster than the
animation, so twenty berries went down in one mouthful and the hunger bar
jumped as if the food were free. The gate is on the server, because the
client is where fast clicks come from: one mouthful holds for the length
of the animation and the rest are refused with a word about it. The
animation is told the same duration, so what you watch and what the server
enforces are the same number.

Using anything also closes the inventory now. Using something is doing
something, and doing it happens in the world — it also removes the way to
press one slot twenty times while the first press is still playing out.

Artwork is wired up: html/images, listed in the manifest so it reaches
clients, with the item's `image` field naming the file and the initials
still standing in for anything without one. Shears have theirs.
2026-09-01 15:40:25 +00:00
fivemservertest 952e5c95a0 feat: notifications resource; chat lightened; bushes stop landing in trees
Adds fiverp-notify and moves every server-to-player message onto it —
berries, banking, staff commands. A toast is read once and forgotten,
which is the wrong shape for a chat log and the reason the log kept
filling with things nobody said. Three kinds, and only `good` spends the
accent: an accent on every message means nothing.

Chat is translucent now. A line sits over the world for a quarter of a
minute at a time, and at the full card fill it read as a panel someone had
opened rather than as someone talking. It is also narrower and shorter,
and its scrollbar is gone entirely — the log pins itself to the bottom, so
the bar was a control nobody needed over a view of the world.

Bushes were still landing in trees. The height-above-patch check cannot
catch a canopy that hangs close to the patch's own level, and the real
problem was upstream: GetGroundZFor_3dCoord answers with the first solid
thing below the height it is given, and a branch is solid. Probing from
high lands on the canopy; probing from low answers nothing while the world
streams in.

So it now descends: find a surface, then keep asking what is below it
until nothing is, and what remains is terrain. Slope is read from the
ground normal rather than from four more probes — a flat floor points
straight up — with the four-point sample kept as a second opinion, since
a normal is one point and a bush can sit on a level rock at the top of a
drop.
2026-09-01 15:24:09 +00:00
fivemservertest 514f988819 fix: stuck marker, unwearable clothes, bushes on cliffs, chat that never left
Six reports, five distinct causes.

The interact marker pinned itself to the screen after cutting a bush.
RemovePoint cleared `active` but told the NUI nothing, so the dial stayed
drawn at its last position with no loop left to move or hide it. It now
sends the hide when the point under the cursor is the one going away.

Worn clothes showed as equipped while the body stayed bare. Three of the
four rows in the database had meta IS NULL — written before meta survived
a drop, and a garment without its drawable describes no actual clothing.
They are discarded on load rather than kept as an item that cannot be put
on, and applyWorn now leaves a slot alone when its row is unreadable
instead of stripping it: acting on a row we cannot read is how the clothes
vanished with no visible reason in the first place.

The berry buyer stood in the Paleto bank's back office. The coordinate was
picked by offsetting from the branch, which established it was on solid
ground and nothing about whether there were walls around it. He is out on
the Grapeseed farm road now.

Bushes were on cliff faces and up in trees. Two fixes: the patches moved
to the farm flats north-east of the Alamo Sea, and placement now refuses
bad ground on its own — a spot more than 18 m above the patch height was a
branch rather than soil, and one with more than 1.4 m of drop across two
metres is somewhere you cannot stand to cut. Rejected spots are remembered
so the sweep stops re-probing them, and the server generates 60% more
candidates than it wants bushes so a thinned patch is still a patch.

Chat piled up and kept its scrollbar. Lines now retire individually after
25 seconds with the chat closed, rather than the whole log fading at once
and leaving a busy minute stacked on screen; opening the chat brings the
history back. The scrollbar is appended to the body, so nothing about its
host fading reached it — it now walks its ancestors and hides with them.
2026-09-01 15:07:36 +00:00
fivemservertest 272510f8c0 fix: pause menu, blip grouping, floating bushes; add staff commands
The HUD stayed on screen over the pause menu. NUI is a layer the game does
not own, so it keeps drawing while everything else stops; it now stands
down on IsPauseMenuActive and comes back after.

Eight banks and four berry patches each took their own line in the map
legend. The map groups blips by sprite *and* name, so giving them one name
each collapses them to a single entry you can page through — the branch is
still named on the marker you walk up to.

The berry buyer had no blip at all: sprite 500 is outside the vanilla
range and renders as nothing. It is a shop now, and separate from the
patches by design, since the two are different errands.

Bushes hung in the air. A single ground probe from a guessed height
answers from whatever sits directly under that altitude — in a forest as
often a branch as soil — and answers nothing while the world there is
still streaming. It now sweeps down from several heights after requesting
collision, and a bush whose ground cannot be found is left for the next
pass rather than planted somewhere merely plausible.

Adds fiverp-admin: /revive, /noclip and /tpm, each gated on fiverp.admin
and checked on the server. The client half holds no permission — it only
runs what the server sent. To anyone without the ace they answer "Unknown
command", because a refusal that names the thing refused is a list of
staff commands for anyone who guesses.

/tpm and leaving noclip use the same downward sweep as the bushes: a
waypoint carries no height, and the world does not stream around an entity
that is not colliding with it.
2026-09-01 14:54:19 +00:00
fivemservertest 37648feafd feat: berry picking — shears, patches, a buyer, and something to eat
Buy shears from the buyer in Paleto, cut berries in the hills, eat them or
sell them back. Four kinds, weighted so the money is in walking further:
blackberries at $12 up to cloudberries at $130.

Shears are a gate rather than a nicety. Cutting the cluster leaves the
bush alive; tearing it off does not, which is the reason the job has a
tool at all and why no bush answers without one.

The server owns the bushes. It places them once from a fixed seed — random
placement would move every bush on every restart, and a patch nobody can
learn is noise rather than a place — and answers cuts by re-measuring the
distance, checking the cooldown and rolling the yield itself. A bush is
claimed before anything can fail, so two people cutting the same one in
the same second cannot both be paid. Cooldowns are announced to everyone,
so a stripped bush goes quiet for all of them and not only the cutter.

Distance is compared flat. The client snaps each bush to the ground it
finds and the server only stores its patch's height, so a 3D comparison
would refuse someone standing right in front of it.

Items live here, not in the inventory's registry: fiverp-inventory gained
a registerItems export and now pushes the merged registry to clients. A
job's berries belong to the job. That also fixes the client only ever
seeing its own half of the registry, which would have left a dropped berry
with no prop.

Buying shears refunds if the bag turns out to be full, rather than taking
the money and handing over nothing.
2026-09-01 14:40:17 +00:00
fivemservertest b1bccda4a8 fix: dropped clothing forgot which garment it was
Take a jacket off, drop it, pick it back up, put it on — the slot filled
but the body did not change.

The drop record carried item and count and left meta behind, so the jacket
came back as a jacket that no longer remembered its drawable. applyWorn
then found a row with no meta, read that as an empty slot, and wrote bare
skin — which is exactly what the inventory and the character disagreeing
looks like.

Meta now travels with the drop and is handed back on pickup. addTo takes
it too, and refuses to merge an item that has any: meta is what makes one
instance different from another, so stacking two would keep one drawable
and silently destroy the other. giveItem goes through the same path
instead of assigning meta afterwards, which only ever worked when the item
happened to land alone in a slot.
2026-09-01 14:30:16 +00:00
fivemservertest 63e571196f feat: worn slots drive the character's actual clothes
The five worn slots were empty on a fully dressed character, because what
the creator put on them are ped components and the inventory only knew
about items. The two are now connected in both directions.

A moment after spawning, the client reads the drawables off the ped and
offers them; the server adopts them into any worn slot that is still
empty. Only the client can read a ped, so that is where the reading
happens — but the rows become the truth immediately, and re-adopting is
skipped for a filled slot, which is what stops a shirt the player took off
reappearing on the next respawn.

From then on the body follows the rows: every state push writes all five
components, filled or not. An empty slot has to actively become bare skin,
or taking a shirt off would change a row and nothing else. "Bare" is the
game's own undressed freemode drawables, per gender, so stripping leaves
you in underwear rather than in a hole where a torso was.

Two details that would otherwise show: the arms drawable belongs to the
torso and is carried with it, since leaving it behind puts sleeves on a
bare chest; and a component already sitting on its bare value is not
adopted, or every character would start out owning an "Undershirt" that is
actually their skin.

This needed five entries in the registry — one carrier per slot, holding
the drawable in its meta. They are the mechanism rather than content: with
an empty registry nothing may sit in a worn slot at all, so the slots
could only ever have stayed empty.
2026-09-01 14:22:02 +00:00
fivemservertest bc610a7f93 feat: inventory — 16 bag slots, five worn slots, and drops on the ground
The server owns every slot. A client asks to move, use, drop or pick up;
it never says what it is holding. Each request is checked against the row
in the database rather than anything that arrived over the wire, and the
reply is the whole inventory instead of a delta — a client that missed one
update would otherwise diverge quietly and stay wrong for the rest of the
session.

character_items stores one row per occupied slot; empty slots are absent,
because a table of NULLs carries no information and has to be kept in step
with the slot count. A unique key on (character, container, slot) is what
stops an item existing twice — enforced by the database rather than by
remembering to check, since a duplication bug here is a duplication bug in
the economy.

shared/items.lua and shared/items.js are the registry and are empty on
purpose. The comment at the top is the contract: label, description,
weight, stack, action, model and an optional wear slot. Both runtimes get
a copy because the server must answer "what does this weigh" without
asking a client, and the FiveM JS runtime has no require(). `action` is a
name, not code — the inventory emits it and whoever owns that effect
handles it, which keeps food, weapons and paperwork out of the registry.

Clothing may only sit in the slot it was made for, checked on both sides:
the panel dims the slots that cannot take what is being dragged, and the
server refuses anyway.

Dropping puts a real object in the world using the registry's model and
registers a hold-to-pick-up marker through fiverp-interact. Piles live in
memory, not the database: a bag left in an alley should not survive a
restart. Pickup re-measures distance server-side, and anything that does
not fit stays on the ground rather than vanishing.

Two grid tracks needed minmax(0, 1fr) rather than 1fr. A track's automatic
minimum is its content's min-content width, and a nowrap label like
"Handheld radio" is wider than a quarter of the panel, so the bag pushed
itself past the panel edge — measured at 175px columns in a 583px space.
2026-09-01 14:16:21 +00:00
fivemservertest e4918bf688 fix: roleplay lines carried the platform name, not the character's
/me, /do and /try printed "FivemTest drop gun" rather than the name the
city knows, and plain speech fell back to "Citizen" even for a player who
had a character.

Both came from reading the wrong export. fiverp-characters'
getActiveCharacter returns the character's row id — a plain number — so
`active.firstName` was undefined every time and the code took its fallback
branch, which is the platform name. It looked like the server was speaking
because that is the only name left when the character's is missing.

fiverp-ids already stores the character's full name beside the ids, from
the row it reads on selection, and hands both back from one getIds call.
Chat now asks it once and derives everything from that: the roleplay
channels take the name, plain speech takes name and id together, and the
Citizen fallback is left for players who genuinely have no character yet.
2026-09-01 13:57:34 +00:00
fivemservertest 35773c1216 fix: chat doubled every line, dropped plain speech, and printed two slashes
Four faults, one round.

Plain speech never reached the server at all. `_chat:messageEntered` was
declared with on() rather than onNet(), so the engine refused to route it —
"Event _chat:messageEntered was not safe for net" in the console, twice per
attempt — and the message was dropped in silence. Commands still worked,
because those go through ExecuteCommand, which is why only the roleplay
channels appeared to function.

Every line arrived twice. RegisterNetEvent with a function attaches a
handler of its own, and it was paired with an AddEventHandler for the same
event, so render ran twice per message. The events are now declared
net-safe on their own and handled exactly once.

The speaker carried neither name nor id. Plain speech is now labelled
"Vinni Admin (14)": the name the city knows, and the id everyone can see
regardless. Before a character is chosen there is no name to use, so it
falls back to "Citizen (14)" and keeps the id.

The input showed two slashes — a decorative one beside the field plus the
one being typed. The decoration is gone; a command line is signalled by
the field's own edge turning accent instead.

Also replaced getPlayers() with an index walk. It is a Lua-side convenience
and not guaranteed in this runtime, and it sat directly in the path every
message takes.
2026-09-01 13:45:41 +00:00
fivemservertest fa7625befd feat: roleplay channels, proximity, and quick buttons in chat
Plain speech now reads "Citizen (14): text" rather than carrying a name.
A stranger's name is something they tell you; the id is the thing everyone
can actually see, so that is what labels the line.

Adds /me, /do and /try, plus /s, /w and /b to round out the family. Each
carries a distance — 4 m for a whisper, 45 m for a shout, 20 m for
everything else — and the audience is worked out on the server from both
players' positions. A client cannot widen its own range.

/try's outcome is decided server-side for the same reason: a client that
rolled its own attempts would never lose one.

The palette has a single accent, so the channels are told apart by shape
instead of colour. /me and /do drop the name chip entirely and become
italic with a leading rule, because they are narration rather than someone
speaking — a wall of them should not read as conversation. /do puts the
author in double brackets, /b wraps the whole line in them, a shout is
uppercase and a whisper is small and grey.

The buttons above the input fill the channel in rather than sending
anything; they bind on mousedown, since click lands after blur and losing
the caret mid-press is what makes a chat box feel broken.
2026-09-01 13:38:41 +00:00
fivemservertest a92879bd41 fix: teller stands behind the counter, marker sits on the ATM keypad
Three things the first pass got wrong, all visible the moment it was
actually walked up to.

The teller was buried in the carpet. The stored coordinate was already a
metre below the player-standing height, and CreatePed subtracted another
metre on top of it. Rather than fix the constant and hope, the floor is
now measured: GetGroundZFor_3dCoord from just above the spot, falling back
to the old offset only if the probe misses.

The teller was also on the customer's side of the counter, because that is
the coordinate the branch was recorded at. That coordinate is now named
`counter` — it is where the player stands — and the teller is placed by
pushing back along the direction they face, which is one number
(tellerSetback) shared by every Fleeca since they share one interior. The
server still measures distance to the counter, not to the teller: the
customer is what has to be in reach.

Ambient peds inside 6 m of a teller are removed while the player is near.
The stock crowd queues at that same counter playing the same clipboard
idle, so from the doorway there looked to be four tellers and only one of
them answered. The game keeps spawning them, so the sweep repeats.

The ATM marker was on the pavement: the supplied coordinates are the
machine's foot. It is lifted to keypad height for display only — the
server keeps measuring against the coordinate it was given.
2026-09-01 13:27:59 +00:00
fivemservertest d23e931ad7 feat: hold-to-act markers, and move banking onto them
Replaces the stock help-text-and-tap prompt with a marker that sits on the
thing itself: a dial at the world point, the key inside it, and a ring that
fills while the key is held. The action fires when the ring is full and
nowhere else, so letting go early cancels and a tap does nothing — which is
the point, since these are the presses that move money.

fiverp-interact owns it, and everything else registers through it:

  exports['fiverp-interact']:add({
    id = 'atm:14', coords = ..., label = 'ATM',
    distance = 1.6, hold = 700, onSelect = function() ... end
  })

The dial tracks a projected world point, so Lua owns its position and the
stylesheet owns everything else. Two things follow from that split. The
ring's fill is a CSS transition whose duration Lua sets, while Lua times
the real hold itself — the bar cannot show one thing and the game accept
another. And the position is only posted when it actually changed in
whole pixels, because the loop runs every frame and most frames the marker
has not moved.

It stands down while dead, in a vehicle, or with a menu holding NUI focus,
and clears itself before firing a callback so a menu never opens behind a
marker that is still on screen.

Banking now registers through it. Points come and go within 40 m rather
than all at once: 121 ATMs as live points would be 121 distance checks a
frame, and the marker decides what is close enough to use anyway.
2026-09-01 13:20:13 +00:00
fivemservertest 3bfb6808f4 feat: chat with a live command list, and banking
fiverp-chat replaces the stock chat. It reimplements the events the stock
one exposes — chat:addMessage, chat:addSuggestion, addSuggestions,
removeSuggestion, chat:clear, both as local and net handlers — so nothing
else on the server had to change; `ensure chat` is simply commented out.

The point of it is the slash key. Typing "/" lists every command that
actually exists here and keeps narrowing as you type; prefix matches come
first, then anything containing what you typed, so a half-remembered name
still finds its command. The list is seeded from GetRegisteredCommands()
rather than only from what resources announce, so a command registered
without a suggestion still shows up — just without help text. Enter on a
partly-typed name completes it instead of running it, which stops a
mistyped command going off by accident; Tab does the same.

fiverp-bank adds 8 branches and the 121 ATM coordinates. Branch blips are
full size, ATM blips are short-range — 121 icons would otherwise bury the
map. Tellers are peds spawned within 60 m and deleted past 90 m, so eight
of them are not standing in empty interiors all session.

A teller deposits, withdraws and transfers to another player by ID, all
free. An ATM does the first two and keeps 3% of a withdrawal, minimum $5.
The fee is taken as its own ledger move rather than folded into the
withdrawal, so a statement can tell the two apart.

Nothing the client sends is trusted. The server re-derives the location
from its own copy of the coordinates, re-measures the distance against the
player's server-side position, re-reads both balances, and recomputes the
fee — the menu computes it too, but only to show you the number before you
commit. Transfers resolve the target through fiverp-ids rather than
accepting a source from the client, and refuse your own ID.
2026-09-01 13:08:46 +00:00
fivemservertest 0434c0979e ui: replace the native scrollbar with one we control
The settings panel was ending in the platform scrollbar: a light trough
with stepper arrows, the one piece of chrome the palette never reached.

Styling it was tried first and does not hold. ::-webkit-scrollbar-thumb
and -track take the palette fine, but -button keeps drawing a 3px stub at
each end in CEF whether it is given display:none, zero size or a
transparent fill. Measured off a render: a stub at y182-184 and y911-913,
both in the thumb's colour, on every attempt.

So the native bar is hidden outright and scrollbar.js draws the
replacement — a plain element, therefore ours. It is position:fixed and
measured from the host's own rect, so it needs no particular ancestor and
survives a modal that moves; it hides itself when there is nothing to
scroll, drags from the window rather than the 4px thumb, and pages on a
track click.

Panels that are hidden until opened have no rect to measure, so opening
the HUD settings and switching an editor tab both fire fiverp:relayout,
and a MutationObserver covers content changing underneath.

Attach with data-scroll. In use on the HUD settings body and the
appearance editor's control list.
2026-09-01 12:55:53 +00:00
fivemservertest 65abfae9ae fix: centre the condition rings instead of pinning them
With "Show numbers" on, the ring had to hold a glyph and a value and did
neither well: the number sat on the arc and the glyph collapsed to a dot.

Two causes. The offsets were hand-tuned for the glyph-only case and never
rechecked with a value present, and `.status.show-numbers .ring svg`
matched the glyph's own icon as well as the arc — a descendant selector
where a child was meant — so it inherited the arc's inset and shrank.

Both go away by letting the box centre its own contents: the ring is a
centred column, the glyph and value are static children, and the arc is
the only absolutely positioned thing in it. Hiding the value now recentres
the glyph on its own, with no second rule to keep in sync. The ring still
grows to 3.9em in numbers mode, because two items genuinely do not fit in
3.1em.

Verified in both modes rather than only the default, which is how this was
missed the first time: the screenshots were taken with numbers off while
the report had them on.
2026-09-01 12:38:43 +00:00
fivemservertest 074e73b07a hud: move condition beside the radar, add voice range, clear the login screen
Login and character select were still showing the game's own radar, health
bar and cash counter — state belonging to a character that does not exist
yet. Both screens now hide them per frame, and spawning turns the radar
back on: DisplayRadar is a sticky flag, so it needs an explicit true.

The condition rings move from a row above the radar to a column beside it,
anchored to the radar's own baseline. Both figures are measured off a 1080p
frame rather than guessed: the radar ends at 15.6vw and its foot sits 1.7vh
from the bottom, so the column starts at 16.4vw and shares that baseline
and stays aligned however many rings are on screen. The "Position"
preference keeps working; its first option is now "Beside radar".

New voice module under the rings: three proximities cycled with Z, applied
to Mumble through MumbleSetTalkerProximity, so the pips are not decoration
— they are how far the player actually carries. The call is wrapped in
pcall so a server without voice loses the indicator, not the HUD. Talking
is the one state that takes the accent, because it is the only thing on the
HUD another player can perceive.

Colours stay strictly inside the eight tokens: no per-need hues.
2026-09-01 12:26:03 +00:00
fivemservertest 5b247ab35e fix: let the loadscreen resource own its own shutdown
The previous commit added ShutdownLoadingScreenNui() but it did nothing.
Without loadscreen_manual_shutdown in the manifest the engine owns the
loading screen's lifetime and tears it down on spawn — which never happens,
because auth keeps spawning disabled until the player has an identity. The
manual call is not honoured in that mode, so the screen stayed up and its
opaque #181818 body covered the world behind every menu.

Measured from the report: the background was exactly rgb(24,24,24), not
black, and fiverp-loadscreen's body is the only full-screen opaque fill
left in the project — auth, characters and hud all render alpha 0 outside
their panels when captured over a transparent backdrop.

Declaring manual shutdown makes the existing call authoritative. Because
that also means nothing else will ever remove the screen, character
selection calls it too: shutting it down twice is harmless, never shutting
it down strands the player behind it.
2026-09-01 11:54:45 +00:00
fivemservertest 2d19815e5b fix: shut the loading screen down before showing the login form
The grey behind the login form was the loading screen, not the auth page.
FiveM destroys the loadscreen NUI when the player spawns, and auth calls
setAutoSpawn(false) to hold the gate shut until there is an identity — so
nothing ever spawned and nothing ever tore it down. It stayed mounted over
the world for the whole login, hiding the drifting city camera underneath.

It only became visible as a problem with the redesign: the old loadscreen
was near-white, the new one is #181818, which reads as a dead grey screen
rather than a backdrop.

Shut it down explicitly once the camera is up.
2026-09-01 11:43:08 +00:00
fivemservertest ea5c8d1924 fix: stop the auth layer painting over the game
The redesign put an opaque background on fiverp-auth's body. That NUI page
is mounted for the whole session, not just while the form is open, so
#181818 covered the entire screen behind every other interface — the
character editor showed a flat grey panel where the live ped should be.

Back to a transparent html/body, with the fill left to the panels. Same
reason removed the fill I had added to the character select screen: the
game renders behind it too, and the slot cards already carry their own
background.

Verified by capturing each page with --default-background-color=00000000:
auth, characters and hud report alpha 0 outside their panels, loadscreen
stays opaque because it does own the whole screen.
2026-09-01 11:21:56 +00:00
fivemservertest 8b65a47790 redesign: Clean UI / Dark Minimalism across every interface
Rewrites all four NUI stylesheets against one palette and one set of
geometry rules, and replaces the design system in CLAUDE.md to match.

Palette is now eight tokens: #181818 background, #1E1E1E cards, #333333
controls, #FF7716 accent, #F1F1F1 / #999999 text, a 50% #333333 hairline
border and a #464646 hover. Nothing outside that set appears in any
stylesheet — depth comes from borders and spacing, not shadows.

Dropped, per the brief: gradients, blur, inset shadows and skeuomorphism.
That took out the loadscreen's drifting colour blobs and the frosted veil
over them, and it took out the auth screen's paper passport — the
guilloche, the laminate sheen and the embossed seal are gone, replaced by
a flat permit card. Every node app.js writes to survived the rewrite, so
the MRZ, the check digits and the issue/expiry dates still work as before.

Typography: Unbounded throughout, headings uppercase at 700-900 with 1px
tracking. The guide sets a 10px floor, which the HUD was breaking in eight
places once it scaled from a 16px base, so those sizes went up and the
server, wallet and vehicle cards widened to hold them.

Also fixed while re-rendering:
- Status rings were sized for a value that is hidden by default, leaving
  the glyph floating above a gap. The glyph now centres, and only shifts
  up when "Show numbers" is on (new .show-numbers class from applyPrefs).
- "Save & enter city" wrapped to two lines beside two ghost buttons in a
  420px panel and stood taller than its row; the primary now takes its
  own row.
- The permit card showed an "Issued" chip before anything was issued.
  It is hidden until .is-stamped.

Screenshots re-rendered from the current code, plus two new ones for the
character select and the appearance editor.
2026-09-01 11:16:42 +00:00
fivemservertest f353327ffd revert: remove fiverp-cinema resource 2026-08-13 19:45:25 +00:00
fivemservertest c3e5012043 feat: add fiverp-cinema cinematic flyover resource
5 pre-built camera sequences over different districts:
Vinewood Hills, Downtown Canyons, Pacific Coast,
Port of LS, Blaine County Desert.

Commands:
  /capturecinema [n]  – play scene n (or all scenes)
  /stopcamera         – abort
  /cinemalist         – list scenes
2026-08-13 19:33:29 +00:00