diff --git a/README.md b/README.md index 552e044..cf7c62f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + + + Triple-C — Coding Container + + # Triple-C (Claude-Code-Container) Triple-C is a cross-platform desktop application that sandboxes Claude Code inside Docker containers. Each project chooses its own **permission mode** — from Plan (read-only) through to Bypass (`--dangerously-skip-permissions`), which gives Claude unrestricted access within the sandbox. diff --git a/app/index.html b/app/index.html index 20f0ace..27595e7 100644 --- a/app/index.html +++ b/app/index.html @@ -2,7 +2,7 @@ - + Triple-C diff --git a/app/public/favicon.svg b/app/public/favicon.svg new file mode 100644 index 0000000..792cf60 --- /dev/null +++ b/app/public/favicon.svg @@ -0,0 +1,13 @@ + + Triple-C application icon, small-size variant + + + + + + + + + diff --git a/app/src-tauri/icons/128x128.png b/app/src-tauri/icons/128x128.png index 5bbaddc..3530848 100644 Binary files a/app/src-tauri/icons/128x128.png and b/app/src-tauri/icons/128x128.png differ diff --git a/app/src-tauri/icons/128x128@2x.png b/app/src-tauri/icons/128x128@2x.png index 2d4476a..7f148be 100644 Binary files a/app/src-tauri/icons/128x128@2x.png and b/app/src-tauri/icons/128x128@2x.png differ diff --git a/app/src-tauri/icons/32x32.png b/app/src-tauri/icons/32x32.png index 8addceb..c7458fd 100644 Binary files a/app/src-tauri/icons/32x32.png and b/app/src-tauri/icons/32x32.png differ diff --git a/app/src-tauri/icons/icon.icns b/app/src-tauri/icons/icon.icns new file mode 100644 index 0000000..61706f0 Binary files /dev/null and b/app/src-tauri/icons/icon.icns differ diff --git a/app/src-tauri/icons/icon.ico b/app/src-tauri/icons/icon.ico index b416517..6f49214 100644 Binary files a/app/src-tauri/icons/icon.ico and b/app/src-tauri/icons/icon.ico differ diff --git a/app/src-tauri/icons/icon.png b/app/src-tauri/icons/icon.png index 9d4faa5..011a125 100644 Binary files a/app/src-tauri/icons/icon.png and b/app/src-tauri/icons/icon.png differ diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index 1b98dc9..6332887 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -33,6 +33,7 @@ "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.ico", + "icons/icon.icns", "icons/icon.png" ] }, diff --git a/app/src/test/icon-config.test.ts b/app/src/test/icon-config.test.ts index 172cfa6..e03c3a5 100644 --- a/app/src/test/icon-config.test.ts +++ b/app/src/test/icon-config.test.ts @@ -33,4 +33,33 @@ describe("Window icon configuration", () => { expect(config.bundle.icon).toContain("icons/icon.ico"); expect(config.bundle.icon).toContain("icons/icon.png"); }); + + it("icon.ico carries the small sizes Windows draws in the taskbar", () => { + // A single-image .ico is the taskbar bug: Windows upscales 16x16 into every + // other slot. Regenerate with `python3 branding/build-icons.py`. + const ico = readFileSync(resolve(srcTauriDir, "icons/icon.ico")); + const count = ico.readUInt16LE(4); + expect(count).toBeGreaterThan(1); + + // Directory entries start at byte 6; width/height of 0 means 256. + const widths = new Set(); + for (let i = 0; i < count; i++) { + const w = ico[6 + i * 16]; + widths.add(w === 0 ? 256 : w); + } + for (const size of [16, 24, 32, 48, 256]) { + expect(widths).toContain(size); + } + }); + + it("icon.icns exists and is bundled for macOS", () => { + const icnsPath = resolve(srcTauriDir, "icons/icon.icns"); + expect(existsSync(icnsPath)).toBe(true); + expect(readFileSync(icnsPath).subarray(0, 4).toString("ascii")).toBe("icns"); + + const config = JSON.parse( + readFileSync(resolve(srcTauriDir, "tauri.conf.json"), "utf-8") + ); + expect(config.bundle.icon).toContain("icons/icon.icns"); + }); }); diff --git a/branding/README.md b/branding/README.md new file mode 100644 index 0000000..4865c00 --- /dev/null +++ b/branding/README.md @@ -0,0 +1,58 @@ +# Branding + +The mark is a container with its right wall opened, so the enclosure itself is the letter **C**, +holding a `>_` prompt. Container plus shell, in one closed shape — no type inside the icon, so +nothing goes illegible when the app is 16 px tall in a taskbar. + +## Files + +| File | What it is | +|---|---| +| `triple-c-mark.svg` | The mark alone, transparent. Use on any ground. | +| `triple-c-mark-small.svg` | Optical variant for 32 px and below. | +| `triple-c-icon.svg` | The app icon: mark at 70% on a `#0D1117` tile, 22% corner radius. | +| `triple-c-icon-small.svg` | The app icon at small sizes — small mark, drawn at 82%. | +| `triple-c-lockup-dark.svg` | Horizontal lockup for dark backgrounds. Wordmark is outlined, so no font is needed to render it. | +| `triple-c-lockup-light.svg` | The same for light backgrounds. | +| `triple-c-icon-1024.png` | Master raster, generated. | +| `build-icons.py` | Regenerates every packaged icon from the SVGs. | + +## Palette + +| Role | Dark ground | Light ground | +|---|---|---| +| Frame | `#58A6FF` (`--accent`) | `#1F6FEB` (`--accent-emphasis`) | +| Prompt | `#F0821E` | `#C4610F` | +| Tile | `#0D1117` (`--bg-primary`) | — | +| Wordmark | `#E6EDF3` (`--text-primary`) | `#131C25` | + +The frame and prompt colours are the app's own accent tokens from `app/src/index.css`, which is why +the icon sits on the app's chrome instead of fighting it. Orange is the only equity carried over +from the previous marks, and it is now an accent rather than a background. + +## Two sources, not one + +`build-icons.py` renders sizes ≥ 48 px from `triple-c-icon.svg` and sizes ≤ 32 px from +`triple-c-icon-small.svg`. At small sizes the cursor bar closes up against the chevron and a 12 px +frame stroke resamples to a grey smear, so the small variant drops the cursor, widens the mouth, +thickens the strokes and draws the mark larger inside the tile. + +This matters most for `icon.ico`: Windows picks the 16 px entry for the window corner and 24/32 px +for the taskbar. The previous `.ico` contained a *single* 16 px image, which Windows then upscaled +everywhere else — the likely cause of `screenshot_for_fix/task_bar_icon_not_correct.png`. The +current one carries 16, 24, 32, 48, 64, 128 and 256, each rendered from vector rather than +downsampled from one bitmap. + +## Regenerating + +```bash +pip install cairosvg pillow +python3 branding/build-icons.py +``` + +Writes `app/src-tauri/icons/{32x32,128x128,128x128@2x,icon}.png`, `icon.ico`, `icon.icns`, +`app/public/favicon.svg` and `branding/triple-c-icon-1024.png`. Do not hand-edit those — edit the +SVG and re-run. + +The lockups are committed sources, not generated: their wordmark is Liberation Mono Bold converted +to outlines (`-1.6` tracking, 44 px cap height), so re-cutting it needs the font and `fonttools`. diff --git a/branding/archive/README.md b/branding/archive/README.md new file mode 100644 index 0000000..b375830 --- /dev/null +++ b/branding/archive/README.md @@ -0,0 +1,11 @@ +# Archive + +The marks Triple-C shipped before the current one, kept for reference. + +| File | Notes | +|---|---| +| `triple-c-app-logo.png` | First mark: orange tile, overlapping C letterforms, "TRIPLE-C / CONTAINERIZED CODING" set inside the icon. | +| `triple-c-app-logov2.png` | Second mark: orange sunburst with "Triple-C / Coding Container" over it, transparent ground. | + +Both were drawn at poster size and carried type inside the icon, so nothing in them survived the +16–32 px range where an app icon actually lives. Neither is referenced by the app or the build. diff --git a/branding/archive/triple-c-app-logo.png b/branding/archive/triple-c-app-logo.png new file mode 100644 index 0000000..0c578c9 Binary files /dev/null and b/branding/archive/triple-c-app-logo.png differ diff --git a/triple-c-app-logov2.png b/branding/archive/triple-c-app-logov2.png similarity index 100% rename from triple-c-app-logov2.png rename to branding/archive/triple-c-app-logov2.png diff --git a/branding/build-icons.py b/branding/build-icons.py new file mode 100644 index 0000000..d260333 --- /dev/null +++ b/branding/build-icons.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Render every packaged icon from the SVG sources in this directory. + + pip install cairosvg pillow + python3 branding/build-icons.py + +The SVGs are the source of truth; everything under app/src-tauri/icons/ and the +favicon are generated. Two sources, not one, on purpose: + + triple-c-icon.svg used for 48 px and up + triple-c-icon-small.svg used for 32 px and down + +At 16-32 px the cursor bar closes up against the chevron and the frame's 12 px +stroke resamples to a grey smear, so the small source drops the cursor, widens +the mouth and draws the mark larger in the tile. Windows picks the 16 and 24 px +entries out of the .ico for the taskbar and window corner, which is exactly +where the old single-size .ico was falling over. +""" + +import io +import struct +from pathlib import Path + +import cairosvg +from PIL import Image + +BRANDING = Path(__file__).resolve().parent +REPO = BRANDING.parent +ICONS = REPO / "app" / "src-tauri" / "icons" +PUBLIC = REPO / "app" / "public" + +FULL_SRC = BRANDING / "triple-c-icon.svg" +SMALL_SRC = BRANDING / "triple-c-icon-small.svg" + +# Below this, render from the small source. +SMALL_MAX = 32 + +# .ico entries. Windows uses 16 in the window corner and 24/32 in the taskbar, +# 48 in list views and 256 in the "extra large icons" view. +ICO_SIZES = [16, 24, 32, 48, 64, 128, 256] + +# .icns entries: (chunk type, pixel size). PNG-backed chunks, macOS 10.7+. +ICNS_ENTRIES = [ + (b"ic11", 32), # 16pt @2x + (b"ic12", 64), # 32pt @2x + (b"ic07", 128), # 128pt + (b"ic13", 256), # 128pt @2x + (b"ic08", 256), # 256pt + (b"ic14", 512), # 256pt @2x + (b"ic09", 512), # 512pt + (b"ic10", 1024), # 512pt @2x +] + + +def render(size: int) -> Image.Image: + """Rasterise the right source at `size` px square.""" + src = SMALL_SRC if size <= SMALL_MAX else FULL_SRC + png = cairosvg.svg2png(url=str(src), output_width=size, output_height=size) + return Image.open(io.BytesIO(png)).convert("RGBA") + + +def write_png(size: int, path: Path) -> None: + render(size).save(path, "PNG", optimize=True) + print(f" {path.relative_to(REPO)} {size}x{size}") + + +def write_ico(path: Path) -> None: + """Hand-assemble the .ico so each entry can come from its own source. + + Pillow's save(sizes=...) downsamples one image, which would put the + 12 px-stroke artwork into the 16 px entry — the thing this avoids. + """ + images = [render(s) for s in ICO_SIZES] + payloads = [] + for img in images: + buf = io.BytesIO() + img.save(buf, "PNG", optimize=True) # PNG-compressed entries, Vista+ + payloads.append(buf.getvalue()) + + offset = 6 + 16 * len(images) + header = struct.pack("= 256 else img.width + h = 0 if img.height >= 256 else img.height + entries += struct.pack(" None: + chunks = b"" + for kind, size in ICNS_ENTRIES: + buf = io.BytesIO() + render(size).save(buf, "PNG", optimize=True) + data = buf.getvalue() + chunks += kind + struct.pack(">I", len(data) + 8) + data + path.write_bytes(b"icns" + struct.pack(">I", len(chunks) + 8) + chunks) + print(f" {path.relative_to(REPO)} {', '.join(str(s) for _, s in ICNS_ENTRIES)}") + + +def main() -> None: + print("branding/") + write_png(1024, BRANDING / "triple-c-icon-1024.png") + + print("app/src-tauri/icons/") + write_png(32, ICONS / "32x32.png") + write_png(128, ICONS / "128x128.png") + write_png(256, ICONS / "128x128@2x.png") + write_png(512, ICONS / "icon.png") + write_ico(ICONS / "icon.ico") + write_icns(ICONS / "icon.icns") + + print("app/public/") + (PUBLIC / "favicon.svg").write_text(SMALL_SRC.read_text()) + print(f" {(PUBLIC / 'favicon.svg').relative_to(REPO)} (copy of {SMALL_SRC.name})") + + +if __name__ == "__main__": + main() diff --git a/branding/triple-c-icon-1024.png b/branding/triple-c-icon-1024.png new file mode 100644 index 0000000..00d0e16 Binary files /dev/null and b/branding/triple-c-icon-1024.png differ diff --git a/branding/triple-c-icon-small.svg b/branding/triple-c-icon-small.svg new file mode 100644 index 0000000..792cf60 --- /dev/null +++ b/branding/triple-c-icon-small.svg @@ -0,0 +1,13 @@ + + Triple-C application icon, small-size variant + + + + + + + + + diff --git a/branding/triple-c-icon.svg b/branding/triple-c-icon.svg new file mode 100644 index 0000000..fc4e1f9 --- /dev/null +++ b/branding/triple-c-icon.svg @@ -0,0 +1,14 @@ + + Triple-C application icon + + + + + + + + + + diff --git a/branding/triple-c-lockup-dark.svg b/branding/triple-c-lockup-dark.svg new file mode 100644 index 0000000..d5d54ba --- /dev/null +++ b/branding/triple-c-lockup-dark.svg @@ -0,0 +1,13 @@ + + Triple-C — Coding Container + + + + + + + + + + + diff --git a/branding/triple-c-lockup-light.svg b/branding/triple-c-lockup-light.svg new file mode 100644 index 0000000..a21597f --- /dev/null +++ b/branding/triple-c-lockup-light.svg @@ -0,0 +1,13 @@ + + Triple-C — Coding Container + + + + + + + + + + + diff --git a/branding/triple-c-mark-small.svg b/branding/triple-c-mark-small.svg new file mode 100644 index 0000000..0f8d3e2 --- /dev/null +++ b/branding/triple-c-mark-small.svg @@ -0,0 +1,10 @@ + + Triple-C mark, small-size variant + + + + + + diff --git a/branding/triple-c-mark.svg b/branding/triple-c-mark.svg new file mode 100644 index 0000000..884f930 --- /dev/null +++ b/branding/triple-c-mark.svg @@ -0,0 +1,11 @@ + + Triple-C mark + + + + + + + +