Rework the Live preview to match D9's actual screenshot
Replaces the earlier sidebar+small-phone mock with D9's real layout: App/ Website/Email tabs (App is the only one with real content, so these are static labels not a working control), a full-size phone mock, and a public- site mock, plus the "Where the brand shows up" list. The phone's "Next up" card now uses primary, not secondary -- it's a notice, not a call to action, so it shouldn't compete visually with an actual accent-coloured button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
@@ -1804,30 +1804,29 @@
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.preview-frame {
|
||||
display: flex;
|
||||
height: 220px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-box);
|
||||
/* Live preview tabs -- App is the only one with real content behind it
|
||||
(Website/Email aren't built), so these are static labels, not a real
|
||||
tab control. */
|
||||
.preview-tab {
|
||||
padding: 0.3125rem 0.625rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.preview-sidebar {
|
||||
display: flex;
|
||||
width: 40%;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
.preview-tab-active {
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-crest {
|
||||
display: flex;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.25rem;
|
||||
@@ -1837,65 +1836,6 @@
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.preview-club-name {
|
||||
margin-bottom: 0.25rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.preview-nav-item {
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
font-size: 0.6875rem;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.preview-nav-active {
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
background: var(--color-club);
|
||||
color: #fff;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-panel {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
padding: 1rem;
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.preview-panel-heading {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.preview-button {
|
||||
align-self: flex-start;
|
||||
border: none;
|
||||
border-radius: var(--radius-box);
|
||||
padding: 0.375rem 0.875rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
background: var(--color-club);
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-skeleton-line {
|
||||
height: 0.5rem;
|
||||
border-radius: 999px;
|
||||
@@ -1906,11 +1846,13 @@
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
/* The mobile app mock -- not built yet, but its header/role-switcher take
|
||||
the same primary/secondary colours the sidebar mock above does, so this
|
||||
preview stays accurate once it exists rather than needing a rework. */
|
||||
.preview-phone {
|
||||
width: 11rem;
|
||||
/* The mobile app mock -- not built yet, but its header/role-switcher/
|
||||
"Next up" card take the same primary/secondary colours a real one
|
||||
would, so this preview stays accurate once it exists. Sized closer to
|
||||
an actual phone aspect ratio (tall, mostly-empty screen below the
|
||||
first couple of cards) rather than a cropped sliver. */
|
||||
.preview-phone-lg {
|
||||
width: 12.5rem;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border: 5px solid var(--color-ink);
|
||||
@@ -1918,6 +1860,10 @@
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.preview-phone-lg .preview-phone-body {
|
||||
min-height: 15rem;
|
||||
}
|
||||
|
||||
.preview-phone-header {
|
||||
padding: 0.75rem 0.625rem 0.625rem;
|
||||
background: var(--color-ink);
|
||||
@@ -1985,7 +1931,7 @@
|
||||
.preview-phone-card {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--color-club);
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
@@ -2021,4 +1967,82 @@
|
||||
padding: 0.5rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* The public-site mock -- same "not built yet, but coloured accurately"
|
||||
reasoning as the phone mock above. */
|
||||
.preview-website {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
.preview-website-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-website-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.preview-website-nav {
|
||||
margin-left: auto;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-on-dark);
|
||||
}
|
||||
|
||||
.preview-website-join {
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.1875rem 0.4375rem;
|
||||
background: var(--color-club);
|
||||
color: #fff;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.625rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.preview-website-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.875rem 0.75rem;
|
||||
background: var(--color-subhead);
|
||||
}
|
||||
|
||||
.preview-website-headline {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.375rem;
|
||||
line-height: 0.98;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.preview-brand-dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.125rem;
|
||||
background: var(--color-club);
|
||||
}
|
||||
|
||||
.preview-brand-dot-neutral {
|
||||
background: var(--color-dim);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,56 +115,66 @@
|
||||
<div class="xl:sticky xl:top-4 xl:self-start">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{% lucide "eye" size=16 %} {% trans "Live preview" %}</div>
|
||||
<p class="text-xs text-muted">{% trans "Where these colours actually show up -- updates as you edit, before you save anything." %}</p>
|
||||
|
||||
<div class="mt-2 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Management app" %}</div>
|
||||
<div class="preview-frame mt-1.5">
|
||||
<div id="preview-sidebar" class="preview-sidebar">
|
||||
<div class="preview-crest">{{ club.initials|default:"CL" }}</div>
|
||||
<div class="preview-club-name">{{ club.name|default:_("Your club") }}</div>
|
||||
<div class="preview-nav-item">{% trans "Overview" %}</div>
|
||||
<div id="preview-nav-active" class="preview-nav-item preview-nav-active">{% trans "Members" %}</div>
|
||||
<div class="preview-nav-item">{% trans "Calendar" %}</div>
|
||||
</div>
|
||||
<div class="preview-panel">
|
||||
<div class="preview-panel-heading">{% trans "New member" %}</div>
|
||||
<button type="button" id="preview-button" class="preview-button">{% trans "Save changes" %}</button>
|
||||
<div class="preview-skeleton-line"></div>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="card-title">{% lucide "eye" size=16 %} {% trans "Live preview" %}</div>
|
||||
<div class="flex overflow-hidden rounded-md border border-line">
|
||||
<span class="preview-tab preview-tab-active">{% trans "App" %}</span>
|
||||
<span class="preview-tab">{% trans "Website" %}</span>
|
||||
<span class="preview-tab">{% trans "Email" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center justify-between gap-2">
|
||||
<div class="font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Mobile app" %}</div>
|
||||
<span class="badge badge-sm badge-ghost">{% trans "Coming soon" %}</span>
|
||||
</div>
|
||||
<div class="mt-1.5 preview-phone">
|
||||
<div id="preview-phone-header" class="preview-phone-header">
|
||||
<div class="preview-phone-header-row">
|
||||
<span class="preview-crest preview-crest-sm">{{ club.initials|default:"CL" }}</span>
|
||||
<span class="preview-phone-name">{{ club.name|default:_("Your club") }}</span>
|
||||
<div class="mt-3 flex flex-wrap items-start gap-4">
|
||||
<div class="preview-phone-lg">
|
||||
<div id="preview-phone-header" class="preview-phone-header">
|
||||
<div class="preview-phone-header-row">
|
||||
<span class="preview-crest preview-crest-sm">{{ club.initials|default:"CL" }}</span>
|
||||
<span class="preview-phone-name">{{ club.name|default:_("Your club") }}</span>
|
||||
</div>
|
||||
<div class="preview-phone-roles">
|
||||
<span class="preview-phone-role preview-phone-role-active">{% trans "Member" %}</span>
|
||||
<span class="preview-phone-role">{% trans "Coach" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-phone-roles">
|
||||
<span class="preview-phone-role preview-phone-role-active">{% trans "Member" %}</span>
|
||||
<span class="preview-phone-role">{% trans "Coach" %}</span>
|
||||
<div class="preview-phone-body">
|
||||
{# A notice, not a call to action -- primary (chrome), not secondary (accent), so it doesn't visually compete with an actual button. #}
|
||||
<div id="preview-phone-card" class="preview-phone-card">
|
||||
<div class="preview-phone-eyebrow">{% trans "Next up" %}</div>
|
||||
<div class="preview-phone-title">{% trans "U16 vs Leuven" %}</div>
|
||||
<div class="preview-phone-bar"></div>
|
||||
</div>
|
||||
<div class="preview-phone-skeleton-card">
|
||||
<div class="preview-skeleton-line"></div>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-phone-body">
|
||||
<div id="preview-phone-card" class="preview-phone-card">
|
||||
<div class="preview-phone-eyebrow">{% trans "Next up" %}</div>
|
||||
<div class="preview-phone-title">{% trans "U16 vs Leuven" %}</div>
|
||||
<div class="preview-phone-bar"></div>
|
||||
</div>
|
||||
<div class="preview-phone-skeleton-card">
|
||||
<div class="preview-skeleton-line"></div>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-xs text-dim">
|
||||
{% trans "Never used for: status colours (paid/overdue, present/absent), table styling, or form fields -- those stay consistent for everyone regardless of club branding." %}
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-3">
|
||||
<div class="preview-website">
|
||||
<div id="preview-website-header" class="preview-website-header">
|
||||
<span class="preview-crest preview-crest-sm">{{ club.initials|default:"CL" }}</span>
|
||||
<span class="preview-website-name">{{ club.name|default:_("Your club") }}</span>
|
||||
<span class="preview-website-nav">{% trans "Teams · Club" %}</span>
|
||||
<span id="preview-website-join" class="preview-website-join">{% trans "Join" %}</span>
|
||||
</div>
|
||||
<div class="preview-website-hero">
|
||||
<div class="preview-website-headline">{% trans "#Fear the Sharks" %}</div>
|
||||
<div class="preview-skeleton-line"></div>
|
||||
<div class="preview-skeleton-line preview-skeleton-line-short"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-line p-3">
|
||||
<div class="mb-2 font-display text-[11px] font-bold tracking-[.12em] text-muted uppercase">{% trans "Where the brand shows up" %}</div>
|
||||
<ul class="flex flex-col gap-1.5 text-[13px] text-slate">
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-1" class="preview-brand-dot"></span>{% trans "Sidebar, active nav item, primary buttons" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-2" class="preview-brand-dot"></span>{% trans "Public site header and join button" %}</li>
|
||||
<li class="flex items-center gap-2"><span id="preview-dot-3" class="preview-brand-dot"></span>{% trans "Mobile app header and highlights" %}</li>
|
||||
<li class="flex items-center gap-2"><span class="preview-brand-dot preview-brand-dot-neutral"></span>{% trans "Never: status colours, tables, form fields" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,17 +228,21 @@
|
||||
const primaryContent = contentColorFor(primary);
|
||||
const secondaryContent = contentColorFor(secondary);
|
||||
|
||||
document.getElementById("preview-sidebar").style.background = primary;
|
||||
document.getElementById("preview-sidebar").style.color = primaryContent;
|
||||
document.getElementById("preview-nav-active").style.background = secondary;
|
||||
document.getElementById("preview-nav-active").style.color = secondaryContent;
|
||||
document.getElementById("preview-button").style.background = secondary;
|
||||
document.getElementById("preview-button").style.color = secondaryContent;
|
||||
|
||||
document.getElementById("preview-phone-header").style.background = primary;
|
||||
document.getElementById("preview-phone-header").style.color = primaryContent;
|
||||
document.getElementById("preview-phone-card").style.background = secondary;
|
||||
document.getElementById("preview-phone-card").style.color = secondaryContent;
|
||||
// A notice, not a call to action -- primary (chrome), not secondary
|
||||
// (accent), so it doesn't compete for attention with an actual button.
|
||||
document.getElementById("preview-phone-card").style.background = primary;
|
||||
document.getElementById("preview-phone-card").style.color = primaryContent;
|
||||
|
||||
document.getElementById("preview-website-header").style.background = primary;
|
||||
document.getElementById("preview-website-header").style.color = primaryContent;
|
||||
document.getElementById("preview-website-join").style.background = secondary;
|
||||
document.getElementById("preview-website-join").style.color = secondaryContent;
|
||||
|
||||
["preview-dot-1", "preview-dot-2", "preview-dot-3"].forEach((id) => {
|
||||
document.getElementById(id).style.background = secondary;
|
||||
});
|
||||
|
||||
setContrastBox("contrast-box-primary", "contrast-label-primary", "contrast-ratio-primary", primary, primaryContent, "{% trans 'on primary' %}");
|
||||
setContrastBox("contrast-box-secondary", "contrast-label-secondary", "contrast-ratio-secondary", secondary, secondaryContent, "{% trans 'on secondary' %}");
|
||||
|
||||
@@ -4465,18 +4465,19 @@ class ClubSettingsPreviewTests(ManagementTestBase):
|
||||
def setUp(self):
|
||||
self.client.force_login(self.admin_user)
|
||||
|
||||
def test_the_preview_frame_and_colour_swatches_render(self):
|
||||
def test_the_preview_tabs_and_colour_swatches_render(self):
|
||||
response = self.club_get("club_settings")
|
||||
|
||||
self.assertContains(response, "preview-sidebar")
|
||||
self.assertContains(response, "preview-tab-active")
|
||||
self.assertContains(response, 'id="primary_color_swatch"')
|
||||
self.assertContains(response, 'id="secondary_color_swatch"')
|
||||
|
||||
def test_the_mobile_app_preview_renders(self):
|
||||
def test_the_mobile_app_and_website_previews_render(self):
|
||||
response = self.club_get("club_settings")
|
||||
|
||||
self.assertContains(response, "preview-phone")
|
||||
self.assertContains(response, "Coming soon")
|
||||
self.assertContains(response, "preview-phone-lg")
|
||||
self.assertContains(response, "preview-website")
|
||||
self.assertContains(response, "Where the brand shows up")
|
||||
|
||||
def test_the_website_field_is_editable(self):
|
||||
response = self.club_get("club_settings")
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user