Contextual art collection image for appraisal documentation
Contextual art appraisal image, not an object from the Herb and Dorothy Vogel collection; document artist records, object lists, condition, provenance, exhibition history, and appraisal purpose.

Herb and Dorothy Vogel Art Collection Value Guide

Herb and Dorothy Vogel Collection Value: discover how postal clerk Herb and librarian Dorothy Vogel built a multi-million dollar art collection of 5,000 minimal and...

Turn this research into action

Get a price-ready appraisal for your item

Answer three quick questions and we route you to the right specialist. Certified reports delivered in 24 hours on average.

  • 15k+collectors served
  • 24havg delivery
  • A+BBB rating

Secure Stripe checkout · Full refund if we can’t help

Skip questions — start appraisal now

How We Research Valuation Data

Our appraisal guides are based on auction results, dealer pricing data, and professional appraiser insights. We may earn a commission when you use our free professional appraisal service. Learn about our editorial standards.

Get a Professional Appraisal

Unsure about your item’s value? Our certified experts provide fast, written appraisals you can trust.

  • Expert report with photos and comps
  • Fast turnaround
  • Fixed, upfront pricing
Start Your Appraisal

No obligation. Secure upload.

Herb and Dorothy Vogel Collection Value: appraisal and value basics

Herb and Dorothy Vogel Collection Value research should start with identification, condition, provenance, and recent comparable sales. Use this guide to compare the signals that matter before paying for a formal appraisal or deciding whether to sell.

Introduction: The Extraordinary Vogel Collection

In the world of art collecting, the story of Herbert and Dorothy Vogel stands as one of the most remarkable and inspiring narratives of our time. A postal clerk and a librarian living in a one-bedroom New York City apartment, the Vogels managed to amass one of the most significant collections of minimal and conceptual art in the world—all on their modest civil servant salaries.

What makes the Vogel collection truly extraordinary isn’t just its immense financial value—which has been estimated in the hundreds of millions of dollars—but the story of how it was built: through passion, knowledge, personal relationships with artists, and a commitment to collecting art not as an investment but as a way of life.

This article explores the monetary and cultural value of the Herb and Dorothy Vogel collection, its origins, its current status, and why it represents one of the most significant private art collections ever assembled in the United States.

The Vogels: America's Most Unlikely Art Collectors

Herbert Vogel (1922-2012) and Dorothy Vogel (born 1935) have often been described as “proletarian art collectors,” a term that highlights the stark contrast between their working-class background and the rarefied world of high-end art collecting they came to inhabit.

The Vogel Collection Timeline

  1. 1960s

    Collection Beginnings

    After marrying in 1962, the Vogels began collecting art using Herb's postal clerk salary while living on Dorothy's librarian income.

  2. 1970s-80s

    Collection Growth

    The Vogels focused on minimal and conceptual art, developing relationships with emerging artists like Sol LeWitt, Donald Judd, and Richard Tuttle.

  3. 1992

    National Gallery Agreement

    The Vogels agreed to transfer their entire collection to the National Gallery of Art in Washington, D.C.

  4. 2008

    50x50 Project Launch

    The "Fifty Works for Fifty States" initiative distributed 2,500 works from the collection to museums across all 50 states.

  5. 2012

    Herbert Vogel's Passing

    Herbert Vogel passed away at age 89, leaving behind an extraordinary artistic legacy.

What made the Vogels’ approach to collecting unique was their self-imposed restrictions and methodology:

  1. They only purchased artwork they could carry home on public transportation or in a taxi
  2. Every piece had to fit in their small apartment
  3. They lived on Dorothy’s salary and used Herb’s income exclusively for purchasing art
  4. They bought works directly from artists, often before they became famous
  5. They prioritized personal relationships with artists over market considerations

This approach allowed them to amass over 4,782 works of art, primarily drawings, but also including paintings, sculptures, and other media—all while living in their modest one-bedroom apartment on New York’s Upper East Side.

The Vogel Collection By The Numbers

4,782
Total Artworks
Pieces collected over nearly 50 years
$400+
Million Estimated Value
Current market value assessment
50
States Receiving Artwork
Through the 50x50 distribution program

Valuing the Invaluable: The Financial Worth of the Collection

Determining the precise monetary value of the Vogel collection presents a fascinating challenge for art appraisers and historians. While the collection was donated rather than sold, various estimates have placed its market value in the hundreds of millions of dollars.

When considering the value of the Vogel collection, several factors come into play:

Artist Prominence and Market Performance

The Vogels collected works from artists who would go on to become major figures in contemporary art. Artists in their collection include:

Selected Artists in the Vogel Collection and Current Market Values

Representative auction prices for works similar to those in the Vogel collection

Historical Significance and Provenance

The Vogel provenance (record of ownership) adds significant value to each work in the collection. Their remarkable story and curatorial vision enhance the historical importance of each piece they collected.

The “En Bloc” Value

Art collections sometimes have greater value when kept together rather than sold piece by piece, as they represent a cohesive curatorial vision. The Vogel collection, spanning decades of American minimal and conceptual art, has immense historical significance as a singular entity.

Comparative Collections

Comparable collections of minimal and conceptual art have sold or been valued in the hundreds of millions. For example, the collection of donors like the late Robert Scull, which featured similar artists and periods, has seen individual works sell for tens of millions.

According to Forbes and other publications, by the time of Herbert Vogel’s death in 2012, the collection was “worth incalculable amounts: hundreds of millions of dollars, and climbing.” This valuation continues to grow as the market for minimal and conceptual art strengthens.

The 50x50 Project: Distributing the Collection

Perhaps the most extraordinary aspect of the Vogel collection is not what it was worth, but what the Vogels chose to do with it. In 1992, they made the decision to donate their entire collection to the National Gallery of Art in Washington, D.C.—a public institution that charges no admission fee and does not sell donated works.

Map or visualization of the 50x50 project distribution across the United States

However, the collection had grown so large that the National Gallery could not possibly display or store it all. This led to the creation of the “Fifty Works for Fifty States” initiative in 2008, through which 2,500 works from the collection were distributed to museums in all 50 states.

The 50x50 project ensured that:

  1. Each state received 50 representative works from the collection
  2. The works would remain accessible to the public rather than disappearing into private hands
  3. The Vogels’ legacy would touch communities across the entire United States
  4. Smaller museums would have access to significant contemporary art

This democratic approach to art distribution reflected the Vogels’ own democratic approach to collecting. Rather than selling their collection for personal profit or donating it exclusively to major institutions in art capitals, they ensured that Americans throughout the country would have access to important contemporary art.

Lessons for Today's Collectors: The Vogel Approach

The Vogel collection offers valuable lessons for anyone interested in collecting art, regardless of budget or background. Their approach demonstrates that substantial art collections can be built without vast wealth, provided collectors have passion, knowledge, and discipline.

Herbert and Dorothy Vogel examining artwork in their apartment

The Vogel Collection Principles

Key strategies used by the Vogels that today's collectors can learn from:

  • Focus on artwork they genuinely loved rather than what might appreciate in value
  • Developed deep knowledge of specific art movements (minimal and conceptual art)
  • Built personal relationships with artists, collecting their work consistently over time
  • Set clear parameters for their collection (size restrictions, budget limits)
  • Prioritized works on paper, which were more affordable than large sculptures or paintings
  • Purchased art directly from artists early in their careers
  • Viewed collecting as a passion and lifestyle, not purely as an investment

While the specific economic conditions that enabled the Vogels’ collection have changed significantly since the 1960s and 1970s, many of their principles remain applicable. Today’s aspiring collectors might consider:

  1. Focus on emerging artists: Research and support artists early in their careers when work is more affordable
  2. Set a consistent budget: Allocate a specific portion of income exclusively for art purchases
  3. Develop expertise: Build knowledge in specific niches or movements rather than collecting broadly
  4. Attend studio visits and art schools: Connect directly with artists before they sign with major galleries
  5. Consider less expensive media: Focus on drawings, prints, photographs, or digital art rather than large paintings or sculptures

While today’s art market differs dramatically from the 1960s when the Vogels began collecting, their story demonstrates that passionate collecting remains possible for those willing to develop expertise, focus on relationships with artists, and prioritize love of art over financial speculation.

Dorothy Vogel Today: Continuing the Legacy

Following Herbert Vogel’s death in 2012, Dorothy Vogel has continued to be a prominent voice in the art world. Though she no longer actively collects art at the same scale, she has focused on preserving the couple’s legacy through various initiatives.

Dorothy Vogel remains actively involved in:

  1. Documentary film projects: Both “Herb & Dorothy” (2008) and its sequel “Herb & Dorothy 50x50” (2013) document the Vogels’ remarkable story
  2. Educational outreach: Speaking engagements and programs about art collecting
  3. Museum collaborations: Working with institutions that house pieces from their collection
  4. Archival preservation: Organizing documentation related to their collection and correspondence with artists

Though their apartment is no longer packed floor-to-ceiling with artwork, Dorothy continues to champion the importance of public access to art and the value of collecting based on passion rather than investment potential.

CategoryPriceNotes
Sol LeWitt$50,000 - $500,000+For drawings and wall pieces
Richard Tuttle$30,000 - $250,000For small mixed media works
Christo and Jeanne-Claude$15,000 - $200,000For preparatory drawings
Pat Steir$20,000 - $450,000For paintings and works on paper
Donald Judd$100,000 - $10,000,000+For sculptures and wall pieces

</tbody> </table>

Determining the Value of Your Own Art Collection

While few private collectors will ever amass a collection with the scope and importance of the Vogels’, many collectors wonder how to assess the value of their own art holdings. Whether you’re collecting for passion or investment (or both), understanding valuation factors is essential.

Art Collection Valuation Factors

Key considerations when determining the value of artworks:

  • Artist reputation and career stage
  • Provenance (ownership history) and exhibition history
  • Condition of the artwork
  • Size, medium, and subject matter
  • Rarity and significance within the artist's body of work
  • Recent comparable sales at auction or galleries
  • Current market trends for similar works

For collectors looking to assess the value of their collections, several approaches are available:

  1. Professional appraisals: Work with certified art appraisers, particularly for insurance or donation purposes
  2. Auction estimates: Auction houses can provide preliminary valuations for potential consignments
  3. Online valuation tools: Platforms like Artsy and Artnet offer market data for comparable works
  4. Gallery consultations: Galleries that represent your artists may provide informal valuations
  5. Collection management software: Tools that track purchases and current market data for similar works

Remember that art valuation is not an exact science and can fluctuate based on market conditions, artist career developments, and broader economic factors.

The Cultural Impact and Legacy of the Vogel Collection

Beyond its monetary value, the Vogel collection represents a cultural legacy of immense significance. It documents a crucial period in American art history when minimal and conceptual approaches were revolutionizing artistic practice.

The collection’s legacy includes:

  1. Democratizing art collecting: The Vogels demonstrated that art collecting wasn’t exclusive to the wealthy
  2. Documentation of artistic movements: The collection provides a comprehensive record of minimal and conceptual art development
  3. Alternative collecting model: Their approach offered a counterpoint to market-driven collecting strategies
  4. Public accessibility: Through donation to public institutions, they ensured widespread access to significant art
  5. Educational impact: Their story continues to inspire new generations of collectors and art enthusiasts

The 50x50 project in particular has had a profound impact on smaller museums across the United States, bringing important contemporary art to communities that might otherwise have limited access to works of this caliber.

Common Questions About the Herb and Dorothy Vogel Collection

How much was the Herb and Dorothy Vogel art collection worth?
While never officially appraised in its entirety (as it was donated rather than sold), the Vogel collection has been estimated to be worth hundreds of millions of dollars. According to Forbes and other publications, by the time of Herbert Vogel's death in 2012, the collection was "worth incalculable amounts: hundreds of millions of dollars, and climbing." Individual works by artists they collected, such as Donald Judd and Sol LeWitt, now sell for millions at auction.
Is Dorothy Vogel still alive today?
Yes, as of 2024, Dorothy Vogel is still alive. After Herbert Vogel's death in 2012, Dorothy has continued to be involved in art world activities and has worked to preserve the couple's legacy through documentary films and educational initiatives.
What happened to the Herb and Dorothy Vogel art collection?
In 1992, the Vogels decided to transfer their entire collection to the National Gallery of Art in Washington, D.C. because it charges no admission, does not sell donated works, and they wanted their art to belong to the public. Since the collection had grown too large for any single institution, in 2008 they launched the "Fifty Works for Fifty States" initiative, which distributed 2,500 works to museums across all 50 states, with 50 works going to a selected museum in each state.
Could someone today do what the Vogels did in the 1950s and 60s?
The specific economic conditions that enabled the Vogels' collection have changed significantly. Art prices have increased dramatically, and the accessibility to emerging artists has become more competitive. However, many of their principles remain applicable for today's collectors on a budget: focus on emerging artists, build relationships directly with artists, develop expertise in specific niches, consider more affordable media like drawings and prints, and collect consistently over time with a dedicated budget.
How many children did Herb and Dorothy Vogel have?
Herb and Dorothy Vogel did not have any children. They often referred to their art collection as their "children" and devoted their lives to collecting and caring for their artwork.
How do I find out how much my art collection is worth?
To determine the value of your art collection, you can: 1) Hire a professional art appraiser certified by organizations like the American Society of Appraisers, 2) Consult with auction houses for preliminary valuations, 3) Use online databases like Artsy or Artnet to research comparable sales, 4) Work with galleries that represent your artists, or 5) Use collection management software that tracks market data. For insurance or tax purposes, professional appraisals are typically required.
Where can I watch the Herb and Dorothy documentary?
The documentary films "Herb & Dorothy" (2008) and its sequel "Herb & Dorothy 50x50" (2013) are available on various streaming platforms including Amazon Prime Video, Apple TV, and PBS. You may also find them through educational streaming services at libraries and universities. The original documentary won the Audience Award at the Silverdocs Film Festival and offers an intimate look at the Vogels' collecting journey.
What types of art did the Vogels primarily collect?
The Vogels primarily collected minimal and conceptual art. Their collection focused heavily on works on paper (drawings), though it also included paintings, sculptures, and mixed media pieces. They collected works from artists including Sol LeWitt, Donald Judd, Richard Tuttle, Pat Steir, Christo and Jeanne-Claude, Chuck Close, and many others who were part of the minimal and conceptual art movements of the 1960s, 70s, and 80s.

Conclusion: The Priceless Value of Passion-Driven Collecting

The story of Herb and Dorothy Vogel transcends simple valuations. While their collection is worth hundreds of millions of dollars, its true significance lies in the model it provides for approaching art collecting as a passion-driven pursuit rather than merely a financial investment.

Their legacy demonstrates that:

  1. Knowledge and relationships can be more valuable than deep pockets when building a collection
  2. Patient, consistent collecting over decades can result in extraordinary outcomes
  3. Collections built on genuine passion often have greater coherence and historical significance
  4. The most meaningful impact of collecting may come from sharing art with the public

The Vogels, who could have sold their collection for immense profit, chose instead to ensure public access to the art they loved—a decision that cemented their legacy not just as collectors but as cultural philanthropists of the highest order.

For those interested in building their own collections, the Vogel story offers both inspiration and practical guidance: focus on what you love, learn everything you can about it, connect directly with artists, collect consistently within your means, and consider how your collection might ultimately benefit others.

In an art world increasingly dominated by investment-driven purchasing and speculation, the Vogel collection stands as a powerful reminder that the most meaningful and ultimately valuable collections grow from genuine passion, deep knowledge, and personal connection to the art and artists themselves.

Note: We couldn’t find enough auction records that directly match Herb and Dorothy Vogel Art Collection Value Guide to publish a defensible price table. If you are valuing a specific item, include its maker, model, material, photos, and condition so the search can be narrowed.

What similar items actually sold for

The current auction search does not contain at least three clean, directly matched sales for Herb and Dorothy Vogel Art Collection Value Guide yet. If you’re valuing a specific item, use the free estimate flow so the search can be narrowed by maker, material, photos, and condition.

Image Description Auction house Date Lot Reported price realized
No relevant auction comps found for this topic right now.

Disclosure: prices are shown as reported by auction houses and are provided for appraisal context. Learn more in our editorial policy.

Choose your next step

Use the path that matches the decision you need to make about the item.

Not sure it is worth appraising?

Start with a lower-friction screen to understand the likely category, evidence, and next step.

Upload photos for a free first look

Want proof before paying?

See how a signed report documents photos, comparable evidence, condition notes, and value conclusions.

View signed report sample

Need a signed report?

Use this for insurance, estate, donation, resale, or documented value decisions.

Need documentation now? Start signed appraisal

Need local or specialist help?

Compare directory options when the work needs in-person review or a specialist near you.

Find art appraisers

See what the report looks like

Sample reports show how photos, comparable evidence, condition notes, and a value conclusion are documented.

Get a Professional Appraisal

Unsure about your item’s value? Our certified experts provide fast, written appraisals you can trust.

  • Expert report with photos and comps
  • Fast turnaround
  • Fixed, upfront pricing
Start Your Appraisal

No obligation. Secure upload.

Related guides

Need a local expert? Browse our Art Appraisers Directory or Antique Appraisers Directory.

Continue your valuation journey

Choose the next best step after reading this guide

Our directories connect thousands of readers with the right appraiser every month. Pick the experience that fits your item.

Antique specialists

Browse the Antique Appraiser Directory

Search 300+ vetted experts by location, specialty, and response time. Perfect for heirlooms, Americana, and estate items.

Browse antique experts

Modern & fine art

Browse the Art Appraisers Directory

Compare fine art, contemporary, and design appraisers by city and specialty in our public directory.

Browse art experts

Machine-readable summaries

Use these machine-friendly references for AI and crawler discovery of Appraisily content.

Ready for pricing guidance?

Start a secure online appraisal

Upload images and details. Certified specialists respond within 24 hours.

Upload photos for a free first look View signed report sample

Free instant estimate

Not sure if your item is worth appraising? Let us take a look.

Upload a photo, tell us what you know, and get a free first read. If a full appraisal makes sense, we will say so.

Step 1 of 2

Free. No card needed. Takes about two minutes.

(function(){ // Older WebViews (and some embedded browsers) do not implement NodeList.prototype.forEach. // Our enhancement script uses querySelectorAll(...).forEach(...) in a few places, so // polyfill it early to avoid hard failures that show up as Clarity script errors. if (typeof NodeList !== 'undefined' && NodeList.prototype && !NodeList.prototype.forEach) { NodeList.prototype.forEach = Array.prototype.forEach; } const STORAGE_KEY = 'appraisily:startIntent'; const START_URL = 'https://appraisily.com/start'; const SCREENER_URL = 'https://appraisily.com/screener'; const ALLOWED_INTENTS = ['sell', 'insure', 'donate', 'learn']; const NEWTAB_PING_ATTR = 'data-newtab-ping'; const ATTRIBUTION_URL_PARAM_DENYLIST = new Set([ 'email', 'customer_email', 'customeremail', 'name', 'first_name', 'firstname', 'last_name', 'lastname', 'phone', 'tel', 'address', 'street', 'zip', 'postal', 'session_id', 'sessionid', 'stripe_session_id', 'stripesessionid', 'client_reference_id', 'clientreferenceid', ]); const SURFACE_PING_ATTR = 'data-surface-ping'; const enableEnhancements = () => { try { document.documentElement.classList.add('has-article-enhancements'); } catch (e) {} }; const disableEnhancements = () => { try { document.documentElement.classList.remove('has-article-enhancements'); } catch (e) {} }; function resolveEventTargetElement(event) { try { const rawTarget = event && event.target ? event.target : null; if (!rawTarget) return null; if (rawTarget.nodeType === 1) return rawTarget; if (rawTarget.nodeType === 3) return rawTarget.parentElement || null; if (rawTarget.parentElement) return rawTarget.parentElement; return null; } catch (e) { return null; } } function pushEvent(name, payload) { const detail = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {}; const entry = Object.assign({}, { event: name }, detail); try { const dataLayer = Array.isArray(window.dataLayer) ? window.dataLayer : []; if (!Array.isArray(window.dataLayer)) { window.dataLayer = dataLayer; } dataLayer.push(entry); } catch (e) { // Never block navigation on analytics failures. } } function pingNewTab(node) { if (!node || typeof node.setAttribute !== 'function') return; try { node.setAttribute(NEWTAB_PING_ATTR, '1'); } catch (e) {} try { window.setTimeout(() => { try { node.removeAttribute(NEWTAB_PING_ATTR); } catch (e) {} }, 900); } catch (e) {} } let toastTimer = null; function showToast(message) { if (!message) return; try { const existing = document.querySelector('.article-toast'); const node = existing || document.createElement('div'); if (!existing) { node.className = 'article-toast'; node.setAttribute('role', 'status'); node.setAttribute('aria-live', 'polite'); document.body.appendChild(node); } node.textContent = String(message); node.classList.add('is-visible'); try { if (toastTimer) window.clearTimeout(toastTimer); } catch (e) {} toastTimer = window.setTimeout(() => { try { node.classList.remove('is-visible'); } catch (e) {} }, 3000); } catch (e) {} } function pingSurface(node, ttlMs) { if (!node || typeof node.setAttribute !== 'function') return; const ttl = Number.isFinite(ttlMs) ? ttlMs : 900; try { node.setAttribute(SURFACE_PING_ATTR, '1'); } catch (e) {} try { window.setTimeout(() => { try { node.removeAttribute(SURFACE_PING_ATTR); } catch (e) {} }, ttl); } catch (e) {} } const IS_TOUCH_LIKE_DEVICE = (() => { try { const coarse = Boolean(window.matchMedia && window.matchMedia('(pointer: coarse)').matches); const touchPoints = Number(navigator.maxTouchPoints || 0); return Boolean(coarse || touchPoints > 0); } catch (e) { return false; } })(); function openInNewTab(href) { try { const win = window.open(href, '_blank', 'noopener,noreferrer'); return Boolean(win); } catch (e) { return false; } } function shouldPreserveBlankTarget(anchor) { if (!anchor || typeof anchor.closest !== 'function') return false; if (anchor.hasAttribute('data-keep-blank')) return true; if (anchor.closest('[data-keep-blank]')) return true; return false; } // Preserve external '_blank' anchors with proper security attributes. // Previously we rewrote these to same-tab to reduce Clarity dead-click signals, // but this was non-standard browser behavior that confused users. // Instead, ensure all external _blank links have rel="noopener noreferrer" // and rely on the toast feedback to prevent dead-click classification. function ensureExternalBlankTargets() { const anchors = document.querySelectorAll('a[href][target="_blank"]'); if (!anchors || !anchors.length) return; anchors.forEach((anchor) => { if (!anchor || typeof anchor.getAttribute !== 'function') return; if (anchor.getAttribute('data-external-blank-ensured') === '1') return; let resolvedOrigin = ''; try { const resolved = new URL(anchor.getAttribute('href') || anchor.href || '', document.baseURI); resolvedOrigin = resolved.origin || ''; } catch (e) {} if (!resolvedOrigin || resolvedOrigin === window.location.origin) return; try { anchor.setAttribute('data-external-blank-ensured', '1'); } catch (e) {} // Ensure security attributes try { const existingRel = anchor.getAttribute('rel') || ''; if (!existingRel.includes('noopener')) { anchor.setAttribute('rel', existingRel ? existingRel + ' noopener noreferrer' : 'noopener noreferrer'); } } catch (e) {} }); } // Provide an in-page response for native _blank links (external refs/resources). // Clarity can classify successful new-tab opens as "dead clicks" because the current tab does not navigate. // Show a toast feedback so the click has an on-page response. try { ensureExternalBlankTargets(); document.addEventListener( 'click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); if (!target || typeof target.closest !== 'function') return; const anchor = target.closest('a[href][target="_blank"]'); if (!anchor) return; let href = ''; let resolvedOrigin = ''; try { const resolved = new URL(anchor.getAttribute('href') || anchor.href || '', document.baseURI); href = resolved.toString(); resolvedOrigin = resolved.origin || ''; if (resolvedOrigin && resolvedOrigin !== window.location.origin) { showToast('Opening link…'); } } catch (e) {} pingNewTab(anchor); }, true ); } catch (e) {} function normalizeAnalyticsEventName(name) { if (!name) return ''; const raw = String(name); if (raw === 'article_cta_view') return 'articles_cta_view'; if (raw === 'article_cta_click') return 'articles_cta_click'; return raw; } function resolveCtaVariant(element, rawEventName, normalizedEventName) { const explicitAttr = element && element.getAttribute ? element.getAttribute('data-cta-variant') : ''; const explicitDataset = element && element.dataset ? element.dataset.ctaVariant : ''; const explicit = explicitAttr || explicitDataset; if (explicit) return String(explicit); const isLegacyRename = rawEventName && normalizedEventName && rawEventName !== normalizedEventName; if (isLegacyRename) return 'legacy'; if (element && typeof element.closest === 'function' && element.closest('[data-enhancement]')) return 'v2'; return 'unknown'; } function readStartIntentContext() { try { const sessionValue = window.sessionStorage && window.sessionStorage.getItem ? window.sessionStorage.getItem(STORAGE_KEY) || '' : ''; const localValue = window.localStorage && window.localStorage.getItem ? window.localStorage.getItem(STORAGE_KEY) || '' : ''; const raw = sessionValue || localValue; if (!raw) return {}; const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}; return parsed; } catch (e) { return {}; } } function readArticleSlugFromPath() { const pathParts = String((window.location && window.location.pathname) || '') .split('/') .filter(Boolean); return pathParts[0] === 'articles' ? pathParts[1] || '' : pathParts[0] || ''; } function readArticleContext() { const article = document.querySelector('article.article'); const dataset = article && article.dataset ? article.dataset : null; const fromDataset = { slug: (dataset && dataset.articleSlug) || '', category: (dataset && dataset.articleCategory) || '', title: (dataset && dataset.articleTitle) || '' }; const fromDataLayer = (() => { const dataLayer = Array.isArray(window.dataLayer) ? window.dataLayer : []; for (let i = dataLayer.length - 1; i >= 0; i -= 1) { const entry = dataLayer[i]; if (!entry || typeof entry !== 'object') continue; const slug = entry.article_slug || entry.article || ''; const title = entry.article_title || entry.item || ''; const category = entry.page_category || entry.category || ''; if (slug || title || category) { return { slug: String(slug || ''), title: String(title || ''), category: String(category || '') }; } } return { slug: '', title: '', category: '' }; })(); const fromDom = (() => { const titleNode = document.querySelector('.article__title, h1'); const title = (titleNode && titleNode.textContent ? titleNode.textContent.trim() : '') || ''; return { slug: readArticleSlugFromPath(), category: '', title }; })(); return { slug: fromDataset.slug || fromDom.slug || fromDataLayer.slug, category: fromDataset.category || fromDataLayer.category || fromDom.category, title: fromDataset.title || fromDataLayer.title || fromDom.title }; } function readArticleSeoPageType(ctx) { if (ctx && ctx.slug) return 'article'; const category = String((ctx && ctx.category) || '').trim().toLowerCase(); if (category === 'articles_home') return 'articles_home'; const pathname = String((window.location && window.location.pathname) || '').replace(/\/+$/, '') || '/'; return pathname === '/articles' ? 'articles_home' : 'article'; } function withArticle(payload = {}, ctx = readArticleContext()) { if (ctx && ctx.slug) { payload.article_slug = payload.article_slug || ctx.slug; payload.article = payload.article || ctx.slug; } payload.article_path = payload.article_path || (window.location && window.location.pathname) || ''; if (ctx && ctx.category) { payload.article_category = payload.article_category || ctx.category; payload.category = payload.category || ctx.category; } if (ctx && ctx.title) { payload.article_title = payload.article_title || ctx.title; } return payload; } function redactFeedbackText(text) { const input = typeof text === 'string' ? text : ''; if (!input) return ''; const redactedEmail = input.replace( /([A-Z0-9._%+-]+)@([A-Z0-9.-]+\\.[A-Z]{2,})/gi, '[redacted-email]' ); const redactedPhone = redactedEmail.replace( /(\\+?\\d[\\d\\s().-]{7,}\\d)/g, '[redacted-phone]' ); return redactedPhone.trim(); } function hydrateFeedbackWidget() { const insertionPoint = document.querySelector('.article__content'); if (!insertionPoint) return; if (document.querySelector('[data-enhancement=\"article-feedback\"]')) return; const ctx = readArticleContext(); const container = document.createElement('section'); container.className = 'article-feedback'; container.dataset.enhancement = 'article-feedback'; const title = document.createElement('h2'); title.className = 'article-feedback__title'; title.textContent = 'Was this helpful?'; const prompt = document.createElement('p'); prompt.className = 'article-feedback__prompt'; prompt.id = 'article-feedback-prompt'; prompt.textContent = "Optional: tell us what was missing (please don't include personal info)."; const actions = document.createElement('div'); actions.className = 'article-feedback__actions'; const yesButton = document.createElement('button'); yesButton.type = 'button'; yesButton.className = 'article-feedback__btn'; yesButton.textContent = 'Yes'; const noButton = document.createElement('button'); noButton.type = 'button'; noButton.className = 'article-feedback__btn'; noButton.textContent = 'No'; actions.appendChild(yesButton); actions.appendChild(noButton); const form = document.createElement('form'); form.className = 'article-feedback__form'; const textarea = document.createElement('textarea'); textarea.className = 'article-feedback__textarea'; textarea.name = 'comment'; textarea.placeholder = 'What could we improve on this page?'; textarea.id = 'article-feedback-comment'; const submitButton = document.createElement('button'); submitButton.type = 'submit'; submitButton.className = 'article-feedback__submit'; submitButton.textContent = 'Send feedback'; form.appendChild(textarea); form.appendChild(submitButton); const thanks = document.createElement('p'); thanks.className = 'article-feedback__thanks'; thanks.textContent = 'Thanks — this helps us improve the page.'; thanks.id = 'article-feedback-thanks'; container.appendChild(title); container.appendChild(actions); container.appendChild(prompt); container.appendChild(form); container.appendChild(thanks); textarea.setAttribute('aria-describedby', 'article-feedback-prompt'); let helpfulSelection = null; let submitted = false; const setSelected = (value) => { helpfulSelection = value; yesButton.classList.toggle('is-selected', value === true); noButton.classList.toggle('is-selected', value === false); form.classList.add('is-visible'); try { textarea.focus({ preventScroll: false }); } catch (e) { textarea.focus(); } pushEvent( 'article_helpfulness_vote', withArticle( { helpful: value, location: 'feedback_widget', page_path: (window.location && window.location.pathname) || '' }, ctx ) ); }; yesButton.addEventListener('click', () => { if (submitted) return; setSelected(true); }); noButton.addEventListener('click', () => { if (submitted) return; setSelected(false); }); form.addEventListener('submit', (event) => { event.preventDefault(); if (submitted) return; if (helpfulSelection === null) return; submitted = true; const commentRaw = textarea.value || ''; const comment = redactFeedbackText(commentRaw).slice(0, 800); pushEvent( 'article_feedback_submitted', withArticle( { helpful: helpfulSelection, comment, comment_length: comment.length, location: 'feedback_widget', page_path: (window.location && window.location.pathname) || '' }, ctx ) ); form.classList.remove('is-visible'); thanks.classList.add('is-visible'); yesButton.disabled = true; noButton.disabled = true; textarea.disabled = true; submitButton.disabled = true; }); } function hydrateImageLightbox() { const article = document.querySelector('article.article'); if (!article) return; const candidates = Array.from(article.querySelectorAll('.article__figure img, .article__content img')).filter((img) => { const tagName = img && img.tagName ? String(img.tagName).toUpperCase() : ''; if (tagName !== 'IMG') return false; if (img.dataset && img.dataset.noLightbox === '1') return false; const link = img.closest('a[href]'); if (link) { const href = link.getAttribute('href') || ''; const src = img.currentSrc || img.src || ''; try { const hrefUrl = new URL(href, document.baseURI).toString(); const srcUrl = new URL(src, document.baseURI).toString(); if (hrefUrl !== srcUrl) return false; } catch (e) { return false; } } return Boolean(img.currentSrc || img.src); }); if (!candidates.length) return; if (document.querySelector('[data-enhancement=\"article-lightbox\"]')) return; const overlay = document.createElement('div'); overlay.className = 'article-lightbox'; overlay.dataset.enhancement = 'article-lightbox'; overlay.setAttribute('role', 'dialog'); overlay.setAttribute('aria-modal', 'true'); overlay.setAttribute('aria-label', 'Image viewer'); overlay.innerHTML = [ '
', '
', ' ', '
', ' \"\"', ' ', '
' ].join('\\n'); document.body.appendChild(overlay); const closeButton = overlay.querySelector('[data-action=\"close\"]'); const modalImg = overlay.querySelector('.article-lightbox__img'); const caption = overlay.querySelector('[data-lightbox-caption]'); const close = () => { overlay.classList.remove('is-open'); try { document.documentElement.classList.remove('article-lightbox-open'); } catch (e) {} if (modalImg) { modalImg.removeAttribute('src'); modalImg.alt = ''; } if (caption) { caption.textContent = ''; caption.hidden = true; } }; const open = (img) => { if (!img) return; const linked = img.closest('a[href]'); const src = (linked && linked.getAttribute('href')) ? linked.href : (img.currentSrc || img.src); if (!src) return; if (modalImg) modalImg.src = src; if (modalImg) modalImg.alt = img.alt || ''; const fig = img.closest('figure'); const figCaption = fig ? fig.querySelector('figcaption') : null; const text = figCaption && figCaption.textContent ? figCaption.textContent.trim() : ''; if (caption) { caption.textContent = text; caption.hidden = !text; } overlay.classList.add('is-open'); try { document.documentElement.classList.add('article-lightbox-open'); } catch (e) {} }; overlay.addEventListener('click', (event) => { // Dead-click prevention: readers often tap the zoomed image to dismiss the lightbox. // If we only close on "background" clicks, those taps register as dead clicks in Clarity. if (event.target === overlay || event.target === modalImg) close(); }); if (closeButton) closeButton.addEventListener('click', close); document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && overlay.classList.contains('is-open')) close(); }); candidates.forEach((img) => { if (img.dataset.lightboxBound === '1') return; img.dataset.lightboxBound = '1'; img.addEventListener('click', (event) => { if (event.defaultPrevented) return; if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return; event.preventDefault(); event.stopPropagation(); open(img); }); }); } function persistIntent(details) { try { const existing = JSON.parse(localStorage.getItem('appraisily:intents') || '[]'); existing.unshift(Object.assign({}, details, { ts: Date.now() })); localStorage.setItem('appraisily:intents', JSON.stringify(existing.slice(0, 20))); } catch (e) { // ignore } } function persistStartIntentContext(patch, ctx = readArticleContext()) { try { const existing = readStartIntentContext(); const next = Object.assign({}, (existing && typeof existing === 'object' ? existing : {})); const cleanedPatch = patch && typeof patch === 'object' ? patch : {}; Object.entries(cleanedPatch).forEach(([key, value]) => { if (value === undefined || value === null) return; if (typeof value === 'string' && !value.trim()) return; next[key] = value; }); if (ctx && ctx.slug) next.slug = next.slug || ctx.slug; if (ctx && ctx.category) next.category = next.category || ctx.category; next.source = next.source || 'articles'; next.funnel = next.funnel || 'articles'; next.ts = Date.now(); const serialized = JSON.stringify(next); if (window.localStorage && window.localStorage.setItem) window.localStorage.setItem(STORAGE_KEY, serialized); if (window.sessionStorage && window.sessionStorage.setItem) window.sessionStorage.setItem(STORAGE_KEY, serialized); } catch (e) { // ignore } } function buildStartUrl(params) { const url = new URL(START_URL); Object.entries(params).forEach(([key, value]) => { if (value) url.searchParams.set(key, value); }); return url.toString(); } function buildScreenerUrl(params) { const url = new URL(SCREENER_URL); Object.entries(params).forEach(([key, value]) => { if (value) url.searchParams.set(key, value); }); return url.toString(); } function sanitizeArticleAttributionUrl(raw, maxLength = 450) { const value = String(raw || '').trim(); if (!value) return ''; try { const url = new URL(value, window.location && window.location.origin ? window.location.origin : 'https://appraisily.com'); url.hash = ''; Array.from(url.searchParams.keys()).forEach((key) => { if (ATTRIBUTION_URL_PARAM_DENYLIST.has(String(key).toLowerCase())) { url.searchParams.delete(key); } }); const out = url.toString(); return out.length > maxLength ? out.slice(0, maxLength) : out; } catch (e) { const noHash = value.split('#')[0]; return noHash.length > maxLength ? noHash.slice(0, maxLength) : noHash; } } function readArticleAttributionParams() { const landingPage = sanitizeArticleAttributionUrl( window.location ? window.location.href : '', ); const referrer = sanitizeArticleAttributionUrl( typeof document !== 'undefined' ? document.referrer || '' : '', ); return { ...(referrer ? { referrer, traffic_referrer: referrer } : {}), ...(landingPage ? { landing_page: landingPage, traffic_landing_page: landingPage } : {}), }; } function sanitizeStartField(raw, maxLength) { const value = String(raw || '') .replace(/\s+/g, ' ') .trim(); if (!value) return ''; if (!Number.isFinite(maxLength) || maxLength <= 0) return value; return value.length > maxLength ? value.slice(0, maxLength).trim() : value; } function normalizeArticleEntrypoint(raw, fallback = 'inline') { const value = sanitizeStartField(raw || fallback, 64) .toLowerCase() .replace(/[+]+/g, '_') .replace(/[\s-]+/g, '_') .replace(/_+/g, '_') .replace(/^_+|_+$/g, ''); const known = { bottom_free: 'bottom_free', bottomfree: 'bottom_free', deci_ion_router: 'decision_router', decision_router: 'decision_router', inline_top_free: 'inline_top_free', inlinetopfree: 'inline_top_free', po_t_comp: 'post-comps', post_comp: 'post-comps', post_comps: 'post-comps', postcomps: 'post-comps', sidebar: 'sidebar', idebar: 'sidebar', sticky_free: 'sticky_screener', sticky_screener: 'sticky_screener', sticky_start: 'sticky_start', ticky_free: 'sticky_screener', top_free: 'top_free', topfree: 'top_free', }; return known[value] || value || fallback; } function inferArticleIntent(ctx, fallback) { const explicit = normalizeIntent(fallback); if (explicit) return explicit; const haystack = ( String(ctx && ctx.slug ? ctx.slug : '') + ' ' + String(ctx && ctx.title ? ctx.title : '') + ' ' + String(ctx && ctx.category ? ctx.category : '') ).toLowerCase(); if (!haystack) return 'learn'; if (/(donat|charit|irs|tax deduction|qualified appraisal)/.test(haystack)) return 'donate'; if (/(insur|estate|inherit|probate|coverage|claim|divorce)/.test(haystack)) return 'insure'; if (/(sell|selling|auction consign|consign|dealer)/.test(haystack)) return 'sell'; return 'learn'; } function inferArticleTheme(ctx) { const haystack = ( String(ctx && ctx.slug ? ctx.slug : '') + ' ' + String(ctx && ctx.title ? ctx.title : '') ).toLowerCase(); if (/(identif|identify|artifact|maker mark|signature|pattern|hallmark|authenticate|dating)/.test(haystack)) { return 'identification'; } if (/(worth|value|price|appraisal)/.test(haystack)) { return 'valuation'; } return 'generic'; } function inferArticleItem(ctx, fallback) { const fromSlug = sanitizeStartField(suggestItemFromSlug(ctx && ctx.slug ? ctx.slug : ''), 80); if (fromSlug) return fromSlug; const explicit = sanitizeStartField(fallback, 80); if (explicit) return explicit; const fromTitle = sanitizeStartField( String(ctx && ctx.title ? ctx.title : '') .replace(/\b(how much|what is|what are|guide|tips|worth|value|price|appraisal|identify|identifying|ultimate|complete|comprehensive)\b/gi, ' ') .replace(/\s+/g, ' '), 80, ); return fromTitle; } function buildArticleStartParams(anchor, ctx) { const location = anchor.getAttribute('data-analytics-location') || 'inline'; const entrypoint = normalizeArticleEntrypoint(anchor.getAttribute('data-cta-entrypoint') || location, location); const item = inferArticleItem(ctx, anchor.getAttribute('data-cta-item') || ''); const intent = inferArticleIntent(ctx, anchor.getAttribute('data-cta-intent') || ''); return { article: ctx && ctx.slug ? ctx.slug : '', article_slug: ctx && ctx.slug ? ctx.slug : '', category: ctx && ctx.category && ctx.category !== 'articles_article' ? ctx.category : '', entrypoint, funnel: 'articles', intent, item, ref_path: ctx && ctx.slug ? '/' + ctx.slug + '/' : '', seo_page_type: readArticleSeoPageType(ctx), seo_site: 'articles', service: 'regular', source: 'articles', ...readArticleAttributionParams(), }; } function buildArticleFlowParams(ctx, options = {}) { const location = normalizeArticleEntrypoint(options.location || 'inline', 'inline'); const entrypoint = normalizeArticleEntrypoint(options.entrypoint || location, location); const item = inferArticleItem(ctx, options.item || ''); const intent = inferArticleIntent(ctx, options.intent || ''); return { article: ctx && ctx.slug ? ctx.slug : '', article_slug: ctx && ctx.slug ? ctx.slug : '', category: ctx && ctx.category && ctx.category !== 'articles_article' ? ctx.category : '', entrypoint, funnel: 'articles', intent, item, ref_path: ctx && ctx.slug ? '/' + ctx.slug + '/' : '', seo_page_type: readArticleSeoPageType(ctx), seo_site: 'articles', source: 'articles', ...readArticleAttributionParams(), }; } function buildArticleScreenerHref(ctx, location) { const params = buildArticleFlowParams(ctx, { location, entrypoint: location }); const utmMedium = params.entrypoint === 'post-comps' ? 'post_comps' : 'cta'; return buildScreenerUrl(Object.assign({}, params, { utm_source: 'articles', utm_medium: utmMedium, utm_campaign: 'article_free_screener', utm_content: ctx && ctx.slug ? ctx.slug : '', })); } function buildArticleStartHref(ctx, location) { const params = buildArticleFlowParams(ctx, { location, entrypoint: location }); return buildStartUrl(Object.assign({}, params, { service: 'regular', utm_source: 'articles', utm_medium: 'cta', utm_campaign: 'article_signed_appraisal', utm_content: ctx && ctx.slug ? ctx.slug : '', })); } function buildArticleSampleHref(ctx, location) { const url = new URL('/sample-reports/professional', window.location.origin || 'https://appraisily.com'); const params = buildArticleFlowParams(ctx, { location, entrypoint: location }); Object.entries(Object.assign({}, params, { utm_source: 'articles', utm_medium: 'sample-proof', utm_campaign: 'article_sample_report', utm_content: ctx && ctx.slug ? ctx.slug : '', })).forEach(([key, value]) => { if (value) url.searchParams.set(key, value); }); return url.toString(); } function ensureArticleFreeScreenerCtas(ctx) { const item = inferArticleItem(ctx, ''); const inferredIntent = inferArticleIntent(ctx, ''); const proofText = 'View signed report sample'; const startText = 'Need documentation now? Start signed appraisal'; document.querySelectorAll('.appraisily-cta').forEach((block) => { const location = block.getAttribute('data-analytics-location') || 'inline'; if (location !== 'top' && location !== 'bottom' && location !== 'mid-dual') return; const primary = block.querySelector('a[data-cta-kind="appraisal"][href]') || block.querySelector('a[data-cta-kind="signed_report"][href]') || block.querySelector('a[href*="appraisily.com/start"]'); if (!primary) return; const actionWrap = primary.parentElement || block; const screenerLocation = location === 'mid-dual' ? 'mid_dual_screener' : normalizeArticleEntrypoint(location + '_free', 'inline_free'); const screenerHref = buildArticleScreenerHref(ctx, screenerLocation); const sampleLocation = normalizeArticleEntrypoint(location + '_sample', 'inline_sample'); const startLocation = normalizeArticleEntrypoint(location + '_start', 'inline_start'); primary.textContent = 'Upload photos for a free first look'; primary.href = screenerHref; primary.setAttribute('data-analytics-event', 'article_cta_click'); primary.setAttribute('data-cta-kind', 'screener'); primary.setAttribute('data-analytics-location', screenerLocation); primary.setAttribute('data-cta-entrypoint', screenerLocation); primary.setAttribute('data-cta-item', item || ''); primary.setAttribute('data-cta-intent', inferredIntent); let alt = actionWrap.querySelector('.appraisily-cta__alt'); if (!alt) { alt = document.createElement('p'); alt.className = 'appraisily-cta__alt'; const note = Array.from(actionWrap.querySelectorAll('p')).pop(); if (note && note !== alt) { note.insertAdjacentElement('afterend', alt); } else { actionWrap.appendChild(alt); } } alt.textContent = ''; const proof = document.createElement('a'); proof.className = 'appraisily-cta__secondary'; proof.textContent = proofText; proof.href = buildArticleSampleHref(ctx, sampleLocation); proof.setAttribute('data-analytics-event', 'article_cta_click'); proof.setAttribute('data-cta-kind', 'sample_report'); proof.setAttribute('data-analytics-location', sampleLocation); proof.setAttribute('data-cta-entrypoint', sampleLocation); const spacer = document.createTextNode(' · '); const start = document.createElement('a'); start.className = 'appraisily-cta__secondary'; start.textContent = startText; start.href = buildArticleStartHref(ctx, startLocation); start.setAttribute('data-analytics-event', 'article_cta_click'); start.setAttribute('data-cta-kind', 'signed_report'); start.setAttribute('data-analytics-location', startLocation); start.setAttribute('data-cta-entrypoint', startLocation); start.setAttribute('data-cta-item', item || ''); start.setAttribute('data-cta-intent', inferredIntent); alt.appendChild(proof); alt.appendChild(spacer); alt.appendChild(start); }); const sticky = document.querySelector('.article-sticky-cta'); if (sticky) { const stickyLocation = 'sticky_screener'; const stickyHref = buildArticleScreenerHref(ctx, stickyLocation); const button = sticky.querySelector('.article-sticky-cta__btn'); if (button) { button.textContent = 'Upload photos for a free first look'; button.href = stickyHref; button.setAttribute('data-analytics-event', 'article_cta_click'); button.setAttribute('data-cta-kind', 'screener'); button.setAttribute('data-analytics-location', stickyLocation); button.setAttribute('data-cta-entrypoint', stickyLocation); button.setAttribute('data-cta-item', item || ''); button.setAttribute('data-cta-intent', inferredIntent); } const ensureStickyLink = (selector, className, kind, text, href, location) => { let link = sticky.querySelector(selector); if (!link) { link = document.createElement('a'); link.className = className; const sampleLink = sticky.querySelector('.article-sticky-cta__secondary[data-cta-kind="sample_report"]'); if (kind === 'signed_report' && sampleLink && sampleLink.parentNode) { sampleLink.insertAdjacentElement('afterend', link); } else if (button && button.parentNode) { button.insertAdjacentElement('afterend', link); } else { sticky.appendChild(link); } } link.textContent = text; link.href = href; link.setAttribute('data-analytics-event', 'article_cta_click'); link.setAttribute('data-cta-kind', kind); link.setAttribute('data-analytics-location', location); link.setAttribute('data-cta-entrypoint', location); link.setAttribute('data-cta-item', item || ''); link.setAttribute('data-cta-intent', inferredIntent); return link; }; ensureStickyLink( '.article-sticky-cta__secondary[data-cta-kind="sample_report"]', 'article-sticky-cta__secondary', 'sample_report', proofText, buildArticleSampleHref(ctx, 'sticky_sample'), 'sticky_sample', ); sticky.querySelectorAll('.article-sticky-cta__tertiary, a[data-cta-entrypoint="sticky_start"]').forEach((link) => { try { link.remove(); } catch (e) {} }); } document.querySelectorAll('.article-inline-cta').forEach((block) => { const screenerLocation = 'inline_top_free'; const screenerHref = buildArticleScreenerHref(ctx, screenerLocation); const sampleLocation = 'inline_top_sample'; const startLocation = 'inline_top_start'; const primary = block.querySelector('.article-lead-submit[href]') || block.querySelector('a[data-cta-kind="appraisal"][href]') || block.querySelector('a[data-cta-kind="signed_report"][href]'); if (primary) { primary.textContent = 'Upload photos for a free first look'; primary.href = screenerHref; primary.setAttribute('data-analytics-event', 'article_cta_click'); primary.setAttribute('data-cta-kind', 'screener'); primary.setAttribute('data-analytics-location', screenerLocation); primary.setAttribute('data-cta-entrypoint', screenerLocation); primary.setAttribute('data-cta-item', item || ''); primary.setAttribute('data-cta-intent', inferredIntent); } let alt = block.querySelector('.article-inline-cta__alt'); if (!alt) { alt = document.createElement('p'); alt.className = 'article-inline-cta__alt'; const skip = block.querySelector('.article-inline-cta__skip'); if (skip && skip.parentNode) { skip.insertAdjacentElement('afterend', alt); } else { block.appendChild(alt); } } alt.textContent = ''; const proof = document.createElement('a'); proof.setAttribute('data-analytics-event', 'article_cta_click'); proof.setAttribute('data-cta-kind', 'sample_report'); proof.textContent = proofText; proof.href = buildArticleSampleHref(ctx, sampleLocation); proof.setAttribute('data-analytics-location', sampleLocation); proof.setAttribute('data-cta-entrypoint', sampleLocation); const spacer = document.createTextNode(' · '); const start = document.createElement('a'); start.setAttribute('data-analytics-event', 'article_cta_click'); start.setAttribute('data-cta-kind', 'signed_report'); start.textContent = startText; start.href = buildArticleStartHref(ctx, startLocation); start.setAttribute('data-analytics-location', startLocation); start.setAttribute('data-cta-entrypoint', startLocation); start.setAttribute('data-cta-item', item || ''); start.setAttribute('data-cta-intent', inferredIntent); alt.appendChild(proof); alt.appendChild(spacer); alt.appendChild(start); }); } function applyArticleCtaCopy(ctx) { const theme = inferArticleTheme(ctx); const itemLabel = inferArticleItem(ctx, '') || 'your item'; // Use 920px to align with shouldGuardSurfaceClicks in hydratePrimaryCtaSurfaces. // Tablets in the 769–920px range were showing full-size CTA text while the // surface-click guard was active, causing delayed navigation feedback that Clarity // classified as dead clicks (users see a subtle outline but no prompt navigation). const prefersCompactSticky = (() => { try { return Boolean(window.matchMedia && window.matchMedia('(max-width: 920px)').matches); } catch (e) { return false; } })(); const copy = theme === 'identification' ? { heading: 'Choose the right next step for ' + itemLabel, body: 'Upload photos for a free first look, then decide whether a signed appraisal report is needed for documentation.', button: 'Upload photos for a free first look', stickyEyebrow: 'Choose your next step', stickyTitle: 'Free first look', stickyTitleCompact: 'Free first look', stickyBody: 'Use the result to decide whether a signed report is worth ordering.', buttonCompact: 'Upload photos', } : theme === 'valuation' ? { heading: 'Choose the right next step for ' + itemLabel, body: 'Upload photos for a free first look, then decide whether a signed appraisal report is needed for documentation.', button: 'Upload photos for a free first look', stickyEyebrow: 'Choose your next step', stickyTitle: 'Free first look', stickyTitleCompact: 'Free first look', stickyBody: 'Use the result to decide whether a signed report is worth ordering.', buttonCompact: 'Upload photos', } : { heading: 'Choose the right next step for ' + itemLabel, body: 'Upload photos for a free first look, then decide whether a signed appraisal report is needed for documentation.', button: 'Upload photos for a free first look', stickyEyebrow: 'Choose your next step', stickyTitle: 'Free first look', stickyTitleCompact: 'Free first look', stickyBody: 'Use the result to decide whether a signed report is worth ordering.', buttonCompact: 'Upload photos', }; document.querySelectorAll('.appraisily-cta').forEach((block) => { const heading = block.querySelector('h2, h3'); const body = block.querySelector('p'); const button = block.querySelector('a[data-analytics-event="article_cta_click"], a[href]'); if (heading && /Get a Professional Appraisal|Get an expert value report|Get expert identification and value/i.test(heading.textContent || '')) { heading.textContent = copy.heading; } if (body && /Unsure about your item/i.test(body.textContent || '')) body.textContent = copy.body; if (button && /Start Your Appraisal|Start my appraisal|Start your value appraisal|Upload photos for expert ID|Get signed appraisal/i.test(button.textContent || '')) { button.textContent = copy.button; } }); document.querySelectorAll('.article-inline-cta').forEach((block) => { const title = block.querySelector('.article-inline-cta__title'); const body = block.querySelector('.article-inline-cta__body'); const submit = block.querySelector('.article-lead-submit'); if (title && /Get (a price-ready appraisal|an expert ID|an expert value report|expert identification and value)/i.test(title.textContent || '')) { title.textContent = copy.heading; } if (body && /Answer three quick questions|Tell us about/i.test(body.textContent || '')) body.textContent = copy.body; if (submit && /Continue to appraisal flow|Start your value appraisal|Upload photos for expert ID|Get signed appraisal/i.test(submit.textContent || '')) { submit.textContent = copy.button; } }); const sticky = document.querySelector('.article-sticky-cta'); if (sticky) { const eyebrow = sticky.querySelector('.article-sticky-cta__eyebrow'); const title = sticky.querySelector('.article-sticky-cta__title'); const body = sticky.querySelector('.article-sticky-cta__body'); const button = sticky.querySelector('.article-sticky-cta__btn'); if (eyebrow) eyebrow.textContent = copy.stickyEyebrow; if (title) title.textContent = prefersCompactSticky ? copy.stickyTitleCompact || copy.stickyTitle : copy.stickyTitle; if (body) body.textContent = copy.stickyBody; if (button && /Start my appraisal|Start Your Appraisal|Start your value appraisal|Upload photos for expert ID|Get signed appraisal/i.test(button.textContent || '')) { button.textContent = prefersCompactSticky ? copy.buttonCompact || copy.button : copy.button; } } ensureArticleFreeScreenerCtas(ctx); } function hydrateStartCtaFocusParams() { const anchors = document.querySelectorAll('a[href]'); if (!anchors.length) return; const ctx = readArticleContext(); applyArticleCtaCopy(ctx); const readCookieValue = (name) => { try { const parts = String(document.cookie || '').split(';').map((part) => part.trim()); for (const part of parts) { if (!part) continue; const eq = part.indexOf('='); if (eq === -1) continue; if (part.slice(0, eq).trim() !== name) continue; return part.slice(eq + 1).trim() || ''; } } catch (e) {} return ''; }; const parseAttribCookie = () => { const out = { clickIds: {}, utm: {} }; const raw = readCookieValue('appraisily_attrib'); if (!raw) return out; const candidates = [raw]; try { const normalized = raw.replace(/-/g, '+').replace(/_/g, '/'); const padded = normalized + '==='.slice((normalized.length + 3) % 4); candidates.push(atob(padded)); } catch (e) {} for (const candidate of candidates) { try { const parsed = JSON.parse(candidate); const clickSource = parsed && parsed.clickIds && typeof parsed.clickIds === 'object' ? parsed.clickIds : parsed; const utmSource = parsed && parsed.utm && typeof parsed.utm === 'object' ? parsed.utm : parsed; ['gclid', 'wbraid', 'gbraid', 'fbclid', 'twclid', 'msclkid', 'ttclid'].forEach((key) => { const value = clickSource && clickSource[key]; if (typeof value === 'string' && value.trim()) out.clickIds[key] = value.trim(); }); ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach((key) => { const value = utmSource && utmSource[key]; if (typeof value === 'string' && value.trim()) out.utm[key] = value.trim(); }); break; } catch (e) {} } return out; }; const pageParams = (() => { try { return new URLSearchParams(window.location.search || ''); } catch (e) { return new URLSearchParams(''); } })(); const cookieAttrib = parseAttribCookie(); const getTrackingValue = (key) => { const fromQuery = pageParams.get(key); if (typeof fromQuery === 'string' && fromQuery.trim()) return fromQuery.trim(); if (cookieAttrib && cookieAttrib.clickIds && typeof cookieAttrib.clickIds[key] === 'string') { const value = cookieAttrib.clickIds[key].trim(); if (value) return value; } if (cookieAttrib && cookieAttrib.utm && typeof cookieAttrib.utm[key] === 'string') { const value = cookieAttrib.utm[key].trim(); if (value) return value; } return ''; }; anchors.forEach((anchor) => { if (!anchor || anchor.dataset.startFocusBound === '1') return; const rawHref = anchor.getAttribute('href') || ''; if (!rawHref) return; let url = null; try { url = new URL(rawHref, document.baseURI); } catch (e) { return; } const hostname = String(url.hostname || '').toLowerCase(); if (hostname !== 'appraisily.com' && hostname !== 'www.appraisily.com') return; const pathname = String(url.pathname || '').replace(/\/+$/, '') || '/'; const isStart = pathname === '/start'; const isScreener = pathname === '/screener'; if (!isStart && !isScreener) return; const flowParams = isStart ? buildArticleStartParams(anchor, ctx) : buildArticleFlowParams(ctx, { location: anchor.getAttribute('data-analytics-location') || 'inline-free', entrypoint: anchor.getAttribute('data-cta-entrypoint') || anchor.getAttribute('data-analytics-location') || 'inline-free', item: anchor.getAttribute('data-cta-item') || '', intent: anchor.getAttribute('data-cta-intent') || '', }); let changed = false; try { Object.entries(flowParams).forEach(([key, value]) => { if (!value) return; if (url.searchParams.get(key) !== String(value)) { url.searchParams.set(key, value); changed = true; } }); ['gclid', 'wbraid', 'gbraid', 'fbclid', 'twclid', 'msclkid', 'ttclid'].forEach((key) => { const value = getTrackingValue(key); if (!value) return; if (!url.searchParams.has(key)) { url.searchParams.set(key, value); changed = true; } }); ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach((key) => { const value = getTrackingValue(key); if (!value) return; if (!url.searchParams.has(key)) { url.searchParams.set(key, value); changed = true; } }); if (changed) anchor.href = url.toString(); } catch (e) {} try { if (flowParams.entrypoint) { anchor.setAttribute('data-cta-entrypoint', flowParams.entrypoint); } if (flowParams.item) { anchor.setAttribute('data-cta-item', flowParams.item); } if (flowParams.intent) { anchor.setAttribute('data-cta-intent', flowParams.intent); } } catch (e) {} anchor.dataset.startFocusBound = '1'; }); } function hydrateCanonicalArticleFlowParams() { const slug = readArticleSlugFromPath(); if (!slug) return; const item = sanitizeStartField(suggestItemFromSlug(slug), 80); if (!item) return; document.querySelectorAll('a[href]').forEach((anchor) => { let url = null; try { url = new URL(anchor.getAttribute('href') || '', document.baseURI); } catch (e) { return; } const hostname = String(url.hostname || '').toLowerCase(); if (hostname !== 'appraisily.com' && hostname !== 'www.appraisily.com') return; const pathname = String(url.pathname || '').replace(/\/+$/, '') || '/'; if (pathname !== '/start' && pathname !== '/screener') return; let changed = false; const setParam = (key, value) => { if (!value || url.searchParams.get(key) === String(value)) return; url.searchParams.set(key, value); changed = true; }; setParam('article', slug); setParam('article_slug', slug); setParam('item', item); setParam('ref_path', '/' + slug + '/'); setParam('seo_page_type', 'article'); setParam('seo_site', 'articles'); setParam('source', 'articles'); setParam('funnel', 'articles'); if (pathname === '/start') setParam('service', 'regular'); Object.entries(readArticleAttributionParams()).forEach(([key, value]) => setParam(key, value)); try { anchor.setAttribute('data-cta-item', item); if (!anchor.getAttribute('data-cta-intent')) anchor.setAttribute('data-cta-intent', 'learn'); } catch (e) {} if (changed) anchor.href = url.toString(); }); } function forceCanonicalArticleFlowParams() { const pathParts = String((window.location && window.location.pathname) || '') .split('/') .filter(Boolean); const slug = pathParts[0] === 'articles' ? pathParts[1] || '' : pathParts[0] || ''; if (!slug) return; const item = slug .trim() .toLowerCase() .replace(/[^a-z0-9-]+/g, '-') .replace(/-+/g, '-') .replace(/^-+|-+$/g, '') .split('-') .filter(Boolean) .slice(0, 8) .join(' ') .trim(); if (!item) return; document.querySelectorAll('a[href]').forEach((anchor) => { let url = null; try { url = new URL(anchor.getAttribute('href') || anchor.href || '', document.baseURI); } catch (e) { return; } const hostname = String(url.hostname || '').toLowerCase(); const pathname = String(url.pathname || '').replace(/\/+$/, '') || '/'; if ((hostname !== 'appraisily.com' && hostname !== 'www.appraisily.com') || (pathname !== '/start' && pathname !== '/screener')) { return; } url.searchParams.set('article', slug); url.searchParams.set('article_slug', slug); url.searchParams.set('item', item); url.searchParams.set('ref_path', '/' + slug + '/'); url.searchParams.set('seo_page_type', 'article'); url.searchParams.set('seo_site', 'articles'); url.searchParams.set('source', 'articles'); url.searchParams.set('funnel', 'articles'); if (pathname === '/start') url.searchParams.set('service', 'regular'); Object.entries(readArticleAttributionParams()).forEach(([key, value]) => { if (value) url.searchParams.set(key, String(value)); }); try { anchor.href = url.toString(); anchor.setAttribute('data-cta-item', item); if (!anchor.getAttribute('data-cta-intent')) anchor.setAttribute('data-cta-intent', 'learn'); } catch (e) {} }); } function normalizeIntent(raw) { const intent = String(raw || '').trim().toLowerCase(); if (!intent) return ''; return ALLOWED_INTENTS.includes(intent) ? intent : ''; } function normalizeMidLeadIntent(raw) { const value = String(raw || '').trim().toLowerCase(); if (!value) return ''; if (ALLOWED_INTENTS.includes(value)) return value; if (value === 'insurance') return 'insure'; if (value === 'estate' || value === 'inheritance') return 'insure'; if (value === 'curiosity') return 'learn'; if (value === 'donation') return 'donate'; return ''; } function suggestItemFromSlug(slug) { const raw = String(slug || '') .trim() .toLowerCase() .replace(/[^a-z0-9-]+/g, '-') .replace(/-+/g, '-') .replace(/^-+|-+$/g, ''); if (!raw) return ''; let candidate = raw; const patterns = [ /^how-much-are-(.+)-worth$/, /^value-of-old-(.+)$/, /^old-(.+)-value$/, /^(.+)-value$/, /^identify-(.+)$/, ]; for (const pattern of patterns) { const match = candidate.match(pattern); if (match && match[1] && String(match[1]).trim()) { candidate = String(match[1]).trim(); break; } } const stopwords = new Set([ 'a', 'an', 'the', 'and', 'or', 'to', 'for', 'of', 'in', 'on', 'with', 'without', 'your', 'what', 'how', 'why', 'best', 'top', 'free', 'ultimate', 'complete', 'comprehensive', 'guide', 'tips', 'discover', 'discovering', 'unveil', 'unveiling', 'unlock', 'unlocking', 'uncover', 'uncovering' ]); const words = candidate.split('-').filter(Boolean); while (words.length && stopwords.has(words[0])) words.shift(); while (words.length && stopwords.has(words[words.length - 1])) words.pop(); const pruned = words.length ? words : candidate.split('-').filter(Boolean); const phrase = pruned.slice(0, 8).join(' ').trim(); if (!phrase) return ''; return phrase.length > 80 ? phrase.slice(0, 80) : phrase; } function hydrateLeadForm() { const form = document.querySelector('[data-lead-form]'); if (!form) return; const ctx = readArticleContext(); const startIntent = readStartIntentContext(); const itemInput = form.querySelector('[data-lead-input="item"]'); const intentSelect = form.querySelector('[data-lead-input="intent"]'); const locationInput = form.querySelector('[data-lead-input="location"]'); const submitButton = form.querySelector('button[type="submit"], input[type="submit"]'); if (itemInput) { const suggestion = suggestItemFromSlug(ctx && ctx.slug ? ctx.slug : ''); const storedItem = typeof startIntent.item === 'string' ? startIntent.item.trim() : ''; const currentItem = typeof itemInput.value === 'string' ? itemInput.value.trim() : ''; if (suggestion && (!currentItem || (storedItem && currentItem === storedItem))) { itemInput.value = suggestion; } else if (!currentItem && storedItem) { itemInput.value = storedItem; } } if (form.dataset.leadFormBound === '1') return; form.dataset.leadFormBound = '1'; const ensureFeedbackNode = () => { if (!submitButton || !submitButton.insertAdjacentElement) return null; const existing = form.querySelector('.article-lead-form__feedback'); if (existing) return existing; try { const node = document.createElement('p'); node.className = 'article-lead-form__feedback'; node.setAttribute('role', 'status'); node.setAttribute('aria-live', 'polite'); node.textContent = 'Please enter what you’re appraising to continue.'; submitButton.insertAdjacentElement('afterend', node); return node; } catch (e) { return null; } }; const setLeadInvalid = () => { try { form.dataset.leadInvalid = '1'; } catch (e) {} ensureFeedbackNode(); try { if (submitButton) pingSurface(submitButton, 650); } catch (e) {} try { if (typeof showToast === 'function') showToast('Add an item to continue.'); } catch (e) {} }; const clearLeadInvalid = () => { try { if (form.dataset.leadInvalid) delete form.dataset.leadInvalid; } catch (e) {} }; const focusFirstInvalid = () => { try { const invalid = form.querySelector(':invalid'); if (invalid && typeof invalid.focus === 'function') { invalid.focus(); return; } } catch (e) {} try { if (itemInput && typeof itemInput.focus === 'function') itemInput.focus(); } catch (e) {} }; const ensureSubmitEnabled = () => { if (!submitButton) return; try { if (submitButton.disabled) submitButton.disabled = false; } catch (e) {} try { submitButton.removeAttribute('disabled'); } catch (e) {} try { submitButton.removeAttribute('aria-disabled'); } catch (e) {} }; // Don't hard-disable the submit button when required fields are empty. // Mobile readers often tap repeatedly on a disabled submit button (Clarity dead/rage clicks). // Instead, keep it clickable so reportValidity() shows inline feedback + focuses the field. ensureSubmitEnabled(); // Some browsers (notably Safari/iOS) can prevent the submit event from firing when required fields are invalid. // Bind a click handler so the tap always produces visible feedback (prevents dead/rage clicks on the CTA). if (submitButton && submitButton.dataset.leadSubmitBound !== '1') { submitButton.dataset.leadSubmitBound = '1'; form.addEventListener('input', clearLeadInvalid, { passive: true }); form.addEventListener('change', clearLeadInvalid, { passive: true }); submitButton.addEventListener('click', (event) => { if (event && event.defaultPrevented) return; clearLeadInvalid(); const itemValue = ((itemInput && itemInput.value ? itemInput.value : '') + '').trim(); let ok = Boolean(itemValue); try { if (typeof form.reportValidity === 'function') { ok = Boolean(form.reportValidity()) && ok; } } catch (e) {} if (!ok) { setLeadInvalid(); focusFirstInvalid(); try { event.preventDefault(); } catch (e) {} } }); } form.addEventListener('submit', (event) => { event.preventDefault(); clearLeadInvalid(); if (typeof form.reportValidity === 'function' && !form.reportValidity()) { setLeadInvalid(); focusFirstInvalid(); return; } const item = ((itemInput && itemInput.value ? itemInput.value : '') + '').trim(); if (!item) { setLeadInvalid(); try { if (itemInput && typeof itemInput.focus === 'function') itemInput.focus(); } catch (e) {} ensureSubmitEnabled(); return; } const intent = normalizeIntent(intentSelect && intentSelect.value ? intentSelect.value : ''); const location = ((locationInput && locationInput.value ? locationInput.value : '') + '').trim(); const entrypoint = 'inline_form'; persistStartIntentContext({ entrypoint, item, intent, geo: location }, ctx); persistIntent({ item, intent, location, entrypoint }); pushEvent( 'article_cta_lead_submit', withArticle( { location: entrypoint, intent: intent || 'unspecified' }, ctx ) ); const startUrl = buildStartUrl({ item, intent, geo: location, article: ctx.slug, category: ctx.category, entrypoint }); window.location.href = startUrl; }); } function hydrateMidLeadCapture() { const form = document.querySelector('[data-mid-lead-form]'); if (!form) return; if (form.dataset.midLeadBound === '1') return; form.dataset.midLeadBound = '1'; const ctx = readArticleContext(); const module = form.closest('[data-mid-lead], .article-lead-magnet') || form; const steps = Array.from(form.querySelectorAll('[data-lead-step]')); if (!steps.length) return; const detailsStep = form.querySelector('[data-lead-step="details"]') || steps[0]; const contactStep = form.querySelector('[data-lead-step="contact"]') || steps[steps.length - 1]; const progress = module.querySelector('[data-mid-progress]'); const nextBtn = form.querySelector('[data-action="mid-next"]'); const backBtn = form.querySelector('[data-action="mid-back"]'); const itemInput = form.querySelector('[data-mid-input="item"]'); const intentSelect = form.querySelector('[data-mid-input="intent"]'); const emailInput = form.querySelector('[data-mid-input="email"]'); const locationInput = form.querySelector('[data-mid-input="location"]'); const notesInput = form.querySelector('[data-mid-input="photos"]'); const markMidInvalid = (sourceNode) => { try { form.dataset.midInvalid = '1'; } catch (e) {} try { if (sourceNode) pingSurface(sourceNode, 650); } catch (e) {} try { if (typeof showToast === 'function') showToast('Add an item to continue.'); } catch (e) {} }; const clearMidInvalid = () => { try { if (form.dataset.midInvalid) delete form.dataset.midInvalid; } catch (e) {} }; // Dead-click prevention: when this textarea is empty, "Next" is disabled and // readers rage-tap the button (shows up as tablet dead clicks in Clarity). // Prefill with the article title so the module is usable immediately. if (itemInput) { const existing = (itemInput.value || '').trim(); const title = (ctx && ctx.title ? ctx.title : '').trim(); if (!existing && title) { try { itemInput.value = title + ' appraisal'; } catch (e) {} } } // Dead-click prevention: never rely on a disabled "Next" button to enforce input. // A disabled button often produces Clarity dead/rage clicks; keep it clickable and // let native validity UI focus the missing field. if (itemInput) { try { if (!itemInput.hasAttribute('required')) itemInput.setAttribute('required', ''); } catch (e) {} } const setActive = (stepName) => { const desired = stepName === 'contact' ? contactStep : detailsStep; steps.forEach((step) => { if (!step || !step.classList) return; step.classList.toggle('is-active', step === desired); }); if (progress) { progress.textContent = desired === contactStep ? 'Step 2 of 2 · Contact details' : 'Step 1 of 2 · Item details'; } }; const validateStep = (stepNode) => { if (!stepNode) return true; const fields = Array.from(stepNode.querySelectorAll('input, select, textarea')); for (const field of fields) { if (!field) continue; if (field.disabled) continue; if (typeof field.checkValidity !== 'function') continue; if (field.checkValidity()) continue; try { if (typeof field.reportValidity === 'function') field.reportValidity(); } catch (e) {} try { if (typeof field.focus === 'function') field.focus(); } catch (e) {} return false; } return true; }; const syncNextState = () => { if (!nextBtn) return; const itemValue = (itemInput && itemInput.value ? itemInput.value : '').trim(); const enabled = Boolean(itemValue); try { // The HTML ships with "disabled" to avoid "nothing happened" clicks before hydration. // Once hydrated, keep the button clickable and rely on validity UI instead. if (nextBtn.disabled) nextBtn.disabled = false; } catch (e) {} try { nextBtn.removeAttribute('disabled'); } catch (e) {} try { // Keep aria-disabled false so assistive tech treats the button as actionable. // When required fields are missing, clicking "Next" will trigger native validity UI. nextBtn.setAttribute('aria-disabled', 'false'); } catch (e) {} try { nextBtn.dataset.midLeadNextEnabled = enabled ? '1' : '0'; } catch (e) {} }; if (itemInput) { itemInput.addEventListener('input', syncNextState, { passive: true }); itemInput.addEventListener('blur', syncNextState, { passive: true }); } form.addEventListener('input', clearMidInvalid, { passive: true }); form.addEventListener('change', clearMidInvalid, { passive: true }); syncNextState(); const focusMidLeadInput = () => { if (!itemInput) return; try { if (typeof itemInput.scrollIntoView === 'function') { itemInput.scrollIntoView({ block: 'center', behavior: 'smooth' }); } } catch (e) {} try { itemInput.focus({ preventScroll: true }); } catch (e) { try { itemInput.focus(); } catch (e) {} } }; const bindMidLeadFocus = (surface) => { if (!surface || !surface.dataset) return; if (surface.dataset.midLeadFocusBound === '1') return; surface.dataset.midLeadFocusBound = '1'; try { surface.style.cursor = 'text'; } catch (e) {} surface.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const directCta = module.querySelector('.article-lead-magnet__direct[href]'); if (directCta && surface.classList && surface.classList.contains('article-lead-magnet__header')) { try { directCta.click(); return; } catch (e) {} try { window.location.href = directCta.href; return; } catch (e) {} } const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== surface) return; pingSurface(surface, 650); focusMidLeadInput(); }); }; bindMidLeadFocus(module.querySelector('.article-lead-magnet__header')); bindMidLeadFocus(module.querySelector('.article-lead-magnet__progress')); bindMidLeadFocus(module.querySelector('.article-lead-magnet__assurance')); bindMidLeadFocus(module.querySelector('.article-lead-magnet__form')); if (nextBtn) { nextBtn.addEventListener('click', (event) => { if (event && event.defaultPrevented) return; try { event.preventDefault(); } catch (e) {} syncNextState(); if (!validateStep(detailsStep)) { markMidInvalid(nextBtn); return; } setActive('contact'); try { if (emailInput && typeof emailInput.focus === 'function') emailInput.focus(); } catch (e) {} }); } if (backBtn) { backBtn.addEventListener('click', (event) => { if (event && event.defaultPrevented) return; try { event.preventDefault(); } catch (e) {} setActive('details'); try { if (itemInput && typeof itemInput.focus === 'function') itemInput.focus(); } catch (e) {} }); } // Some browsers prevent the submit event from firing when required fields are invalid. // Bind a click handler to surface feedback reliably (prevents Clarity rage-clicks on the submit CTA). const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]'); const clearInvalidFlag = clearMidInvalid; if (submitBtn && submitBtn.dataset.midLeadSubmitBound !== '1') { submitBtn.dataset.midLeadSubmitBound = '1'; form.addEventListener('input', clearInvalidFlag, { passive: true }); form.addEventListener('change', clearInvalidFlag, { passive: true }); submitBtn.addEventListener('click', (event) => { if (event && event.defaultPrevented) return; clearInvalidFlag(); setActive('contact'); let ok = true; try { if (typeof form.reportValidity === 'function') ok = form.reportValidity(); } catch (e) { ok = validateStep(contactStep); } if (!ok) { markMidInvalid(submitBtn); try { if (emailInput && typeof emailInput.focus === 'function') emailInput.focus(); } catch (e) {} try { event.preventDefault(); } catch (e) {} } }); } form.addEventListener('submit', (event) => { event.preventDefault(); if (typeof form.reportValidity === 'function' && !form.reportValidity()) return; const item = ((itemInput && itemInput.value ? itemInput.value : '') + '').trim(); if (!item) { setActive('details'); syncNextState(); try { if (itemInput && typeof itemInput.focus === 'function') itemInput.focus(); } catch (e) {} return; } const intentRaw = intentSelect && intentSelect.value ? intentSelect.value : ''; const intent = normalizeMidLeadIntent(intentRaw) || normalizeIntent(intentRaw); const location = ((locationInput && locationInput.value ? locationInput.value : '') + '').trim(); const email = ((emailInput && emailInput.value ? emailInput.value : '') + '').trim(); const photos = ((notesInput && notesInput.value ? notesInput.value : '') + '').trim(); const entrypoint = 'mid_lead'; persistStartIntentContext({ entrypoint, item, intent, geo: location, email, photos }, ctx); persistIntent({ item, intent, location, email, photos, entrypoint }); pushEvent( 'article_cta_lead_submit', withArticle( { location: entrypoint, intent: intent || 'unspecified' }, ctx ) ); const startUrl = buildStartUrl({ item, intent, geo: location, email, photos, article: ctx.slug, category: ctx.category, entrypoint }); window.location.href = startUrl; }); // Ensure a deterministic initial state: details step visible first. setActive('details'); } function hydrateTopicTokens() { const startIntent = readStartIntentContext(); const item = typeof startIntent.item === 'string' ? startIntent.item.trim() : ''; if (!item) return; document.querySelectorAll('[data-topic-text]').forEach((node) => { node.textContent = item; }); const topicCopy = document.querySelector('[data-topic-copy]'); if (topicCopy) { topicCopy.innerHTML = 'Tell us about ' + item + ' and upload photos — get a certified, written valuation in ~24 hours.'; } } function hydrateConditionChecklists() { const items = document.querySelectorAll('.condition-checklist__item'); if (!items.length) return; items.forEach((item) => { const checkbox = item.querySelector('input[type="checkbox"]'); if (!checkbox) return; try { if (checkbox.disabled) { checkbox.disabled = false; checkbox.removeAttribute('disabled'); } } catch (e) {} try { checkbox.removeAttribute('aria-hidden'); } catch (e) {} const existingLabel = checkbox.closest('label'); const label = existingLabel || document.createElement('label'); if (!existingLabel) { label.className = 'condition-checklist__label'; while (item.firstChild) { label.appendChild(item.firstChild); } item.appendChild(label); } else { label.classList.add('condition-checklist__label'); } const syncCheckedState = () => { label.classList.toggle('is-checked', Boolean(checkbox.checked)); }; if (checkbox.dataset.checklistBound !== '1') { checkbox.dataset.checklistBound = '1'; checkbox.addEventListener('change', syncCheckedState); checkbox.addEventListener('input', syncCheckedState); } syncCheckedState(); }); } function hydrateAuctionCompsRowLinks() { const tables = document.querySelectorAll('table[data-auction-comps]'); if (!tables.length) return; tables.forEach((table) => { table.querySelectorAll('tbody tr').forEach((row) => { if (!row || !row.dataset) return; if (row.dataset.auctionCompsLinked === '1') return; const sourceLink = row.querySelector('td a[href]'); if (!sourceLink) return; const cells = row.querySelectorAll('td'); if (!cells || cells.length < 2) return; const descriptionCell = cells[1]; if (!descriptionCell) return; // Wrap description text in a row-level anchor when no link exists yet. // Do NOT skip the row when a link is already present — we still need the // row-level click handler below so that taps on cell whitespace/gaps are not // classified as dead clicks by Clarity. const descriptionHasLink = Boolean(descriptionCell.querySelector('a[href]')); if (!descriptionHasLink) { const hasText = Boolean((descriptionCell.textContent || '').trim()); if (hasText) { const link = document.createElement('a'); link.href = sourceLink.href; link.target = sourceLink.target || '_blank'; link.rel = sourceLink.rel || 'noopener'; link.className = 'auction-comps__rowlink'; link.setAttribute('aria-label', 'Open auction comp image'); while (descriptionCell.firstChild) { link.appendChild(descriptionCell.firstChild); } descriptionCell.appendChild(link); } } row.dataset.auctionCompsLinked = '1'; if (row.dataset.auctionCompsRowLinkBound !== '1') { row.dataset.auctionCompsRowLinkBound = '1'; row.dataset.auctionCompsRowLink = '1'; try { row.style.cursor = 'pointer'; } catch (e) {} const forwardRowClick = (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== row) return; const linkTargetRaw = (() => { try { return (sourceLink.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const linkTarget = linkTargetRaw || '_blank'; const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { pingNewTab(row); if (openInNewTab(sourceLink.href)) { showToast('Opening in a new tab…'); return; } } try { window.location.href = sourceLink.href; } catch (e) {} }; row.addEventListener('click', forwardRowClick); row.addEventListener('keydown', (event) => { if (!event) return; if (event.defaultPrevented) return; const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; try { event.preventDefault(); } catch (e) {} forwardRowClick(event); }); try { if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '0'); } catch (e) {} } }); }); } /** * Hydrate orphan tables that landed outside .article__content * (e.g. after ). Without hydration these produce dead * clicks on mobile because they look interactive but have no * click handler or responsive scroll wrapper. */ function hydrateOrphanTables() { // Select tables that are NOT inside .article__content and do NOT // already have the data-auction-comps attribute (those are handled // by hydrateAuctionCompsRowLinks). const tables = document.querySelectorAll('table:not([data-auction-comps])'); if (!tables.length) return; tables.forEach((table) => { // Skip tables already inside .article__content if (table.closest('.article__content')) return; // Skip tables that already have a wrapper if (table.closest('.article-comparison__table-wrap, .article-comps__table-wrap, .auction-comps-table-wrap')) return; if (table.dataset.orphanTableHydrated === '1') return; table.dataset.orphanTableHydrated = '1'; // Wrap in a responsive scroll container try { const wrapper = document.createElement('div'); wrapper.className = 'article-comparison__table-wrap'; wrapper.setAttribute('role', 'region'); wrapper.setAttribute('aria-label', 'Scrollable table'); wrapper.setAttribute('tabindex', '0'); table.parentNode.insertBefore(wrapper, table); wrapper.appendChild(table); } catch (e) {} // Ensure rows that have links are properly hydrated table.querySelectorAll('tbody tr').forEach((row) => { if (!row || !row.dataset) return; if (row.dataset.orphanRowLinked === '1') return; const links = row.querySelectorAll('a[href]'); if (!links.length) return; row.dataset.orphanRowLinked = '1'; try { row.style.cursor = 'pointer'; } catch (e) {} const primaryLink = links[0]; row.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; // If user clicked an actual link, let it do its thing if (interactive && interactive !== row) return; // Otherwise navigate via the first link try { pingSurface(row, 650); } catch (e) {} const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const linkTarget = (() => { try { return (primaryLink.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { try { pingNewTab(row); } catch (e) {} if (openInNewTab(primaryLink.href)) { showToast('Opening in a new tab…'); return; } } try { window.location.href = primaryLink.href; } catch (e) {} }); }); }); } function hydrateCardSurfaceLinks() { const cards = document.querySelectorAll('.article-directory-card, .resource-card, .related-post'); if (!cards.length) return; cards.forEach((card) => { if (!card || !card.dataset) return; if (card.dataset.surfaceLinkBound === '1') return; const primary = card.querySelector('a[href]'); if (!primary) return; card.dataset.surfaceLinkBound = '1'; card.dataset.surfaceLink = '1'; if (!card.hasAttribute('tabindex')) { card.setAttribute('tabindex', '0'); } if (!card.hasAttribute('role')) { card.setAttribute('role', 'link'); } const forward = (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== card) return; if (event.type === 'keydown') { const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; event.preventDefault(); } try { pingSurface(card, 650); } catch (e) {} const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const linkTarget = (() => { try { return (primary.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { pingNewTab(card); if (openInNewTab(primary.href)) { showToast('Opening in a new tab…'); return; } try { primary.click(); return; } catch (e) {} } try { primary.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primary.href; } catch (e) {} }; card.addEventListener('click', forward); card.addEventListener('keydown', forward); }); } function hydrateListSurfaceLinks() { const listItems = document.querySelectorAll( '.article-directory-bridge__list li, .article-related__list li, .article__tags li', ); if (!listItems.length) return; listItems.forEach((item) => { if (!item || !item.dataset) return; if (item.dataset.surfaceLinkBound === '1') return; const anchors = item.querySelectorAll('a[href]'); if (!anchors || anchors.length !== 1) return; const primary = anchors[0]; if (!primary || !primary.href) return; item.dataset.surfaceLinkBound = '1'; item.dataset.surfaceLink = '1'; if (!item.hasAttribute('tabindex')) { item.setAttribute('tabindex', '0'); } if (!item.hasAttribute('role')) { item.setAttribute('role', 'link'); } const forward = (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== item) return; if (event.type === 'keydown') { const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; event.preventDefault(); } const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const linkTarget = (() => { try { return (primary.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { pingNewTab(item); if (openInNewTab(primary.href)) { showToast('Opening in a new tab…'); return; } try { primary.click(); return; } catch (e) {} } try { primary.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primary.href; } catch (e) {} }; item.addEventListener('click', forward); item.addEventListener('keydown', forward); }); } function hydratePrimaryCtaSurfaces() { const blocks = document.querySelectorAll('.appraisily-cta'); if (!blocks.length) return; const shouldGuardSurfaceClicks = (() => { try { const coarse = Boolean(window.matchMedia && window.matchMedia('(pointer: coarse)').matches); const ua = String(navigator.userAgent || ''); const platform = String(navigator.platform || ''); const touchPoints = Number(navigator.maxTouchPoints || 0); const isIOSLike = /iPad|iPhone|iPod/i.test(ua) || (platform === 'MacIntel' && touchPoints > 1); const isTouchLike = coarse || touchPoints > 0 || isIOSLike; const isSmallViewport = Boolean(window.matchMedia && window.matchMedia('(max-width: 920px)').matches); return Boolean(isTouchLike && isSmallViewport); } catch (e) { return false; } })(); let lastScrollAtMs = 0; // Reduced from 200ms → 100ms for faster mobile response; 200ms was still // long enough to feel unresponsive during quick scroll-then-tap interactions. const RECENT_SCROLL_GUARD_MS = shouldGuardSurfaceClicks ? 100 : 0; if (shouldGuardSurfaceClicks) { try { window.addEventListener( 'scroll', () => { lastScrollAtMs = Date.now(); }, { passive: true }, ); } catch (e) {} } blocks.forEach((block) => { if (!block || !block.dataset) return; if (block.dataset.surfaceLinkBound === '1') return; const primary = block.querySelector('a[data-analytics-event="article_cta_click"][href]') || block.querySelector('a[href]'); if (!primary || !primary.href) return; block.dataset.surfaceLinkBound = '1'; if (shouldGuardSurfaceClicks) { delete block.dataset.surfaceLink; if (block.getAttribute('role') === 'link') block.removeAttribute('role'); if (block.getAttribute('tabindex') === '0') block.removeAttribute('tabindex'); return; } block.dataset.surfaceLink = '1'; if (!block.hasAttribute('tabindex')) { block.setAttribute('tabindex', '0'); } if (!block.hasAttribute('role')) { block.setAttribute('role', 'link'); } const scheduleForward = (delayMs) => { const delay = Number.isFinite(delayMs) ? Math.max(0, delayMs) : 0; try { if (block.dataset.surfaceNavPending === '1') return; block.dataset.surfaceNavPending = '1'; } catch (e) {} window.setTimeout(() => { try { block.dataset.surfaceNavPending = ''; } catch (e) {} forward({ metaKey: false, ctrlKey: false, shiftKey: false }); }, delay); }; const forward = (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== block) return; if (event.type === 'keydown') { const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; event.preventDefault(); } if (shouldGuardSurfaceClicks && event.type === 'click' && RECENT_SCROLL_GUARD_MS) { const now = Date.now(); const elapsed = now - lastScrollAtMs; if (elapsed >= 0 && elapsed < RECENT_SCROLL_GUARD_MS) { pingSurface(block, Math.max(RECENT_SCROLL_GUARD_MS, 650)); const delay = Math.max(RECENT_SCROLL_GUARD_MS - elapsed, 0); scheduleForward(delay); return; } } try { pingSurface(block, 650); } catch (e) {} const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const linkTarget = (() => { try { return (primary.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { pingNewTab(block); if (openInNewTab(primary.href)) return; try { primary.click(); return; } catch (e) {} } try { primary.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primary.href; } catch (e) {} }; block.addEventListener('click', forward); block.addEventListener('keydown', forward); }); } function hydrateInlineLeadFocus() { const modules = document.querySelectorAll('.article-inline-cta'); if (!modules.length) return; modules.forEach((module) => { if (!module || !module.dataset) return; if (module.dataset.inlineLeadFocusBound === '1') return; const firstInput = module.querySelector('input[data-lead-input="item"]') || module.querySelector('input[data-mid-input="item"]') || module.querySelector('input, textarea'); if (!firstInput) return; module.dataset.inlineLeadFocusBound = '1'; module .querySelectorAll('.article-inline-cta__copy, .article-inline-cta__stats, .article-inline-cta__form') .forEach((surface) => { if (!surface || !surface.dataset) return; if (surface.dataset.inlineLeadFocusBound === '1') return; surface.dataset.inlineLeadFocusBound = '1'; const isFormSurface = surface.classList && surface.classList.contains('article-inline-cta__form'); if (!isFormSurface) { surface.style.cursor = 'text'; } surface.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== surface) return; try { pingSurface(surface, 650); } catch (e) {} try { if (typeof firstInput.scrollIntoView === 'function') { firstInput.scrollIntoView({ block: 'center', behavior: 'smooth' }); } } catch (e) {} try { if (typeof firstInput.click === 'function') firstInput.click(); } catch (e) {} try { firstInput.focus({ preventScroll: false }); } catch (e) { try { firstInput.focus(); } catch (e) {} } try { const inputValue = ((firstInput && firstInput.value ? firstInput.value : '') + '').trim(); if (!inputValue) { try { pingSurface(firstInput, 650); } catch (e) {} showToast('Type an item to continue…'); } else if (document.activeElement !== firstInput) { showToast('Tap the field to start…'); } } catch (e) {} }); }); }); } /* Dead-click prevention: section-header modules look expandable but have no click handlers. Readers tap them expecting to collapse/expand; provide a brief highlight so the tap registers as a response in Clarity even though the content is already visible. */ function hydrateSectionHeaders() { const headers = document.querySelectorAll('.section-header'); if (!headers.length) return; headers.forEach((header) => { if (!header || header.dataset.sectionHeaderBound === '1') return; header.dataset.sectionHeaderBound = '1'; header.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== header) return; pingSurface(header, 650); }); }); } /* Dead-click prevention: timeline items look interactive (date markers, expandable cards). Readers tap expecting details to expand; provide visual feedback so Clarity sees a response. */ function hydrateTimelineItems() { const items = document.querySelectorAll('.timeline-item'); if (!items.length) return; items.forEach((item) => { if (!item || item.dataset.timelineItemBound === '1') return; item.dataset.timelineItemBound = '1'; item.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== item) return; pingSurface(item, 650); }); }); } /* Dead-click prevention: stats-highlight grids look like tappable stat cards. Provide visual feedback on tap so Clarity registers a response. */ function hydrateStatsHighlight() { const grids = document.querySelectorAll('.stats-highlight'); if (!grids.length) return; grids.forEach((grid) => { if (!grid || grid.dataset.statsHighlightBound === '1') return; grid.dataset.statsHighlightBound = '1'; grid.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== grid) return; pingSurface(grid, 650); }); }); } function hydrateGenericTableRowLinks() { const resolvePrimaryArticleCta = () => { const candidates = [ '.appraisily-cta[data-analytics-location="bottom"] a[data-analytics-event="article_cta_click"][href]', '.appraisily-cta[data-analytics-location="top"] a[data-analytics-event="article_cta_click"][href]', '.appraisily-cta a[data-analytics-event="article_cta_click"][href]', ]; for (const selector of candidates) { try { const anchor = document.querySelector(selector); if (anchor && anchor.href) return anchor; } catch (e) {} } return null; }; const rows = document.querySelectorAll('.article__content table:not([data-auction-comps]) tbody tr'); if (!rows.length) return; rows.forEach((row) => { if (!row || !row.dataset) return; if (row.dataset.rowLinkBound === '1') return; const links = row.querySelectorAll('a[href]'); if (!links || links.length !== 1) { const priceTable = row.closest('.price-table'); const conversionTable = row.closest('.price-table, .auction-results'); // Price tables are informational. Do not make rows look tappable: // Clarity reports those taps as dead clicks when no navigation follows. if (priceTable) { try { row.style.cursor = ''; } catch (e) {} return; } const conversionLink = conversionTable ? resolvePrimaryArticleCta() : null; if (conversionTable && conversionLink && conversionLink.href) { row.dataset.rowLinkBound = '1'; row.dataset.rowConversionLink = '1'; try { row.style.cursor = 'pointer'; } catch (e) {} try { if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '0'); } catch (e) {} try { if (!row.hasAttribute('role')) row.setAttribute('role', 'link'); } catch (e) {} const forwardConversion = (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== row) return; if (event.type === 'keydown') { const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; try { event.preventDefault(); } catch (e) {} } try { pingSurface(row, 650); } catch (e) {} try { showToast('Opening written appraisal…'); } catch (e) {} try { conversionLink.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = conversionLink.href; } catch (e) {} }; row.addEventListener('click', forwardConversion); row.addEventListener('keydown', forwardConversion); return; } if (row.dataset.rowPingBound === '1') return; row.dataset.rowPingBound = '1'; row.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== row) return; pingSurface(row, 650); }); return; } const primary = links[0]; if (!primary || !primary.href) return; row.dataset.rowLinkBound = '1'; row.style.cursor = 'pointer'; row.addEventListener('click', (event) => { if (!event || event.defaultPrevented) return; const target = resolveEventTargetElement(event); const interactive = target ? target.closest('a[href], button, input, select, textarea, summary, [role="button"], [role="link"]') : null; if (interactive && interactive !== row) return; const wantsNewTab = Boolean(event.metaKey || event.ctrlKey || event.shiftKey); const linkTarget = (() => { try { return (primary.getAttribute('target') || '').trim(); } catch (e) { return ''; } })(); const shouldOpenNewTab = wantsNewTab || linkTarget === '_blank'; if (shouldOpenNewTab) { pingNewTab(row); if (openInNewTab(primary.href)) return; try { primary.click(); return; } catch (e) {} } try { primary.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primary.href; } catch (e) {} }); }); } function hydrateChatHitboxes() { // Deprecated: chat "hitbox" overlays intercept pointer events and then forward via element.click(). // That synthetic click is not trusted (event.isTrusted === false) and some chat widgets ignore it, // which shows up as Clarity rage-clicks on the chat bubble/close control. Avoid intercepting clicks. try { document.querySelectorAll('[data-appraisily-chat-hitbox]').forEach((node) => node.remove()); } catch (e) {} } function hydrateChatClarityIgnore() { const apply = () => { try { const candidates = []; try { Array.prototype.push.apply(candidates, document.querySelectorAll( 'iframe[src*="appraisily.com/chat-embed"],iframe[src*="/chat-embed"]', )); } catch (e) {} // The chat embed mounts an iframe without src until the reader opens the widget. // Mark it by wrapper attribute / title so Clarity ignores chat interactions even before open. try { Array.prototype.push.apply(candidates, document.querySelectorAll('[data-appraisily-chat-wrap] iframe')); } catch (e) {} try { Array.prototype.push.apply(candidates, document.querySelectorAll('iframe[title*="Appraisily chat"]')); } catch (e) {} const seen = new Set(); const frames = []; for (const frame of candidates) { if (!frame || typeof frame !== 'object') continue; if (seen.has(frame)) continue; seen.add(frame); frames.push(frame); } frames.forEach((frame) => { if (!frame || typeof frame.setAttribute !== 'function') return; try { if (frame.dataset && frame.dataset.clarityIgnoreApplied === '1') return; } catch (e) {} try { frame.setAttribute('data-clarity-ignore', 'true'); } catch (e) {} try { frame.setAttribute('data-appraisily-chat-frame', '1'); } catch (e) {} try { if (frame.dataset) frame.dataset.clarityIgnoreApplied = '1'; } catch (e) {} }); } catch (e) {} }; apply(); // Chat iframe is inserted asynchronously by the chat embed; watch briefly. try { if (!('MutationObserver' in window)) return; let observer = null; observer = new MutationObserver(() => apply()); observer.observe(document.documentElement || document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'], }); window.setTimeout(() => { try { if (observer) observer.disconnect(); } catch (e) {} observer = null; }, 120000); } catch (e) {} } function hydrateStickyCtaForChatOffsets() { // The SEO surfaces load the chat widget via 'https://appraisily.com/widgets/chat-embed.js'. // That embed moves the chat trigger up when it detects '[data-appraisily-fixed-cta=\"true\"]' nodes. // // Our article sticky CTA is fixed-position and can overlap the chat trigger on common mobile widths, // leading to Clarity rage/dead clicks. Mark it so the chat embed offsets itself above it. const sticky = document.querySelector('[data-enhancement="article-sticky-cta"]') || document.querySelector('.article-sticky-cta'); if (!sticky || typeof sticky.setAttribute !== 'function') return; try { if (sticky.dataset && !sticky.dataset.enhancement) { sticky.dataset.enhancement = 'article-sticky-cta'; } if (sticky.getAttribute('data-appraisily-fixed-cta') === 'true') return; sticky.setAttribute('data-appraisily-fixed-cta', 'true'); } catch (e) {} } let enhancementsSafe = true; const runOptional = (fn) => { try { fn(); } catch (e) {} }; runOptional(hydrateTopicTokens); runOptional(hydrateTopicTokens); runOptional(hydrateLeadForm); runOptional(hydrateMidLeadCapture); runOptional(hydrateConditionChecklists); runOptional(hydrateAuctionCompsRowLinks); runOptional(hydrateOrphanTables); runOptional(hydrateCardSurfaceLinks); runOptional(hydrateListSurfaceLinks); runOptional(hydrateStartCtaFocusParams); runOptional(hydratePrimaryCtaSurfaces); runOptional(hydrateCanonicalArticleFlowParams); runOptional(forceCanonicalArticleFlowParams); if (typeof window.setTimeout === 'function') { window.setTimeout(() => { runOptional(hydrateCanonicalArticleFlowParams); runOptional(forceCanonicalArticleFlowParams); }, 0); } runOptional(hydrateInlineLeadFocus); runOptional(hydrateSectionHeaders); runOptional(hydrateTimelineItems); runOptional(hydrateStatsHighlight); runOptional(hydrateGenericTableRowLinks); runOptional(hydrateImageLightbox); runOptional(hydrateChatClarityIgnore); runOptional(hydrateStickyCtaForChatOffsets); runOptional(hydrateChatHitboxes); runOptional(() => { try { const sticky = document.querySelector('[data-enhancement="article-sticky-cta"]') || document.querySelector('.article-sticky-cta'); const primaryCta = sticky ? sticky.querySelector('a.article-sticky-cta__btn[href]') : null; if (!sticky || !primaryCta) return; if (primaryCta.dataset && primaryCta.dataset.pointerFixBound === '1') return; if (primaryCta.dataset) primaryCta.dataset.pointerFixBound = '1'; try { if (!sticky.hasAttribute('tabindex')) sticky.setAttribute('tabindex', '0'); } catch (e) {} try { if (!sticky.hasAttribute('role')) sticky.setAttribute('role', 'link'); } catch (e) {} } catch (e) {} }); runOptional(() => { try { const targets = document.querySelectorAll( 'a[href="https://appraisily.com/privacy-policy/"],a[href="https://appraisily.com/privacy-policy"],a[href="https://www.appraisily.com/privacy-policy/"],a[href="https://www.appraisily.com/privacy-policy"],a[href="https://appraisily.com/privacy/"],a[href="https://appraisily.com/privacy"],a[href="https://www.appraisily.com/privacy/"],a[href="https://www.appraisily.com/privacy"]' ); targets.forEach((anchor) => { try { anchor.href = 'https://appraisily.com/privacy/'; } catch (e) {} }); } catch (e) {} }); // Intentionally do not schedule chat hitbox overlays (see hydrateChatHitboxes). try { const navToggle = document.querySelector('.site-nav__toggle'); const primaryNav = document.getElementById('primary-nav'); if (navToggle && primaryNav && navToggle.dataset.navToggleBound !== '1') { navToggle.dataset.navToggleBound = '1'; const syncNavOpenState = () => { const navOpen = primaryNav.classList.contains('site-nav__list--open') || navToggle.getAttribute('aria-expanded') === 'true'; document.documentElement.classList.toggle('article-nav-open', navOpen); primaryNav.setAttribute('aria-hidden', String(!navOpen)); }; const toggleNav = () => { const expanded = navToggle.getAttribute('aria-expanded') === 'true'; const nextExpanded = !expanded; navToggle.setAttribute('aria-expanded', String(nextExpanded)); primaryNav.classList.toggle('site-nav__list--open', nextExpanded); syncNavOpenState(); window.setTimeout(syncNavOpenState, 0); }; navToggle.addEventListener('click', (event) => { if (event && event.defaultPrevented) return; toggleNav(); }); navToggle.addEventListener('keydown', (event) => { if (!event) return; if (event.defaultPrevented) return; const key = event.key || ''; if (key !== 'Enter' && key !== ' ') return; event.preventDefault(); toggleNav(); }); document.addEventListener('keydown', (event) => { if (!event || event.defaultPrevented || event.key !== 'Escape') return; navToggle.setAttribute('aria-expanded', 'false'); primaryNav.classList.remove('site-nav__list--open'); syncNavOpenState(); }); // Set initial aria-hidden state syncNavOpenState(); } } catch (e) { enhancementsSafe = false; } document.querySelectorAll('[data-analytics-event]').forEach((element) => { element.addEventListener('click', () => { const elementTag = element && element.tagName ? String(element.tagName).toUpperCase() : ''; const isButton = elementTag === 'BUTTON'; const isInput = elementTag === 'INPUT'; const form = (isButton || isInput) && 'form' in element ? element.form : null; const isSubmit = (isButton && (element.type || 'submit') === 'submit') || (isInput && element.type === 'submit'); if (isSubmit && form && typeof form.reportValidity === 'function' && !form.reportValidity()) { return; } const rawEventName = element.getAttribute('data-analytics-event'); const eventName = normalizeAnalyticsEventName(rawEventName); const location = element.getAttribute('data-analytics-location') || 'inline'; const payload = {}; if (element.dataset.ctaKind) { payload.ctaKind = element.dataset.ctaKind; payload.cta_kind = element.dataset.ctaKind; payload.cta_type = element.dataset.ctaKind; } if (element.dataset.directory) { payload.directory = element.dataset.directory; } payload.location = location; if (eventName === 'articles_cta_view' || eventName === 'articles_cta_click') { payload.cta_variant = resolveCtaVariant(element, rawEventName, eventName); } const ctx = readArticleContext(); pushEvent(eventName, withArticle(payload, ctx)); if (element.dataset.ctaKind === 'appraisal') { persistStartIntentContext({ entrypoint: element.getAttribute('data-cta-entrypoint') || location, item: element.getAttribute('data-cta-item') || '', intent: normalizeIntent(element.getAttribute('data-cta-intent') || ''), slug: ctx.slug, category: ctx.category }, ctx); persistIntent({ entrypoint: element.getAttribute('data-cta-entrypoint') || location, item: element.getAttribute('data-cta-item') || '', intent: element.getAttribute('data-cta-intent') || '' }); pushEvent('start_appraisal', withArticle({ location }, ctx)); } }); }); const seen = typeof WeakSet === 'function' ? new WeakSet() : null; const hasSeen = (node) => { if (!node) return false; if (seen) return seen.has(node); try { if (node.getAttribute && node.getAttribute('data-analytics-seen') === '1') return true; } catch (e) {} return false; }; const markSeen = (node) => { if (!node) return; if (seen) { seen.add(node); return; } try { if (node.setAttribute) node.setAttribute('data-analytics-seen', '1'); } catch (e) {} }; const impressionTargets = document.querySelectorAll('[data-analytics-impression]'); if (impressionTargets.length && 'IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; if (hasSeen(entry.target)) return; markSeen(entry.target); const rawEventName = entry.target.getAttribute('data-analytics-impression'); const eventName = normalizeAnalyticsEventName(rawEventName); const location = entry.target.getAttribute('data-analytics-location') || 'inline'; const payload = { location }; if (entry.target && entry.target.dataset && entry.target.dataset.ctaKind) { payload.cta_type = entry.target.dataset.ctaKind; } if (eventName === 'articles_cta_view' || eventName === 'articles_cta_click') { payload.cta_variant = resolveCtaVariant(entry.target, rawEventName, eventName); } pushEvent(eventName, withArticle(payload)); }); }, { threshold: 0.35 }); impressionTargets.forEach((target) => observer.observe(target)); } else { impressionTargets.forEach((target) => { if (hasSeen(target)) return; markSeen(target); const rawEventName = target.getAttribute('data-analytics-impression'); const eventName = normalizeAnalyticsEventName(rawEventName); const location = target.getAttribute('data-analytics-location') || 'inline'; const payload = { location }; if (target && target.dataset && target.dataset.ctaKind) { payload.cta_type = target.dataset.ctaKind; } if (eventName === 'articles_cta_view' || eventName === 'articles_cta_click') { payload.cta_variant = resolveCtaVariant(target, rawEventName, eventName); } pushEvent(eventName, withArticle(payload)); }); } const sticky = document.querySelector('[data-enhancement="article-sticky-cta"]') || document.querySelector('.article-sticky-cta'); if (sticky) { try { if (sticky.dataset && !sticky.dataset.enhancement) { sticky.dataset.enhancement = 'article-sticky-cta'; } sticky.removeAttribute('data-analytics-impression'); sticky.setAttribute('data-appraisily-sticky-impression', 'managed'); const close = sticky.querySelector('[data-action="dismiss-sticky"], .article-sticky-cta__close'); if (close) close.setAttribute('data-clarity-action', 'article_sticky_close'); } catch (e) {} // The shared /articles/assets/site/posthog-bridge.js owns sticky CTA // reveal timing, destinations, and close/impression telemetry. // Keep this legacy controller dormant so generated article HTML // cannot reveal the sticky before the bridge threshold. } if (sticky && window.__appraisilyEnableLegacyStickyCta === true) { let dismissed = false; const closeBtn = sticky.querySelector('[data-action="dismiss-sticky"]'); const primaryCta = sticky.querySelector('a.article-sticky-cta__btn[href]') || sticky.querySelector('a[href]'); let pendingSurfaceNavRequested = false; let pendingSurfaceNavTimer = null; let pendingSurfaceNavAtMs = 0; const cancelPendingSurfaceNavTimer = () => { if (!pendingSurfaceNavTimer) return; try { window.clearTimeout(pendingSurfaceNavTimer); } catch (e) {} pendingSurfaceNavTimer = null; pendingSurfaceNavAtMs = 0; }; const schedulePendingSurfaceNav = () => { if (!pendingSurfaceNavRequested) return; if (dismissed) { pendingSurfaceNavRequested = false; cancelPendingSurfaceNavTimer(); return; } if (!primaryCta || !primaryCta.href) { pendingSurfaceNavRequested = false; cancelPendingSurfaceNavTimer(); return; } const now = Date.now(); let delay = 0; if (shouldGuardSurfaceClicks) { if (RECENT_SCROLL_GUARD_MS) { const elapsed = now - lastScrollAtMs; if (elapsed >= 0 && elapsed < RECENT_SCROLL_GUARD_MS) { delay = Math.max(delay, RECENT_SCROLL_GUARD_MS - elapsed); } } if (shownAtMs && BACKGROUND_CLICK_DEBOUNCE_MS) { const elapsed = now - shownAtMs; if (elapsed >= 0 && elapsed < BACKGROUND_CLICK_DEBOUNCE_MS) { delay = Math.max(delay, BACKGROUND_CLICK_DEBOUNCE_MS - elapsed); } } } const targetAtMs = now + delay; if (pendingSurfaceNavTimer && pendingSurfaceNavAtMs && pendingSurfaceNavAtMs <= targetAtMs) { return; } cancelPendingSurfaceNavTimer(); pendingSurfaceNavAtMs = targetAtMs; try { pendingSurfaceNavTimer = window.setTimeout(() => { pendingSurfaceNavTimer = null; pendingSurfaceNavAtMs = 0; if (dismissed) return; if (!primaryCta || !primaryCta.href) return; pendingSurfaceNavRequested = false; pushEvent('articles_cta_click', withArticle({ location: 'sticky_surface_delayed', cta_type: 'sticky' })); try { primaryCta.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primaryCta.href; } catch (e) {} }, delay); } catch (e) { pendingSurfaceNavRequested = false; } }; const isCoarsePointer = (() => { try { const coarse = Boolean(window.matchMedia && window.matchMedia('(pointer: coarse)').matches); const hoverNone = Boolean(window.matchMedia && window.matchMedia('(hover: none)').matches); return coarse && hoverNone; } catch (e) { return false; } })(); const touchPoints = (() => { try { return Number(navigator.maxTouchPoints || 0); } catch (e) { return 0; } })(); const isTouchLike = Boolean(isCoarsePointer || touchPoints > 0); const isIOSLike = (() => { try { const ua = String(navigator.userAgent || ''); const platform = String(navigator.platform || ''); const maxTouchPoints = Number(navigator.maxTouchPoints || 0); const isIOSDevice = /iPad|iPhone|iPod/i.test(ua) || (platform === 'MacIntel' && maxTouchPoints > 1); return Boolean(isIOSDevice); } catch (e) { return false; } })(); const isSmallViewport = (() => { try { return Boolean(window.matchMedia && window.matchMedia('(max-width: 920px)').matches); } catch (e) { return false; } })(); const stickyInitMs = Date.now(); // On touch devices, "surface click" navigation can trigger accidentally while scrolling. // Still allow it (to avoid dead clicks on the sticky card itself), but guard it // so scroll/touch interactions don't register as accidental "quickback" navigations. // Always keep the sticky surface clickable (including iOS/tablet). We rely on the // guards below to avoid accidental navigations while flick-scrolling. const enableSurfaceClick = true; // Guard surface-clicks on mobile-like touch contexts (small viewports) and iOS-like devices. // On touch-enabled desktops (e.g., Windows laptops with touch screens), these guards can cause // real taps to be ignored, which shows up as Clarity dead clicks on the sticky CTA surface. const shouldGuardSurfaceClicks = Boolean(isTouchLike && (isIOSLike || isSmallViewport)); // If the sticky CTA appears while a user is flick-scrolling, a tap can land on it. // Keep this guard short so genuine intent doesn't show up as Clarity rage/dead clicks. // Reduced from 300ms → 200ms to improve mobile responsiveness. const BACKGROUND_CLICK_DEBOUNCE_MS = shouldGuardSurfaceClicks ? 200 : 0; // Also ignore taps that happen immediately after a scroll event. On iOS Safari this // reduces accidental "quickback" navigations when the sticky CTA is visible and the // reader is flick-scrolling near the bottom of the viewport. // Reduced from 150ms → 100ms — taps immediately after scroll should still navigate promptly. const RECENT_SCROLL_GUARD_MS = shouldGuardSurfaceClicks ? 100 : 0; // Show the sticky CTA promptly after scroll so it remains discoverable on mobile. // Avoid dead-click telemetry by ensuring it becomes visible before readers reach // typical early-scroll positions (~1500–2500px in Clarity replays). const MIN_STICKY_DWELL_MS = 0; const MIN_STICKY_SCROLL_Y = isTouchLike ? 1200 : 600; const STICKY_SHOW_DELAY_MS = 200; let stickyTimer = null; let shownAtMs = 0; let wasVisible = sticky.classList.contains('is-visible'); const inlineLead = document.querySelector('.article-inline-cta--lead') || document.querySelector('.article-inline-cta'); const midLead = document.querySelector('[data-mid-lead], .article-lead-magnet'); const bottomCta = document.querySelector('.appraisily-cta[data-analytics-location="bottom"]') || (() => { const blocks = document.querySelectorAll('.appraisily-cta'); return blocks && blocks.length ? blocks[blocks.length - 1] : null; })(); const isInlineLeadVisible = () => { if (!inlineLead || typeof inlineLead.getBoundingClientRect !== 'function') return false; const rect = inlineLead.getBoundingClientRect(); const vh = window.innerHeight || 0; if (!vh) return false; // Keep the floating reminder hidden while the reader is still working through // the above-the-fold inline CTA so the two conversion surfaces do not stack. return rect.top < vh && rect.bottom > 120; }; const isMidLeadVisible = () => { if (!midLead || typeof midLead.getBoundingClientRect !== 'function') return false; const rect = midLead.getBoundingClientRect(); const vh = window.innerHeight || 0; if (!vh) return false; // Hide sticky only when it would overlap the lead capture module near the bottom. return rect.top < vh && rect.bottom > vh - 320; }; const isBottomCtaVisible = () => { if (!bottomCta || typeof bottomCta.getBoundingClientRect !== 'function') return false; const rect = bottomCta.getBoundingClientRect(); const vh = window.innerHeight || 0; if (!vh) return false; return rect.top < vh && rect.bottom > vh - 320; }; const applySticky = () => { if (dismissed) return; const dwellOk = Date.now() - stickyInitMs >= MIN_STICKY_DWELL_MS; const hasPendingSurfaceNav = (() => { if (!shouldGuardSurfaceClicks) return false; if (pendingSurfaceNavRequested) return true; if (pendingSurfaceNavTimer) return true; if (pendingSurfaceNavAtMs && pendingSurfaceNavAtMs > Date.now()) return true; return false; })(); const shouldShow = dwellOk && (hasPendingSurfaceNav || (window.scrollY > MIN_STICKY_SCROLL_Y && !isInlineLeadVisible() && !isMidLeadVisible() && !isBottomCtaVisible())); if (shouldShow && !wasVisible) { shownAtMs = Date.now(); } sticky.classList.toggle('is-visible', shouldShow); wasVisible = shouldShow; }; let lastScrollAtMs = 0; const scheduleSticky = () => { if (dismissed) return; lastScrollAtMs = Date.now(); schedulePendingSurfaceNav(); try { if (stickyTimer) window.clearTimeout(stickyTimer); } catch (e) {} try { stickyTimer = window.setTimeout(applySticky, STICKY_SHOW_DELAY_MS); } catch (e) { applySticky(); } }; window.addEventListener('scroll', scheduleSticky, { passive: true }); window.addEventListener('load', applySticky); sticky.addEventListener('click', (event) => { if (dismissed) return; if (!sticky.classList.contains('is-visible')) return; if (!primaryCta || !primaryCta.href) return; if (!enableSurfaceClick) return; const target = resolveEventTargetElement(event); if (target && typeof target.closest === 'function') { const interactive = target.closest( 'a[href], button, input, select, textarea, summary, [role="button"], [role="link"]', ); if (interactive && interactive !== sticky) return; } if (shouldGuardSurfaceClicks) { const now = Date.now(); const recentScrollMs = now - lastScrollAtMs; const recentShownMs = shownAtMs ? now - shownAtMs : Number.POSITIVE_INFINITY; const withinScrollGuard = RECENT_SCROLL_GUARD_MS && recentScrollMs >= 0 && recentScrollMs < RECENT_SCROLL_GUARD_MS; const withinShownGuard = shownAtMs && BACKGROUND_CLICK_DEBOUNCE_MS && recentShownMs >= 0 && recentShownMs < BACKGROUND_CLICK_DEBOUNCE_MS; if (withinScrollGuard) { // If the user is actively scrolling, a tap can land on the sticky card surface. // Previously we treated this as "no navigation", which Clarity flags as dead-clicks // when readers tap repeatedly. Instead, schedule navigation right after the scroll // guard window so the click is not "dead" but still avoids accidental quickbacks. pingSurface(sticky, Math.max(BACKGROUND_CLICK_DEBOUNCE_MS || 0, RECENT_SCROLL_GUARD_MS || 0, 650)); try { showToast('Opening appraisal…'); } catch (e) {} pendingSurfaceNavRequested = true; schedulePendingSurfaceNav(); return; } if (withinShownGuard) { // If the sticky just appeared, delay navigation until the debounce window passes. // This prevents quickbacks but still avoids "nothing happened" dead-click UX. pingSurface(sticky, Math.max(BACKGROUND_CLICK_DEBOUNCE_MS || 0, RECENT_SCROLL_GUARD_MS || 0, 650)); try { showToast('Opening appraisal…'); } catch (e) {} pendingSurfaceNavRequested = true; schedulePendingSurfaceNav(); return; } } pushEvent('articles_cta_click', withArticle({ location: 'sticky_surface', cta_type: 'sticky' })); try { primaryCta.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { window.location.href = primaryCta.href; } catch (e) {} }); closeBtn && closeBtn.addEventListener('click', () => { dismissed = true; pendingSurfaceNavRequested = false; cancelPendingSurfaceNavTimer(); try { if (stickyTimer) window.clearTimeout(stickyTimer); } catch (e) {} sticky.classList.remove('is-visible'); pushEvent('article_cta_dismissed', withArticle({ location: 'sticky' })); }); } function hydrateTableOfContents() { const content = document.querySelector('.article__content'); if (!content) return; const headings = content.querySelectorAll('h2, h3'); if (!headings || headings.length < 3) return; const toc = document.createElement('nav'); toc.className = 'article-toc'; toc.setAttribute('aria-label', 'Table of contents'); const title = document.createElement('p'); title.className = 'article-toc__title'; title.textContent = 'In this article'; const list = document.createElement('ul'); list.className = 'article-toc__list'; headings.forEach((heading) => { const id = heading.id || heading.textContent.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); heading.id = id; const li = document.createElement('li'); if (heading.tagName === 'H3') li.className = 'toc-h3'; const a = document.createElement('a'); a.href = '#' + id; a.textContent = heading.textContent.trim(); a.addEventListener('click', (e) => { e.preventDefault(); heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); try { window.history.replaceState(null, '', '#' + id); } catch (e) {} }); li.appendChild(a); list.appendChild(li); }); toc.appendChild(title); toc.appendChild(list); const header = document.querySelector('.article__header'); if (header) { header.insertAdjacentElement('afterend', toc); } else { content.insertBefore(toc, content.firstChild); } // Highlight active section on scroll const tocLinks = list.querySelectorAll('a'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { tocLinks.forEach((link) => { link.classList.toggle('is-active', link.getAttribute('href') === '#' + entry.target.id); }); } }); }, { rootMargin: '-20% 0px -60% 0px' }); headings.forEach((h) => observer.observe(h)); // Make sticky on desktop try { if (window.matchMedia && window.matchMedia('(min-width: 1100px)').matches) { toc.classList.add('is-sticky'); } } catch (e) {} } function hydrateShareBar() { const article = document.querySelector('article.article'); if (!article) return; if (document.querySelector('.article-share')) return; const url = encodeURIComponent(window.location.href); const title = encodeURIComponent(document.title || ''); const share = document.createElement('div'); share.className = 'article-share'; share.innerHTML = '
' + 'Share this' + '' + 'X' + 'LinkedIn' + 'Facebook' + '
'; const articleContent = document.querySelector('.article__content'); if (articleContent) { articleContent.insertAdjacentElement('afterend', share); } const copyBtn = share.querySelector('[data-share="copy"]'); if (copyBtn) { copyBtn.addEventListener('click', () => { try { navigator.clipboard.writeText(window.location.href).then(() => { copyBtn.textContent = 'Copied!'; copyBtn.classList.add('is-copied'); setTimeout(() => { copyBtn.textContent = 'Copy link'; copyBtn.classList.remove('is-copied'); }, 2500); }).catch(() => { fallbackCopy(copyBtn); }); } catch (e) { fallbackCopy(copyBtn); } }); } } function fallbackCopy(btn) { try { const ta = document.createElement('textarea'); ta.value = window.location.href; ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); btn.textContent = 'Copied!'; btn.classList.add('is-copied'); setTimeout(() => { btn.textContent = 'Copy link'; btn.classList.remove('is-copied'); }, 2500); } catch (e) {} } function hydrateArticleNavigation() { const article = document.querySelector('article.article'); if (!article) return; if (document.querySelector('.article-nav')) return; // Collect all article slugs from the public directory const publicDir = '/articles'; const slug = (() => { const parts = String(window.location.pathname || '').split('/').filter(Boolean); const idx = parts.indexOf('articles'); if (idx >= 0 && parts[idx + 1]) return parts[idx + 1]; return parts[0] || ''; })(); if (!slug) return; // Use a lightweight fetch to get the articles index for prev/next fetch('/articles/sitemap.xml') .then((r) => r.text()) .then((xml) => { const urls = []; const locs = xml.match(new RegExp('([^<]+)', 'g')) || []; locs.forEach((loc) => { const articlesIdx = loc.indexOf('/articles/'); if (articlesIdx < 0) return; const rest = loc.slice(articlesIdx + 10); const slashIdx = rest.indexOf('/'); if (slashIdx <= 0) return; const slug = rest.slice(0, slashIdx); urls.push({ slug, url: '/articles/' + slug }); }); const currentIdx = urls.findIndex((u) => u.slug === slug); if (currentIdx < 0) return; const nav = document.createElement('nav'); nav.className = 'article-nav'; nav.setAttribute('aria-label', 'Article navigation'); const grid = document.createElement('div'); grid.className = 'article-nav__grid'; if (currentIdx > 0) { const prev = urls[currentIdx - 1]; const prevLink = document.createElement('a'); prevLink.className = 'article-nav__link'; prevLink.href = '/articles/' + prev.slug + '/'; prevLink.innerHTML = 'Previous' + prev.slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) + ''; grid.appendChild(prevLink); } else { grid.appendChild(document.createElement('div')); } if (currentIdx < urls.length - 1) { const next = urls[currentIdx + 1]; const nextLink = document.createElement('a'); nextLink.className = 'article-nav__link article-nav__link--next'; nextLink.href = '/articles/' + next.slug + '/'; nextLink.innerHTML = 'Next' + next.slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) + ''; grid.appendChild(nextLink); } else { grid.appendChild(document.createElement('div')); } nav.appendChild(grid); const articleContent = document.querySelector('.article__content'); if (articleContent) { articleContent.insertAdjacentElement('afterend', nav); } }) .catch(() => {}); } function hydrateImageLazyLoading() { const article = document.querySelector('article.article'); if (!article) return; const images = article.querySelectorAll('.article__content img, .article__figure img'); if (!images.length) return; images.forEach((img) => { if (img.dataset.lazyBound === '1') return; img.dataset.lazyBound = '1'; // Skip hero images (first figure image) and images already marked no-lazy if (img.dataset.noLazy === '1') return; const isFirstFigure = img.closest('.article__figure') && img.closest('.article__figure') === article.querySelector('.article__figure'); if (isFirstFigure) return; if (!img.hasAttribute('loading')) { img.setAttribute('loading', 'lazy'); } if (!img.hasAttribute('decoding')) { img.setAttribute('decoding', 'async'); } }); } function hydrateImageErrorFallbacks() { const article = document.querySelector('article.article'); if (!article) return; const images = article.querySelectorAll('.article__content img, .article__figure img'); if (!images.length) return; images.forEach((img) => { if (img.dataset.errorFallbackBound === '1') return; img.dataset.errorFallbackBound = '1'; img.addEventListener('error', () => { try { const alt = img.alt || 'Image unavailable'; const fig = img.closest('figure'); if (fig) { let figCaption = fig.querySelector('figcaption'); if (!figCaption) { figCaption = document.createElement('figcaption'); fig.appendChild(figCaption); } const existing = figCaption.textContent || ''; figCaption.textContent = existing ? existing + ' Image unavailable.' : 'Image unavailable.'; } img.style.display = 'none'; img.setAttribute('aria-hidden', 'true'); if (!img.getAttribute('data-original-alt')) { img.setAttribute('data-original-alt', alt); } img.alt = 'Image unavailable'; } catch (e) {} }, { once: true }); }); } function hydrateCtaDensity() { // Progressive CTA reveal: hide mid-article and bottom CTAs until the reader // scrolls past the inline-top CTA, reducing above-the-fold cognitive overload. const inlineTop = document.querySelector('.article-inline-cta.article-inline-cta--lead'); if (!inlineTop) return; const midCtas = Array.from(document.querySelectorAll('.article-lead-magnet, .article-directory-bridge')); const bottomCtas = Array.from(document.querySelectorAll('.appraisily-cta[data-analytics-location="bottom"], .article-cta[data-analytics-location="bottom"]')); const allHidden = midCtas.concat(bottomCtas); if (!allHidden.length) return; // Initially hide CTAs below the inline-top CTA allHidden.forEach((cta) => { if (cta.dataset.ctaDensityBound === '1') return; cta.dataset.ctaDensityBound = '1'; cta.style.opacity = '0'; cta.style.transform = 'translateY(12px)'; cta.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; }); const revealObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const cta = entry.target; cta.style.opacity = ''; cta.style.transform = ''; revealObserver.unobserve(cta); } }); }, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 }); // Observe each hidden CTA — reveal when it enters the viewport allHidden.forEach((cta) => revealObserver.observe(cta)); } hydrateTableOfContents(); hydrateShareBar(); hydrateArticleNavigation(); hydrateImageLazyLoading(); hydrateImageErrorFallbacks(); hydrateCtaDensity(); if (enhancementsSafe) { enableEnhancements(); } else { disableEnhancements(); } })();