---
title: "Smart Grid | Advanced Content Grid Element for YOOtheme Pro Sites"
description: "Build smarter content grids in YOOtheme Pro with favorites, ratings, filters, search, social feeds, Joomla articles and ZOO support for dynamic content."
url: "https://www.yoozecase.com/?app_id=1&item_id=1"
date: "2026-09-28T03:39:15+00:00"
language: "en-GB"
---

#  Smart Grid

We 've created a beast!

**Latest Version**
4.3.7
**Requires**
Joomla 5, PHP 8.1 or newer
**Release date**
21 September 2026
**Made for**
Yootheme Pro, Joomla
**Price**
€65.00 (incl. 6 months of updates)

 [ 🛒 Get it for Joomla 👍 ](https://yoozecase.lemonsqueezy.com/checkout/buy/715f7f63-a1b9-490d-9539-3950f48b55c4)

 [ Demo page ](https://www.yoozecase.com/smart-grid-demo)

Ever wish your site could remember what each visitor loves? Smart Grid makes it happen. Every card it shows gets a heart and a 5-star rating - visitors save what they like with a tap, filter the page down to just their favourites in a click, and show you what's actually worth the time. Then fill those cards with anything: your Joomla articles, your ZOO items, hand-picked highlights, or live posts from eight social platforms. One drag-and-drop YOOtheme Pro element, one Joomla install, zero code - and easily the most epic grid you'll ever add to a page.

##  What's new in version 4.3.7

- **Changed:** Titles and buttons on cards now automatically stay readable against the background colour of the section they sit in. On some themes and dark or coloured sections, a title or a button could previously render in a colour that was too close to its own background - white on white was the worst case - making it hard or impossible to read.
- **Changed:** Fixed the "Read more" button being cut off at the bottom of cards using the horizontal layouts (image left/right, podcast, news feed) - it now sits fully inside the card on every layout.
- **Changed:** Fixed tag filter links and pagination links sending visitors to the site's home page instead of filtering or paging the grid, on sites using search-engine-friendly URLs.
- **Changed:** Fixed the page jumping straight to the bottom-most grid on first load for any visitor who had previously favourited an item - the page now opens normally and favourites still show as favourited.
- **Changed:** Fixed the bundled example item's picture showing as broken on grids created since the previous release moved the grid's images and styling to a new location; existing example items repair themselves automatically, nothing needs to be re-added.
- **Changed:** Added 5-column and 6-column options to the grid's column count setting (previously capped at 4).
- **Changed:** Moved the All / Favourites toggle above the tag filter bar.
- **Changed:** Fixed the RSS feed picker (used for the "related links" extra field) showing as a plain text box instead of the proper element dropdown, which meant the RSS feed source could not be selected.
- **Changed:** Added an optional free-text note that can be shown under a card's extra fields, and left/centre/right alignment for the star rating shown below the item text.
- **Changed:** The older "Smart Grid" builder element - kept only so pages built before the 2024 rename keep working - no longer shows up in the YOOtheme element picker as a broken image icon. Pages already using it are unaffected; it simply no longer appears as a choice for new pages.
- **Note:** This release only changes how cards render and what shows in the element picker - no settings need to be re-saved, and nothing here changes how your grids are configured.

 ![Smart Grid 1](https://www.yoozecase.com/files/smart-grid_1.png)

 [](#)

 [](#)

---

##  Description

**Give every card a heart and a rating and watch your site finally remember the people who visit.**

Start with the part nobody else gives you: a website that remembers people. Smart Grid puts a little heart on every card it displays. A visitor taps it to save that card - think of it like a bookmark or a wishlist - and from then on they can filter the whole page down to just the things they loved, in a single click. Add the built-in 5-star rating widget and they can score what they like too, the way you'd rate a film or a product online. Those ratings stick around, so you can finally see what your audience actually values - and if you use ZOO, the averages can write straight back into your content. No third-party service, no spreadsheet, no guesswork. Your site remembers, and your visitors feel seen.

So what goes on those cards? Whatever you want - and this is where jaws hit the floor. A YOOtheme Pro "element" is just a block you drag onto a page, Smart Grid is that block, except it can pull its cards from four completely different places. Hand-pick them one by one. Or point it at your Joomla articles and let it build the grid for you (it even notices a YouTube link inside an article and turns that card into a playable video). Or wire it into ZOO, the heavy-duty content app. Or - the showstopper - feed it live posts from eight social platforms at once: YouTube, Bluesky, Mastodon, Reddit, Vimeo, Spotify, SoundCloud and TikTok. You never touch a line of code. You pick a source from a dropdown and the cards appear.

From there it just keeps over-delivering. Twelve one-click layout presets mean it looks designed, not assembled. Cards flip to reveal more, spotlight tiers show off your best content, and media plays right on the card - with only one video or song ever playing at a time, so nothing talks over itself. Visitors filter by tag, search as they type, and page through smoothly. You can even earn from it: drop in Google ads, including native "in-feed" cards that blend into the grid instead of screaming "banner" - and a bonus RSS Feed element rides along in the same download for clean news feeds.

And it's built like grown-ups built it: it heals its own database tables, survives the cheapest shared hosting, shrugs off flaky APIs, and is properly security-hardened. It speaks six languages out of the box and installs from a single zip - no FTP, no cPanel, no phpMyAdmin. Drag it on, pick a source, pick a preset, and you're live before your coffee cools. You came for a grid. You're leaving with a site that remembers, rewards, and sells - quite possibly the most epic plugin you'll ever buy, and we say that with our whole chest.

##  Feature request

 ### What is the next feature for this plugin?

Submit your idea or vote on existing ones.

  #### Top voted

   Website (leave empty)

'; } html += ''; if (oc.title) { html += '##### ' + escapeHtml(oc.title) + '

'; } if (oc.text) { html += '' + escapeHtml(oc.text).replace(/\\n/g, '
') + '

'; } html += '

'; } // Auto winner(s) - top N from the already vote-sorted features if (mode === 'auto_winner' || mode === 'both') { var n = oc.winnerCount | 0; if (n '; for (var i = 0; i ' + (w.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(w.image_url)%20+%20')

' : '') + '' + (i + 1) + '' + '' + escapeHtml(w.title) + '' + '' + getIcon(this.cfg.voteIcon) + ' ' + (w.votes | 0) + '' + ''; } html += ''; } } if (html === '') { this.elOutcome.hidden = true; return; } this.elOutcome.innerHTML = html; this.elOutcome.hidden = false; }; /** * Roadmap view (v3.2.0): renders the items as status columns, in the * canonical status order from cfg.statuses. Each column is an 12. inside \* the existing .yc-fb-list , holding its own inner  of items - \* this keeps the delegated click/change handlers on elList working \* unchanged. Unknown statuses fall back into the "open" column so no item \* can silently disappear. Empty columns are skipped when \* cfg.roadmapHideEmpty is set. \*/ Board.prototype.renderRoadmap = function (items) { var stats = (this.cfg.statuses && this.cfg.statuses.length) ? this.cfg.statuses : \['open', 'planned', 'in\_progress', 'shipped', 'declined'\]; var labels = this.cfg.statusLabels || {}; var groups = {}; var i, st; for (i = 0; i ' + '##### ' + '' + escapeHtml(labels\[sv\] || sv) + '' + arr.length + '' + '

    ' + '' + itemsHtml + '
    ' + '
'; } return html; }; Board.prototype.renderItem = function (f, index) { var disabled = this.cfg.isClosed ? 'disabled' : ''; var voted = f.voted ? ' yc-voted' : ''; var descHtml = f.description ? '' + escapeHtml(f.description).replace(/\\n/g, '
') + '

' : ''; var thumbHtml = f.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

' : ''; // Status workflow (v3.0.0): badge for any non-default status. The // roadmap view (v3.2.0) skips the badge - the column already // communicates the status, a badge would be redundant noise. var st = (f.status || 'open'); var labels = this.cfg.statusLabels || {}; var statusBadge = (!this.isRoadmap && st && st !== 'open' && labels[st]) ? '' + escapeHtml(labels\[st\]) + '' : ''; // Design themes (v3.1.0): arena / neon / pixel render extra per-item // furniture - rank number, avatar (image or initials), a "leader" pill // on rank 1, and a "votes" label. Classic/colour themes are untouched. var t = this.cfg.theme || 'light'; var themed = (t === 'arena' || t === 'neon' || t === 'pixel'); var rank = (typeof index === 'number') ? (index + 1) : 0; var rankHtml = '', leaderHtml = '', avatarHtml = '', votesLabel = ''; if (themed) { rankHtml = '' + pad2(rank) + ''; if (rank === 1) { leaderHtml = '' + escapeHtml(this.cfg.leaderLabel || 'Leader') + ''; } if (f.image_url) { avatarHtml = '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

'; } else { avatarHtml = '' + escapeHtml(initialsOf(f.title)) + '

'; } votesLabel = '' + escapeHtml(this.cfg.votesLabel || 'votes') + ''; } // Moderation overlay: subtle corner buttons, only when canModerate. // Buttons appear on hover/focus (styled in CSS). Even in customizer // preview we render them when canModerate is true so the moderator // can see what visitors with moderation rights will see. var modHtml = ''; if (this.cfg.canModerate) { var parts = []; var hideTitle = escapeHtml(this.t('hideTitle', 'Hide this feature')); var deleteTitle = escapeHtml(this.t('deleteTitle', 'Delete this feature')); if (this.cfg.allowSoftHide) { parts.push( '' ); } if (this.cfg.allowHardDelete) { parts.push( '' ); } if (parts.length || (this.cfg.statuses && this.cfg.statuses.length)) { var sParts = parts.slice(); if (this.cfg.statuses && this.cfg.statuses.length) { var opts = ''; for (var si = 0; si ' + escapeHtml(sl) + ''; } var setStatus = escapeHtml(this.t('setStatus', 'Set status')); sParts.push( '' ); } modHtml = '' + sParts.join('') + '

'; } } var mediaHtml = themed ? avatarHtml : thumbHtml; return ( '32. ' + leaderHtml + rankHtml + '' + mediaHtml + '' + '##### ' + escapeHtml(f.title) + '

    ' + statusBadge + descHtml + '

    ' + modHtml + '
' ); }; /** * Detects whether a JSON response looks like a CSRF/token rejection. * Two cases: * 1. com_ajax-level rejection (Joomla's auto-token-check on POST): top * level message contains the JINVALID_TOKEN_NOTICE text and HTTP * status is typically 403. * 2. Plugin-level rejection from requireToken(): payload.error === "Invalid token". */ Board.prototype.isTokenError = function (status, raw, payload) { if (status === 403) return true; var hay = ''; try { hay = JSON.stringify(raw || {}) + '|' + JSON.stringify(payload || {}); } catch (e) {} return /invalid (security )?token/i.test(hay); }; /** * Fetch a fresh CSRF token from the server. Updates this.cfg.csrfToken so * the next call (and any future calls in this Board's lifetime) use the * correct one. Cache-bust with a timestamp so no upstream CDN caches the * response. */ Board.prototype.fetchFreshToken = function () { var self = this; var url = this.cfg.ajaxUrl + '&task=token&_=' + Date.now(); return fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Cache-Control': 'no-cache' } }) .then(function (r) { return r.json(); }) .then(function (data) { var p = self.unwrapAjax(data); if (p && p.ok === true && typeof p.token === 'string' && p.token.length >= 16) { self.cfg.csrfToken = p.token; return p.token; } throw new Error('No fresh token in response'); }); }; /** * Generic POST-with-token helper. Builds FormData from a plain object, * adds the current token, posts. On 403 / token-error response, fetches a * fresh token and retries exactly once. * * @param {string} task The task name (appended to URL and body) * @param {object} fields Key-value form fields (no token) * @return {Promise} Resolves to the unwrapped payload on success. \* Rejects with Error containing .reason on failure. \*/ Board.prototype.postWithToken = function (task, fields) { var self = this; function attempt(token) { var fd = new FormData(); fd.append('task', task); for (var k in fields) { if (Object.prototype.hasOwnProperty.call(fields, k)) { fd.append(k, fields\[k\]); } } fd.append(token, '1'); return fetch(self.cfg.ajaxUrl + '&task=' + encodeURIComponent(task), { method: 'POST', credentials: 'same-origin', body: fd, headers: { 'Accept': 'application/json' } }).then(function (r) { return r.json().then(function (data) { return { status: r.status, raw: data, payload: self.unwrapAjax(data) }; }); }); } return attempt(this.cfg.csrfToken).then(function (res) { // Success path if (res.payload && res.payload.ok === true) { return res.payload; } // Token rejection? Fetch fresh and retry once. if (self.isTokenError(res.status, res.raw, res.payload)) { return self.fetchFreshToken().then(function (fresh) { return attempt(fresh).then(function (res2) { if (res2.payload && res2.payload.ok === true) { return res2.payload; } var err = new Error( (res2.payload && res2.payload.error) || (res2.raw && res2.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res2.payload || null; throw err; }); }); } // Some other error - surface it var err = new Error( (res.payload && res.payload.error) || (res.raw && res.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res.payload || null; throw err; }); }; Board.prototype.handleSubmit = function () { var self = this; if (isInBuilder()) { this.showStatus(this.t('previewSubmit', 'Preview mode - submissions are disabled in the YOOtheme builder. Try this on the published page.'), 'info'); return; } if (this.cfg.isClosed) { this.showStatus(this.cfg.closedMessage || this.t('votingClosed', 'Voting is closed.'), 'info'); return; } var title = (this.elTitle && this.elTitle.value || '').trim(); var desc = (this.elDesc && this.elDesc.value || '').trim(); // Client-side validation: minimum title length. Server re-validates. var minLen = (this.cfg.minTitleLength | 0) || 2; if (title.length for hide/delete, remember parent + sibling for rollback. var li = btn.closest('li'); var parentEl = li ? li.parentNode : null; var nextEl = li ? li.nextSibling : null; if (li && (action === 'hide' || action === 'delete')) { li.remove(); } btn.disabled = true; var task = action === 'hide' ? 'moderate\_hide' : action === 'delete' ? 'moderate\_delete' : action === 'restore' ? 'moderate\_restore' : ''; if (!task) { btn.disabled = false; return; } this.postWithToken(task, { feature\_id: featureId }) .then(function (payload) { // Success - keep the UI as-is (already removed) and refresh // so vote counts, top box, and hidden-features panel reflect // the new state. self.refresh(); if (self.hiddenOpen) { self.hiddenLoaded = false; // force a refetch self.fetchHiddenFeatures(); } var msg = action === 'delete' ? self.t('deleted', 'Feature deleted.') : action === 'hide' ? self.t('hidden', 'Feature hidden.') : action === 'restore' ? self.t('restored', 'Feature restored.') : self.t('done', 'Done.'); self.showStatus(msg, 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { // Rollback: re-insert the row if we removed it. if (li && parentEl) { if (nextEl) { parentEl.insertBefore(li, nextEl); } else { parentEl.appendChild(li); } } btn.disabled = false; self.showStatus(err && err.message ? err.message : self.t('modFailed', 'Moderation action failed.'), 'error'); }); }; Board.prototype.handleStatusChange = function (featureId, status, sel) { var self = this; if (!featureId || !status) return; sel.disabled = true; this.postWithToken('moderate\_status', { feature\_id: featureId, status: status }) .then(function () { self.refresh(); var label = (self.cfg.statusLabels && self.cfg.statusLabels\[status\]) ? self.cfg.statusLabels\[status\] : status; self.showStatus(self.tf('statusSet', 'Status: %s', label), 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { sel.disabled = false; self.showStatus(err && err.message ? err.message : self.t('statusFailed', 'Could not set status.'), 'error'); }); }; /\*\* \* Toggle the hidden-features panel open/closed. First open triggers \* an AJAX fetch; subsequent toggles just hide/show the cached list. \*/ Board.prototype.toggleHiddenPanel = function () { if (!this.elHiddenList || !this.elHiddenLabel) return; var arrow = this.elHiddenToggle && this.elHiddenToggle.querySelector('.yc-fb-hidden-arrow'); if (this.hiddenOpen) { this.hiddenOpen = false; this.elHiddenList.hidden = true; this.elHiddenLabel.textContent = this.t('showHidden', 'Show hidden features'); if (arrow) arrow.classList.remove('yc-open'); return; } this.hiddenOpen = true; this.elHiddenList.hidden = false; this.elHiddenLabel.textContent = this.t('hideHidden', 'Hide hidden features'); if (arrow) arrow.classList.add('yc-open'); if (!this.hiddenLoaded) { this.fetchHiddenFeatures(); } }; /\*\* \* Fetch the hidden-features list via the moderator-only endpoint. \* Re-callable to refresh the list after a restore. \*/ Board.prototype.fetchHiddenFeatures = function () { var self = this; if (!this.elHiddenList) return; this.elHiddenList.innerHTML = '2. ' + escapeHtml(this.t('loading', 'Loading...')) + '
'; var url = this.cfg.ajaxUrl + '&task=moderate\_list\_hidden&board\_id=' + encodeURIComponent(this.cfg.boardId); fetch(url, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } }) .then(function (r) { return r.json(); }) .then(function (data) { var payload = self.unwrapAjax(data); if (!payload || payload.ok !== true) { self.elHiddenList.innerHTML = '4. ' + escapeHtml((payload && payload.error) ? payload.error : self.t('hiddenLoadFailed', 'Failed to load hidden features.')) + '
'; return; } self.hiddenLoaded = true; var features = payload.features || \[\]; if (features.length === 0) { self.elHiddenList.innerHTML = '6. ' + escapeHtml(self.t('noHidden', 'No hidden features.')) + '
'; return; } var html = ''; for (var i = 0; i ' + escapeHtml(f.description).replace(/\\n/g, '
') + '' : ''; var restoreTitle = escapeHtml(self.t('restoreTitle', 'Restore this feature')); html += '9. ' + '' + '##### ' + escapeHtml(f.title) + '

    ' + descHtml + '' + escapeHtml(self.tf('votesMeta', '%s votes', f.votes | 0)) + '' + '

    ' + '' + '
'; } self.elHiddenList.innerHTML = html; }) .catch(function () { self.elHiddenList.innerHTML = '11. ' + escapeHtml(self.t('hiddenNetwork', 'Network error while loading hidden features.')) + '
'; }); }; function initAll(scope) { var roots = (scope || document).querySelectorAll('.yc-fb-root'); for (var i = 0; i ---

##  Changelog

---

 [ v4.7.0 Breaking 21/09/2026 ](#) Added- \*\*A Custom CSS box for the RSS Feed Display\*\*, under the Advanced tab, with the full list of selectors you can target. The element simply never declared the field before, which is why the box was missing.

 Changed- \*\*The RSS Feed Display's CSS class names now use the `ycrss-` prefix\*\* instead of the old `pdrss-`, finishing the renaming started in 4.0.0. If you wrote your own CSS against `.pdrss-...` names, replace `pdrss-` with `ycrss-`; nothing else about them changed.

 Fixed- \*\*RSS Feed Display: headlines were white on a white card.\*\* Inside a dark section the headline inherited the section's white text while the card kept its own white background. It is now set to a readable colour whenever the card has an opaque background, and still inherits on the Transparent and Glass card styles, where the section really does show through.
- \*\*The Read original link now sits at the bottom of the card\*\*, so the links line up across a row however much text each item has.

 [ v4.6.2 Breaking 18/09/2026 ](#) Fixed- On a coloured category card the expand button and the sub-category links stayed dark, while the title next to them turned white. They now follow the card like the rest of its text: white on a dark card, dark on a light one.

 [ v4.6.1 Breaking 18/09/2026 ](#) Fixed- The \*\*Category opens page\*\* dropdown was always empty, so no target page could be chosen. The list is fetched from the site, where an administrator is not logged in - Joomla keeps the site and administrator logins separate - and the permission check added in 4.6.0 rejected it every time. The list is now filtered by what the visitor may see, like the other pickers, instead of asking for an administrator.

- An empty target-page list now says so, rather than looking like a working dropdown with nothing to offer.

 [ v4.6.0 Breaking 18/09/2026 ](#) Added- \*\*Category pictures.\*\* A picture set on a ZOO category now shows on its card, like any other image.

- \*\*A category can open a page of yours.\*\* Pick the page under \*Category opens page\* (or type an address) and the category id travels with the link as `?yc\_cat=&lt;id&gt;`.
- \*\*Take the category from the URL.\*\* Switch this on for the ZOO items grid that sits on that page and it shows whichever category was clicked. The category saved in its settings stays the fallback.
- \*\*A colour per category.\*\* Off by default. Choose one base colour and every top-level category gets its own, spread evenly around it; sub-categories keep their parent's colour a shade lighter or darker. Solid or gradient.
- Card text is set light or dark from the colour that was chosen, in the same pass, so it is readable from the first frame with no flicker. Measured across 960 combinations: nothing below the 3.0 contrast floor.
- \*\*Sub-categories listed inside the card.\*\* \*Show sub-categories\* gains "List inside the card": the sub-categories appear as small links pinned to the bottom of the parent card, lined up across the row.
- A parent category now links as well as expanding. The expand control is a separate chevron button, so one click never has two meanings.

 Fixed- Card text could be given the worse of the two colours on a mid-tone card. The light/dark decision now uses the luminance where white and black read equally (0.1791) instead of 0.45, which called a mid-tone card dark and put white text on it below the readability floor.

 [ v4.5.1 Breaking 18/09/2026 ](#) Fixed- The \*\*ZOO categories\*\* source could not be set up: choosing it hid the whole ZOO block, so there was no ZOO app to pick and the grid stayed empty. The block now stays visible for both ZOO sources and shows the settings belonging to the one you chose.

- \*\*Start from category\*\* and \*\*Show sub-categories\*\* never reached the settings panel at all. They now appear, with the category picker as a real dropdown.
- \*\*Extra fields text\*\* (added in 4.3.6) had the same problem and has never been editable. It now appears under the extra fields heading, for ZOO grids with extra fields switched on.
- With `?sgdebug=1` an empty category grid now says why: no ZOO app picked, the app has no categories, or the chosen starting category has no children.

 [ v4.5.0 Breaking 18/09/2026 ](#) Added- A new content source: \*\*ZOO categories\*\*. Instead of filling the grid with items, the cards are the categories themselves - each showing the category name and its description.

- Sub-categories expand in place. Click a category that has sub-categories and their cards appear right next to it in the same grid, with no page reload; click again to close. A category with no sub-categories links straight to its own ZOO category page instead.
- Choose where to start: leave the new "Start from category" setting empty for your top-level categories, or pick one to show only what sits underneath it. Sub-categories can also be switched off entirely.

 [ v4.4.0 Breaking 18/09/2026 ](#) Added- A new layout preset, \*\*Numbered - Dark Panel\*\*: each card becomes a dark panel with a large faint number in its corner, numbered in order down the grid. Pick it under Layout and everything else is set for you; any individual setting can still be changed afterwards.

- Every layout preset now also puts its own class on the grid, so a preset can be targeted from Custom CSS - for example `.yc-preset-magazine`.

 Fixed- A button sitting on a coloured panel inside the card could be given the wrong colour. The plugin judged the button against the card, not against the panel it was actually on, which on a dark panel produced dark-on-dark text. It now reads the surface directly behind the button.

 [ v4.3.7 Breaking 16/09/2026 ](#) Fixed- The old Smart Grid element no longer appears in the builder's element list, where it showed as an entry with a broken image. It stays installed behind the scenes so that any page still built with it keeps working - it is simply no longer offered when you add a new element.

 [ v4.3.6 Breaking 16/09/2026 ](#) Added- A free-text note for the extra fields, shown under the heading. Type whatever you like; leave it empty for none.

- The star rating below the text can now be left-aligned, centred or right-aligned.

 Fixed- The RSS feed element could not be picked in the extra fields. Its setting was showing as a plain text box instead of the list of ZOO elements.
- The example card's image is repaired as the page renders, so example items added before version 4.3.0 stop showing a broken image without having to be re-added.

 [ v4.3.5 Breaking 16/09/2026 ](#) Added- Five and six column layouts. Both step down as the screen narrows - to four columns under 1200px, two under 960px and one on a phone - so a wide grid stays readable.

 Changed- The All / Favourites buttons (and the search box beside them) now sit above the tag bar instead of below it.

 Fixed- The page no longer scrolls by itself. If you had saved favourites, every grid on the page treated them as freshly favourited on load and scrolled to show them, so the page jumped to the last grid. Restoring saved favourites is now silent; clicking a heart still scrolls the grid into view as before.
- The example card's image no longer 404s. Its address had not been updated when the bundled files moved in 4.3.0.

## Schema

```json
{
    "@context": "https://schema.org",
    "@type": "Organization",
    "@id": "https://www.yoozecase.com/#organization",
    "name": "YOOzeCase",
    "alternateName": "A YOOzeCase Company",
    "description": "Premium plugins, extensions and YOOtheme Pro elements for WordPress and Joomla. A YOOzeCase Company: polished tools built for usability and performance.",
    "email": "info@yoozecase.com",
    "url": "https://www.yoozecase.com",
    "foundingDate": "2026-01-01",
    "logo": "https://www.yoozecase.com/images/yoozecase/Yoozecase logo.png",
    "image": "https://www.yoozecase.com/images/yoozecase/Yoozecase logo.png",
    "address": {
        "@type": "PostalAddress",
        "addressCountry": "NL",
        "addressLocality": "Rotterdam"
    },
    "sameAs": [
        "https://www.facebook.com/yoozecase",
        "https://x.com/yoozecase",
        "https://www.instagram.com/yoozecase",
        "https://www.youtube.com/@yoozecase",
        "https://bsky.app/profile/yoozecase.bsky.social",
        "https://www.threads.com/@yoozecase",
        "https://mastodon.social/@yoozecase",
        "https://www.reddit.com/user/yoozecase"
    ]
}
```

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "name": "Home",
            "item": "https://www.yoozecase.com"
        },
        {
            "@type": "ListItem",
            "position": 2,
            "name": "Extensions",
            "item": "https://www.yoozecase.com/extensions"
        },
        {
            "@type": "ListItem",
            "position": 3,
            "name": "Smart Grid",
            "item": "https://www.yoozecase.com/"
        }
    ]
}
```
