Skip to content

Commit df228cb

Browse files
committed
プロダクトページ追加
1 parent c8884a9 commit df228cb

File tree

7 files changed

+347
-4
lines changed

7 files changed

+347
-4
lines changed

assets/css/products.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Products 用カードのサムネイル枠 */
2+
.product-card .card__thumb {
3+
flex-shrink: 0;
4+
width: 500px;
5+
height: 180px;
6+
overflow: hidden;
7+
border-radius: 16px;
8+
}
9+
10+
/* 画像を枠いっぱいにフィットさせる */
11+
.product-card .card__thumb img {
12+
width: 100%;
13+
height: 100%;
14+
object-fit: cover; /* 全体をカバーしつつトリミング */
15+
display: block;
16+
}

assets/data/products.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[
2+
{
3+
"id": "product_0001",
4+
"title": "Sample Action Game",
5+
"type": "Game",
6+
"platform": ["Windows", "Android"],
7+
"description": "個人で開発した3Dアクションゲーム。ゲームパッド対応。",
8+
"tags": ["game", "action", "unity"],
9+
"thumbnail": "assets/img/ogp.png",
10+
"storeLinks": [
11+
{
12+
"label": "Booth",
13+
"url": "https://example.com/booth/sample-game"
14+
},
15+
{
16+
"label": "itch.io",
17+
"url": "https://example.com/itch/sample-game"
18+
}
19+
],
20+
"downloadLinks": [
21+
{
22+
"label": "Windows版 .zip",
23+
"url": "https://example.com/downloads/sample-game-win.zip"
24+
}
25+
]
26+
},
27+
{
28+
"id": "product_0002",
29+
"title": "Unity Editor Helper Plugin",
30+
"type": "Plugin",
31+
"platform": ["Unity"],
32+
"description": "インスペクター拡張とコンテキストメニューをまとめた Unity 用エディタ拡張。",
33+
"tags": ["unity", "editor", "tool", "odin"],
34+
"thumbnail": "assets/img/ogp.png",
35+
"storeLinks": [
36+
{
37+
"label": "Asset Store",
38+
"url": "https://example.com/assetstore/editor-helper"
39+
}
40+
],
41+
"downloadLinks": []
42+
},
43+
{
44+
"id": "product_0003",
45+
"title": "Build Pipeline Tool",
46+
"type": "Tool",
47+
"platform": ["Windows"],
48+
"description": "ビルドとデプロイを自動化するためのスタンドアロンツール。",
49+
"tags": ["tool", "build", "pipeline"],
50+
"thumbnail": "assets/img/ogp.png",
51+
"storeLinks": [],
52+
"downloadLinks": [
53+
{
54+
"label": "実行ファイルダウンロード",
55+
"url": "https://example.com/downloads/build-tool.zip"
56+
}
57+
]
58+
}
59+
]

assets/js/products.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// assets/js/products.js
2+
document.addEventListener("DOMContentLoaded", () => {
3+
const listEl = document.getElementById("productList");
4+
const emptyEl = document.getElementById("productEmptyMessage");
5+
const searchInput = document.getElementById("productSearch");
6+
const typeSelect = document.getElementById("productTypeFilter");
7+
8+
if (!listEl) return;
9+
10+
let allProducts = [];
11+
12+
async function loadProducts() {
13+
try {
14+
const res = await fetch("assets/data/products.json");
15+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
16+
allProducts = await res.json();
17+
render();
18+
} catch (err) {
19+
console.error("Failed to load products:", err);
20+
if (emptyEl) {
21+
emptyEl.textContent = "プロダクト一覧の読み込みに失敗しました。";
22+
emptyEl.style.display = "block";
23+
}
24+
}
25+
}
26+
27+
function render() {
28+
const keyword = (searchInput?.value || "").trim().toLowerCase();
29+
const typeFilter = typeSelect?.value || "";
30+
31+
const filtered = allProducts.filter((p) => {
32+
// 種類フィルタ
33+
if (typeFilter && p.type !== typeFilter) return false;
34+
35+
// キーワードフィルタ
36+
if (keyword) {
37+
const haystack = [
38+
p.title,
39+
p.description,
40+
p.type || "",
41+
...(p.platform || []),
42+
...(p.tags || []),
43+
]
44+
.join(" ")
45+
.toLowerCase();
46+
if (!haystack.includes(keyword)) return false;
47+
}
48+
49+
return true;
50+
});
51+
52+
listEl.innerHTML = "";
53+
54+
if (filtered.length === 0) {
55+
if (emptyEl) emptyEl.style.display = "block";
56+
return;
57+
} else if (emptyEl) {
58+
emptyEl.style.display = "none";
59+
}
60+
61+
filtered.forEach((p) => {
62+
const card = document.createElement("article");
63+
card.className = "card card--clickable product-card";
64+
65+
// サムネ
66+
if (p.thumbnail) {
67+
const thumb = document.createElement("div");
68+
thumb.className = "card__thumb";
69+
const img = document.createElement("img");
70+
img.src = p.thumbnail;
71+
img.alt = p.title;
72+
thumb.appendChild(img);
73+
card.appendChild(thumb);
74+
}
75+
76+
const body = document.createElement("div");
77+
body.className = "card__body";
78+
79+
const titleRow = document.createElement("div");
80+
titleRow.className = "card__title-row";
81+
82+
const title = document.createElement("h3");
83+
title.className = "card__title";
84+
title.textContent = p.title;
85+
86+
if (p.type) {
87+
const pill = document.createElement("span");
88+
pill.className = "pill pill--accent";
89+
pill.textContent = p.type;
90+
titleRow.appendChild(pill);
91+
}
92+
93+
titleRow.appendChild(title);
94+
body.appendChild(titleRow);
95+
96+
if (p.platform && p.platform.length > 0) {
97+
const plat = document.createElement("p");
98+
plat.className = "card__meta";
99+
plat.textContent = `Platform: ${p.platform.join(", ")}`;
100+
body.appendChild(plat);
101+
}
102+
103+
if (p.description) {
104+
const desc = document.createElement("p");
105+
desc.className = "card__description";
106+
desc.textContent = p.description;
107+
body.appendChild(desc);
108+
}
109+
110+
// タグ
111+
if (p.tags && p.tags.length > 0) {
112+
const tagRow = document.createElement("div");
113+
tagRow.className = "card__tags";
114+
p.tags.forEach((t) => {
115+
const tag = document.createElement("span");
116+
tag.className = "tag";
117+
tag.textContent = t;
118+
tagRow.appendChild(tag);
119+
});
120+
body.appendChild(tagRow);
121+
}
122+
123+
// リンク群
124+
const linkRow = document.createElement("div");
125+
linkRow.className = "card__actions";
126+
127+
if (p.storeLinks && p.storeLinks.length > 0) {
128+
p.storeLinks.forEach((link) => {
129+
const a = document.createElement("a");
130+
a.href = link.url;
131+
a.target = "_blank";
132+
a.rel = "noopener noreferrer";
133+
a.className = "btn btn--sm btn--outline";
134+
a.textContent = link.label || "Store";
135+
linkRow.appendChild(a);
136+
});
137+
}
138+
139+
if (p.downloadLinks && p.downloadLinks.length > 0) {
140+
p.downloadLinks.forEach((link) => {
141+
const a = document.createElement("a");
142+
a.href = link.url;
143+
a.className = "btn btn--sm btn--primary";
144+
a.textContent = link.label || "Download";
145+
linkRow.appendChild(a);
146+
});
147+
}
148+
149+
if (linkRow.children.length > 0) {
150+
body.appendChild(linkRow);
151+
}
152+
153+
card.appendChild(body);
154+
listEl.appendChild(card);
155+
});
156+
}
157+
158+
if (searchInput) {
159+
searchInput.addEventListener("input", () => {
160+
render();
161+
});
162+
}
163+
if (typeSelect) {
164+
typeSelect.addEventListener("change", () => {
165+
render();
166+
});
167+
}
168+
169+
loadProducts();
170+
});

download.html

Whitespace-only changes.

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ <h1>PanKUN</h1>
7171
<a href="blog.html" class="btn btn-primary"
7272
>ブログを読む</a
7373
>
74-
<a href="download.html" class="btn btn-primary"
74+
<a href="products.html" class="btn btn-primary"
7575
>配布販売コンテンツを見る</a
7676
>
7777
</div>
@@ -240,10 +240,10 @@ <h3>Portfolio</h3>
240240
</p>
241241
</li>
242242
<li class="card">
243-
<h3>Download</h3>
243+
<h3>Products</h3>
244244
<p class="text-muted">配布販売コンテンツ</p>
245245
<p>
246-
<a href="download.html">コンテンツ一覧 →</a>
246+
<a href="products.html">コンテンツ一覧 →</a>
247247
</p>
248248
</li>
249249
<li class="card">

partials/header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<a href="index.html" data-nav="index.html">Home</a>
1010
<a href="blog.html" data-nav="blog.html">Blog</a>
1111
<a href="portfolio.html" data-nav="portfolio.html">Portfolio</a>
12-
<a href="download.html" data-nav="download.html">Download</a>
12+
<a href="products.html" data-nav="products.html">Products</a>
1313
<a href="contact.html" data-nav="contact.html">Contact</a>
1414
</nav>
1515
</div>

products.html

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!-- products.html -->
2+
<!doctype html>
3+
<html lang="ja">
4+
<head>
5+
<meta charset="UTF-8" />
6+
<title>PanKUN | Products</title>
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
9+
<!-- 共通CSS -->
10+
<link rel="stylesheet" href="assets/css/base.css" />
11+
<link rel="stylesheet" href="assets/css/layout.css" />
12+
<link rel="stylesheet" href="assets/css/products.css" />
13+
<!-- 必要なら専用CSSを追加してもOK -->
14+
<!-- <link rel="stylesheet" href="assets/css/products.css" /> -->
15+
16+
<meta
17+
name="description"
18+
content="制作・販売しているゲームやプラグイン、ツールの一覧ページ"
19+
/>
20+
21+
<meta property="og:title" content="PanKUN Products | pankun.dev" />
22+
<meta
23+
property="og:description"
24+
content="販売・配布しているゲーム、プラグイン、ツールの一覧"
25+
/>
26+
<meta property="og:type" content="website" />
27+
<meta
28+
property="og:url"
29+
content="https://breadmotion.github.io/WebSite/products.html"
30+
/>
31+
<meta
32+
property="og:image"
33+
content="https://breadmotion.github.io/WebSite/assets/img/ogp.png"
34+
/>
35+
36+
<meta name="twitter:card" content="summary_large_image" />
37+
<meta name="twitter:title" content="PanKUN Products | pankun.dev" />
38+
<meta
39+
name="twitter:description"
40+
content="販売・配布しているゲーム、プラグイン、ツールの一覧"
41+
/>
42+
<meta
43+
name="twitter:image"
44+
content="https://breadmotion.github.io/WebSite/assets/img/ogp.png"
45+
/>
46+
</head>
47+
<body>
48+
<div class="page-shell">
49+
<main class="main-container">
50+
<section class="page-header reveal-on-scroll">
51+
<h1>Products</h1>
52+
<p>
53+
自分が制作・販売しているゲームやプラグイン、ツールの一覧です。
54+
</p>
55+
<p>
56+
外部ストアへのリンクや、配布実行ファイルのダウンロードリンクをまとめています。
57+
</p>
58+
</section>
59+
60+
<section class="section reveal-on-scroll">
61+
<div class="section-header">
62+
<h2>一覧</h2>
63+
<div class="section-header__controls">
64+
<input
65+
type="search"
66+
id="productSearch"
67+
class="input input--search"
68+
placeholder="タイトル・タグ・プラットフォームで検索"
69+
/>
70+
<select id="productTypeFilter" class="select">
71+
<option value="">すべての種類</option>
72+
<option value="Game">Game</option>
73+
<option value="Plugin">Plugin</option>
74+
<option value="Tool">Tool</option>
75+
</select>
76+
</div>
77+
</div>
78+
79+
<div id="productList" class="card-grid"></div>
80+
81+
<p
82+
id="productEmptyMessage"
83+
class="text-muted"
84+
style="display: none"
85+
>
86+
条件に一致するプロダクトがありません。
87+
</p>
88+
</section>
89+
</main>
90+
</div>
91+
92+
<!-- 共通JS -->
93+
<script src="assets/js/layout.js" defer></script>
94+
<script src="assets/js/ui.js"></script>
95+
<!-- Products専用JS -->
96+
<script src="assets/js/products.js" defer></script>
97+
</body>
98+
</html>

0 commit comments

Comments
 (0)