Skip to content

Commit 4ab9b8c

Browse files
committed
Push
1 parent 4366b53 commit 4ab9b8c

5 files changed

Lines changed: 295 additions & 0 deletions

File tree

17.8 MB
Binary file not shown.

beos-icons/index.html

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>BeOS r5 Icons</title>
6+
<meta property="og:title" content="BeOS r5 Icons" />
7+
<meta property="og:description" content="A large collection of classic BeOS r5, Haiku OS, Zeta OS, and other retro operating system icons in PNG format." />
8+
<meta property="og:type" content="website" />
9+
<meta property="og:url" content="https://maxbo.me/beos-icons/" />
10+
<meta property="og:image" content="https://maxbo.me/beos-icons/og.png" />
11+
<meta property="og:site_name" content="maxbo.me" />
12+
<meta name="twitter:card" content="summary_large_image" />
13+
<meta name="twitter:title" content="BeOS r5 Icons" />
14+
<meta name="twitter:description" content="A large collection of classic BeOS r5, Haiku OS, Zeta OS, and other retro operating system icons in PNG format." />
15+
<meta name="twitter:image" content="https://maxbo.me/beos-icons/og.png" />
16+
<style>
17+
body { margin: 20px; }
18+
h1 { font-size: 18px; margin-bottom: 10px; }
19+
h2 { font-size: 14px; margin: 20px 0 8px 0; border-bottom: 1px solid #ccc; }
20+
.category { margin-bottom: 25px; }
21+
.icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; margin-bottom: 15px; }
22+
.icon-item { text-align: center; padding: 5px; }
23+
.icon-item img {
24+
image-rendering: pixelated;
25+
display: block;
26+
margin: 0 auto 3px;
27+
width: 64px;
28+
height: 64px;
29+
object-fit: contain;
30+
}
31+
.icon-name { font-size: 9px; color: #666; word-break: break-word; }
32+
.size-label { font-size: 10px; color: #888; margin-bottom: 5px; }
33+
#loading { margin: 20px 0; }
34+
</style>
35+
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
36+
</head>
37+
<body>
38+
<h1>BeOS r5 Icons</h1>
39+
<p>Icons from the <a href="https://gitlab.com/metsatron/BeOS-r5-Icons">BeOS-r5-Icons</a> repository by <a href="https://gitlab.com/metsatron">Metsatron</a></p>
40+
<p style="font-size: 0.9em; color: #666;">This collection includes original creations and adaptations by Metsatron, as well as icons from BeOS-r5, Zeta OS, Haiku OS, KDEclassic, OldGNOME2, Copland, and classic95 (Windows 95).</p>
41+
<div id="loading">
42+
<p>Loading icons...</p>
43+
</div>
44+
<div id="icons-container"></div>
45+
46+
<script>
47+
// Store unpacked files in memory
48+
const zipFiles = new Map();
49+
let zipLoaded = false;
50+
51+
// Unpack zip file from array buffer
52+
async function unpackZip(arrayBuffer) {
53+
try {
54+
const zip = await JSZip.loadAsync(arrayBuffer);
55+
56+
// Extract all files into memory
57+
const filePromises = [];
58+
zip.forEach((relativePath, file) => {
59+
if (!file.dir) {
60+
filePromises.push(
61+
file.async('blob').then(blob => {
62+
// Normalize path (remove leading directory if present, e.g., "BeOS-r5-Icons/")
63+
let normalizedPath = relativePath;
64+
// Remove leading slashes
65+
normalizedPath = normalizedPath.replace(/^\/+/, '');
66+
// Remove the root directory name if it matches common patterns
67+
const parts = normalizedPath.split('/');
68+
if (parts.length > 0 && (parts[0].includes('BeOS') || parts[0] === 'BeOS-r5-Icons' || parts[0] === 'BeOS-r5-Icons-master')) {
69+
normalizedPath = parts.slice(1).join('/');
70+
}
71+
// Store both the normalized path and original path for lookup
72+
zipFiles.set(normalizedPath, blob);
73+
if (normalizedPath !== relativePath) {
74+
zipFiles.set(relativePath, blob);
75+
}
76+
})
77+
);
78+
}
79+
});
80+
81+
await Promise.all(filePromises);
82+
zipLoaded = true;
83+
84+
// Log zip structure for debugging
85+
console.log('=== ZIP FILE STRUCTURE ===');
86+
console.log(`Total files in zip: ${zipFiles.size}`);
87+
const allPaths = Array.from(zipFiles.keys()).sort();
88+
console.log('All paths in zip:');
89+
allPaths.forEach(path => console.log(` ${path}`));
90+
console.log('=== END ZIP STRUCTURE ===');
91+
92+
document.getElementById('loading').style.display = 'none';
93+
return true;
94+
} catch (error) {
95+
console.error('Error unpacking zip:', error);
96+
document.getElementById('loading').innerHTML = `<p>Error unpacking zip file: ${error.message}</p>`;
97+
return false;
98+
}
99+
}
100+
101+
// Download and unpack the zip file
102+
async function loadZip() {
103+
try {
104+
// Try to load from a zip file - adjust the path as needed
105+
const zipUrl = 'BeOS-r5-Icons-master.zip';
106+
const response = await fetch(zipUrl);
107+
if (!response.ok) {
108+
throw new Error(`Failed to fetch zip: ${response.statusText}`);
109+
}
110+
const arrayBuffer = await response.arrayBuffer();
111+
return await unpackZip(arrayBuffer);
112+
} catch (error) {
113+
console.error('Error loading zip:', error);
114+
document.getElementById('loading').innerHTML = `<p>Error loading zip file: ${error.message}</p>`;
115+
return false;
116+
}
117+
}
118+
119+
function getIconUrl(path) {
120+
const blob = zipFiles.get(path);
121+
if (blob) {
122+
return URL.createObjectURL(blob);
123+
}
124+
125+
// Try case-insensitive search as fallback
126+
for (const [zipPath, zipBlob] of zipFiles.entries()) {
127+
if (zipPath.toLowerCase() === path.toLowerCase()) {
128+
return URL.createObjectURL(zipBlob);
129+
}
130+
}
131+
132+
return '';
133+
}
134+
135+
function createIconElement(path, name) {
136+
const iconItem = document.createElement('div');
137+
iconItem.className = 'icon-item';
138+
139+
const img = document.createElement('img');
140+
const iconUrl = getIconUrl(path);
141+
if (iconUrl) {
142+
img.src = iconUrl;
143+
img.alt = name;
144+
// Extract size from path for data-size attribute
145+
const pathParts = path.split('/');
146+
if (pathParts.length >= 2) {
147+
const size = pathParts[pathParts.length - 2];
148+
img.setAttribute('data-size', size);
149+
}
150+
img.onerror = () => {
151+
// Hide broken images
152+
iconItem.style.display = 'none';
153+
};
154+
} else {
155+
// Hide if icon not found in zip
156+
iconItem.style.display = 'none';
157+
return null;
158+
}
159+
160+
const label = document.createElement('div');
161+
label.className = 'icon-name';
162+
label.textContent = name;
163+
164+
iconItem.appendChild(img);
165+
iconItem.appendChild(label);
166+
return iconItem;
167+
}
168+
169+
function loadIconsForCategory(category, sizes) {
170+
const categoryDiv = document.createElement('div');
171+
categoryDiv.className = 'category';
172+
173+
const heading = document.createElement('h2');
174+
heading.textContent = category;
175+
categoryDiv.appendChild(heading);
176+
177+
const numericSizes = Object.keys(sizes).filter(s => /^\d+$/.test(s)).sort((a, b) => parseInt(a) - parseInt(b));
178+
const otherSizes = Object.keys(sizes).filter(s => !/^\d+$/.test(s)).sort();
179+
180+
[...numericSizes, ...otherSizes].forEach(size => {
181+
const sizeLabel = document.createElement('div');
182+
sizeLabel.className = 'size-label';
183+
sizeLabel.textContent = `${size}x${size}`;
184+
categoryDiv.appendChild(sizeLabel);
185+
186+
const iconGrid = document.createElement('div');
187+
iconGrid.className = 'icon-grid';
188+
189+
sizes[size].forEach(icon => {
190+
const element = createIconElement(icon.path, icon.name);
191+
if (element) {
192+
iconGrid.appendChild(element);
193+
}
194+
});
195+
196+
categoryDiv.appendChild(iconGrid);
197+
});
198+
199+
document.getElementById('icons-container').appendChild(categoryDiv);
200+
}
201+
202+
// Discover icons from zip file structure
203+
function discoverIconsFromZip() {
204+
const iconMap = {}; // { category: { size: [{ name, path }] } }
205+
206+
console.log('=== DISCOVERING ICONS FROM ZIP ===');
207+
console.log(`Processing ${zipFiles.size} files from zip`);
208+
209+
// Process all files in the zip
210+
for (const [path, blob] of zipFiles.entries()) {
211+
// Skip non-image files
212+
if (!/\.(png|gif|jpg|jpeg|svg|ico|webp)$/i.test(path)) {
213+
continue;
214+
}
215+
216+
const parts = path.split('/').filter(p => p.length > 0);
217+
if (parts.length < 2) continue; // Need at least category/size/filename
218+
219+
let category, size, name;
220+
221+
// Handle extras/misc structure: extras/misc/subcat/filename.ext
222+
if (parts[0] === 'extras' && parts[1] === 'misc' && parts.length >= 4) {
223+
category = 'extras';
224+
size = `misc-${parts[2]}`;
225+
name = parts.slice(3).join('/').replace(/\.[^/.]+$/, ''); // Remove extension
226+
}
227+
// Handle scalable structure: scalable/size/filename.ext
228+
else if (parts[0] === 'scalable' && parts.length >= 3) {
229+
category = 'scalable';
230+
size = parts[1];
231+
name = parts.slice(2).join('/').replace(/\.[^/.]+$/, ''); // Remove extension
232+
}
233+
// Handle standard structure: category/size/filename.ext
234+
else if (parts.length >= 3) {
235+
category = parts[0];
236+
size = parts[1];
237+
name = parts.slice(2).join('/').replace(/\.[^/.]+$/, ''); // Remove extension
238+
} else {
239+
continue;
240+
}
241+
242+
// Initialize category if needed
243+
if (!iconMap[category]) {
244+
iconMap[category] = {};
245+
}
246+
// Initialize size if needed
247+
if (!iconMap[category][size]) {
248+
iconMap[category][size] = [];
249+
}
250+
251+
// Add icon if not already present (avoid duplicates)
252+
const exists = iconMap[category][size].some(icon => icon.name === name && icon.path === path);
253+
if (!exists) {
254+
iconMap[category][size].push({ name, path });
255+
}
256+
}
257+
258+
// Sort icons by name within each size
259+
Object.keys(iconMap).forEach(category => {
260+
Object.keys(iconMap[category]).forEach(size => {
261+
iconMap[category][size].sort((a, b) => a.name.localeCompare(b.name));
262+
});
263+
});
264+
265+
console.log('=== DISCOVERED ICON MAP ===');
266+
console.log(JSON.stringify(iconMap, null, 2));
267+
console.log('=== END DISCOVERED ICON MAP ===');
268+
269+
return iconMap;
270+
}
271+
272+
// Load icons from zip file structure
273+
function loadIcons() {
274+
const iconMap = discoverIconsFromZip();
275+
276+
Object.keys(iconMap).sort().forEach(category => {
277+
loadIconsForCategory(category, iconMap[category]);
278+
});
279+
}
280+
281+
// Load zip first, then load icons
282+
loadZip().then(success => {
283+
if (success) {
284+
loadIcons();
285+
}
286+
});
287+
</script>
288+
</body>
289+
</html>

beos-icons/og.png

108 KB
Loading

beos-icons/og.webp

70.4 KB
Loading

index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,12 @@ <h2>
18891889
<p class="title">cursors</p>
18901890
</a>
18911891
</article>
1892+
<article class="listing">
1893+
<a href="beos-icons/">
1894+
<img src="beos-icons/og.webp" alt="BeOS r5 Icons" />
1895+
<p class="title">BeOS r5 Icons</p>
1896+
</a>
1897+
</article>
18921898
<article class="listing" style="background-color: #FFCCCC;">
18931899
<a href="https://maxbo.me/docomo">
18941900
<div class="preview">

0 commit comments

Comments
 (0)