button example
This commit is contained in:
370
themes/README.md
Normal file
370
themes/README.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# NOSTR_LOGIN_LITE Theme System
|
||||
|
||||
A comprehensive theming system supporting CSS custom properties, JSON metadata, and runtime theme switching.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The theme system consists of:
|
||||
|
||||
- **CSS Custom Properties**: Dynamic styling variables
|
||||
- **JSON Metadata**: Theme descriptions and configurations
|
||||
- **Theme Manager**: Runtime loading and switching
|
||||
- **Directory Organization**: Structured theme packages
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
themes/
|
||||
├── README.md # This documentation
|
||||
├── theme-manager.js # Theme management system
|
||||
├── default/ # Default monospace theme
|
||||
│ ├── theme.json # Theme metadata
|
||||
│ ├── theme.css # CSS custom properties
|
||||
│ └── assets/ # Theme assets (fonts, images)
|
||||
├── dark/ # Dark cyberpunk theme
|
||||
│ ├── theme.json
|
||||
│ ├── theme.css
|
||||
│ └── assets/
|
||||
└── community/ # Community contributed themes
|
||||
└── [theme-name]/
|
||||
├── theme.json
|
||||
├── theme.css
|
||||
└── assets/
|
||||
```
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
All themes use standardized CSS custom properties with the `--nl-` prefix:
|
||||
|
||||
### Colors
|
||||
- `--nl-primary-color`: Main text/border color
|
||||
- `--nl-secondary-color`: Background color
|
||||
- `--nl-accent-color`: Hover/active accent color
|
||||
|
||||
### Typography
|
||||
- `--nl-font-family`: Base font family
|
||||
- `--nl-font-size-base`: Base font size (14px)
|
||||
- `--nl-font-size-title`: Title font size (24px)
|
||||
- `--nl-font-size-heading`: Heading font size (18px)
|
||||
- `--nl-font-size-button`: Button font size (16px)
|
||||
- `--nl-font-weight-normal`: Normal weight (400)
|
||||
- `--nl-font-weight-medium`: Medium weight (500)
|
||||
- `--nl-font-weight-bold`: Bold weight (600)
|
||||
|
||||
### Layout
|
||||
- `--nl-border-radius`: Border radius (15px)
|
||||
- `--nl-border-width`: Border thickness (3px)
|
||||
- `--nl-border-style`: Border style (solid)
|
||||
- `--nl-padding-button`: Button padding (12px 16px)
|
||||
- `--nl-padding-container`: Container padding (20px 24px)
|
||||
|
||||
### Effects
|
||||
- `--nl-transition-duration`: Animation duration (0.2s)
|
||||
- `--nl-transition-easing`: Animation easing (ease)
|
||||
- `--nl-shadow`: Box shadow effects
|
||||
- `--nl-backdrop-filter`: Backdrop filter effects
|
||||
|
||||
### Component States
|
||||
- `--nl-button-bg`: Button background
|
||||
- `--nl-button-color`: Button text color
|
||||
- `--nl-button-border`: Button border
|
||||
- `--nl-button-hover-border-color`: Button hover border
|
||||
- `--nl-button-active-bg`: Button active background
|
||||
- `--nl-button-active-color`: Button active text
|
||||
|
||||
## Theme Metadata (theme.json)
|
||||
|
||||
Each theme must include a `theme.json` file with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Theme Display Name",
|
||||
"version": "1.0.0",
|
||||
"author": "Author Name/Email",
|
||||
"description": "Theme description",
|
||||
"preview": "preview.png",
|
||||
"compatibility": "1.0+",
|
||||
"license": "MIT",
|
||||
"variables": {
|
||||
"--nl-primary-color": "#000000",
|
||||
"--nl-secondary-color": "#ffffff",
|
||||
"--nl-accent-color": "#ff0000"
|
||||
},
|
||||
"assets": ["fonts/", "images/"],
|
||||
"tags": ["monospace", "dark", "accessibility"]
|
||||
}
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
- `name`: Human-readable theme name
|
||||
- `version`: Semantic version number
|
||||
- `variables`: CSS custom property values
|
||||
|
||||
### Optional Fields
|
||||
- `author`: Theme creator information
|
||||
- `description`: Theme description
|
||||
- `preview`: Preview image filename
|
||||
- `compatibility`: Minimum library version
|
||||
- `license`: License identifier (MIT, GPL, etc.)
|
||||
- `assets`: Additional asset directories
|
||||
- `tags`: Theme categorization tags
|
||||
|
||||
## Creating a New Theme
|
||||
|
||||
### 1. Create Theme Directory
|
||||
|
||||
```bash
|
||||
mkdir themes/my-theme
|
||||
cd themes/my-theme
|
||||
```
|
||||
|
||||
### 2. Create theme.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Custom Theme",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"description": "A custom theme for NOSTR_LOGIN_LITE",
|
||||
"variables": {
|
||||
"--nl-primary-color": "#your-color",
|
||||
"--nl-secondary-color": "#your-bg-color",
|
||||
"--nl-accent-color": "#your-accent-color",
|
||||
"--nl-font-family": "\"Your Font\", monospace"
|
||||
},
|
||||
"tags": ["custom"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create theme.css
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--nl-primary-color: #your-color;
|
||||
--nl-secondary-color: #your-bg-color;
|
||||
--nl-accent-color: #your-accent-color;
|
||||
|
||||
/* Typography */
|
||||
--nl-font-family: "Your Font", monospace;
|
||||
|
||||
/* Layout - inherit defaults or customize */
|
||||
--nl-border-radius: 15px;
|
||||
--nl-border-width: 3px;
|
||||
|
||||
/* Add custom variables */
|
||||
--nl-custom-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Optional: Custom component styles */
|
||||
.nl-button {
|
||||
/* Theme-specific enhancements */
|
||||
box-shadow: var(--nl-custom-shadow);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Assets (Optional)
|
||||
|
||||
```
|
||||
my-theme/
|
||||
├── theme.json
|
||||
├── theme.css
|
||||
└── assets/
|
||||
├── fonts/
|
||||
│ └── custom-font.woff2
|
||||
└── images/
|
||||
└── pattern.png
|
||||
```
|
||||
|
||||
### 5. Register Theme
|
||||
|
||||
Update `theme-manager.js` to include your theme:
|
||||
|
||||
```javascript
|
||||
this.availableThemes.set('my-theme', {
|
||||
name: 'My Custom Theme',
|
||||
path: 'my-theme',
|
||||
description: 'A custom theme for NOSTR_LOGIN_LITE'
|
||||
});
|
||||
```
|
||||
|
||||
## Using Themes
|
||||
|
||||
### Initialization
|
||||
|
||||
```javascript
|
||||
await NOSTR_LOGIN_LITE.init({
|
||||
theme: 'default',
|
||||
themePath: './themes/'
|
||||
});
|
||||
```
|
||||
|
||||
### Runtime Switching
|
||||
|
||||
```javascript
|
||||
// Switch theme
|
||||
await NOSTR_LOGIN_LITE.switchTheme('dark');
|
||||
|
||||
// Get current theme
|
||||
const current = NOSTR_LOGIN_LITE.getCurrentTheme();
|
||||
|
||||
// List available themes
|
||||
const available = NOSTR_LOGIN_LITE.getAvailableThemes();
|
||||
```
|
||||
|
||||
### Custom Variables
|
||||
|
||||
```javascript
|
||||
// Set custom variable
|
||||
NOSTR_LOGIN_LITE.setThemeVariable('--nl-accent-color', '#ff00ff');
|
||||
|
||||
// Get variable value
|
||||
const value = NOSTR_LOGIN_LITE.getThemeVariable('--nl-accent-color');
|
||||
```
|
||||
|
||||
### Theme Export
|
||||
|
||||
```javascript
|
||||
// Export current theme configuration
|
||||
const themeData = NOSTR_LOGIN_LITE.exportTheme();
|
||||
console.log(JSON.stringify(themeData, null, 2));
|
||||
```
|
||||
|
||||
## Theme Guidelines
|
||||
|
||||
### Accessibility
|
||||
- Ensure sufficient color contrast (4.5:1 minimum)
|
||||
- Test with screen readers
|
||||
- Support high contrast mode
|
||||
- Use semantic color names
|
||||
|
||||
### Performance
|
||||
- Minimize CSS file size
|
||||
- Optimize asset files
|
||||
- Use web-safe fonts as fallbacks
|
||||
- Consider loading performance
|
||||
|
||||
### Compatibility
|
||||
- Test across browsers
|
||||
- Ensure mobile responsiveness
|
||||
- Validate CSS custom property support
|
||||
- Test with different font sizes
|
||||
|
||||
### Best Practices
|
||||
- Use consistent naming conventions
|
||||
- Provide clear documentation
|
||||
- Include preview images
|
||||
- Tag themes appropriately
|
||||
- Test thoroughly before submission
|
||||
|
||||
## Community Contributions
|
||||
|
||||
### Submission Process
|
||||
1. Fork the repository
|
||||
2. Create theme in `themes/community/your-theme/`
|
||||
3. Follow all guidelines above
|
||||
4. Test thoroughly
|
||||
5. Submit pull request with:
|
||||
- Theme files
|
||||
- Preview screenshot
|
||||
- Documentation updates
|
||||
|
||||
### Review Criteria
|
||||
- Code quality and organization
|
||||
- Accessibility compliance
|
||||
- Cross-browser compatibility
|
||||
- Unique design contribution
|
||||
- Proper documentation
|
||||
|
||||
## Built-in Themes
|
||||
|
||||
### Default Theme
|
||||
- **Colors**: Black/white/red
|
||||
- **Typography**: Courier New monospace
|
||||
- **Style**: Clean, minimalist, accessible
|
||||
- **Use Case**: General purpose, high readability
|
||||
|
||||
### Dark Theme
|
||||
- **Colors**: Green/black/magenta
|
||||
- **Typography**: Courier New monospace
|
||||
- **Style**: Cyberpunk, terminal-inspired
|
||||
- **Use Case**: Low light environments, developer aesthetic
|
||||
|
||||
## API Reference
|
||||
|
||||
### ThemeManager Class
|
||||
|
||||
```javascript
|
||||
const themeManager = new NostrThemeManager();
|
||||
|
||||
// Load theme
|
||||
await themeManager.loadTheme('theme-name');
|
||||
|
||||
// Switch theme
|
||||
await themeManager.switchTheme('theme-name');
|
||||
|
||||
// Get available themes
|
||||
const themes = themeManager.getAvailableThemes();
|
||||
|
||||
// Set/get variables
|
||||
themeManager.setThemeVariable('--nl-accent-color', '#ff0000');
|
||||
const value = themeManager.getThemeVariable('--nl-accent-color');
|
||||
|
||||
// Export current theme
|
||||
const exported = themeManager.exportCurrentTheme();
|
||||
```
|
||||
|
||||
### NOSTR_LOGIN_LITE Integration
|
||||
|
||||
```javascript
|
||||
// Initialize with theme
|
||||
await NOSTR_LOGIN_LITE.init({ theme: 'dark' });
|
||||
|
||||
// Theme management
|
||||
await NOSTR_LOGIN_LITE.switchTheme('theme-name');
|
||||
const current = NOSTR_LOGIN_LITE.getCurrentTheme();
|
||||
const available = NOSTR_LOGIN_LITE.getAvailableThemes();
|
||||
|
||||
// Variable management
|
||||
NOSTR_LOGIN_LITE.setThemeVariable('--nl-primary-color', '#000000');
|
||||
const color = NOSTR_LOGIN_LITE.getThemeVariable('--nl-primary-color');
|
||||
|
||||
// Export functionality
|
||||
const themeData = NOSTR_LOGIN_LITE.exportTheme();
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
The theme system dispatches events for integration:
|
||||
|
||||
```javascript
|
||||
// Theme change event
|
||||
window.addEventListener('nlThemeChanged', (event) => {
|
||||
console.log('New theme:', event.detail.theme);
|
||||
console.log('Theme data:', event.detail.data);
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Theme Not Loading
|
||||
- Check theme.json syntax
|
||||
- Verify file paths
|
||||
- Check browser console for errors
|
||||
- Ensure CSS custom properties are supported
|
||||
|
||||
### Variables Not Applying
|
||||
- Verify CSS custom property names (--nl- prefix)
|
||||
- Check CSS specificity
|
||||
- Ensure theme CSS is loaded after base styles
|
||||
- Validate variable values
|
||||
|
||||
### Performance Issues
|
||||
- Optimize CSS file size
|
||||
- Compress assets
|
||||
- Use efficient selectors
|
||||
- Consider lazy loading for large themes
|
||||
|
||||
## License
|
||||
|
||||
The theme system is open source under the MIT license. Individual themes may have their own licenses as specified in their theme.json files.
|
||||
115
themes/dark/theme.css
Normal file
115
themes/dark/theme.css
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* NOSTR_LOGIN_LITE - Dark Monospace Theme
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Core Variables (6) */
|
||||
--nl-primary-color: #white;
|
||||
--nl-secondary-color: #black;
|
||||
--nl-accent-color: #ff0000;
|
||||
--nl-muted-color: #666666;
|
||||
--nl-font-family: "Courier New", Courier, monospace;
|
||||
--nl-border-radius: 15px;
|
||||
--nl-border-width: 3px;
|
||||
|
||||
/* Floating Tab Variables (8) */
|
||||
--nl-tab-bg-logged-out: #ffffff;
|
||||
--nl-tab-bg-logged-in: #000000;
|
||||
--nl-tab-bg-opacity-logged-out: 0.9;
|
||||
--nl-tab-bg-opacity-logged-in: 0.8;
|
||||
--nl-tab-color-logged-out: #000000;
|
||||
--nl-tab-color-logged-in: #ffffff;
|
||||
--nl-tab-border-logged-out: #000000;
|
||||
--nl-tab-border-logged-in: #ff0000;
|
||||
--nl-tab-border-opacity-logged-out: 1.0;
|
||||
--nl-tab-border-opacity-logged-in: 0.9;
|
||||
}
|
||||
|
||||
/* Base component styles using simplified variables */
|
||||
.nl-component {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
.nl-button {
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
font-family: var(--nl-font-family);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nl-button:hover {
|
||||
border-color: var(--nl-accent-color);
|
||||
}
|
||||
|
||||
.nl-button:active {
|
||||
background: var(--nl-accent-color);
|
||||
color: var(--nl-secondary-color);
|
||||
}
|
||||
|
||||
.nl-input {
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
font-family: var(--nl-font-family);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nl-input:focus {
|
||||
border-color: var(--nl-accent-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.nl-container {
|
||||
background: var(--nl-secondary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
}
|
||||
|
||||
.nl-title, .nl-heading {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nl-text {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
.nl-text--muted {
|
||||
color: var(--nl-muted-color);
|
||||
}
|
||||
|
||||
.nl-icon {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
/* Floating tab styles */
|
||||
.nl-floating-tab {
|
||||
font-family: var(--nl-font-family);
|
||||
border-radius: var(--nl-border-radius);
|
||||
border: var(--nl-border-width) solid;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nl-floating-tab--logged-out {
|
||||
background: rgba(255, 255, 255, var(--nl-tab-bg-opacity-logged-out));
|
||||
color: var(--nl-tab-color-logged-out);
|
||||
border-color: rgba(0, 0, 0, var(--nl-tab-border-opacity-logged-out));
|
||||
}
|
||||
|
||||
.nl-floating-tab--logged-in {
|
||||
background: rgba(0, 0, 0, var(--nl-tab-bg-opacity-logged-in));
|
||||
color: var(--nl-tab-color-logged-in);
|
||||
border-color: rgba(255, 0, 0, var(--nl-tab-border-opacity-logged-in));
|
||||
}
|
||||
|
||||
.nl-transition {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
117
themes/default/theme.css
Normal file
117
themes/default/theme.css
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* NOSTR_LOGIN_LITE - Default Monospace Theme
|
||||
* Black/white/red color scheme with monospace typography
|
||||
* Simplified 14-variable system (6 core + 8 floating tab)
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Core Variables (6) */
|
||||
--nl-primary-color: #000000;
|
||||
--nl-secondary-color: #ffffff;
|
||||
--nl-accent-color: #ff0000;
|
||||
--nl-muted-color: #666666;
|
||||
--nl-font-family: "Courier New", Courier, monospace;
|
||||
--nl-border-radius: 15px;
|
||||
--nl-border-width: 3px;
|
||||
|
||||
/* Floating Tab Variables (8) */
|
||||
--nl-tab-bg-logged-out: #ffffff;
|
||||
--nl-tab-bg-logged-in: #ffffff;
|
||||
--nl-tab-bg-opacity-logged-out: 0.9;
|
||||
--nl-tab-bg-opacity-logged-in: 0.2;
|
||||
--nl-tab-color-logged-out: #000000;
|
||||
--nl-tab-color-logged-in: #ffffff;
|
||||
--nl-tab-border-logged-out: #000000;
|
||||
--nl-tab-border-logged-in: #ff0000;
|
||||
--nl-tab-border-opacity-logged-out: 1.0;
|
||||
--nl-tab-border-opacity-logged-in: 0.1;
|
||||
}
|
||||
|
||||
/* Base component styles using simplified variables */
|
||||
.nl-component {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
.nl-button {
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
font-family: var(--nl-font-family);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nl-button:hover {
|
||||
border-color: var(--nl-accent-color);
|
||||
}
|
||||
|
||||
.nl-button:active {
|
||||
background: var(--nl-accent-color);
|
||||
color: var(--nl-secondary-color);
|
||||
}
|
||||
|
||||
.nl-input {
|
||||
background: var(--nl-secondary-color);
|
||||
color: var(--nl-primary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
font-family: var(--nl-font-family);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.nl-input:focus {
|
||||
border-color: var(--nl-accent-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.nl-container {
|
||||
background: var(--nl-secondary-color);
|
||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||
border-radius: var(--nl-border-radius);
|
||||
}
|
||||
|
||||
.nl-title, .nl-heading {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nl-text {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
.nl-text--muted {
|
||||
color: var(--nl-muted-color);
|
||||
}
|
||||
|
||||
.nl-icon {
|
||||
font-family: var(--nl-font-family);
|
||||
color: var(--nl-primary-color);
|
||||
}
|
||||
|
||||
/* Floating tab styles */
|
||||
.nl-floating-tab {
|
||||
font-family: var(--nl-font-family);
|
||||
border-radius: var(--nl-border-radius);
|
||||
border: var(--nl-border-width) solid;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nl-floating-tab--logged-out {
|
||||
background: rgba(255, 255, 255, var(--nl-tab-bg-opacity-logged-out));
|
||||
color: var(--nl-tab-color-logged-out);
|
||||
border-color: rgba(0, 0, 0, var(--nl-tab-border-opacity-logged-out));
|
||||
}
|
||||
|
||||
.nl-floating-tab--logged-in {
|
||||
background: rgba(0, 0, 0, var(--nl-tab-bg-opacity-logged-in));
|
||||
color: var(--nl-tab-color-logged-in);
|
||||
border-color: rgba(255, 0, 0, var(--nl-tab-border-opacity-logged-in));
|
||||
}
|
||||
|
||||
.nl-transition {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
35
themes/index.json
Normal file
35
themes/index.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"themes": {
|
||||
"default": {
|
||||
"name": "Default Monospace",
|
||||
"path": "default",
|
||||
"description": "Black/white/red monospace theme with rounded buttons",
|
||||
"author": "NOSTR_LOGIN_LITE",
|
||||
"version": "1.0.0",
|
||||
"preview": "default/preview.png",
|
||||
"tags": ["monospace", "minimalist", "accessibility", "default"],
|
||||
"featured": true
|
||||
},
|
||||
"dark": {
|
||||
"name": "Dark Monospace",
|
||||
"path": "dark",
|
||||
"description": "Dark mode with green accents and monospace typography",
|
||||
"author": "NOSTR_LOGIN_LITE",
|
||||
"version": "1.0.0",
|
||||
"preview": "dark/preview.png",
|
||||
"tags": ["dark", "cyberpunk", "monospace", "accessibility"],
|
||||
"featured": true
|
||||
}
|
||||
},
|
||||
"categories": {
|
||||
"official": ["default", "dark"],
|
||||
"community": [],
|
||||
"experimental": []
|
||||
},
|
||||
"metadata": {
|
||||
"total_themes": 2,
|
||||
"last_updated": "2025-01-14T11:13:00.000Z",
|
||||
"schema_version": "1.0.0"
|
||||
}
|
||||
}
|
||||
286
themes/theme-manager.js
Normal file
286
themes/theme-manager.js
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* NOSTR_LOGIN_LITE Theme Manager
|
||||
* Handles theme loading, switching, and CSS custom property management
|
||||
*/
|
||||
|
||||
class NostrThemeManager {
|
||||
constructor() {
|
||||
this.currentTheme = null;
|
||||
this.availableThemes = new Map();
|
||||
this.themeCache = new Map();
|
||||
this.basePath = './themes/';
|
||||
|
||||
// Initialize with default theme
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
// Load available themes index
|
||||
await this.loadThemeIndex();
|
||||
|
||||
// Set default theme if none is set
|
||||
if (!this.currentTheme) {
|
||||
await this.loadTheme('default');
|
||||
}
|
||||
|
||||
console.log('NostrThemeManager: Initialized with themes:', Array.from(this.availableThemes.keys()));
|
||||
} catch (error) {
|
||||
console.error('NostrThemeManager: Initialization failed:', error);
|
||||
this.fallbackToInlineStyles();
|
||||
}
|
||||
}
|
||||
|
||||
async loadThemeIndex() {
|
||||
// For now, we'll manually register available themes
|
||||
// In production, this could fetch from a themes.json index file
|
||||
this.availableThemes.set('default', {
|
||||
name: 'Default Monospace',
|
||||
path: 'default',
|
||||
description: 'Black/white/red monospace theme'
|
||||
});
|
||||
|
||||
this.availableThemes.set('dark', {
|
||||
name: 'Dark Monospace',
|
||||
path: 'dark',
|
||||
description: 'Dark mode with green accents and monospace typography'
|
||||
});
|
||||
|
||||
// Future themes can be registered here or loaded from an index
|
||||
// this.availableThemes.set('cyberpunk', { ... });
|
||||
}
|
||||
|
||||
async loadTheme(themeName) {
|
||||
try {
|
||||
console.log(`NostrThemeManager: Loading theme "${themeName}"`);
|
||||
|
||||
// Check if theme exists
|
||||
if (!this.availableThemes.has(themeName)) {
|
||||
throw new Error(`Theme "${themeName}" not found`);
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if (this.themeCache.has(themeName)) {
|
||||
const cachedTheme = this.themeCache.get(themeName);
|
||||
this.applyTheme(cachedTheme);
|
||||
this.currentTheme = themeName;
|
||||
return cachedTheme;
|
||||
}
|
||||
|
||||
// Load theme metadata
|
||||
const themeInfo = this.availableThemes.get(themeName);
|
||||
const metadataUrl = `${this.basePath}${themeInfo.path}/theme.json`;
|
||||
|
||||
const response = await fetch(metadataUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load theme metadata: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const themeData = await response.json();
|
||||
|
||||
// Validate theme data
|
||||
this.validateThemeData(themeData);
|
||||
|
||||
// Load CSS file
|
||||
await this.loadThemeCSS(themeInfo.path);
|
||||
|
||||
// Cache the theme data
|
||||
this.themeCache.set(themeName, themeData);
|
||||
|
||||
// Apply the theme
|
||||
this.applyTheme(themeData);
|
||||
this.currentTheme = themeName;
|
||||
|
||||
console.log(`NostrThemeManager: Successfully loaded theme "${themeName}"`);
|
||||
|
||||
// Dispatch theme change event
|
||||
this.dispatchThemeChangeEvent(themeName, themeData);
|
||||
|
||||
return themeData;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`NostrThemeManager: Failed to load theme "${themeName}":`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async loadThemeCSS(themePath) {
|
||||
const cssUrl = `${this.basePath}${themePath}/theme.css`;
|
||||
|
||||
// Remove existing theme CSS
|
||||
const existingThemeCSS = document.getElementById('nl-theme-css');
|
||||
if (existingThemeCSS) {
|
||||
existingThemeCSS.remove();
|
||||
}
|
||||
|
||||
// Load new theme CSS
|
||||
const link = document.createElement('link');
|
||||
link.id = 'nl-theme-css';
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = cssUrl;
|
||||
|
||||
// Wait for CSS to load
|
||||
await new Promise((resolve, reject) => {
|
||||
link.onload = resolve;
|
||||
link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
applyTheme(themeData) {
|
||||
if (!themeData.variables) {
|
||||
console.warn('NostrThemeManager: Theme data has no variables to apply');
|
||||
return;
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
|
||||
// Apply CSS custom properties
|
||||
Object.entries(themeData.variables).forEach(([property, value]) => {
|
||||
root.style.setProperty(property, value);
|
||||
});
|
||||
|
||||
console.log(`NostrThemeManager: Applied ${Object.keys(themeData.variables).length} CSS variables`);
|
||||
}
|
||||
|
||||
validateThemeData(themeData) {
|
||||
const required = ['name', 'version', 'variables'];
|
||||
|
||||
for (const field of required) {
|
||||
if (!themeData[field]) {
|
||||
throw new Error(`Theme validation failed: missing required field "${field}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof themeData.variables !== 'object') {
|
||||
throw new Error('Theme validation failed: variables must be an object');
|
||||
}
|
||||
}
|
||||
|
||||
fallbackToInlineStyles() {
|
||||
console.log('NostrThemeManager: Falling back to inline styles');
|
||||
|
||||
// Apply default theme variables directly
|
||||
const defaultVariables = {
|
||||
'--nl-primary-color': '#000000',
|
||||
'--nl-secondary-color': '#ffffff',
|
||||
'--nl-accent-color': '#ff0000',
|
||||
'--nl-font-family': '"Courier New", Courier, monospace',
|
||||
'--nl-border-radius': '15px',
|
||||
'--nl-border-width': '3px',
|
||||
'--nl-border-style': 'solid',
|
||||
'--nl-padding-button': '12px 16px',
|
||||
'--nl-padding-container': '20px 24px',
|
||||
'--nl-font-size-base': '14px',
|
||||
'--nl-font-size-title': '24px',
|
||||
'--nl-font-size-button': '16px',
|
||||
'--nl-transition-duration': '0.2s'
|
||||
};
|
||||
|
||||
const root = document.documentElement;
|
||||
Object.entries(defaultVariables).forEach(([property, value]) => {
|
||||
root.style.setProperty(property, value);
|
||||
});
|
||||
|
||||
this.currentTheme = 'fallback';
|
||||
}
|
||||
|
||||
dispatchThemeChangeEvent(themeName, themeData) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const event = new CustomEvent('nlThemeChanged', {
|
||||
detail: {
|
||||
theme: themeName,
|
||||
data: themeData,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
getCurrentTheme() {
|
||||
return this.currentTheme;
|
||||
}
|
||||
|
||||
getAvailableThemes() {
|
||||
return Array.from(this.availableThemes.keys());
|
||||
}
|
||||
|
||||
getThemeInfo(themeName) {
|
||||
return this.availableThemes.get(themeName);
|
||||
}
|
||||
|
||||
async switchTheme(themeName) {
|
||||
return await this.loadTheme(themeName);
|
||||
}
|
||||
|
||||
getThemeVariable(variableName) {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
const root = document.documentElement;
|
||||
const style = getComputedStyle(root);
|
||||
return style.getPropertyValue(variableName);
|
||||
}
|
||||
|
||||
setThemeVariable(variableName, value) {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty(variableName, value);
|
||||
}
|
||||
|
||||
resetTheme() {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Remove all nl- prefixed custom properties
|
||||
const style = getComputedStyle(root);
|
||||
for (let i = 0; i < style.length; i++) {
|
||||
const property = style[i];
|
||||
if (property.startsWith('--nl-')) {
|
||||
root.style.removeProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove theme CSS
|
||||
const themeCSS = document.getElementById('nl-theme-css');
|
||||
if (themeCSS) {
|
||||
themeCSS.remove();
|
||||
}
|
||||
|
||||
this.currentTheme = null;
|
||||
}
|
||||
|
||||
// Theme creation utilities (for developers)
|
||||
exportCurrentTheme() {
|
||||
const root = document.documentElement;
|
||||
const style = getComputedStyle(root);
|
||||
const variables = {};
|
||||
|
||||
for (let i = 0; i < style.length; i++) {
|
||||
const property = style[i];
|
||||
if (property.startsWith('--nl-')) {
|
||||
variables[property] = style.getPropertyValue(property);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'Custom Theme',
|
||||
version: '1.0.0',
|
||||
author: 'User',
|
||||
description: 'Exported theme',
|
||||
variables,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in NOSTR_LOGIN_LITE
|
||||
if (typeof window !== 'undefined') {
|
||||
window.NostrThemeManager = NostrThemeManager;
|
||||
console.log('NostrThemeManager: Class available globally');
|
||||
} else {
|
||||
// Node.js environment
|
||||
module.exports = NostrThemeManager;
|
||||
}
|
||||
Reference in New Issue
Block a user