Skip to content
View in the app

A better way to browse. Learn more.

Unofficial Cork City FC Fans Forum

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.

KKaway24

Members
  • Joined

  • Last visited

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.
* Where no explicit, per-topic state exists the dot is NOT shown. * Never infer topic state from forum badges, URL actions or timestamps. */ (function () { 'use strict'; var OLD = 'ccfcIndexRecentTopicForumUnread'; var PREVIOUS = 'ccfcIndexNativeForumUnread'; var NEW = 'ccfcIndexTopicUnread'; var ROOT = '.ipsData--forum-category, .ipsData--forum-grid, .ipsData--category, .ipsCategoryWithFeed__item'; var SELECTOR = 'a[href*="/topic/"]'; var MAP_ID = 'ccfc-topic-read-states'; var META = 'time, .ipsData__last-secondary, .ipsData__meta, .ipsDataItem_meta, .ipsData__stats, .ipsData__footer, .ipsBreadcrumb'; var ID_PATTERN = /^[1-9][0-9]{0,15}$/; var scheduled = false; var requestSignature = null; var requestNumber = 0; function isMember() { // Conservative guest guard: never derive read state from the forum itself. return !!document.querySelector('a[href*="/logout/"]'); } function getTopicId(link) { var href = link.getAttribute('href') || ''; var match = href.match(/(?:^|\/)topic\/(\d+)(?:[-/?#]|$)/i); return match && ID_PATTERN.test(match[1]) ? match[1] : null; } function readExplicitState(node) { if (!node) return null; var keys = ['data-ccfc-topic-unread', 'data-topic-unread']; for (var i = 0; i < keys.length; i++) { var val = node.getAttribute(keys[i]); if (val === 'false' || val === '0') return false; if (val === 'true' || val === '1') return true; } var nativeValue = node.getAttribute('data-ips-read'); if (nativeValue === 'read' || nativeValue === 'true') return false; if (nativeValue === 'unread' || nativeValue === 'false') return true; if (node.classList.contains('ipsItemStatus_read') || node.classList.contains('ipsTopicRead')) return false; if (node.classList.contains('ipsItemStatus_unread') || node.classList.contains('ipsTopicUnread')) return true; return null; } function getBatch() { var result = Object.create(null); var element = document.getElementById(MAP_ID); if (!element || element.getAttribute('type') !== 'application/json') return result; var payload; try { var raw = element.textContent || ''; if (raw.length > 16384) return result; payload = JSON.parse(raw); } catch (_) { return result; } if (!payload || payload.schema !== 1 || !payload.topics || typeof payload.topics !== 'object' || Array.isArray(payload.topics)) return result; var ids = Object.keys(payload.topics); if (ids.length > 100) return result; for (var i = 0; i < ids.length; i++) { var id = ids[i]; if (ID_PATTERN.test(id) && typeof payload.topics[id] === 'boolean') { result[id] = payload.topics[id]; } } return result; } function isTopicTitle(link) { if (!link.textContent || !link.textContent.trim()) return false; if (!getTopicId(link)) return false; if (link.classList.contains('ipsLinkPanel') || link.getAttribute('aria-hidden') === 'true') return false; var href = link.getAttribute('href') || ''; if (/[?&]do=getLastComment(?:[&#]|$)/i.test(href)) return false; // Exclude time/metadata links even if they accidentally share a topic URL. if (link.closest(META) || link.querySelector('time')) return false; // Confirm the visible topic-title position, not a whole row's navigation overlay. if (!link.closest('.ipsData__last-primary, .ipsData__title')) return false; return true; } function stateFor(link, topicId, batch) { var direct = readExplicitState(link); if (direct !== null) return direct; // A wrapper is authoritative ONLY when its own topic ID matches this link. var owner = link.closest('[data-topicid]'); if (owner && owner.getAttribute('data-topicid') === topicId) { var wrapped = readExplicitState(owner); if (wrapped !== null) return wrapped; } return Object.prototype.hasOwnProperty.call(batch, topicId) ? batch[topicId] : null; } function update() { if (!document.querySelector('body[data-pageapp="forums"]')) return; var member = isMember(); var batch = member ? getBatch() : Object.create(null); var groups = document.querySelectorAll(ROOT); var visited = new Set(); var ids = []; var idSet = new Set(); for (var i = 0; i < groups.length; i++) { var links = groups[i].querySelectorAll(SELECTOR); for (var j = 0; j < links.length; j++) { var link = links[j]; if (visited.has(link)) continue; visited.add(link); link.classList.remove(OLD); link.classList.remove(PREVIOUS); var id = member && isTopicTitle(link) ? getTopicId(link) : null; if (id && !idSet.has(id) && ids.length < 100) { ids.push(id); idSet.add(id); } var unread = id !== null ? stateFor(link, id, batch) : null; link.classList.toggle(NEW, unread === true); } } // Only make the same-origin call when the companion app explicitly // advertises its endpoint via UINode::dataAttributes on an index row. if (member) requestReadStates(ids); } function replaceBatch(topics) { var element = document.getElementById(MAP_ID); if (!element) { element = document.createElement('script'); element.id = MAP_ID; element.setAttribute('type', 'application/json'); document.querySelector('body[data-pageapp="forums"]').appendChild(element); } element.textContent = JSON.stringify({schema: 1, topics: topics}); queueUpdate(); } function requestReadStates(ids) { var anchor = document.querySelector('[data-ccfc-read-endpoint]'); if (!anchor || !ids.length || typeof fetch !== 'function' || typeof URL !== 'function' || !window.location) return; var raw = anchor.getAttribute('data-ccfc-read-endpoint') || ''; var endpoint; try { endpoint = new URL(raw, window.location.href); if (endpoint.origin !== window.location.origin || !/^https?:$/.test(endpoint.protocol)) return; } catch (_) { return; } ids.sort(function(a,b){ return Number(a)-Number(b); }); var signature = endpoint.href + '|' + ids.join(','); if (signature === requestSignature) return; requestSignature = signature; var serial = ++requestNumber; endpoint.searchParams.set('ids', ids.join(',')); // Invalidate any previous member-state snapshot before fresh results. replaceBatch({}); fetch(endpoint.href, {credentials: 'same-origin', cache: 'no-store', headers: {'Accept': 'application/json'}}).then(function(response) { if (!response.ok || !(response.headers.get('content-type') || '').includes('application/json')) { throw new Error('Read-state response unavailable'); } return response.json(); }).then(function(payload) { if (serial !== requestNumber) return; if (!payload || payload.schema !== 1 || !payload.topics || Array.isArray(payload.topics) || typeof payload.topics !== 'object') return; var clean = Object.create(null); var permitted = new Set(ids); var keys = Object.keys(payload.topics); if (keys.length > 100) return; for (var i=0; iSwitch to dark mode'; } if (item.parentElement !== list || item !== list.lastElementChild) list.appendChild(item); } function queueReconcile() { if (queued || !controller.active) return; queued = true; queueMicrotask(function () { queued = false; reconcile(); }); } function boot() { if (!controller.active) return; reconcile(); observer = new MutationObserver(function (records) { for (var i = 0; i < records.length; i++) { var record = records[i]; if (record.type !== 'childList') continue; // Only react to header/user-nav changes, not progress/scroll or general page content. var parent = record.target.nodeType === 1 ? record.target : record.target.parentElement; // Skip composer-only mutations; they cannot add/remove a native header // theme toggle, and processing them needlessly scans editor content. if (parent && parent.closest && parent.closest('.ipsEditor, .ipsComposeArea, .ipsCommentForm, [contenteditable], [role="textbox"]')) continue; var inHeader = parent && parent.closest && parent.closest('.ipsHeader, .ipsHeader__primary, .ipsUserNav'); var matched = Boolean(inHeader); if (!matched) { var nodes = Array.from(record.addedNodes).concat(Array.from(record.removedNodes)); for (var j = 0; j < nodes.length; j++) { if (nodes[j].nodeType !== 1) continue; if (nodes[j].matches('.ipsHeader, .ipsHeader__primary, .ipsUserNav, .untt_themeToggle') || nodes[j].querySelector('.ipsHeader, .ipsHeader__primary, .ipsUserNav, .untt_themeToggle')) { matched = true; break; } } } if (matched) { queueReconcile(); break; } } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); })(); /* CCFC lightweight read-status correction: no custom unread JS. Native IPS read tracking remains in full control. */ /* CCFC navigation state corrective: exact native routes, then intentional section mapping. Single class owner; no rewritten IPS links, menu nodes or layout dimensions. */ (function () { 'use strict'; if (window.__ccfcNavStableV2) return; window.__ccfcNavStableV2 = true; var busy = false; function pathOf(url) { var p = url.pathname || '/'; /* IPS installations can use index.php?/discover/8/ style URLs. */ if (/\/index\.php$/i.test(p) && /^\?\//.test(url.search)) p = url.search.slice(1).split(/[?&]/)[0]; return p.replace(/\/+$/, '') || '/'; } function page() { var nav = document.querySelector('.ipsHeader__secondary .ipsNavBar'); if (!nav) return; var now = new URL(window.location.href); var here = pathOf(now); var anchors = Array.from(nav.querySelectorAll(':scope > li > a[href]')).filter(function(a) { try { return new URL(a.href, now.href).origin === now.origin; } catch(e) { return false; } }); if (!anchors.length) return; function route(a) { return pathOf(new URL(a.href, now.href)); } var selected = anchors.find(function(a) { return route(a) === here && here !== '/'; }); /* A content stream page is a configured Discover entry, not Home. */ if (!selected && /^\/discover(?:\/|$)/i.test(here)) { selected = anchors.find(function(a) { var p = route(a); return p === '/discover/8' || (p === '/discover' && /view new content/i.test(a.textContent || '')); }); } if (!selected && (here === '/' || /^\/(?:forums?|topic)(?:\/|$)/i.test(here))) { selected = anchors.find(function(a) { return route(a) === '/' || (here === '/forums' && route(a) === '/forums'); }); } if (!selected) selected = anchors.find(function(a) { var li = a.parentElement; return a.matches('[aria-current="page"], [aria-current="true"]') || (li && li.matches('.ipsNav__active, .ipsNavBar__active, .ipsNavBar_active, .ipsNavBar__item--active')); }); anchors.forEach(function(a) { a.classList.toggle('ccfcNavCurrent', a === selected); a.classList.toggle('ccfcNavNotCurrent', a !== selected); }); nav.classList.add('ccfcNavResolved'); } function queue() { if (busy) return; busy = true; requestAnimationFrame(function () { busy = false; page(); }); } function init() { page(); var header = document.querySelector('.ipsHeader__secondary'); if (header && typeof MutationObserver !== 'undefined') { /* IPS may reuse the same menu nodes while changing its selected route. Watch native state attributes as well as replaced links, but not our own ccfcNavCurrent classes (which would cause an observer loop). */ new MutationObserver(function(changes) { if (changes.some(function(r) { return r.type === 'childList' || r.type === 'attributes'; })) queue(); }).observe(header, { childList: true, subtree: true, attributes: true, attributeFilter: ['aria-current', 'aria-selected', 'data-active'] }); } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true }); else init(); window.addEventListener('pageshow', queue); window.addEventListener('popstate', queue); /* IPS can use history navigation without a popstate event. Observe route commits without changing the original History API return values. */ ['pushState', 'replaceState'].forEach(function(method) { var original = window.history[method]; if (typeof original !== 'function') return; window.history[method] = function() { var result = original.apply(this, arguments); queue(); return result; }; }); })();