Package Exports
- tiny-mood
- tiny-mood/browser
Readme
tiny-mood
Generate deterministic, mood-driven background colors from text — no images, no LLM calls, no network requests at runtime. Just a word table and OKLCH color math.
Feed it a blog post's title and description, get back a CSS background gradient that reflects the tone of the text — calmer posts render softer and cooler, energetic posts render brighter and warmer — optionally clamped to your own brand colors so it never looks off-brand.
What it is
tiny-mood looks up each word in your text against a small mood table (word → weight/energy/warmth), averages the matches into a single mood vector, then maps that vector onto an OKLCH color palette. The result is rendered as a soft, blurred gradient background — entirely deterministic, so the same text and seed always produce the same output.
The mood table itself is just JSON. You can use the small built-in default, or generate your own from your actual content using tiny-mood-generate, a separate companion CLI that builds a table from your real vocabulary using GloVe word embeddings.
Install
npm install tiny-moodQuick start
import { getMoodBackground } from 'tiny-mood'
import moodTable from './mood-table.json' // generated by tiny-mood-generate, or your own
const post = {
title: 'Announcing our new release',
description: 'A fast, exciting launch into something new',
slug: 'announcing-release' // used as the seed for stable, repeatable layout
}
const { background, filter, mood } = getMoodBackground(
`${post.title} ${post.description}`,
moodTable,
post.slug
)
// background and filter are ready-to-use CSS values:
// <div style={{ background, filter }} />Examples
Basic usage, no brand constraint
const { background, filter } = getMoodBackground(text, moodTable, seed)Hue varies freely based on the text's warmth — calm/cold text trends toward blues and greens, warm/energetic text trends toward oranges and reds.
Clamped to your brand colors
import { createBrandPalette, getMoodVector, composeBackground } from 'tiny-mood'
const brand = createBrandPalette({
colors: ['#0f380f', '#306230', '#8bac0f', '#9bbc0f'] // any hex/rgb()/hsl() strings
})
const mood = getMoodVector(text, moodTable)
const palette = brand.paletteFor(mood)
const { background, filter } = composeBackground(palette, seed)Mood now picks a position along the real spread of hues in your brand colors, rather than inventing a color outside your palette. A calm post and an energetic post will land in genuinely different parts of your brand's color range, not just vary in lightness.
One-liner with brand colors
const { background, filter } = getMoodBackground(text, moodTable, seed, {
colors: ['#0f380f', '#306230', '#8bac0f', '#9bbc0f']
})Same effect as above, without the intermediate steps — pass colors directly and getMoodBackground derives the brand palette internally.
Controlling the visual shape
const { background, filter } = getMoodBackground(text, moodTable, seed, {
colors: brandColors,
blendShape: 'linear', // 'round' | 'linear' | 'spiral'
blendIntensity: 0.7, // 0–1, how strongly the shape expresses
blendAngleRange: [100, 160] // direction guardrail, degrees
})round (the default) produces soft, organic blooms. linear produces diagonal color streaks. spiral produces sharper conic wedge-rays — a different visual register from the other two, intentionally less blurred so its structure stays visible.
Functions
getMoodBackground(text, table, seed?, options?)
The main convenience function. Extracts a mood vector from text using table, generates a palette (brand-clamped if options.colors is provided, otherwise free-hue), and composes it into CSS.
Returns { background, filter, mood }.
getMoodVector(text, table)
Extracts just the mood vector, if you want it without rendering anything.
Returns { weight, energy, warmth }, each roughly in the range -1 to 1.
createBrandPalette(options)
Builds a reusable palette generator from your brand colors.
const brand = createBrandPalette({ colors: [...] })
brand.paletteFor(mood) // -> Oklch[]
brand.sortedColors // your colors, sorted by hue, for inspectioncomposeBackground(palette, seed, options?)
Lower-level: takes an array of OKLCH colors and a seed, returns the final { background, filter } CSS strings. Use this directly if you're building your own palette logic instead of createBrandPalette.
Parameters reference
| Parameter | Where | Type | Default | What it does |
|---|---|---|---|---|
colors |
getMoodBackground options, createBrandPalette |
string[] |
— | Your brand colors (hex, rgb()/rgba(), hsl()/hsla()). Omit for free-hue output. |
blendShape |
getMoodBackground options, composeBackground options |
'round' | 'linear' | 'spiral' |
'round' |
The visual form of each color region. |
blendIntensity |
same | number (0–1) |
0 |
How strongly the selected shape expresses. 0 always looks like round regardless of blendShape. |
blendAngleRange |
same | [number, number] |
[100, 160] |
Degree range streak/spiral direction is randomly drawn from per post. Has no effect when blendShape is 'round'. |
blobCount |
composeBackground options |
number |
5 |
Number of color regions composited together. |
blurPx |
composeBackground options |
number |
70 |
Base blur radius. Actual blur is reduced automatically as blendIntensity rises, more aggressively for spiral than linear. |
blobAlpha |
composeBackground options |
number |
0.7 |
Opacity of each color region before blending. |
moodToPosition |
createBrandPalette options |
(mood) => number |
warmth-weighted blend | Override how the 3-axis mood vector becomes a single position along your sorted brand colors. |
Where the mood table comes from
tiny-mood itself ships with a small default table, but the better option for a real project is generating one from your actual content with tiny-mood-generate:
npx tiny-mood-generate ./content ./mood-table.jsonThis reads your blog posts, looks up each word's real semantic position using GloVe embeddings, and writes a small JSON table containing only the words your content actually uses. tiny-mood's runtime has no dependency on GloVe or any generation tooling — it only ever reads the resulting JSON.
Try it live
StackBlitz demo — tabbed examples with brand presets, blend shapes, and an interactive Blend Lab.
License
MIT