diff --git a/deploy/bin/64bit/obs64.exe b/deploy/bin/64bit/obs64.exe
index 74bcc21..eac8c80 100644
Binary files a/deploy/bin/64bit/obs64.exe and b/deploy/bin/64bit/obs64.exe differ
diff --git a/docs/icon_preview.png b/docs/icon_preview.png
index def164b..be31c99 100644
Binary files a/docs/icon_preview.png and b/docs/icon_preview.png differ
diff --git a/obs-studio/cmake/bundle/windows/obs-studio.ico b/obs-studio/cmake/bundle/windows/obs-studio.ico
index 7127372..4499d08 100644
Binary files a/obs-studio/cmake/bundle/windows/obs-studio.ico and b/obs-studio/cmake/bundle/windows/obs-studio.ico differ
diff --git a/obs-studio/frontend/cmake/windows/obs-studio.ico b/obs-studio/frontend/cmake/windows/obs-studio.ico
index bbb75dc..4499d08 100644
Binary files a/obs-studio/frontend/cmake/windows/obs-studio.ico and b/obs-studio/frontend/cmake/windows/obs-studio.ico differ
diff --git a/obs-studio/frontend/forms/OBSAbout.ui b/obs-studio/frontend/forms/OBSAbout.ui
index 9cbb4c0..1c7861b 100644
--- a/obs-studio/frontend/forms/OBSAbout.ui
+++ b/obs-studio/frontend/forms/OBSAbout.ui
@@ -7,7 +7,7 @@
0
0
520
- 300
+ 340
@@ -73,6 +73,22 @@
+ -
+
+
+ Based on <a href="https://obsproject.com">OBS Studio</a> (GPLv2)<br/>Secondary development by CAIIC
+
+
+ true
+
+
+ Qt::TextBrowserInteraction
+
+
+ true
+
+
+
-
diff --git a/obs-studio/frontend/forms/images/obs.png b/obs-studio/frontend/forms/images/obs.png
index def164b..be31c99 100644
Binary files a/obs-studio/frontend/forms/images/obs.png and b/obs-studio/frontend/forms/images/obs.png differ
diff --git a/tools/dome-icon-gen.js b/tools/dome-icon-gen.js
new file mode 100644
index 0000000..09da2a8
--- /dev/null
+++ b/tools/dome-icon-gen.js
@@ -0,0 +1,206 @@
+// DOME-style icon generator for the CAIIC OBS fork.
+// Renders a blue observatory/planetarium dome with a white three-blade
+// aperture (nod to the OBS logo) wrapped by three CAIIC-colored arcs
+// (orange top, green lower-left, yellow lower-right).
+// Usage: node dome-icon-gen.js
+
+const fs = require('fs');
+const path = require('path');
+const zlib = require('zlib');
+
+const DEG = Math.PI / 180;
+
+// ---------- CRC32 / PNG ----------
+const crcTable = (() => {
+ const t = new Uint32Array(256);
+ for (let n = 0; n < 256; n++) {
+ let c = n;
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
+ t[n] = c >>> 0;
+ }
+ return t;
+})();
+function crc32(buf) {
+ let c = 0xffffffff;
+ for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
+ return (c ^ 0xffffffff) >>> 0;
+}
+function chunk(type, data) {
+ const out = Buffer.alloc(8 + data.length + 4);
+ out.writeUInt32BE(data.length, 0);
+ out.write(type, 4, 'ascii');
+ data.copy(out, 8);
+ out.writeUInt32BE(crc32(out.subarray(4, 8 + data.length)), 8 + data.length);
+ return out;
+}
+function pngEncode(width, height, rgba) {
+ const stride = width * 4;
+ const raw = Buffer.alloc((stride + 1) * height);
+ for (let y = 0; y < height; y++) {
+ raw[y * (stride + 1)] = 0;
+ rgba.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
+ }
+ const ihdr = Buffer.alloc(13);
+ ihdr.writeUInt32BE(width, 0);
+ ihdr.writeUInt32BE(height, 4);
+ ihdr[8] = 8; // bit depth
+ ihdr[9] = 6; // RGBA
+ return Buffer.concat([
+ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
+ chunk('IHDR', ihdr),
+ chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
+ chunk('IEND', Buffer.alloc(0)),
+ ]);
+}
+function icoEncode(images) {
+ // images: [{size, png}]
+ const header = Buffer.alloc(6);
+ header.writeUInt16LE(1, 2);
+ header.writeUInt16LE(images.length, 4);
+ let offset = 6 + images.length * 16;
+ const entries = [];
+ for (const img of images) {
+ const e = Buffer.alloc(16);
+ e[0] = img.size >= 256 ? 0 : img.size;
+ e[1] = img.size >= 256 ? 0 : img.size;
+ e[4] = 1; // planes
+ e.writeUInt16LE(32, 6);
+ e.writeUInt32LE(img.png.length, 8);
+ e.writeUInt32LE(offset, 12);
+ offset += img.png.length;
+ entries.push(e);
+ }
+ return Buffer.concat([header, ...entries, ...images.map((i) => i.png)]);
+}
+
+// ---------- geometry ----------
+function clamp(x, a, b) { return Math.min(Math.max(x, a), b); }
+function mix(a, b, t) { return a + (b - a) * t; }
+function mix3(c1, c2, t) { return [mix(c1[0], c2[0], t), mix(c1[1], c2[1], t), mix(c1[2], c2[2], t)]; }
+
+// signed distance to a circular arc stroke (rounded caps), angles in radians
+function arcSDF(px, py, cx, cy, r, a0, a1, w) {
+ const dx = px - cx, dy = py - cy;
+ const d = Math.hypot(dx, dy);
+ let ang = Math.atan2(dy, dx);
+ // normalize ang into [a0, a0 + 2PI)
+ const TWO_PI = Math.PI * 2;
+ while (ang < a0) ang += TWO_PI;
+ while (ang >= a0 + TWO_PI) ang -= TWO_PI;
+ let dist;
+ if (ang <= a1) {
+ dist = Math.abs(d - r);
+ } else {
+ const e0x = cx + r * Math.cos(a0), e0y = cy + r * Math.sin(a0);
+ const e1x = cx + r * Math.cos(a1), e1y = cy + r * Math.sin(a1);
+ dist = Math.min(Math.hypot(px - e0x, py - e0y), Math.hypot(px - e1x, py - e1y));
+ }
+ return dist - w / 2;
+}
+// rounded rect sdf
+function rrSDF(px, py, cx, cy, hw, hh, r) {
+ const qx = Math.abs(px - cx) - (hw - r);
+ const qy = Math.abs(py - cy) - (hh - r);
+ const ax = Math.max(qx, 0), ay = Math.max(qy, 0);
+ return Math.hypot(ax, ay) + Math.min(Math.max(qx, qy), 0) - r;
+}
+
+// ---------- design ----------
+const DOME_C = [0.5, 0.56]; // dome circle center
+const DOME_R = 0.295; // dome radius
+const BLADE_C = [0.5, 0.475]; // aperture center
+const BLADE_R = 0.155;
+const BLADE_W = 0.048;
+const ARC_C = [0.5, 0.53];
+const ARC_R = 0.4;
+const ARC_W = 0.036;
+
+const NAVY = [10, 52, 128];
+const BLUE_HI = [110, 196, 250];
+const BLUE_LO = [9, 68, 152];
+const WHITE = [248, 252, 255];
+const ORANGE = [240, 128, 60];
+const GREEN = [110, 190, 74];
+const YELLOW = [240, 204, 60];
+
+function domeColor(px, py) {
+ const t = Math.pow(clamp(Math.hypot(px - 0.34, py - 0.36) / 0.5, 0, 1), 0.8);
+ let c = mix3(BLUE_HI, BLUE_LO, t);
+ // darken slightly toward the base for depth
+ const base = clamp((py - 0.42) / 0.16, 0, 1) * 0.18;
+ return [c[0] * (1 - base), c[1] * (1 - base), c[2] * (1 - base)];
+}
+
+function bladeSDF(px, py) {
+ let best = 1e9;
+ const span = 100 * DEG;
+ for (let i = 0; i < 3; i++) {
+ const a0 = (-90 + i * 120) * DEG;
+ best = Math.min(best, arcSDF(px, py, BLADE_C[0], BLADE_C[1], BLADE_R, a0, a0 + span, BLADE_W));
+ }
+ return best;
+}
+function arcsSDF(px, py) {
+ return [
+ { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, -160 * DEG, -20 * DEG, ARC_W), color: ORANGE },
+ { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, 105 * DEG, 170 * DEG, ARC_W), color: GREEN },
+ { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, 10 * DEG, 75 * DEG, ARC_W), color: YELLOW },
+ ];
+}
+
+function render(size) {
+ const SS = 4;
+ const R = size * SS;
+ const out = Buffer.alloc(size * size * 4);
+ const px = 1 / R;
+
+ for (let y = 0; y < size; y++) {
+ for (let x = 0; x < size; x++) {
+ let r = 0, g = 0, b = 0, a = 0;
+ for (let sy = 0; sy < SS; sy++) {
+ for (let sx = 0; sx < SS; sx++) {
+ const nx = (x * SS + sx + 0.5) / R;
+ const ny = (y * SS + sy + 0.5) / R;
+ const aa = (sdf) => clamp(0.5 - sdf / px, 0, 1);
+
+ let cr = 0, cg = 0, cb = 0, ca = 0;
+ const over = (sdf, col, opacity) => {
+ const cov = aa(sdf) * opacity;
+ cr = cr * (1 - cov) + col[0] * cov;
+ cg = cg * (1 - cov) + col[1] * cov;
+ cb = cb * (1 - cov) + col[2] * cov;
+ ca = ca * (1 - cov) + cov;
+ };
+
+ // base bar
+ over(rrSDF(nx, ny, 0.5, DOME_C[1] + 0.022, DOME_R + 0.045, 0.028, 0.026), NAVY, 1);
+ // dome (upper half of circle)
+ const dd = Math.max(Math.hypot(nx - DOME_C[0], ny - DOME_C[1]) - DOME_R, ny - DOME_C[1]);
+ over(dd, domeColor(nx, ny), 1);
+ // aperture blades
+ over(bladeSDF(nx, ny), WHITE, 0.95);
+ // colored arcs
+ for (const arc of arcsSDF(nx, ny)) over(arc.sdf, arc.color, 1);
+
+ r += cr; g += cg; b += cb; a += ca;
+ }
+ }
+ const n = SS * SS;
+ const o = (y * size + x) * 4;
+ out[o] = Math.round(clamp(r / n, 0, 255));
+ out[o + 1] = Math.round(clamp(g / n, 0, 255));
+ out[o + 2] = Math.round(clamp(b / n, 0, 255));
+ out[o + 3] = Math.round(clamp(a / n, 0, 1) * 255);
+ }
+ }
+ return out;
+}
+
+// ---------- main ----------
+const outDir = process.argv[2] || '.';
+const sizes = [16, 24, 32, 48, 64, 128, 256];
+const images = sizes.map((s) => ({ size: s, png: pngEncode(s, s, render(s)) }));
+
+fs.writeFileSync(path.join(outDir, 'obs.png'), images[images.length - 1].png);
+fs.writeFileSync(path.join(outDir, 'obs-studio.ico'), icoEncode(images));
+console.log('wrote obs.png (256x256) and obs-studio.ico (' + sizes.join(',') + ') to ' + outDir);