Fortnite Season 1
by StormPanda35499 lines23.0 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fortnite Season 1</title>
<style>
*{box-sizing:border-box}
body{margin:0;font-family:Arial,sans-serif;color:white;background:linear-gradient(135deg,#123b76,#1687c5,#101d48);min-height:100vh}
button,input{font:inherit}button{cursor:pointer;font-weight:900;border:0;transition:.15s}button:hover{filter:brightness(1.15)}
.hidden{display:none!important}
#authScreen{min-height:100vh;display:flex;justify-content:center;align-items:center;padding:20px;background:linear-gradient(135deg,rgba(4,16,46,.7),rgba(7,77,132,.45))}
.authbox{width:100%;max-width:430px;padding:32px;background:#0a1d3aee;border:2px solid #548ac0;box-shadow:0 20px 60px #06132b80}
.logo{font-size:32px;font-weight:1000;font-style:italic;text-align:center;margin-bottom:6px}
.logo span{color:#ffda48}
.auth-sub{text-align:center;color:#bcd3ef;font-size:13px;margin-bottom:25px}
.auth-tabs{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:22px}
.auth-tabs button{padding:13px;background:#25476e;color:white}
.auth-tabs button.active{background:#ffcf3d;color:#14213c}
label{display:block;font-size:12px;font-weight:bold;margin:14px 0 7px}
input{width:100%;padding:13px;background:#07152c;border:1px solid #54749a;color:white;outline:none}
input:focus{border-color:#ffdb4d}
.primary{width:100%;padding:15px;background:#ffcf3d;color:#16233d;margin-top:20px}
#authMessage{min-height:35px;color:#ffd85b;font-size:13px;line-height:1.5;margin-top:14px}
.fineprint{font-size:11px;line-height:1.5;color:#9bb5d5;margin-top:14px}
header{height:70px;background:#07152df2;display:flex;align-items:center;padding:0 20px;gap:16px;position:sticky;top:0;z-index:5;border-bottom:2px solid #456b95}
.smalllogo{font-size:21px;font-weight:1000;font-style:italic}.smalllogo span{color:#ffda48}
nav{display:flex;gap:4px;flex-wrap:wrap}
nav button{background:transparent;color:#c2d3e9;padding:13px 12px}
nav button.active{color:white;border-bottom:3px solid #ffda48;background:#ffffff12}
.userbar{margin-left:auto;font-size:12px;font-weight:bold;display:flex;align-items:center;gap:10px}
.avatar{background:#f8ce45;color:#17223c;border-radius:50%;width:34px;height:34px;display:grid;place-items:center}
.logout{background:#294464;color:white;padding:9px}
.screen{display:none;min-height:calc(100vh - 70px)}.screen.active{display:block}
.lobby{display:flex;align-items:center;gap:20px;padding:35px 5%;min-height:calc(100vh - 70px);position:relative;overflow:hidden}
.left{width:330px;max-width:100%;z-index:2}
.season{font-size:12px;color:#ffe16a;letter-spacing:2px;font-weight:1000}
h1{font-size:36px;font-style:italic;margin:12px 0;font-weight:1000}
.sub{color:#c8ddf5;font-size:13px;line-height:1.5}
.card{background:#081a3cce;border:1px solid #7694b4;padding:16px;margin-top:22px}
.modes{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px}
.mode{background:#29476f;color:white;padding:14px 4px;border:2px solid transparent}
.mode.selected{border-color:#ffdb49;background:#3c6393}
#play{width:100%;padding:17px;background:#ffcf3d;color:#17233b;font-size:20px;margin-top:15px}
#unready{width:100%;padding:12px;background:#29476f;color:white;margin-top:10px}
#status{font-size:11px;margin-top:14px;color:#d4e4f7;font-weight:bold}
.character-area{flex:1;min-width:210px;min-height:480px;display:flex;flex-direction:column;justify-content:center;align-items:center;position:relative}
.charname{font-size:20px;font-weight:1000;z-index:1;text-shadow:0 2px 5px #000;margin-bottom:15px}
.charname small{display:block;text-align:center;color:#ffe16a;font-size:10px;letter-spacing:2px;margin-top:5px}
.figure{width:190px;height:365px;position:relative;z-index:1;animation:float 3s ease-in-out infinite;filter:drop-shadow(0 10px 8px #06132c70)}
@keyframes float{50%{transform:translateY(-8px)}}
.head{position:absolute;width:94px;height:100px;left:48px;top:15px;background:#e5b98e;border:3px solid #17213a;border-radius:28px 28px 23px 23px;z-index:4}
.hair{position:absolute;left:46px;top:8px;width:98px;height:31px;border-radius:35px 35px 10px 10px;background:#30291f;border:3px solid #17213a;z-index:5}
.eye{position:absolute;width:9px;height:12px;top:45px;left:20px;background:#17213a;border-radius:50%}
.eye.right{left:59px}
.mouth{position:absolute;width:31px;height:12px;left:28px;top:70px;border-bottom:4px solid #803d32;border-radius:50%}
.body{position:absolute;top:108px;left:33px;width:124px;height:148px;background:#2779c7;border:4px solid #17213a;border-radius:22px 22px 12px 12px;z-index:3}
.belt{position:absolute;bottom:15px;left:-3px;height:19px;width:124px;background:#23344e;border-top:3px solid #17213a;border-bottom:3px solid #17213a}
.buckle{position:absolute;width:19px;height:19px;left:48px;bottom:13px;background:#e4bc45;border:3px solid #17213a;z-index:2}
.arm{position:absolute;top:120px;width:37px;height:124px;background:#e5b98e;border:4px solid #17213a;border-radius:17px;z-index:2}
.arm.left{left:7px;transform:rotate(8deg)}.arm.right{right:7px;transform:rotate(-8deg)}
.sleeve{position:absolute;top:119px;width:42px;height:47px;background:#2779c7;border:4px solid #17213a;z-index:4;border-radius:12px}
.sleeve.left{left:3px;transform:rotate(8deg)}.sleeve.right{right:3px;transform:rotate(-8deg)}
.legs{position:absolute;top:248px;left:38px;width:114px;height:122px;display:flex;gap:5px;z-index:2}
.leg{position:relative;flex:1;background:#283e66;border:4px solid #17213a;border-radius:8px 8px 13px 13px}
.leg:after{content:"";position:absolute;height:22px;bottom:-7px;left:-5px;right:-5px;background:#161e2b;border:3px solid #101726;border-radius:8px}
.platform{width:270px;height:48px;background:linear-gradient(#9ac9ef,#3569a3);border:3px solid #ffffff80;transform:perspective(300px) rotateX(50deg);border-radius:50%;box-shadow:0 0 40px #7dc6ff55;margin-top:-8px}
.clowner .hair{background:#e83a45;height:35px}
.clowner .head{background:#f5f1e8}
.clowner .body{background:#e63d4e}
.clowner .sleeve{background:#f4cf43}
.clowner .arm{background:#f5f1e8}
.clowner .leg{background:#463c88}
.clowner .eye{width:12px;height:14px}
.clowner .mouth{width:48px;height:19px;left:20px;top:65px;background:#bb263e;border:3px solid #7f1830;border-radius:8px 8px 50% 50%}
.clown-nose{display:none;position:absolute;top:53px;left:34px;width:23px;height:23px;background:#f52b39;border:3px solid #a71426;border-radius:50%;z-index:8}
.clowner .clown-nose{display:block}
.clown-hat{display:none;position:absolute;left:59px;top:-9px;width:73px;height:26px;background:#f4cf43;border:3px solid #17213a;border-radius:50% 50% 5px 5px;z-index:6;transform:rotate(-8deg)}
.clowner .clown-hat{display:block}
.clown-cheek{display:none;position:absolute;top:62px;width:15px;height:10px;background:#ed4354;border-radius:50%}
.clown-cheek.left{left:7px}.clown-cheek.right{right:7px}
.clowner .clown-cheek{display:block}
.page{padding:35px 5%}
.page-title{font-size:34px;font-weight:1000;font-style:italic}
.locker-layout{display:grid;grid-template-columns:1.2fr .8fr;gap:20px;max-width:1050px}
.skin-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.skin-card{background:#081a3cce;color:white;border:2px solid #ffffff30;padding:15px;min-width:0;text-align:left;overflow:hidden}
.skin-card.selected{border-color:#ffdb49}
.skin-card.equipped{box-shadow:inset 0 -4px #ffdb49}
.skin-card .figure{transform:scale(.55);transform-origin:top center;margin:0 auto -165px;animation:none}
.skin-name{display:block;font-size:17px;font-weight:1000;margin-top:8px}
.rarity{font-size:10px;color:#b5d0f0;display:block;margin-top:5px}
.preview{min-height:430px;background:#081a3cce;border:1px solid #ffffff30;display:flex;align-items:center;flex-direction:column;justify-content:center;overflow:hidden}
.preview .figure{transform:scale(.75);transform-origin:center;margin-bottom:-65px}
.preview-name{font-size:25px;font-weight:1000;margin-top:12px}
.preview-rarity{font-size:11px;color:#ffdf5c;margin-top:5px}
#equip{width:80%;padding:15px;background:#ffcf3d;color:#17233b;margin-top:17px}
#equip:disabled{background:#526a85;color:white}
#lockerNotice{font-size:12px;color:#bfe5ff;margin:12px;text-align:center}
.notice{font-size:12px;color:#c5d9f3;margin-top:12px}
@media(max-width:750px){header{height:auto;min-height:70px;flex-wrap:wrap;padding:10px}.userbar{margin-left:auto}.lobby{flex-direction:column;align-items:stretch}.character-area{min-height:440px;width:100%}.locker-layout{grid-template-columns:1fr}.page{padding:25px 4%}h1{font-size:30px}}
</style>
</head>
<body>
<!-- ACCOUNT SCREEN -->
<section id="authScreen">
<div class="authbox">
<div class="logo">FORTNITE<span>★</span></div>
<div class="auth-sub">CHAPTER 1 · SEASON 1</div>
<div class="auth-tabs">
<button id="loginTab" class="active">LOG IN</button>
<button id="signinTab">SIGN IN</button>
</div>
<form id="authForm">
<label for="username">USERNAME</label>
<input id="username" maxlength="20" minlength="3"
autocomplete="username" placeholder="Choose a username" required>
<label for="password">PASSWORD</label>
<input id="password" type="password" minlength="6"
autocomplete="current-password" placeholder="Enter your password" required>
<button id="authSubmit" class="primary" type="submit">CREATE ACCOUNT</button>
</form>
<div id="authMessage" role="status"></div>
<div class="fineprint">
Use a made-up password for this demo, not a password you use for other accounts.
Accounts are saved only in this browser. This is a prototype, not a secure online account system.
</div>
</div>
</section>
<!-- GAME -->
<div id="game" class="hidden">
<header>
<div class="smalllogo">FORTNITE<span>★</span></div>
<nav>
<button class="active" data-tab="lobby">LOBBY</button>
<button data-tab="locker">LOCKER</button>
<button data-tab="shop">ITEM SHOP</button>
<button data-tab="career">CAREER</button>
</nav>
<div class="userbar">
<div class="avatar">1</div>
<span id="headerUser">PLAYER</span>
<button id="logout" class="logout">LOG OUT</button>
</div>
</header>
<main id="lobby" class="screen active">
<div class="lobby">
<section class="left">
<div class="season">CHAPTER 1 · SEASON 1</div>
<h1>BATTLE ROYALE</h1>
<div class="sub">The original island. The original vibe.<br>Drop in and be the last player standing.</div>
<div class="card">
<b>SELECT GAME MODE</b>
<div class="modes">
<button class="mode selected" data-mode="SOLO">👤 SOLO</button>
<button class="mode" data-mode="DUOS">👥 DUOS</button>
</div>
<p id="modeDescription" class="sub">Every player for themselves.</p>
<button id="play">PLAY</button>
<button id="unready" class="hidden">UNREADY</button>
<div id="status">● ONLINE · IN LOBBY</div>
</div>
</section>
<section class="character-area">
<div class="charname"><span id="lobbySkinName">Blue</span><small>OUTFIT · EQUIPPED</small></div>
<div id="lobbyFigure" class="figure">
<div class="clown-hat"></div><div class="hair"></div>
<div class="head"><div class="eye"></div><div class="eye right"></div><div class="clown-cheek left"></div><div class="clown-cheek right"></div><div class="clown-nose"></div><div class="mouth"></div></div>
<div class="arm left"></div><div class="arm right"></div><div class="sleeve left"></div><div class="sleeve right"></div>
<div class="body"><div class="belt"></div><div class="buckle"></div></div>
<div class="legs"><div class="leg"></div><div class="leg"></div></div>
</div>
<div class="platform"></div>
</section>
</div>
</main>
<main id="locker" class="screen">
<section class="page">
<h1 class="page-title">MY LOCKER</h1>
<p class="sub">Choose your outfit. Your account keeps its equipped skin.</p>
<div class="locker-layout">
<div class="skin-grid">
<button class="skin-card" data-skin="Blue">
<div class="figure">
<div class="hair"></div><div class="head"><div class="eye"></div><div class="eye right"></div><div class="mouth"></div></div>
<div class="arm left"></div><div class="arm right"></div><div class="sleeve left"></div><div class="sleeve right"></div>
<div class="body"><div class="belt"></div><div class="buckle"></div></div>
<div class="legs"><div class="leg"></div><div class="leg"></div></div>
</div>
<span class="skin-name">Blue</span><span class="rarity">COMMON OUTFIT</span>
</button>
<button class="skin-card" data-skin="Clowner">
<div class="figure clowner">
<div class="clown-hat"></div><div class="hair"></div>
<div class="head"><div class="eye"></div><div class="eye right"></div><div class="clown-cheek left"></div><div class="clown-cheek right"></div><div class="clown-nose"></div><div class="mouth"></div></div>
<div class="arm left"></div><div class="arm right"></div><div class="sleeve left"></div><div class="sleeve right"></div>
<div class="body"><div class="belt"></div><div class="buckle"></div></div>
<div class="legs"><div class="leg"></div><div class="leg"></div></div>
</div>
<span class="skin-name">Clowner 🤡</span><span class="rarity">UNCOMMON OUTFIT</span>
</button>
</div>
<div class="preview">
<div id="previewFigure" class="figure">
<div class="clown-hat"></div><div class="hair"></div>
<div class="head"><div class="eye"></div><div class="eye right"></div><div class="clown-cheek left"></div><div class="clown-cheek right"></div><div class="clown-nose"></div><div class="mouth"></div></div>
<div class="arm left"></div><div class="arm right"></div><div class="sleeve left"></div><div class="sleeve right"></div>
<div class="body"><div class="belt"></div><div class="buckle"></div></div>
<div class="legs"><div class="leg"></div><div class="leg"></div></div>
</div>
<div class="platform"></div>
<div id="previewName" class="preview-name">Blue</div>
<div id="previewRarity" class="preview-rarity">COMMON OUTFIT</div>
<button id="equip" disabled>EQUIPPED</button>
<div id="lockerNotice">Blue is equipped.</div>
</div>
</div>
</section>
</main>
<main id="shop" class="screen">
<section class="page"><h1>ITEM SHOP</h1><p class="sub">The shop isn't available yet.</p><button class="primary" onclick="showTab('lobby')">BACK TO LOBBY</button></section>
</main>
<main id="career" class="screen">
<section class="page"><h1>CAREER</h1><p class="sub">PLAYER: <span id="careerUser"></span></p><p class="sub">OUTFIT: <span id="careerSkin"></span></p><button class="primary" onclick="showTab('lobby')">BACK TO LOBBY</button></section>
</main>
</div>
<script>
const ACCOUNT_KEY = "season1_demo_accounts_v1";
const SESSION_KEY = "season1_demo_session_v1";
let authMode = "login";
let currentUser = null;
let selectedMode = "SOLO";
let selectedSkin = "Blue";
let equippedSkin = "Blue";
let isMatchmaking = false;
const $ = id => document.getElementById(id);
function getAccounts() {
try {
return JSON.parse(localStorage.getItem(ACCOUNT_KEY) || "{}");
} catch {
return {};
}
}
function saveAccounts(accounts) {
localStorage.setItem(ACCOUNT_KEY, JSON.stringify(accounts));
}
// Usernames are case-insensitive: "Prem" and "prem" count as the same name.
function usernameKey(name) {
return name.trim().toLowerCase();
}
function setAuthMode(mode) {
authMode = mode;
$("loginTab").classList.toggle("active", mode === "login");
$("signinTab").classList.toggle("active", mode === "signin");
$("authSubmit").textContent = mode === "login" ? "CREATE ACCOUNT" : "SIGN IN";
$("username").placeholder = mode === "login" ? "Choose a username" : "Your username";
$("password").autocomplete = mode === "login" ? "new-password" : "current-password";
$("authMessage").textContent = "";
}
$("loginTab").addEventListener("click", () => setAuthMode("login"));
$("signinTab").addEventListener("click", () => setAuthMode("signin"));
async function hashPassword(password) {
// Hashing is better than storing plain text, but browser-only storage is
// still not a secure replacement for a real authentication server.
if (window.crypto && crypto.subtle) {
const data = new TextEncoder().encode(password);
const hash = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, "0")).join("");
}
// Fallback for environments without Web Crypto. Demo use only.
return "demo-" + btoa(unescape(encodeURIComponent(password)));
}
$("authForm").addEventListener("submit", async event => {
event.preventDefault();
const username = $("username").value.trim();
const password = $("password").value;
if (!/^[A-Za-z0-9_]{3,20}$/.test(username)) {
$("authMessage").textContent =
"Username must be 3–20 characters: letters, numbers, and underscores only.";
return;
}
if (password.length < 6) {
$("authMessage").textContent = "Password must be at least 6 characters.";
return;
}
const key = usernameKey(username);
const accounts = getAccounts();
const passHash = await hashPassword(password);
if (authMode === "login") {
if (accounts[key]) {
$("authMessage").textContent =
"That username is already taken. Choose another one or sign in.";
return;
}
// Volxer alone starts with Clowner. Every other new account starts with Blue.
accounts[key] = {
username,
passwordHash: passHash,
skin: key === "volxer" ? "Clowner" : "Blue"
};
saveAccounts(accounts);
signInUser(key);
return;
}
if (!accounts[key]) {
$("authMessage").textContent = "Account not found. Create an account first.";
return;
}
if (accounts[key].passwordHash !== passHash) {
$("authMessage").textContent = "Incorrect password. Try again.";
return;
}
signInUser(key);
});
function signInUser(key) {
const accounts = getAccounts();
const account = accounts[key];
if (!account) return;
currentUser = key;
selectedSkin = account.skin || (key === "volxer" ? "Clowner" : "Blue");
equippedSkin = selectedSkin;
// Keep the selected account logged in within this browser.
sessionStorage.setItem(SESSION_KEY, key);
$("authScreen").classList.add("hidden");
$("game").classList.remove("hidden");
$("headerUser").textContent = account.username;
$("careerUser").textContent = account.username;
updateSkinUI();
showTab("lobby");
}
function logout() {
sessionStorage.removeItem(SESSION_KEY);
currentUser = null;
isMatchmaking = false;
$("game").classList.add("hidden");
$("authScreen").classList.remove("hidden");
$("password").value = "";
$("authMessage").textContent = "";
$("play").textContent = "PLAY";
$("play").disabled = false;
$("unready").classList.add("hidden");
$("status").textContent = "● ONLINE · IN LOBBY";
setAuthMode("signin");
}
$("logout").addEventListener("click", logout);
function showTab(tabName) {
document.querySelectorAll(".screen").forEach(s => s.classList.remove("active"));
$(tabName).classList.add("active");
document.querySelectorAll("nav button").forEach(b => {
b.classList.toggle("active", b.dataset.tab === tabName);
});
}
document.querySelectorAll("nav button").forEach(button => {
button.addEventListener("click", () => showTab(button.dataset.tab));
});
document.querySelectorAll(".mode").forEach(button => {
button.addEventListener("click", () => {
selectedMode = button.dataset.mode;
document.querySelectorAll(".mode").forEach(b => b.classList.toggle("selected", b === button));
$("modeDescription").textContent = selectedMode === "SOLO"
? "Every player for themselves. Be the last one standing."
: "Team up with one friend and fight to be the last duo.";
if (isMatchmaking) $("status").textContent = "● MATCHMAKING · " + selectedMode;
});
});
$("play").addEventListener("click", () => {
isMatchmaking = true;
$("play").textContent = "MATCHMAKING...";
$("play").disabled = true;
$("unready").classList.remove("hidden");
$("status").textContent = "● MATCHMAKING · " + selectedMode;
});
$("unready").addEventListener("click", () => {
isMatchmaking = false;
$("play").textContent = "PLAY";
$("play").disabled = false;
$("unready").classList.add("hidden");
$("status").textContent = "● ONLINE · IN LOBBY";
});
document.querySelectorAll(".skin-card").forEach(card => {
card.addEventListener("click", () => {
selectedSkin = card.dataset.skin;
updateSkinUI();
});
});
$("equip").addEventListener("click", () => {
if (!currentUser) return;
// Volxer is the special account with Clowner permanently assigned.
if (currentUser === "volxer") {
equippedSkin = "Clowner";
selectedSkin = "Clowner";
$("lockerNotice").textContent = "Clowner is exclusive to the Volxer account!";
} else {
equippedSkin = selectedSkin;
$("lockerNotice").textContent = equippedSkin + " equipped!";
}
const accounts = getAccounts();
accounts[currentUser].skin = equippedSkin;
saveAccounts(accounts);
updateSkinUI();
});
function updateSkinUI() {
const isVolxer = currentUser === "volxer";
if (isVolxer) {
equippedSkin = "Clowner";
selectedSkin = "Clowner";
}
$("lobbyFigure").classList.toggle("clowner", equippedSkin === "Clowner");
$("lobbySkinName").textContent = equippedSkin;
$("careerSkin").textContent = equippedSkin;
$("previewFigure").classList.toggle("clowner", selectedSkin === "Clowner");
$("previewName").textContent = selectedSkin;
$("previewRarity").textContent =
selectedSkin === "Clowner" ? "UNCOMMON OUTFIT" : "COMMON OUTFIT";
document.querySelectorAll(".skin-card").forEach(card => {
const skin = card.dataset.skin;
card.classList.toggle("selected", skin === selectedSkin);
card.classList.toggle("equipped", skin === equippedSkin);
});
if (isVolxer) {
$("equip").disabled = true;
$("equip").textContent = "VOLXER EXCLUSIVE";
$("lockerNotice").textContent = "Clowner is exclusive to the Volxer account!";
} else if (selectedSkin === equippedSkin) {
$("equip").disabled = true;
$("equip").textContent = "EQUIPPED";
$("lockerNotice").textContent = equippedSkin + " is equipped.";
} else {
$("equip").disabled = false;
$("equip").textContent = "EQUIP " + selectedSkin.toUpperCase();
$("lockerNotice").textContent = "Choose EQUIP to wear " + selectedSkin + ".";
}
}
// Restore the current session if the page reloads in the same tab.
const savedSession = sessionStorage.getItem(SESSION_KEY);
if (savedSession && getAccounts()[savedSession]) {
signInUser(savedSession);
}
</script>
</body>
</html>
Game Source: Fortnite Season 1
Creator: StormPanda35
Libraries: none
Complexity: complex (499 lines, 23.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: fortnite-season-1-stormpanda35" to link back to the original. Then publish at arcadelab.ai/publish.