/* Curio — design system (evolved: bold, gamified, motion). Exports to window. */
const {useState,useContext,createContext,useEffect,useRef,useCallback} = React;

/* ── THEME — iOS HIG inspired, glassmorphism surfaces ──
   Brand colours (ink/gold/green accents) and type scale are unchanged.
   Only the neutral "surface" tokens (card/cardAlt/line/bg) became translucent
   glass values, meant to sit on top of the .cu-frame gradient-mesh backdrop. */
const T = {
  bg:'rgba(250,250,252,.92)', card:'rgba(255,255,255,.82)', cardAlt:'rgba(245,245,248,.72)',
  line:'rgba(255,255,255,.55)', lineMd:'rgba(255,255,255,.68)',
  text:'#141416', sub:'#2C2C2E', muted:'#636366',
  ink:'var(--ink)', inkDeep:'var(--ink-deep)',
  gold:'#A9762A', goldBg:'rgba(251,241,222,.62)', goldLine:'rgba(234,206,155,.55)', goldSolid:'#E0B25A',
  green:'#34C759', greenBg:'rgba(232,248,237,.62)', greenLine:'rgba(168,224,188,.55)',
  pink:'#FF5DA2',
};
/* glass blur presets, shared by every translucent surface below */
const GLASS='blur(28px) saturate(180%)';
const GLASS_SOFT='blur(20px) saturate(170%)';
const GLASS_DARK='blur(24px) saturate(150%)';
/* iOS-standard spacing scale */
const SP = { xs:4, sm:8, md:16, lg:20, xl:24, xxl:32, safe:20, section:28 };
/* single shared motion curve — every interactive transition should reference this */
const EASE = 'cubic-bezier(.2,.8,.2,1)';
const F='-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI",sans-serif';
const FD='-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",sans-serif';
const FW='"Oregano","Plus Jakarta Sans",cursive';
const fz=n=>`calc(${n}px * var(--ts,1))`;
const tint=p=>`color-mix(in srgb, var(--ink) ${p}%, #FFFFFF)`;
/* translucent ink tint for glass surfaces (vs. tint() which is opaque) */
const tintGlass=p=>`color-mix(in srgb, var(--ink) ${p}%, transparent)`;
const GRAD='linear-gradient(135deg, var(--grad-a) 0%, var(--grad-b) 100%)';
/* real, host-agnostic links: every in-app QR points to wherever the app is
   actually served, so scanning it on a phone opens THIS deployment (and
   auto-joins a crew when a code is passed). */
const APP_BASE = (typeof location!=='undefined') ? (location.origin+location.pathname).replace(/[?#].*$/,'') : '';
function joinUrl(code){ return APP_BASE + (code?('?join='+encodeURIComponent(code)):''); }
function prettyUrl(code){ return (APP_BASE.replace(/^https?:\/\//,'').replace(/\/index\.html$|\/app\.html$/,'')||'curio.app') + (code?('?join='+code):''); }
const GRAD_WARM='linear-gradient(135deg, #8979DD 0%, #6D5AD0 100%)';
const SH='0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04)';
const SHmd='0 4px 16px rgba(0,0,0,.08), 0 2px 6px rgba(0,0,0,.04)';
const SHlg='0 12px 40px rgba(0,0,0,.12), 0 4px 12px rgba(0,0,0,.06)';

/* avatar gradient palette */
const AV_GRADS=[
  ['#8979DD','#6D5AD0'],['#9B6BD8','#6D5AD0'],['#6D8AD8','#5A47C0'],
  ['#6FB5C0','#4E8FB8'],['#C9A24A','#A9762A'],['#C77BB8','#8979DD'],
];

/* ── CONTEXT ── */
const AppCtx=createContext(null);
const useApp=()=>useContext(AppCtx);

/* ── ICONS (clean line set) ── */
const ICONS={
  home:'M3 10.2 12 3l9 7.2M5 9.5V20a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V9.5',
  map:'M9 3 3 5.5v15L9 18l6 3 6-2.5v-15L15 6 9 3Zm0 0v15m6-12v15',
  scan:'M4 8V5a1 1 0 0 1 1-1h3M20 8V5a1 1 0 0 0-1-1h-3M4 16v3a1 1 0 0 0 1 1h3M20 16v3a1 1 0 0 1-1 1h-3M3 12h18',
  users:'M16 19c0-2.2-1.8-4-4-4s-4 1.8-4 4M12 12a3 3 0 1 0 0-6 3 3 0 0 0 0 6M19 18c0-1.6-.9-3-2.3-3.6M5 18c0-1.6.9-3 2.3-3.6',
  user:'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM5 20c0-3.3 3-6 7-6s7 2.7 7 6',
  arrowL:'M15 5l-7 7 7 7', arrowR:'M9 5l7 7-7 7', chevR:'M9 6l6 6-6 6', chevD:'M6 9l6 6 6-6', chevU:'M6 15l6-6 6 6',
  search:'M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14ZM20 20l-4-4',
  plus:'M12 5v14M5 12h14', check:'M5 12l4.5 4.5L19 7',
  info:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM12 11v5M12 8h.01',
  camera:'M4 8a2 2 0 0 1 2-2h2l1.5-2h5L18 6h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8Zm8 9a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z',
  location:'M12 21c4-4 7-7.5 7-11a7 7 0 1 0-14 0c0 3.5 3 7 7 11ZM12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
  lock:'M6 10V8a6 6 0 0 1 12 0v2M5 10h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1Z',
  globe:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM3 12h18M12 3c2.5 2.5 3.5 6 3.5 9S14.5 18.5 12 21M12 3c-2.5 2.5-3.5 6-3.5 9s1 6.5 3.5 9',
  x:'M6 6l12 12M18 6 6 18', heart:'M12 20s-7-4.5-9.3-9C1 7.5 3 4 6.5 4 9 4 12 7 12 7s3-3 5.5-3C21 4 23 7.5 21.3 11 19 15.5 12 20 12 20Z',
  share:'M12 3v13M8 7l4-4 4 4M5 13v6a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6',
  settings:'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM19.4 13.5l1.5 1-1.5 2.6-1.8-.6a6 6 0 0 1-1.4.8L15.8 21h-3l-.4-1.7a6 6 0 0 1-1.4-.8l-1.8.6L7 16.5l1.5-1a6 6 0 0 1 0-1.6L7 12.9l1.5-2.6 1.8.6a6 6 0 0 1 1.4-.8L12.2 8h3l.4 1.7a6 6 0 0 1 1.4.8l1.8-.6 1.5 2.6-1.5 1a6 6 0 0 1 0 1.6Z',
  qr:'M4 4h6v6H4V4Zm10 0h6v6h-6V4ZM4 14h6v6H4v-6Zm10 3h3m3 0h-3m0 0v3m0-6v3',
  bolt:'M13 3 4 14h6l-1 7 9-11h-6l1-7Z', trophy:'M7 4h10v3a5 5 0 0 1-10 0V4ZM7 6H4v2a3 3 0 0 0 3 3M17 6h3v2a3 3 0 0 1-3 3M9 16h6M10 20h4M12 16v4',
  flame:'M12 22c4 0 6.5-2.6 6.5-6 0-3.2-2.5-5-3.5-7.5C14 6 14.5 3 12 2c.5 3-2 4.5-3.5 6.5S5.5 12.8 5.5 16c0 3.4 2.5 6 6.5 6Z',
  sparkle:'M12 3l1.6 5.4L19 10l-5.4 1.6L12 17l-1.6-5.4L5 10l5.4-1.6L12 3Z',
  chat:'M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H9l-4 4V6a1 1 0 0 1 1-1Z',
  star:'M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17.8 6.6 20l1-6.1L3.2 9.5l6.1-.9L12 3Z',
};
function Ic({k,size=24,color,stroke=2,fill=false,style:ext={}}){
  const d=ICONS[k]; if(!d) return null;
  return <svg width={size} height={size} viewBox="0 0 24 24" fill={fill?(color||'currentColor'):'none'} style={{flexShrink:0,display:'block',...ext}} aria-hidden="true">
    <path d={d} stroke={fill?'none':(color||'currentColor')} strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"/>
  </svg>;
}

/* ── MICRO ── */
const Gap=({n=8})=><div style={{height:n,flexShrink:0}}></div>;

function Chip({label,kind='ink',dot,icon,style:ext={}}){
  const k={
    ink:{bg:tintGlass(20),bd:tintGlass(38),fg:'var(--ink-deep)'},
    gold:{bg:T.goldBg,bd:T.goldLine,fg:T.gold},
    green:{bg:T.greenBg,bd:T.greenLine,fg:T.green},
    neutral:{bg:T.cardAlt,bd:T.line,fg:T.sub},
    onDark:{bg:'rgba(255,255,255,.20)',bd:'rgba(255,255,255,.34)',fg:'#FFFFFF'},
    goldOnDark:{bg:'rgba(224,178,90,.24)',bd:'rgba(224,178,90,.5)',fg:'#F7D799'},
  }[kind];
  return <span style={{display:'inline-flex',alignItems:'center',gap:5,padding:'4px 10px',borderRadius:100,background:k.bg,backdropFilter:GLASS_SOFT,WebkitBackdropFilter:GLASS_SOFT,border:`1px solid ${k.bd}`,color:k.fg,fontSize:fz(12),fontWeight:700,fontFamily:F,flexShrink:0,lineHeight:1.3,whiteSpace:'nowrap',...ext}}>
    {dot&&<span style={{width:6,height:6,borderRadius:'50%',background:'currentColor',display:'inline-block',flexShrink:0}}></span>}
    {icon}{label}
  </span>;
}

function Btn({label,variant='primary',sm,full=true,onClick,disabled,icon,style:ext={},ariaLabel}){
  const v={
    primary:{bg:disabled?'rgba(231,228,240,.7)':GRAD,fg:disabled?T.muted:'#FFFFFF',bd:'none',sh:disabled?'none':SHmd,bf:'none'},
    solid:{bg:disabled?'rgba(231,228,240,.7)':'var(--ink)',fg:disabled?T.muted:'#FFFFFF',bd:'none',sh:disabled?'none':SH,bf:'none'},
    secondary:{bg:T.card,fg:T.text,bd:`1.5px solid rgba(20,16,40,.12)`,sh:SH,bf:GLASS},
    tonal:{bg:tintGlass(16),fg:'var(--ink-deep)',bd:`1.5px solid ${tintGlass(38)}`,sh:'none',bf:GLASS_SOFT},
    ghost:{bg:'transparent',fg:'var(--ink-deep)',bd:'none',sh:'none',bf:'none'},
    onGradient:{bg:'rgba(255,255,255,.82)',fg:'var(--ink-deep)',bd:'1px solid rgba(255,255,255,.6)',sh:SH,bf:GLASS_SOFT},
    gold:{bg:'linear-gradient(135deg,#FFC247,#FF8A3D)',fg:'#3A2400',bd:'none',sh:'0 6px 18px rgba(255,150,40,.32)',bf:'none'},
  }[variant];
  return <button type="button" aria-label={ariaLabel} onClick={disabled?undefined:onClick} disabled={disabled} className="cu-press" style={{width:full?'100%':'auto',minHeight:sm?44:54,padding:sm?'10px 18px':'15px 22px',borderRadius:14,border:v.bd,background:v.bg,backdropFilter:v.bf,WebkitBackdropFilter:v.bf,color:v.fg,fontSize:sm?fz(14):fz(15.5),fontWeight:800,fontFamily:F,cursor:disabled?'default':'pointer',letterSpacing:'-0.1px',display:'flex',alignItems:'center',justifyContent:'center',gap:8,boxShadow:v.sh,whiteSpace:'nowrap',...ext}}>
    {icon}{label}
  </button>;
}

function IconBtn({k,onClick,ariaLabel,onDark,size=44,style:ext={}}){
  const bf=onDark?GLASS_DARK:GLASS;
  return <button type="button" onClick={onClick} aria-label={ariaLabel} className="cu-press" style={{width:size,height:size,borderRadius:size/2,background:onDark?'rgba(20,16,32,.34)':T.card,border:onDark?'1px solid rgba(255,255,255,.26)':`1px solid ${T.line}`,color:onDark?'#FFFFFF':T.text,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,backdropFilter:bf,WebkitBackdropFilter:bf,boxShadow:onDark?'none':SH,...ext}}>
    <Ic k={k} size={size*0.46}/>
  </button>;
}

function BackRow({title,right}){
  const{nav}=useApp();
  return <div style={{display:'flex',alignItems:'center',gap:8,padding:`8px ${SP.safe}px 4px`}}>
    <button type="button" onClick={nav.pop} aria-label="Back" className="cu-press" style={{width:44,height:44,borderRadius:22,background:'transparent',border:'none',color:T.text,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,marginLeft:-8}}>
      <Ic k="arrowL" size={24}/>
    </button>
    {title&&<h1 style={{color:T.text,fontSize:fz(17),fontWeight:800,fontFamily:FD,flex:1,margin:0,letterSpacing:'-0.3px',minWidth:0}}>{title}</h1>}
    {right}
  </div>;
}

function StepShell({header, children, footer, label, footerStack}){
  return <div data-screen-label={label} className="cu-step-shell">
    {header}
    <div className="cu-step-body">{children}</div>
    {footer ? <div className={'cu-step-footer'+(footerStack?' cu-step-footer--stack':'')}>{footer}</div> : null}
  </div>;
}

function Switch({ checked, onChange, ariaLabel, decorative }) {
  if (decorative) {
    return <span className="cu-switch cu-switch--decorative" aria-hidden="true" data-checked={checked ? 'true' : 'false'} style={{ background: checked ? 'var(--ink)' : '#AEAEB2', boxShadow: checked ? 'none' : 'inset 0 0 0 1px rgba(0,0,0,.1)' }}>
      <span className="cu-switch-thumb" style={{ left: checked ? 22 : 2 }}/>
    </span>;
  }
  return <button type="button" role="switch" aria-checked={!!checked} aria-label={ariaLabel} className="cu-switch cu-press" onClick={() => onChange && onChange(!checked)}>
    <span className="cu-switch-thumb"/>
  </button>;
}

const Av=({n,ci=0,sz=36,ring,photo})=>{
  const g=AV_GRADS[ci%AV_GRADS.length];
  if(photo) return <span aria-hidden="true" style={{width:sz,height:sz,borderRadius:'50%',flexShrink:0,display:'inline-block',overflow:'hidden',background:`linear-gradient(135deg,${g[0]},${g[1]})`,boxShadow:ring?`0 0 0 3px ${T.card}, 0 0 0 5px ${g[0]}`:'none'}}><img src={photo} alt="" style={{width:'100%',height:'100%',objectFit:'cover',display:'block'}}/></span>;
  return <span aria-hidden="true" style={{width:sz,height:sz,borderRadius:'50%',background:`linear-gradient(135deg,${g[0]},${g[1]})`,display:'inline-flex',alignItems:'center',justifyContent:'center',color:'#FFFFFF',fontSize:Math.round(sz*.4),fontWeight:800,fontFamily:F,flexShrink:0,boxShadow:ring?`0 0 0 3px ${T.card}, 0 0 0 5px ${g[0]}`:'none'}}>{(n||'?')[0].toUpperCase()}</span>;
};
function AvStack({names,sz=26,photos}){
  return <span style={{display:'inline-flex'}}>{names.map((n,i)=><span key={n+i} style={{marginLeft:i>0?-(sz*.32):0,zIndex:names.length-i,position:'relative',display:'inline-flex',borderRadius:'50%',boxShadow:`0 0 0 2.5px ${T.card}`}}><Av n={n} ci={i} sz={sz} photo={photos&&photos[i]}/></span>)}</span>;
}

function PBar({val,h=8,onDark,grad=false,label}){
  return <div role="progressbar" aria-valuenow={Math.round(val)} aria-label={label||'Progress'} style={{height:h,background:onDark?'rgba(255,255,255,.26)':tint(16),borderRadius:100,overflow:'hidden'}}>
    <div style={{height:'100%',width:`${val}%`,background:onDark?'#FFFFFF':(grad?GRAD:'var(--ink)'),borderRadius:100,transition:`width .8s ${EASE}`}}></div>
  </div>;
}

function Card({children,onClick,selected,padding='16px',style:ext={},ariaLabel,role}){
  const Tag=onClick?'button':'div';
  return <Tag type={onClick?'button':undefined} role={role} aria-label={ariaLabel} onClick={onClick} className={onClick?'cu-press':''} style={{display:'block',width:'100%',textAlign:'left',padding,borderRadius:18,background:selected?tintGlass(16):T.card,backdropFilter:GLASS,WebkitBackdropFilter:GLASS,border:`1px solid ${selected?tintGlass(40):T.lineMd}`,boxShadow:SH,cursor:onClick?'pointer':'default',fontFamily:F,...ext}}>{children}</Tag>;
}

/* ── empty / first-time state — calm, consistent across the app ── */
function EmptyState({icon,emoji,title,message,actionLabel,onAction,style:ext={}}){
  return <div className="cu-rise" style={{padding:'34px 24px',borderRadius:18,background:T.cardAlt,backdropFilter:GLASS_SOFT,WebkitBackdropFilter:GLASS_SOFT,border:`1px dashed ${T.lineMd}`,textAlign:'center',display:'flex',flexDirection:'column',alignItems:'center',...ext}}>
    <span aria-hidden="true" style={{width:52,height:52,borderRadius:16,background:T.card,backdropFilter:GLASS,WebkitBackdropFilter:GLASS,border:`0.5px solid ${T.line}`,display:'flex',alignItems:'center',justifyContent:'center',marginBottom:14,fontSize:emoji?24:undefined,color:T.muted}}>{emoji||icon}</span>
    <h3 style={{color:T.text,fontSize:fz(15.5),fontWeight:700,fontFamily:F,margin:'0 0 6px'}}>{title}</h3>
    {message&&<p style={{color:T.muted,fontSize:fz(13.5),fontFamily:F,lineHeight:1.55,margin:actionLabel?'0 0 18':0,maxWidth:280}}>{message}</p>}
    {actionLabel&&<Btn label={actionLabel} sm full={false} onClick={onAction}/>}
  </div>;
}

/* ── lightweight toast — one consistent motion & shape for every confirmation ── */
let __toastListeners=[];
function showToast(msg,opts={}){
  __toastListeners.forEach(fn=>fn({id:Date.now()+Math.random(),msg,icon:opts.icon,kind:opts.kind||'default'}));
}
function ToastHost(){
  const[items,setItems]=useState([]);
  useEffect(()=>{
    const fn=(t)=>{ setItems(cur=>[...cur,t]); setTimeout(()=>setItems(cur=>cur.filter(x=>x.id!==t.id)),2400); };
    __toastListeners.push(fn);
    return ()=>{ __toastListeners=__toastListeners.filter(x=>x!==fn); };
  },[]);
  if(!items.length) return null;
  return <div aria-live="polite" style={{position:'absolute',left:16,right:16,bottom:'max(100px, calc(env(safe-area-inset-bottom,0px) + 100px))',display:'flex',flexDirection:'column',alignItems:'center',gap:8,zIndex:150,pointerEvents:'none'}}>
    {items.map(it=>
      <div key={it.id} className="cu-toast" style={{display:'flex',alignItems:'center',gap:8,padding:'11px 18px',borderRadius:100,background:'rgba(28,28,30,.94)',backdropFilter:'blur(14px)',WebkitBackdropFilter:'blur(14px)',color:'#fff',fontSize:fz(13.5),fontWeight:600,fontFamily:F,boxShadow:SHmd,maxWidth:'100%'}}>
        {it.icon&&<span style={{flexShrink:0,display:'flex'}}>{it.icon}</span>}
        <span style={{whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{it.msg}</span>
      </div>
    )}
  </div>;
}

const SecTitle=({c,action,onAction})=>
  <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',padding:`0 ${SP.safe}px`,marginBottom:14}}>
    <h2 style={{color:T.text,fontSize:fz(18),fontWeight:800,letterSpacing:'-0.4px',fontFamily:FD,margin:0,flex:1,minWidth:0,whiteSpace:'nowrap'}}>{c}</h2>
    {action&&<button type="button" onClick={onAction} style={{display:'flex',alignItems:'center',gap:2,border:'none',background:'transparent',color:'var(--ink-deep)',fontSize:fz(13),fontWeight:700,fontFamily:F,cursor:'pointer',padding:'8px 0 8px 8px',whiteSpace:'nowrap',flexShrink:0}}>{action}<Ic k="chevR" size={16}/></button>}
  </div>;

function ListRow({icon,iconKind='ink',emoji,title,sub,trailing,onClick,selected,role,ariaChecked}){
  const Tag=onClick?'button':'div';
  const iconBg={ink:tintGlass(18),gold:T.goldBg,neutral:T.cardAlt,green:T.greenBg}[iconKind];
  const iconFg={ink:'var(--ink-deep)',gold:T.gold,neutral:T.sub,green:T.green}[iconKind];
  return <Tag type={onClick?'button':undefined} role={role} aria-checked={ariaChecked} onClick={onClick} className={onClick?'cu-press':''} style={{display:'flex',gap:12,alignItems:'center',width:'100%',textAlign:'left',padding:'12px 14px',minHeight:58,borderRadius:16,background:selected?tintGlass(14):T.card,backdropFilter:GLASS,WebkitBackdropFilter:GLASS,border:`1px solid ${selected?tintGlass(46):T.line}`,boxShadow:SH,cursor:onClick?'pointer':'default',fontFamily:F}}>
    {(icon||emoji)&&<span aria-hidden="true" style={{width:42,height:42,borderRadius:12,background:iconBg,display:'inline-flex',alignItems:'center',justifyContent:'center',color:iconFg,flexShrink:0,fontSize:emoji?20:undefined}}>{emoji||icon}</span>}
    <span style={{flex:1,minWidth:0}}>
      <span style={{display:'block',color:T.text,fontSize:fz(14.5),fontWeight:700,fontFamily:F,lineHeight:1.35}}>{title}</span>
      {sub&&<span style={{display:'block',color:T.muted,fontSize:fz(12.5),fontFamily:F,marginTop:2,lineHeight:1.45}}>{sub}</span>}
    </span>
    {trailing}
  </Tag>;
}

function Tabs({tabs,active,onChange}){
  return <div role="tablist" style={{display:'flex',background:T.cardAlt,backdropFilter:GLASS_SOFT,WebkitBackdropFilter:GLASS_SOFT,border:`1px solid ${T.line}`,borderRadius:14,padding:4,margin:`0 ${SP.safe}px`,gap:4}}>
    {tabs.map(t=>
      <button key={t} type="button" role="tab" aria-selected={t===active} onClick={()=>onChange(t)} className="cu-press" style={{flex:1,minHeight:40,border:'none',borderRadius:11,background:t===active?T.card:'transparent',backdropFilter:t===active?GLASS:'none',WebkitBackdropFilter:t===active?GLASS:'none',cursor:'pointer',color:t===active?T.text:T.muted,fontSize:fz(13.5),fontWeight:700,fontFamily:F,boxShadow:t===active?SH:'none',transition:`all .2s ${EASE}`}}>{t}</button>
    )}
  </div>;
}

function Field({label,id,type='text',value,onChange,placeholder,icon}){
  return <label style={{display:'block',marginBottom:14}}>
    <span style={{display:'block',color:T.sub,fontSize:fz(13),fontWeight:600,fontFamily:F,marginBottom:7}}>{label}</span>
    <span style={{display:'flex',alignItems:'center',gap:10,padding:'0 14px',minHeight:52,borderRadius:14,background:T.card,backdropFilter:GLASS,WebkitBackdropFilter:GLASS,border:`1.5px solid ${value?tint(40):T.lineMd}`,boxShadow:SH,transition:`border-color .15s ${EASE}`}}>
      {icon&&<span style={{color:T.muted,display:'inline-flex'}}><Ic k={icon} size={18}/></span>}
      <input id={id} type={type} value={value} onChange={e=>onChange(e.target.value)} placeholder={placeholder} autoComplete="off" autoCapitalize="off" style={{flex:1,border:'none',outline:'none',background:'transparent',color:T.text,fontSize:fz(15),fontFamily:F,minWidth:0}}/>
    </span>
  </label>;
}

/* ── XP / level pill ── */
function LevelBar({xp,onDark}){
  const info=window.CurioStore.levelInfo(xp);
  return <div>
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:7}}>
      <span style={{display:'inline-flex',alignItems:'center',gap:7,color:onDark?'#FFFFFF':T.text,fontSize:fz(13),fontWeight:800,fontFamily:F}}>
        <span style={{width:24,height:24,borderRadius:8,background:onDark?'rgba(255,255,255,.22)':'var(--ink)',color:'#FFFFFF',display:'inline-flex',alignItems:'center',justifyContent:'center',fontSize:fz(12),fontWeight:800}}>{info.level}</span>
        {xp.toLocaleString()} XP
      </span>
      <span style={{color:onDark?'rgba(255,255,255,.8)':T.muted,fontSize:fz(12),fontFamily:F}}>{info.toNext} XP →</span>
    </div>
    <PBar val={info.pct} onDark={onDark} grad={!onDark} label="Level progress"/>
  </div>;
}

/* ── confetti burst ── */
function Confetti({n=44}){
  const cols=['#7C5CFF','#FF5DA2','#FFB020','#0FB5C9','#18A957'];
  const bits=React.useMemo(()=>Array.from({length:n},(_,i)=>({
    x:Math.random()*100, d:Math.random()*0.5, r:Math.random()*360,
    c:cols[i%cols.length], s:6+Math.random()*8, dur:1.6+Math.random()*1.2, dx:(Math.random()-0.5)*120,
  })),[]);
  return <div aria-hidden="true" style={{position:'absolute',inset:0,overflow:'hidden',pointerEvents:'none',zIndex:50}}>
    {bits.map((b,i)=><span key={i} className="cu-confetti" style={{position:'absolute',top:-20,left:`${b.x}%`,width:b.s,height:b.s*0.6,background:b.c,borderRadius:2,transform:`rotate(${b.r}deg)`,animationDelay:`${b.d}s`,animationDuration:`${b.dur}s`,'--dx':`${b.dx}px`}}></span>)}
  </div>;
}

/* ── bottom sheet ── */
function Sheet({children,onClose,style:ext={}}){
  return <div style={{position:'absolute',inset:0,zIndex:60,display:'flex',flexDirection:'column',justifyContent:'flex-end',pointerEvents:'none'}}>
    {onClose&&<button type="button" aria-label="Close" onClick={onClose} style={{position:'absolute',inset:0,background:'rgba(16,12,28,.32)',backdropFilter:'blur(2px)',WebkitBackdropFilter:'blur(2px)',border:'none',cursor:'pointer',pointerEvents:'auto'}}></button>}
    <div className="cu-sheet" style={{position:'relative',background:T.card,backdropFilter:GLASS,WebkitBackdropFilter:GLASS,borderTop:'1px solid rgba(255,255,255,.65)',borderLeft:'1px solid rgba(255,255,255,.35)',borderRight:'1px solid rgba(255,255,255,.35)',borderRadius:'24px 24px 0 0',boxShadow:'0 -10px 40px rgba(16,12,28,.24)',padding:'12px 20px max(28px, env(safe-area-inset-bottom))',pointerEvents:'auto',...ext}}>
      <div aria-hidden="true" style={{width:44,height:5,borderRadius:5,background:T.lineMd,margin:'0 auto 20px'}}></div>
      {children}
    </div>
  </div>;
}

/* ── stops timeline ── */
function Stops({stops,activeIndex=0}){
  return <ol style={{listStyle:'none',margin:0,padding:0}}>
    {stops.map((s,i)=><li key={i} style={{display:'flex',gap:12}}>
      <span style={{display:'flex',flexDirection:'column',alignItems:'center',flexShrink:0}}>
        <span aria-hidden="true" style={{width:30,height:30,borderRadius:'50%',background:i===activeIndex?'var(--ink)':i<activeIndex?tint(16):T.cardAlt,border:`1.5px solid ${i<=activeIndex?tint(44):T.line}`,display:'inline-flex',alignItems:'center',justifyContent:'center',color:i===activeIndex?'#FFFFFF':i<activeIndex?'var(--ink-deep)':T.muted,fontSize:fz(12),fontWeight:800,fontFamily:F}}>{i<activeIndex?'✓':i+1}</span>
        {i<stops.length-1&&<span style={{width:2,flex:1,minHeight:20,background:i<activeIndex?tint(50):T.line,display:'block'}}></span>}
      </span>
      <span style={{paddingTop:5,paddingBottom:i<stops.length-1?16:0}}>
        <span style={{display:'block',color:i===activeIndex?T.text:T.sub,fontSize:fz(14.5),fontWeight:i===activeIndex?800:600,fontFamily:F}}>{s}</span>
      </span>
    </li>)}
  </ol>;
}

/* ── photo with overlay — skeleton while loading, calm fallback if the image fails ── */
function Photo({src,alt,h,radius=18,overlay,children,style:ext={}}){
  const[state,setState]=useState('loading');
  useEffect(()=>{ setState('loading'); },[src]);
  return <div style={{position:'relative',height:h,borderRadius:radius,overflow:'hidden',flexShrink:0,background:state==='error'?T.cardAlt:'#1a1530',...ext}}>
    {state==='loading'&&<div aria-hidden="true" className="cu-skel" style={{position:'absolute',inset:0}}></div>}
    {state==='error'
      ?<div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:T.muted}}><Ic k="camera" size={28}/></div>
      :<img src={src} alt={alt||''} loading="lazy" onLoad={()=>setState('loaded')} onError={()=>setState('error')} style={{position:'absolute',inset:0,width:'100%',height:'100%',objectFit:'cover',display:'block',opacity:state==='loaded'?1:0,transition:`opacity .35s ${EASE}`}}/>}
    {overlay&&state!=='error'&&<div aria-hidden="true" style={{position:'absolute',inset:0,background:overlay}}></div>}
    {children}
  </div>;
}

/* ── small thumbnail with skeleton + error fallback — for list rows/grids ── */
function Thumb({src,alt,size=56,radius=14,style:ext={}}){
  const[state,setState]=useState('loading');
  useEffect(()=>{ setState('loading'); },[src]);
  return <div style={{position:'relative',width:size,height:size,borderRadius:radius,overflow:'hidden',flexShrink:0,background:state==='error'?T.cardAlt:T.line,...ext}}>
    {state==='loading'&&<div aria-hidden="true" className="cu-skel" style={{position:'absolute',inset:0}}></div>}
    {state==='error'
      ?<div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:T.muted}}><Ic k="camera" size={Math.round(size*.34)}/></div>
      :<img src={src} alt={alt||''} loading="lazy" onLoad={()=>setState('loaded')} onError={()=>setState('error')} style={{width:'100%',height:'100%',objectFit:'cover',display:'block',opacity:state==='loaded'?1:0,transition:`opacity .3s ${EASE}`}}/>}
  </div>;
}

/* ── bottom nav ── */
function BNav({tab,setTab,t}){
  const items=[
    {id:'home',k:'home',l:t('tabHome')},
    {id:'explore',k:'map',l:t('tabExplore')},
    {id:'scan',k:'scan',l:t('tabScan'),center:true},
    {id:'crew',k:'users',l:t('tabCrew')},
    {id:'profile',k:'user',l:t('tabProfile')},
  ];
  return <nav aria-label="Main" className="cu-bnav" style={{minHeight:'calc(var(--cu-nav-h, 88px) + var(--cu-safe-bottom))',paddingTop:10,paddingBottom:'max(8px, var(--cu-safe-bottom))',background:'rgba(255,255,255,.78)',backdropFilter:GLASS,WebkitBackdropFilter:GLASS,borderTop:'1px solid rgba(255,255,255,.62)',display:'flex',alignItems:'stretch',flexShrink:0,position:'relative',zIndex:20}}>
    {items.map(it=>{
      const active=it.id===tab;
      if(it.center) return <button key={it.id} type="button" aria-label={it.l} onClick={()=>setTab(it.id)} className="cu-press" style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'flex-end',gap:4,paddingBottom:2,border:'none',background:'transparent',cursor:'pointer',color:T.muted}}>
        <span style={{width:48,height:48,borderRadius:16,marginTop:-8,background:GRAD,boxShadow:SHmd,display:'flex',alignItems:'center',justifyContent:'center',color:'#FFFFFF',border:'2.5px solid rgba(255,255,255,.92)'}}><Ic k={it.k} size={22} stroke={2.2}/></span>
        <span style={{fontSize:fz(10.5),fontWeight:700,fontFamily:F,color:active?'var(--ink-deep)':T.muted}}>{it.l}</span>
      </button>;
      return <button key={it.id} type="button" aria-label={it.l} aria-current={active?'page':undefined} onClick={()=>setTab(it.id)} className="cu-press" style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:5,border:'none',background:'transparent',cursor:'pointer',color:active?'var(--ink-deep)':T.muted,paddingTop:6}}>
        <Ic k={it.k} size={24} stroke={active?2.4:2} fill={false}/>
        <span style={{fontSize:fz(10.5),fontWeight:active?800:600,fontFamily:F}}>{it.l}</span>
      </button>;
    })}
  </nav>;
}

/* ── responsive shell: fullscreen on phone, framed on desktop ── */
function Shell({children,noNav,tab,setTab,t}){
  return <div className="cu-shell">
    <div className="cu-frame">
      <div style={{flex:1,overflow:'hidden',display:'flex',flexDirection:'column',position:'relative'}}>
        {children}
      </div>
      {!noNav&&<BNav tab={tab} setTab={setTab} t={t}/>}
    </div>
  </div>;
}

/* removed — prototype fake status bar; real devices use OS status bar + .cu-top-safe / .cu-scroll padding */
function SBar(){ return null; }

/* ── AR / scan helpers ── */
function ScanCorners({size=224,sweep=true}){
  const L=30,W=4,c='#FFFFFF';
  return <div aria-hidden="true" style={{width:size,height:size,position:'relative'}}>
    {[0,1].map(i=><div key={i} className="cu-pulse" style={{position:'absolute',inset:-(14+i*12),borderRadius:24,border:'1.5px solid rgba(255,255,255,.5)',animationDelay:`${i*.6}s`}}></div>)}
    {[{t:0,l:0,bt:1,bl:1},{t:0,r:0,bt:1,br:1},{b:0,l:0,bb:1,bl:1},{b:0,r:0,bb:1,br:1}].map((s,i)=><div key={i} style={{position:'absolute',width:L,height:L,top:s.t,left:s.l,right:s.r,bottom:s.b,borderTop:s.bt?`${W}px solid ${c}`:'none',borderLeft:s.bl?`${W}px solid ${c}`:'none',borderRight:s.br?`${W}px solid ${c}`:'none',borderBottom:s.bb?`${W}px solid ${c}`:'none',borderRadius:8}}></div>)}
    {sweep&&<div className="cu-sweep" style={{position:'absolute',left:10,right:10,height:3,borderRadius:3,background:'linear-gradient(90deg,transparent,#FFFFFF,transparent)',boxShadow:'0 0 12px rgba(255,255,255,.8)'}}></div>}
  </div>;
}

/* ── live camera — auto-starts on mobile, calm status pill if it can't, silent still-image fallback ── */
function CameraView({fallback,facingMode='environment',children}){
  const videoRef=useRef(null);
  const streamRef=useRef(null);
  const[state,setState]=useState('requesting');
  const[imgOk,setImgOk]=useState(true);
  useEffect(()=>{ setImgOk(true); },[fallback]);
  const{t}=useApp()||{t:(k)=>k};

  const stop=useCallback(()=>{ if(streamRef.current){ streamRef.current.getTracks().forEach(tr=>tr.stop()); streamRef.current=null; } },[]);
  const start=useCallback(async()=>{
    const md=navigator.mediaDevices;
    if(!md||!md.getUserMedia){ setState('unavailable'); return; }
    setState('requesting');
    try{
      const stream=await md.getUserMedia({video:{facingMode:{ideal:facingMode},width:{ideal:1280},height:{ideal:720}},audio:false});
      streamRef.current=stream;
      const v=videoRef.current;
      if(v){ v.srcObject=stream; v.setAttribute('playsinline',''); v.setAttribute('webkit-playsinline',''); v.muted=true; try{await v.play();}catch(e){} }
      setState('live');
    }catch(e){
      setState((e&&(e.name==='NotAllowedError'||e.name==='SecurityError'))?'denied':'unavailable');
    }
  },[facingMode]);

  useEffect(()=>{ start(); return stop; },[]); // eslint-disable-line
  const live=state==='live';
  const showImg=!live;

  return <div style={{position:'absolute',inset:0,overflow:'hidden',background:'#0B0814'}}>
    {showImg&&imgOk&&<img src={fallback} alt="" onError={()=>setImgOk(false)} style={{position:'absolute',inset:0,width:'100%',height:'100%',objectFit:'cover',opacity:1,transition:`opacity .4s ${EASE}`}}/>}
    {showImg&&!imgOk&&<div aria-hidden="true" style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',color:'rgba(255,255,255,.18)'}}><Ic k="camera" size={64}/></div>}
    <video ref={videoRef} playsInline muted autoPlay aria-hidden="true" style={{position:'absolute',inset:0,width:'100%',height:'100%',objectFit:'cover',opacity:live?1:0,transition:`opacity .5s ${EASE}`,background:'#0B0814'}}></video>

    {/* calm, non-blocking status pill — only shown when the camera truly can't start */}
    {(state==='denied'||state==='unavailable')&&
      <button type="button" onClick={start} className="cu-rise cu-press" style={{position:'absolute',top:'calc(64px + var(--cu-safe-top))',left:0,right:0,margin:'0 auto',width:'fit-content',maxWidth:'calc(100% - 32px)',display:'flex',alignItems:'center',gap:7,padding:'7px 14px',borderRadius:100,background:'rgba(20,16,32,.62)',backdropFilter:GLASS_DARK,WebkitBackdropFilter:GLASS_DARK,border:'1px solid rgba(255,255,255,.2)',color:'#FFFFFF',fontSize:fz(12),fontWeight:600,fontFamily:F,cursor:'pointer',zIndex:5,whiteSpace:'nowrap'}}>
        <Ic k="camera" size={13} color="rgba(255,255,255,.8)"/>
        {state==='denied'?t('camDeniedPill'):t('camUnavailablePill')}
      </button>}

    {children}
  </div>;
}

/* Full-screen image viewer */
function ImageViewer({src,alt,onClose}){
  return <div className="cu-rise" style={{position:'fixed',inset:0,zIndex:200,background:'rgba(0,0,0,.92)',display:'flex',alignItems:'center',justifyContent:'center'}} onClick={onClose}>
    <button type="button" onClick={onClose} aria-label="Close" className="cu-press" style={{position:'absolute',top:'max(16px, env(safe-area-inset-top))',right:16,width:44,height:44,borderRadius:22,background:'rgba(255,255,255,.15)',border:'none',color:'#fff',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',zIndex:201}}><Ic k="x" size={22}/></button>
    <img src={src} alt={alt||''} style={{maxWidth:'92%',maxHeight:'80%',objectFit:'contain',borderRadius:12}} onClick={e=>e.stopPropagation()}/>
  </div>;
}

/* tiny QR renderer using qrcode-generator global `qrcode` */
function QR({text,size=150,color='#16131F'}){
  const ref=useRef(null);
  useEffect(()=>{
    if(!window.qrcode||!ref.current) return;
    try{
      const q=window.qrcode(0,'M'); q.addData(text||'CURIO'); q.make();
      const n=q.getModuleCount(); const cv=ref.current; const ctx=cv.getContext('2d');
      const dpr=window.devicePixelRatio||1; cv.width=size*dpr; cv.height=size*dpr; cv.style.width=size+'px'; cv.style.height=size+'px';
      ctx.scale(dpr,dpr); ctx.fillStyle='#FFFFFF'; ctx.fillRect(0,0,size,size);
      const cell=size/n; ctx.fillStyle=color;
      for(let r=0;r<n;r++)for(let col=0;col<n;col++){ if(q.isDark(r,col)){ const x=col*cell,y=r*cell; ctx.fillRect(Math.floor(x),Math.floor(y),Math.ceil(cell),Math.ceil(cell)); } }
    }catch(e){}
  },[text,size]);
  return <canvas ref={ref} style={{borderRadius:10,display:'block'}} aria-label="QR code"></canvas>;
}

const INSTALL_COPY = {
  en: {
    title: 'Add Curio to your home screen',
    sub: 'Install once for full-screen, faster access, and camera on quests.',
    notNow: 'Not now',
    install: 'Install app',
    gotIt: 'Got it',
    steps: {
      safari: ['Tap the Share button ⎙', 'Choose Add to Home Screen', 'Tap Add'],
      chrome: ['Tap the menu ⋮', 'Tap Install app or Add to Home screen', 'Confirm Install'],
      edge: ['Tap the menu …', 'Tap Apps → Install this site as an app', 'Confirm Install'],
      firefox: ['Tap the menu ☰', 'Tap Install or Add to Home screen', 'Confirm'],
      samsung: ['Tap the menu ≡', 'Tap Add page to → Home screen', 'Tap Add'],
      other: ['Open browser menu', 'Choose Add to Home Screen or Install', 'Confirm'],
    },
  },
  de: {
    title: 'Curio zum Home-Bildschirm hinzufügen',
    sub: 'Einmal installieren für Vollbild, schnelleren Zugriff und Kamera bei Quests.',
    notNow: 'Später',
    install: 'App installieren',
    gotIt: 'Verstanden',
    steps: {
      safari: ['Teilen-Symbol ⎙ antippen', 'Zum Home-Bildschirm wählen', 'Hinzufügen antippen'],
      chrome: ['Menü ⋮ antippen', 'App installieren oder Zum Startbildschirm', 'Installieren bestätigen'],
      edge: ['Menü … antippen', 'Apps → Als App installieren', 'Installieren bestätigen'],
      firefox: ['Menü ☰ antippen', 'Installieren oder Zum Startbildschirm', 'Bestätigen'],
      samsung: ['Menü ≡ antippen', 'Seite hinzufügen → Startbildschirm', 'Hinzufügen antippen'],
      other: ['Browser-Menü öffnen', 'Zum Home-Bildschirm oder Installieren', 'Bestätigen'],
    },
  },
};

function InstallPrompt({lang='en', visible, onClose}){
  const [nativeReady, setNativeReady] = useState(() => window.CurioInstall?.canNativeInstall?.() || false);
  useEffect(() => {
    if (!visible) return;
    const fn = () => setNativeReady(true);
    window.addEventListener('curio-install-ready', fn);
    return () => window.removeEventListener('curio-install-ready', fn);
  }, [visible]);
  const CI = window.CurioInstall;
  if (!visible || !CI || CI.isStandalone() || CI.isDismissed()) return null;
  const de = lang === 'de';
  const copy = INSTALL_COPY[de ? 'de' : 'en'];
  const browser = CI.detectBrowser();
  const canInstall = nativeReady && CI.canNativeInstall();
  const steps = copy.steps[browser] || copy.steps.other;
  function dismiss() { CI.dismiss(); onClose?.(); }
  async function handlePrimary() {
    if (canInstall) { try { await CI.promptInstall(); } catch (e) {} }
    else dismiss();
    onClose?.();
  }
  return <div className="cu-install-overlay cu-rise" role="dialog" aria-labelledby="cu-install-title" aria-modal="true" onClick={dismiss}>
    <div className="cu-install-card" onClick={e => e.stopPropagation()}>
      <button type="button" className="cu-install-close" aria-label="Close" onClick={dismiss}>×</button>
      <div className="cu-install-row">
        <img className="cu-install-icon" src="img/icon-192.png" alt="" width="52" height="52"/>
        <div>
          <div id="cu-install-title" className="cu-install-title">{copy.title}</div>
          <p className="cu-install-sub">{copy.sub}</p>
        </div>
      </div>
      <ol className="cu-install-steps">
        {steps.map((s, i) => <li key={i}>{s}</li>)}
      </ol>
      <div className="cu-install-actions">
        <button type="button" className="cu-install-btn cu-install-btn-secondary" onClick={dismiss}>{copy.notNow}</button>
        <button type="button" className="cu-install-btn cu-install-btn-primary" onClick={handlePrimary}>{canInstall ? copy.install : copy.gotIt}</button>
      </div>
    </div>
  </div>;
}

function ActiveSessionBar(){
  const{state,nav,t,lang}=useApp();
  const L=v=>pick(v,lang);
  const de=lang==='de';
  const visit=state.activeVisit;
  const q=state.activeQuestId?window.DATA.questById(state.activeQuestId):null;
  const prog=q&&state.questProgress[q.id];
  const questLive=q&&prog&&prog.started&&!prog.completed;
  if(visit){
    const vq=window.DATA.questById(visit.questId);
    const cur=visit.currentStop||0;
    const total=visit.stops.length;
    return <button type="button" className="cu-session-bar cu-session-bar--visit cu-press" onClick={()=>nav.push('visit-hub')}>
      <span style={{width:40,height:40,borderRadius:12,background:'rgba(255,255,255,.18)',display:'inline-flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><Ic k="map" size={20} color="#fff"/></span>
      <span style={{flex:1,minWidth:0}}>
        <span style={{display:'block',color:'#fff',fontSize:fz(13),fontWeight:800,fontFamily:F}}>{de?'Besuch läuft':'Visit in progress'}</span>
        <span style={{display:'block',color:'rgba(255,255,255,.85)',fontSize:fz(12),fontFamily:F,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{vq?.venue||''} · {Math.min(cur+1,total)}/{total}</span>
      </span>
      <span style={{color:'#fff',fontSize:fz(12),fontWeight:700,fontFamily:F,flexShrink:0}}>{de?'Fortsetzen':'Resume'}</span>
      <Ic k="chevR" size={16} color="#fff"/>
    </button>;
  }
  if(questLive){
    return <button type="button" className="cu-session-bar cu-press" onClick={()=>nav.push('group-board',{questId:q.id})}>
      <span style={{width:40,height:40,borderRadius:12,background:'rgba(255,255,255,.18)',display:'inline-flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><Ic k="sparkle" size={20} color="#fff" fill/></span>
      <span style={{flex:1,minWidth:0}}>
        <span style={{display:'block',color:'#fff',fontSize:fz(13),fontWeight:800,fontFamily:F}}>{de?'Quest läuft':'Quest in progress'}</span>
        <span style={{display:'block',color:'rgba(255,255,255,.85)',fontSize:fz(12),fontFamily:F,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{L(q.title)} · {prog.stop+1}/{prog.total}</span>
      </span>
      <span style={{color:'#fff',fontSize:fz(12),fontWeight:700,fontFamily:F,flexShrink:0}}>{t('continueQuest')}</span>
      <Ic k="chevR" size={16} color="#fff"/>
    </button>;
  }
  return null;
}

Object.assign(window,{
  T,F,FD,FW,fz,tint,tintGlass,GRAD,GRAD_WARM,SH,SHmd,SHlg,SP,EASE,GLASS,GLASS_SOFT,GLASS_DARK,AV_GRADS,
  AppCtx,useApp,Ic,Gap,Chip,Btn,IconBtn,BackRow,StepShell,Switch,Av,AvStack,PBar,Card,SecTitle,ListRow,Tabs,Field,EmptyState,
  LevelBar,Confetti,Sheet,Photo,Thumb,BNav,Shell,SBar,ScanCorners,CameraView,ImageViewer,QR,InstallPrompt,Stops,joinUrl,prettyUrl,APP_BASE,
  showToast,ToastHost,ActiveSessionBar,
});
