FliesentischKingandDevin fe9e723c72 Media attachment cards with popover players and shared volume
- Audio/video attachments render as media cards (cover or icon,
  title/artist/size) instead of plain file chips; image attachments
  use 3:2 thumbnails matching the audio card height
- Players open in a Popover (autoplay on open) with a dimming
  overlay; AudioPlayerDialog/VideoPlayerDialog removed
- New useMediaVolume composable shares volume/mute between all
  audio players and Plyr via the 4brett-player storage key
- Player buttons and Plyr controls follow the theme border radius
- Portrait videos: aspect is applied even when metadata loaded
  before hydration, and the raw video is height-capped pre-JS

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-04 03:06:11 +02:00
2026-10-03 23:45:04 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 23:45:04 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 20:07:49 +02:00
2026-10-03 23:45:04 +02:00
2026-10-03 23:45:04 +02:00
2026-10-03 23:45:04 +02:00
2026-10-03 20:07:49 +02:00

BRETT

An imageboard-style upload and discussion platform built with Nuxt 4 and shadcn-vue. Users can upload files or URLs, comment in threads (including file attachments and voice messages), respond with Discord-style reactions, subscribe to threads, and pick from over 20 themes.

Screenshot of the upload grid

Features

  • Uploads: Drag & drop (page dropzone + upload popover), file or URL upload, max. 200 MB. Types: images (generated thumbnails), videos (embedded Plyr player), audio (MP3/FLAC with embedded cover art and music metadata via music-metadata, dedicated player dialog), YouTube and Twitch embeds, plus link preview cards
  • Threads & comments: Every upload is a thread with comments. Comments support file attachments and voice messages recorded in the browser (converted to MP3 via ffmpeg-static). Discord-style reactions on both comments and uploads (toggle, counters, tooltips, optimistic updates, burst animation), @mentions with autocomplete suggestions and profile hover cards — all with live WebSocket updates
  • Thread subscriptions & notifications: Creating, commenting, favoriting, or reacting to a thread automatically subscribes you (thread_subscriptions table). Can be toggled per thread via a bell button (a disabled subscription stays disabled even on later participation and also suppresses mentions in that thread). Notifications are grouped per thread (no spam from multiple comments), there are no self-notifications, and the bell shows live events plus toasts
  • Announcement (MOTD): Admin-editable slim bar below the header (MotdBanner.vue), publicly readable and updated live via WebSocket
  • Tags & filters: Uploads can be tagged; filter popover by type, uploader, time range, and tag (URL query–driven)
  • Command palette: Ctrl/Cmd + K opens a global search across uploads/users/actions
  • Favorites: Favorite uploads, visible on the profile page
  • Auth & roles: Session-cookie login (30 days); the first registered user automatically becomes admin. Admin panel with user list, role management, and per-role configurable permissions (admin.access, roles.manage, users.manage, site.manage, comment.delete, upload.delete, tag.delete) — the admin role is an implicit wildcard and can do everything; moderator is a system role with moderation rights
  • Profiles & personalization: Profile pages with own uploads, comments, and stats; user overview page. In settings: avatar (max. 5 MB), name color, card effects (WebGL shaders via ogl — Aurora, Particles, Plasma, Silk, Dither, Scanner and more as hover-card backgrounds), border effects, and vignette
  • Themes: 21 themes (Standard, Discord, Cyberpunk, Spring, Terminal, Infoseer, Homebrew, Florense, Kawi Green, Clo.nz, Clawmorphism, Realmorphism, Amber, Sunny Sprout, Spotify, Nordic Theme, SS, Amethyst Haze, Deep Wine, myClaude, Neo), switched via data-theme on <html>, persisted via cookie — SSR-safe without flash. The imported TweakCN themes are included as dark variants only
  • Layouts: Switchable Bento/uniform grid for the upload overview

Tech stack

Area Technology
Framework Nuxt 4 / Nitro, Vue 3 (<script setup> + TS)
UI shadcn-vue (shadcn-nuxt), Reka UI, Tailwind CSS v4, Phosphor/Simple Icons via @nuxt/icon (local SVG bundle mode), vue-sonner
Database MariaDB 11 + Drizzle ORM (mysql2)
Realtime Nitro WebSocket (crossws)
Media @nuxt/image, Plyr, music-metadata, ffmpeg-static, ogl (shader effects)
Data/Utils @tanstack/vue-table, Zod, Unovis charts, dnd-kit-vue
State Nuxt useState composables, VueUse

Project structure

app/
  assets/css/tailwind.css   # Tailwind v4 entry + all theme tokens ([data-theme="…"])
  components/               # App components (header, dropzone, tiles, comments,
                            #   MotdBanner, CommandPalette, ThreadSubscriptionButton, …)
    effects/                # WebGL card effects + vignette/border
    ui/                     # shadcn-vue base components
  composables/              # useAuth, useTheme, useCommandPalette, useCommentReactions,
                            #   useNotifications, useCardShader, …
  layouts/ middleware/ plugins/ pages/
    index.vue               # Upload grid with filters / homepage
    thread/[id].vue         # Thread detail page with comments, attachments, voice recording
    admin.vue               # Admin panel (users, roles, permissions, MOTD)
    settings.vue            # Profile settings (avatar, color, card effects)
    users.vue user/[id].vue login.vue register.vue
server/
  api/                      # Nitro endpoints (auth, uploads, comments, notifications,
                            #   users, tags, link-preview, motd, admin/…)
  db/schema.ts              # Drizzle schema (users, sessions, uploads, comments,
                            #   comment_attachments, comment_reactions,
                            #   upload_reactions, favorites, thread_subscriptions,
                            #   link_previews, notifications, roles, user_roles,
                            #   permissions, role_permissions, tags, upload_tags,
                            #   settings)
  db/migrations/            # Generated SQL migrations
  plugins/migrate.ts        # Runs migrations on server start (dev)
  routes/_ws.ts             # WebSocket route for live events (broadcastToAll)
  utils/                    # auth (requirePermission), db, roles, uploads,
                            #   notifications, threadSubscriptions, linkPreview
scripts/migrate.ts          # Manual migration runner (npm run db:migrate)
shared/utils/               # Shared utilities — incl. permissions.ts (catalog + seeding)
public/uploads/             # Upload target, incl. uploads/avatars/ (gitignored)
docker-compose.yml          # MariaDB container

Setup

Requirements: Node.js ≥ 20, Docker (for the bundled MariaDB) or your own MySQL/MariaDB instance.

# 1. Start the database
docker compose up -d

# 2. Configure environment
cp .env.example .env   # contains DATABASE_URL, matches the Docker setup

# 3. Install dependencies
npm install

# 4. Fetch the ffmpeg binary (needed for voice messages → MP3;
#    the download runs in ffmpeg-static's postinstall script, which may be
#    blocked depending on your npm configuration)
node node_modules/ffmpeg-static/install.js

# 5. Start the dev server (migrations + permission seeding run automatically)
npm run dev

The app runs on http://localhost:3000. The first registered account automatically gets the admin role and can then create additional roles, assign permissions, and set the announcement in the admin panel.

Without Docker

Provide your own MariaDB/MySQL instance and adjust DATABASE_URL in .env:

DATABASE_URL=mysql://user:pass@host:3306/database

NPM scripts

Command Purpose
npm run dev Dev server with HMR, auto-migrations
npm run build Production build
npm run preview Run the built app locally
npm run db:generate Generate a new Drizzle migration from a schema change
npm run db:migrate Apply migrations manually
npm run db:studio Drizzle Studio (DB GUI)

Development

  • Change the schema: Edit server/db/schema.ts → npm run db:generate → commit the migration. In dev mode, server/plugins/migrate.ts applies it automatically on start.
  • Add a new permission: Add an entry to PERMISSIONS in shared/utils/permissions.ts → seeded on next start via seedPermissions(); check server-side with requirePermission(event, key), client-side with can(key) from useAuth.
  • Live events: The server broadcasts via broadcastToAll in server/routes/_ws.ts; the client handles events in app/plugins/notifications.client.ts.
  • Add a new theme: Create a [data-theme="…"] block with the CSS variables in app/assets/css/tailwind.css, add the name to THEME_NAMES in app/composables/useTheme.ts, and add an entry in app/components/ThemeSelector.vue. Themes can also override the shadow-* tokens (glow, offset, or flat shadows) and font-sans/font-heading/font-mono; --font-logo intentionally stays theme-independent.
  • Conventions: Semantic tokens instead of hardcoded colors (primary, accent, muted, …); rounded-md/rounded-sm scale via --radius with the theme (neo is square: --radius: 0). More details in AGENTS.md.
  • Health check: GET /api/health reports the DB connection status.
S
Description
Ultimate Imageboard Software
Readme
6.2 MiB
Languages
Vue 66.8%
TypeScript 26.2%
CSS 7%