// rooms.jsx — room catalog, generic Item system, frames, empty wall cells.
//
// Model:
//   room = { id, kind, title, wallColor?, items: Item[] }
//   item = {
//     id, type: 'photo' | 'text' | 'link',
//     frame: 'none' | 'thin' | 'wood' | 'polaroid' | 'tape' | 'oval',
//     size:  'sm' | 'md' | 'lg',
//     // photo
//     src?, seed?, caption?, url?
//     // text
//     text?, font: 'hand' | 'mono' | 'serif' | 'display'
//     // link (rendered as a small pinned symbol+label, NOT a big card)
//     icon?, label?, sublabel?, url?
//   }
//
// `kind` only controls the furniture silhouettes on the floor — the wall is
// fully customizable.  An "empty cell" (no room object) renders as a plain
// wall + floor so an unfilled half-floor still looks like part of the house.

// ─────────────────────────────────────────────────────────────
// i18n bridge for the house pages.
//
// The house UI is React rendered by in-browser Babel; i18n.js only knows how
// to translate DOM nodes carrying data-i18n. So instead we expose a helper
// `ht(key, fallback)` that reads from window.i18n.t at render time, with a
// French fallback baked in — if a key is missing (or i18n hasn't loaded
// yet), the user still sees clean French rather than a raw key. House
// components call `useHouseLang()` so they re-render when the visitor changes
// language via the picker (i18n.js dispatches an "i18n:change" event).
// ─────────────────────────────────────────────────────────────
function ht(key, fallback, vars) {
  try {
    if (window.i18n && typeof window.i18n.t === 'function') {
      const out = window.i18n.t(key, vars || null);
      // i18n.t returns the key itself when missing in every dict; guard so we
      // never surface a raw dotted key to the user.
      if (out && out !== key) return out;
    }
  } catch { /* fall through to fallback */ }
  // Apply {var} substitution on the fallback too, so callers can rely on it.
  let s = fallback != null ? fallback : key;
  if (vars) for (const k in vars) s = s.split('{' + k + '}').join(vars[k]);
  return s;
}
function useHouseLang() {
  const [, force] = React.useReducer((n) => n + 1, 0);
  React.useEffect(() => {
    const onChange = () => force();
    window.addEventListener('i18n:change', onChange);
    return () => window.removeEventListener('i18n:change', onChange);
  }, []);
  return (window.i18n && window.i18n.lang && window.i18n.lang()) || 'fr';
}

// ─────────────────────────────────────────────────────────────
// Catalog of room kinds (drives furniture + default wall/floor color).
// Each kind also carries a default wall *pattern* and floor *style* so a
// freshly-created room already has a distinct, finished-looking ambience
// instead of a flat colour. Both are per-room overridable (room.wallPattern,
// room.floorColor, room.floorStyle).
// ─────────────────────────────────────────────────────────────
const ROOM_LIBRARY = {
  kitchen:    { label: 'Cuisine',        wall: '#FFE9B8', floor: '#D9A055', furn: 'kitchen',    pattern: 'tile',    floorStyle: 'tiles' },
  livingroom: { label: 'Salon TV',       wall: '#F8D9B0', floor: '#A87648', furn: 'livingroom', pattern: 'plain',   floorStyle: 'wood'  },
  music:      { label: 'Musique',        wall: '#F9C8DC', floor: '#7A4566', furn: 'music',      pattern: 'dots',    floorStyle: 'wood'  },
  bedroom:    { label: 'Chambre',        wall: '#D7CCF5', floor: '#544B85', furn: 'bedroom',    pattern: 'stripe',  floorStyle: 'rug'   },
  bathroom:   { label: 'Salle de bain',  wall: '#C2E2EE', floor: '#5C8FA3', furn: 'bathroom',   pattern: 'tile',    floorStyle: 'tiles' },
  library:    { label: 'Bibliothèque',   wall: '#EBD8B5', floor: '#82603A', furn: 'library',    pattern: 'plain',   floorStyle: 'wood'  },
  studio:     { label: 'Atelier',        wall: '#CFE5C5', floor: '#4D6B41', furn: 'studio',     pattern: 'grid',    floorStyle: 'concrete' },
  gym:        { label: 'Gym',            wall: '#EFC2C1', floor: '#945454', furn: 'gym',        pattern: 'stripe',  floorStyle: 'concrete' },
  studio_art: { label: 'Atelier d\'art', wall: '#FFD9C2', floor: '#B36A48', furn: 'art',        pattern: 'dots',    floorStyle: 'wood'  },
  travel:     { label: 'Salle des cartes', wall: '#DDE9F2', floor: '#5B7388', furn: 'travel',   pattern: 'grid',    floorStyle: 'wood'  },
  empty:      { label: 'Pièce',          wall: '#F1ECDF', floor: '#9E8060', furn: 'none',       pattern: 'plain',   floorStyle: 'wood'  },
};

const ROOM_ORDER = ['kitchen','livingroom','music','bedroom','bathroom','library','studio','gym','studio_art','travel','empty'];

// Translated display name for a room kind. ROOM_LIBRARY.label stays as the
// French fallback; this resolves the localized label at render time.
function roomLabel(kind) {
  const meta = ROOM_LIBRARY[kind] || ROOM_LIBRARY.empty;
  return ht('house.room.kind.' + kind, meta.label);
}

// Wall-color palette the user can pick from (per-room override). Grouped:
// warm pastels, cool pastels, then rich/dark tones for contrast.
const WALL_PALETTE = [
  '#FFE9B8','#FFD9C2','#F9C8DC','#EFC2C1','#FCE3A8',
  '#D7CCF5','#C2E2EE','#CFE5C5','#DDE9F2','#BFE3D6',
  '#F8D9B0','#EBD8B5','#FFF0E8','#F1ECDF','#E9DFF7',
  '#E1604F','#3E7CB1','#2E8B6F','#222831','#2B1F1B',
];

// Wall patterns — subtle textures layered over the wall colour. 'plain' is
// just the flat colour (back-compatible default for old rooms).
const WALL_PATTERNS = ['plain','stripe','dots','grid','tile'];

// Floor styles — distinct floor treatments, each derived from the floor
// colour so they stay coherent with the room palette.
const FLOOR_STYLES = ['wood','tiles','rug','concrete'];

const FRAME_STYLES = ['none','thin','wood','polaroid','tape','oval'];
const FONT_STYLES  = ['hand','mono','serif','display'];

// ─────────────────────────────────────────────────────────────
// wallPatternCss — returns a CSS background-image for a given pattern over a
// base wall colour. Kept deliberately soft so wall items stay readable.
// ─────────────────────────────────────────────────────────────
function wallPatternCss(pattern, dark) {
  const ink = dark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.06)';
  const ink2 = dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.045)';
  switch (pattern) {
    case 'stripe':
      return `repeating-linear-gradient(45deg, ${ink} 0 2px, transparent 2px 11px)`;
    case 'dots':
      return `radial-gradient(${ink} 1.4px, transparent 1.5px)`;
    case 'grid':
      return `linear-gradient(0deg, ${ink2} 0 1px, transparent 1px 16px), linear-gradient(90deg, ${ink2} 0 1px, transparent 1px 16px)`;
    case 'tile':
      return `linear-gradient(0deg, ${ink} 0 1px, transparent 1px 15px), linear-gradient(90deg, ${ink} 0 1px, transparent 1px 15px)`;
    case 'plain':
    default:
      return 'none';
  }
}
function wallPatternSize(pattern) {
  switch (pattern) {
    case 'dots': return '11px 11px';
    case 'grid': return '16px 16px';
    case 'tile': return '15px 15px';
    default:     return 'auto';
  }
}

// ─────────────────────────────────────────────────────────────
// FloorLayer — renders the bottom 30% of a room as a styled floor. Each
// style is built from the floor colour so it always matches the palette.
// ─────────────────────────────────────────────────────────────
function FloorLayer({ color, style }) {
  const base = { position:'absolute', left:0, right:0, bottom:0, height:'30%', background: color };
  let bgImage = '', bgSize = 'auto';
  if (style === 'tiles') {
    bgImage = 'linear-gradient(0deg, rgba(0,0,0,0.16) 0 1.5px, transparent 1.5px 14px), linear-gradient(90deg, rgba(0,0,0,0.16) 0 1.5px, transparent 1.5px 14px)';
    bgSize = '14px 14px';
  } else if (style === 'rug') {
    bgImage = 'repeating-linear-gradient(90deg, rgba(255,255,255,0.10) 0 6px, rgba(0,0,0,0.06) 6px 12px)';
  } else if (style === 'concrete') {
    bgImage = 'radial-gradient(rgba(0,0,0,0.08) 1px, transparent 1.5px)';
    bgSize = '7px 7px';
  } else { // wood
    bgImage = 'repeating-linear-gradient(90deg, rgba(0,0,0,0.10) 0 1px, transparent 1px 12px)';
  }
  return <div style={{ ...base, backgroundImage: bgImage, backgroundSize: bgSize }}/>;
}

// ─────────────────────────────────────────────────────────────
// Frame — pluggable visual wrapper for any item content
// ─────────────────────────────────────────────────────────────
function Frame({ style, w, h, children }) {
  const base = { width: w, height: h, boxSizing:'border-box', position:'relative' };

  if (style === 'none') {
    return <div style={{ ...base, overflow:'hidden' }}>{children}</div>;
  }
  if (style === 'thin') {
    return <div style={{ ...base, border:'2px solid #5A4A40', background:'#fff', overflow:'hidden', boxShadow:'0 3px 8px rgba(74,59,51,0.22)' }}>{children}</div>;
  }
  if (style === 'wood') {
    return (
      <div style={{ ...base, padding: 4, background:'#8A5A2C', border:'2px solid #5A4A40', overflow:'hidden', boxShadow:'0 3px 8px rgba(74,59,51,0.22)',
        backgroundImage:'repeating-linear-gradient(90deg, rgba(0,0,0,0.12) 0 1px, transparent 1px 4px)' }}>
        <div style={{ width:'100%', height:'100%', overflow:'hidden', border:'1px solid #4A3B33' }}>{children}</div>
      </div>
    );
  }
  if (style === 'polaroid') {
    return (
      <div style={{ ...base, background:'#fff', border:'2px solid #5A4A40', padding:'3px 3px 12px', boxShadow:'0 3px 9px rgba(74,59,51,0.24)', transform:'rotate(-1.5deg)' }}>
        <div style={{ width:'100%', height:'100%', overflow:'hidden' }}>{children}</div>
      </div>
    );
  }
  if (style === 'tape') {
    return (
      <div style={{ ...base }}>
        <div style={{ position:'absolute', left:-2, top:-3, width:14, height:5, background:'rgba(255,240,140,0.85)', border:'1px solid rgba(0,0,0,0.4)', transform:'rotate(-22deg)', zIndex:2 }}/>
        <div style={{ position:'absolute', right:-2, top:-3, width:14, height:5, background:'rgba(255,240,140,0.85)', border:'1px solid rgba(0,0,0,0.4)', transform:'rotate(22deg)', zIndex:2 }}/>
        <div style={{ width:'100%', height:'100%', overflow:'hidden', background:'#fff', border:'1.5px solid #4A3B33' }}>{children}</div>
      </div>
    );
  }
  if (style === 'oval') {
    return <div style={{ ...base, border:'2px solid #5A4A40', borderRadius:'50%', overflow:'hidden', background:'#fff', boxShadow:'0 3px 8px rgba(74,59,51,0.22)' }}>{children}</div>;
  }
  // fallback
  return <div style={{ ...base, overflow:'hidden' }}>{children}</div>;
}

// ─────────────────────────────────────────────────────────────
// LinkPin — a small symbol + label pinned to the wall.
// Always small, never a big card.  This is what a "link" looks like.
// ─────────────────────────────────────────────────────────────
function LinkPin({ item, scale = 1 }) {
  const meta = LINK_ICONS_META[item.icon] || LINK_ICONS_META.web;
  const r = 22 * scale;
  // Cosy ambient motion: music/play discs slowly spin like a record on a
  // turntable; hearts gently pulse. Everything is slow, looping, and disabled
  // for reduced-motion users (handled by the .stx-spin / .stx-pulse CSS).
  const spins = item.icon === 'music' || item.icon === 'play';
  const pulses = item.icon === 'heart';
  const discClass = spins ? 'stx-spin' : pulses ? 'stx-pulse' : '';
  return (
    <div style={{ display:'flex', flexDirection:'column', alignItems:'center', gap: 2, maxWidth: 70 * scale }}>
      {/* pushpin tack */}
      <div style={{ width: 4, height: 4, background:'#E15B5B', border:'1px solid #4A3B33', borderRadius:'50%', marginBottom: -2, zIndex:2 }}/>
      <div className={discClass} style={{
        width: r*2, height: r*2, borderRadius:'50%',
        background: meta.bg, border:'2px solid #5A4A40',
        boxShadow:'0 2px 6px rgba(74,59,51,0.22)',
        display:'flex', alignItems:'center', justifyContent:'center',
        position:'relative',
      }}>
        <LinkIcon kind={item.icon} color={meta.fg} size={r * 1.1}/>
        {/* tiny spindle hole to sell the "spinning record" read */}
        {spins && <div style={{ position:'absolute', width: Math.max(3, r*0.18), height: Math.max(3, r*0.18),
          borderRadius:'50%', background:'#4A3B33', opacity:0.55 }}/>}
      </div>
      {item.label && (
        <div style={{
          fontFamily:'"JetBrains Mono", monospace', fontWeight:500, fontSize: 7 * scale,
          color:'#111', textAlign:'center', lineHeight: 1.1, letterSpacing: 0.2,
          background:'rgba(255,255,255,0.85)', padding:'1px 4px', borderRadius: 3,
          maxWidth: 70 * scale, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
        }}>{item.label}</div>
      )}
      <style>{`
        @keyframes stxSpin { from{transform:rotate(0)} to{transform:rotate(360deg)} }
        @keyframes stxPulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.08)} }
        .stx-spin { animation: stxSpin 8s linear infinite; }
        .stx-pulse { animation: stxPulse 2.6s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .stx-spin, .stx-pulse { animation: none !important; }
        }
      `}</style>
    </div>
  );
}

const LINK_ICONS_META = {
  music:    { bg:'#1DB954', fg:'#fff' },
  play:     { bg:'#FF0033', fg:'#fff' },
  web:      { bg:'#3A4D8F', fg:'#fff' },
  camera:   { bg:'#E29BBE', fg:'#fff' },
  book:     { bg:'#8A6A42', fg:'#fff' },
  food:     { bg:'#E89D5A', fg:'#fff' },
  art:      { bg:'#A95EA0', fg:'#fff' },
  heart:    { bg:'#E15B5B', fg:'#fff' },
  chat:     { bg:'#5BB0E1', fg:'#fff' },
  shop:     { bg:'#222',    fg:'#FFD159' },
  code:     { bg:'#111',    fg:'#7CE38B' },
  star:     { bg:'#FFD159', fg:'#111' },
};
const LINK_ICONS_LIST = Object.keys(LINK_ICONS_META);

function LinkIcon({ kind, color, size }) {
  const c = color;
  const sw = Math.max(1.5, size * 0.12);
  const props = { width: size, height: size, viewBox: '0 0 24 24', fill:'none' };
  const stroke = (d, extra = {}) => <path d={d} stroke={c} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" {...extra}/>;
  switch (kind) {
    case 'music':  return <svg {...props}><circle cx="8" cy="18" r="3" fill={c}/><circle cx="18" cy="16" r="3" fill={c}/><path d="M11 18V5l10-2v13" stroke={c} strokeWidth={sw} fill="none" strokeLinejoin="round"/></svg>;
    case 'play':   return <svg {...props}><polygon points="8,5 8,19 19,12" fill={c}/></svg>;
    case 'web':    return <svg {...props}><circle cx="12" cy="12" r="9" stroke={c} strokeWidth={sw}/><path d="M3 12h18M12 3c2.5 3 4 6 4 9s-1.5 6-4 9c-2.5-3-4-6-4-9s1.5-6 4-9z" stroke={c} strokeWidth={sw} fill="none"/></svg>;
    case 'camera': return <svg {...props}><rect x="3" y="7" width="18" height="13" rx="2" stroke={c} strokeWidth={sw}/><path d="M9 7l2-3h2l2 3" stroke={c} strokeWidth={sw}/><circle cx="12" cy="14" r="3.5" stroke={c} strokeWidth={sw}/></svg>;
    case 'book':   return <svg {...props}><path d="M4 5a2 2 0 012-2h12v18H6a2 2 0 01-2-2V5z" stroke={c} strokeWidth={sw}/><path d="M8 7h6M8 11h6" stroke={c} strokeWidth={sw}/></svg>;
    case 'food':   return <svg {...props}>{stroke('M5 4v6a3 3 0 003 3v8M5 4v6m3 0V4M16 4c-2 0-3 2-3 5s1 5 3 5v7')}</svg>;
    case 'art':    return <svg {...props}><path d="M12 3a9 9 0 100 18 2.5 2.5 0 002.5-2.5c0-.7-.3-1.3-.8-1.7-.4-.4-.7-.9-.7-1.5 0-1 .8-1.8 1.8-1.8h2.2a3 3 0 003-3A8 8 0 0012 3z" stroke={c} strokeWidth={sw}/><circle cx="7" cy="11" r="1.2" fill={c}/><circle cx="10" cy="7" r="1.2" fill={c}/><circle cx="14" cy="7" r="1.2" fill={c}/><circle cx="17" cy="11" r="1.2" fill={c}/></svg>;
    case 'heart':  return <svg {...props}><path d="M12 21s-7-4.5-7-10a4 4 0 017-2.6A4 4 0 0119 11c0 5.5-7 10-7 10z" fill={c}/></svg>;
    case 'chat':   return <svg {...props}><path d="M4 5h16v11H8l-4 4V5z" stroke={c} strokeWidth={sw} fill="none" strokeLinejoin="round"/></svg>;
    case 'shop':   return <svg {...props}><path d="M5 8h14l-1 12H6L5 8z" stroke={c} strokeWidth={sw}/><path d="M8 8V6a4 4 0 018 0v2" stroke={c} strokeWidth={sw}/></svg>;
    case 'code':   return <svg {...props}><path d="M8 8l-4 4 4 4M16 8l4 4-4 4M14 5l-4 14" stroke={c} strokeWidth={sw}/></svg>;
    case 'star':   return <svg {...props}><polygon points="12,3 14.5,9 21,9.5 16,13.5 17.5,20 12,16.5 6.5,20 8,13.5 3,9.5 9.5,9" fill={c}/></svg>;
    default:       return <svg {...props}><circle cx="12" cy="12" r="9" stroke={c} strokeWidth={sw}/></svg>;
  }
}

// ─────────────────────────────────────────────────────────────
// PhotoBody — pixels inside a photo item (cover; clipped by Frame)
// ─────────────────────────────────────────────────────────────
function PhotoBody({ item }) {
  if (item.src) {
    return <img src={item.src} alt="" style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }}/>;
  }
  return <FakePhoto seed={item.seed || 1}/>;
}

// ─────────────────────────────────────────────────────────────
// TextBody — text inside a text item.
//
// The wall thumbnail is tiny, so the old version just let long notes spill
// and get sliced mid-word (the unreadable "to…" in a busy room). Instead we
// treat the wall as a *preview*: the text scales to the box and is clamped to
// a whole number of lines with a trailing ellipsis — so it always breaks
// cleanly between words, never inside one. The full note is still readable by
// tapping the room (RoomViewer shows it in full). `box` carries the actual
// rendered size so we can pick a font size and line count that genuinely fit.
// ─────────────────────────────────────────────────────────────
function TextBody({ item, box }) {
  const fonts = {
    hand:    '"Caveat", cursive',
    mono:    '"JetBrains Mono", monospace',
    serif:   'Georgia, "Times New Roman", serif',
    display: '"Fredoka", system-ui',
  };
  const f = item.font || 'hand';
  const h = (box && box.h) || 48;
  const w = (box && box.w) || 90;
  // Scale the font to the available height; hand-writing reads larger.
  const big = f === 'hand';
  let fontSize = big
    ? Math.max(9,  Math.min(15, Math.round(h * 0.30)))
    : Math.max(7,  Math.min(11, Math.round(h * 0.22)));
  const lineHeight = 1.18;
  // How many whole lines fit in the box (leave a little vertical padding).
  const maxLines = Math.max(1, Math.floor((h - 6) / (fontSize * lineHeight)));
  return (
    <div style={{
      width:'100%', height:'100%', display:'flex', alignItems:'center', justifyContent:'center',
      textAlign:'center', padding: '3px 5px', boxSizing:'border-box',
      background: item.bg || (item.frame === 'none' ? 'transparent' : '#FFFDF1'),
    }}>
      <div style={{
        fontFamily: fonts[f], fontSize, color: item.color || '#111', lineHeight,
        fontWeight: f === 'display' ? 600 : 400,
        display:'-webkit-box', WebkitBoxOrient:'vertical', WebkitLineClamp: maxLines,
        overflow:'hidden', textOverflow:'ellipsis',
        wordBreak:'normal', overflowWrap:'break-word', maxWidth: w,
      }}>{item.text || '…'}</div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Item — render a single wall item with link-badge if applicable
// ─────────────────────────────────────────────────────────────
function Item({ item, max }) {
  // For pure 'link' items: always a LinkPin, no frame. We shrink the pin so
  // the whole thing (tack + disc + label) fits inside the wall band height —
  // otherwise the label used to spill down onto the furniture.
  if (item.type === 'link') {
    const base = item.size === 'sm' ? 0.85 : item.size === 'lg' ? 1.15 : 1;
    const fit  = Math.max(0.5, Math.min(base, (max.h - 12) / 52));
    return <LinkPin item={item} scale={fit}/>;
  }
  // photo / text — sized by `size` within container max
  const sizeFactor = item.size === 'sm' ? 0.6 : item.size === 'lg' ? 1.0 : 0.82;
  const maxW = max.w;
  // Reserve space for a caption (photos) so the frame + caption together stay
  // inside the band and never spill onto the furniture below.
  const captionH = (item.type === 'photo' && item.caption) ? 13 : 0;
  const maxH = Math.max(20, max.h - captionH);
  let w, h;
  if (item.type === 'photo') {
    h = Math.min(maxH * sizeFactor, maxW * 0.85);
    w = h * 1.25;
    if (w > maxW * sizeFactor) { w = maxW * sizeFactor; h = w / 1.25; }
  } else { // text
    // Notes get a touch more width than other items so a few words fit per
    // line — a clamped multi-line preview reads far better than one cramped,
    // truncated line. Height fills most of the band.
    w = Math.min(maxW, maxW * Math.max(sizeFactor, 0.92));
    h = Math.min(maxH, Math.max(34, maxH * 0.92));
  }
  // tape/polaroid need extra margin so they fit
  const fr = item.frame || 'thin';
  const padForFrame = fr === 'polaroid' ? { w: 6, h: 14 } : { w: 0, h: 0 };
  const inner = item.type === 'photo'
    ? <PhotoBody item={item}/>
    : <TextBody item={item} box={{ w, h }}/>;
  return (
    <div style={{ position:'relative' }}>
      <Frame style={fr} w={w} h={h}>{inner}</Frame>
      {/* A folded "dog-ear" corner marks a text note as tappable to read in
          full — the wall only ever shows a preview of longer notes. */}
      {item.type === 'text' && (
        <div style={{ position:'absolute', right: -1, bottom: -1, width: 0, height: 0,
          borderLeft: '9px solid transparent', borderBottom: '9px solid rgba(0,0,0,0.28)',
          pointerEvents:'none', zIndex: 2 }}/>
      )}
      {item.url && <LinkBadge/>}
      {item.caption && item.type === 'photo' && (
        <div style={{ marginTop: 2, fontFamily:'"JetBrains Mono", monospace', fontSize: 7,
          color:'#111', textAlign:'center', maxWidth: w + padForFrame.w,
          whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
          background:'rgba(255,255,255,0.85)', padding:'1px 3px', borderRadius: 2 }}>{item.caption}</div>
      )}
    </div>
  );
}

function LinkBadge() {
  return (
    <div style={{ position:'absolute', top:-6, right:-6, zIndex:3,
      width: 16, height: 16, borderRadius:'50%', border:'1.5px solid #4A3B33',
      background:'#FFD159', display:'flex', alignItems:'center', justifyContent:'center',
      boxShadow:'0 2px 5px rgba(74,59,51,0.2)', fontFamily:'"Fredoka", system-ui', fontWeight:700, fontSize: 10, color:'#111' }}>↗</div>
  );
}

// ─────────────────────────────────────────────────────────────
// ItemsArea — lay items out on a strict "wall shelf" band.
//
// The room cell is tiny (152×118), so wall items, the room-name tag and the
// furniture all compete for the same space. To stop them overlapping we pin
// items to a reserved rectangle:
//   • top    — a fixed strip below the room-name tag (so the tag is never
//               clipped by an item or its little ↗ badge),
//   • bottom — just above the central low furniture (sofa / bed / sink /
//               desk all sit at ≥ 0.58·h), so items never reach the floor,
//   • sides  — generous gutters so the centred items steer clear of the
//               tall *side* furniture (speakers, lamps, shelves, fridge).
// Items are a centred flex row (so they never overlap each other), and each
// item — link pins and photo captions included — is sized to fit *inside*
// the band height (see <Item>), so nothing spills out the bottom.
// ─────────────────────────────────────────────────────────────
// Default free-layout positions for items that don't have x/y/scale/rot yet
// (everything migrates to manual placement). We fan them across the upper
// wall band so an un-arranged room still looks intentional; the owner then
// drags/scales/rotates them in the enlarged room editor. Positions are in
// PERCENT of the room box (0..100) for the item's CENTER; scale is relative
// to a base item size; rot in degrees. Deterministic per index.
function defaultItemLayout(idx, total) {
  const n = Math.max(1, total);
  // spread across the wall, slight vertical stagger and gentle tilt
  const fx = n === 1 ? 50 : 22 + (56 * idx) / (n - 1);
  const fy = 30 + (idx % 2 === 0 ? 0 : 9);
  const rot = (idx % 2 === 0 ? -1 : 1) * (3 + (idx % 3) * 2);
  return { x: fx, y: fy, scale: 1, rot };
}
function withItemLayout(item, idx, total) {
  const d = defaultItemLayout(idx, total);
  return {
    x:     typeof item.x === 'number' ? item.x : d.x,
    y:     typeof item.y === 'number' ? item.y : d.y,
    scale: typeof item.scale === 'number' ? item.scale : d.scale,
    rot:   typeof item.rot === 'number' ? item.rot : d.rot,
  };
}

// Free-placement layer: items are absolutely positioned by their x/y (% of
// the room box, measured at the item CENTER), scaled and rotated. They may
// overflow the room edges a little (overflow visible) so things feel placed
// by hand rather than boxed in. Base item size scales with the room so a
// scale of 1 reads as a comfortable medium object.
// Fraction of the room width an item occupies at scale 1. Used by BOTH the
// in-house render and the arrange editor so an item's on-screen size matches
// exactly between the two (no surprise when you scale it up in the editor and
// it looks different in the house). Kept as a pure fraction — no absolute
// pixel cap — so the proportion holds at any display size.
const ITEM_BASE_FRAC = 0.42;
function FreeItemsLayer({ items, w, h, onTap }) {
  if (!items || items.length === 0) return null;
  const total = items.length;
  const baseW = w * ITEM_BASE_FRAC;   // an item at scale 1
  return (
    <div style={{ position:'absolute', inset:0, pointerEvents:'none', zIndex: 3, overflow:'visible' }}>
      {items.map((it, i) => {
        const L = withItemLayout(it, i, total);
        const itemW = baseW * (L.scale || 1);
        return (
          <div key={it.id} style={{
            position:'absolute', left: `${L.x}%`, top: `${L.y}%`,
            width: itemW, maxWidth: 'none',
            transform: `translate(-50%, -50%) rotate(${L.rot || 0}deg)`,
            transformOrigin: 'center center',
            pointerEvents:'auto', cursor:'pointer',
            filter: 'drop-shadow(0 4px 8px rgba(74,59,51,0.22))',
          }}
            onClick={(e)=>{ e.stopPropagation(); onTap?.(it.id); }}>
            <Item item={it} max={{ w: itemW, h: itemW * 1.3 }}/>
          </div>
        );
      })}
    </div>
  );
}

function ItemsArea({ items, w, h, onTap }) {
  if (!items || items.length === 0) return null;
  const bandTop    = 24;                          // fixed strip under the name tag
  const bandBottom = h * 0.56;                     // above the central furniture
  const bandH      = Math.max(26, bandBottom - bandTop);
  const sideGutter = Math.max(16, w * 0.22);       // clear of the side furniture
  const n          = Math.min(items.length, 3);
  const gap        = n > 1 ? 8 : 0;
  const itemMaxW   = (w - sideGutter * 2 - gap * (n - 1)) / n;
  return (
    <div style={{
      position:'absolute', left: sideGutter, right: sideGutter,
      top: bandTop, height: bandH,
      display:'flex', alignItems:'center', justifyContent:'center', gap,
      pointerEvents:'none',
    }}>
      {items.slice(0, 3).map((it) => (
        <div key={it.id} style={{ pointerEvents:'auto', cursor:'pointer',
          display:'flex', alignItems:'center', justifyContent:'center',
          flex:'0 1 auto', minWidth:0, maxWidth: itemMaxW, maxHeight: bandH }}
          onClick={(e)=>{ e.stopPropagation(); onTap?.(it.id); }}>
          <Item item={it} max={{ w: itemMaxW, h: bandH }}/>
        </div>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Furniture — silhouettes on the room floor (unchanged from before)
// ─────────────────────────────────────────────────────────────
function Furniture({ kind, w, h }) {
  const floorTop = h * 0.70;
  const f = ROOM_LIBRARY[kind]?.furn;
  if (!f || f === 'none') return null;
  const blk = '#4A3B33', wht = '#fff';

  if (f === 'kitchen') {
    return (<>
      <div style={{ position:'absolute', left: w*0.06, top: floorTop - h*0.14, width: w*0.22, height: h*0.18,
        border:`2.5px solid ${blk}`, background:wht, borderRadius:'4px 4px 0 0' }}>
        <div style={{ position:'absolute', top: -h*0.10, left:'50%', transform:'translateX(-50%)',
          width: w*0.12, height: h*0.10, border:`2.5px solid ${blk}`, borderRadius:'40% 40% 6px 6px', background:wht }}/>
        <div style={{ position:'absolute', top:-h*0.18, left:'40%', width:2, height: h*0.06, background:blk, borderRadius:2, opacity:0.35 }}/>
        <div style={{ position:'absolute', top:-h*0.20, left:'55%', width:2, height: h*0.07, background:blk, borderRadius:2, opacity:0.25 }}/>
        <div style={{ position:'absolute', bottom:3, left:3, right:3, height: 3, background:blk, borderRadius:2, opacity:0.6 }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.06, top: floorTop - h*0.28, width: w*0.18, height: h*0.32,
        border:`2.5px solid ${blk}`, background:wht, borderRadius:'4px 4px 0 0' }}>
        <div style={{ position:'absolute', top:'40%', left:0, right:0, height:2, background:blk }}/>
        <div style={{ position:'absolute', top:'45%', right:'14%', width:3, height: h*0.05, background:blk, borderRadius:2 }}/>
      </div>
    </>);
  }
  if (f === 'livingroom') {
    return (<>
      <div style={{ position:'absolute', left: w*0.10, right: w*0.10, top: floorTop - h*0.12, height: h*0.18,
        border:`2.5px solid ${blk}`, background:'#7BA67C', borderRadius:'12px 12px 0 0' }}>
        <div style={{ position:'absolute', top: 4, left:6, right:6, height:'45%', background:'#A8C9A6', border:`1.5px solid ${blk}`, borderRadius: 6 }}/>
        <div style={{ position:'absolute', top: 4, left:'48%', bottom:'40%', width:2, background:blk }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.02, top: floorTop - h*0.36, width: w*0.08, height: h*0.40 }}>
        <div style={{ position:'absolute', left:'30%', top:0, width: 2, height:'100%', background:blk }}/>
        <div style={{ position:'absolute', top:-2, left:-2, width:'120%', height: h*0.10,
          background:'#FFE08A', border:`2px solid ${blk}`, borderRadius:'70% 70% 4px 4px / 90% 90% 4px 4px' }}/>
      </div>
    </>);
  }
  if (f === 'music') {
    return (<>
      <div style={{ position:'absolute', left: w*0.06, top: floorTop - h*0.26, width: w*0.14, height: h*0.30,
        border:`2.5px solid ${blk}`, background:'#222', borderRadius:'3px 3px 0 0' }}>
        <div style={{ position:'absolute', top:'12%', left:'50%', transform:'translateX(-50%)', width: w*0.06, height: w*0.06, border:`2px solid ${wht}`, borderRadius:'50%' }}/>
        <div style={{ position:'absolute', bottom:'10%', left:'50%', transform:'translateX(-50%)', width: w*0.09, height: w*0.09, border:`2px solid ${wht}`, borderRadius:'50%' }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.06, top: floorTop - h*0.26, width: w*0.14, height: h*0.30,
        border:`2.5px solid ${blk}`, background:'#222', borderRadius:'3px 3px 0 0' }}>
        <div style={{ position:'absolute', top:'12%', left:'50%', transform:'translateX(-50%)', width: w*0.06, height: w*0.06, border:`2px solid ${wht}`, borderRadius:'50%' }}/>
        <div style={{ position:'absolute', bottom:'10%', left:'50%', transform:'translateX(-50%)', width: w*0.09, height: w*0.09, border:`2px solid ${wht}`, borderRadius:'50%' }}/>
      </div>
    </>);
  }
  if (f === 'bedroom') {
    return (<>
      <div style={{ position:'absolute', left: w*0.06, right: w*0.18, top: floorTop - h*0.10, height: h*0.16,
        border:`2.5px solid ${blk}`, background:'#F2D7A8', borderRadius:'4px 4px 0 0' }}>
        <div style={{ position:'absolute', top: 3, left: 4, width:'28%', height:'55%', background:'#fff', border:`1.5px solid ${blk}`, borderRadius: 4 }}/>
        <div style={{ position:'absolute', top: 3, left: '34%', right:4, height:'70%', background:'#C18BD0', border:`1.5px solid ${blk}`, borderRadius: 3 }}/>
        <div style={{ position:'absolute', top:-h*0.06, left:0, width:'34%', height: h*0.08, background:'#fff', border:`2px solid ${blk}`, borderRadius:'4px 4px 0 0' }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.05, top: floorTop - h*0.16, width: w*0.10, height: h*0.20,
        border:`2.5px solid ${blk}`, background:wht }}>
        <div style={{ position:'absolute', top: h*0.05, left:'50%', transform:'translateX(-50%)', width: w*0.06, height: w*0.06, borderRadius:'50%', background:'#FFE08A', border:`2px solid ${blk}` }}/>
      </div>
    </>);
  }
  if (f === 'bathroom') {
    return (
      <div style={{ position:'absolute', left: w*0.08, right: w*0.08, top: floorTop - h*0.10, height: h*0.18,
        border:`2.5px solid ${blk}`, background:wht, borderRadius:'40px 40px 6px 6px / 20px 20px 6px 6px' }}>
        <div style={{ position:'absolute', top: 3, left: 5, right: 5, bottom: 3, background:'#A4D2E8', borderRadius:'34px 34px 4px 4px / 18px 18px 4px 4px' }}/>
        <div style={{ position:'absolute', top: -8, right: '12%', width: 6, height: 12, background:blk, borderRadius:'2px 2px 0 0' }}/>
      </div>
    );
  }
  if (f === 'library') {
    return (<>
      <Bookshelf x={w*0.04} y={floorTop - h*0.22} w={w*0.20} h={h*0.30}/>
      <Bookshelf x={w*0.76} y={floorTop - h*0.22} w={w*0.20} h={h*0.30}/>
    </>);
  }
  if (f === 'studio') {
    return (<>
      <div style={{ position:'absolute', left: w*0.10, right: w*0.10, top: floorTop - h*0.04, height: h*0.06,
        border:`2.5px solid ${blk}`, background:'#C28D5B' }}/>
      <div style={{ position:'absolute', left: w*0.14, top: floorTop + h*0.02, width: 4, height: h*0.20, background:blk }}/>
      <div style={{ position:'absolute', right: w*0.14, top: floorTop + h*0.02, width: 4, height: h*0.20, background:blk }}/>
      <div style={{ position:'absolute', left: w*0.30, right: w*0.30, top: floorTop - h*0.10, height: h*0.05,
        border:`2px solid ${blk}`, background:wht, borderRadius: 3 }}/>
      <div style={{ position:'absolute', left:'46%', top: floorTop + h*0.05, width: w*0.18, height: h*0.10,
        border:`2.5px solid ${blk}`, background:'#384247', borderRadius:'4px 4px 0 0' }}/>
    </>);
  }
  if (f === 'gym') {
    return (<>
      <div style={{ position:'absolute', left: w*0.08, top: floorTop - h*0.04, display:'flex', alignItems:'center', gap:2 }}>
        <div style={{ width: w*0.05, height: h*0.10, background:blk, borderRadius:2 }}/>
        <div style={{ width: w*0.10, height: 3, background:blk }}/>
        <div style={{ width: w*0.05, height: h*0.10, background:blk, borderRadius:2 }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.08, top: floorTop - h*0.04, width: w*0.28, height: h*0.04,
        border:`2.5px solid ${blk}`, background:'#7C2E2E' }}/>
      <div style={{ position:'absolute', right: w*0.10, top: floorTop + h*0.02, width: 3, height: h*0.10, background:blk }}/>
      <div style={{ position:'absolute', right: w*0.32, top: floorTop + h*0.02, width: 3, height: h*0.10, background:blk }}/>
    </>);
  }
  if (f === 'art') {
    return (<>
      <div style={{ position:'absolute', left:'40%', top: floorTop + h*0.02, width: 3, height: h*0.20, background:blk, transform:'rotate(-8deg)', transformOrigin:'top' }}/>
      <div style={{ position:'absolute', left:'58%', top: floorTop + h*0.02, width: 3, height: h*0.20, background:blk, transform:'rotate(8deg)', transformOrigin:'top' }}/>
      <div style={{ position:'absolute', left: w*0.08, top: floorTop + h*0.04, width: w*0.10, height: h*0.18,
        border:`2.5px solid ${blk}`, background:'#E8B873', borderRadius:'2px 2px 6px 6px' }}/>
      <div style={{ position:'absolute', left: w*0.20, top: floorTop + h*0.08, width: w*0.08, height: h*0.14,
        border:`2.5px solid ${blk}`, background:'#8FB68C', borderRadius:'2px 2px 6px 6px' }}/>
    </>);
  }
  if (f === 'travel') {
    return (<>
      <div style={{ position:'absolute', left: w*0.12, top: floorTop - h*0.04, width: w*0.24, height: h*0.16,
        border:`2.5px solid ${blk}`, background:'#A6512E', borderRadius: 3 }}>
        <div style={{ position:'absolute', top: -h*0.04, left:'42%', width:'16%', height: h*0.05, border:`2px solid ${blk}`, borderRadius:'4px 4px 0 0', background:wht, borderBottom: 0 }}/>
        <div style={{ position:'absolute', top:'45%', left:0, right:0, height:2, background:blk, opacity:0.5 }}/>
      </div>
      <div style={{ position:'absolute', right: w*0.12, top: floorTop - h*0.10, width: w*0.18, height: w*0.18,
        border:`2.5px solid ${blk}`, background:'#9EC4DD', borderRadius:'50%' }}>
        <div style={{ position:'absolute', top:'40%', left:'10%', right:'30%', height:3, background:'#5C8270', borderRadius: 2 }}/>
        <div style={{ position:'absolute', top:'60%', left:'40%', right:'10%', height:3, background:'#5C8270', borderRadius: 2 }}/>
      </div>
    </>);
  }
  return null;
}

function Bookshelf({ x, y, w, h }) {
  const spines = ['#9C5E5E','#5E7B9C','#9C8A5E','#5E9C7C','#7B5E9C','#9C9C5E'];
  return (
    <div style={{ position:'absolute', left:x, top:y, width:w, height:h, border:'2px solid #5A4A40', background:'#fff' }}>
      {[0,1,2].map(row=>(
        <div key={row} style={{ position:'absolute', left:0, right:0, top: `${(row+1)*33}%`, height:1.5, background:'#4A3B33' }}/>
      ))}
      {[0,1,2].map(row=>(
        <div key={`b${row}`} style={{ position:'absolute', left:2, right:2, top: `${row*33+2}%`, bottom: `${(2-row)*33+2}%`,
          display:'flex', gap:1, alignItems:'flex-end' }}>
          {spines.map((c,i)=>(
            <div key={i} style={{ flex:1, height: `${70+(i*7)%25}%`, background:c, border:'1px solid #4A3B33' }}/>
          ))}
        </div>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// RoomInterior — wall (custom colour + pattern), styled floor, furniture,
// items, name tag, and a clear "tappable" affordance: on hover/press the
// room lifts slightly and shows a small "ouvrir" pill, so visitors know a
// room opens on tap. The whole cell is one big click target.
// ─────────────────────────────────────────────────────────────
function RoomInterior({ room, w, h, onTapItem, onTapRoom }) {
  useHouseLang();
  const meta = ROOM_LIBRARY[room.kind] || ROOM_LIBRARY.empty;
  const wallColor = room.wallColor || meta.wall;
  const pattern   = room.wallPattern || meta.pattern || 'plain';
  const floorColor = room.floorColor || meta.floor;
  const floorStyle = room.floorStyle || meta.floorStyle || 'wood';
  const dark = isDarkColor(wallColor);
  const [hover, setHover] = React.useState(false);
  const patternImg = wallPatternCss(pattern, dark);
  return (
    <div
      style={{ position:'absolute', inset:0, overflow:'hidden', background: wallColor, cursor:'pointer',
        transition:'filter .15s ease' , filter: hover ? 'brightness(1.05)' : 'none' }}
      onClick={onTapRoom}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      onTouchStart={() => setHover(true)}
      onTouchEnd={() => setHover(false)}
    >
      {/* wall pattern layer (soft, behind everything) */}
      {patternImg !== 'none' && (
        <div style={{ position:'absolute', inset:0, backgroundImage: patternImg,
          backgroundSize: wallPatternSize(pattern), pointerEvents:'none' }}/>
      )}
      <FloorLayer color={floorColor} style={floorStyle}/>
      <div style={{ position:'absolute', left:0, right:0, top:'70%', height: 2.5, background:'rgba(74,59,51,0.45)' }}/>
      <Furniture kind={room.kind} w={w} h={h}/>
      <FreeItemsLayer items={room.items} w={w} h={h} onTap={(itemId) => { onTapRoom?.(itemId); }}/>
      {/* room name tag — a soft little label rather than a hard UI chip */}
      <div style={{ position:'absolute', right:6, top:6,
        fontFamily:'"JetBrains Mono", monospace', fontSize:7.5, letterSpacing:0.5,
        color: dark ? '#F4ECDC' : '#4A3B33',
        background: dark ? 'rgba(40,34,44,0.62)' : 'rgba(255,250,240,0.82)',
        padding:'2px 7px', border:`1px solid ${dark ? 'rgba(255,255,255,0.5)' : 'rgba(74,59,51,0.4)'}`,
        borderRadius:10, boxShadow:'0 1px 4px rgba(74,59,51,0.16)',
        WebkitBackdropFilter:'blur(3px)', backdropFilter:'blur(3px)',
        textTransform:'uppercase', whiteSpace:'nowrap', maxWidth: w - 12, overflow:'hidden', textOverflow:'ellipsis',
        zIndex: 4 }}>
        {room.title || roomLabel(room.kind)}
      </div>
      {/* "ouvrir" affordance — fades in on hover/press, bottom-left so it
          never collides with the name tag. Signals the room is tappable. */}
      <div style={{ position:'absolute', left:6, bottom:6, zIndex:4,
        display:'flex', alignItems:'center', gap:3,
        fontFamily:'"Fredoka", system-ui', fontWeight:600, fontSize:8, color:'#111',
        background:'rgba(255,255,255,0.92)', padding:'2px 6px 2px 5px', borderRadius:10,
        border:'1.5px solid #4A3B33', boxShadow:'0 2px 6px rgba(74,59,51,0.22)',
        opacity: hover ? 1 : 0, transform: hover ? 'translateY(0)' : 'translateY(3px)',
        transition:'opacity .15s ease, transform .15s ease', pointerEvents:'none' }}>
        <svg viewBox="0 0 24 24" width="9" height="9" fill="none" stroke="#4A3B33"
          strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
          <path d="M9 6l6 6-6 6"/>
        </svg>
        {ht('house.room.open', 'ouvrir')}
      </div>
    </div>
  );
}

// EmptyCell — a wall slot with no room. In edit mode it shows a clear "+"
// to add a room. For visitors (no edit) it now renders as a calm, finished
// "empty room" — a soft tinted wall + styled floor borrowed from a neighbour
// — instead of the old grey striped placeholder that read as broken.
function EmptyCell({ w, h, editMode, onAdd, neighborFloor }) {
  useHouseLang();
  const floorColor = neighborFloor || '#A88562';
  const wallColor = '#EFE7D8';
  return (
    <div style={{ position:'relative', width:w, height:h, background: wallColor, overflow:'hidden' }}>
      {/* soft, even wall texture — reads as a real (if bare) wall */}
      <div style={{ position:'absolute', inset:0, pointerEvents:'none',
        backgroundImage:'radial-gradient(rgba(0,0,0,0.045) 1.2px, transparent 1.3px)',
        backgroundSize:'12px 12px' }}/>
      <FloorLayer color={floorColor} style="wood"/>
      <div style={{ position:'absolute', left:0, right:0, top:'70%', height:2.5, background:'rgba(74,59,51,0.45)' }}/>
      {editMode ? (
        <button onClick={onAdd} style={{
          position:'absolute', top:'34%', left:'50%', transform:'translate(-50%,-50%)',
          display:'flex', flexDirection:'column', alignItems:'center', gap:3,
          border:'none', background:'transparent', cursor:'pointer', padding:0 }}>
          <span style={{
            width: 40, height: 40, borderRadius:'50%', border:'2px dashed #5A4A40',
            background:'rgba(255,255,255,0.9)', display:'flex', alignItems:'center', justifyContent:'center',
            fontFamily:'"Fredoka", system-ui', fontWeight:700, fontSize: 24, color:'#111',
            boxShadow:'2px 2px 0 rgba(0,0,0,0.4)' }}>+</span>
          <span style={{ fontFamily:'"Fredoka", system-ui', fontWeight:600, fontSize:8, color:'#111',
            background:'rgba(255,255,255,0.9)', padding:'1px 6px', borderRadius:8,
            border:'1.5px solid #4A3B33' }}>{ht('house.room.add', 'ajouter une pièce')}</span>
        </button>
      ) : (
        // Visitor view: a faint hint that this room is simply empty, centered
        // on the wall — quiet, not a "broken/unfinished" striped block.
        <div style={{ position:'absolute', top:'32%', left:'50%', transform:'translate(-50%,-50%)',
          display:'flex', flexDirection:'column', alignItems:'center', gap:4, opacity:0.5,
          pointerEvents:'none' }}>
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#7a6f5c"
            strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M3 11.5L12 4l9 7.5"/><path d="M5 10.5V20h14v-9.5"/>
          </svg>
          <span style={{ fontFamily:'"JetBrains Mono", monospace', fontSize:7.5, color:'#7a6f5c' }}>{ht('house.room.empty', 'pièce vide')}</span>
        </div>
      )}
    </div>
  );
}

// Tiny icon for room palette + tags
function RoomIcon({ kind, size = 22 }) {
  const G = {
    kitchen:    <><rect x="6" y="14" width="12" height="8" rx="1" fill="none" stroke="#4A3B33" strokeWidth="2"/><ellipse cx="12" cy="10" rx="5" ry="2.5" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M9 8v-3M12 8v-3M15 8v-3" stroke="#4A3B33" strokeWidth="1.5" strokeLinecap="round"/></>,
    livingroom: <><rect x="4" y="6" width="16" height="11" rx="1.5" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M9 17v3M15 17v3" stroke="#4A3B33" strokeWidth="2" strokeLinecap="round"/></>,
    music:      <><circle cx="12" cy="12" r="8" fill="none" stroke="#4A3B33" strokeWidth="2"/><circle cx="12" cy="12" r="2" fill="#4A3B33"/></>,
    bedroom:    <><path d="M3 18v-7h18v7M3 13h18M7 11V8h6v3" stroke="#4A3B33" strokeWidth="2" fill="none" strokeLinejoin="round"/></>,
    bathroom:   <><path d="M4 14h16v3a2 2 0 01-2 2H6a2 2 0 01-2-2v-3z" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M8 14V6a1.5 1.5 0 013 0v1" fill="none" stroke="#4A3B33" strokeWidth="2" strokeLinecap="round"/></>,
    library:    <><rect x="4" y="4" width="16" height="16" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M8 4v16M16 4v16M4 12h16" stroke="#4A3B33" strokeWidth="1.5"/></>,
    studio:     <><rect x="3" y="5" width="18" height="11" rx="1" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M9 19h6M12 16v3" stroke="#4A3B33" strokeWidth="2" strokeLinecap="round"/></>,
    gym:        <><path d="M3 12h2M19 12h2M7 8v8M9 7v10M15 7v10M17 8v8M9 12h6" stroke="#4A3B33" strokeWidth="2" strokeLinecap="round"/></>,
    studio_art: <><path d="M12 3a9 9 0 100 18 2.5 2.5 0 002.5-2.5c0-.7-.3-1.3-.8-1.7-.4-.4-.7-.9-.7-1.5 0-1 .8-1.8 1.8-1.8h2.2a3 3 0 003-3A8 8 0 0012 3z" fill="none" stroke="#4A3B33" strokeWidth="2"/><circle cx="7" cy="11" r="1" fill="#4A3B33"/><circle cx="10" cy="7" r="1" fill="#4A3B33"/><circle cx="14" cy="7" r="1" fill="#4A3B33"/><circle cx="17" cy="11" r="1" fill="#4A3B33"/></>,
    travel:     <><circle cx="12" cy="12" r="8" fill="none" stroke="#4A3B33" strokeWidth="2"/><path d="M4 12h16M12 4c-2.5 2.5-4 5.5-4 8s1.5 5.5 4 8M12 4c2.5 2.5 4 5.5 4 8s-1.5 5.5-4 8" fill="none" stroke="#4A3B33" strokeWidth="1.8"/></>,
    empty:      <><rect x="4" y="4" width="16" height="16" fill="none" stroke="#4A3B33" strokeWidth="2" strokeDasharray="2 2"/></>,
  };
  return <svg viewBox="0 0 24 24" width={size} height={size}>{G[kind] || G.empty}</svg>;
}

// Fake photo for placeholders
function FakePhoto({ seed = 1, palette }) {
  const palettes = [
    ['#E89B5A','#D4633E','#FFCD8B','#6B2E1F'],
    ['#9CC4A1','#5C8F70','#E6E2C2','#3A5947'],
    ['#E29BBE','#9C5E84','#FFE2EF','#3F1E2E'],
    ['#A4B7E0','#5775A8','#E2EAFA','#1E2B4A'],
    ['#E8C56B','#B07A2B','#FFEEC2','#5B3A12'],
    ['#C7A0E0','#7A5198','#EEDCFF','#2E1A45'],
  ];
  const p = palette || palettes[(seed - 1) % palettes.length];
  return (
    <svg viewBox="0 0 100 60" preserveAspectRatio="xMidYMid slice" style={{ display:'block', width:'100%', height:'100%' }}>
      <rect width="100" height="60" fill={p[0]}/>
      <ellipse cx="30" cy="50" rx="40" ry="22" fill={p[1]}/>
      <circle cx="78" cy="20" r="14" fill={p[2]}/>
      <path d="M0 45 Q 30 35 60 45 T 100 42 V60 H0 Z" fill={p[3]} opacity="0.85"/>
    </svg>
  );
}

// brightness check for wall color → adjust label contrast
function isDarkColor(hex) {
  if (!hex || hex[0] !== '#') return false;
  const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
  return (r*0.299 + g*0.587 + b*0.114) < 130;
}

// Default starter items for a freshly-created room of a given kind. Every
// kind now starts with a couple of coherent items (a focal piece + an
// accent) so a brand-new room already looks intentional and finished rather
// than bare — bathroom/gym used to start empty, which read as unfinished.
function defaultItemsFor(kind) {
  const id = () => 'i_' + Math.random().toString(36).slice(2, 8);
  switch (kind) {
    case 'kitchen':    return [
      { id:id(), type:'photo', frame:'polaroid', size:'md', seed:1, caption:"le repas du jour" },
      { id:id(), type:'link',  size:'sm', icon:'food',  label:'recette', url:'' },
    ];
    case 'livingroom': return [
      { id:id(), type:'photo', frame:'thin',  size:'lg', seed:4, caption:"ce que je regarde" },
      { id:id(), type:'link',  size:'sm', icon:'play',  label:'à voir', url:'' },
    ];
    case 'music':      return [
      { id:id(), type:'link',  size:'md', icon:'music', label:'ma playlist', url:'' },
      { id:id(), type:'photo', frame:'thin', size:'sm', seed:6, caption:"en boucle" },
    ];
    case 'bedroom':    return [
      { id:id(), type:'text',  frame:'tape', size:'md', font:'hand', text:'une pensée du jour' },
      { id:id(), type:'photo', frame:'polaroid', size:'sm', seed:3, caption:"" },
    ];
    case 'bathroom':   return [
      { id:id(), type:'text',  frame:'oval', size:'md', font:'display', text:'oui' },
    ];
    case 'library':    return [
      { id:id(), type:'link',  size:'md', icon:'book',  label:'mes lectures', url:'' },
      { id:id(), type:'text',  frame:'thin', size:'sm', font:'serif', text:'à lire' },
    ];
    case 'studio':     return [
      { id:id(), type:'link',  size:'md', icon:'code',  label:'mes projets', url:'' },
      { id:id(), type:'link',  size:'sm', icon:'web',   label:'site', url:'' },
    ];
    case 'gym':        return [
      { id:id(), type:'text',  frame:'tape', size:'md', font:'display', text:'no excuses' },
      { id:id(), type:'link',  size:'sm', icon:'heart', label:'progrès', url:'' },
    ];
    case 'studio_art': return [
      { id:id(), type:'photo', frame:'wood', size:'md', seed:5, caption:"dernière œuvre" },
      { id:id(), type:'link',  size:'sm', icon:'art',   label:'galerie', url:'' },
    ];
    case 'travel':     return [
      { id:id(), type:'photo', frame:'polaroid', size:'md', seed:3, caption:"où je suis" },
      { id:id(), type:'link',  size:'sm', icon:'camera', label:'photos', url:'' },
    ];
    case 'empty':      return [
      { id:id(), type:'text',  frame:'tape', size:'md', font:'hand', text:'bienvenue' },
    ];
    default:           return [];
  }
}

Object.assign(window, {
  ROOM_LIBRARY, ROOM_ORDER, WALL_PALETTE, WALL_PATTERNS, FLOOR_STYLES,
  FRAME_STYLES, FONT_STYLES,
  LINK_ICONS_LIST, LINK_ICONS_META,
  RoomInterior, EmptyCell, Furniture, RoomIcon, FakePhoto,
  Frame, Item, LinkPin, LinkIcon, PhotoBody, TextBody, ItemsArea, FreeItemsLayer,
  defaultItemLayout, withItemLayout, ITEM_BASE_FRAC,
  FloorLayer, wallPatternCss, wallPatternSize,
  defaultItemsFor, isDarkColor,
  ht, useHouseLang, roomLabel,
});
