Loading screens
[FREE] Loading Screen — Video & Music [ESX/QBCore/Qbox/vRP/Standalone]
Original Lua/JavaScript resource developed independently by WebMerc. Requires FiveM (GTA V). No Rockstar game files are included.
Independent third-party developer. Not affiliated with, endorsed, sponsored or approved by Rockstar Games, Take-Two Interactive, Cfx.re or FiveM. GTA, Grand Theft Auto and FiveM are trademarks of their respective owners.
Read it before you buy
These are the same files that come in the download, updated with every release.
lastresort_loading
A free, fully configurable FiveM loading screen by LastResort Development.
Local MP4 / WebM background video, a music player that takes local MP3 / OGG files and draws a real spectrum from them, a rising particle canvas, pattern overlays, an animated progress bar with stage labels and a clean glass-panel UI — backdrop blur, vignette scrim and film grain. The framework is auto-detected: drop it onto ESX, QBCore, Qbox, vRP or a standalone server and the player's character name appears in the welcome line with zero edits.
Everything is driven by a single config.js. No build step, no framework, no markup edits.
- Resource name:
lastresort_loading - Version: 2.1.3
- Author: LastResort Development — intelnpc.com
- Price: Free — Download on Tebex
- Dependencies: None
Specifications
| Code is accessible | Yes — all of it. The page logic, the two Lua bridge files, config.js, locales/*.json, the news and tips you publish in data/, the media in assets/ and this documentation all ship as plain readable source. No obfuscation, no licence check, no subscription. You get every line and you can change every line. |
| Subscription-based | No |
| Lines of code | ~3,100 (HTML/CSS/JS page plus two small Lua bridge files) |
| Requirements | None. No framework, no database, no ox_lib. Framework detection is opt-in and degrades to the FiveM display name. |
| Support | Yes |
Performance
A loading screen runs while the client is streaming the entire map, so it is the one UI where wasted work is felt directly — it competes with asset streaming for the same frame.
| Scenario | Cost | Workload |
|---|---|---|
| Particles off, no video | negligible | CSS animation only, all on compositable properties (transform, opacity). No canvas, no render loop. |
| Default — particles on, video playing | one canvas pass per frame over ~60 particles | Each particle's halo is pre-rendered once into an offscreen canvas and blitted with drawImage, so a frame costs blits rather than gradient allocations. Device pixel ratio is capped at 1.5. |
| Server bridge | one request per 2s per connecting client | Rate-limited server-side to one reply per second per player. |
The page is not measured with resmon — it runs in the loading-screen NUI
before the client is in a session, where resmon is not available. Judge it
instead in the browser: open index.html directly and record a performance
profile. Lower particles.count, or set particles.enabled to false, on
servers where clients are streaming from slow disks.
Features
- Background video — a local MP4 / WebM file, or a direct URL to one. Sources are tried in order, so a list is its own fallback chain, and a video that never decodes leaves the image layer or the gradient background showing
- Static image or slideshow background for servers that don't want video — cross-fade, Ken Burns drift, and it doubles as the safety net under a video that never decodes
- Server logo image above the wordmark, or in place of the title entirely
- Rules acceptance gate — an optional panel the player has to scroll and accept before the screen hands over, recorded server-side per identifier so it is asked once, or once per rules version
- Server news / patch notes tab — published from a JSON file, an HTTP feed or another resource, so the notes change without anyone touching the loading screen
- Live tips from the server through the same routes, with the locale list as the fallback
- Music player — drop
.mp3/.oggintoassets/audio/and list them; transport, track list, shuffle, loop and a volume that follows the player across joins - Real spectrum visualizer — the cover art is an analyser reading the audio that is actually playing, with a waveform line under the track title; audio the browser will not let the page tap falls back to a synthetic signal rather than sitting still
- Animated rising particle canvas — weighted color palette, density, speed, size, halo and alpha all configurable
- Four pattern overlays (fine stripes, dots, hex grid, square grid) with edge masking — or off entirely
- Smooth animated progress bar with stage labels, live percentage and real FiveM load events (simulated fallback for preview)
- Tips rotator, Rules and Staff tabs with color-coded role cards (owner / admin / dev / mod)
- Live server status chip — online state plus the real player count, slot count and ping, fed by the server bridge while the screen is up
- Social links row — Discord, website, store, any icon from the bundled Lucide set
- Auto character name in the welcome line on Qbox / QBCore / ESX / vRP, FiveM display name as fallback
- 10 translations out of the box — add your own with a single JSON file
- Glass-panel look: backdrop blur, configurable scrim, film grain, gradient background stack and corner brackets
- Graceful handover — the screen fades out and the music ramps to silence before the NUI is released, instead of the engine cutting the frame away mid-bar
- One
config.jsfor every knob
Install
- Place the
lastresort_loading/folder into your server'sresources/directory. - Add
ensure lastresort_loadingtoserver.cfgearly — before anything that touches the loading screen. - Put your video in
assets/video/and your music inassets/audio/, then reference them inconfig.js. - Restart. The screen takes effect on the next client connect.
ensure lastresort_loading # add this early
Only one loadscreen. Only one resource may declare
loadscreen. Stop any previous loading-screen resource so it doesn't compete withlastresort_loading.
See INSTALLATION.md for the step-by-step guide.
Video & music
Background video — a local file or a URL
The background video tries video.sources in order — the first source that plays is shown. If a source errors, the next one is tried; if none work, the video layer hides and the configurable gradient background shows instead. List as many sources as you like and put your most compatible format last as the safety net.
video: {
enabled: true,
sources: [
'assets/video/loading.mkv',
'assets/video/loading.webm',
'assets/video/loading.mp4'
],
autoplay: true,
loop: true,
muted: true, // muted is required for autoplay everywhere
objectFit: 'cover', // 'cover' | 'contain'
opacity: 1.0,
blurPx: 0 // optional CSS blur over the video
}
Prefer .webm (VP9). Every Chromium build decodes it, including the one inside FiveM; H.264 in .mp4 depends on how that build was compiled.
Stream it, or pack it
A source is either a URL or a path inside the resource, and the difference is worth a moment.
A packed file is part of the resource, so every player downloads all of it during the connect handshake — before the loading screen they are waiting on can appear. A 30 MB background makes the join 30 MB heavier for everyone with a cold cache.
A URL is fetched by the browser while the screen is already up, and it streams: playback starts on the first buffered seconds instead of the last byte. Nothing is in the join download, the bandwidth is your web server's rather than your game server's, and swapping the video later needs no resource restart.
sources: [ 'https://cdn.example.com/loading.webm' ]
Any host that serves the file over https with a video/webm or video/mp4 content type works — your own web server, a CDN, object storage. It does not need byte-range support; a loading screen never seeks.
Use a packed file for a short, small clip. Use a URL for anything else.
Drop files into assets/video/ — the manifest packs .webm, .mp4, .mkv, .m4v and .ogv, so no manifest edit is needed. A container the manifest does not pack is never shipped to the client at all, and the request for it answers 404.
.mkv plays. Matroska is the container WebM is a profile of, and CEF demuxes both with the same code, so an .mkv holding VP8, VP9 or H.264 is played as it is. What decides it is the codec inside, not the extension: HEVC (H.265), AV1, AC-3 and DTS do not decode. An .mkv source is therefore tried first and the .webm and .mp4 of the same name are queued behind it, so a file CEF cannot decode still falls through to a converted copy if you keep one.
Re-wrapping an .mkv that already holds H.264 is lossless and instant — no re-encode, so nothing is lost:
ffmpeg -i loading.mkv -c copy loading.mp4 # re-wrap, seconds
ffmpeg -i loading.mkv -c:v libvpx-vp9 -crf 34 -b:v 0 -an loading.webm # re-encode, safest
Dropping the audio track (-an) is worth doing either way: the video is muted, so every byte of its audio is downloaded and thrown away.
.avi, .wmv, .flv, .mpg, .mov and .m2ts never play. A source in one of those is not requested at all — the page looks up the .webm and the .mp4 of the same name instead, so an older config keeps its video after you re-encode the file.
If a video does not appear, the F8 console names the file and the reason: video source failed with the media error code, or video timed out. A source that is genuinely still downloading is never cut off — the timeout re-arms as long as bytes keep arriving.
Image background — static or slideshow
Not every server wants a video. Set image.enabled and list your files: one source is a static background, several cross-fade as a slideshow with an optional slow Ken Burns zoom.
image: {
enabled: false, // draw the image layer at all
sources: [
'assets/img/bg1.jpg',
'assets/img/bg2.jpg'
],
intervalMs: 9000, // how long each slide is held (ignored with a single source)
fadeMs: 1200, // cross-fade length between slides
objectFit: 'cover', // 'cover' | 'contain'
opacity: 1.0,
blurPx: 0, // optional CSS blur over the image layer
kenBurns: true // slow zoom drift on each slide
}
Drop the files into assets/img/ — the manifest already packs .png, .jpg, .svg and .webp from there. The image layer is drawn under the video layer, so it is also what shows if a video is configured but never decodes. For images only, set video.enabled: false.
Server logo
logo: {
enabled: false,
src: 'assets/img/logo.png',
height: 84, // rendered height in px, the width follows the aspect ratio
placement: 'above', // 'above' keeps the H1 title under the logo, 'replace' hides the title
glow: true // accent drop-shadow behind the logo
}
The logo appears in the top-left, above the wordmark. It is only shown once the file has really decoded — a wrong path costs you the logo and nothing else, never a broken-image icon on the screen.
Music
music: {
enabled: true,
showUI: true, // hide the whole music panel if false
autoplay: true, // tries to play on load (gesture unlock fallback)
volume: 50, // 0–100
loop: true, // loop the whole playlist
shuffle: false,
tracks: [
{ title: 'Last Resort', src: 'assets/audio/last_resort.mp3' }
],
visualizer: {
enabled: true,
bars: 14,
wave: true,
analyse: true,
smoothing: 0.72,
sensitivity: 1.0
}
}
A src is a file this resource ships — drop .mp3 / .ogg into assets/audio/ and list the path. title can be left out, in which case the filename is shown.
The controls are previous / play-pause / next, a draggable volume slider and a track list — the button next to Next opens the whole queue, the track that is playing is highlighted, and clicking any row jumps straight to it. With loop: false playback stops after the last track; with shuffle: true the order is randomized once per load and the list shows that order. A track whose file is missing, or in a container the game's browser cannot decode, is skipped instead of stalling the playlist.
The volume follows the player. Whatever they drag the slider to is stored client-side (SetResourceKvp, so a cleared browser cache cannot lose it) and restored on their next connect — music.volume is only the starting point for someone who has never touched it. Set rememberVolume: false to always start at your configured volume, and showPlaylist: false to keep only the transport controls.
Visualizer
The cover art is the spectrum. The audio is routed through an AnalyserNode and the bars follow the frequencies that are actually sounding, with a thin waveform line under the track title. Where the browser will not hand the page a running audio context the same bars are driven by a synthetic signal instead — the panel reads as alive either way, which is the entire job of a visualizer on a loading screen.
| Key | Type | Default | Notes |
|---|---|---|---|
enabled |
bool | true |
false hides both canvases and leaves a plain cover square. |
bars |
number | 14 |
4–48. Bars drawn in the cover square. |
wave |
bool | true |
The waveform line under the track title. |
analyse |
bool | true |
Tap the audio for a real spectrum. false always uses the synthetic signal. |
smoothing |
number | 0.72 |
0–0.95 analyser smoothing — higher is calmer. |
sensitivity |
number | 1.0 |
Overall bar height multiplier. |
The audio is only ever re-routed through the analyser once the browser has given the page a running audio context. If it will not, the tap is skipped and retried after the player's first click — a visualizer is never worth risking silent music for.
Autoplay note. Browsers block audible autoplay until the user interacts. The player ships a gesture-unlock fallback — the first click or keypress starts playback at your configured volume.
config.js reference
The whole screen is driven by one object, window.LR_CONFIG. These are the top-level keys:
| Key | Type | Notes |
|---|---|---|
locale |
string | Active language code (en, hu, de, …) — matches a file in locales/. |
fallbackLocale |
string | Used when the active locale file is missing or fails to load. |
brand |
object | preTitle, title, subtitle, footer — null falls back to the locale strings. A null or empty title is filled with the server's own sv_projectName in-game, so leaving it alone is a valid choice; any value you set wins. |
logo |
object | Server logo image: enabled, src, height, placement ('above' | 'replace'), glow. |
playerName |
string | Static welcome-line name. Overridden at runtime by the framework bridge / handover data. |
status |
object | Status chip: show, online, playersCurrent, playersMax, ping. |
video |
object | enabled, ordered sources chain (packed files and/or direct URLs), autoplay, loop, muted, objectFit, opacity, blurPx. |
image |
object | Static image / slideshow background: enabled, sources, intervalMs, fadeMs, objectFit, opacity, blurPx, kenBurns. |
music |
object | enabled, showUI, showPlaylist, rememberVolume, autoplay, volume (0–100), loop, shuffle, tracks playlist, visualizer. |
background |
object | baseColor + stacked CSS gradients shown under the video (and while it loads). |
pattern |
object | type: 'stripes' | 'dots' | 'hex' | 'grid' | 'none', plus color, opacity, maskEdges and per-type tuning. |
scrim |
object | Readability vignette: topDark, midDark, bottomDark, vignette, tintTop, tintBottom. |
grain |
object | Film grain overlay: enabled, opacity. |
particles |
object | Rising particle canvas: density, count clamps, size / speed / alpha ranges, halo, weighted palette. |
corners |
object | Corner bracket frame: color, size, thickness, inset. |
theme |
object | Accent, line and text colors applied as CSS variables — one block drives every accent. |
tabs |
object | Toggle the rules / news / staff tabs and set defaultTab. |
staff |
array | Staff cards: { name, role, badge, tone, avatar } with color-coded role badges. |
staffDefaults |
object | Locale-driven default role / badge per tone (owner, admin, dev, mod). |
socials |
array | { icon, url } — Discord, website, store or any bundled Lucide icon name. |
tips |
object | Tips rotator: enabled, intervalMs, fadeMs, override (null = locale tips). A live list from the server wins over both. |
news |
object | News tab fallback: maxItems and an items array of { title, date, tag, body }. The server's list wins. |
rules |
array | null | Hard-coded rules list, or null to pull them from the locale file. |
rulesGate |
object | Acceptance panel look and feel: requireScroll, showDecline, dim. Switched on server-side with lrl:rulesGate. |
progress |
object | Simulated-progress tuning (simulate, intervalMs, stepMin, stepMax) plus logHoldMs. The first real FiveM loadProgress event takes the bar over automatically. |
devDock |
boolean | Shows a small preview dock with a Replay button — for browser preview only. |
Every key is documented inline in config.js. On i18n-aware fields, null means "use the value from locales/<lang>.json".
Theme
Theme colors are CSS variables set from one block — change the accent and the progress bar, particles halo, pattern tint and panel borders all move together:
theme: {
accent: '#5fe0ff',
accent2: '#a78bfa',
accent3: '#f472b6',
accentSoft: 'rgba(95, 224, 255, 0.16)',
accentGlow: 'rgba(95, 224, 255, 0.4)',
text: '#f4f7fb',
muted: '#a8b0c2',
faint: '#6f7891',
line: 'rgba(255, 255, 255, 0.08)',
lineStrong: 'rgba(255, 255, 255, 0.16)',
ok: '#34d399',
bg: '#06080d'
}
Server data bridge
Two bridge scripts feed the screen real data. No snippets to copy, no config to edit.
bridge/server.lua (a server_script):
- On
playerConnectingit attaches everything the screen can be told that early — the connecting player's name, the current player count, the slot count, the server name, and the newest news entries and tips — to the connection throughdeferrals.handover(), exposed to the page aswindow.nuiHandoverData. This is what the first frame is drawn from. The client script that pushes the live numbers cannot start until the player has downloaded the resources, which is most of the way through the join; until then the handover is the only data the page has, so the screen opens on real content instead of placeholders that get corrected later. - The payload rides in the connect handshake, so it is deliberately small: the four newest news entries trimmed to six lines each, and ten tips. The client bridge replaces all of it with the full lists the moment it is up.
-
Only one resource can own the connection deferral, so the bridge scans the running resources on the first connect and picks one of three modes, printing which once:
- Take — nothing that holds connections (a queue, a whitelist, txAdmin's
monitor) is running, so the bridge defers for a single frame, hands over and releases. - Attach — something else owns the deferral. The payload is attached to the deferral that resource already holds, without deferring: the screen still gets its data early and nothing competes for the connection.
- Off —
setr lrl:useDeferrals 0. Nothing is attached and the data arrives from the client bridge later, as it did before.
The scan is redone whenever a resource starts or stops.
setr lrl:useDeferrals 1forces the take,0forces off. - Take — nothing that holds connections (a queue, a whitelist, txAdmin's
- While the loading screen is visible it answers stat requests with the live player count,
sv_maxclientsand the client's real ping (rate-limited per player), and onplayerJoiningit pushes the stats unprompted — ten times, densely at first, because a push that lands before the client script has registered its handler is simply gone.
bridge/handover.lua (a client_script) requests those stats on its very first frame and retries every 250 ms until an answer lands, then settles to every 2 seconds while the screen is up. Server news and tips are requested on the same pattern. It also detects the framework and pushes the FiveM display name into the welcome line immediately — so the line is never blank — then swaps in the character name once the framework loads it.
| Framework | Detected | Name shown |
|---|---|---|
| Qbox | auto | Character name (charinfo) |
| QBCore | auto | Character name (charinfo) |
| ESX | auto | Character name (firstName + lastName) |
| vRP | auto | Character identity |
| Standalone / unsupported | fallback | FiveM display name |
How it reaches the UI (all handled inside index.html):
window.nuiHandoverData— read once, before anything is drawn. The name, the counts, the server name, the news and the tips it carries are seeded into the same variables the client bridge's messages write to, so there is one path from data to screen and nothing renders twice.- A
setPlayerNameloading-screen message updates the name live while the screen is showing. - With a name the line reads the locale's
welcomestring ("Welcome back,") plus the name; without one it showswelcomeAnon("Welcome"). playerNameinconfig.jscan force a static name; leave it empty in normal use.
Server news, patch notes & live tips
The News tab and the tip rotator are content, not code. Four sources feed
them and the highest one that answers wins, so you can start with the array in
config.js and move up to a live feed later without editing the page:
| # | Source | Changes without | Good for |
|---|---|---|---|
| 1 | exports.lastresort_loading:SetNews({...}) / SetTips({...}), or the lastresort_loading:setNews / :setTips server events |
a restart | a Discord bot, an admin panel, another resource |
| 2 | setr lrl:newsUrl "https://…" / setr lrl:tipsUrl "https://…" |
touching the resource at all | a web panel, a gist, anything that serves JSON |
| 3 | data/news.json / data/tips.json |
a restart | editing a file over FTP |
| 4 | news.items in config.js, tips in the locale file |
— | the fallback a fresh install ships with |
Both files are re-read while the server runs, so saving one is enough for the
next player who connects. lrl_reload in the console applies everything at
once. A URL feed is polled every lrl:contentRefreshMinutes (5 by default,
0 fetches once at startup).
[
{
"tag": "Update",
"date": "2026-07-24",
"title": "Winter update",
"body": [
"Two new legal jobs at the docks.",
"Vehicle handling pass on every sports car."
]
}
]
Every field is optional as long as an entry has a title or a body; body may
also be one string with newlines. A bare array of strings works for tips. A
document with both a "news" and a "tips" key set as lrl:newsUrl fills
both lists from one endpoint. Everything is length-capped and stripped of
non-strings server-side before it is sent, and rendered as text — a feed you do
not control cannot inject markup into the screen.
The News tab only exists once there is something in it. A server that publishes nothing shows the panel exactly as it always did.
Rules acceptance gate
An optional panel the player has to scroll through and accept before the loading screen hands over. It is off by default and switched on server-side, because that is where the acceptance is recorded:
setr lrl:rulesGate 1 # ask for an acceptance
setr lrl:rulesVersion 1 # bump this and everyone is asked again
setr lrl:rulesMode version # 'version' asks once per version, 'always' asks every join
The rules shown are the same list the Rules tab uses — config.js rules, or
the locale file. The acceptance is stored in data/rules_accepted.json keyed by
the player's license: identifier (falling back to fivem:, discord:,
steam:), together with the version they accepted and when. The client never
decides whether it owes an acceptance and never supplies the version it is
recorded under: the server answers both, so a modified client can skip its own
panel but not the record.
rulesGate: {
requireScroll: true, // Accept unlocks only after the rules are scrolled to the end
showDecline: true, // offer a Decline button, which disconnects the player
dim: 0.72 // how dark the loading screen behind the panel goes
}
Decline disconnects with the gateDeclined line from the locale pack the page
is showing, in the player's own language. The panel appears within a second or
two of connecting, so in practice it is answered long before the map has
finished streaming and nothing is ever held up.
Two console commands, both restricted to command.<name> ACE (the console
always has it):
add_ace group.admin command.lrl_reload allow
add_ace group.admin command.lrl_rules_reset allow
lrl_reload # re-read news and tips from every source now
lrl_rules_reset all # ask everyone again
lrl_rules_reset 3 # ask server id 3 again
lrl_rules_reset me # ask yourself again — the fastest way to re-test the panel
lrl_rules_reset license:abc… # ask one identifier again
The screen is never held open for a gate that cannot be answered: if the page
has not opened its return channel, or loadscreen_manual_shutdown is off, the
gate is skipped and the reason is printed to the client console.
Convars
| Convar | Default | Effect |
|---|---|---|
lrl:useDeferrals |
auto | Who owns the connection deferral. auto takes it when nothing else holds connections and otherwise attaches the handover data to the deferral that resource already owns. 1 always takes it, 0 sends no handover data at all. |
lrl:rulesGate |
0 |
1 asks connecting players to accept the rules. |
lrl:rulesVersion |
1 |
Bumping it asks everyone again. |
lrl:rulesMode |
version |
version asks once per version, always asks every join. |
lrl:newsUrl |
— | JSON endpoint for the news list. |
lrl:tipsUrl |
— | JSON endpoint for the tip list. |
lrl:contentRefreshMinutes |
5 |
How often the feeds are polled. 0 fetches once at startup. |
Shutdown & handover API
loadscreen_manual_shutdown 'yes' in fxmanifest.lua means the screen belongs to this resource instead of the engine. Once the session starts, bridge/handover.lua fades the page out and ramps the music down to silence before it calls ShutdownLoadingScreen() and ShutdownLoadingScreenNui() — the player sees a clean fade instead of a frame ripped away mid-bar. Comment that manifest line out to hand the shutdown back to FiveM; everything else keeps working.
Other resources can wait for that moment. Both surfaces are client-side, and both report done when the screen was never shown at all — a mid-session restart of this resource, for example — so nothing can ever wait forever:
-- Fired once, when the loading screen is done.
AddEventHandler('lastresort_loading:finished', function()
-- safe to take over the screen now
end)
-- Or ask at any point.
local done = exports.lastresort_loading:IsLoadingScreenDone()
A resource that only optionally integrates should treat a missing lastresort_loading as "already done":
local function loadingDone()
local state = GetResourceState('lastresort_loading')
if state ~= 'started' and state ~= 'starting' then return true end
local ok, res = pcall(function() return exports.lastresort_loading:IsLoadingScreenDone() end)
return (not ok) or res == true
end
Progress bar
The bar is simulated until FiveM emits its first loadProgress message; from there the real load drives it and the travel that is left is remapped, so control passes over without the bar ever jumping backwards. A game log line owns the status row for progress.logHoldMs (2500 ms by default) before the stage name takes it back.
Particles & patterns
Rising particles
An animated canvas of softly glowing dots drifting upward. Count is derived from viewport area divided by density, clamped between minCount and maxCount; colors are picked from a weighted RGB palette.
particles: {
enabled: true,
density: 24000, // viewport area / density = count (clamped)
minCount: 50,
maxCount: 120,
sizeMin: 0.5,
sizeMax: 1.5,
speedMin: 0.12, // px/frame
speedMax: 0.47,
haloMultiplier: 3.5,
haloOpacity: 0.45,
alphaMin: 0.18,
alphaMax: 0.6,
palette: [
{ r: 220, g: 232, b: 248, weight: 0.65 },
{ r: 150, g: 220, b: 255, weight: 0.25 },
{ r: 200, g: 180, b: 255, weight: 0.10 }
]
}
Pattern overlays
Layer one of four patterns over the video, with optional edge masking, or set type: 'none' for a clean flat look. All types share color and opacity; each has its own tuning keys:
pattern: {
enabled: true,
type: 'stripes', // 'stripes' | 'dots' | 'hex' | 'grid' | 'none'
color: 'rgba(255, 255, 255, 0.55)',
opacity: 0.07,
maskEdges: true, // fade pattern toward the screen edges
stripeAngle: 35, stripeSpacing: 6, stripeThickness: 1, // stripes
dotSize: 1, dotSpacing: 22, // dots
hexAccent: 'rgba(95, 224, 255, 0.6)', // hex
gridSpacing: 28, gridThickness: 1 // grid
}
Tips, rules, staff, status & socials
Tips rotator
Tips come from the locale file (tips[]) and rotate with a fade. Set tips.override to an array to hard-code them across all languages, or feed them live from the server — see Server news, patch notes & live tips, which takes over the rotation the moment it arrives:
tips: { enabled: true, intervalMs: 5000, fadeMs: 400, override: null }
Rules, news & staff tabs
Three tabs in the info panel — toggle any of them and pick the default:
tabs: { rules: true, staff: true, news: true, defaultTab: 'rules' }
Rules come from the locale file, or set rules: [ ... ] in config.js to hard-code them. Staff cards are color-coded by tone — the four tones map to badge colors out of the box, and role / badge labels fall back to the locale when left null:
staff: [
{ name: 'Viktor', role: null, badge: null, tone: 'owner', avatar: 'V' },
{ name: 'Dora', role: null, badge: null, tone: 'admin', avatar: 'D' },
{ name: 'Marcus', role: null, badge: null, tone: 'dev', avatar: 'M' },
{ name: 'Esther', role: null, badge: null, tone: 'mod', avatar: 'E' }
]
Server status chip
The chip in the top corner shows online state, player count and ping. In-game the numbers are live — seeded from the connect-time handover so the chip opens on the real count, then refreshed by the server bridge while the screen is up. The config values are only used when previewing index.html in a plain browser:
status: {
show: true,
online: true, // green dot + "Online" label
playersCurrent: 0, // preview only — live in-game
playersMax: 0, // preview only — live in-game
ping: 0 // preview only — live in-game
}
Socials
Any name from the bundled Lucide set works, written without the icon- prefix — the whole icon font ships inside assets/lucide/, so nothing is fetched at runtime. A loading screen has no tab to open into, so a click hands the URL to the game's own browser (openUrl); in a plain browser preview the link is copied to the clipboard instead and the icon flashes green:
socials: [
{ icon: 'message-circle', url: 'https://discord.gg/yourserver' },
{ icon: 'globe', url: 'https://yourserver.example' },
{ icon: 'shopping-bag', url: 'https://store.example' }
]
Translations
Ten translations ship out of the box. Every UI string — rules, tips, progress stages, tab labels, staff roles / badges and the welcome line — comes from a locale file in locales/. Switch the active language with the locale key.
| Language | Code |
|---|---|
| English (default) | en |
| Hungarian | hu |
| German | de |
| French | fr |
| Italian | it |
| Spanish | es |
| Portuguese (BR) | pt-BR |
| Polish | pl |
| Czech | cs |
| Dutch | nl |
Add your own: copy locales/en.json to locales/<code>.json, translate the values, and set locale: '<code>' in config.js. If a locale file is missing or fails to load, the screen falls back to fallbackLocale.
Compatibility
- FiveM / Cfx.re — any artifact with a
loadscreenslot. - Frameworks: ESX, QBCore, Qbox and vRP auto-detected; standalone works with the display-name fallback.
- No dependencies — nothing else to ensure.
- Only one resource may declare
loadscreen; stop other loading screens before enabling this one. - Ships with
loadscreen_manual_shutdown 'yes'— the resource fades the screen out and releases it itself. Comment the line out infxmanifest.luato give the shutdown back to FiveM. The rules gate needs it: without it the engine owns the shutdown and the screen cannot be held open, so the gate steps aside and says so in the console. - Ships with a blank
ui_page(nui/callbacks.html). NUI callbacks are only routed to a resource that declares one, and the loading screen has to be able to call the client script back for the acceptance and the remembered volume. It draws nothing and never takes focus. - Runs alongside all other LastResort Development resources with no conflicts — it only occupies the loading-screen slot and does nothing in-game after loading.
- Fonts (Inter, JetBrains Mono, Saira Condensed) and icons (Lucide) ship inside the resource — the screen makes no external request at all and looks identical on a filtered, slow or fully offline network.
Support & license
lastresort_loading is a free resource. It ships as readable source — every line of the page, the bridge and the config is yours to open and to change, with nothing obfuscated and nothing checked against a server. Updates, including major versions, are included. Distributed per Tebex terms — re-uploads, paid resales and removal of attribution are not allowed.
- Website: intelnpc.com
- Support: help@intelnpc.com
- Tebex store: fivem-last-resort.tebex.io
© LastResort Development. All rights reserved.
Installation
- Copy
lastresort_loadinginto theresourcesdirectory. Do not rename it. - Stop or remove the old loading-screen resource; only one can run at a time.
- Add this near the top of
server.cfg:
ensure lastresort_loading
- Set the text, colors, images, video, and music in
config.js, then restart the server.
Configure
- Put videos in
assets/video, images and logos inassets/img, and music inassets/audio. - List every file in
config.js; the first compatible media source will be used. - Configure branding, server links, staff, news, tips, rules, and locale in the same file.
- Optional live news and tips can be loaded from local JSON files or server convars.
Verify
- Connect with a clean client and confirm the screen opens before other resources load.
- Check video or slideshow playback, music controls, links, and responsive layout.
- Confirm the loading screen closes after the player finishes connecting.
- Check the console for deferral, remote content, or malformed configuration warnings.
Important
- Put media files in the matching
assetssubdirectory. - Disable video in the config when using images only.
- The rules gate is disabled by default; enable it only after writing your rules.
- Back up
config.js, custom assets, anddatabefore updating.
Changelog
2026-09-04
- 2.1.3: drop backdrop-filter, FiveM's CEF never rendered it.
2026-08-12
- Play the .mkv that was uploaded, and stop waiting for server data.
2026-07-27
- Make streaming from a URL the documented background path.
- Stop embedding YouTube, ship the video as a file.
- Ship the plainest embed YouTube documents.
- Give YouTube less to refuse, and say what it refused.
- A courtesy toast killed the showcase, and the API killed the background.
- The YouTube background played, invisibly.
- Move the levels panel off the character, stop the chat camera fighting the UI, and cut the cluster's cost.
- Give the loading screen a real visualizer, fit the levels panel to the screen, and let the chat demo just be a street.
2026-07-26
- Give the two lite products their own reason to exist.
- Make the breaking changes upgrade cleanly, and finish the deferred features.
- Document and package lastresort_loading 3.0.0.
What you get
- The full source code. Every Lua, config and UI file, readable and editable. Rename it, restyle it or hook your own systems into it.
- No escrow, no license checks. No key and nothing that phones home. It keeps running as long as your server does.
- Free updates. New versions land in your account for as long as we maintain the script. Download the latest one whenever you like.
- One community per purchase. Use it on that community's live, development and test servers. Reselling or re-uploading the code is not allowed.
Get [FREE] Loading Screen — Video & Music [ESX/QBCore/Qbox/vRP/Standalone]
It is free. Log in and download it from your account.
