PageSourceSearch

https://komekaigi.dev/js/sponsors.js

js komekaigi.dev collected 2026-09-26 04:37:30 UTC 4,353 bytes, 112 lines download raw bytes

1/**
2 * KomeKaigi 2026 スポンサー表示
3 * data/sponsors.json を読み込み #sponsors セクションに描画する。
4 * plans はプラン(ゴールド・個人など)ごとの配列で、JSON の並び順に表示する。
5 * layout は "logo"(企業ロゴ) または "avatar"(個人アイコン+名前)。
6 * logo で image を省略すると、ロゴと同じ枠に社名だけを組んだテキストカードで表示する
7 * (ロゴ未提供のスポンサーを先に掲載するため)。
8 * スポンサーが1件も無い・取得に失敗した場合はセクションとメニュー項目を非表示のままにする。
9 */
10(function () {
11    'use strict';
12
13    const DATA_URL = 'data/sponsors.json';
14
15    const section = document.getElementById('sponsors');
16    const list = section && section.querySelector('.sponsor-plan-list');
17    const menuItem = document.querySelector('[data-sponsors-menu]');
18
19    if (!section || !list) {
20        return;
21    }
22
23    const el = (tag, className, text) => {
24        const node = document.createElement(tag);
25        if (className) node.className = className;
26        if (text != null) node.textContent = text;
27        return node;
28    };
29
30    const createSponsor = (sponsor, layout, showName) => {
31        // url が空ならリンクにしない
32        const hasLink = typeof sponsor.url === 'string' && sponsor.url.trim() !== '';
33        const item = hasLink ? el('a', 'sponsor') : el('div', 'sponsor');
34        if (hasLink) {
35            item.href = sponsor.url;
36            item.target = '_blank';
37            item.rel = 'noopener';
38        }
39
40        const hasImage = typeof sponsor.image === 'string' && sponsor.image.trim() !== '';
41        const media = el('div', 'sponsor-media');
42
43        if (hasImage) {
44            const img = el('img');
45            img.src = sponsor.image;
46            img.alt = sponsor.name || '';
47            img.loading = 'lazy';
48            media.appendChild(img);
49        } else {
50            // ロゴ未提供: 枠の中に社名を組んで、ロゴのカードと同じ並びを保つ
51            media.appendChild(el('span', 'sponsor-media-text', sponsor.name));
52        }
53
54        item.appendChild(media);
55
56        // 企業ロゴは画像内に社名が含まれるため、名前はアイコン表示のときだけ出す。
57        // プランに showName: true を指定すると、ロゴ表示でも名前を添える。
58        // テキストカードは枠内に社名が出ているので、重複させない。
59        if (layout === 'avatar' || (showName && hasImage)) {
60            item.appendChild(el('span', 'sponsor-name', sponsor.name));
61        }
62
63        return item;
64    };
65
66    const createPlan = (plan) => {
67        const sponsors = (plan.sponsors || []).filter((sponsor) => sponsor && sponsor.name);
68        if (sponsors.length === 0) {
69            return null;
70        }
71
72        const layout = plan.layout === 'logo' ? 'logo' : 'avatar';
73        const className = ['sponsor-plan', `sponsor-plan-${layout}`];
74        if (plan.id) className.push(`sponsor-plan-${plan.id}`);
75        const block = el('div', className.join(' '));
76
77        const heading = el('h3', 'sponsor-plan-heading');
78        if (plan.typeEn) heading.appendChild(el('span', 'sponsor-plan-heading-en', plan.typeEn));
79        if (plan.type) heading.appendChild(el('span', 'sponsor-plan-heading-ja', plan.type));
80        block.appendChild(heading);
81
82        const grid = el('div', 'sponsor-grid');
83        sponsors.forEach((sponsor) => grid.appendChild(createSponsor(sponsor, layout, plan.showName === true)));
84        block.appendChild(grid);
85
86        return block;
87    };
88
89    const render = (plans) => {
90        const blocks = plans.map(createPlan).filter(Boolean);
91
92        if (blocks.length === 0) {
93            return;
94        }
95
96        list.replaceChildren(...blocks);
97        section.hidden = false;
98        if (menuItem) menuItem.hidden = false;
99    };
100
101    fetch(DATA_URL)
102        .then((response) => {
103            if (!response.ok) {
104                throw new Error(`HTTP error! status: ${response.status}`);
105            }
106            return response.json();
107        })
108        .then((data) => render(data.plans || []))
109        .catch((error) => {
110            console.error('スポンサーデータの取得エラー:', error);
111        });
112})();

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.