Streaming — Profile Switcher
A cinematic who-is-watching profile-select screen for a fictional streaming service. A centered grid of gradient avatar tiles greets the viewer, each scaling up with a glowing ring on hover, kids profiles wearing a badge. A manage mode flips every tile into edit cards with rename prompts and remove buttons, while an add-profile dialog creates new viewers with a live avatar preview and a kids toggle. Selecting a profile plays a full-screen loading curtain before welcoming you in.
MCP
Code
:root {
--bg: #0b0b0f;
--surface: #15151c;
--surface-2: #1e1e27;
--ink: #f4f4f7;
--ink-2: #b6b7c3;
--muted: #83859a;
--brand: #e50914;
--accent: #ffffff;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.16);
--r-sm: 8px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
--glow: 0 0 0 2px var(--accent), 0 18px 40px rgba(0, 0, 0, 0.6);
--c-1: linear-gradient(150deg, #f7b733, #fc4a1a);
--c-2: linear-gradient(150deg, #4facfe, #00f2fe);
--c-3: linear-gradient(150deg, #a18cd1, #fbc2eb);
--c-4: linear-gradient(150deg, #43e97b, #38f9d7);
--c-5: linear-gradient(150deg, #fa709a, #fee140);
--c-kids: linear-gradient(150deg, #ff6ec4, #7873f5);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
min-height: 100vh;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1100px 700px at 50% -10%, rgba(229, 9, 20, 0.16), transparent 60%),
radial-gradient(900px 600px at 85% 110%, rgba(79, 172, 254, 0.1), transparent 60%),
var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
flex-direction: column;
}
.skip-link {
position: absolute;
left: -999px;
top: 8px;
background: var(--accent);
color: #000;
padding: 8px 14px;
border-radius: var(--r-sm);
z-index: 60;
font-weight: 600;
}
.skip-link:focus { left: 12px; }
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px clamp(16px, 4vw, 44px);
position: sticky;
top: 0;
z-index: 30;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand__mark {
width: 16px;
height: 22px;
border-radius: 3px;
background: var(--brand);
box-shadow: 0 0 18px rgba(229, 9, 20, 0.7);
}
.brand__word {
font-weight: 800;
letter-spacing: 0.22em;
font-size: 18px;
color: var(--brand);
}
.btn-ghost {
display: inline-flex;
align-items: center;
gap: 8px;
background: transparent;
border: 1px solid var(--line);
color: var(--ink-2);
padding: 9px 14px;
border-radius: var(--r-sm);
font: inherit;
font-weight: 600;
font-size: 13px;
cursor: pointer;
transition: border-color 0.18s, color 0.18s, background 0.18s;
}
.btn-ghost:hover { color: var(--ink); border-color: var(--line-2); background: rgba(255, 255, 255, 0.04); }
.btn-ghost[aria-pressed="true"] {
color: #000;
background: var(--accent);
border-color: var(--accent);
}
/* ---------- Stage ---------- */
.stage {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: clamp(24px, 6vh, 70px) 16px 80px;
gap: 6px;
}
.stage__title {
font-size: clamp(28px, 5vw, 52px);
font-weight: 700;
margin: 0;
letter-spacing: -0.01em;
}
.stage__sub {
margin: 0 0 clamp(28px, 5vh, 52px);
color: var(--muted);
font-size: clamp(14px, 2vw, 17px);
}
.stage.is-managing .stage__title { color: var(--ink); }
/* ---------- Grid ---------- */
.grid {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: clamp(18px, 3.4vw, 40px);
max-width: 980px;
}
.profile {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.profile__btn {
position: relative;
border: 0;
padding: 0;
background: none;
cursor: pointer;
border-radius: var(--r-md);
transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.profile__avatar {
width: clamp(96px, 13vw, 150px);
height: clamp(96px, 13vw, 150px);
border-radius: var(--r-md);
display: grid;
place-items: center;
font-size: clamp(34px, 6vw, 56px);
font-weight: 800;
color: rgba(0, 0, 0, 0.72);
border: 3px solid transparent;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
user-select: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.profile__btn:hover { transform: scale(1.07); }
.profile__btn:hover .profile__avatar,
.profile__btn:focus-visible .profile__avatar {
border-color: var(--accent);
box-shadow: var(--glow);
}
.profile__btn:focus-visible { outline: none; }
.profile__name {
color: var(--muted);
font-size: clamp(14px, 1.8vw, 17px);
font-weight: 500;
transition: color 0.18s;
}
.profile__btn:hover ~ .profile__name,
.profile:hover .profile__name { color: var(--ink); }
.badge-kids {
position: absolute;
left: 8px;
bottom: 8px;
background: var(--c-kids);
color: #fff;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.08em;
padding: 3px 8px;
border-radius: 999px;
text-transform: uppercase;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
/* edit overlay on each avatar in manage mode */
.profile__edit {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: var(--r-md);
background: rgba(0, 0, 0, 0.62);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.profile__edit svg { width: 34px; height: 34px; color: var(--accent); }
.stage.is-managing .profile__avatar { border-color: var(--line); }
.stage.is-managing .profile__btn { cursor: default; }
.stage.is-managing .profile__btn:hover { transform: scale(1.02); }
.stage.is-managing .profile__edit { opacity: 1; pointer-events: none; }
.stage.is-managing .profile__name { color: var(--ink-2); }
/* delete button (manage mode only) */
.profile__delete {
position: absolute;
top: -8px;
right: -8px;
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid var(--bg);
background: var(--brand);
color: #fff;
display: none;
place-items: center;
cursor: pointer;
z-index: 4;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
transition: transform 0.16s;
}
.profile__delete:hover { transform: scale(1.12); }
.stage.is-managing .profile__delete { display: grid; }
/* add-profile tile */
.profile--add .profile__avatar {
background: var(--surface);
border: 2px dashed var(--line-2);
color: var(--muted);
box-shadow: none;
}
.profile--add .profile__btn:hover .profile__avatar {
color: var(--ink);
border-color: var(--accent);
background: var(--surface-2);
box-shadow: none;
}
.profile--add .profile__btn:hover { transform: scale(1.05); }
.btn-done {
margin-top: clamp(30px, 5vh, 56px);
background: transparent;
border: 1px solid var(--line-2);
color: var(--ink-2);
letter-spacing: 0.16em;
text-transform: uppercase;
font-size: 13px;
font-weight: 600;
padding: 12px 34px;
border-radius: var(--r-sm);
cursor: pointer;
transition: color 0.18s, border-color 0.18s, background 0.18s;
}
.btn-done:hover { color: #000; background: var(--accent); border-color: var(--accent); }
/* ---------- Loading curtain ---------- */
.curtain {
position: fixed;
inset: 0;
z-index: 50;
background: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 22px;
opacity: 0;
visibility: hidden;
transition: opacity 0.35s ease;
}
.curtain.is-open { opacity: 1; visibility: visible; }
.curtain__avatar {
width: 96px;
height: 96px;
border-radius: var(--r-md);
display: grid;
place-items: center;
font-size: 38px;
font-weight: 800;
color: rgba(0, 0, 0, 0.72);
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.5);
transform: scale(0.9);
transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.curtain.is-open .curtain__avatar { transform: scale(1.18); }
.curtain__name { color: var(--ink-2); font-weight: 600; letter-spacing: 0.06em; margin: 0; }
.curtain__spinner { display: flex; gap: 8px; }
.curtain__spinner span {
width: 9px; height: 9px; border-radius: 50%;
background: var(--brand);
animation: bounce 1s infinite ease-in-out both;
}
.curtain__spinner span:nth-child(2) { animation-delay: 0.16s; }
.curtain__spinner span:nth-child(3) { animation-delay: 0.32s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
/* ---------- Modal ---------- */
.overlay {
position: fixed;
inset: 0;
z-index: 55;
background: rgba(5, 5, 8, 0.74);
backdrop-filter: blur(6px);
display: grid;
place-items: center;
padding: 18px;
animation: fade 0.2s ease;
}
@keyframes fade { from { opacity: 0; } }
.modal {
width: min(440px, 100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 26px;
animation: pop 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes pop { from { transform: translateY(14px) scale(0.97); opacity: 0; } }
.modal__title { margin: 0 0 4px; font-size: 22px; font-weight: 700; }
.modal__hint { margin: 0 0 20px; color: var(--muted); font-size: 14px; }
.modal__body { display: flex; gap: 18px; align-items: flex-start; }
.modal__avatar {
flex: 0 0 auto;
width: 84px;
height: 84px;
border-radius: var(--r-md);
display: grid;
place-items: center;
font-size: 34px;
font-weight: 800;
color: rgba(0, 0, 0, 0.72);
}
.modal__fields { flex: 1; display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field__label { font-size: 12px; font-weight: 600; color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.06em; }
.field__input {
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
padding: 11px 13px;
border-radius: var(--r-sm);
font: inherit;
font-size: 15px;
outline: none;
transition: border-color 0.18s, box-shadow 0.18s;
}
.field__input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.12); }
.kids-toggle { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.kids-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.kids-toggle__box {
flex: 0 0 auto;
margin-top: 2px;
width: 20px; height: 20px;
border-radius: 6px;
border: 2px solid var(--line-2);
display: grid;
place-items: center;
transition: background 0.18s, border-color 0.18s;
}
.kids-toggle__box::after {
content: "✓";
font-size: 13px;
font-weight: 800;
color: #000;
opacity: 0;
transform: scale(0.6);
transition: opacity 0.16s, transform 0.16s;
}
.kids-toggle input:checked + .kids-toggle__box { background: var(--accent); border-color: var(--accent); }
.kids-toggle input:checked + .kids-toggle__box::after { opacity: 1; transform: scale(1); }
.kids-toggle input:focus-visible + .kids-toggle__box { box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.18); }
.kids-toggle__text { display: flex; flex-direction: column; }
.kids-toggle__text strong { font-size: 14px; }
.kids-toggle__text small { color: var(--muted); font-size: 12.5px; }
.modal__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 24px; }
.btn-outline, .btn-primary {
font: inherit;
font-weight: 600;
font-size: 14px;
padding: 11px 22px;
border-radius: var(--r-sm);
cursor: pointer;
transition: transform 0.14s, background 0.18s, opacity 0.18s;
}
.btn-outline { background: transparent; border: 1px solid var(--line-2); color: var(--ink-2); }
.btn-outline:hover { color: var(--ink); border-color: var(--accent); }
.btn-primary { background: var(--accent); border: 1px solid var(--accent); color: #000; }
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 30px);
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
padding: 12px 20px;
border-radius: var(--r-md);
font-size: 14px;
font-weight: 500;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
z-index: 70;
transition: opacity 0.25s, transform 0.25s;
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 520px) {
.topbar { padding: 14px 16px; }
.brand__word { font-size: 15px; }
.btn-ghost span:not(.btn-ghost) { display: inline; }
.grid { gap: 20px 16px; }
.modal__body { flex-direction: column; align-items: center; text-align: center; }
.kids-toggle { text-align: left; }
}(function () {
"use strict";
var GRADIENTS = ["c-1", "c-2", "c-3", "c-4", "c-5"];
var profiles = [
{ id: "p1", name: "Mara", color: "c-1", kids: false },
{ id: "p2", name: "Devon", color: "c-2", kids: false },
{ id: "p3", name: "Priya", color: "c-3", kids: false },
{ id: "p4", name: "Lil' Cosmos", color: "c-kids", kids: true }
];
var managing = false;
var grid = document.getElementById("grid");
var stage = document.getElementById("profiles");
var stageTitle = document.getElementById("stageTitle");
var stageSub = document.getElementById("stageSub");
var manageBtn = document.getElementById("manageBtn");
var manageLabel = document.getElementById("manageLabel");
var exitManage = document.getElementById("exitManage");
var curtain = document.getElementById("curtain");
var curtainAvatar = document.getElementById("curtainAvatar");
var curtainName = document.getElementById("curtainName");
var overlay = document.getElementById("overlay");
var nameInput = document.getElementById("nameInput");
var kidsInput = document.getElementById("kidsInput");
var newAvatar = document.getElementById("newAvatar");
var confirmAdd = document.getElementById("confirmAdd");
var cancelAdd = document.getElementById("cancelAdd");
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2400);
}
function gradientVar(color) {
return "var(--" + color + ")";
}
function initial(name) {
return (name.trim()[0] || "?").toUpperCase();
}
function nextColor() {
return GRADIENTS[profiles.length % GRADIENTS.length];
}
/* ---------- Render ---------- */
function render() {
grid.innerHTML = "";
profiles.forEach(function (p) {
var li = document.createElement("li");
li.className = "profile";
li.dataset.id = p.id;
var del = document.createElement("button");
del.className = "profile__delete";
del.type = "button";
del.setAttribute("aria-label", "Delete " + p.name);
del.innerHTML =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>';
del.addEventListener("click", function (e) {
e.stopPropagation();
removeProfile(p.id);
});
var btn = document.createElement("button");
btn.className = "profile__btn";
btn.type = "button";
btn.setAttribute("aria-label", p.name + (p.kids ? " (kids profile)" : ""));
var avatar = document.createElement("span");
avatar.className = "profile__avatar";
avatar.style.background = gradientVar(p.color);
avatar.textContent = initial(p.name);
if (p.kids) {
var badge = document.createElement("span");
badge.className = "badge-kids";
badge.textContent = "Kids";
avatar.appendChild(badge);
}
var edit = document.createElement("span");
edit.className = "profile__edit";
edit.innerHTML =
'<svg viewBox="0 0 24 24" fill="none"><path d="M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>';
avatar.appendChild(edit);
btn.appendChild(avatar);
var nameEl = document.createElement("span");
nameEl.className = "profile__name";
nameEl.textContent = p.name;
btn.addEventListener("click", function () {
if (managing) {
renameProfile(p);
} else {
selectProfile(p);
}
});
li.appendChild(del);
li.appendChild(btn);
li.appendChild(nameEl);
grid.appendChild(li);
});
if (!managing && profiles.length < 6) {
grid.appendChild(buildAddTile());
}
}
function buildAddTile() {
var li = document.createElement("li");
li.className = "profile profile--add";
var btn = document.createElement("button");
btn.className = "profile__btn";
btn.type = "button";
btn.setAttribute("aria-label", "Add profile");
var avatar = document.createElement("span");
avatar.className = "profile__avatar";
avatar.innerHTML =
'<svg width="46" height="46" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
btn.appendChild(avatar);
var nameEl = document.createElement("span");
nameEl.className = "profile__name";
nameEl.textContent = "Add Profile";
btn.addEventListener("click", openAdd);
li.appendChild(btn);
li.appendChild(nameEl);
return li;
}
/* ---------- Select (loading transition) ---------- */
function selectProfile(p) {
curtainAvatar.style.background = gradientVar(p.color);
curtainAvatar.textContent = initial(p.name);
curtainName.textContent = "Loading " + p.name + "…";
curtain.classList.add("is-open");
curtain.setAttribute("aria-hidden", "false");
setTimeout(function () {
curtain.classList.remove("is-open");
curtain.setAttribute("aria-hidden", "true");
toast("Welcome back, " + p.name + (p.kids ? " — Kids mode on" : ""));
}, 1700);
}
/* ---------- Manage mode ---------- */
function setManaging(on) {
managing = on;
stage.classList.toggle("is-managing", on);
manageBtn.setAttribute("aria-pressed", String(on));
manageLabel.textContent = on ? "Editing…" : "Manage Profiles";
stageTitle.textContent = on ? "Manage Profiles" : "Who's watching?";
stageSub.textContent = on
? "Tap a profile to rename, or remove it"
: "Select a profile to continue";
exitManage.hidden = !on;
render();
}
function renameProfile(p) {
var next = window.prompt("Rename profile", p.name);
if (next === null) return;
next = next.trim();
if (!next) {
toast("Name can't be empty.");
return;
}
p.name = next.slice(0, 16);
render();
toast("Profile renamed to " + p.name + ".");
}
function removeProfile(id) {
if (profiles.length <= 1) {
toast("You need at least one profile.");
return;
}
var p = profiles.find(function (x) { return x.id === id; });
if (!window.confirm("Delete " + (p ? p.name : "this profile") + "? This can't be undone.")) {
return;
}
profiles = profiles.filter(function (x) { return x.id !== id; });
render();
toast("Profile removed.");
}
/* ---------- Add profile modal ---------- */
function openAdd() {
nameInput.value = "";
kidsInput.checked = false;
paintNewAvatar();
overlay.hidden = false;
confirmAdd.disabled = true;
setTimeout(function () { nameInput.focus(); }, 30);
document.addEventListener("keydown", onModalKey);
}
function closeAdd() {
overlay.hidden = true;
document.removeEventListener("keydown", onModalKey);
}
function onModalKey(e) {
if (e.key === "Escape") closeAdd();
}
function paintNewAvatar() {
var color = kidsInput.checked ? "c-kids" : nextColor();
newAvatar.style.background = gradientVar(color);
var name = nameInput.value.trim();
newAvatar.textContent = name ? initial(name) : "?";
newAvatar.dataset.color = color;
}
function commitAdd() {
var name = nameInput.value.trim();
if (!name) return;
profiles.push({
id: "p" + Date.now(),
name: name.slice(0, 16),
color: newAvatar.dataset.color || nextColor(),
kids: kidsInput.checked
});
closeAdd();
render();
toast(name + " added to Nebula.");
}
/* ---------- Wire up ---------- */
manageBtn.addEventListener("click", function () { setManaging(!managing); });
exitManage.addEventListener("click", function () { setManaging(false); });
nameInput.addEventListener("input", function () {
confirmAdd.disabled = nameInput.value.trim().length === 0;
paintNewAvatar();
});
kidsInput.addEventListener("change", paintNewAvatar);
confirmAdd.addEventListener("click", commitAdd);
cancelAdd.addEventListener("click", closeAdd);
overlay.addEventListener("click", function (e) {
if (e.target === overlay) closeAdd();
});
nameInput.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !confirmAdd.disabled) commitAdd();
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nebula — Who's Watching?</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#profiles">Skip to profiles</a>
<header class="topbar">
<div class="brand" aria-label="Nebula">
<span class="brand__mark" aria-hidden="true"></span>
<span class="brand__word">NEBULA</span>
</div>
<button id="manageBtn" class="btn-ghost" type="button" aria-pressed="false">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span id="manageLabel">Manage Profiles</span>
</button>
</header>
<main class="stage" id="profiles">
<h1 class="stage__title" id="stageTitle">Who's watching?</h1>
<p class="stage__sub" id="stageSub">Select a profile to continue</p>
<ul class="grid" id="grid" aria-describedby="stageSub">
<!-- profiles injected by script.js -->
</ul>
<button id="exitManage" class="btn-done" type="button" hidden>Done</button>
</main>
<!-- Loading curtain shown on profile select -->
<div class="curtain" id="curtain" aria-hidden="true">
<div class="curtain__avatar" id="curtainAvatar"></div>
<div class="curtain__spinner" aria-hidden="true">
<span></span><span></span><span></span>
</div>
<p class="curtain__name" id="curtainName"></p>
</div>
<!-- Add profile dialog -->
<div class="overlay" id="overlay" hidden>
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<h2 id="modalTitle" class="modal__title">Add Profile</h2>
<p class="modal__hint">Create a profile for another person watching Nebula.</p>
<div class="modal__body">
<div class="modal__avatar" id="newAvatar"></div>
<div class="modal__fields">
<label class="field">
<span class="field__label">Profile name</span>
<input id="nameInput" class="field__input" type="text" maxlength="16" placeholder="e.g. Jordan" autocomplete="off" />
</label>
<label class="kids-toggle">
<input id="kidsInput" type="checkbox" />
<span class="kids-toggle__box" aria-hidden="true"></span>
<span class="kids-toggle__text">
<strong>Kids profile</strong>
<small>Only shows titles rated for children.</small>
</span>
</label>
</div>
</div>
<div class="modal__actions">
<button id="cancelAdd" class="btn-outline" type="button">Cancel</button>
<button id="confirmAdd" class="btn-primary" type="button">Create</button>
</div>
</div>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Profile Switcher
The “Who’s watching?” gateway every streaming app opens with, rebuilt for the fictional Nebula service. A centered, responsive grid of profile tiles uses bold gradient avatars stamped with each viewer’s initial; hovering scales a tile up and wraps it in a white glow ring, and kids profiles carry a pill badge so they’re easy to spot. An add-profile tile rounds out the row until you reach the cap.
Selecting a profile triggers a full-screen loading curtain — the avatar swells, a bouncing-dot spinner runs, and a toast welcomes the viewer back (noting kids mode where relevant). The Manage Profiles button in the top bar flips the screen into edit mode: tiles dim into edit cards, a red delete button appears on each, and tapping a tile opens a rename prompt. Removing a profile asks for confirmation and refuses to delete your last one.
Adding a profile opens a modal with a live avatar preview that recolors as you type or flip the Kids profile toggle, with the Create button staying disabled until a name is entered. Everything is vanilla JS — no frameworks, no build step — with semantic landmarks, keyboard-friendly controls, a toast helper, and a layout that holds up from desktop down to a 360px phone.
Illustrative UI only — fictional titles, not a real streaming service.