Area riservata: menu essenziale e veste grafica del sito

Il menu in alto si riduce alle voci che servono davvero — Articoli,
Contenuti, Utenti, Sito, Esci — con la voce corrente evidenziata; ognuna
resta visibile solo a chi ha il ruolo per aprirla, e in pagina di login la
barra mostra il solo marchio. "Nuovo" esce dal menu perché il pulsante per
creare un articolo è già in cima all'elenco Articoli.

Il pannello smette di essere un foglio di stile a sé: adotta i font e la
palette del sito, come Campus e Stress Index, con tabelle, pulsanti, campi
e pillole di stato riportati allo stesso linguaggio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:28:35 +02:00
parent 8207627425
commit 03510d3940
3 changed files with 140 additions and 46 deletions
+123 -33
View File
@@ -1,7 +1,28 @@
--- ---
// Layout dell'area riservata. Usa i token e i font del sito, come il Campus e Stress Index:
// il pannello non è più un foglio di stile a sé, così chi passa dal sito all'amministrazione
// resta nello stesso mondo visivo.
import '@fontsource/montserrat/400.css';
import '@fontsource/montserrat/500.css';
import '@fontsource/montserrat/600.css';
import '@fontsource/open-sans/400.css';
import '@fontsource/open-sans/600.css';
import '../styles/global.css';
interface Props { title: string } interface Props { title: string }
const { title } = Astro.props; const { title } = Astro.props;
const user = Astro.locals.user; const user = Astro.locals.user;
const path = Astro.url.pathname;
const isManager = user?.role === 'superuser' || user?.role === 'admin';
// Voci del menu: ognuna compare solo a chi può davvero aprirla.
const VOCI = [
{ href: '/admin', label: 'Articoli', visibile: true, exact: true },
{ href: '/admin/content', label: 'Contenuti', visibile: isManager },
{ href: '/admin/users', label: 'Utenti', visibile: user?.role === 'admin' },
];
const attiva = (v: { href: string; exact?: boolean }) =>
v.exact ? path === v.href : path.startsWith(v.href);
--- ---
<!doctype html> <!doctype html>
<html lang="it"> <html lang="it">
@@ -10,41 +31,110 @@ const user = Astro.locals.user;
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title} — Admin InsanityLab</title> <title>{title} — Admin InsanityLab</title>
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<style is:global> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
body { font-family: system-ui, sans-serif; margin: 0; background: #f4f2ef; color: #333; }
.abar { background: #3a2d26; color: #fff; padding: 12px 24px; display: flex; justify-content: space-between; align-items: center; }
.abar a { color: #d9cfc4; text-decoration: none; margin-left: 16px; }
.awrap { max-width: 960px; margin: 30px auto; padding: 0 20px; }
.abtn { background: #b5a48b; color: #fff; border: 0; padding: 10px 18px; cursor: pointer; font-size: .9rem; text-decoration: none; display: inline-block; }
.abtn--danger { background: #a33; }
.afield { width: 100%; padding: 10px; margin-bottom: 14px; border: 1px solid #ccc; box-sizing: border-box; font: inherit; }
table { width: 100%; border-collapse: collapse; background: #fff; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #eee; font-size: .92rem; }
.pill { font-size: .75rem; padding: 2px 10px; border-radius: 10px; background: #e5decf; }
.pill--draft { background: #f3d9a4; }
.form-msg { font-size: .9rem; margin-top: 8px; }
.form-msg--ok { color: #3c7a3c; }
.form-msg--err { color: #b03030; }
</style>
</head> </head>
<body> <body class="ad">
<div class="abar"> <header class="abar">
<strong>InsanityLab · Admin</strong> <a class="abar__brand" href="/admin">
<span> <span class="abar__eyebrow">InsanityLab</span>
Area riservata
</a>
{user && ( {user && (
<> <nav class="abar__nav" aria-label="pannello">
{`Ciao, ${user.username}`} {VOCI.filter((v) => v.visibile).map((v) => (
<a href="/admin">Articoli</a> <a href={v.href} class:list={['abar__link', { 'is-active': attiva(v) }]}>{v.label}</a>
{(user.role === 'superuser' || user.role === 'admin') && <a href="/admin/content">Contenuti</a>} ))}
{user.role === 'admin' && <a href="/admin/users">Utenti</a>} <a class="abar__link" href="/" target="_blank" rel="noopener">Sito</a>
{user.role === 'admin' && <a href="/piattaforme">Piattaforme</a>} <a class="abar__link abar__link--out" href="/admin/logout">Esci</a>
<a href="/admin/new">Nuovo</a> </nav>
<a href="/admin/logout">Esci</a>
<a href="/blog" target="_blank">Vedi sito ↗</a>
</>
)} )}
</span>
</div> {user && <p class="abar__user">{user.username}</p>}
<div class="awrap"><slot /></div> </header>
<main class="awrap"><slot /></main>
<style is:global>
body.ad { background: var(--c-bg-alt); }
/* --- barra --- */
.abar {
display: flex; align-items: center; gap: 28px; flex-wrap: wrap;
background: var(--c-dark); color: #cfc6bd; padding: 16px 32px;
}
.abar__brand {
font-family: var(--font-heading); font-size: 1rem; font-weight: 600; letter-spacing: .12em;
text-transform: uppercase; color: #fff; text-decoration: none; line-height: 1.15;
}
.abar__eyebrow { display: block; font-size: .62rem; letter-spacing: .2em; color: var(--c-accent); }
.abar__nav { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.abar__link {
font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .16em;
text-transform: uppercase; color: #b6ada3; text-decoration: none; padding: 4px 0;
border-bottom: 1px solid transparent;
}
.abar__link:hover { color: #fff; }
.abar__link.is-active { color: #fff; border-bottom-color: var(--c-accent); }
.abar__link--out { color: var(--c-accent); }
.abar__user {
margin: 0 0 0 auto; font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; color: #8d8379;
}
/* --- contenuto --- */
.awrap { max-width: 1080px; margin: 40px auto 80px; padding: 0 24px; }
.awrap h1 {
font-size: clamp(1.6rem, 3vw, 2.1rem); text-transform: none; letter-spacing: .02em; margin-bottom: .6em;
}
.awrap h2 { font-size: 1.15rem; text-transform: none; letter-spacing: .02em; }
/* --- comandi --- */
.abtn {
display: inline-block; background: var(--c-dark); color: #fff; border: 1px solid var(--c-dark);
padding: 10px 20px; cursor: pointer; text-decoration: none; font-family: var(--font-heading);
font-size: .7rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
}
.abtn:hover { background: var(--c-dark-2); border-color: var(--c-dark-2); }
.abtn--danger { background: none; color: #a4544a; border-color: #ddcfc9; }
.abtn--danger:hover { background: #a4544a; border-color: #a4544a; color: #fff; }
.afield {
width: 100%; padding: 11px 13px; margin-bottom: 14px; border: 1px solid #ddd6cb;
box-sizing: border-box; font: inherit; font-size: .92rem; background: #fff; border-radius: 0;
}
.afield:focus { outline: 2px solid var(--c-accent); outline-offset: -2px; }
/* --- tabelle --- */
table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #e4dfd7; }
th, td { text-align: left; padding: 12px 14px; border-bottom: 1px solid #efeae2; font-size: .9rem; }
th {
font-family: var(--font-heading); font-size: .68rem; font-weight: 600; letter-spacing: .14em;
text-transform: uppercase; color: var(--c-text-light); background: #faf8f5;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: #faf8f5; }
td a { color: var(--c-accent-dark); font-weight: 600; text-decoration: none; }
td a:hover { text-decoration: underline; }
.pill {
display: inline-block; font-family: var(--font-heading); font-size: .64rem; font-weight: 600;
letter-spacing: .1em; text-transform: uppercase; padding: 3px 10px;
background: rgba(111,143,106,.16); color: #5f7c5a;
}
.pill--draft { background: rgba(192,138,62,.18); color: #96682a; }
.aform { max-width: 380px; background: #fff; border: 1px solid #e4dfd7; padding: 26px 24px; }
.form-msg { font-size: .88rem; margin-top: 8px; }
.form-msg--ok { color: #5f7c5a; }
.form-msg--err { color: #a4544a; }
@media (max-width: 720px) {
.abar { padding: 14px 20px; gap: 14px; }
.abar__nav { gap: 16px; width: 100%; }
.abar__user { margin-left: 0; }
.awrap { margin-top: 26px; padding: 0 20px; }
}
</style>
</body> </body>
</html> </html>
+13 -9
View File
@@ -109,22 +109,26 @@ const allStyles = STYLE_GROUPS as Record<string, readonly string[]>;
<style is:global> <style is:global>
.chead { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; flex-wrap: wrap; } .chead { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.chead a { color: #7a6a55; } .chead a { color: var(--c-accent-dark); font-weight: 600; text-decoration: none; }
.cgroup { background: #fff; border: 1px solid #ddd; margin-bottom: 14px; } .cgroup { background: #fff; border: 1px solid #e4dfd7; margin-bottom: 14px; }
.cgroup > summary { padding: 12px 16px; cursor: pointer; background: #eee9e1; font-size: 1.05rem; } .cgroup > summary {
.crow { border-top: 1px solid #eee; } padding: 13px 16px; cursor: pointer; background: #faf8f5; border-bottom: 1px solid #efeae2;
font-family: var(--font-heading); font-size: .78rem; font-weight: 600; letter-spacing: .14em;
text-transform: uppercase; color: var(--c-heading);
}
.crow { border-top: 1px solid #efeae2; }
.crow > summary { padding: 8px 16px; cursor: pointer; display: flex; gap: 12px; align-items: baseline; } .crow > summary { padding: 8px 16px; cursor: pointer; display: flex; gap: 12px; align-items: baseline; }
.crow > summary code { background: #f0ece5; padding: 1px 6px; font-size: .82rem; white-space: nowrap; } .crow > summary code { background: #f2ede5; padding: 2px 7px; font-size: .78rem; white-space: nowrap; color: #6f6357; }
.crow-snippet { color: #888; font-size: .85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .crow-snippet { color: #888; font-size: .85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crow-body { padding: 6px 16px 12px; } .crow-body { padding: 6px 16px 12px; }
.crow-body .afield { margin-bottom: 8px; } .crow-body .afield { margin-bottom: 8px; }
.cstyles { display: flex; gap: 14px; flex-wrap: wrap; margin: 0 0 8px; } .cstyles { display: flex; gap: 14px; flex-wrap: wrap; margin: 0 0 8px; }
.cstyles label { font-size: .85rem; color: #555; } .cstyles label { font-size: .82rem; color: var(--c-text-light); }
.cstyles select { margin-left: 4px; padding: 4px; font: inherit; } .cstyles select { margin-left: 4px; padding: 4px; font: inherit; }
.cactions { margin: 0 0 4px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } .cactions { margin: 0 0 4px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.cimg-preview img { max-width: 240px; max-height: 140px; display: block; border: 1px solid #ddd; } .cimg-preview img { max-width: 240px; max-height: 140px; display: block; border: 1px solid #e4dfd7; }
.cmeta { color: #999; font-size: .78rem; margin: 4px 0 0; } .cmeta { color: var(--c-text-light); font-size: .76rem; margin: 4px 0 0; }
.crow--hl { outline: 2px solid #b5a48b; } .crow--hl { outline: 2px solid var(--c-accent); }
</style> </style>
<script> <script>
+2 -2
View File
@@ -22,8 +22,8 @@ if (Astro.request.method === 'POST') {
--- ---
<Admin title="Login"> <Admin title="Login">
<h1>Accesso</h1> <h1>Accesso</h1>
{error && <p style="color:#a33">{error}</p>} {error && <p class="form-msg form-msg--err">{error}</p>}
<form method="post" style="max-width:360px"> <form class="aform" method="post">
<input class="afield" name="username" placeholder="Utente" required autocomplete="username" /> <input class="afield" name="username" placeholder="Utente" required autocomplete="username" />
<input class="afield" type="password" name="password" placeholder="Password" required autocomplete="current-password" /> <input class="afield" type="password" name="password" placeholder="Password" required autocomplete="current-password" />
<button class="abtn" type="submit">Entra</button> <button class="abtn" type="submit">Entra</button>