diff --git a/README.md b/README.md index c50c988..4f9d0e5 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ You can also build and run Sucker as a Docker container with `docker compose bui ### Changelog +- 2.4: CSS editor for Janitor profiles. New banner links to the live-preview editor at tools.from-avalon.com/janitor-profile-maker. - 2.3: ECHO. Put ECHO: anywhere in a message and sucker repeats whatever comes after it in the reply — for triggering char-side script and lorebook keywords. - 2.2: Added support for directly inputting webp file names or character image links when creating PNG character cards - 2.1: updated deps, note about image fetching, list of mirrors diff --git a/public/avalon/css-banner.mp4 b/public/avalon/css-banner.mp4 new file mode 100644 index 0000000..a1dd559 Binary files /dev/null and b/public/avalon/css-banner.mp4 differ diff --git a/src/app/page.tsx b/src/app/page.tsx index 447e6d6..26c3c42 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -32,6 +32,8 @@ import { DropdownMenuItem, } from "@/components/ui/dropdown-menu"; import { DiscordBannerPermanent } from "@/components/ui/discord-banner-permanent"; +import { CssEditorBanner } from "@/components/ui/css-editor-banner"; +import { BannerCarousel } from "@/components/ui/banner-carousel"; import Script from "next/script"; interface CardDataV2 { @@ -371,7 +373,7 @@ export default function Home() { {/* Row 1: Title on left, Hamburger on right */}
-

Sucker v2.3

+

Sucker v2.4

ECHO: Consider joining Avalon!

@@ -409,7 +411,7 @@ export default function Home() { {/* Desktop Layout */}
-

Sucker v2.3

+

Sucker v2.4

ECHO: Consider joining Avalon!

@@ -447,12 +449,35 @@ export default function Home() { {/* Discord Banner - Disabled if NEXT_PUBLIC_DISABLE_DISCORD_BANNER is set */} {process.env.NEXT_PUBLIC_DISABLE_DISCORD_BANNER !== "true" && ( - + <> + {/* Desktop: banners side by side */} +
+ + +
+ + {/* Mobile: carousel */} + , + , + ]} + /> + )}
@@ -936,6 +961,33 @@ export default function Home() {
+
+

Sep 2026 - CSS Editor for Janitor profiles

+
+

+ We'd like to introduce a CSS editor we have been working + on for Janitor profiles. It comes loaded with some simple + presets, and we plan to include more in the future, be it + ours or provided by the community. You can see changes occur + in live time and it should accurately represent the + behaviour of the CSS if used in your Janitor profile. +

+

+ Check it out at{" "} + + tools.from-avalon.com/janitor-profile-maker + {" "} + and DM eslezer on Discord if you have any bug or suggestion + for the editor. +

+
+
+

Aug 2026 - ECHO

diff --git a/src/components/ui/banner-carousel.tsx b/src/components/ui/banner-carousel.tsx new file mode 100644 index 0000000..37a0a03 --- /dev/null +++ b/src/components/ui/banner-carousel.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useState, useEffect, ReactNode } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; + +interface BannerCarouselProps { + slides: ReactNode[]; + intervalMs?: number; + className?: string; +} + +export function BannerCarousel({ + slides, + intervalMs = 8000, + className = "", +}: BannerCarouselProps) { + const [index, setIndex] = useState(0); + + // Pick a random starting slide on the client to avoid hydration mismatch + useEffect(() => { + setIndex(Math.floor(Math.random() * slides.length)); + }, [slides.length]); + + // Re-keyed on index so a manual change restarts the countdown + useEffect(() => { + const id = setTimeout( + () => setIndex((i) => (i + 1) % slides.length), + intervalMs + ); + return () => clearTimeout(id); + }, [index, intervalMs, slides.length]); + + const go = (delta: number) => + setIndex((i) => (i + delta + slides.length) % slides.length); + + return ( +
+ {/* Slides stacked in one grid cell so the container fits the tallest */} +
+ {slides.map((slide, i) => ( +
+ {slide} +
+ ))} +
+ + + + +
+ {slides.map((_, i) => ( +
+
+ ); +} diff --git a/src/components/ui/css-editor-banner.tsx b/src/components/ui/css-editor-banner.tsx new file mode 100644 index 0000000..cfa5c3f --- /dev/null +++ b/src/components/ui/css-editor-banner.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +interface CssEditorBannerProps { + href?: string; + className?: string; + /** "contain" letterboxes the video so its caption is never cropped */ + fit?: "cover" | "contain"; +} + +export function CssEditorBanner({ + href = "https://tools.from-avalon.com/janitor-profile-maker/", + className = "", + fit = "cover", +}: CssEditorBannerProps) { + const videoRef = useRef(null); + const canvasRef = useRef(null); + + // Browsers like Opera GX overlay pop-out controls on large