Commit Graph

48 Commits

Author SHA1 Message Date
Peter Meier a2ca0db9ca feat(security+obs): SSRF allowlist, HTML sanitization, timing-safe tokens, fetch timeouts, structured logger
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m17s
Security:
  - cms-images / cms-files now share resolveCmsSource (cms-source.server.ts)
    which enforces an allowlist: asset filename, absolute path on the CMS
    host, or full URL exact-matching the configured CMS host+protocol.
    Foreign hosts, protocol-relative URLs and path traversal return 400.
  - markdown-safe.ts is the single configured marked entrypoint; it
    disables raw HTML rendering globally (renderer.html() returns ''),
    removing the inline script/iframe injection vector via markdown.
    All 8 marked imports across components and loaders now go through it.
  - sanitize-blocks.server.ts walks resolved page/post content and runs
    HtmlBlock html fields through sanitize-html with a tight allowlist
    (no script/style/on*, only youtube/vimeo/spotify iframes, mailto/tel
    plus http(s) schemes, target=_blank gets rel=noopener).
  - Webhook tokens (revalidate, cache/clear) compared via
    crypto.timingSafeEqual through auth-token.server.ts.
  - cms.ts wraps every fetch in cmsFetch(): 5 s default timeout via
    AbortSignal.timeout, override per call or globally via
    PUBLIC_CMS_FETCH_TIMEOUT_MS. Stops slow CMS responses from pinning
    SSR workers indefinitely.

Observability:
  - log.server.ts gives logWarn / logError / logInfo with scope + context;
    swallowed catch {} blocks in hooks, layout.server, +page.server,
    [...slug]/+page.server and sitemap now log instead of going silent.

Routing:
  - Legacy /blog, /blog/page/:n, /blog/tag/:t and /p/:slug stub routes
    deleted; their 301 redirects moved into hooks.server.ts so the
    request never enters the SvelteKit page pipeline.

Tests (vitest, server-side):
  - auth-token.server.test.ts (6 cases for constant-time compare)
  - cms-source.server.test.ts  (8 cases for SSRF allowlist)
  - sanitize-blocks.server.test.ts (9 cases for sanitizer + walker)
  All 23 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 16:09:25 +02:00
Peter Meier 1e9ef44b52 feat(header): highlight active nav link with bold + underline
Deploy / verify (push) Successful in 45s
Deploy / deploy (push) Successful in 1m9s
Compares the link href against `$page.url.pathname` (with trailing-slash
normalization + subpath match), applies `font-bold underline` and
`aria-current="page"` on a match. Wired into both the desktop horizontal
nav and the mobile slide-down menu.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 11:29:05 +02:00
Peter Meier 5cf141fa4a chore(blog-overview): apply prettier formatting
No behavior change — IDE auto-format pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 11:29:00 +02:00
Peter Meier 616fb66c00 fix(event-badges): suppress parent-link underline on hover
When EventBadges sits inside a `<a hover:underline>` (upcoming-events
list, post cards) the underline propagated through the badge spans on
hover. Force `text-decoration: none` on the date and location pills so
only the headline text underlines.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 10:07:56 +02:00
Peter Meier 4db2fd8562 fix(blog-overview): use EventBadges in upcoming-events list
The plain `<time>` was visually weak next to the headline. Reuse the
`EventBadges` component from the post card so the date renders as a
proper wald-tinted pill — same shape as event posts. Drops the local
`formatEventDate` since EventBadges does the localization itself
(weekday + time included, matching post cards).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 10:06:41 +02:00
Peter Meier dcce4fdea1 fix(blog-overview): upcoming-events block escapes prose ul-bullet rule
Deploy / verify (push) Successful in 46s
Deploy / deploy (push) Successful in 1m7s
`main ul:not(.not-prose) li` injects a disc bullet on every list under
`main`, which the upcoming-events list inherited. Mark the ul `not-prose`
+ `list-none` so the rule doesn't match. Also bump the calendar-clock
icon from `h-4 w-4` to `size-5` so it visually matches the heading
ascender.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 10:03:47 +02:00
Peter Meier 35f7aa8f90 feat(blocks): YoutubeVideoGalleryBlock (carousel + grid)
Deploy / verify (push) Successful in 2m19s
Deploy / deploy (push) Successful in 1m7s
New block for `youtube_video_gallery` CMS type. Carousel renders
2 iframes side-by-side on md+ (1 on mobile) with prev/next/dots; grid
shows YT thumbnails opening modal embeds. Description supports
markdown via marked; long URLs break with overflow-wrap:anywhere.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 08:31:38 +02:00
Peter Meier 3d343822e9 feat: print-friendly layout + comment count on every post overview
Deploy / verify (push) Successful in 42s
Deploy / deploy (push) Successful in 1m4s
- Hides Header / HeaderOverlay / Footer / TopBanner / Breadcrumbs and
  the comment accordion when printing. Action toolbar already had
  `print:hidden`.
- Adds @media print rules in app.css: 1.5cm page margins, 11pt body,
  forced black-on-white, expand link URLs after anchor text inside
  articles, keep images and headings together at page breaks.
- PostCard now renders the count pill whenever a value is known (incl.
  zero); previously hidden for `count === 0`, so posts whose comments
  are still pending always looked empty.
- Extracts the bulk-fetch helper to `lib/comment-counts.ts`
  (`fetchCommentCounts` + `postSlugForComments`) and uses it from both
  `BlogOverview` and `PostOverviewBlock`. Homepage / any page with a
  post_overview block now shows the badges too — previously only the
  paginated /posts/* routes did.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 15:29:15 +02:00
Peter Meier 6f14e4f150 style(post): polish post header + actions toolbar
Deploy / verify (push) Successful in 43s
Deploy / deploy (push) Successful in 1m4s
- PostActions: every item is a rounded-full pill with subtle border;
  the three icon-only actions (share/copy/print) cluster in one
  segmented control with divider lines, count + reading-time stand
  alone left and right
- Share button only renders when `navigator.share` is available so
  desktop doesn't get a duplicate of "Copy link"
- All action buttons drop their inline labels and use `title=` +
  `aria-label` for tooltip-only access; copy still flips icon to a
  checkmark for 1.5s on success
- Empty comment state stacks vertically on mobile (icon above), goes
  horizontal + left-aligned on ≥sm
- Post header rebuilt: image fixed at 150x150 (no more mobile blow-up),
  right column is `flex flex-col gap-3` so date/tags/actions share one
  rhythm instead of ad-hoc margins
- Reading-time now also walks `row*Content` and pulls `content`/`body`/
  `text` strings — Termin-Posts with empty body but markdown rows now
  show a meaningful estimate

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 15:08:04 +02:00
Peter Meier 6b2b7245f4 refactor: fold comment count into PostActions toolbar
CommentCountBadge was a separate component rendered next to PostActions
— two adjacent strips of buttons that should have been one toolbar.
Merges the count fetch + click-to-scroll behaviour into PostActions
behind `commentPageId` / `commentTargetId` props. The standalone
CommentCountBadge.svelte is now redundant; deleted.

Layout under the post hero is now a single row:
[💬 3]  [↗ Teilen]  [📋 Link]  [🖨️]  [⏱ 4 min]

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 14:52:30 +02:00
Peter Meier 3318fe9e17 feat(post): post actions toolbar (share / copy / print / reading time)
Adds a small toolbar next to the comment-count badge with four actions:

- Share — uses `navigator.share()` on supporting devices, falls back
  to copy-link otherwise
- Copy link — clipboard API with `<input>` + `execCommand` fallback,
  flashes a checkmark for 1.5s on success
- Print — `window.print()`, hidden via `print:hidden` so it doesn't
  show up in the printed output itself
- Reading time — server-computed from headline+body HTML at ~200 wpm,
  hidden when 0

Reading time + canonical URL come from `+page.server.ts` so SSR
output is identical and shareable URLs render correctly.

All labels go through the translations layer (5 new
`post_action_*` keys).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 14:42:16 +02:00
Peter Meier e4804ce673 i18n(comments): all UI strings through translations
Pulls every German string out of Comments.svelte, PostCard.svelte and
CommentCountBadge.svelte and routes it through `useTranslate()` +
the `T.*` key registry. New keys (`comments_*`, `post_overview` count
aria, `comments_jump_to`) are registered in `lib/translations.ts`.

Default DE values land in
`cms_content/windwiderstand/de/translation_bundle/app.json5` (committed
separately) so the CMS-loaded bundle keeps the same look without each
component falling back to its raw key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 14:34:48 +02:00
Peter Meier 8f791ebc54 feat(blog): comment count badge on post overview
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 59s
PostCard now shows a small comment-count pill in the top-left of the
image when count > 0. BlogOverview bulk-fetches counts for the visible
posts via the new `/api/comments/counts?ids=…` endpoint and passes
them down. One request per page render, regardless of card count.

- New helper `getCommentCounts()` in `cms.ts`
- `BlogOverview.svelte` derives slugs from posts, fires bulk fetch
  in `$effect`, hands the result map to `PostCard`
- `PostCard.svelte` accepts `commentCount?: number | null`, renders
  pill only when value is a positive number

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:26:09 +02:00
Peter Meier 8f45094f64 style(comments): move count badge under image, drop underline
Deploy / verify (push) Successful in 43s
Deploy / deploy (push) Successful in 1m3s
Repositions the CommentCountBadge out of the date row into its own
container below the post image. Removes hover colour-change + the
prose underline (`no-underline\!`) so the badge sits flat with the
post chrome.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:21:05 +02:00
Peter Meier 40b2d406ae fix(rusty-image): derive arValue from arString for layout match
`arValue` (height computation) and `arString` (URL `ar=` param) used
to read from different sources, so the URL could request a different
aspect than the `<img>` element reserved — caused a visible flicker
when the transformed image loaded. Both now derive from `arString`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:20:59 +02:00
Peter Meier 9d1a7ab46d feat(comments): polish UI + send client_age_ms
- Avatars with hash-derived hue, initials per author
- Relative dates (Intl.RelativeTimeFormat), absolute as title
- localStorage remembers author name for next visit
- Auto-resize textarea, char counter, Cmd+Enter submit
- Skeleton loading state, icon empty state, retry button on error
- Pending comments get amber card + status pill (visible only to author)
- Reply count in toggle ("3 Antworten")
- Edit/delete buttons fade in on hover (group-hover)
- DRY: meta + body rendering as snippets, used by top-level + replies
- Sends `client_age_ms` for backend's anti-instant-bot check

No content filtering — only timing / structure / re-use checks live in
the backend now (sha256 dedupe, session cooldown, banned IPs, author
char sanity).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:18:23 +02:00
Peter Meier 817f658977 fix(comments): inline repliesOf() — {@const} placement
Deploy / verify (push) Successful in 47s
Deploy / deploy (push) Successful in 1m3s
Svelte rejected `{@const replies = repliesOf(c.id)}` because it sat
inside `<li>` rather than directly under `{#each}`. The Gitea deploy
typecheck (`svelte-check --threshold error`) failed on this, leaving
the previous Cusdis bundle live. Inlining the call costs one extra
filter per render — fine here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 11:49:27 +02:00
Peter Meier febc027e6b feat: replace Cusdis with own comment plugin
Deploy / verify (push) Failing after 45s
Deploy / deploy (push) Has been skipped
Drops the Cusdis embed (postCommentSlot in pageConfig) in favour of the
new RustyCMS comment plugin. New Comments.svelte renders the thread,
form, replies and inline edit/delete via /api/comments. Count-badge near
the post date links to the accordion and uses /api/comments/:id/count.

Accordion gets a `lazy` mode so the comment iframe / fetch happens only
on first open (avoids the previous "empty on first render" issue with
hidden details).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 11:17:10 +02:00
Peter Meier 9b4191151c docs(images): correct misleading AVIF default in RustyImage / TopBanner
Deploy / verify (push) Successful in 41s
Deploy / deploy (push) Successful in 1m5s
The runtime default has been `["webp", "jpeg"]` for a while, but the doc
comments still referenced AVIF. Update them and add the rationale (server-side
AVIF encoding is 5-10× slower than WebP for marginal bandwidth gain).

No runtime change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 18:56:39 +02:00
Peter Meier 354d3f09d3 feat(youtube): playlist support + URL parsing in youtube_video block
Deploy / verify (push) Successful in 42s
Deploy / deploy (push) Successful in 1m5s
YoutubeVideoBlockData gains an optional `playlistId`. The block now
accepts either a video, a playlist, or both, and either field can be
filled with a raw ID *or* a full youtube.com / youtu.be URL — a
helper extracts the ?v= and ?list= parameters automatically.

Embed routing:
- video only           → /embed/<id>?rel=0
- playlist only        → /embed/videoseries?list=<plid>&rel=0
- video + playlist     → /embed/<id>?list=<plid>&rel=0
- block.params merged  via URLSearchParams

Also tweaks ImageGalleryBlock (grid variant): green padding/contain
fit, darker caption gradient, smaller left-aligned label that expands
on hover. Hides post_overview block when no posts and no intro text
(prevents a bare heading on /mediathek).
2026-04-25 13:15:11 +02:00
Peter Meier 942b9feefa feat(files-block): icons for tsv/json/xml/py/js/ts/rs/sh
Deploy / verify (push) Successful in 39s
Deploy / deploy (push) Successful in 59s
iconForExt now matches the backend's expanded ALLOWED_EXTENSIONS:
- tsv routes to the excel-box icon (alongside csv).
- json → mdi:code-json, xml/html → mdi:xml.
- py → mdi:language-python; js/ts/jsx/tsx → language-javascript;
  rs → language-rust; sh/bash → console.

Visitors who download a CMS-hosted dataset, source dump or archive
now see a meaningful icon instead of the generic file-outline
fallback.
2026-04-25 11:21:40 +02:00
Peter Meier 9eab404240 fix(post-overview): hide block entirely when no posts + no intro text
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 59s
The post_overview block kept rendering its <h3> headline + the
"all posts" footer link even when both `postsResolved` was empty
and `text` was unset, leaving a heading with nothing under it on
pages like /mediathek that include the block defensively.

Wrap the whole render in `{#if hasContent}` so empty overviews
disappear; the heading reappears as soon as posts or intro text
are present.
2026-04-25 10:54:05 +02:00
Peter Meier 998fba8904 feat(gallery): grid title, subtle download icon, full i18n
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 59s
- Render block.title as <h3> above the grid when set; the carousel
  variant is unchanged.
- Download CTA in the modal shrunk to a 9×9 icon-only button with
  text-white/80 hover-tint instead of the dark green link colour the
  app's `main a` rule was forcing in. Adds `no-underline` so the
  global underline doesn't sneak back in.
- All previously hardcoded German strings (Download, Schließen,
  modal aria, open aria) now go through translations.ts → CMS
  translation_bundle. Cms_content app.json5 ships the matching keys.

Open follow-up: per-image title/alt/caption is empty on the existing
windwiderstand mediathek gallery → editor needs to fill those if
hover labels are wanted.
2026-04-25 10:44:30 +02:00
Peter Meier 1154be3c25 fix(gallery): use not-prose on grid <ul> to escape Typography styles
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 1m1s
The Tailwind Typography (`prose prose-zinc`) wrapper applied higher in
the page hierarchy injects `.prose ul` rules — list-style: disc, an
indent, and ::marker color — at a higher specificity than single-class
utilities, so list-none/p-0/m-0 silently lost.

`not-prose` is the official escape hatch from the typography plugin
and zaps every prose rule on the element plus its descendants. One
class instead of three \!important overrides.
2026-04-25 10:34:06 +02:00
Peter Meier 6246d7c3a4 feat(gallery): grid variant with hover labels + download modal
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 1m1s
ImageGalleryBlock now branches on `block.variant`:

- "standard" (default) → existing carousel.
- "grid" → 2/3/4-column thumbnail grid (1:1 squares, w=400/q=75
  through /cms-images). Each tile scales 5% on hover and surfaces
  the image's title/alt/caption/description as a gradient overlay
  caption that fades in on hover and keyboard focus.

Click any tile → fullscreen lightbox (z-100 overlay + 90vh image).
The lightbox carries:
- A download button using the asset's filename (extracted from the
  src URL).
- Prev/Next arrows when there's more than one image (keyboard:
  ←/→), Esc to close, click on the backdrop closes too.
- Position counter (n/total) when applicable.

Type updates in block-types.ts: ImageGalleryBlockData gains
`variant?: "standard"|"grid"` and `title?`. ImageGalleryImage gains
`alt`/`caption`/`title` so the schema's `image` field shape is
honoured for the hover label.
2026-04-25 10:32:37 +02:00
Peter Meier ecff7b0bbf feat(images): cache-bust /cms-images URLs by post._updated
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 1m1s
Browser caches /cms-images responses immutably for a year. When the
editor changes a focal point or alt text, the underlying transform
output changes too, but the URL stays the same — so the browser keeps
serving the previous bytes. Manual hard-reloads and /api/cache/clear
help once but not for visitors who already have the file.

- RustyImageTransformParams gains an optional `version` that
  serializes as `_v` in the /cms-images URL.
- RustyImage.svelte takes a matching `version` prop and forwards it
  through `baseParams`, so every <picture> source carries the tag.
- The post thumbnail passes `post._updated` (truncated to seconds)
  as version. Each save bumps `_updated`, the URL changes, browsers
  re-fetch.

`_v` is intentionally not consumed by `/cms-images` — its sole job is
to break the browser cache key. The server-side image-cache and the
RustyCMS transform cache key both ignore it, so disk usage doesn't
balloon when the version tag changes.
2026-04-25 09:43:34 +02:00
Peter Meier 9216138a8c style(FilesBlock): enhance list styling for improved presentation
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 1m0s
- Updated the list styling in FilesBlock component to remove default list styles and improve layout consistency.
- Adjusted list item classes for better alignment and visual clarity, enhancing the overall user experience.
2026-04-22 10:51:39 +02:00
Peter Meier 5b5ec7c659 feat: add FilesBlock interface and component integration
Deploy / verify (push) Successful in 46s
Deploy / deploy (push) Successful in 1m8s
- Introduced FilesBlockItem and FilesBlockData interfaces to support file handling in the CMS.
- Integrated FilesBlock component into ContentRows.svelte for rendering file blocks.
- Updated block type handling to include support for files, enhancing content versatility.
2026-04-22 10:23:20 +02:00
Peter Meier bedfe597ab style: update typography and enhance OrganisationsBlock layout
Deploy / verify (push) Successful in 41s
Deploy / deploy (push) Successful in 1m0s
- Changed comment in app.css to reflect German terminology for quotes.
- Added Button component to OrganisationsBlock for improved call-to-action presentation.
- Adjusted button styling and layout in OrganisationsBlock for better responsiveness and visual consistency.
2026-04-21 13:39:57 +02:00
Peter Meier 47cca3abc6 chore: update package.json and yarn.lock for dependency management
Deploy / verify (push) Successful in 48s
Deploy / deploy (push) Successful in 1m15s
- Added `packageManager` field to package.json for Yarn versioning.
- Updated yarn.lock to include new dependencies for `@emnapi` and `@esbuild` packages, enhancing compatibility and performance.
- Adjusted styling in TopBanner.svelte for improved layout and responsiveness.
2026-04-21 13:16:55 +02:00
Peter Meier d327798ce6 feat: add class-variance-authority for card styling and enhance color palettes
Deploy / verify (push) Failing after 1m40s
Deploy / deploy (push) Has been skipped
- Introduced `class-variance-authority` to manage card component styles, allowing for flexible variant and layout options.
- Added a new color palette for "Fire" to the CSS for improved theming.
- Updated `Card` and `PostCard` components to utilize the new styling system, enhancing visual consistency and responsiveness.
- Refined layout and styling in various components for better user experience.
2026-04-20 22:09:43 +02:00
Peter Meier 2d0a6a7747 style(PostCard): update headline styling for improved readability
Deploy / verify (push) Successful in 39s
Deploy / deploy (push) Failing after 1m10s
- Changed headline class from `line-clamp-2` to `truncate` and adjusted font size to `text-sm`, enhancing the visual presentation and readability of post headlines.
2026-04-19 14:29:45 +02:00
Peter Meier 4ec2aa10ec fix(cms): adjust caching behavior for development environment
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Failing after 57s
- Modified cached function to bypass caching in development mode.
- Updated fetchOpenApi function to return cached data only if not in development mode, ensuring accurate API fetching during development.
2026-04-19 14:09:26 +02:00
Peter Meier 1d75610cd3 feat(organisations-block): enhance OrganisationsBlock with carousel presentation option and improved layout
Deploy / verify (push) Successful in 37s
Deploy / deploy (push) Successful in 58s
- Added "carousel" option to the presentation property in OrganisationsBlockData for horizontal slider functionality.
- Updated OrganisationsBlock.svelte to support carousel layout with smooth scrolling feature.
- Refined Badge component styling for better visual consistency.
- Improved overall structure and responsiveness of the organisations display.
2026-04-19 13:13:12 +02:00
Peter Meier 5f3a60003b fix(cms): add _resolve parameter to getCalendarItems for comprehensive data retrieval
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 56s
- Updated getCalendarItems function to include the _resolve parameter set to "all", ensuring all related data is fetched.
- Refined getHref function in DeadlineBannerBlock.svelte to simplify link handling by directly checking for the url property.
2026-04-19 12:49:51 +02:00
Peter Meier da57ad1ac8 fix: update styling and structure in various components
Deploy / verify (push) Successful in 43s
Deploy / deploy (push) Successful in 59s
- Changed margin class from `my-6` to `mb-6` in ContentRows.svelte for consistent spacing.
- Updated PostCard.svelte to use `$derived` for post properties, enhancing reactivity.
- Refined DeadlineBannerBlock.svelte by removing unnecessary border classes and improving layout for better content alignment.
- Modified Button.svelte to support rendering children directly, enhancing flexibility.
- Added `tabindex="0"` to TabBar.svelte for improved accessibility.
- Fixed textarea closing tag in Textarea.svelte for proper HTML structure.
- Corrected span closing tag in Toggle.svelte for valid markup.
2026-04-19 12:42:37 +02:00
Peter Meier f02613054a fix(deadline-banner): annotate map return type for ts strict
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 59s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:14:42 +02:00
Peter Meier ad6fce493d fix(deadline-banner): true full-width in breakout mode
Deploy / verify (push) Successful in 37s
Deploy / deploy (push) Successful in 57s
Skip container-custom py-8 wrapper for deadline_banner so the colored
bar spans the viewport. Banner ships its own inner container-custom
for content positioning.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:07:19 +02:00
Peter Meier 1e51e6c200 fix(deadline-banner): left-align content
Deploy / verify (push) Successful in 37s
Deploy / deploy (push) Successful in 55s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:05:36 +02:00
Peter Meier 7994d5e8d5 fix(deadline-banner): center content; drop ml-auto on link
Deploy / verify (push) Successful in 37s
Deploy / deploy (push) Successful in 56s
ml-auto pushed the "Mehr" link to the right edge which cancelled
justify-center for the rest of the content. Remove it so the whole
row is centered when a link is present.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:05:03 +02:00
Peter Meier 62d25202a6 feat(deadline-banner): auto mode pulls all calendar_items globally
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 59s
Auto-mode deadline_banner now resolves to the next future event across
all calendar_items in the CMS instead of requiring a manually-curated
items[] pool. Server load hooks (+page.server.ts for /, /[...slug],
/post/[slug]) call resolveDeadlineBannerBlocks which fetches the
global calendar_item list once and attaches it as items[] when mode
is "auto" and items[] is not already resolved. Component logic
unchanged — it still picks the soonest future entry.

Also:
- cms.ts: getCalendarItems() bulk list fetcher
- PostOverviewBlock.svelte: design defaults to "cards" when value is
  empty string (not just null/undefined) so existing entries with
  design: "" render

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 11:02:50 +02:00
Peter Meier ffd4f599cd feat(quote): enhance QuoteBlock and QuoteCarouselBlock components
Deploy / verify (push) Successful in 44s
Deploy / deploy (push) Successful in 1m3s
- Updated QuoteBlock to support a new "center" variant for text alignment.
- Modified QuoteCarouselBlock to include an option for displaying navigation arrows.
- Enhanced type definitions for quote-related data structures to accommodate new features.
- Improved styling and layout handling for better visual presentation of quotes.
2026-04-19 09:31:30 +02:00
Peter Meier 597b920e7c feat(quote-carousel): add QuoteCarouselBlock component and related types
Deploy / verify (push) Successful in 41s
Deploy / deploy (push) Successful in 1m1s
- Introduced a new QuoteCarouselBlock component for displaying rotating quotes.
- Added QuoteCarouselBlockData interface to define the structure of the quote carousel data.
- Updated existing components and types to integrate the new quote carousel functionality.
- Enhanced blog-utils to support filtering posts by tags more effectively.
- Made various UI improvements in the Footer and PostCard components for better user experience.
2026-04-18 13:19:59 +02:00
Peter Meier da294010c8 feat: update site URLs and enhance image handling
Deploy / verify (push) Successful in 50s
Deploy / deploy (push) Failing after 1m3s
- Changed site URLs in docker-compose and deployment configurations to remove "www" prefix for consistency.
- Improved image handling by introducing warmPostCardImages function for pre-fetching images in posts and tags.
- Enhanced PostCard component with responsive image sizes for better performance.
- Updated layout to include robots meta tag and JSON-LD structured data for improved SEO.
- Added loading state for images in TopBanner component to enhance user experience.
2026-04-18 12:25:45 +02:00
Peter Meier a2f2d5bfb0 feat(image): enhance image handling with new properties and responsive support
Deploy / verify (push) Successful in 41s
Deploy / deploy (push) Successful in 1m2s
- Updated PostEntry type to include _rawImageUrl and _imageFocal for improved image management.
- Refactored image handling in various components to utilize the new properties, ensuring better focal point support and responsive image rendering.
- Introduced warmResponsiveImage function for pre-fetching image variants to optimize loading performance.
- Replaced CmsImage component with RustyImage in multiple blocks for consistent image processing.
2026-04-18 08:43:17 +02:00
Peter Meier a139372ce2 feat(image): enhance image handling with focal points and responsive support
Deploy / verify (push) Failing after 14s
Deploy / deploy (push) Has been skipped
- Added support for extracting focal points and alt text from CMS image fields.
- Introduced responsive image handling in TopBanner component, allowing for AVIF/WebP sources and LQIP placeholders.
- Updated image transformation functions to accommodate focal point parameters for better cropping.
- Refactored image URL resolution logic across various components and routes to utilize new image field structure.
2026-04-18 00:42:33 +02:00
Peter Meier f328989aa8 seo: enforce trailing slash on all URLs
Deploy / verify (push) Successful in 37s
Deploy / deploy (push) Successful in 55s
- Set trailingSlash='always' globally via root layout.
- Update postHref, pageHref, Pagination, tag/breadcrumb hrefs, blog→posts
  redirects to emit trailing slash.
- Match old Astro canonical so indexed URLs hit 200 directly instead of
  308-redirect.
2026-04-17 22:23:16 +02:00
Peter Meier 2fef91a548 Initial SvelteKit frontend port of windwiderstand.de
Deploy / verify (push) Failing after 32s
Deploy / deploy (push) Has been skipped
Full parity with Astro site: content rows, post/tag routes, pagination,
event badges + OSM map, comments, Live-Search via /api/search-index,
CMS image proxy, RSS, sitemap.

Deploy: Dockerfile + docker-compose.prod.yml + Gitea Actions workflow
to build + push to git.pm86.de registry and ssh-deploy to Contabo.
2026-04-17 22:01:30 +02:00