Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.
Knowledge base

Things that cost me a day, so they cost you none

Working notes from building Invision Community and Flarum applications. Mostly the failures that give no error at all — the ones where everything installs cleanly and quietly does the wrong thing.

92 articles

Invision Community 5

86 articles

Extensions and contracts

39

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

Languages and text

5

The string table, translation, and the places where text does not appear where you expected it to.

Theming, templates and forms

9

Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.

Background work and scheduled tasks

5

The queue system, work that has to happen after the response, and jobs that finish without doing anything.

Data, settings and storage

11

The database layer, settings, tags, file storage, and backing up a live site.

AI features and expectations

5

What these features do, what they cost, and what buyers reasonably but wrongly assume they do.

Application structure and releases

11

The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.

Realtime, chat and calls

1

WebSocket gateways, relays and the server-side pieces live features depend on — where "it works when I test it" and "it works for your members" are different claims.

Nothing matches that.

Custom CSS that works in both colour schemes, and the var() name trap

Invision Community 5 renders in a light or a dark scheme. Custom CSS written while looking at one of them usually breaks in the other, and the way it breaks is quiet: nothing errors, nothing logs, and the developer's own screen looks correct.

How the two schemes work

Core declares its palette twice: light values at :root, then overrides under [data-ips-scheme="dark"]. The attribute is written onto <html> from front/global/htmlDataAttributes.phtml and can be light, dark, or system. When it is system, script resolves the OS preference and rewrites the attribute; data-ips-scheme-active keeps the unresolved choice.

So light is the default and dark is the override — not the other way round.

🚨 The trap: a wrong variable name is not an error

This is the one that will cost you a day.

/* Looks scheme-aware. Is not. */
background: var(--i-box--background, #171b21);

--i-box--background does not exist. The correct name is --i-box--ba-co. When a custom property is undefined, var() quietly uses the fallback — so this rule paints #171b21 in both schemes. On dark it looks perfect. On light you get black cards on a white page, and because you wrote a fallback you will be certain the rule is scheme-aware and go looking somewhere else.

The abbreviations are not guessable, which is exactly why this happens. Verified names:

--i-box--ba-cobox background
--i-box--bo-cobox border colour
--i-box--bo-rabox radius
--i-boxHeader--ba-cobox header background
--i-background_1--i-background_6surface ramp, 1 = page, 6 = strongest
--i-color_hard / --i-color_softprimary / secondary text
--i-color_primary, --i-primaryaccent
--i-design-radiustheme radius setting

Names that look right and are wrong: --i-box--background, --i-box--border, --i-border, --i-background-color_1, --i-background-color_2.

Check before you rely on one. In the browser console:

getComputedStyle(document.documentElement).getPropertyValue('--i-box--ba-co')
// empty string means the property does not exist and your fallback is what renders

Worth a build-time sweep too: grep your CSS for --i-[a-z0-9_-]+ and compare against a known-good list. Finding this by eye across a catalogue of applications does not happen.

Declare your own tokens, twice

Rather than duplicating every rule per scheme, mirror what core does — but with your own prefix. Never redefine core's --i-* variables at :root; every component that depends on them changes underneath you.

:root{
	/* On light a "raised surface" highlight has to be a DARK tint — a white
	   highlight on a white card is invisible. */
	--ed-lift: rgba(15, 23, 42, .035);
	--ed-hairline: rgba(15, 23, 42, .12);
	/* Light needs far less shadow opacity or it reads as soot. */
	--ed-shadow: rgba(15, 23, 42, .16);
	--ed-art-bg: #e9eef4;
	--ed-code-bg: #f6f8fa;
}

[data-ips-scheme="dark"]{
	--ed-lift: rgba(255, 255, 255, .055);
	--ed-hairline: rgba(255, 255, 255, .10);
	--ed-shadow: rgba(0, 0, 0, .85);
	--ed-art-bg: #0f141b;
	--ed-code-bg: #0d1117;
}

Two habits worth keeping: rgba(255,255,255,x) for hairlines and highlights is a dark-only assumption, and heavy rgba(0,0,0,.85) shadows that read as depth on dark read as dirt on light.

Not everything should follow the scheme

Some surfaces carry their own ground and must stay fixed. Tokenising these is a regression, not a fix:

  • A header band that is always a brand gradient — white text on it is correct in both schemes.
  • A chip or badge sitting on top of a photograph or cover art, with its own dark backing.
  • A darkening tint over a header photograph. Lighten it in light mode and the texture washes out and the logo stops standing off the background.

Comment those, or the next person to "fix" the scheme handling will flatten them.

Testing it: the attribute is set once

🚨 data-ips-scheme is written at page load. Changing your OS scheme in another window does not update it — the stale value stays and your CSS appears broken when it is fine. Reload after every scheme change.

Also: computed values are not verification. Confirming that --ed-art-bg resolved to the light value proves the token works, not that the page looks right — a single un-tokenised rule elsewhere still ruins it. Look at the rendered page in both schemes.

Writing custom CSS from code

🚨 $theme->custom_css = $css; $theme->save(); is a silent no-op. The setter writes to _data while save() only persists $this->changed. Write the column and clear the compiled copy:

\IPS\Db::i()->update( 'core_themes', [ 'set_custom_css' => $css ], [ 'set_id=?', $id ] );

$key = $id . '_theme_editor_custom_css';
unset( \IPS\Data\Store::i()->$key );

Bonus: the 1px white seam on hover

A card with overflow: hidden, a border-radius, and a transform on hover, containing an image that also transforms, flashes a hairline of the page through its rounded corners. The rounded clip is re-rasterised on the card's compositing layer and leaks an antialiased edge.

🚨 The instinctive cures — transform: translateZ(0), will-change: transform — make it worse, because promoting the element to its own layer is what causes the clip to be rasterised separately in the first place.

Fix it geometrically instead: never let the image's edge sit at the clipping boundary.

.card{ overflow: clip; isolation: isolate; }

/* clip on the image's OWN container, which never transforms, with the radius
   matched to the card's (16px card minus its 1px border) */
.card__image{ overflow: clip; border-radius: 15px 15px 0 0; }

/* resting overscan: invisible on a photo, but the edge already sits outside
   the clip before the animation starts */
.card__image img{ transform: scale(1.02); }
.card:hover .card__image img{ transform: scale(1.08); }

overflow: clip is preferable to hidden here: it does not create a scroll container and rasterises more predictably. Note also that a hover box-shadow containing a hard 0 0 0 1px ring is itself indistinguishable from a flash — check for one before blaming the clip.

Verified against

Invision Community 5.0.19, variable names read from the live computed styles of a running community and from applications/core/dev/css/global/framework/1-3-variables.css and 1-4-colors.css.


User Feedback

Recommended Comments

There are no comments to display.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.