// Site-wide shared components for Round-4 (full site mockup).
// Pattern 2 aesthetic, normalised to look like a company-portal (not a magazine).
//
// Important constraints from the brief:
// - No claims of 無料 / 即日返信 / 自社施工 / LINE対応 (unverified).
// - Sample 所在地/築年数/工期/お客様コメント are flagged as 仮データ.

const SL = window.SL;
const T = window.R3_TOK;
const { COMPANY, ASSETS, COPY, FLOW_STEPS, CERTIFICATIONS, SERVICES, KNOWLEDGE, CoverImg, Icon, BrandMark, BrandLockup, slHref, slServiceHref, slArticleHref, slCaseHref, slNavHref, dialablePhone, hasText, hasImageRef } = SL;
const rawCopyValue = SL.rawCopyValue || ((id) => {
  const value = COPY && COPY[id];
  return typeof value === 'string' ? value : undefined;
});
const copyText = SL.copyText || ((id, fallback) => {
  const value = COPY && COPY[id];
  return typeof value === 'string' && value.trim() ? value : fallback;
});

const siteHref = slHref;
const siteServiceHref = slServiceHref;
const siteArticleHref = slArticleHref;
const siteCaseHref = slCaseHref;
const siteNavHref = slNavHref;
const sitePhoneHref = `tel:${dialablePhone(COMPANY.phone)}`;
const sitePrivacyHref = `${siteHref('contact')}#privacy-notice`;
const companyHours = String(COMPANY.hours || '').trim();
const siteFooterHref = (label) => ({
  '雨漏り診断': siteServiceHref('leak'),
  '部分・応急補修': siteServiceHref('spot'),
  '定期メンテナンス': siteServiceHref('maint'),
  '全体改修': siteServiceHref('full'),
  '外壁・内装・水回り': siteServiceHref('reform'),
  '施工事例': siteHref('cases'),
  'お役立ち情報': siteHref('knowledge'),
  '相談の流れ': `${siteHref('top')}#flow`,
  '会社案内': siteHref('company'),
  'メールフォーム': siteHref('contact'),
  [COMPANY.phone]: sitePhoneHref,
}[label]) || (String(label).startsWith('対応エリア / ') ? siteHref('company') : undefined);

// =========== buttons ===========
function Btn({ children, icon, kind = 'primary', big, full, href, style }) {
  const base = {
    fontFamily: 'inherit', fontWeight: 700, letterSpacing: 0.4,
    fontSize: big ? 15.5 : 14,
    padding: big ? '14px 24px' : '11px 20px',
    cursor: 'pointer', border: 'none', borderRadius: 6,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10,
    width: full ? '100%' : 'auto',
    whiteSpace: 'nowrap',
    ...style,
  };
  let finalStyle = { ...base, background: '#fff', color: T.ink, border: `1.5px solid ${T.ink}` };
  if (kind === 'phone')   finalStyle = { ...base, background: T.orange, color: '#fff' };
  if (kind === 'email')   finalStyle = { ...base, background: '#fff',    color: T.orangeDeep, border: `1.5px solid ${T.orangeLine}` };
  if (kind === 'primary') finalStyle = { ...base, background: T.orange, color: '#fff' };
  if (kind === 'dark')    finalStyle = { ...base, background: T.greenInk, color: '#fff' };
  if (href) return <a href={href} style={{ ...finalStyle, textDecoration: 'none' }}>{icon}{children}</a>;
  return <button type="button" style={finalStyle}>{icon}{children}</button>;
}

// =========== shared bits ===========
function KanjiSeal({ char = 'ス', color = T.green, size = 44 }) {
  return (
    <span style={{
      width: size, height: size, borderRadius: '50%', border: `1.5px solid ${color}`,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      fontFamily: '"Noto Sans JP", sans-serif', color, fontSize: size * 0.45, fontWeight: 700,
    }}>{char}</span>
  );
}

function SectionHeading({ num, en, title, lead, align = 'split', anchorRight, style }) {
  // align: split (two columns) | left (single)
  const eyebrow = (
    <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: 11, color: T.green, letterSpacing: 4, marginBottom: 12, fontWeight: 700 }}>
      {num ? `${num} ／ ${en}` : en}
    </div>
  );
  const heading = (
    <h2 style={{ fontFamily: '"Noto Sans JP", sans-serif', fontSize: 36, fontWeight: 900, color: T.ink, margin: 0, lineHeight: 1.4, letterSpacing: '-0.01em', fontFeatureSettings: "'palt'", wordBreak: 'auto-phrase', overflowWrap: 'break-word' }}>
      {title}
    </h2>
  );
  if (align === 'left') {
    return (
      <div style={{ marginBottom: 32, paddingBottom: 22, borderBottom: `1px solid ${T.line}`, ...style }}>
        {eyebrow}{heading}
        {lead && <div style={{ marginTop: 16, fontSize: 14, color: T.ink70, lineHeight: 1.95, maxWidth: 640, fontFamily: '"Noto Sans JP", sans-serif' }}><BudouXText>{lead}</BudouXText></div>}
      </div>
    );
  }
  return (
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'end', marginBottom: 32, paddingBottom: 22, borderBottom: `1px solid ${T.line}`, ...style }}>
      <div>{eyebrow}{heading}</div>
      <div style={{ maxWidth: 360, textAlign: 'right' }}>
        {lead && <div style={{ fontSize: 14, color: T.ink70, lineHeight: 1.95, fontFamily: '"Noto Sans JP", sans-serif' }}><BudouXText>{lead}</BudouXText></div>}
        {anchorRight}
      </div>
    </div>
  );
}

function Breadcrumb({ items = [] }) {
  const hrefFor = (label) => ({
    'ホーム': siteHref('top'),
    'サービス': siteHref('services'),
    'サービス一覧': siteHref('services'),
    '施工事例': siteHref('cases'),
    'お役立ち情報': siteHref('knowledge'),
    '住まいのお役立ちコラム': siteHref('knowledge'),
    '会社案内': siteHref('company'),
    'お問い合わせ': siteHref('contact'),
  }[label]);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12.5, color: T.ink70, fontFamily: '"Noto Sans JP", sans-serif' }}>
      {items.map((item, i) => {
        const label = typeof item === 'string' ? item : item.label;
        const href = i === items.length - 1 ? null : (typeof item === 'string' ? hrefFor(item) : item.href);
        const style = { color: i === items.length - 1 ? T.ink : T.ink70, textDecoration: 'none' };
        return (
        <React.Fragment key={i}>
          {href ? <a href={href} style={style}>{label}</a> : <span style={style}>{label}</span>}
          {i < items.length - 1 && <span style={{ color: T.ink50 }}>／</span>}
        </React.Fragment>
      )})}
    </div>
  );
}

function PlaceholderTag() {
  return null;
}

const FLOW_NO_BREAK = Object.freeze(['お問い合わせ', '修繕方法', '安全第一']);

// 文字列専用API。CMS本文などのプレーンテキストだけを渡し、React要素は渡さない。
function BudouXText({ children, noBreakPhrases = [] }) {
  if (children == null) return null;
  const text = String(children);
  const phrases = (Array.isArray(noBreakPhrases) ? noBreakPhrases : [])
    .filter((phrase) => (
      typeof phrase === 'string'
      && phrase.length > 0
      && phrase.length <= 20
      && !/\s/.test(phrase)
    ))
    .filter((phrase, index, all) => all.indexOf(phrase) === index)
    .sort((a, b) => b.length - a.length);
  if (!phrases.length) {
    return <budoux-ja style={{ whiteSpace: 'pre-line' }}>{text}</budoux-ja>;
  }
  const pattern = new RegExp(`(${phrases.map((phrase) => phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})`, 'g');
  return (
    <budoux-ja style={{ whiteSpace: 'pre-line' }}>
      {text.split(pattern).map((part, index) => (
        phrases.includes(part)
          ? <span key={index} style={{ whiteSpace: 'nowrap' }}>{part}</span>
          : <React.Fragment key={index}>{part}</React.Fragment>
      ))}
    </budoux-ja>
  );
}

const TOP_HERO_DEFAULTS = {
  line1: '屋根・外壁・リフォームなど',
  line2a: '建物のご相談は',
  line2b: '株式会社スマートライフに。',
  line2c: '',
};

// PC / モバイルで同じCMS値を読む。CMSにキーが存在するときは空文字も
// その値を優先し、未設定時だけrepo既定値へ戻す。
function topHeroLines() {
  const resolve = (id, fallback) => {
    const raw = rawCopyValue(id);
    return String(raw === undefined ? fallback : raw).trim();
  };
  const normalizeCompany = (value) => String(value || '')
    .replace(/株式会社[\s　]*スマートライフ/g, '株式会社スマートライフ')
    .trim();
  return {
    line1: resolve('top.hero.line1', TOP_HERO_DEFAULTS.line1),
    line2a: resolve('top.hero.line2a', TOP_HERO_DEFAULTS.line2a),
    line2b: normalizeCompany(resolve('top.hero.line2b', TOP_HERO_DEFAULTS.line2b)),
    line2c: resolve('top.hero.line2c', TOP_HERO_DEFAULTS.line2c),
  };
}

// ヒーロー専用ロゴ。透過PNGを素のまま描画し、未設定または読込失敗時だけline2bを表示する。
function HeroBrandLogo({ height = 64, maxWidth = 300, fallbackText = '', fallbackStyle = {}, style = {} }) {
  const src = typeof ASSETS.heroLogo === 'string' && ASSETS.heroLogo.trim() ? ASSETS.heroLogo : null;
  const [failed, setFailed] = React.useState(false);
  React.useEffect(() => { setFailed(false); }, [src]);
  if (!src || failed) {
    if (!fallbackText) return null;
    return (
      <span data-top-hero-line="line2b" data-top-hero-logo-fallback style={{ display: 'block', color: T.orange, fontWeight: 800, lineHeight: 1.45, ...style, ...fallbackStyle }}>
        {fallbackText}
      </span>
    );
  }
  return (
    <img
      data-top-hero-logo
      data-top-hero-logo-key="heroLogo"
      src={src}
      alt={COMPANY.name}
      decoding="async"
      onError={() => setFailed(true)}
      style={{
        height, width: 'auto', maxWidth, display: 'block', objectFit: 'contain',
        ...style,
      }}
    />
  );
}

function serviceIconKey(service) {
  if (/ドローン/.test(String(service && service.name || ''))) return 'drone';
  return ({
    roof: 'roof', wall: 'brick', leak: 'drop', interior: 'bath',
    exterior: 'fence', whole: 'housePlus',
  })[String(service && service.id || '')] || 'home';
}

function HeroAccentText({ children, color = T.orange }) {
  const parts = String(children || '').split(/(屋根|外壁|雨漏り|リフォーム)/g);
  return (
    <React.Fragment>
      {parts.map((part, index) => (
        /^(屋根|外壁|雨漏り|リフォーム)$/.test(part)
          ? <span key={`${part}-${index}`} style={{ color }}>{part}</span>
          : <React.Fragment key={`text-${index}`}>{part}</React.Fragment>
      ))}
    </React.Fragment>
  );
}

// 工事名ではなく相談目的から選ぶ共通入口。サービスが改名・削除されても
// 存在確認後に一覧またはお問い合わせへ安全に戻す。
function topPurposeItems() {
  const services = Array.isArray(SERVICES) ? SERVICES.filter(Boolean) : [];
  const identifiers = (service) => [service.id, ...(Array.isArray(service.aliases) ? service.aliases : [])]
    .filter(Boolean)
    .map((value) => String(value).toLowerCase());
  const findByIdentifiers = (ids) => {
    const wanted = (Array.isArray(ids) ? ids : [ids]).map((value) => String(value).toLowerCase());
    return services.find((service) => identifiers(service).some((id) => wanted.includes(id)));
  };
  const leak = findByIdentifiers('leak');
  const inspection = services.find((service) => {
    if (!service || (leak && String(service.id) === String(leak.id))) return false;
    return identifiers(service).some((id) => /inspection|diagnosis|check/.test(id))
      || /点検|診断/.test(String(service.name || ''));
  });
  const reform = findByIdentifiers(['reform', 'interior']);
  // 解体の解決優先順: ①名前に「解体」を含む専用サービス ②id指定 ③なんでも相談(whole)へのフォールバック
  const demolition = services.find((service) => /解体/.test(String(service && service.name || '')))
    || services.find((service) => identifiers(service).some((id) => /demolition|dismantling/.test(id)))
    || services.find((service) => identifiers(service).some((id) => id === 'whole'));
  const drone = services.find((service) => /ドローン/.test(String(service && service.name || '')));

  return [
    { label: 'お見積もりの相談', icon: 'chat', href: siteHref('contact') },
    { label: '現地調査・点検', icon: 'check', href: inspection ? siteServiceHref(inspection.id) : siteHref('services') },
    { label: '雨漏りの相談', icon: 'drop', href: leak ? siteServiceHref(leak.id) : siteHref('contact') },
    { label: '屋根・外壁の工事', icon: 'home', href: siteHref('services') },
    { label: 'リフォーム・内装', icon: 'wrench', href: reform ? siteServiceHref(reform.id) : siteHref('services') },
    { label: '解体事業', icon: 'houseCrack', href: demolition ? siteServiceHref(demolition.id) : siteHref('contact') },
    { label: 'ドローン検査の相談', icon: 'camera', href: drone ? siteServiceHref(drone.id) : siteHref('contact') },
    { label: 'その他なんでも相談', icon: 'housePlus', href: siteHref('contact') },
  ];
}

function licenseContent() {
  const resolve = (id, fallback) => {
    const raw = rawCopyValue(id);
    return String(raw === undefined ? fallback : raw).trim();
  };
  return {
    heading: resolve('trust.license.heading', '建設業許可'),
    name: resolve('trust.license.name', '屋根工事業'),
    number: resolve('trust.license.number', '広島県知事許可（般－5）第40934号'),
    certifications: (Array.isArray(CERTIFICATIONS) ? CERTIFICATIONS : [])
      .map((certification) => ({
        label: String(certification && certification.label || '').trim(),
        name: String(certification && certification.name || '').trim(),
        number: String(certification && certification.number || '').trim(),
        icon: certification && ['badge', 'drop', 'drone', 'camera'].includes(certification.icon) ? certification.icon : 'badge',
      }))
      .filter((certification) => certification.name && certification.number),
  };
}

function LicensePlate({ label = '建設業許可', name, number, icon = 'badge' }) {
  if (!name || !number) return null;
  return (
    <div data-license-plate style={{
      display: 'flex', alignItems: 'center', gap: 'clamp(12px, 2vw, 22px)',
      width: '100%', maxWidth: 720, margin: '0 auto',
      background: '#fff', border: `1.5px solid ${T.heritageGold}`, borderRadius: 12,
      padding: 'clamp(13px, 2vw, 24px)', fontFeatureSettings: "'palt'",
    }}>
      <span aria-hidden="true" data-license-icon={icon} style={{
        width: 'clamp(64px, 6vw, 88px)', height: 'clamp(64px, 6vw, 88px)',
        borderRadius: '50%', flexShrink: 0, border: `2px solid ${T.heritageGold}`,
        background: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      }}>
        {icon === 'camera' ? (
          <svg viewBox="0 0 24 24" width="42" height="42" fill="none" stroke={T.green} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <rect x="3" y="7" width="18" height="13" rx="2.2" />
            <circle cx="12" cy="13.2" r="4" />
            <path d="M8.5 7 L10 4.5 H14 L15.5 7" />
          </svg>
        ) : icon === 'drone' ? (
          <svg viewBox="0 0 24 24" width="42" height="42" fill="none" stroke={T.green} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M3 5.5 h6.4 M14.6 5.5 h6.4" />
            <path d="M6.2 5.5 v2.8 M17.8 5.5 v2.8" />
            <rect x="8.2" y="8.3" width="7.6" height="5.9" rx="1.9" />
            <path d="M9.8 14.2 L7.8 19.5 M14.2 14.2 L16.2 19.5" />
          </svg>
        ) : icon === 'drop' ? (
          <svg viewBox="0 0 24 24" width="42" height="42" fill="none" stroke={T.green} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M12 3.5 C12 3.5 6 10.2 6 14.2 a6 6 0 0 0 12 0 C18 10.2 12 3.5 12 3.5 Z" />
            <path d="M9.4 14.6 a2.6 2.6 0 0 0 2.2 2.7" />
          </svg>
        ) : (
          <svg viewBox="0 0 24 24" width="42" height="42" fill="none" stroke={T.green} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <circle cx="12" cy="8.5" r="5.5" />
            <path d="M8.2 12.6 L7 21 L12 18.4 L17 21 L15.8 12.6" />
          </svg>
        )}
      </span>
      <span style={{ display: 'grid', gap: 3, minWidth: 0 }}>
        <span style={{ fontSize: 'clamp(11px, 1vw, 13px)', fontWeight: 800, color: T.green, letterSpacing: 0.2 }}>{label}</span>
        <span style={{ fontSize: 'clamp(16px, 1.5vw, 21px)', fontWeight: 800, color: T.ink, lineHeight: 1.3, wordBreak: 'auto-phrase' }}>{name}</span>
        <span style={{ fontSize: 'clamp(11.5px, 1vw, 14px)', fontWeight: 600, color: T.ink70, lineHeight: 1.5, letterSpacing: -0.2, wordBreak: 'keep-all', overflowWrap: 'anywhere' }}>{number}</span>
      </span>
    </div>
  );
}

// =========== header / footer ===========
function Header({ active = 'home' }) {
  const nav = [
    { id: 'services',  label: 'サービス' },
    { id: 'cases',     label: '施工事例' },
    { id: 'knowledge', label: 'お役立ち情報' },
    { id: 'company',   label: '会社案内' },
    { id: 'contact',   label: 'お問い合わせ' },
  ];
  return (
    <header>
      <div style={{ background: '#fff', padding: '11px 56px', display: 'flex', justifyContent: 'space-between', fontSize: 11.5, color: T.ink70, letterSpacing: 1.5, borderBottom: `1px solid ${T.line}` }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon.pin width="13" height="13" color={T.green} />
          <span>{COMPANY.area}の屋根・外壁・住まいの相談窓口</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          {companyHours && <span>受付 {companyHours}</span>}
          {companyHours && <span style={{ color: T.ink20 }}>｜</span>}
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <Icon.phone width="13" height="13" color={T.orange} /> {COMPANY.phone}
          </span>
        </div>
      </div>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '16px 56px', background: '#fff', borderBottom: `1px solid ${T.line}`,
      }}>
        <a href={siteHref('top')} aria-label={COMPANY.name} style={{ display: 'inline-flex', alignItems: 'center', gap: 12, textDecoration: 'none' }}>
          <BrandLockup height={84} maxWidth={380} fallback={
            <React.Fragment>
              <BrandMark size={60} tone="orange" />
              <span style={{ display: 'inline-flex', flexDirection: 'column', lineHeight: 1 }}>
                <span style={{ color: T.orange, fontSize: 34, fontWeight: 800, letterSpacing: 0.2, fontFamily: '"Noto Sans JP", sans-serif' }}>Smartlife</span>
                <span style={{ color: T.ink70, fontSize: 13.5, fontWeight: 800, letterSpacing: 0.2, marginTop: 3, fontFamily: '"Noto Sans JP", sans-serif' }}>{COMPANY.name}</span>
              </span>
            </React.Fragment>
          } />
        </a>
        <nav style={{ display: 'flex', gap: 22, alignItems: 'center' }}>
          {nav.map((n) => (
            <a key={n.id} href={siteNavHref(n.id)} style={{
              color: active === n.id ? T.orangeDeep : T.ink,
              fontFamily: '"Noto Sans JP", sans-serif', fontSize: 14.5, fontWeight: 600,
              borderBottom: active === n.id ? `2px solid ${T.orange}` : '2px solid transparent',
              paddingBottom: 4,
              textDecoration: 'none',
            }}>{n.label}</a>
          ))}
          <a href={sitePhoneHref} style={{ marginLeft: 8, display: 'inline-flex', alignItems: 'center', gap: 8, padding: '10px 16px', background: T.orange, color: '#fff', fontFamily: 'inherit', fontSize: 13.5, fontWeight: 700, borderRadius: 6, textDecoration: 'none' }}>
            <Icon.phone width="14" height="14" /> 電話で相談
          </a>
        </nav>
      </div>
    </header>
  );
}

function Footer() {
  return (
    <footer style={{ background: '#fff', padding: '60px 56px 28px', borderTop: `1px solid ${T.line}` }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr 1fr 1fr', gap: 44 }}>
        <div>
          <a href={siteHref('top')} aria-label={COMPANY.name} style={{ display: 'inline-flex', marginBottom: 18 }}>
            <BrandLockup height={72} maxWidth={320} fallback={<BrandMark size={56} tone="orange" />} />
          </a>
          <div style={{ fontSize: 13, color: T.ink70, lineHeight: 1.95 }}>
            {copyText('footer.description.1', '広島市・周辺エリアの屋根・外壁・住まいの相談窓口です。')}<br />
            {copyText('footer.description.2', '気になることがあれば、お電話またはメールでお気軽にお問い合わせください。')}
          </div>
        </div>
        {[
          { h: 'サービス', items: ['雨漏り診断', '部分・応急補修', '定期メンテナンス', '全体改修', '外壁・内装・水回り'] },
          { h: 'コンテンツ', items: ['施工事例', 'お役立ち情報', '相談の流れ', '会社案内'] },
          { h: 'お問い合わせ', items: [COMPANY.phone, companyHours ? '受付 ' + companyHours : null, 'メールフォーム', `対応エリア / ${COMPANY.area}`].filter(Boolean) },
        ].map((g) => (
          <div key={g.h}>
            <div style={{ fontFamily: '"Noto Sans JP", sans-serif', fontSize: 13, color: T.ink, fontWeight: 700, marginBottom: 14, letterSpacing: 1.5 }}>{g.h}</div>
            {g.items.map((it) => {
              const href = siteFooterHref(it);
              const itemStyle = { fontSize: 13, color: T.ink70, padding: '5px 0', textDecoration: 'none', display: 'block' };
              return href
                ? <a key={it} href={href} style={itemStyle}>{it}</a>
                : <div key={it} style={itemStyle}>{it}</div>;
            })}
          </div>
        ))}
      </div>
      <div style={{ marginTop: 36, paddingTop: 18, borderTop: `1px solid ${T.line}`, display: 'flex', justifyContent: 'space-between', fontSize: 12, color: T.ink50 }}>
        <div>© Smartlife Co., Ltd.</div>
        <a href={sitePrivacyHref} style={{ color: T.ink70, textDecoration: 'underline', textUnderlineOffset: 3 }}>個人情報の取り扱いについて</a>
      </div>
    </footer>
  );
}

// =========== a common CTA block reused on every page ===========
function CTABand({ tone = 'green' } = {}) {
  return (
    <section style={{ background: tone === 'green' ? `linear-gradient(158deg, ${T.orange}, ${T.orangeDeep})` : T.greenInk, color: '#fff', padding: '72px 56px' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.05fr', gap: 56, alignItems: 'center' }}>
        <div>
          <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: 11, color: 'rgba(255,255,255,0.85)', letterSpacing: 4, marginBottom: 14, fontWeight: 700 }}>CONTACT — お問い合わせ</div>
          <h2 style={{ fontFamily: '"Noto Sans JP", sans-serif', fontSize: 40, fontWeight: 900, margin: '0 0 18px', lineHeight: 1.4, letterSpacing: '-0.01em', fontFeatureSettings: "'palt'" }}>
            {copyText('cta.title', 'お問い合わせ')}
          </h2>
          <p data-text-contract="common-cta-leads" style={{ fontSize: 15, lineHeight: 2, opacity: 0.85, margin: 0, maxWidth: 460 }}>
            <BudouXText>{copyText('cta.lead1', '屋根や外壁、住まいのことで気になることはありませんか？')}</BudouXText><br />
            <BudouXText>{copyText('cta.lead2', '小さなことでも、お電話またはメールフォームよりお気軽にご相談ください。')}</BudouXText>
          </p>
        </div>
        <div style={{ background: '#fff', color: T.ink, padding: 28, borderRadius: 6 }}>
          <div style={{ fontSize: 12.5, color: T.ink70, fontWeight: 700, letterSpacing: 1.5, marginBottom: 8 }}>{companyHours ? `お電話でのご相談 ／ 受付 ${companyHours}` : 'お電話でのご相談'}</div>
          <a href={sitePhoneHref} style={{ fontFamily: '"M PLUS 1p", "Helvetica Neue", Arial, sans-serif', fontSize: 34, fontWeight: 800, color: T.ink, letterSpacing: 0.5, marginBottom: 14, display: 'flex', alignItems: 'center', gap: 12, textDecoration: 'none' }}>
            <Icon.phone width="26" height="26" color={T.orange} /> {COMPANY.phone}
          </a>
          <div style={{ height: 1, background: T.line, margin: '12px 0' }} />
          <div style={{ display: 'grid', gridTemplateColumns: 'minmax(220px, 1fr) 1fr', gap: 10 }}>
            <Btn kind="email" big href={siteHref('contact')} icon={<Icon.chat width="16" height="16" />} style={{ paddingLeft: 18, paddingRight: 18 }}>メールでお問い合わせ</Btn>
            <Btn kind="ghost" big href={siteHref('cases')}>施工事例を見る</Btn>
          </div>
        </div>
      </div>
    </section>
  );
}

// =========== Page chrome wrapper ===========
function Page({ active, children }) {
  return (
    <div style={{
      fontFamily: '"Noto Sans JP", "Hiragino Sans", sans-serif',
      color: T.ink, background: '#fff', width: '100%',
    }}>
      <Header active={active} />
      {children}
      <CTABand />
      <Footer />
    </div>
  );
}

// =========== Page hero (sub-pages — small unified header) ===========
function PageHero({ num, en, title, lead }) {
  return (
    <section style={{ background: T.paper, padding: '56px 56px 60px', borderBottom: `1px solid ${T.line}` }}>
      <Breadcrumb items={['ホーム', title]} />
      <div style={{ marginTop: 22, display: 'grid', gridTemplateColumns: '1.1fr 1fr', gap: 56, alignItems: 'end' }}>
        <div>
          <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: 11, color: T.green, letterSpacing: 4, marginBottom: 14, fontWeight: 700 }}>
            {num} ／ {en}
          </div>
          <h1 style={{ fontFamily: '"Noto Sans JP", sans-serif', fontSize: 56, fontWeight: 900, color: T.ink, margin: 0, lineHeight: 1.3, letterSpacing: '-0.02em', fontFeatureSettings: "'palt'", wordBreak: 'auto-phrase', overflowWrap: 'break-word' }}>
            {title}
          </h1>
        </div>
        {lead && (
          <div style={{ fontFamily: '"Noto Sans JP", sans-serif', fontSize: 16, color: T.ink70, lineHeight: 2.1 }}>
            <BudouXText>{lead}</BudouXText>
          </div>
        )}
      </div>
    </section>
  );
}

// =========== data — sample cases / articles ===========
// Case imagery uses supplied photo folders where the same-site sequence is visually traceable.
const CASES = [
  {
    id: 'c01',
    area: '広島市周辺',
    year: '築年数確認中',
    term: '工期確認中',
    work: '屋根葺き替え',
    cat: '屋根',
    photo: ASSETS.caseYayamaAfter,
    before: ASSETS.caseYayamaBefore,
    after: ASSETS.caseYayamaAfter,
    summary: '既存屋根の状態を確認したうえで、下地と雨仕舞いを整えながら葺き替えを行った事例です。',
    flow: [
      { ph: ASSETS.caseYayamaFlowCheck, t: '現地確認', d: '屋根に上がって既存材の傷みや雨水が回りやすい箇所を確認しました。' },
      { ph: ASSETS.caseYayamaFlowWork,  t: '施工',     d: '下地と防水層を確認しながら、必要な範囲を順番に施工しました。' },
      { ph: ASSETS.caseYayamaAfter,     t: '完工確認', d: '葺き替え後の仕上がりと雨仕舞いを確認し、写真で共有できるよう整理しました。' },
    ],
  },
  {
    id: 'c02',
    area: '広島市周辺',
    year: '築年数確認中',
    term: '工期確認中',
    work: '棟板金・貫板交換',
    cat: '屋根',
    photo: ASSETS.caseHatashiAfter,
    before: ASSETS.caseHatashiBefore,
    after: ASSETS.caseHatashiAfter,
    summary: '棟部分の下地を確認し、傷みのある貫板を交換して板金を納め直した事例です。',
  },
  {
    id: 'c03',
    area: '広島市周辺',
    year: '築年数確認中',
    term: '工期確認中',
    work: '金属屋根塗装・補修',
    cat: '屋根',
    photo: ASSETS.caseRedAfter,
    before: ASSETS.caseRedBefore,
    after: ASSETS.caseRedAfter,
    summary: '塗膜の劣化が見られる金属屋根を確認し、補修後に塗装で仕上げた事例です。',
  },
  { id: 'c04', area: '広島市東区',     year: '築 35 年', term: '工期 10 日', work: 'カバー工法（金属屋根）',     cat: '屋根', photo: ASSETS.case4, before: ASSETS.heroMain,  after: ASSETS.case4, summary: '既存の屋根を残したまま、金属屋根で覆うカバー工法を採用しました。' },
  { id: 'c05', area: '広島市佐伯区',   year: '築 18 年', term: '工期 7 日',  work: '外壁塗装（部分補修込み）',   cat: '外壁', photo: ASSETS.serviceWallReal, before: ASSETS.heroWide1, after: ASSETS.serviceWallReal, summary: 'チョーキングとひびが見られたため、部分補修の後に塗装を行いました。' },
  { id: 'c06', area: '広島市中区',     year: '築 12 年', term: '工期 2 日',  work: '雨樋部分交換',               cat: '雨漏り', photo: ASSETS.serviceGutterReal, before: ASSETS.heroWide2, after: ASSETS.serviceGutterReal, summary: '集水器まわりからのあふれを確認し、該当箇所のみ部分交換しました。' },
  { id: 'c07', area: '広島市南区',     year: '築 25 年', term: '工期 12 日', work: '住まいの修理相談',           cat: '内装', photo: ASSETS.svcFull, before: ASSETS.knowHomeRepairWatercolor, after: ASSETS.svcFull, summary: '住まい全体の気になる箇所を確認し、必要な工事範囲を整理しました。' },
  { id: 'c08', area: '広島市安佐北区', year: '築 30 年', term: '工期 4 日',  work: '屋根定期点検 + 軽微補修',   cat: '屋根', photo: ASSETS.svcMaint, before: ASSETS.heroMain,   after: ASSETS.svcMaint, summary: '前回点検から3年経過。瓦の小さなずれを補修し、雨樋の清掃まで行いました。' },
];

const ARTICLES = [
  {
    id: 'k01',
    tag: '雨漏り',
    photo: ASSETS.knowLeakWatercolor,
    title: '雨漏りに気づいたら、まず確認すること',
    lead: '天井のシミ、雨漏りの音、サッシまわりのにじみ。慌てて屋根に上がる前に、まずは落ち着いてこちらをチェックしてください。',
    cat: '雨漏り',
    bodyTitle: '慌てて屋根に上がる前に、室内から確認しましょう。',
    paragraphs: [
      '天井や壁のシミ、壁紙の浮き、サッシまわりのにじみ、収納のカビ臭さなどは、雨漏りの入口になることがあります。雨の後だけ広がる、同じ場所で繰り返すといった変化があれば、早めに状況を確認しておくと安心です。',
      '雨水が入った場所と、室内でシミが出る場所は離れていることがあります。屋根、外壁、サッシ、バルコニーなど、いくつかの経路を見ていく必要があるため、真上だけを原因と決めつけないことが大切です。',
      'ご自身で屋根に上がるのは危険です。手の届く室内側から状況を確認し、いつ・どこで・どんな雨のあとに気づいたかをメモしておくと、相談時に状況を共有しやすくなります。',
    ],
    point: '雨漏りに気づいた時は、ご自身で屋根に上がらず、まずは室内側から見える範囲を確認してください。',
    consultations: [
      '天井にシミができたが、雨漏りかどうか自信がない',
      '雨の日だけ音がするが、原因がわからない',
      '訪問してきた業者にすぐ工事が必要と言われ、不安になった',
    ],
    ctaTitle: '雨漏りかも、と感じた方へ',
    ctaBody: '慌てて屋根に上がる必要はありません。まずはお電話またはメールで状況をお聞かせください。',
  },
  {
    id: 'k02',
    tag: '屋根',
    photo: ASSETS.knowRoofWatercolor,
    title: '屋根の種類とメンテナンス時期の目安',
    lead: '瓦・スレート・金属屋根。屋根材ごとの特徴と、適切な補修・改修のタイミングをまとめました。',
    cat: '屋根',
    bodyTitle: '屋根材ごとの特徴と、補修・改修のタイミングを知る。',
    paragraphs: [
      '屋根材には、瓦、スレート、金属屋根などがあります。素材ごとに見ておきたい場所が違い、同じ築年数でも、日当たりや風雨の当たり方、過去の補修履歴によって状態は変わります。',
      '瓦の場合は、瓦そのものだけでなく、棟や漆喰、板金まわりも確認します。スレートや金属屋根の場合は、表面の劣化、浮き、サビ、固定部分の状態などが判断材料になります。',
      '屋根全体を一度に直す必要があるとは限りません。応急処置、部分補修、葺き替え、カバー工法など、建物の状態とご希望に合わせて選択肢を整理していきます。',
    ],
    point: '築年数だけで判断せず、屋根材の種類、これまでの補修履歴、棟や板金など部位ごとの傷み方をあわせて見ることが大切です。',
    consultations: [
      '築20年を超えたが、まだ大丈夫か知りたい',
      '台風のあと、自宅の屋根も確認したい',
      '外壁塗装と一緒に、屋根も見てほしい',
    ],
    ctaTitle: '屋根の状況を、一度見てほしい方へ',
    ctaBody: '気になることがあれば、お電話またはメールでお気軽にお問い合わせください。状況をお聞きしたうえで、現地での確認に伺います。',
  },
  {
    id: 'k03',
    tag: '外壁',
    photo: ASSETS.knowWallWatercolor,
    title: '外壁のひび割れ・チョーキング・剥がれの見方',
    lead: '壁のひびや白い粉、塗装の剥がれ。住まいからのサインを、プロに相談する前にセルフチェックしてみましょう。',
    cat: '外壁',
    bodyTitle: '住まいからのサインを、早めに見つける。',
    paragraphs: [
      '外壁のひび割れ、手で触ると白い粉がつくチョーキング、塗装のふくれや剥がれは、外壁の状態を知る手がかりになります。すぐに大きな工事が必要とは限りませんが、放置すると雨水が入りやすくなる場合があります。',
      'サッシまわり、外壁の継ぎ目、ベランダまわり、雨がよく当たる面は、傷みが出やすい場所です。気づいた場所と範囲をメモしておくと、現地確認のときに状況を共有しやすくなります。',
      '外壁は、塗装だけで済む場合もあれば、下地やシーリングの補修が必要な場合もあります。見た目だけで決めず、原因と範囲を確認してから工事内容を選ぶことが大切です。',
    ],
    point: '外壁のひび割れやチョーキングは、範囲と場所によって対応が変わります。写真だけで断定せず、現地で状態を確認してから判断します。',
    consultations: [
      '外壁を触ると白い粉がつく',
      'サッシまわりや目地にヒビがある',
      '塗装の剥がれやふくれが気になっている',
    ],
    ctaTitle: '外壁の傷みが気になった方へ',
    ctaBody: 'ヒビや白い粉、剥がれが気になったら、お電話またはメールでご相談ください。範囲や場所をお聞きし、必要に応じて現地確認をご案内します。',
  },
  {
    id: 'k04',
    tag: '相談準備',
    photo: ASSETS.knowEstimateWatercolor,
    title: '相見積もりを取るときに、見ておきたいところ',
    lead: '金額だけでなく、工事範囲、材料、追加費用の条件も見ておくと比較しやすくなります。',
    cat: '相談準備',
    bodyTitle: '相見積もりは、内容をそろえて比べることが大切です。',
    paragraphs: [
      '屋根や外壁の工事は、会社によって見ている範囲や提案内容が違うことがあります。金額だけで判断せず、どこまで含まれているかを確認しておくと安心です。',
      '気になっている症状、築年数、過去の補修履歴、希望する工事範囲を最初に伝えておくと、各社の見積もりを比較しやすくなります。',
    ],
    point: '「一式」だけで内容が分かりにくい項目は、範囲や材料を確認しておくと後から迷いにくくなります。',
    consultations: [
      '見積もりの内容が分かりにくい',
      '他社と金額差が大きく、判断に迷っている',
      'どこまで工事に含まれるのか確認したい',
    ],
    ctaTitle: '見積もりについて、迷っている方へ',
    ctaBody: '他社の見積もりがある状態でも構いません。気になる箇所と一緒に、工事の範囲や工法をご一緒に整理します。',
  },
  {
    id: 'k05',
    tag: '点検',
    photo: ASSETS.knowInspectionWatercolor,
    title: '定期点検で見ているところ',
    lead: '屋根・外壁・雨樋・板金。点検時に確認するポイントを紹介します。',
    cat: '点検',
    bodyTitle: '点検では、普段見えにくい場所を中心に確認します。',
    paragraphs: [
      '屋根のずれ、棟や板金まわり、雨樋の詰まり、外壁のヒビなど、普段の生活では見えにくい場所を確認します。',
      '小さな不具合のうちに気づけると、補修の選択肢を整理しやすくなります。',
    ],
    point: '点検の結果、すぐに工事が必要とは限りません。状態と選択肢を確認することが目的です。',
    consultations: [
      'しばらく屋根を見てもらっていない',
      '雨樋のあふれや詰まりが気になる',
      '台風のあとに一度確認したい',
    ],
    ctaTitle: '点検を相談したい方へ',
    ctaBody: '気になる場所があれば、お電話またはメールでご相談ください。現地確認が必要かどうかも含めてご案内します。',
  },
  {
    id: 'k06',
    tag: '住まい',
    photo: ASSETS.knowHomeRepairWatercolor,
    title: '住まいの修理を相談するときに伝えること',
    lead: '症状、場所、気づいた時期。最初の相談で伝えておくと話が進めやすい内容です。',
    cat: '住まい',
    bodyTitle: '最初の相談では、分かる範囲で大丈夫です。',
    paragraphs: [
      '屋根、外壁、内装、水回りなど、気になっている場所と症状をお聞かせください。いつ頃から気づいたか、雨の日だけか、広がっているかなども参考になります。',
      '分かる範囲で構いませんので、気になる場所と症状を言葉でお聞かせください。',
    ],
    point: '分からないことが多くても問題ありません。まずは気になっていることを言葉にしていただければ、確認する順番を整理します。',
    consultations: [
      'これは屋根屋さんに相談してよいのか分からない',
      '屋根と外壁をまとめて見てほしい',
      '内装や水回りも一緒に相談したい',
    ],
    ctaTitle: '住まいのことで迷っている方へ',
    ctaBody: '屋根・外壁・内装・水回りまで、住まいのことをまとめてご相談いただけます。まずはお気軽にお問い合わせください。',
  },
];

// お役立ち一覧は詳細記事を正とする。旧「一覧カード」は、対応する記事の
// 写真・短い説明だけを上書きする任意データとして扱う。
// 現行CMSの一覧カードには記事ID欄がないためタイトル一致で紐付ける。
// 将来ID欄が追加された場合はID一致を優先し、同名タイトルが複数ある
// 場合は誤った上書きを避けるためタイトルでは紐付けない。
function knowledgeCardsFromArticles(articles, knowledgeCards) {
  const articleList = (Array.isArray(articles) ? articles : [])
    .filter((article) => article && article.id);
  const cards = (Array.isArray(knowledgeCards) ? knowledgeCards : [])
    .filter(Boolean);
  const titleCards = cards.filter((card) => !String(card.id || '').trim());
  const articleTitleCounts = articleList.reduce((counts, article) => {
    const title = String(article.title || '').trim();
    if (title) counts.set(title, (counts.get(title) || 0) + 1);
    return counts;
  }, new Map());
  const cardTitleCounts = titleCards.reduce((counts, card) => {
    const title = String(card.title || '').trim();
    if (title) counts.set(title, (counts.get(title) || 0) + 1);
    return counts;
  }, new Map());

  return articleList
    .map((article) => {
      const articleId = String(article.id);
      const title = String(article.title || '').trim();
      const category = String(article.cat || article.tag || '').trim() || 'その他';
      const tag = String(article.tag || article.cat || '').trim() || 'その他';
      const normalizedArticle = { ...article, cat: category, tag };
      const idCard = cards.find((candidate) => (
        candidate.id && String(candidate.id) === articleId
      ));
      const titleCard = !idCard
        && title
        && articleTitleCounts.get(title) === 1
        && cardTitleCounts.get(title) === 1
        ? titleCards.find((candidate) => String(candidate.title || '').trim() === title)
        : null;
      const card = idCard || titleCard;
      if (!card) return normalizedArticle;
      const photo = String(card.photo || '').trim();
      const blurb = String(card.blurb || '').trim();
      return {
        ...normalizedArticle,
        ...(photo ? { photo } : {}),
        ...(blurb ? { lead: blurb } : {}),
      };
    });
}

// CMSが自動採番する英数字IDは管理用なので公開しない。
// 既存の c01 / c39 と、数字だけのIDに限り従来の事例番号として表示する。
function caseNumberLabel(caseId) {
  const match = String(caseId == null ? '' : caseId).trim().match(/^c?(\d+)$/);
  return match ? `事例 No.${match[1]}` : '';
}

window.Site = {
  T, COMPANY, ASSETS, COPY, FLOW_STEPS, CERTIFICATIONS, SERVICES, KNOWLEDGE,
  CoverImg, Icon, Btn, KanjiSeal, BrandMark, BrandLockup, SectionHeading, Breadcrumb, PlaceholderTag, BudouXText, FLOW_NO_BREAK,
  HeroAccentText, LicensePlate,
  Header, Footer, CTABand, Page, PageHero, CASES, ARTICLES,
  siteHref, siteServiceHref, siteArticleHref, siteCaseHref, siteNavHref, sitePhoneHref, copyText, rawCopyValue,
  knowledgeCardsFromArticles, caseNumberLabel, topHeroLines, HeroBrandLogo, serviceIconKey, topPurposeItems, licenseContent, hasText, hasImageRef,
};
