NullSect
Systems nominal

Docs / Loading Screen / Configuration

Configuration — ns-loading

config.js

Everything lives in html/config.js. You never need to touch HTML, CSS or JS.

Text values can be a plain string or one string per language:

tagline: 'Your story starts here'
tagline: { en: 'Your story starts here', tr: 'Hikâyen burada başlıyor' }

File paths are relative to the html/ folder, e.g. 'assets/images/logo.png'.

Mistakes are safe. A wrong type, an invalid colour or an unsafe URL is replaced by the default and reported once in the console. Open the browser preview to see a count of config warnings.

brand: {
    serverName: 'YOUR CITY',
    tagline: { en: 'Your story starts here', tr: 'Hikâyen burada başlıyor' },
    logo: '',              // 'assets/images/logo.png'
    logoHeight: 44,        // px at 1080p, scales with resolution
    monogram: '',          // e.g. 'YC'; empty = initials of serverName
    showServerName: true,  // set false if your logo already contains the name
    showTagline: true
}

To change the logo: put your file (PNG with transparency, SVG or WebP; around 2× the displayed height looks sharp) in html/assets/images/, then set logo: 'assets/images/your-logo.png'. If the logo is empty or fails to load, a typographic monogram built from your server name is shown instead.

Hero text

hero: {
    eyebrow: { en: 'Welcome to Los Santos', tr: "Los Santos'a hoş geldin" },
    title:   { en: 'Your next\n*chapter* begins', tr: 'Yeni *bölümün*\nbaşlıyor' },
    subtitle: { en: 'Los Santos awaits. Make your story count.', tr: '…' },
    stats: [
        { live: 'players', label: { en: 'In the city', tr: 'Şehirde' } },
        { live: 'discordOnline', label: { en: 'Online on Discord', tr: "Discord'da aktif" } },
        { value: '18+', label: { en: 'Age requirement', tr: 'Yaş sınırı' } }
    ]
},

live: {
    discordInvite: ''   // empty = the discord link from `links`
}
  • In title, \n starts a new line and *word* gets the brushed-chrome style.
  • Very long titles shrink automatically to fit (up to 3 lines).
  • stats holds up to 4 short facts about your server. Use [] to hide the row.
  • Live stats show a pulsing dot next to the label:
    • live: 'players': players on the server, e.g. 87/128. Needs server/handover.lua (see handover). The number is taken when the player starts connecting.
    • live: 'discordOnline' / live: 'discordMembers': online / total members from Discord's public invite API. No bot or token needed, but the invite must be permanent. Set live.discordInvite (a code like chromeheart or a full link), or leave it empty to use the discord link from links.
    • An optional value is shown while the live number is unavailable. Without one, the stat stays hidden until the number arrives, so the screen never shows a made-up count.
    • Discord counts are cached for 24 hours, so the next connection shows them instantly. In the browser preview the player count is simulated (64/128).

Background

appearance: {
    background: {
        type: 'scene',        // 'scene' | 'image' | 'slideshow' | 'video'
        image: '',            // 'assets/images/background.jpg'
        slides: [],           // ['assets/images/bg-1.jpg', 'assets/images/bg-2.jpg']
        slideInterval: 9000,
        video: '',            // 'assets/videos/background.webm'
        dim: 0.35,            // 0–1 extra darkening for readability
        kenBurns: true        // slow zoom on images
    },
    scene: { stars: true, searchlights: true, fog: true, aircraft: true, palms: true },
    grain: true
}
TypeWhat to do
sceneNothing. This is the built-in skyline.
imagePut a JPG/WebP (1920×1080 or larger, ideally under 1 MB) in html/assets/images/ and set image.
slideshowPut several images in html/assets/images/ and list them in slides. Broken slides are skipped.
videoPut a WebM (VP9) file in html/assets/videos/ and set video. It plays muted and looped.

Video notes: FiveM's browser (CEF) does not reliably play H.264 MP4. Use WebM/VP9. Keep it short (10–30 s loop), 1080p and ideally under 15 MB, because every player downloads it while connecting. A conversion example with ffmpeg:

ffmpeg -i input.mp4 -an -c:v libvpx-vp9 -b:v 2M -vf scale=1920:-2 -deadline good background.webm

If an image or video cannot be loaded, the built-in scene appears automatically. You will never get a white or empty screen.

Colours & fonts

appearance: {
    accentColor: '#B8F36B',   // progress bar, active tab, small markers
    colors: { background: '', surface: '', text: '', textMuted: '', chrome: '' },
    fonts: {
        display: { family: '', src: '' },   // headings and numbers
        body:    { family: '', src: '' }    // paragraphs and UI
    }
}
  • Colours must be hex (#RRGGBB or #RGB). Empty values keep the NullSect palette.
  • The accent is used sparingly on purpose. A single strong colour reads as premium.
  • Default fonts: Bahnschrift (headings) and Segoe UI (text). Both ship with Windows 10/11, so every FiveM player has them and nothing is downloaded.
  • Custom font: put a .woff2 file in html/assets/fonts/ and set e.g. display: { family: 'Oswald', src: 'assets/fonts/Oswald.woff2' }. Make sure the font's licence allows embedding. If it fails to load, the default font stays.

Music

audio: {
    enabled: true,
    tracks: [
        { src: 'assets/audio/theme.ogg', title: 'Night Drive', artist: 'Your Artist', cover: 'assets/images/cover.jpg' }
    ],
    autoplay: true,
    volume: 0.25,
    shuffle: false,
    rememberVolume: true
}
  1. Put OGG or MP3 files in html/assets/audio/.
  2. List them in tracks and set enabled: true.
  • Only use music you own or are licensed to use. Replace the example track before going live.
  • The player is a card above the info panel: cover, title, artist, a seekable progress bar with times, and previous / play-pause / next buttons.
  • cover is optional: a square image (JPG/PNG/WebP) in html/assets/images/. Without it, or if it fails to load, a themed placeholder is shown.
  • With several tracks, "next" is enabled and tracks advance automatically. "Previous" restarts the track, and a second press goes back one track.
  • Autoplay: the screen tries to start the music automatically. FiveM normally allows this. If autoplay is blocked, the player shows "Press play for music" with a gentle highlight and never shows a fake "playing" state.
  • Missing or broken files are skipped. If none can play, the player shows "Music unavailable" with disabled controls, and the rest of the screen keeps working.
  • Players can press M to mute or unmute. Their volume and mute choice is remembered (rememberVolume).
  • With enabled: false, or with no tracks, the player is hidden completely.

Tips

tips: {
    enabled: true,
    interval: 7000,        // ms per tip (min 2500)
    shuffle: false,
    controls: true,        // ‹ › buttons
    items: [
        { en: 'Your choices shape your story.', tr: 'Seçimlerin hikâyeni şekillendirir.' },
        'A plain-text tip works too.'
    ]
}
  • Add as many tips as you like. Long tips switch to a smaller size and are clamped neatly.
  • Hovering a tip pauses its countdown. Players can also use ← →.
  • With an empty list or enabled: false, the module disappears and the layout closes the gap.

Info panel

A tabbed panel on the right. Sections appear in the order you list them. Remove one or set enabled: false to hide it. title is optional, and a translated default is used.

panel: {
    enabled: true,
    autoRotate: 0,          // ms between automatic tab changes (0 = off, min 3000)
    sections: [
        { type: 'about', text: { en: '…', tr: '…' },
          facts: [ { label: { en: 'Economy', tr: 'Ekonomi' }, value: { en: 'Player-driven', tr: 'Oyuncu odaklı' } } ] },

        { type: 'rules', items: [
            { title: { en: 'Respect everyone', tr: 'Herkese saygı' }, text: { en: '…', tr: '…' } },
            'A rule can also be a single line'
        ] },

        { type: 'news', items: [
            { tag: 'Update', date: '2026-10-01', title: 'Season one is live', text: '…' }
        ] },

        { type: 'links', title: { en: 'Community', tr: 'Topluluk' } },  // shows the `links` block

        { type: 'team', members: [
            { name: 'Alex', role: { en: 'Founder', tr: 'Kurucu' }, avatar: 'assets/images/team/alex.png' },
            { name: 'Sam', role: { en: 'Developer', tr: 'Geliştirici' } }
        ] }
    ]
}
  • date in YYYY-MM-DD format is shown in the player's language ("1 Oct 2026" / "1 Eki 2026"). Any other text is shown as written.
  • Empty sections are hidden automatically. If no section has content, the panel disappears and the hero uses the full width.
  • Long content scrolls inside the panel and never pushes the page.
  • team: avatar is optional, either a file in html/assets/images/ or an https:// image such as a Discord avatar link. Without one, or if it fails to load, the initials are shown.
  • When there are more tabs than fit, the tab row scrolls (mouse wheel works too), the active tab is kept in view, and the hidden edge fades out.

Remote content

Update the news and the team from your own website, with no resource restart and no new download for players.

remote: {
    url: 'https://your-city.com/loading.json',
    timeout: 5000      // ms to wait before falling back
}
  1. Copy remote-content.example.json (project root) to your site, edit it and point remote.url at it. Both keys are optional:

    {
      "news": [ { "tag": { "en": "Update", "tr": "Güncelleme" }, "date": "2026-10-10", "title": "…", "text": "…" } ],
      "team": [ { "name": "Alex", "role": { "en": "Founder", "tr": "Kurucu" }, "avatar": "https://…" } ]
    }
    
  2. The file must be served over https with the header Access-Control-Allow-Origin: *, otherwise the loading screen is not allowed to read it:

    • nginx: location = /loading.json { add_header Access-Control-Allow-Origin "*"; }
    • Apache (.htaccess): <Files "loading.json">Header set Access-Control-Allow-Origin "*"</Files>
    • PHP: header('Access-Control-Allow-Origin: *'); header('Content-Type: application/json');
    • Cloudflare Pages / Netlify: a _headers file with /loading.json followed by Access-Control-Allow-Origin: *
    • Vercel: a headers entry for /loading.json in vercel.json

How it behaves:

  • What the file sends replaces the items of the matching panel section (news / team). If config.js has no such section, one is added (unless you set it to enabled: false).
  • Every value is validated like config.js and always shown as plain text, never HTML.
  • If your site is slow or down, the screen shows the last downloaded copy (kept for 24 hours), or else the content from config.js. Nothing blocks or breaks.
  • In the browser preview (?preview=1), problems with the file appear as warnings in the console.
links: {
    showInHeader: true,
    items: [
        { type: 'discord', url: 'https://discord.gg/your-invite' },
        { type: 'website', url: 'https://your-city.com' },
        { type: 'store',   url: 'https://store.your-city.com', label: { en: 'Store', tr: 'Mağaza' } }
    ]
}
  • Types: discord, website, store, instagram, youtube, tiktok, x, link.
  • Only http:// and https:// addresses are accepted. A bare domain such as discord.gg/abc is upgraded to https. Anything else is rejected and logged as a config warning.
  • Links appear as icon buttons in the header (with a tooltip showing the domain) and in the links panel section (showing the full domain, so players can also type it).
  • In FiveM, links open in the player's own browser. The loading screen itself never navigates away.

Language

locale: 'en'   // 'en' or 'tr'
  • All interface strings live in html/locales/en.js and html/locales/tr.js. Your own content (titles, tips, rules…) uses { en: '…', tr: '…' } objects in config.js.
  • If a translation is missing, English is used.
  • Adding a language: copy locales/en.js to e.g. locales/de.js, change .en = to .de = and translate it. Then add <script src="locales/de.js"></script> to index.html next to the other locale scripts, and set locale: 'de'. The manifest already includes html/locales/*.js.

Loading progress

progress: {
    showBar: true,
    showPercentage: true,
    showStatus: true,       // "Loading game data", "Joining the session"…
    showSteps: true,        // real FiveM step counter, e.g. "Step 12 / 48"
    showElapsed: true,      // time on the loading screen
    monotonic: true,        // never move the bar backwards
    showLogLines: false     // FiveM's raw log lines (technical)
}

monotonic: true keeps the highest value FiveM has reported. FiveM's loadFraction can occasionally dip between phases, and a bar that jumps backwards looks broken. Set it to false to display the raw value.

Motion & performance

motion: {
    intro: true,               // opening animation (~1.4 s)
    reducedMotion: 'auto',     // 'auto' follows the OS setting, 'always', 'never'
    performance: 'auto'        // 'auto' | 'high' | 'low'
}
  • Reduced motion: ambient scene motion, the intro and the sheen are turned off. Content still updates, without movement.
  • Low performance mode removes the panel blur and all continuous ambient animation (drift, fog, searchlights, grain) and keeps the look. auto picks low on machines reporting ≤ 4 CPU threads or ≤ 4 GB memory. This is a heuristic, so set 'low' explicitly if your players have weak PCs.
  • Animations pause automatically when the window is hidden.

NullSect credit / white-label

developer: { showCredit: true }

The small "Crafted by NullSect" signature sits in the bottom-right corner. Set showCredit: false for a fully white-label screen.

Interface options

ui: {
    showAudioControls: true,   // false = music plays (if enabled) without the player UI; M still mutes
    showKeyboardHints: true,   // the "M Mute · ← → Tips" hints
    keyboardShortcuts: true
}

Preview in a browser

You can design and test without FiveM:

  1. Open html/index.html in Chrome or Edge, adding ?preview=1 to the address:

    file:///C:/path/to/ns-loading/html/index.html?preview=1
    

    Or serve the html folder with any static server (e.g. npx serve html) and open http://localhost:3000/?preview=1.

  2. An amber PREVIEW · Simulated loading data bar appears with tools: restart, pause, No data (the state before FiveM sends anything), language switch, sample player name and a fade-out test. It also shows a config warning count, and the details are printed in the console (F12).

The simulator sends messages in FiveM's exact format through the same code path the game uses. It only exists with ?preview=1. FiveM never opens the page with that flag, so production can never show simulated data. To disable the flag entirely, set preview.allowUrlFlag: false.

URL options: lang=tr, sim=0 (start without data), speed=2 (faster simulation), name=Alex, reduced=1 (reduced motion), perf=low.

Without ?preview=1, a browser shows exactly what FiveM shows before loading data arrives: the standby state. That is expected.

Cart

Your cart is empty.