Update CONTRIBUTING.md for clarity and consistency in theme development guidelines
This commit is contained in:
@@ -45,7 +45,7 @@ The orchestrator file `seasonals.js` manages theme loading at runtime. It reads
|
|||||||
|
|
||||||
## Standard Theme File Structure
|
## Standard Theme File Structure
|
||||||
|
|
||||||
Here is the complete file layout for a theme called `mytheme`:
|
Here is a complete file layout for a theme called `mytheme`:
|
||||||
|
|
||||||
```
|
```
|
||||||
Jellyfin.Plugin.Seasonals/
|
Jellyfin.Plugin.Seasonals/
|
||||||
@@ -65,7 +65,7 @@ Jellyfin.Plugin.Seasonals/
|
|||||||
Every theme JS file follows a **consistent skeleton**. Use this as your starting template:
|
Every theme JS file follows a **consistent skeleton**. Use this as your starting template:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ── 1. Read Configuration ──────────────────────────────────────────
|
// 1. Read Configuration
|
||||||
const config = window.SeasonalsPluginConfig?.MyTheme || {};
|
const config = window.SeasonalsPluginConfig?.MyTheme || {};
|
||||||
|
|
||||||
const enabled = config.EnableMyTheme !== undefined ? config.EnableMyTheme : true;
|
const enabled = config.EnableMyTheme !== undefined ? config.EnableMyTheme : true;
|
||||||
@@ -74,8 +74,8 @@ const elementCount = config.ElementCount || 25;
|
|||||||
|
|
||||||
let msgPrinted = false;
|
let msgPrinted = false;
|
||||||
|
|
||||||
// ── 2. Toggle Function ────────────────────────────────────────────
|
// 2. Toggle Function
|
||||||
// Hides the effect when a video player, trailer, dashboard, or user menu is active.
|
// Hides the effect when a video player, trailer (in full width mode), dashboard, or user menu is active.
|
||||||
function toggleMyTheme() {
|
function toggleMyTheme() {
|
||||||
const container = document.querySelector('.mytheme-container');
|
const container = document.querySelector('.mytheme-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -100,7 +100,7 @@ function toggleMyTheme() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 3. MutationObserver ────────────────────────────────────────────
|
// 3. MutationObserver
|
||||||
// Watches the DOM for changes so the effect can auto-hide/show.
|
// Watches the DOM for changes so the effect can auto-hide/show.
|
||||||
const observer = new MutationObserver(toggleMyTheme);
|
const observer = new MutationObserver(toggleMyTheme);
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
@@ -109,7 +109,7 @@ observer.observe(document.body, {
|
|||||||
attributes: true
|
attributes: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 4. Element Creation ────────────────────────────────────────────
|
// 4. Element Creation
|
||||||
// Create and append your animated elements to the container.
|
// Create and append your animated elements to the container.
|
||||||
function createElements() {
|
function createElements() {
|
||||||
const container = document.querySelector('.mytheme-container') || document.createElement('div');
|
const container = document.querySelector('.mytheme-container') || document.createElement('div');
|
||||||
@@ -140,7 +140,7 @@ function createElements() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 5. Initialization ─────────────────────────────────────────────
|
// 5. Initialization
|
||||||
function initializeMyTheme() {
|
function initializeMyTheme() {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
createElements();
|
createElements();
|
||||||
@@ -153,9 +153,9 @@ initializeMyTheme();
|
|||||||
### Key Rules
|
### Key Rules
|
||||||
|
|
||||||
- **Always** read config from `window.SeasonalsPluginConfig?.{ThemeName}`.
|
- **Always** read config from `window.SeasonalsPluginConfig?.{ThemeName}`.
|
||||||
- **Always** implement the toggle function with the same selectors (`.videoPlayerContainer`, `.youtubePlayerContainer`, `.dashboardDocument`, `#app-user-menu`).
|
- **Always** implement the toggle function with the same selectors (`.videoPlayerContainer`, `.youtubePlayerContainer`, `.dashboardDocument`, `#app-user-menu`, just use the above template).
|
||||||
- **Always** use `aria-hidden="true"` on the container for accessibility.
|
- **Always** use `aria-hidden="true"` on the container for accessibility.
|
||||||
- **Always** call your `initialize` function at the end of the file.
|
- Call your `initialize` function at the end of the file.
|
||||||
- For **canvas-based** themes (like `snowfall.js`), use a `<canvas>` element with `requestAnimationFrame` instead of CSS animations. Make sure to clean up with `cancelAnimationFrame` when hidden.
|
- For **canvas-based** themes (like `snowfall.js`), use a `<canvas>` element with `requestAnimationFrame` instead of CSS animations. Make sure to clean up with `cancelAnimationFrame` when hidden.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -165,7 +165,7 @@ initializeMyTheme();
|
|||||||
Every theme CSS file follows this structure:
|
Every theme CSS file follows this structure:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* ── Container ──────────────────────────────────────────────────── */
|
/* Container */
|
||||||
/* Full-screen overlay, transparent, non-interactive */
|
/* Full-screen overlay, transparent, non-interactive */
|
||||||
.mytheme-container {
|
.mytheme-container {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -179,7 +179,7 @@ Every theme CSS file follows this structure:
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Animated Element ───────────────────────────────────────────── */
|
/* Animated Element */
|
||||||
.mytheme-element {
|
.mytheme-element {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 15;
|
z-index: 15;
|
||||||
@@ -193,7 +193,7 @@ Every theme CSS file follows this structure:
|
|||||||
animation-iteration-count: infinite, infinite;
|
animation-iteration-count: infinite, infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Keyframes ──────────────────────────────────────────────────── */
|
/* Keyframes */
|
||||||
@keyframes mytheme-fall {
|
@keyframes mytheme-fall {
|
||||||
0% { top: -10%; }
|
0% { top: -10%; }
|
||||||
100% { top: 100%; }
|
100% { top: 100%; }
|
||||||
@@ -204,7 +204,7 @@ Every theme CSS file follows this structure:
|
|||||||
50% { transform: translateX(80px); }
|
50% { transform: translateX(80px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Staggered Delays for Base Elements ─────────────────────────── */
|
/* Staggered Delays for Base Elements */
|
||||||
/* Spread the initial 12 elements across the screen */
|
/* Spread the initial 12 elements across the screen */
|
||||||
.mytheme-element:nth-of-type(1) { left: 10%; animation-delay: 1s, 1s; }
|
.mytheme-element:nth-of-type(1) { left: 10%; animation-delay: 1s, 1s; }
|
||||||
.mytheme-element:nth-of-type(2) { left: 20%; animation-delay: 6s, 0.5s; }
|
.mytheme-element:nth-of-type(2) { left: 20%; animation-delay: 6s, 0.5s; }
|
||||||
@@ -214,9 +214,9 @@ Every theme CSS file follows this structure:
|
|||||||
|
|
||||||
### Key Rules
|
### Key Rules
|
||||||
|
|
||||||
- **Container** must be `position: fixed`, full-screen, with `pointer-events: none` and `z-index: 10`.
|
- **Container** must be `position: fixed`, full-screen, with `pointer-events: none` and at least `z-index: 10`.
|
||||||
- **Elements** should use `position: fixed` with `z-index: 15`.
|
- **Elements** should use `position: fixed` with at least `z-index: 15`.
|
||||||
- Use **two animations** (primary movement + secondary effect) for natural-looking motion.
|
- Use **animations** (eg. primary movement + secondary effect for natural-looking motion).
|
||||||
- Include **`nth-of-type` rules** for the initial set of base elements to stagger them.
|
- Include **`nth-of-type` rules** for the initial set of base elements to stagger them.
|
||||||
- Include **webkit prefixes** (`-webkit-animation-*`, `@-webkit-keyframes`) for broader compatibility (see existing themes for examples).
|
- Include **webkit prefixes** (`-webkit-animation-*`, `@-webkit-keyframes`) for broader compatibility (see existing themes for examples).
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ Every theme CSS file follows this structure:
|
|||||||
|
|
||||||
## Image Assets (Optional)
|
## Image Assets (Optional)
|
||||||
|
|
||||||
If your theme uses image sprites (e.g., leaves, ghosts, eggs):
|
If your theme uses images (e.g., leaves, ghosts, eggs):
|
||||||
|
|
||||||
1. Create a folder: `Jellyfin.Plugin.Seasonals/Web/{themeName}_images/`
|
1. Create a folder: `Jellyfin.Plugin.Seasonals/Web/{themeName}_images/`
|
||||||
2. Place your assets inside (PNG recommended, keep files small)
|
2. Place your assets inside (PNG recommended, keep files small)
|
||||||
@@ -232,11 +232,6 @@ If your theme uses image sprites (e.g., leaves, ghosts, eggs):
|
|||||||
```javascript
|
```javascript
|
||||||
img.src = '../Seasonals/Resources/mytheme_images/sprite1.png';
|
img.src = '../Seasonals/Resources/mytheme_images/sprite1.png';
|
||||||
```
|
```
|
||||||
4. For local testing, you can reference them directly:
|
|
||||||
```javascript
|
|
||||||
img.src = './mytheme_images/sprite1.png';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Registering Your Theme
|
## Registering Your Theme
|
||||||
@@ -264,7 +259,7 @@ const ThemeConfigs = {
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The backend registration is handled by the plugin maintainers. You do **not** need to modify C# files for your theme submission. Just focus on the JS/CSS/images.
|
> The backend registration is handled by the plugin maintainers. You do **not** need to modify C# files for your theme submission. Just focus on the JS/CSS/images.
|
||||||
>
|
>
|
||||||
> However, if you'd like to include full backend integration, add your theme to the enum/configuration in `Configuration/PluginConfiguration.cs`.
|
> However, if you'd like to include full backend integration, add your theme to the enum/configuration in `Configuration/PluginConfiguration.cs` and the selectors in `configPage.html`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -275,19 +270,18 @@ You can test your theme without a Jellyfin server by using the included test sit
|
|||||||
### Steps
|
### Steps
|
||||||
|
|
||||||
1. Navigate to the `Jellyfin.Plugin.Seasonals/Web/` directory
|
1. Navigate to the `Jellyfin.Plugin.Seasonals/Web/` directory
|
||||||
2. Open `test-site-new.html` in your browser (just double-click the file)
|
2. Open `test-site.html` in your browser (just double-click the file) or vscode or what ever you use...
|
||||||
3. Use the **theme selector dropdown** to pick an existing theme or select **"Custom (Local Files)"** to test your own
|
3. Use the **theme selector dropdown** to pick an existing theme or select **"Custom (Local Files)"** to test your own
|
||||||
4. When "Custom" is selected, enter your theme's JS and CSS filenames (e.g., `mytheme.js` and `mytheme.css`)
|
4. When "Custom" is selected, enter your theme's JS and CSS filenames (e.g., `mytheme.js` and `mytheme.css` (must be in the same folder as `test-site.html` for this to work))
|
||||||
5. Click **"Load Theme"** to apply. Click **"Clear & Reload"** to reset and try again
|
5. Click **"Load Theme"** to apply. Click **"Clear & Reload"** to reset and try again
|
||||||
|
|
||||||
### What to Verify
|
### What to Verify
|
||||||
|
|
||||||
- ✅ The effect is visible on the dark background
|
- ✅ The effect is visible on the background
|
||||||
- ✅ The animation runs smoothly without jank
|
- ✅ The animation runs smoothly
|
||||||
- ✅ Elements are spread across the full viewport
|
- ✅ Elements are spread across the full viewport
|
||||||
- ✅ The mock header is **not blocked** by the effect (thanks to `pointer-events: none`)
|
- ✅ The mock header is **not blocked** by the effect (thanks to `pointer-events: none`)
|
||||||
- ✅ Performance is acceptable (check DevTools → Performance tab)
|
- ✅ No theme related console errors appear (check DevTools → Console)
|
||||||
- ✅ No console errors appear (check DevTools → Console)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -299,8 +293,8 @@ You can test your theme without a Jellyfin server by using the included test sit
|
|||||||
- [ ] Created `{themeName}.css` following the [CSS pattern](#css-file-pattern)
|
- [ ] Created `{themeName}.css` following the [CSS pattern](#css-file-pattern)
|
||||||
- [ ] (If applicable) Created `{themeName}_images/` with optimized assets
|
- [ ] (If applicable) Created `{themeName}_images/` with optimized assets
|
||||||
- [ ] Added theme to `ThemeConfigs` in `seasonals.js`
|
- [ ] Added theme to `ThemeConfigs` in `seasonals.js`
|
||||||
- [ ] Tested locally with `test-site-new.html`
|
- [ ] Tested locally with `test-site.html`
|
||||||
- [ ] No console errors
|
- [ ] No theme related console errors
|
||||||
- [ ] Effect has `pointer-events: none` (doesn't block the UI)
|
- [ ] Effect has `pointer-events: none` (doesn't block the UI)
|
||||||
- [ ] Effect hides during video/trailer playback (toggle function implemented)
|
- [ ] Effect hides during video/trailer playback (toggle function implemented)
|
||||||
- [ ] (Optional) Included a screenshot or short recording of the effect to the readme
|
- [ ] (Optional) Included a screenshot or short recording of the effect to the readme
|
||||||
@@ -312,13 +306,8 @@ You can test your theme without a Jellyfin server by using the included test sit
|
|||||||
|
|
||||||
**Description:** Brief description of the theme and what occasion/season it's for.
|
**Description:** Brief description of the theme and what occasion/season it's for.
|
||||||
|
|
||||||
**Files Added:**
|
|
||||||
- `{themeName}.js`
|
|
||||||
- `{themeName}.css`
|
|
||||||
- `{themeName}_images/` (if applicable)
|
|
||||||
|
|
||||||
**Screenshot / Recording:**
|
**Screenshot / Recording:**
|
||||||
[Attach a screenshot or GIF here]
|
[Attach a screenshot or GIF showcasing the theme in action]
|
||||||
|
|
||||||
**Testing:**
|
**Testing:**
|
||||||
- Tested locally with test-site-new.html ✅
|
- Tested locally with test-site-new.html ✅
|
||||||
|
|||||||
Reference in New Issue
Block a user