// GrubSlider.jsx — auto-rotating regional hot-dog carousel on mustard canvas. // Images are sourced from Loremflickr using tag queries — it returns real Flickr // food photos for the given tag set. If an image fails, we fall back to the // regional color tile (no broken-image icon is ever shown). const imgUrl = (tag, seed) => `https://loremflickr.com/800/1000/${encodeURIComponent(tag)}/?lock=${seed}`; const SLIDES = [ {title:'The Snap Dog', region:'Brooklyn, NY', note:'Natural-casing all-beef, blistered over lump charcoal.', c:'#8a2b1c', q:'brooklyn snap hot dog natural casing', tag:'hotdog,charcoal,grill', seed:11}, {title:'Chicago Drag', region:'Chicago, IL', note:'Poppy-seed bun. Sport peppers. Zero ketchup — ever.', c:'#3c5a2a', q:'chicago style hot dog poppy seed bun', tag:'chicago,hotdog,poppyseed', seed:12}, {title:'Sonoran Wrap', region:'Tucson, AZ', note:'Bacon-wrapped, pinto beans, jalapeño salsa, bolillo bun.', c:'#6b3a1c', q:'sonoran hot dog bacon wrapped', tag:'sonoran,hotdog,bacon', seed:13}, {title:'Coney Chili', region:'Detroit, MI', note:'Loose chili, mustard, raw onion. Three at 2 a.m.', c:'#5a3520', q:'detroit coney island hot dog chili', tag:'coney,hotdog,chili', seed:14}, {title:'Water Dog', region:'Manhattan, NY', note:'Dirty-water frank, sauerkraut, street onions. No apologies.', c:'#7a5a28', q:'new york dirty water hot dog street cart',tag:'newyork,hotdog,cart', seed:15}, {title:'Kansas City Reuben',region:'Kansas City, MO', note:'Sauerkraut, swiss, Thousand Island on a toasted sesame bun.', c:'#6b4a28', q:'kansas city reuben hot dog sauerkraut', tag:'reuben,hotdog,sauerkraut', seed:16}, {title:'The Seattle Dog', region:'Seattle, WA', note:'Cream cheese, grilled onions, jalapeños. Post-bar canon.', c:'#4a5f6a', q:'seattle hot dog cream cheese grilled onions',tag:'seattle,hotdog,creamcheese',seed:17}, {title:'The Slaw Dog', region:'Charleston, WV', note:'West Virginia-style: chili, mustard, onion, and coleslaw.', c:'#5c6a2a', q:'west virginia slaw dog coleslaw chili', tag:'slaw,hotdog,coleslaw', seed:18}, {title:'The Half-Smoke', region:'Washington, D.C.', note:"Smoked half pork / half beef. Onions, mustard, chili. Ben's law.", c:'#6a3528', q:"ben's chili bowl half smoke washington dc",tag:'halfsmoke,hotdog,smoked', seed:19}, {title:'The Ripper', region:'Clifton, NJ', note:"Deep-fried until the casing blows wide open. Rutt's rule.", c:'#8a3a1c', q:"rutt's hut ripper deep fried hot dog", tag:'deepfried,hotdog,fried', seed:20}, {title:'Texas Tommy', region:'Philadelphia, PA', note:'Bacon-wrapped, stuffed with American cheese. Philly diner classic.', c:'#7a4a28', q:'texas tommy hot dog bacon cheese philadelphia',tag:'bacon,cheese,hotdog', seed:21}, {title:'The Red Snapper', region:'Bangor, ME', note:'Bright-red natural-casing Maine dog. Split-top New England bun.', c:'#8a2020', q:'maine red snapper hot dog split top bun',tag:'maine,hotdog,red', seed:22}, {title:'The Michigan', region:'Plattsburgh, NY', note:'Steamed bun, meat sauce, mustard, onion. Not to be confused.', c:'#5a3a20', q:'plattsburgh michigan hot dog meat sauce',tag:'hotdog,meatsauce,bun', seed:23}, {title:'The Tex-Mex Dog', region:'San Antonio, TX', note:'Bacon-wrapped, pickled jalapeños, pinto beans, cotija.', c:'#6a4828', q:'tex mex hot dog san antonio bacon wrapped',tag:'texmex,hotdog,jalapeno', seed:24}, {title:'The Luther', region:'Atlanta, GA', note:'Glazed donut instead of a bun. Bacon. Sweetness meets smoke.', c:'#7a5520', q:'luther hot dog glazed donut bun', tag:'donut,hotdog,glazed', seed:25}, {title:'Puka Dog', region:'Honolulu, HI', note:'Hawaiian sweet bun speared hole-through. Tropical relish.', c:'#8a5a20', q:'puka hot dog hawaii tropical relish', tag:'hawaii,hotdog,tropical', seed:26}, {title:'The Mission Dog', region:'San Francisco, CA', note:'Bacon-wrapped street dog. Grilled peppers, onions, mayo.', c:'#6a3a28', q:'san francisco mission street hot dog bacon',tag:'sanfrancisco,hotdog,bacon', seed:27}, {title:'Minneapolis Juicy', region:'Minneapolis, MN', note:'Cheese-stuffed frank, split and grilled. Bun is a formality.', c:'#7a5528', q:'juicy cheese stuffed hot dog minneapolis',tag:'cheesestuffed,hotdog,grilled',seed:28}, {title:"The Pink's", region:'Los Angeles, CA', note:'Chili-cheese stretch dog. Nine-inch frank, warm steamed bun.', c:'#8a3825', q:"pink's hot dog los angeles chili cheese",tag:'chili,cheese,hotdog', seed:29}, {title:'The Carolina Slaw', region:'Charlotte, NC', note:'Chili, mustard, slaw, onion. Steamed bun. Southern gospel.', c:'#6a5528', q:'carolina hot dog chili slaw mustard onion',tag:'carolina,hotdog,slaw', seed:30}, ].map(s => ({...s, img: imgUrl(s.tag, s.seed)})); function GrubSlider(){ const [loaded,setLoaded] = React.useState(false); const [paused,setPaused] = React.useState(false); const scrollerRef = React.useRef(null); React.useEffect(()=>{const t=setTimeout(()=>setLoaded(true),80);return ()=>clearTimeout(t)},[]); // Slow auto-rotate: ~80px/sec, infinite loop via duplicate set React.useEffect(()=>{ if(paused) return; const el = scrollerRef.current; if(!el) return; let raf, last = performance.now(); const tick = (now)=>{ const dt = now - last; last = now; if(!paused && el){ el.scrollLeft += (80 * dt/1000); // 80px per second // wrap at halfway (since we render SLIDES twice) const half = el.scrollWidth / 2; if(el.scrollLeft >= half){ el.scrollLeft -= half; } } raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return ()=>cancelAnimationFrame(raf); },[paused]); const scrollBy = (dir)=>{ const el = scrollerRef.current; if(!el) return; el.scrollBy({left: dir * (el.clientWidth * 0.7), behavior:'smooth'}); }; const searchUrl = (q)=>`https://www.google.com/search?tbm=isch&q=${encodeURIComponent(q)}`; const loopSlides = [...SLIDES, ...SLIDES]; return (
Field Log // Regional

The Grub Slider

Twenty dogs.
Twenty verdicts.

setPaused(true)} onMouseLeave={()=>setPaused(false)} onTouchStart={()=>setPaused(true)} onTouchEnd={()=>setTimeout(()=>setPaused(false),2000)} > {loopSlides.map((s,i)=>( {/* Fallback color layer underneath the image */}
{/* Real photo — falls back silently if load fails */} {`${s.title}{ e.currentTarget.style.display='none'; }} style={{position:'absolute',inset:0,width:'100%',height:'100%',objectFit:'cover',zIndex:1}} /> {/* Bottom-to-top dark overlay so text remains legible */}
Google Images
{s.region} {String((i%SLIDES.length)+1).padStart(2,'0')}

{s.title}

{s.note}

))}
); } window.GrubSlider = GrubSlider;