feat(calendar-compact): polish + global not-prose opt-out support
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m9s

CalendarCompact:
- denser layout (smaller padding/font/badge), header icon size-8
- icon stein-900 (black), subtle wald gradient background
- rounded-sm to match design tokens (was rounded-lg)
- list items link to linkTo + "#event-<slug>" anchor; <svelte:element>
  falls back to div when no linkTo is set
- not-prose on root so global content margins/list/link rules don't
  apply to the card subtree

blog-utils.resolveCalendarBlocks:
- when items array is empty, fall back to the full calendar_item pool
  (both default and compact variant get this) — saves editors from
  re-listing items on every page.

app.css: add `:where(:not(.not-prose *))` to .content p/h1-h4,
main a (link colour + underline), main ul/ol so subtrees marked
not-prose stop inheriting the global prose-like defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-22 09:16:21 +02:00
parent b7bcdd76ee
commit d781b3140e
4 changed files with 60 additions and 38 deletions
+17 -13
View File
@@ -304,13 +304,15 @@ h4 {
} }
} }
/* Content-Links: Himmel (Design System Links) */ /* Content-Links: Himmel (Design System Links).
main a:not(.no-underline):not([class*="btn"]) { * `:where(:not(.not-prose *))` schließt jeden Subtree mit `.not-prose`
* aus — Card-Links etc. behalten ihre eigene Farbe + kein Underline. */
main a:not(.no-underline):not([class*="btn"]):where(:not(.not-prose *)) {
color: var(--color-link); color: var(--color-link);
text-decoration: underline; text-decoration: underline;
} }
main a:not(.no-underline):not([class*="btn"]):hover { main a:not(.no-underline):not([class*="btn"]):where(:not(.not-prose *)):hover {
color: var(--color-himmel-600); color: var(--color-himmel-600);
} }
@@ -327,21 +329,21 @@ main strong {
font-weight: 600; font-weight: 600;
} }
main ul:not(.not-prose) { main ul:not(.not-prose):where(:not(.not-prose *)) {
margin-left: 1em; margin-left: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
main ul:not(.not-prose) li { main ul:not(.not-prose):where(:not(.not-prose *)) li {
list-style: disc; list-style: disc;
} }
main ol:not(.not-prose) { main ol:not(.not-prose):where(:not(.not-prose *)) {
margin-left: 1.1em; margin-left: 1.1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
main ol:not(.not-prose) li { main ol:not(.not-prose):where(:not(.not-prose *)) li {
list-style-type: decimal; list-style-type: decimal;
} }
@@ -473,15 +475,17 @@ article [data-block-type="markdown"] .markdown > p:first-child {
max-width: 50%; max-width: 50%;
} }
/* Content-Blöcke */ /* Content-Blöcke. `:where(:not(.not-prose *))` lässt jeden Subtree mit
.content p { * .not-prose komplett ausserhalb dieser Margin-Defaults — Components, die
* eigene Abstände setzen, müssen nur `not-prose` auf den Root packen. */
.content p:where(:not(.not-prose *)) {
@apply mb-4; @apply mb-4;
} }
.content h1, .content h1:where(:not(.not-prose *)),
.content h2, .content h2:where(:not(.not-prose *)),
.content h3, .content h3:where(:not(.not-prose *)),
.content h4 { .content h4:where(:not(.not-prose *)) {
@apply mb-2; @apply mb-2;
} }
+7
View File
@@ -593,6 +593,13 @@ export async function resolveCalendarBlocks(
resolve: "items", resolve: "items",
}); });
const raw = calendar?.items ?? rawItems; const raw = calendar?.items ?? rawItems;
// Leere items-Liste → alle calendar_items aus der Collection (Pool).
// Greift sowohl für default-Variante als auch compact-Anteaser.
if (raw.length === 0) {
const pool = (await getCalendarItems({ locale: "de" })) as unknown as CalendarItemData[];
(item as { items?: CalendarItemData[] }).items = pool;
continue;
}
const settled = await Promise.all( const settled = await Promise.all(
raw.map(async (x): Promise<CalendarItemData | null> => { raw.map(async (x): Promise<CalendarItemData | null> => {
if ( if (
@@ -62,45 +62,53 @@
</script> </script>
<div <div
class="{layoutClasses} w-full min-w-0 card-surface overflow-hidden rounded-lg border border-stein-200 bg-white" class="not-prose {layoutClasses} w-full min-w-0 card-surface overflow-hidden rounded-sm border border-stein-200 bg-gradient-to-br from-wald-50/60 via-white to-white"
data-block="CalendarCompact" data-block="CalendarCompact"
data-block-type="calendar" data-block-type="calendar"
data-block-variant="compact" data-block-variant="compact"
data-block-slug={block._slug} data-block-slug={block._slug}
> >
<header class="flex items-center gap-2 border-b border-stein-100 px-4 py-3 sm:px-5"> <header class="flex items-center gap-2 border-b border-stein-100 px-3 py-2">
<Icon icon="mdi:calendar-month-outline" class="size-5 text-wald-600" aria-hidden="true" /> <Icon icon="mdi:calendar-month-outline" class="size-8 shrink-0 text-stein-900" aria-hidden="true" />
<h2 class="text-base font-semibold text-stein-800"> <h2 class="text-sm font-semibold leading-none text-stein-800">
{block.title ?? t(T.calendar_next_events)} {block.title ?? t(T.calendar_next_events)}
</h2> </h2>
</header> </header>
{#if upcoming.length === 0} {#if upcoming.length === 0}
<p class="px-4 py-6 text-center text-sm text-stein-500 sm:px-5"> <p class="px-3 py-4 text-center text-xs text-stein-500">
{t(T.calendar_no_future_events)} {t(T.calendar_no_future_events)}
</p> </p>
{:else} {:else}
<ul class="divide-y divide-stein-100"> <ul class="divide-y divide-stein-100">
{#each upcoming as { it, start } (it._slug ?? it.title)} {#each upcoming as { it, start } (it._slug ?? it.title)}
{@const timeStr = fmtTime(start)} {@const timeStr = fmtTime(start)}
<li class="flex items-start gap-3 px-4 py-3 sm:px-5"> {@const base = linkHref()}
<div class="flex w-14 shrink-0 flex-col items-center rounded bg-wald-50 px-1 py-1.5 text-wald-700"> {@const itemHref = base && it._slug ? `${base}#event-${it._slug}` : null}
<span class="text-[0.65rem] font-semibold uppercase leading-none"> <li>
{start.toLocaleDateString("de-DE", { month: "short" }).replace(".", "")} <svelte:element
</span> this={itemHref ? "a" : "div"}
<span class="text-lg font-bold tabular-nums leading-none"> href={itemHref ?? undefined}
{start.getDate()} class="flex items-start gap-2.5 px-3 py-2 {itemHref ? 'transition-colors hover:bg-wald-50/40' : ''}"
</span> >
</div> <div class="flex w-11 shrink-0 flex-col items-center rounded-sm bg-wald-50 px-1 py-1 text-wald-700">
<div class="min-w-0 flex-1"> <span class="text-[0.6rem] font-semibold uppercase leading-none">
<p class="truncate text-sm font-semibold text-stein-800">{it.title ?? t(T.calendar_untitled)}</p> {start.toLocaleDateString("de-DE", { month: "short" }).replace(".", "")}
<p class="mt-0.5 text-xs text-stein-500"> </span>
{fmtDate(start)}{timeStr ? ` · ${timeStr}` : ""} <span class="text-base font-bold tabular-nums leading-none mt-0.5">
{#if it.location} {start.getDate()}
<span class="ml-1">· {typeof it.location === "string" ? it.location : (it.location as { text?: string })?.text ?? ""}</span> </span>
{/if} </div>
</p> <div class="min-w-0 flex-1">
</div> <p class="truncate text-xs font-semibold text-stein-800">{it.title ?? t(T.calendar_untitled)}</p>
<p class="mt-0.5 text-[0.7rem] text-stein-500">
{fmtDate(start)}{timeStr ? ` · ${timeStr}` : ""}
{#if it.location}
<span class="ml-1">· {typeof it.location === "string" ? it.location : (it.location as { text?: string })?.text ?? ""}</span>
{/if}
</p>
</div>
</svelte:element>
</li> </li>
{/each} {/each}
</ul> </ul>
@@ -109,10 +117,10 @@
{#if linkHref()} {#if linkHref()}
<a <a
href={linkHref()} href={linkHref()}
class="flex items-center justify-center gap-1 border-t border-stein-100 bg-stein-50 px-4 py-2.5 text-sm font-medium text-wald-700 hover:bg-wald-50" class="flex items-center justify-center gap-1 border-t border-stein-100 bg-stein-50 px-3 py-1.5 text-xs font-medium text-wald-700 hover:bg-wald-50"
> >
{t(T.calendar_all_upcoming)} {t(T.calendar_all_upcoming)}
<Icon icon="mdi:arrow-right" class="size-4" aria-hidden="true" /> <Icon icon="mdi:arrow-right" class="size-3.5" aria-hidden="true" />
</a> </a>
{/if} {/if}
</div> </div>
@@ -96,6 +96,9 @@
"calendar-plus": { "calendar-plus": {
"body": "<path fill=\"currentColor\" d=\"M19 19V8H5v11zM16 1h2v2h1a2 2 0 0 1 2 2v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8zm-5 8.5h2v3h3v2h-3v3h-2v-3H8v-2h3z\"/>" "body": "<path fill=\"currentColor\" d=\"M19 19V8H5v11zM16 1h2v2h1a2 2 0 0 1 2 2v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8zm-5 8.5h2v3h3v2h-3v3h-2v-3H8v-2h3z\"/>"
}, },
"calendar-month-outline": {
"body": "<path fill=\"currentColor\" d=\"M7 11h2v2H7zm14-6v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h1V1h2v2h8V1h2v2h1a2 2 0 0 1 2 2M5 7h14V5H5zm14 12V9H5v10zm-4-6v-2h2v2zm-4 0v-2h2v2zm-4 2h2v2H7zm8 2v-2h2v2zm-4 0v-2h2v2z\"/>"
},
"arrow-right": { "arrow-right": {
"body": "<path fill=\"currentColor\" d=\"M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11z\"/>" "body": "<path fill=\"currentColor\" d=\"M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11z\"/>"
}, },