Arslan1997 commited on
Commit
f1ab9e4
·
1 Parent(s): 9499163

LETS GOOOOOOOOOOOOOOOOOOOOOOOOO

Browse files
frontend/index.html CHANGED
@@ -5,7 +5,12 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <meta name="theme-color" content="#000000" />
7
  <meta name="description" content="Notestack turns any blog, newsletter, site or markdown files into a grounded research notebook, audio overviews, videos and a launch kit, all in your own voice." />
8
- <link rel="icon" href="/logo.svg" />
 
 
 
 
 
9
  <!-- Link previews (X, LinkedIn, Bluesky, Threads, Facebook, Slack, Discord, iMessage). %SITE_URL% is filled in at build. -->
10
  <meta property="og:type" content="website" />
11
  <meta property="og:site_name" content="Notestack" />
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <meta name="theme-color" content="#000000" />
7
  <meta name="description" content="Notestack turns any blog, newsletter, site or markdown files into a grounded research notebook, audio overviews, videos and a launch kit, all in your own voice." />
8
+ <!-- Favicons: regenerate with python frontend/scripts/make_favicons.py after changing public/logo.svg. -->
9
+ <link rel="icon" href="/favicon.ico" sizes="48x48" />
10
+ <link rel="icon" href="/logo.svg" type="image/svg+xml" />
11
+ <link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
12
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
13
+ <link rel="manifest" href="/site.webmanifest" />
14
  <!-- Link previews (X, LinkedIn, Bluesky, Threads, Facebook, Slack, Discord, iMessage). %SITE_URL% is filled in at build. -->
15
  <meta property="og:type" content="website" />
16
  <meta property="og:site_name" content="Notestack" />
frontend/public/site.webmanifest ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Notestack",
3
+ "short_name": "Notestack",
4
+ "description": "Your knowledge, in orbit. The ultimate NotebookLM alternative for writers.",
5
+ "start_url": "/app",
6
+ "display": "standalone",
7
+ "background_color": "#000000",
8
+ "theme_color": "#000000",
9
+ "icons": [
10
+ { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
11
+ { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
12
+ { "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
13
+ ]
14
+ }
frontend/scripts/make_favicons.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Favicons from the logo mark (public/logo.svg), rendered with the renderer's headless Chrome.
2
+
3
+ Writes to public/: favicon.ico (16, 32, 48), favicon-32.png, apple-touch-icon.png (180, full bleed:
4
+ iOS rounds the corners itself), icon-192.png, icon-512.png and icon-maskable-512.png (planet inside
5
+ Android's 80% safe zone). Run from the repo root: python frontend/scripts/make_favicons.py
6
+ """
7
+
8
+ import struct
9
+ import subprocess
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+ ROOT = Path(__file__).resolve().parents[2]
14
+ PUBLIC = ROOT / "frontend" / "public"
15
+ CHROME = next((ROOT / "renderer" / "node_modules" / ".remotion").rglob("chrome-headless-shell.exe"))
16
+ MARK = (PUBLIC / "logo.svg").read_text(encoding="utf-8")
17
+
18
+ # The planet and sparkle without the rounded tile, to place on a full bleed square.
19
+ ART = """
20
+ <circle cx="26" cy="36" r="13" fill="#000000"/>
21
+ <ellipse cx="26" cy="36" rx="21" ry="5" fill="none" stroke="#000000" stroke-width="3" transform="rotate(-18 26 36)"/>
22
+ <path d="M46 10 l2.2 5.8 5.8 2.2 -5.8 2.2 -2.2 5.8 -2.2 -5.8 -5.8 -2.2 5.8 -2.2z" fill="#000000"/>
23
+ """
24
+
25
+
26
+ def full_bleed(scale: float) -> str:
27
+ """White square, artwork scaled about the centre (iOS and Android apply their own masks)."""
28
+ off = 32 * (1 - scale)
29
+ return (
30
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
31
+ '<rect width="64" height="64" fill="#ffffff"/>'
32
+ f'<g transform="translate({off} {off}) scale({scale})">{ART}</g></svg>'
33
+ )
34
+
35
+
36
+ def render(svg: str, size: int, out: Path) -> bytes:
37
+ with tempfile.TemporaryDirectory() as tmp:
38
+ page = Path(tmp) / "icon.html"
39
+ page.write_text(
40
+ "<!doctype html><html><body style='margin:0;background:transparent'>"
41
+ f"<div style='width:{size}px;height:{size}px'>{svg.replace('<svg ', f'<svg width=\"{size}\" height=\"{size}\" ', 1)}</div>"
42
+ "</body></html>",
43
+ encoding="utf-8",
44
+ )
45
+ shot = Path(tmp) / "shot.png"
46
+ subprocess.run(
47
+ [str(CHROME), "--headless", "--disable-gpu", "--hide-scrollbars", "--force-device-scale-factor=1",
48
+ "--default-background-color=00000000", f"--window-size={size},{size}",
49
+ f"--screenshot={shot}", page.as_uri()],
50
+ check=True, capture_output=True,
51
+ )
52
+ data = shot.read_bytes()
53
+ out.write_bytes(data)
54
+ return data
55
+
56
+
57
+ def ico(images: dict[int, bytes]) -> bytes:
58
+ """ICO with PNG payloads (supported by every browser since IE Vista era)."""
59
+ header = struct.pack("<HHH", 0, 1, len(images))
60
+ entries, blobs, offset = b"", b"", 6 + 16 * len(images)
61
+ for size, png in sorted(images.items()):
62
+ entries += struct.pack("<BBBBHHII", size % 256, size % 256, 0, 0, 1, 32, len(png), offset)
63
+ blobs += png
64
+ offset += len(png)
65
+ return header + entries + blobs
66
+
67
+
68
+ tmp_dir = Path(tempfile.mkdtemp())
69
+ small = {s: render(MARK, s, tmp_dir / f"f{s}.png") for s in (16, 32, 48)}
70
+ (PUBLIC / "favicon.ico").write_bytes(ico(small))
71
+ (PUBLIC / "favicon-32.png").write_bytes(small[32])
72
+ render(full_bleed(0.86), 180, PUBLIC / "apple-touch-icon.png")
73
+ render(MARK, 192, PUBLIC / "icon-192.png")
74
+ render(MARK, 512, PUBLIC / "icon-512.png")
75
+ render(full_bleed(0.7), 512, PUBLIC / "icon-maskable-512.png")
76
+ for name in ("favicon.ico", "favicon-32.png", "apple-touch-icon.png", "icon-192.png", "icon-512.png", "icon-maskable-512.png"):
77
+ print(name, (PUBLIC / name).stat().st_size)