Skip to content

Commit 33357e3

Browse files
authored
Add files via upload
0 parents  commit 33357e3

24 files changed

Lines changed: 2709 additions & 0 deletions

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
---
9+
10+
## [0.1.0] - 2026-02-18
11+
12+
### Added
13+
14+
- Initial release of pxLoadingScreen
15+
- Video background with configurable loop support via `General.loopVideo`
16+
- Background image fallback when video is disabled or not set
17+
- Music player with play/pause, volume slider, and animated visualizer bars
18+
- Playlist support via `Media.audioPlaylist` with sequential auto-advance
19+
- Previous/next track controls (visible only when playlist has more than one track)
20+
- Audio fade-out on shutdown controlled by `General.audioFadeOut`
21+
- Animated progress bar with icon dot marker positioned at current progress value
22+
- Simulated progress via `Progress.simulateProgress` and `Progress.checkpoints`
23+
- Real FiveM `loadProgress` event integration — real values always override simulated ones
24+
- Configurable minimum display duration via `Progress.minDuration`
25+
- Info cards with per-card background color, border color, icon, and description
26+
- Rotating tips panel with 6-second fade transition
27+
- Tabbed content panel with configurable tabs, icons, and string content lists
28+
- Full theme color system via `Theme.colors` applied as CSS custom properties at runtime
29+
- Custom font family support via `Layout.fontFamily`
30+
- Server branding: icon, title, subtitle, optional glow effect
31+
- Optional skip key support via `General.allowSkip` and `General.skipKeyBinding`
32+
- FiveM native "Loading game" overlay hidden via inline `<head>` styles
33+
- Manual shutdown flow in `client.lua` — fires on `playerSpawned`, fades out, calls `ShutdownLoadingScreenNui()`
34+
- Runtime configuration via `config.js` (`window.CONFIG`) — no rebuild required for config changes
35+
- All configuration documented in `docs/CONFIGURATION.md`
36+
- Usage examples documented in `docs/USAGE_EXAMPLES.md`
37+
- Setup guide documented in `docs/SETUP.md`

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# pxLoadingScreen
2+
3+
Premium Loading Screen for FiveM. Supports background video, background image fallback, a music playlist with skip controls, animated progress bar with icon marker, info cards, rotating tips, and tabbed content panels.
4+
5+
All configuration is done through a single `config.js` file no rebuild required when making changes.
6+
7+
## Features
8+
9+
- Video background with image fallback
10+
- Music playlist with prev/next controls and volume slider
11+
- Animated progress bar with watermark icon marker
12+
- Simulated progress with configurable checkpoints
13+
- Real FiveM `loadProgress` event integration
14+
- Info cards with custom colors and icons
15+
- Rotating tips
16+
- Tabbed content panels
17+
- Theme color customization via CSS variables
18+
- Manual shutdown screen dismisses cleanly after player spawns
19+
- Optional skip key support
20+
- Hides FiveM's native "Loading game" overlay
21+
22+
## File Structure
23+
24+
```
25+
pxLoadingScreen/
26+
index.html # HTML entry point loads config.js then build/app.js
27+
config.js # Runtime configuration (edit without rebuilding)
28+
fxmanifest.lua # FiveM resource manifest
29+
client.lua # Manual shutdown handler (fires on playerSpawned)
30+
build/
31+
app.js # Compiled Svelte application
32+
media/ # Your audio/video/image files
33+
```
34+
35+
## Installation
36+
37+
1. Copy `pxLoadingScreen` into your server's `resources` directory.
38+
2. Add to `server.cfg`:
39+
```
40+
ensure pxLoadingScreen
41+
```
42+
3. Place your media files in the `media/` directory.
43+
4. Edit `config.js` to match your server.
44+
5. Restart the server.
45+
46+
## Configuration
47+
48+
Edit `config.js` directly. Changes take effect on server restart no rebuild needed.
49+
50+
See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for the full reference.
51+
52+
## Adding Media
53+
54+
Place files in the `media/` directory and reference them in `config.js`:
55+
56+
```js
57+
Media: {
58+
backgroundVideo: 'media/background.mp4',
59+
backgroundImage: 'media/background.jpg', // shown if video is disabled
60+
audioFile: 'media/track1.mp3',
61+
audioPlaylist: [
62+
'media/track2.mp3',
63+
'media/track3.mp3',
64+
],
65+
},
66+
```
67+
68+
Supported audio formats: MP3, WAV, OGG, M4A
69+
Supported video format: MP4 (H.264)
70+
71+
## How Progress Works
72+
73+
The loading screen receives real `loadProgress` events from FiveM and updates the bar automatically. If `simulateProgress` is enabled in `config.js`, it also steps through your configured checkpoints at evenly spaced intervals useful when the connection is fast and you want a minimum display duration. Real FiveM events always override simulated values.
74+
75+
## Manual Shutdown
76+
77+
`client.lua` listens for the `playerSpawned` event and sends a shutdown message to the loading screen before calling `ShutdownLoadingScreenNui()`. The screen fades out over the duration set in `General.audioFadeOut`.
78+
79+
## Support
80+
81+
- Discord: [discord.gg/BsEhHBTbXw](https://discord.gg/BsEhHBTbXw)
82+
- Email: [hey@codemeapixel.dev](mailto:hey@codemeapixel.dev)
83+
- Issues: [github.com/CodeMeAPixel/pxLoadingScreen/issues](https://github.com/CodeMeAPixel/pxLoadingScreen/issues)
84+
- Docs: [cmap.lol/docs/pxLoadingScreen](https://cmap.lol/docs/pxLoadingScreen)

assets/App.svelte

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<script>
2+
import { onMount, onDestroy } from 'svelte'
3+
import LoadingScreen from './components/LoadingScreen.svelte'
4+
import { progress, audio, config } from './stores.js'
5+
6+
onMount(() => {
7+
const cfg = window.CONFIG || getDevConfig()
8+
config.set(cfg)
9+
10+
// Apply theme colors as CSS custom properties
11+
const colors = cfg.Theme?.colors
12+
if (colors) {
13+
const root = document.documentElement
14+
if (colors.accent) root.style.setProperty('--accent', colors.accent)
15+
if (colors.accentRGB) root.style.setProperty('--accent-rgb', colors.accentRGB)
16+
if (colors.secondary) root.style.setProperty('--tech', colors.secondary)
17+
if (colors.secondaryRGB) root.style.setProperty('--tech-rgb', colors.secondaryRGB)
18+
if (colors.success) root.style.setProperty('--success', colors.success)
19+
if (colors.warning) root.style.setProperty('--warning', colors.warning)
20+
if (colors.danger) root.style.setProperty('--danger', colors.danger)
21+
}
22+
23+
// Apply font family
24+
if (cfg.Layout?.fontFamily) {
25+
document.documentElement.style.setProperty('--fontFamily', cfg.Layout.fontFamily)
26+
}
27+
28+
// Allow skip
29+
let skipEnabled = false
30+
if (cfg.General?.allowSkip) {
31+
const key = (cfg.General.skipKeyBinding || 'ENTER').toUpperCase()
32+
const onKey = (e) => {
33+
if (e.key.toUpperCase() === key || e.code.toUpperCase() === key) {
34+
skipEnabled = true
35+
document.removeEventListener('keydown', onKey)
36+
const screen = document.querySelector('.loading-screen')
37+
if (screen) {
38+
screen.style.transition = 'opacity 0.5s ease-out'
39+
screen.style.opacity = '0'
40+
}
41+
}
42+
}
43+
document.addEventListener('keydown', onKey)
44+
}
45+
46+
// Simulated progress
47+
let simTimer = null
48+
let realProgress = 0
49+
50+
if (cfg.Progress?.simulateProgress) {
51+
const duration = cfg.Progress.minDuration || 8000
52+
const checkpoints = cfg.Progress.checkpoints || [
53+
{ label: 'Initializing...', progress: 15 },
54+
{ label: 'Loading Resources...', progress: 35 },
55+
{ label: 'Connecting...', progress: 55 },
56+
{ label: 'Syncing Data...', progress: 75 },
57+
{ label: 'Almost There...', progress: 90 },
58+
]
59+
60+
let cpIndex = 0
61+
const interval = duration / (checkpoints.length + 1)
62+
63+
simTimer = setInterval(() => {
64+
if (cpIndex < checkpoints.length) {
65+
const cp = checkpoints[cpIndex]
66+
if (realProgress < cp.progress) {
67+
progress.update(cp.progress)
68+
progress.setLabel(cp.label)
69+
}
70+
cpIndex++
71+
} else {
72+
clearInterval(simTimer)
73+
}
74+
}, interval)
75+
}
76+
77+
window.addEventListener('message', (event) => {
78+
let item = event.data
79+
if (!item) return
80+
81+
if (typeof item === 'string') {
82+
try { item = JSON.parse(item) } catch (e) { return }
83+
}
84+
85+
if (item.eventName === 'loadProgress') {
86+
const fraction = item.loadFraction ?? 0
87+
const pct = Math.round(fraction * 100)
88+
realProgress = pct
89+
progress.update(pct)
90+
91+
if (pct >= 100) {
92+
progress.setLabel('Ready!')
93+
if (simTimer) clearInterval(simTimer)
94+
} else if (pct >= 75) {
95+
progress.setLabel('Almost there...')
96+
} else if (pct >= 50) {
97+
progress.setLabel('Loading resources...')
98+
} else if (pct >= 25) {
99+
progress.setLabel('Connecting...')
100+
} else if (pct > 0) {
101+
progress.setLabel('Initializing...')
102+
}
103+
}
104+
105+
if (item.eventName === 'startInitFunctionOrder') {
106+
progress.setLabel('Loading game...')
107+
}
108+
109+
if (item.type === 'shutdown') {
110+
if (simTimer) clearInterval(simTimer)
111+
const fadeMs = cfg.General?.audioFadeOut ?? 1000
112+
audio.stop()
113+
const screen = document.querySelector('.loading-screen')
114+
if (screen) {
115+
screen.style.transition = `opacity ${fadeMs}ms ease-out`
116+
screen.style.opacity = '0'
117+
}
118+
}
119+
})
120+
121+
return () => {
122+
if (simTimer) clearInterval(simTimer)
123+
}
124+
})
125+
126+
onDestroy(() => {
127+
audio.stop()
128+
})
129+
130+
function getDevConfig() {
131+
return {
132+
General: {
133+
enableAudio: false,
134+
audioVolume: 0.5,
135+
audioFadeOut: 1000,
136+
enableVideo: false,
137+
loopVideo: true,
138+
allowSkip: false,
139+
skipKeyBinding: 'ENTER',
140+
},
141+
Theme: {
142+
colors: {
143+
accent: '#ff6b35',
144+
accentRGB: '255, 107, 53',
145+
secondary: '#00d4ff',
146+
secondaryRGB: '0, 212, 255',
147+
success: '#06d6a0',
148+
warning: '#ffd700',
149+
danger: '#ef476f',
150+
},
151+
branding: {
152+
enabled: true,
153+
icon: {
154+
show: true,
155+
url: 'https://cmap.lol/icon.svg',
156+
size: 120,
157+
showGlow: true,
158+
},
159+
title: 'Your Server Name',
160+
subtitle: 'Loading...',
161+
},
162+
},
163+
Content: {
164+
loadingText: 'Initializing Server',
165+
loadingDescription: 'Please wait while we get things ready...',
166+
cards: [
167+
{
168+
title: 'Welcome',
169+
icon: 'wrench',
170+
description: 'Welcome to our server!',
171+
bgColor: 'rgba(255, 107, 53, 0.1)',
172+
borderColor: 'rgba(255, 107, 53, 0.3)',
173+
},
174+
{
175+
title: 'Features',
176+
icon: 'tools',
177+
description: 'Tons of custom content',
178+
bgColor: 'rgba(0, 212, 255, 0.1)',
179+
borderColor: 'rgba(0, 212, 255, 0.3)',
180+
},
181+
{
182+
title: 'Community',
183+
icon: 'person',
184+
description: 'Join our growing community',
185+
bgColor: 'rgba(6, 214, 160, 0.1)',
186+
borderColor: 'rgba(6, 214, 160, 0.3)',
187+
},
188+
],
189+
tips: [
190+
'Explore the city and discover hidden locations',
191+
'Join our Discord for updates and support',
192+
'Check out the controls in the settings menu',
193+
'Report bugs to help us improve',
194+
],
195+
},
196+
Media: {
197+
backgroundVideo: '',
198+
backgroundImage: '',
199+
audioFile: '',
200+
audioPlaylist: [],
201+
},
202+
Progress: {
203+
showProgressBar: true,
204+
progressColor: '#ff6b35',
205+
simulateProgress: true,
206+
minDuration: 5000,
207+
checkpoints: [
208+
{ label: 'Initializing Core', progress: 10 },
209+
{ label: 'Loading Resources', progress: 25 },
210+
{ label: 'Connecting to Server', progress: 50 },
211+
{ label: 'Syncing Data', progress: 75 },
212+
{ label: 'Finalizing Setup', progress: 90 },
213+
],
214+
},
215+
Tabs: {
216+
enabled: false,
217+
tabs: [],
218+
},
219+
Layout: {
220+
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
221+
},
222+
}
223+
}
224+
</script>
225+
226+
<LoadingScreen />
227+
228+
<style>
229+
:global(body) {
230+
margin: 0;
231+
padding: 0;
232+
overflow: hidden;
233+
}
234+
235+
:global(#app) {
236+
width: 100%;
237+
height: 100%;
238+
}
239+
</style>

0 commit comments

Comments
 (0)