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.
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.
What made the Vogels’ approach to collecting unique was their self-imposed restrictions and methodology:
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.
When considering the value of the Vogel collection, several factors come into play:
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.
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:
Each state received 50 representative works from the collection The works would remain accessible to the public rather than disappearing into private hands The Vogels’ legacy would touch communities across the entire United States 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.
Museums Housing Vogel Collection Works
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:
Focus on emerging artists: Research and support artists early in their careers when work is more affordableSet a consistent budget: Allocate a specific portion of income exclusively for art purchasesDevelop expertise: Build knowledge in specific niches or movements rather than collecting broadlyAttend studio visits and art schools: Connect directly with artists before they sign with major galleriesConsider less expensive media: Focus on drawings, prints, photographs, or digital art rather than large paintings or sculpturesWhile 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 remains actively involved in:
Documentary film projects: Both “Herb & Dorothy” (2008) and its sequel “Herb & Dorothy 50x50” (2013) document the Vogels’ remarkable storyEducational outreach: Speaking engagements and programs about art collectingMuseum collaborations: Working with institutions that house pieces from their collectionArchival preservation: Organizing documentation related to their collection and correspondence with artistsThough 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.
Category Price Notes Sol LeWitt $50,000 - $500,000+ For drawings and wall pieces Richard Tuttle $30,000 - $250,000 For small mixed media works Christo and Jeanne-Claude $15,000 - $200,000 For preparatory drawings Pat Steir $20,000 - $450,000 For paintings and works on paper Donald Judd $100,000 - $10,000,000+ For sculptures and wall pieces
</tbody> </table>
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:
Professional appraisals: Work with certified art appraisers, particularly for insurance or donation purposesAuction estimates: Auction houses can provide preliminary valuations for potential consignmentsOnline valuation tools: Platforms like Artsy and Artnet offer market data for comparable worksGallery consultations: Galleries that represent your artists may provide informal valuationsCollection management software: Tools that track purchases and current market data for similar worksRemember that art valuation is not an exact science and can fluctuate based on market conditions, artist career developments, and broader economic factors.
Resources for Art Collection Valuation The collection’s legacy includes:
Democratizing art collecting: The Vogels demonstrated that art collecting wasn’t exclusive to the wealthyDocumentation of artistic movements: The collection provides a comprehensive record of minimal and conceptual art developmentAlternative collecting model: Their approach offered a counterpoint to market-driven collecting strategiesPublic accessibility: Through donation to public institutions, they ensured widespread access to significant artEducational impact: Their story continues to inspire new generations of collectors and art enthusiastsThe 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.
Their legacy demonstrates that:
Knowledge and relationships can be more valuable than deep pockets when building a collection Patient, consistent collecting over decades can result in extraordinary outcomes Collections built on genuine passion often have greater coherence and historical significance 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.
Think your item might be worth something?
Upload a photo and see what similar items have sold for.
Free instant estimate. No card needed. A specialist review is only the next step if the evidence supports it.
Get my free estimate
Takes about two minutes.
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.
Want proof before paying?
See how a signed report documents photos, comparable evidence, condition notes, and value conclusions.
View signed report sample
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
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.
Modern & fine art
Browse the Art Appraisers Directory
Compare fine art, contemporary, and design appraisers by city and specialty in our public directory.
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.
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 = [
'',
'
',
' Close ',
'
',
'
',
'
',
'
'
].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 ' +
'
Copy link ' +
'
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();
}
})();