Streaming — Account / Plan
A cinematic dark-mode account and membership screen for the fictional Lumora streaming service. A glowing current-plan card shows pricing, features and the next charge, with a change-plan modal that lets you pick Basic, Standard or Premium and confirm the switch. Billing history sits in a scrollable table, registered devices can be removed with a smooth sign-out animation, playback toggles flip with accessible switches, and a no-fee cancel flow updates the live status pill.
MCP
Code
:root {
--bg: #0b0b0f;
--surface: #15151c;
--surface-2: #1e1e27;
--ink: #f4f4f7;
--ink-2: #b6b7c3;
--muted: #83859a;
--brand: #e50914;
--brand-2: #7c5cff;
--accent: #ffffff;
--line: rgba(255, 255, 255, 0.1);
--line-2: rgba(255, 255, 255, 0.16);
--green: #1f9e6e;
--amber: #f5a623;
--r-sm: 8px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
--glow: 0 0 0 1px var(--line-2), 0 12px 30px rgba(0, 0, 0, 0.5);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
background:
radial-gradient(1100px 540px at 82% -8%, rgba(124, 92, 255, 0.16), transparent 60%),
radial-gradient(900px 480px at 8% -4%, rgba(229, 9, 20, 0.14), transparent 58%),
var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
min-height: 100vh;
}
a {
color: inherit;
}
.skip {
position: absolute;
left: -999px;
top: 0;
background: var(--brand);
color: #fff;
padding: 10px 16px;
border-radius: var(--r-sm);
z-index: 100;
}
.skip:focus {
left: 12px;
top: 12px;
}
/* ---------- top nav ---------- */
.topnav {
position: sticky;
top: 0;
z-index: 40;
backdrop-filter: blur(12px);
background: linear-gradient(180deg, rgba(11, 11, 15, 0.92), rgba(11, 11, 15, 0.55));
border-bottom: 1px solid var(--line);
}
.nav-inner {
max-width: 1180px;
margin: 0 auto;
padding: 14px 24px;
display: flex;
align-items: center;
gap: 28px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 9px;
font-weight: 800;
letter-spacing: 0.16em;
text-decoration: none;
color: var(--ink);
font-size: 18px;
}
.brand-mark {
width: 14px;
height: 22px;
border-radius: 3px;
background: linear-gradient(180deg, var(--brand), #ff4d57);
box-shadow: 0 0 16px rgba(229, 9, 20, 0.7);
}
.nav-links {
display: flex;
gap: 22px;
margin-left: 8px;
}
.nav-links a {
text-decoration: none;
color: var(--ink-2);
font-size: 14px;
font-weight: 500;
transition: color 0.15s;
}
.nav-links a:hover,
.nav-links a.active {
color: var(--ink);
}
.avatar-btn {
margin-left: auto;
background: none;
border: none;
cursor: pointer;
padding: 0;
}
.avatar {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: linear-gradient(135deg, var(--brand), var(--brand-2));
color: #fff;
font-weight: 700;
font-size: 13px;
}
/* ---------- layout ---------- */
.wrap {
max-width: 1180px;
margin: 0 auto;
padding: 34px 24px 80px;
}
.page-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
margin-bottom: 26px;
}
.eyebrow {
margin: 0 0 4px;
color: var(--brand);
font-weight: 700;
font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.page-head h1 {
margin: 0;
font-size: clamp(26px, 4vw, 38px);
font-weight: 800;
letter-spacing: -0.02em;
}
.lead {
margin: 8px 0 0;
color: var(--ink-2);
font-size: 14px;
}
.lead strong {
color: var(--ink);
font-weight: 600;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-radius: 999px;
background: rgba(31, 158, 110, 0.14);
border: 1px solid rgba(31, 158, 110, 0.4);
color: #6fe0b3;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
}
.status-pill .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 10px var(--green);
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.span-2 {
grid-column: span 2;
}
.span-3 {
grid-column: span 3;
}
/* ---------- cards ---------- */
.card {
background: linear-gradient(180deg, var(--surface), var(--surface-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 22px;
box-shadow: var(--shadow);
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.card-head h2 {
margin: 0;
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
}
.badge {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 4px 9px;
border-radius: 999px;
background: var(--line);
color: var(--ink-2);
text-transform: uppercase;
}
.badge-brand {
background: linear-gradient(135deg, var(--brand), #ff4d57);
color: #fff;
}
/* ---------- plan ---------- */
.plan-card {
position: relative;
overflow: hidden;
}
.plan-card::after {
content: "";
position: absolute;
inset: -40% -10% auto auto;
width: 280px;
height: 280px;
background: radial-gradient(circle, rgba(124, 92, 255, 0.25), transparent 70%);
pointer-events: none;
}
.plan-body {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 24px;
align-items: start;
}
.plan-name {
margin: 0;
font-size: 26px;
font-weight: 800;
background: linear-gradient(90deg, #fff, #c8c2ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.plan-price {
margin: 2px 0 14px;
font-size: 18px;
font-weight: 700;
}
.plan-price .per {
font-size: 13px;
font-weight: 500;
color: var(--muted);
}
.plan-features {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 8px;
}
.plan-features li {
position: relative;
padding-left: 24px;
font-size: 14px;
color: var(--ink-2);
}
.plan-features li::before {
content: "✓";
position: absolute;
left: 0;
top: -1px;
color: var(--green);
font-weight: 800;
}
.plan-side {
display: grid;
gap: 10px;
position: relative;
z-index: 1;
}
.next-bill {
margin: 0 0 4px;
font-size: 13px;
color: var(--muted);
}
.next-bill strong {
display: block;
color: var(--ink);
font-size: 15px;
font-weight: 600;
margin-top: 2px;
}
/* ---------- buttons ---------- */
.btn {
font-family: inherit;
font-size: 14px;
font-weight: 600;
border-radius: var(--r-md);
padding: 11px 16px;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.12s ease, background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn-primary {
background: linear-gradient(135deg, var(--brand), #ff3b45);
color: #fff;
box-shadow: 0 8px 24px rgba(229, 9, 20, 0.35);
}
.btn-primary:hover {
box-shadow: 0 10px 30px rgba(229, 9, 20, 0.5);
}
.btn-ghost {
background: var(--line);
color: var(--ink);
border-color: var(--line-2);
}
.btn-ghost:hover {
background: var(--line-2);
}
.btn-danger {
background: rgba(229, 9, 20, 0.12);
color: #ff7a82;
border-color: rgba(229, 9, 20, 0.45);
}
.btn-danger:hover {
background: rgba(229, 9, 20, 0.22);
}
.btn.full {
width: 100%;
margin-top: 6px;
}
.btn.sm {
padding: 7px 12px;
font-size: 13px;
}
/* ---------- profiles ---------- */
.profiles {
list-style: none;
margin: 0 0 12px;
padding: 0;
display: grid;
gap: 10px;
}
.profiles li {
display: flex;
align-items: center;
gap: 12px;
font-size: 14px;
font-weight: 500;
}
.pf {
width: 38px;
height: 38px;
border-radius: 9px;
display: grid;
place-items: center;
font-size: 13px;
font-weight: 700;
color: #fff;
background: var(--c, var(--brand));
}
.pf-kid {
border: 2px solid var(--amber);
}
/* ---------- devices ---------- */
.devices {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 10px;
}
.device {
display: flex;
align-items: center;
gap: 14px;
padding: 13px 14px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.02);
transition: border-color 0.15s, background 0.15s, opacity 0.3s, transform 0.3s;
}
.device:hover {
border-color: var(--line-2);
}
.device.removing {
opacity: 0;
transform: translateX(18px);
}
.dev-ic {
width: 40px;
height: 40px;
border-radius: 10px;
display: grid;
place-items: center;
background: var(--line);
font-size: 19px;
flex: none;
}
.dev-meta {
display: grid;
gap: 2px;
min-width: 0;
}
.dev-name {
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.dev-sub {
font-size: 12px;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tag-this {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.05em;
padding: 2px 7px;
border-radius: 999px;
background: rgba(124, 92, 255, 0.18);
color: #c2b6ff;
text-transform: uppercase;
}
.dev-remove {
margin-left: auto;
flex: none;
background: none;
border: 1px solid var(--line-2);
color: var(--ink-2);
font-size: 12px;
font-weight: 600;
padding: 7px 12px;
border-radius: 999px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.dev-remove:hover:not([disabled]) {
color: #ff7a82;
border-color: rgba(229, 9, 20, 0.5);
background: rgba(229, 9, 20, 0.1);
}
.dev-remove[disabled] {
opacity: 0.4;
cursor: not-allowed;
}
.hint {
margin: 14px 0 0;
font-size: 12px;
color: var(--muted);
}
/* ---------- settings ---------- */
.settings {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 4px;
}
.setting {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 4px;
border-bottom: 1px solid var(--line);
}
.setting:last-child {
border-bottom: none;
}
.set-text {
display: grid;
gap: 2px;
}
.set-title {
font-size: 14px;
font-weight: 600;
}
.set-sub {
font-size: 12px;
color: var(--muted);
}
.switch {
margin-left: auto;
flex: none;
position: relative;
width: 46px;
height: 26px;
border-radius: 999px;
border: 1px solid var(--line-2);
background: var(--surface-2);
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
.switch::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--ink-2);
transition: transform 0.2s, background 0.2s;
}
.switch[aria-checked="true"] {
background: linear-gradient(135deg, var(--brand), #ff4d57);
border-color: transparent;
}
.switch[aria-checked="true"]::after {
transform: translateX(20px);
background: #fff;
}
.switch:focus-visible {
outline: 2px solid var(--brand-2);
outline-offset: 2px;
}
/* ---------- billing ---------- */
.table-scroll {
overflow-x: auto;
}
.billing {
width: 100%;
border-collapse: collapse;
font-size: 14px;
min-width: 560px;
}
.billing th,
.billing td {
text-align: left;
padding: 12px 14px;
border-bottom: 1px solid var(--line);
white-space: nowrap;
}
.billing th {
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
font-weight: 700;
}
.billing tbody tr {
transition: background 0.15s;
}
.billing tbody tr:hover {
background: rgba(255, 255, 255, 0.03);
}
.billing .num {
text-align: right;
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.pay-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 600;
}
.pay-status::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green);
}
.pay-status.refunded {
color: var(--amber);
}
.pay-status.refunded::before {
background: var(--amber);
}
.pay-status.paid {
color: #6fe0b3;
}
/* ---------- modal ---------- */
.modal {
position: fixed;
inset: 0;
z-index: 60;
display: grid;
place-items: center;
padding: 20px;
background: rgba(4, 4, 8, 0.72);
backdrop-filter: blur(6px);
animation: fade 0.18s ease;
}
.modal[hidden] {
display: none;
}
@keyframes fade {
from {
opacity: 0;
}
}
.modal-card {
position: relative;
width: min(560px, 100%);
max-height: 90vh;
overflow: auto;
background: linear-gradient(180deg, var(--surface), var(--surface-2));
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
padding: 28px;
box-shadow: var(--shadow);
animation: pop 0.2s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
@keyframes pop {
from {
transform: translateY(14px) scale(0.97);
opacity: 0;
}
}
.modal-card h2 {
margin: 0;
font-size: 22px;
font-weight: 800;
}
.modal-sub {
margin: 8px 0 20px;
color: var(--ink-2);
font-size: 14px;
}
.modal-close {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--line);
color: var(--ink);
font-size: 20px;
line-height: 1;
cursor: pointer;
}
.modal-close:hover {
background: var(--line-2);
}
.plan-options {
display: grid;
gap: 10px;
margin-bottom: 22px;
}
.opt {
display: flex;
align-items: center;
gap: 14px;
padding: 16px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.02);
cursor: pointer;
text-align: left;
width: 100%;
font-family: inherit;
color: inherit;
transition: border-color 0.15s, background 0.15s;
}
.opt:hover {
border-color: var(--line-2);
}
.opt[aria-pressed="true"] {
border-color: var(--brand);
background: rgba(229, 9, 20, 0.08);
box-shadow: 0 0 0 1px var(--brand) inset;
}
.opt-radio {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid var(--line-2);
flex: none;
position: relative;
}
.opt[aria-pressed="true"] .opt-radio {
border-color: var(--brand);
}
.opt[aria-pressed="true"] .opt-radio::after {
content: "";
position: absolute;
inset: 3px;
border-radius: 50%;
background: var(--brand);
}
.opt-info {
display: grid;
gap: 2px;
}
.opt-name {
font-size: 15px;
font-weight: 700;
}
.opt-desc {
font-size: 12px;
color: var(--muted);
}
.opt-price {
margin-left: auto;
text-align: right;
font-weight: 700;
font-size: 15px;
white-space: nowrap;
}
.opt-price small {
display: block;
font-size: 11px;
font-weight: 500;
color: var(--muted);
}
.opt .opt-current {
margin-left: auto;
font-size: 11px;
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
flex-wrap: wrap;
}
.cancel-perks {
list-style: none;
margin: 0 0 22px;
padding: 0;
display: grid;
gap: 10px;
}
.cancel-perks li {
position: relative;
padding-left: 26px;
font-size: 14px;
color: var(--ink-2);
}
.cancel-perks li::before {
content: "★";
position: absolute;
left: 0;
color: var(--amber);
}
/* ---------- toast ---------- */
.toast-host {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
z-index: 90;
display: grid;
gap: 8px;
width: min(380px, calc(100% - 32px));
}
.toast {
background: var(--surface-2);
border: 1px solid var(--line-2);
color: var(--ink);
padding: 13px 16px;
border-radius: var(--r-md);
font-size: 14px;
box-shadow: var(--shadow);
display: flex;
align-items: center;
gap: 10px;
animation: toast-in 0.22s ease;
}
.toast::before {
content: "";
width: 6px;
align-self: stretch;
border-radius: 999px;
background: linear-gradient(180deg, var(--brand), var(--brand-2));
}
.toast.out {
animation: toast-out 0.25s ease forwards;
}
@keyframes toast-in {
from {
transform: translateY(16px);
opacity: 0;
}
}
@keyframes toast-out {
to {
transform: translateY(10px);
opacity: 0;
}
}
/* ---------- responsive ---------- */
@media (max-width: 880px) {
.grid {
grid-template-columns: 1fr 1fr;
}
.span-2,
.span-3 {
grid-column: span 2;
}
.plan-body {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.nav-links {
display: none;
}
.nav-inner {
padding: 12px 16px;
}
.wrap {
padding: 24px 16px 64px;
}
.page-head {
flex-direction: column;
align-items: flex-start;
}
.grid {
grid-template-columns: 1fr;
}
.span-2,
.span-3 {
grid-column: span 1;
}
.card {
padding: 18px;
}
.modal-card {
padding: 22px 18px;
}
.modal-actions {
justify-content: stretch;
}
.modal-actions .btn {
flex: 1;
}
.opt {
flex-wrap: wrap;
}
}(function () {
"use strict";
/* ----------------------------- data ----------------------------- */
var plans = [
{
id: "basic",
name: "Basic",
price: 8.99,
desc: "720p HD · 1 device · with ads",
features: ["720p HD streaming", "Watch on 1 device", "Limited downloads"],
},
{
id: "standard",
name: "Standard",
price: 13.99,
desc: "1080p Full HD · 2 devices",
features: ["1080p Full HD", "Watch on 2 devices", "Ad-free · downloads"],
},
{
id: "premium",
name: "Premium",
price: 19.99,
desc: "4K UHD + HDR · 4 devices · spatial audio",
features: ["Ultra HD (4K) + HDR", "Watch on 4 devices at once", "Spatial audio & downloads"],
},
];
var devices = [
{ id: "d1", name: "Living Room TV", type: "LG OLED · Lumora app", icon: "📺", here: false, loc: "Madrid, ES · last used 2h ago" },
{ id: "d2", name: "MacBook Pro", type: "Chrome · macOS", icon: "💻", here: true, loc: "Madrid, ES · this device" },
{ id: "d3", name: "iPhone 15", type: "iOS app", icon: "📱", here: false, loc: "Barcelona, ES · last used yesterday" },
{ id: "d4", name: "iPad Air", type: "iOS app", icon: "📱", here: false, loc: "Madrid, ES · last used 4 days ago" },
];
var settings = [
{ id: "autoplay", title: "Autoplay next episode", sub: "Continue playing in a series", on: true },
{ id: "previews", title: "Autoplay previews", sub: "Play trailers while browsing", on: false },
{ id: "hd", title: "Always stream in 4K", sub: "Uses more data on mobile", on: true },
{ id: "spatial", title: "Spatial audio", sub: "Where the device supports it", on: true },
];
var billing = [
{ date: "Jun 3, 2026", desc: "Premium — monthly", method: "Visa •••• 4218", amt: "$19.99", status: "Paid", cls: "paid" },
{ date: "May 3, 2026", desc: "Premium — monthly", method: "Visa •••• 4218", amt: "$19.99", status: "Paid", cls: "paid" },
{ date: "Apr 3, 2026", desc: "Premium — monthly", method: "Visa •••• 4218", amt: "$19.99", status: "Paid", cls: "paid" },
{ date: "Mar 14, 2026", desc: "Service credit — outage", method: "Account balance", amt: "−$4.50", status: "Refunded", cls: "refunded" },
{ date: "Mar 3, 2026", desc: "Premium — monthly", method: "Visa •••• 4218", amt: "$19.99", status: "Paid", cls: "paid" },
];
var currentPlanId = "premium";
var selectedPlanId = currentPlanId;
/* ----------------------------- toast ----------------------------- */
var host = document.getElementById("toast-host");
window.toast = function (msg) {
var el = document.createElement("div");
el.className = "toast";
el.textContent = msg;
host.appendChild(el);
setTimeout(function () {
el.classList.add("out");
el.addEventListener("animationend", function () {
el.remove();
});
}, 2600);
};
/* ----------------------------- helpers ----------------------------- */
function $(id) {
return document.getElementById(id);
}
function money(n) {
return "$" + n.toFixed(2);
}
function planById(id) {
return plans.filter(function (p) {
return p.id === id;
})[0];
}
/* ----------------------------- render: devices ----------------------------- */
function renderDevices() {
var ul = $("devices");
ul.innerHTML = "";
devices.forEach(function (d) {
var li = document.createElement("li");
li.className = "device";
li.dataset.id = d.id;
li.innerHTML =
'<span class="dev-ic" aria-hidden="true">' + d.icon + "</span>" +
'<span class="dev-meta">' +
'<span class="dev-name">' + d.name +
(d.here ? ' <span class="tag-this">This device</span>' : "") +
"</span>" +
'<span class="dev-sub">' + d.type + " · " + d.loc + "</span>" +
"</span>";
var btn = document.createElement("button");
btn.className = "dev-remove";
btn.type = "button";
if (d.here) {
btn.disabled = true;
btn.textContent = "In use";
} else {
btn.textContent = "Remove";
btn.setAttribute("aria-label", "Remove " + d.name);
btn.addEventListener("click", function () {
removeDevice(d.id, li);
});
}
li.appendChild(btn);
ul.appendChild(li);
});
updateDeviceCount();
}
function removeDevice(id, li) {
li.classList.add("removing");
setTimeout(function () {
devices = devices.filter(function (d) {
return d.id !== id;
});
renderDevices();
window.toast("Device removed and signed out");
}, 280);
}
function updateDeviceCount() {
$("device-count").textContent = devices.length + " active";
}
/* ----------------------------- render: settings ----------------------------- */
function renderSettings() {
var ul = $("settings");
ul.innerHTML = "";
settings.forEach(function (s) {
var li = document.createElement("li");
li.className = "setting";
var sw = document.createElement("button");
sw.className = "switch";
sw.type = "button";
sw.setAttribute("role", "switch");
sw.setAttribute("aria-checked", String(s.on));
sw.setAttribute("aria-label", s.title);
sw.addEventListener("click", function () {
s.on = !s.on;
sw.setAttribute("aria-checked", String(s.on));
window.toast(s.title + (s.on ? " turned on" : " turned off"));
});
li.innerHTML =
'<span class="set-text"><span class="set-title">' + s.title +
'</span><span class="set-sub">' + s.sub + "</span></span>";
li.appendChild(sw);
ul.appendChild(li);
});
}
/* ----------------------------- render: billing ----------------------------- */
function renderBilling() {
var tb = $("billing");
tb.innerHTML = "";
billing.forEach(function (b) {
var tr = document.createElement("tr");
tr.innerHTML =
"<td>" + b.date + "</td>" +
"<td>" + b.desc + "</td>" +
"<td>" + b.method + "</td>" +
'<td class="num">' + b.amt + "</td>" +
'<td><span class="pay-status ' + b.cls + '">' + b.status + "</span></td>";
tb.appendChild(tr);
});
}
/* ----------------------------- render: current plan ----------------------------- */
function renderCurrentPlan() {
var p = planById(currentPlanId);
$("plan-name").textContent = p.name;
$("plan-price").textContent = money(p.price);
var fl = $("plan-features");
fl.innerHTML = "";
p.features.forEach(function (f) {
var li = document.createElement("li");
li.textContent = f;
fl.appendChild(li);
});
$("next-bill").textContent = money(p.price) + " on Jul 3, 2026";
}
/* ----------------------------- plan modal ----------------------------- */
function renderPlanOptions() {
var box = $("plan-options");
box.innerHTML = "";
plans.forEach(function (p) {
var btn = document.createElement("button");
btn.type = "button";
btn.className = "opt";
btn.dataset.id = p.id;
btn.setAttribute("aria-pressed", String(p.id === selectedPlanId));
var right =
p.id === currentPlanId
? '<span class="opt-current">Current</span>'
: '<span class="opt-price">' + money(p.price) + "<small>/month</small></span>";
btn.innerHTML =
'<span class="opt-radio" aria-hidden="true"></span>' +
'<span class="opt-info"><span class="opt-name">' + p.name +
'</span><span class="opt-desc">' + p.desc + "</span></span>" +
right;
btn.addEventListener("click", function () {
selectedPlanId = p.id;
renderPlanOptions();
});
box.appendChild(btn);
});
}
/* ----------------------------- modal plumbing ----------------------------- */
var lastFocused = null;
function openModal(which) {
var m = which === "plan" ? $("modal-plan") : $("modal-cancel");
lastFocused = document.activeElement;
if (which === "plan") {
selectedPlanId = currentPlanId;
renderPlanOptions();
}
m.hidden = false;
var focusable = m.querySelector("button:not([disabled])");
if (focusable) focusable.focus();
document.addEventListener("keydown", onKey);
}
function closeModal() {
["modal-plan", "modal-cancel"].forEach(function (id) {
$(id).hidden = true;
});
document.removeEventListener("keydown", onKey);
if (lastFocused) lastFocused.focus();
}
function onKey(e) {
if (e.key === "Escape") closeModal();
}
document.querySelectorAll("[data-open]").forEach(function (b) {
b.addEventListener("click", function () {
openModal(b.dataset.open);
});
});
document.querySelectorAll("[data-close]").forEach(function (b) {
b.addEventListener("click", closeModal);
});
document.querySelectorAll(".modal").forEach(function (m) {
m.addEventListener("click", function (e) {
if (e.target === m) closeModal();
});
});
$("confirm-plan").addEventListener("click", function () {
if (selectedPlanId === currentPlanId) {
window.toast("That's already your current plan");
closeModal();
return;
}
var p = planById(selectedPlanId);
currentPlanId = selectedPlanId;
renderCurrentPlan();
closeModal();
window.toast("Switched to " + p.name + " — effective Jul 3");
});
$("confirm-cancel").addEventListener("click", function () {
closeModal();
window.toast("Membership set to cancel on Jul 3, 2026");
var pill = document.querySelector(".status-pill");
pill.style.background = "rgba(245,166,35,0.14)";
pill.style.borderColor = "rgba(245,166,35,0.4)";
pill.style.color = "#ffce82";
pill.childNodes[2].textContent = " Ending Jul 3";
pill.querySelector(".dot").style.background = "#f5a623";
pill.querySelector(".dot").style.boxShadow = "0 0 10px #f5a623";
});
/* ----------------------------- nav fade on scroll ----------------------------- */
var nav = $("topnav");
window.addEventListener(
"scroll",
function () {
nav.style.boxShadow = window.scrollY > 8 ? "0 6px 24px rgba(0,0,0,0.5)" : "none";
},
{ passive: true }
);
/* ----------------------------- init ----------------------------- */
renderCurrentPlan();
renderDevices();
renderSettings();
renderBilling();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lumora — Account & Plan</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" href="#main">Skip to content</a>
<header class="topnav" id="topnav">
<div class="nav-inner">
<a class="brand" href="#" aria-label="Lumora home">
<span class="brand-mark" aria-hidden="true"></span>
LUMORA
</a>
<nav class="nav-links" aria-label="Primary">
<a href="#">Home</a>
<a href="#">Series</a>
<a href="#">Films</a>
<a href="#" aria-current="page" class="active">Account</a>
</nav>
<button class="avatar-btn" aria-label="Account menu">
<span class="avatar" aria-hidden="true">RM</span>
</button>
</div>
</header>
<main id="main" class="wrap">
<header class="page-head">
<div>
<p class="eyebrow">Account</p>
<h1>Manage your membership</h1>
<p class="lead">
Signed in as <strong>rosa.mendez@lumora.example</strong> · Member since March 2022
</p>
</div>
<div class="status-pill" aria-label="Account status">
<span class="dot" aria-hidden="true"></span> Active
</div>
</header>
<div class="grid">
<!-- PLAN -->
<section class="card span-2 plan-card" aria-labelledby="plan-h">
<div class="card-head">
<h2 id="plan-h">Current plan</h2>
<span class="badge badge-brand">4K UHD</span>
</div>
<div class="plan-body">
<div class="plan-meta">
<p class="plan-name" id="plan-name">Premium</p>
<p class="plan-price">
<span id="plan-price">$19.99</span><span class="per">/month</span>
</p>
<ul class="plan-features" id="plan-features">
<li>Ultra HD (4K) + HDR</li>
<li>Watch on 4 devices at once</li>
<li>Spatial audio & downloads</li>
</ul>
</div>
<div class="plan-side">
<p class="next-bill">
Next charge <strong id="next-bill">$19.99 on Jul 3, 2026</strong>
</p>
<button class="btn btn-primary" data-open="plan">Change plan</button>
<button class="btn btn-ghost" data-open="cancel">Cancel membership</button>
</div>
</div>
</section>
<!-- PROFILES -->
<section class="card" aria-labelledby="prof-h">
<div class="card-head">
<h2 id="prof-h">Profiles</h2>
<span class="badge">4 of 5</span>
</div>
<ul class="profiles">
<li><span class="pf" style="--c:#e50914">RM</span>Rosa</li>
<li><span class="pf" style="--c:#7c5cff">DA</span>Diego</li>
<li><span class="pf" style="--c:#1f9e6e">LU</span>Lucía</li>
<li><span class="pf pf-kid" style="--c:#f5a623">KD</span>Kids</li>
</ul>
<button class="btn btn-ghost full" onclick="toast('Profile manager coming soon')">
Manage profiles
</button>
</section>
<!-- DEVICES -->
<section class="card span-2" aria-labelledby="dev-h">
<div class="card-head">
<h2 id="dev-h">Registered devices</h2>
<span class="badge" id="device-count">4 active</span>
</div>
<ul class="devices" id="devices"></ul>
<p class="hint">Devices are signed out immediately when removed.</p>
</section>
<!-- PLAYBACK SETTINGS -->
<section class="card" aria-labelledby="set-h">
<div class="card-head"><h2 id="set-h">Playback</h2></div>
<ul class="settings" id="settings"></ul>
</section>
<!-- BILLING -->
<section class="card span-3" aria-labelledby="bill-h">
<div class="card-head">
<h2 id="bill-h">Billing history</h2>
<button class="btn btn-ghost sm" onclick="toast('Statement exported (demo)')">
Export
</button>
</div>
<div class="table-scroll">
<table class="billing">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Description</th>
<th scope="col">Method</th>
<th scope="col" class="num">Amount</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="billing"></tbody>
</table>
</div>
</section>
</div>
</main>
<!-- CHANGE PLAN MODAL -->
<div class="modal" id="modal-plan" role="dialog" aria-modal="true" aria-labelledby="mp-title" hidden>
<div class="modal-card">
<button class="modal-close" data-close aria-label="Close">×</button>
<h2 id="mp-title">Choose your plan</h2>
<p class="modal-sub">Switch anytime. Changes apply on your next billing date.</p>
<div class="plan-options" id="plan-options"></div>
<div class="modal-actions">
<button class="btn btn-ghost" data-close>Keep current</button>
<button class="btn btn-primary" id="confirm-plan">Confirm change</button>
</div>
</div>
</div>
<!-- CANCEL MODAL -->
<div class="modal" id="modal-cancel" role="dialog" aria-modal="true" aria-labelledby="mc-title" hidden>
<div class="modal-card">
<button class="modal-close" data-close aria-label="Close">×</button>
<h2 id="mc-title">We're sorry to see you go</h2>
<p class="modal-sub">
Your membership stays active until <strong>Jul 3, 2026</strong>. You can resume anytime
before then with no interruption.
</p>
<ul class="cancel-perks">
<li>You'll keep 4K UHD & all profiles until the period ends.</li>
<li>Downloads remain playable while your plan is active.</li>
<li>No cancellation fee — pause instead and keep your watchlist.</li>
</ul>
<div class="modal-actions">
<button class="btn btn-primary" data-close>Keep my membership</button>
<button class="btn btn-danger" id="confirm-cancel">Cancel membership</button>
</div>
</div>
</div>
<div class="toast-host" id="toast-host" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Account / Plan
A membership management screen for the fictional Lumora streaming service, built dark-first with a fading top nav, a red-and-violet signature palette, and pure CSS gradients — no external images. The hero is a glowing current-plan card that surfaces the plan name, monthly price, included features (4K UHD, device count, spatial audio) and the next billing date, flanked by clear upgrade and cancel actions.
Everything is interactive. Change plan opens a modal where Basic, Standard and Premium are radio-style options; selecting one and confirming swaps the live plan card and fires a toast. The devices list lets you remove any registered device — except the one you’re using — with a slide-out animation, an immediate sign-out toast, and a live device counter. Playback settings are real role="switch" toggles, billing history renders into a horizontally scrollable table with paid and refunded states, and a profiles summary rounds out the layout.
The no-fee cancel flow explains what you keep until the period ends, and confirming repaints the account status pill from green “Active” to amber “Ending Jul 3”. Modals trap initial focus, close on Escape or backdrop click, restore focus to their trigger, and the whole grid collapses cleanly from three columns down to a single column at ~360px.
Illustrative UI only — fictional titles, not a real streaming service.