Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

MeowPy Community

MeowPy Community is a full Discourse theme with paired light and dark color schemes. It uses #1781B5 as the shared interaction anchor in both modes, with neutral surfaces and semantic colors doing most of the visual work.

The layout has three deliberate surface treatments:

  • Latest starts with a transparent header and sidebar rail so the navigation shares the page canvas; the sticky header gains the chrome surface after the welcome area collapses, while topic pages keep the chrome header from the start;
  • the topic list is a single-column feed of horizontal discussion cards;
  • Latest adds an unframed recommendation rail on wide screens;
  • topic details remain a continuous, non-card reading surface with the Discourse timeline and composer intact.

On desktop screens the sidebar is the first track of the same centered layout that contains the topic content and recommendation rail. On Latest, the sidebar starts beside the list and recommendation rail, below the independent welcome/search area. That upper area expands across the reserved sidebar track while remaining centered against the viewport. At medium desktop widths the reserved track narrows so the navigation never covers the topic list.

On desktop topic-list pages, Discourse's native category, tag, and sort controls are placed in a compact row directly below the topic table headings and above the first topic. The sidebar keeps the native new-topic action at the top of its rail, and begins below the welcome area so it shares the recommendation rail's visual starting point.

Installation

Import this directory as a Discourse theme, then select either MeowPy Light or MeowPy Dark as the active color scheme. To enable light/dark/auto switching, pair the schemes in the admin theme settings: keep MeowPy Light as the theme's color scheme and assign MeowPy Dark as its dark color scheme. Discourse then serves both palettes and follows the chosen mode.

With both schemes paired, the theme adds a color-mode selector at the bottom of the sidebar, in the footer actions row. Opening it shows a compact menu with light, dark, and auto options; the current mode is highlighted and the trigger's icon reflects it. The control is styled after the interface color selector that Discourse ships in core, but implemented by the theme itself. It delegates to Discourse's native interface color service: auto follows the system prefers-color-scheme setting, and the choice persists in the same cookie core uses. If the site has only one scheme, the control stays out of the sidebar. Because the selector is provided by the theme, the core interface color selector site setting does not need to be enabled; leaving it off avoids two selectors appearing side by side.

Mode changes are softened with motion: each switch wraps the stylesheet flip in a one-shot palette fade so the whole page cross-fades over the theme's slow motion token (skipped under reduced motion), and the menu enters with the shared dropdown animation already defined for every other surface.

The theme adds no dependencies or custom routes.

Its JavaScript uses supported Discourse theme APIs for the integrations below, plus a small desktop DOM observer that keeps the native topic filters in their table position after route updates:

  • a sidebar footer selector offering light, dark, and auto interface color modes through a menu, driven entirely by Discourse's interface-color service;
  • page-transition motion reserved for context crossings: leaving or arriving at the outer list layer (home, latest, categories, tags) and content-to- content switches animate; the permanently instant lanes are list-to-list hops, topic-to-topic binge-reading, and same-signature repeats inside 1.2s. On capable desktops the crossing plays the banner carousel's own vocabulary (zoom entrance, back easing on the banner, tightened to 600ms) through the View Transitions API via a router-service hook that keeps the header and sidebar perfectly still; the banner departs and arrives through a corner-anchored circular mask (a 150%-radius clip-path: circle(), sized to actually cover the far corner) that animates only clip-path — no opacity, no geometry — with a short start delay so the mask's clock begins after the route's capture/render storm has drained, then drains or opens over a tier-tuned ~300–320ms. Departures are narrated in two beats: the mask closes to half its footprint first, and only at that halfway mark does the content region hand off (old fades out, new zooms in) while the mask finishes draining alongside; when renders run slow (measured via a rolling average), the same entrance is instead bridged by dimming the outgoing page in the click frame and lifting it on arrival, so the animation can never be truncated by the server. Device tiers soften or skip the effects (no View Transitions on low-memory/low-core or save-data devices, softened timings on touch), Safari and all other Apple-WebKit browsers are routed to the dim bridge because WebKit's same-document View Transitions still flicker and stall on full-page snapshots (escape hatch: localStorage["meowpy-page-transitions-force-vt"] = "on"), group geometry never animates, the unnamed shell stays static, reduced motion skips everything, and localStorage["meowpy-page-transitions"] = "off" disables it all. The animation level itself is an admin-only theme setting (motion_mode, default "reduced") that applies site-wide: every visitor rides the same grammar, there is no per-user override anywhere in the client, and Apple-WebKit devices plus hardware that tiered down to lite always run the fades regardless of the setting. During transitions the outgoing content and sidebar snapshots are measured against the rendered destination and translated back onto their true origins, so route pairs whose geometry differs (banner leaving or joining the layout, mid-list scroll positions) never read as the list or column jumping over the banner or snapping to the viewport top. Every armed navigation also remembers where the departing page sat and opens a short restoration session on arrival: each frame it re-asserts the remembered list offset (or the top, for forward entries) until it holds steady past the transition, so async height collapses — which make the browser clamp scrollY natively mid-navigation — can no longer undo the restore; redirect chains refine the target instead of consuming it ("/" and "/latest" are remembered as one list); browser back restores too; and the transition's snapshot is only taken once the scroll has converged, so returning to a scrolled list never flashes the unscrolled viewport first. Alignment pairing waits for the same convergence plus one rendered frame after page change, so the ghosts are never measured against the outgoing route's stale DOM;
  • a native CreateTopicButton in the sidebar with category/tag context;
  • desktop use of Discourse's existing navigation dropdown;
  • a five-item Latest recommendation rail populated from Discourse's Hot, Top, and Latest endpoints, in that priority order with duplicate topics removed. Results are cached in memory for five minutes and shared across remounts, so repeated visits to Latest do not refetch the endpoints.

Typography uses the font selected by the Discourse site. The theme does not load a remote font or override an administrator's typography choice.

Banner Carousel Compatibility

discourse-banner-carousel is compatible with this theme without a layout override. The plugin renders its Ember carousel through Discourse's verified below-site-header Plugin Outlet. In the current Discourse application template (2026.8.0-latest.1), that outlet is rendered after the optional welcome banner and before <div id="main-outlet-wrapper">. The carousel is therefore outside the sidebar/content/recommendation grid on every route and does not become a child of .has-sidebar-page #main-outlet-wrapper.

The theme intentionally does not style the carousel or modify .welcome-banner, .list-controls, .sidebar-wrapper, or #main-outlet-wrapper. The plugin stylesheet uses dbc--prefixed selectors and reads the theme's existing --meowpy-surface, --meowpy-text, --meowpy-border, and --meowpy-radius tokens, with native Discourse color tokens as fallbacks. The light and dark MeowPy schemes consequently provide the carousel's surface, text, border, and accent colors automatically.

The application-level outlet remains mounted during Ember route transitions, so only one carousel instance is rendered while its route-aware data is updated. Do not add a second connector in this theme; doing so would create a duplicate carousel and would place it inside the three-column content area.

Palette

Light

Role Value
Primary text #20333D
Page background #FFFFFF
Surface #FFFFFF
Primary interaction #1781B5
Secondary blue #4CA4D6
Selected surface #E9EEF1
Teal accent #2FA58D
Coral accent #E16F62
Rose accent #C45F82

Dark

Role Value
Primary text #DCE8EE
Page background #11181D
Surface #151E24
Primary interaction #1781B5
Accessible text blend brand/text contrast blend
Highlight base #1D282E
Teal accent #53BFA7
Coral accent #EF8177
Rose accent #D77898

Dark mode keeps the same #1781B5 interaction anchor. Text links use a small contrast-safe blend with the mode's primary text, while hover and selected surfaces are neutral mixes of the surface and text colors. The native Discourse hover, selected, and highlight tokens are also mapped to those neutral surfaces so modernized list controls do not reintroduce pale blue highlights. Large surfaces stay neutral in both modes; category colors are reserved for small icons, markers, tags, and semantic states.

In light mode, the exact #1781B5 brand color is used for markers, state accents, and solid buttons. Small text links mix that brand value with the mode's primary text color so their contrast remains readable. Third-party color schemes can re-skin every brand-derived color by overriding --meowpy-brand-override before the theme tokens resolve.

Motion

Animation follows one small vocabulary declared as custom properties at the top of common/common.scss: three durations and two curves. The intent is a community that feels young but trustworthy — professional and quiet, with a pulse of energy — so motion is punctuation, not performance.

Token Value Use
--meowpy-motion-fast 120ms hover, focus, and press state shifts
--meowpy-motion-base 180ms state changes on larger surfaces
--meowpy-motion-slow 320ms entrances and expressive swaps
--meowpy-motion-ease-standard ease the calm default
--meowpy-motion-ease-emphasized cubic-bezier(0.34, 1.56, 0.64, 1) slight overshoot, accent moments only

Rules every animation in the theme must follow:

  • Durations stay under 350ms and always come from these tokens; no ad-hoc values.
  • Only opacity, the individual transform properties (translate, rotate, scale), colors, borders, and backgrounds animate — never layout properties such as width, height, or top.
  • Entrances travel at most 6px; button presses contract at most 2%.
  • Every effect must degrade gracefully under prefers-reduced-motion: reduce via the global override at the bottom of common/common.scss.

Where motion appears today: interactive surfaces (sidebar links, header icons, nav controls, post actions, buttons) settle their hover and focus colors with the fast duration; topic-list rows settle at the base duration; buttons contract slightly while pressed; dropdown and select-kit panels enter with a quiet fade-and-settle; the recommendation rail's skeletons sweep a soft highlight while loading and its entries rise in with a short stagger once loaded; the rail's accent dot emits a slow, faint ping to mark live content; and the color-mode trigger cross-fades through a rotation using the emphasized curve.

One piece of core motion is intentionally removed: Discourse fades the heading anchor icon (the link glyph beside a post heading on hover) in from opacity: 0 over 250ms. The icon is a content-image ::before positioned outside the zero-width inline anchor, and starting that fade promotes a fresh compositor layer mid-hover whose first frames can paint the icon partially — half a glyph, or a box stretched to the heading's line-height — so it appears to render "one half first, then the other half". The theme sets transition: none on .cooked/.d-editor-preview heading anchors so the icon paints once, fully formed; core keeps ownership of when it shows.

Browser Support

The theme targets current evergreen browsers: Chrome/Edge 111+, Firefox 121+, and Safari 16.4+. Its layout relies on :has() selector support, color-mix() for token derivation, scrollbar-gutter, and range syntax in media queries. Older engines are not supported and may fall back to Discourse's base styles in places.

Design Boundaries

  • Topic cards use thin borders and no elevation. They should read as discussions, not SaaS widgets.
  • Desktop sidebar groups use whitespace instead of nested panels, accent stripes, shadows, or footer fades. The mobile drawer keeps a solid surface because it overlays the reading area.
  • Navigation dropdowns and category/tag comboboxes share one compact control vocabulary and use borders without shadows.
  • The desktop sidebar, content grid, and banner share one responsive maximum width. They follow the viewport at ordinary desktop sizes, then remain centered with equal outer gutters on wide screens; reading text keeps its own line-length cap, and the recommendation rail disappears before it would crowd the topic table.
  • Topic posts, headers, and quotes never become independent floating cards.
  • Mobile keeps the card hierarchy while allowing Discourse to collapse its desktop navigation and timeline normally.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages