What Is SEO Optimization & Why It Matters For B2B Growth

What Is SEO Optimization & Why It Matters For B2B Growth

What **_is_** SEO optimization?! We hear that a lot… It is the strategic process of fine-tuning every aspect of your website - from how pages are structured and coded to the quality of your content and external signals - to earn higher rankings in search engines and engage the right audience. By treating optimization as an ongoing commitment, B2B organizations can ensure their digital presence evolves with market demands and algorithm updates. For B2B companies, where purchase decisions involve lengthy research phases and multiple stakeholders, being visible in the right search results is essential to capturing high-value leads. Unlike consumer markets driven by impulse, B2B buyers follow a deliberate journey; they prioritise authoritative insights, peer recommendations and detailed comparisons before making a decision. A targeted SEO strategy helps position your brand at each stage, turning prospects into conversion-ready opportunities. Canadian businesses are increasingly embracing digital channels to reach clients beyond their local networks. According to Statistics Canada, one-third of enterprises reported e-commerce sales in 2021, highlighting the urgency to stand out in a crowded online environment. As B2B firms invest in smarter marketing, a robust SEO foundation has become a competitive necessity rather than an optional extra. This article will begin by defining SEO optimization within a B2B context, then explore its core components - on-page, technical and off-page - before offering practical guidance on implementation and measurement. Finally, we’ll look ahead to emerging trends that will keep your strategy future-proof and primed for growth. ## 1\. Defining what is SEO optimization in the B2B context In B2B marketing, “SEO optimization” goes beyond a one-off tweak. It’s the coordinated effort to make your site both understandable to search engines and genuinely useful to potential buyers, coupled with a continuous feedback loop that adapts to user behaviour and algorithm updates. By weaving together on-page, technical and off-page activities under an ongoing optimization mindset, B2B organisations can stay visible to decision-makers throughout long and complex purchase journeys. Key terms: - **[Search Engine Optimization (SEO)](/blog/what-is-seo-optimization-why-it-matters):** The practice of improving your site’s structure, content and link profile to earn higher rankings in unpaid (organic) search results. - **Optimization:** An iterative cycle of collecting performance data (traffic, rankings, conversions), identifying opportunities and refining content, technical setup or outreach tactics. - **SEO optimization:** The fusion of both ideas,targeted SEO efforts maintained by regular analysis, testing and updates. ### 1.1 What is SEO? At its core, SEO is about two things: making your website easy for search-engine crawlers to index and valuable enough that users click through and engage. The primary goal is to rank for keywords that align with your buyer personas. Historically, SEO began with simple tactics,think exact-match keywords and link-quantity,but has evolved into a holistic discipline that balances relevance, authority and user experience. ### 1.2 What does optimization add? Optimization transforms SEO from a project into a practice. Rather than “set and forget,” it introduces a loop: 1. **Research:** Gather data on keyword performance, page load times and backlink health. 2. **Implement:** Adjust headings, compress images or secure new links from industry publications. 3. **Measure:** Track changes in organic traffic, bounce rates and lead submissions. 4. **Update:** Refresh content, fix technical errors or pivot outreach to new domains. For example, you might publish a white paper, monitor its download rate and search positioning, then refine the title, add new internal links and promote it via a guest post to boost authority. ### 1.3 SEO vs. SEM vs. Search Experience Optimization B2B marketers often juggle related disciplines. Understanding their differences clarifies where to focus budgets and resources: | | SEO | SEM | Search Experience Optimization | | --- | --- | --- | --- | | Objective | Improve organic rankings and traffic | Combine organic and paid search efforts | Holistic user experience across search and on-site | | Visibility | Unpaid (organic) | Paid and unpaid | Organic, paid, site search, UX metrics | | Key tactics | Keyword research, [content updates](/blog), link building | PPC campaigns, bid management, ad copy testing | On-page UX, personalisation, CRO, AI-driven insights | | Typical KPIs | Impressions, clicks, organic conversions | Cost per click (CPC), ad impressions, ROI | Engagement rate, time on site, search‐to-lead conversion | Integrating all three ensures you capture interest at every touchpoint,whether prospects click an ad, land via search or explore your site’s rich content. For B2B growth, this blended approach not only drives top-of-funnel visibility but also fosters seamless buyer experiences. ## 2\. The significance of SEO optimization for B2B growth In B2B markets, leads are fewer but far more valuable than in consumer segments. A single contract can be worth thousands,or even millions,of dollars, so every opportunity counts. [SEO optimization fuels top-of-funnel visibility](/blog), ensuring your brand appears when decision-makers research solutions. By investing in organic search, you build a pipeline of qualified prospects that sustains longer sales cycles and reduces reliance on costly, short-lived paid campaigns. Key advantages of a robust SEO programme: - **Increased qualified leads:** Prospects who find you via targeted keywords are already researching solutions, not just browsers. - **Lower cost per acquisition:** While paid ads stop delivering once the budget is spent, organic traffic compounds over time. - **Sustainable growth:** High-ranking pages continue to attract traffic and inquiries long after initial publication. ### 2.1 Unique characteristics of the B2B buyer journey B2B purchasing is a multi-stage, research-intensive process, often involving several stakeholders. SEO must address the distinct needs at each step: | Stage | Buyer intent | SEO tactic | | --- | --- | --- | | Awareness | “What is a [fractional CMO](/blog/what-are-outsourced-cmo-services)?” | Blog posts, “what is” guides | | Consideration | “Benefits of outsourcing a CMO” | White papers, comparison articles | | Decision | “Top B2B marketing consultants” | Case studies, service landing pages | By mapping content formats to intent, you guide prospects from initial curiosity through to a conversion-ready decision. ### 2.2 Core benefits of SEO optimization for B2B 1. **Higher-quality leads** Organisations that find you via specific, intent-based queries are more likely to convert than general traffic. 2. **Improved brand authority** Consistent visibility for niche topics,through thought-leadership content and case studies,positions your business as a trusted expert. 3. **Better alignment between sales and marketing** SEO data (e.g., top-performing pages, search terms) informs both teams, ensuring marketing efforts address real pain points. Hypothetical ROI comparison over six months: ``` Paid ads: Spend | $30,000 Leads | 60 (avg. CPC $5) Conversion | 10% New clients | 6 Organic SEO: Investment | $20,000 (content + technical work) Leads | 100 Conversion | 15% New clients | 15 ``` Even with a lower upfront investment, SEO can deliver 2.5× more new clients and a lower cost per acquisition. ### 2.3 Evidence of impact in Canada Canadian enterprises are rapidly adopting digital channels: - One-third (33%) of Canadian businesses reported e-commerce sales in 2021 (Statistics Canada). - A significant share of small and medium firms have embraced cloud computing and ICT tools to streamline operations and collaborate remotely. These trends underscore that **online visibility is no longer optional**. B2B firms looking to capture a growing pool of digitally active buyers must prioritise SEO optimization to compete and thrive in Canada’s evolving marketplace. ## 3\. Core components of SEO optimization Every successful [SEO strategy](/blog/optimize-your-marketing-strategy-for-b2b) rests on three interdependent pillars: on-page, technical and off-page optimization. Treat them as the gears in a finely tuned engine,each one must turn smoothly for your entire site to perform at its best. On-page work makes pages relevant and engaging; technical work ensures they can be found and displayed correctly; off-page activities signal to search engines that your brand is trusted and authoritative. These components often overlap. For example, fast-loading pages (technical) improve user engagement (on-page), which in turn encourages more backlinks (off-page). Likewise, insightful content (on-page) attracts mentions and social shares (off-page), while clear site structure (technical) makes it easier for search engines to reward that content. Balancing all three creates the synergy you need for sustainable B2B growth. ### 3.1 On-page optimization On-page optimization focuses on everything a user (and search-engine crawler) sees on an individual page. Its goal is to match each page’s content and HTML elements with the search intent of your target audience. Key on-page elements include: - **[Content relevance and quality](/blog/what-is-seo-optimization-why-it-matters):** In-depth, authoritative information that answers your buyer’s questions. - **Keyword placement:** Natural use of target terms in titles, headings, and body copy. - **Meta tags:** Compelling `` tags and `<meta description>` snippets that drive clicks. - **Header structure:** Logical use of H1, H2 and H3 tags to guide readers and crawlers. Example: Optimising a product page for “Fractional CMO Services” 1. Rewrite the H1 to “Fractional CMO Services for B2B Growth” to reflect the main keyword. 2. Add a succinct meta description: “Part-time CMO expertise to accelerate your B2B marketing,no long-term commitment.” 3. Break content into sections: Benefits, Pricing, Case Studies, FAQs,each with relevant subheadings. 4. Weave secondary keywords like “outsourced marketing leadership” into the introduction and bullet lists. These tweaks not only clarify topic relevance but make the page more scannable, boosting both user satisfaction and search visibility. ### 3.2 Technical optimization Technical SEO underpins everything on your site. Even the best content won’t rank if search engines can’t crawl, index or render your pages properly. This pillar addresses the behind-the-scenes infrastructure that powers on-page and off-page efforts. Focus areas for technical optimization: - **Site architecture & XML sitemaps:** Logical folder structures and up-to-date sitemaps guide crawlers to all important pages. - **Mobile-first indexing:** Ensuring responsive design and touch-friendly navigation, as most searches now originate on mobile devices. - **Core Web Vitals:** - Largest Contentful Paint (LCP) for loading speed - First Input Delay (FID) for interactivity - Cumulative Layout Shift (CLS) for visual stability - **HTTPS & site security:** Encrypted connections (SSL/TLS) build trust with both users and search algorithms. By ironing out slow load times, crawl errors or insecure protocols, technical optimization ensures your content has a solid foundation to earn visibility,and keeps visitors on-site rather than bouncing to a faster competitor. ### 3.3 Off-page optimization Off-page optimization encompasses all activities outside your website that influence how search engines perceive your brand’s authority and trustworthiness. When executed strategically, these efforts amplify the impact of your on-site work and broaden your reach. Key off-page tactics for B2B: - **Link building:** Secure high-quality backlinks through guest articles in trade publications, collaborations with industry associations and contributions to authoritative blogs. - **Social proof & brand mentions:** Encourage reviews on platforms like G2 or Capterra, and monitor unlinked brand mentions to convert them into links. - **Directory & partnership listings:** Claim and optimise your profile in niche directories and partner portals (for example, local chambers of commerce or sector-specific alliances). Sample link outreach approaches: 1. Offer a data-driven insight report to a well-known trade magazine in exchange for a link. 2. Co-author a thought-leadership article with an industry association, publishing it on both sites. 3. Host a webinar with a technology partner, then syndicate the event summary to generate natural mentions. Together, these off-page signals send a clear message to search engines: your brand is a credible resource in its niche. ## 4\. On-page optimization strategies for B2B websites On-page optimization is where SEO meets your buyer’s journey. By aligning each page’s elements with specific user needs,from the first exploratory query to the final decision,you can guide prospects smoothly toward conversion. This involves a three-phase approach: planning and research, creation and optimization, then review and improvement. Each phase ensures your content stays relevant, authoritative and discoverable. ### 4.1 Conducting B2B keyword research and mapping user intent Effective on-page SEO starts with understanding what your audience is searching for and why. For B2B, this often means identifying terms that reflect the stages of a longer, more considered purchasing process. 1. Tools and methods: - Use platforms like Google Keyword Planner, Ahrefs or SEMrush to uncover search volume and competition for niche B2B terms. - Interview sales teams and customer-facing staff to surface real-world language and pain points. 2. Grouping by intent: - Informational: “What is a fractional CMO?” - Commercial: “Benefits of outsourcing marketing leadership” - Transactional: “Hire B2B marketing consultant” 3. Keyword mapping template:Buyer stagePersona questionTarget keywordAwarenessWhat is a fractional CMO?fractional CMO servicesConsiderationHow to evaluate marketing consultancies?marketing consultancy comparisonDecisionBest B2B marketing consultant Canadahire B2B marketing consultant Linking these keywords to the appropriate stage of your buyer persona ensures your pages answer the right questions at the right time. ### 4.2 Crafting and optimising high-value content [High-value content](/content-marketing) is non-negotiable in B2B SEO. It requires depth, expertise and a clear path to action. - Depth and authority: Draw on case studies, client success stories and original data. A “How to choose a CMO” guide, for instance, might include a decision matrix, interviews with past clients and side-by-side feature comparisons. - Clear calls to action: Every piece of content should prompt the next step,download a white paper, book a strategy call or subscribe to an industry webinar. - Performance measurement and updates: Track engagement metrics (time on page, scroll depth) and lead indicators (form fills, downloads). Schedule quarterly reviews to refresh statistics, update links and refine the narrative. By weaving in fresh insights and structured calls to action, you transform static pages into dynamic lead generators. ### 4.3 Optimising meta tags, headers and URLs Meta tags, headers and URL structures are micro-optimizations that pack a macro punch in search performance. - Title tags (<60 characters): Craft concise headlines that include your primary keyword and a value proposition. Example: • Before: “Services - B2Better” • After: “Fractional CMO Services for B2B Growth \| B2Better” - Meta descriptions (<155 characters): Summarise the page’s benefit compellingly, enticing clicks without keyword stuffing. Example: • Before: “Learn about our services.” • After: “Accelerate your marketing ROI with part-time CMO expertise,no long-term commitments.” - Header hierarchy and keywords: Use a single H1 that mirrors the title tag, then H2s and H3s for logical subtopics. Ensure each header incorporates related search terms where natural. - Descriptive URLs: Keep URLs brief and keyword-rich. Example: • Before: `/services/cmo/` • After: `/fractional-cmo-services/` Together, these adjustments fine-tune how both users and search engines perceive your content, improving relevance signals and click-through rates. ## 5\. Technical SEO essentials to support growth A rock-solid technical foundation ensures that your on-page and off-page efforts are amplified rather than undermined. Before you invest further in content or link building, it pays to audit your site’s health in three critical areas: 1. **Crawl analysis** - Confirm that search engines can discover and index your key pages. 2. **Site speed & performance testing** - Measure and improve how quickly pages load and respond. 3. **Accessibility review** - Ensure every visitor, regardless of ability, can engage with your content and navigate your site. Tackle these in sequence to uncover hidden barriers, then prioritise fixes that deliver the greatest impact on visibility and user satisfaction. ### 5.1 Ensuring crawlability and indexability If search engines can’t crawl your site, even the best content remains hidden. Start by: • Reviewing your **robots.txt** file to ensure you’re not accidentally blocking important directories. • Checking Google Search Console’s **Coverage** report (and Bing Webmaster Tools) for errors like “crawl blocked” or “server error”. • Submitting an up-to-date **XML sitemap** so crawlers know exactly which URLs matter. A logical internal linking structure guides bots,and users,through your pages. Group related content into clear directory paths (e.g., `/services/strategy/`) and link between complementary resources. Watch out for common traps: - Duplicate content served under multiple URLs (use `rel="canonical"` tags to consolidate). - Infinite parameter loops (limit or standardise query strings). - Orphan pages (no inbound internal links, hence invisible to crawlers). ### 5.2 Improving site speed and Core Web Vitals Fast, responsive pages delight users and earn favour with [search algorithms](/blog). Focus on Google’s Core Web Vitals: • **Largest Contentful Paint (LCP):** Time until the main content is visible. • **First Input Delay (FID):** Delay before the page reacts to user interaction. • **Cumulative Layout Shift (CLS):** How much layout shifts as resources load. Practical optimizations: - **Compress and serve modern image formats** (e.g., WebP or AVIF). - **Implement lazy loading** for off-screen images and videos. - **Minimise and defer JavaScript/CSS**, bundling and removing unused code. - Leverage browser **caching**, **resource preconnect** and a **CDN** to reduce latency. Use free tools such as [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) and Lighthouse to identify bottlenecks and track improvements over time. ### 5.3 Enhancing accessibility to improve UX and SEO Accessible sites cater to all users and often enjoy better engagement metrics,another positive ranking signal. Align with WCAG 2.1 criteria (including new mobile and cognitive guidelines) by: - Providing **descriptive alt text** for every image to assist screen readers. - Ensuring full **keyboard navigation** (tab order, visible focus states). - Maintaining **sufficient colour contrast** between text and backgrounds. - Labelling form elements clearly (using `<label>` and `aria-label`) so users understand field purposes. By removing barriers,whether technical or perceptual,you unlock new traffic opportunities and strengthen the overall user experience, which in turn reinforces your SEO gains. ## 6\. Off-page SEO tactics to build authority While on-page and technical optimization ensure your site can be discovered and consumed, off-page activities signal to search engines that your brand is a trusted resource in its niche. Quality matters far more than quantity: a handful of links from respected industry sites will move the needle more than dozens of low-value mentions. To make off-page SEO manageable, consider a four-step framework: 1. Identify - map out target domains, influencers and publications. 2. Outreach - craft personalised pitches that demonstrate value. 3. Amplify - promote earned coverage through your own channels. 4. Monitor - track links, mentions and brand sentiment over time. When combined with content marketing, PR and strategic partnerships, these tactics compound your authority and create an ecosystem of endorsements that search engines reward. ### 6.1 Earning high-quality backlinks Not all backlinks are created equal. A valuable link typically comes from a site that: - Ranks well for relevant keywords (authority). - Operates in your industry vertical (relevance). - Features editorially placed, contextual links (natural fit). Proven tactics for acquiring these links include: - Guest articles on respected trade blogs or association sites. - Data-driven content such as original surveys or market reports, which editors love to quote. - Digital PR campaigns that pitch compelling news angles to journalists. Email outreach template: Subject: “\[Name\], a quick idea for your next \[Industry\] article” Hi \[Name\], I’m \[Your Name\] from \[Company\], and I enjoyed your recent piece on \[Topic\]. We’ve just published a \[survey/report/guide\] that offers new data on \[Key Insight\]. I believe it would add value to your readers. Would you consider linking to it here? \[URL\] If you’d like additional context or a quote from our CEO, just let me know. Thanks for considering, and keep up the great work! Best regards, \[Your Name\] \[Title\] \| \[Company\] ### 6.2 Establishing brand authority through digital PR Digital PR extends beyond link building; it shapes how your brand is perceived both online and in the press. To build authority: - Collaborate with industry publications and thought leaders on co-authored articles or interviews. - Craft press releases around proprietary data, product launches or notable events,distribution via PR wire services can spark coverage and links. - Use media-monitoring tools to track brand mentions. When you spot unlinked mentions, reach out and politely request a link to your site. These efforts position you as a go-to expert, generating both direct referral traffic and search-friendly citations. ### 6.3 Leveraging social platforms and directories Strong social and directory presences reinforce your off-page signals: - Ensure consistent Name, Address, Phone (NAP) information across every business listing to avoid confusing search engines. - Claim and fully optimise your profile on LinkedIn, [industry directories](/blog/what-is-seo-optimization-why-it-matters) and partner portals,complete descriptions, logos, and links all matter. - Encourage satisfied clients to leave reviews on platforms like G2, Capterra or industry-specific forums. High ratings and thoughtful testimonials not only boost credibility but can also earn you followable backlinks. Regularly audit these external profiles to keep information fresh and to capitalise on every opportunity to demonstrate your expertise. ## 7\. Integrating SEO into your B2B marketing strategy Integrating SEO into your B2B marketing plan means treating it as the thread that weaves together content, design, development and sales. Rather than delegating SEO to a single specialist or agency, build a culture where every team understands how their work impacts organic visibility, user experience and lead generation. Establish a regular planning rhythm,quarterly strategy reviews combined with monthly execution sprints,to keep optimization aligned with evolving business priorities and market trends. ### 7.1 Aligning SEO with overall goals and buyer personas Start by defining how SEO supports your overarching objectives, whether that’s increasing MQLs by 30% or boosting e-commerce revenue by $500,000. Translate these targets into clear, measurable SEO OKRs or SMART goals: - Objective: Increase organic leads by 25% in six months. - Key Result 1: Publish ten new thought-leadership articles targeting high-value keywords. - Key Result 2: Improve average page one ranking for five priority terms. - Key Result 3: Decrease bounce rate on top-to-middle funnel pages by 15%. Next, build keyword and content plans around your buyer personas. For instance, map “Operations Officer Olivia,” who researches efficiency tools, to informational keywords like “best CRM for operations” and later to decision-stage terms such as “CRM implementation services.” A simple persona-to-keyword exercise might look like: | Persona | Stage | Persona Question | Target Keyword | | --- | --- | --- | --- | | Operations Officer Olivia | Awareness | What is a CRM? | what is a CRM | | Marketing Manager Mark | Consideration | CRM vs ERP features | CRM features comparison | | CEO Caroline | Decision | CRM providers for SMBs | best CRM for small business Canada | For deeper guidance, refer to B2Better’s [guide to formulating your B2B marketing strategy](/blog/guide-to-formulating-your-b2b-marketing-strategy), which outlines how to align each marketing channel,including SEO,with your business goals and buyer journeys. ### 7.2 Developing a tactical SEO roadmap and timeline Once goals and personas are in place, translate them into a phased SEO roadmap. A typical timeline might include: | Phase | Duration | Activities | Milestone | | --- | --- | --- | --- | | Audit | Month 1 | Technical crawl, content inventory, backlink review | Complete SEO health report | | Prioritisation | Month 2 | Score issues by impact/effort, define quick wins | Finalised priority list | | Execution | Months 3-6 | On-page updates, technical fixes, link outreach | Top 10 issues resolved | | Measurement | Ongoing | Monthly traffic, ranking and lead tracking | Quarterly performance review | Visualise this roadmap in your project-management tool or as a simple Gantt chart, and keep stakeholders informed through monthly sprint demos and quarterly strategy check-ins. ### 7.3 Collaborating across teams and agencies Seamless collaboration is essential. For internal teams, establish clear handoff processes: - SEO → Content: Use shared brief templates detailing target keywords, suggested headings and desired CTAs. - SEO → Development: Log technical tasks in your ticketing system (e.g., Jira, Trello) with priority tags and due dates. - Sales → SEO: Gather frontline insights on prospect questions and objections to inform new content topics. When working with external partners,a fractional CMO or agency,define roles and responsibilities in a service-level agreement (SLA). Schedule regular check-ins (bi-weekly or monthly) to review performance metrics, adjust priorities and address any roadblocks. Centralise all documentation in a collaborative workspace like Confluence or shared Google Drive folders to ensure transparency and accountability across every phase of your SEO programme. ## 8\. Optimising website content to maximise results Content is the engine that powers SEO success for B2B organisations. A well-structured, authoritative page not only attracts search traffic but also guides prospects smoothly through each stage of a complex buying journey. To stay ahead, you need a mix of strategic oversight, rigorous audits and continuous refinement. In this section, we’ll explore how specialised services can jump-start your content performance, how to conduct systematic audits, and how to optimise landing pages and touchpoints for maximum impact. ### 8.1 Refining website content with SEO-driven services Expert content-optimization services offer an outsider’s perspective and the technical chops to transform under-performing pages into lead magnets. A typical engagement includes: - A detailed **competitive analysis** that benchmarks your content against top-ranking rivals. - **Content-gap identification** to uncover topics your audience searches for but your site doesn’t yet address. - An **editorial calendar** prioritising high-value assets such as white papers, case studies and guided tutorials. By tapping into these deliverables, you gain not only fresh, data-backed recommendations but also an implementation roadmap. Learn how our [website content optimization services](/content-marketing) can enhance your messaging, improve SEO metrics and accelerate lead generation. ### 8.2 Conducting content audits and ongoing refresh A content audit is your periodic check-up,ensuring every page still meets your buyer’s needs and search-engine requirements. A straightforward audit process looks like this: 1. **Inventory existing pages**: Catalogue all URLs, titles, word counts and publication dates. 2. **Evaluate performance metrics**: Use Google Analytics and Search Console to flag under-performing pages (low traffic, high bounce). 3. **Identify stale or thin content**: Mark any pages with outdated statistics, thin text or irrelevant topics. 4. **Prioritise updates**: Score each page by impact (traffic potential) and effort (rewrite complexity). 5. **Implement refreshes**: Update facts, sharpen headlines, add new internal links and improve calls to action. Ongoing refreshes keep your content relevant, maintain your rankings and demonstrate authority to both users and search engines. For a deeper dive, see our guide on [B2B website content optimization to boost online presence](/blog/what-is-seo-optimization-why-it-matters). ### 8.3 Optimising landing pages and buyer journey touchpoints Landing pages are the frontline of conversions. Whether you’re driving downloads, demo sign-ups or contact requests, each element must work together to build trust and remove friction: - **Clear, benefit-focused headlines**: State immediately what visitors will gain. - **Concise, scannable copy**: Use bullet points, subheadings and short paragraphs. - **Strong social proof**: Showcase testimonials, client logos or quantified results. - **Focused calls to action**: Limit each page to a single primary goal (e.g., “Download the Guide”). A/B testing frameworks help you iterate rapidly. Test variables such as button colour, headline length or testimonial placement. Track which combination yields higher form completions, and roll out the winner site-wide. For advanced tactics,like dynamic personalisation and interactive content,explore our [innovative B2B content marketing strategies](/blog). These approaches can boost engagement, reduce drop-off and guide prospects toward a purchase decision more effectively. ## 9\. Implementing SEO optimization on a startup budget Many early-stage B2B firms juggle tight budgets and lean teams, yet they still need to lay the foundations for long-term SEO success. By zeroing in on high-impact, low-effort tactics and harnessing free or low-cost tools, startups can ignite organic growth without blowing their budget. ### 9.1 Prioritising quick wins for early-stage B2B startups When resources are scarce, focus on projects that deliver noticeable gains with minimal effort. Consider these low-hanging fruit: 1. **Keyword-focused page tweaks** - Refine title tags and meta descriptions on your top three product or service pages to include primary keywords and clear value propositions. 2. **Meta tag and header optimization** - Fix missing or duplicate `<title>` tags site-wide and ensure each page uses a single, descriptive H1. 3. **Internal linking audit** - Link each new blog post back to a cornerstone page (e.g., solutions or service overview) and confirm key pages are no more than three clicks from the homepage. 4. **Basic technical clean-up** - Submit an up-to-date XML sitemap in Google Search Console and repair critical 404 errors or broken links. 5. **Google My Business setup** _(if location-relevant)_ - Claim and verify your profile, then encourage happy clients to leave reviews. Impact vs Effort Scoring Matrix: | Task | Effort | Impact | Priority | | --- | --- | --- | --- | | Title tag & meta fixes | Low | High | 1 | | Internal link updates | Low | Medium | 2 | | XML sitemap submission | Low | Medium | 2 | | Broken link repairs | Medium | Medium | 3 | | New content topic ideation | High | High | 4 | For a full blueprint tailored to young B2B businesses, check out B2Better’s guide on early-stage B2B startups. ### 9.2 Cost-effective tools and techniques Startups don’t need expensive subscriptions to get organised. Here’s a toolkit of free or affordable resources, plus DIY tactics to boost your profile: - **Google Search Console & Google Analytics** Monitor keyword performance, identify crawl errors and track conversions,at zero cost. - **PageSpeed Insights & Lighthouse** Diagnose page-speed issues and prioritise fixes like image compression, caching and code minification. - **Answer The Public & Ubersuggest (free tier)** Unearth content ideas and long-tail keyword suggestions for blogs or FAQs. - **HARO (Help a Reporter Out)** Respond to relevant journalist queries to earn high-quality mentions and backlinks. - **Local and industry partnerships** Co-author webinars or joint case studies with complementary vendors or trade associations. - **In-house subject-matter experts** Interview your team’s experts to generate blog posts, white papers or LinkedIn video scripts. By leaning on free insights and internal expertise, you can build momentum before investing in premium tools or agencies. ### 9.3 Scaling SEO efforts over time Once quick wins are in hand and basic processes are running smoothly, plan for gradual growth: 1. **Milestone-based hiring** - At ~$500K-$1M annual revenue: onboard a part-time or junior SEO specialist. - At ~$2M+: engage a fractional CMO or small-scale agency retainer. 2. **Budget allocation guidelines** - Dedicate 5-10% of your overall marketing budget to SEO activities. - Reinvest savings from organic leads into expanded content and link outreach. 3. **Transition to paid tools** - Upgrade to entry-level plans on platforms like SEMrush or Ahrefs for deeper keyword and backlink insights. - Implement a lightweight project-management tool (Trello, Asana) to track SEO sprints. 4. **Establish a review cadence** - Conduct quarterly SEO audits to catch technical issues and re-score priorities. - Hold monthly content check-ins to refresh or retire underperforming pages. By layering in expertise, budget and advanced tools over time, your startup can evolve from DIY SEO to a mature, data-driven programme,without a sudden spike in costs. ## 10\. Measuring and demonstrating ROI of SEO optimization Measuring ROI of SEO is essential to justify ongoing investment, refine tactics and demonstrate concrete value to stakeholders. Unlike [paid campaigns](/blog/what-is-marketing-roi) with immediate spend-to-return metrics, SEO unfolds over months. A clear measurement framework based on relevant KPIs, robust tracking and regular reporting ensures you can connect organic efforts to real business outcomes. ### 10.1 Selecting the right KPIs and setting benchmarks Selecting KPIs that align with your business objectives is the first step. Focus on metrics that reflect visibility, engagement and conversion: - Impressions: How often your pages appear in search results - Click-through rate (CTR): The percentage of impressions that turn into clicks - Organic sessions: The volume of visits from unpaid search - Conversion rate: The percentage of visitors completing desired actions (form fills, downloads) - Revenue or lead value: The average value per lead or sale attributed to organic search Make your KPIs SMART,specific, measurable, achievable, relevant and time-bound. For instance: | Metric | Baseline (Month 0) | Target (Month 6) | | --- | --- | --- | | Organic leads | 40 | 48 (↑20%) | | CTR | 3% | 4% (↑1 pp) | | Average time on page | 1 min 30 sec | 2 min | Benchmarks give context to progress and help prioritise initiatives that will move the needle. ### 10.2 Tracking performance with analytics and reports Accurate tracking requires the right tools and configurations: - Google Analytics: Set up goals or events for key actions (e.g., contact form submissions) - Google Search Console: Monitor queries, clicks, impressions and indexing issues - SEO platforms (Ahrefs, SEMrush, Moz): Track keyword rankings and backlink profiles over time Build custom dashboards to visualise trends at a glance and configure automated alerts for significant drops in traffic or increases in crawl errors. A typical monthly SEO report might include: 1. Executive summary: Key wins and challenges 2. Traffic overview: Sessions, new users and bounce rate 3. Ranking performance: Changes in position for priority keywords 4. Conversion metrics: Leads generated and conversion rate 5. Technical health check: Core Web Vitals and coverage issues 6. Next steps: Recommendations for the coming month Consistent reporting keeps leadership informed and teams aligned on priorities. ### 10.3 Calculating content marketing ROI and iterating Quantifying content ROI demonstrates the direct impact of your optimization efforts. Use the standard ROI formula: `ROI = (Gain from investment − Cost of investment) / Cost of investment` For example, if content-driven SEO brought in $50,000 in revenue and cost $15,000, the calculation is: `ROI = ($50,000 − $15,000) / $15,000 = 2.33 (233%)` In B2B, factor in customer lifetime value (LTV) and multi-touch attribution to capture the full benefit of SEO over longer sales cycles. Track first-touch and assist interactions in your analytics platform to understand how content contributes across the funnel. Finally, treat measurement as a catalyst for continuous improvement. Regularly review metrics, test new content angles or technical fixes, and iterate your strategy based on what the data reveals. For more on optimising content performance and ROI, explore B2Better’s [content marketing best practices](/blog). ## 11\. Future trends and sustaining SEO performance SEO never stands still. As search engines evolve and user behaviours shift, you need proactive strategies to keep your site in top shape. Below, we explore three forward-looking areas,AI-driven search, algorithm agility and ongoing optimization cycles,and outline how to embed them into your routine. ### 11.1 Adapting to AI and generative search Search results increasingly feature AI-generated answers,think Google’s AI Overviews, Bing’s generative snippets and AI assistants like ChatGPT or Gemini. These highlights can replace traditional blue links and steer clicks toward concise, directly embedded responses. To prepare for this shift: - Structure content as clear questions and answers, using headings that match user queries (for example, “What is a fractional CMO?”). - Apply FAQ or Q&A schema to flag key points for AI systems. - Build authority with well-sourced, in-depth information,AI models often draw from trusted sources. - Test how your top pages appear in emerging AI platforms (SearchGPT, Perplexity, Copilot) and iterate based on feedback. ### 11.2 Responding to algorithm changes and updates Search engines adjust ranking algorithms frequently, impacting factors such as page experience, content depth and link quality. Staying alert and adaptable is crucial. Best practices include: - Subscribing to Google’s Search Central blog and respected SEO outlets to spot announcements early. - Maintaining a simple log of updates alongside any traffic or ranking changes you observe in Google Analytics or Search Console. - Running targeted site health checks,such as re-evaluating Core Web Vitals after performance-focused updates. - Planning content sprints that address new priorities (for instance, enriching thin pages when a quality-update arrives). ### 11.3 Continuous monitoring and optimization cycles Embedding routine reviews into your calendar turns SEO into a perpetual cycle of improvement rather than a one-off project. - Quarterly check-ins: • Audit technical health (crawl errors, mobile usability, speed). • Evaluate content performance,refresh top pages and rescue under-performers. • Set targets for link outreach and new content creation. - Annual strategy reviews: • Revisit buyer-persona maps and keyword focuses. • Scan for new tools, channels or technologies to integrate. • Align SEO goals with your wider business plan. Use this basic checklist to stay on track: - Site health: No fresh critical crawl errors; Core Web Vitals remain within optimal thresholds. - Content: Statistics updated; emerging topics covered; internal linking strengthened. - Links: High-value backlinks earned; unlinked brand mentions converted. By treating SEO as an ongoing, data-driven practice,punctuated by regular audits, experiments and strategic updates,you’ll keep your B2B site agile, visible and primed for sustained growth. ## Driving B2B growth through strategic SEO optimization Strategic SEO optimization is more than a set of one-off tasks,it’s a continuous commitment to making your website both discoverable and persuasive for B2B buyers. By consistently refining on-page content, ensuring technical excellence and earning high-value off-page signals, you build a foundation for sustainable organic traffic, higher-quality leads and measurable ROI. Understanding what is SEO optimization,and embedding it at the heart of your marketing operations,turns search into a dependable growth channel rather than a hit-and-miss experiment. For B2B organisations, the real power of this approach lies in its integration. When content teams, developers and outreach specialists work from the same playbook, every blog post, product page and white paper becomes a targeted asset in your lead-generation engine. Technical health checks prevent surprises, and off-page outreach amplifies your authority in niche markets. Layer in regular performance reviews,tracking metrics from impressions and click-throughs to conversion rates,and you create a self-reinforcing cycle of improvement. Ultimately, driving B2B growth through strategic SEO optimization means treating optimization as an ongoing practice, not a project with a start and finish date. Each insight you gain,whether from keyword data, Core Web Vitals or backlink analytics,feeds back into your content roadmap, your technical backlog and your PR strategies. Over time, this integrated, data-driven framework delivers compounding benefits: a stronger brand presence, an expanding funnel of qualified prospects and the flexibility to pivot as buyer behaviour or search algorithms evolve. Ready to turn search into your most reliable source of high-value leads? Explore [B2Better’s services](/blog) to see how our Fractional CMO and SEO experts can tailor this strategic framework to your unique B2B growth goals.</div> </div> </article> <section class="cta grid-bg"> <div class="container"> <h2>Let's talk.</h2> <p>15 minutes. No pitch deck, no pressure. Tell us where your marketing is stuck and we'll tell you honestly if we can help.</p> <a href="/contact" class="btn btn-solid btn-sheen">Book a free consultation</a> </div> </section> <site-footer></site-footer> <script> // Render featured image + clickable pillar tag. // __CMS_ITEM_DATA__ is assigned after this script runs, so wait for it. var PILLARS = { 'ai-marketing-os': 'AI Marketing OS', 'fractional-cmo': 'Fractional CMO', 'brand-architecture': 'Brand Architecture', 'solo-operator': 'Solo Operator', 'signal-curation': 'Signal Curation' }; (function () { function render() { var item = window.__CMS_ITEM_DATA__; if (!item) { return false; } // Featured image var host = document.getElementById('post-featured'); if (host && item.featuredImage) { var img = document.createElement('img'); img.src = item.featuredImage; img.alt = item.title || 'B2Better'; img.loading = 'lazy'; host.appendChild(img); } // Clickable pillar tag(s) -> filtered blog view var meta = document.querySelector('.post-meta'); if (meta && item.pillar && item.pillar.length) { meta.innerHTML = item.pillar.map(function (id) { var label = PILLARS[id] || id; return '<a class="post-pillar" href="/blog?pillar=' + id + '">' + label + '</a>'; }).join(' '); } // BlogPosting schema for GEO/SEO var schema = { '@context': 'https://schema.org', '@type': 'BlogPosting', 'headline': item.title || '', 'datePublished': item.date || '', 'dateModified': item.date || '', 'image': item.featuredImage || '', 'description': (item.excerpt || '').replace(/<[^>]+>/g, ''), 'author': { '@type': 'Organization', 'name': 'B2Better', 'url': 'https://b2better.co' }, 'publisher': { '@type': 'Organization', 'name': 'B2Better', 'logo': { '@type': 'ImageObject', 'url': 'https://b2better.co/wp-content/uploads/2020/06/b2better-co-logo-wht-1024x749.png' } }, 'mainEntityOfPage': { '@type': 'WebPage', '@id': 'https://b2better.co/blog/' + item.slug } }; var s = document.createElement('script'); s.type = 'application/ld+json'; s.textContent = JSON.stringify(schema); document.head.appendChild(s); return true; } if (render()) { return; } var tries = 0; var timer = setInterval(function () { tries++; if (render() || tries > 20) { clearInterval(timer); } }, 250); })(); </script> </body> </html> <script>window.__QC_COMPONENT_CONFIGS__ = {};</script> <!-- Web Component: site-services-list --> <script> class SiteServicesListElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>/* ============ B2Better SERVICES LIST COMPONENT ============ */ .qc-svc-list{display:contents} /* dropdown mode (navbar) */ .qc-svc-list-dropdown{list-style:none;margin:0;padding:0} .qc-svc-list-dropdown a{display:block;padding:9px 12px;border-radius:8px;font-size:14px; color:#a0a0aa;text-decoration:none;transition:background .2s,color .2s} .qc-svc-list-dropdown a:hover{background:rgba(113,0,255,.2);color:#fff} /* related mode (service pages) */ .qc-svc-related-grid{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:rgba(255,255,255,.10); border:1px solid rgba(255,255,255,.10);margin-top:48px} .qc-svc-rel{background:#0b0b0e;padding:28px 32px;display:flex;align-items:center;gap:20px; text-decoration:none;transition:background .25s} .qc-svc-rel:hover{background:#17171d} .qc-svc-rel-n{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;color:#8b2bff} .qc-svc-rel h3{color:#ffffff;font-size:18px;font-weight:700;flex:1;margin:0} .qc-svc-rel-arr{color:#ffd00a;font-size:20px;transition:transform .25s} .qc-svc-rel:hover .qc-svc-rel-arr{transform:translateX(5px)} .qc-svc-viewall{display:inline-flex;align-items:center;gap:8px;margin-top:24px; font-family:'JetBrains Mono',ui-monospace,monospace;font-size:13px;color:#ffd00a; text-decoration:none;letter-spacing:.3px} .qc-svc-viewall:hover{color:#fff} .qc-svc-viewall .qc-svc-arr{transition:transform .25s} .qc-svc-viewall:hover .qc-svc-arr{transform:translateX(5px)} /* index mode (services page) */ .qc-svc-index-grid{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:rgba(255,255,255,.10); border:1px solid rgba(255,255,255,.10);margin-top:48px} .qc-svc-index-card{background:#0b0b0e;padding:34px;position:relative;text-decoration:none; display:flex;flex-direction:column;gap:12px;transition:background .25s} .qc-svc-index-card:hover{background:#17171d} .qc-svc-index-card .qc-svc-index-n{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;color:#8b2bff} .qc-svc-index-card h3{color:#fff;font-size:22px;font-weight:800;margin:0} .qc-svc-index-card p{color:#a0a0aa;font-size:15px;line-height:1.6;margin:0} .qc-svc-index-card .qc-svc-index-go{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:13px; color:#ffd00a;margin-top:8px} .qc-svc-index-card:hover .qc-svc-index-go{color:#fff} @media(max-width:900px){ .qc-svc-related-grid,.qc-svc-index-grid{grid-template-columns:1fr} } </style> <div class="qc-svc-list" data-mode="dropdown"> <ul class="qc-svc-list-dropdown"></ul> </div> <div class="qc-svc-list" data-mode="related"> <div class="qc-svc-related-grid"></div> <a class="qc-svc-viewall" href="/services">View all services <span class="qc-svc-arr">→</span></a> </div> <div class="qc-svc-list" data-mode="index"> <div class="qc-svc-index-grid"></div> </div> `; (function(component) { /* ============ B2Better SERVICES LIST — SINGLE SOURCE OF TRUTH ============ Add a new service page by adding ONE entry here. The navbar dropdown, the /services index page, and every service page's related section all render from this array, so they stay in sync automatically. `related` = curated slugs of the most-interconnected services (shown on that service's own page, excluding itself). NOTE: QuickCasa compiles this into a shadow-DOM Web Component and wraps the script in (function(component){ ... })(this), so the component element arrives as the `component` argument. Query within component.shadowRoot, not document. */ const QC_SERVICES = [ { slug: 'fractional-cmo-services', title: 'Fractional CMO', blurb: 'Senior marketing leadership, strategy and execution on a part-time basis.', related: ['digital-marketing-services', 'content-marketing', 'strategic-marketing-plan'] }, { slug: 'content-marketing', title: 'Content Marketing', blurb: 'Blogging and thought leadership that builds trust, fuels SEO and generates leads.', related: ['automated-content-marketing', 'digital-marketing-services', 'fractional-cmo-services'] }, { slug: 'digital-marketing-services', title: 'Digital Marketing', blurb: 'SEO, content, social and paid ads, bundled to fit your business.', related: ['content-marketing', 'social-media-management', 'fractional-cmo-services'] }, { slug: 'social-media-management', title: 'Social Media', blurb: 'Generate authority and build trust across LinkedIn, X, Instagram and more.', related: ['content-marketing', 'digital-marketing-services', 'fractional-cmo-services'] }, { slug: 'strategic-marketing-plan', title: 'Strategic Plan', blurb: 'A clear marketing roadmap aligned to your brand, buyers and revenue goals.', related: ['fractional-cmo-services', 'digital-marketing-services', 'content-marketing'] }, { slug: 'automated-content-marketing', title: 'Automated Content', blurb: 'The B2B Content Engine. From strategy to publish, in your voice.', related: ['content-marketing', 'digital-marketing-services', 'strategic-marketing-plan'] }, ]; const root = (typeof component !== 'undefined' && component.shadowRoot) ? component.shadowRoot : document; const current = location.pathname.replace(/\/+$/,'').split('/').pop(); // current page slug function renderDropdown(container){ container.innerHTML = QC_SERVICES.map(s => `<li><a href="/${s.slug}">${s.title}</a></li>`).join(''); } function renderRelated(container){ const me = QC_SERVICES.find(s => s.slug === current); const slugs = me ? me.related : QC_SERVICES.slice(0,3).map(s=>s.slug); const items = slugs.map((slug,i) => { const s = QC_SERVICES.find(x => x.slug === slug); if(!s) return ''; return `<a class="qc-svc-rel" href="/${s.slug}"> <span class="qc-svc-rel-n">${String(i+1).padStart(2,'0')}</span> <h3>${s.title}</h3><span class="qc-svc-rel-arr">→</span></a>`; }).join(''); container.innerHTML = items; } function renderIndex(container){ container.innerHTML = QC_SERVICES.map((s,i) => `<a class="qc-svc-index-card" href="/${s.slug}"> <span class="qc-svc-index-n">${String(i+1).padStart(2,'0')}</span> <h3>${s.title}</h3> <p>${s.blurb}</p> <span class="qc-svc-index-go">Learn more →</span> </a>`).join(''); } /* Render ONLY the mode this component instance is set to; hide the others. */ const activeMode = (typeof component !== 'undefined' && component.getAttribute) ? component.getAttribute('mode') : 'related'; root.querySelectorAll('.qc-svc-list').forEach(el=>{ const mode = el.getAttribute('data-mode'); if(mode !== activeMode){ el.style.display = 'none'; return; } const target = el.querySelector('.qc-svc-list-dropdown, .qc-svc-related-grid, .qc-svc-index-grid'); if(!target) return; if(mode==='dropdown') renderDropdown(target); else if(mode==='related') renderRelated(target); else if(mode==='index') renderIndex(target); }); })(this); } } if (!customElements.get('site-services-list')) { customElements.define('site-services-list', SiteServicesListElement); } </script> <!-- Web Component: site-navbar --> <script> class SiteNavbarElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>/* ============ B2Better NAVBAR ============ */ .qc-nav-shell{position:sticky;top:0;z-index:50} .qc-nav{background:rgba(11,11,14,.85);backdrop-filter:blur(12px); border-bottom:1px solid rgba(255,255,255,.10)} .qc-nav-inner{max-width:1200px;margin:0 auto;padding:0 28px;height:100px; display:flex;align-items:center;justify-content:space-between;gap:24px} .qc-nav-logo{display:flex;align-items:center;line-height:1} .qc-nav-logo img{height:82px;width:auto;display:block} .qc-nav-links{display:flex;gap:30px;align-items:center;list-style:none;margin:0;padding:0} .qc-nav-links > li{position:relative} .qc-nav-links a{color:#a0a0aa;text-decoration:none;font-size:15px;font-weight:600;transition:color .2s} .qc-nav-links a:hover{color:#ffffff} .qc-caret{font-size:11px;margin-left:6px;color:#ffd00a;display:inline-block;transition:transform .25s} .qc-has-dropdown > a{display:inline-flex;align-items:center;padding:8px 16px;border:1px solid rgba(255,255,255,.14); border-radius:999px;color:#ffffff;transition:border-color .2s,background .2s} .qc-has-dropdown > a:hover{border-color:#8b2bff;background:rgba(113,0,255,.12)} .qc-has-dropdown:hover > a .qc-caret, .qc-has-dropdown:focus-within > a .qc-caret{transform:rotate(180deg)} .qc-nav-cta{background:#f4f3ef;color:#0b0b0e!important;padding:9px 20px;border-radius:999px;font-weight:700;text-decoration:none} .qc-nav-cta:hover{background:#8b2bff;color:#fff!important} /* dropdown */ .qc-dropdown{position:absolute;top:100%;left:0;min-width:220px;background:rgba(11,11,14,.97); backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.10);border-radius:12px; padding:10px;list-style:none;margin:0;opacity:0;visibility:hidden;transform:translateY(8px); transition:opacity .2s,transform .2s,visibility .2s;box-shadow:0 20px 40px -16px rgba(0,0,0,.7)} .qc-has-dropdown:hover .qc-dropdown, .qc-has-dropdown:focus-within .qc-dropdown{opacity:1;visibility:visible;transform:translateY(0)} .qc-dropdown a{display:block;padding:9px 12px;border-radius:8px;font-size:14px} .qc-dropdown a:hover{background:rgba(113,0,255,.2);color:#fff} /* mobile */ .qc-menu-button{display:none;background:none;border:none;cursor:pointer;color:#fff;padding:8px} .qc-icon-close{display:none} .qc-mobile-panel{display:none;background:rgba(11,11,14,.97);border-top:1px solid rgba(255,255,255,.10); padding:20px 28px 28px} .qc-mobile-links{list-style:none;margin:0 0 20px;padding:0;display:flex;flex-direction:column;gap:16px} .qc-mobile-links a{color:#a0a0aa;text-decoration:none;font-size:16px;font-weight:600} .qc-mobile-links a:hover{color:#fff} .qc-mobile-cta{display:inline-block} @media(max-width:900px){ .qc-desktop-only{display:none!important} .qc-menu-button{display:block} .qc-nav-inner{height:88px} .qc-nav-logo img{height:64px} .qc-mobile-panel.open{display:block} } </style> <div class="qc-nav-shell"> <nav class="qc-nav" aria-label="Main"> <div class="qc-nav-inner"> <a class="qc-nav-logo" href="/"> <img src="https://firebasestorage.googleapis.com/v0/b/quick-casa.appspot.com/o/media-library%2FXL3l2exG5NxKAaaSnv4V%2Ftemp_b9c44a8db2d641f9bdea7ae720723423.png?alt=media&token=15557fd0-163c-4109-99e1-1987c58adacc" alt="B2Better"> </a> <ul class="qc-nav-links qc-desktop-only"> <li><a href="/">Home</a></li> <li class="qc-has-dropdown"> <a href="/services">Services <span class="qc-caret">▾</span></a> <ul class="qc-dropdown"> <site-services-list mode="dropdown"></site-services-list> </ul> </li> <li><a href="/about">About</a></li> <li><a href="/how-we-work">How We Work</a></li> <li><a href="/blog">Blog</a></li> </ul> <a class="qc-nav-cta qc-desktop-only" href="/contact">Let's Talk</a> <button class="qc-menu-button qc-mobile-only" type="button" aria-label="Open menu" aria-expanded="false" aria-controls="mobile-nav"> <svg class="qc-icon-open" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> <svg class="qc-icon-close" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> </button> </div> </nav> <div class="qc-mobile-panel" id="mobile-nav" aria-hidden="true"> <ul class="qc-mobile-links"> <li><a href="/">Home</a></li> <li><a href="/services">Services</a></li> <li><a href="/fractional-cmo-services">Fractional CMO</a></li> <li><a href="/content-marketing">Content Marketing</a></li> <li><a href="/digital-marketing-services">Digital Marketing</a></li> <li><a href="/social-media-management">Social Media</a></li> <li><a href="/strategic-marketing-plan">Strategic Plan</a></li> <li><a href="/automated-content-marketing">Automated Content</a></li> <li><a href="/about">About</a></li> <li><a href="/how-we-work">How We Work</a></li> <li><a href="/blog">Blog</a></li> </ul> <a class="qc-nav-cta qc-mobile-cta" href="/contact">Let's Talk</a> </div> </div> `; (function(component) { (function(){ const btn = document.querySelector('.qc-menu-button'); const panel = document.getElementById('mobile-nav'); if(!btn || !panel) return; btn.addEventListener('click', ()=>{ const open = panel.classList.toggle('open'); btn.setAttribute('aria-expanded', open ? 'true' : 'false'); panel.setAttribute('aria-hidden', open ? 'false' : 'true'); btn.querySelector('.qc-icon-open').style.display = open ? 'none' : 'block'; btn.querySelector('.qc-icon-close').style.display = open ? 'block' : 'none'; }); // close on link click panel.querySelectorAll('a').forEach(a=>{ a.addEventListener('click', ()=>{ panel.classList.remove('open'); btn.setAttribute('aria-expanded','false'); panel.setAttribute('aria-hidden','true'); btn.querySelector('.qc-icon-open').style.display = 'block'; btn.querySelector('.qc-icon-close').style.display = 'none'; }); }); })(); })(this); } } if (!customElements.get('site-navbar')) { customElements.define('site-navbar', SiteNavbarElement); } </script> <!-- Web Component: site-footer --> <script> class SiteFooterElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>/* ============ B2Better FOOTER ============ */ .qc-footer{border-top:1px solid rgba(255,255,255,.10);padding:56px 0 40px;background:#0b0b0e} .qc-footer-inner{max-width:1200px;margin:0 auto;padding:0 28px} .qc-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:40px} .qc-footer-logo{display:inline-block;margin-bottom:14px} .qc-footer-logo img{height:56px;width:auto;display:block} .qc-footer-tagline{color:#a0a0aa;font-size:14px;line-height:1.6;max-width:300px} .qc-footer-heading{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;color:#ffd00a; text-transform:uppercase;margin-bottom:18px;letter-spacing:.5px} .qc-footer-col a{display:block;color:#a0a0aa;text-decoration:none;font-size:14px;margin-bottom:12px;transition:color .2s} .qc-footer-col a:hover{color:#ffffff} .qc-footer-bottom{margin-top:48px;padding-top:24px;border-top:1px solid rgba(255,255,255,.10); display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap} .qc-footer-copy,.qc-footer-legal{color:#a0a0aa;font-size:12px;font-family:'JetBrains Mono',ui-monospace,monospace} .qc-footer-legal a{color:#a0a0aa;text-decoration:none} .qc-footer-legal a:hover{color:#fff} @media(max-width:900px){ .qc-footer-grid{grid-template-columns:1fr 1fr} } @media(max-width:560px){ .qc-footer-grid{grid-template-columns:1fr} } </style> <footer class="qc-footer"> <div class="qc-footer-inner"> <div class="qc-footer-grid"> <div class="qc-footer-brand"> <a class="qc-footer-logo" href="/"> <img src="https://firebasestorage.googleapis.com/v0/b/quick-casa.appspot.com/o/media-library%2FXL3l2exG5NxKAaaSnv4V%2Ftemp_b9c44a8db2d641f9bdea7ae720723423.png?alt=media&token=15557fd0-163c-4109-99e1-1987c58adacc" alt="B2Better"> </a> <p class="qc-footer-tagline">Marketing consulting without the BS. Fractional CMO services and tailored digital marketing for B2B companies in Ontario, Canada and beyond.</p> </div> <div class="qc-footer-col"> <h4 class="qc-footer-heading">Services</h4> <a href="/fractional-cmo-services">Fractional CMO</a> <a href="/content-marketing">Content Marketing</a> <a href="/digital-marketing-services">Digital Marketing</a> <a href="/social-media-management">Social Media</a> <a href="/strategic-marketing-plan">Strategic Plan</a> <a href="/automated-content-marketing">Automated Content</a> </div> <div class="qc-footer-col"> <h4 class="qc-footer-heading">Company</h4> <a href="/about">About</a> <a href="/how-we-work">How We Work</a> <a href="/blog">Blog</a> <a href="/contact">Contact</a> </div> <div class="qc-footer-col"> <h4 class="qc-footer-heading">Contact</h4> <a href="/contact">Ontario, Canada</a> <a href="/contact">hello@b2better.co</a> <a href="/contact">Schedule a call</a> </div> </div> <div class="qc-footer-bottom"> <span class="qc-footer-copy">© <span class="qc-footer-year">2026</span> B2Better. All rights reserved.</span> <span class="qc-footer-legal"> <a href="#">Terms</a> · <a href="#">Privacy</a> · <a href="#">Cookies</a> </span> </div> </div> </footer> `; (function(component) { var yearElement = component.shadowRoot.querySelector('.qc-footer-year'); if (yearElement) { yearElement.textContent = String(new Date().getFullYear()); } })(this); } } if (!customElements.get('site-footer')) { customElements.define('site-footer', SiteFooterElement); } </script> <!-- Web Component: section-cta --> <script> class SectionCtaElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>:host { display: block; font-family: var(--font-family, inherit); } .qc-cta { background: var(--color-primary, #1a1a1a); color: #ffffff; padding: 72px 24px; } .qc-cta-inner { max-width: 760px; margin: 0 auto; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px; } .qc-cta-headline { font-size: 36px; font-weight: 700; margin: 0; line-height: 1.15; } .qc-cta-subhead { font-size: 17px; line-height: 1.55; opacity: 0.88; margin: 0; max-width: 56ch; } .qc-cta-button { display: inline-flex; align-items: center; background: var(--color-secondary, #ffffff); color: var(--color-primary, #1a1a1a); padding: 14px 26px; border-radius: 999px; text-decoration: none; font-weight: 600; font-size: 15px; margin-top: 8px; transition: transform 0.15s ease, box-shadow 0.15s ease; } .qc-cta-button:hover { transform: translateY(-1px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.18); } @media (max-width: 600px) { .qc-cta-headline { font-size: 28px; } .qc-cta { padding: 56px 20px; } } </style> <section class="qc-cta"> <div class="qc-cta-inner"> <h2 class="qc-cta-headline">Ready when you are.</h2> <p class="qc-cta-subhead">Reach out and we'll get back to you within one business day.</p> <a class="qc-cta-button" href="/contact">Get in Touch</a> </div> </section> `; (function(component) { })(this); } } if (!customElements.get('section-cta')) { customElements.define('section-cta', SectionCtaElement); } </script> <!-- Web Component: qc-blog-grid --> <script> class QcBlogGridElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>:host { display: block; font-family: var(--font-family, inherit); } .qc-blog { padding: 64px 24px; max-width: 1200px; margin: 0 auto; } .qc-blog-header { text-align: center; margin-bottom: 40px; } .qc-blog-title { font-size: 32px; font-weight: 700; color: #1a1a1a; margin: 0 0 8px; } .qc-blog-subtitle { font-size: 16px; color: #555555; margin: 0; } .qc-blog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 28px; } .qc-blog-loading, .qc-blog-empty { grid-column: 1 / -1; text-align: center; padding: 40px; color: #777777; font-size: 15px; } .qc-blog-card { background: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06); transition: transform 0.15s ease, box-shadow 0.15s ease; text-decoration: none; color: inherit; display: flex; flex-direction: column; } .qc-blog-card:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.1); } .qc-blog-card-image { width: 100%; height: 180px; object-fit: cover; background: #f0f0f0; } .qc-blog-card-body { padding: 20px; display: flex; flex-direction: column; gap: 8px; } .qc-blog-card-title { font-size: 18px; font-weight: 600; color: #1a1a1a; margin: 0; } .qc-blog-card-excerpt { font-size: 14px; color: #555555; line-height: 1.5; margin: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .qc-blog-card-date { font-size: 12px; color: #888888; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 6px; } </style> <section class="qc-blog"> <div class="qc-blog-header"> <h2 class="qc-blog-title">Latest from the Blog</h2> <p class="qc-blog-subtitle">Stories and updates from the team.</p> </div> <div class="qc-blog-grid" data-state="loading"> <div class="qc-blog-loading">Loading posts…</div> </div> <div class="qc-blog-empty" hidden> <p>No posts published yet.</p> </div> </section> `; (function(component) { var grid = component.shadowRoot.querySelector('.qc-blog-grid'); var emptyState = component.shadowRoot.querySelector('.qc-blog-empty'); var collectionSlug = component.getAttribute('collection') || 'blog'; var limitAttr = component.getAttribute('limit'); var limit = limitAttr ? parseInt(limitAttr, 10) : 6; function formatDate(value) { if (!value) { return ''; } var date = typeof value === 'number' ? new Date(value) : new Date(String(value)); if (isNaN(date.getTime())) { return ''; } return date.toLocaleDateString('en-CA', { year: 'numeric', month: 'long', day: 'numeric' }); } function buildCard(item) { var title = item.title || item.headline || item.name || 'Untitled'; var excerpt = item.excerpt || item.summary || item.description || ''; var image = item.featuredImage || item.image || (Array.isArray(item.images) && item.images[0] ? (item.images[0].url || item.images[0]) : ''); var dateValue = item.publishedAt || item.createdAt; var dateLabel = formatDate(dateValue); var slug = item.slug || item.id; return '' + '<a class="qc-blog-card" href="/' + collectionSlug + '/' + slug + '">' + (image ? '<img class="qc-blog-card-image" src="' + image + '" alt="' + title + '" loading="lazy" decoding="async">' : '<div class="qc-blog-card-image"></div>') + '<div class="qc-blog-card-body">' + (dateLabel ? '<span class="qc-blog-card-date">' + dateLabel + '</span>' : '') + '<h3 class="qc-blog-card-title">' + title + '</h3>' + (excerpt ? '<p class="qc-blog-card-excerpt">' + excerpt + '</p>' : '') + '</div>' + '</a>'; } function render(items) { if (!items || items.length === 0) { grid.setAttribute('data-state', 'empty'); grid.innerHTML = ''; if (emptyState) { emptyState.hidden = false; } return; } grid.setAttribute('data-state', 'ready'); if (emptyState) { emptyState.hidden = true; } grid.innerHTML = items.map(buildCard).join(''); } if (window.__QC_CMS__ && typeof window.__QC_CMS__.getItems === 'function') { window.__QC_CMS__.getItems(collectionSlug, { limit: limit, orderBy: 'publishedAt', orderDirection: 'desc' }).then(render).catch(function() { grid.setAttribute('data-state', 'error'); grid.innerHTML = '<p class="qc-blog-loading">Posts could not be loaded right now.</p>'; }); } else { grid.setAttribute('data-state', 'error'); grid.innerHTML = '<p class="qc-blog-loading">CMS API unavailable.</p>'; } })(this); } } if (!customElements.get('qc-blog-grid')) { customElements.define('qc-blog-grid', QcBlogGridElement); } </script> <!-- Web Component: qc-listings-grid --> <script> class QcListingsGridElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.shadowRoot.innerHTML = ` <style>:host { display: block; font-family: var(--font-family, inherit); } .qc-listings { padding: 64px 24px; max-width: 1200px; margin: 0 auto; } .qc-listings-header { text-align: center; margin-bottom: 40px; } .qc-listings-title { font-size: 32px; font-weight: 700; color: #1a1a1a; margin: 0 0 8px; } .qc-listings-subtitle { font-size: 16px; color: #555555; margin: 0; } .qc-listings-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; } .qc-listings-loading { grid-column: 1 / -1; text-align: center; padding: 40px; color: #777777; font-size: 15px; } .qc-listing-card { background: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06); transition: transform 0.15s ease, box-shadow 0.15s ease; text-decoration: none; color: inherit; display: flex; flex-direction: column; } .qc-listing-card:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.1); } .qc-listing-card-image { width: 100%; height: 200px; object-fit: cover; background: #f0f0f0; } .qc-listing-card-body { padding: 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; } .qc-listing-card-title { font-size: 18px; font-weight: 600; color: #1a1a1a; margin: 0; } .qc-listing-card-address { font-size: 14px; color: #666666; margin: 0; } .qc-listing-card-price { font-size: 18px; font-weight: 700; color: var(--color-primary, #1a1a1a); margin-top: 6px; } .qc-listing-card-specs { font-size: 13px; color: #777777; margin: 0; display: flex; gap: 12px; } .qc-listings-empty { text-align: center; padding: 40px; color: #777777; } </style> <section class="qc-listings"> <div class="qc-listings-header"> <h2 class="qc-listings-title">Featured Listings</h2> <p class="qc-listings-subtitle">Real properties from our active inventory.</p> </div> <div class="qc-listings-grid" data-state="loading"> <div class="qc-listings-loading">Loading listings.</div> </div> <div class="qc-listings-empty" hidden> <p>No listings available yet. Check back soon.</p> </div> </section> `; (function(component) { var grid = component.shadowRoot.querySelector('.qc-listings-grid'); var emptyState = component.shadowRoot.querySelector('.qc-listings-empty'); var limitAttr = component.getAttribute('limit'); var limit = limitAttr ? parseInt(limitAttr, 10) : 6; var sortBy = component.getAttribute('sort-by') || 'default'; var filterLogic = (component.getAttribute('filter-logic') || 'and').toLowerCase() === 'or' ? 'or' : 'and'; var filtersRaw = component.getAttribute('filters') || ''; var filters = []; if (filtersRaw) { try { var parsed = JSON.parse(filtersRaw); if (Array.isArray(parsed)) { filters = parsed; } } catch (parseError) { filters = []; } } function formatPrice(low, high) { if (!low && !high) { return 'Contact for pricing'; } var formatter = new Intl.NumberFormat('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 }); if (!high || low === high) { return formatter.format(low) + '/mo'; } return formatter.format(low) + ' to ' + formatter.format(high) + '/mo'; } function buildCard(item) { var images = item.images || []; var firstImage = images[0] && images[0].url ? images[0].url : ''; var headline = item.unitName || item.buildingName || item.compiledAddress || 'Unit'; var address = item.compiledAddress || item.buildingName || ''; var price = formatPrice(item.costLowest, item.costHighest); var beds = item.bedrooms != null ? item.bedrooms + ' bed' : ''; var baths = item.washrooms != null ? item.washrooms + ' bath' : ''; var specs = [beds, baths].filter(Boolean).join(', '); return '' + '<a class="qc-listing-card" href="/listings/' + (item.id || '') + '">' + (firstImage ? '<img class="qc-listing-card-image" src="' + firstImage + '" alt="' + headline + '" loading="lazy" decoding="async">' : '<div class="qc-listing-card-image"></div>') + '<div class="qc-listing-card-body">' + '<h3 class="qc-listing-card-title">' + headline + '</h3>' + (address ? '<p class="qc-listing-card-address">' + address + '</p>' : '') + '<p class="qc-listing-card-price">' + price + '</p>' + (specs ? '<p class="qc-listing-card-specs">' + specs + '</p>' : '') + '</div>' + '</a>'; } function render(items) { if (!items || items.length === 0) { grid.setAttribute('data-state', 'empty'); grid.innerHTML = ''; if (emptyState) { emptyState.hidden = false; } return; } grid.setAttribute('data-state', 'ready'); if (emptyState) { emptyState.hidden = true; } grid.innerHTML = items.map(buildCard).join(''); } if (window.__QC_CMS__ && typeof window.__QC_CMS__.getInventory === 'function') { window.__QC_CMS__.getInventory({ limit: limit, filters: filters, filterLogic: filterLogic, sortBy: sortBy }).then(render).catch(function() { grid.setAttribute('data-state', 'error'); grid.innerHTML = '<p class="qc-listings-loading">Listings could not be loaded right now.</p>'; }); } else { grid.setAttribute('data-state', 'error'); grid.innerHTML = '<p class="qc-listings-loading">Inventory API unavailable.</p>'; } })(this); } } if (!customElements.get('qc-listings-grid')) { customElements.define('qc-listings-grid', QcListingsGridElement); } </script> <script> (function() { // Pre-loaded item data for this virtual page window.__CMS_ITEM_DATA__ = {"id":"QBJaGcoSQ0B5IOkgLprI","slug":"what-is-seo-optimization-why-it-matters","status":"published","publishedAt":1793030455073,"displayOrder":0,"createdAt":1793030455073,"title":"What Is SEO Optimization & Why It Matters For B2B Growth","body":"What **_is_** SEO optimization?! We hear that a lot…\n\nIt is the strategic process of fine-tuning every aspect of your website - from how pages are structured and coded to the quality of your content and external signals - to earn higher rankings in search engines and engage the right audience. By treating optimization as an ongoing commitment, B2B organizations can ensure their digital presence evolves with market demands and algorithm updates.\n\nFor B2B companies, where purchase decisions involve lengthy research phases and multiple stakeholders, being visible in the right search results is essential to capturing high-value leads. Unlike consumer markets driven by impulse, B2B buyers follow a deliberate journey; they prioritise authoritative insights, peer recommendations and detailed comparisons before making a decision. A targeted SEO strategy helps position your brand at each stage, turning prospects into conversion-ready opportunities.\n\nCanadian businesses are increasingly embracing digital channels to reach clients beyond their local networks. According to Statistics Canada, one-third of enterprises reported e-commerce sales in 2021, highlighting the urgency to stand out in a crowded online environment. As B2B firms invest in smarter marketing, a robust SEO foundation has become a competitive necessity rather than an optional extra.\n\nThis article will begin by defining SEO optimization within a B2B context, then explore its core components - on-page, technical and off-page - before offering practical guidance on implementation and measurement. Finally, we’ll look ahead to emerging trends that will keep your strategy future-proof and primed for growth.\n\n## 1\\. Defining what is SEO optimization in the B2B context\n\nIn B2B marketing, “SEO optimization” goes beyond a one-off tweak. It’s the coordinated effort to make your site both understandable to search engines and genuinely useful to potential buyers, coupled with a continuous feedback loop that adapts to user behaviour and algorithm updates. By weaving together on-page, technical and off-page activities under an ongoing optimization mindset, B2B organisations can stay visible to decision-makers throughout long and complex purchase journeys.\n\nKey terms:\n\n- **[Search Engine Optimization (SEO)](/blog/what-is-seo-optimization-why-it-matters):** The practice of improving your site’s structure, content and link profile to earn higher rankings in unpaid (organic) search results.\n- **Optimization:** An iterative cycle of collecting performance data (traffic, rankings, conversions), identifying opportunities and refining content, technical setup or outreach tactics.\n- **SEO optimization:** The fusion of both ideas,targeted SEO efforts maintained by regular analysis, testing and updates.\n\n### 1.1 What is SEO?\n\nAt its core, SEO is about two things: making your website easy for search-engine crawlers to index and valuable enough that users click through and engage. The primary goal is to rank for keywords that align with your buyer personas. Historically, SEO began with simple tactics,think exact-match keywords and link-quantity,but has evolved into a holistic discipline that balances relevance, authority and user experience.\n\n### 1.2 What does optimization add?\n\nOptimization transforms SEO from a project into a practice. Rather than “set and forget,” it introduces a loop:\n\n1. **Research:** Gather data on keyword performance, page load times and backlink health.\n2. **Implement:** Adjust headings, compress images or secure new links from industry publications.\n3. **Measure:** Track changes in organic traffic, bounce rates and lead submissions.\n4. **Update:** Refresh content, fix technical errors or pivot outreach to new domains.\n\nFor example, you might publish a white paper, monitor its download rate and search positioning, then refine the title, add new internal links and promote it via a guest post to boost authority.\n\n### 1.3 SEO vs. SEM vs. Search Experience Optimization\n\nB2B marketers often juggle related disciplines. Understanding their differences clarifies where to focus budgets and resources:\n\n| | SEO | SEM | Search Experience Optimization |\n| --- | --- | --- | --- |\n| Objective | Improve organic rankings and traffic | Combine organic and paid search efforts | Holistic user experience across search and on-site |\n| Visibility | Unpaid (organic) | Paid and unpaid | Organic, paid, site search, UX metrics |\n| Key tactics | Keyword research, [content updates](/blog), link building | PPC campaigns, bid management, ad copy testing | On-page UX, personalisation, CRO, AI-driven insights |\n| Typical KPIs | Impressions, clicks, organic conversions | Cost per click (CPC), ad impressions, ROI | Engagement rate, time on site, search‐to-lead conversion |\n\nIntegrating all three ensures you capture interest at every touchpoint,whether prospects click an ad, land via search or explore your site’s rich content. For B2B growth, this blended approach not only drives top-of-funnel visibility but also fosters seamless buyer experiences.\n\n## 2\\. The significance of SEO optimization for B2B growth\n\nIn B2B markets, leads are fewer but far more valuable than in consumer segments. A single contract can be worth thousands,or even millions,of dollars, so every opportunity counts. [SEO optimization fuels top-of-funnel visibility](/blog), ensuring your brand appears when decision-makers research solutions. By investing in organic search, you build a pipeline of qualified prospects that sustains longer sales cycles and reduces reliance on costly, short-lived paid campaigns.\n\nKey advantages of a robust SEO programme:\n\n- **Increased qualified leads:** Prospects who find you via targeted keywords are already researching solutions, not just browsers.\n- **Lower cost per acquisition:** While paid ads stop delivering once the budget is spent, organic traffic compounds over time.\n- **Sustainable growth:** High-ranking pages continue to attract traffic and inquiries long after initial publication.\n\n### 2.1 Unique characteristics of the B2B buyer journey\n\nB2B purchasing is a multi-stage, research-intensive process, often involving several stakeholders. SEO must address the distinct needs at each step:\n\n| Stage | Buyer intent | SEO tactic |\n| --- | --- | --- |\n| Awareness | “What is a [fractional CMO](/blog/what-are-outsourced-cmo-services)?” | Blog posts, “what is” guides |\n| Consideration | “Benefits of outsourcing a CMO” | White papers, comparison articles |\n| Decision | “Top B2B marketing consultants” | Case studies, service landing pages |\n\nBy mapping content formats to intent, you guide prospects from initial curiosity through to a conversion-ready decision.\n\n### 2.2 Core benefits of SEO optimization for B2B\n\n1. **Higher-quality leads**\n\nOrganisations that find you via specific, intent-based queries are more likely to convert than general traffic.\n2. **Improved brand authority**\n\nConsistent visibility for niche topics,through thought-leadership content and case studies,positions your business as a trusted expert.\n3. **Better alignment between sales and marketing**\n\nSEO data (e.g., top-performing pages, search terms) informs both teams, ensuring marketing efforts address real pain points.\n\nHypothetical ROI comparison over six months:\n\n```\nPaid ads:\n Spend | $30,000\n Leads | 60 (avg. CPC $5)\n Conversion | 10%\n New clients | 6\n\nOrganic SEO:\n Investment | $20,000 (content + technical work)\n Leads | 100\n Conversion | 15%\n New clients | 15\n```\n\nEven with a lower upfront investment, SEO can deliver 2.5× more new clients and a lower cost per acquisition.\n\n### 2.3 Evidence of impact in Canada\n\nCanadian enterprises are rapidly adopting digital channels:\n\n- One-third (33%) of Canadian businesses reported e-commerce sales in 2021 (Statistics Canada).\n- A significant share of small and medium firms have embraced cloud computing and ICT tools to streamline operations and collaborate remotely.\n\nThese trends underscore that **online visibility is no longer optional**. B2B firms looking to capture a growing pool of digitally active buyers must prioritise SEO optimization to compete and thrive in Canada’s evolving marketplace.\n\n## 3\\. Core components of SEO optimization\n\nEvery successful [SEO strategy](/blog/optimize-your-marketing-strategy-for-b2b) rests on three interdependent pillars: on-page, technical and off-page optimization. Treat them as the gears in a finely tuned engine,each one must turn smoothly for your entire site to perform at its best. On-page work makes pages relevant and engaging; technical work ensures they can be found and displayed correctly; off-page activities signal to search engines that your brand is trusted and authoritative.\n\nThese components often overlap. For example, fast-loading pages (technical) improve user engagement (on-page), which in turn encourages more backlinks (off-page). Likewise, insightful content (on-page) attracts mentions and social shares (off-page), while clear site structure (technical) makes it easier for search engines to reward that content. Balancing all three creates the synergy you need for sustainable B2B growth.\n\n### 3.1 On-page optimization\n\nOn-page optimization focuses on everything a user (and search-engine crawler) sees on an individual page. Its goal is to match each page’s content and HTML elements with the search intent of your target audience.\n\nKey on-page elements include:\n\n- **[Content relevance and quality](/blog/what-is-seo-optimization-why-it-matters):** In-depth, authoritative information that answers your buyer’s questions.\n- **Keyword placement:** Natural use of target terms in titles, headings, and body copy.\n- **Meta tags:** Compelling `<title>` tags and `<meta description>` snippets that drive clicks.\n- **Header structure:** Logical use of H1, H2 and H3 tags to guide readers and crawlers.\n\nExample: Optimising a product page for “Fractional CMO Services”\n\n1. Rewrite the H1 to “Fractional CMO Services for B2B Growth” to reflect the main keyword.\n2. Add a succinct meta description: “Part-time CMO expertise to accelerate your B2B marketing,no long-term commitment.”\n3. Break content into sections: Benefits, Pricing, Case Studies, FAQs,each with relevant subheadings.\n4. Weave secondary keywords like “outsourced marketing leadership” into the introduction and bullet lists.\n\nThese tweaks not only clarify topic relevance but make the page more scannable, boosting both user satisfaction and search visibility.\n\n### 3.2 Technical optimization\n\nTechnical SEO underpins everything on your site. Even the best content won’t rank if search engines can’t crawl, index or render your pages properly. This pillar addresses the behind-the-scenes infrastructure that powers on-page and off-page efforts.\n\nFocus areas for technical optimization:\n\n- **Site architecture & XML sitemaps:** Logical folder structures and up-to-date sitemaps guide crawlers to all important pages.\n- **Mobile-first indexing:** Ensuring responsive design and touch-friendly navigation, as most searches now originate on mobile devices.\n- **Core Web Vitals:**\n - Largest Contentful Paint (LCP) for loading speed\n - First Input Delay (FID) for interactivity\n - Cumulative Layout Shift (CLS) for visual stability\n- **HTTPS & site security:** Encrypted connections (SSL/TLS) build trust with both users and search algorithms.\n\nBy ironing out slow load times, crawl errors or insecure protocols, technical optimization ensures your content has a solid foundation to earn visibility,and keeps visitors on-site rather than bouncing to a faster competitor.\n\n### 3.3 Off-page optimization\n\nOff-page optimization encompasses all activities outside your website that influence how search engines perceive your brand’s authority and trustworthiness. When executed strategically, these efforts amplify the impact of your on-site work and broaden your reach.\n\nKey off-page tactics for B2B:\n\n- **Link building:** Secure high-quality backlinks through guest articles in trade publications, collaborations with industry associations and contributions to authoritative blogs.\n- **Social proof & brand mentions:** Encourage reviews on platforms like G2 or Capterra, and monitor unlinked brand mentions to convert them into links.\n- **Directory & partnership listings:** Claim and optimise your profile in niche directories and partner portals (for example, local chambers of commerce or sector-specific alliances).\n\nSample link outreach approaches:\n\n1. Offer a data-driven insight report to a well-known trade magazine in exchange for a link.\n2. Co-author a thought-leadership article with an industry association, publishing it on both sites.\n3. Host a webinar with a technology partner, then syndicate the event summary to generate natural mentions.\n\nTogether, these off-page signals send a clear message to search engines: your brand is a credible resource in its niche.\n\n## 4\\. On-page optimization strategies for B2B websites\n\nOn-page optimization is where SEO meets your buyer’s journey. By aligning each page’s elements with specific user needs,from the first exploratory query to the final decision,you can guide prospects smoothly toward conversion. This involves a three-phase approach: planning and research, creation and optimization, then review and improvement. Each phase ensures your content stays relevant, authoritative and discoverable.\n\n### 4.1 Conducting B2B keyword research and mapping user intent\n\nEffective on-page SEO starts with understanding what your audience is searching for and why. For B2B, this often means identifying terms that reflect the stages of a longer, more considered purchasing process.\n\n1. Tools and methods:\n - Use platforms like Google Keyword Planner, Ahrefs or SEMrush to uncover search volume and competition for niche B2B terms.\n - Interview sales teams and customer-facing staff to surface real-world language and pain points.\n2. Grouping by intent:\n - Informational: “What is a fractional CMO?”\n - Commercial: “Benefits of outsourcing marketing leadership”\n - Transactional: “Hire B2B marketing consultant”\n3. Keyword mapping template:Buyer stagePersona questionTarget keywordAwarenessWhat is a fractional CMO?fractional CMO servicesConsiderationHow to evaluate marketing consultancies?marketing consultancy comparisonDecisionBest B2B marketing consultant Canadahire B2B marketing consultant\n\nLinking these keywords to the appropriate stage of your buyer persona ensures your pages answer the right questions at the right time.\n\n### 4.2 Crafting and optimising high-value content\n\n[High-value content](/content-marketing) is non-negotiable in B2B SEO. It requires depth, expertise and a clear path to action.\n\n- Depth and authority:\n\nDraw on case studies, client success stories and original data. A “How to choose a CMO” guide, for instance, might include a decision matrix, interviews with past clients and side-by-side feature comparisons.\n- Clear calls to action:\n\nEvery piece of content should prompt the next step,download a white paper, book a strategy call or subscribe to an industry webinar.\n- Performance measurement and updates:\n\nTrack engagement metrics (time on page, scroll depth) and lead indicators (form fills, downloads). Schedule quarterly reviews to refresh statistics, update links and refine the narrative.\n\nBy weaving in fresh insights and structured calls to action, you transform static pages into dynamic lead generators.\n\n### 4.3 Optimising meta tags, headers and URLs\n\nMeta tags, headers and URL structures are micro-optimizations that pack a macro punch in search performance.\n\n- Title tags (<60 characters):\n\nCraft concise headlines that include your primary keyword and a value proposition.\n\nExample:\n\n• Before: “Services - B2Better”\n\n• After: “Fractional CMO Services for B2B Growth \\| B2Better”\n- Meta descriptions (<155 characters):\n\nSummarise the page’s benefit compellingly, enticing clicks without keyword stuffing.\n\nExample:\n\n• Before: “Learn about our services.”\n\n• After: “Accelerate your marketing ROI with part-time CMO expertise,no long-term commitments.”\n- Header hierarchy and keywords:\n\nUse a single H1 that mirrors the title tag, then H2s and H3s for logical subtopics. Ensure each header incorporates related search terms where natural.\n- Descriptive URLs:\n\nKeep URLs brief and keyword-rich.\n\nExample:\n\n• Before: `/services/cmo/`\n\n• After: `/fractional-cmo-services/`\n\nTogether, these adjustments fine-tune how both users and search engines perceive your content, improving relevance signals and click-through rates.\n\n## 5\\. Technical SEO essentials to support growth\n\nA rock-solid technical foundation ensures that your on-page and off-page efforts are amplified rather than undermined. Before you invest further in content or link building, it pays to audit your site’s health in three critical areas:\n\n1. **Crawl analysis** - Confirm that search engines can discover and index your key pages.\n2. **Site speed & performance testing** - Measure and improve how quickly pages load and respond.\n3. **Accessibility review** - Ensure every visitor, regardless of ability, can engage with your content and navigate your site.\n\nTackle these in sequence to uncover hidden barriers, then prioritise fixes that deliver the greatest impact on visibility and user satisfaction.\n\n### 5.1 Ensuring crawlability and indexability\n\nIf search engines can’t crawl your site, even the best content remains hidden. Start by:\n\n• Reviewing your **robots.txt** file to ensure you’re not accidentally blocking important directories.\n\n• Checking Google Search Console’s **Coverage** report (and Bing Webmaster Tools) for errors like “crawl blocked” or “server error”.\n\n• Submitting an up-to-date **XML sitemap** so crawlers know exactly which URLs matter.\n\nA logical internal linking structure guides bots,and users,through your pages. Group related content into clear directory paths (e.g., `/services/strategy/`) and link between complementary resources.\n\nWatch out for common traps:\n\n- Duplicate content served under multiple URLs (use `rel=\"canonical\"` tags to consolidate).\n- Infinite parameter loops (limit or standardise query strings).\n- Orphan pages (no inbound internal links, hence invisible to crawlers).\n\n### 5.2 Improving site speed and Core Web Vitals\n\nFast, responsive pages delight users and earn favour with [search algorithms](/blog). Focus on Google’s Core Web Vitals:\n\n• **Largest Contentful Paint (LCP):** Time until the main content is visible.\n\n• **First Input Delay (FID):** Delay before the page reacts to user interaction.\n\n• **Cumulative Layout Shift (CLS):** How much layout shifts as resources load.\n\nPractical optimizations:\n\n- **Compress and serve modern image formats** (e.g., WebP or AVIF).\n- **Implement lazy loading** for off-screen images and videos.\n- **Minimise and defer JavaScript/CSS**, bundling and removing unused code.\n- Leverage browser **caching**, **resource preconnect** and a **CDN** to reduce latency.\n\nUse free tools such as [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/) and Lighthouse to identify bottlenecks and track improvements over time.\n\n### 5.3 Enhancing accessibility to improve UX and SEO\n\nAccessible sites cater to all users and often enjoy better engagement metrics,another positive ranking signal. Align with WCAG 2.1 criteria (including new mobile and cognitive guidelines) by:\n\n- Providing **descriptive alt text** for every image to assist screen readers.\n- Ensuring full **keyboard navigation** (tab order, visible focus states).\n- Maintaining **sufficient colour contrast** between text and backgrounds.\n- Labelling form elements clearly (using `<label>` and `aria-label`) so users understand field purposes.\n\nBy removing barriers,whether technical or perceptual,you unlock new traffic opportunities and strengthen the overall user experience, which in turn reinforces your SEO gains.\n\n## 6\\. Off-page SEO tactics to build authority\n\nWhile on-page and technical optimization ensure your site can be discovered and consumed, off-page activities signal to search engines that your brand is a trusted resource in its niche. Quality matters far more than quantity: a handful of links from respected industry sites will move the needle more than dozens of low-value mentions. To make off-page SEO manageable, consider a four-step framework:\n\n1. Identify - map out target domains, influencers and publications.\n2. Outreach - craft personalised pitches that demonstrate value.\n3. Amplify - promote earned coverage through your own channels.\n4. Monitor - track links, mentions and brand sentiment over time.\n\nWhen combined with content marketing, PR and strategic partnerships, these tactics compound your authority and create an ecosystem of endorsements that search engines reward.\n\n### 6.1 Earning high-quality backlinks\n\nNot all backlinks are created equal. A valuable link typically comes from a site that:\n\n- Ranks well for relevant keywords (authority).\n- Operates in your industry vertical (relevance).\n- Features editorially placed, contextual links (natural fit).\n\nProven tactics for acquiring these links include:\n\n- Guest articles on respected trade blogs or association sites.\n- Data-driven content such as original surveys or market reports, which editors love to quote.\n- Digital PR campaigns that pitch compelling news angles to journalists.\n\nEmail outreach template:\n\nSubject: “\\[Name\\], a quick idea for your next \\[Industry\\] article”\n\nHi \\[Name\\],\n\nI’m \\[Your Name\\] from \\[Company\\], and I enjoyed your recent piece on \\[Topic\\]. We’ve just published a \\[survey/report/guide\\] that offers new data on \\[Key Insight\\]. I believe it would add value to your readers. Would you consider linking to it here? \\[URL\\]\n\nIf you’d like additional context or a quote from our CEO, just let me know. Thanks for considering, and keep up the great work!\n\nBest regards,\n\n\\[Your Name\\]\n\n\\[Title\\] \\| \\[Company\\]\n\n### 6.2 Establishing brand authority through digital PR\n\nDigital PR extends beyond link building; it shapes how your brand is perceived both online and in the press. To build authority:\n\n- Collaborate with industry publications and thought leaders on co-authored articles or interviews.\n- Craft press releases around proprietary data, product launches or notable events,distribution via PR wire services can spark coverage and links.\n- Use media-monitoring tools to track brand mentions. When you spot unlinked mentions, reach out and politely request a link to your site.\n\nThese efforts position you as a go-to expert, generating both direct referral traffic and search-friendly citations.\n\n### 6.3 Leveraging social platforms and directories\n\nStrong social and directory presences reinforce your off-page signals:\n\n- Ensure consistent Name, Address, Phone (NAP) information across every business listing to avoid confusing search engines.\n- Claim and fully optimise your profile on LinkedIn, [industry directories](/blog/what-is-seo-optimization-why-it-matters) and partner portals,complete descriptions, logos, and links all matter.\n- Encourage satisfied clients to leave reviews on platforms like G2, Capterra or industry-specific forums. High ratings and thoughtful testimonials not only boost credibility but can also earn you followable backlinks.\n\nRegularly audit these external profiles to keep information fresh and to capitalise on every opportunity to demonstrate your expertise.\n\n## 7\\. Integrating SEO into your B2B marketing strategy\n\nIntegrating SEO into your B2B marketing plan means treating it as the thread that weaves together content, design, development and sales. Rather than delegating SEO to a single specialist or agency, build a culture where every team understands how their work impacts organic visibility, user experience and lead generation. Establish a regular planning rhythm,quarterly strategy reviews combined with monthly execution sprints,to keep optimization aligned with evolving business priorities and market trends.\n\n### 7.1 Aligning SEO with overall goals and buyer personas\n\nStart by defining how SEO supports your overarching objectives, whether that’s increasing MQLs by 30% or boosting e-commerce revenue by $500,000. Translate these targets into clear, measurable SEO OKRs or SMART goals:\n\n- Objective: Increase organic leads by 25% in six months.\n- Key Result 1: Publish ten new thought-leadership articles targeting high-value keywords.\n- Key Result 2: Improve average page one ranking for five priority terms.\n- Key Result 3: Decrease bounce rate on top-to-middle funnel pages by 15%.\n\nNext, build keyword and content plans around your buyer personas. For instance, map “Operations Officer Olivia,” who researches efficiency tools, to informational keywords like “best CRM for operations” and later to decision-stage terms such as “CRM implementation services.” A simple persona-to-keyword exercise might look like:\n\n| Persona | Stage | Persona Question | Target Keyword |\n| --- | --- | --- | --- |\n| Operations Officer Olivia | Awareness | What is a CRM? | what is a CRM |\n| Marketing Manager Mark | Consideration | CRM vs ERP features | CRM features comparison |\n| CEO Caroline | Decision | CRM providers for SMBs | best CRM for small business Canada |\n\nFor deeper guidance, refer to B2Better’s [guide to formulating your B2B marketing strategy](/blog/guide-to-formulating-your-b2b-marketing-strategy), which outlines how to align each marketing channel,including SEO,with your business goals and buyer journeys.\n\n### 7.2 Developing a tactical SEO roadmap and timeline\n\nOnce goals and personas are in place, translate them into a phased SEO roadmap. A typical timeline might include:\n\n| Phase | Duration | Activities | Milestone |\n| --- | --- | --- | --- |\n| Audit | Month 1 | Technical crawl, content inventory, backlink review | Complete SEO health report |\n| Prioritisation | Month 2 | Score issues by impact/effort, define quick wins | Finalised priority list |\n| Execution | Months 3-6 | On-page updates, technical fixes, link outreach | Top 10 issues resolved |\n| Measurement | Ongoing | Monthly traffic, ranking and lead tracking | Quarterly performance review |\n\nVisualise this roadmap in your project-management tool or as a simple Gantt chart, and keep stakeholders informed through monthly sprint demos and quarterly strategy check-ins.\n\n### 7.3 Collaborating across teams and agencies\n\nSeamless collaboration is essential. For internal teams, establish clear handoff processes:\n\n- SEO → Content: Use shared brief templates detailing target keywords, suggested headings and desired CTAs.\n- SEO → Development: Log technical tasks in your ticketing system (e.g., Jira, Trello) with priority tags and due dates.\n- Sales → SEO: Gather frontline insights on prospect questions and objections to inform new content topics.\n\nWhen working with external partners,a fractional CMO or agency,define roles and responsibilities in a service-level agreement (SLA). Schedule regular check-ins (bi-weekly or monthly) to review performance metrics, adjust priorities and address any roadblocks. Centralise all documentation in a collaborative workspace like Confluence or shared Google Drive folders to ensure transparency and accountability across every phase of your SEO programme.\n\n## 8\\. Optimising website content to maximise results\n\nContent is the engine that powers SEO success for B2B organisations. A well-structured, authoritative page not only attracts search traffic but also guides prospects smoothly through each stage of a complex buying journey. To stay ahead, you need a mix of strategic oversight, rigorous audits and continuous refinement. In this section, we’ll explore how specialised services can jump-start your content performance, how to conduct systematic audits, and how to optimise landing pages and touchpoints for maximum impact.\n\n### 8.1 Refining website content with SEO-driven services\n\nExpert content-optimization services offer an outsider’s perspective and the technical chops to transform under-performing pages into lead magnets. A typical engagement includes:\n\n- A detailed **competitive analysis** that benchmarks your content against top-ranking rivals.\n- **Content-gap identification** to uncover topics your audience searches for but your site doesn’t yet address.\n- An **editorial calendar** prioritising high-value assets such as white papers, case studies and guided tutorials.\n\nBy tapping into these deliverables, you gain not only fresh, data-backed recommendations but also an implementation roadmap. Learn how our [website content optimization services](/content-marketing) can enhance your messaging, improve SEO metrics and accelerate lead generation.\n\n### 8.2 Conducting content audits and ongoing refresh\n\nA content audit is your periodic check-up,ensuring every page still meets your buyer’s needs and search-engine requirements. A straightforward audit process looks like this:\n\n1. **Inventory existing pages**: Catalogue all URLs, titles, word counts and publication dates.\n2. **Evaluate performance metrics**: Use Google Analytics and Search Console to flag under-performing pages (low traffic, high bounce).\n3. **Identify stale or thin content**: Mark any pages with outdated statistics, thin text or irrelevant topics.\n4. **Prioritise updates**: Score each page by impact (traffic potential) and effort (rewrite complexity).\n5. **Implement refreshes**: Update facts, sharpen headlines, add new internal links and improve calls to action.\n\nOngoing refreshes keep your content relevant, maintain your rankings and demonstrate authority to both users and search engines. For a deeper dive, see our guide on [B2B website content optimization to boost online presence](/blog/what-is-seo-optimization-why-it-matters).\n\n### 8.3 Optimising landing pages and buyer journey touchpoints\n\nLanding pages are the frontline of conversions. Whether you’re driving downloads, demo sign-ups or contact requests, each element must work together to build trust and remove friction:\n\n- **Clear, benefit-focused headlines**: State immediately what visitors will gain.\n- **Concise, scannable copy**: Use bullet points, subheadings and short paragraphs.\n- **Strong social proof**: Showcase testimonials, client logos or quantified results.\n- **Focused calls to action**: Limit each page to a single primary goal (e.g., “Download the Guide”).\n\nA/B testing frameworks help you iterate rapidly. Test variables such as button colour, headline length or testimonial placement. Track which combination yields higher form completions, and roll out the winner site-wide.\n\nFor advanced tactics,like dynamic personalisation and interactive content,explore our [innovative B2B content marketing strategies](/blog). These approaches can boost engagement, reduce drop-off and guide prospects toward a purchase decision more effectively.\n\n## 9\\. Implementing SEO optimization on a startup budget\n\nMany early-stage B2B firms juggle tight budgets and lean teams, yet they still need to lay the foundations for long-term SEO success. By zeroing in on high-impact, low-effort tactics and harnessing free or low-cost tools, startups can ignite organic growth without blowing their budget.\n\n### 9.1 Prioritising quick wins for early-stage B2B startups\n\nWhen resources are scarce, focus on projects that deliver noticeable gains with minimal effort. Consider these low-hanging fruit:\n\n1. **Keyword-focused page tweaks**\n\n- Refine title tags and meta descriptions on your top three product or service pages to include primary keywords and clear value propositions.\n2. **Meta tag and header optimization**\n\n- Fix missing or duplicate `<title>` tags site-wide and ensure each page uses a single, descriptive H1.\n3. **Internal linking audit**\n\n- Link each new blog post back to a cornerstone page (e.g., solutions or service overview) and confirm key pages are no more than three clicks from the homepage.\n4. **Basic technical clean-up**\n\n- Submit an up-to-date XML sitemap in Google Search Console and repair critical 404 errors or broken links.\n5. **Google My Business setup** _(if location-relevant)_\n\n- Claim and verify your profile, then encourage happy clients to leave reviews.\n\nImpact vs Effort Scoring Matrix:\n\n| Task | Effort | Impact | Priority |\n| --- | --- | --- | --- |\n| Title tag & meta fixes | Low | High | 1 |\n| Internal link updates | Low | Medium | 2 |\n| XML sitemap submission | Low | Medium | 2 |\n| Broken link repairs | Medium | Medium | 3 |\n| New content topic ideation | High | High | 4 |\n\nFor a full blueprint tailored to young B2B businesses, check out B2Better’s guide on early-stage B2B startups.\n\n### 9.2 Cost-effective tools and techniques\n\nStartups don’t need expensive subscriptions to get organised. Here’s a toolkit of free or affordable resources, plus DIY tactics to boost your profile:\n\n- **Google Search Console & Google Analytics**\n\nMonitor keyword performance, identify crawl errors and track conversions,at zero cost.\n- **PageSpeed Insights & Lighthouse**\n\nDiagnose page-speed issues and prioritise fixes like image compression, caching and code minification.\n- **Answer The Public & Ubersuggest (free tier)**\n\nUnearth content ideas and long-tail keyword suggestions for blogs or FAQs.\n- **HARO (Help a Reporter Out)**\n\nRespond to relevant journalist queries to earn high-quality mentions and backlinks.\n- **Local and industry partnerships**\n\nCo-author webinars or joint case studies with complementary vendors or trade associations.\n- **In-house subject-matter experts**\n\nInterview your team’s experts to generate blog posts, white papers or LinkedIn video scripts.\n\nBy leaning on free insights and internal expertise, you can build momentum before investing in premium tools or agencies.\n\n### 9.3 Scaling SEO efforts over time\n\nOnce quick wins are in hand and basic processes are running smoothly, plan for gradual growth:\n\n1. **Milestone-based hiring**\n\n- At ~$500K-$1M annual revenue: onboard a part-time or junior SEO specialist.\n\n- At ~$2M+: engage a fractional CMO or small-scale agency retainer.\n2. **Budget allocation guidelines**\n\n- Dedicate 5-10% of your overall marketing budget to SEO activities.\n\n- Reinvest savings from organic leads into expanded content and link outreach.\n3. **Transition to paid tools**\n\n- Upgrade to entry-level plans on platforms like SEMrush or Ahrefs for deeper keyword and backlink insights.\n\n- Implement a lightweight project-management tool (Trello, Asana) to track SEO sprints.\n4. **Establish a review cadence**\n\n- Conduct quarterly SEO audits to catch technical issues and re-score priorities.\n\n- Hold monthly content check-ins to refresh or retire underperforming pages.\n\nBy layering in expertise, budget and advanced tools over time, your startup can evolve from DIY SEO to a mature, data-driven programme,without a sudden spike in costs.\n\n## 10\\. Measuring and demonstrating ROI of SEO optimization\n\nMeasuring ROI of SEO is essential to justify ongoing investment, refine tactics and demonstrate concrete value to stakeholders. Unlike [paid campaigns](/blog/what-is-marketing-roi) with immediate spend-to-return metrics, SEO unfolds over months. A clear measurement framework based on relevant KPIs, robust tracking and regular reporting ensures you can connect organic efforts to real business outcomes.\n\n### 10.1 Selecting the right KPIs and setting benchmarks\n\nSelecting KPIs that align with your business objectives is the first step. Focus on metrics that reflect visibility, engagement and conversion:\n\n- Impressions: How often your pages appear in search results\n- Click-through rate (CTR): The percentage of impressions that turn into clicks\n- Organic sessions: The volume of visits from unpaid search\n- Conversion rate: The percentage of visitors completing desired actions (form fills, downloads)\n- Revenue or lead value: The average value per lead or sale attributed to organic search\n\nMake your KPIs SMART,specific, measurable, achievable, relevant and time-bound. For instance:\n\n| Metric | Baseline (Month 0) | Target (Month 6) |\n| --- | --- | --- |\n| Organic leads | 40 | 48 (↑20%) |\n| CTR | 3% | 4% (↑1 pp) |\n| Average time on page | 1 min 30 sec | 2 min |\n\nBenchmarks give context to progress and help prioritise initiatives that will move the needle.\n\n### 10.2 Tracking performance with analytics and reports\n\nAccurate tracking requires the right tools and configurations:\n\n- Google Analytics: Set up goals or events for key actions (e.g., contact form submissions)\n- Google Search Console: Monitor queries, clicks, impressions and indexing issues\n- SEO platforms (Ahrefs, SEMrush, Moz): Track keyword rankings and backlink profiles over time\n\nBuild custom dashboards to visualise trends at a glance and configure automated alerts for significant drops in traffic or increases in crawl errors. A typical monthly SEO report might include:\n\n1. Executive summary: Key wins and challenges\n2. Traffic overview: Sessions, new users and bounce rate\n3. Ranking performance: Changes in position for priority keywords\n4. Conversion metrics: Leads generated and conversion rate\n5. Technical health check: Core Web Vitals and coverage issues\n6. Next steps: Recommendations for the coming month\n\nConsistent reporting keeps leadership informed and teams aligned on priorities.\n\n### 10.3 Calculating content marketing ROI and iterating\n\nQuantifying content ROI demonstrates the direct impact of your optimization efforts. Use the standard ROI formula:\n\n`ROI = (Gain from investment − Cost of investment) / Cost of investment`\n\nFor example, if content-driven SEO brought in $50,000 in revenue and cost $15,000, the calculation is:\n\n`ROI = ($50,000 − $15,000) / $15,000 = 2.33 (233%)`\n\nIn B2B, factor in customer lifetime value (LTV) and multi-touch attribution to capture the full benefit of SEO over longer sales cycles. Track first-touch and assist interactions in your analytics platform to understand how content contributes across the funnel.\n\nFinally, treat measurement as a catalyst for continuous improvement. Regularly review metrics, test new content angles or technical fixes, and iterate your strategy based on what the data reveals. For more on optimising content performance and ROI, explore B2Better’s [content marketing best practices](/blog).\n\n## 11\\. Future trends and sustaining SEO performance\n\nSEO never stands still. As search engines evolve and user behaviours shift, you need proactive strategies to keep your site in top shape. Below, we explore three forward-looking areas,AI-driven search, algorithm agility and ongoing optimization cycles,and outline how to embed them into your routine.\n\n### 11.1 Adapting to AI and generative search\n\nSearch results increasingly feature AI-generated answers,think Google’s AI Overviews, Bing’s generative snippets and AI assistants like ChatGPT or Gemini. These highlights can replace traditional blue links and steer clicks toward concise, directly embedded responses.\n\nTo prepare for this shift:\n\n- Structure content as clear questions and answers, using headings that match user queries (for example, “What is a fractional CMO?”).\n- Apply FAQ or Q&A schema to flag key points for AI systems.\n- Build authority with well-sourced, in-depth information,AI models often draw from trusted sources.\n- Test how your top pages appear in emerging AI platforms (SearchGPT, Perplexity, Copilot) and iterate based on feedback.\n\n### 11.2 Responding to algorithm changes and updates\n\nSearch engines adjust ranking algorithms frequently, impacting factors such as page experience, content depth and link quality. Staying alert and adaptable is crucial.\n\nBest practices include:\n\n- Subscribing to Google’s Search Central blog and respected SEO outlets to spot announcements early.\n- Maintaining a simple log of updates alongside any traffic or ranking changes you observe in Google Analytics or Search Console.\n- Running targeted site health checks,such as re-evaluating Core Web Vitals after performance-focused updates.\n- Planning content sprints that address new priorities (for instance, enriching thin pages when a quality-update arrives).\n\n### 11.3 Continuous monitoring and optimization cycles\n\nEmbedding routine reviews into your calendar turns SEO into a perpetual cycle of improvement rather than a one-off project.\n\n- Quarterly check-ins:\n\n• Audit technical health (crawl errors, mobile usability, speed).\n\n• Evaluate content performance,refresh top pages and rescue under-performers.\n\n• Set targets for link outreach and new content creation.\n- Annual strategy reviews:\n\n• Revisit buyer-persona maps and keyword focuses.\n\n• Scan for new tools, channels or technologies to integrate.\n\n• Align SEO goals with your wider business plan.\n\nUse this basic checklist to stay on track:\n\n- Site health: No fresh critical crawl errors; Core Web Vitals remain within optimal thresholds.\n- Content: Statistics updated; emerging topics covered; internal linking strengthened.\n- Links: High-value backlinks earned; unlinked brand mentions converted.\n\nBy treating SEO as an ongoing, data-driven practice,punctuated by regular audits, experiments and strategic updates,you’ll keep your B2B site agile, visible and primed for sustained growth.\n\n## Driving B2B growth through strategic SEO optimization\n\nStrategic SEO optimization is more than a set of one-off tasks,it’s a continuous commitment to making your website both discoverable and persuasive for B2B buyers. By consistently refining on-page content, ensuring technical excellence and earning high-value off-page signals, you build a foundation for sustainable organic traffic, higher-quality leads and measurable ROI. Understanding what is SEO optimization,and embedding it at the heart of your marketing operations,turns search into a dependable growth channel rather than a hit-and-miss experiment.\n\nFor B2B organisations, the real power of this approach lies in its integration. When content teams, developers and outreach specialists work from the same playbook, every blog post, product page and white paper becomes a targeted asset in your lead-generation engine. Technical health checks prevent surprises, and off-page outreach amplifies your authority in niche markets. Layer in regular performance reviews,tracking metrics from impressions and click-throughs to conversion rates,and you create a self-reinforcing cycle of improvement.\n\nUltimately, driving B2B growth through strategic SEO optimization means treating optimization as an ongoing practice, not a project with a start and finish date. Each insight you gain,whether from keyword data, Core Web Vitals or backlink analytics,feeds back into your content roadmap, your technical backlog and your PR strategies. Over time, this integrated, data-driven framework delivers compounding benefits: a stronger brand presence, an expanding funnel of qualified prospects and the flexibility to pivot as buyer behaviour or search algorithms evolve.\n\nReady to turn search into your most reliable source of high-value leads? Explore [B2Better’s services](/blog) to see how our Fractional CMO and SEO experts can tailor this strategic framework to your unique B2B growth goals.","excerpt":"What is SEO optimization?! We hear that a lot… It is the strategic process of fine-tuning every aspect of your website - from how pages are structured and coded to the quality of your content and external signals - to earn higher rankings in search engines and engage the right audience.","featuredImage":"https://firebasestorage.googleapis.com/v0/b/quick-casa.appspot.com/o/media-library%2FXL3l2exG5NxKAaaSnv4V%2Ftemp_41cdbc9c833c481480b721c8c9af2799.jpg?alt=media&token=7e67a20a-68ba-43cf-803f-67de2f6f5241","pillar":["signal-curation"],"tags":["b2b marketing","customer journey","marketing strategy","search engine optimization","seo"],"date":"2025-06-06","_seoTitle":"What Is SEO Optimization & Why It Matters For B2B Growth","_seoDescription":"What is SEO optimization?! We hear that a lot… It is the strategic process of fine-tuning every aspect of your website - from how pages are structured and ","_slug":"what-is-seo-optimization-why-it-matters"}; window.__CMS_ITEM_SLUG__ = 'what-is-seo-optimization-why-it-matters'; window.__WEBSITE_ID__ = 'mMBKjJorF6mxrxFDK4zo'; // Utility: Format currency function formatCurrency(amount) { if (!amount && amount !== 0) return ''; return new Intl.NumberFormat('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(amount); } // Utility: Format price range function formatPriceRange(low, high) { if (!low && !high) return 'Contact for pricing'; if (low === high || !high) return formatCurrency(low) + '/mo'; return formatCurrency(low) + ' - ' + formatCurrency(high) + '/mo'; } // Make data available globally for any custom scripts window.__CMS_HELPERS__ = { formatCurrency: formatCurrency, formatPriceRange: formatPriceRange, getItemData: function() { return window.__CMS_ITEM_DATA__; } }; // Hydrate CMS field elements that need formatting document.querySelectorAll('[data-cms-format]').forEach(function(element) { var field = element.dataset.cmsField; var format = element.dataset.cmsFormat; var value = window.__CMS_ITEM_DATA__[field]; if (format === 'currency') { element.textContent = formatCurrency(value); } else if (format === 'price-range') { var lowField = element.dataset.cmsLowField || 'costLowest'; var highField = element.dataset.cmsHighField || 'costHighest'; element.textContent = formatPriceRange( window.__CMS_ITEM_DATA__[lowField], window.__CMS_ITEM_DATA__[highField] ); } }); // Handle image galleries document.querySelectorAll('[data-cms-gallery]').forEach(function(gallery) { var field = gallery.dataset.cmsGallery || 'images'; var images = window.__CMS_ITEM_DATA__[field]; if (!images || !Array.isArray(images) || images.length === 0) { gallery.innerHTML = '<p class="cms-empty">No images available</p>'; return; } var html = images.map(function(img, index) { var url = typeof img === 'string' ? img : img.url; return '<div class="cms-gallery-item' + (index === 0 ? ' active' : '') + '">' + '<img src="' + url + '" alt="Image ' + (index + 1) + '">' + '</div>'; }).join(''); gallery.innerHTML = html; }); })(); </script> <script> window.__QC_API_BASE_URL__ = 'https://api.quickcasa.ai'; window.__WEBSITE_ID__ = 'mMBKjJorF6mxrxFDK4zo'; window.__QC_CMS__ = { getInventory: function(options) { options = options || {}; var baseUrl = window.__QC_API_BASE_URL__ || 'https://api.quickcasa.ai'; var config = { source: 'inventory' }; if (options.filters && options.filters.length > 0) { config.inventoryFilters = { filters: options.filters, filterLogic: options.filterLogic || 'and' }; } return fetch(baseUrl + '/v1/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { command: 'websitebuilder-getcmsdata', websiteId: window.__WEBSITE_ID__, componentType: 'cms-list', config: config } }) }) .then(function(response) { return response.json(); }) .then(function(json) { var raw = (json && json.data) ? json.data : ((json && json.result && json.result.data) ? json.result.data : ''); var payload = {}; try { payload = raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : {}; } catch (parseError) { payload = {}; } var items = payload.listings || payload.items || []; var sortBy = options.sortBy || 'default'; var toNumber = function(value) { if (value === null || value === undefined || value === '') { return Number.POSITIVE_INFINITY; } var parsed = parseFloat(value); return isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY; }; if (sortBy === 'price-asc') { items = items.slice().sort(function(a, b) { return toNumber(a.costLowest) - toNumber(b.costLowest); }); } else if (sortBy === 'price-desc') { items = items.slice().sort(function(a, b) { return toNumber(b.costLowest) - toNumber(a.costLowest); }); } else if (sortBy === 'bedrooms-asc') { items = items.slice().sort(function(a, b) { return toNumber(a.bedrooms) - toNumber(b.bedrooms); }); } else if (sortBy === 'bedrooms-desc') { items = items.slice().sort(function(a, b) { return toNumber(b.bedrooms) - toNumber(a.bedrooms); }); } else if (sortBy === 'square-footage-asc') { items = items.slice().sort(function(a, b) { return toNumber(a.squareFootageLowest) - toNumber(b.squareFootageLowest); }); } else if (sortBy === 'square-footage-desc') { items = items.slice().sort(function(a, b) { return toNumber(b.squareFootageLowest) - toNumber(a.squareFootageLowest); }); } else if (sortBy === 'recently-listed') { items = items.slice().sort(function(a, b) { return toNumber(b.listedOnTimestamp) - toNumber(a.listedOnTimestamp); }); } if (options.limit) { items = items.slice(0, options.limit); } return items; }) .catch(function() { return []; }); }, getItems: function(collectionSlug, options) { options = options || {}; var baseUrl = window.__QC_API_BASE_URL__ || 'https://api.quickcasa.ai'; return fetch(baseUrl + '/v1/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { command: 'websitebuilder-getcmsdata', websiteId: window.__WEBSITE_ID__, componentType: 'cms-list', config: { source: 'cms-collection', cmsCollectionConfig: { collectionSlug: collectionSlug, status: 'published', limit: options.limit || 50, orderBy: options.orderBy || 'createdAt', orderDirection: options.orderDirection || 'desc' } } } }) }) .then(function(response) { return response.json(); }) .then(function(json) { var raw = (json && json.data) ? json.data : ((json && json.result && json.result.data) ? json.result.data : ''); try { var payload = raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : {}; return payload.items || []; } catch (parseError) { return []; } }) .catch(function() { return []; }); }, getItem: function(collectionSlug, itemSlug) { var baseUrl = window.__QC_API_BASE_URL__ || 'https://api.quickcasa.ai'; return fetch(baseUrl + '/v1/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { command: 'websitebuilder-getcmscollectionitem', websiteId: window.__WEBSITE_ID__, collectionSlug: collectionSlug, itemSlug: itemSlug } }) }) .then(function(response) { return response.json(); }) .then(function(json) { var raw = (json && json.data) ? json.data : ((json && json.result && json.result.data) ? json.result.data : ''); try { return raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : null; } catch (parseError) { return null; } }) .catch(function() { return null; }); } }; </script> <script> (function() { var collections = document.querySelectorAll('[data-cms-collection]'); if (collections.length === 0) return; var API_BASE = 'https://northamerica-northeast1-quick-casa.cloudfunctions.net/V1-P34TU-O'; var WEBSITE_ID = 'mMBKjJorF6mxrxFDK4zo'; function formatCurrency(amount) { if (!amount && amount !== 0) return ''; return new Intl.NumberFormat('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(amount); } function formatPriceRange(low, high) { if (!low && !high) return 'Contact for pricing'; if (low === high || !high) return formatCurrency(low) + '/mo'; return formatCurrency(low) + ' - ' + formatCurrency(high) + '/mo'; } function formatValue(value, field, format) { if (value == null || value === '') return ''; if (format === 'currency') return formatCurrency(value); if (format === 'price-range') return String(value); if (Array.isArray(value)) return value.join(', '); return String(value); } function hydrateCollection(collectionEl, items) { var templateEl = collectionEl.querySelector('[data-cms-item]'); var emptyEl = collectionEl.querySelector('[data-cms-empty]'); if (!templateEl) return; var limitAttr = collectionEl.getAttribute('data-cms-limit'); var limit = limitAttr ? parseInt(limitAttr, 10) : 50; var itemsToRender = items.slice(0, limit); if (itemsToRender.length === 0) { templateEl.style.display = 'none'; if (emptyEl) emptyEl.style.display = ''; return; } templateEl.style.display = 'none'; if (emptyEl) emptyEl.style.display = 'none'; var parent = templateEl.parentElement || collectionEl; itemsToRender.forEach(function(itemData) { var clone = templateEl.cloneNode(true); clone.removeAttribute('data-cms-item'); clone.style.display = ''; // Text fields (skip elements with data-cms-template - those are handled below) clone.querySelectorAll('[data-cms-field]').forEach(function(el) { if (el.hasAttribute('data-cms-template')) return; var field = el.getAttribute('data-cms-field'); if (!field) return; var value = itemData[field]; var format = el.getAttribute('data-cms-format'); if (value != null && value !== '') { el.textContent = formatValue(value, field, format); } else { var fallback = el.getAttribute('data-cms-fallback'); if (fallback) { el.textContent = fallback; } else if (el.getAttribute('data-cms-hide-empty') === 'true') { el.style.display = 'none'; } } }); // Template fields - resolve {{fieldName}} placeholders within the template string clone.querySelectorAll('[data-cms-template]').forEach(function(el) { var template = el.getAttribute('data-cms-template'); if (!template) return; var result = template.replace(/{{([^}]+)}}/g, function(_match, fieldPath) { var trimmed = fieldPath.trim(); var parts = trimmed.split('.'); var value = itemData; for (var i = 0; i < parts.length; i++) { if (value == null) return ''; value = value[parts[i]]; } if (value == null) return ''; var format = el.getAttribute('data-cms-format'); return formatValue(value, parts[0], format); }); el.textContent = result; }); // Price range fields clone.querySelectorAll('[data-cms-format="price-range"]').forEach(function(el) { var lowField = el.getAttribute('data-cms-low-field') || 'costLowest'; var highField = el.getAttribute('data-cms-high-field') || 'costHighest'; el.textContent = formatPriceRange(itemData[lowField], itemData[highField]); }); // Image fields clone.querySelectorAll('[data-cms-image-field]').forEach(function(el) { var field = el.getAttribute('data-cms-image-field'); if (!field) return; var value = itemData[field]; if (value) { if (Array.isArray(value) && value.length > 0) { var img = value[0]; el.src = (typeof img === 'string') ? img : (img && img.url ? img.url : ''); } else if (typeof value === 'string') { el.src = value; } } }); // Link fields clone.querySelectorAll('[data-cms-link-field]').forEach(function(el) { var field = el.getAttribute('data-cms-link-field'); if (!field) return; var value = itemData[field]; if (value && typeof value === 'string') { el.href = value; } }); // Detail page links (virtual page navigation) clone.querySelectorAll('[data-cms-detail-link]').forEach(function(el) { var urlPrefix = el.getAttribute('data-cms-detail-link'); if (!urlPrefix) return; var slugField = el.getAttribute('data-cms-slug-field') || 'id'; var slug = itemData[slugField] || itemData.id || ''; if (!slug) return; var isPreview = window.location.hostname === 'preview.quickcasa.ai' || window.location.hostname.includes('localhost'); var basePath = isPreview ? '/' + WEBSITE_ID : ''; el.href = basePath + '/' + urlPrefix + '/' + slug; }); parent.appendChild(clone); }); } collections.forEach(function(collectionEl) { var source = collectionEl.getAttribute('data-cms-collection'); if (!source) return; // Context collections - data comes from pre-loaded entity, no API call needed if (source.indexOf('context:') === 0) { var contextField = source.replace('context:', ''); var contextData = window.__CMS_ITEM_DATA__; if (!contextData) return; var contextItems = contextData[contextField]; if (!contextItems || !Array.isArray(contextItems)) return; hydrateCollection(collectionEl, contextItems); return; } var filtersJson = collectionEl.getAttribute('data-cms-filters') || ''; var filterLogic = collectionEl.getAttribute('data-cms-filter-logic') || 'and'; var parsedFilters = []; try { if (filtersJson) { parsedFilters = JSON.parse(filtersJson); } } catch (filterError) { console.warn('Failed to parse CMS filters:', filterError); } var apiConfig = {}; if (source === 'inventory') { apiConfig = { source: 'inventory' }; if (parsedFilters.length > 0) { apiConfig.inventoryFilters = { filters: parsedFilters, filterLogic: filterLogic }; } } else if (source.indexOf('datastore:') === 0) { var collectionPath = source.replace('datastore:', ''); apiConfig = { source: 'datastore', datastoreConfig: { collection: collectionPath } }; if (parsedFilters.length > 0) { apiConfig.datastoreConfig.filters = parsedFilters; apiConfig.datastoreConfig.filterLogic = filterLogic; } } else if (source.indexOf('cms-collection:') === 0) { var collectionSlug = source.replace('cms-collection:', ''); apiConfig = { source: 'cms-collection', cmsCollectionConfig: { collectionSlug: collectionSlug, status: 'published' } }; } else { return; } collectionEl.classList.add('cms-loading'); fetch(API_BASE, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { command: 'WebsiteBuilder-getCmsData', websiteId: WEBSITE_ID, componentType: 'collection', config: apiConfig } }) }) .then(function(r) { return r.json(); }) .then(function(result) { if (result.code === 200) { var data = JSON.parse(result.data); var items = data.listings || data.items || []; hydrateCollection(collectionEl, items); } }) .catch(function(err) { console.error('CMS collection hydration failed:', err); }) .finally(function() { collectionEl.classList.remove('cms-loading'); }); }); })(); </script> <script>// Shared utility functions available to all pages /** * Formats a number as currency * @param {number} amount - The amount to format * @param {string} currency - The currency code (default: 'CAD') * @returns {string} The formatted currency string */ function formatCurrency(amount, currency) { currency = currency || 'CAD'; return new Intl.NumberFormat('en-CA', { style: 'currency', currency: currency, }).format(amount); } /** * Fetches data from the QuickCasa API * @param {string} endpoint - The API endpoint path * @param {object} options - Fetch options * @returns {Promise<object>} The response data */ async function fetchFromApi(endpoint, options) { options = options || {}; var baseUrl = window.__QC_API_BASE_URL__ || 'https://api.quickcasa.ai'; var response = await fetch(baseUrl + endpoint, options); return response.json(); }</script> <script>// Detail template for "blog-posts". Rendered once per published item // at /blog/{item-slug}. The current item's data is on window.__CMS_ITEM_DATA__. // var item = window.__CMS_ITEM_DATA__; // console.log(item.title, item.slug);</script> <script> (function() { 'use strict'; var OPEN_CLASS = 'qc-open'; var ACTIVE_CLASS = 'qc-active'; var MENU_OPEN_CLASS = 'qc-menu-open'; function closeAllDropdowns(except) { var dropdowns = document.querySelectorAll('[data-qc-dropdown].' + OPEN_CLASS); for (var i = 0; i < dropdowns.length; i++) { if (dropdowns[i] !== except) { dropdowns[i].classList.remove(OPEN_CLASS); var menu = dropdowns[i].querySelector('[data-qc-dropdown-menu]'); if (menu) { menu.classList.remove(OPEN_CLASS); } var trigger = dropdowns[i].querySelector('[data-qc-toggle="dropdown"]'); if (trigger) { trigger.classList.remove(ACTIVE_CLASS); } } } } function toggleDropdown(trigger) { var container = trigger.closest('[data-qc-dropdown]'); if (!container) { return; } var menu = container.querySelector('[data-qc-dropdown-menu]'); if (!menu) { return; } var isOpen = container.classList.contains(OPEN_CLASS); closeAllDropdowns(isOpen ? null : container); if (isOpen) { container.classList.remove(OPEN_CLASS); menu.classList.remove(OPEN_CLASS); trigger.classList.remove(ACTIVE_CLASS); } else { container.classList.add(OPEN_CLASS); menu.classList.add(OPEN_CLASS); trigger.classList.add(ACTIVE_CLASS); } } function toggleCollapse(trigger) { var targetSelector = trigger.getAttribute('data-qc-target'); if (!targetSelector) { return; } var target = document.querySelector(targetSelector); if (!target) { return; } var isOpen = target.classList.contains(OPEN_CLASS); var accordion = trigger.closest('[data-qc-accordion]'); if (accordion && !isOpen) { var openItems = accordion.querySelectorAll('[data-qc-collapse].' + OPEN_CLASS); for (var i = 0; i < openItems.length; i++) { openItems[i].classList.remove(OPEN_CLASS); openItems[i].style.maxHeight = '0px'; var openTrigger = accordion.querySelector('[data-qc-target="#' + openItems[i].id + '"]'); if (openTrigger) { openTrigger.classList.remove(ACTIVE_CLASS); } } } if (isOpen) { target.classList.remove(OPEN_CLASS); target.style.maxHeight = '0px'; trigger.classList.remove(ACTIVE_CLASS); } else { target.classList.add(OPEN_CLASS); target.style.maxHeight = target.scrollHeight + 'px'; trigger.classList.add(ACTIVE_CLASS); } } function toggleMenu(trigger) { var targetSelector = trigger.getAttribute('data-qc-target'); console.log('[QC Menu] toggleMenu called. targetSelector:', targetSelector); if (!targetSelector) { console.warn('[QC Menu] No data-qc-target attribute found on trigger'); return; } var target = document.querySelector(targetSelector); console.log('[QC Menu] Target element found:', !!target, 'selector:', targetSelector); if (!target) { console.warn('[QC Menu] Target element not found for selector:', targetSelector); return; } var isOpen = target.classList.contains(OPEN_CLASS); if (isOpen) { target.classList.remove(OPEN_CLASS); trigger.classList.remove(ACTIVE_CLASS); document.body.classList.remove(MENU_OPEN_CLASS); } else { target.classList.add(OPEN_CLASS); trigger.classList.add(ACTIVE_CLASS); document.body.classList.add(MENU_OPEN_CLASS); } } document.addEventListener('click', function(event) { var trigger = event.target.closest('[data-qc-toggle]'); if (trigger) { event.preventDefault(); var toggleType = trigger.getAttribute('data-qc-toggle'); console.log('[QC Toggle] Clicked:', toggleType, 'target:', trigger.getAttribute('data-qc-target')); if (toggleType === 'dropdown') { toggleDropdown(trigger); } else if (toggleType === 'collapse') { toggleCollapse(trigger); } else if (toggleType === 'menu') { toggleMenu(trigger); } return; } if (!event.target.closest('[data-qc-dropdown]')) { closeAllDropdowns(null); } }); document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { closeAllDropdowns(null); var openMenus = document.querySelectorAll('[data-qc-menu].' + OPEN_CLASS); for (var i = 0; i < openMenus.length; i++) { openMenus[i].classList.remove(OPEN_CLASS); document.body.classList.remove(MENU_OPEN_CLASS); } var activeMenuTriggers = document.querySelectorAll('[data-qc-toggle="menu"].' + ACTIVE_CLASS); for (var j = 0; j < activeMenuTriggers.length; j++) { activeMenuTriggers[j].classList.remove(ACTIVE_CLASS); } } }); function initialize() { var dropdownMenus = document.querySelectorAll('[data-qc-dropdown-menu]'); for (var i = 0; i < dropdownMenus.length; i++) { if (!dropdownMenus[i].closest('[data-qc-dropdown].' + OPEN_CLASS)) { dropdownMenus[i].classList.remove(OPEN_CLASS); } } var collapseElements = document.querySelectorAll('[data-qc-collapse]'); for (var j = 0; j < collapseElements.length; j++) { if (!collapseElements[j].classList.contains(OPEN_CLASS)) { collapseElements[j].style.maxHeight = '0px'; collapseElements[j].style.overflow = 'hidden'; } else { collapseElements[j].style.overflow = 'hidden'; collapseElements[j].style.maxHeight = collapseElements[j].scrollHeight + 'px'; } } var menus = document.querySelectorAll('[data-qc-menu]'); for (var k = 0; k < menus.length; k++) { if (!menus[k].classList.contains(OPEN_CLASS)) { menus[k].classList.remove(OPEN_CLASS); } } } // ─── Lightbox ──────────────────────────────────────────────────────────────── var LIGHTBOX_OPEN_CLASS = 'qc-lightbox-open'; var LIGHTBOX_ZOOMED_CLASS = 'qc-lightbox-zoomed'; var LIGHTBOX_ZOOM_MIN = 1; var LIGHTBOX_ZOOM_MAX = 4; var LIGHTBOX_HOLD_ZOOM_SCALE = 2.5; var LIGHTBOX_HOLD_DELAY_MS = 280; var LIGHTBOX_SWIPE_THRESHOLD = 50; var LIGHTBOX_MOVE_CANCEL_THRESHOLD = 10; var LIGHTBOX_WHEEL_STEP = 0.25; var lightboxZoomScale = 1; var lightboxTranslateX = 0; var lightboxTranslateY = 0; function applyLightboxTransform(imageNode, useTransition) { if (!imageNode) { return; } imageNode.style.transition = useTransition ? 'transform 0.25s ease' : 'none'; imageNode.style.transform = 'translate(' + lightboxTranslateX + 'px, ' + lightboxTranslateY + 'px) scale(' + lightboxZoomScale + ')'; } function syncLightboxZoomClass(overlay) { if (!overlay) { return; } if (lightboxZoomScale > LIGHTBOX_ZOOM_MIN) { overlay.classList.add(LIGHTBOX_ZOOMED_CLASS); } else { overlay.classList.remove(LIGHTBOX_ZOOMED_CLASS); } } function resetLightboxZoom(useTransition) { lightboxZoomScale = LIGHTBOX_ZOOM_MIN; lightboxTranslateX = 0; lightboxTranslateY = 0; var overlay = document.getElementById('qc-lightbox-overlay'); if (!overlay) { return; } applyLightboxTransform(overlay.querySelector('.qc-lightbox-image'), useTransition); overlay.classList.remove(LIGHTBOX_ZOOMED_CLASS); } /** * Zooms toward a focal point given in client coordinates, keeping the point * under the cursor or finger fixed on screen. Shared by desktop scroll-zoom, * press-and-hold zoom, pinch-zoom, and double-click toggle. */ function zoomLightboxToPoint(targetScale, focalClientX, focalClientY, useTransition) { var overlay = document.getElementById('qc-lightbox-overlay'); if (!overlay) { return; } var imageNode = overlay.querySelector('.qc-lightbox-image'); if (!imageNode) { return; } var clampedScale = Math.max(LIGHTBOX_ZOOM_MIN, Math.min(LIGHTBOX_ZOOM_MAX, targetScale)); var previousScale = lightboxZoomScale; if (clampedScale === previousScale) { return; } var imageRect = imageNode.getBoundingClientRect(); var displayedCenterX = imageRect.left + imageRect.width / 2; var displayedCenterY = imageRect.top + imageRect.height / 2; var scaleRatio = clampedScale / previousScale; lightboxTranslateX = lightboxTranslateX + (focalClientX - displayedCenterX) * (1 - scaleRatio); lightboxTranslateY = lightboxTranslateY + (focalClientY - displayedCenterY) * (1 - scaleRatio); lightboxZoomScale = clampedScale; if (lightboxZoomScale <= LIGHTBOX_ZOOM_MIN) { lightboxTranslateX = 0; lightboxTranslateY = 0; } applyLightboxTransform(imageNode, useTransition); syncLightboxZoomClass(overlay); } function getLightboxOverlay() { var existing = document.getElementById('qc-lightbox-overlay'); if (existing) { return existing; } var overlay = document.createElement('div'); overlay.id = 'qc-lightbox-overlay'; overlay.innerHTML = '<div class="qc-lightbox-backdrop"></div>' + '<div class="qc-lightbox-container">' + '<button class="qc-lightbox-close" aria-label="Close lightbox">×</button>' + '<div class="qc-lightbox-content">' + '<button class="qc-lightbox-prev" aria-label="Previous image">‹</button>' + '<div class="qc-lightbox-image-wrapper">' + '<img class="qc-lightbox-image" src="" alt="" />' + '</div>' + '<button class="qc-lightbox-next" aria-label="Next image">›</button>' + '</div>' + '<div class="qc-lightbox-info">' + '<div class="qc-lightbox-counter"></div>' + '<h3 class="qc-lightbox-title"></h3>' + '<p class="qc-lightbox-description"></p>' + '<a class="qc-lightbox-cta" href="#" target="_blank" rel="noopener noreferrer"></a>' + '</div>' + '</div>'; document.body.appendChild(overlay); overlay.querySelector('.qc-lightbox-backdrop').addEventListener('click', closeLightbox); overlay.querySelector('.qc-lightbox-close').addEventListener('click', closeLightbox); overlay.querySelector('.qc-lightbox-prev').addEventListener('click', function(navEvent) { navEvent.stopPropagation(); navigateLightbox(-1); }); overlay.querySelector('.qc-lightbox-next').addEventListener('click', function(navEvent) { navEvent.stopPropagation(); navigateLightbox(1); }); // Touch gestures. A single-finger horizontal swipe navigates, a two-finger // pinch zooms and never navigates, and a press-and-hold zooms in until the // finger is released. While zoomed, a single finger pans the image. var gestureUsedMultiTouch = false; var singleTouchStartX = 0; var singleTouchStartY = 0; var singleTouchMoved = false; var isHoldZooming = false; var isPanning = false; var holdZoomTimer = null; var pinchStartDistance = 0; var pinchStartScale = 1; var panOriginX = 0; var panOriginY = 0; var panOriginTranslateX = 0; var panOriginTranslateY = 0; function getTouchDistance(touchA, touchB) { var deltaX = touchA.clientX - touchB.clientX; var deltaY = touchA.clientY - touchB.clientY; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); } function clearHoldZoomTimer() { if (holdZoomTimer) { clearTimeout(holdZoomTimer); holdZoomTimer = null; } } overlay.addEventListener('touchstart', function(touchEvent) { if (touchEvent.touches.length >= 2) { gestureUsedMultiTouch = true; isHoldZooming = false; isPanning = false; clearHoldZoomTimer(); pinchStartDistance = getTouchDistance(touchEvent.touches[0], touchEvent.touches[1]); pinchStartScale = lightboxZoomScale; touchEvent.preventDefault(); return; } gestureUsedMultiTouch = false; singleTouchMoved = false; isPanning = false; singleTouchStartX = touchEvent.touches[0].clientX; singleTouchStartY = touchEvent.touches[0].clientY; if (lightboxZoomScale <= LIGHTBOX_ZOOM_MIN) { clearHoldZoomTimer(); holdZoomTimer = setTimeout(function() { if (!singleTouchMoved && !gestureUsedMultiTouch) { isHoldZooming = true; zoomLightboxToPoint(LIGHTBOX_HOLD_ZOOM_SCALE, singleTouchStartX, singleTouchStartY, true); } }, LIGHTBOX_HOLD_DELAY_MS); } }, { passive: false }); overlay.addEventListener('touchmove', function(touchEvent) { var imageNode = overlay.querySelector('.qc-lightbox-image'); if (touchEvent.touches.length >= 2) { gestureUsedMultiTouch = true; clearHoldZoomTimer(); if (pinchStartDistance > 0) { var currentDistance = getTouchDistance(touchEvent.touches[0], touchEvent.touches[1]); var midpointX = (touchEvent.touches[0].clientX + touchEvent.touches[1].clientX) / 2; var midpointY = (touchEvent.touches[0].clientY + touchEvent.touches[1].clientY) / 2; zoomLightboxToPoint(pinchStartScale * (currentDistance / pinchStartDistance), midpointX, midpointY, false); } touchEvent.preventDefault(); return; } if (touchEvent.touches.length !== 1) { return; } var currentX = touchEvent.touches[0].clientX; var currentY = touchEvent.touches[0].clientY; if (Math.abs(currentX - singleTouchStartX) > LIGHTBOX_MOVE_CANCEL_THRESHOLD || Math.abs(currentY - singleTouchStartY) > LIGHTBOX_MOVE_CANCEL_THRESHOLD) { singleTouchMoved = true; clearHoldZoomTimer(); } if (lightboxZoomScale > LIGHTBOX_ZOOM_MIN) { if (!isPanning) { isPanning = true; panOriginX = currentX; panOriginY = currentY; panOriginTranslateX = lightboxTranslateX; panOriginTranslateY = lightboxTranslateY; } lightboxTranslateX = panOriginTranslateX + (currentX - panOriginX); lightboxTranslateY = panOriginTranslateY + (currentY - panOriginY); applyLightboxTransform(imageNode, false); touchEvent.preventDefault(); } }, { passive: false }); overlay.addEventListener('touchend', function(touchEvent) { clearHoldZoomTimer(); if (isHoldZooming) { isHoldZooming = false; if (touchEvent.touches.length === 0) { resetLightboxZoom(true); } return; } if (gestureUsedMultiTouch) { if (touchEvent.touches.length === 0) { isPanning = false; if (lightboxZoomScale <= LIGHTBOX_ZOOM_MIN + 0.05) { resetLightboxZoom(true); } } return; } if (touchEvent.touches.length === 0) { isPanning = false; } if (lightboxZoomScale > LIGHTBOX_ZOOM_MIN || touchEvent.changedTouches.length === 0) { return; } var endX = touchEvent.changedTouches[0].clientX; var endY = touchEvent.changedTouches[0].clientY; var horizontalDistance = singleTouchStartX - endX; var verticalDistance = Math.abs(endY - singleTouchStartY); if (Math.abs(horizontalDistance) > LIGHTBOX_SWIPE_THRESHOLD && Math.abs(horizontalDistance) > verticalDistance) { navigateLightbox(horizontalDistance > 0 ? 1 : -1); } }, { passive: true }); overlay.addEventListener('touchcancel', function() { clearHoldZoomTimer(); isHoldZooming = false; isPanning = false; }, { passive: true }); // Desktop: scroll the wheel to zoom toward the cursor. overlay.addEventListener('wheel', function(wheelEvent) { if (!overlay.classList.contains(LIGHTBOX_OPEN_CLASS)) { return; } wheelEvent.preventDefault(); var zoomDirection = wheelEvent.deltaY < 0 ? 1 : -1; zoomLightboxToPoint(lightboxZoomScale + zoomDirection * LIGHTBOX_WHEEL_STEP, wheelEvent.clientX, wheelEvent.clientY, false); }, { passive: false }); // Desktop: double-click toggles between a zoomed view and the reset view. overlay.querySelector('.qc-lightbox-image').addEventListener('dblclick', function(doubleClickEvent) { doubleClickEvent.preventDefault(); if (lightboxZoomScale > LIGHTBOX_ZOOM_MIN) { resetLightboxZoom(true); } else { zoomLightboxToPoint(LIGHTBOX_HOLD_ZOOM_SCALE, doubleClickEvent.clientX, doubleClickEvent.clientY, true); } }); return overlay; } var currentGalleryImages = []; var currentGalleryIndex = 0; function openLightbox(imageElement) { var overlay = getLightboxOverlay(); var imageSrc = imageElement.getAttribute('src') || ''; var imageAlt = imageElement.getAttribute('alt') || ''; var title = imageElement.getAttribute('data-qc-lightbox-title-resolved') || imageElement.getAttribute('data-qc-lightbox-title') || ''; var description = imageElement.getAttribute('data-qc-lightbox-description-resolved') || imageElement.getAttribute('data-qc-lightbox-description') || ''; var ctaEnabled = imageElement.getAttribute('data-qc-lightbox-cta') === 'true'; var ctaText = imageElement.getAttribute('data-qc-lightbox-cta-text') || ''; var ctaLink = imageElement.getAttribute('data-qc-lightbox-cta-link') || ''; var inGallery = imageElement.getAttribute('data-qc-lightbox-gallery') === 'true'; currentGalleryImages = []; currentGalleryIndex = 0; if (inGallery) { var allGalleryImages = document.querySelectorAll('[data-qc-lightbox="true"][data-qc-lightbox-gallery="true"]'); for (var galleryIndex = 0; galleryIndex < allGalleryImages.length; galleryIndex++) { var galleryImage = allGalleryImages[galleryIndex]; // Skip images inside an unrendered CMS template - templates stay in // the DOM (display:none) after hydration and would otherwise appear as // a stray slide with raw {{fieldName}} placeholders. if (galleryImage.closest('[data-cms-item]')) { continue; } if (galleryImage === imageElement) { currentGalleryIndex = currentGalleryImages.length; } currentGalleryImages.push(galleryImage); } } else { currentGalleryImages = [imageElement]; } updateLightboxContent(imageSrc, imageAlt, title, description, ctaEnabled, ctaText, ctaLink); var hasMultiple = currentGalleryImages.length > 1; overlay.querySelector('.qc-lightbox-prev').style.display = hasMultiple ? '' : 'none'; overlay.querySelector('.qc-lightbox-next').style.display = hasMultiple ? '' : 'none'; overlay.querySelector('.qc-lightbox-counter').style.display = hasMultiple ? '' : 'none'; if (hasMultiple) { overlay.querySelector('.qc-lightbox-counter').textContent = (currentGalleryIndex + 1) + ' / ' + currentGalleryImages.length; } resetLightboxZoom(false); overlay.classList.add(LIGHTBOX_OPEN_CLASS); document.body.style.overflow = 'hidden'; } function updateLightboxContent(imageSrc, imageAlt, title, description, ctaEnabled, ctaText, ctaLink) { var overlay = document.getElementById('qc-lightbox-overlay'); if (!overlay) { return; } var imageNode = overlay.querySelector('.qc-lightbox-image'); imageNode.src = imageSrc; imageNode.alt = imageAlt; var titleNode = overlay.querySelector('.qc-lightbox-title'); titleNode.textContent = title; titleNode.style.display = title ? '' : 'none'; var descriptionNode = overlay.querySelector('.qc-lightbox-description'); descriptionNode.textContent = description; descriptionNode.style.display = description ? '' : 'none'; var ctaNode = overlay.querySelector('.qc-lightbox-cta'); if (ctaEnabled && ctaText && ctaLink) { ctaNode.textContent = ctaText; ctaNode.href = ctaLink; ctaNode.style.display = ''; } else { ctaNode.style.display = 'none'; } } function navigateLightbox(direction) { if (currentGalleryImages.length <= 1) { return; } currentGalleryIndex += direction; if (currentGalleryIndex < 0) { currentGalleryIndex = currentGalleryImages.length - 1; } else if (currentGalleryIndex >= currentGalleryImages.length) { currentGalleryIndex = 0; } var targetImage = currentGalleryImages[currentGalleryIndex]; var imageSrc = targetImage.getAttribute('src') || ''; var imageAlt = targetImage.getAttribute('alt') || ''; var title = targetImage.getAttribute('data-qc-lightbox-title-resolved') || targetImage.getAttribute('data-qc-lightbox-title') || ''; var description = targetImage.getAttribute('data-qc-lightbox-description-resolved') || targetImage.getAttribute('data-qc-lightbox-description') || ''; var ctaEnabled = targetImage.getAttribute('data-qc-lightbox-cta') === 'true'; var ctaText = targetImage.getAttribute('data-qc-lightbox-cta-text') || ''; var ctaLink = targetImage.getAttribute('data-qc-lightbox-cta-link') || ''; updateLightboxContent(imageSrc, imageAlt, title, description, ctaEnabled, ctaText, ctaLink); resetLightboxZoom(false); var overlay = document.getElementById('qc-lightbox-overlay'); if (overlay) { overlay.querySelector('.qc-lightbox-counter').textContent = (currentGalleryIndex + 1) + ' / ' + currentGalleryImages.length; } } function closeLightbox() { var overlay = document.getElementById('qc-lightbox-overlay'); if (overlay) { overlay.classList.remove(LIGHTBOX_OPEN_CLASS); document.body.style.overflow = ''; } resetLightboxZoom(false); } document.addEventListener('click', function(clickEvent) { var lightboxImage = clickEvent.target.closest('[data-qc-lightbox="true"]'); if (lightboxImage && lightboxImage.tagName === 'IMG') { clickEvent.preventDefault(); openLightbox(lightboxImage); } }); document.addEventListener('keydown', function(keyEvent) { if (keyEvent.key === 'Escape') { closeLightbox(); } var overlay = document.getElementById('qc-lightbox-overlay'); if (overlay && overlay.classList.contains(LIGHTBOX_OPEN_CLASS)) { if (keyEvent.key === 'ArrowLeft') { navigateLightbox(-1); } else if (keyEvent.key === 'ArrowRight') { navigateLightbox(1); } } }); // ─── CMS Collection Interactivity ───────────────────────────────────────── function debounce(cb, delay) { var tid = null; return function() { var ctx = this; var args = arguments; if (tid) { clearTimeout(tid); } tid = setTimeout(function() { cb.apply(ctx, args); }, delay); }; } function createDrawer(title, options) { var toggleLabel = (options && options.toggleLabel) || title; var toggleIcon = (options && options.toggleIcon) || ''; var toggle = document.createElement('button'); toggle.type = 'button'; toggle.className = 'qc-drawer-toggle'; toggle.innerHTML = (toggleIcon ? toggleIcon + ' ' : '') + toggleLabel; var backdrop = document.createElement('div'); backdrop.className = 'qc-drawer-backdrop'; var drawer = document.createElement('div'); drawer.className = 'qc-drawer'; var header = document.createElement('div'); header.className = 'qc-drawer-header'; var titleElement = document.createElement('span'); titleElement.className = 'qc-drawer-title'; titleElement.textContent = title; header.appendChild(titleElement); var closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.className = 'qc-drawer-close'; closeButton.setAttribute('aria-label', 'Close ' + title.toLowerCase()); closeButton.innerHTML = '×'; header.appendChild(closeButton); drawer.appendChild(header); var openDrawer = function() { drawer.classList.add('qc-drawer-open'); backdrop.classList.add('qc-drawer-open'); document.body.classList.add('qc-drawer-body-lock'); }; var closeDrawer = function() { drawer.classList.remove('qc-drawer-open'); backdrop.classList.remove('qc-drawer-open'); document.body.classList.remove('qc-drawer-body-lock'); }; toggle.addEventListener('click', openDrawer); closeButton.addEventListener('click', closeDrawer); backdrop.addEventListener('click', closeDrawer); return { toggle: toggle, backdrop: backdrop, drawer: drawer, open: openDrawer, close: closeDrawer }; } function createFlyout(title, options) { var toggleLabel = (options && options.toggleLabel) || title; var toggleIcon = (options && options.toggleIcon) || ''; var wrapper = document.createElement('div'); wrapper.className = 'qc-flyout-wrapper'; var toggle = document.createElement('button'); toggle.type = 'button'; toggle.className = 'qc-flyout-toggle'; toggle.innerHTML = (toggleIcon ? toggleIcon + ' ' : '') + toggleLabel; wrapper.appendChild(toggle); var backdrop = document.createElement('div'); backdrop.className = 'qc-flyout-backdrop'; wrapper.appendChild(backdrop); var panel = document.createElement('div'); panel.className = 'qc-flyout-panel'; var header = document.createElement('div'); header.className = 'qc-flyout-header'; var titleElement = document.createElement('span'); titleElement.className = 'qc-flyout-title'; titleElement.textContent = title; header.appendChild(titleElement); var closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.className = 'qc-flyout-close'; closeButton.setAttribute('aria-label', 'Close ' + title.toLowerCase()); closeButton.innerHTML = '×'; header.appendChild(closeButton); panel.appendChild(header); wrapper.appendChild(panel); var isOpen = false; var openFlyout = function() { if (isOpen) { return; } isOpen = true; panel.classList.add('qc-flyout-open'); backdrop.classList.add('qc-flyout-open'); toggle.classList.add('qc-flyout-active'); }; var closeFlyout = function() { if (!isOpen) { return; } isOpen = false; panel.classList.remove('qc-flyout-open'); backdrop.classList.remove('qc-flyout-open'); toggle.classList.remove('qc-flyout-active'); }; var toggleFlyout = function(e) { e.stopPropagation(); if (isOpen) { closeFlyout(); } else { openFlyout(); } }; toggle.addEventListener('click', toggleFlyout); closeButton.addEventListener('click', closeFlyout); backdrop.addEventListener('click', closeFlyout); document.addEventListener('click', function(e) { if (isOpen && !wrapper.contains(e.target)) { closeFlyout(); } }); document.addEventListener('keydown', function(e) { if (isOpen && e.key === 'Escape') { closeFlyout(); } }); return { toggle: toggle, wrapper: wrapper, panel: panel, open: openFlyout, close: closeFlyout }; } function parseSynonymDictionary(ds) { var sm = {}; if (!ds) { return sm; } var lines = ds.split('\n'); for (var i = 0; i < lines.length; i++) { var ln = lines[i].trim(); if (!ln || ln.indexOf('=') === -1) { continue; } var pts = ln.split('='); var key = pts[0].trim().toLowerCase(); var vals = pts[1].split(',').map(function(v) { return v.trim().toLowerCase(); }).filter(Boolean); var all = [key].concat(vals); for (var j = 0; j < all.length; j++) { sm[all[j]] = all; } } return sm; } function fuzzyMatch(st, tt) { if (tt.indexOf(st) !== -1) { return true; } if (st.length < 3) { return false; } var sw = st.split(/\s+/u); var tl = tt.toLowerCase(); for (var i = 0; i < sw.length; i++) { if (sw[i].length < 2) { continue; } var ff = false; var tw = tl.split(/\s+/u); for (var j = 0; j < tw.length; j++) { if (levenshteinDistance(sw[i], tw[j]) <= Math.floor(sw[i].length / 3)) { ff = true; break; } } if (!ff && tl.indexOf(sw[i]) === -1) { return false; } } return true; } function levenshteinDistance(a, b) { var la = a.length; var lb = b.length; if (la === 0) { return lb; } if (lb === 0) { return la; } var m = []; for (var i = 0; i <= la; i++) { m[i] = [i]; } for (var j = 0; j <= lb; j++) { m[0][j] = j; } for (var r = 1; r <= la; r++) { for (var c = 1; c <= lb; c++) { var co = a[r-1] === b[c-1] ? 0 : 1; m[r][c] = Math.min(m[r-1][c]+1, m[r][c-1]+1, m[r-1][c-1]+co); } } return m[la][lb]; } function expandSearchWithSynonyms(q, sm) { var terms = [q]; var words = q.split(/\s+/u); for (var i = 0; i < words.length; i++) { var w = words[i].toLowerCase(); if (sm[w]) { for (var j = 0; j < sm[w].length; j++) { if (terms.indexOf(sm[w][j]) === -1) { terms.push(sm[w][j]); } } } } return terms; } function initCategoryFilters() { var containers = document.querySelectorAll('[data-qc-category-filter]'); console.log('[QC Categories] Found', containers.length, 'category filter container(s)'); for (var ci = 0; ci < containers.length; ci++) { var container = containers[ci]; if (container.getAttribute('data-qc-category-filter-init') === 'true') { continue; } container.setAttribute('data-qc-category-filter-init', 'true'); var categoryElements = container.querySelectorAll('[data-qc-category]'); var categorySet = {}; var categoryOrder = []; for (var ei = 0; ei < categoryElements.length; ei++) { var category = categoryElements[ei].getAttribute('data-qc-category'); if (category && !categorySet[category]) { categorySet[category] = true; categoryOrder.push(category); } } if (categoryOrder.length === 0) { continue; } var filterBar = document.createElement('div'); filterBar.className = 'qc-category-bar'; var allButton = document.createElement('button'); allButton.type = 'button'; allButton.className = 'qc-category-pill qc-category-active'; allButton.textContent = 'All'; filterBar.appendChild(allButton); for (var pi = 0; pi < categoryOrder.length; pi++) { var pill = document.createElement('button'); pill.type = 'button'; pill.className = 'qc-category-pill'; pill.textContent = categoryOrder[pi]; pill.setAttribute('data-qc-category-value', categoryOrder[pi]); filterBar.appendChild(pill); } var categoryAlign = container.getAttribute('data-qc-category-align') || 'left'; if (categoryAlign === 'center') { filterBar.style.justifyContent = 'center'; } else if (categoryAlign === 'right') { filterBar.style.justifyContent = 'flex-end'; } var categoryBorderColor = container.getAttribute('data-qc-category-border-color') || ''; var categoryTextColor = container.getAttribute('data-qc-category-text-color') || ''; var categoryActiveColor = container.getAttribute('data-qc-category-active-color') || ''; var categoryActiveText = container.getAttribute('data-qc-category-active-text') || ''; if (categoryBorderColor) { filterBar.style.setProperty('--qc-cat-border', categoryBorderColor); } if (categoryTextColor) { filterBar.style.setProperty('--qc-cat-text', categoryTextColor); } if (categoryActiveColor) { filterBar.style.setProperty('--qc-cat-active', categoryActiveColor); } if (categoryActiveText) { filterBar.style.setProperty('--qc-cat-active-text', categoryActiveText); } var barWrapper = document.createElement('div'); barWrapper.className = 'qc-category-bar-wrapper'; barWrapper.appendChild(filterBar); container.insertBefore(barWrapper, container.firstChild); var animationType = container.getAttribute('data-qc-category-animation') || 'fade'; container.classList.add('qc-category-animate-' + animationType); var transitionDuration = animationType === 'none' ? 0 : 300; (function(filterBarElement, containerElement, duration) { var activePill = filterBarElement.querySelector('.qc-category-active'); filterBarElement.addEventListener('click', function(clickEvent) { var target = clickEvent.target; if (!target.classList || !target.classList.contains('qc-category-pill')) { return; } if (activePill) { activePill.classList.remove('qc-category-active'); } target.classList.add('qc-category-active'); activePill = target; var selectedCategory = target.getAttribute('data-qc-category-value') || ''; var items = containerElement.querySelectorAll('[data-qc-category]'); for (var filterIndex = 0; filterIndex < items.length; filterIndex++) { var item = items[filterIndex]; var isVisible = !selectedCategory || item.getAttribute('data-qc-category') === selectedCategory; if (isVisible) { item.classList.remove('qc-category-hiding'); item.classList.remove('qc-category-hidden'); } else if (!item.classList.contains('qc-category-hidden')) { item.classList.add('qc-category-hiding'); (function(hidingItem) { setTimeout(function() { if (hidingItem.classList.contains('qc-category-hiding')) { hidingItem.classList.remove('qc-category-hiding'); hidingItem.classList.add('qc-category-hidden'); } }, duration); })(item); } } }); })(filterBar, container, transitionDuration); } } function initCollectionControls() { var collections = document.querySelectorAll('[data-cms-collection]'); console.log('[QC Controls] Found', collections.length, 'collection(s)'); for (var ci = 0; ci < collections.length; ci++) { var collection = collections[ci]; if (collection.getAttribute('data-qc-controls-init') === 'true') { console.log('[QC Controls] Collection', ci, 'already initialized, skipping'); continue; } collection.setAttribute('data-qc-controls-init', 'true'); var hasSearch = collection.getAttribute('data-cms-search') === 'true'; var hasSort = collection.getAttribute('data-cms-sort') === 'true'; var hasPagination = collection.getAttribute('data-cms-pagination') === 'true'; var visitorFiltersJson = collection.getAttribute('data-cms-visitor-filters') || ''; var hasFilters = visitorFiltersJson.length > 2; console.log('[QC Controls] Collection', ci, '{ search:', hasSearch, ', sort:', hasSort, ', pagination:', hasPagination, ', filters:', hasFilters, '}'); var cloneCount = collection.querySelectorAll('[data-cms-collection-clone]').length; console.log('[QC Controls] Collection', ci, 'has', cloneCount, 'clone item(s)'); if (cloneCount > 0) { var firstClone = collection.querySelector('[data-cms-collection-clone]'); if (firstClone) { var fieldElements = firstClone.querySelectorAll('[data-cms-field]'); var fieldNames = []; for (var fieldIndex = 0; fieldIndex < fieldElements.length; fieldIndex++) { fieldNames.push(fieldElements[fieldIndex].getAttribute('data-cms-field')); } console.log('[QC Controls] Fields in first clone:', fieldNames.join(', ')); } } if (!hasSearch && !hasSort && !hasFilters && !hasPagination) { console.log('[QC Controls] Collection', ci, 'has no interactive features, skipping'); continue; } var controlsStyle = collection.getAttribute('data-cms-controls-style') || 'clean'; var controlsAccent = collection.getAttribute('data-cms-controls-accent') || ''; var controlsContainer = document.createElement('div'); controlsContainer.className = 'qc-collection-controls qc-controls-' + controlsStyle; if (controlsAccent) { controlsContainer.style.setProperty('--qc-accent', controlsAccent); } collection.parentNode.insertBefore(controlsContainer, collection); var state = { searchQuery: '', searchFields: (collection.getAttribute('data-cms-search-fields') || '').split(',').map(function(f) { return f.trim(); }).filter(Boolean), searchFuzzy: collection.getAttribute('data-cms-search-fuzzy') === 'true', searchSynonyms: parseSynonymDictionary(collection.getAttribute('data-cms-search-synonyms') || ''), sortField: '', sortDirection: 'asc', currentPage: 1, pageSize: parseInt(collection.getAttribute('data-cms-page-size') || '12', 10), paginationType: collection.getAttribute('data-cms-pagination-type') || 'load-more', visitorFilters: [], activeFilters: {} }; var sortDefault = collection.getAttribute('data-cms-sort-default') || ''; if (sortDefault) { var sp = sortDefault.split(':'); state.sortField = sp[0] || ''; state.sortDirection = sp[1] || 'asc'; } console.log('[QC Controls] Sort default:', sortDefault || '(none)', '→ field:', state.sortField, 'direction:', state.sortDirection); try { if (hasFilters) { state.visitorFilters = JSON.parse(visitorFiltersJson); } } catch (e) { console.warn('Invalid visitor filters JSON:', e); } if (hasSearch) { var sw = document.createElement('div'); sw.className = 'qc-collection-search'; if (controlsStyle === 'verbose') { var searchLabel = document.createElement('label'); searchLabel.className = 'qc-collection-search-label'; searchLabel.textContent = 'Search'; sw.appendChild(searchLabel); } var si = document.createElement('input'); si.type = 'text'; si.className = 'qc-collection-search-input'; si.placeholder = collection.getAttribute('data-cms-search-placeholder') || 'Search...'; sw.appendChild(si); controlsContainer.appendChild(sw); si.addEventListener('input', (function(cs, ce) { var da = debounce(function() { applyCollectionState(ce, cs); }, 200); return function(e) { cs.searchQuery = e.target.value.toLowerCase(); cs.currentPage = 1; da(); }; })(state, collection)); } var hasFlyoutContent = hasFilters || hasSort; var filterContentTarget = null; var filterIcon = '<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M2 3h12M4 8h8M6 13h4"/></svg>'; if (hasFlyoutContent && controlsStyle === 'minimal') { var filterFlyout = createFlyout('Filters & Sort', { toggleLabel: 'Filters', toggleIcon: filterIcon }); controlsContainer.appendChild(filterFlyout.wrapper); filterContentTarget = filterFlyout.panel; } else if (hasFlyoutContent) { var filterDrawer = createDrawer('Filters & Sort', { toggleLabel: 'Filters', toggleIcon: filterIcon }); controlsContainer.appendChild(filterDrawer.toggle); controlsContainer.appendChild(filterDrawer.backdrop); filterContentTarget = filterDrawer.drawer; } if (hasFilters) { var fw = document.createElement('div'); fw.className = 'qc-collection-filters'; for (var fi = 0; fi < state.visitorFilters.length; fi++) { var fe = buildFilterControl(state.visitorFilters[fi], state, collection); if (fe) { fw.appendChild(fe); } } if (filterContentTarget) { filterContentTarget.appendChild(fw); } else { controlsContainer.appendChild(fw); } } if (hasSort) { var sow = document.createElement('div'); sow.className = 'qc-collection-sort'; var sl = document.createElement('span'); sl.className = 'qc-collection-sort-label'; sl.textContent = 'Sort by:'; sow.appendChild(sl); var ss = document.createElement('select'); ss.className = 'qc-collection-sort-select'; var dopt = document.createElement('option'); dopt.value = ''; dopt.textContent = 'Default'; ss.appendChild(dopt); var sortCfg = null; try { var scj = collection.getAttribute('data-cms-sort-config') || ''; if (scj) { sortCfg = JSON.parse(scj); } } catch (sce) { sortCfg = null; } var sfs = []; if (sortCfg && sortCfg.length > 0) { sfs = sortCfg; } else { var rfs = (collection.getAttribute('data-cms-sort-fields') || '').split(',').map(function(f) { return f.trim(); }).filter(Boolean); for (var ri = 0; ri < rfs.length; ri++) { sfs.push({ field: rfs[ri], label: rfs[ri], type: 'string' }); } } for (var sfi = 0; sfi < sfs.length; sfi++) { var sfc = sfs[sfi]; var isNum = sfc.type === 'number'; var oa = document.createElement('option'); oa.value = sfc.field + ':asc'; oa.textContent = sfc.label + (isNum ? ' (low to high)' : ' (A–Z)'); ss.appendChild(oa); var od = document.createElement('option'); od.value = sfc.field + ':desc'; od.textContent = sfc.label + (isNum ? ' (high to low)' : ' (Z–A)'); ss.appendChild(od); } if (state.sortField) { ss.value = state.sortField + ':' + state.sortDirection; } ss.addEventListener('change', (function(cs, ce) { return function(e) { var v = e.target.value; if (v) { var p = v.split(':'); cs.sortField = p[0]; cs.sortDirection = p[1] || 'asc'; } else { cs.sortField = ''; cs.sortDirection = 'asc'; } cs.currentPage = 1; applyCollectionState(ce, cs); }; })(state, collection)); sow.appendChild(ss); if (filterContentTarget) { filterContentTarget.appendChild(sow); } else { controlsContainer.appendChild(sow); } } if (hasFlyoutContent && controlsStyle !== 'minimal' && filterContentTarget) { controlsContainer.appendChild(filterContentTarget); } collection.__qcState = state; console.log('[QC Controls] Collection', ci, 'initialized. State:', JSON.stringify({ searchFields: state.searchFields, sortField: state.sortField, sortDirection: state.sortDirection, pageSize: state.pageSize, paginationType: state.paginationType, filterCount: state.visitorFilters.length })); if (hasPagination || state.sortField) { console.log('[QC Controls] Applying initial state (pagination:', hasPagination, ', sortField:', state.sortField, ')'); applyCollectionState(collection, state); } else { console.log('[QC Controls] No initial apply needed (no pagination or sort default)'); } } } function buildFilterControl(fc, cs, ce) { console.log('[QC Filter] Building filter:', fc.field, 'type:', fc.type, 'label:', fc.label); var fw = document.createElement('div'); fw.className = 'qc-collection-filter'; var fh = document.createElement('div'); fh.className = 'qc-collection-filter-header'; var fl = document.createElement('label'); fl.className = 'qc-collection-filter-label'; fl.textContent = fc.label || fc.field; fh.appendChild(fl); var rb = document.createElement('button'); rb.className = 'qc-collection-filter-reset'; rb.type = 'button'; rb.textContent = '×'; rb.setAttribute('aria-label', 'Reset ' + (fc.label || fc.field) + ' filter'); rb.style.display = 'none'; fh.appendChild(rb); fw.appendChild(fh); var ft = fc.type || 'dropdown'; var urv = function() { rb.style.display = cs.activeFilters[fc.field] ? '' : 'none'; }; if (ft === 'dropdown') { var sel = document.createElement('select'); sel.className = 'qc-collection-filter-select'; var ao = document.createElement('option'); ao.value = ''; ao.textContent = 'All'; sel.appendChild(ao); var uv = collectUniqueFieldValues(ce, fc.field); for (var i = 0; i < uv.length; i++) { var o = document.createElement('option'); o.value = uv[i]; o.textContent = uv[i]; sel.appendChild(o); } sel.addEventListener('change', (function(fn, s, e, rv) { return function(ev) { if (ev.target.value) { s.activeFilters[fn] = { type: 'equals', value: ev.target.value }; } else { delete s.activeFilters[fn]; } s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(fc.field, cs, ce, urv)); rb.addEventListener('click', (function(se, fn, s, e, rv) { return function() { se.value = ''; delete s.activeFilters[fn]; s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(sel, fc.field, cs, ce, urv)); fw.appendChild(sel); } else if (ft === 'range') { var nv = []; var ri = ce.querySelectorAll('[data-cms-collection-clone]'); for (var rii = 0; rii < ri.length; rii++) { var rfe = ri[rii].querySelector('[data-cms-field="' + fc.field + '"]'); if (rfe && rfe.textContent) { var pv = parseFloat(rfe.textContent.replace(/[^0-9.-]/gu, '')); if (!isNaN(pv)) { nv.push(pv); } } } var rMin = nv.length > 0 ? Math.min.apply(null, nv) : 0; var rMax = nv.length > 0 ? Math.max.apply(null, nv) : 100; if (rMin === rMax) { rMin = Math.max(0, rMin - 1); rMax = rMax + 1; } var stepVal = fc.step ? String(fc.step) : 'any'; if (fc.step) { rMin = Math.floor(rMin / fc.step) * fc.step; rMax = Math.ceil(rMax / fc.step) * fc.step; } var rw = document.createElement('div'); rw.className = 'qc-collection-filter-range-slider'; var rls = document.createElement('div'); rls.className = 'qc-collection-filter-range-labels'; var mnL = document.createElement('span'); mnL.className = 'qc-collection-filter-range-min-label'; mnL.textContent = rMin.toLocaleString(); var mxL = document.createElement('span'); mxL.className = 'qc-collection-filter-range-max-label'; mxL.textContent = rMax.toLocaleString(); rls.appendChild(mnL); rls.appendChild(mxL); var st = document.createElement('div'); st.className = 'qc-collection-filter-slider-track'; var sf = document.createElement('div'); sf.className = 'qc-collection-filter-slider-fill'; st.appendChild(sf); var sc = document.createElement('div'); sc.className = 'qc-collection-filter-slider-container'; var mnS = document.createElement('input'); mnS.type = 'range'; mnS.className = 'qc-collection-filter-slider qc-collection-filter-slider-min'; mnS.min = String(rMin); mnS.max = String(rMax); mnS.value = String(rMin); mnS.step = stepVal; mnS.setAttribute('aria-label', (fc.label || fc.field) + ' minimum'); var mxS = document.createElement('input'); mxS.type = 'range'; mxS.className = 'qc-collection-filter-slider qc-collection-filter-slider-max'; mxS.min = String(rMin); mxS.max = String(rMax); mxS.value = String(rMax); mxS.step = stepVal; mxS.setAttribute('aria-label', (fc.label || fc.field) + ' maximum'); var usf = function(mnE, mxE, fE, tMn, tMx) { var mnP = ((parseFloat(mnE.value) - tMn) / (tMx - tMn)) * 100; var mxP = ((parseFloat(mxE.value) - tMn) / (tMx - tMn)) * 100; fE.style.left = mnP + '%'; fE.style.width = (mxP - mnP) + '%'; }; var frv = function(v) { var n = parseFloat(v); if (fc.step) { n = Math.round(n / fc.step) * fc.step; } if (n % 1 !== 0) { return n.toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }); } return n.toLocaleString(); }; var hrc = (function(fn, s, e, mnE, mxE, fE, mnLb, mxLb, tMn, tMx, rv) { var da = debounce(function() { applyCollectionState(e, s); }, 150); return function() { var cMn = parseFloat(mnE.value); var cMx = parseFloat(mxE.value); if (cMn > cMx) { mnE.value = String(cMx); cMn = cMx; } if (cMx < cMn) { mxE.value = String(cMn); cMx = cMn; } mnLb.textContent = frv(String(cMn)); mxLb.textContent = frv(String(cMx)); usf(mnE, mxE, fE, tMn, tMx); if (cMn > tMn || cMx < tMx) { s.activeFilters[fn] = { type: 'range', min: cMn, max: cMx }; } else { delete s.activeFilters[fn]; } s.currentPage = 1; da(); rv(); }; })(fc.field, cs, ce, mnS, mxS, sf, mnL, mxL, rMin, rMax, urv); mnS.addEventListener('input', hrc); mxS.addEventListener('input', hrc); rb.addEventListener('click', (function(mnE, mxE, fE, mnLb, mxLb, fn, s, e, tMn, tMx, rv) { return function() { mnE.value = String(tMn); mxE.value = String(tMx); mnLb.textContent = String(tMn); mxLb.textContent = String(tMx); usf(mnE, mxE, fE, tMn, tMx); delete s.activeFilters[fn]; s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(mnS, mxS, sf, mnL, mxL, fc.field, cs, ce, rMin, rMax, urv)); sc.appendChild(st); sc.appendChild(mnS); sc.appendChild(mxS); rw.appendChild(rls); rw.appendChild(sc); fw.appendChild(rw); usf(mnS, mxS, sf, rMin, rMax); } else if (ft === 'checkbox') { var cg = document.createElement('div'); cg.className = 'qc-collection-filter-checkboxes'; var cv = collectUniqueFieldValues(ce, fc.field); for (var ci = 0; ci < cv.length; ci++) { var cl = document.createElement('label'); cl.className = 'qc-collection-filter-checkbox-label'; var cb = document.createElement('input'); cb.type = 'checkbox'; cb.value = cv[ci]; cb.addEventListener('change', (function(fn, s, e, g, rv) { return function() { var ch = g.querySelectorAll('input:checked'); var sv = []; for (var j = 0; j < ch.length; j++) { sv.push(ch[j].value); } if (sv.length > 0) { s.activeFilters[fn] = { type: 'includes', values: sv }; } else { delete s.activeFilters[fn]; } s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(fc.field, cs, ce, cg, urv)); cl.appendChild(cb); cl.appendChild(document.createTextNode(' ' + cv[ci])); cg.appendChild(cl); } rb.addEventListener('click', (function(g, fn, s, e, rv) { return function() { var ac = g.querySelectorAll('input[type="checkbox"]'); for (var u = 0; u < ac.length; u++) { ac[u].checked = false; } delete s.activeFilters[fn]; s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(cg, fc.field, cs, ce, urv)); fw.appendChild(cg); } else if (ft === 'toggle') { var tg = document.createElement('input'); tg.type = 'checkbox'; tg.className = 'qc-collection-filter-toggle'; tg.addEventListener('change', (function(fn, s, e, rv) { return function(ev) { if (ev.target.checked) { s.activeFilters[fn] = { type: 'truthy' }; } else { delete s.activeFilters[fn]; } s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(fc.field, cs, ce, urv)); rb.addEventListener('click', (function(te, fn, s, e, rv) { return function() { te.checked = false; delete s.activeFilters[fn]; s.currentPage = 1; applyCollectionState(e, s); rv(); }; })(tg, fc.field, cs, ce, urv)); fw.appendChild(tg); } return fw; } function collectUniqueFieldValues(ce, fn) { var vals = []; var seen = {}; var items = ce.querySelectorAll('[data-cms-collection-clone]'); console.log('[QC Filter] collectUniqueFieldValues for field:', fn, 'scanning', items.length, 'clones'); for (var i = 0; i < items.length; i++) { var el = items[i].querySelector('[data-cms-field="' + fn + '"]'); if (el && el.textContent) { var tv = el.textContent.trim(); if (tv && !seen[tv]) { seen[tv] = true; vals.push(tv); } } } console.log('[QC Filter] Found', vals.length, 'unique values for', fn, ':', vals.slice(0, 5).join(', '), vals.length > 5 ? '...' : ''); vals.sort(); return vals; } function applyCollectionState(ce, cs) { var allItems = ce.querySelectorAll('[data-cms-collection-clone]'); console.log('[QC Apply] Running with', allItems.length, 'items. Search:', cs.searchQuery || '(none)', 'Sort:', cs.sortField || '(none)', cs.sortDirection, 'Filters:', Object.keys(cs.activeFilters).length); var visibleItems = []; for (var i = 0; i < allItems.length; i++) { var item = allItems[i]; var isVis = true; if (cs.searchQuery && cs.searchFields.length > 0) { var searchTerms = expandSearchWithSynonyms(cs.searchQuery, cs.searchSynonyms || {}); var ms = false; for (var si = 0; si < cs.searchFields.length; si++) { var se = item.querySelector('[data-cms-field="' + cs.searchFields[si] + '"]'); if (!se || !se.textContent) { continue; } var ft = se.textContent.toLowerCase(); for (var ti = 0; ti < searchTerms.length; ti++) { if (ft.indexOf(searchTerms[ti]) !== -1) { ms = true; break; } if (cs.searchFuzzy && fuzzyMatch(searchTerms[ti], ft)) { ms = true; break; } } if (ms) { break; } } if (!ms) { isVis = false; } } if (isVis) { var fns = Object.keys(cs.activeFilters); for (var fi = 0; fi < fns.length; fi++) { var af = cs.activeFilters[fns[fi]]; var ffe = item.querySelector('[data-cms-field="' + fns[fi] + '"]'); var ffv = ffe ? ffe.textContent.trim() : ''; if (af.type === 'equals' && ffv !== af.value) { isVis = false; } else if (af.type === 'range') { var nv = parseFloat(ffv.replace(/[^0-9.-]/gu, '')); if (!isNaN(nv)) { if (af.min !== null && nv < af.min) { isVis = false; } if (af.max !== null && nv > af.max) { isVis = false; } } } else if (af.type === 'includes' && af.values.indexOf(ffv) === -1) { isVis = false; } else if (af.type === 'truthy' && (!ffv || ffv === '0' || ffv.toLowerCase() === 'false' || ffv.toLowerCase() === 'no')) { isVis = false; } if (!isVis) { break; } } } if (isVis) { visibleItems.push(item); } item.style.display = isVis ? '' : 'none'; } if (cs.sortField && visibleItems.length > 1) { var sorted = visibleItems.slice().sort(function(a, b) { var ea = a.querySelector('[data-cms-field="' + cs.sortField + '"]'); var eb = b.querySelector('[data-cms-field="' + cs.sortField + '"]'); var va = ea ? ea.textContent.trim() : ''; var vb = eb ? eb.textContent.trim() : ''; var na = parseFloat(va.replace(/[^0-9.-]/gu, '')); var nb = parseFloat(vb.replace(/[^0-9.-]/gu, '')); var cmp = (!isNaN(na) && !isNaN(nb)) ? na - nb : va.localeCompare(vb); return cs.sortDirection === 'desc' ? -cmp : cmp; }); var par = visibleItems[0].parentNode; console.log('[QC Apply] Sort: parent tag:', par ? par.tagName : 'null', 'parent id:', par ? par.id : 'none'); console.log('[QC Apply] Sort: first 3 values:', sorted.slice(0, 3).map(function(item) { var fieldEl = item.querySelector('[data-cms-field="' + cs.sortField + '"]'); return fieldEl ? fieldEl.textContent.trim() : '(no field)'; })); for (var si = 0; si < sorted.length; si++) { par.appendChild(sorted[si]); } console.log('[QC Apply] Sorted', sorted.length, 'items by', cs.sortField, cs.sortDirection); } if (ce.getAttribute('data-cms-pagination') === 'true' && visibleItems.length > 0) { var ps = cs.pageSize; var tv = cs.currentPage * ps; for (var pi = 0; pi < visibleItems.length; pi++) { visibleItems[pi].style.display = pi < tv ? '' : 'none'; } var ep = ce.parentNode.querySelector('.qc-collection-pagination'); if (ep) { ep.remove(); } if (visibleItems.length > tv || cs.currentPage > 1) { var pc = document.createElement('div'); pc.className = 'qc-collection-pagination'; if (cs.paginationType === 'load-more' && visibleItems.length > tv) { var lb = document.createElement('button'); lb.className = 'qc-collection-load-more'; lb.textContent = 'Load More'; lb.addEventListener('click', (function(s, e) { return function() { s.currentPage++; applyCollectionState(e, s); }; })(cs, ce)); pc.appendChild(lb); } else if (cs.paginationType === 'numbered') { var tp = Math.ceil(visibleItems.length / ps); for (var pn = 1; pn <= tp; pn++) { var pb = document.createElement('button'); pb.className = 'qc-collection-page-button' + (pn === cs.currentPage ? ' qc-active' : ''); pb.textContent = String(pn); pb.addEventListener('click', (function(s, e, t) { return function() { s.currentPage = t; applyCollectionState(e, s); }; })(cs, ce, pn)); pc.appendChild(pb); } } ce.parentNode.insertBefore(pc, ce.nextSibling); } } var cc = ce.previousElementSibling; if (cc && cc.classList.contains('qc-collection-controls')) { var rc = cc.querySelector('.qc-collection-result-count'); if (!rc) { rc = document.createElement('div'); rc.className = 'qc-collection-result-count'; cc.appendChild(rc); } rc.textContent = visibleItems.length + ' result' + (visibleItems.length !== 1 ? 's' : ''); } } function reinitCollectionControls() { console.log('[QC Controls] Reinitializing - cleaning up existing controls'); var existingControls = document.querySelectorAll('.qc-collection-controls'); for (var removeIndex = 0; removeIndex < existingControls.length; removeIndex++) { existingControls[removeIndex].remove(); } var existingPagination = document.querySelectorAll('.qc-collection-pagination'); for (var removePagIndex = 0; removePagIndex < existingPagination.length; removePagIndex++) { existingPagination[removePagIndex].remove(); } var initFlags = document.querySelectorAll('[data-qc-controls-init="true"]'); for (var clearIndex = 0; clearIndex < initFlags.length; clearIndex++) { initFlags[clearIndex].removeAttribute('data-qc-controls-init'); } initCollectionControls(); } // Expose reinit so the CMS hydration script can re-trigger after clones are created window.__qcInitCollectionControls = reinitCollectionControls; window.__qcInitCategoryFilters = initCategoryFilters; console.log('[QC Script] Interactivity script loaded. readyState:', document.readyState); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { console.log('[QC Script] DOMContentLoaded fired - initializing'); initialize(); initCollectionControls(); initCategoryFilters(); console.log('[QC Script] Initialization complete'); }); } else { console.log('[QC Script] DOM already ready - initializing immediately'); initialize(); initCollectionControls(); initCategoryFilters(); console.log('[QC Script] Initialization complete'); } })(); </script> </body> </html>