PageSourceSearch

https://komekaigi.dev/2025/js/staff.js

js komekaigi.dev collected 2026-09-26 04:37:34 UTC 4,241 bytes, 129 lines download raw bytes

1/**
2 * KomeKaigi スタッフ表示機能
3 */
4(function(window, document) {
5    'use strict';
6
7    // 設定
8    const CONFIG = {
9        API_URL: 'https://fortee.jp/komekaigi-2025/api/staff?type=structured',
10        SECTION_ID: 'staff',
11        CONTENT_ID: 'staff-content'
12    };
13
14    /**
15     * スタッフセクションを非表示にする
16     */
17    function hideStaffSection() {
18        const staffSection = document.getElementById(CONFIG.SECTION_ID);
19        if (staffSection) {
20            staffSection.style.display = 'none';
21        }
22    }
23
24    /**
25     * スタッフデータを取得して表示
26     */
27    async function loadStaffMembers() {
28        const contentElement = document.getElementById(CONFIG.CONTENT_ID);
29        if (!contentElement) return;
30        
31        try {
32            const response = await fetch(CONFIG.API_URL);
33            if (!response.ok) {
34                throw new Error(`HTTP error! status: ${response.status}`);
35            }
36            const data = await response.json();
37            
38            renderStaffList(data.staff_types, contentElement);
39            
40        } catch (error) {
41            console.error('スタッフデータの取得エラー:', error);
42            contentElement.innerHTML = '';
43            hideStaffSection();
44        }
45    }
46
47    /**
48     * スタッフリストをレンダリング
49     * @param {Array} staffTypes - スタッフタイプの配列
50     * @param {HTMLElement} container - 表示先のコンテナ要素
51     */
52    function renderStaffList(staffTypes, container) {
53        if (!staffTypes || staffTypes.length === 0) {
54            container.innerHTML = '';
55            hideStaffSection();
56            return;
57        }
58        
59        const sectionTitle = `
60            <div class="text-center mb-8">
61                <h3 class="text-3xl section-title">スタッフ</h3>
62            </div>
63        `;
64        
65        const staffHTML = staffTypes.map(type => {
66            // アバター画像があるメンバーのみフィルタリング
67            const membersWithAvatar = type.staff.filter(member => member.avatar_url);
68            
69            // アバター画像があるメンバーがいない場合はセクションを表示しない
70            if (membersWithAvatar.length === 0) {
71                return '';
72            }
73            
74            return `
75                <div class="mb-12">
76                    <h4 class="text-2xl font-semibold text-center mb-8 text-primary">
77                        ${escapeHtml(type.name)}
78                    </h4>
79                    <div class="staff-grid">
80                        ${membersWithAvatar.map(member => createStaffCard(member)).join('')}
81                    </div>
82                </div>
83            `;
84        }).join('');
85        
86        container.innerHTML = sectionTitle + staffHTML;
87    }
88
89    /**
90     * スタッフカードを作成
91     * @param {Object} member - スタッフメンバーのデータ
92     * @returns {string} HTMLテキスト
93     */
94    function createStaffCard(member) {
95        // アバター画像がない場合は空文字を返す(すでにフィルタリング済みだが念のため)
96        if (!member.avatar_url) {
97            return '';
98        }
99        
100        const hasLink = member.url && member.url.trim() !== '';
101        
102        const cardContent = `
103            <div class="staff-member">
104                <div class="staff-avatar-wrapper">
105                    <img src="${escapeHtml(member.avatar_url)}" 
106                         alt="${escapeHtml(member.name)}" 
107                         class="staff-avatar"
108                         loading="lazy">
109                </div>
110                <div class="staff-name">
111                    ${escapeHtml(member.name)}
112                </div>
113            </div>
114        `;
115        
116        return hasLink 
117            ? `<a href="${escapeHtml(member.url)}" 
118                  target="_blank" 
119                  rel="noopener noreferrer"
120                  class="staff-link">
121                  ${cardContent}
122               </a>`
123            : cardContent;
124    }
125
126    // 初期化
127    document.addEventListener('DOMContentLoaded', loadStaffMembers);
128
129})(window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.