The Positive Affirmation Generator combines a predefined content library with a small JavaScript application. It selects a message, updates the page, remembers saved favorites, and draws a downloadable image using features built into the browser.
The selection logic runs on your device. It chooses existing entries rather than asking an AI service to write new sentences. That makes the wording predictable while allowing the selected result to vary with each click.
- 660
- predefined affirmations
- 4
- selection modes
- 1080 × 1080
- pixels per image card
01 / Content
A structured library gives every message a place.
The current library contains eleven categories with 60 entries each: Confidence, Motivation, Stress, Work, Relationships, Faith, Healing, Joy, Self-Discipline, Business, and Leadership. The text lives inside the page’s JavaScript, so choosing an affirmation does not require a separate content request.
Most categories hold arrays of strings. Faith entries hold an affirmation and an accompanying Bible verse. The normalizeItem() function turns both formats into the same object structure:
{
id: "confidence-0",
category: "confidence",
text: "I can enter the room as myself and still be enough.",
verse: ""
}
The shared shape means the selection, favorites, and image functions can work with any category. An empty verse simply means there is no verse to display. The ID combines the category and its zero-based position in the array.
Random is a combined pool, not an extra content category. It draws from the ten non-Faith categories, giving it 600 possible entries. Faith is included when the visitor explicitly selects that category.
02 / Selection
The category sets the pool. The mode changes the selection.
The getPool() function collects eligible entries. Then pickOne() filters any IDs already used in the current set and chooses a random array position with Math.random().
function pickOne(category, usedIds) {
const pool = getPool(category).filter(function(item) {
return !usedIds.includes(item.id);
});
const available = pool.length ? pool : getPool(category);
return available[Math.floor(Math.random() * available.length)];
}
Multiplying by the pool length and rounding down produces a valid zero-based index. If filtering leaves no entries, the function falls back to the full category pool. For the current 60-entry categories, a three-item selection has plenty of unused entries.
| Mode | Selection behavior |
|---|---|
| New Affirmation | Picks one entry from the 600-entry non-Faith pool. |
| Morning Affirmation | Randomly chooses Confidence, Motivation, Work, Self-Discipline, Business, or Leadership, then picks one entry from that category. |
| I Need This Right Now | Randomly chooses Stress, Healing, or Confidence, then picks one entry from that category. |
| Daily 3 Affirmations | Picks three entries from the non-Faith pool, excluding IDs already chosen in that set. |
An explicit category takes priority. If you select Joy and then Morning Affirmation, the result still comes from Joy. Daily 3 with Faith selected produces three distinct Faith entries.
03 / Application Flow
One state object keeps the controls and output connected.
The application keeps its working information in state: the selected category, the current mode and color, one current entry, a currentList for Daily 3, and the favorites collection.
- ChooseA category, a mode, or Generate
- SelectResolve the pool and pick one or three entries
- RenderUpdate the text and redraw the image
- RememberSave the category and mode preferences
Both category and mode buttons generate a result immediately. The central generate() function updates state, calls renderResult(), calls drawCard(), and saves preferences. The page also generates an initial result when it loads.
The visible text and the image are separate outputs. Regular text updates use textContent. When the app constructs HTML for lists and favorites, escapeHtml() replaces characters such as angle brackets and quotation marks before inserting the values.
The result supports Enter and Space to open its image preview. Status areas use aria-live="polite" to make changes available to assistive technology, and category and mode controls update aria-checked to reflect the selection.
Category links can set the starting point.
A link with ?category=stress or #confidence can open the tool with a category selected. A valid URL category takes priority over the saved category. The saved mode is restored separately. These links select a focus, not a particular sentence.
04 / Persistence
Favorites are saved as data, not finished pictures.
The tool uses localStorage, a browser feature that stores strings for a website’s origin. JSON.stringify() converts the JavaScript objects into stored text; JSON.parse() reconstructs them when the page loads. MDN explains how this storage persists across browser sessions.
sidney-vega-affirmation-preferences- The selected category, mode, and color.
sidney-vega-affirmation-favorites- An array of saved entries, including each entry’s ID, category, text, and optional verse.
The heart button checks the current entry’s ID. An unsaved entry is added to the beginning of the favorites array; an existing favorite is removed. Opening a favorite switches to single mode, restores its text and category, redraws the card, and opens the preview.
Because the stored favorite is text data, reopening it uses the current card design. The ID depends on the entry’s array position, so reordering the library can change which entry an ID identifies. Keeping those IDs stable matters when maintaining the content.
There is no account synchronization in this implementation. Clearing site data can remove favorites, and private browsing storage is temporary. Directly opened file: pages can behave differently across browsers. Storage reads have a fallback for access or parsing errors, while failed writes are caught; a blocked write can still leave a favorite visible for the current session without preserving it after reload.
05 / Graphics
Canvas turns the current selection into a square image.
The page includes a <canvas> with an internal resolution of 1080 × 1080 pixels. JavaScript obtains a 2D drawing context and paints the card in layers: the chosen background color, decorative circles, rounded inner panel, category heading, affirmation text, and the website address. Orange is the default.
This is a separate drawing, not a screenshot of the page. The Colors panel offers the Bible reader’s eight presets, Orange, and a custom color picker. applyAffirmationColor() validates a six-digit hex color, updates the tool’s accents, saves the preference, and redraws the existing result. Reset restores orange. Opening a favorite uses the currently selected color.
drawCard() blends the chosen color with 94% white for the inner panel and adjusts the heading color for contrast on that panel. Text on the solid color background stays white, including the card’s website address and the tool’s active controls. The preview and PNG download use the same drawing. Window size and the site’s light and dark modes do not change the selected card color.
Canvas does not automatically lay out paragraphs like HTML. The custom wrapText() function adds words to a candidate line and measures its width with measureText(). When the line is too wide, it starts the next one. It then draws each line with fillText() and returns the total line height.
Single cards use larger affirmation text and position an optional verse below it. Daily 3 uses smaller, numbered text. The current Daily 3 image layout includes the three affirmations but omits their verses, even though Faith verses remain visible in the on-page list and copied text.
The website address at the bottom provides the card’s branding. Since the layout uses fixed font sizes and coordinates, longer future entries would need checking for fit.
06 / Output
The same result can become text or a PNG file.
Copying the words
renderedText() assembles either one affirmation or a numbered Daily 3 list, including verses when present. The app passes that text to navigator.clipboard.writeText() when available. If the API is absent, it uses a temporary text area and an older copy command. The modern API depends on browser security and permission rules; the fallback is not a retry for a rejected permission request.
Previewing the card
Opening the preview converts the canvas into a PNG data URL with toDataURL() and places it in an image inside a dialog. Closing the preview returns focus to the result card.
Downloading the image
Saving calls canvas.toBlob() to create PNG file data. A temporary object URL connects that data to a download link. After triggering the download, the code removes the link and revokes the object URL after one second.
The filename follows Sidney-Vega-Affirmation-123456.png, with a randomly chosen six-digit suffix. The suffix uses crypto.getRandomValues() when available, otherwise Math.random(). It is a filename convenience, not an affirmation ID or a guarantee of uniqueness.
The Design in Practice
Small parts handle distinct jobs.
The content library supplies the words. Selection functions choose entries. State connects the controls to the result. Browser storage remembers favorites, while canvas creates a portable image. The generator does not need a custom backend for those operations.
That describes the tool’s processing, not a promise that the entire webpage makes no network requests. The surrounding page also loads Google Analytics, external fonts, and an icon stylesheet. Local favorites and on-device rendering are separate from those page resources.
This separation makes the tool understandable to maintain: content can be edited in the library, selection rules in a few functions, and the card design in its drawing routine. The result is a focused example of how standard browser features can turn a simple idea into something people can use and keep.
See the process in action.
Choose a category, generate a result, and open the card preview. For ideas on using the tool, read Using the Positive Affirmation Generator with intention.
Get Started