From e7e06ea875c96082af0aa26fb378bee94d1c02ec Mon Sep 17 00:00:00 2001 From: ramforth Date: Mon, 5 Jan 2026 20:42:17 +0100 Subject: [PATCH] Implement Style Editor, Preview, and Settings Schema --- package-lock.json | 23 ++- package.json | 4 +- src/app/editor/page.tsx | 21 +++ src/app/page.tsx | 72 +++------ src/components/dashboard/PreviewFrame.tsx | 77 ++++++++++ src/components/dashboard/StyleControls.tsx | 168 +++++++++++++++++++++ src/lib/types.ts | 33 ++++ src/lib/utils.ts | 75 +++++++++ 8 files changed, 418 insertions(+), 55 deletions(-) create mode 100644 src/app/editor/page.tsx create mode 100644 src/components/dashboard/PreviewFrame.tsx create mode 100644 src/components/dashboard/StyleControls.tsx create mode 100644 src/lib/types.ts create mode 100644 src/lib/utils.ts diff --git a/package-lock.json b/package-lock.json index 27e0e43..c57b655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "open-chat-overlay", "version": "0.1.0", "dependencies": { + "clsx": "^2.1.1", "next": "16.1.1", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -2587,6 +2589,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6029,6 +6040,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", diff --git a/package.json b/package.json index c330c5a..c770abe 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "lint": "eslint" }, "dependencies": { + "clsx": "^2.1.1", "next": "16.1.1", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx new file mode 100644 index 0000000..c9abf6f --- /dev/null +++ b/src/app/editor/page.tsx @@ -0,0 +1,21 @@ +"use client"; + +import React, { useState } from 'react'; +import StyleControls from '@/components/dashboard/StyleControls'; +import PreviewFrame from '@/components/dashboard/PreviewFrame'; +import { OverlaySettings, DEFAULT_SETTINGS } from '@/lib/types'; + +export default function EditorPage() { + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + + const updateSettings = (newSettings: Partial) => { + setSettings(prev => ({ ...prev, ...newSettings })); + }; + + return ( +
+ + +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 295f8fd..931958a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,65 +1,31 @@ -import Image from "next/image"; +import Link from 'next/link'; export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
+
+
+

OpenChat Overlay

+

+ A free, open-source alternative to StreamElements chat overlay. +

+ +
); -} +} \ No newline at end of file diff --git a/src/components/dashboard/PreviewFrame.tsx b/src/components/dashboard/PreviewFrame.tsx new file mode 100644 index 0000000..1133006 --- /dev/null +++ b/src/components/dashboard/PreviewFrame.tsx @@ -0,0 +1,77 @@ +"use client"; + +import React from 'react'; +import { OverlaySettings } from '@/lib/types'; + +interface PreviewFrameProps { + settings: OverlaySettings; +} + +const MOCK_MESSAGES = [ + { id: 1, user: 'StreamElements', color: '#5b99ff', text: 'Welcome to the stream! Enjoy your stay.', badges: ['moderator'] }, + { id: 2, user: 'Viewer123', color: '#ff5b5b', text: 'PogChamp this overlay is amazing!', badges: ['subscriber'] }, + { id: 3, user: 'Lurker', color: '#5bff89', text: 'Can I get a shoutout?', badges: [] }, + { id: 4, user: 'ProGamer', color: '#ffb35b', text: 'What game are we playing today? kappa', badges: ['subscriber', 'turbo'] }, +]; + +export default function PreviewFrame({ settings }: PreviewFrameProps) { + // Convert hex to rgb for background opacity + const r = parseInt(settings.backgroundColor.slice(1, 3), 16); + const g = parseInt(settings.backgroundColor.slice(3, 5), 16); + const b = parseInt(settings.backgroundColor.slice(5, 7), 16); + const rgbaBackground = `rgba(${r}, ${g}, ${b}, ${settings.backgroundOpacity})`; + + const containerStyle: React.CSSProperties = { + fontFamily: settings.fontFamily, + backgroundColor: 'transparent', // The frame background is transparent, messages have their own bg or the container has it? + // In many overlays, the messages themselves are transparent or the whole container has a background. + // Based on the CSS generator: .chat-message has the background. Wait, no. + // In generateOverlayCSS: + // .chat-message { background-color: var(--chat-bg-color); ... } + // So the background applies to individual messages. + }; + + const messageStyle: React.CSSProperties = { + backgroundColor: rgbaBackground, + color: settings.textColor, + textShadow: settings.textShadow, + fontSize: settings.fontSize, + marginBottom: settings.messageSpacing, + }; + + return ( +
+
+ {/* Mock Stream Background (Optional) */} +
+ +
+ {MOCK_MESSAGES.map((msg) => ( +
+
+ {settings.showBadges && msg.badges.length > 0 && ( + + {msg.badges.map(badge => ( + + ))} + + )} + + {msg.user}: + + {msg.text} +
+
+ ))} +
+
+
Live Preview
+
+ ); +} diff --git a/src/components/dashboard/StyleControls.tsx b/src/components/dashboard/StyleControls.tsx new file mode 100644 index 0000000..fac8964 --- /dev/null +++ b/src/components/dashboard/StyleControls.tsx @@ -0,0 +1,168 @@ +"use client"; + +import React from 'react'; +import { OverlaySettings } from '@/lib/types'; + +interface StyleControlsProps { + settings: OverlaySettings; + updateSettings: (newSettings: Partial) => void; +} + +export default function StyleControls({ settings, updateSettings }: StyleControlsProps) { + const handleChange = (e: React.ChangeEvent) => { + const { name, value, type } = e.target; + let newValue: string | number | boolean = value; + + if (type === 'range') { + newValue = parseFloat(value); + } else if (type === 'checkbox') { + newValue = (e.target as HTMLInputElement).checked; + } + + updateSettings({ [name]: newValue }); + }; + + const handleEmoteChange = (e: React.ChangeEvent) => { + const { name, checked } = e.target; + updateSettings({ + emotes: { + ...settings.emotes, + [name]: checked, + }, + }); + }; + + return ( +
+

Style Editor

+ + {/* Font Family */} +
+ + +
+ + {/* Font Size */} +
+ +
+ +
+
+ + {/* Text Color */} +
+ +
+ + +
+
+ + {/* Background Color */} +
+ +
+ + +
+
+ + {/* Background Opacity */} +
+ + +
+ + {/* Toggles */} +
+ + + + + +
+
+ ); +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..aea6b8f --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,33 @@ +export interface OverlaySettings { + fontFamily: string; + fontSize: string; + textColor: string; + backgroundColor: string; + backgroundOpacity: number; + showBadges: boolean; + textShadow: string; + messageSpacing: string; + usernameColor: string; // 'custom' or 'twitch' + emotes: { + bttv: boolean; + seventv: boolean; + ffz: boolean; + }; +} + +export const DEFAULT_SETTINGS: OverlaySettings = { + fontFamily: 'Inter, sans-serif', + fontSize: '16px', + textColor: '#ffffff', + backgroundColor: '#000000', + backgroundOpacity: 0.5, + showBadges: true, + textShadow: '1px 1px 2px rgba(0,0,0,0.8)', + messageSpacing: '0.5rem', + usernameColor: 'twitch', + emotes: { + bttv: true, + seventv: true, + ffz: true, + }, +}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..e5f20e1 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,75 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; +import { OverlaySettings } from "./types"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function generateOverlayCSS(settings: OverlaySettings): string { + // Convert backgroundOpacity (0-1) to hex alpha if needed, or use rgba + // Here we assume CSS Injection will be used as a