@@ -2257,11 +2257,20 @@ function PluginPromptPresets({
22572257 ) ;
22582258}
22592259
2260+ const FIRST_PARTY_WEB_CLONE_SITE_ICONS : Record < string , string > = {
2261+ 'open-design.ai' : '/logo.svg' ,
2262+ } ;
2263+
2264+ function webCloneFaviconUrl ( domain : string ) : string {
2265+ return `https://www.google.com/s2/favicons?sz=128&domain=${ encodeURIComponent ( domain ) } ` ;
2266+ }
2267+
22602268// A Website-clone text example ("Website URL to clone: https://open-design.ai") —
2261- // pull the site out so the card can show the site's own favicon + bare domain
2262- // instead of the raw prompt line. Returns null for non-URL examples so the
2263- // generic text card renders unchanged.
2264- function webCloneExampleSite ( example : string ) : { domain : string ; faviconUrl : string } | null {
2269+ // pull the site out so the card can show the site's own mark + bare domain
2270+ // instead of the raw prompt line. First-party bundled examples use local assets
2271+ // so the first screen is stable without waiting on a remote favicon service.
2272+ // Returns null for non-URL examples so the generic text card renders unchanged.
2273+ function webCloneExampleSite ( example : string ) : { domain : string ; iconUrl : string ; fallbackIconUrl ?: string } | null {
22652274 const match = example . match ( / h t t p s ? : \/ \/ [ ^ \s " ' < > ] + / i) ;
22662275 if ( ! match ) return null ;
22672276 let hostname : string ;
@@ -2271,12 +2280,11 @@ function webCloneExampleSite(example: string): { domain: string; faviconUrl: str
22712280 return null ;
22722281 }
22732282 if ( ! hostname || ! hostname . includes ( '.' ) ) return null ;
2274- // The site's own favicon, resolved at render time via Google's public service
2275- // — no third-party brand assets are bundled into the repo, and a broken/blocked
2276- // fetch falls back to a lettered tile.
2283+ const firstPartyIcon = FIRST_PARTY_WEB_CLONE_SITE_ICONS [ hostname ] ;
22772284 return {
22782285 domain : hostname ,
2279- faviconUrl : `https://www.google.com/s2/favicons?sz=128&domain=${ encodeURIComponent ( hostname ) } ` ,
2286+ iconUrl : firstPartyIcon ?? webCloneFaviconUrl ( hostname ) ,
2287+ ...( firstPartyIcon ? { fallbackIconUrl : webCloneFaviconUrl ( hostname ) } : { } ) ,
22802288 } ;
22812289}
22822290
@@ -2289,10 +2297,16 @@ function WebClonePromptExampleCard({
22892297 pulse : boolean ;
22902298 onPick : ( example : string ) => void ;
22912299} ) {
2292- const [ iconFailed , setIconFailed ] = useState ( false ) ;
2300+ const [ iconStage , setIconStage ] = useState < 'primary' | 'fallback' | 'failed' > ( 'primary' ) ;
22932301 const site = webCloneExampleSite ( example ) ;
22942302 const domain = site ?. domain ?? example ;
22952303 const monogram = ( domain . replace ( / [ ^ a - z 0 - 9 ] / i, '' ) [ 0 ] ?? '?' ) . toUpperCase ( ) ;
2304+ let iconUrl : string | null = null ;
2305+ if ( site && iconStage === 'primary' ) {
2306+ iconUrl = site . iconUrl ;
2307+ } else if ( site && iconStage === 'fallback' ) {
2308+ iconUrl = site . fallbackIconUrl ?? null ;
2309+ }
22962310 return (
22972311 < button
22982312 type = "button"
@@ -2302,12 +2316,15 @@ function WebClonePromptExampleCard({
23022316 title = { domain }
23032317 >
23042318 < span className = "home-hero__site-badge" aria-hidden >
2305- { site && ! iconFailed ? (
2319+ { site && iconUrl ? (
23062320 < img
2307- src = { site . faviconUrl }
2321+ src = { iconUrl }
23082322 alt = ""
2309- loading = "lazy"
2310- onError = { ( ) => setIconFailed ( true ) }
2323+ loading = "eager"
2324+ fetchPriority = "high"
2325+ onError = { ( ) => {
2326+ setIconStage ( ( stage ) => ( stage === 'primary' && site . fallbackIconUrl ? 'fallback' : 'failed' ) ) ;
2327+ } }
23112328 />
23122329 ) : (
23132330 < span className = "home-hero__site-monogram" > { monogram } </ span >
0 commit comments