Shop — Account
A customer account hub for a fictional storefront, with a sticky sidebar that switches between Orders, Addresses, Payment, Returns, and Profile. The orders list carries colour-coded status badges plus reorder and track actions, a slide-out order-detail drawer shows a delivery timeline and line items, the address manager supports add, edit, remove, and set-default, and the profile form validates and saves with a confirmation toast.
MCP
Code
:root {
--bg: #ffffff;
--bg-soft: #f6f7fb;
--ink: #16181d;
--muted: #6b7280;
--brand: #3457ff;
--brand-d: #2742d6;
--brand-tint: #eef1ff;
--sale: #e0245e;
--ok: #1f9d55;
--warn: #c2710c;
--line: rgba(16, 18, 29, .1);
--line-2: rgba(16, 18, 29, .06);
--shadow: 0 1px 2px rgba(16, 18, 29, .05), 0 8px 24px rgba(16, 18, 29, .06);
--shadow-lg: 0 24px 60px rgba(16, 18, 29, .18);
--radius: 16px;
--radius-sm: 10px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
font-size: 15px;
line-height: 1.5;
color: var(--ink);
background: var(--bg-soft);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, p, ul, figure { margin: 0; }
ul { list-style: none; padding: 0; }
button { font: inherit; cursor: pointer; }
a { color: inherit; }
:focus-visible {
outline: 3px solid color-mix(in srgb, var(--brand) 55%, transparent);
outline-offset: 2px;
border-radius: 6px;
}
.skip-link {
position: absolute;
left: 12px;
top: -48px;
background: var(--ink);
color: #fff;
padding: 9px 14px;
border-radius: 8px;
z-index: 90;
transition: top .18s ease;
}
.skip-link:focus { top: 12px; }
/* ── Topbar ───────────────────────────── */
.topbar {
position: sticky;
top: 0;
z-index: 40;
background: color-mix(in srgb, var(--bg) 86%, transparent);
backdrop-filter: saturate(1.4) blur(12px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
max-width: 1180px;
margin: 0 auto;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 22px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
text-decoration: none;
font-weight: 800;
letter-spacing: -.01em;
}
.brand__mark { display: inline-flex; }
.brand__name { white-space: nowrap; }
.topbar__nav {
display: flex;
gap: 6px;
margin-left: 8px;
}
.topbar__nav a {
text-decoration: none;
color: var(--muted);
font-weight: 600;
font-size: 14px;
padding: 8px 12px;
border-radius: 9px;
transition: background .15s, color .15s;
}
.topbar__nav a:hover { background: var(--bg-soft); color: var(--ink); }
.topbar__user {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
.iconbtn {
position: relative;
display: inline-grid;
place-items: center;
width: 40px;
height: 40px;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--bg);
color: var(--ink);
transition: background .15s, border-color .15s, transform .12s;
}
.iconbtn:hover { background: var(--bg-soft); border-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.iconbtn:active { transform: scale(.96); }
.iconbtn__badge {
position: absolute;
top: -6px;
right: -6px;
min-width: 18px;
height: 18px;
padding: 0 4px;
display: grid;
place-items: center;
border-radius: 999px;
background: var(--brand);
color: #fff;
font-size: 11px;
font-weight: 700;
}
.avatar {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: linear-gradient(135deg, #3457ff, #7b5cff);
color: #fff;
font-weight: 700;
font-size: 13px;
letter-spacing: .02em;
}
.avatar--lg { width: 52px; height: 52px; font-size: 17px; }
/* ── Shell / layout ───────────────────── */
.shell {
max-width: 1180px;
margin: 0 auto;
padding: 28px 20px 64px;
}
.account {
display: grid;
grid-template-columns: 268px 1fr;
gap: 24px;
align-items: start;
}
/* ── Sidebar ──────────────────────────── */
.side {
position: sticky;
top: 84px;
display: flex;
flex-direction: column;
gap: 14px;
}
.side__card {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 18px;
}
.side__id { display: flex; gap: 13px; align-items: center; }
.side__hello { color: var(--muted); font-size: 12.5px; }
.side__name { font-weight: 700; font-size: 16px; letter-spacing: -.01em; }
.side__tier {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 3px;
font-size: 12px;
font-weight: 600;
color: var(--brand-d);
}
.side__tier .dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--brand);
box-shadow: 0 0 0 3px var(--brand-tint);
}
.side__nav {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.navlink {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 11px 13px;
border: 0;
border-radius: 11px;
background: transparent;
color: var(--muted);
font-weight: 600;
font-size: 14.5px;
text-align: left;
transition: background .15s, color .15s;
}
.navlink svg { width: 20px; height: 20px; flex: none; }
.navlink:hover { background: var(--bg-soft); color: var(--ink); }
.navlink.is-active {
background: var(--brand-tint);
color: var(--brand-d);
}
.side__logout {
align-self: flex-start;
margin-left: 4px;
border: 0;
background: none;
color: var(--muted);
font-weight: 600;
font-size: 13.5px;
padding: 6px 4px;
text-decoration: underline;
text-underline-offset: 3px;
}
.side__logout:hover { color: var(--sale); }
/* ── Content panels ───────────────────── */
.content { min-width: 0; }
.panel { display: none; animation: fade .26s ease; }
.panel.is-active { display: block; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.panel__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.panel__title { font-size: 23px; font-weight: 800; letter-spacing: -.02em; }
.panel__sub { color: var(--muted); font-size: 14px; margin-top: 3px; }
/* segmented filter */
.seg {
display: inline-flex;
background: var(--bg);
border: 1px solid var(--line);
border-radius: 999px;
padding: 4px;
box-shadow: var(--shadow);
}
.seg__btn {
border: 0;
background: none;
padding: 7px 15px;
border-radius: 999px;
font-weight: 600;
font-size: 13.5px;
color: var(--muted);
transition: background .15s, color .15s;
}
.seg__btn:hover { color: var(--ink); }
.seg__btn.is-active { background: var(--ink); color: #fff; }
/* ── Buttons ──────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
gap: 7px;
border: 1px solid transparent;
border-radius: 11px;
padding: 10px 16px;
font-weight: 700;
font-size: 14px;
transition: background .15s, border-color .15s, transform .12s, box-shadow .15s;
}
.btn:active { transform: translateY(1px); }
.btn--brand {
background: var(--brand);
color: #fff;
box-shadow: 0 8px 18px color-mix(in srgb, var(--brand) 32%, transparent);
}
.btn--brand:hover { background: var(--brand-d); }
.btn--ghost {
background: var(--bg);
border-color: var(--line);
color: var(--ink);
}
.btn--ghost:hover { background: var(--bg-soft); border-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.btn--sm { padding: 8px 13px; font-size: 13px; border-radius: 9px; }
/* ── Orders list ──────────────────────── */
.orders { display: flex; flex-direction: column; gap: 14px; }
.order {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: border-color .15s, box-shadow .15s;
}
.order:hover { border-color: color-mix(in srgb, var(--brand) 35%, transparent); }
.order__top {
display: flex;
flex-wrap: wrap;
gap: 8px 22px;
padding: 14px 18px;
background: var(--bg-soft);
border-bottom: 1px solid var(--line-2);
}
.order__meta { display: flex; flex-direction: column; }
.order__meta dt { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 600; }
.order__meta dd { margin: 1px 0 0; font-weight: 700; font-size: 14px; }
.order__status { margin-left: auto; align-self: center; }
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 11px;
border-radius: 999px;
font-size: 12.5px;
font-weight: 700;
}
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.badge--delivered { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, #fff); }
.badge--transit { color: var(--brand-d); background: var(--brand-tint); }
.badge--processing{ color: var(--warn); background: color-mix(in srgb, var(--warn) 13%, #fff); }
.badge--cancelled { color: var(--sale); background: color-mix(in srgb, var(--sale) 11%, #fff); }
.order__body {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 18px;
flex-wrap: wrap;
}
.thumbs { display: flex; }
.thumb {
width: 52px;
height: 52px;
border-radius: 12px;
display: grid;
place-items: center;
border: 2px solid var(--bg);
margin-left: -12px;
box-shadow: 0 1px 4px rgba(16,18,29,.12);
}
.thumb:first-child { margin-left: 0; }
.thumb svg { width: 30px; height: 30px; }
.thumb--more {
background: var(--bg-soft);
color: var(--muted);
font-weight: 700;
font-size: 13px;
}
.order__summary { min-width: 0; }
.order__items { font-weight: 600; font-size: 14px; }
.order__total { color: var(--muted); font-size: 13px; }
.order__total b { color: var(--ink); }
.order__actions {
margin-left: auto;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.orders__empty {
text-align: center;
color: var(--muted);
padding: 40px;
background: var(--bg);
border: 1px dashed var(--line);
border-radius: var(--radius);
}
/* ── Addresses ────────────────────────── */
.addr-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.addr {
position: relative;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 18px;
display: flex;
flex-direction: column;
gap: 4px;
}
.addr.is-default { border-color: color-mix(in srgb, var(--brand) 45%, transparent); box-shadow: 0 0 0 3px var(--brand-tint), var(--shadow); }
.addr__top { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.addr__label { font-weight: 800; font-size: 15px; }
.addr__chip {
font-size: 11px;
font-weight: 700;
color: var(--brand-d);
background: var(--brand-tint);
padding: 3px 8px;
border-radius: 999px;
}
.addr p { color: var(--ink); font-size: 14px; }
.addr .addr__line { color: var(--muted); }
.addr__actions {
margin-top: auto;
padding-top: 12px;
display: flex;
gap: 14px;
border-top: 1px solid var(--line-2);
}
.linkbtn {
border: 0;
background: none;
color: var(--brand-d);
font-weight: 700;
font-size: 13px;
padding: 4px 0;
}
.linkbtn:hover { text-decoration: underline; text-underline-offset: 3px; }
.linkbtn--danger { color: var(--sale); }
.linkbtn:disabled { color: var(--muted); cursor: default; text-decoration: none; opacity: .7; }
/* ── Payment cards ────────────────────── */
.secure-badge {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--ok);
font-weight: 700;
font-size: 13px;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
}
.paycard {
border-radius: var(--radius);
padding: 18px;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #fff;
border: 0;
text-align: left;
}
.paycard--visa { background: linear-gradient(135deg, #2742d6, #6b5cff); box-shadow: 0 14px 30px rgba(39,66,214,.3); }
.paycard--mc { background: linear-gradient(135deg, #1b1d2a, #3a3550); box-shadow: 0 14px 30px rgba(27,29,42,.28); }
.paycard__top { display: flex; justify-content: space-between; align-items: center; }
.paycard__brand { font-weight: 800; letter-spacing: .04em; font-size: 15px; }
.paycard__def {
font-size: 11px; font-weight: 700;
background: rgba(255,255,255,.22);
padding: 3px 9px; border-radius: 999px;
}
.paycard__num { font-size: 17px; font-weight: 600; letter-spacing: .08em; }
.paycard__bottom { display: flex; justify-content: space-between; font-size: 12.5px; opacity: .85; }
.paycard--add {
background: var(--bg);
border: 2px dashed var(--line);
color: var(--muted);
align-items: center;
justify-content: center;
gap: 8px;
font-weight: 700;
transition: border-color .15s, color .15s, background .15s;
}
.paycard--add:hover { border-color: var(--brand); color: var(--brand-d); background: var(--brand-tint); }
/* ── Returns ──────────────────────────── */
.returns { display: flex; flex-direction: column; gap: 12px; }
.return {
display: flex;
align-items: center;
gap: 14px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 14px 18px;
flex-wrap: wrap;
}
.return__thumb {
width: 48px; height: 48px; border-radius: 11px;
display: grid; place-items: center; flex: none;
}
.return__thumb svg { width: 28px; height: 28px; }
.return__info { min-width: 0; }
.return__name { font-weight: 700; }
.return__sub { color: var(--muted); font-size: 13px; }
.return__status { margin-left: auto; }
/* ── Profile form ─────────────────────── */
.profile {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 22px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px 18px;
max-width: 680px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field--full { grid-column: 1 / -1; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; grid-column: 1 / -1; }
.field label, .field legend { font-weight: 600; font-size: 13px; color: var(--ink); padding: 0; }
.field input, .field select {
font: inherit;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: var(--bg);
color: var(--ink);
transition: border-color .15s, box-shadow .15s;
}
.field input:focus, .field select:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-tint);
}
.field input[aria-invalid="true"] { border-color: var(--sale); box-shadow: 0 0 0 3px color-mix(in srgb, var(--sale) 18%, transparent); }
.field__err { color: var(--sale); font-size: 12.5px; font-weight: 600; }
fieldset.field { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px 14px; }
.check {
display: flex;
align-items: center;
gap: 9px;
font-size: 14px;
font-weight: 500;
color: var(--ink);
padding: 4px 0;
}
.check input { width: 17px; height: 17px; accent-color: var(--brand); }
.profile__actions {
grid-column: 1 / -1;
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 4px;
}
/* ── Order drawer ─────────────────────── */
.drawer {
position: fixed;
inset: 0;
z-index: 60;
visibility: hidden;
pointer-events: none;
}
.drawer.is-open { visibility: visible; pointer-events: auto; }
.drawer__scrim {
position: absolute;
inset: 0;
background: rgba(16, 18, 29, .42);
opacity: 0;
transition: opacity .26s ease;
}
.drawer.is-open .drawer__scrim { opacity: 1; }
.drawer__panel {
position: absolute;
top: 0;
right: 0;
height: 100%;
width: min(440px, 92vw);
background: var(--bg);
box-shadow: var(--shadow-lg);
transform: translateX(100%);
transition: transform .3s cubic-bezier(.4, 0, .15, 1);
display: flex;
flex-direction: column;
outline: none;
}
.drawer.is-open .drawer__panel { transform: none; }
.drawer__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 20px 22px 16px;
border-bottom: 1px solid var(--line);
}
.drawer__eyebrow { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 700; }
.drawer__title { font-size: 20px; font-weight: 800; letter-spacing: -.01em; margin-top: 2px; }
.iconbtn--close { width: 38px; height: 38px; }
.drawer__body { padding: 20px 22px 28px; overflow-y: auto; }
.track {
display: flex;
flex-direction: column;
gap: 0;
margin: 4px 0 22px;
}
.track__step {
display: flex;
gap: 12px;
position: relative;
}
.track__step:not(:last-child)::before {
content: "";
position: absolute;
left: 9px;
top: 22px;
bottom: -4px;
width: 2px;
background: var(--line);
}
.track__step.is-done:not(:last-child)::before { background: var(--ok); }
.track__dot {
width: 20px; height: 20px; border-radius: 50%;
border: 2px solid var(--line);
background: var(--bg);
flex: none;
display: grid;
place-items: center;
margin-top: 1px;
}
.track__step.is-done .track__dot { background: var(--ok); border-color: var(--ok); }
.track__step.is-current .track__dot { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-tint); }
.track__dot svg { width: 12px; height: 12px; color: #fff; }
.track__txt { padding-bottom: 16px; }
.track__label { font-weight: 700; font-size: 14px; }
.track__when { color: var(--muted); font-size: 12.5px; }
.dline {
display: flex;
align-items: center;
gap: 13px;
padding: 12px 0;
border-top: 1px solid var(--line-2);
}
.dline__thumb { width: 46px; height: 46px; border-radius: 10px; display: grid; place-items: center; flex: none; }
.dline__thumb svg { width: 28px; height: 28px; }
.dline__name { font-weight: 600; font-size: 14px; }
.dline__qty { color: var(--muted); font-size: 13px; }
.dline__price { margin-left: auto; font-weight: 700; }
.drawer__totals {
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid var(--line);
display: grid;
gap: 7px;
}
.drawer__row { display: flex; justify-content: space-between; font-size: 14px; color: var(--muted); }
.drawer__row--grand { color: var(--ink); font-weight: 800; font-size: 17px; padding-top: 8px; border-top: 1px solid var(--line-2); margin-top: 4px; }
.drawer__ship {
margin-top: 16px;
background: var(--bg-soft);
border: 1px solid var(--line-2);
border-radius: var(--radius-sm);
padding: 12px 14px;
font-size: 13px;
color: var(--muted);
}
.drawer__ship b { color: var(--ink); }
/* ── Modal ────────────────────────────── */
.modal {
position: fixed;
inset: 0;
z-index: 70;
display: grid;
place-items: center;
padding: 20px;
visibility: hidden;
pointer-events: none;
}
.modal.is-open { visibility: visible; pointer-events: auto; }
.modal__scrim {
position: absolute;
inset: 0;
background: rgba(16, 18, 29, .42);
opacity: 0;
transition: opacity .22s ease;
}
.modal.is-open .modal__scrim { opacity: 1; }
.modal__panel {
position: relative;
width: min(460px, 100%);
background: var(--bg);
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
transform: translateY(10px) scale(.98);
opacity: 0;
transition: transform .24s cubic-bezier(.4, 0, .15, 1), opacity .2s ease;
max-height: 92vh;
overflow-y: auto;
}
.modal.is-open .modal__panel { transform: none; opacity: 1; }
.modal__head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px;
border-bottom: 1px solid var(--line);
}
.modal__head h2 { font-size: 18px; font-weight: 800; }
.modal__form { padding: 20px; display: flex; flex-direction: column; gap: 14px; }
.modal__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }
/* ── Toast ────────────────────────────── */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--ink);
color: #fff;
padding: 12px 18px;
border-radius: 12px;
font-weight: 600;
font-size: 14px;
box-shadow: var(--shadow-lg);
z-index: 80;
opacity: 0;
transition: opacity .22s ease, transform .22s ease;
display: flex;
align-items: center;
gap: 9px;
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
.toast::before {
content: "";
width: 16px; height: 16px;
border-radius: 50%;
background: var(--ok);
box-shadow: inset 0 0 0 2px rgba(255,255,255,.85);
}
/* ── Responsive ───────────────────────── */
@media (max-width: 880px) {
.account { grid-template-columns: 1fr; }
.side { position: static; }
.side__nav { flex-direction: row; overflow-x: auto; }
.navlink { flex-direction: column; gap: 5px; padding: 10px 12px; font-size: 12.5px; white-space: nowrap; }
.navlink svg { width: 22px; height: 22px; }
.side__logout { display: none; }
}
@media (max-width: 720px) {
.topbar__nav { display: none; }
.profile { grid-template-columns: 1fr; }
.field-row { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
.brand__name { display: none; }
.panel__head { align-items: flex-start; }
.order__actions { margin-left: 0; width: 100%; }
.order__actions .btn { flex: 1; justify-content: center; }
.seg { width: 100%; }
.seg__btn { flex: 1; }
}
@media (max-width: 380px) {
body { font-size: 14.5px; }
.shell { padding: 18px 14px 48px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}(function () {
"use strict";
/* ── Helpers ───────────────────────── */
var money = function (n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
var esc = function (s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
};
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.hidden = false;
requestAnimationFrame(function () { toastEl.classList.add("is-show"); });
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
setTimeout(function () { toastEl.hidden = true; }, 240);
}, 2400);
}
/* product silhouettes (inline SVG) keyed by type */
var ICONS = {
headphones: '<svg viewBox="0 0 24 24"><path d="M4 14v-2a8 8 0 0116 0v2" fill="none" stroke="#fff" stroke-width="1.7"/><rect x="3" y="13" width="4" height="7" rx="2" fill="#fff"/><rect x="17" y="13" width="4" height="7" rx="2" fill="#fff"/></svg>',
mug: '<svg viewBox="0 0 24 24"><path d="M5 7h11v8a4 4 0 01-4 4H9a4 4 0 01-4-4z" fill="none" stroke="#fff" stroke-width="1.7"/><path d="M16 9h2a2 2 0 010 4h-2" fill="none" stroke="#fff" stroke-width="1.7"/></svg>',
shoe: '<svg viewBox="0 0 24 24"><path d="M3 16h13l4-2 1 4H3z" fill="none" stroke="#fff" stroke-width="1.7" stroke-linejoin="round"/><path d="M3 16l1-6 4 2 2-2 2 3" fill="none" stroke="#fff" stroke-width="1.7" stroke-linejoin="round"/></svg>',
watch: '<svg viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" rx="3" fill="none" stroke="#fff" stroke-width="1.7"/><path d="M9 7V4h6v3M9 17v3h6v-3M12 10v2.5l1.6 1" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>',
lamp: '<svg viewBox="0 0 24 24"><path d="M8 4h8l3 6H5z" fill="none" stroke="#fff" stroke-width="1.7" stroke-linejoin="round"/><path d="M12 10v8M9 20h6" fill="none" stroke="#fff" stroke-width="1.7" stroke-linecap="round"/></svg>',
bottle: '<svg viewBox="0 0 24 24"><path d="M10 3h4v3l1 2v11a2 2 0 01-2 2h-2a2 2 0 01-2-2V8l1-2z" fill="none" stroke="#fff" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 12h6" stroke="#fff" stroke-width="1.7"/></svg>',
bag: '<svg viewBox="0 0 24 24"><path d="M6 8h12l-1 12H7z" fill="none" stroke="#fff" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 8V6a3 3 0 016 0v2" fill="none" stroke="#fff" stroke-width="1.7"/></svg>',
candle: '<svg viewBox="0 0 24 24"><rect x="8" y="9" width="8" height="11" rx="2" fill="none" stroke="#fff" stroke-width="1.7"/><path d="M12 9V6m0 0c1.4-1.2 1.4-2.5 0-4-1.4 1.5-1.4 2.8 0 4z" fill="none" stroke="#fff" stroke-width="1.5" stroke-linejoin="round"/></svg>'
};
var TINTS = {
headphones: "#3457ff", mug: "#e0245e", shoe: "#1f9d55", watch: "#7b5cff",
lamp: "#c2710c", bottle: "#0d9488", bag: "#db2777", candle: "#a16207"
};
function tile(type, size) {
var t = TINTS[type] || "#3457ff";
return '<span class="thumb" style="background:linear-gradient(135deg,' + t + ',' + shade(t) + ')">' + (ICONS[type] || ICONS.bag) + "</span>";
}
function shade(hex) {
// simple lighten by mixing toward a violet
return "color-mix(in srgb," + hex + " 70%, #7b5cff)";
}
/* ── Data (fictional) ───────────────── */
var ORDERS = [
{
id: "NMB-48207", placed: "Jun 9, 2026", status: "transit", statusLabel: "In transit",
eta: "Arrives Jun 16", carrier: "NimbusExpress", track: "1Z-NX-77 4109 220",
items: [
{ type: "headphones", name: "Aurora Wireless Headphones", qty: 1, price: 149.0 },
{ type: "bottle", name: "Glacier Insulated Bottle 24oz", qty: 2, price: 28.0 }
],
ship: 0, address: "Jordan Reyes · 88 Larkspur Ln, Apt 12, Portland, OR 97204"
},
{
id: "NMB-47913", placed: "May 28, 2026", status: "delivered", statusLabel: "Delivered",
eta: "Delivered Jun 2", carrier: "NimbusExpress", track: "1Z-NX-77 3998 014",
items: [
{ type: "shoe", name: "Trailwind Runner — Slate", qty: 1, price: 118.0 },
{ type: "watch", name: "Pulse Field Watch", qty: 1, price: 189.0 }
],
ship: 0, address: "Jordan Reyes · 88 Larkspur Ln, Apt 12, Portland, OR 97204"
},
{
id: "NMB-47640", placed: "May 14, 2026", status: "delivered", statusLabel: "Delivered",
eta: "Delivered May 18", carrier: "PostNimbus", track: "PN-77 5521 882",
items: [
{ type: "lamp", name: "Halo Desk Lamp — Brass", qty: 1, price: 96.0 },
{ type: "candle", name: "Member Cedar Candle", qty: 3, price: 22.0 },
{ type: "mug", name: "Stoneware Mug Set (4)", qty: 1, price: 44.0 }
],
ship: 5.99, address: "Jordan Reyes · 200 Office Park Rd, Suite 9, Portland, OR 97209"
},
{
id: "NMB-47102", placed: "Apr 30, 2026", status: "processing", statusLabel: "Processing",
eta: "Ships within 2 days", carrier: "—", track: "Pending",
items: [
{ type: "bag", name: "Daybreak Weekender Bag", qty: 1, price: 168.0 }
],
ship: 0, address: "Jordan Reyes · 88 Larkspur Ln, Apt 12, Portland, OR 97204"
}
];
var addresses = [
{ id: "a1", label: "Home", recipient: "Jordan Reyes", line: "88 Larkspur Ln, Apt 12", city: "Portland, OR", zip: "97204", default: true },
{ id: "a2", label: "Office", recipient: "Jordan Reyes", line: "200 Office Park Rd, Suite 9", city: "Portland, OR", zip: "97209", default: false }
];
var RETURNS = [
{ type: "watch", name: "Pulse Field Watch", order: "NMB-47913", status: "transit", statusLabel: "Refund in transit" },
{ type: "mug", name: "Stoneware Mug Set (4)", order: "NMB-47640", status: "delivered", statusLabel: "Refunded $44.00" }
];
/* ── Section switching ──────────────── */
var navlinks = Array.prototype.slice.call(document.querySelectorAll(".navlink"));
var panels = {
orders: document.getElementById("panel-orders"),
addresses: document.getElementById("panel-addresses"),
payment: document.getElementById("panel-payment"),
returns: document.getElementById("panel-returns"),
profile: document.getElementById("panel-profile")
};
function showSection(name) {
navlinks.forEach(function (b) {
var on = b.dataset.section === name;
b.classList.toggle("is-active", on);
if (on) { b.setAttribute("aria-current", "page"); } else { b.removeAttribute("aria-current"); }
});
Object.keys(panels).forEach(function (k) {
var p = panels[k];
if (!p) return;
var on = k === name;
p.hidden = !on;
p.classList.toggle("is-active", on);
});
}
navlinks.forEach(function (b) {
b.addEventListener("click", function () { showSection(b.dataset.section); });
});
/* ── Orders render + filter ─────────── */
var ordersList = document.getElementById("ordersList");
var ordersEmpty = document.getElementById("ordersEmpty");
var activeFilter = "all";
function renderOrders() {
var rows = ORDERS.filter(function (o) {
return activeFilter === "all" || o.status === activeFilter;
});
ordersEmpty.hidden = rows.length > 0;
ordersList.innerHTML = rows.map(function (o) {
var total = o.items.reduce(function (s, i) { return s + i.price * i.qty; }, 0) + o.ship;
var count = o.items.reduce(function (s, i) { return s + i.qty; }, 0);
var thumbs = o.items.slice(0, 3).map(function (i) { return tile(i.type); }).join("");
if (o.items.length > 3) {
thumbs += '<span class="thumb thumb--more">+' + (o.items.length - 3) + "</span>";
}
var secondAction = o.status === "delivered"
? '<button class="btn btn--ghost btn--sm" data-reorder="' + o.id + '">Reorder</button>'
: '<button class="btn btn--ghost btn--sm" data-track="' + o.id + '">Track</button>';
return (
'<li class="order">' +
'<div class="order__top">' +
'<dl class="order__meta"><dt>Order</dt><dd>#' + esc(o.id) + "</dd></dl>" +
'<dl class="order__meta"><dt>Placed</dt><dd>' + esc(o.placed) + "</dd></dl>" +
'<dl class="order__meta"><dt>Total</dt><dd>' + money(total) + "</dd></dl>" +
'<span class="order__status"><span class="badge badge--' + o.status + '">' + esc(o.statusLabel) + "</span></span>" +
"</div>" +
'<div class="order__body">' +
'<div class="thumbs">' + thumbs + "</div>" +
'<div class="order__summary">' +
'<p class="order__items">' + count + (count === 1 ? " item" : " items") + " · " + esc(o.eta) + "</p>" +
'<p class="order__total">Total <b>' + money(total) + "</b></p>" +
"</div>" +
'<div class="order__actions">' +
secondAction +
'<button class="btn btn--brand btn--sm" data-detail="' + o.id + '">View details</button>' +
"</div>" +
"</div>" +
"</li>"
);
}).join("");
}
document.querySelectorAll(".seg__btn").forEach(function (b) {
b.addEventListener("click", function () {
document.querySelectorAll(".seg__btn").forEach(function (x) {
x.classList.remove("is-active");
x.setAttribute("aria-selected", "false");
});
b.classList.add("is-active");
b.setAttribute("aria-selected", "true");
activeFilter = b.dataset.filter;
renderOrders();
});
});
ordersList.addEventListener("click", function (e) {
var d = e.target.closest("[data-detail]");
var r = e.target.closest("[data-reorder]");
var t = e.target.closest("[data-track]");
if (d) openOrder(d.getAttribute("data-detail"));
else if (r) { toast("Items added to your cart — ready to reorder."); }
else if (t) { openOrder(t.getAttribute("data-track")); }
});
/* ── Order drawer ───────────────────── */
var drawer = document.getElementById("orderDrawer");
var drawerPanel = drawer.querySelector(".drawer__panel");
var drawerBody = document.getElementById("drawerBody");
var drawerTitle = document.getElementById("drawerTitle");
var lastTrigger = null;
function trackSteps(status) {
var stages = [
{ key: "ordered", label: "Order placed", when: "Confirmed" },
{ key: "processing", label: "Processing", when: "Packed at warehouse" },
{ key: "transit", label: "In transit", when: "On the way" },
{ key: "delivered", label: "Delivered", when: "Left at front door" }
];
var order = ["processing", "transit", "delivered"];
var idx = status === "delivered" ? 3 : status === "transit" ? 2 : 1; // current stage index
return stages.map(function (s, i) {
var cls = i < idx ? "is-done" : i === idx ? "is-current" : "";
var check = i < idx ? '<svg viewBox="0 0 24 24"><path d="M5 12l4 4 10-10" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>' : "";
return (
'<div class="track__step ' + cls + '">' +
'<span class="track__dot">' + check + "</span>" +
'<span class="track__txt"><span class="track__label">' + s.label + '</span><br><span class="track__when">' + s.when + "</span></span>" +
"</div>"
);
}).join("");
}
function openOrder(id) {
var o = ORDERS.filter(function (x) { return x.id === id; })[0];
if (!o) return;
lastTrigger = document.activeElement;
drawerTitle.textContent = "#" + o.id;
var sub = o.items.reduce(function (s, i) { return s + i.price * i.qty; }, 0);
var lines = o.items.map(function (i) {
return (
'<div class="dline">' +
tile(i.type) +
'<div><p class="dline__name">' + esc(i.name) + '</p><p class="dline__qty">Qty ' + i.qty + " · " + money(i.price) + " each</p></div>" +
'<span class="dline__price">' + money(i.price * i.qty) + "</span>" +
"</div>"
);
}).join("");
drawerBody.innerHTML =
'<p style="font-weight:700;margin-bottom:4px">' + esc(o.statusLabel) + " · " + esc(o.eta) + "</p>" +
'<div class="track">' + trackSteps(o.status) + "</div>" +
'<p style="font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:4px">Items</p>' +
lines +
'<div class="drawer__totals">' +
'<div class="drawer__row"><span>Subtotal</span><span>' + money(sub) + "</span></div>" +
'<div class="drawer__row"><span>Shipping</span><span>' + (o.ship ? money(o.ship) : "Free") + "</span></div>" +
'<div class="drawer__row drawer__row--grand"><span>Total</span><span>' + money(sub + o.ship) + "</span></div>" +
"</div>" +
'<div class="drawer__ship"><b>Carrier:</b> ' + esc(o.carrier) + ' · <b>Tracking:</b> ' + esc(o.track) + "<br><b>Ship to:</b> " + esc(o.address) + "</div>";
drawer.classList.add("is-open");
drawer.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
setTimeout(function () { drawerPanel.focus(); }, 60);
}
function closeDrawer() {
drawer.classList.remove("is-open");
drawer.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
if (lastTrigger && lastTrigger.focus) lastTrigger.focus();
}
drawer.querySelectorAll("[data-close]").forEach(function (el) {
el.addEventListener("click", closeDrawer);
});
/* ── Addresses ──────────────────────── */
var addrGrid = document.getElementById("addrGrid");
function renderAddresses() {
addrGrid.innerHTML = addresses.map(function (a) {
return (
'<article class="addr' + (a.default ? " is-default" : "") + '">' +
'<div class="addr__top"><span class="addr__label">' + esc(a.label) + "</span>" +
(a.default ? '<span class="addr__chip">Default</span>' : "") + "</div>" +
'<p>' + esc(a.recipient) + "</p>" +
'<p class="addr__line">' + esc(a.line) + "</p>" +
'<p class="addr__line">' + esc(a.city) + " " + esc(a.zip) + "</p>" +
'<div class="addr__actions">' +
'<button class="linkbtn" data-edit="' + a.id + '">Edit</button>' +
'<button class="linkbtn" data-default="' + a.id + '"' + (a.default ? " disabled" : "") + ">Set default</button>" +
'<button class="linkbtn linkbtn--danger" data-remove="' + a.id + '">Remove</button>' +
"</div>" +
"</article>"
);
}).join("");
}
addrGrid.addEventListener("click", function (e) {
var ed = e.target.closest("[data-edit]");
var df = e.target.closest("[data-default]");
var rm = e.target.closest("[data-remove]");
if (ed) openAddrModal(ed.getAttribute("data-edit"));
else if (df) {
var id = df.getAttribute("data-default");
addresses.forEach(function (a) { a.default = a.id === id; });
renderAddresses();
toast("Default address updated.");
} else if (rm) {
var rid = rm.getAttribute("data-remove");
var target = addresses.filter(function (a) { return a.id === rid; })[0];
addresses = addresses.filter(function (a) { return a.id !== rid; });
if (target && target.default && addresses.length) addresses[0].default = true;
renderAddresses();
toast("Address removed.");
}
});
/* ── Address modal ──────────────────── */
var addrModal = document.getElementById("addrModal");
var addrForm = document.getElementById("addrForm");
var addrModalTitle = document.getElementById("addrModalTitle");
var modalTrigger = null;
function openAddrModal(id) {
modalTrigger = document.activeElement;
addrForm.reset();
if (id) {
var a = addresses.filter(function (x) { return x.id === id; })[0];
if (!a) return;
addrModalTitle.textContent = "Edit address";
addrForm.id.value = a.id;
addrForm.label.value = a.label;
addrForm.recipient.value = a.recipient;
addrForm.line.value = a.line;
addrForm.city.value = a.city;
addrForm.zip.value = a.zip;
addrForm.default.checked = a.default;
} else {
addrModalTitle.textContent = "Add address";
addrForm.id.value = "";
}
addrModal.classList.add("is-open");
addrModal.setAttribute("aria-hidden", "false");
setTimeout(function () { addrForm.label.focus(); }, 60);
}
function closeAddrModal() {
addrModal.classList.remove("is-open");
addrModal.setAttribute("aria-hidden", "true");
if (modalTrigger && modalTrigger.focus) modalTrigger.focus();
}
document.getElementById("addAddressBtn").addEventListener("click", function () { openAddrModal(null); });
addrModal.querySelectorAll("[data-close]").forEach(function (el) {
el.addEventListener("click", closeAddrModal);
});
addrForm.addEventListener("submit", function (e) {
e.preventDefault();
if (!addrForm.checkValidity()) { addrForm.reportValidity(); return; }
var data = {
id: addrForm.id.value || "a" + Date.now(),
label: addrForm.label.value.trim(),
recipient: addrForm.recipient.value.trim(),
line: addrForm.line.value.trim(),
city: addrForm.city.value.trim(),
zip: addrForm.zip.value.trim(),
default: addrForm.default.checked
};
var existing = addresses.filter(function (a) { return a.id === data.id; })[0];
if (data.default) addresses.forEach(function (a) { a.default = false; });
if (existing) {
Object.keys(data).forEach(function (k) { existing[k] = data[k]; });
} else {
if (!addresses.length) data.default = true;
addresses.push(data);
}
renderAddresses();
closeAddrModal();
toast(existing ? "Address updated." : "Address added.");
});
/* ── Add card (demo) ────────────────── */
document.getElementById("addCardBtn").addEventListener("click", function () {
toast("Opening secure card form…");
});
document.getElementById("logoutBtn").addEventListener("click", function () {
toast("Signing out…");
});
/* ── Returns ────────────────────────── */
var returnsList = document.getElementById("returnsList");
returnsList.innerHTML = RETURNS.map(function (r) {
var t = TINTS[r.type] || "#3457ff";
return (
'<li class="return">' +
'<span class="return__thumb" style="background:linear-gradient(135deg,' + t + "," + shade(t) + ')">' + (ICONS[r.type] || ICONS.bag) + "</span>" +
'<div class="return__info"><p class="return__name">' + esc(r.name) + '</p><p class="return__sub">From order #' + esc(r.order) + "</p></div>" +
'<span class="return__status badge badge--' + r.status + '">' + esc(r.statusLabel) + "</span>" +
"</li>"
);
}).join("");
/* ── Profile form ───────────────────── */
var profileForm = document.getElementById("profileForm");
var emailInput = document.getElementById("pf-email");
var emailErr = document.getElementById("err-email");
var sideName = document.getElementById("sideName");
profileForm.addEventListener("submit", function (e) {
e.preventDefault();
var emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value.trim());
if (!emailOk) {
emailErr.hidden = false;
emailInput.setAttribute("aria-invalid", "true");
emailInput.focus();
return;
}
emailErr.hidden = true;
emailInput.removeAttribute("aria-invalid");
var name = profileForm.name.value.trim();
if (name) sideName.textContent = name;
toast("Profile saved.");
});
emailInput.addEventListener("input", function () {
if (emailErr.hidden) return;
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value.trim())) {
emailErr.hidden = true;
emailInput.removeAttribute("aria-invalid");
}
});
/* ── Global key handling + focus trap ─ */
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
if (addrModal.classList.contains("is-open")) closeAddrModal();
else if (drawer.classList.contains("is-open")) closeDrawer();
}
if (e.key === "Tab") {
var openLayer = addrModal.classList.contains("is-open")
? addrModal.querySelector(".modal__panel")
: drawer.classList.contains("is-open") ? drawerPanel : null;
if (!openLayer) return;
var foci = openLayer.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select,textarea,[tabindex]:not([tabindex="-1"])');
if (!foci.length) return;
var first = foci[0], last = foci[foci.length - 1];
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
});
/* ── Init ───────────────────────────── */
renderOrders();
renderAddresses();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nimbus Goods Co. — Your Account</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="#main">Skip to content</a>
<header class="topbar">
<div class="topbar__inner">
<a class="brand" href="#" aria-label="Nimbus Goods Co. home">
<span class="brand__mark" aria-hidden="true">
<svg viewBox="0 0 32 32" width="26" height="26" role="img" aria-label="Nimbus logo">
<defs>
<linearGradient id="lg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3457ff" />
<stop offset="1" stop-color="#7b5cff" />
</linearGradient>
</defs>
<rect x="2" y="2" width="28" height="28" rx="9" fill="url(#lg)" />
<path d="M9 21c2-7 5-10 9-10 3 0 5 2 5 5 0 4-4 6-9 6" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" />
</svg>
</span>
<span class="brand__name">Nimbus Goods Co.</span>
</a>
<nav class="topbar__nav" aria-label="Storefront">
<a href="#">Shop</a>
<a href="#">Deals</a>
<a href="#">Support</a>
</nav>
<div class="topbar__user">
<button class="iconbtn" type="button" aria-label="Cart, 2 items">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M6 6h15l-1.6 9H8L6 4H3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="9" cy="20" r="1.4" fill="currentColor"/><circle cx="18" cy="20" r="1.4" fill="currentColor"/></svg>
<span class="iconbtn__badge">2</span>
</button>
<div class="avatar" aria-hidden="true">JR</div>
</div>
</div>
</header>
<main id="main" class="shell">
<div class="account">
<!-- Sidebar -->
<aside class="side" aria-label="Account navigation">
<div class="side__card">
<div class="side__id">
<div class="avatar avatar--lg" aria-hidden="true">JR</div>
<div>
<p class="side__hello">Hello,</p>
<p class="side__name" id="sideName">Jordan Reyes</p>
<p class="side__tier"><span class="dot" aria-hidden="true"></span> Nimbus Plus member</p>
</div>
</div>
</div>
<nav class="side__nav" aria-label="Account sections">
<button class="navlink is-active" type="button" data-section="orders" aria-current="page">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7l8-4 8 4v10l-8 4-8-4z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M4 7l8 4 8-4M12 11v10" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span>Orders</span>
</button>
<button class="navlink" type="button" data-section="addresses">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-7-5.2-7-10a7 7 0 0114 0c0 4.8-7 10-7 10z" fill="none" stroke="currentColor" stroke-width="1.7"/><circle cx="12" cy="11" r="2.4" fill="none" stroke="currentColor" stroke-width="1.7"/></svg>
<span>Addresses</span>
</button>
<button class="navlink" type="button" data-section="payment">
<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M3 9.5h18" stroke="currentColor" stroke-width="1.7"/></svg>
<span>Payment</span>
</button>
<button class="navlink" type="button" data-section="returns">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 7H6.5a3.5 3.5 0 000 7H14" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M11 4L8 7l3 3" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 17h2.5a3.5 3.5 0 000-7" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span>Returns</span>
</button>
<button class="navlink" type="button" data-section="profile">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.4" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M5 20c1.2-3.6 4-5 7-5s5.8 1.4 7 5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span>Profile</span>
</button>
</nav>
<button class="side__logout" type="button" id="logoutBtn">Sign out</button>
</aside>
<!-- Content -->
<section class="content" aria-live="polite">
<!-- ORDERS -->
<div class="panel is-active" id="panel-orders" role="region" aria-label="Orders">
<header class="panel__head">
<div>
<h1 class="panel__title">Your orders</h1>
<p class="panel__sub">Track packages, reorder favourites, and view receipts.</p>
</div>
<div class="seg" role="tablist" aria-label="Filter orders">
<button class="seg__btn is-active" type="button" data-filter="all" role="tab" aria-selected="true">All</button>
<button class="seg__btn" type="button" data-filter="transit" role="tab" aria-selected="false">In transit</button>
<button class="seg__btn" type="button" data-filter="delivered" role="tab" aria-selected="false">Delivered</button>
</div>
</header>
<ul class="orders" id="ordersList"></ul>
<p class="orders__empty" id="ordersEmpty" hidden>No orders match this filter.</p>
</div>
<!-- ADDRESSES -->
<div class="panel" id="panel-addresses" role="region" aria-label="Addresses" hidden>
<header class="panel__head">
<div>
<h1 class="panel__title">Saved addresses</h1>
<p class="panel__sub">Manage delivery destinations and set your default.</p>
</div>
<button class="btn btn--brand" type="button" id="addAddressBtn">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
Add address
</button>
</header>
<div class="addr-grid" id="addrGrid"></div>
</div>
<!-- PAYMENT -->
<div class="panel" id="panel-payment" role="region" aria-label="Payment methods" hidden>
<header class="panel__head">
<div>
<h1 class="panel__title">Payment methods</h1>
<p class="panel__sub">Cards stored for faster, secure checkout.</p>
</div>
<span class="secure-badge"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6z" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M9 12l2 2 4-4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg> PCI-secure</span>
</header>
<div class="cards" id="cardsGrid">
<article class="paycard paycard--visa">
<div class="paycard__top"><span class="paycard__brand">VISA</span><span class="paycard__def">Default</span></div>
<p class="paycard__num">•••• •••• •••• 4291</p>
<div class="paycard__bottom"><span>Jordan Reyes</span><span>09 / 28</span></div>
</article>
<article class="paycard paycard--mc">
<div class="paycard__top"><span class="paycard__brand">Mastercard</span></div>
<p class="paycard__num">•••• •••• •••• 8830</p>
<div class="paycard__bottom"><span>Jordan Reyes</span><span>02 / 27</span></div>
</article>
<button class="paycard paycard--add" type="button" id="addCardBtn">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span>Add new card</span>
</button>
</div>
</div>
<!-- RETURNS -->
<div class="panel" id="panel-returns" role="region" aria-label="Returns" hidden>
<header class="panel__head">
<div>
<h1 class="panel__title">Returns & refunds</h1>
<p class="panel__sub">Start a return within 30 days of delivery.</p>
</div>
</header>
<ul class="returns" id="returnsList"></ul>
</div>
<!-- PROFILE -->
<div class="panel" id="panel-profile" role="region" aria-label="Profile" hidden>
<header class="panel__head">
<div>
<h1 class="panel__title">Profile</h1>
<p class="panel__sub">Your contact details and preferences.</p>
</div>
</header>
<form class="profile" id="profileForm" novalidate>
<div class="field">
<label for="pf-name">Full name</label>
<input id="pf-name" name="name" type="text" value="Jordan Reyes" autocomplete="name" required />
</div>
<div class="field">
<label for="pf-email">Email</label>
<input id="pf-email" name="email" type="email" value="[email protected]" autocomplete="email" required />
<span class="field__err" id="err-email" hidden>Enter a valid email address.</span>
</div>
<div class="field">
<label for="pf-phone">Phone</label>
<input id="pf-phone" name="phone" type="tel" value="+1 (415) 555-0192" autocomplete="tel" />
</div>
<div class="field">
<label for="pf-lang">Preferred language</label>
<select id="pf-lang" name="lang">
<option>English (US)</option>
<option>Español</option>
<option>Français</option>
<option>Deutsch</option>
</select>
</div>
<fieldset class="field field--full">
<legend>Notifications</legend>
<label class="check"><input type="checkbox" name="promo" checked /> <span>Order updates & shipping alerts</span></label>
<label class="check"><input type="checkbox" name="news" /> <span>Weekly deals newsletter</span></label>
</fieldset>
<div class="profile__actions">
<button class="btn btn--ghost" type="reset">Reset</button>
<button class="btn btn--brand" type="submit">Save changes</button>
</div>
</form>
</div>
</section>
</div>
</main>
<!-- Order detail drawer -->
<div class="drawer" id="orderDrawer" aria-hidden="true">
<div class="drawer__scrim" data-close></div>
<aside class="drawer__panel" role="dialog" aria-modal="true" aria-labelledby="drawerTitle" tabindex="-1">
<header class="drawer__head">
<div>
<p class="drawer__eyebrow">Order detail</p>
<h2 class="drawer__title" id="drawerTitle">Order</h2>
</div>
<button class="iconbtn iconbtn--close" type="button" data-close aria-label="Close order detail">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</header>
<div class="drawer__body" id="drawerBody"></div>
</aside>
</div>
<!-- Address editor modal -->
<div class="modal" id="addrModal" aria-hidden="true">
<div class="modal__scrim" data-close></div>
<div class="modal__panel" role="dialog" aria-modal="true" aria-labelledby="addrModalTitle">
<header class="modal__head">
<h2 id="addrModalTitle">Add address</h2>
<button class="iconbtn iconbtn--close" type="button" data-close aria-label="Close">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</header>
<form class="modal__form" id="addrForm" novalidate>
<input type="hidden" name="id" />
<div class="field">
<label for="af-label">Label</label>
<input id="af-label" name="label" type="text" placeholder="Home, Office…" required />
</div>
<div class="field">
<label for="af-name">Recipient</label>
<input id="af-name" name="recipient" type="text" placeholder="Full name" required />
</div>
<div class="field">
<label for="af-line">Street address</label>
<input id="af-line" name="line" type="text" placeholder="123 Market St, Apt 4" required />
</div>
<div class="field-row">
<div class="field">
<label for="af-city">City</label>
<input id="af-city" name="city" type="text" required />
</div>
<div class="field">
<label for="af-zip">Postal code</label>
<input id="af-zip" name="zip" type="text" required />
</div>
</div>
<label class="check"><input type="checkbox" name="default" /> <span>Set as default address</span></label>
<div class="modal__actions">
<button class="btn btn--ghost" type="button" data-close>Cancel</button>
<button class="btn btn--brand" type="submit">Save address</button>
</div>
</form>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Account
A self-service account page for the fictional “Nimbus Goods Co.” A sticky sidebar carries the member card and a five-item nav — Orders, Addresses, Payment, Returns, Profile — that switches the main panel without a reload. Orders render as rich cards with overlapping product thumbnails (inline-SVG silhouettes on gradient tiles), colour-coded status badges (In transit, Delivered, Processing), and contextual actions: delivered orders offer Reorder, open orders offer Track, and every order opens a View details drawer.
The order drawer slides in with a four-stage delivery timeline, itemised lines, subtotal/shipping/total math, and carrier plus tracking metadata — it traps focus, closes on Escape or scrim click, and restores focus to its trigger. The addresses tab is a working manager: add a new address through a validated modal, edit any card, remove one, or set a default (the previous default clears automatically). Payment shows stored card visuals, Returns lists in-progress refunds, and the Profile form validates the email, updates the sidebar name, and confirms via toast.
Everything is keyboard-usable with visible focus rings, landmark roles, and live-region announcements. The layout collapses gracefully toward ~360px — the sidebar nav turns into a horizontal scroller, grids reflow to one column, and order actions stretch full width.
Illustrative storefront UI only — fictional products, prices, and reviews. No real checkout.