Consolidate CSS architecture and eliminate repetition
All checks were successful
Lint and Build / build (pull_request) Successful in 2m43s

- Add CSS custom properties for commonly used gradients
- Consolidate duplicate button variants (.btn.active and .btn-success)
- Replace inline gradient styles with semantic CSS classes
- Standardize spacing with utility classes (mr-1, mr-2, mr-4, ml-3)
- Remove unused Home.module.css file
- Replace hard-coded colors with Solarized CSS variables
- Add scene-specific button classes (btn-scene-preview, btn-scene-transition)

Reduces CSS duplication, improves maintainability, and ensures consistent
styling throughout the application with reusable utility classes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Decobus 2025-07-25 21:39:49 -04:00
parent 3bad71cb26
commit 4f9e6d2097
5 changed files with 77 additions and 81 deletions

View file

@ -20,6 +20,14 @@
--solarized-red: #dc322f; --solarized-red: #dc322f;
--solarized-magenta: #d33682; --solarized-magenta: #d33682;
--solarized-violet: #6c71c4; --solarized-violet: #6c71c4;
/* Gradient Custom Properties */
--gradient-primary: linear-gradient(135deg, var(--solarized-blue), var(--solarized-cyan));
--gradient-active: linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow));
--gradient-danger: linear-gradient(135deg, var(--solarized-red), var(--solarized-orange));
--gradient-preview: linear-gradient(135deg, var(--solarized-yellow), var(--solarized-orange));
--gradient-transition: linear-gradient(135deg, var(--solarized-red), var(--solarized-magenta));
--gradient-body: linear-gradient(135deg, #002b36 0%, #073642 50%, #002b36 100%);
} }
/* Modern CSS Foundation */ /* Modern CSS Foundation */
@ -34,8 +42,8 @@ html {
} }
body { body {
background: linear-gradient(135deg, #002b36 0%, #073642 50%, #002b36 100%); background: var(--gradient-body);
color: #93a1a1; color: var(--solarized-base1);
min-height: 100vh; min-height: 100vh;
line-height: 1.6; line-height: 1.6;
} }
@ -75,8 +83,8 @@ body {
/* Modern Button System */ /* Modern Button System */
.btn { .btn {
background: linear-gradient(135deg, #268bd2, #2aa198); background: var(--gradient-primary);
color: #fdf6e3; color: var(--solarized-base3);
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
border-radius: 12px; border-radius: 12px;
@ -93,9 +101,10 @@ body {
text-decoration: none; text-decoration: none;
} }
.btn.active { .btn.active,
background: linear-gradient(135deg, #859900, #b58900); .btn-success {
color: #fdf6e3; background: var(--gradient-active);
color: var(--solarized-base3);
box-shadow: 0 0 0 3px rgba(133, 153, 0, 0.5); box-shadow: 0 0 0 3px rgba(133, 153, 0, 0.5);
transform: translateY(-1px); transform: translateY(-1px);
font-weight: 700; font-weight: 700;
@ -117,7 +126,7 @@ body {
.btn-secondary { .btn-secondary {
background: rgba(88, 110, 117, 0.3); background: rgba(88, 110, 117, 0.3);
border: 1px solid rgba(131, 148, 150, 0.4); border: 1px solid rgba(131, 148, 150, 0.4);
color: #93a1a1; color: var(--solarized-base1);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
@ -127,25 +136,14 @@ body {
box-shadow: 0 6px 20px rgba(88, 110, 117, 0.3); box-shadow: 0 6px 20px rgba(88, 110, 117, 0.3);
} }
/* Success Button */
.btn-success {
background: linear-gradient(135deg, #859900, #b58900);
color: #fdf6e3;
}
.btn-success:hover {
background: linear-gradient(135deg, #b58900, #859900);
box-shadow: 0 6px 20px rgba(133, 153, 0, 0.4);
}
/* Danger Button */ /* Danger Button */
.btn-danger { .btn-danger {
background: linear-gradient(135deg, #dc322f, #cb4b16); background: var(--gradient-danger);
color: #fdf6e3; color: var(--solarized-base3);
} }
.btn-danger:hover { .btn-danger:hover {
background: linear-gradient(135deg, #cb4b16, #dc322f); background: linear-gradient(135deg, var(--solarized-orange), var(--solarized-red));
box-shadow: 0 6px 20px rgba(220, 50, 47, 0.4); box-shadow: 0 6px 20px rgba(220, 50, 47, 0.4);
} }
@ -169,6 +167,18 @@ body {
flex-shrink: 0; flex-shrink: 0;
} }
/* Scene Button Variants */
.btn-scene-preview {
background: var(--gradient-preview);
color: var(--solarized-base3);
}
.btn-scene-transition {
background: var(--gradient-transition);
color: var(--solarized-base3);
min-width: 120px;
}
/* Form spacing fixes since Tailwind gap classes aren't working */ /* Form spacing fixes since Tailwind gap classes aren't working */
.form-row { .form-row {
display: flex; display: flex;
@ -194,7 +204,7 @@ body {
border: 1px solid rgba(88, 110, 117, 0.4); border: 1px solid rgba(88, 110, 117, 0.4);
border-radius: 12px; border-radius: 12px;
padding: 12px 16px; padding: 12px 16px;
color: #93a1a1; color: var(--solarized-base1);
width: 100%; width: 100%;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -205,7 +215,7 @@ body {
.input:focus { .input:focus {
outline: none; outline: none;
border-color: #268bd2; border-color: var(--solarized-blue);
box-shadow: 0 0 0 3px rgba(38, 139, 210, 0.2); box-shadow: 0 0 0 3px rgba(38, 139, 210, 0.2);
} }
@ -215,7 +225,7 @@ body {
border: 1px solid rgba(88, 110, 117, 0.4); border: 1px solid rgba(88, 110, 117, 0.4);
border-radius: 12px; border-radius: 12px;
padding: 12px 16px; padding: 12px 16px;
color: #93a1a1; color: var(--solarized-base1);
width: 100%; width: 100%;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
@ -270,7 +280,7 @@ body {
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
border-bottom: 1px solid rgba(88, 110, 117, 0.2); border-bottom: 1px solid rgba(88, 110, 117, 0.2);
color: #93a1a1; color: var(--solarized-base1);
} }
.dropdown-item:last-child { .dropdown-item:last-child {
@ -283,7 +293,7 @@ body {
.dropdown-item.active { .dropdown-item.active {
background: rgba(38, 139, 210, 0.3); background: rgba(38, 139, 210, 0.3);
color: #fdf6e3; color: var(--solarized-base3);
} }
/* Icon Sizes */ /* Icon Sizes */
@ -378,6 +388,22 @@ body {
margin-bottom: 32px; margin-bottom: 32px;
} }
.ml-3 {
margin-left: 12px;
}
.mr-1 {
margin-right: 4px;
}
.mr-2 {
margin-right: 8px;
}
.mr-4 {
margin-right: 16px;
}
.p-4 { .p-4 {
padding: 16px; padding: 16px;
} }
@ -420,7 +446,7 @@ body {
.collapsible-icon { .collapsible-icon {
flex-shrink: 0; flex-shrink: 0;
transition: transform 0.3s ease; transition: transform 0.3s ease;
color: #93a1a1; color: var(--solarized-base1);
} }
.collapsible-icon.open { .collapsible-icon.open {
@ -431,14 +457,14 @@ body {
flex: 1; flex: 1;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: #fdf6e3; color: var(--solarized-base3);
text-align: left; text-align: left;
margin: 0; margin: 0;
} }
.collapsible-count { .collapsible-count {
background: rgba(38, 139, 210, 0.2); background: rgba(38, 139, 210, 0.2);
color: #268bd2; color: var(--solarized-blue);
padding: 4px 12px; padding: 4px 12px;
border-radius: 20px; border-radius: 20px;
font-size: 14px; font-size: 14px;

View file

@ -193,28 +193,28 @@ export default function Home() {
return { return {
isActive: true, isActive: true,
text: `Program & Preview: ${sceneName}`, text: `Program & Preview: ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))', className: 'active',
showTransition: false showTransition: false
}; };
} else if (isProgram) { } else if (isProgram) {
return { return {
isActive: true, isActive: true,
text: `Program: ${sceneName}`, text: `Program: ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))', className: 'active',
showTransition: false showTransition: false
}; };
} else if (isPreview) { } else if (isPreview) {
return { return {
isActive: true, isActive: true,
text: `Preview: ${sceneName}`, text: `Preview: ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-yellow), var(--solarized-orange))', className: 'btn-scene-preview',
showTransition: true showTransition: true
}; };
} else { } else {
return { return {
isActive: false, isActive: false,
text: `Set Preview: ${sceneName}`, text: `Set Preview: ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-blue), var(--solarized-cyan))', className: '',
showTransition: false showTransition: false
}; };
} }
@ -224,14 +224,14 @@ export default function Home() {
return { return {
isActive: true, isActive: true,
text: `Active: ${sceneName}`, text: `Active: ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-green), var(--solarized-yellow))', className: 'active',
showTransition: false showTransition: false
}; };
} else { } else {
return { return {
isActive: false, isActive: false,
text: `Switch to ${sceneName}`, text: `Switch to ${sceneName}`,
background: 'linear-gradient(135deg, var(--solarized-blue), var(--solarized-cyan))', className: '',
showTransition: false showTransition: false
}; };
} }
@ -289,20 +289,14 @@ export default function Home() {
<> <>
<button <button
onClick={() => handleSceneSwitch('1-Screen')} onClick={() => handleSceneSwitch('1-Screen')}
className={`btn ${buttonState.isActive ? 'active' : ''}`} className={`btn ${buttonState.className}`}
style={{ background: buttonState.background }}
> >
{buttonState.text} {buttonState.text}
</button> </button>
{buttonState.showTransition && ( {buttonState.showTransition && (
<button <button
onClick={handleTransition} onClick={handleTransition}
className="btn" className="btn btn-scene-transition ml-3"
style={{
background: 'linear-gradient(135deg, var(--solarized-red), var(--solarized-magenta))',
minWidth: '120px',
marginLeft: '12px'
}}
> >
Go Live Go Live
</button> </button>
@ -335,20 +329,14 @@ export default function Home() {
<> <>
<button <button
onClick={() => handleSceneSwitch('2-Screen')} onClick={() => handleSceneSwitch('2-Screen')}
className={`btn ${buttonState.isActive ? 'active' : ''}`} className={`btn ${buttonState.className}`}
style={{ background: buttonState.background }}
> >
{buttonState.text} {buttonState.text}
</button> </button>
{buttonState.showTransition && ( {buttonState.showTransition && (
<button <button
onClick={handleTransition} onClick={handleTransition}
className="btn" className="btn btn-scene-transition ml-3"
style={{
background: 'linear-gradient(135deg, var(--solarized-red), var(--solarized-magenta))',
minWidth: '120px',
marginLeft: '12px'
}}
> >
Go Live Go Live
</button> </button>
@ -395,20 +383,14 @@ export default function Home() {
<> <>
<button <button
onClick={() => handleSceneSwitch('4-Screen')} onClick={() => handleSceneSwitch('4-Screen')}
className={`btn ${buttonState.isActive ? 'active' : ''}`} className={`btn ${buttonState.className}`}
style={{ background: buttonState.background }}
> >
{buttonState.text} {buttonState.text}
</button> </button>
{buttonState.showTransition && ( {buttonState.showTransition && (
<button <button
onClick={handleTransition} onClick={handleTransition}
className="btn" className="btn btn-scene-transition ml-3"
style={{
background: 'linear-gradient(135deg, var(--solarized-red), var(--solarized-magenta))',
minWidth: '120px',
marginLeft: '12px'
}}
> >
Go Live Go Live
</button> </button>

View file

@ -131,12 +131,11 @@ function StreamsByTeam({ streams, teams, onDelete }: StreamsByTeamProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center"> <div className="flex items-center">
<div <div
className="bg-gradient-to-br from-green-500 to-blue-600 rounded-lg flex items-center justify-center text-white font-bold flex-shrink-0" className="bg-gradient-to-br from-green-500 to-blue-600 rounded-lg flex items-center justify-center text-white font-bold flex-shrink-0 mr-4"
style={{ style={{
width: '64px', width: '64px',
height: '64px', height: '64px',
fontSize: '24px', fontSize: '24px'
marginRight: '16px'
}} }}
> >
{stream.name.charAt(0).toUpperCase()} {stream.name.charAt(0).toUpperCase()}
@ -153,8 +152,7 @@ function StreamsByTeam({ streams, teams, onDelete }: StreamsByTeamProps) {
href={stream.url} href={stream.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="btn btn-primary text-sm" className="btn btn-primary text-sm mr-2"
style={{ marginRight: '8px' }}
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />

View file

@ -86,7 +86,7 @@ export default function Footer() {
<div className="mb-4"> <div className="mb-4">
<h3 className="font-semibold mb-2">OBS Studio</h3> <h3 className="font-semibold mb-2">OBS Studio</h3>
<div className="flex items-center"> <div className="flex items-center">
<div className={`status-dot ${obsStatus?.connected ? 'connected' : 'disconnected'}`} style={{marginRight: '4px'}}></div> <div className={`status-dot ${obsStatus?.connected ? 'connected' : 'disconnected'} mr-1`}></div>
<p className="text-sm opacity-60"> <p className="text-sm opacity-60">
{obsStatus?.connected ? 'Connected' : 'Disconnected'} {obsStatus?.connected ? 'Connected' : 'Disconnected'}
</p> </p>
@ -102,17 +102,17 @@ export default function Footer() {
{obsStatus.connected && ( {obsStatus.connected && (
<div className="flex flex-wrap gap-6 mt-4"> <div className="flex flex-wrap gap-6 mt-4">
<div className={`flex items-center ${obsStatus.streaming ? 'text-red-400' : 'opacity-60'}`}> <div className={`flex items-center ${obsStatus.streaming ? 'text-red-400' : 'opacity-60'}`}>
<div className={`status-dot ${obsStatus.streaming ? 'streaming' : 'idle'}`} style={{width: '10px', height: '10px', marginRight: '4px'}}></div> <div className={`status-dot ${obsStatus.streaming ? 'streaming' : 'idle'} mr-1`} style={{width: '10px', height: '10px'}}></div>
<span className="text-sm font-medium" style={{marginRight: '8px'}}>{obsStatus.streaming ? 'LIVE' : 'OFFLINE'}</span> <span className="text-sm font-medium mr-2">{obsStatus.streaming ? 'LIVE' : 'OFFLINE'}</span>
</div> </div>
<div className={`flex items-center ${obsStatus.recording ? 'text-red-400' : 'opacity-60'}`}> <div className={`flex items-center ${obsStatus.recording ? 'text-red-400' : 'opacity-60'}`}>
<div className={`status-dot ${obsStatus.recording ? 'streaming' : 'idle'}`} style={{width: '10px', height: '10px', marginRight: '4px'}}></div> <div className={`status-dot ${obsStatus.recording ? 'streaming' : 'idle'} mr-1`} style={{width: '10px', height: '10px'}}></div>
<span className="text-sm font-medium" style={{marginRight: '8px'}}>{obsStatus.recording ? 'REC' : 'IDLE'}</span> <span className="text-sm font-medium mr-2">{obsStatus.recording ? 'REC' : 'IDLE'}</span>
</div> </div>
<div className={`flex items-center ${obsStatus.studioModeEnabled ? 'text-yellow-400' : 'opacity-60'}`}> <div className={`flex items-center ${obsStatus.studioModeEnabled ? 'text-yellow-400' : 'opacity-60'}`}>
<div className={`status-dot ${obsStatus.studioModeEnabled ? 'connected' : 'idle'}`} style={{width: '10px', height: '10px', marginRight: '4px'}}></div> <div className={`status-dot ${obsStatus.studioModeEnabled ? 'connected' : 'idle'} mr-1`} style={{width: '10px', height: '10px'}}></div>
<span className="text-sm font-medium">{obsStatus.studioModeEnabled ? 'STUDIO' : 'DIRECT'}</span> <span className="text-sm font-medium">{obsStatus.studioModeEnabled ? 'STUDIO' : 'DIRECT'}</span>
</div> </div>
</div> </div>

View file

@ -1,10 +0,0 @@
.linkButton {
padding: 10px 20px;
background: #0070f3;
color: #fff;
text-decoration: none;
border-radius: 5px;
display: inline-block;
text-align: center;
}