cd9081823d
New Druck tab: upload or pick from the library, choose a target size and copy count per image, pan/zoom crop on a locked aspect ratio, and a live preview that renders the same layout the PDF will contain. Sheet controls for paper, margin, gap, bleed, crop marks, dpi and templates.
126 lines
6.3 KiB
Plaintext
126 lines
6.3 KiB
Plaintext
---
|
|
import '@fontsource/space-grotesk/400.css';
|
|
import '@fontsource/space-grotesk/500.css';
|
|
import '@fontsource/space-grotesk/700.css';
|
|
import '@fontsource/space-mono/400.css';
|
|
import '@fontsource/space-mono/700.css';
|
|
import '../styles/tokens.css';
|
|
interface Props { title?: string }
|
|
const { title = 'Klarbild' } = Astro.props;
|
|
const user = Astro.locals.user;
|
|
const path = Astro.url.pathname;
|
|
const active = (href: string) => href === '/' ? path === '/' : path.startsWith(href);
|
|
|
|
const nav = [
|
|
{ href: '/', label: 'Studio', icon: 'studio' },
|
|
{ href: '/bibliothek', label: 'Bibliothek', icon: 'library' },
|
|
{ href: '/druck', label: 'Druck', icon: 'print' },
|
|
{ href: '/warteschlange', label: 'Warteschlange', icon: 'queue' },
|
|
{ href: '/anleitung', label: 'Hilfe', icon: 'help' },
|
|
...(user?.role === 'admin' ? [{ href: '/admin', label: 'Admin', icon: 'admin' }] : []),
|
|
];
|
|
|
|
const S = 'fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"';
|
|
const ICONS: Record<string, string> = {
|
|
studio: `<svg viewBox="0 0 24 24" ${S}><rect x="3.5" y="3.5" width="17" height="17" rx="4"/><path d="M12 8.5v7M8.5 12h7"/></svg>`,
|
|
library: `<svg viewBox="0 0 24 24" ${S}><rect x="3.5" y="3.5" width="7" height="7" rx="1.5"/><rect x="13.5" y="3.5" width="7" height="7" rx="1.5"/><rect x="3.5" y="13.5" width="7" height="7" rx="1.5"/><rect x="13.5" y="13.5" width="7" height="7" rx="1.5"/></svg>`,
|
|
queue: `<svg viewBox="0 0 24 24" ${S}><path d="M9 6h11M9 12h11M9 18h7"/><circle cx="4.5" cy="6" r="1.15" fill="currentColor" stroke="none"/><circle cx="4.5" cy="12" r="1.15" fill="currentColor" stroke="none"/><circle cx="4.5" cy="18" r="1.15" fill="currentColor" stroke="none"/></svg>`,
|
|
admin: `<svg viewBox="0 0 24 24" ${S}><path d="M4 8h9M17 8h3M4 16h3M11 16h9"/><circle cx="15" cy="8" r="2.2"/><circle cx="9" cy="16" r="2.2"/></svg>`,
|
|
print: `<svg viewBox="0 0 24 24" ${S}><path d="M7 9V4.5h10V9"/><rect x="3.5" y="9" width="17" height="7" rx="2"/><path d="M7 14h10v5.5H7z"/></svg>`,
|
|
help: `<svg viewBox="0 0 24 24" ${S}><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.4a2.5 2.5 0 1 1 3.4 2.3c-.7.3-1 .8-1 1.6v.3"/><circle cx="12" cy="16.7" r="0.6" fill="currentColor" stroke="none"/></svg>`,
|
|
};
|
|
---
|
|
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#EAEAE3" />
|
|
<title>{title}</title>
|
|
</head>
|
|
<body>
|
|
{user && (
|
|
<header class="topbar">
|
|
<a href="/" class="brand"><span class="regmark"></span>klarbild</a>
|
|
<nav class="topnav">
|
|
{nav.map((n) => (
|
|
<a href={n.href} class:list={['tnav', { on: active(n.href) }]}>{n.label}</a>
|
|
))}
|
|
</nav>
|
|
<form method="post" action="/api/auth/logout" class="logout">
|
|
<span class="uname">{user.name}</span>
|
|
<button type="submit" aria-label="Abmelden">Abmelden</button>
|
|
</form>
|
|
</header>
|
|
)}
|
|
|
|
<main class="wrap"><slot /></main>
|
|
|
|
{user && (
|
|
<nav class="tabbar" aria-label="Hauptnavigation">
|
|
{nav.map((n) => (
|
|
<a href={n.href} class:list={['tab', { on: active(n.href) }]}>
|
|
<span class="ticon" set:html={ICONS[n.icon]} />
|
|
<span class="tlabel">{n.label}</span>
|
|
</a>
|
|
))}
|
|
</nav>
|
|
)}
|
|
|
|
<style is:global>
|
|
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 20px 16px calc(88px + env(safe-area-inset-bottom)); }
|
|
@media (min-width: 768px) { .wrap { padding: 26px 22px 80px; } }
|
|
|
|
/* Top bar */
|
|
.topbar {
|
|
display: flex; align-items: center; gap: 14px;
|
|
padding: 10px 16px; border-bottom: 1px solid var(--line);
|
|
background: color-mix(in oklab, var(--card) 88%, transparent);
|
|
backdrop-filter: saturate(1.2) blur(8px);
|
|
position: sticky; top: 0; z-index: 20;
|
|
padding-top: calc(10px + env(safe-area-inset-top));
|
|
}
|
|
.brand { display: inline-flex; align-items: center; gap: 8px;
|
|
font-family: var(--font-display); font-weight: 700; font-size: 1.2rem;
|
|
color: var(--ink); text-decoration: none; letter-spacing: -0.02em; }
|
|
.brand .regmark { transform: translateY(1px); }
|
|
.topnav { display: none; }
|
|
@media (min-width: 768px) {
|
|
.topnav { display: flex; gap: 4px; margin-left: 8px; }
|
|
.tnav { padding: 7px 12px; border-radius: 999px; color: var(--ink-soft);
|
|
text-decoration: none; font-size: 0.92rem; transition: background .15s, color .15s; }
|
|
.tnav:hover { color: var(--ink); background: var(--surface-2, var(--paper)); }
|
|
.tnav.on { color: var(--accent); background: var(--accent-bg); font-weight: 600; }
|
|
}
|
|
.logout { margin-left: auto; display: flex; align-items: center; gap: 10px; }
|
|
.uname { color: var(--ink-mute, var(--soft)); font-size: 0.9rem; display: none; }
|
|
@media (min-width: 480px) { .uname { display: inline; } }
|
|
.logout button { background: none; border: 1px solid var(--line); border-radius: 999px;
|
|
padding: 7px 14px; color: var(--ink-soft); cursor: pointer; font-family: inherit; font-size: 0.85rem;
|
|
transition: transform .06s ease, background .15s; min-height: 36px; }
|
|
.logout button:active { transform: scale(0.96); }
|
|
|
|
/* Bottom tab bar (mobile-first) */
|
|
.tabbar {
|
|
position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
|
|
display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
|
|
background: color-mix(in oklab, var(--card) 92%, transparent);
|
|
backdrop-filter: saturate(1.3) blur(12px);
|
|
border-top: 1px solid var(--line);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
}
|
|
.tab { display: flex; flex-direction: column; align-items: center; gap: 3px;
|
|
padding: 9px 4px 8px; text-decoration: none; color: var(--ink-mute, var(--soft));
|
|
font-size: 10.5px; letter-spacing: .01em; transition: color .15s, transform .06s; }
|
|
.tab:active { transform: scale(0.94); }
|
|
.tab.on { color: var(--accent); }
|
|
.tab .ticon { width: 22px; height: 22px; display: grid; place-items: center; }
|
|
.tab .ticon svg { width: 22px; height: 22px; }
|
|
.tlabel { line-height: 1; }
|
|
@media (min-width: 768px) { .tabbar { display: none; } }
|
|
|
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
</style>
|
|
</body>
|
|
</html>
|