diff --git a/README.md b/README.md index 9fae8532..4c9df95e 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| | [News Ticker](./plugins/news/) | RSS news headlines from ESPN, NCAA, custom sources | | -| [Of The Day](./plugins/of-the-day/) | Daily quotes, Bible verses, word of the day | | +| [Of The Day](./plugins/of-the-day/) | Daily quotes, Bible verses, word of the day | of-the-day on an LED panel | ### Integrations (2) diff --git a/docs/assets/of-the-day/auto-fit.png b/docs/assets/of-the-day/auto-fit.png new file mode 100644 index 00000000..9b0d79c7 Binary files /dev/null and b/docs/assets/of-the-day/auto-fit.png differ diff --git a/docs/assets/of-the-day/categories.png b/docs/assets/of-the-day/categories.png new file mode 100644 index 00000000..6840b7bf Binary files /dev/null and b/docs/assets/of-the-day/categories.png differ diff --git a/docs/assets/of-the-day/customization.png b/docs/assets/of-the-day/customization.png new file mode 100644 index 00000000..5255d8ef Binary files /dev/null and b/docs/assets/of-the-day/customization.png differ diff --git a/docs/assets/of-the-day/hero.png b/docs/assets/of-the-day/hero.png new file mode 100644 index 00000000..9daebdff Binary files /dev/null and b/docs/assets/of-the-day/hero.png differ diff --git a/docs/assets/of-the-day/panel-sizes.png b/docs/assets/of-the-day/panel-sizes.png new file mode 100644 index 00000000..d3928324 Binary files /dev/null and b/docs/assets/of-the-day/panel-sizes.png differ diff --git a/docs/assets/of-the-day/shots.json b/docs/assets/of-the-day/shots.json new file mode 100644 index 00000000..46217260 --- /dev/null +++ b/docs/assets/of-the-day/shots.json @@ -0,0 +1,277 @@ +{ + "plugin": "of-the-day", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-02T16:00:00+00:00", + "config": { + "enabled": true, + "categories": { + "word_of_the_day": { + "enabled": true, + "data_file": "of_the_day/word_of_the_day.json", + "display_name": "Word of the Day" + } + }, + "category_order": [ + "word_of_the_day" + ] + } + }, + "shots": [ + { + "name": "hero", + "height": 64, + "scale": 6 + }, + { + "name": "cat-english", + "config": { + "categories": { + "word_of_the_day": { + "enabled": true, + "data_file": "of_the_day/word_of_the_day.json", + "display_name": "Word of the Day" + } + }, + "category_order": [ + "word_of_the_day" + ] + }, + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "cat-slovenian", + "config": { + "categories": { + "slovenian_word_of_the_day": { + "enabled": true, + "data_file": "of_the_day/slovenian_word_of_the_day.json", + "display_name": "Slovenian Word of the Day" + } + }, + "category_order": [ + "slovenian_word_of_the_day" + ] + }, + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "fit-on", + "config": { + "auto_fit_text": true, + "customization": { + "body_text": { + "font_size": 12 + } + } + }, + "standalone": false, + "height": 64, + "scale": 6 + }, + { + "name": "fit-off", + "config": { + "auto_fit_text": false, + "customization": { + "body_text": { + "font_size": 12 + } + } + }, + "standalone": false, + "height": 64, + "scale": 6 + }, + { + "name": "col-default", + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "col-amber", + "config": { + "customization": { + "title_text": { + "text_color": [ + 255, + 176, + 0 + ] + }, + "body_text": { + "text_color": [ + 200, + 130, + 0 + ] + } + } + }, + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "col-cyan", + "config": { + "customization": { + "title_text": { + "text_color": [ + 0, + 229, + 255 + ] + }, + "body_text": { + "text_color": [ + 120, + 180, + 200 + ] + } + } + }, + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "col-big", + "config": { + "customization": { + "title_text": { + "font_size": 10 + }, + "body_text": { + "font_size": 8 + } + } + }, + "height": 64, + "scale": 6, + "standalone": false + }, + { + "name": "size-64x32", + "width": 64, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x32", + "width": 128, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x64", + "width": 128, + "height": 64, + "scale": 8, + "standalone": false + }, + { + "name": "size-256x32", + "width": 256, + "height": 32, + "scale": 4, + "standalone": false + } + ], + "composites": [ + { + "name": "categories", + "columns": 1, + "cells": [ + { + "shot": "cat-english", + "label": "word_of_the_day", + "sublabel": "the bundled English list, 365 entries" + }, + { + "shot": "cat-slovenian", + "label": "slovenian_word_of_the_day", + "sublabel": "the bundled Slovenian list" + } + ] + }, + { + "name": "auto-fit", + "columns": 1, + "cells": [ + { + "shot": "fit-on", + "label": "auto_fit_text: true", + "sublabel": "the default; body font_size 12 is stepped down until the whole definition fits" + }, + { + "shot": "fit-off", + "label": "auto_fit_text: false", + "sublabel": "held at 12, so the text is cut to the lines that fit and ellipsized" + } + ] + }, + { + "name": "customization", + "columns": 2, + "cells": [ + { + "shot": "col-default", + "label": "The defaults", + "sublabel": "white title, grey body" + }, + { + "shot": "col-amber", + "label": "Amber", + "sublabel": "title_text.text_color and body_text.text_color" + }, + { + "shot": "col-cyan", + "label": "Cyan", + "sublabel": "" + }, + { + "shot": "col-big", + "label": "Larger sizes", + "sublabel": "title_text.font_size 10, body_text.font_size 8" + } + ] + }, + { + "name": "panel-sizes", + "columns": 1, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32", + "sublabel": "only the title and a fragment fit" + }, + { + "shot": "size-128x32", + "label": "128 x 32", + "sublabel": "title plus one truncated body line" + }, + { + "shot": "size-128x64", + "label": "128 x 64", + "sublabel": "the definition wraps and fits in full" + }, + { + "shot": "size-256x32", + "label": "256 x 32", + "sublabel": "a long chain; width buys a longer body line" + } + ] + } + ] +} diff --git a/plugins.json b/plugins.json index f900d57c..e383ff41 100644 --- a/plugins.json +++ b/plugins.json @@ -653,10 +653,10 @@ "plugin_path": "plugins/of-the-day", "stars": 0, "downloads": 0, - "last_updated": "2026-08-06", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.4.1" + "latest_version": "1.4.2" }, { "id": "olympics", diff --git a/plugins/of-the-day/README.md b/plugins/of-the-day/README.md index 9300bf12..58f39801 100644 --- a/plugins/of-the-day/README.md +++ b/plugins/of-the-day/README.md @@ -1,286 +1,360 @@ -# Of The Day Display Plugin +# Of The Day -Display daily featured content like Word of the Day, Bible verses, or custom daily items with automatic rotation and multi-category support. +A different word, quote, verse or fact each day, from JSON files you control. +Ships with an English and a Slovenian word-of-the-day list, and takes any +number of your own. -## Features +![The word "Opportune" on a 128x64 panel, underlined, with its definition +wrapped below](../../docs/assets/of-the-day/hero.png) -- **Multiple Categories**: Word of the Day, Bible verses, custom content -- **Automatic Daily Updates**: Loads new content each day -- **Rotating Display**: Alternates between title and content -- **Multi-line Text Wrapping**: Handles long definitions -- **Configurable Data Sources**: Load from JSON files -- **Category Rotation**: Cycles through enabled categories +*Every image in this README is real plugin output, rendered at the true panel +size against a frozen clock — "Opportune" is genuinely the entry for 2 September +in the bundled list. Nothing here is a mockup.* -## Configuration +--- -### Web UI Configuration +## Table of Contents -This plugin is fully configurable through the LEDMatrix web interface: +1. [What's On Screen](#whats-on-screen) +2. [Installation](#installation) +3. [Adding a Category](#adding-a-category) +4. [The Data File Format](#the-data-file-format) +5. [Configuration Reference](#configuration-reference) + - [Rotation and timing](#rotation-and-timing) + - [auto_fit_text](#auto_fit_text) + - [Fonts and colours](#fonts-and-colours) +6. [Panel Sizes](#panel-sizes) +7. [Troubleshooting](#troubleshooting) +8. [Development](#development) +9. [Support](#support) -1. Open the LEDMatrix web interface (`http://your-pi-ip:5000`) -2. Open the **Plugin Manager** tab and install **Of The Day Display** - from the **Plugin Store** section if it isn't already -3. Open the **Of The Day Display** tab in the second nav row -4. Adjust settings using the auto-generated form (enable/disable - categories, update intervals, rotation timings, display duration) -5. Click **Save** +--- -The web UI form is generated directly from -[`config_schema.json`](config_schema.json), including the nested -category configurations with collapsible sections. +## What's On Screen -### Example Configuration +A title, underlined, with body text beneath it: -```json -{ - "enabled": true, - "update_interval": 3600, - "display_rotate_interval": 20, - "subtitle_rotate_interval": 10, - "category_order": ["word_of_the_day", "bible_verse_of_the_day"], - "categories": { - "word_of_the_day": { - "enabled": true, - "data_file": "of_the_day/word_of_the_day.json", - "display_name": "Word of the Day" - }, - "bible_verse_of_the_day": { - "enabled": false, - "data_file": "of_the_day/bible_verse_of_the_day.json", - "display_name": "Bible Verse" - } - }, - "display_duration": 40 -} +```text + Opportune <- title, from the entry's "title" +────────────────── + Well-chosen or <- body, from "subtitle" then "description" +favorable or appropriate ``` -### Configuration Options +The body rotates between the entry's `subtitle` and `description` every +`display_rotate_interval` seconds, so a definition and an example sentence both +get their turn without needing a taller panel. -- `enabled`: Enable/disable the plugin -- `update_interval`: Seconds between checking for new day (default: 3600) -- `display_rotate_interval`: Seconds between category rotations (default: 20) -- `subtitle_rotate_interval`: Seconds between title/content rotation (default: 10) -- `auto_fit_text`: Shrink the body font automatically when a long definition or subtitle can't fit the panel at the configured size (default: true). Wrapping is font-aware either way — line breaks and line count follow the actual font metrics, so custom fonts/sizes wrap correctly. Text that can't fit even at the smallest size is shortened with `...` -- `category_order`: Order to display categories -- `categories`: Dictionary of category configurations -- `display_duration`: Total display duration in seconds +With more than one category configured, the plugin also rotates between +categories — a word today, a quote next turn — in `category_order`. -## Plugin Structure +--- -This plugin is fully self-contained. All data files are stored within the plugin directory: +## Installation -``` -of-the-day/ -├── manifest.json -├── config_schema.json -├── manager.py -├── requirements.txt -├── README.md -└── of_the_day/ # Data directory - ├── word_of_the_day.json - ├── slovenian_word_of_the_day.json - └── bible_verse_of_the_day.json -``` +**From the Plugin Store (recommended).** Open the LEDMatrix web interface at +`http://:5000`, go to **Plugin Manager**, find **Of The Day** in the +**Plugin Store** section, and click **Install**. -When you download/install this plugin, everything it needs is included. When you delete it, no traces remain. +**Manually.** Copy this directory into your LEDMatrix `plugin-repos/` and +restart the display service. -## Data File Format +> **A fresh install shows "No Data".** Two things are off by default: +> `enabled` is `false`, and — more surprisingly — **the `categories` block is +> empty**. The bundled data files exist, but nothing points at them until you +> add a category. See below. -Each category uses a JSON file with daily entries: +--- -### Word of the Day Format +## Adding a Category -```json -{ - "2025-10-11": { - "word": "Ephemeral", - "pronunciation": "ih-FEM-er-uhl", - "type": "adjective", - "definition": "Lasting for a very short time; transitory", - "example": "The beauty of cherry blossoms is ephemeral, lasting only a week or two." - } -} -``` +The easiest route is the plugin's own tab in the web interface, which lists the +bundled files, lets you upload your own, and writes the config for you. -### Bible Verse Format +By hand, a category is an entry in the `categories` object keyed by an id of +your choosing: ```json { - "2025-10-11": { - "title": "John 3:16", - "content": "For God so loved the world that he gave his one and only Son...", - "reference": "John 3:16 (NIV)" + "of-the-day": { + "enabled": true, + "categories": { + "word_of_the_day": { + "enabled": true, + "data_file": "of_the_day/word_of_the_day.json", + "display_name": "Word of the Day" + } + }, + "category_order": ["word_of_the_day"] } } ``` -### Custom Format +| Key | Type | Default | What it does | +|-----|------|---------|--------------| +| `enabled` | boolean | `true` | Include this category in the rotation | +| `data_file` | string | — | Path to the JSON file, relative to the plugin directory | +| `display_name` | string | — | Label shown in the configuration UI | + +Two lists ship with the plugin: -You can create custom categories with any structure containing: -- `word` or `title`: Main heading -- `definition` or `content` or `text`: Main content -- Optional: `pronunciation`, `type`, `example`, etc. +![Two panels: the English word of the day, and the Slovenian +one](../../docs/assets/of-the-day/categories.png) -## Usage +| File | Contents | +|------|----------| +| `of_the_day/word_of_the_day.json` | An English word a day, with definition and example | +| `of_the_day/slovenian_word_of_the_day.json` | A Slovenian word a day, with its English meaning | -### Adding New Categories +`category_order` controls the order categories are shown in. A category listed +in `category_order` but missing from `categories` is skipped, and one present +in `categories` but absent from `category_order` is appended after the +listed ones. -1. Create a JSON data file in the plugin's `of_the_day/` directory - - Path: `plugins/of-the-day/of_the_day/my_custom.json` -2. Add entries for each date in YYYY-MM-DD format -3. Add category to config: +--- + +## The Data File Format + +A data file is a JSON object keyed by **day of the year as a string**, `"1"` +through `"365"`: ```json { - "categories": { - "my_custom_category": { - "enabled": true, - "data_file": "of_the_day/my_custom.json", - "display_name": "My Daily Item" - } + "1": { + "title": "Abstruse", + "subtitle": "Difficult to understand; obscure", + "description": "The philosopher's work was filled with abstruse concepts." }, - "category_order": ["my_custom_category", "word_of_the_day"] + "245": { + "title": "Opportune", + "subtitle": "Well-chosen or particularly favorable or appropriate", + "description": "She waited for an opportune moment to raise the subject." + } } ``` -### Display Behavior +| Field | Shown as | +|-------|----------| +| `title` | The underlined heading | +| `subtitle` | The first body line — a definition or short gloss | +| `description` | The second body line, rotated in after `display_rotate_interval` | + +Selection is by calendar date, not random: day 245 of any year shows entry +`"245"`. That makes the display stable — it will not change halfway through the +day — and means a file needs 365 entries for full coverage. Missing days are +skipped rather than erroring. + +Because it is keyed by day rather than date, the same file works every year. +For a leap year, day 366 has no entry unless you add one. + +> Earlier documentation described these files as keyed by `YYYY-MM-DD`. They are +> not — the bundled files and the loader both use the day-of-year number. A file +> written with date keys will load without error and then show nothing, because +> no key ever matches. + +### Writing your own list + +- **Keep the subtitle short.** It is the line most often shown, and on a 128×32 + panel roughly 24 characters survive before truncation. The description can be + longer since it only needs to fit when it takes its turn. +- **Use the same fields throughout.** An entry missing `subtitle` or + `description` simply shows less; it does not fall back to another field. +- **Cover all 365 days** if you want the category to appear every day. Gaps are + skipped, so a sparse file means the category quietly disappears on the missing + days. +- **Generate rather than type.** The format is a flat JSON object, so a + dictionary API, an RSS feed or a spreadsheet export can all be turned into one + with a short script. Nothing in the plugin cares how the file was produced — + drop the result in via the file manager. + +--- + +## Configuration Reference + +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `enabled` | boolean | `false` | Whether the plugin runs at all | +| `categories` | object | *(empty)* | Your categories — see [above](#adding-a-category) | +| `category_order` | array | `["word_of_the_day", "slovenian_word_of_the_day"]` | Display order | +| `display_duration` | number | `40` | Seconds each category holds the panel | +| `display_rotate_interval` | number | `20` | Seconds between body elements within a category | +| `subtitle_rotate_interval` | number | `10` | Seconds between subtitle variants | +| `update_interval` | integer | `3600` | Seconds between checks for a new day | +| `auto_fit_text` | boolean | `true` | Shrink the body font so long text fits | +| `customization` | object | *(defaults)* | Fonts, sizes, colours and offsets | +| `file_manager` | — | — | Not a setting; the data-file widget in the web UI | + +### Rotation and timing + +Three intervals stack, from slowest to fastest: + +- **`display_duration`** (default `40`) — how long the whole category holds the + panel before the display controller moves on. +- **`display_rotate_interval`** (default `20`) — within that turn, how often the + body swaps between the entry's subtitle and description. At the defaults, a + 40-second turn shows each of the two for 20 seconds. +- **`subtitle_rotate_interval`** (default `10`) — how often the subtitle line + itself cycles, for entries carrying more than one. + +If you shorten `display_duration` below `display_rotate_interval`, the second +body element never appears — the turn ends first. + +`update_interval` is only a check for the date rolling over. Since entries +change once a day, the hourly default is already far more often than needed. + +### `auto_fit_text` + +When the body text will not fit at the configured size, this steps the font +down until it does — largest size that fits wins. Only scalable fonts can +shrink; a bitmap font is left alone. Text that still cannot fit at the smallest +size is cut to the lines that fit, with the last one ellipsized. + +![Two 128x64 panels with body font_size 12: with auto-fit the definition +shrinks and fits in three lines, without it the text is cut and ends in an +ellipsis](../../docs/assets/of-the-day/auto-fit.png) + +It has no visible effect when the text already fits, or when the panel has room +for only one body line whatever the size — on a 128×32 panel the result is the +same either way. It earns its keep on taller panels and with larger body fonts. + +### Fonts and colours + +Two elements are styled independently under `customization`: `title_text` and +`body_text`. + +![Four 128x64 panels: the default white-on-grey, an amber scheme, a cyan +scheme, and larger title and body +sizes](../../docs/assets/of-the-day/customization.png) + +| Element | `font` | `font_size` | `text_color` | +|---------|--------|-------------|--------------| +| `title_text` | `PressStart2P-Regular.ttf` | `8` (4–16) | `[255, 255, 255]` | +| `body_text` | `4x6-font.ttf` | `6` (4–12) | `[200, 200, 200]` | + +```json +{ + "customization": { + "title_text": { "text_color": [255, 176, 0], "font_size": 10 }, + "body_text": { "text_color": [200, 130, 0], "font_size": 8 } + } +} +``` -The plugin rotates through: -1. **Category rotation**: Changes every `display_rotate_interval` seconds -2. **Content rotation**: Within each category, alternates between: - - Title/word display with category name - - Definition/content display with word wrapping +> **The key names are `font_size` and `text_color`.** The schema declares these +> elements in an `x-style-elements` block that uses the short names `size` and +> `color`, but that is the *declaration* format — the core expands it into the +> config properties `font`, `font_size` and `text_color`. Writing `size` or +> `color` in your config is silently ignored, with no warning and no visible +> change. It is an easy mistake to make from reading the schema. -### Built-in Categories +Both elements also accept `x_offset` and `y_offset` for nudging position. -**word_of_the_day** -- Displays vocabulary words with definitions -- Shows pronunciation and part of speech -- Great for learning and education +Customization needs a LEDMatrix core with the element-style system. On an older +core the section is not offered and the classic styling above is used, which is +why an untouched config renders identically either way. -**bible_verse_of_the_day** -- Displays daily Bible verses -- Shows reference and translation -- Perfect for inspiration +--- -**slovenian_word_of_the_day** -- Example of foreign language words -- Can be adapted for any language +## Panel Sizes -## Plugin Isolation +![The same word on 64x32, 128x32, 128x64 and 256x32 +panels](../../docs/assets/of-the-day/panel-sizes.png) -This plugin is designed to be completely self-contained: +Height matters far more than width here, because the body wraps: -- ✅ All data files stored in plugin directory -- ✅ No external dependencies on main project structure -- ✅ Can be installed/uninstalled cleanly -- ✅ No traces left after deletion -- ✅ Easy to backup (just copy the plugin folder) -- ✅ Easy to share (distribute the entire plugin directory) +- **64×32** fits the title and a fragment of the definition. +- **128×32** fits the title and one truncated body line. +- **128×64** is where the plugin comes into its own — the definition wraps and + fits in full. +- **256×32** buys a longer single body line, but is still one line. -Data files are automatically loaded from `of_the_day/` subdirectory within the plugin. +If the definitions matter to you, 64 rows is worth more than 256 columns. -## Creating Data Files +--- -### Template Structure +## Troubleshooting -```json -{ - "2025-01-01": { - "word": "Example", - "definition": "A representative form or pattern", - "pronunciation": "ig-ZAM-puhl", - "type": "noun", - "example": "This is an example sentence." - }, - "2025-01-02": { - "word": "Another", - "definition": "One more; an additional", - ... - } -} -``` +**It says "No Data".** +The most likely cause is that no category is configured — the `categories` +block is empty on a fresh install even though the data files ship with the +plugin. Add one from the plugin's tab in the web UI, or by hand as +[shown above](#adding-a-category). -### Tips for Creating Content +**Nothing appears at all.** +`enabled` defaults to `false`. -1. **Date Format**: Always use YYYY-MM-DD -2. **Keep it Short**: Definitions should fit on 4-5 lines -3. **Clear Pronunciation**: Use phonetic spelling -4. **Good Examples**: Make them memorable and relevant -5. **Consistent Structure**: Use same fields across all entries +**A category is configured but never shows.** +Check its own `enabled` flag, that `data_file` resolves relative to the plugin +directory, and that the file has an entry for today's day-of-year number. -## Display Examples +**The definition is cut off.** +That is the panel height. Turn on `auto_fit_text` (it is on by default), reduce +`body_text.font_size`, or use a taller panel — see +[Panel Sizes](#panel-sizes). -### Title Display -``` - Word of the Day - - Ephemeral - - (ih-FEM-er-uhl) -``` +**I changed a colour or size and nothing happened.** +Check the key names: they are `font_size` and `text_color`, not `size` and +`color`. See [Fonts and colours](#fonts-and-colours). -### Content Display -``` -Lasting for a very -short time; transitory -The beauty of cherry -blossoms is ephemeral -``` +**The example sentence never appears.** +`display_duration` is probably shorter than `display_rotate_interval`, so the +turn ends before the body rotates. Raise the former or lower the latter. -## Advanced Usage +**The word did not change at midnight.** +`update_interval` decides how often the date is re-checked; at the default it +can be up to an hour late. -### Multiple Data Sources +--- -You can maintain separate data files for different purposes: -- Educational words -- Technical terms -- Inspirational quotes -- Historical facts -- Daily trivia +## Development -### Automated Updates +### Project structure -Data files can be updated automatically: -- From APIs (Merriam-Webster, etc.) -- From RSS feeds -- From custom scripts -- From database exports +```text +of-the-day/ +├── manifest.json # Plugin metadata and version history +├── manager.py # OfTheDayPlugin +├── config_schema.json # Settings schema; source of truth for defaults +├── of_the_day/ # Bundled data files +│ ├── word_of_the_day.json +│ └── slovenian_word_of_the_day.json +├── scripts/ # Backend actions for the web UI file manager +├── web_ui/ # The file-manager interface +├── test/ # Harness config and golden images +└── README.md +``` -## Troubleshooting +The `scripts/` directory holds the actions the file manager calls — +`list_files`, `get_file`, `save_file`, `create_file`, `delete_file`, +`upload_file`, `toggle_category` and `update_config`. They are invoked by the +web UI rather than run directly. -**No content displayed:** -- Verify data file path is correct -- Check that today's date exists in JSON file -- Ensure category is enabled in config -- Check file permissions +### Tests -**Content cut off:** -- Reduce text length in data file -- Adjust display dimensions -- Shorten definitions or examples +```bash +python plugins/of-the-day/test_text_fitting.py +python plugins/of-the-day/test_element_styles.py +``` -**Category not showing:** -- Verify it's in `category_order` -- Check that `enabled` is `true` -- Ensure data file exists and is valid JSON +### Regenerating the images in this README -**Wrong date content:** -- Check system date/time -- Verify date format in JSON (YYYY-MM-DD) -- Check timezone settings +```bash +python scripts/render_docs_assets.py --plugin of-the-day +``` -## Integration with Education +`--check` verifies the committed images still match what the plugin renders. +The clock is frozen in the shot list, which is what pins the entry to +"Opportune" — without that the images would change daily. -Perfect for: -- Vocabulary building -- Language learning -- Daily inspiration -- Historical facts -- Science concepts -- Programming terms +--- -## License +## Support -GPL-3.0 License - see main LEDMatrix repository for details. +- YouTube: +- Instagram: +- Discord: +- Sponsor: [GitHub Sponsors](https://github.com/sponsors/ChuckBuilds) · + [Buy Me a Coffee](https://buymeacoffee.com/chuckbuilds) · + [Ko-fi](https://ko-fi.com/chuckbuilds/) +Released under the GNU General Public License v3.0 — see [LICENSE](LICENSE). diff --git a/plugins/of-the-day/manifest.json b/plugins/of-the-day/manifest.json index 61c589a4..c8fcf6c4 100644 --- a/plugins/of-the-day/manifest.json +++ b/plugins/of-the-day/manifest.json @@ -1,7 +1,7 @@ { "id": "of-the-day", "name": "Of The Day Display", - "version": "1.4.1", + "version": "1.4.2", "author": "ChuckBuilds", "description": "Display daily featured content like Word of the Day, Bible verses, or custom daily items. Supports multiple categories with rotating display and configurable data sources.", "category": "information", @@ -21,6 +21,13 @@ "of_the_day" ], "versions": [ + { + "version": "1.4.2", + "released": "2026-09-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Documentation only, no behaviour change. Rewrites the README around real rendered screenshots and documents all 13 settings. Leads with the reason a fresh install shows 'No Data': the categories block is empty by default, so the bundled data files exist but nothing points at them until a category is added. Corrects the data-file format, which the old README described as keyed by YYYY-MM-DD -- the files and the loader both use the day-of-year number, so a file written with date keys loads cleanly and then shows nothing. Documents that the customization keys are font_size and text_color, not the size and color used by the schema's x-style-elements declaration; the short names are silently ignored. Also separates the three rotation intervals and notes that auto_fit_text only has a visible effect where shrinking can gain a line.", + "changelog": "Documentation only, no behaviour change. Rewrites the README around real rendered screenshots and documents all 13 settings. Leads with the reason a fresh install shows 'No Data': the categories block is empty by default, so the bundled data files exist but nothing points at them until a category is added. Corrects the data-file format, which the old README described as keyed by YYYY-MM-DD -- the files and the loader both use the day-of-year number, so a file written with date keys loads cleanly and then shows nothing. Documents that the customization keys are font_size and text_color, not the size and color used by the schema's x-style-elements declaration; the short names are silently ignored. Also separates the three rotation intervals and notes that auto_fit_text only has a visible effect where shrinking can gain a line." + }, { "version": "1.4.1", "released": "2026-08-06", @@ -66,7 +73,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-08-06", + "last_updated": "2026-09-02", "stars": 0, "downloads": 0, "verified": true,