const {useEffect,useMemo,useRef,useState}=React;
const {createRoot}=ReactDOM;
const {animate,stagger}=anime;

const api=async(url,options={})=>{const response=await fetch(url,{...options,headers:{'Content-Type':'application/json',...(options.headers||{})}});const body=await response.json().catch(()=>({}));if(!response.ok)throw new Error(body.error||'Something went wrong.');return body};
const loadExternalScript=src=>new Promise((resolve,reject)=>{if(document.querySelector(`script[src="${src}"]`))return resolve();const script=document.createElement('script');script.src=src;script.async=true;script.onload=resolve;script.onerror=()=>reject(new Error('Payment checkout could not load.'));document.head.appendChild(script)});
const money=value=>new Intl.NumberFormat('en-IN',{style:'currency',currency:'INR',minimumFractionDigits:0,maximumFractionDigits:2}).format(Number(value||0)/100);
const stars=value=>`${'★'.repeat(Math.round(Number(value||0)))}${'☆'.repeat(5-Math.round(Number(value||0)))}`;
const currentMode=()=>'light';
const colorThemes={
  campus:{name:'Campus Signal',note:'IIT Delhi red, cobalt and paper gold.',light:{primary:'#ed3b24',secondary:'#163ea8',accent:'#f5ce3e',background:'#f4eddf',ink:'#17171d'},dark:{darkPrimary:'#ff4b4b',darkSecondary:'#7f8cff',darkAccent:'#f4d35e',darkBackground:'#101014',darkInk:'#f7f6ef',darkStoryCopy:'#bcd4ff',darkKineticBackground:'#222a4a'}},
  midnight:{name:'Midnight Lecture',note:'Indigo, coral and chalk for a focused editorial look.',light:{primary:'#c43f31',secondary:'#2d3f8f',accent:'#e9b949',background:'#f3f0e9',ink:'#182039'},dark:{darkPrimary:'#ff826c',darkSecondary:'#92a6ff',darkAccent:'#ffd166',darkBackground:'#111a2f',darkInk:'#f7f4ed',darkStoryCopy:'#c8d7ff',darkKineticBackground:'#263455'}},
  verdant:{name:'Botanical Lab',note:'Forest, mint and copper with a high-contrast night counterpart.',light:{primary:'#b9432c',secondary:'#155e52',accent:'#d7b65b',background:'#eef1e7',ink:'#15231f'},dark:{darkPrimary:'#ff8a6a',darkSecondary:'#73d6bd',darkAccent:'#f1d47a',darkBackground:'#10231d',darkInk:'#edf7f0',darkStoryCopy:'#c5eadf',darkKineticBackground:'#1c4038'}}
};
const themePalette=(s,mode=currentMode())=>mode==='dark'?{primary:s.darkPrimary||'#ff4b4b',secondary:s.darkSecondary||'#7f8cff',accent:s.darkAccent||'#f4d35e',background:s.darkBackground||'#101014',ink:s.darkInk||'#f7f6ef'}:{primary:s.primary||'#ed3b24',secondary:s.secondary||'#163ea8',accent:s.accent||'#f5ce3e',background:s.background||'#f4eddf',ink:s.ink||'#17171d'};
const themeVars=(s,mode=currentMode())=>{const p=themePalette(s,mode),dark=mode==='dark';return {'--primary':p.primary,'--secondary':p.secondary,'--accent':p.accent,'--paper':p.background,'--ink':p.ink,'--story-copy':dark?(s.darkStoryCopy||'#bcd4ff'):(s.secondary||'#163ea8'),'--kinetic-background':dark?(s.darkKineticBackground||'#222a4a'):(s.secondary||'#163ea8'),'--radius':`${s.radius||20}px`,'--surface':dark?'#1f2029':'#ffffff','--surface-raised':dark?'#292a35':'#ffffff','--surface-muted':dark?'#323340':'#f4eddf','--muted-ink':dark?'#d9d4c9':'#5d5b55','--border':dark?'rgba(244,237,223,.28)':'rgba(23,23,29,.18)','--ticker-ink':dark?p.background:'#ffffff'}};
const TYPO_FIELDS=[['brand','Brand name'],['announcement','Announcement ticker'],['eyebrow','Hero eyebrow'],['headline','Hero headline'],['subhead','Hero supporting copy'],['heroButton','Hero button'],['footerNote','Footer note']];
const TYPO_FONT=/^[A-Za-z0-9][A-Za-z0-9 ]{0,39}$/;
const typography=settings=>{try{const parsed=JSON.parse(settings?.typography||'{}');return parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?parsed:{}}catch{return {}}};
// Inline so that an unset field leaves the stylesheet's own rule untouched, and so a value here
// beats the font shorthands the design uses. font-synthesis is re-enabled because :root turns it
// off and most display fonts ship a single weight, which would otherwise render bold copy as regular.
const typoStyle=(settings,field)=>{const entry=typography(settings)[field]||{},style={};if(TYPO_FONT.test(entry.font||'')){style.fontFamily=`'${entry.font}','DM Sans',Arial,sans-serif`;style.fontSynthesis='weight'}const size=Number(entry.size);if(Number.isFinite(size)&&size>=8&&size<=200)style.fontSize=`${Math.round(size)}px`;return style};
// One stylesheet link for every family in use. Weights are deliberately not requested: css2 rejects
// the whole request for a weight a family does not publish, which would drop single-weight fonts.
const useGoogleFonts=settings=>{const spec=settings?.typography||'';useEffect(()=>{const families=[...new Set(Object.values(typography({typography:spec})).map(entry=>entry&&entry.font).filter(font=>TYPO_FONT.test(font||'')))];if(!families.length)return;const href='https://fonts.googleapis.com/css2?'+families.map(font=>'family='+encodeURIComponent(font).replace(/%20/g,'+')).join('&')+'&display=swap';if(document.querySelector(`link[data-typography][href="${href}"]`))return;const link=document.createElement('link');link.rel='stylesheet';link.href=href;link.setAttribute('data-typography','');document.head.appendChild(link)},[spec])};
const reduced=()=>matchMedia('(prefers-reduced-motion: reduce)').matches;
const Icon=({name,size=20})=>{const paths={bag:'M6 7h12l1 14H5L6 7Zm3 0V5a3 3 0 0 1 6 0v2',arrow:'M5 12h14m-6-6 6 6-6 6',plus:'M12 5v14M5 12h14',close:'M6 6l12 12M18 6 6 18',search:'m21 21-4.4-4.4m2.4-5.6a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z',user:'M20 21a8 8 0 0 0-16 0m12-13a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z',filter:'M4 6h16M7 12h10m-7 6h4',edit:'m4 20 4.5-1 10-10a2.1 2.1 0 0 0-3-3l-10 10L4 20Z',eye:'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Zm10 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',expand:'M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3'};return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d={paths[name]||paths.arrow}/></svg>};

function useCart(){
  const [cart,setCart]=useState(()=>{try{return JSON.parse(localStorage.getItem('iitd-drop-cart-v2')||'[]')}catch{return []}}),[notice,setNotice]=useState('');
  useEffect(()=>localStorage.setItem('iitd-drop-cart-v2',JSON.stringify(cart)),[cart]);
  useEffect(()=>{let cancelled=false;api('/api/catalog?limit=100').then(result=>{if(cancelled)return;const activeProducts=new Set(),activeVariants=new Set();(result.items||[]).forEach(item=>{activeProducts.add(item.id);(item.variants||[]).forEach(variant=>{if(variant.active!==false)activeVariants.add(variant.id)})});setCart(items=>{const kept=items.filter(item=>activeProducts.has(item.productId)&&activeVariants.has(item.variantId));if(kept.length!==items.length)setNotice(`${items.length-kept.length} discontinued ${items.length-kept.length===1?'item was':'items were'} removed from your bag.`);return kept})}).catch(()=>{});return()=>{cancelled=true}},[]);
  const add=(product,variant,qty,customization)=>{const key=`${variant.id}:${customization?.text||''}:${customization?.placement||''}`;setCart(items=>{const found=items.find(x=>x.key===key);const next=found?items.map(x=>x.key===key?{...x,qty:Math.min(10,x.qty+qty)}:x):[...items,{key,variantId:variant.id,productId:product.id,slug:product.slug,name:product.name,image:product.image,price:Number(variant.price),size:variant.size,color:variant.color,sku:variant.sku,qty,customization:customization||null,customizationSurcharge:customization?Number(product.customization?.surcharge||0):0}];return next})};
  const update=(key,qty)=>setCart(items=>items.map(x=>x.key===key?{...x,qty:Math.max(1,Math.min(10,qty))}:x));
  const remove=key=>setCart(items=>items.filter(x=>x.key!==key));
  const clear=()=>{try{localStorage.removeItem('iitd-drop-cart-v2')}catch{};setCart([])};
  return {cart,add,update,remove,clear,count:cart.reduce((n,x)=>n+x.qty,0),notice,dismissNotice:()=>setNotice('')};
}

function MobileMenu({open,onClose,session,cartCount}){
  const panel=useRef(),links=useRef();
  useEffect(()=>{if(!panel.current)return;if(reduced()){panel.current.style.transform=open?'translateX(0)':'translateX(-105%)';return}animate(panel.current,{x:open?['-105%','0%']:['0%','-105%'],duration:open?520:360,ease:open?'outExpo':'inOutQuart'});animate(links.current.querySelectorAll('a'),{opacity:open?[0,1]:[1,0],x:open?[-28,0]:[0,-18],delay:open?stagger(55):0,duration:open?430:180,ease:'outQuart'})},[open]);
  useEffect(()=>{if(!open)return;const close=e=>e.key==='Escape'&&onClose();addEventListener('keydown',close);document.body.classList.add('menu-locked');return()=>{removeEventListener('keydown',close);document.body.classList.remove('menu-locked')}},[open]);
  return <><div className={`menu-backdrop ${open?'show':''}`} onClick={onClose}/><aside className={`mobile-drawer ${open?'open':''}`} ref={panel} aria-hidden={!open} aria-label="Main menu"><div className="drawer-brand"><span>THE IIT DELHI DROP</span><button onClick={onClose} aria-label="Close menu"><Icon name="close"/></button></div><nav ref={links}><a onClick={onClose} href="/#catalog"><span>01</span>Merchandise</a><a onClick={onClose} href="/account"><span>03</span>{session?'My account':'Sign in'}</a><a onClick={onClose} href="/cart"><span>04</span>Bag <b>{cartCount}</b></a></nav><div className="drawer-poster"><small>MADE FOR</small><strong>CURIOUS<br/>MINDS.</strong><i>New Delhi / 1961 → ∞</i></div></aside></>;
}

function Header({settings,session,cartCount=0}){const [menu,setMenu]=useState(false),[accountOpen,setAccountOpen]=useState(false),button=useRef(),signOut=async()=>{try{await api('/api/auth/logout',{method:'POST',headers:{'X-CSRF-Token':session.csrf}})}catch{}location.href='/'};useEffect(()=>{if(!button.current||reduced())return;const lines=button.current.querySelectorAll('i');animate(lines,{rotate:menu?(_,i)=>i===0?45:i===2?-45:0:0,y:menu?(_,i)=>i===0?7:i===2?-7:0:0,opacity:menu?(_,i)=>i===1?0:1:1,duration:330,ease:'outBack'})},[menu]);return <><div className="ticker" style={typoStyle(settings,'announcement')}><div>{Array.from({length:4},(_,i)=><React.Fragment key={i}>{settings.announcement} ✦ </React.Fragment>)}</div></div><header className="site-nav shell"><button className="menu-trigger" ref={button} onClick={()=>setMenu(!menu)} aria-expanded={menu} aria-label={menu?'Close menu':'Open menu'}><i/><i/><i/></button><a className="brand" href="/" style={typoStyle(settings,'brand')}><span>●</span>{settings.brand}</a><nav><a href="/#catalog">Merchandise</a><a href="/contact">Contact</a></nav><div className="nav-actions">{session?<div className="account-menu-wrap" onMouseEnter={()=>setAccountOpen(true)} onMouseLeave={()=>setAccountOpen(false)}><button type="button" className="account-trigger" aria-haspopup="menu" aria-expanded={accountOpen} onFocus={()=>setAccountOpen(true)}><Icon name="user"/><span>{session.user.fullName?.split(' ')[0]||'Account'}</span></button><div className={`account-menu ${accountOpen?'show':''}`} role="menu"><a href="/account?tab=orders" role="menuitem">MY ORDERS</a><a href="/account?tab=profile" role="menuitem">PROFILE</a><a href="/account?tab=addresses" role="menuitem">ADDRESSES</a><button type="button" role="menuitem" className="account-signout" onClick={signOut}>SIGN OUT</button></div></div>:<a href="/account" aria-label="Sign in"><Icon name="user"/><span>Sign in</span></a>}<a className="bag-link" href="/cart"><Icon name="bag"/><b>{cartCount}</b></a></div></header><MobileMenu open={menu} onClose={()=>setMenu(false)} session={session} cartCount={cartCount}/></>;}
function ProductCard({product,index}){const [shownPhoto,setShownPhoto]=useState(0);const colorways=product.colorways||[],[activeColorway,setActiveColorway]=useState(()=>colorways.find(colorway=>colorway.id===product.colorwayId)||colorways[0]||null),colors=colorways.map(colorway=>colorway.name).join(' · '),activeImage=activeColorway?.image||product.image,activeCard=activeColorway?.cardColor||product.color,media=(activeColorway?.media||product.media||[]).filter(item=>item.type==='image').slice(0,2),photos=media.length?media:[{id:'fallback',url:activeImage,alt:''}],variants=(product.variants||[]).filter(item=>item.active!==false&&Number(item.stock||0)>0&&(!activeColorway||item.colorwayId===activeColorway.id)),variant=variants.sort((a,b)=>SIZE_ORDER.indexOf(a.size)-SIZE_ORDER.indexOf(b.size))[0],href='/products/'+product.slug+(activeColorway?.slug?'?color='+encodeURIComponent(activeColorway.slug):'');const selectColor=(event,colorway)=>{event.preventDefault();event.stopPropagation();setActiveColorway(colorway)};useEffect(()=>setShownPhoto(0),[activeColorway?.id]);useEffect(()=>{if(photos.length<2||reduced())return;const dwell=(shownPhoto===0?2000:5000)+Math.random()*900;const timer=setTimeout(()=>setShownPhoto(current=>(current+1)%photos.length),dwell);return()=>clearTimeout(timer)},[shownPhoto,photos.length]);const chooseSize=event=>{event.preventDefault();event.stopPropagation();if(!variant)return;location.href=href+(href.includes('?')?'&':'?')+'pick=size'};return <article className="catalog-card"><a className="catalog-art" href={href} style={{'--card':activeCard}}><div className="catalog-card-photos">{photos.map((item,photoIndex)=><img src={item.url} loading={index<2?'eager':'lazy'} fetchpriority={index<2&&!photoIndex?'high':'auto'} decoding="async" width="640" height="720" className={photoIndex===shownPhoto?'shown':''} alt={`${product.name}${activeColorway?.name?` in ${activeColorway.name}`:''} ${photoIndex?'back':'front'} view`} key={item.id||item.url}/>)}</div><span className="card-number">{String(index+1).padStart(2,'0')}</span></a><div className="catalog-color-dots" aria-label={`Available colours: ${colors||'Campus edition'}`}>{colorways.map(colorway=><button type="button" className={activeColorway?.id===colorway.id?'active':''} key={colorway.id||colorway.name} title={colorway.name} aria-label={`Show ${product.name} in ${colorway.name}`} aria-pressed={activeColorway?.id===colorway.id} onClick={event=>selectColor(event,colorway)}><i style={{backgroundColor:colorway.swatch||colorway.cardColor||'#999'}}/></button>)}</div><div className="catalog-card-actions"><a className="catalog-info" href={href}><div><h3>{product.name}</h3><span>{activeColorway?.name||colors||'Campus edition'}</span><small className="rating">{stars(product.rating)} <b>{product.rating?Number(product.rating).toFixed(1):'NEW'}</b></small></div><div><b>{money(product.price)}</b>{product.compare_price>product.price&&<s>{money(product.compare_price)}</s>}</div></a><button type="button" className="catalog-add" disabled={!variant} onClick={chooseSize}>{variant?'ADD TO BAG':'SOLD OUT'}<Icon name="arrow" size={16}/></button></div></article>}
function MoreSoonSection(){return <section className="more-soon shell" aria-label="More soon to come"><span className="kicker">NEXT UP</span><h2>MORE SOON<br/><em>TO COME.</em></h2></section>}

function Catalog({store}){
  const [items,setItems]=useState([]),[cursor,setCursor]=useState(null),[loading,setLoading]=useState(true),[error,setError]=useState(''),[requestKey,setRequestKey]=useState(0);const grid=useRef();
  const batch=matchMedia('(max-width:760px)').matches?4:12;
  const params=useMemo(()=>new URLSearchParams({limit:String(batch)}),[batch]);
  useEffect(()=>{const controller=new AbortController();setLoading(true);setError('');history.replaceState(null,'',location.pathname+'#catalog');api('/api/catalog?'+params,{signal:controller.signal}).then(result=>{setItems(result.items);setCursor(result.nextCursor);setTimeout(()=>{if(!reduced()&&grid.current)animate(grid.current.querySelectorAll('.catalog-card'),{opacity:[0,1],y:[18,0],delay:stagger(45),duration:420,ease:'outQuart'})})}).catch(e=>{if(e.name!=='AbortError')setError(e.message)}).finally(()=>setLoading(false));return()=>controller.abort()},[params,requestKey]);
  const loadMore=async()=>{if(!cursor||loading)return;setLoading(true);setError('');try{const result=await api('/api/catalog?'+params+'&cursor='+cursor);setItems(current=>[...current,...result.items]);setCursor(result.nextCursor)}catch(e){setError(e.message)}finally{setLoading(false)}};

  return <section className="catalog shell" id="catalog"><header className="catalog-heading"><div><span className="kicker">THE CURRENT EDIT</span><h2>Wear the<br/><em>idea.</em></h2></div><p>Small-batch campus goods made for lectures, late nights and wherever you go next. Each product carries its own colourways.</p></header><div className="catalog-filter-row"><span className="kicker">SHOP THE DROP</span></div>{error&&<div className="catalog-message error"><b>THE GRID TOOK A WRONG TURN.</b><span>{error}</span><button onClick={()=>setRequestKey(x=>x+1)}>Try again</button></div>}<div className={'catalog-grid '+(loading?'updating':'')} ref={grid}>{items.map((product,index)=><ProductCard product={product} index={index} key={product.catalogItemId||product.id}/>)}</div>{loading&&items.length===0&&<div className="catalog-grid skeleton-grid">{Array.from({length:batch},(_,i)=><div className="product-skeleton" key={i}><i/><b/><span/></div>)}</div>}{!loading&&!error&&items.length===0&&<div className="catalog-message"><b>THE DROP IS QUIET—FOR NOW.</b><span>Check back soon for the next campus release.</span></div>}{cursor&&<button className="load-more" disabled={loading} onClick={loadMore}>{loading?'BRINGING MORE…':'SHOW MORE PIECES'}<span>↓</span></button>}<small className="catalog-count">Showing {items.length} products</small></section>
}

function Storefront({store,session}){
  const {count}=useCart(),root=useRef();
  useEffect(()=>{if(reduced()||!root.current)return;animate(root.current.querySelectorAll('.hero-copy>*'),{opacity:[0,1],y:[24,0],delay:stagger(75),duration:650,ease:'outExpo'});animate(root.current.querySelector('.hero-visual'),{opacity:[0,1],scale:[.96,1],rotate:[1.5,0],duration:900,ease:'outQuart'})},[]);
  useEffect(()=>{if(reduced()||!root.current)return;const targets=root.current.querySelectorAll('.catalog,.story'),observer=new IntersectionObserver(entries=>entries.forEach(entry=>{if(!entry.isIntersecting)return;animate(entry.target,{opacity:[.72,1],y:[26,0],duration:680,ease:'outQuart'});observer.unobserve(entry.target)}),{threshold:.14});targets.forEach(x=>observer.observe(x));let queued=false;const parallax=()=>{if(!queued&&scrollY<innerHeight*1.2){queued=true;requestAnimationFrame(()=>{const photo=root.current?.querySelector('.hero-photo');if(photo)photo.style.transform=`translate3d(0,${Math.min(28,scrollY*.035)}px,0)`;queued=false})}};addEventListener('scroll',parallax,{passive:true});return()=>{observer.disconnect();removeEventListener('scroll',parallax)}},[]);
  const s=store.settings;return <div className="store" data-theme={currentMode()} style={themeVars(s)} ref={root}><Header settings={s} session={session} cartCount={count}/><main><section className="hero shell"><div className="hero-copy"><div className="eyebrow" style={typoStyle(s,'eyebrow')}><i/>{s.eyebrow}</div><h1 style={typoStyle(s,'headline')}>{s.headline.split('\n').map((x,i)=><React.Fragment key={x}>{x}{i===0&&<br/>}</React.Fragment>)}</h1><p style={typoStyle(s,'subhead')}>{s.subhead}</p><a className="primary-cta" href="#catalog" style={typoStyle(s,'heroButton')}>{s.heroButton}<Icon name="arrow"/></a><div className="hero-meta"><span>NEW DELHI</span><i/><span>SCROLL TO DISCOVER</span></div></div><div className="hero-visual"><div className="hero-photo"><img src={s.heroImage} alt="The IIT Delhi Drop merchandise"/><span>DROP<br/>01</span></div><div className="hero-sticker">100%<br/><b>CAMPUS</b><br/>ENERGY</div><div className="hero-note">Curiosity<br/>looks good<br/>on you ↗</div></div></section><div className="kinetic-line"><div>{Array.from({length:4},(_,i)=><React.Fragment key={i}>BIG IDEAS ✦ GOOD THREADS ✦ CAMPUS CULTURE ✦ BUILT TO WANDER ✦&nbsp;</React.Fragment>)}</div></div><Catalog store={store}/><MoreSoonSection/></main><Footer settings={s}/></div>
}

const SIZE_ORDER=['XS','S','M','L','XL','XXL'];
const SIZE_CHART={title:'Oversized drop-shoulder t-shirt',strap:'Relaxed fit. Street-ready style.',
 columns:['Size','Chest','Length','Shoulder','Sleeve'],
 rows:[['XS',40,27,19,8],['S',42,27.5,20,8.5],['M',44,28,21,9],['L',46,28.5,22,9.5],['XL',48,29,23,10],['XXL',50,29.5,24,10.5]],
 fit:'Designed for a loose and relaxed fit. For a true oversized look, choose your regular size.',
 features:['Premium cotton','Breathable fabric','Durable & long lasting','Machine washable'],
 tolerance:'All measurements are in inches. Expect a tolerance of ± 1 inch.'};
// Terms, privacy and shipping are editable from Studio. The stored form is plain text where a
// '## ' line starts a section, which is far easier to edit in a textarea than JSON. When a
// setting is empty the built-in copy below is used, so the pages are never blank.
const POLICY_KEYS={terms:'policyTerms',privacy:'policyPrivacy',shipping:'policyShipping'};
const policyToText=page=>page.sections.map(([heading,body])=>`## ${heading}\n${body}`).join('\n\n');
const parsePolicy=text=>{const out=[];let current=null;
 for(const line of String(text||'').split(/\r?\n/)){const trimmed=line.trim();if(!trimmed)continue;
  if(trimmed.startsWith('## ')){current=[trimmed.slice(3).trim(),''];out.push(current)}
  else if(current)current[1]=current[1]?`${current[1]} ${trimmed}`:trimmed;
  else out.push(['',trimmed])}
 return out.filter(([heading,body])=>heading||body)};
const policySections=(settings,policy,page)=>{const stored=POLICY_KEYS[policy]?String(settings?.[POLICY_KEYS[policy]]||'').trim():'';
 const parsed=stored?parsePolicy(stored):[];return parsed.length?parsed:page.sections};
function ProductPage({store,session,slug}){
  const {count,add}=useCart(),[data,setData]=useState(null),[error,setError]=useState(''),[size,setSize]=useState(''),[color,setColor]=useState(''),[qty,setQty]=useState(1),[customText,setCustomText]=useState(''),[placement,setPlacement]=useState(''),[style,setStyle]=useState(''),[textColor,setTextColor]=useState(''),[added,setAdded]=useState(false),[selectedMedia,setSelectedMedia]=useState(''),[touchStartX,setTouchStartX]=useState(null),[zoomed,setZoomed]=useState(false),[sizeGuide,setSizeGuide]=useState(false);
  useEffect(()=>{api(`/api/products/${slug}`).then(result=>{setData(result);const requested=new URLSearchParams(location.search).get('color'),chosen=result.product.colorways?.find(x=>x.slug===requested)||result.product.colorways?.find(x=>String(x.name).toLowerCase()==='black')||result.product.colorways?.[0],v=result.product.variants.find(x=>x.colorwayId===chosen?.id)||result.product.variants[0];setColor(chosen?.name||v?.color||'');setSize(v?.size||'');const c=result.product.customization;setPlacement(c?.placements?.[0]||'');setStyle(c?.styles?.[0]||'');setTextColor(c?.colors?.[0]||'')}).catch(e=>setError(e.message))},[slug]);
  useEffect(()=>{if(!data||!color)return;const available=data.product.variants.filter(x=>x.active!==false&&x.stock>0&&x.color===color);if(!available.some(x=>x.size===size))setSize(available[0]?.size||'');const colorway=data.product.colorways?.find(x=>x.name===color);if(colorway){const url=new URL(location.href);url.searchParams.set('color',colorway.slug);history.replaceState(null,'',url)}},[data,color]);
  useEffect(()=>setSelectedMedia(''),[color]);
  useEffect(()=>{if(!sizeGuide)return;document.body.classList.add('menu-locked');const escapeGuide=event=>{if(event.key==='Escape')setSizeGuide(false)};addEventListener('keydown',escapeGuide);return()=>{document.body.classList.remove('menu-locked');removeEventListener('keydown',escapeGuide)}},[sizeGuide]);useEffect(()=>{if(!zoomed)return;document.body.classList.add('menu-locked');const frame=document.querySelector('.pdp-zoom');if(frame)frame.scrollLeft=(frame.scrollWidth-frame.clientWidth)/2;const escape=event=>{if(event.key==='Escape')setZoomed(false)};addEventListener('keydown',escape);return()=>{document.body.classList.remove('menu-locked');removeEventListener('keydown',escape)}},[zoomed]);
  const [sizePrompt,setSizePrompt]=useState(false),[promptHeld,setPromptHeld]=useState(false);
  useEffect(()=>{if(!data||new URLSearchParams(location.search).get('pick')!=='size')return;setSizePrompt(true);const url=new URL(location.href);url.searchParams.delete('pick');history.replaceState(null,'',url);document.getElementById('select-size')?.scrollIntoView({behavior:reduced()?'auto':'smooth',block:'center'})},[data]);
  useEffect(()=>{if(!sizePrompt||promptHeld)return;const timer=setTimeout(()=>setSizePrompt(false),5000);return()=>clearTimeout(timer)},[sizePrompt,promptHeld]);
  if(error)return <NotFound store={store}/>;if(!data)return <Loading/>;const {product,reviews}=data,variants=product.variants.filter(x=>x.active!==false&&x.stock>0),colorways=product.colorways?.filter(x=>x.active!==false)||[],colors=colorways.length?colorways.map(x=>x.name):[...new Set(variants.map(x=>x.color))],activeColorway=colorways.find(x=>x.name===color),sizeOrder=SIZE_ORDER,sizes=[...new Set(variants.filter(x=>x.color===color).map(x=>x.size))].sort((a,b)=>{const ai=sizeOrder.indexOf(a),bi=sizeOrder.indexOf(b);return (ai<0?99:ai)-(bi<0?99:bi)}),variant=variants.find(x=>x.size===size&&x.color===color)||variants.find(x=>x.color===color)||variants[0],activeImage=activeColorway?.image||product.image,activeCard=activeColorway?.cardColor||product.color,gallery=(product.media||[]).filter(x=>x.colorwayId===activeColorway?.id||(x.colorwayId==null&&!activeColorway)).sort((a,b)=>Number(b.isThumbnail)-Number(a.isThumbnail)||Number(a.sortOrder)-Number(b.sortOrder)),fallbackMedia={id:'fallback',url:activeImage,type:'image',alt:`${product.name} in ${color}`},activeMedia=gallery.find(x=>x.id===selectedMedia)||gallery[0]||fallbackMedia,activeMediaIndex=Math.max(0,gallery.findIndex(x=>x.id===activeMedia.id)),custom=product.customization?.enabled?product.customization:null,validCustom=!custom||!customText||(customText.trim().length>=Number(custom.min||1)&&customText.trim().length<=custom.max);
  const addItem=()=>{if(!variant)return;const customization=customText.trim()?{text:customText.trim(),placement,style,color:textColor}:null;add({...product,image:activeImage},variant,qty,customization);setAdded(true);setTimeout(()=>setAdded(false),1600)};const moveGallery=direction=>{if(gallery.length>1)setSelectedMedia(gallery[(activeMediaIndex+direction+gallery.length)%gallery.length].id)};const handleTouchStart=event=>setTouchStartX(event.touches[0]?.clientX??null);const handleTouchEnd=event=>{if(touchStartX===null)return;const delta=(event.changedTouches[0]?.clientX??touchStartX)-touchStartX;if(Math.abs(delta)>42)moveGallery(delta<0?1:-1);setTouchStartX(null)};
  return <div className="store product-page" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings} session={session} cartCount={count}/><main className="pdp shell"><div className="pdp-gallery"><div className="pdp-gallery-stage" onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} style={{'--card':activeCard}}><a className="back-link mobile-photo-back" href="/#catalog">← BACK TO THE DROP</a>{activeMedia.type==='video'?<video src={activeMedia.url} poster={activeMedia.poster||undefined} controls playsInline preload="metadata"/>:<img src={activeMedia.url} alt={activeMedia.alt||`${product.name} in ${color}`}/>} <strong className="custom-preview" style={{color:textColor==='Red'?'#ed3b24':textColor==='Cobalt'?'#163ea8':'#fff'}}>{customText}</strong>{activeMedia.type!=='video'&&<button type="button" className="pdp-fullscreen" aria-label="View photo full screen" onClick={()=>setZoomed(true)}><Icon name="expand" size={18}/></button>}{gallery.length>1&&<><button className="carousel-nav carousel-prev" aria-label="Previous product photo" onClick={()=>moveGallery(-1)}>←</button><button className="carousel-nav carousel-next" aria-label="Next product photo" onClick={()=>moveGallery(1)}>→</button></>}</div><div className="pdp-mini">{(gallery.length?gallery:[fallbackMedia]).map(item=><button className={activeMedia.id===item.id?'active':''} onClick={()=>setSelectedMedia(item.id)} key={item.id}>{item.type==='video'?<><video src={item.url} muted playsInline preload="metadata"/><i>PLAY</i></>:<img src={item.url} alt=""/>}</button>)}</div></div><div className="pdp-content"><a className="back-link" href="/#catalog">← BACK TO THE DROP</a><span className="kicker">CAMPUS EDITION</span><h1>{product.name}</h1><a href="#reviews" className="title-rating"><b>{stars(product.rating)}</b><span>{product.rating?`${Number(product.rating).toFixed(1)} · ${product.reviewCount} verified reviews`:'NEW · BE THE FIRST TO REVIEW'}</span></a><div className="product-price"><strong>{money(variant?.price||product.price)}</strong>{product.compare_price>product.price&&<s>{money(product.compare_price)}</s>}<small>Tax included</small></div><p>{product.description}</p><div className="variant-block colorway-selector"><header><b>COLOUR</b><span>{color}</span></header><div>{colors.map(x=>{const swatch=colorways.find(c=>c.name===x)?.swatch;return <button className={color===x?'active':''} onClick={()=>setColor(x)} key={x}><i style={{background:swatch}}/>{x}</button>})}</div></div><div className="variant-block" id="select-size"><header><b>SELECT SIZE</b><button type="button" className="size-guide-link" onClick={()=>setSizeGuide(true)}>SIZE GUIDE ↗</button></header><div>{sizes.map(x=><button className={size===x?'active':''} onClick={()=>setSize(x)} key={x}>{x}</button>)}</div></div><p className="pdp-material-copy">Heavyweight brushed cotton built for late labs and early Delhi winters.</p>{product.features?.length>0&&<ul className="product-features">{product.features.map((feature,index)=><li key={index}>{feature}</li>)}</ul>}{custom&&<section className="customizer"><span className="kicker">MAKE IT YOURS</span><h2>Add your name to the story.</h2><label>{custom.label}<div><input value={customText} minLength={custom.min} maxLength={custom.max} pattern={custom.pattern} onChange={e=>setCustomText(e.target.value)} placeholder="Your name or nickname"/><b>{customText.length}/{custom.max}</b></div></label><div className="custom-grid"><label>PLACEMENT<select value={placement} onChange={e=>setPlacement(e.target.value)}>{custom.placements.map(x=><option key={x}>{x}</option>)}</select></label><label>STYLE<select value={style} onChange={e=>setStyle(e.target.value)}>{custom.styles.map(x=><option key={x}>{x}</option>)}</select></label><label>TEXT COLOUR<select value={textColor} onChange={e=>setTextColor(e.target.value)}>{custom.colors.map(x=><option key={x}>{x}</option>)}</select></label></div><p>+ {money(custom.surcharge)} · adds approximately {custom.addedDays} working days. {custom.returnPolicy}</p></section>}<div className="stock-line"><span>●</span>{variant?`${variant.stock} available in this option`:'Out of stock'}<small>{variant?.sku}</small></div><div className="buy-row"><div><button onClick={()=>setQty(Math.max(1,qty-1))}>−</button><b>{qty}</b><button onClick={()=>setQty(Math.min(10,qty+1))}>+</button></div><button disabled={!variant||!validCustom} onClick={addItem}>{added?'ADDED TO BAG ✓':`ADD TO BAG · ${money(((variant?.price||product.price)+(customText?Number(custom?.surcharge||0):0))*qty)}`}<Icon name="arrow"/></button></div><div className="pdp-details"><details open><summary>MATERIAL & FEEL <span>+</span></summary><p>Comfort-first construction selected for repeat wear across campus and beyond.</p></details><details><summary>DELIVERY & RETURNS <span>+</span></summary><p>Dispatch target: 2–4 working days, plus customization time where applicable.</p></details></div></div></main><Reviews product={product} reviews={reviews}/>{sizeGuide&&<div className="size-guide-backdrop" role="dialog" aria-modal="true" aria-label="Size guide" onClick={()=>setSizeGuide(false)}><div className="size-guide" onClick={event=>event.stopPropagation()}><header><div><span className="kicker">SIZE CHART</span><h2>{SIZE_CHART.title}</h2><p>{SIZE_CHART.strap}</p></div><button type="button" onClick={()=>setSizeGuide(false)} aria-label="Close size guide"><Icon name="close"/></button></header><div className="size-guide-scroll"><table><thead><tr>{SIZE_CHART.columns.map((column,index)=><th key={column}>{column}{index?<i>inches</i>:null}</th>)}</tr></thead><tbody>{SIZE_CHART.rows.map(row=><tr key={row[0]} className={row[0]===size?'is-yours':''}>{row.map((cell,index)=><td key={index}>{index?cell:<b>{cell}</b>}</td>)}</tr>)}</tbody></table></div><div className="size-guide-fit"><b>OVERSIZED FIT</b><p>{SIZE_CHART.fit}</p></div><ul className="size-guide-features">{SIZE_CHART.features.map(feature=><li key={feature}>{feature}</li>)}</ul><small>{SIZE_CHART.tolerance}</small></div></div>}{zoomed&&<div className="pdp-zoom" role="dialog" aria-modal="true" aria-label={`${product.name} enlarged`} onClick={()=>setZoomed(false)}><button type="button" className="pdp-zoom-close" aria-label="Close enlarged photo"><Icon name="close" size={18}/></button><div className="pdp-zoom-canvas"><img src={activeMedia.url} alt={activeMedia.alt||`${product.name} in ${color}`}/></div><small>TAP ANYWHERE TO CLOSE · PINCH TO ZOOM FURTHER</small></div>}{sizePrompt&&<aside className="size-prompt" role="status" aria-live="polite" onMouseEnter={()=>setPromptHeld(true)} onMouseLeave={()=>setPromptHeld(false)} onFocusCapture={()=>setPromptHeld(true)} onBlurCapture={()=>setPromptHeld(false)}><button type="button" className="size-prompt-close" aria-label="Dismiss size picker" onClick={()=>setSizePrompt(false)}><Icon name="close" size={13}/></button><span className="kicker">ONE MORE THING</span><strong>PICK YOUR SIZE</strong><p>Choose a size for {product.name} before it goes in your bag.</p><div className="size-prompt-sizes">{sizes.map(x=><button type="button" className={size===x?'active':''} key={x} onClick={()=>{setSize(x);setSizePrompt(false)}}>{x}</button>)}</div></aside>}<div className="mobile-buy"><div><b>{money(variant?.price||product.price)}</b><span>{size} · {color}</span></div><button disabled={!variant||!validCustom} onClick={addItem}>{added?'ADDED ✓':`ADD TO BAG · ${money(((variant?.price||product.price)+(customText?Number(custom?.surcharge||0):0))*qty)}`}</button><a className="mobile-bag-link" href="/cart" aria-label={`View bag, ${count} ${count===1?'item':'items'}`} title="View bag"><Icon name="bag"/><b className="mobile-bag-count" aria-hidden="true">{count>99?'99+':count}</b></a></div><Footer settings={store.settings}/></div>
}

function Reviews({product,reviews}){return <section className="reviews shell" id="reviews"><header><div><span className="kicker">VERIFIED CAMPUS VOICES</span><h2>Worn. Lived in.<br/>Honestly reviewed.</h2></div><div className="review-score"><b>{product.rating?Number(product.rating).toFixed(1):'—'}</b><span>{stars(product.rating)}</span><small>{product.reviewCount} approved reviews</small></div></header><div className="review-list">{reviews.length?reviews.map(review=><article key={review.id}><div><b>{stars(review.rating)}</b><span>VERIFIED PURCHASE</span></div><h3>{review.title||'Campus approved'}</h3><p>{review.body}</p><footer><span>{review.full_name}</span><time>{new Date(review.submitted_at).toLocaleDateString('en-IN')}</time></footer></article>):<div className="empty-reviews"><strong>THE FIRST WORD IS YOURS.</strong><span>Delivered customers can review this product from My Orders.</span></div>}</div></section>}

function AddressCapture({store,session,addresses,selectedAddressId,onSelect,onSaved,initiallyOpen=false}){const [open,setOpen]=useState(initiallyOpen||!addresses.length),[saving,setSaving]=useState(false),[error,setError]=useState(''),[form,setForm]=useState({hostelId:'',roomNumber:'',isDefault:!addresses.length});const set=(key,value)=>setForm(current=>({...current,[key]:value}));useEffect(()=>{if(!initiallyOpen&&addresses.length)setOpen(false)},[addresses.length,initiallyOpen]);const reset=()=>setForm({hostelId:'',roomNumber:'',isDefault:false});const save=async e=>{e.preventDefault();setError('');if(!form.hostelId||!form.roomNumber.trim()){setError('Select your hostel and enter your room number.');return}setSaving(true);try{const result=await api('/api/account/addresses',{method:'POST',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify({...form,recipientName:session?.user?.fullName||'IIT Delhi customer',phone:session?.user?.phone||''})});onSaved(result.address);setOpen(false);reset()}catch(e){setError(e.message)}finally{setSaving(false)}};const addressLabel=address=>address.hostel_name?`${address.hostel_name} · Room ${address.room_number}`:`${address.line_1||'Saved delivery address'}${address.line_2?`, ${address.line_2}`:''}`;return <section className="cart-address-panel"><header><div><span className="kicker">DELIVERY SETTINGS</span><h2>Hostel delivery, for now.</h2><p>Choose your IIT Delhi hostel and type your room number. We’ll add full address selection later.</p></div>{addresses.length>0&&<button type="button" className="address-add-toggle" onClick={()=>setOpen(value=>!value)}>{open?'CANCEL':'ADD HOSTEL DELIVERY'} <Icon name={open?'close':'plus'}/></button>}</header>{addresses.length>0&&<div className="saved-addresses">{addresses.map(address=><label className={`saved-address ${selectedAddressId===address.id?'selected':''}`} key={address.id}><input type="radio" name="cart-address" checked={selectedAddressId===address.id} onChange={()=>onSelect(address.id)}/><span><b>HOSTEL DELIVERY</b>{address.is_default&&<em>DEFAULT</em>}<small>{addressLabel(address)}</small></span></label>)}</div>}{open&&<form className="address-form" onSubmit={save}><label>HOSTEL<select required value={form.hostelId} onChange={e=>set('hostelId',e.target.value)}><option value="">SELECT YOUR HOSTEL</option>{(store.hostels||[]).map(hostel=><option value={hostel.id} key={hostel.id}>{hostel.name}</option>)}</select></label><label>ROOM NUMBER<input required maxLength="30" value={form.roomNumber} onChange={e=>set('roomNumber',e.target.value)} placeholder="e.g. A-204" autoComplete="off"/></label><label className="address-default-check"><input type="checkbox" checked={form.isDefault} onChange={e=>set('isDefault',e.target.checked)}/> Make this my default delivery setting</label>{error&&<div className="form-error">{error}</div>}<button disabled={saving||!form.hostelId||!form.roomNumber.trim()}>{saving?'SAVING DELIVERY SETTING…':'SAVE DELIVERY SETTING'} <Icon name="arrow"/></button></form>}</section>}
function CartPage({store,session}){const {cart,count,update,remove,clear,notice,dismissNotice}=useCart(),[quoteResult,setQuote]=useState(null),[couponCode,setCouponCode]=useState(''),[couponDraft,setCouponDraft]=useState(''),[error,setError]=useState(''),[order,setOrder]=useState(null),[busy,setBusy]=useState(false),[addresses,setAddresses]=useState([]),[selectedAddressId,setSelectedAddressId]=useState(''),[addressError,setAddressError]=useState(''),checkoutReady=Boolean(store.payment?.live||store.payment?.demoAllowed);const quoteKey=JSON.stringify([cart,couponCode,session?.user?.id]),quote=quoteResult?.requestKey===quoteKey?quoteResult.value:null;useEffect(()=>{if(!session){setAddresses([]);setSelectedAddressId('');return}api('/api/account/addresses',{headers:{'X-CSRF-Token':session.csrf}}).then(result=>{const rows=result.addresses||[];setAddresses(rows);setSelectedAddressId(current=>current||rows.find(x=>x.is_default)?.id||rows[0]?.id||'')}).catch(e=>setAddressError(e.message))},[session]);useEffect(()=>{
  let current=true;setQuote(null);setError('');
  if(!cart.length)return;
  api('/api/checkout/quote',{method:'POST',body:JSON.stringify({items:cart.map(x=>({variantId:x.variantId,qty:x.qty,customization:x.customization})),couponCode})})
    .then(value=>{if(current)setQuote({requestKey:quoteKey,value})})
    .catch(error=>{if(current){setQuote(null);setError(error.message)}});
  return()=>{current=false};
},[quoteKey]);const addSavedAddress=address=>{setAddresses(rows=>[address,...rows.filter(x=>x.id!==address.id)]);setSelectedAddressId(address.id);setAddressError('')};const orderPayload={items:cart.map(x=>({variantId:x.variantId,qty:x.qty,customization:x.customization})),couponCode:couponCode.trim(),addressId:selectedAddressId};const finishOrder=result=>{clear();setBusy(false);location.href='/account'};const placeDemoOrder=async()=>{if(!checkoutReady){setError('Checkout is temporarily unavailable until Razorpay is configured.');return}if(!session||!quote)return;if(!selectedAddressId){setAddressError('Select or save a delivery address before checkout.');return}setBusy(true);setError('');try{const result=await api('/api/checkout/demo-order',{method:'POST',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify(orderPayload)});finishOrder(result)}catch(e){setError(e.message);setBusy(false)}};const placeRazorpayOrder=async()=>{if(!session||!quote)return;if(!selectedAddressId){setAddressError('Select or save a delivery address before checkout.');return}setBusy(true);setError('');try{const result=await api('/api/checkout/razorpay/order',{method:'POST',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify(orderPayload)});setQuote({requestKey:quoteKey,value:result.quote});await loadExternalScript('https://checkout.razorpay.com/v1/checkout.js');const checkout=new window.Razorpay({key:result.payment.keyId,amount:result.payment.amount,currency:result.payment.currency,name:store.settings.brand,description:'The IIT Delhi Drop',order_id:result.payment.orderId,prefill:{name:session.user.fullName||'',email:session.user.email||'',contact:session.user.phone||''},theme:{color:store.settings.primary},handler:async response=>{try{const verified=await api('/api/checkout/razorpay/verify',{method:'POST',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify({...response,internalOrderId:result.payment.internalOrderId,attemptId:result.payment.attemptId})});finishOrder(verified)}catch(e){setError(e.message);setBusy(false)}},modal:{ondismiss:()=>setBusy(false)}});checkout.on('payment.failed',response=>{setError(response?.error?.description||'Razorpay payment failed.');setBusy(false)});checkout.open()}catch(e){setError(e.message);setBusy(false)}};return <div className="store cart-page" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings} session={session} cartCount={count}/><main className="cart-shell shell"><header><span className="kicker">YOUR SELECTION</span><h1>Bag full of<br/><em>good ideas.</em></h1><p>{count} {count===1?'piece':'pieces'} ready for review.</p></header>{notice&&<div className="cart-discontinued-notice" role="status"><span>{notice}</span><button type="button" onClick={dismissNotice} aria-label="Dismiss discontinued item notice">×</button></div>}{order&&<div className="order-success"><span className="kicker">{store.payment?.live?'PAYMENT CONFIRMED':'DEMO ORDER CONFIRMED'}</span><h2>That drop is yours.</h2><p>Order <b>#{order.orderNo}</b> is confirmed. We will let you know as soon as it is on its way.</p><a href="/account">VIEW MY ACCOUNT <Icon name="arrow"/></a></div>}{session&&<AddressCapture store={store} session={session} addresses={addresses} selectedAddressId={selectedAddressId} onSelect={setSelectedAddressId} onSaved={addSavedAddress}/>}<div className="cart-layout"><section>{cart.length?<>{cart.map(item=><article className="cart-item-v2" key={item.key}><a href={`/products/${item.slug}`}><img src={item.image}/></a><div><span>{item.sku}</span><h2>{item.name}</h2><p>{item.size} · {item.color}</p>{item.customization&&<div className="cart-custom"><b>MAKE IT YOURS · {item.customization.placement}</b><span>“{item.customization.text}” · {item.customization.style}</span></div>}</div><div><b>{money(item.price*item.qty)}</b><span><button onClick={()=>item.qty<=1?remove(item.key):update(item.key,item.qty-1)}>−</button>{item.qty}<button onClick={()=>update(item.key,item.qty+1)} disabled={item.qty>=10}>+</button></span></div></article>)}<a className="back-to-store" href="/#catalog">GO TO MERCHANDISE <Icon name="arrow"/></a></>:<div className="empty-cart"><strong>ROOM FOR BIG IDEAS.</strong><a href="/#catalog">EXPLORE THE DROP <Icon name="arrow"/></a></div>}</section><aside className="order-summary"><span className="kicker">ORDER SUMMARY</span><h2>Ready when you are.</h2>{addressError&&<p className="quote-error">{addressError}</p>}{error&&<p className="quote-error">{error}</p>}<form className="coupon-entry" onSubmit={event=>{event.preventDefault();setCouponCode(couponDraft.trim().toUpperCase())}}><label>COUPON CODE<div className="coupon-input-row"><input value={couponDraft} maxLength="40" disabled={busy} onChange={event=>setCouponDraft(event.target.value.toUpperCase())} placeholder="ENTER CODE"/><button type="submit" disabled={busy||!couponDraft.trim()}>APPLY</button>{couponCode&&<button type="button" disabled={busy} title="Remove coupon" aria-label="Remove coupon" onClick={()=>{setCouponCode('');setCouponDraft('')}}><Icon name="close" size={16}/></button>}</div></label>{quote?.couponCode&&<p className="coupon-applied" role="status">{quote.couponCode} applied</p>}</form><div><span>Products</span><b>{quote?money(quote.subtotal):'—'}</b></div>{quote?.customizationTotal>0&&<div><span>Customization</span><b>{money(quote.customizationTotal)}</b></div>} {quote?.discount>0&&<div><span>Coupon discount</span><b>−{money(quote.discount)}</b></div>}<div><span>Shipping</span><b>{quote?money(quote.shipping||0):'—'}</b></div><div className="total"><span>TOTAL</span><b>{quote?money(quote.total):'—'}</b></div>{session&&checkoutReady?<button disabled={!quote||busy||!cart.length||!selectedAddressId} onClick={store.payment?.live?placeRazorpayOrder:placeDemoOrder}>{busy?(store.payment?.live?'OPENING RAZORPAY…':'CONFIRMING…'):(store.payment?.live?`PAY SECURELY · ${quote?money(quote.total):'—'}`:`PLACE DEMO ORDER · ${quote?money(quote.total):'—'}`)}<Icon name="arrow"/></button>:session?<button className="checkout-unavailable" disabled>CHECKOUT UNAVAILABLE <Icon name="arrow"/></button>:<a href="/login">SIGN IN TO CHECK OUT <Icon name="arrow"/></a>}<small>{session&&!selectedAddressId?'Choose your hostel and room number above and save it to continue.':store.payment?.live?'Secure checkout is powered by Razorpay. Your order is created only after verified payment.':store.payment?.demoAllowed?'Demo checkout is available only in a non-production preview.':'Checkout is temporarily unavailable until Razorpay is configured.'}</small></aside></div></main></div>}
function LoginPage({store,onSession}){const [step,setStep]=useState('phone'),[phone,setPhone]=useState(''),[challenge,setChallenge]=useState(null),[otp,setOtp]=useState(''),[demoOtp,setDemoOtp]=useState(''),[error,setError]=useState(''),[busy,setBusy]=useState(false),[resendCooldown,setResendCooldown]=useState(0),[session,setSession]=useState(null),[profile,setProfile]=useState({fullName:'',email:'',affiliation:'alumni',isHostelResident:false,hostelId:'',roomNumber:''}),loginReady=Boolean(store.sms?.configured||store.sms?.demoAllowed);useEffect(()=>{if(resendCooldown<=0)return;const timer=setInterval(()=>setResendCooldown(value=>Math.max(0,value-1)),1000);return()=>clearInterval(timer)},[resendCooldown]);const requestOtp=async e=>{e?.preventDefault?.();setError('');if(!loginReady){setError('Customer sign-in is temporarily unavailable until MSG91 is configured.');return}if(phone.length!==10){setError('Enter a valid 10-digit Indian mobile number.');return}setBusy(true);try{const result=await api('/api/auth/otp/request',{method:'POST',body:JSON.stringify({phone})});setChallenge(result.challengeId);setDemoOtp(result.demoOtp||'');setStep('otp');setResendCooldown(30)}catch(e){setError(e.message)}finally{setBusy(false)}};const verify=async e=>{e.preventDefault();setBusy(true);setError('');try{const result=await api('/api/auth/otp/verify',{method:'POST',body:JSON.stringify({challengeId:challenge,otp})});setSession(result);onSession(result);if(result.needsProfile)setStep('profile');else location.href='/account'}catch(e){setError(e.message)}finally{setBusy(false)}};const save=async e=>{e.preventDefault();setBusy(true);setError('');try{await api('/api/account/profile',{method:'PATCH',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify(profile)});location.href='/account'}catch(e){setError(e.message)}finally{setBusy(false)}};return <div className="auth-page" style={themeVars(store.settings)}><div className="auth-art"><a href="/">THE IIT DELHI DROP <span>●</span></a><div><small>{step==='phone'?'YOUR CAMPUS ACCOUNT':step==='otp'?'ONE QUICK CHECK':'MAKE IT YOURS'}</small><h1>{step==='phone'?'One number.\nEvery drop.':step==='otp'?'Check your\nmessages.':'Tell us\nwho you are.'}</h1><p>Save addresses, follow orders, unlock loyalty cash and review what you have actually worn.</p></div><span>SECURE PHONE ACCESS / NO PASSWORD TO REMEMBER</span></div><div className="auth-panel">{step==='phone'&&<form onSubmit={requestOtp}><span className="step-count">01 / 03</span><h2>Welcome back—or welcome in.</h2><p>Use your phone once. We’ll recognize you next time without another password.</p><label>INDIAN MOBILE NUMBER<div className="phone-input"><span>+91</span><input type="tel" inputMode="numeric" autoComplete="tel" maxLength="10" required value={phone} onChange={e=>setPhone(e.target.value.replace(/\D/g,''))} placeholder="98765 43210"/></div></label>{!loginReady&&<div className="form-error">Customer sign-in is temporarily unavailable until MSG91 is configured.</div>}{error&&<div className="form-error">{error}</div>}<button disabled={busy||!loginReady}>{busy?'SENDING…':loginReady?'SEND SECURE CODE':'SIGN-IN NOT CONFIGURED'}<Icon name="arrow"/></button><small className="auth-trust">No password to remember. Your number is used only for account access and order updates.</small></form>}{step==='otp'&&<form onSubmit={verify}><span className="step-count">02 / 03</span><button type="button" className="text-back" onClick={()=>{setStep('phone');setOtp('');setError('')}}>← CHANGE NUMBER</button><h2>Enter the code.</h2><p>We sent a single-use code to +91 {phone}. It expires in five minutes.</p>{demoOtp&&<div className="demo-otp"><span>LOCAL REVIEW CODE</span><b>{demoOtp}</b></div>}<label>VERIFICATION CODE<input className="otp-input" inputMode="numeric" autoComplete="one-time-code" maxLength="4" required value={otp} onChange={e=>setOtp(e.target.value.replace(/\D/g,'').slice(0,4))} placeholder="••••"/></label>{error&&<div className="form-error">{error}</div>}<button disabled={busy||otp.length!==4}>{busy?'VERIFYING…':'VERIFY & CONTINUE'}<Icon name="arrow"/></button><button type="button" className="text-back resend-button" disabled={busy||resendCooldown>0} onClick={requestOtp}>{resendCooldown?`RESEND CODE IN ${resendCooldown}S`:'RESEND CODE'}</button></form>}{step==='profile'&&<form onSubmit={save}><span className="step-count">03 / 03</span><h2>Make your account useful.</h2><p>Complete this once so checkout, hostel delivery and loyalty rewards stay personal.</p><label>FULL NAME<input autoComplete="name" required value={profile.fullName} onChange={e=>setProfile({...profile,fullName:e.target.value})}/></label><label>EMAIL · OPTIONAL<input autoComplete="email" type="email" value={profile.email} onChange={e=>setProfile({...profile,email:e.target.value})}/></label><label>AFFILIATION<select value={profile.affiliation} onChange={e=>setProfile({...profile,affiliation:e.target.value,isHostelResident:false,hostelId:''})}><option value="student">Current student</option><option value="faculty_staff">Faculty / staff</option><option value="alumni">Alumni</option><option value="visitor_other">Visitor / other</option></select></label>{profile.affiliation==='student'&&<label className="resident-check"><input type="checkbox" checked={profile.isHostelResident} onChange={e=>setProfile({...profile,isHostelResident:e.target.checked,hostelId:''})}/><i/>I currently live in an IIT Delhi hostel</label>}{profile.affiliation==='student'&&profile.isHostelResident&&<div className="two-fields"><label>HOSTEL<select required value={profile.hostelId} onChange={e=>setProfile({...profile,hostelId:e.target.value})}><option value="">Select hostel</option>{store.hostels.map(x=><option value={x.id} key={x.id}>{x.name}</option>)}</select></label><label>ROOM · OPTIONAL<input value={profile.roomNumber} onChange={e=>setProfile({...profile,roomNumber:e.target.value})}/></label></div>}{error&&<div className="form-error">{error}</div>}<button disabled={busy}>{busy?'SAVING…':'SAVE & OPEN ACCOUNT'}<Icon name="arrow"/></button></form>}</div></div>}

function AccountAddresses({store,session}){const [addresses,setAddresses]=useState([]),[adding,setAdding]=useState(false),[editing,setEditing]=useState(''),[draft,setDraft]=useState(''),[message,setMessage]=useState(''),[error,setError]=useState('');const load=()=>api('/api/account/addresses',{headers:{'X-CSRF-Token':session.csrf}}).then(result=>setAddresses(result.addresses||[])).catch(e=>setError(e.message));useEffect(()=>{load()},[session]);const addSavedAddress=address=>{setAddresses(rows=>[address,...rows.filter(x=>x.id!==address.id)]);setAdding(false);setMessage('Address saved.');setError('')};const patchAddress=async(id,payload)=>{setError('');try{const result=await api(`/api/account/addresses/${id}`,{method:'PATCH',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify(payload)});setAddresses(rows=>rows.map(address=>payload.isDefault?{...address,is_default:address.id===id}:address.id===id?result.address:address));setEditing('');setMessage(payload.isDefault?'Default address updated.':'Address label updated.')}catch(e){setError(e.message)}};const deleteAddress=async address=>{if(!window.confirm(`Remove ${address.label} address?`))return;setError('');try{await api(`/api/account/addresses/${address.id}`,{method:'DELETE',headers:{'X-CSRF-Token':session.csrf}});const remaining=addresses.filter(x=>x.id!==address.id);setAddresses(remaining);if(address.is_default&&remaining[0])await patchAddress(remaining[0].id,{isDefault:true});setMessage('Address removed.')}catch(e){setError(e.message)}};return <div className="account-addresses"><span className="kicker">DELIVERY BOOK</span><h1>Addresses that<br/><em>move with you.</em></h1><p>Save an IIT Delhi hostel and room number for delivery. Full address selection will be added later.</p>{message&&<div className="account-message">{message}</div>}{error&&<div className="form-error">{error}</div>}<button className="add-address-button" onClick={()=>setAdding(true)}>{addresses.length?'ADD ANOTHER ADDRESS':'ADD AN ADDRESS'} <Icon name="plus"/></button>{adding&&<div className="account-address-composer"><div className="address-composer-heading"><div><span className="kicker">NEW DELIVERY ADDRESS</span><h2>Where should we send it?</h2></div><button className="muted-action" onClick={()=>setAdding(false)}>CANCEL</button></div><AddressCapture store={store} session={session} addresses={addresses} selectedAddressId={addresses.find(x=>x.is_default)?.id||addresses[0]?.id||''} onSelect={()=>{}} onSaved={addSavedAddress} initiallyOpen/></div>}{addresses.length>0&&<section className="account-saved-addresses"><span className="kicker">SAVED ADDRESSES</span>{addresses.map(address=><article key={address.id} className="account-address-row"><div><b>{address.label}</b>{address.is_default&&<em>DEFAULT</em>}<p>{address.hostel_name?`${address.hostel_name} · Room ${address.room_number}`:`${address.line_1||'Saved delivery address'}${address.line_2?`, ${address.line_2}`:''}`}<br/>{address.recipient_name}</p></div><div className="address-row-actions">{editing===address.id?<><input value={draft} maxLength="30" onChange={e=>setDraft(e.target.value)} /><button onClick={()=>patchAddress(address.id,{label:draft})}>SAVE</button><button className="muted-action" onClick={()=>setEditing('')}>CANCEL</button></>:<><button onClick={()=>{setEditing(address.id);setDraft(address.label)}}>RENAME</button>{!address.is_default&&<button onClick={()=>patchAddress(address.id,{isDefault:true})}>MAKE DEFAULT</button>}<button className="muted-action" onClick={()=>deleteAddress(address)}>DELETE</button></>}</div></article>)}</section>}</div>}

function AccountProfileEditor({store,session,onSession}){const [form,setForm]=useState({fullName:session.user.fullName||'',email:session.user.email||'',affiliation:session.user.affiliation||'alumni',isHostelResident:!!session.user.isHostelResident,hostelId:session.user.hostelId||'',roomNumber:session.user.roomNumber||''}),[notice,setNotice]=useState(''),[error,setError]=useState(''),[busy,setBusy]=useState(false);const save=async e=>{e.preventDefault();setBusy(true);setNotice('');setError('');try{const result=await api('/api/account/profile',{method:'PATCH',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify(form)});onSession({...session,user:result.user});setNotice('Profile saved. You will stay signed in.') }catch(e){setError(e.message)}finally{setBusy(false)}};return <form className="account-profile-editor" onSubmit={save}><span className="kicker">PROFILE DETAILS</span><h2>Edit your account once.</h2><p>These details are saved to your existing phone session. You do not need to verify your number again after editing.</p><label>FULL NAME<input required autoComplete="name" value={form.fullName} onChange={e=>setForm({...form,fullName:e.target.value})}/></label><label>EMAIL · OPTIONAL<input type="email" autoComplete="email" value={form.email} onChange={e=>setForm({...form,email:e.target.value})}/></label><label>AFFILIATION<select value={form.affiliation} onChange={e=>setForm({...form,affiliation:e.target.value})}><option value="student">Student</option><option value="faculty_staff">Faculty / staff</option><option value="alumni">Alumni</option><option value="visitor_other">Visitor / other</option></select></label>{form.affiliation==='student'&&<><label className="address-default-check"><input type="checkbox" checked={form.isHostelResident} onChange={e=>setForm({...form,isHostelResident:e.target.checked})}/> I live in a hostel</label>{form.isHostelResident&&<div className="two-fields"><label>HOSTEL<select required value={form.hostelId} onChange={e=>setForm({...form,hostelId:e.target.value})}><option value="">Choose your hostel</option>{(store.hostels||[]).map(hostel=><option key={hostel.id} value={hostel.id}>{hostel.name}</option>)}</select></label><label>ROOM NUMBER<input required value={form.roomNumber} onChange={e=>setForm({...form,roomNumber:e.target.value})}/></label></div>}</>}{notice&&<div className="editor-notice">{notice}</div>}{error&&<div className="form-error">{error}</div>}<button disabled={busy}>{busy?'SAVING…':'SAVE PROFILE'} <Icon name="arrow"/></button></form>}

function AccountPage({store,session,onSession}){const initialTab=new URLSearchParams(location.search).get('tab'),[orders,setOrders]=useState([]),[reviewing,setReviewing]=useState(null),[message,setMessage]=useState(''),[tab,setTab]=useState(['orders','profile','addresses','security'].includes(initialTab)?initialTab:'orders');useEffect(()=>{if(!session)return;api('/api/account/orders',{headers:{'X-CSRF-Token':session.csrf}}).then(result=>setOrders(result.orders||[])).catch(error=>setMessage(error.message||'Your orders could not be loaded.'));},[session]);if(!session)return <div className="store" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings}/><div className="account-guest"><span className="kicker">MY DROP</span><h1>Your orders,<br/>your story.</h1><p>Sign in with your phone to see orders, addresses, loyalty cash and customizations.</p><a href="/login">SIGN IN SECURELY <Icon name="arrow"/></a></div></div>;return <div className="store account-page" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings} session={session}/><main className="account-shell shell"><aside><div className="avatar">{session.user.fullName?.split(' ').map(x=>x[0]).join('').slice(0,2)||'ID'}</div><h2>{session.user.fullName||'Complete your profile'}</h2><span>{session.user.phone}</span><nav><button className={tab==='orders'?'active':''} onClick={()=>setTab('orders')}>My orders</button><button className={tab==='profile'?'active':''} onClick={()=>setTab('profile')}>Profile</button><button className={tab==='addresses'?'active':''} onClick={()=>setTab('addresses')}>Addresses</button><button className={tab==='security'?'active':''} onClick={()=>setTab('security')}>Security</button></nav></aside><section>{tab==='addresses'?<AccountAddresses store={store} session={session}/>:tab==='profile'?<AccountProfileEditor store={store} session={session} onSession={onSession}/>:<><span className="kicker">MY ACCOUNT</span><h1>From checkout<br/>to campus.</h1>{tab==='orders'&&<>{message&&<div className="account-message">{message}</div>}<div className="order-list"><span className="kicker">MY ORDERS</span>{orders.length?orders.map(order=><article key={order.id}><header><div><span>ORDER</span><b>#{order.order_no}</b></div><div><span>PLACED</span><b>{new Date(order.created_at).toLocaleDateString('en-IN')}</b></div><div><span>TOTAL</span><b>{money(order.total)}</b></div><strong className={order.fulfilment_status==='delivered'?'order-state delivered':'order-state'}>{order.fulfilment_status==='delivered'?'ORDER DELIVERED':String(order.fulfilment_status||order.order_status||'').replace(/_/g,' ')}</strong></header>{order.items.map(item=><div className="order-product" key={item.id}><a href={`/products/${item.slug}`}><img src={item.image}/></a><div><h3>{item.name}</h3><p>{item.size} · {item.color} · Qty {item.quantity}</p>{item.customization&&<span className="order-custom">CUSTOM: “{item.customization.text}” · {item.customization.placement} · {item.customization.style}</span>}</div><div>{item.deliveredAt&&!item.reviewed?<button onClick={()=>setReviewing(item)}>WRITE A REVIEW</button>:item.reviewed?<span className="reviewed">REVIEWED ✓</span>:<span>REVIEW AVAILABLE AFTER DELIVERY</span>}</div></div>)}</article>):<div className="no-orders"><b>NO ORDERS YET.</b><span>Anything you order will show up here.</span><a href="/#catalog">Find your first piece →</a></div>}</div></>}{tab==='security'&&<div className="account-placeholder"><h2>Phone-first security.</h2><p>Your account uses one-time codes. Logging in again with a verified number creates a session immediately; profile edits do not require another sign-in.</p></div>}</>}</section></main>{reviewing&&<ReviewModal item={reviewing} session={session} close={()=>setReviewing(null)} done={()=>{setReviewing(null);setMessage('Review submitted for moderation. Thank you.');api('/api/account/orders',{headers:{'X-CSRF-Token':session.csrf}}).then(x=>setOrders(x.orders))}}/>}</div>}
function ReviewModal({item,session,close,done}){const [rating,setRating]=useState(5),[title,setTitle]=useState(''),[body,setBody]=useState(''),[error,setError]=useState('');const wordCount=body.trim()?body.trim().split(/\s+/).length:0;const submit=async e=>{e.preventDefault();setError('');try{await api('/api/reviews',{method:'POST',headers:{'X-CSRF-Token':session.csrf},body:JSON.stringify({orderItemId:item.id,rating,title,body})});done()}catch(e){setError(e.message)}};return <div className="review-backdrop"><form className="review-modal" onSubmit={submit}><header><div><span>VERIFIED REVIEW</span><h2>{item.name}</h2></div><button type="button" onClick={close}><Icon name="close"/></button></header><div className="review-form"><label>YOUR RATING<div className="star-picker">{[1,2,3,4,5].map(x=><button type="button" className={rating>=x?'active':''} onClick={()=>setRating(x)} key={x}>★</button>)}</div></label><label>SHORT TITLE<input maxLength="100" value={title} onChange={e=>setTitle(e.target.value)} placeholder="What stood out?"/></label><label>YOUR REVIEW<textarea rows="7" value={body} onChange={e=>setBody(e.target.value)} placeholder="Fit, feel, quality, campus life…"/><span className={wordCount>400?'over':''}>{wordCount}/400 words</span></label>{error&&<div className="form-error">{error}</div>}<button disabled={wordCount>400}>SUBMIT FOR MODERATION <Icon name="arrow"/></button><small>Your review appears after moderation. Location metadata is removed from approved images.</small></div></form></div>}

function Studio({store}){const [session,setSession]=useState(null),[data,setData]=useState(null),[tab,setTab]=useState('Overview'),[email,setEmail]=useState(''),[password,setPassword]=useState(''),[error,setError]=useState('');const load=proof=>api('/api/admin/data',{headers:{'X-Admin-Proof':proof}}).then(setData);const login=async e=>{e.preventDefault();setError('');try{const result=await api('/api/admin/login',{method:'POST',body:JSON.stringify({email,password})});setSession(result);await load(result.proof)}catch(e){setError(e.message)}};const mutate=async(path,body,method='PATCH')=>{const result=await api(path,{method,headers:{'X-Admin-Proof':session.proof},body:JSON.stringify(body)});await load(session.proof);return result};const moderate=(id,status)=>mutate(`/api/admin/reviews/${id}`,{status});if(!session)return <div className="studio-login"><div><span>THE IIT DELHI DROP / STUDIO</span><h1>Control the<br/><em>whole story.</em></h1><p>PostgreSQL-backed catalogue, customers, customization, reviews and order operations.</p></div><form onSubmit={login}><h2>Administrator access</h2><label htmlFor="studio-email">EMAIL<input id="studio-email" type="email" autoComplete="username" value={email} onChange={e=>setEmail(e.target.value)}/></label><label htmlFor="studio-password">PASSWORD<input id="studio-password" type="password" autoComplete="current-password" value={password} onChange={e=>setPassword(e.target.value)}/></label>{error&&<div className="form-error">{error}</div>}<button>ENTER THE STUDIO <Icon name="arrow"/></button><small>Use the administrator credentials configured for this deployment.</small></form></div>;if(!data)return <Loading/>;const nav=['Overview','Products','Appearance','Policies','Reviews','Customers','Orders','Promotions','Motion','Payments'];return <div className="studio-v2"><aside><div className="studio-logo">THE IIT DELHI DROP<span>STUDIO / POSTGRESQL</span></div><nav>{nav.map(x=><button className={tab===x?'active':''} onClick={()=>setTab(x)} key={x}>{x}{x==='Reviews'&&<b>{data.reviews.filter(r=>r.status==='pending').length}</b>}</button>)}</nav><a href="/">View storefront ↗</a></aside><main><header><span>CONTROL ROOM / <b>{tab.toUpperCase()}</b></span><div><i/>SYSTEM HEALTHY</div></header>{tab==='Overview'&&<StudioOverview data={data}/>} {tab==='Products'&&<StudioProductsV2 data={data} mutate={mutate} proof={session.proof} reload={()=>load(session.proof)}/>} {tab==='Policies'&&<StudioPolicies settings={data.settings} mutate={mutate}/>} {tab==='Appearance'&&<StudioAppearance settings={data.settings} mutate={mutate}/>} {tab==='Reviews'&&<StudioReviews rows={data.reviews} moderate={moderate}/>} {tab==='Customers'&&<StudioCustomers rows={data.customers}/>} {tab==='Orders'&&<StudioOrders proof={session.proof} mutate={mutate}/>} {tab==='Promotions'&&<StudioPromotions rows={data.coupons||[]} mutate={mutate}/>} {tab==='Motion'&&<StudioMotion settings={data.settings} mutate={mutate}/>} {tab==='Payments'&&<StudioPayments payment={data.payment}/>}</main></div>}
function StudioTitle({kicker,title,copy}){return <header className="studio-title"><span>{kicker}</span><h1>{title}</h1><p>{copy}</p></header>}
function StudioOverview({data}){return <section className="studio-page"><StudioTitle kicker="COMMERCE OPERATIONS" title="A calmer control room." copy="The essential signals, without hiding operational risk."/><div className="studio-stats"><article><span>CUSTOMERS</span><b>{data.customers.length}</b><small>Verified accounts</small></article><article><span>ORDERS</span><b>{data.orders.length}</b><small>Across every state</small></article><article><span>REVIEWS PENDING</span><b>{data.reviews.filter(x=>x.status==='pending').length}</b><small>Moderation queue</small></article><article><span>DATABASE</span><b>PG</b><small>PostgreSQL primary</small></article></div><div className="studio-callout"><div><span>RELEASE POSTURE</span><h2>Strong foundations before live money.</h2><p>Customer identity, catalogue queries, customization, reviews and payment events now have explicit boundaries. Razorpay remains gated.</p></div><strong>DEMO<br/>PAYMENTS</strong></div></section>}
function StudioProducts_REMOVED(){return null;}

function StudioCatalogManager(){return null;}

function StudioVariants({productId,productName,proof,onNotice}){
  const [rows,setRows]=useState([]),[loading,setLoading]=useState(true),[busy,setBusy]=useState(''),[error,setError]=useState('');
  const headers={'X-Admin-Proof':proof};
  const load=async()=>{setLoading(true);setError('');try{const result=await api(`/api/admin/products/${productId}/variants`,{headers});setRows(result.variants||[])}catch(e){setError(e.message)}finally{setLoading(false)}};
  useEffect(()=>{load()},[productId]);
  // every write goes through the API so it lands in inventory_movements; reserved units are the
  // floor the server enforces, and we surface that here rather than let the save fail blind.
  const apply=async(variant,patch)=>{setBusy(variant.id);setError('');try{const result=await api(`/api/admin/variants/${variant.id}`,{method:'PATCH',headers,body:JSON.stringify(patch)});setRows(current=>current.map(row=>row.id===variant.id?result.variant:row));onNotice&&onNotice(patch.stock!==undefined?`${variant.size} stock set to ${result.variant.stockOnHand}.`:'Variant updated.')}catch(e){setError(e.message);setRows(current=>[...current])}finally{setBusy('')}};
  const commitNumber=(variant,field,raw,currentValue)=>{const text=String(raw).trim();if(field==='priceOverride'&&text===''){if(currentValue!==null)apply(variant,{priceOverride:null});return}const value=Number(text);if(!Number.isFinite(value)||value===currentValue)return;apply(variant,{[field]:field==='priceOverride'?Math.round(value*100):value});};
  const groups=rows.reduce((map,row)=>{const key=row.colorwayName||row.color||'Default';(map[key]=map[key]||[]).push(row);return map},{});
  const lowStock=rows.filter(r=>r.active&&r.available<=3).length;
  return <section className="ops-editor stock-manager"><header><div><span>STOCK &amp; SIZES</span><h2>Every size, every colour.</h2><p>Set what is physically on hand. Units already reserved for paid orders cannot be removed, and each change is written to the stock ledger.</p></div><button type="button" onClick={load} disabled={loading}>{loading?'REFRESHING…':'REFRESH'}</button></header>
    {error&&<p className="stock-error">{error}</p>}
    {lowStock>0&&<p className="stock-low-banner">{lowStock} size{lowStock===1?'':'s'} at three units or fewer.</p>}
    {loading&&!rows.length?<p className="orders-desk-empty">Loading sizes…</p>:!rows.length?<div className="media-empty"><b>No sizes yet.</b><span>Add a colour to create its size variants.</span></div>:Object.entries(groups).map(([name,items])=><div className="stock-group" key={name}>
      <b className="stock-group-name">{name}</b>
      <div className="stock-scroll"><table className="stock-table"><thead><tr><th>Size</th><th>SKU</th><th>On hand</th><th>Reserved</th><th>Available</th><th>Price override</th><th>Live</th></tr></thead><tbody>
        {items.map(variant=><tr key={variant.id} className={variant.active?'':'is-off'}>
          <td><b>{variant.size}</b></td>
          <td className="stock-sku">{variant.sku}</td>
          <td><input type="number" min="0" step="1" defaultValue={variant.stockOnHand} disabled={busy===variant.id} onBlur={event=>commitNumber(variant,'stock',event.target.value,variant.stockOnHand)} aria-label={`${variant.size} units on hand`}/></td>
          <td className={variant.reserved?'stock-reserved':''}>{variant.reserved}</td>
          <td><b className={variant.available<=3?'stock-low':''}>{variant.available}</b></td>
          <td><input type="number" min="0" step="1" placeholder={'base'} defaultValue={variant.priceOverride===null?'':variant.priceOverride/100} disabled={busy===variant.id} onBlur={event=>commitNumber(variant,'priceOverride',event.target.value,variant.priceOverride)} aria-label={`${variant.size} price override in rupees`}/></td>
          <td><input type="checkbox" checked={variant.active} disabled={busy===variant.id} onChange={event=>apply(variant,{active:event.target.checked})} aria-label={`${variant.size} available to buy`}/></td>
        </tr>)}
      </tbody></table></div>
    </div>)}
  </section>}

function StudioProductsV2({data,mutate,proof,reload}){
  const rows=data.products||[],[selected,setSelected]=useState(rows[0]?.id||''),[draft,setDraft]=useState({}),[showCreate,setShowCreate]=useState(false),[showColorwayForm,setShowColorwayForm]=useState(false),[showArchived,setShowArchived]=useState(false),[search,setSearch]=useState(''),[statusFilter,setStatusFilter]=useState('all'),[notice,setNotice]=useState(''),[busy,setBusy]=useState(false),[confirmName,setConfirmName]=useState(''),[mediaAlt,setMediaAlt]=useState(''),[mediaColorway,setMediaColorway]=useState(''),[colorwayDraft,setColorwayDraft]=useState({name:'',swatch:'#17171d',cardColor:'#163ea8',sizes:'',skuPrefix:'',stock:'',photos:[]}),[create,setCreate]=useState({name:'',shortDescription:'',features:'',basePrice:0,comparePrice:0,cardColor:'#163ea8',colorName:'Default',swatch:'#17171d',sizes:'One Size',skuPrefix:'',stock:0,customizable:false,photos:[]});
  const term=search.trim().toLowerCase();const visibleRows=rows.filter(x=>(showArchived||x.status!=='archived')&&(statusFilter==='all'||x.status===statusFilter)&&(!term||String(x.name||'').toLowerCase().includes(term))),current=rows.find(x=>x.id===selected)||visibleRows[0],media=[...(current?.media||[])].sort((a,b)=>Number(a.sortOrder)-Number(b.sortOrder)),uploadMedia=media.filter(item=>mediaColorway?String(item.colorwayId)===String(mediaColorway):item.colorwayId==null);
  const featureList=value=>[...new Set(String(value||'').split(/\r?\n/).map(x=>x.trim()).filter(Boolean))].slice(0,12);
  useEffect(()=>{if(!current)return;setSelected(current.id);setDraft({name:current.name||'',shortDescription:current.short_description||'',description:current.description||'',featuresText:(Array.isArray(current.features)?current.features:[]).join('\n'),basePrice:Number(current.base_price||0),comparePrice:Number(current.compare_price||0),cardColor:current.card_color||'#163ea8',status:current.status||'draft',customizable:!!current.customizable});setMediaAlt(current.name||'');setMediaColorway('');setConfirmName('')},[current?.id]);
  const quickStatus=async product=>{const next=product.status==='active'?'draft':'active';setBusy(true);setNotice(next==='active'?'Publishing…':'Unpublishing…');try{await mutate('/api/admin/products/'+product.id,{status:next});await reload();setNotice(product.name+(next==='active'?' is live.':' moved to draft.'))}catch(error){setNotice(error.message)}finally{setBusy(false)}};const duplicate=async product=>{setBusy(true);setNotice('Duplicating '+product.name+'…');try{const result=await mutate('/api/admin/products/'+product.id+'/duplicate',{},'POST');setSelected(result.product.id);await reload();setNotice('Copied as a draft. Rename it and publish when ready.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};const save=async e=>{e.preventDefault();setBusy(true);setNotice('Saving product…');try{await mutate('/api/admin/products/'+current.id,{name:draft.name,shortDescription:draft.shortDescription,description:draft.description||draft.shortDescription,features:featureList(draft.featuresText),basePrice:Number(draft.basePrice),comparePrice:Number(draft.comparePrice),cardColor:draft.cardColor,status:draft.status,customizable:!!draft.customizable});await reload();setNotice('Product saved.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const uploadFiles=async(productId,files,colorwayId,altText)=>{for(const file of files){const response=await fetch('/api/admin/products/'+productId+'/media/upload',{method:'POST',headers:{'X-Admin-Proof':proof,'Content-Type':file.type,'X-Media-Name':encodeURIComponent(file.name),'X-Media-Alt':encodeURIComponent(altText||'Product photo'),...(colorwayId?{'X-Colorway-Id':colorwayId}:{})},body:file});const payload=await response.json();if(!response.ok)throw new Error(payload.error||'Could not upload '+file.name+'.')}};
  const createProduct=async e=>{e.preventDefault();setBusy(true);setNotice('Creating draft product…');try{const payload={name:create.name,shortDescription:create.shortDescription,description:create.shortDescription,features:featureList(create.features),basePrice:Number(create.basePrice),comparePrice:Number(create.comparePrice),cardColor:create.cardColor,colorName:create.colorName,swatch:create.swatch,sizes:create.sizes.split(',').map(x=>x.trim()).filter(Boolean),skuPrefix:create.skuPrefix,stock:Number(create.stock),customizable:create.customizable};const result=await mutate('/api/admin/products',payload,'POST');if(create.photos.length)await uploadFiles(result.product.id,create.photos,'',create.name);setSelected(result.product.id);setShowCreate(false);setCreate({name:'',shortDescription:'',features:'',basePrice:0,comparePrice:0,cardColor:'#163ea8',colorName:'Default',swatch:'#17171d',sizes:'One Size',skuPrefix:'',stock:0,customizable:false,photos:[]});await reload();setNotice('Draft created with its product photos. Choose a thumbnail before publishing.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const upload=async event=>{const files=[...event.target.files];if(!files.length)return;setBusy(true);setNotice('Uploading '+files.length+' photo'+(files.length===1?'':'s')+'…');try{await uploadFiles(current.id,files,mediaColorway,mediaAlt||current.name);await reload();setNotice('Photos uploaded. Select the image customers should see first.')}catch(error){setNotice(error.message)}finally{event.target.value='';setBusy(false)}};
  const move=async(item,direction)=>{const index=media.indexOf(item),target=media[index+direction];if(!target)return;try{await mutate('/api/admin/media/'+item.id,{sortOrder:target.sortOrder});await mutate('/api/admin/media/'+target.id,{sortOrder:item.sortOrder});await reload();setNotice('Gallery order updated.')}catch(error){setNotice(error.message)}};
  const setThumbnail=async item=>{if(item.type!=='image'||item.isThumbnail)return;setBusy(true);setNotice('Making this the thumbnail…');try{await mutate('/api/admin/media/'+item.id+'/thumbnail',{},'POST');await reload();setNotice('Thumbnail selected. Catalogue cards and product pages now use this image.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const removeMedia=async item=>{if(!confirm('Remove this '+item.type+' from the product gallery?'))return;setBusy(true);try{await mutate('/api/admin/media/'+item.id,{},'DELETE');await reload();setNotice('Media removed.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const archive=async()=>{setBusy(true);try{await mutate('/api/admin/products/'+current.id,{},'DELETE');setShowArchived(true);await reload();setNotice('Product archived. Historical orders remain intact.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const restore=async()=>{setBusy(true);try{await mutate('/api/admin/products/'+current.id+'/restore',{},'POST');await reload();setNotice('Product restored as a draft.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const permanentDelete=async()=>{if(confirmName!==current.name)return;setBusy(true);try{await mutate('/api/admin/products/'+current.id+'?permanent=true',{},'DELETE');setSelected('');await reload();setNotice('Unused draft permanently deleted.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};const openColorwayForm=()=>{setColorwayDraft({name:'',swatch:'#17171d',cardColor:current?.card_color||'#163ea8',sizes:'',skuPrefix:'',stock:'',photos:[]});setShowColorwayForm(true)};const createColorway=async e=>{e.preventDefault();if(!current)return;setBusy(true);setNotice('Adding product colour…');try{const payload={name:colorwayDraft.name,swatch:colorwayDraft.swatch,cardColor:colorwayDraft.cardColor,sizes:colorwayDraft.sizes.split(',').map(x=>x.trim()).filter(Boolean),skuPrefix:colorwayDraft.skuPrefix,stock:colorwayDraft.stock===''?undefined:Number(colorwayDraft.stock)};const result=await mutate('/api/admin/products/'+current.id+'/colorways',payload,'POST');if(colorwayDraft.photos.length)await uploadFiles(current.id,colorwayDraft.photos,result.colorway.id,colorwayDraft.name);setShowColorwayForm(false);setColorwayDraft({name:'',swatch:'#17171d',cardColor:'#163ea8',sizes:'',skuPrefix:'',stock:'',photos:[]});await reload();setMediaColorway(result.colorway.id);setNotice('Colour added. Uploads are linked to this colour and the first image is its thumbnail.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  return <section className="studio-page catalog-manager"><StudioTitle kicker="CATALOGUE OPERATIONS" title="Products & photos" copy="Create and maintain the products customers can buy. Add the story, colour, photos and stock, then choose the exact image that represents the product."/><div className="product-index-head"><div className="product-search"><input type="search" value={search} placeholder="Search products" onChange={event=>setSearch(event.target.value)} aria-label="Search products"/><select value={statusFilter} onChange={event=>setStatusFilter(event.target.value)} aria-label="Filter by status">{['all','active','draft','archived'].map(value=><option value={value} key={value}>{value==="all"?"All statuses":value.toUpperCase()}</option>)}</select></div><label><input type="checkbox" checked={showArchived} onChange={e=>setShowArchived(e.target.checked)}/> Show archived</label><button className="new-product-button" onClick={()=>setShowCreate(true)}><Icon name="plus"/> NEW PRODUCT</button></div><div className="product-workbench"><div className="product-index"><span>{visibleRows.length} {showArchived?'VISIBLE':'ACTIVE'} PRODUCTS</span>{visibleRows.map(x=><div className={'product-index-row '+(current?.id===x.id?'active':'')} key={x.id}><button type="button" className="product-index-pick" onClick={()=>setSelected(x.id)}><i style={{background:x.card_color}}/><span><b>{x.name}</b><small>{x.status} · {x.stock||0} units{Number(x.stock||0)<=3&&<em className="index-low">LOW</em>}</small></span><span className="product-index-photos">{x.media?.filter(m=>m.type==='image').length||0} photos</span></button><div className="product-index-actions"><button type="button" disabled={busy} onClick={()=>quickStatus(x)}>{x.status==='active'?'UNPUBLISH':'PUBLISH'}</button><button type="button" disabled={busy} onClick={()=>duplicate(x)}>DUPLICATE</button></div></div>)}{!visibleRows.length&&<p className="product-index-empty">No products match this view.</p>}</div>{current&&<div className="product-editor-stack"><form className="product-editor" onSubmit={save}><div className="editor-preview" style={{background:draft.cardColor}}><img src={current.image} alt=""/><span>{draft.status}</span><b>{draft.name}</b></div><div className="editor-fields"><h2>Product basics</h2><label>PRODUCT NAME<input required value={draft.name||''} onChange={e=>setDraft({...draft,name:e.target.value})}/></label><label>SHORT DESCRIPTION<input maxLength="240" value={draft.shortDescription||''} onChange={e=>setDraft({...draft,shortDescription:e.target.value})}/></label><label>FEATURES <small className="field-hint">One feature per line</small><textarea rows="5" maxLength="2000" value={draft.featuresText||''} onChange={e=>setDraft({...draft,featuresText:e.target.value})} placeholder="Heavyweight cotton · Printed on campus · Designed for everyday wear"/></label><div className="two-fields"><label>PRICE · PAISE<input type="number" min="0" required value={draft.basePrice||0} onChange={e=>setDraft({...draft,basePrice:Number(e.target.value)})}/></label><label>COMPARE AT · PAISE<input type="number" min="0" value={draft.comparePrice||0} onChange={e=>setDraft({...draft,comparePrice:Number(e.target.value)})}/></label></div><label>CARD COLOUR<input type="color" value={draft.cardColor||'#163ea8'} onChange={e=>setDraft({...draft,cardColor:e.target.value})}/></label><label>LIFECYCLE<select value={draft.status||'draft'} disabled={draft.status==='archived'} onChange={e=>setDraft({...draft,status:e.target.value})}><option value="draft">Draft</option><option value="active">Active</option><option value="archived">Archived</option></select></label><label className="simple-check"><input type="checkbox" checked={!!draft.customizable} onChange={e=>setDraft({...draft,customizable:e.target.checked})}/> Allow customer customization</label><button disabled={busy||draft.status==='archived'}>SAVE PRODUCT <Icon name="arrow"/></button></div></form><section className="ops-editor colorway-manager"><header><div><span>PRODUCT COLOURS</span><h2>Give every colour its own story.</h2><p>Add a colour, choose its swatch, create matching size variants, and optionally upload its photos now. Customers will see that colour’s gallery when they select it.</p></div><button type="button" onClick={openColorwayForm}><Icon name="plus"/> ADD COLOUR</button></header><div className="colorway-layout"><div className="colorway-list">{(current.colorways||[]).map(colorway=><article className="colorway-row" key={colorway.id}><div className="colorway-preview" style={{background:colorway.cardColor||draft.cardColor}}><img src={colorway.image||current.image} alt=""/><i style={{background:colorway.swatch||'#17171d'}}/><b>{colorway.name}</b><span>{current.media?.filter(item=>String(item.colorwayId)===String(colorway.id)&&item.type==='image').length||0} linked photos</span></div><div className="colorway-row-copy"><b>{colorway.name}</b><small>{colorway.showInCatalog===false?'Hidden from catalogue':'Visible in catalogue'} · {current.variants?.filter(item=>String(item.colorwayId)===String(colorway.id)).length||0} sizes</small><button type="button" onClick={()=>setMediaColorway(colorway.id)}>UPLOAD PHOTOS FOR {colorway.name.toUpperCase()} <Icon name="arrow"/></button></div></article>)}{!(current.colorways||[]).length&&<div className="media-empty"><b>Add the first product colour.</b><span>Each colour can have its own swatch, variants and photo gallery.</span></div>}</div><div className="colorway-guide"><span>HOW IT WORKS</span><b>1. Add a colour</b><p>Studio creates its size variants without touching the existing colour.</p><b>2. Upload its photos</b><p>Choose the colour in the photo uploader below before selecting files.</p><b>3. Customers switch colour</b><p>The product page changes the gallery and thumbnail to match the selected colour.</p></div></div></section><StudioVariants productId={current.id} productName={current.name} proof={proof} onNotice={setNotice}/><section className="media-manager"><header><div><span>PRODUCT PHOTOS</span><h2>Choose the image that sells it.</h2><p>Upload multiple JPG, PNG or WebP photos. The image marked <b>THUMBNAIL</b> becomes the catalogue image and opens first on the product page.</p></div><strong>{media.filter(x=>x.type==='image').length} photos</strong></header><div className="media-toolbar"><label>ALT TEXT<input value={mediaAlt} maxLength="180" onChange={e=>setMediaAlt(e.target.value)}/></label>{(current.colorways||[]).length>1&&<label>PHOTO COLOURWAY<select value={mediaColorway} onChange={e=>setMediaColorway(e.target.value)}><option value="">General product photos</option>{current.colorways.map(x=><option key={x.id} value={x.id}>{x.name}</option>)}</select></label>}<label className="upload-drop featured-upload"><input type="file" multiple accept="image/jpeg,image/png,image/webp,video/mp4,video/webm" onChange={upload} disabled={busy||uploadMedia.length>=8}/><Icon name="plus"/><b>{busy?'UPLOADING…':'UPLOAD MULTIPLE PHOTOS'}</b><span>Select several files at once</span></label></div><div className="thumbnail-instruction"><Icon name="eye"/><span>Click <b>SET THUMBNAIL</b> on any image below. You can change it at any time.</span></div><div className="media-grid">{media.map((item,index)=><article className={'media-card '+(item.isThumbnail?'is-thumbnail':'')} key={item.id}><div>{item.type==='video'?<video src={item.url} muted playsInline preload="metadata"/>:<img src={item.url} alt={item.alt||''}/>}<span>{item.type.toUpperCase()}</span>{item.isThumbnail&&<strong className="media-thumbnail-label">THUMBNAIL</strong>}</div><label>ALT TEXT<input defaultValue={item.alt||''} onBlur={e=>e.target.value!==item.alt&&mutate('/api/admin/media/'+item.id,{alt:e.target.value})}/></label><small>{current.colorways?.find(x=>x.id===item.colorwayId)?.name||'General product photos'} · {item.fileSize?Math.round(item.fileSize/1024)+' KB':'External'}</small><footer><button disabled={!index||busy} onClick={()=>move(item,-1)} aria-label="Move earlier">←</button><button disabled={index===media.length-1||busy} onClick={()=>move(item,1)} aria-label="Move later">→</button><button className={'thumbnail-button '+(item.isThumbnail?'selected':'')} disabled={busy||item.type!=='image'} onClick={()=>setThumbnail(item)}>{item.isThumbnail?'✓ THUMBNAIL':'SET THUMBNAIL'}</button><button className="danger-link" disabled={busy} onClick={()=>removeMedia(item)}>REMOVE</button></footer></article>)}{!media.length&&<div className="media-empty"><b>No product photos yet.</b><span>Upload several photos above; the first image will be selected automatically.</span></div>}</div></section><section className="danger-zone"><div><span>PRODUCT LIFECYCLE</span><h2>{current.status==='archived'?'Restore this product':'Archive without losing history'}</h2><p>Archiving removes the product from sale without deleting its photos, stock history, orders or reviews.</p></div>{current.status==='archived'?<button disabled={busy} onClick={restore}>RESTORE AS DRAFT</button>:<button disabled={busy} onClick={archive}>ARCHIVE PRODUCT</button>}{current.status==='draft'&&<div className="confirm-delete"><label>TYPE “{current.name}”<input value={confirmName} onChange={e=>setConfirmName(e.target.value)}/></label><button disabled={busy||confirmName!==current.name} onClick={permanentDelete}>DELETE DRAFT FOREVER</button></div>}</section>{notice&&<div className="studio-toast">{notice}</div>}</div>}</div>{showColorwayForm&&<div className="studio-modal"><form className="product-create colorway-create" onSubmit={createColorway}><header><div><span>NEW PRODUCT COLOUR</span><h2>Add another colour to {current?.name}.</h2><p>Existing sizes are copied by default. Add photos here or upload them from the colour picker below.</p></div><button type="button" onClick={()=>setShowColorwayForm(false)}>×</button></header><div className="create-grid"><label className="span-two">COLOUR NAME<input required maxLength="60" value={colorwayDraft.name} onChange={e=>setColorwayDraft({...colorwayDraft,name:e.target.value})} placeholder="Cobalt Blue"/></label><label>SWATCH<input type="color" value={colorwayDraft.swatch} onChange={e=>setColorwayDraft({...colorwayDraft,swatch:e.target.value})}/></label><label>CARD COLOUR<input type="color" value={colorwayDraft.cardColor} onChange={e=>setColorwayDraft({...colorwayDraft,cardColor:e.target.value})}/></label><label>SIZES · OPTIONAL<input value={colorwayDraft.sizes} onChange={e=>setColorwayDraft({...colorwayDraft,sizes:e.target.value})} placeholder="Leave blank to copy existing sizes"/></label><label>STOCK PER SIZE · OPTIONAL<input type="number" min="0" value={colorwayDraft.stock} onChange={e=>setColorwayDraft({...colorwayDraft,stock:e.target.value})} placeholder="Copy existing stock"/></label><label>SKU PREFIX · OPTIONAL<input value={colorwayDraft.skuPrefix} onChange={e=>setColorwayDraft({...colorwayDraft,skuPrefix:e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g,'')})} placeholder="Uses product name"/></label><label className="span-two create-photo-input">PHOTOS FOR THIS COLOUR<input type="file" multiple accept="image/jpeg,image/png,image/webp,video/mp4,video/webm" onChange={e=>setColorwayDraft({...colorwayDraft,photos:[...e.target.files]})}/><small>{colorwayDraft.photos.length?colorwayDraft.photos.length+' files selected':'The first image becomes this colour’s thumbnail.'}</small></label></div><footer><small>One colour = one swatch, one set of variants, and one product gallery. Customers can switch colour on the product page.</small><button disabled={busy}>{busy?'ADDING…':'ADD COLOUR'} <Icon name="arrow"/></button></footer></form></div>}{showCreate&&<div className="studio-modal"><form className="product-create" onSubmit={createProduct}><header><div><span>NEW PRODUCT</span><h2>Add the next piece to the drop.</h2><p>It starts as a draft. Add photos and choose its thumbnail before publishing.</p></div><button type="button" onClick={()=>setShowCreate(false)}>×</button></header><div className="create-grid"><label className="span-two">PRODUCT NAME<input required value={create.name} onChange={e=>setCreate({...create,name:e.target.value})}/></label><label className="span-two">SHORT DESCRIPTION<input maxLength="240" value={create.shortDescription} onChange={e=>setCreate({...create,shortDescription:e.target.value})}/></label><label className="span-two">FEATURES <small className="field-hint">One feature per line</small><textarea rows="5" maxLength="2000" value={create.features} onChange={e=>setCreate({...create,features:e.target.value})} placeholder="Heavyweight cotton · Printed on campus · Designed for everyday wear"/></label><label>PRICE · PAISE<input required type="number" min="0" value={create.basePrice} onChange={e=>setCreate({...create,basePrice:Number(e.target.value)})}/></label><label>COMPARE AT · PAISE<input type="number" min="0" value={create.comparePrice} onChange={e=>setCreate({...create,comparePrice:Number(e.target.value)})}/></label><label>CARD COLOUR<input type="color" value={create.cardColor} onChange={e=>setCreate({...create,cardColor:e.target.value})}/></label><label>FIRST COLOUR<input required value={create.colorName} onChange={e=>setCreate({...create,colorName:e.target.value})}/></label><label>SWATCH<input type="color" value={create.swatch} onChange={e=>setCreate({...create,swatch:e.target.value})}/></label><label>SIZES · COMMA SEPARATED<input required value={create.sizes} onChange={e=>setCreate({...create,sizes:e.target.value})}/></label><label>SKU PREFIX<input required value={create.skuPrefix} onChange={e=>setCreate({...create,skuPrefix:e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g,'')})} placeholder="PEAK-TEE"/></label><label>STOCK PER SIZE<input type="number" min="0" value={create.stock} onChange={e=>setCreate({...create,stock:Number(e.target.value)})}/></label><label className="simple-check span-two"><input type="checkbox" checked={!!create.customizable} onChange={e=>setCreate({...create,customizable:e.target.checked})}/> Allow customer customization</label><label className="span-two create-photo-input">PRODUCT PHOTOS<input type="file" multiple accept="image/jpeg,image/png,image/webp,video/mp4,video/webm" onChange={e=>setCreate({...create,photos:[...e.target.files]})}/><small>{create.photos.length?create.photos.length+' files selected':'You can select multiple photos now; the first image will be the initial thumbnail.'}</small></label></div><footer><small>Required selling data: price, colour, sizes and stock. You can archive the product later without losing order history.</small><button disabled={busy}>{busy?'CREATING…':'CREATE DRAFT'} <Icon name="arrow"/></button></footer></form></div>}</section>
}

function StudioOrders({proof}){
  const [data,setData]=useState({pending:[],delivered:[]}),[loading,setLoading]=useState(true),[notice,setNotice]=useState(''),[busy,setBusy]=useState('');
  const headers={'X-Admin-Proof':proof};
  const load=async()=>{setLoading(true);try{const result=await api('/api/admin/orders/fulfilment',{headers});setData({pending:result.pending||[],delivered:result.delivered||[]});setNotice('')}catch(error){setNotice(error.message)}finally{setLoading(false)}};
  useEffect(()=>{load()},[]);
  const mark=async(orderId,delivered)=>{setBusy(orderId);setNotice('');try{await api(`/api/admin/orders/${orderId}/delivered`,{method:'PATCH',headers,body:JSON.stringify({delivered})});await load()}catch(error){setNotice(error.message)}finally{setBusy('')}};
  const day=value=>value?new Date(value).toLocaleDateString('en-IN',{day:'2-digit',month:'short',year:'numeric'}):'';
  const download=(orders,withDelivered,filename)=>{
    const head=['Order','Item','Colour','Size','Qty','Hostel','Room','Ordered on','Phone','Customer','Coupon','Discount','Paid value','Payment'].concat(withDelivered?['Delivered on']:[]);
    const rows=orders.flatMap(order=>order.items.map(item=>[order.orderNo,item.name,item.color,item.size,item.quantity,order.hostel,order.roomNumber,day(order.orderedAt),order.phone,order.username,order.coupon||'',order.discount?(Number(order.discount)/100).toFixed(2):'',(Number(order.total)/100).toFixed(2),order.paymentStatus].concat(withDelivered?[day(order.deliveredAt)]:[])));
    const cell=value=>{const text=String(value??'');return /[",\r\n]/.test(text)?`"${text.replace(/"/g,'""')}"`:text};
    // Excel only reads a CSV as UTF-8 when the file opens with a byte order mark; without it rupee values and names garble.
    const csv='﻿'+[head,...rows].map(line=>line.map(cell).join(',')).join('\r\n');
    const url=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'})),link=document.createElement('a');
    link.href=url;link.download=filename;document.body.appendChild(link);link.click();link.remove();URL.revokeObjectURL(url);
  };
  // One row per line item, but the order-level cells span the group: a delivery arrives whole, so a
  // single tick moves every line of that order across to the delivered table.
  const table=(orders,delivered)=><div className="orders-desk-scroll"><table className="orders-desk-table"><thead><tr><th>Item</th><th>Colour</th><th>Size</th><th>Qty</th><th>Hostel</th><th>Ordered</th><th>Phone</th><th>Customer</th><th>Coupon</th><th>Paid</th>{delivered&&<th>Delivered</th>}<th>{delivered?'Undo':'Done'}</th></tr></thead><tbody>{orders.flatMap(order=>order.items.map((item,index)=><tr key={item.id} className={index===0?'order-start':''}><td>{item.name}</td><td>{item.color||'-'}</td><td>{item.size||'-'}</td><td>{item.quantity}</td>{index===0&&<><td rowSpan={order.items.length}>{order.hostel||'-'}{order.roomNumber?` · ${order.roomNumber}`:''}</td><td rowSpan={order.items.length}>{day(order.orderedAt)}</td><td rowSpan={order.items.length}>{order.phone||'-'}</td><td rowSpan={order.items.length}>{order.username}</td><td rowSpan={order.items.length}>{order.coupon?<><b className="orders-coupon">{order.coupon}</b>{order.discount>0&&<small>&minus;{money(order.discount)}</small>}</>:<span className="orders-nocoupon">&mdash;</span>}</td><td rowSpan={order.items.length}><b>{money(order.total)}</b></td>{delivered&&<td rowSpan={order.items.length}>{day(order.deliveredAt)}</td>}<td rowSpan={order.items.length}><input type="checkbox" checked={delivered} disabled={busy===order.orderId} onChange={event=>mark(order.orderId,event.target.checked)} aria-label={`Mark order ${order.orderNo} delivered`}/></td></>}</tr>))}</tbody></table></div>;
  const panel=(title,orders,delivered,filename,empty)=><div className="orders-desk"><header><div><b>{title}</b><span>{orders.length} {orders.length===1?'order':'orders'}</span></div><button type="button" onClick={()=>download(orders,delivered,filename)} disabled={!orders.length}>DOWNLOAD CSV <Icon name="arrow" size={15}/></button></header>{orders.length?table(orders,delivered):<p className="orders-desk-empty">{empty}</p>}</div>;
  return <section className="studio-page"><StudioTitle kicker="FULFILMENT" title="Orders" copy="Every line still to go out, and everything already handed over. Tick an order once it reaches the hostel."/>{notice&&<small className="editor-notice">{notice}</small>}{loading?<p className="orders-desk-empty">Loading orders...</p>:<>{panel('TO DELIVER',data.pending,false,'orders-to-deliver.csv','Nothing waiting to go out.')}{panel('DELIVERED',data.delivered,true,'orders-delivered.csv','No deliveries logged yet.')}</>}</section>}
function StudioPolicies({settings,mutate}){
  // Each page is edited as plain text. An empty box means the page falls back to its built-in
  // copy, so a shop can never accidentally publish a blank terms page.
  const pages=[['terms','Terms of service'],['privacy','Privacy policy'],['shipping','Shipping and delivery']];
  const initial=()=>Object.fromEntries(pages.map(([key])=>[key,String(settings[POLICY_KEYS[key]]||'').trim()||policyToText(POLICIES[key])]));
  const [draft,setDraft]=useState(initial),[notice,setNotice]=useState(''),[busy,setBusy]=useState(false);
  useEffect(()=>{setDraft(initial())},[settings]);
  const save=async event=>{event.preventDefault();setBusy(true);setNotice('Saving policy pages…');
   try{await mutate('/api/admin/settings',Object.fromEntries(pages.map(([key])=>[POLICY_KEYS[key],draft[key]])));
    setNotice('Saved. The public pages now show this text.')}catch(error){setNotice(error.message)}finally{setBusy(false)}};
  const reset=key=>{setDraft(current=>({...current,[key]:policyToText(POLICIES[key])}));setNotice('Reset to the built-in wording. Save to publish it.')};
  return <section className="studio-page"><StudioTitle kicker="PUBLISHED PAGES" title="Policies" copy="These are the pages customers and payment providers read. Start a line with ## to begin a new section; everything under it is that section's text."/>
    <form className="policy-editor" onSubmit={save}>
      {pages.map(([key,label])=><label key={key}><div className="policy-editor-head"><b>{label.toUpperCase()}</b><a href={`/${key}`} target="_blank" rel="noreferrer">VIEW PAGE ↗</a><button type="button" onClick={()=>reset(key)}>RESET</button></div>
        <textarea rows="14" value={draft[key]||''} onChange={event=>setDraft(current=>({...current,[key]:event.target.value}))}/>
        <small>{(draft[key]||'').split('## ').length-1} section(s)</small></label>)}
      {notice&&<small className="editor-notice">{notice}</small>}
      <button disabled={busy}>{busy?'SAVING…':'SAVE POLICY PAGES'} <Icon name="arrow"/></button>
    </form></section>}

function StudioAppearance({settings,mutate}){const [draft,setDraft]=useState(settings),[notice,setNotice]=useState(''),[previewMode,setPreviewMode]=useState('light'),[preset,setPreset]=useState('custom');useEffect(()=>{setDraft(settings);setPreset('custom')},[settings]);useGoogleFonts(draft);const save=async e=>{e.preventDefault();setNotice('Saving…');try{await mutate('/api/admin/settings',draft);setNotice('Light and dark themes saved.')}catch(e){setNotice(e.message)}};const set=(key,value)=>{setDraft(current=>({...current,[key]:value}));setPreset('custom')};const choosePreset=key=>{const selection=colorThemes[key];if(!selection)return;setDraft(current=>({...current,...selection.light,...selection.dark}));setPreset(key);setNotice(selection.name+' loaded. Save & publish both themes to make it live.')};const typo=typography(draft),setTypo=(field,part,value)=>{const next={...typo,[field]:{...(typo[field]||{}),[part]:value}};for(const key of Object.keys(next)){const entry=next[key]||{},kept={};if(entry.font)kept.font=entry.font;if(Number(entry.size))kept.size=Number(entry.size);if(Object.keys(kept).length)next[key]=kept;else delete next[key]}set('typography',JSON.stringify(next))};const colorValue=(key,fallback)=>/^#[0-9a-f]{6}$/i.test(draft[key]||'')?draft[key]:fallback;const light=[['primary','Red','#ed3b24'],['secondary','Blue','#163ea8'],['accent','Yellow','#f5ce3e'],['background','Canvas','#f4eddf'],['ink','Ink','#17171d']],dark=[['darkPrimary','Signal','#ff4b4b'],['darkSecondary','Blue','#7f8cff'],['darkAccent','Yellow','#f4d35e'],['darkBackground','Canvas','#101014'],['darkInk','Ink','#f7f6ef'],['darkStoryCopy','Story text','#bcd4ff'],['darkKineticBackground','Ticker background','#222a4a']];return <section className="studio-page"><StudioTitle kicker="VISUAL SYSTEM" title="Appearance & theme" copy="Choose a ready-made colour direction, then refine separate light and dark palettes without changing storefront code."/><form className="appearance-editor" onSubmit={save} style={themeVars(draft,previewMode)}><div className="theme-preview"><small>{previewMode.toUpperCase()} MODE PREVIEW</small><div className="studio-mode-tabs"><button type="button" className={previewMode==='light'?'active':''} onClick={()=>setPreviewMode('light')}>LIGHT</button><button type="button" className={previewMode==='dark'?'active':''} onClick={()=>setPreviewMode('dark')}>DARK</button></div><h2 style={typoStyle(draft,'headline')}>{draft.headline?.split('\n').map((x,i)=><React.Fragment key={i}>{x}<br/></React.Fragment>)}</h2><p style={typoStyle(draft,'subhead')}>{draft.subhead}</p><button type="button" style={typoStyle(draft,'heroButton')}>{draft.heroButton}</button><div>{(previewMode==='light'?light:dark).map(([key,,fallback])=><i style={{background:colorValue(key,fallback)}} key={key}/>)}</div></div><div className="appearance-fields"><div className="theme-presets"><div><b>COLOUR THEME STARTING POINTS</b><small>Each applies a coordinated light palette and dark palette. You can still adjust every colour below.</small></div><div className="theme-preset-grid">{Object.entries(colorThemes).map(([key,theme])=><button type="button" className={'theme-preset '+(preset===key?'active':'')} onClick={()=>choosePreset(key)} key={key}><span className="theme-preset-name">{theme.name}</span><span className="theme-preset-swatches">{[theme.light.primary,theme.light.secondary,theme.light.accent,theme.dark.darkBackground].map((colour,index)=><i key={index} style={{background:colour}}/>)}</span><small>{theme.note}</small></button>)}</div></div><label>BRAND NAME<input value={draft.brand||''} onChange={e=>set('brand',e.target.value)}/></label><label>ANNOUNCEMENT<input value={draft.announcement||''} onChange={e=>set('announcement',e.target.value)}/></label><label>HERO HEADLINE<textarea rows="3" value={draft.headline||''} onChange={e=>set('headline',e.target.value)}/></label><label>HERO SUPPORTING COPY<textarea rows="3" value={draft.subhead||''} onChange={e=>set('subhead',e.target.value)}/></label><div className="theme-control-group"><b>TYPOGRAPHY</b><small className="typo-note">Name any Google font exactly as it appears on fonts.google.com. Leave a field blank to keep the current design.</small><div className="typo-controls">{TYPO_FIELDS.map(([key,label])=><label key={key}>{label}<span className="typo-input-pair"><input value={typo[key]?.font||''} maxLength="40" placeholder="Default font" aria-label={label+' font family'} onChange={e=>setTypo(key,'font',e.target.value)}/><input type="number" min="8" max="200" value={typo[key]?.size||''} placeholder="Auto" aria-label={label+' size in pixels'} onChange={e=>setTypo(key,'size',e.target.value)}/></span>{typo[key]?.font&&!TYPO_FONT.test(typo[key].font)&&<em className="typo-warning">Letters, numbers and spaces only</em>}</label>)}</div></div><div className="theme-control-group"><b>LIGHT MODE COLOURS</b><div className="color-controls">{light.map(([key,label,fallback])=><label key={key}>{label}<span className="color-input-pair"><input type="color" value={colorValue(key,fallback)} onChange={e=>set(key,e.target.value)}/><input aria-label={label+' light mode hex value'} className="hex-input" value={draft[key]||fallback} maxLength="7" onChange={e=>set(key,e.target.value)} onBlur={e=>set(key,colorValue(key,fallback))}/></span></label>)}</div></div><div className="theme-control-group"><b>DARK MODE COLOURS</b><div className="color-controls">{dark.map(([key,label,fallback])=><label key={key}>{label}<span className="color-input-pair"><input type="color" value={colorValue(key,fallback)} onChange={e=>set(key,e.target.value)}/><input aria-label={label+' dark mode hex value'} className="hex-input" value={draft[key]||fallback} maxLength="7" onChange={e=>set(key,e.target.value)} onBlur={e=>set(key,colorValue(key,fallback))}/></span></label>)}</div></div>{notice&&<small className="editor-notice">{notice}</small>}<button>SAVE & PUBLISH BOTH THEMES <Icon name="arrow"/></button></div></form></section>}
function StudioReviews({rows,moderate}){return <section className="studio-page"><StudioTitle kicker="VERIFIED CAMPUS VOICES" title="Review moderation" copy="Only delivered purchases enter this queue."/><div className="moderation-list">{rows.length?rows.map(r=><article key={r.id}><div className="review-rating"><b>{stars(r.rating)}</b><span>{r.status}</span></div><h3>{r.product_name}</h3><strong>{r.title||'Untitled review'}</strong><p>{r.body}</p><small>{r.full_name}</small>{r.status==='pending'&&<div><button onClick={()=>moderate(r.id,'approved')}>APPROVE</button><button onClick={()=>moderate(r.id,'rejected')}>REJECT</button><button onClick={()=>moderate(r.id,'flagged')}>FLAG</button></div>}</article>):<div className="studio-empty-rich"><h2>No reviews need attention.</h2></div>}</div></section>}
function StudioCustomers({rows}){return <section className="studio-page"><StudioTitle kicker="CUSTOMER ACCOUNTS" title="Customers" copy="Verified phones, affiliation-aware profiles and privacy-conscious operations."/><div className="studio-table"><header><span>Customer</span><span>Phone</span><span>Affiliation</span><span>Joined</span></header>{rows.map(x=><div key={x.id}><b>{x.full_name||'Profile incomplete'}</b><span>{x.phone_e164}</span><span>{x.affiliation||'Not selected'}</span><span>{new Date(x.created_at).toLocaleDateString('en-IN')}</span></div>)}</div></section>}
const couponDateInput=value=>value?new Date(new Date(value).getTime()+330*60000).toISOString().slice(0,16):'';
const couponSchedule=value=>value?new Date(value).toLocaleString('en-IN',{timeZone:'Asia/Kolkata',dateStyle:'medium',timeStyle:'short'}):'';
function StudioPromotions({rows,mutate}){
  const empty={code:'',type:'percentage',value:'10',min_order:'0',max_discount:'',starts_at:'',ends_at:'',active:true};
  const [editing,setEditing]=useState(null),[form,setForm]=useState(empty),[busy,setBusy]=useState(false),[error,setError]=useState(''),[notice,setNotice]=useState(''),[now,setNow]=useState(Date.now());
  useEffect(()=>{const timer=setInterval(()=>setNow(Date.now()),30000);return()=>clearInterval(timer)},[]);
  const field=key=>event=>setForm(current=>({...current,[key]:event.target.value}));
  const reset=()=>{setEditing(null);setForm(empty)};
  const edit=row=>{setEditing(row.id);setError('');setNotice('');setForm({code:row.code,type:row.type,value:String(row.type==='fixed'?Number(row.value)/100:row.value),min_order:String(Number(row.min_order)/100),max_discount:row.max_discount==null?'':String(Number(row.max_discount)/100),starts_at:couponDateInput(row.starts_at),ends_at:couponDateInput(row.ends_at),active:row.active});document.getElementById('coupon-code')?.focus()};
  const save=async event=>{
    event.preventDefault();setBusy(true);setError('');setNotice('');
    try{
      const payload={...form,value:form.type==='fixed'?Math.round(Number(form.value)*100):Number(form.value),min_order:Math.round(Number(form.min_order)*100),max_discount:form.max_discount===''?null:Math.round(Number(form.max_discount)*100),starts_at:form.starts_at?new Date(form.starts_at+':00+05:30').toISOString():null,ends_at:form.ends_at?new Date(form.ends_at+':00+05:30').toISOString():null};
      await mutate(editing?'/api/admin/coupons/'+editing:'/api/admin/coupons',payload,editing?'PATCH':'POST');
      setNotice(editing?'Coupon updated.':'Coupon created.');reset();
    }catch(error){setError(error.message)}finally{setBusy(false)}
  };
  const action=async(row,remove=false)=>{
    if(remove&&!window.confirm('Remove '+row.code+'? It will no longer be accepted at checkout.'))return;
    setBusy(true);setError('');setNotice('');
    try{await mutate('/api/admin/coupons/'+row.id,remove?{}:{active:!row.active},remove?'DELETE':'PATCH');if(editing===row.id)reset();setNotice(remove?'Coupon removed.':'Coupon updated.')}catch(error){setError(error.message)}finally{setBusy(false)}
  };
  const status=row=>!row.active?'Paused':row.ends_at&&Date.parse(row.ends_at)<=now?'Expired':row.starts_at&&Date.parse(row.starts_at)>now?'Scheduled':'Active';
  return <section className="studio-page promotions-page">
    <StudioTitle kicker="CHECKOUT CONTROL" title="Coupons & promotions"/>
    {error&&<div className="form-error" role="alert">{error}</div>}{notice&&<p className="editor-notice" role="status">{notice}</p>}
    <form className="coupon-editor" onSubmit={save}>
      <h2>{editing?'Edit coupon':'New coupon'}</h2>
      <div className="coupon-fields">
        <label>CODE<input id="coupon-code" required minLength="2" maxLength="40" pattern="[A-Za-z0-9_-]+" value={form.code} onChange={event=>setForm({...form,code:event.target.value.toUpperCase()})}/></label>
        <label>DISCOUNT TYPE<select value={form.type} onChange={field('type')}><option value="percentage">Percentage</option><option value="fixed">Fixed amount</option><option value="free_shipping">Free shipping</option></select></label>
        {form.type!=='free_shipping'&&<label>{form.type==='percentage'?'DISCOUNT (%)':'DISCOUNT (INR)'}<input type="number" required min={form.type==='percentage'?1:0.01} max={form.type==='percentage'?100:1000000} step={form.type==='percentage'?1:0.01} value={form.value} onChange={field('value')}/></label>}
        <label>MINIMUM PURCHASE (INR)<input type="number" required min="0" max="1000000" step="0.01" value={form.min_order} onChange={field('min_order')}/></label>
        <label>MAXIMUM DISCOUNT (INR, OPTIONAL)<input type="number" min="0.01" max="1000000" step="0.01" value={form.max_discount} onChange={field('max_discount')} placeholder="No cap"/></label>
        <label>STARTS (IST, OPTIONAL)<input type="datetime-local" value={form.starts_at} onChange={field('starts_at')}/></label>
        <label>ENDS (IST, OPTIONAL)<input type="datetime-local" value={form.ends_at} onChange={field('ends_at')}/></label>
      </div>
      <div className="coupon-editor-actions"><label><input type="checkbox" checked={form.active} onChange={event=>setForm({...form,active:event.target.checked})}/>Enabled</label><button disabled={busy} type="submit"><Icon name="plus" size={16}/>{busy?'SAVING...':editing?'SAVE COUPON':'CREATE COUPON'}</button>{editing&&<button type="button" disabled={busy} onClick={reset}>CANCEL</button>}</div>
    </form>
    <div className="coupon-list">{rows.map(row=><article key={row.id}>
      <div><span>{status(row)}</span><h2>{row.code}</h2><p>{row.type==='percentage'?row.value+'% off':row.type==='fixed'?money(row.value)+' off':'Free shipping'}{row.max_discount!=null?' · max '+money(row.max_discount):''} · minimum {money(row.min_order)}</p><p>{row.starts_at?couponSchedule(row.starts_at):'Immediately'} → {row.ends_at?couponSchedule(row.ends_at):'No expiry'} (IST)</p></div>
      <div><b>{row.used_count} / {row.usage_limit||'Unlimited'}</b><small>USES{row.per_customer_limit>0?' · '+row.per_customer_limit+' PER CUSTOMER':''}</small><div className="coupon-row-actions"><button type="button" disabled={busy} onClick={()=>edit(row)}>EDIT</button><button type="button" disabled={busy} onClick={()=>action(row)}>{row.active?'PAUSE':'ACTIVATE'}</button><button type="button" disabled={busy} onClick={()=>action(row,true)} title={'Remove '+row.code} aria-label={'Remove '+row.code}><Icon name="close" size={16}/></button></div></div>
    </article>)}</div>{rows.length===0&&<p>No coupons yet.</p>}
  </section>
}
function StudioMotion({settings,mutate}){const [preset,setPreset]=useState(settings.motionPreset||'campus-pop'),[intensity,setIntensity]=useState(settings.motionIntensity||'1'),[enabled,setEnabled]=useState(settings.motion!=='0'),[notice,setNotice]=useState('');const save=async()=>{await mutate('/api/admin/settings',{motion:enabled?'1':'0',motionPreset:preset,motionIntensity:intensity});setNotice('Motion policy saved.')};return <section className="studio-page"><StudioTitle kicker="ANIME.JS / MOBILE-FIRST" title="Motion policy" copy="Useful transitions, interruptible choreography and reduced-motion parity."/><div className="motion-controls"><label className="motion-toggle"><input type="checkbox" checked={enabled} onChange={e=>setEnabled(e.target.checked)}/><i/><span><b>Storefront motion</b><small>Reduced-motion preferences always take priority.</small></span></label><label>PERSONALITY<select value={preset} onChange={e=>setPreset(e.target.value)}><option value="campus-pop">Campus pop</option><option value="editorial-calm">Editorial calm</option><option value="kinetic-grid">Kinetic grid</option></select></label><label>INTENSITY<input type="range" min="0.5" max="1.5" step="0.25" value={intensity} onChange={e=>setIntensity(e.target.value)}/><span>{intensity}×</span></label><button onClick={save}>SAVE MOTION SETTINGS <Icon name="arrow"/></button>{notice&&<small className="editor-notice">{notice}</small>}</div><div className="motion-policy">{[['MENU MORPH','Hamburger → X with reversible drawer choreography'],['CATALOGUE BATCH','Four-card mobile batches without hidden content'],['SCROLL REVEAL','Intersection-aware sections with reduced-motion parity'],['CART FEEDBACK','Fast acknowledgement after a decisive tap']].map(([a,b])=><article key={a}><span>●</span><b>{a}</b><p>{b}</p></article>)}</div></section>}
function StudioPayments({payment}){const configured=Boolean(payment?.configured&&payment?.live);return <section className="studio-page"><StudioTitle kicker="PAYMENT BOUNDARY" title="Razorpay readiness" copy="Use test keys first, then switch to live credentials only after a controlled verification and webhook test."/><div className="payment-gate"><div><span>PROVIDER</span><h2>{payment?.provider||'Razorpay'}</h2><p>Database: {payment?.database||'PostgreSQL'} · Mode: {payment?.mode||'demo'} · {configured?'Provider configured':'Demo fallback active'}</p></div><strong className={configured?'provider-ready':''}>{configured?'READY':'DEMO'}<br/>{configured?'CHECKOUT':'ONLY'}</strong></div><div className="gate-list">{[['Server-authoritative quote',true],['PostgreSQL payment attempts',true],['Webhook event deduplication',true],['Razorpay key ID and secret',configured],['Signed webhook secret',Boolean(payment?.webhookConfigured)],['Test payment and reconciliation',false],['Controlled live penny test',false]].map(([label,done])=><div key={label}><span className={done?'done':''}>{done?'✓':'○'}</span><b>{label}</b><small>{done?'READY':'REQUIRED'}</small></div>)}</div><div className="studio-callout"><div><span>NO PURCHASE REQUIRED YET</span><h2>{configured?'Razorpay checkout is configured.':'Production checkout is paused.'}</h2><p>{configured?'Complete the provider test, webhook and reconciliation checklist before switching to live keys.':'Add Razorpay test credentials when you are ready. Until then, customers can browse and build a bag, but production will not create no-charge orders.'}</p></div><strong>{configured?payment?.mode?.toUpperCase():'CATALOGUE\nONLY'}</strong></div></section>}
const SUPPORT_EMAIL='TheIITDelhiDrop@gmail.com';
// Razorpay checks that a store publishes these before it will activate an account, and they are
// reachable as their own routes so the footer can link them directly.
const POLICIES={
  terms:{kicker:'THE FINE PRINT',title:'Terms of service',updated:'21 September 2026',sections:[
    ['Who we are','The IIT Delhi Drop is a small student-run merchandise store operating on the IIT Delhi campus. We are an independent venture and are not an official store of, nor endorsed by, the Indian Institute of Technology Delhi.'],
    ['Placing an order','An order is a request to buy, not a confirmed sale. We confirm it once payment succeeds and stock is reserved. If an item sells out between your order and our confirmation, we will contact you and refund you in full.'],
    ['Prices and payment','All prices are in Indian Rupees and include applicable taxes unless stated otherwise. Payment is handled by Razorpay. We never see or store your card, UPI or bank details.'],
    ['Your account','You sign in with your mobile number and a one-time code. Keep access to that number secure, since anyone who can receive your code can reach your order history and saved delivery details.'],
    ['Delivery area','We deliver to hostel rooms on the IIT Delhi campus only. We do not ship elsewhere.'],
    ['Product photography','Colours can differ between screens and the finished garment. Sizes follow the size chart on each product page.'],
    ['Acceptable use','Do not use the store to place fraudulent orders, abuse the verification system, or submit reviews for products you have not bought.'],
    ['Changes','We may update these terms as the store grows. The date above shows when they last changed.']]},
  privacy:{kicker:'YOUR DATA',title:'Privacy policy',updated:'21 September 2026',sections:[
    ['What we collect','Your mobile number, and if you provide them your name and email. For delivery we store your hostel and room number. We keep a record of your orders, and any review you choose to write.'],
    ['Why we collect it','To verify it is you signing in, to deliver what you ordered, to answer support questions, and to show you your own order history. Nothing else.'],
    ['Who else sees it','Two processors. MSG91 handles the sign-in code sent to your phone. Razorpay handles payment and receives the amount and contact details needed to process it. We do not sell your data, and we do not share it for advertising.'],
    ['Payment details','We never receive your card, UPI or bank credentials. Those go directly to Razorpay.'],
    ['How long we keep it','Order records are kept as business records. If you want your account and personal details removed, email us and we will remove what we are not required to retain.'],
    ['Cookies','We use a single sign-in cookie to keep you logged in. There is no advertising or third-party tracking on this site.'],
    ['Contact','Questions about your data go to '+SUPPORT_EMAIL+'.']]},
  shipping:{kicker:'GETTING IT TO YOU',title:'Shipping and delivery',updated:'21 September 2026',sections:[
    ['Where we deliver','IIT Delhi campus hostels only. You choose your hostel and room number at checkout. We do not currently deliver off campus or ship by post.'],
    ['How long it takes','Orders are usually handed over within 5 to 7 working days of confirmation. Small-batch items can take longer, and we will tell you if yours will.'],
    ['Delivery charges','Delivery within campus is free.'],
    ['Handover','We deliver to your hostel and contact you on the number you signed in with. If nobody is available we will arrange another time with you.'],
    ['Tracking','Your order page shows its current status, and it updates to delivered once we have handed it over.']]},
  contact:{kicker:'TALK TO US',title:'Contact us',updated:'21 September 2026',sections:[
    ['Customer support',"Email "+SUPPORT_EMAIL+" for anything about an order you have placed. Include your order number and we will get back to you as quickly as we can."],
    ['General enquiries','Bulk orders, collaborations and anything else also go to '+SUPPORT_EMAIL+'.'],
    ['Problems with a delivery','If an item arrives damaged, defective or wrong, email us within 48 hours with photographs and we will sort it out.'],
    ['Who you are dealing with','The IIT Delhi Drop is a student-run merchandise venture based on the IIT Delhi campus. We are not an official IIT Delhi store.']]}};

function PolicyPage({store,session,policy}){
  const {count}=useCart(),page=POLICIES[policy];
  if(!page)return <NotFound store={store}/>;
  return <div className="store policy-page" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings} session={session} cartCount={count}/>
    <main className="policy shell"><a className="back-link" href="/">&larr; BACK TO THE DROP</a>
      <span className="kicker">{page.kicker}</span><h1>{page.title}</h1>
      <p className="policy-updated">Last updated {page.updated}</p>
      {policySections(store.settings,policy,page).map(([heading,body],index)=><section key={heading||index}>{heading?<h2>{heading}</h2>:null}<p>{body}</p></section>)}
      <p className="policy-foot">Questions about anything on this page? Email <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a>.</p>
    </main><Footer settings={store.settings}/></div>}


function Footer({settings}){return <footer className="site-footer"><div className="shell"><div style={typoStyle(settings,'brand')}>{settings.brand}<span>●</span></div><p style={typoStyle(settings,'footerNote')}>{settings.footerNote}</p><nav><a href="/#catalog">MERCH</a><a href="/account">ACCOUNT</a><a href="/contact">CONTACT</a></nav><nav className="footer-policies"><a href="/terms">TERMS</a><a href="/privacy">PRIVACY</a><a href="/shipping">SHIPPING</a></nav><small>© 2026 {settings.brand} · Student-run, not an official IIT Delhi store</small></div></footer>}
function Loading(){return <div className="loading"><span>THE IIT DELHI DROP</span><i>●</i></div>}
function NotFound({store}){return <div className="store" data-theme={currentMode()} style={themeVars(store.settings)}><Header settings={store.settings}/><div className="not-found"><span>404 / LOST ON CAMPUS</span><h1>This drop wandered off.</h1><a href="/">RETURN HOME <Icon name="arrow"/></a></div></div>}

function App(){const [store,setStore]=useState(null),[session,setSession]=useState(null);useEffect(()=>{api('/api/store').then(setStore);api('/api/account/session').then(setSession).catch(()=>{})},[]);useGoogleFonts(store?.settings);if(!store)return <Loading/>;const path=location.pathname;if(path.startsWith('/studio'))return <Studio store={store}/>;if(POLICIES[path.replace('/','')])return <PolicyPage store={store} session={session} policy={path.replace('/','')}/>;if(path==='/login')return <LoginPage store={store} onSession={setSession}/>;if(path==='/account')return <AccountPage store={store} session={session}/>;if(path==='/cart')return <CartPage store={store} session={session}/>;if(path.startsWith('/products/'))return <ProductPage store={store} session={session} slug={decodeURIComponent(path.split('/').filter(Boolean).pop())}/>;return <Storefront store={store} session={session}/>}

createRoot(document.getElementById('root')).render(<App/>);
