// Screen-level views const { useState, useEffect, useRef, useMemo } = React; // ============================================================ // Team & Files (dashboard side cards) // ============================================================ const TEAM = [ { name: 'Joe Marshall', role: 'Head of Client Strategy', hue: 200, src: 'assets/team/joe.png', status: 'online', meta: 'Responds in ~1 hr', primary: true, email: 'joe@clicky.co.uk', phone: '0161 808 1888', reportsTo: null, joined: '2018', focus: ['Strategy', 'Quarterly reviews', 'Roadmap', 'Exec stakeholder'], bio: 'Owns the relationship end-to-end. Sets quarterly direction with you and steers the delivery team day-to-day.', qualifications: [ { id: 'q-jm-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Aug 2024', verified: true, source: 'google-api' }, { id: 'q-jm-2', name: 'Meta Blueprint — Buying Professional', issuer: 'Meta', issuerSlug: 'meta', issued: 'Mar 2025', verified: true, source: 'meta-api' }, { id: 'q-jm-3', name: 'LinkedIn Marketing Solutions Fundamentals', issuer: 'LinkedIn', issuerSlug: 'linkedin', issued: 'Jan 2024', verified: true, source: 'linkedin-api' }, { id: 'q-jm-4', name: 'HubSpot Inbound Marketing', issuer: 'HubSpot', issuerSlug: 'hubspot', issued: 'Sep 2023', verified: false, source: 'manual' }, ], }, { name: 'Matt Jones', role: 'PPC Team Lead', hue: 30, src: 'assets/team/matt.png', status: 'online', meta: 'Google Ads · Bing', email: 'matt@clicky.co.uk', phone: '0161 808 1888', reportsTo: 'Joe Marshall', joined: '2019', focus: ['Google Ads', 'PMax', 'Bing', 'Bidding strategy'], bio: 'Leads the paid media pod. Hands-on with bidding, structure and channel mix decisions for ownership + holiday breaks.', qualifications: [ { id: 'q-mj-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' }, { id: 'q-mj-2', name: 'Google Ads Display Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' }, { id: 'q-mj-3', name: 'Google Ads Performance Max', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' }, { id: 'q-mj-4', name: 'Google Ads Shopping Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Feb 2025', verified: true, source: 'google-api' }, { id: 'q-mj-5', name: 'Microsoft Advertising Certified Pro', issuer: 'Microsoft', issuerSlug: 'microsoft', issued: 'Nov 2024', verified: true, source: 'manual' }, ], }, { name: 'Nathan Smith', role: 'Agency Systems Manager', hue: 280, src: 'assets/team/nathan.png', status: 'online', meta: 'GA4 · Looker', email: 'nathan@clicky.co.uk', phone: '0161 808 1888', reportsTo: 'Joe Marshall', joined: '2020', focus: ['GA4', 'Looker Studio', 'Tag manager', 'Conversion tracking'], bio: 'Keeps measurement honest — ships GA4, Looker and conversion-tracking work, and audits data quality before reports go out.', qualifications: [ { id: 'q-ns-1', name: 'Google Analytics Individual Qualification (GAIQ)', issuer: 'Google', issuerSlug: 'google', issued: 'Jan 2025', verified: true, source: 'google-api' }, { id: 'q-ns-2', name: 'Google Tag Manager Fundamentals', issuer: 'Google', issuerSlug: 'google', issued: 'Jan 2025', verified: true, source: 'google-api' }, { id: 'q-ns-3', name: 'Looker Studio Foundations', issuer: 'Google', issuerSlug: 'google', issued: 'Apr 2024', verified: true, source: 'google-api' }, { id: 'q-ns-4', name: 'Hotjar Foundations', issuer: 'Hotjar', issuerSlug: 'manual', issued: 'Oct 2023', verified: false, source: 'manual' }, ], }, { name: 'Hayley Roberts', role: 'UX Research & Design Manager', hue: 340, src: 'assets/team/hayley.png', status: 'away', meta: 'Ads · Landing pages', email: 'hayley@clicky.co.uk', phone: '0161 808 1888', reportsTo: 'Joe Marshall', joined: '2021', focus: ['Ad creative', 'Landing pages', 'CRO', 'Brand'], bio: 'Runs design + research. Owns ad creative, landing-page concepts and the CRO experiment backlog.', qualifications: [ { id: 'q-hr-1', name: 'Nielsen Norman UX Certification', issuer: 'NN/g', issuerSlug: 'manual', issued: 'Jul 2023', verified: true, source: 'manual' }, { id: 'q-hr-2', name: 'Optimizely Certified Expert', issuer: 'Optimizely', issuerSlug: 'manual', issued: 'Sep 2024', verified: true, source: 'manual' }, { id: 'q-hr-3', name: 'Meta Blueprint — Creative Strategy', issuer: 'Meta', issuerSlug: 'meta', issued: 'May 2024', verified: true, source: 'meta-api' }, ], }, { name: 'Shannon May', role: 'Senior Paid Media Specialist', hue: 150, src: 'assets/team/shannon.png', status: 'offline', meta: 'Back tomorrow 09:00', email: 'shannon@clicky.co.uk', phone: '0161 808 1888', reportsTo: 'Matt Jones', joined: '2022', focus: ['Day-to-day Google Ads', 'Search', 'Reporting cadence'], bio: 'Hands-on day-to-day operator on the Evergreen accounts. First to spot anomalies and triage with Matt.', qualifications: [ { id: 'q-sm-1', name: 'Google Ads Search Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' }, { id: 'q-sm-2', name: 'Google Ads Display Certification', issuer: 'Google', issuerSlug: 'google', issued: 'Mar 2025', verified: true, source: 'google-api' }, { id: 'q-sm-3', name: 'Meta Blueprint Foundations', issuer: 'Meta', issuerSlug: 'meta', issued: 'Feb 2025', verified: true, source: 'meta-api' }, { id: 'q-sm-4', name: 'TikTok Ads Specialist', issuer: 'TikTok', issuerSlug: 'tiktok', issued: 'Apr 2025', verified: false, source: 'manual' }, ], }, ]; // Issuer brand-mark lookup. Used by the qualifications row everywhere // (TeamProfilePanel + AM profile editor). Falls back to a generic certificate // glyph for issuers without an SVG asset. const ISSUER_LOGO_SRC = { google: 'assets/google.svg', meta: 'assets/meta.svg', microsoft: 'assets/microsoft.svg', linkedin: 'assets/linkedin.svg', tiktok: 'assets/tiktok.svg', reddit: 'assets/reddit.svg', }; const ISSUER_HUE = { google: '#4285f4', meta: '#0866ff', microsoft: '#00a4ef', tiktok: '#000000', linkedin: '#0a66c2', hubspot: '#ff7a59', manual: '#52525b', }; const IssuerMark = ({ slug, size = 20 }) => { const src = ISSUER_LOGO_SRC[slug]; const hue = ISSUER_HUE[slug] || ISSUER_HUE.manual; return ( {src ? ( ) : ( )} ); }; // Qualifications storage — overlays defaults from TEAM with anything the // AM has saved/edited. Returns the resolved list for a given person. const getTeamQualifications = (name) => { try { const saved = JSON.parse(localStorage.getItem('clicky_team_quals') || '{}'); if (Array.isArray(saved[name])) return saved[name]; } catch (e) {} const t = TEAM.find(p => p.name === name); return (t && t.qualifications) || []; }; const setTeamQualifications = (name, list) => { try { const saved = JSON.parse(localStorage.getItem('clicky_team_quals') || '{}'); saved[name] = list; localStorage.setItem('clicky_team_quals', JSON.stringify(saved)); } catch (e) {} }; // Reusable qualifications list — used by TeamProfilePanel (read-only) // and AmProfileScreen (with onEdit / onRemove handlers passed in). const QualificationsList = ({ qualifications, onEdit, onRemove }) => { if (!qualifications || qualifications.length === 0) { return (
No qualifications listed yet.
); } return (
{qualifications.map(q => (
{q.name} {q.verified && ( Verified )}
{q.issuer} · Issued {q.issued} {q.source !== 'manual' && q.source && ( <> · via {q.issuer} API )}
{(onEdit || onRemove) && (
{onEdit && ( )} {onRemove && ( )}
)}
))}
); }; // Expose helpers + components to other script tags (am.jsx uses them). Object.assign(window, { TEAM, getTeamQualifications, setTeamQualifications, QualificationsList, IssuerMark, ISSUER_LOGO_SRC, ISSUER_HUE }); const FILES = [ { name: 'Q2 2026 Strategy Deck.pdf', kind: 'pdf', size: '2.4 MB', updated: '2 days ago', who: 'Joe M.' }, { name: 'April Performance Report.xlsx', kind: 'sheet', size: '184 KB', updated: '2 days ago', who: 'Nathan S.' }, { name: 'Spring Breaks — Ad Creatives', kind: 'folder', size: '12 files',updated: '5 days ago', who: 'Hayley R.' }, { name: 'Brand Guidelines v3.pdf', kind: 'pdf', size: '6.1 MB', updated: '3 weeks ago',who: 'You' }, { name: 'Conversion Tracking Audit.docx', kind: 'doc', size: '92 KB', updated: '1 month ago',who: 'Nathan S.' }, ]; const EXTERNAL_TOOLS = [ { name: 'Google Ads', sub: '482-209-7716', icon: I.google, url: '#', kind: 'analytics' }, { name: 'Google Analytics', sub: 'GA4 · st-mabyn-escape', icon: 'ga4', url: '#', kind: 'analytics' }, { name: 'Search Console', sub: 'st-mabyn-escape.co.uk', icon: 'gsc', url: '#', kind: 'analytics' }, { name: 'Looker Studio', sub: 'Live performance dashboard', icon: 'looker', url: '#', kind: 'analytics' }, { name: 'Basecamp · Evergreen', sub: 'Active project workspace', icon: 'basecamp',url: '#', kind: 'project' }, { name: 'Hospitality Vertical Report 2026', sub: 'Q1 benchmark · Clicky Insider', icon: 'report', url: '#', kind: 'report' }, { name: 'Holiday Park Search Trends Report', sub: 'Updated weekly · st-mabyn-escape', icon: 'report', url: '#', kind: 'report' }, ]; // ============================================================ // Topbar search — overlay panel that surfaces matching items across // performance targets, services, websites, team, reports, tickets, // invoices, contracts, opportunities and insider posts. // ============================================================ const SearchOverlay = ({ query, onClose, onJump }) => { const [phase, setPhase] = useState('loading'); // 'loading' | 'ready' useEffect(() => { setPhase('loading'); if (!query.trim()) return; const t = setTimeout(() => setPhase('ready'), 280); return () => clearTimeout(t); }, [query]); // Build a lazy index every time so newly-added data flows in. Each entry has // { type, title, sub, target, icon } where target is a callback to navigate. const buildIndex = () => { const idx = []; (window.AGREED_KPIS || []).forEach(k => idx.push({ type: 'Performance Target', title: k.label, sub: k.sub || '', target: () => onJump('cpts', { kpi: k.key }), iconName: 'target', })); (window.CLIENT?.sites || []).forEach(s => idx.push({ type: 'Website', title: s.name, sub: s.url + ' · ' + (s.role || ''), target: () => onJump('maintenance', { siteId: s.id }), iconName: 'site', })); (window.TEAM || []).forEach(p => idx.push({ type: 'Team', title: p.name, sub: p.role, target: () => onJump('team'), iconName: 'avatar', avatar: p, })); (window.REPORTS || []).forEach(r => idx.push({ type: 'Report', title: r.name, sub: `${r.channel} · ${r.period} · ${r.status}`, target: () => onJump('reports'), iconName: 'file', })); (window.TICKETS || []).forEach(t => idx.push({ type: 'Ticket', title: t.subject, sub: `${t.id} · ${t.status} · ${t.assignee}`, target: () => onJump('tickets'), iconName: 'ticket', })); (window.CONTRACTS || []).forEach(c => idx.push({ type: 'Contract', title: c.name, sub: `${c.id} · ${c.term} · ${c.monthly}`, target: () => onJump('billing', { tab: 'contracts' }), iconName: 'doc', })); (window.INVOICES || []).forEach(inv => idx.push({ type: 'Invoice', title: `${inv.id} · ${inv.amount}`, sub: `${inv.period} · due ${inv.due}`, target: () => onJump('billing', { tab: 'invoices' }), iconName: 'receipt', })); (window.GROWTH_OPPORTUNITIES || []).forEach(o => idx.push({ type: 'Opportunity', title: o.title, sub: `${o.category} · ${o.impact?.range || ''} ${o.impact?.cadence || ''}`.trim(), target: () => onJump('opportunities'), iconName: 'spark', })); SERVICES.forEach(s => idx.push({ type: 'Service', title: s.title, sub: `${s.category} · ${s.pricing}`, target: () => onJump(s.active ? (s.id === 'google' ? 'google' : s.id === 'seo' ? 'seo' : s.id === 'meta' ? 'meta' : 'channels') : 'growth'), iconName: 'service', })); INSIDER.forEach(i => idx.push({ type: 'Insider', title: i.title, sub: `${i.tag} · ${i.when} · ${i.by}`, target: () => onJump('insider', { articleId: i.id }), iconName: 'star', })); return idx; }; const q = query.trim().toLowerCase(); const matches = q ? buildIndex().filter(m => (m.title + ' ' + (m.sub || '')).toLowerCase().includes(q) ).slice(0, 30) : []; // Group matches by type for readable rendering const grouped = {}; matches.forEach(m => { if (!grouped[m.type]) grouped[m.type] = []; grouped[m.type].push(m); }); const typeOrder = ['Performance Target', 'Website', 'Team', 'Report', 'Ticket', 'Contract', 'Invoice', 'Service', 'Opportunity', 'Insider']; const renderIcon = (m) => { const tile = (children, color = 'var(--text-muted)', bg = 'var(--surface-2)') => (
{children}
); switch (m.iconName) { case 'target': return tile(, 'var(--primary)', 'color-mix(in oklab, var(--primary) 12%, transparent)'); case 'site': return tile(); case 'avatar': return ; case 'file': return tile(); case 'ticket': return tile(I.ticket); case 'doc': return tile(I.doc); case 'receipt': return tile(); case 'spark': return tile(, '', 'var(--surface)'); case 'service': return tile(); case 'star': return tile(); default: return tile(I.search); } }; // Highlight the matched substring in the title const highlight = (text) => { if (!q || !text) return text; const i = String(text).toLowerCase().indexOf(q); if (i < 0) return text; return (<> {String(text).slice(0, i)} {String(text).slice(i, i + q.length)} {String(text).slice(i + q.length)} ); }; return (
e.stopPropagation()} style={{ maxWidth: 720, margin: '24px auto 0', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', boxShadow: '0 24px 48px -12px rgba(0,0,0,0.4)', overflow: 'hidden', maxHeight: 'calc(100vh - 100px)', display: 'flex', flexDirection: 'column', }}> {/* Header — search summary */}
{phase === 'loading' ? ( <> Searching across the account… ) : ( <> {I.search} {matches.length} result{matches.length === 1 ? '' : 's'} for "{query}" )}
esc
{/* Body */}
{phase === 'loading' ? (
{[0, 1, 2, 3].map(i => (
))}
) : matches.length === 0 ? (
No matches for "{query}".
Try a different keyword, or ask ClickyAI™ to investigate.
) : (
{typeOrder.filter(t => grouped[t]).map(type => (
{type}{grouped[type].length > 1 ? 's' : ''} · {grouped[type].length}
{grouped[type].map((m, i) => ( ))}
))}
)}
{/* Footer */}
Searches your performance, services, sites, team, files, billing and AI surfaces to open
); }; window.SearchOverlay = SearchOverlay; // ============================================================ // Sortable-table helpers — used by every in the app so // columns can be sorted asc/desc by clicking the header. // ============================================================ const useTableSort = (data, defaultKey, defaultDir = 'desc') => { const [sortKey, setSortKey] = useState(defaultKey); const [sortDir, setSortDir] = useState(defaultDir); const onSort = (key) => { if (key === sortKey) setSortDir(d => d === 'asc' ? 'desc' : 'asc'); else { setSortKey(key); setSortDir('desc'); } }; const sorted = useMemo(() => { if (!sortKey) return data; // Strip currency / units so "£1,250.00", "12.4%", "4.8x" compare numerically. const toNum = (v) => { if (v == null) return null; if (typeof v === 'number') return v; const cleaned = String(v).replace(/[£,$%]/g, '').replace(/[a-zA-Z]+$/, '').trim(); const n = parseFloat(cleaned); return isFinite(n) ? n : null; }; return [...data].sort((a, b) => { const va = a[sortKey]; const vb = b[sortKey]; let cmp; const na = toNum(va), nb = toNum(vb); if (na != null && nb != null && !isNaN(na) && !isNaN(nb)) cmp = na - nb; else if (va == null && vb == null) cmp = 0; else if (va == null) cmp = 1; else if (vb == null) cmp = -1; else cmp = String(va).localeCompare(String(vb)); return sortDir === 'asc' ? cmp : -cmp; }); }, [data, sortKey, sortDir]); return { sorted, sortKey, sortDir, onSort }; }; const SortableTh = ({ children, sortKey, currentKey, dir, onSort, align = 'left', width }) => { const isActive = currentKey === sortKey; return ( ); }; const StatusDot = ({ status }) => { const c = status === 'online' ? 'var(--positive)' : status === 'away' ? 'var(--warning)' : 'var(--text-faint)'; return ; }; const TeamCard = ({ setActive }) => { return (
Your Clicky team {TEAM.length} people
{TEAM.map((p, i) => { const isLead = !!p.primary; const isLastSub = !isLead && i === TEAM.length - 1; return ( {/* Subheader between Lead and the delivery team — line aligns with tree connector below */} {!isLead && i === 1 && (
Delivery team
)}
e.currentTarget.style.background = 'var(--hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} > {/* Tree connector for indented sub-team rows */} {!isLead && ( <> )}
{p.name} {isLead && Lead}
{p.role}
{isLead && (
{p.meta}
)}
); })}
Submit ticket setActive && setActive('team')}>Book a call
); }; // Tiny coloured tile icons for external tools (mock brand marks) const ToolIcon = ({ kind }) => { const box = (bg, fg) => ({ width: 28, height: 28, borderRadius: 6, background: bg, color: fg, display: 'grid', placeItems: 'center', fontSize: 12, fontWeight: 700, fontFamily: 'Inter', flexShrink: 0, }); if (kind === 'ga4') return
A
; if (kind === 'gsc') return
S
; if (kind === 'looker') return
L
; if (kind === 'drive') return
D
; if (kind === 'basecamp')return
B
; if (kind === 'report') return (
); // Pass-through for I.google etc — render in a neutral tile return
{kind}
; }; const FileIcon = ({ kind }) => { const tone = kind === 'pdf' ? '#E03E3E' : kind === 'sheet' ? '#0F9D58' : kind === 'doc' ? '#2A6DF4' : '#9C7B2D'; const letter = kind === 'pdf' ? 'PDF' : kind === 'sheet' ? 'XLS' : kind === 'doc' ? 'DOC' : ''; return (
{kind === 'folder' ? ( ) : ( {letter} )}
); }; const FilesCard = () => { return (
Google Drive Files Connected
{/* Drive breadcrumb */}
Drive Clicky / Evergreen Shared with you
); }; // External tools — analytics platforms, project workspaces, vertical reports const ExternalToolsCard = () => { const groups = [ { id: 'analytics', label: 'Analytics & ad platforms' }, { id: 'project', label: 'Project workspaces' }, { id: 'report', label: 'Vertical reports' }, ]; return (
External tools
{groups.map(g => { const items = EXTERNAL_TOOLS.filter(t => t.kind === g.id); if (!items.length) return null; return ( ); })}
); }; window.TeamCard = TeamCard; window.FilesCard = FilesCard; window.ExternalToolsCard = ExternalToolsCard; // ============================================================ // Events Screen — full page; replaces the dashboard EventsCard // ============================================================ // ============================================================ const CapacityChart = ({ capacity, premium }) => { const { total, remaining, unlimited } = capacity; const filled = total - remaining; const [hover, setHover] = useState(false); const w = 100, h = 6; const pct = total > 0 ? Math.min(1, Math.max(0, filled / total)) : 0; const fillX = pct * w; const lineY = h * 0.5; const remainPct = total > 0 ? remaining / total : 0; const color = premium ? 'var(--brand-primary)' : remainPct < 0.15 ? '#dc2626' : remainPct < 0.4 ? '#d97706' : '#10b981'; const label = unlimited ? `${remaining} of ${total} seats available` : remaining <= 0 ? 'Sold out' : remaining <= 3 ? `Only ${remaining} of ${total} seats left` : `${remaining} of ${total} seats left`; return (
setHover(true)} onMouseLeave={() => setHover(false)} > {fillX > 0 && ( )} {hover && (
{label}
)}
); }; // ============================================================ const EventsScreen = () => { const [filter, setFilter] = useState('all'); const visible = EVENTS.filter(e => filter === 'all' ? true : filter === 'in-person' ? e.type === 'In person' : filter === 'online' ? e.type === 'Online' : filter === 'premium' ? e.premium : true ); return (
{[ { id: 'all', label: 'All' }, { id: 'in-person', label: 'In person' }, { id: 'online', label: 'Online' }, { id: 'premium', label: 'Premium only' }, ].map(t => ( ))}
Sync to my calendar
{visible.map(ev => ( {/* Hero image */}
{ev.image ? ( ) : (
)} {/* Gradient for legibility */}
{/* Date block top-left */}
{ev.when.month}
{ev.when.day}
{/* Tags top-right */}
{ev.type === 'In person' ? : } {ev.city} {ev.premium && ( Premium only )} {ev.soldOut && ( Sold out )}
{/* Time bottom-left over gradient */}
{ev.when.time}
{/* Body */}
{ev.title}
{ev.venue}
{ev.summary}
{ev.capacity ? : null}
{ev.cta}
))}
); }; window.EventsScreen = EventsScreen; // ============================================================ // Events & Insider (dashboard side cards) // ============================================================ const EVENTS = [ { id: 'e-1', when: { day: '14', month: 'May', time: '09:30 — 16:00 BST' }, type: 'In person', city: 'Manchester', title: 'Clicky Performance Day 2026', venue: 'The Bridgewater Hall · Manchester', image: 'assets/events/event-audience.jpg', summary: 'Annual client day — Google Ads roadmap, GA4 deep dives, and case studies from 6 leading UK brands.', capacity: { total: 120, remaining: 0, series: [120, 118, 110, 96, 78, 56, 38, 22, 0] }, soldOut: true, cta: 'Join waitlist', }, { id: 'e-2', when: { day: '21', month: 'May', time: '13:00 — 14:00 BST' }, type: 'Online', city: 'Webinar', title: 'Performance Max — what changed in Q2', venue: 'Zoom · 60 min', image: 'assets/events/event-talk.jpg', summary: 'Walk-through of Google\'s May update: new asset group controls, expanded reporting, and what to test.', capacity: { total: 500, remaining: 312, series: [500, 488, 460, 430, 405, 378, 354, 330, 312], unlimited: true }, cta: 'Register', }, { id: 'e-3', when: { day: '04', month: 'Jun', time: '18:30 — 21:00 BST' }, type: 'In person', city: 'Manchester', title: 'Hospitality Marketers Mixer', venue: 'Hawksmoor Deansgate · Manchester', image: 'assets/events/event-audience.jpg', summary: 'Drinks & canapés with the Clicky hospitality team and 30 invited operators. Hosted by Joe Marshall.', capacity: { total: 30, remaining: 2, series: [30, 28, 24, 20, 16, 12, 8, 5, 2] }, cta: 'Accept invite', premium: true, }, { id: 'e-4', when: { day: '18', month: 'Jun', time: '12:00 — 13:00 BST' }, type: 'Online', city: 'Workshop', title: 'GA4: building useful exploration reports', venue: 'Zoom · 60 min', image: 'assets/events/event-workshop.jpg', summary: 'Hands-on workshop with Nathan Smith — bring your GA4 property and leave with 3 saved explorations.', capacity: { total: 24, remaining: 8, series: [24, 23, 21, 19, 16, 14, 11, 9, 8] }, cta: 'Register', }, { id: 'e-5', when: { day: '25', month: 'Jun', time: '09:30 — 13:00 BST' }, type: 'In person', city: 'Manchester', title: 'Marketing as a Profit Centre', venue: 'Clicky HQ · King Street, Manchester', image: 'assets/events/event-profit.jpg', summary: 'Half-day session on shifting marketing reporting from cost-control to P&L contribution. Live financial modelling.', capacity: { total: 20, remaining: 6, series: [20, 19, 17, 15, 13, 11, 9, 7, 6] }, cta: 'Reserve seat', }, { id: 'e-6', when: { day: '09', month: 'Jul', time: '14:00 — 15:30 BST' }, type: 'Online', city: 'Webinar', title: '"It\'ll take work — but the payoff is worth it"', venue: 'Zoom · 90 min', image: 'assets/events/event-payoff.jpg', summary: 'Long-form session with Joe Marshall on building an honest 12-month growth roadmap with your agency.', capacity: { total: 500, remaining: 421, series: [500, 495, 488, 478, 466, 452, 440, 430, 421], unlimited: true }, cta: 'Register', }, ]; const INSIDER = [ { id: 'i-1', tag: 'Beta access', category: 'Beta access', title: 'AI bid script — early access for premium clients', when: 'Yesterday', readTime: '4 min', body: "We're piloting a Clicky-built bid adjustment script that uses GA4 micro-conversions. Evergreen Escapes is on the shortlist — Joe will brief you on Tuesday.", by: 'Matt Jones · PPC Team Lead', cover: { hue: 220, glyph: 'spark' }, image: 'assets/insider/insider-1.jpg', featured: true, }, { id: 'i-2', tag: 'Heads-up', category: 'Heads-up', title: 'Google CSS programme price change · 1 June', when: '2 days ago', readTime: '3 min', body: "Comparison Shopping Service fees rise 8% from June. We've already modelled the impact on your Shopping spend — net effect is negligible at current ROAS.", by: 'Joe Marshall · Head of Client Strategy', cover: { hue: 28, glyph: 'cart' }, image: 'assets/insider/insider-2.png', }, { id: 'i-3', tag: 'Benchmark', category: 'Benchmark', title: 'Hospitality CPC down 11% MoM across Clicky portfolio', when: '4 days ago', readTime: '5 min', body: 'Aggregate data from 42 hospitality clients shows softer auction pressure into May. Good window to test broader match types on branded campaigns.', by: 'Nathan Smith · Agency Systems', cover: { hue: 160, glyph: 'trend' }, image: 'assets/insider/insider-3.png', }, { id: 'i-4', tag: 'Playbook', category: 'Playbook', title: 'Performance Max for hospitality — the 9-month playbook', when: '6 days ago', readTime: '8 min', body: 'Asset-group structure, audience signals, and the conversion-value setup that gets PMax to outperform standard Shopping for holiday-park brands. Built from 9 months of testing across 12 client accounts.', by: 'Matt Jones · PPC Team Lead', cover: { hue: 240, glyph: 'spark' }, }, { id: 'i-5', tag: 'Benchmark', category: 'Benchmark', title: 'Easter 2026 demand patterns vs 2025 — what we saw', when: '8 days ago', readTime: '4 min', body: 'Search demand for "lodge breaks" peaked 9 days earlier than last year. Aggregate impression-share data across 30 Clicky hospitality clients suggests a permanent shift in booking lead times.', by: 'Nathan Smith · Agency Systems', cover: { hue: 18, glyph: 'trend' }, }, { id: 'i-6', tag: 'Case study', category: 'Case study', title: 'How Lakeland Lodges grew direct bookings 38%', when: '11 days ago', readTime: '6 min', body: 'A 9-month story of swapping OTA reliance for owned demand: brand campaigns, an email reactivation series, and a CRO-led booking flow rebuild. Numbers, screenshots, and the things we got wrong.', by: 'Hayley Roberts · UX Research & Design', cover: { hue: 150, glyph: 'cart' }, }, { id: 'i-7', tag: 'Industry', category: 'Industry', title: 'Search Generative Experience — what travel operators need to know', when: '2 weeks ago', readTime: '7 min', body: 'Google\'s SGE is now rolling out broadly to UK searches. We tracked 1,200 hospitality queries to understand where AI overviews appear, what they cite, and what it means for your organic strategy.', by: 'Joe Marshall · Head of Client Strategy', cover: { hue: 280, glyph: 'spark' }, }, { id: 'i-8', tag: 'Beta access', category: 'Beta access', title: 'GA4 Booking Engine integration — open beta', when: '2 weeks ago', readTime: '3 min', body: 'New direct booking-engine connector for Sykes, SuperControl and TravelClick — pulls confirmed booking value into GA4 within 90 seconds. Premium clients can request access from this week.', by: 'Nathan Smith · Agency Systems', cover: { hue: 200, glyph: 'cog' }, }, { id: 'i-9', tag: 'Industry', category: 'Industry', title: 'Q3 booking demand outlook — early signals', when: '3 weeks ago', readTime: '5 min', body: 'Forecast aggregating Google Trends, Skyscanner inbound flight searches, and our own portfolio booking-curve data. Early read: school-holiday weeks looking strong, shoulder weeks softer than 2025.', by: 'Joe Marshall · Head of Client Strategy', cover: { hue: 200, glyph: 'trend' }, }, { id: 'i-10', tag: 'Webinar', category: 'Webinar', title: 'Webinar: Profitable PMax for travel — 14 May', when: '3 weeks ago', readTime: '1 min', body: 'Matt and Joe walk through the PMax setup blueprint we use across the Clicky hospitality portfolio. 45-min session, live Q&A. Premium client invites going out next week.', by: 'Matt Jones · PPC Team Lead', cover: { hue: 340, glyph: 'cog' }, }, { id: 'i-11', tag: 'Heads-up', category: 'Heads-up', title: 'Cookie consent: TCF v2.4 changes from 30 June', when: '3 weeks ago', readTime: '4 min', body: 'IAB TCF 2.4 brings new vendor disclosure rules and stricter purpose mapping. We\'ve audited the top 6 CMP providers and have a 1-page summary of what action (if any) you need to take.', by: 'Nathan Smith · Agency Systems', cover: { hue: 50, glyph: 'cog' }, }, { id: 'i-12', tag: 'Benchmark', category: 'Benchmark', title: 'Brand vs non-brand spend split — 200-account benchmark', when: '4 weeks ago', readTime: '6 min', body: 'We pulled the brand/non-brand split for 200 hospitality accounts across the UK Clicky network. Median sits at 38/62; top-quartile direct-booking brands sit closer to 28/72. The gap is closing fast.', by: 'Matt Jones · PPC Team Lead', cover: { hue: 110, glyph: 'trend' }, }, { id: 'i-13', tag: 'Playbook', category: 'Playbook', title: 'Reels playbook — 12 shot patterns that drove £3.20 CPM', when: '5 weeks ago', readTime: '9 min', body: 'After 6 months of Meta Reels testing across 8 hospitality clients, we mapped the 12 shot/copy patterns that consistently outperformed the average. With timing, music guidance, and the briefs we used.', by: 'Hayley Roberts · UX Research & Design', cover: { hue: 320, glyph: 'spark' }, }, { id: 'i-14', tag: 'Industry', category: 'Industry', title: 'Microsoft Ads UK growth report — 2025 wrap', when: '6 weeks ago', readTime: '5 min', body: 'Microsoft\'s UK ad business grew 18% YoY in 2025, with travel and hospitality leading the surge. Pulling together their own data with our portfolio benchmarks to size the opportunity for clients without a Bing presence.', by: 'Nathan Smith · Agency Systems', cover: { hue: 200, glyph: 'cart' }, }, ]; const INSIDER_CATEGORIES = ['All', 'Beta access', 'Heads-up', 'Benchmark', 'Playbook', 'Case study', 'Industry', 'Webinar']; const EventsCard = () => { return (
Upcoming events
{EVENTS.map(ev => (
e.currentTarget.style.background = 'var(--hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'} > {/* Date block */}
{ev.when.month}
{ev.when.day}
{/* Body */}
{ev.type === 'In person' ? : } {ev.city} {ev.premium && Premium only} {ev.when.time}
{ev.title}
{ev.venue}
{ev.summary}
{ev.capacity ? (ev.capacity.unlimited ? 'Open to all clients' : `${ev.capacity.remaining} of ${ev.capacity.total} seats left`) : ''} {ev.cta}
))}
); }; window.EventsCard = EventsCard; const InsiderGlyph = ({ kind }) => { const stroke = { stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', fill: 'none' }; if (kind === 'spark') return ( ); if (kind === 'cart') return ( ); if (kind === 'trend') return ( ); return null; }; const InsiderTiles = ({ setActive, openInsiderArticle }) => { return (
Clicky Insider — Premium Pro
Confidential briefings for Clicky premium clients
setActive && setActive('insider')}>Read all
{/* Tile grid */}
{INSIDER.slice(0, 3).map(item => { const hue = item.cover?.hue ?? 220; const tagTone = item.tag === 'Beta access' ? 'positive' : item.tag === 'Heads-up' ? 'warning' : 'neutral'; return (
{ if (openInsiderArticle) openInsiderArticle(item.id); else if (setActive) setActive('insider'); }} style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius)', background: 'var(--surface)', overflow: 'hidden', display: 'flex', flexDirection: 'column', cursor: 'pointer', transition: 'border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease', }} onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--primary)'; e.currentTarget.style.boxShadow = 'var(--shadow-md)'; e.currentTarget.style.transform = 'translateY(-1px)'; }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }} > {/* Cover */}
{item.image ? ( ) : ( <>
)} {/* Top gradient for legibility when there's an image */} {item.image && (
)} {/* Corner badge */} {item.tag} {item.when}
{/* Body */}
{item.title}
{item.body}
{item.by}
); })}
Confidential to Clicky premium clients · do not forward
); }; window.InsiderTiles = InsiderTiles; // ============================================================ // Clicky Insider — full page (sidebar destination) // ============================================================ const INSIDER_LONG = { 'i-1': [ "We're piloting a Clicky-built bid adjustment script that uses GA4 micro-conversions — add-to-cart, scroll-depth and 60-second engagement — as leading signals to bid against, days before a booking actually completes.", "Early results from three premium portfolios show a 14–22% lift in tROAS efficiency in the first four weeks, with the biggest gains on long-consideration purchases (luxury hospitality, B2B, considered DTC).", "Evergreen Escapes is on the shortlist for the next cohort. Joe will brief you on Tuesday — bring questions on your assisted-conversion paths and we'll model the impact live.", ], 'i-2': [ "Google's Comparison Shopping Service fees rise 8% from 1 June. For most accounts the headline number sounds bigger than the impact — at current ROAS your net effective change is well under 1%.", "We've already modelled this against your current Shopping mix and the recommendation is no change to bidding strategy this month. We'll revisit at the June review once the new fees are live in the auction.", "If you want to see the full sensitivity table — fees vs. ROAS vs. budget — Joe has it ready and can walk you through it in your next monthly.", ], 'i-3': [ "Aggregate data from 42 hospitality clients across the Clicky portfolio shows a clear softening of auction pressure into May — average CPCs down 11% MoM and impression share up 6 points on branded campaigns.", "The market is telling us competitors have pulled budget after a softer Easter. That's a window — not forever, but a window — to test broader match types on brand and capture defensive volume cheaply.", "Nathan's running a structured test on three accounts already; we'll share the playbook in next month's Insider once we've got 4 weeks of data.", ], }; // Per-card cover (image or coloured-gradient fallback with a glyph) const InsiderCover = ({ item, aspect = '16 / 9' }) => { const hue = item.cover?.hue ?? 220; return (
{item.image ? ( ) : ( <>
)} {item.image && (
)} {item.tag} {item.when}
); }; const InsiderCard = ({ item, onOpen }) => ( ); const InsiderHero = ({ item, onOpen }) => ( ); const PAGE_SIZE = 9; const InsiderArticleView = ({ item, onBack }) => { const long = INSIDER_LONG[item.id] || [item.body]; return (
} onClick={onBack}> Back to Insider
{/* Hero */}
{item.image ? : } {item.image && (
)} {item.image && ( <>
{item.tag} {item.when}
{item.title}
)}
{/* Title (when image is absent it isn't already shown over the hero) */} {!item.image && (

{item.title}

)}
{item.by.split(' · ')[0]}
{item.by.split(' · ')[1]} · {item.when} · {item.readTime} read
}>Save PDF
{long.map((p, i) => (

{p}

))}
Confidential to Clicky premium clients · do not forward
); }; const InsiderScreen = ({ initialArticleId, clearInitialArticle }) => { const [view, setView] = useState(initialArticleId ? 'article' : 'index'); // 'index' | 'article' const [activeId, setActiveId] = useState(initialArticleId || null); const [category, setCategory] = useState('All'); const [query, setQuery] = useState(''); const [page, setPage] = useState(1); const [subscribed, setSubscribed] = useState(false); // If app-level deep-link arrives later (user clicks another article from // the dashboard while we're already on this screen), honour it. useEffect(() => { if (initialArticleId) { setActiveId(initialArticleId); setView('article'); window.scrollTo?.({ top: 0, behavior: 'smooth' }); if (clearInitialArticle) clearInitialArticle(); } }, [initialArticleId]); // eslint-disable-line const open = (id) => { setActiveId(id); setView('article'); window.scrollTo?.({ top: 0, behavior: 'smooth' }); }; const back = () => { setView('index'); window.scrollTo?.({ top: 0, behavior: 'smooth' }); }; // Featured = first article tagged featured (or first overall) const featured = INSIDER.find(i => i.featured) || INSIDER[0]; // Filter the rest const filtered = INSIDER.filter(a => a.id !== featured.id || category !== 'All' || query.trim()) .filter(a => category === 'All' || a.category === category) .filter(a => !query.trim() || (a.title + ' ' + (a.body || '')).toLowerCase().includes(query.trim().toLowerCase())); const visible = filtered.slice(0, page * PAGE_SIZE); const moreLeft = filtered.length - visible.length; const article = view === 'article' && activeId ? INSIDER.find(i => i.id === activeId) : null; return (
Clicky Insider Pro )} subtitle="Confidential briefings, betas and benchmarks for Clicky premium clients." actions={( subscribed ? ( setSubscribed(false)} icon={} title="You're subscribed — click to unsubscribe" >Email alerts on ) : ( setSubscribed(true)} icon={} >Sign up for Emails ) )} /> {article ? ( ) : ( <> {/* Featured hero — only when not filtering */} {category === 'All' && !query.trim() && (
)} {/* Filter bar */}
{INSIDER_CATEGORIES.map(t => { const count = t === 'All' ? INSIDER.length : INSIDER.filter(a => a.category === t).length; if (count === 0) return null; return ( ); })}
{I.search} { setQuery(e.target.value); setPage(1); }} placeholder="Search briefings" style={{ border: 'none', outline: 'none', background: 'transparent', color: 'var(--text)', fontSize: 12, flex: 1, fontFamily: 'inherit', }}/> {query && ( )}
{/* Result count */}
Showing {visible.length} of {filtered.length} Sorted by most recent
{/* Grid */} {visible.length === 0 ? (
No briefings match these filters.
{ setCategory('All'); setQuery(''); setPage(1); }}>Reset filters
) : (
{visible.map(item => ( ))}
)} {/* Load more */} {moreLeft > 0 && (
setPage(p => p + 1)}> Load {Math.min(moreLeft, PAGE_SIZE)} more
)} {/* Confidentiality footer */}
Every briefing on this page is confidential to Clicky premium clients — please don't forward outside your team.
)}
); }; window.InsiderScreen = InsiderScreen; // ============================================================ // Dashboard (welcome + account manager) // ============================================================ const SERVICES = [ // === Discovery & Strategy === { id: 'playbook', category: 'Discovery & Strategy', title: 'Clicky Playbook®', icon: 'book', blurb: 'Annual strategy deep-dive — market position, platforms, and the year ahead.', pricing: 'From £5,000 + VAT', timeline: '4–6 weeks', active: false, isNew: true, why: 'Your booking funnel and channel mix would benefit from a 12-month plan — half the cost is credited back against future work.' }, // === Paid Media === { id: 'google', category: 'Paid Media', title: 'Google Ads', icon: 'google', blurb: 'Search, Performance Max & Shopping campaigns — managed end-to-end.', pricing: '£1,200 / month retainer', timeline: 'Active', active: true, since: 'Apr 2024' }, { id: 'microsoft', category: 'Paid Media', title: 'Microsoft Ads', icon: 'bing', blurb: 'Bing & Edge audiences — typically older, higher-intent search traffic.', pricing: 'From £450 / month', timeline: '1 week setup', active: false, why: 'Bing audience skews older with higher disposable income — strong fit for premium lodge stays.' }, { id: 'aishop', category: 'Paid Media', title: 'Google AI Shopping Ads', icon: 'cart', blurb: 'AI-driven Shopping placements across Search, YouTube and Discover.', pricing: 'From £900 / month', timeline: '2 weeks', active: false, isNew: true, why: 'Pulls product feeds into AI-curated answers — early-mover advantage while CPCs are still low.' }, { id: 'meta', category: 'Paid Media', title: 'Meta Ads', icon: 'meta', blurb: 'Facebook & Instagram — creative, targeting and reporting.', pricing: 'From £1,200 / month', timeline: '2 weeks setup', active: false, why: 'Strong fit for lodge imagery — Meta drives ~38% lower CPA than Google for hospitality clients.' }, { id: 'youtube', category: 'Paid Media', title: 'YouTube Ads', icon: 'youtube', blurb: 'Pre-roll, in-stream and shorts — visual storytelling at scale.', pricing: 'From £900 / month', timeline: '2–3 weeks', active: false, why: 'Connected-TV viewing is the largest UK travel-research channel — under-utilised in your sector.' }, { id: 'linkedin', category: 'Paid Media', title: 'LinkedIn Ads', icon: 'linkedin',blurb: 'B2B targeting by job title, company and seniority.', pricing: 'From £1,500 / month', timeline: '2 weeks setup', active: false }, { id: 'tiktok', category: 'Paid Media', title: 'TikTok Ads', icon: 'tiktok', blurb: 'Short-form video for younger, mobile-first audiences.', pricing: 'From £1,000 / month', timeline: '2 weeks setup', active: false }, { id: 'pinterest', category: 'Paid Media', title: 'Pinterest Ads', icon: 'pinterest',blurb: 'High-intent inspiration searches — strong for travel & home.', pricing: 'From £600 / month', timeline: '1–2 weeks', active: false, why: 'Cornish hospitality brands see 3× higher save-rates on Pinterest vs. paid social.' }, { id: 'chatgpt', category: 'Paid Media', title: 'ChatGPT Ads', icon: 'spark', blurb: 'Sponsored placements inside ChatGPT search results.', pricing: 'From £750 / month', timeline: '1 week', active: false, isNew: true }, { id: 'spotify', category: 'Paid Media', title: 'Spotify Ads', icon: 'audio', blurb: 'Audio & display ads served between tracks.', pricing: 'From £500 / month', timeline: '1 week', active: false }, { id: 'reddit', category: 'Paid Media', title: 'Reddit Ads', icon: 'reddit', blurb: 'Community-driven targeting against UK travel and lifestyle subs.', pricing: 'From £600 / month', timeline: '2 weeks', active: false }, // === SEO & Social === { id: 'seo', category: 'SEO & Social', title: 'SEO', icon: 'search', blurb: 'Technical audits, keyword strategy and ongoing content production.', pricing: 'From £900 / month', timeline: 'Ongoing', active: false, why: 'Likely 3× organic bookings within 12 months — your domain has untapped authority.' }, { id: 'llm', category: 'SEO & Social', title: 'GEO / LLM Optimisation', icon: 'spark', blurb: 'Be the answer ChatGPT, Gemini & Copilot give to your category questions.', pricing: 'From £750 / month', timeline: '6 weeks', active: false, isNew: true, why: 'AI search is rising fast — getting cited now beats catching up later.' }, { id: 'organic', category: 'SEO & Social', title: 'Organic Social Media', icon: 'instagram',blurb: 'Daily content planning, posting and community management.', pricing: 'From £700 / month', timeline: '2 weeks setup', active: false, why: 'Lodge imagery is your biggest content asset and it\u2019s sitting unused on a phone.' }, { id: 'influencer', category: 'SEO & Social', title: 'Influencer Marketing', icon: 'people', blurb: 'Vetted creator partnerships — content, distribution and reporting.', pricing: 'From £2,000 / campaign', timeline: '3–4 weeks', active: false }, // === Digital Experiences === { id: 'websites', category: 'Digital Experiences', title: 'Websites', icon: 'page', blurb: 'End-to-end website builds — design, build and launch.', pricing: 'From £15,000', timeline: '8–12 weeks', active: false }, { id: 'webmgmt', category: 'Digital Experiences', title: 'Website Management', icon: 'wrench', blurb: 'Hosting, updates, fixes and ongoing improvements.', pricing: 'From £350 / month', timeline: 'Ongoing', active: false }, { id: 'landing', category: 'Digital Experiences', title: 'Landing Pages', icon: 'page', blurb: 'Conversion-optimised pages built and A/B tested by our design team.', pricing: 'From £450 / page', timeline: '2–3 weeks', active: true, since: 'Sep 2024', activity: '2 pages live' }, { id: 'aicreative', category: 'Digital Experiences', title: 'AI Creative & Automation', icon: 'spark', blurb: 'AI-generated and AI-assisted creative production at scale.', pricing: 'From £1,200 / month', timeline: '3 weeks setup', active: false, isNew: true, why: 'Your seasonal-shoot cycle is a 6-month process — AI cuts that to days.' }, { id: 'chatbot', category: 'Digital Experiences', title: 'AI Chatbots', icon: 'chat', blurb: 'Branded conversational agents for booking, FAQs and support.', pricing: 'From £2,500 setup', timeline: '4 weeks', active: false, isNew: true }, // === Analytics, CRM & AI Automation === { id: 'aicrm', category: 'Analytics, CRM & AI Automation', title: 'AI & CRM Consultancy', icon: 'wand', blurb: 'Bridge marketing and sales with AI-powered automation.', pricing: 'From £1,800 / month', timeline: '4 weeks', active: false, why: 'Your booking enquiries currently sit in email — a CRM with AI triage would 2× response time.' }, { id: 'salesforce', category: 'Analytics, CRM & AI Automation', title: 'Salesforce', icon: 'cloud', blurb: 'Implementation, integration and ongoing admin.', pricing: 'Custom quote', timeline: '8–16 weeks', active: false }, { id: 'hubspot', category: 'Analytics, CRM & AI Automation', title: 'HubSpot', icon: 'cog', blurb: 'CRM, marketing hub and sales automation setup.', pricing: 'From £4,500 setup', timeline: '4–8 weeks', active: false }, { id: 'zoho', category: 'Analytics, CRM & AI Automation', title: 'Zoho CRM', icon: 'cog', blurb: 'Affordable CRM with full Clicky implementation support.', pricing: 'From £2,500 setup', timeline: '3–6 weeks', active: false }, { id: 'ga4', category: 'Analytics, CRM & AI Automation', title: 'Google Analytics 4 (GA4)', icon: 'gauge', blurb: 'Setup, custom events, dashboards and ongoing analysis.', pricing: '£1,800 fixed setup', timeline: 'Active', active: true, since: 'Mar 2026' }, ]; const ServiceIcon = ({ kind, size = 18 }) => { const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }; switch (kind) { case 'page': return ; case 'mail': return ; case 'search': return ; case 'share': return ; case 'film': return ; case 'gauge': return ; case 'book': return ; case 'google': return ; case 'bing': return ; case 'cart': return ; case 'meta': return ; case 'youtube': return ; case 'linkedin': return ; case 'tiktok': return ; case 'pinterest': return ; case 'spark': return ; case 'audio': return ; case 'reddit': return ; case 'instagram': return ; case 'people': return ; case 'wrench': return ; case 'chat': return ; case 'wand': return ; case 'cloud': return ; case 'cog': return ; default: return null; } }; const ServicesSidebar = ({ onPick }) => (
Explore other services
Tell us what you'd like to grow next.
{SERVICES.map(s => ( ))}
); const ServicesGrid = ({ onPick }) => (
Explore other services
Beyond Google Ads — tell us what you'd like to grow next.
No commitment · free quote
{SERVICES.map(s => ( ))}
Don't see what you need? Tell us more.
onPick({ id: 'other', title: 'Something else', blurb: 'Tell us what you have in mind.', icon: 'page', pricing: 'Custom quote', timeline: 'Varies' })}>Request something else
); // ============================================================ // Growth Opportunities — full Clicky service catalog organised // by category. Active services are shown in-line, dimmed, with // an "Active" badge; inactive services pop forward as the // suggested next step. Recommended (with `why:`) bubbles up. // ============================================================ const SERVICE_CATEGORIES = [ 'Discovery & Strategy', 'Paid Media', 'SEO & Social', 'Digital Experiences', 'Analytics, CRM & AI Automation', ]; const GrowthOpportunitiesCard = ({ onPick, manager }) => { const [activeCat, setActiveCat] = useState('Recommended'); const recommended = SERVICES.filter(s => !s.active && s.why); const totalActive = SERVICES.filter(s => s.active).length; const totalInactive = SERVICES.length - totalActive; const visible = activeCat === 'Recommended' ? recommended : SERVICES.filter(s => s.category === activeCat); const tabs = [ { id: 'Recommended', label: 'Recommended', count: recommended.length, badge: true }, ...SERVICE_CATEGORIES.map(c => ({ id: c, label: c, count: SERVICES.filter(s => s.category === c).length, })), ]; return ( {/* Header */}
Clicky services for {CLIENT.shortName}
Everything we offer — recommended additions surface first
{totalActive} active · {totalInactive} available
{/* Category tabs */}
{tabs.map(t => { const isActive = activeCat === t.id; return ( ); })}
{/* Service rows */}
{visible.length === 0 && (
Nothing in this category yet.
)} {visible.map(s => { const isActive = s.active; return (
{ if (!isActive) e.currentTarget.style.background = 'var(--hover)'; }} onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }} >
{s.title} {isActive && ( Active{s.since ? ` · since ${s.since}` : ''} )} {s.isNew && !isActive && ( New )} {s.why && !isActive && ( Recommended )}
{s.why && !isActive ? s.why : s.blurb}
{s.pricing} · {s.timeline}
{isActive ? ( onPick(s)}>View ) : ( <> onPick(s)}>Ask Joe onPick({ ...s, mode: 'proposal' })}>Get proposal )}
); })}
{/* Footer */}
{manager && (
Your account manager
{manager.name}
)}
onPick({ id: 'other', title: 'Something else', blurb: 'Tell us what you have in mind.', icon: 'page', pricing: 'Custom quote', timeline: 'Varies' })}> + Request something else Book a 30-min review
); }; window.GrowthOpportunitiesCard = GrowthOpportunitiesCard; window.SERVICES = SERVICES; const EnquireModal = ({ service, onClose }) => { const [form, setForm] = useState({ goal: '', timeline: service.timeline || 'Within a month', budget: '£1k–£5k', notes: '' }); const [sent, setSent] = useState(false); if (!service) return null; const submit = (e) => { e.preventDefault(); setSent(true); }; return (
e.stopPropagation()} style={{ width: '100%', maxWidth: 520, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', boxShadow: '0 10px 40px rgba(0,0,0,0.3)', }}>
Enquire about {service.title}
{service.pricing} · {service.timeline}
{sent ? (
{I.check}
Enquiry sent
Joe will be in touch within 1 working day with next steps and a quote.
Done
) : (
onSort && onSort(sortKey)} style={{ padding: '12px 14px', textAlign: align, fontSize: 11, fontWeight: 600, color: isActive ? 'var(--text)' : 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', borderBottom: '1px solid var(--border)', cursor: 'pointer', userSelect: 'none', transition: 'color 120ms ease', whiteSpace: 'nowrap', ...(width != null ? { width } : {}), }} onMouseEnter={e => e.currentTarget.style.color = 'var(--text)'} onMouseLeave={e => e.currentTarget.style.color = isActive ? 'var(--text)' : 'var(--text-muted)'} > {children} {isActive ? ( dir === 'asc' ? : ) : ( )}