button example

This commit is contained in:
Your Name
2025-09-14 18:51:27 -04:00
parent 9f0b0638e5
commit bac621bbaa
16 changed files with 1155 additions and 1460 deletions

370
themes/README.md Normal file
View 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
View 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
View 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
View 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
View 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;
}