// VYB LIFE — Detail Views (full-page views for each section)

// ─── ViewHeader ────────────────────────────────────────────────
// Subtitle prop is accepted for back-compat but intentionally not rendered:
// section headers are kept minimal (eyebrow + title only).
// Mobile layout: row 1 = eyebrow (left) + date (right); row 2 = title.
const ViewHeader = ({ label, title, subtitle, action }) => {
  const [isMobile, setIsMobile] = React.useState(() =>
    typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(max-width: 768px)').matches);
  React.useEffect(() => {
    if (typeof window === 'undefined' || !window.matchMedia) return;
    const mq = window.matchMedia('(max-width: 768px)');
    const onChange = e => setIsMobile(e.matches);
    mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange); };
  }, []);
  const lang = (typeof getLang === 'function') ? getLang() : 'en';
  const dateLocale = lang === 'es' ? 'es-ES' : 'en-US';
  const dateStr = new Date().toLocaleDateString(dateLocale, { weekday:'long', month:'long', day:'numeric' });

  if (isMobile) {
    return (
      <div style={{marginBottom:20}}>
        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:12,marginBottom:6}}>
          <div style={{fontSize:10,fontWeight:200,letterSpacing:'0.22em',color:C.textFaint,textTransform:'uppercase',whiteSpace:'nowrap'}}>{label}</div>
          <div style={{fontSize:10,fontWeight:300,letterSpacing:'0.12em',color:C.textFaint,textTransform:'uppercase',textAlign:'right',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis',maxWidth:'58%'}}>{dateStr}</div>
        </div>
        <div className="vyb-page-title" style={{fontSize:34,fontWeight:900,fontStyle:'italic',color:C.textPrimary,letterSpacing:'-0.025em',textTransform:'uppercase',lineHeight:1,marginBottom:action?12:0,wordBreak:'break-word'}}>{title}</div>
        {action && <div style={{display:'flex',justifyContent:'flex-end',marginTop:10}}>{action}</div>}
      </div>
    );
  }
  return (
    <div style={{display:'flex',alignItems:'flex-end',justifyContent:'space-between',flexWrap:'wrap',gap:16,marginBottom:32}}>
      <div>
        <div style={{fontSize:10,fontWeight:200,letterSpacing:'0.22em',color:C.textFaint,textTransform:'uppercase',marginBottom:10}}>{label}</div>
        <div className="vyb-page-title" style={{fontSize:48,fontWeight:900,fontStyle:'italic',color:C.textPrimary,letterSpacing:'-0.025em',textTransform:'uppercase',lineHeight:1,marginBottom:0}}>{title}</div>
      </div>
      {action}
    </div>
  );
};

// ─── HABITS VIEW ───────────────────────────────────────────────
const HABIT_ICONS = ['target','pen-line','book-open','circle','droplets','activity','sunrise','moon','coffee','salad','dumbbell','footprints','brain','sparkles','heart','leaf','briefcase'];

// Day map: Sun=0..Sat=6 (matches JS Date.getDay()).
// Day labels for compact preset summaries: 'daily', 'weekdays', 'Sunday', or 'Nx/week'.
const WEEKDAYS = [1,2,3,4,5];
const summarizePreset = (frequency, customDays) => {
  if (frequency === 'daily') return 'daily';
  const set = new Set(customDays || []);
  if (set.size === 5 && WEEKDAYS.every(d => set.has(d))) return 'weekdays';
  if (set.size === 1) {
    const map = {0:'Sunday',1:'Monday',2:'Tuesday',3:'Wednesday',4:'Thursday',5:'Friday',6:'Saturday'};
    return map[[...set][0]];
  }
  return `${set.size}x/week`;
};
// Smart presets: clicking opens the Add Habit form prefilled (does NOT auto-create).
const HABIT_TEMPLATES = {
  'Health': [
    { name:'Gym',         frequency:'custom', customDays:[1,2,4,6] },
    { name:'Run',         frequency:'custom', customDays:[2,4,6] },
    { name:'Eat healthy', frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Drink water', frequency:'daily',  customDays:[] },
  ],
  'Personal Growth': [
    { name:'Read',           frequency:'daily',  customDays:[] },
    { name:'Journal',        frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Wake up early',  frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Sleep early',    frequency:'custom', customDays:[1,2,3,4,5] },
  ],
  'Focus': [
    { name:'Deep work',           frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Plan top 3',          frequency:'daily',  customDays:[] },
    { name:'No phone first hour', frequency:'custom', customDays:[1,2,3,4,5] },
  ],
  'Finance': [
    { name:'Track expenses', frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Review budget',  frequency:'custom', customDays:[0] },
    { name:'Save money',     frequency:'custom', customDays:[5] },
  ],
  'Creativity': [
    { name:'Capture ideas',  frequency:'daily',  customDays:[] },
    { name:'Write hooks',    frequency:'custom', customDays:[1,3,5] },
    { name:'Create content', frequency:'custom', customDays:[2,4,6] },
  ],
  'Lifestyle': [
    { name:'Clean room',  frequency:'custom', customDays:[1,3,6] },
    { name:'Go outside',  frequency:'daily',  customDays:[] },
    { name:'Call family', frequency:'custom', customDays:[0] },
  ],
  'Business': [
    { name:'Sales action',         frequency:'custom', customDays:[1,2,3,4,5] },
    { name:'Review pipeline',      frequency:'custom', customDays:[5] },
    { name:'Work on main project', frequency:'custom', customDays:[1,2,3,4,5] },
  ],
};

const AREA_ORDER = ['Health','Personal Growth','Finance','Focus','Creativity','Lifestyle','Business'];

const HABIT_EXAMPLES = {
  'Health':          'Gym · Run · Eat healthy · Drink water',
  'Personal Growth': 'Read · Journal · Wake up early · Sleep early',
  'Focus':           'Deep work · Plan top 3 · No phone first hour',
  'Finance':         'Track expenses · Save money · Review investments',
  'Creativity':      'Capture ideas · Write hooks · Create content',
  'Business':        'Sales action · Review pipeline · Work on main project',
  'Lifestyle':       'Clean room · Go outside · Call family',
};

const MAX_AREAS = 4;

const HabitFormModal = ({ open, onClose, initial, categories, onSave, onDelete }) => {
  const tr = useT();
  const selectable = categories.filter(c => c.name !== 'Uncategorized');
  const active = selectable.filter(c => c.active !== false);
  const inactive = selectable.filter(c => c.active === false);
  const blank = { name:'', icon:'target', categoryId: null, frequency: 'daily', customDays: [] };
  const [form, setForm] = React.useState(initial ? { frequency:'daily', customDays:[], ...initial } : blank);
  const [error, setError] = React.useState('');
  React.useEffect(() => {
    if (open) {
      setForm(initial ? { frequency:'daily', customDays:[], ...initial } : blank);
      setError('');
    }
  }, [open, initial]);
  const isEdit = !!initial?.id;
  const toggleDay = (d) => {
    setForm(f => {
      const cur = Array.isArray(f.customDays) ? f.customDays : [];
      const next = cur.includes(d) ? cur.filter(x => x !== d) : [...cur, d].sort();
      return { ...f, customDays: next };
    });
  };
  const save = () => {
    if (!form.name.trim()) { setError(tr('habits.formErrName')); return; }
    if (!form.categoryId) { setError(tr('habits.formErrArea')); return; }
    if (form.frequency === 'custom' && (!form.customDays || form.customDays.length === 0)) {
      setError(tr('habits.formErrDays')); return;
    }
    onSave({
      name: form.name.trim(),
      icon: form.icon,
      categoryId: form.categoryId,
      frequency: form.frequency || 'daily',
      customDays: form.frequency === 'custom' ? (form.customDays || []) : [],
    });
    onClose();
  };
  // Sun=0..Sat=6 (matches Date.getDay()).
  const DAY_LABELS = [
    { d: 1, l: tr('weekday.shortMon') }, { d: 2, l: tr('weekday.shortTue') }, { d: 3, l: tr('weekday.shortWed') },
    { d: 4, l: tr('weekday.shortThu') }, { d: 5, l: tr('weekday.shortFri') }, { d: 6, l: tr('weekday.shortSat') }, { d: 0, l: tr('weekday.shortSun') },
  ];
  const sel = {
    background:'rgba(250,250,248,0.03)', border:`1px solid ${C.border}`, borderRadius:8,
    padding:'10px 14px', fontSize:12, color:C.textPrimary, outline:'none',
    fontFamily:'Montserrat,sans-serif', width:'100%', appearance:'none',
  };
  // Subtle identity / change-with-intention microcopy.
  const initialFreq = initial && (initial.frequency || 'daily');
  const initialDays = (initial && Array.isArray(initial.customDays)) ? initial.customDays : [];
  const scheduleChanged = isEdit && (
    (form.frequency || 'daily') !== initialFreq ||
    JSON.stringify((form.customDays || []).slice().sort()) !== JSON.stringify(initialDays.slice().sort())
  );

  return (
    <Modal open={open} onClose={onClose} title={isEdit ? tr('habits.formEditHabit') : tr('habits.formNewHabit')} width={460}>
      <div style={{marginTop:2,marginBottom:18,
        fontSize:11,fontWeight:300,fontStyle:'italic',color:C.textFaint,letterSpacing:'0.02em',
        lineHeight:1.5,whiteSpace:'normal',overflow:'visible'}}>
        {isEdit ? tr('habits.formIntroEdit') : tr('habits.formIntroNew')}
      </div>

      {/* Name + Area on the same row. Name takes the remaining space, Area is
          a compact dropdown anchored right. Stacks only if the row gets very
          narrow (<360px) via wrap. */}
      <div style={{display:'flex',gap:12,alignItems:'flex-end',flexWrap:'wrap'}}>
        <div style={{flex:'1 1 200px',minWidth:0}}>
          <Label mb={8}>{tr('habits.formName')}</Label>
          <Input value={form.name} onChange={e=>{setForm({...form,name:e.target.value}); if(error) setError('');}} placeholder={tr('habits.formNamePlaceholder')} autoFocus
            onKeyDown={e=>e.key==='Enter'&&save()} />
        </div>
        <div style={{flex:'0 1 150px',minWidth:130}}>
          {/* Label removed — placeholder "Área/Area" inside the dropdown is enough.
              Parent row uses alignItems:'flex-end' so Name's input and this select
              still bottom-align cleanly. */}
          <select
            style={{
              width:'100%',
              background:'rgba(250,250,248,0.03)',border:`1px solid ${C.border}`,borderRadius:8,
              padding:'10px 28px 10px 12px',fontSize:12,fontWeight:500,
              color: form.categoryId ? C.textSecondary : C.textFaint,
              outline:'none',fontFamily:'Montserrat,sans-serif',appearance:'none',cursor:'pointer',
              backgroundImage:`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23665E58' stroke-width='2'><polyline points='6 9 12 15 18 9'/></svg>")`,
              backgroundRepeat:'no-repeat',backgroundPosition:'right 10px center',
            }}
            value={form.categoryId ?? ''}
            onChange={e=>{setForm({...form,categoryId: e.target.value ? Number(e.target.value) : null}); if(error) setError('');}}>
            <option value="">{tr('habits.formArea')}</option>
            {active.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            {inactive.length > 0 && <option disabled>──────────</option>}
            {inactive.map(c => <option key={c.id} value={c.id}>+ {c.name} {tr('habits.formAreaSuggested')}</option>)}
          </select>
        </div>
      </div>

      <div style={{height:18}}/>
      <Label mb={8}>{tr('habits.formFrequency')}</Label>
      {/* Equal-width frequency buttons across the row. */}
      <div style={{display:'flex',gap:10}}>
        {[
          { v:'daily',  l:tr('habits.formEveryDay') },
          { v:'custom', l:tr('habits.formCustomDays') },
        ].map(opt => {
          const active = (form.frequency || 'daily') === opt.v;
          return (
            <button key={opt.v} type="button"
              onClick={()=>{ setForm(f=>({...f, frequency: opt.v})); if(error) setError(''); }}
              style={{
                flex:1,padding:'10px 12px',borderRadius:999,cursor:'pointer',
                background: active ? C.sandFaint : 'rgba(255,255,255,0.03)',
                border:`1px solid ${active ? C.sand : C.border}`,
                color: active ? C.textPrimary : C.textMuted,
                fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:600,letterSpacing:'0.04em',
                whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
              {opt.l}
            </button>
          );
        })}
      </div>

      {form.frequency === 'custom' && (
        <div style={{marginTop:12,display:'grid',gap:6,
          gridTemplateColumns:'repeat(7, minmax(0, 1fr))'}}>
          {DAY_LABELS.map(({d,l}) => {
            const on = (form.customDays || []).includes(d);
            return (
              <button key={d} type="button" onClick={()=>{ toggleDay(d); if(error) setError(''); }}
                style={{
                  width:'100%',height:36,borderRadius:8,cursor:'pointer',
                  background: on ? C.sand : 'rgba(255,255,255,0.03)',
                  border:`1px solid ${on ? C.sand : C.border}`,
                  color: on ? C.bg : C.textMuted,
                  fontFamily:'Montserrat,sans-serif',fontSize:10,fontWeight:700,letterSpacing:'0.04em'}}>
                {l}
              </button>
            );
          })}
        </div>
      )}

      <div style={{marginTop:12,fontSize:11,fontWeight:300,color:C.textMuted,
        letterSpacing:'0.02em',lineHeight:1.55,fontStyle:'italic',
        display:'block',width:'100%',whiteSpace:'normal',wordBreak:'normal',
        overflow:'visible',maxWidth:'100%'}}>
        {(form.frequency || 'daily') === 'daily'
          ? tr('habits.formHelperDaily')
          : tr('habits.formHelperCustom')}
      </div>

      {error && (
        <div style={{marginTop:8,fontSize:11,fontWeight:300,color:C.sand,fontStyle:'italic'}}>{error}</div>
      )}

      <div style={{display:'flex',gap:10,marginTop:24,justifyContent:'space-between'}}>
        {onDelete ? <Button variant="danger" icon="trash-2" onClick={()=>{ onDelete(); onClose(); }}>{tr('common.delete')}</Button> : <span/>}
        <div style={{display:'flex',gap:10}}>
          <Button variant="ghost" onClick={onClose}>{tr('common.cancel')}</Button>
          <Button onClick={save}>{isEdit?tr('common.save'):tr('habits.formCreate')}</Button>
        </div>
      </div>
    </Modal>
  );
};

const HabitRow = ({ h, today, editMode, onToggle, onRename, onDelete, onEdit, editHint }) => {
  const streak = calcStreak(h.checkIns);
  const done = !!h.checkIns[today];
  const [renaming, setRenaming] = React.useState(false);
  const [draft, setDraft] = React.useState(h.name);
  const [celebrate, setCelebrate] = React.useState(0); // bump key to retrigger
  const prevDoneRef = React.useRef(done);
  const rowRef = React.useRef(null);
  React.useEffect(() => { setDraft(h.name); }, [h.name]);
  // Gently scroll the new habit into view so the pulse hint is visible.
  React.useEffect(() => {
    if (!editHint || !rowRef.current) return;
    try { rowRef.current.scrollIntoView({ behavior:'smooth', block:'nearest' }); } catch {}
  }, [editHint]);
  React.useEffect(() => {
    const prev = prevDoneRef.current;
    prevDoneRef.current = done;
    if (!prev && done && !editMode) {
      const reduced = typeof window !== 'undefined' && window.matchMedia
        && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      if (reduced) return;
      setCelebrate(c => c + 1);
      const t = setTimeout(() => setCelebrate(0), 1500);
      return () => clearTimeout(t);
    }
  }, [done, editMode]);
  const commit = () => {
    const v = draft.trim();
    if (v && v !== h.name) onRename(v);
    setRenaming(false);
  };
  const animating = celebrate > 0;
  return (
    <div ref={rowRef} className={`vyb-habit-row${animating?' vyb-habit-celebrate':''}`}
      style={{display:'flex',alignItems:'center',gap:12,padding:'8px 0',borderTop:`1px solid ${C.border}`,position:'relative'}}>
      <div className="vyb-habit-checkbox" onClick={()=>onToggle()} title={done?'Mark not done':'Mark done'}
        style={{width:20,height:20,borderRadius:999,flexShrink:0,cursor:'pointer',
          border:`1.5px solid ${done?C.sand:C.borderMid}`,
          background:done?C.sand:'transparent',
          display:'flex',alignItems:'center',justifyContent:'center',
          transition:'background 0.25s ease, border-color 0.25s ease'}}>
        {done && (
          <span key={celebrate} className={animating?'vyb-check-pop':''} style={{display:'flex'}}>
            <Icon name="check" size={10} color={C.bg} sw={2.5}/>
          </span>
        )}
        {animating && <span key={`r1-${celebrate}`} className="vyb-ripple"/>}
        {animating && <span key={`r2-${celebrate}`} className="vyb-ripple vyb-ripple-2"/>}
      </div>
      {animating && <span key={`m-${celebrate}`} className="vyb-momentum">Momentum built.</span>}
      {editMode && renaming ? (
        <input value={draft} onChange={e=>setDraft(e.target.value)} autoFocus
          onBlur={commit} onKeyDown={e=>{ if(e.key==='Enter') commit(); if(e.key==='Escape'){ setDraft(h.name); setRenaming(false); } }}
          style={{flex:1,minWidth:0,background:'rgba(250,250,248,0.03)',border:`1px solid ${C.sand}`,borderRadius:6,
            padding:'5px 9px',fontSize:13,fontWeight:500,color:C.textPrimary,outline:'none',fontFamily:'Montserrat,sans-serif'}}/>
      ) : (
        <span
          onClick={editMode ? ()=>setRenaming(true) : undefined}
          title={editMode ? 'Click to rename' : undefined}
          style={{fontSize:13,fontWeight:500,color:C.textPrimary,flex:1,minWidth:0,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',
            textDecoration:done?'line-through':'none',textDecorationColor:C.textFaint,
            cursor: editMode ? 'text' : 'default'}}>{h.name}</span>
      )}
      {editMode ? (
        <div style={{display:'inline-flex',gap:4}}>
          {onEdit && (
            <span className={editHint ? 'vyb-edit-hint' : ''} style={{position:'relative',display:'inline-flex'}}>
              <IconButton icon="settings-2" onClick={onEdit} title="Edit schedule"/>
            </span>
          )}
          <IconButton icon="trash-2" onClick={onDelete} title="Delete"/>
        </div>
      ) : (
        <span style={{fontSize:12,fontWeight:700,color:streak>0?C.sand:C.textFaint,fontStyle:'italic',minWidth:28,textAlign:'right'}}>
          {streak}<span style={{fontSize:9,fontWeight:300,color:C.textFaint,marginLeft:2,fontStyle:'normal'}}>d</span>
        </span>
      )}
    </div>
  );
};

const AreaChip = ({ area, selected, onClick }) => (
  <button onClick={onClick}
    style={{
      display:'inline-flex',alignItems:'center',gap:8,
      padding:'8px 14px',borderRadius:999,cursor:'pointer',
      background: selected ? C.sandFaint : 'rgba(255,255,255,0.03)',
      border: `1px solid ${selected ? C.sand : C.border}`,
      color: selected ? C.textPrimary : C.textMuted,
      fontSize:12,fontWeight:600,fontFamily:'Montserrat,sans-serif',
      transition:'all 0.18s ease',
    }}
    onMouseEnter={e=>{ if(!selected){ e.currentTarget.style.borderColor=C.borderMid; e.currentTarget.style.color=C.textPrimary; }}}
    onMouseLeave={e=>{ if(!selected){ e.currentTarget.style.borderColor=C.border; e.currentTarget.style.color=C.textMuted; }}}>
    <Icon name={area.icon || 'target'} size={13} color={selected ? C.sand : 'currentColor'}/>
    {area.name}
    {selected
      ? <Icon name="check" size={12} color={C.sand} sw={2.5}/>
      : <Icon name="plus" size={12}/>}
  </button>
);

const AreaListRow = ({ area, selected, disabled, onClick }) => {
  const examples = HABIT_EXAMPLES[area.name];
  const interactive = !disabled || selected;
  return (
    <div onClick={interactive ? onClick : undefined}
      style={{
        position:'relative',
        display:'flex',flexDirection:'column',alignItems:'flex-start',gap:10,
        padding:'16px 14px 14px',borderRadius:12,
        cursor: interactive ? 'pointer' : 'not-allowed',
        opacity: interactive ? 1 : 0.45,
        background: selected ? 'rgba(160,138,86,0.07)' : 'transparent',
        border: `1px solid ${selected ? C.sand : C.border}`,
        boxShadow: selected ? '0 0 0 1px rgba(160,138,86,0.18)' : 'none',
        transition:'all 0.18s ease',
        minHeight:108,
      }}
      onMouseEnter={e=>{ if(interactive && !selected){ e.currentTarget.style.borderColor=C.borderMid; e.currentTarget.style.background='rgba(255,255,255,0.02)'; }}}
      onMouseLeave={e=>{ if(interactive && !selected){ e.currentTarget.style.borderColor=C.border; e.currentTarget.style.background='transparent'; }}}>
      <div style={{
        width:30,height:30,borderRadius:8,
        display:'flex',alignItems:'center',justifyContent:'center',
        background: selected ? C.sandFaint : 'transparent',
        border: `1px solid ${selected ? C.sand : C.border}`,
        transition:'all 0.18s ease',
        opacity: selected ? 1 : 0.85,
      }}>
        <Icon name={area.icon || 'target'} size={14} color={selected ? C.sand : C.textMuted}/>
      </div>
      <div style={{minWidth:0,width:'100%'}}>
        <div style={{fontSize:14.5,fontWeight:700,color:C.textPrimary,letterSpacing:'-0.015em',lineHeight:1.2,marginBottom:4}}>
          {area.name}
        </div>
        {examples && (
          <div style={{fontSize:10,fontWeight:300,color:C.textFaint,letterSpacing:'0.01em',lineHeight:1.45,
            overflow:'hidden',display:'-webkit-box',WebkitLineClamp:2,WebkitBoxOrient:'vertical'}}>
            {examples}
          </div>
        )}
      </div>
      {selected && (
        <div style={{position:'absolute',top:10,right:10,width:18,height:18,borderRadius:999,background:C.sand,
          display:'flex',alignItems:'center',justifyContent:'center'}}>
          <Icon name="check" size={10} color={C.bg} sw={2.5}/>
        </div>
      )}
    </div>
  );
};

const AreaQuickAdd = ({ areaName, onAdd }) => {
  const [val, setVal] = React.useState('');
  const submit = () => {
    const v = val.trim();
    if (!v) return;
    onAdd(v);
    setVal('');
  };
  const has = val.trim().length > 0;
  return (
    <div style={{display:'flex',gap:6,marginBottom:10,marginTop:10}}>
      <input value={val} onChange={e=>setVal(e.target.value)}
        onKeyDown={e=>{ if(e.key==='Enter'){ e.preventDefault(); submit(); } }}
        placeholder={`Add a habit for your ${areaName} system…`}
        style={{flex:1,background:'rgba(255,255,255,0.03)',border:`1px solid ${C.border}`,borderRadius:8,
          padding:'7px 11px',fontSize:12,color:C.textPrimary,fontFamily:'Montserrat,sans-serif',outline:'none'}}/>
      <button onClick={submit} disabled={!has}
        style={{background:has?C.sand:'rgba(255,255,255,0.05)',border:'none',borderRadius:8,
          padding:'7px 14px',fontSize:10,fontWeight:700,letterSpacing:'0.14em',textTransform:'uppercase',
          color:has?C.bg:C.textFaint,cursor:has?'pointer':'not-allowed',
          fontFamily:'Montserrat,sans-serif'}}>Add</button>
    </div>
  );
};

const AreaSelector = ({ categories, expanded, onExpand, onCollapse, onCommit }) => {
  // Hide "Uncategorized" — it's only a fallback bucket, never user-selectable.
  const selectable = categories.filter(c => c.name !== 'Uncategorized');
  const active = selectable.filter(c => c.active !== false);
  const setupMode = active.length === 0;

  // In setup: pending starts empty. In established (expanded): seed with current active.
  const [pending, setPending] = React.useState(() => new Set(active.map(c=>c.id)));
  React.useEffect(() => {
    setPending(setupMode ? new Set() : new Set(active.map(c=>c.id)));
    // Re-seed whenever the picker opens or the underlying active set changes.
  }, [expanded, setupMode, categories.map(c=>`${c.id}:${c.active!==false?1:0}`).join(',')]);

  const [limitHit, setLimitHit] = React.useState(false);
  const togglePending = (id) => {
    setPending(prev => {
      const next = new Set(prev);
      if (next.has(id)) {
        next.delete(id);
        setLimitHit(false);
      } else {
        if (next.size >= MAX_AREAS) { setLimitHit(true); return prev; }
        next.add(id);
        setLimitHit(false);
      }
      return next;
    });
  };

  if (setupMode) {
    return (
      <Card style={{padding:18, marginBottom:14}}>
        <Label mb={4}>Choose your areas</Label>
        <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginBottom:14,fontStyle:'italic'}}>
          Pick up to {MAX_AREAS}.
        </div>
        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(150px,1fr))',gap:10}}>
          {selectable.map(a => (
            <AreaListRow key={a.id} area={a}
              selected={pending.has(a.id)}
              disabled={!pending.has(a.id) && pending.size >= MAX_AREAS}
              onClick={()=>togglePending(a.id)}/>
          ))}
        </div>
        {limitHit && (
          <div style={{fontSize:11,fontWeight:300,color:C.sand,fontStyle:'italic',marginTop:10}}>
            Start with up to {MAX_AREAS} areas. You can adjust later.
          </div>
        )}
        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginTop:12,gap:10,flexWrap:'wrap'}}>
          <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
            {pending.size === 0 ? 'Select at least one area to continue.' : `${pending.size} ${pending.size===1?'area':'areas'} selected`}
          </div>
          <Button icon="arrow-right" disabled={pending.size === 0}
            onClick={()=>onCommit(Array.from(pending))}>
            Start building
          </Button>
        </div>
      </Card>
    );
  }

  // Established mode: subtle "Edit areas" trigger; expanded picker on click.
  if (!expanded) {
    return (
      <div style={{marginBottom:16,display:'flex',justifyContent:'flex-end'}}>
        <button onClick={onExpand}
          style={{background:'transparent',border:'none',padding:0,cursor:'pointer',
            color:C.textMuted,fontSize:11,fontWeight:500,letterSpacing:'0.12em',textTransform:'uppercase',
            fontFamily:'Montserrat,sans-serif',display:'inline-flex',alignItems:'center',gap:6,
            textDecoration:'underline',textUnderlineOffset:'3px',textDecorationColor:C.border}}>
          <Icon name="layers" size={11}/>Edit areas
        </button>
      </div>
    );
  }
  return (
    <Card style={{padding:14, marginBottom:14}}>
      <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',gap:10,marginBottom:10}}>
        <div>
          <Label mb={4}>Edit areas</Label>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted}}>Add or remove areas anytime.</div>
        </div>
        <button onClick={onCollapse}
          style={{background:'transparent',border:'none',padding:4,cursor:'pointer',color:C.textMuted,lineHeight:0}} title="Close">
          <Icon name="x" size={14}/>
        </button>
      </div>
      <div style={{display:'grid',gridTemplateColumns:'repeat(2,minmax(0,1fr))',gap:6}}>
        {selectable.map(a => (
          <AreaListRow key={a.id} area={a}
            selected={pending.has(a.id)}
            disabled={!pending.has(a.id) && pending.size >= MAX_AREAS}
            onClick={()=>togglePending(a.id)}/>
        ))}
      </div>
      {limitHit && (
        <div style={{fontSize:11,fontWeight:300,color:C.sand,fontStyle:'italic',marginTop:10}}>
          Start with up to {MAX_AREAS} areas. You can adjust later.
        </div>
      )}
      <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginTop:12,gap:10,flexWrap:'wrap'}}>
        <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
          {pending.size === 0 ? 'Select at least one area.' : `${pending.size} ${pending.size===1?'area':'areas'} selected`}
        </div>
        <Button icon="check" disabled={pending.size === 0}
          onClick={()=>onCommit(Array.from(pending))}>
          Update my system
        </Button>
      </div>
    </Card>
  );
};

const HabitsOnboarding = ({ onStart }) => {
  const steps = [
    { n:'01', short:'Areas',  icon:'layers',         title:'Choose your areas', copy:'Pick the parts of life you want to build first.' },
    { n:'02', short:'Habits', icon:'plus-circle',    title:'Add your habits',   copy:'Start with small actions you can repeat.' },
    { n:'03', short:'Days',   icon:'calendar-check', title:'Plan the days',     copy:'Choose when each habit should happen.' },
    { n:'04', short:'Today',  icon:'check-circle',   title:'Complete today',    copy:'Check off today’s habits and build momentum.' },
  ];
  const [i, setI] = React.useState(0);
  const s = steps[i];
  const go = (n) => setI(Math.max(0, Math.min(steps.length - 1, n)));
  return (
    <Card style={{padding:24,marginBottom:24}}>
      <div style={{textAlign:'center',marginBottom:18}}>
        <Label mb={0}>How it works</Label>
      </div>

      {/* Step pills */}
      <div style={{display:'flex',justifyContent:'center',gap:6,marginBottom:22,flexWrap:'wrap'}}>
        {steps.map((st, idx) => {
          const active = idx === i;
          return (
            <button key={st.n} onClick={()=>setI(idx)}
              style={{
                display:'inline-flex',alignItems:'center',gap:6,padding:'6px 12px',borderRadius:999,
                cursor:'pointer',
                background: active ? C.sandFaint : 'transparent',
                border:`1px solid ${active ? C.sand : C.border}`,
                color: active ? C.textPrimary : C.textMuted,
                fontFamily:'Montserrat,sans-serif',fontSize:10.5,fontWeight:600,letterSpacing:'0.10em',textTransform:'uppercase',
                transition:'all 0.18s ease',
              }}>
              <span style={{fontSize:9,fontWeight:300,letterSpacing:'0.18em',color: active ? C.sand : C.textFaint}}>{st.n}</span>
              {st.short}
            </button>
          );
        })}
      </div>

      {/* Active step */}
      <div key={s.n} className="view-fade" style={{textAlign:'center',padding:'8px 12px 4px',minHeight:160}}>
        <div style={{width:44,height:44,borderRadius:11,background:C.sandFaint,border:`1px solid ${C.sand}`,
          display:'inline-flex',alignItems:'center',justifyContent:'center',marginBottom:14}}>
          <Icon name={s.icon} size={18} color={C.sand}/>
        </div>
        <div style={{fontSize:11,fontWeight:700,letterSpacing:'0.28em',color:C.sand,marginBottom:6}}>
          {s.n}
        </div>
        <div style={{fontSize:24,fontWeight:800,fontStyle:'italic',color:C.textPrimary,letterSpacing:'-0.02em',marginBottom:10}}>
          {s.title}
        </div>
        <div style={{fontSize:13,fontWeight:300,color:C.textMuted,maxWidth:380,margin:'0 auto',lineHeight:1.55}}>
          {s.copy}
        </div>
      </div>

      {/* Step nav arrows */}
      <div style={{display:'flex',alignItems:'center',justifyContent:'center',gap:14,marginTop:10,marginBottom:18}}>
        <button onClick={()=>go(i-1)} disabled={i===0}
          style={{background:'transparent',border:'none',cursor:i===0?'not-allowed':'pointer',
            color: i===0 ? C.textFaint : C.textMuted,padding:6,opacity:i===0?0.4:1,lineHeight:0}}>
          <Icon name="chevron-left" size={16}/>
        </button>
        <div style={{fontSize:10,fontWeight:300,letterSpacing:'0.18em',color:C.textFaint}}>
          {String(i+1).padStart(2,'0')} · {String(steps.length).padStart(2,'0')}
        </div>
        <button onClick={()=>go(i+1)} disabled={i===steps.length-1}
          style={{background:'transparent',border:'none',cursor:i===steps.length-1?'not-allowed':'pointer',
            color: i===steps.length-1 ? C.textFaint : C.textMuted,padding:6,opacity:i===steps.length-1?0.4:1,lineHeight:0}}>
          <Icon name="chevron-right" size={16}/>
        </button>
      </div>

      <div style={{textAlign:'center',paddingTop:10,borderTop:`1px solid ${C.border}`}}>
        <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontStyle:'italic',marginBottom:14,marginTop:14}}>
          Start small. Consistency first.
        </div>
        <Button icon="arrow-right" onClick={onStart}>Start now</Button>
      </div>
    </Card>
  );
};

const HabitCelebrationOverlay = ({ confetti }) => {
  React.useEffect(() => {
    console.log('[Habits Celebration] overlay mounted', { pieces: confetti.length });
    return () => console.log('[Habits Celebration] overlay unmounted');
  }, []);
  if (typeof document === 'undefined') return null;
  return ReactDOM.createPortal(
    <div className="vyb-celebrate-overlay" aria-hidden="true">
      <div className="vyb-celebrate-splash"/>
      <div className="vyb-celebrate-microcopy">Daily habits complete.</div>
      {confetti.map(p => (
        <div key={p.id} className="vyb-confetti" style={{
          left: `${p.left}%`, background: p.color, width: p.w, height: p.h,
          animation: `vyb-confetti-fall ${p.dur}s cubic-bezier(0.22,1,0.36,1) ${p.delay}s forwards`,
          '--drift': `${p.drift}px`, '--rot': `${p.rot}deg`,
        }}/>
      ))}
    </div>,
    document.body
  );
};

// Shared row used by Completed Extras and Extra Today sections.
// Extras are NOT required for the Daily Seal — they only help weekly consistency.
const ExtraHabitRow = ({ h, today, dim, justLogged, consistency, onToggle, tr }) => {
  const isCatchUp = !!(consistency && consistency.catchUpAvailableToday);
  const missed = (consistency && consistency.days || []).filter(d => d.status === 'missed');
  const lastMissed = missed.length ? missed[missed.length - 1].label : null;
  return (
    <div style={{position:'relative',opacity: dim ? 0.92 : 1,
      background: dim ? 'rgba(94,117,88,0.05)' : 'transparent',
      borderRadius: dim ? 10 : 0, transition:'background 0.25s'}}>
      <HabitRow h={h} today={today} editMode={false}
        onToggle={onToggle} onRename={()=>{}} onDelete={()=>{}}/>
      <div style={{position:'absolute',top:'50%',right:54,transform:'translateY(-50%)',
        display:'flex',gap:6,alignItems:'center',pointerEvents:'none'}}>
        {justLogged ? (
          <span style={{fontSize:10,fontWeight:400,color:C.sageLight,fontStyle:'italic',letterSpacing:'0.02em'}}>
            {tr('habits.extraLogged')}
          </span>
        ) : dim ? (
          <span style={{fontFamily:'Montserrat,sans-serif',fontSize:9,fontWeight:700,
            letterSpacing:'0.14em',textTransform:'uppercase',color:C.sageLight}}>
            {tr('habits.doneBadge')}
          </span>
        ) : isCatchUp ? (
          <>
            <span style={{fontFamily:'Montserrat,sans-serif',fontSize:9,fontWeight:700,
              letterSpacing:'0.14em',textTransform:'uppercase',padding:'2px 7px',borderRadius:6,
              color:C.sandLight,border:'1px solid rgba(160,138,86,0.45)',background:'rgba(160,138,86,0.10)'}}>
              Catch-up
            </span>
            {lastMissed && (
              <span style={{fontSize:10,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
                Missed {lastMissed}
              </span>
            )}
          </>
        ) : (
          <span style={{fontFamily:'Montserrat,sans-serif',fontSize:9,fontWeight:600,
            letterSpacing:'0.14em',textTransform:'uppercase',color:C.textFaint}}>
            {tr('habits.extraBadge')}
          </span>
        )}
      </div>
    </div>
  );
};

// Completed extras — placed HIGHER on the page (above Extra today).
// Visible by default, premium subtle, slight celebratory tone.
const CompletedExtrasSection = ({ habits, today, consistencyById, onToggle, tr }) => {
  const [justLoggedId, setJustLoggedId] = React.useState(null);
  const handleToggle = (h) => {
    onToggle(h.id);
    setJustLoggedId(h.id);
    setTimeout(() => setJustLoggedId(p => p === h.id ? null : p), 1800);
  };
  if (!habits || habits.length === 0) return null;
  return (
    <div style={{marginTop:24,padding:'14px 16px 12px',borderRadius:14,
      background:'linear-gradient(180deg, rgba(94,117,88,0.07) 0%, rgba(94,117,88,0.02) 100%)',
      border:'1px solid rgba(94,117,88,0.18)'}}>
      <div style={{display:'flex',alignItems:'center',gap:8,padding:'2px 0 4px'}}>
        <Icon name="check-circle" size={12} color={C.sageLight}/>
        <div style={{fontSize:10,fontWeight:700,letterSpacing:'0.22em',color:C.textPrimary,textTransform:'uppercase'}}>
          {tr('habits.completedExtras')}
        </div>
        <div style={{marginLeft:'auto',fontSize:10,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
          {habits.length}
        </div>
      </div>
      <div style={{fontSize:11,fontWeight:300,color:C.textMuted,fontStyle:'italic',marginBottom:8,letterSpacing:'0.02em'}}>
        {tr('habits.completedExtrasHelper')}
      </div>
      <div style={{display:'flex',flexDirection:'column',gap:0}}>
        {habits.map(h => (
          <ExtraHabitRow key={h.id} h={h} today={today} dim
            justLogged={justLoggedId === h.id}
            consistency={consistencyById[h.id]}
            onToggle={()=>handleToggle(h)} tr={tr}/>
        ))}
      </div>
    </div>
  );
};

// Extra today — collapsible list of UNSCHEDULED habits not yet completed today.
// Logging here counts as a check-in for today and may help recover the
// weekly target. Does not block or affect the Daily Seal.
const ExtraTodaySection = ({ habits, today, consistencyById, onToggle, tr }) => {
  const [open, setOpen] = React.useState(false);
  const [justLoggedId, setJustLoggedId] = React.useState(null);
  const handleToggle = (h) => {
    onToggle(h.id);
    setJustLoggedId(h.id);
    setTimeout(() => setJustLoggedId(p => p === h.id ? null : p), 1800);
  };
  if (!habits || habits.length === 0) return null;
  return (
    <div style={{marginTop:28}}>
      <div onClick={()=>setOpen(o=>!o)}
        style={{display:'flex',alignItems:'center',gap:8,padding:'10px 0',cursor:'pointer',
          borderTop:`1px solid ${C.border}`}}>
        <Icon name="plus-circle" size={12} color={C.sand}/>
        <div style={{fontSize:10,fontWeight:600,letterSpacing:'0.22em',color:C.textPrimary,textTransform:'uppercase'}}>
          {tr('habits.extraTodayTitle')}
        </div>
        <div style={{marginLeft:'auto',fontSize:10,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
          {habits.length}
        </div>
        <Icon name={open ? 'chevron-up' : 'chevron-down'} size={12} color={C.textFaint}/>
      </div>
      {open && (
        <div style={{marginTop:6}}>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted,fontStyle:'italic',marginBottom:8,letterSpacing:'0.02em'}}>
            {tr('habits.extraTodayHelper')}
          </div>
          <div style={{display:'flex',flexDirection:'column',gap:0}}>
            {habits.map(h => (
              <ExtraHabitRow key={h.id} h={h} today={today}
                justLogged={justLoggedId === h.id}
                consistency={consistencyById[h.id]}
                onToggle={()=>handleToggle(h)} tr={tr}/>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const HabitsView = ({ store }) => {
  const tr = useT();
  const [modalOpen, setModalOpen] = React.useState(false);
  const [editing, setEditing] = React.useState(null);
  const [editMode, setEditMode] = React.useState(false);
  const [areasOpen, setAreasOpen] = React.useState(false);
  const [started, setStarted] = React.useState(false);
  const [isMobile, setIsMobile] = React.useState(() => typeof window!=='undefined' && window.matchMedia('(max-width: 768px)').matches);
  React.useEffect(() => {
    if (typeof window==='undefined') return;
    const mq = window.matchMedia('(max-width: 768px)');
    const onChange = () => setIsMobile(mq.matches);
    mq.addEventListener?.('change', onChange);
    return () => mq.removeEventListener?.('change', onChange);
  }, []);
  // Briefly highlight a newly-added habit's edit button so users know
  // they can customize schedule. Cleared after ~2s.
  const [recentlyAddedHabitId, setRecentlyAddedHabitId] = React.useState(null);
  const recentTimerRef = React.useRef(null);
  const flagRecentlyAdded = React.useCallback((id) => {
    if (!id) return;
    if (recentTimerRef.current) clearTimeout(recentTimerRef.current);
    setRecentlyAddedHabitId(id);
    recentTimerRef.current = setTimeout(() => setRecentlyAddedHabitId(null), 2000);
  }, []);
  React.useEffect(() => () => { if (recentTimerRef.current) clearTimeout(recentTimerRef.current); }, []);
  const wk = weekKeys();
  const tIdx = todayDow();
  // Localized short weekday labels (Mon..Sun in current language).
  const days = React.useMemo(() => {
    const lang = (typeof getLang === 'function' ? getLang() : 'en');
    const locale = lang === 'es' ? 'es-ES' : 'en-US';
    return wk.map(k => {
      const [y,m,d] = k.split('-').map(Number);
      const label = new Date(y, m-1, d).toLocaleDateString(locale, { weekday: 'short' });
      return label.charAt(0).toUpperCase() + label.slice(1, 3);
    });
  }, [wk, tr]);
  const { habits, habitCategories=[] } = store.state;
  const today = todayKey();

  const openNew = () => { setEditing(null); setModalOpen(true); };
  const openEdit = h => { setEditing(h); setModalOpen(true); };

  const categoryForId = React.useMemo(() => Object.fromEntries(habitCategories.map(c => [c.id, c])), [habitCategories]);

  // Order: by saved sortOrder, fall back to AREA_ORDER for legacy rows that share order=0.
  const orderedCategories = React.useMemo(() => {
    const fallback = (n) => {
      const i = AREA_ORDER.indexOf(n);
      return i === -1 ? 999 : i;
    };
    return [...habitCategories].sort((a,b) => {
      if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder;
      return fallback(a.name) - fallback(b.name);
    });
  }, [habitCategories]);

  // Active areas drive normal-mode visibility AND the today/weekly totals.
  // "Uncategorized" is an internal fallback bucket — never counted as a user area.
  const activeCategoryIds = React.useMemo(
    () => new Set(orderedCategories.filter(c => c.active !== false && c.name !== 'Uncategorized').map(c => c.id)),
    [orderedCategories]
  );
  const visibleHabits = React.useMemo(
    () => habits.filter(h => activeCategoryIds.has(h.categoryId)),
    [habits, activeCategoryIds]
  );
  // Scheduled-today filter for the "Today" surface. Unscheduled habits are
  // hidden from the daily list (they appear in their own area only when
  // catch-up is offered or in edit mode).
  const isSchedToday = window.isHabitScheduledOn || (() => true);
  const todayDate = new Date();
  const scheduledTodayHabits = React.useMemo(
    () => visibleHabits.filter(h => isSchedToday(h, todayDate)),
    [visibleHabits]
  );
  // Weekly consistency from the momentum helper — used to know which
  // unscheduled habits offer a catch-up today.
  const calcMomentum = window.calculateMomentum || (() => null);
  const consistencyById = React.useMemo(() => {
    const m = calcMomentum({ habits });
    const map = {};
    if (m && m.weekly && Array.isArray(m.weekly.consistency)) {
      for (const r of m.weekly.consistency) map[r.id] = r;
    }
    return map;
  }, [habits]);
  const catchUpHabits = React.useMemo(
    () => visibleHabits.filter(h => {
      const c = consistencyById[h.id];
      return c && c.catchUpAvailableToday && !h.checkIns[today];
    }),
    [visibleHabits, consistencyById, today]
  );
  // All habits NOT scheduled for today — surfaced as optional "Extra today".
  // Split: available (not yet logged) vs completed (already logged today).
  // Completed extras stay visible as "nice extras" — they don't disappear.
  const unscheduledTodayHabits = React.useMemo(
    () => visibleHabits.filter(h => !isSchedToday(h, todayDate)),
    [visibleHabits]
  );
  const availableExtras = React.useMemo(
    () => unscheduledTodayHabits.filter(h => !h.checkIns[today]),
    [unscheduledTodayHabits, today]
  );
  const completedExtras = React.useMemo(
    () => unscheduledTodayHabits.filter(h => !!h.checkIns[today]),
    [unscheduledTodayHabits, today]
  );
  const doneToday = scheduledTodayHabits.filter(h => h.checkIns[today]).length;
  const pct = scheduledTodayHabits.length ? Math.round(doneToday / scheduledTodayHabits.length * 100) : 0;

  // ── Daily-complete celebration: only on the actual transition moment ──
  // Use SCHEDULED-today habits only — extras don't gate the daily seal.
  const totalHabits = scheduledTodayHabits.length;
  const [celebrateKey, setCelebrateKey] = React.useState(0);
  const hasInitializedRef = React.useRef(false);
  const prevDoneCountRef = React.useRef(doneToday);
  const prevTotalRef = React.useRef(totalHabits);

  const fireCelebration = React.useCallback(() => {
    const reduced = typeof window !== 'undefined' && window.matchMedia
      && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) { console.log('[Habits Celebration] skipped — reduced motion'); return; }
    console.log('[Habits Celebration] firing');
    setCelebrateKey(Date.now());
  }, []);

  React.useEffect(() => {
    if (!celebrateKey) return;
    const t = setTimeout(() => setCelebrateKey(0), 2600);
    return () => clearTimeout(t);
  }, [celebrateKey]);

  React.useEffect(() => {
    const prevDone  = prevDoneCountRef.current;
    const prevTotal = prevTotalRef.current;
    const wasAllComplete = prevTotal > 0 && prevDone === prevTotal;
    const isAllComplete  = totalHabits > 0 && doneToday === totalHabits;
    const initialized    = hasInitializedRef.current;
    const shouldCelebrate = initialized && !wasAllComplete && isAllComplete && !editMode;

    console.log('[Habits Celebration]', {
      prevDone, doneToday, prevTotal, totalHabits,
      wasAllComplete, isAllComplete, editMode, initialized, shouldCelebrate,
    });

    prevDoneCountRef.current = doneToday;
    prevTotalRef.current = totalHabits;
    hasInitializedRef.current = true;

    if (shouldCelebrate) fireCelebration();
  }, [doneToday, totalHabits, editMode, fireCelebration]);

  // Dev manual test trigger (only on localhost).
  React.useEffect(() => {
    if (typeof window === 'undefined') return;
    if (!/^(localhost|127\.|0\.0\.0\.0)/.test(window.location.hostname)) return;
    window.__vybCelebrate = fireCelebration;
    console.log('[Habits Celebration] manual trigger → window.__vybCelebrate()');
    return () => { try { delete window.__vybCelebrate; } catch (_) {} };
  }, [fireCelebration]);
  const confetti = React.useMemo(() => {
    if (!celebrateKey) return [];
    const colors = ['#A08A56','#D4B36A','#C9A86A','#8C7448','#E6CC8A','#B89A60'];
    const out = [];
    for (let i = 0; i < 30; i++) {
      out.push({
        id: i,
        left: 8 + Math.random() * 84,
        delay: Math.random() * 0.22,
        dur: 1.0 + Math.random() * 1.0,
        drift: Math.round((Math.random() - 0.5) * 220),
        rot: Math.round((Math.random() - 0.5) * 720),
        color: colors[i % colors.length],
        w: 4 + Math.round(Math.random() * 4),
        h: 7 + Math.round(Math.random() * 8),
      });
    }
    return out;
  }, [celebrateKey]);

  // Drag-and-drop reordering of area cards (Edit Mode only).
  const areasRef = React.useRef(null);
  React.useEffect(() => {
    if (!editMode || !areasRef.current || !window.Sortable) return;
    const inst = window.Sortable.create(areasRef.current, {
      handle: '.vyb-area-handle',
      animation: 180,
      forceFallback: true,        // unified mouse/touch UX, no native ghost
      fallbackTolerance: 4,
      delay: 0,
      ghostClass: 'vyb-area-ghost',
      chosenClass: 'vyb-area-chosen',
      dragClass: 'vyb-area-drag',
      onStart: () => { document.body.classList.add('vyb-dragging'); },
      onEnd: () => {
        document.body.classList.remove('vyb-dragging');
        const ids = Array.from(areasRef.current.children)
          .map(el => Number(el.getAttribute('data-area-id')))
          .filter(Boolean);
        if (ids.length) store.reorderHabitCategories(ids);
      },
    });
    return () => { document.body.classList.remove('vyb-dragging'); inst.destroy(); };
  }, [editMode, orderedCategories.length]);

  const toggleArea = (area) => {
    const items = habits.filter(h => h.categoryId === area.id);
    const turningOff = area.active !== false;
    if (turningOff && items.length > 0) {
      if (!confirm(`Hide "${area.name}"? Its ${items.length} habit${items.length===1?'':'s'} will be kept but stop counting toward today's progress.`)) return;
    }
    store.updateHabitCategory(area.id, { active: !turningOff });
  };

  const habitsByCat = React.useMemo(() => {
    const m = {};
    for (const h of habits) {
      const k = h.categoryId ?? 'unassigned';
      (m[k] ||= []).push(h);
    }
    return m;
  }, [habits]);

  const weeklyTotal = React.useMemo(() => {
    let total = 0;
    for (const h of visibleHabits) for (const k of wk) if (h.checkIns[k]) total++;
    return total;
  }, [visibleHabits, wk]);

  // Open Add Habit form prefilled from a preset — user reviews/edits before saving.
  const openPreset = (preset, categoryId) => {
    setEditing({
      name: preset.name,
      icon: 'target',
      categoryId,
      frequency: preset.frequency || 'daily',
      customDays: preset.customDays || [],
    });
    setModalOpen(true);
  };

  const extraDoneCount = completedExtras.length;
  const extraDoneLabel = extraDoneCount === 1
    ? tr('habits.extraDone_one')
    : tr('habits.extraDone').replace('{n}', String(extraDoneCount));
  const ProgressBlock = (
    <div style={{display:'grid',gridTemplateColumns:'1fr 1.4fr',gap:20,marginBottom:28}}>
      <Card style={{padding:22}}>
        <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:14}}>
          <div>
            <Label mb={2}>{tr('habits.todayProgress')}</Label>
            <div style={{fontSize:11,fontWeight:300,color:C.textMuted}}>
              {doneToday > 0 && doneToday === scheduledTodayHabits.length
                ? tr('habits.todayHelperDone')
                : tr('habits.todayHelper')}
            </div>
          </div>
          <div style={{display:'flex',alignItems:'baseline',gap:6}}>
            <span style={{fontSize:28,fontWeight:800,fontStyle:'italic',color:C.sand,letterSpacing:'-0.02em'}}>{doneToday}</span>
            <span style={{fontSize:14,color:C.textFaint}}>/ {scheduledTodayHabits.length}</span>
          </div>
        </div>
        <div style={{height:4,borderRadius:999,background:'rgba(255,255,255,0.07)'}}>
          <div style={{width:`${pct}%`,height:'100%',borderRadius:999,background:`linear-gradient(90deg,${C.sand},${C.sandLight})`,transition:'width 0.4s'}}/>
        </div>
        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:10,marginTop:10}}>
          <div style={{fontSize:10,fontWeight:200,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase'}}>{pct}% {tr('habits.percentComplete')}</div>
          {extraDoneCount > 0 && (
            <div style={{fontSize:10,fontWeight:600,letterSpacing:'0.06em',color:C.sageLight,fontStyle:'italic'}}>
              {extraDoneLabel}
            </div>
          )}
        </div>
      </Card>
      <Card style={{padding:22}}>
        <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:14}}>
          <Label mb={0}>{tr('habits.weeklyProgress')}</Label>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted}}>
            <span style={{color:C.sand,fontWeight:700,fontStyle:'italic'}}>{weeklyTotal}</span> {weeklyTotal===1?tr('habits.checkInsThisWeek_one'):tr('habits.checkInsThisWeek')}
          </div>
        </div>
        <div style={{display:'flex',justifyContent:'space-between',gap:8}}>
          {wk.map((k,i)=>{
            const count = visibleHabits.filter(h=>h.checkIns[k]).length;
            const dayPct = visibleHabits.length ? Math.min(1, count/visibleHabits.length) : 0;
            return (
              <div key={i} style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',gap:6}}>
                <div style={{fontSize:9,fontWeight:300,letterSpacing:'0.12em',color:i===tIdx?C.sand:C.textFaint,textTransform:'uppercase'}}>{days[i]}</div>
                <div style={{width:'100%',height:38,borderRadius:6,background:'rgba(255,255,255,0.03)',position:'relative',overflow:'hidden',border:`1px solid ${i===tIdx?C.borderMid:C.border}`}}>
                  <div style={{position:'absolute',bottom:0,left:0,right:0,height:`${dayPct*100}%`,background:`linear-gradient(to top, ${C.sand}, ${C.sandLight})`,transition:'height 0.5s'}}/>
                </div>
                <div style={{fontSize:10,fontWeight:600,color:dayPct>0?C.sand:C.textFaint}}>{count}</div>
              </div>
            );
          })}
        </div>
      </Card>
    </div>
  );

  // Phase: no selected/active areas → onboarding only (regardless of orphan habits).
  const isFreshUser = activeCategoryIds.size === 0;
  // Exiting edit with zero active areas should drop the user back into onboarding,
  // not leave them on a blank page.
  const exitEdit = () => {
    setEditMode(false);
    setAreasOpen(false);
    if (activeCategoryIds.size === 0) setStarted(false);
  };
  const showOnboarding = isFreshUser && !started && !editMode;
  // After "Start now", show only the AreaSelector setup card (no progress, no cards).
  const showSetupOnly = isFreshUser && started && !editMode;
  // Areas exist but no habits yet → show "ready, add habits" instruction.
  const showAreasReady = !editMode && activeCategoryIds.size > 0 && visibleHabits.length === 0;
  // Hide progress when no active areas, when areas-ready instruction is showing, or while in Edit Mode.
  const showProgress = !editMode && !showAreasReady && activeCategoryIds.size > 0;

  return (
    <div>
      <ViewHeader label={tr('habits.label')} title={tr('habits.title')}
        subtitle={tr('habits.subtitle')}
        action={
          !isMobile && !showOnboarding && !showSetupOnly && (
            <div style={{display:'flex',gap:14,alignItems:'center'}}>
              <button
                onClick={()=>{ if (editMode) exitEdit(); else setEditMode(true); }}
                style={{background:'transparent',border:'none',padding:'4px 2px',
                  fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:500,
                  color:C.textMuted,letterSpacing:'0.04em',cursor:'pointer',
                  display:'inline-flex',alignItems:'center',gap:6,opacity:0.85,transition:'opacity 0.18s, color 0.18s'}}
                onMouseEnter={e=>{e.currentTarget.style.opacity='1';e.currentTarget.style.color=C.textSecondary;}}
                onMouseLeave={e=>{e.currentTarget.style.opacity='0.85';e.currentTarget.style.color=C.textMuted;}}>
                <Icon name={editMode?'check':'pencil'} size={12} color="currentColor" sw={1.8}/>
                {editMode ? 'Done' : 'Edit'}
              </button>
              {activeCategoryIds.size > 0 && !showAreasReady && (
                <button onClick={openNew}
                  style={{background:'transparent',border:'none',padding:'4px 2px',
                    fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:500,
                    color:C.textMuted,letterSpacing:'0.04em',cursor:'pointer',
                    display:'inline-flex',alignItems:'center',gap:6,opacity:0.85,transition:'opacity 0.18s, color 0.18s'}}
                  onMouseEnter={e=>{e.currentTarget.style.opacity='1';e.currentTarget.style.color=C.textSecondary;}}
                  onMouseLeave={e=>{e.currentTarget.style.opacity='0.85';e.currentTarget.style.color=C.textMuted;}}>
                  <Icon name="plus" size={12} color="currentColor" sw={1.8}/>
                  New Habit
                </button>
              )}
            </div>
          )
        } />

      {showOnboarding && (
        <HabitsOnboarding onStart={()=>setStarted(true)}/>
      )}

      {showSetupOnly && (
        <AreaSelector
          categories={orderedCategories}
          expanded={false}
          onExpand={()=>{}}
          onCollapse={()=>{}}
          onCommit={(ids) => {
            const want = new Set(ids);
            for (const c of orderedCategories) {
              if (c.name === 'Uncategorized') continue;
              const wasActive = c.active !== false;
              const willActive = want.has(c.id);
              if (wasActive !== willActive) store.updateHabitCategory(c.id, { active: willActive });
            }
            setStarted(false);
            setEditMode(true); // drop into edit mode so user can add habits immediately
          }}
        />
      )}

      {!showOnboarding && !showSetupOnly && (
        <>
      {/* Today's Progress + Weekly Progress cards moved to Momentum.
          ProgressBlock + showProgress remain defined above for reuse. */}

      {showAreasReady && (
        <Card style={{padding:32,textAlign:'center',marginBottom:24}}>
          <div style={{width:54,height:54,borderRadius:14,background:C.sandFaint,display:'flex',alignItems:'center',justifyContent:'center',margin:'0 auto 16px',border:`1px solid ${C.sand}`}}>
            <Icon name="check" size={22} color={C.sand} sw={2.2}/>
          </div>
          <div style={{fontSize:18,fontWeight:800,fontStyle:'italic',color:C.textPrimary,letterSpacing:'-0.02em',marginBottom:8}}>Your areas are ready.</div>
          <div style={{fontSize:13,fontWeight:300,color:C.textMuted,maxWidth:380,margin:'0 auto 8px',lineHeight:1.55}}>
            Now add a few habits and choose the days you’ll actually repeat.
          </div>
          <div style={{fontSize:11,fontWeight:300,fontStyle:'italic',color:C.textFaint,maxWidth:360,margin:'0 auto 20px',letterSpacing:'0.02em'}}>
            Start with 2–3 simple habits. You can adjust your system later.
          </div>
          <Button icon="plus" onClick={()=>setEditMode(true)}>{tr('habits.addHabits')}</Button>
        </Card>
      )}

      {/* Areas header — hidden during the areas-ready instruction */}
      {!showAreasReady && (
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:editMode && !areasOpen && activeCategoryIds.size > 0 ? 6 : 14}}>
        <Label mb={0}>Areas</Label>
        <div style={{fontSize:10,fontWeight:200,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase'}}>{visibleHabits.length} {visibleHabits.length===1?'habit':'habits'}</div>
      </div>
      )}
      {editMode && !areasOpen && activeCategoryIds.size > 0 && (
        <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontStyle:'italic',marginBottom:14}}>
          Drag areas to prioritize what matters most. Start small — choose only what you can actually repeat.
        </div>
      )}

      {editMode && (
        <AreaSelector
          categories={orderedCategories}
          expanded={areasOpen}
          onExpand={()=>setAreasOpen(true)}
          onCollapse={()=>setAreasOpen(false)}
          onCommit={(ids) => {
            const want = new Set(ids);
            // Activate selected, deactivate previously-active that are no longer selected.
            // Habits stay attached — just hidden when their area is inactive.
            for (const c of orderedCategories) {
              if (c.name === 'Uncategorized') continue;
              const wasActive = c.active !== false;
              const willActive = want.has(c.id);
              if (wasActive !== willActive) store.updateHabitCategory(c.id, { active: willActive });
            }
            setAreasOpen(false);
          }}
        />
      )}

      {/* Post-setup instruction: show when areas exist, picker is closed, and no habits yet. */}
      {editMode && !areasOpen && activeCategoryIds.size > 0 && visibleHabits.length === 0 && (
        <div style={{marginBottom:14,fontSize:12,fontWeight:400,color:C.textMuted,fontStyle:'italic'}}>
          Your areas are ready. Add the habits that will move you forward.
        </div>
      )}

      <style>{`
        .vyb-area-edit { transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; user-select: none; -webkit-user-select: none; }
        .vyb-area-edit > div { transition: border-color 0.18s ease; }
        .vyb-area-edit:hover { transform: translateY(-1px) scale(1.005); }
        .vyb-area-edit:hover > div {
          border-color: ${C.borderMid} !important;
          box-shadow: 0 6px 20px rgba(0,0,0,0.35), 0 0 0 1px rgba(160,138,86,0.18);
        }
        .vyb-area-edit:hover .vyb-area-handle { color: ${C.sand}; }
        .vyb-area-handle {
          touch-action: none;
          -webkit-user-drag: none;
          transition: color 0.15s ease;
          cursor: grab;
        }
        .vyb-area-handle:hover { color: ${C.sand} !important; }
        .vyb-area-handle:active { cursor: grabbing; }
        /* While dragging anywhere, prevent text selection across the whole page */
        body.vyb-dragging, body.vyb-dragging * {
          user-select: none !important;
          -webkit-user-select: none !important;
          cursor: grabbing !important;
        }
        .vyb-area-ghost { opacity: 0.25; }
        .vyb-area-ghost > div { background: rgba(160,138,86,0.04) !important; border-style: dashed !important; }
        .vyb-area-chosen > div { border-color: ${C.sand} !important; }
        /* SortableJS forceFallback clone (the element being dragged) */
        .vyb-area-drag {
          opacity: 1 !important;
          pointer-events: none;
        }
        .vyb-area-drag > div {
          box-shadow: 0 24px 48px rgba(0,0,0,0.6), 0 0 0 1px ${C.sand};
          border-color: ${C.sand} !important;
          background: ${C.bg} !important;
          transform: rotate(-1deg);
        }
      `}</style>
      <div ref={areasRef} style={{display:areasOpen?'none':'flex',flexDirection:'column',gap:editMode?14:22}}>
        {orderedCategories.map((area, areaIdx) => {
          const rawItems = habitsByCat[area.id] || [];
          // Normal mode: only show habits scheduled for today. Edit mode shows all.
          const items = editMode
            ? rawItems
            : rawItems.filter(h => isSchedToday(h, todayDate));
          const templates = (HABIT_TEMPLATES[area.name] || []).filter(t =>
            !rawItems.some(h => h.name.toLowerCase() === t.name.toLowerCase())
          );
          const isActive = area.active !== false;
          const showTemplates = editMode && templates.length > 0;
          // Uncategorized is an internal fallback — never visible to the user.
          if (area.name === 'Uncategorized') return null;
          // Inactive areas are managed in the AreaSelector at the top — never render as a card.
          if (!isActive) return null;
          // Normal mode: hide empty active areas too.
          if (!editMode && items.length === 0) return null;

          // NORMAL MODE — clean section, not a card.
          if (!editMode) {
            return (
              <div key={area.id} data-area-id={area.id}>
                <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:4}}>
                  <Icon name={area.icon || 'target'} size={12} color={C.sand}/>
                  <div style={{fontSize:10,fontWeight:600,letterSpacing:'0.22em',color:C.textPrimary,textTransform:'uppercase'}}>{area.name}</div>
                </div>
                <div style={{display:'flex',flexDirection:'column',gap:0}}>
                  {items.map(h => (
                    <HabitRow key={h.id} h={h} today={today} editMode={false}
                      onToggle={()=>store.toggleHabit(h.id, today)}
                      onRename={()=>{}}
                      onDelete={()=>{}}/>
                  ))}
                </div>
              </div>
            );
          }

          // EDIT MODE — keep the card with controls.
          return (
            <div key={area.id} data-area-id={area.id} className="vyb-area-edit">
            <Card style={{padding:16}}>
              <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:items.length||editMode?10:6}}>
                <div className="vyb-area-handle" title="Drag to reorder"
                  style={{cursor:'grab',padding:'4px 2px',color:C.textMuted,display:'flex',alignItems:'center',marginLeft:-4}}
                  onMouseDown={e=>e.currentTarget.style.cursor='grabbing'}
                  onMouseUp={e=>e.currentTarget.style.cursor='grab'}>
                  <Icon name="grip-vertical" size={16}/>
                </div>
                <div style={{width:28,height:28,borderRadius:8,background:C.sandFaint,display:'flex',alignItems:'center',justifyContent:'center'}}>
                  <Icon name={area.icon || 'target'} size={14} color={C.sand}/>
                </div>
                <div style={{fontSize:13,fontWeight:700,color:C.textPrimary,letterSpacing:'-0.01em'}}>{area.name}</div>
                <div style={{fontSize:9,letterSpacing:'0.14em',color:C.textFaint,textTransform:'uppercase',marginLeft:'auto'}}>{items.length}</div>
              </div>

              {items.length > 0 && (
                <div style={{display:'flex',flexDirection:'column',gap:0}}>
                  {items.map(h => (
                    <HabitRow key={h.id} h={h} today={today} editMode
                      editHint={h.id === recentlyAddedHabitId}
                      onToggle={()=>store.toggleHabit(h.id, today)}
                      onRename={(name)=>store.updateHabit(h.id, { name })}
                      onEdit={()=>{ if (h.id === recentlyAddedHabitId) setRecentlyAddedHabitId(null); openEdit(h); }}
                      onDelete={()=>store.deleteHabit(h.id)}/>
                  ))}
                </div>
              )}

              <AreaQuickAdd areaName={area.name}
                onAdd={async (name)=>{
                  const newId = await store.addHabit({ name, icon:'target', categoryId: area.id });
                  if (newId) flagRecentlyAdded(newId);
                }}/>

              {showTemplates && (
                <div style={{marginTop:8}}>
                  <div style={{fontSize:10,fontWeight:300,color:C.textFaint,fontStyle:'italic',marginBottom:6,letterSpacing:'0.02em'}}>
                    Need ideas?
                  </div>
                  <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
                    {templates.map((t) => {
                      const sched = summarizePreset(t.frequency, t.customDays);
                      return (
                        <button key={t.name} onClick={()=>openPreset(t, area.id)}
                          title="Open prefilled — review before adding"
                          style={{display:'inline-flex',alignItems:'center',gap:6,
                            background:'rgba(255,255,255,0.02)',border:`1px solid ${C.border}`,
                            borderRadius:999,padding:'5px 10px',cursor:'pointer',
                            fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:500,
                            color:C.textSecondary,transition:'all 0.15s ease'}}
                          onMouseEnter={e=>{e.currentTarget.style.borderColor=C.sand;e.currentTarget.style.color=C.textPrimary;}}
                          onMouseLeave={e=>{e.currentTarget.style.borderColor=C.border;e.currentTarget.style.color=C.textSecondary;}}>
                          <span>{t.name}</span>
                          <span style={{fontSize:9,fontWeight:400,letterSpacing:'0.06em',color:C.textFaint,fontStyle:'italic'}}>
                            · {sched}
                          </span>
                        </button>
                      );
                    })}
                  </div>
                </div>
              )}

            </Card>
            </div>
          );
        })}

      </div>

      {/* Extra today — habits not on today's plan, available as optional logging.
          Counts as a check-in for today and toward weekly consistency, but is not
          required for the Daily Seal. */}
      {!editMode && completedExtras.length > 0 && (
        <CompletedExtrasSection
          habits={completedExtras}
          today={today}
          consistencyById={consistencyById}
          onToggle={(id)=>store.toggleHabit(id, today)}
          tr={tr}/>
      )}
      {!editMode && availableExtras.length > 0 && (
        <ExtraTodaySection
          habits={availableExtras}
          today={today}
          consistencyById={consistencyById}
          onToggle={(id)=>store.toggleHabit(id, today)}
          tr={tr}/>
      )}

      {editMode && !areasOpen && (
        <div style={{display:'flex',justifyContent:'flex-end',marginTop:18}}>
          <Button icon="check" onClick={exitEdit}>Done</Button>
        </div>
      )}

      {isMobile && !showAreasReady && (
        <div style={{display:'flex',gap:18,justifyContent:'center',alignItems:'center',
          marginTop:20,paddingTop:14,borderTop:`1px solid ${C.border}`}}>
          <button
            onClick={()=>{ if (editMode) exitEdit(); else setEditMode(true); }}
            style={{background:'transparent',border:'none',padding:'6px 4px',
              fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:500,
              color:C.textMuted,letterSpacing:'0.04em',cursor:'pointer',
              display:'inline-flex',alignItems:'center',gap:6,opacity:0.85}}>
            <Icon name={editMode?'check':'pencil'} size={12} color="currentColor" sw={1.8}/>
            {editMode ? 'Done' : 'Edit'}
          </button>
          {activeCategoryIds.size > 0 && (
            <button onClick={openNew}
              style={{background:'transparent',border:'none',padding:'6px 4px',
                fontFamily:'Montserrat,sans-serif',fontSize:11,fontWeight:500,
                color:C.textMuted,letterSpacing:'0.04em',cursor:'pointer',
                display:'inline-flex',alignItems:'center',gap:6,opacity:0.85}}>
              <Icon name="plus" size={12} color="currentColor" sw={1.8}/>
              Add Habit
            </button>
          )}
        </div>
      )}

        </>
      )}

      <HabitFormModal open={modalOpen} onClose={()=>setModalOpen(false)} initial={editing} categories={orderedCategories}
        onSave={(p)=>{
          // Activate the chosen area if user picked a "(suggested)" inactive one.
          const cat = orderedCategories.find(c => c.id === p.categoryId);
          if (cat && cat.active === false) store.updateHabitCategory(cat.id, { active: true });
          // Only update when editing has a real id; preset prefills have no id and must create.
          if (editing && editing.id) store.updateHabit(editing.id, p);
          else (async () => { const id = await store.addHabit(p); if (id) flagRecentlyAdded(id); })();
        }}
        onDelete={editing && editing.id ? ()=>store.deleteHabit(editing.id) : null}/>

      {celebrateKey ? <HabitCelebrationOverlay confetti={confetti}/> : null}

    </div>
  );
};

// ─── WORKOUTS VIEW ─────────────────────────────────────────────
const WORKOUT_TYPES = ['Upper body','Lower body','Full body','Push','Pull','Cardio','Run','HIIT','Yoga','Mobility','Hike','Swim','Rest','Active recovery'];

const WorkoutsView = ({ store }) => {
  const wk = weekKeys();
  const tIdx = todayDow();
  const days = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];
  const [editDay, setEditDay] = React.useState(null);
  const [form, setForm] = React.useState({type:'',duration:'',intensity:3,notes:''});

  const openEdit = (i) => {
    const w = store.state.workouts[wk[i]] || {};
    setForm({type:w.type||'',duration:w.duration||'',intensity:w.intensity||3,notes:w.notes||''});
    setEditDay(i);
  };
  const save = () => {
    store.setWorkout(wk[editDay], {
      type: form.type,
      duration: form.duration ? parseInt(form.duration) : null,
      intensity: parseInt(form.intensity),
      notes: form.notes,
    });
    setEditDay(null);
  };
  const clearDay = () => {
    store.deleteWorkout(wk[editDay]);
    setEditDay(null);
  };
  const toggleDone = (dayK) => {
    const w = store.state.workouts[dayK] || {};
    store.setWorkout(dayK, { done: !w.done });
  };

  const doneCount = wk.filter(k=>store.state.workouts[k]?.done).length;
  const planned = wk.filter(k=>store.state.workouts[k]?.type).length;

  return (
    <div>
      <ViewHeader label="Training" title="Workouts"
        subtitle="Plan your week. Train with intention. Recover with the same discipline." />

      <div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:20,marginBottom:20}}>
        <Card>
          <Label>Completed</Label>
          <div style={{fontSize:40,fontWeight:800,fontStyle:'italic',color:C.sand,lineHeight:1}}>{doneCount}<span style={{fontSize:22,color:C.textFaint}}>/{planned||7}</span></div>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginTop:8}}>{planned?`${planned} session${planned===1?'':'s'} planned`:'No sessions planned'}</div>
        </Card>
        <Card>
          <Label>Avg Intensity</Label>
          <div style={{fontSize:40,fontWeight:800,fontStyle:'italic',color:C.sage,lineHeight:1}}>
            {(() => { const ws = wk.map(k=>store.state.workouts[k]).filter(w=>w?.done&&w.intensity); return ws.length? (ws.reduce((a,w)=>a+w.intensity,0)/ws.length).toFixed(1): '—'; })()}
          </div>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginTop:8}}>out of 5</div>
        </Card>
        <Card>
          <Label>Total Minutes</Label>
          <div style={{fontSize:40,fontWeight:800,fontStyle:'italic',color:C.sandLight,lineHeight:1}}>
            {wk.reduce((a,k)=>a+(store.state.workouts[k]?.done ? (store.state.workouts[k].duration||0) : 0),0)}
          </div>
          <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginTop:8}}>this week</div>
        </Card>
      </div>

      <Card>
        <Label mb={20}>Weekly Plan</Label>
        <div style={{display:'flex',flexDirection:'column',gap:10}}>
          {wk.map((k,i)=>{
            const w = store.state.workouts[k] || {};
            const isToday = i===tIdx;
            return (
              <div key={k} style={{display:'flex',alignItems:'center',gap:16,padding:'16px 18px',borderRadius:12,
                border:`1px solid ${w.done?C.sageFaint:isToday?C.borderMid:C.border}`,
                background:w.done?C.sageFaint:isToday?'rgba(250,250,248,0.02)':'transparent',transition:'all 0.2s'}}>
                <div style={{width:80}}>
                  <div style={{fontSize:13,fontWeight:600,color:isToday?C.sand:C.textSecondary,letterSpacing:'0.02em'}}>{days[i]}</div>
                  <div style={{fontSize:9,fontWeight:300,color:C.textFaint,letterSpacing:'0.1em',textTransform:'uppercase'}}>
                    {(() => { const [y,m,d]=k.split('-'); return new Date(+y,+m-1,+d).toLocaleDateString('en-US',{month:'short',day:'numeric'}); })()}
                  </div>
                </div>
                <div style={{flex:1,minWidth:0}}>
                  {w.type ? (
                    <>
                      <div style={{fontSize:14,fontWeight:500,color:w.done?C.textPrimary:C.textSecondary,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
                        {w.type} {w.duration && <span style={{fontWeight:300,color:C.textMuted,fontSize:12}}>· {w.duration} min</span>}
                      </div>
                      {w.notes && <div style={{fontSize:11,fontWeight:300,color:C.textFaint,marginTop:3,fontStyle:'italic'}}>{w.notes}</div>}
                    </>
                  ) : (
                    <div style={{fontSize:13,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>No workout planned</div>
                  )}
                </div>
                {w.intensity>0 && <div style={{display:'flex',gap:3}}>
                  {[1,2,3,4,5].map(n=><div key={n} style={{width:4,height:14,borderRadius:2,background:n<=w.intensity?C.sage:'rgba(255,255,255,0.08)'}}/>)}
                </div>}
                {w.type && <div onClick={()=>toggleDone(k)}
                  style={{width:28,height:28,borderRadius:999,border:`1px solid ${w.done?C.sage:C.borderMid}`,
                    background:w.done?C.sage:'transparent',display:'flex',alignItems:'center',justifyContent:'center',cursor:'pointer',flexShrink:0,transition:'all 0.2s'}}>
                  {w.done && <Icon name="check" size={13} color={C.bg} sw={2.5}/>}
                </div>}
                <IconButton icon="pencil" onClick={()=>openEdit(i)} title="Edit" />
              </div>
            );
          })}
        </div>
      </Card>

      <Modal open={editDay!==null} onClose={()=>setEditDay(null)} title={editDay!==null ? `${days[editDay]} workout`:'Edit'}>
        <Label mb={8}>Type</Label>
        <div style={{display:'flex',flexWrap:'wrap',gap:6,marginBottom:20}}>
          {WORKOUT_TYPES.map(t=>(
            <div key={t} onClick={()=>setForm({...form,type:t})}
              style={{padding:'6px 12px',borderRadius:999,fontSize:10,fontWeight:600,letterSpacing:'0.1em',textTransform:'uppercase',cursor:'pointer',
                border:`1px solid ${form.type===t?C.sand:C.border}`,background:form.type===t?C.sandFaint:'transparent',color:form.type===t?C.sandLight:C.textMuted,transition:'all 0.2s'}}>
              {t}
            </div>
          ))}
        </div>
        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16,marginBottom:20}}>
          <div>
            <Label mb={8}>Duration (min)</Label>
            <Input value={form.duration} onChange={e=>setForm({...form,duration:e.target.value.replace(/\D/g,'')})} placeholder="60" />
          </div>
          <div>
            <Label mb={8}>Intensity</Label>
            <div style={{display:'flex',gap:6,marginTop:4}}>
              {[1,2,3,4,5].map(n=>(
                <div key={n} onClick={()=>setForm({...form,intensity:n})}
                  style={{flex:1,height:36,borderRadius:8,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
                    border:`1px solid ${n<=form.intensity?C.sage:C.border}`,background:n<=form.intensity?C.sageFaint:'transparent',transition:'all 0.2s'}}>
                  <span style={{fontSize:11,fontWeight:700,color:n<=form.intensity?C.sageLight:C.textFaint}}>{n}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
        <Label mb={8}>Notes</Label>
        <textarea value={form.notes} onChange={e=>setForm({...form,notes:e.target.value})} placeholder="How did it feel? What did you focus on?"
          style={{width:'100%',minHeight:70,background:'rgba(250,250,248,0.03)',border:`1px solid ${C.border}`,borderRadius:8,padding:'10px 14px',fontSize:12,fontFamily:'Montserrat,sans-serif',color:C.textPrimary,outline:'none',resize:'vertical',lineHeight:1.6}}/>
        <div style={{display:'flex',gap:10,marginTop:24,justifyContent:'space-between'}}>
          <Button variant="danger" onClick={clearDay} icon="trash-2">Clear Day</Button>
          <div style={{display:'flex',gap:10}}>
            <Button variant="ghost" onClick={()=>setEditDay(null)}>Cancel</Button>
            <Button onClick={save}>Save</Button>
          </div>
        </div>
      </Modal>
    </div>
  );
};

// ─── READING VIEW ──────────────────────────────────────────────
const STATUSES = [
  { key:'all' }, { key:'reading' }, { key:'finished' }, { key:'wishlist' },
];
const ENTRY_KINDS = [
  { key:'insight' }, { key:'quote' }, { key:'note' },
];

const fmtDate = (s) => {
  if (!s) return '';
  const [y,m,d] = s.split('-');
  const loc = (window.getLang && window.getLang()==='es') ? 'es-ES' : 'en-US';
  return new Date(+y,+m-1,+d).toLocaleDateString(loc,{month:'short',day:'numeric',year:'numeric'});
};

const StatusBadge = ({ status }) => {
  const map = {
    reading:  { bg:C.sandFaint,  fg:C.sand },
    finished: { bg:C.sageFaint,  fg:C.sageLight },
    wishlist: { bg:'rgba(255,255,255,0.05)', fg:C.textMuted },
  };
  const s = map[status] || map.wishlist;
  const label = (window.dl ? window.dl('bookStatus', status) : status);
  return (
    <span style={{display:'inline-flex',alignItems:'center',gap:6,padding:'3px 10px',borderRadius:999,background:s.bg,color:s.fg,fontSize:9,fontWeight:600,letterSpacing:'0.16em',textTransform:'uppercase'}}>{label}</span>
  );
};

const BookCover = ({ book, size='md', style={}, interactive3D=false, onOpen }) => {
  const dims = ({
    sm: { w:52,  h:74,  r:6, fs:18 },
    md: { w:96,  h:140, r:8, fs:28 },
    lg: { w:104, h:148, r:8, fs:34 },
    xs: { w:44,  h:62,  r:5, fs:14 },
  })[size] || { w:96,h:140,r:8,fs:28 };
  const common = {
    width:dims.w, height:dims.h, borderRadius:dims.r, flexShrink:0,
    border:`1px solid ${C.borderMid}`,
    ...style,
  };
  const initials = (book?.title || '').trim().split(/\s+/).slice(0,2)
    .map(w => w[0] || '').join('').toUpperCase() || '·';

  const wrapperRef = React.useRef(null);
  const noHover = typeof window !== 'undefined'
    && window.matchMedia
    && (window.matchMedia('(hover: none)').matches
        || window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  const enable3D = interactive3D && !noHover;
  const clickable = typeof onOpen === 'function';

  const fireOpen = (e) => {
    const rect = wrapperRef.current?.getBoundingClientRect();
    onOpen(rect || null, e);
  };
  const a11y = clickable ? {
    role:'button', tabIndex:0,
    onClick: (e) => { e.stopPropagation(); fireOpen(e); },
    onKeyDown: (e) => {
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fireOpen(e); }
    },
    'aria-label': book?.title ? `Open ${book.title}` : 'Open book',
  } : {};
  const cursor = clickable ? { cursor:'pointer' } : {};

  // ── Plain (non-3D) — used on touch / reduced-motion / unspecified ──
  if (!enable3D) {
    if (book && book.coverUrl) {
      return <img ref={wrapperRef} src={book.coverUrl} alt={book.title || ''} loading="lazy" draggable={false} {...a11y}
        onError={e => { e.currentTarget.style.display='none'; }}
        style={{...common, ...cursor, objectFit:'cover', background:C.cardEl, boxShadow:'0 8px 24px rgba(0,0,0,0.3)', userSelect:'none', WebkitUserDrag:'none', WebkitTouchCallout:'none'}}/>;
    }
    return (
      <div ref={wrapperRef} {...a11y}
        style={{...common, ...cursor, boxShadow:'0 8px 24px rgba(0,0,0,0.3)',
          background:`linear-gradient(135deg,${C.cardEl} 0%,${C.card} 100%)`,
          display:'flex',alignItems:'center',justifyContent:'center',
          color:C.sand,fontStyle:'italic',fontWeight:800,fontSize:dims.fs,letterSpacing:'-0.02em'}}>
        {initials}
      </div>
    );
  }

  // ── Interactive 3D cover ──
  return <BookCover3D book={book} dims={dims} common={common} initials={initials}
    forwardRef={wrapperRef} a11y={a11y} cursor={cursor} sizeKey={size}/>;
};

// Cursor-driven tilt + soft shine. Premium, not flashy.
const BookCover3D = ({ book, dims, common, initials, forwardRef, a11y={}, cursor={}, sizeKey='md' }) => {
  const ref = forwardRef || React.useRef(null);
  const [t, setT] = React.useState(null); // { rx, ry, px, py }
  // Smaller covers get a slightly toned-down effect so it stays elegant.
  const factor = (sizeKey === 'xs' || sizeKey === 'sm') ? 0.75
    : sizeKey === 'lg' ? 1.18
    : 1;
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width;
    const py = (e.clientY - r.top)  / r.height;
    const rx = (0.5 - py) * 18 * factor;
    const ry = (px - 0.5) * 24 * factor;
    setT({ rx, ry, px, py });
  };
  const onLeave = () => setT(null);
  const liftScale = 1 + 0.07 * factor;
  const transform = t
    ? `perspective(800px) rotateX(${t.rx}deg) rotateY(${t.ry}deg) scale(${liftScale})`
    : 'perspective(800px) rotateX(0) rotateY(0) scale(1)';
  const shadow = t
    ? `${(-t.ry*1.2).toFixed(1)}px ${(32 + Math.abs(t.rx)*1.8).toFixed(1)}px 72px rgba(0,0,0,0.72)`
    : '0 8px 24px rgba(0,0,0,0.3)';
  const sx = t ? Math.round(t.px*100) : 50;
  const sy = t ? Math.round(t.py*100) : 50;
  const inner = (book && book.coverUrl)
    ? <img src={book.coverUrl} alt={book.title || ''} loading="lazy" draggable={false}
        onError={e => { e.currentTarget.style.display='none'; }}
        style={{width:'100%',height:'100%',objectFit:'cover',background:C.cardEl,display:'block',borderRadius:dims.r,userSelect:'none',WebkitUserDrag:'none',WebkitTouchCallout:'none'}}/>
    : <div style={{width:'100%',height:'100%',borderRadius:dims.r,
        background:`linear-gradient(135deg,${C.cardEl} 0%,${C.card} 100%)`,
        display:'flex',alignItems:'center',justifyContent:'center',
        color:C.sand,fontStyle:'italic',fontWeight:800,fontSize:dims.fs,letterSpacing:'-0.02em'}}>
        {initials}
      </div>;
  return (
    <div ref={ref} onMouseMove={onMove} onMouseLeave={onLeave} {...a11y}
      style={{...common, ...cursor, position:'relative', overflow:'hidden',
        transform, transformStyle:'preserve-3d', willChange:'transform',
        boxShadow: shadow,
        transition: t
          ? 'transform 0.08s linear, box-shadow 0.18s ease-out'
          : 'transform 0.45s cubic-bezier(0.22,1,0.36,1), box-shadow 0.45s ease-out'}}>
      {inner}
      <div aria-hidden="true" style={{position:'absolute',inset:0,borderRadius:dims.r,pointerEvents:'none',
        background:`radial-gradient(200% 140% at ${sx}% ${sy}%, rgba(255,255,255,0.42) 0%, rgba(255,255,255,0.18) 24%, rgba(255,255,255,0.05) 52%, transparent 78%)`,
        mixBlendMode:'screen',
        opacity: t ? 1 : 0,
        transition:'opacity 0.25s ease-out'}}/>
    </div>
  );
};

// Walks the various shapes returned by Google Books / Open Library and picks the
// best page-count field, with a small confidence signal so we can label the UI
// honestly. Filters obvious garbage (non-integers, 0, > 5000).
const extractPageCount = (book) => {
  const candidates = [];

  if (book?.volumeInfo?.pageCount) {
    candidates.push({ value: book.volumeInfo.pageCount, source: 'Google Books pageCount', confidence: 'medium' });
  }
  if (book?.number_of_pages_median) {
    candidates.push({ value: book.number_of_pages_median, source: 'Open Library median pages', confidence: 'low' });
  }
  if (book?.number_of_pages) {
    candidates.push({ value: book.number_of_pages, source: 'Open Library edition pages', confidence: 'medium' });
  }
  if (book?.pageCount) {
    candidates.push({ value: book.pageCount, source: 'pageCount', confidence: 'low' });
  }
  if (book?.pages) {
    candidates.push({ value: book.pages, source: 'pages', confidence: 'low' });
  }

  const valid = candidates
    .map(item => ({ ...item, value: Number(item.value) }))
    .filter(item => Number.isInteger(item.value) && item.value > 0 && item.value < 5000);

  if (valid.length === 0) {
    return { totalPages: '', source: null, confidence: 'none', isEstimated: false };
  }

  const priority = [
    'Open Library edition pages',
    'Google Books pageCount',
    'Open Library median pages',
    'pageCount',
    'pages',
  ];
  valid.sort((a, b) => priority.indexOf(a.source) - priority.indexOf(b.source));
  const best = valid[0];
  return {
    totalPages: best.value,
    source: best.source,
    confidence: best.confidence,
    isEstimated: best.confidence !== 'high',
  };
};

const searchOpenLibrary = async (q) => {
  const url = `https://openlibrary.org/search.json?q=${encodeURIComponent(q)}&limit=10`;
  const res = await fetch(url);
  if (!res.ok) throw new Error('ol-failed');
  const data = await res.json();
  return (data.docs || []).map(d => {
    const isbn = Array.isArray(d.isbn) && d.isbn.length ? d.isbn[0] : '';
    const cover = d.cover_i ? `https://covers.openlibrary.org/b/id/${d.cover_i}-L.jpg` : '';
    const year = d.first_publish_year ? String(d.first_publish_year) : '';
    const editionKey = d.cover_edition_key
      || (Array.isArray(d.edition_key) && d.edition_key.length ? d.edition_key[0] : '');
    return {
      externalSource: 'open_library',
      externalId: d.key || (d.cover_i ? `cover:${d.cover_i}` : ''),
      title: d.title || '',
      author: Array.isArray(d.author_name) ? d.author_name.join(', ') : '',
      ...((p)=>({ totalPages: p.totalPages || 0, pageSource: p.source, pageConfidence: p.confidence }))(extractPageCount(d)),
      coverUrl: cover,
      description: '',
      publisher: Array.isArray(d.publisher) && d.publisher.length ? d.publisher[0] : '',
      publishedDate: year,
      isbn,
      year,
      editionKey,
      _raw: d,
    };
  });
};

// Try to resolve a missing page count from auxiliary sources.
const resolvePageCount = async (r) => {
  if (r.editionKey) {
    try {
      const res = await fetch(`https://openlibrary.org/books/${r.editionKey}.json`);
      if (res.ok) {
        const ed = await res.json();
        const ext = extractPageCount(ed);
        if (ext.totalPages) return { ...ext, raw: ed };
      }
    } catch (e) { console.warn('[pages] ol edition lookup failed', e); }
  }
  try {
    const q = r.isbn ? `isbn:${r.isbn}` : `${r.title} ${r.author || ''}`.trim();
    if (q) {
      const url = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(q)}&maxResults=3&printType=books`;
      const res = await fetch(url);
      if (res.ok) {
        const data = await res.json();
        for (const it of (data.items || [])) {
          const ext = extractPageCount(it);
          if (ext.totalPages) return { ...ext, raw: it };
        }
      }
    }
  } catch (e) { console.warn('[pages] google books fallback failed', e); }
  return { totalPages: '', source: null, confidence: 'none', isEstimated: false, raw: null };
};

const searchGoogleBooks = async (q) => {
  const url = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(q)}&maxResults=10&printType=books`;
  const res = await fetch(url);
  if (res.status === 429) { const e = new Error('quota'); e.quota = true; throw e; }
  if (!res.ok) throw new Error('gb-failed');
  const data = await res.json();
  return (data.items || []).map(it => {
    const v = it.volumeInfo || {};
    const ids = v.industryIdentifiers || [];
    const isbn = (ids.find(x => x.type === 'ISBN_13') || ids.find(x => x.type === 'ISBN_10') || {}).identifier || '';
    let cover = (v.imageLinks && (v.imageLinks.thumbnail || v.imageLinks.smallThumbnail)) || '';
    cover = cover.replace(/^http:/, 'https:').replace(/&edge=curl/, '');
    return {
      externalSource: 'google_books',
      externalId: it.id,
      title: v.title || '',
      author: (v.authors || []).join(', '),
      ...((p)=>({ totalPages: p.totalPages || 0, pageSource: p.source, pageConfidence: p.confidence }))(extractPageCount(it)),
      coverUrl: cover,
      description: v.description || '',
      publisher: v.publisher || '',
      publishedDate: v.publishedDate || '',
      isbn,
      year: (v.publishedDate || '').slice(0,4),
      _raw: it,
    };
  });
};

// Open Library is primary; Google Books is an optional fallback when OL is empty.
// Quota errors from Google are swallowed silently per product requirement.
const searchBooks = async (q) => {
  if (!q.trim()) return [];
  let primary = [];
  try { primary = await searchOpenLibrary(q); }
  catch (e) { console.warn('[search] open library failed', e); }
  if (primary.length > 0) return primary;
  try { return await searchGoogleBooks(q); }
  catch (e) {
    if (e && e.quota) console.warn('[search] google books quota exceeded; using empty result');
    else console.warn('[search] google books failed', e);
    return [];
  }
};

const Chip = ({ active, onClick, children }) => (
  <button onClick={onClick} style={{
    padding:'7px 14px',borderRadius:999,fontSize:11,fontWeight:500,letterSpacing:'0.06em',
    background: active ? C.sandFaint : 'transparent',
    border: `1px solid ${active ? C.sand : C.border}`,
    color: active ? C.sand : C.textSecondary,
    cursor:'pointer',fontFamily:'Montserrat,sans-serif',transition:'all 0.15s',
  }}>{children}</button>
);

const toDateInput = (iso) => {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d)) return '';
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
};

// Month/Year only — returns 'YYYY-MM' suitable for the form, used to construct
// 'YYYY-MM-15' on save. Stable mid-month value avoids timezone month-shift.
const toMonthInput = (iso) => {
  if (!iso) return { month:'', year:'' };
  const d = new Date(iso);
  if (isNaN(d)) return { month:'', year:'' };
  return { month: String(d.getMonth()+1).padStart(2,'0'), year: String(d.getFullYear()) };
};

const MONTH_NAMES_EN = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const MONTH_NAMES_ES = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'];

const MonthYearPicker = ({ value, onChange }) => {
  const tr = useT();
  const sel = {
    background:'rgba(250,250,248,0.03)', border:`1px solid ${C.border}`, borderRadius:8,
    padding:'10px 14px', fontSize:12, color:C.textPrimary, outline:'none',
    fontFamily:'Montserrat,sans-serif', width:'100%', appearance:'none',
  };
  const thisYear = new Date().getFullYear();
  const years = [];
  for (let y = thisYear; y >= thisYear - 80; y--) years.push(String(y));
  const months = (window.getLang && window.getLang()==='es') ? MONTH_NAMES_ES : MONTH_NAMES_EN;
  return (
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
      <select style={sel} value={value.month} onChange={e=>onChange({...value, month:e.target.value})}>
        <option value="">{tr('reading.monthLabel')}</option>
        {months.map((n, i) => <option key={n} value={String(i+1).padStart(2,'0')}>{n}</option>)}
      </select>
      <select style={sel} value={value.year} onChange={e=>onChange({...value, year:e.target.value})}>
        <option value="">{tr('reading.yearLabel')}</option>
        {years.map(y => <option key={y} value={y}>{y}</option>)}
      </select>
    </div>
  );
};

const BookFormModal = ({ open, onClose, initial, onSave, onDelete }) => {
  const tr = useT();
  const blank = { title:'', author:'', currentPage:0, totalPages:0, status:'reading',
    coverUrl:'', isbn:'', description:'', publisher:'', publishedDate:'',
    externalSource:'', externalId:'', finishedMonth:'', finishedYear:'',
    pageSource:null, pageConfidence:'none' };
  const initForm = initial
    ? { ...initial, ...((my)=>({finishedMonth:my.month, finishedYear:my.year}))(toMonthInput(initial.finishedAt)) }
    : blank;
  const [form, setForm] = React.useState(initForm);
  const [query, setQuery] = React.useState('');
  const [searching, setSearching] = React.useState(false);
  const [results, setResults] = React.useState([]);
  const [searchMsg, setSearchMsg] = React.useState('');
  const isEdit = !!initial?.id;
  // 'search' | 'selected' | 'manual'   (edit always uses 'manual')
  const [mode, setMode] = React.useState(isEdit ? 'manual' : 'search');

  React.useEffect(() => {
    if (open) {
      const my = toMonthInput(initial?.finishedAt);
      setForm(initial ? { ...initial, finishedMonth: my.month, finishedYear: my.year } : blank);
      setQuery(''); setResults([]); setSearchMsg('');
      setMode(isEdit ? 'manual' : 'search');
    }
  }, [open, initial]);

  const runSearch = async () => {
    if (!query.trim()) return;
    setSearching(true); setSearchMsg(''); setResults([]);
    try {
      const items = await searchBooks(query);
      setResults(items);
      if (items.length === 0) setSearchMsg(tr('reading.noResults'));
    } catch (e) {
      setSearchMsg(tr('reading.searchUnavailable'));
    } finally { setSearching(false); }
  };

  const pickResult = async (r) => {
    // TEMP DEBUG: inspect raw metadata + extractor output for the picked result.
    const raw = r._raw || r;
    const ext = extractPageCount(raw);
    console.log('Selected book metadata:', raw);
    console.log('Extracted page count:', ext);
    setForm(f => ({
      ...f,
      title: r.title || f.title,
      author: r.author || f.author,
      totalPages: r.totalPages || f.totalPages || 0,
      pageSource: r.pageSource || ext.source || null,
      pageConfidence: r.pageConfidence || ext.confidence || 'none',
      coverUrl: r.coverUrl || f.coverUrl || '',
      isbn: r.isbn || f.isbn || '',
      description: r.description || f.description || '',
      publisher: r.publisher || f.publisher || '',
      publishedDate: r.publishedDate || f.publishedDate || '',
      externalSource: r.externalSource || 'open_library',
      externalId: r.externalId || '',
    }));
    setResults([]); setSearchMsg('');
    setMode('selected');
    if (!r.totalPages) {
      const resolved = await resolvePageCount(r);
      console.log('Resolved page count (fallback):', resolved);
      if (resolved.totalPages) {
        setForm(f => f.totalPages
          ? f
          : { ...f, totalPages: resolved.totalPages, pageSource: resolved.source, pageConfidence: resolved.confidence });
      }
    }
  };

  const save = () => {
    if (!form.title.trim()) return;
    const totalPagesN = parseInt(form.totalPages)||0;
    let currentPageN = parseInt(form.currentPage)||0;
    if (form.status === 'finished' && totalPagesN > 0) currentPageN = totalPagesN;
    onSave({
      title: form.title.trim(),
      author: form.author.trim(),
      currentPage: currentPageN,
      totalPages: totalPagesN,
      status: form.status,
      coverUrl: form.coverUrl || null,
      isbn: form.isbn || null,
      description: form.description || null,
      publisher: form.publisher || null,
      publishedDate: form.publishedDate || null,
      externalSource: form.externalSource || null,
      externalId: form.externalId || null,
      finishedAt: form.status === 'finished'
        ? ((form.finishedMonth && form.finishedYear) ? `${form.finishedYear}-${form.finishedMonth}-15` : '')
        : '',
    });
    onClose();
  };

  const subtleLink = {
    background:'transparent',border:'none',padding:'8px 0',cursor:'pointer',
    color:C.textMuted,fontSize:11,fontWeight:500,letterSpacing:'0.08em',
    textDecoration:'underline',textUnderlineOffset:'3px',textDecorationColor:C.border,
    fontFamily:'Montserrat,sans-serif',
  };

  const StatusFields = (
    <>
      <Label mb={8}>{tr('reading.status')}</Label>
      <div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
        {STATUSES.filter(s=>s.key!=='all').map(s => (
          <Chip key={s.key} active={form.status===s.key} onClick={()=>setForm({...form,status:s.key})}>{window.dl ? window.dl('bookStatus', s.key) : s.key}</Chip>
        ))}
      </div>

      {form.status === 'reading' && (
        <>
          <div style={{height:16}}/>
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}>
            <div>
              <Label mb={8}>{tr('reading.currentPage')}</Label>
              <Input inputMode="numeric" value={form.currentPage ?? ''} onChange={e=>setForm({...form,currentPage:e.target.value.replace(/\D/g,'')})} placeholder="0"/>
            </div>
            <div>
              <Label mb={8}>{tr('reading.totalPages')} {form.totalPages && form.pageSource ? <span style={{color:C.textFaint,fontWeight:300,letterSpacing:'0.06em'}}>· estimated / editable</span> : null}</Label>
              <Input inputMode="numeric" value={form.totalPages ?? ''} onChange={e=>setForm({...form,totalPages:e.target.value.replace(/\D/g,''),pageSource:null,pageConfidence:'user'})} placeholder="300"/>
              {(() => {
                if (!form.totalPages && form.pageSource === null && form.externalSource) {
                  return <div style={{fontSize:10,color:C.textFaint,marginTop:6,letterSpacing:'0.04em'}}>{tr('reading.pageNotFound')}</div>;
                }
                if (form.totalPages && form.pageSource && form.pageConfidence !== 'user') {
                  const friendly = form.pageSource === 'Google Books pageCount' ? 'Google Books'
                    : form.pageSource === 'Open Library edition pages' ? 'Open Library edition'
                    : form.pageSource === 'Open Library median pages' ? 'Open Library (median across editions)'
                    : form.pageSource;
                  return <div style={{fontSize:10,color:C.textFaint,marginTop:6,letterSpacing:'0.04em'}}>{tr('reading.autofilledFrom').replace('{src}', friendly)}</div>;
                }
                return null;
              })()}
            </div>
          </div>
        </>
      )}

      {form.status === 'finished' && (
        <>
          <div style={{height:16}}/>
          <Label mb={8}>{tr('reading.whenFinish')}</Label>
          <MonthYearPicker
            value={{ month: form.finishedMonth || '', year: form.finishedYear || '' }}
            onChange={(my)=>setForm({...form, finishedMonth: my.month, finishedYear: my.year})}/>
          <div style={{fontSize:10,color:C.textFaint,letterSpacing:'0.06em',marginTop:8}}>{tr('reading.optionalToday')}</div>
        </>
      )}
    </>
  );

  const title = isEdit ? tr('reading.editBook')
    : mode === 'manual' ? tr('reading.addManually')
    : mode === 'selected' ? tr('reading.confirmBook')
    : tr('reading.addBook');

  return (
    <Modal open={open} onClose={onClose} title={title} width={560}>
      {/* SEARCH STEP */}
      {!isEdit && mode === 'search' && (
        <>
          <Label mb={8}>{tr('reading.searchByTitleAuthor')}</Label>
          <div style={{display:'flex',gap:8}}>
            <Input value={query} onChange={e=>setQuery(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); runSearch(); } }}
              placeholder="e.g. Piensa y hágase rico Napoleon Hill" autoFocus/>
            <Button onClick={runSearch} icon="search">{searching ? '...' : tr('reading.search')}</Button>
          </div>
          {searchMsg && <div style={{fontSize:11,color:C.textMuted,marginTop:10}}>{searchMsg}</div>}
          {results.length > 0 && (
            <div style={{display:'flex',flexDirection:'column',gap:8,marginTop:14,maxHeight:340,
              overflowY:'auto',overflowX:'hidden',paddingRight:4,width:'100%',minWidth:0}}>
              {results.map(r => (
                <button key={r.externalId || r.title} onClick={()=>pickResult(r)}
                  style={{display:'flex',gap:12,padding:10,borderRadius:10,background:'rgba(250,250,248,0.02)',
                    border:`1px solid ${C.border}`,cursor:'pointer',textAlign:'left',fontFamily:'Montserrat,sans-serif',
                    transition:'all 0.15s',width:'100%',boxSizing:'border-box',minWidth:0,
                    alignItems:'center',minHeight:84,overflow:'hidden'}}
                  onMouseEnter={e=>e.currentTarget.style.borderColor=C.sand}
                  onMouseLeave={e=>e.currentTarget.style.borderColor=C.border}>
                  <BookCover book={r} size="xs"/>
                  <div style={{flex:'1 1 auto',minWidth:0,overflow:'hidden',display:'flex',flexDirection:'column',gap:2}}>
                    <div style={{fontSize:13,fontWeight:600,color:C.textPrimary,lineHeight:1.3,
                      display:'-webkit-box',WebkitLineClamp:2,WebkitBoxOrient:'vertical',
                      overflow:'hidden',wordBreak:'break-word',whiteSpace:'normal'}}>{r.title}</div>
                    {r.author && <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginTop:2,
                      overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0}}>{r.author}</div>}
                    <div style={{fontSize:10,letterSpacing:'0.14em',color:C.textFaint,textTransform:'uppercase',
                      marginTop:4,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0}}>
                      {r.year || '—'}{r.totalPages ? ` · ${r.totalPages}p` : ''}{r.isbn ? ` · ISBN ${r.isbn}` : ''}
                    </div>
                  </div>
                </button>
              ))}
            </div>
          )}
          <div style={{marginTop:14,paddingTop:14,borderTop:`1px solid ${C.border}`,textAlign:'center'}}>
            <button onClick={()=>{ setForm(blank); setMode('manual'); }} style={subtleLink}>
              {tr('reading.cantFind')}
            </button>
          </div>
          <div style={{display:'flex',justifyContent:'flex-end',marginTop:14}}>
            <Button variant="ghost" onClick={onClose}>{tr('reading.cancel')}</Button>
          </div>
        </>
      )}

      {/* SELECTED — confirm with personal fields only */}
      {!isEdit && mode === 'selected' && (
        <>
          <div style={{display:'flex',gap:16,alignItems:'flex-start',marginBottom:18,paddingBottom:18,borderBottom:`1px solid ${C.border}`}}>
            <BookCover book={form} size="sm"/>
            <div style={{flex:1,minWidth:0}}>
              <div style={{fontSize:15,fontWeight:700,color:C.textPrimary,lineHeight:1.25}}>{form.title}</div>
              {form.author && <div style={{fontSize:12,fontWeight:300,color:C.textMuted,marginTop:4}}>{form.author}</div>}
              <div style={{fontSize:10,letterSpacing:'0.14em',color:C.textFaint,textTransform:'uppercase',marginTop:8}}>
                {form.publishedDate ? form.publishedDate.slice(0,4) : '—'}{form.totalPages ? ` · ${form.totalPages}p` : ''}{form.isbn ? ` · ISBN ${form.isbn}` : ''}
              </div>
              <button onClick={()=>{ setForm(blank); setMode('search'); }} style={{...subtleLink,fontSize:10,padding:'6px 0',marginTop:6}}>
                {tr('reading.chooseDifferent')}
              </button>
            </div>
          </div>

          {StatusFields}

          <div style={{display:'flex',gap:10,marginTop:24,justifyContent:'flex-end'}}>
            <Button variant="ghost" onClick={onClose}>{tr('reading.cancel')}</Button>
            <Button onClick={save}>{tr('reading.addBook')}</Button>
          </div>
        </>
      )}

      {/* MANUAL — full form (also used for Edit) */}
      {(isEdit || mode === 'manual') && (
        <>
          {!isEdit && (
            <div style={{marginBottom:14}}>
              <button onClick={()=>{ setForm(blank); setMode('search'); }} style={subtleLink}>{tr('reading.backToSearch')}</button>
            </div>
          )}

          {(form.coverUrl || form.title) && (
            <div style={{display:'flex',gap:14,alignItems:'center',marginBottom:16}}>
              <BookCover book={form} size="xs"/>
              <div style={{flex:1,minWidth:0,fontSize:11,color:C.textMuted}}>
                {form.coverUrl ? tr('reading.coverAttached') : tr('reading.noCover')}
                {form.coverUrl && (
                  <button onClick={()=>setForm({...form,coverUrl:''})}
                    style={{display:'block',marginTop:6,background:'transparent',border:'none',color:C.textMuted,cursor:'pointer',fontSize:10,letterSpacing:'0.1em',textDecoration:'underline',padding:0,fontFamily:'Montserrat,sans-serif'}}>{tr('reading.removeCover')}</button>
                )}
              </div>
            </div>
          )}

          <Label mb={8}>{tr('reading.bookTitle')}</Label>
          <Input value={form.title} onChange={e=>setForm({...form,title:e.target.value})} placeholder="e.g. The Daily Stoic" autoFocus={!isEdit}/>
          <div style={{height:16}}/>
          <Label mb={8}>{tr('reading.author')}</Label>
          <Input value={form.author} onChange={e=>setForm({...form,author:e.target.value})} placeholder="e.g. Ryan Holiday"/>
          <div style={{height:16}}/>
          {StatusFields}
          <div style={{height:16}}/>
          <Label mb={8}>{tr('reading.coverUrl')}</Label>
          <Input value={form.coverUrl} onChange={e=>setForm({...form,coverUrl:e.target.value})} placeholder="https://..."/>

          <div style={{display:'flex',gap:10,marginTop:24,justifyContent:'space-between'}}>
            {onDelete ? <Button variant="danger" icon="trash-2" onClick={()=>{ onDelete(); onClose(); }}>{tr('reading.delete')}</Button> : <span/>}
            <div style={{display:'flex',gap:10}}>
              <Button variant="ghost" onClick={onClose}>{tr('reading.cancel')}</Button>
              <Button onClick={save}>{isEdit ? tr('reading.saveBookChanges') : tr('reading.addBook')}</Button>
            </div>
          </div>
        </>
      )}
    </Modal>
  );
};

// Draggable progress bar — pointer events, clamps 0..total, commits on release.
const ProgressBar = ({ currentPage, totalPages, onCommit }) => {
  const tr = useT();
  const ref = React.useRef(null);
  const [drag, setDrag] = React.useState(null); // { page } during drag
  const total = Math.max(0, totalPages|0);
  const shown = drag ? drag.page : currentPage;
  const pct = total > 0 ? Math.min(100, Math.max(0, Math.round(shown/total*100))) : 0;

  const pageFromClientX = (clientX) => {
    if (!ref.current || total <= 0) return null;
    const r = ref.current.getBoundingClientRect();
    const ratio = Math.min(1, Math.max(0, (clientX - r.left) / r.width));
    return Math.round(ratio * total);
  };
  const onThumbPointerDown = (e) => {
    if (total <= 0) return;
    e.stopPropagation();
    e.preventDefault();
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setDrag({ page: currentPage });
  };
  const onPointerMove = (e) => {
    if (drag == null) return;
    const p = pageFromClientX(e.clientX);
    if (p != null) setDrag({ page: p });
  };
  const onPointerUp = (e) => {
    if (drag == null) return;
    const finalPage = drag.page;
    setDrag(null);
    if (finalPage !== currentPage) onCommit(finalPage);
  };
  const onKeyDown = (e) => {
    if (total <= 0) return;
    let next = currentPage;
    if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') next = Math.max(0, currentPage - 1);
    else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') next = Math.min(total, currentPage + 1);
    else if (e.key === 'Home') next = 0;
    else if (e.key === 'End') next = total;
    else return;
    e.preventDefault();
    if (next !== currentPage) onCommit(next);
  };

  return (
    <div>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:10}}>
        <span style={{fontSize:10,fontWeight:200,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase'}}>{tr('reading.progress')}</span>
        <span style={{fontSize:22,fontWeight:800,color:C.sand,fontStyle:'italic',letterSpacing:'-0.01em'}}>{pct}%</span>
      </div>
      <div ref={ref}
        style={{position:'relative',height:24,padding:'10px 0',userSelect:'none'}}>
        <div style={{height:6,borderRadius:999,background:'rgba(255,255,255,0.08)',position:'relative',pointerEvents:'none'}}>
          <div style={{width:`${pct}%`,height:'100%',borderRadius:999,background:`linear-gradient(90deg,${C.sand},${C.sandLight})`,transition: drag ? 'none' : 'width 0.25s'}}/>
          <div role="slider" tabIndex={total>0?0:-1}
            aria-valuemin={0} aria-valuemax={total} aria-valuenow={shown}
            onPointerDown={onThumbPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerCancel={onPointerUp}
            onKeyDown={onKeyDown}
            style={{position:'absolute',top:'50%',left:`${pct}%`,transform:'translate(-50%,-50%)',width:24,height:24,borderRadius:'50%',background:C.sand,boxShadow:'0 4px 12px rgba(0,0,0,0.5), 0 0 0 4px rgba(160,138,86,0.18)',border:`2px solid ${C.bg}`,transition: drag ? 'none' : 'left 0.25s',cursor:total>0?'grab':'default',touchAction:'none',pointerEvents:'auto',outline:'none'}}/>
        </div>
      </div>
      {drag && (
        <div style={{fontSize:11,fontWeight:300,color:C.sand,marginTop:4}}>
          {tr('reading.ofPages').replace('{shown}', String(shown)).replace('{total}', String(total || '—'))}
        </div>
      )}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// Reading Sessions (MVP, localStorage-only).
// A confirmed reading session is the ONLY thing Momentum counts as
// reading activity — slider movement updates book progress but does
// NOT create a session and is NOT counted as pages read.
// Session shape: { id, bookId, startedAt, finishedAt, startPage, endPage, pagesRead }
// ─────────────────────────────────────────────────────────────
const RS_KEY = 'vyb-reading-sessions';
const RS_ACTIVE_KEY = 'vyb-active-reading-session';

const readReadingSessions = () => {
  try { const v = JSON.parse(localStorage.getItem(RS_KEY) || '[]'); return Array.isArray(v) ? v : []; }
  catch { return []; }
};
const writeReadingSessions = (list) => {
  try { localStorage.setItem(RS_KEY, JSON.stringify(list || [])); } catch {}
  try { window.dispatchEvent(new Event('vyb-reading-sessions-changed')); } catch {}
};
const readActiveReadingSession = () => {
  try { return JSON.parse(localStorage.getItem(RS_ACTIVE_KEY) || 'null'); } catch { return null; }
};
const writeActiveReadingSession = (s) => {
  try {
    if (s) localStorage.setItem(RS_ACTIVE_KEY, JSON.stringify(s));
    else   localStorage.removeItem(RS_ACTIVE_KEY);
  } catch {}
  try { window.dispatchEvent(new Event('vyb-reading-sessions-changed')); } catch {}
};
const deleteReadingSession = (id) => {
  if (id == null) return;
  const next = readReadingSessions().filter(s => s && s.id !== id);
  writeReadingSessions(next);
};
Object.assign(window, { readReadingSessions, readActiveReadingSession, deleteReadingSession });

// Find a "reading" habit: prefer category name match (Reading/Lectura),
// fall back to habit-name regex (read|reading|leer|lectura|libro|books).
const findReadingHabit = (habits, categories) => {
  if (!Array.isArray(habits) || habits.length === 0) return null;
  const cats = Array.isArray(categories) ? categories : [];
  const readingCatIds = new Set(
    cats.filter(c => /^(reading|lectura)$/i.test((c && c.name) || '')).map(c => c.id)
  );
  if (readingCatIds.size > 0) {
    const byCat = habits.find(h => h && h.categoryId != null && readingCatIds.has(h.categoryId));
    if (byCat) return byCat;
  }
  const re = /\b(read|reading|leer|leyendo|lectura|libro|libros|books?)\b/i;
  return habits.find(h => h && re.test(h.name || h.text || h.title || '')) || null;
};

// Inline panel rendered inside BookDetailFullScreen's left column.
// "Today's Reading" model — no active-session lifecycle. The startPage always
// reflects the book's current page; the user enters an endPage and saves.
const ReadingSessionPanel = ({ book, store }) => {
  const tr = useT();
  const startPage = Number(book.currentPage) || 0;
  const [endPageInput, setEndPageInput] = React.useState(String(startPage));
  const [pendingConfirm, setPendingConfirm] = React.useState(null); // { pagesRead, endPage }
  const [confirmDelete, setConfirmDelete] = React.useState(null); // session id awaiting 2nd tap
  const [toast, setToast] = React.useState(null); // { variant: 'scheduled'|'extra'|'simple' }
  const toastTimerRef = React.useRef(null);
  React.useEffect(() => () => { if (toastTimerRef.current) clearTimeout(toastTimerRef.current); }, []);
  // Collapsed by default — CTA expands the mini session form so the panel
  // doesn't dominate the detail by default.
  const [expanded, setExpanded] = React.useState(false);
  // Re-render when sessions change in localStorage (cross-component signal).
  const [sessionsTick, setSessionsTick] = React.useState(0);
  React.useEffect(() => {
    const h = () => setSessionsTick(t => t + 1);
    window.addEventListener('vyb-reading-sessions-changed', h);
    return () => window.removeEventListener('vyb-reading-sessions-changed', h);
  }, []);

  // Keep endPage in sync with startPage when the book's currentPage shifts
  // (e.g. user used +/- on the progress card, or switched books).
  React.useEffect(() => { setEndPageInput(String(startPage)); setPendingConfirm(null); }, [book.id, startPage]);

  // End-page bounds: never below startPage; never above totalPages if known.
  const clampEnd = (n) => {
    const total  = Number(book.totalPages) || 0;
    let v = Math.max(startPage, Math.floor(Number(n) || 0));
    if (total > 0) v = Math.min(total, v);
    return v;
  };
  const bumpEnd = (delta) => {
    const cur = endPageInput === '' ? startPage : (parseInt(endPageInput, 10) || 0);
    setEndPageInput(String(clampEnd(cur + delta)));
  };

  const endPageNum = endPageInput === '' ? startPage : clampEnd(parseInt(endPageInput, 10) || 0);
  const livePages  = Math.max(0, endPageNum - startPage);
  const canSave    = livePages > 0;

  const trySave = () => {
    if (!canSave) return;
    if (livePages > 50) { setPendingConfirm({ endPage: endPageNum, pagesRead: livePages }); return; }
    finalize(endPageNum, livePages);
  };
  const finalize = (endPage, pagesRead) => {
    const finishedAt = Date.now();
    const session = {
      id: 'rs_' + Date.now().toString(36),
      bookId: book.id,
      startedAt: finishedAt,
      finishedAt,
      startPage,
      endPage,
      pagesRead,
    };
    const list = readReadingSessions(); list.push(session); writeReadingSessions(list);
    if (book.totalPages ? endPage <= book.totalPages : true) {
      if (endPage !== book.currentPage) store.updateBook(book.id, { currentPage: endPage });
    }
    setPendingConfirm(null);
    // endPageInput will be reset by the effect above when book.currentPage changes.

    // Auto-complete the user's reading habit (if any) for today.
    let variant = 'simple';
    try {
      const habit = findReadingHabit(store.habits, store.habitCategories);
      const tKey = (window.todayKey && window.todayKey()) || todayKey;
      if (habit) {
        const already = !!(habit.checkIns && habit.checkIns[tKey]);
        const scheduled = window.isHabitScheduledOn ? window.isHabitScheduledOn(habit, new Date()) : true;
        if (!already) store.toggleHabit(habit.id, tKey);
        variant = scheduled ? 'scheduled' : 'extra';
      }
    } catch (_) {}
    setToast({ variant });
    if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
    toastTimerRef.current = setTimeout(() => setToast(null), 2400);
  };

  const dKey = (ms) => { const d=new Date(ms); const p=n=>String(n).padStart(2,'0');
    return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`; };
  const todayKey = dKey(Date.now());
  const sessionsToday = React.useMemo(
    () => readReadingSessions().filter(s => s && s.bookId === book.id && dKey(s.finishedAt) === todayKey),
    [book.id, sessionsTick]
  );
  const pagesToday = sessionsToday.reduce((a,s) => a + (s.pagesRead||0), 0);

  // Two-tap delete: 1st tap arms, 2nd within 4s deletes. Auto-disarms.
  const requestDelete = (id) => {
    if (confirmDelete === id) {
      const s = sessionsToday.find(x => x.id === id);
      deleteReadingSession(id);
      // If the deleted session had moved currentPage, roll back to its startPage
      // so progress stays honest.
      if (s && book.currentPage === s.endPage && Number.isFinite(s.startPage)) {
        store.updateBook(book.id, { currentPage: s.startPage });
      }
      setConfirmDelete(null);
    } else {
      setConfirmDelete(id);
      setTimeout(() => setConfirmDelete(prev => prev === id ? null : prev), 4000);
    }
  };

  const fmtTime = (ms) => {
    try {
      const d = new Date(ms);
      return d.toLocaleTimeString(undefined, { hour:'2-digit', minute:'2-digit' });
    } catch { return ''; }
  };

  // ─── render ─────────────────────────────────────────────
  const SessionsList = sessionsToday.length === 0 ? null : (
    <div style={{marginTop:14,paddingTop:12,borderTop:`1px solid ${C.border}`}}>
      <div style={{fontSize:9,fontWeight:700,letterSpacing:'0.18em',color:C.textFaint,
        textTransform:'uppercase',marginBottom:8}}>{tr('reading.sessionsToday')} · {sessionsToday.length}</div>
      <div style={{display:'flex',flexDirection:'column',gap:6}}>
        {sessionsToday.map(s => {
          const armed = confirmDelete === s.id;
          return (
            <div key={s.id} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 10px',
              borderRadius:8,background:'rgba(250,250,248,0.02)',border:`1px solid ${armed ? C.clay : C.border}`,
              transition:'border-color 0.15s'}}>
              <span style={{fontSize:11,color:C.textPrimary,fontWeight:600,fontVariantNumeric:'tabular-nums'}}>
                p.{s.startPage} → p.{s.endPage}
              </span>
              <span style={{fontSize:10,color:C.textFaint}}>· {s.pagesRead} pp</span>
              <span style={{marginLeft:'auto',fontSize:10,color:C.textFaint,fontVariantNumeric:'tabular-nums'}}>{fmtTime(s.finishedAt)}</span>
              <button onClick={()=>requestDelete(s.id)}
                title={armed ? tr('reading.confirmDeleteSession') : tr('reading.deleteSession')}
                aria-label={tr('reading.deleteSession')}
                style={{background:'transparent',border:'none',padding:4,cursor:'pointer',
                  color: armed ? C.clay : C.textFaint,
                  display:'inline-flex',alignItems:'center',gap:4,
                  fontFamily:'Montserrat,sans-serif',fontSize:9,fontWeight:600,letterSpacing:'0.12em',textTransform:'uppercase'}}>
                <Icon name="trash-2" size={12} color={armed ? C.clay : C.textFaint} sw={2}/>
                {armed && <span>{tr('reading.confirmDeleteSession')}</span>}
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );

  const stepBtn = {
    width:30,height:30,borderRadius:999,border:`1px solid ${C.border}`,background:'rgba(250,250,248,0.03)',
    color:C.textSecondary,cursor:'pointer',fontSize:14,fontWeight:600,fontFamily:'Montserrat,sans-serif',
    display:'inline-flex',alignItems:'center',justifyContent:'center',transition:'all 0.15s',padding:0,
  };
  // Collapsed header: title + summary on the left, expand CTA on the right.
  const summary = sessionsToday.length === 0
    ? tr('reading.addTodaysSession')
    : `${pagesToday} pp · ${sessionsToday.length} session${sessionsToday.length===1?'':'s'}`;

  return (
    <Card style={{padding:18,background:'transparent',borderColor:C.border}}>
      <button onClick={()=>setExpanded(v=>!v)}
        aria-expanded={expanded}
        style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:12,
          width:'100%',background:'transparent',border:'none',padding:0,cursor:'pointer',textAlign:'left'}}>
        <div style={{minWidth:0}}>
          <div style={{fontSize:10,fontWeight:700,letterSpacing:'0.18em',
            textTransform:'uppercase',color:C.textFaint,marginBottom:4}}>{tr('reading.todaysReading')}</div>
          <div style={{fontSize:13,color:expanded?C.textPrimary:C.textMuted,fontWeight:300}}>{summary}</div>
        </div>
        <div style={{display:'inline-flex',alignItems:'center',gap:8,flexShrink:0,
          padding: expanded ? '6px 10px' : '8px 14px',
          borderRadius: 999,
          background: expanded ? 'transparent' : C.sandFaint,
          border: `1px solid ${expanded ? C.border : 'rgba(160,138,86,0.45)'}`,
          color: expanded ? C.textMuted : C.sandLight,
          fontSize: 10, fontWeight: 700,
          letterSpacing:'0.14em', textTransform:'uppercase', fontFamily:'Montserrat,sans-serif',
          transition:'all 0.18s ease-out'}}>
          {!expanded && <Icon name="plus" size={12} color={C.sandLight} sw={2.4}/>}
          <span>{expanded ? tr('reading.cancelSession') : tr('reading.addTodaysSession')}</span>
          <Icon name={expanded?'chevron-up':'chevron-down'} size={12} color={expanded?C.textMuted:C.sandLight} sw={2}/>
        </div>
      </button>

      {expanded && (
        <div className="vyb-rs-form" style={{marginTop:14,paddingTop:14,borderTop:`1px solid ${C.border}`,
          display:'flex',flexDirection:'column',alignItems:'center',gap:10}}>
          <style>{`
            .vyb-rs-row { display:inline-flex; align-items:center; gap:14px; flex-wrap:wrap; justify-content:center; }
            .vyb-rs-step {
              width:40px; height:40px; border-radius:999px;
              border:1px solid ${C.border}; background:rgba(250,250,248,0.04);
              color:${C.textSecondary}; cursor:pointer;
              font-size:20px; font-weight:500; line-height:1;
              font-family:Montserrat,sans-serif; padding:0;
              display:inline-flex; align-items:center; justify-content:center;
              transition:all 0.15s;
            }
            .vyb-rs-step:hover { background:${C.sandFaint}; border-color:rgba(160,138,86,0.45); color:${C.sandLight}; }
            .vyb-rs-step:disabled { opacity:0.35; cursor:not-allowed; }
            .vyb-rs-end-input {
              width:96px; text-align:center;
              font-style:italic; font-weight:800; letter-spacing:-0.02em; line-height:1;
              font-variant-numeric:tabular-nums; font-size:44px;
              background:transparent; border:none; outline:none; padding:0;
              font-family:Montserrat,sans-serif;
            }
            .vyb-rs-start { font-variant-numeric:tabular-nums; font-weight:600; letter-spacing:-0.01em; }
          `}</style>

          {/* Start → End primary range. End page is the main editable
              value — minus / [endPage] / plus. Start is shown smaller,
              left of the arrow. */}
          <div className="vyb-rs-row">
            {/* Start chip */}
            <div style={{display:'inline-flex',flexDirection:'column',alignItems:'center',gap:4}}>
              <span style={{fontSize:9,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase',fontWeight:700}}>
                {tr('reading.startPageLabel')}
              </span>
              <span className="vyb-rs-start" style={{fontSize:22,color:C.textMuted}}>{startPage}</span>
            </div>

            {/* Connector */}
            <span style={{fontSize:18,color:C.textFaint,marginTop:14}}>→</span>

            {/* End page — main editable. */}
            <div style={{display:'inline-flex',flexDirection:'column',alignItems:'center',gap:6}}>
              <span style={{fontSize:9,letterSpacing:'0.18em',color:canSave?C.sandLight:C.textFaint,textTransform:'uppercase',fontWeight:700}}>
                {tr('reading.endPageLabel')}
              </span>
              <div style={{display:'inline-flex',alignItems:'center',gap:10}}>
                <button className="vyb-rs-step" onClick={()=>bumpEnd(-1)}
                  disabled={livePages <= 0} title="−1">−</button>
                <input className="vyb-rs-end-input"
                  inputMode="numeric"
                  value={endPageInput}
                  onChange={e=>{
                    const raw = e.target.value.replace(/\D/g,'');
                    setEndPageInput(raw === '' ? '' : String(clampEnd(parseInt(raw,10))));
                  }}
                  placeholder={String(startPage)}
                  style={{color: canSave ? C.sand : C.textFaint}}/>
                <button className="vyb-rs-step" onClick={()=>bumpEnd(1)} title="+1">+</button>
              </div>
            </div>
          </div>

          {/* Calculated pages-read — secondary text below. */}
          <div style={{fontSize:11,color:C.textFaint,fontVariantNumeric:'tabular-nums',
            letterSpacing:'0.02em',fontStyle:'italic'}}>
            <span style={{fontWeight:600,color:canSave?C.textMuted:C.textFaint}}>{livePages}</span>
            {' '}{tr('reading.pagesReadSuffix')}
          </div>

          {/* Save lives directly under the counter. */}
          <div style={{marginTop:6}}>
            <Button icon="check" onClick={trySave}
              style={canSave?{}:{opacity:0.4,cursor:'not-allowed',pointerEvents:'none',background:'transparent',color:C.textFaint,border:`1px solid ${C.border}`}}>
              {tr('reading.saveSession')}
            </Button>
          </div>

          {/* Helper line — reserved height so the layout doesn't jump. */}
          <div style={{fontSize:10,fontWeight:300,fontStyle:'italic',
            color: canSave ? 'transparent' : C.textFaint, minHeight:14, letterSpacing:'0.02em',textAlign:'center'}}>
            {canSave ? '' : tr('reading.addAtLeastOnePage')}
          </div>

          {pendingConfirm && (
            <div style={{marginTop:14,padding:14,borderRadius:10,
              background:'rgba(160,138,86,0.08)',border:`1px solid rgba(160,138,86,0.35)`}}>
              <div style={{fontSize:13,color:C.textPrimary,marginBottom:10}}>
                That is a big jump. Save <span style={{fontWeight:700}}>{pendingConfirm.pagesRead}</span> pages for this session?
                {pendingConfirm.pagesRead > 100 && <span style={{color:C.sandLight}}> Are you sure?</span>}
              </div>
              <div style={{display:'flex',gap:10}}>
                <Button icon="check" onClick={()=>finalize(pendingConfirm.endPage, pendingConfirm.pagesRead)}>Yes, save</Button>
                <button onClick={()=>setPendingConfirm(null)} style={{background:'transparent',border:'none',padding:'6px 8px',
                  cursor:'pointer',color:C.textMuted,fontSize:11,fontWeight:500,letterSpacing:'0.08em',
                  fontFamily:'Montserrat,sans-serif',textDecoration:'underline',textUnderlineOffset:'3px'}}>
                  Back
                </button>
              </div>
            </div>
          )}
        </div>
      )}
      {SessionsList}
      {toast && (
        <div style={{marginTop:12,padding:'10px 14px',borderRadius:999,
          background:toast.variant==='simple'?'rgba(250,250,248,0.04)':'rgba(160,138,86,0.10)',
          border:`1px solid ${toast.variant==='simple'?C.border:'rgba(160,138,86,0.35)'}`,
          display:'inline-flex',alignItems:'center',gap:8,
          animation:'fadeIn 0.25s ease-out'}}>
          <Icon name={toast.variant==='simple'?'check':'sparkles'} size={12} color={toast.variant==='simple'?C.textMuted:C.sandLight} sw={2}/>
          <span style={{fontSize:11,fontWeight:500,letterSpacing:'0.02em',
            color:toast.variant==='simple'?C.textSecondary:C.sandLight,fontStyle:'italic'}}>
            {toast.variant==='scheduled' ? tr('reading.habitCompleteToday')
              : toast.variant==='extra' ? tr('reading.habitExtraToday')
              : tr('reading.sessionSavedSimple')}
          </span>
        </div>
      )}
    </Card>
  );
};

// Full-screen book detail — desktop two-column, mobile stacked.
const BookDetailFullScreen = ({ open, onClose, book: bookProp, store, originRect }) => {
  let book = bookProp;
  const tr = useT();
  const [tab, setTab] = React.useState('insight');
  const [text, setText] = React.useState('');
  const [page, setPage] = React.useState('');
  const [error, setError] = React.useState('');
  const [editOpen, setEditOpen] = React.useState(false);
  const textareaRef = React.useRef(null);
  const dictation = (window.useDictation || (() => ({ supported:false, listening:false, toggle:()=>{}, cancel:()=>{}, confirm:()=>{}, transcript:'', levelRef:{current:0}, error:'', clearError:()=>{} })))({
    onAppend: chunk => setText(v => (window.appendDictated ? window.appendDictated(v, chunk) : (v ? v + ' ' + chunk : chunk))),
    lang: (window.getLang && window.getLang() === 'es') ? 'es-GT' : 'en-US',
    inputRef: textareaRef,
  });
  const dictationError = dictation.error
    ? (dictation.error === 'not-allowed' ? tr('reading.dictationPermissionDenied')
      : dictation.error === 'no-speech' ? tr('reading.dictationNoSpeech')
      : dictation.error === 'aborted' ? ''
      : tr('reading.dictationGenericError'))
    : '';
  const [isMobile, setIsMobile] = React.useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches);
  // 'enter' (mounted, scaled at origin) → 'open' (full) → 'exit' (collapse back)
  const [phase, setPhase] = React.useState('open');
  // Render-gate so the overlay stays mounted through the exit animation even
  // after the parent flips `open` to false.
  const [render, setRender] = React.useState(open);
  // Snapshot the originRect on open so the exit animation collapses to the
  // *same* card the user opened from, even if `book` later changes/unmounts.
  const exitRectRef = React.useRef(null);
  // Keep the last non-null book so the exit animation can finish rendering
  // after the parent clears its `detailId` (which would otherwise null `book`).
  const bookRef = React.useRef(null);
  if (bookProp) bookRef.current = bookProp;
  const liveBook = bookProp || bookRef.current;
  const overlayRef = React.useRef(null);
  const reduced = typeof window !== 'undefined' && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  // Always open the detail at the top — both the overlay (mobile scroll) and
  // any inner scroll panes. Runs synchronously after layout so the first paint
  // is at top; reruns on phase changes to clobber any browser scroll restore.
  React.useLayoutEffect(() => {
    if (!render) return;
    const reset = () => {
      const el = overlayRef.current;
      if (!el) return;
      el.scrollTop = 0;
      el.querySelectorAll('[data-vyb-scroll]').forEach(n => { n.scrollTop = 0; });
    };
    reset();
    const r1 = requestAnimationFrame(reset);
    const r2 = requestAnimationFrame(() => requestAnimationFrame(reset));
    return () => { cancelAnimationFrame(r1); cancelAnimationFrame(r2); };
  }, [render, book?.id, phase]);

  // Mount + enter animation. Snapshot the origin rect for the matching exit.
  React.useLayoutEffect(() => {
    if (!open) return;
    setRender(true);
    exitRectRef.current = originRect || null;
    if (reduced || !originRect) { setPhase('open'); return; }
    setPhase('enter');
    let r2 = 0;
    const r1 = requestAnimationFrame(() => {
      r2 = requestAnimationFrame(() => setPhase('open'));
    });
    return () => { cancelAnimationFrame(r1); if (r2) cancelAnimationFrame(r2); };
  }, [open, book?.id]);

  // Parent closed us → run exit animation, then unmount.
  React.useEffect(() => {
    if (open || !render) return;
    if (reduced) { setRender(false); return; }
    // Re-snapshot the source card's *current* position if it still exists
    // (it may have shifted after edits). Fall back to the open-time rect.
    const fresh = (() => {
      try {
        const id = bookRef.current?.id;
        if (id == null) return null;
        const node = document.querySelector(`[data-book-cover-anchor="${id}"]`);
        return node?.getBoundingClientRect?.() || null;
      } catch { return null; }
    })();
    if (fresh && fresh.width > 0 && fresh.height > 0) exitRectRef.current = fresh;
    setPhase('exit');
    const t = setTimeout(() => setRender(false), 320);
    return () => clearTimeout(t);
  }, [open]);

  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 900px)');
    const h = e => setIsMobile(e.matches);
    mq.addEventListener ? mq.addEventListener('change', h) : mq.addListener(h);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', h) : mq.removeListener(h); };
  }, []);

  React.useEffect(() => { if (open) { setText(''); setPage(''); setTab('insight'); setError(''); } }, [open, book?.id]);

  // Escape close + iOS-safe background scroll lock. Saves window.scrollY,
  // pins body via position:fixed so the page can't drift, restores on close.
  React.useEffect(() => {
    if (!render) return;
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    const scrollY = window.scrollY || window.pageYOffset || 0;
    const body = document.body;
    const prev = {
      position: body.style.position,
      top: body.style.top,
      left: body.style.left,
      right: body.style.right,
      width: body.style.width,
      overflow: body.style.overflow,
    };
    body.style.position = 'fixed';
    body.style.top = `-${scrollY}px`;
    body.style.left = '0';
    body.style.right = '0';
    body.style.width = '100%';
    body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', onKey);
      body.style.position = prev.position;
      body.style.top = prev.top;
      body.style.left = prev.left;
      body.style.right = prev.right;
      body.style.width = prev.width;
      body.style.overflow = prev.overflow;
      // Restore the exact scroll position the Reading page had before opening.
      window.scrollTo(0, scrollY);
    };
  }, [render]);

  if (!render || !liveBook) return null;
  // From here on, use `book` as the always-defined reference (live during open,
  // last-known during exit animation).
  book = liveBook;

  const addEntry = () => {
    if (!text.trim()) { setError('Write something before saving.'); return; }
    setError('');
    store.addBookEntry(book.id, { text: text.trim(), kind: tab, page: page ? parseInt(page) : null });
    setText(''); setPage('');
  };
  const commitProgress = (newPage) => {
    const total = book.totalPages || 0;
    const clamped = Math.max(0, total > 0 ? Math.min(total, newPage) : newPage);
    store.updateBook(book.id, { currentPage: clamped });
  };
  const bump = (delta) => {
    const total = book.totalPages || 0;
    let next = book.currentPage + delta;
    next = Math.max(0, total > 0 ? Math.min(total, next) : next);
    if (next !== book.currentPage) store.updateBook(book.id, { currentPage: next });
  };

  const stepBtnStyle = {
    width:26,height:26,borderRadius:999,border:`1px solid transparent`,background:'transparent',
    color:C.textFaint,cursor:'pointer',fontSize:14,fontWeight:500,fontFamily:'Montserrat,sans-serif',
    display:'flex',alignItems:'center',justifyContent:'center',transition:'all 0.15s',padding:0,
  };
  const textActionStyle = {
    background:'transparent',border:'none',padding:'6px 4px',cursor:'pointer',
    color:C.textMuted,fontSize:11,fontWeight:500,letterSpacing:'0.08em',fontFamily:'Montserrat,sans-serif',
    textDecoration:'underline',textUnderlineOffset:'3px',textDecorationColor:C.border,
  };

  // Hero progress numbers — used as the dominant signal in the left column.
  const heroPct = book.totalPages
    ? Math.min(100, Math.round((book.currentPage / book.totalPages) * 100))
    : 0;

  const Left = (
    <div style={{display:'flex',flexDirection:'column',gap:18,minHeight:0, ...(isMobile ? {} : {height:'100%'})}}>
      {/* Hero block: cover + title + status + huge progress.
          Single visual unit (no inner Card) so progress reads as the hero,
          not as another competing card. */}
      <div style={{flexShrink:0,
        paddingTop:24,paddingLeft:20,paddingRight:20,paddingBottom:18,
        marginLeft:-20,marginRight:-20,
        overflow:'visible', position:'relative', zIndex:5,
        borderBottom:`1px solid ${C.border}`}}>
        <div style={{display:'flex',gap:20,alignItems:'flex-start'}}>
          <div style={{position:'relative',zIndex:6,overflow:'visible'}}>
            <BookCover book={book} size="lg" interactive3D/>
          </div>
          <div style={{flex:1,minWidth:0,paddingTop:4}}>
            <div style={{marginBottom:10}}><StatusBadge status={book.status}/></div>
            <div style={{fontSize:26,fontWeight:800,color:C.textPrimary,lineHeight:1.15,letterSpacing:'-0.015em',fontStyle:'italic'}}>{book.title || tr('reading.untitled')}</div>
            {book.author && <div style={{fontSize:13,fontWeight:300,color:C.textMuted,marginTop:8}}>{book.author}</div>}
            {/* Secondary actions — quiet text links. */}
            <div style={{display:'flex',gap:14,marginTop:12,flexWrap:'wrap'}}>
              <button onClick={()=>setEditOpen(true)} style={textActionStyle}>{tr('reading.editBook')}</button>
              {book.status!=='finished' && (
                <button onClick={()=>store.finishBook(book.id)} style={textActionStyle}>{tr('reading.markFinished')}</button>
              )}
            </div>
          </div>
        </div>

        {/* Hero progress — the page's dominant signal. */}
        <div style={{marginTop:22}}>
          <div style={{display:'flex',alignItems:'baseline',justifyContent:'space-between',gap:12,marginBottom:10,flexWrap:'wrap'}}>
            <div style={{display:'flex',alignItems:'baseline',gap:14,minWidth:0}}>
              <div style={{fontSize:48,fontWeight:800,color:C.sand,fontStyle:'italic',letterSpacing:'-0.02em',lineHeight:1,fontVariantNumeric:'tabular-nums'}}>
                {heroPct}<span style={{fontSize:24,opacity:0.7}}>%</span>
              </div>
              <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontVariantNumeric:'tabular-nums',letterSpacing:'0.02em'}}>
                <span style={{color:C.textMuted,fontWeight:500}}>{book.currentPage}</span>
                <span> / {book.totalPages || '—'} </span>
                <span>{tr('reading.pages').toLowerCase()}</span>
              </div>
            </div>
            <div style={{display:'inline-flex',alignItems:'center',gap:6}}>
              <button onClick={()=>bump(-1)} title={tr('reading.minusPage')} style={stepBtnStyle}>−</button>
              <button onClick={()=>bump(1)}  title={tr('reading.plusPage')}  style={stepBtnStyle}>+</button>
            </div>
          </div>
          <ProgressBar currentPage={book.currentPage} totalPages={book.totalPages} onCommit={commitProgress}/>
        </div>
      </div>

      {/* Today's reading — collapsed CTA by default. */}
      <div data-vyb-scroll style={{display:'flex',flexDirection:'column',gap:16,position:'relative',zIndex:1,
        ...(isMobile ? {} : {flex:1,minHeight:0,overflowY:'auto',paddingRight:4})}}>
        <ReadingSessionPanel book={book} store={store}/>
      </div>
    </div>
  );

  const tinyTabBtn = (active, color) => ({
    padding:'4px 10px', borderRadius:999, cursor:'pointer',
    border:`1px solid ${active ? color : C.border}`,
    background: active ? 'rgba(160,138,86,0.10)' : 'transparent',
    color: active ? C.sandLight : C.textMuted,
    fontFamily:'Montserrat,sans-serif', fontSize:9, fontWeight:600,
    letterSpacing:'0.14em', textTransform:'uppercase', transition:'all 0.15s',
  });

  const Right = (
    <div style={{display:'flex',flexDirection:'column',gap:14, minHeight:0, ...(isMobile ? {} : {height:'100%'})}}>
      {/* Tertiary section header — entries are optional, so keep this quiet. */}
      <div style={{flexShrink:0,fontSize:9,fontWeight:700,letterSpacing:'0.20em',
        textTransform:'uppercase',color:C.textFaint,padding:'4px 0'}}>
        {tr('reading.addEntry')}
      </div>
      {/* Compact composer — borderless surface so it reads as secondary. */}
      <Card style={{padding:16, flexShrink:0, background:'transparent', borderColor:C.border}}>
        <div style={{display:'flex',alignItems:'center',gap:6,marginBottom:10,flexWrap:'wrap'}}>
          {ENTRY_KINDS.map(k => (
            <button key={k.key} onClick={()=>setTab(k.key)} style={tinyTabBtn(tab===k.key, C.sand)}>
              {window.dl ? window.dl('entryKind', k.key) : k.key}
            </button>
          ))}
        </div>
        {dictation.listening && window.RecordingComposer ? (
          <window.RecordingComposer levelRef={dictation.levelRef} transcript={dictation.transcript}
            onCancel={dictation.cancel} onConfirm={dictation.confirm}/>
        ) : (
          <textarea ref={textareaRef} value={text} onChange={e=>{ setText(e.target.value); if (error) setError(''); }}
            placeholder={tr('reading.placeholderEntry')}
            style={{width:'100%',minHeight:72,background:'rgba(250,250,248,0.03)',
              border:`1px solid ${error?C.clay:C.border}`,borderRadius:10,padding:'12px 14px',
              fontSize:14,fontStyle: tab==='quote'?'italic':'normal',fontWeight:300,
              fontFamily:'Montserrat,sans-serif',color:C.textPrimary,outline:'none',resize:'vertical',lineHeight:1.65}}/>
        )}
        {error && <div style={{fontSize:11,color:C.clay,marginTop:6}}>{error}</div>}
        <div style={{display:'flex',gap:10,marginTop:10,alignItems:'center',justifyContent:'space-between',flexWrap:'wrap'}}>
          <div style={{display:'flex',gap:8,alignItems:'center'}}>
            <span style={{fontSize:9,letterSpacing:'0.16em',color:C.textFaint,textTransform:'uppercase'}}>{tr('reading.pageOptional')}</span>
            <Input value={page} onChange={e=>setPage(e.target.value.replace(/\D/g,''))} placeholder="—" style={{width:72}}/>
          </div>
          <div style={{display:'flex',gap:8,alignItems:'center'}}>
            {window.DictationButton && !dictation.listening && (
              <window.DictationButton supported={dictation.supported} listening={false}
                onClick={dictation.toggle} title={tr('reading.dictate')}/>
            )}
            <Button onClick={addEntry} icon="plus" size="sm" variant="ghost">{tr('reading.saveEntry')}</Button>
          </div>
        </div>
        {(dictationError || (!dictation.supported) || dictation.listening) && (
          <div style={{marginTop:8,fontSize:10,fontWeight:300,color: dictationError ? C.clay : C.textFaint,
            letterSpacing:'0.04em',display:'flex',alignItems:'center',gap:6}}>
            {dictationError
              ? <><Icon name="alert-circle" size={11} color={C.clay} sw={2}/>{dictationError}</>
              : !dictation.supported
                ? tr('reading.dictationUnsupported')
                : tr('reading.dictationPrivacyNote')}
          </div>
        )}
      </Card>

      {/* Entries list — only this scrolls. */}
      <div style={{display:'flex',flexDirection:'column',minHeight:0, ...(isMobile ? {} : {flex:1})}}>
        <Label mb={10}>{tr('reading.entriesCount').replace('{n}', String(book.entries.length))}</Label>
        {book.entries.length === 0 ? (
          <div style={{padding:'18px 4px',display:'flex',flexDirection:'column',gap:4}}>
            <div style={{fontSize:12,fontWeight:400,color:C.textMuted,letterSpacing:'0.01em'}}>
              {tr('reading.noEntriesYet')}
            </div>
            <div style={{fontSize:11,fontWeight:300,color:C.textFaint,fontStyle:'italic'}}>
              {tr('reading.captureInsightHint')}
            </div>
          </div>
        ) : (
          <div data-vyb-scroll style={{display:'flex',flexDirection:'column',gap:10,paddingRight:4,
            ...(isMobile ? {} : {overflowY:'auto',flex:1,minHeight:0})}}>
            {book.entries.map(e => (
              <div key={e.id} style={{padding:14,borderRadius:12,background:'rgba(250,250,248,0.02)',border:`1px solid ${C.border}`}}>
                <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
                  <span style={{fontSize:9,letterSpacing:'0.18em',color:C.sand,textTransform:'uppercase',fontWeight:700}}>{window.dl ? window.dl('entryKind', e.kind) : e.kind}{e.page?` · p.${e.page}`:''}</span>
                  <IconButton icon="trash-2" onClick={()=>store.deleteBookEntry(book.id, e.id)} title={tr('reading.delete')}/>
                </div>
                <div style={{fontSize:14,fontStyle: e.kind==='quote'?'italic':'normal',fontWeight:300,color:C.textSecondary,lineHeight:1.7}}>{e.kind==='quote'?`"${e.text}"`:e.text}</div>
                <div style={{fontSize:9,letterSpacing:'0.16em',color:C.textFaint,textTransform:'uppercase',marginTop:10}}>{fmtDate(e.date)}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );

  // ── Open/close transition: scale to/from the source card's rect ──
  const vw = typeof window !== 'undefined' ? window.innerWidth  : 1200;
  const vh = typeof window !== 'undefined' ? window.innerHeight : 800;
  const rectForPhase = (phase === 'exit' ? exitRectRef.current : originRect) || exitRectRef.current;
  const animate = !!rectForPhase && !reduced;
  const ox = animate ? (rectForPhase.left + rectForPhase.width / 2)  : vw / 2;
  const oy = animate ? (rectForPhase.top  + rectForPhase.height / 2) : vh / 2;
  const startScale = animate
    ? Math.max(rectForPhase.width / vw, rectForPhase.height / vh, 0.12)
    : 0.96;
  const isEntering = animate && phase === 'enter';
  const isExiting  = animate && phase === 'exit';
  const collapsed  = isEntering || isExiting;
  const overlayStyle = {
    position:'fixed', inset:0, background:C.bg, zIndex:600,
    display:'flex', flexDirection:'column',
    height: '100dvh',
    transformOrigin: `${ox}px ${oy}px`,
    transform: collapsed ? `scale(${startScale})` : 'scale(1)',
    opacity:   collapsed ? (isExiting ? 0.0 : 0.4) : 1,
    borderRadius: collapsed ? 16 : 0,
    overflowY: isMobile ? 'auto' : 'hidden',
    overflowX: 'hidden',
    maxWidth: '100vw',
    touchAction: 'pan-y',
    transition: animate
      ? 'transform 0.34s cubic-bezier(0.22,1,0.36,1), opacity 0.28s ease-out, border-radius 0.34s cubic-bezier(0.22,1,0.36,1)'
      : 'none',
    willChange: animate ? 'transform, opacity' : 'auto',
    overscrollBehavior: 'contain',
    overscrollBehaviorX: 'none',
    WebkitOverflowScrolling: 'touch',
  };
  const contentStyle = {
    opacity: collapsed ? 0 : 1,
    transition: animate ? `opacity ${isExiting ? 0.18 : 0.28}s ease-out ${isExiting ? '0s' : '0.08s'}` : 'none',
    display:'flex', flexDirection:'column', minHeight:0,
    ...(isMobile ? {} : { flex:1 }),
  };

  const overlayNode = (
    <>
      {animate && (
        <div aria-hidden="true" style={{position:'fixed',inset:0,background:C.bg,zIndex:599,
          opacity: collapsed ? 0 : 1,
          transition:'opacity 0.28s ease-out', pointerEvents:'none'}}/>
      )}
      <div ref={overlayRef} style={overlayStyle}>
        <div style={contentStyle}>
        <div style={{flexShrink:0,background:`${C.bg}EE`,backdropFilter:'blur(12px)',WebkitBackdropFilter:'blur(12px)',borderBottom:`1px solid ${C.border}`}}>
          <div style={{maxWidth:1280,margin:'0 auto',padding: isMobile ? '14px 18px' : '16px 32px',display:'flex',alignItems:'center',justifyContent:'space-between',gap:12}}>
            <div style={{display:'flex',alignItems:'center',gap:10,minWidth:0}}>
              <IconButton icon="arrow-left" onClick={onClose} title={tr('reading.close')} size={18}/>
              <span style={{fontSize:10,letterSpacing:'0.2em',color:C.textFaint,textTransform:'uppercase',fontWeight:700}}>{tr('reading.readingDetail')}</span>
            </div>
            <IconButton icon="x" onClick={onClose} title={tr('reading.close')} size={18}/>
          </div>
        </div>
        <div style={{maxWidth:1280,width:'100%',margin:'0 auto',padding: isMobile ? '20px 18px 40px' : '24px 32px 24px',
          display:'grid',gap: isMobile ? 20 : 28,
          gridTemplateColumns: isMobile ? '1fr' : 'minmax(0, 1fr) minmax(0, 1.1fr)',
          ...(isMobile ? {} : {flex:1,minHeight:0,gridTemplateRows:'1fr',overflow:'hidden'})}}>
          {Left}
          {Right}
        </div>
        {/* Centered Done for Today — warm sand primary so it reads as
            a clear closing action. Exits the fullscreen via the same
            close animation as the X / back arrow. */}
        <div style={{flexShrink:0, display:'flex', justifyContent:'center',
          padding: isMobile ? '4px 18px calc(env(safe-area-inset-bottom, 0px) + 20px)' : '4px 32px 22px'}}>
          <button onClick={onClose}
            style={{display:'inline-flex',alignItems:'center',gap:8,
              padding:'10px 22px',borderRadius:999,cursor:'pointer',
              background: C.sandFaint,
              border: `1px solid rgba(160,138,86,0.55)`,
              color: C.sandLight,
              fontFamily:'Montserrat,sans-serif',
              fontSize:11,fontWeight:700,letterSpacing:'0.16em',textTransform:'uppercase',
              boxShadow:'0 0 0 1px rgba(160,138,86,0.10), 0 6px 18px rgba(160,138,86,0.10)',
              transition:'all 0.18s ease-out'}}
            onMouseEnter={e=>{ e.currentTarget.style.background='rgba(160,138,86,0.18)'; e.currentTarget.style.color=C.textPrimary; }}
            onMouseLeave={e=>{ e.currentTarget.style.background=C.sandFaint; e.currentTarget.style.color=C.sandLight; }}>
            <Icon name="check-circle" size={13} color="currentColor" sw={2.2}/>
            {tr('reading.doneForToday')}
          </button>
        </div>
        </div>
        <BookFormModal open={editOpen} onClose={()=>setEditOpen(false)} initial={book}
          onSave={(p)=>store.updateBook(book.id, p)}
          onDelete={()=>{ store.deleteBook(book.id); onClose(); }}/>
      </div>
    </>
  );

  // Portal to body so no transformed/contain ancestor can break position:fixed
  // or shift the overlay off-screen when the Reading page is scrolled.
  return ReactDOM.createPortal(overlayNode, document.body);
};

const fmtMonthYear = (iso) => {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d)) return '';
  const loc = (window.getLang && window.getLang()==='es') ? 'es-ES' : 'en-US';
  return d.toLocaleString(loc, { month: 'long', year: 'numeric' });
};

const BookCard = ({ book, onOpen }) => {
  const tr = useT();
  const isFinished = book.status === 'finished';
  const pct = isFinished ? 100 : (book.totalPages ? Math.min(100, Math.round(book.currentPage/book.totalPages*100)) : 0);
  const showProgress = isFinished || (book.status !== 'wishlist' && book.totalPages > 0);
  const footer = isFinished
    ? (book.finishedAt ? tr('reading.finishedOn').replace('{date}', fmtMonthYear(book.finishedAt)) : tr('reading.finished'))
    : tr('reading.updatedOn').replace('{date}', fmtDate(book.updatedAt) || '—');
  const coverRef = React.useRef(null);
  const handleCardClick = (e) => {
    // Use the cover's rect as the open-transition origin even when the
    // user clicks elsewhere on the card.
    const rect = coverRef.current?.getBoundingClientRect?.() || null;
    onOpen(rect, e);
  };
  return (
    <Card style={{padding:18,cursor:'pointer'}} onClick={handleCardClick} hoverable>
      <div style={{display:'flex',gap:14,alignItems:'flex-start'}}>
        <div ref={coverRef} data-book-cover-anchor={book.id} style={{display:'inline-block'}}>
          <BookCover book={book} size="sm" interactive3D
            onOpen={(rect, e) => { e?.stopPropagation?.(); onOpen(rect, e); }}/>
        </div>
        <div style={{flex:1,minWidth:0}}>
          <div style={{fontSize:14,fontWeight:600,color:C.textPrimary,lineHeight:1.3,marginBottom:4,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{book.title || tr('reading.untitled')}</div>
          {book.author && <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginBottom:10}}>{book.author}</div>}
          <div style={{marginBottom:8}}><StatusBadge status={book.status}/></div>
          {showProgress && (
            <div>
              <div style={{height:3,borderRadius:999,background:'rgba(255,255,255,0.07)',marginBottom:6}}>
                <div style={{width:`${pct}%`,height:'100%',borderRadius:999,background:`linear-gradient(90deg,${C.sand},${C.sandLight})`}}/>
              </div>
              <div style={{fontSize:10,color:C.textMuted}}>
                {isFinished
                  ? (book.totalPages > 0 ? `p. ${book.totalPages}/${book.totalPages} · 100%` : '100%')
                  : `p. ${book.currentPage}/${book.totalPages} · ${pct}%`}
              </div>
            </div>
          )}
        </div>
      </div>
      <div style={{fontSize:9,letterSpacing:'0.16em',color:C.textFaint,textTransform:'uppercase',marginTop:12,paddingTop:10,borderTop:`1px solid ${C.border}`}}>
        {footer}
      </div>
    </Card>
  );
};

const ReadingGoals = ({ books, goals, onSave }) => {
  const tr = useT();
  const now = new Date();
  const y = now.getFullYear(), m = now.getMonth();
  const counts = React.useMemo(() => {
    let yearN = 0, monthN = 0;
    for (const b of books) {
      if (b.status !== 'finished') continue;
      const src = b.finishedAt || b.updatedAt;
      if (!src) continue;
      const d = new Date(src);
      if (isNaN(d)) continue;
      if (d.getFullYear() === y) {
        yearN++;
        if (d.getMonth() === m) monthN++;
      }
    }
    return { yearN, monthN };
  }, [books, y, m]);

  const [editing, setEditing] = React.useState(false);
  const [yt, setYt] = React.useState(goals.yearlyTarget);
  const [mt, setMt] = React.useState(goals.monthlyTarget);
  React.useEffect(() => { setYt(goals.yearlyTarget); setMt(goals.monthlyTarget); }, [goals.yearlyTarget, goals.monthlyTarget]);

  const yearTarget = Math.max(0, goals.yearlyTarget|0);
  const monthTarget = Math.max(0, goals.monthlyTarget|0);
  const yearPct = yearTarget ? Math.min(100, Math.round(counts.yearN/yearTarget*100)) : 0;
  const monthPct = monthTarget ? Math.min(100, Math.round(counts.monthN/monthTarget*100)) : 0;

  const monthName = now.toLocaleString((window.getLang && window.getLang()==='es') ? 'es-ES' : 'en-US', { month: 'long' });

  const Stat = ({ label, n, target, pct }) => (
    <div style={{flex:1,minWidth:200}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:8}}>
        <span style={{fontSize:10,fontWeight:200,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase'}}>{label}</span>
        <span style={{fontSize:11,fontWeight:300,color:C.textMuted}}>
          <span style={{fontSize:22,fontWeight:800,color:C.sand,fontStyle:'italic',letterSpacing:'-0.02em'}}>{n}</span>
          <span style={{margin:'0 4px'}}> / </span>{target || '—'}
        </span>
      </div>
      <div style={{height:4,borderRadius:999,background:'rgba(255,255,255,0.07)'}}>
        <div style={{width:`${pct}%`,height:'100%',borderRadius:999,background:`linear-gradient(90deg,${C.sand},${C.sandLight})`,transition:'width 0.4s'}}/>
      </div>
    </div>
  );

  return (
    <Card style={{padding:24,marginBottom:24}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:18,flexWrap:'wrap',gap:10}}>
        <Label mb={0}>{tr('reading.readingGoals')}</Label>
        {!editing ? (
          <button onClick={()=>setEditing(true)} style={{background:'transparent',border:'none',padding:0,cursor:'pointer',
            color:C.textMuted,fontSize:10,fontWeight:500,letterSpacing:'0.14em',textTransform:'uppercase',
            fontFamily:'Montserrat,sans-serif',textDecoration:'underline',textUnderlineOffset:'3px',textDecorationColor:C.border}}>
            {tr('reading.editGoals')}
          </button>
        ) : (
          <div style={{display:'flex',gap:8}}>
            <Button variant="ghost" size="sm" onClick={()=>{ setYt(goals.yearlyTarget); setMt(goals.monthlyTarget); setEditing(false); }}>{tr('reading.cancel')}</Button>
            <Button size="sm" icon="check" onClick={()=>{
              onSave({ yearlyTarget: parseInt(yt)||0, monthlyTarget: parseInt(mt)||0 });
              setEditing(false);
            }}>{tr('reading.save')}</Button>
          </div>
        )}
      </div>

      {!editing ? (
        <div style={{display:'flex',gap:32,flexWrap:'wrap'}}>
          <Stat label={`${y} · ${tr('reading.thisYear')}`} n={counts.yearN} target={yearTarget} pct={yearPct}/>
          <Stat label={`${monthName} · ${tr('reading.thisMonth')}`} n={counts.monthN} target={monthTarget} pct={monthPct}/>
        </div>
      ) : (
        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}>
          <div>
            <Label mb={8}>{tr('reading.yearlyGoalBooks')}</Label>
            <Input inputMode="numeric" value={yt ?? ''} onChange={e=>setYt(e.target.value.replace(/\D/g,''))} placeholder="12"/>
          </div>
          <div>
            <Label mb={8}>{tr('reading.monthlyGoalBooks')}</Label>
            <Input inputMode="numeric" value={mt ?? ''} onChange={e=>setMt(e.target.value.replace(/\D/g,''))} placeholder="1"/>
          </div>
        </div>
      )}
    </Card>
  );
};

const ReadingView = ({ store }) => {
  const tr = useT();
  const { books } = store.state;
  const active = books.find(b => b.status === 'reading') || null;
  const [filter, setFilter] = React.useState('all');
  const [addOpen, setAddOpen] = React.useState(false);
  const [editActiveOpen, setEditActiveOpen] = React.useState(false);
  const [isMobile, setIsMobile] = React.useState(() => typeof window!=='undefined' && window.matchMedia('(max-width: 768px)').matches);
  React.useEffect(() => {
    if (typeof window==='undefined') return;
    const mq = window.matchMedia('(max-width: 768px)');
    const onChange = () => setIsMobile(mq.matches);
    mq.addEventListener?.('change', onChange);
    return () => mq.removeEventListener?.('change', onChange);
  }, []);
  const [detailId, setDetailId] = React.useState(null);
  const [detailOrigin, setDetailOrigin] = React.useState(null);
  const detailBook = books.find(b => b.id === detailId) || null;
  const openDetail = (id, rect) => {
    if (id == null) return;
    setDetailOrigin(rect || null);
    setDetailId(id);
  };

  // Deep link from Overview: if a bookId was set on window, open that book.
  React.useEffect(() => {
    const id = window.__vybOpenBook;
    if (id != null) {
      window.__vybOpenBook = null;
      // Defer so the Reading view paints first; detail then animates in.
      requestAnimationFrame(() => openDetail(id, null));
    }
  }, []);

  const counts = React.useMemo(() => {
    const c = { all: books.length, reading:0, finished:0, wishlist:0 };
    for (const b of books) c[b.status] = (c[b.status]||0) + 1;
    return c;
  }, [books]);

  const libraryBooks = React.useMemo(() => {
    const order = { reading:0, finished:1 };
    return books.filter(b => b.status !== 'wishlist').sort((a,b) => {
      const oa = order[a.status] ?? 3, ob = order[b.status] ?? 3;
      if (oa !== ob) return oa - ob;
      if (a.status === 'finished' && b.status === 'finished') {
        return (b.finishedAt || '').localeCompare(a.finishedAt || '');
      }
      return (b.updatedAt || '').localeCompare(a.updatedAt || '');
    });
  }, [books]);

  const wishlistBooks = React.useMemo(() => {
    return books.filter(b => b.status === 'wishlist')
      .sort((a,b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
  }, [books]);

  const LIBRARY_FILTERS = [
    { key:'all' }, { key:'reading' }, { key:'finished' },
  ];
  const libraryCounts = {
    all: libraryBooks.length,
    reading: counts.reading || 0,
    finished: counts.finished || 0,
  };
  const safeFilter = filter === 'wishlist' ? 'all' : filter;
  const filtered = safeFilter==='all' ? libraryBooks : libraryBooks.filter(b => b.status===safeFilter);
  const activePct = active && active.totalPages ? Math.min(100, Math.round(active.currentPage/active.totalPages*100)) : 0;

  return (
    <div>
      <ViewHeader label={tr('reading.label')} title={tr('reading.title')}
        subtitle={tr('reading.subtitle')}
        action={<Button onClick={()=>setAddOpen(true)} icon="plus">{tr('reading.addBook')}</Button>} />

      {/* Currently Reading */}
      {active ? (
        <Card style={{padding:28,marginBottom:32}}>
          <Label mb={16}>{tr('reading.currentlyReading')}</Label>
          <div style={{display:'flex',gap:24,alignItems:'flex-start',flexWrap:'wrap'}}>
            <div data-book-cover-anchor={active.id} style={{display:'inline-block'}}>
              <BookCover book={active} size="md" interactive3D
                onOpen={(rect)=>openDetail(active.id, rect)}/>
            </div>
            <div style={{flex:1,minWidth:240}}>
              <div style={{fontSize:22,fontWeight:700,color:C.textPrimary,lineHeight:1.2,letterSpacing:'-0.01em'}}>{active.title || tr('reading.untitled')}</div>
              {active.author && <div style={{fontSize:12,fontWeight:300,color:C.textMuted,marginTop:6}}>{active.author}</div>}
              <div style={{marginTop:20}}>
                <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:8}}>
                  <span style={{fontSize:10,fontWeight:200,letterSpacing:'0.18em',color:C.textFaint,textTransform:'uppercase'}}>{tr('reading.progress')}</span>
                  <span style={{fontSize:18,fontWeight:800,color:C.sand,fontStyle:'italic'}}>{activePct}%</span>
                </div>
                <div style={{height:4,borderRadius:999,background:'rgba(255,255,255,0.07)',marginBottom:10}}>
                  <div style={{width:`${activePct}%`,height:'100%',borderRadius:999,background:`linear-gradient(90deg,${C.sand},${C.sandLight})`,transition:'width 0.4s'}}/>
                </div>
                <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',flexWrap:'wrap',gap:10}}>
                  <div style={{fontSize:11,fontWeight:300,color:C.textMuted}}>{tr('reading.ofPages').replace('{shown}', String(active.currentPage)).replace('{total}', String(active.totalPages||'—'))}</div>
                  <Button icon="book-open" onClick={(e)=>{
                    e && e.stopPropagation && e.stopPropagation();
                    if (!active || active.id == null) return;
                    // Snapshot the Currently Reading cover so the fullscreen
                    // expand uses the same shared-element transition as
                    // clicking a normal book card.
                    let rect = null;
                    try {
                      const node = document.querySelector(`[data-book-cover-anchor="${active.id}"]`);
                      rect = node && node.getBoundingClientRect ? node.getBoundingClientRect() : null;
                      if (rect && (rect.width === 0 || rect.height === 0)) rect = null;
                    } catch (_) {}
                    openDetail(active.id, rect);
                  }}>{tr('reading.continueReading')}</Button>
                </div>
              </div>
            </div>
          </div>
        </Card>
      ) : (
        <Card style={{marginBottom:32}}>
          <Empty icon="book-open" title={tr('reading.startLibrary')}
            sub={tr('reading.addBookPlaceholder')}
            action={<Button onClick={()=>setAddOpen(true)} icon="plus">{tr('reading.addBook')}</Button>}/>
        </Card>
      )}

      <ReadingGoals books={books} goals={store.state.readingGoals || { yearlyTarget:12, monthlyTarget:1 }} onSave={(p)=>store.setReadingGoal(p)}/>

      {/* My Library — Reading + Finished only */}
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:16,flexWrap:'wrap',gap:12}}>
        <Label mb={0}>{tr('reading.libraryCount').replace('{n}', String(libraryBooks.length))}</Label>
        <div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
          {LIBRARY_FILTERS.map(s => (
            <Chip key={s.key} active={safeFilter===s.key} onClick={()=>setFilter(s.key)}>{(window.dl ? window.dl('bookStatus', s.key) : s.key)} · {libraryCounts[s.key]||0}</Chip>
          ))}
        </div>
      </div>

      {filtered.length === 0 ? (
        <Card>
          <Empty icon="library" title={libraryBooks.length===0 ? tr('reading.captureIdeasWorth') : tr('reading.nothingHereYet')}
            sub={libraryBooks.length===0 ? tr('reading.addBooksReading') : tr('reading.tryDifferentFilter')}
            action={<Button onClick={()=>setAddOpen(true)} icon="plus">{tr('reading.addBook')}</Button>}/>
        </Card>
      ) : (
        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(280px,1fr))',gap:14}}>
          {filtered.map(b => <BookCard key={b.id} book={b} onOpen={(rect)=>openDetail(b.id, rect)}/>)}
        </div>
      )}

      {/* Wishlist — books to read later */}
      {wishlistBooks.length > 0 && (
        <>
          <div style={{height:48}}/>
          <div style={{borderTop:`1px solid ${C.border}`,paddingTop:24}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:6,flexWrap:'wrap',gap:8}}>
              <Label mb={0}>{tr('reading.wishlistCount').replace('{n}', String(wishlistBooks.length))}</Label>
            </div>
            <div style={{fontSize:11,fontWeight:300,color:C.textMuted,marginBottom:16}}>{tr('reading.booksToReadLater')}</div>
            <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(280px,1fr))',gap:14}}>
              {wishlistBooks.map(b => <BookCard key={b.id} book={b} onOpen={(rect)=>openDetail(b.id, rect)}/>)}
            </div>
          </div>
        </>
      )}

      {/* Add Book CTA — desktop: fixed FAB bottom-right. Mobile: inline,
          centered below the last book card so it never overlaps content.
          Hidden while a book detail is open (the detail has its own header). */}
      {!detailId && (
        <div style={isMobile
          ? { display:'flex', justifyContent:'center', marginTop:24,
              marginBottom:'calc(24px + env(safe-area-inset-bottom))' }
          : { position:'fixed', right:32, bottom:32, zIndex:60 }}>
          <button onClick={()=>setAddOpen(true)} title={tr('reading.addBook')} aria-label={tr('reading.addBook')}
            className="vyb-add-book-fab"
            style={{
              display:'inline-flex', alignItems:'center', gap:8,
              padding:'12px 18px', borderRadius:999,
              background:C.sand, color:C.bg, border:'none',
              fontFamily:'Montserrat,sans-serif', fontSize:11, fontWeight:700,
              letterSpacing:'0.14em', textTransform:'uppercase', cursor:'pointer',
              boxShadow:'0 14px 36px rgba(0,0,0,0.55), 0 0 0 1px rgba(160,138,86,0.25)',
              transition:'transform 0.15s ease-out, box-shadow 0.15s ease-out',
            }}
            onMouseEnter={e=>{ e.currentTarget.style.transform='translateY(-1px)'; e.currentTarget.style.boxShadow='0 18px 44px rgba(0,0,0,0.6), 0 0 0 1px rgba(160,138,86,0.35)'; }}
            onMouseLeave={e=>{ e.currentTarget.style.transform='none'; e.currentTarget.style.boxShadow='0 14px 36px rgba(0,0,0,0.55), 0 0 0 1px rgba(160,138,86,0.25)'; }}>
            <Icon name="plus" size={14} color={C.bg} sw={2.5}/>
            {tr('reading.addBook')}
          </button>
        </div>
      )}

      <BookFormModal open={addOpen} onClose={()=>setAddOpen(false)} initial={null}
        onSave={(p)=>store.addBook(p)}/>
      <BookFormModal open={editActiveOpen} onClose={()=>setEditActiveOpen(false)} initial={active}
        onSave={(p)=>store.updateBook(active.id, p)}
        onDelete={active ? ()=>store.deleteBook(active.id) : null}/>
      <BookDetailFullScreen open={!!detailId} onClose={()=>setDetailId(null)} book={detailBook} store={store}
        originRect={detailOrigin}/>
    </div>
  );
};

// ─── HOME VIEW ─────────────────────────────────────────────────
// Minimal post-login start screen. Asks the user where they want to focus
// today and offers four large actions + a secondary Dashboard CTA.
// Not added to the nav menu — reached only via the logo or the default
// post-login redirect.
const HomeView = ({ goTo }) => {
  const tr = useT();
  const actions = [
    { id: 'tasks',   icon: 'list-todo',    label: tr('home.actions.tasks'),   short: tr('home.actionsShort.tasks') },
    { id: 'habits',  icon: 'check-circle', label: tr('home.actions.habits'),  short: tr('home.actionsShort.habits') },
    { id: 'reading', icon: 'book-open',    label: tr('home.actions.reading'), short: tr('home.actionsShort.reading') },
    { id: 'ideas',   icon: 'lightbulb',    label: tr('home.actions.ideas'),   short: tr('home.actionsShort.ideas') },
  ];
  return (
    <div className="vyb-home-root"
      style={{minHeight:'70vh',display:'flex',alignItems:'center',justifyContent:'center',
        padding:'24px 0 48px'}}>
      <style>{`
        @keyframes vybHomeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
        .vyb-home-card { animation: vybHomeIn 0.36s cubic-bezier(0.22,1,0.36,1) both; }
        .vyb-home-card:hover { border-color: var(--c-sand, #A08A56) !important; background: var(--c-sand-faint, rgba(160,138,86,0.08)) !important; transform: translateY(-2px); }
        .vyb-home-card:active { transform: translateY(0); background: var(--c-sand-faint, rgba(160,138,86,0.12)) !important; }
        .vyb-home-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; max-width: 560px; }
        .vyb-home-subtitle { display: block; }
        /* Mobile: compact one-row launcher (with 2x2 fallback at very narrow widths). */
        @media (max-width: 768px) {
          .vyb-home-root { min-height: auto; padding: 12px 0 24px; }
          .vyb-home-grid { gap: 8px; grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 100%; padding: 0 8px; }
          .vyb-home-card { flex-direction: column !important; align-items: center !important; justify-content: center !important;
            gap: 6px !important; padding: 12px 6px !important; border-radius: 14px !important; min-height: 72px; }
          .vyb-home-card .vyb-home-icon { width: 30px !important; height: 30px !important; }
          .vyb-home-card .vyb-home-label { font-size: 11px !important; font-weight: 500 !important;
            letter-spacing: 0.04em !important; text-align: center; line-height: 1.1; }
          .vyb-home-subtitle { display: none; }
          .vyb-home-label-long { display: none; }
          .vyb-home-label-short { display: inline !important; }
        }
        @media (max-width: 360px) {
          .vyb-home-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 0 12px; }
          .vyb-home-card { min-height: 80px; }
        }
        @media (prefers-reduced-motion: reduce) {
          .vyb-home-card { animation: none; }
          .vyb-home-card:hover, .vyb-home-card:active { transform: none; }
        }
      `}</style>
      <div style={{width:'100%',maxWidth:640,display:'flex',flexDirection:'column',alignItems:'center',gap:28}}>
        <div style={{textAlign:'center',display:'flex',flexDirection:'column',gap:8,
          animation:'vybHomeIn 0.4s cubic-bezier(0.22,1,0.36,1) both'}}>
          <div style={{fontSize:10,fontWeight:200,letterSpacing:'0.28em',color:C.textFaint,textTransform:'uppercase'}}>
            VYB Creators
          </div>
          <div style={{fontSize:'clamp(20px, 4vw, 34px)',fontWeight:300,fontStyle:'italic',
            color:C.textPrimary,letterSpacing:'-0.01em',lineHeight:1.15,maxWidth:520,padding:'0 16px'}}>
            {tr('home.title')}
          </div>
          <div className="vyb-home-subtitle"
            style={{fontSize:13,fontWeight:300,color:C.textMuted,letterSpacing:'0.02em',marginTop:2}}>
            {tr('home.subtitle')}
          </div>
        </div>

        <div className="vyb-home-grid">
          {actions.map((a, idx) => (
            <div key={a.id} onClick={()=>goTo && goTo(a.id)}
              className="vyb-home-card"
              style={{display:'flex',flexDirection:'column',alignItems:'flex-start',gap:14,
                padding:'22px 20px',borderRadius:16,cursor:'pointer',
                background:C.card,border:`1px solid ${C.border}`,
                transition:'all 0.22s cubic-bezier(0.22,1,0.36,1)',
                animationDelay: `${0.08 + idx * 0.06}s`}}>
              <div className="vyb-home-icon"
                style={{width:38,height:38,borderRadius:999,
                  display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,
                  background:C.sandFaint,border:`1px solid ${C.sandGlow || C.border}`}}>
                <Icon name={a.icon} size={18} color={C.sandLight} sw={2}/>
              </div>
              <div className="vyb-home-label"
                style={{fontSize:14,fontWeight:600,color:C.textPrimary,letterSpacing:'0.02em'}}>
                <span className="vyb-home-label-long">{a.label}</span>
                <span className="vyb-home-label-short" style={{display:'none'}}>{a.short}</span>
              </div>
            </div>
          ))}
        </div>

        <div onClick={()=>goTo && goTo('overview')}
          style={{marginTop:4,padding:'10px 18px',cursor:'pointer',
            display:'inline-flex',alignItems:'center',gap:8,
            color:C.textFaint,fontSize:11,fontWeight:500,letterSpacing:'0.16em',textTransform:'uppercase',
            transition:'color 0.18s ease-out',
            animation:'vybHomeIn 0.5s cubic-bezier(0.22,1,0.36,1) 0.4s both'}}
          onMouseEnter={e=>e.currentTarget.style.color=C.sandLight}
          onMouseLeave={e=>e.currentTarget.style.color=C.textFaint}>
          <span>{tr('home.dashboardCta')}</span>
          <Icon name="arrow-right" size={13} color="currentColor" sw={2}/>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { ViewHeader, HabitsView, WorkoutsView, ReadingView, BookCover, HomeView });
