feat: add Porchlight branding with logo, favicon, and redesigned CSS theme
Rebrand from FastAPI OIDC OP to Porchlight with warm amber/gold identity: - Add doorway-with-light SVG logo and 32x32 PNG favicon - Rewrite style.css with full design system (color tokens, spacing scale, typography scale, section cards, button variants, dark mode) - Update base template with site header, logo, and favicon - Update all page titles and FastAPI app title to Porchlight
This commit is contained in:
parent
e15dcc4745
commit
84e61464c7
8 changed files with 310 additions and 76 deletions
|
|
@ -68,7 +68,7 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
|||
settings = Settings() # type: ignore[call-arg]
|
||||
|
||||
app = FastAPI(
|
||||
title="FastAPI OIDC OP",
|
||||
title="Porchlight",
|
||||
version="0.1.0",
|
||||
docs_url="/docs" if settings.debug else None,
|
||||
redoc_url=None,
|
||||
|
|
|
|||
BIN
src/fastapi_oidc_op/static/favicon.png
Normal file
BIN
src/fastapi_oidc_op/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 B |
14
src/fastapi_oidc_op/static/logo.svg
Normal file
14
src/fastapi_oidc_op/static/logo.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
||||
<!-- Light / glow circle -->
|
||||
<circle cx="32" cy="12" r="5" fill="#d97706"/>
|
||||
<!-- Subtle rays -->
|
||||
<line x1="32" y1="3" x2="32" y2="5" stroke="#d97706" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="23.5" y1="5.5" x2="25" y2="7" stroke="#d97706" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="40.5" y1="5.5" x2="39" y2="7" stroke="#d97706" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12" stroke="#d97706" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="43" y1="12" x2="41" y2="12" stroke="#d97706" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Doorway arch -->
|
||||
<path d="M16 62 V32 A16 16 0 0 1 48 32 V62" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<!-- Threshold line -->
|
||||
<line x1="14" y1="62" x2="50" y2="62" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 984 B |
|
|
@ -1,30 +1,67 @@
|
|||
/* ==========================================================================
|
||||
Porchlight — Default Theme
|
||||
========================================================================== */
|
||||
|
||||
/* ---------- Design tokens ---------- */
|
||||
|
||||
:root {
|
||||
--bg: #fdfdfd;
|
||||
--fg: #1a1a1a;
|
||||
--accent: #2563eb;
|
||||
/* Colors — light mode */
|
||||
--bg: #fafaf9;
|
||||
--fg: #1c1917;
|
||||
--fg-muted: #78716c;
|
||||
--accent: #d97706;
|
||||
--accent-hover: #b45309;
|
||||
--accent-fg: #fff;
|
||||
--border: #d1d5db;
|
||||
--surface: #f5f5f4;
|
||||
--border: #d6d3d1;
|
||||
--error-bg: #fef2f2;
|
||||
--error-fg: #991b1b;
|
||||
--error-fg: #dc2626;
|
||||
--success-bg: #f0fdf4;
|
||||
--success-fg: #166534;
|
||||
--success-fg: #16a34a;
|
||||
--radius: 0.375rem;
|
||||
|
||||
/* Spacing scale (4px base) */
|
||||
--sp-1: 0.25rem; /* 4px */
|
||||
--sp-2: 0.5rem; /* 8px */
|
||||
--sp-3: 0.75rem; /* 12px */
|
||||
--sp-4: 1rem; /* 16px */
|
||||
--sp-5: 1.25rem; /* 20px */
|
||||
--sp-6: 1.5rem; /* 24px */
|
||||
--sp-8: 2rem; /* 32px */
|
||||
--sp-10: 2.5rem; /* 40px */
|
||||
--sp-12: 3rem; /* 48px */
|
||||
|
||||
/* Typography */
|
||||
--font-family: system-ui, -apple-system, sans-serif;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--font-size-2xl: 1.5rem;
|
||||
--font-size-3xl: 1.875rem;
|
||||
--line-height: 1.6;
|
||||
--line-height-tight: 1.25;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #111;
|
||||
--fg: #e5e5e5;
|
||||
--accent: #60a5fa;
|
||||
--accent-fg: #111;
|
||||
--border: #404040;
|
||||
--error-bg: #450a0a;
|
||||
--error-fg: #fca5a5;
|
||||
--success-bg: #052e16;
|
||||
--success-fg: #86efac;
|
||||
--bg: #1c1917;
|
||||
--fg: #fafaf9;
|
||||
--fg-muted: #a8a29e;
|
||||
--accent: #f59e0b;
|
||||
--accent-hover: #fbbf24;
|
||||
--accent-fg: #1c1917;
|
||||
--surface: #292524;
|
||||
--border: #44403c;
|
||||
--error-bg: #451a1a;
|
||||
--error-fg: #f87171;
|
||||
--success-bg: #14532d;
|
||||
--success-fg: #4ade80;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Reset ---------- */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
|
|
@ -32,23 +69,247 @@
|
|||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height);
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
max-width: 40rem;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ---------- Layout ---------- */
|
||||
|
||||
.site-header {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--sp-4) var(--sp-4) 0;
|
||||
}
|
||||
|
||||
.site-header a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
text-decoration: none;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.site-header a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--sp-6) var(--sp-4) var(--sp-12);
|
||||
}
|
||||
|
||||
/* ---------- Typography ---------- */
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 700;
|
||||
line-height: var(--line-height-tight);
|
||||
margin: 0 0 var(--sp-6);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 600;
|
||||
line-height: var(--line-height-tight);
|
||||
margin: 0 0 var(--sp-4);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 600;
|
||||
line-height: var(--line-height-tight);
|
||||
margin: 0 0 var(--sp-3);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 var(--sp-4);
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
/* ---------- Links ---------- */
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* ---------- Sections / Cards ---------- */
|
||||
|
||||
section {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--sp-6);
|
||||
margin-bottom: var(--sp-6);
|
||||
}
|
||||
|
||||
section h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
section h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* ---------- Forms ---------- */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--sp-1);
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height);
|
||||
margin-bottom: var(--sp-4);
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="email"]:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
|
||||
}
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
|
||||
button,
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--sp-2);
|
||||
padding: var(--sp-2) var(--sp-4);
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
text-decoration: none;
|
||||
line-height: var(--line-height-tight);
|
||||
}
|
||||
|
||||
button:hover,
|
||||
.btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
button:active,
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Secondary button */
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--surface);
|
||||
border-color: var(--fg-muted);
|
||||
}
|
||||
|
||||
/* Danger button */
|
||||
.btn-danger {
|
||||
background: var(--error-fg);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* ---------- Alerts ---------- */
|
||||
|
||||
[role="alert"] {
|
||||
background: var(--error-bg);
|
||||
color: var(--error-fg);
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid color-mix(in srgb, var(--error-fg) 20%, transparent);
|
||||
margin-bottom: var(--sp-4);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
[role="status"] {
|
||||
background: var(--success-bg);
|
||||
color: var(--success-fg);
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid color-mix(in srgb, var(--success-fg) 20%, transparent);
|
||||
margin-bottom: var(--sp-4);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ---------- Lists ---------- */
|
||||
|
||||
ul {
|
||||
padding-left: var(--sp-5);
|
||||
margin: 0 0 var(--sp-4);
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
|
||||
/* ---------- Accessibility ---------- */
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
padding: 0.5rem 1rem;
|
||||
padding: var(--sp-2) var(--sp-4);
|
||||
z-index: 100;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
|
|
@ -60,56 +321,6 @@ body {
|
|||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
[role="alert"] {
|
||||
background: var(--error-bg);
|
||||
color: var(--error-fg);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
[role="status"] {
|
||||
background: var(--success-bg);
|
||||
color: var(--success-fg);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
|
|
@ -122,6 +333,8 @@ button:hover {
|
|||
border: 0;
|
||||
}
|
||||
|
||||
/* ---------- Reduced motion ---------- */
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,18 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}FastAPI OIDC OP{% endblock %}</title>
|
||||
<title>{% block title %}Porchlight{% endblock %}</title>
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<header class="site-header">
|
||||
<a href="/">
|
||||
<img class="site-logo" src="/static/logo.svg" alt="" width="32" height="32">
|
||||
<span class="site-title">Porchlight</span>
|
||||
</a>
|
||||
</header>
|
||||
<main id="main" tabindex="-1">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login — FastAPI OIDC OP{% endblock %}
|
||||
{% block title %}Sign in — Porchlight{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Sign in</h1>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Credentials — FastAPI OIDC OP{% endblock %}
|
||||
{% block title %}Credentials — Porchlight{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Credentials</h1>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ async def test_app_has_title(client: AsyncClient) -> None:
|
|||
response = await client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["info"]["title"] == "FastAPI OIDC OP"
|
||||
assert data["info"]["title"] == "Porchlight"
|
||||
|
||||
|
||||
async def test_app_has_repos_on_state(client: AsyncClient) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue