From ec2f3930ad60a358267e2d4a84fc20deb52dd8e3 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Wed, 3 Jun 2026 15:59:34 -0500 Subject: [PATCH] feat: proper logo, favicon, and pwa icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replace placeholder house svg with clean geometric house mark on indigo-700 (#4338CA) — roof triangle, body, door cutout - add favicon.svg with prefers-color-scheme dark variant - add icon-16/32 sizes; regenerate all pngs from svg via sharp - update BrandMark component to use inline svg house instead of 'f' - wire favicon + sized pngs into layout metadata - fix middleware to allow icon/manifest/favicon paths without auth (android launcher fetches icons in a cookieless system context) - fix getCurrentSession() to redirect to /login instead of throwing when session cookie is stale/invalid --- public/favicon.svg | 9 +++ public/icon-16.png | Bin 0 -> 335 bytes public/icon-180.png | Bin 496 -> 3115 bytes public/icon-192.png | Bin 547 -> 3606 bytes public/icon-32.png | Bin 0 -> 546 bytes public/icon-384.png | Bin 1289 -> 9133 bytes public/icon-512-maskable.png | Bin 1881 -> 10621 bytes public/icon-512.png | Bin 1881 -> 13572 bytes public/icon.svg | 14 ++--- public/manifest.webmanifest | 13 +++-- scripts/generate-icons.mjs | 106 +++++++++------------------------- src/app/layout.tsx | 5 ++ src/components/brand-mark.tsx | 13 ++++- src/lib/session.ts | 3 +- src/middleware.ts | 4 +- 15 files changed, 69 insertions(+), 98 deletions(-) create mode 100644 public/favicon.svg create mode 100644 public/icon-16.png create mode 100644 public/icon-32.png diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..bb21eb4 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/public/icon-16.png b/public/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe28ed836c0ed469feb4c4442380606d409ca5d GIT binary patch literal 335 zcmV-V0kHmwP)16;h zh}DP%cQD|yIn3ug7~?VEIRh@qnBa^5pFa8V|Hbp4{}aM4;WUH+n_y6L#``=W32fG0+ zwRir1c>f!0z%LvaBnMIhQj60IUp`|x9|k~j1jFD9wG8<1{`ddoi=Kfoh)uEq7tVfy zi-Fj*Gr$RwG2jBNb$9=FHQ)Q+T7UO{Ug|Y42C>0nb$3w>FhBK#qRixEajKmGoSiLB hb)dThuSTTo3;@MyuuLZdSLXl#002ovPDHLkV1fu0nEwC( literal 0 HcmV?d00001 diff --git a/public/icon-180.png b/public/icon-180.png index c2ddc726dc728ca6ac6170ce76e88f6bb5f08d10..f05509cace6904f35791076ac4de94d303f00960 100644 GIT binary patch literal 3115 zcmcIm`#;m)AAj3?%qkt#Ij_s}^?IH2!+9pz+g;cvDlZBEz&^{1=N$RE zc5ev_@g=fP=p1Yg920f3nF-Vy*_yp-c_SeEBZog!Z@ley0hb{_14lpkr6|K+h& zu%8}51X$=R4l?glWrW`g%1n!?`c0EgRM}XiOcjV5Ppf1&l|0%y4k!6wGEcP5w`RFz zx@cGqi{;eT#;<+>56#UE_7&$${G5wS$oa*P3rp};TEkraQIsVc zk9n1VDWK~22Awqvi&pAY*6#CzG4s+6MFOU1ium;A=uwOU`e1fl?WUhsvw)uG zNO80$odoY0>{AuvYN3Qa(34>rSQk3g{1tq3IDkvcoITYpo_#72!l)=|EAzQ?1m0ZK zv17VtD}pduGcNjFvgFEm3iTR@!GyVNVK4UxMXs&V6w&DRCAWo?>7xz@R&1qF0vcph z$@pa*L)u)jn$DpQ#*ul3U6M_cDpKQf&kf!OG}dA`^;tCNR?rYkR$?x{V zD03oXK-s{vjj{W2@Ra`X!0c0B!Dj7XBuMfr`p2!%fuepJLjGzyXNd?;oga{{gd2G@ytb)~UYz*~mEQn!QE2q+J$Ht;Y&wR>dK@r&-_l8W*^i6({ zAV_G(2ZXi#(|+ut$P0R~yB)fWXYlrCtcM^~KBY}D4UQGCetU*Z3 z79Z+vbxYR0mOGk;2!jp*3gpCWKDUk1-@j)41zXx&ng)xKqJ{3GB<_+->SEM?`=rh2 zg=T!bmFw^0vG1-BSaThq`Fq4WHvPpkl@^-Lt5r9EFqzYSQqK$wrlCul%U)p8}4C#b4yc1ovU}ejZND7pv;CHqrdlNS@%7| zin8We%`|~H=!T$tY4z&M-FB`}b=(3Gx8{6&U>`GfYdL&z#cuw)N!sV3p$k2V+sUk0 zfJ_m&w0mrV#`Yt1=fy@JGV~vQIED7jv-&cuk#WdxYfcb__GDz#o#%|Ya+?l!znpZ# zDb3fE6xa{k;B9hwnbkuh>boMFWs6gU)|;V8RG(-t9Z>7d`EsrqtmRWw-r^C#jQg8$ECW(ywtGEL4?3n8c+4;W z`lli#@VTNz@mCxr$fGeSRY9VSM6OSTF6Glw&eROtsvxFjIftw1A+|8XZQQ;?>|Zae zDT+IIq;HEE9xh4U2keiRPx5f%#Ap`1@-;uLCXosuz)pcdDstcoY%t(hDN?i3ftrE za?_tCA2r-fjS18h|M#_aM*vyn+XO$$Z@|}eP9uJUA(ciyIX0p%N^60?D=4eGn`(Aj zSA2$Bwy5!L&sojn`|9{Um6iH_hYx#ILhAWJL43G}rR8Cy#J#Aq;|3R^u>8CNzNKh` zVZRkXE_hpa=m8^e>7@ZPLU+<;bFxbHQ{;9N(q`zXV#}Yex$oxss-}=kEYt&_l}3I! zy%8nEw7(8wUdg}j_~@X#Yw8mjurC08dL7tDF?STF2;8}tEPx?# z|LKu(@$He&BG}mj{T06)Wa7%wOgUSSBB$5uwVi3}@FGG41pcmjG1D^Vl)PMUJ*?d8+)4&15;}ex0@i2 zfxUUd=X8x~a0@57TZ+Q`AMmIV zyhlYfr2(V?(7>xG%NqFcM<>w1l7a$~caEU4bkY`=&~EHOC_&U7n})P!z+yCBr5sV` z^Pm+0$@=Y!{%!J8s??l1{jhF``?O&g`JOHNe&e&)F{;w3D3n0$ze=ILGABU)E zf;{BY1qBN2`Qd$PSzSYefq)aiiu;f0g3U@4-1cc_$ki9E;^MLblD-c!ni}BYWQ7B> zyDdsDvOLvb8e}g?5@O&&$M9y1_9 zyW-=i`g*PG0hD&{N#5V6b}(+lr9U+!v?nqlI=J(q^Yr2!0Fzj)?R7Z?PJ0 zBe@|g9qA*V8}n8}a)_&w_Xqh5&EfRtUB+2A5pO+DakJM$6J;_rsD);n`co2Gp<&T}fuoI0;bAMY#>3avFdSWL XIkC4QUwx$b>>sc^Z+EW3%rowPcA(KD literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD2}o{QKQWbofpNR1i(^Q|oVS+@c^MQK4j5ed z_$Z#i&fq|v#jKD6eZ8jUF}=@OYfj6k6i+zgXvBVUlrbm*fsfVsb-T@q=JmjsWbkzL Kb6Mw<&;$UZ=&|?! diff --git a/public/icon-192.png b/public/icon-192.png index e755168040e86fad2857a221116320f604d501c3..3d956e6e64614e5c46f2d551c27f961eed2ef492 100644 GIT binary patch literal 3606 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 literal 0 HcmV?d00001 diff --git a/public/icon-384.png b/public/icon-384.png index 1498832dfcfa51c942c077b1de9fab775bbe6e81..38f4c7ba42e37ad8d66f22ebbaa7fde23ab2158d 100644 GIT binary patch literal 9133 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 WVJSIEj^9=Z9NcHKw_vw({67F6n}jX^ literal 1881 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_pKoPZ!6KiaBqu8uBt2@Eq8% z=(hDghGWSFRYxWu7)=GE8DX?67%dJ*M2%3p Y_-n+>#EX*}fK?`gr>mdKI;Vst0HBgi-v9sr diff --git a/public/icon-512.png b/public/icon-512.png index 745c0b98851652d830667a5edf1ce2fb5fa2dc06..99613a1f43c0d1f4650274341f98748eb057bb00 100644 GIT binary patch literal 13572 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 literal 1881 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_pKoPZ!6KiaBqu8uBt2@Eq8% z=(hDghGWSFRYxWu7)=GE8DX?67%dJ*M2%3p Y_-n+>#EX*}fK?`gr>mdKI;Vst0HBgi-v9sr diff --git a/public/icon.svg b/public/icon.svg index b495eef..0723161 100644 --- a/public/icon.svg +++ b/public/icon.svg @@ -1,12 +1,6 @@ - - - - - - - - - - + + + + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index b470957..c21203b 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -4,23 +4,26 @@ "description": "Family coordination app", "start_url": "/", "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#4F46E5", + "background_color": "#4338CA", + "theme_color": "#4338CA", "icons": [ { "src": "/icon-192.png", "sizes": "192x192", - "type": "image/png" + "type": "image/png", + "purpose": "any" }, { "src": "/icon-384.png", "sizes": "384x384", - "type": "image/png" + "type": "image/png", + "purpose": "any" }, { "src": "/icon-512.png", "sizes": "512x512", - "type": "image/png" + "type": "image/png", + "purpose": "any" }, { "src": "/icon-512-maskable.png", diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs index bc4e57e..a0eff59 100644 --- a/scripts/generate-icons.mjs +++ b/scripts/generate-icons.mjs @@ -1,100 +1,48 @@ /** - * Generates placeholder PNG icons for the famapp PWA. + * Rasterizes public/icon.svg into all required PNG icon sizes. * * Usage: node scripts/generate-icons.mjs * * Produces: + * public/icon-16.png + * public/icon-32.png + * public/icon-180.png (apple-touch-icon) * public/icon-192.png * public/icon-384.png * public/icon-512.png - * public/icon-512-maskable.png (same image; OS applies its own mask) - * public/icon-180.png (apple-touch-icon) - * - * All images are solid indigo (#4F46E5) squares — replace with a real - * branded export from icon.svg when assets are finalised. + * public/icon-512-maskable.png (square bg, content in safe zone) */ -import { deflateSync } from "zlib"; -import { writeFileSync } from "fs"; -import { resolve, dirname } from "path"; +import sharp from "sharp"; +import { readFileSync } from "fs"; import { fileURLToPath } from "url"; +import { dirname, join } from "path"; -const __dir = dirname(fileURLToPath(import.meta.url)); -const publicDir = resolve(__dir, "../public"); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pub = join(__dirname, "..", "public"); -// Build CRC-32 lookup table once. -const CRC_TABLE = new Uint32Array(256); -for (let i = 0; i < 256; i++) { - let c = i; - for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; - CRC_TABLE[i] = c; -} +const svg = readFileSync(join(pub, "icon.svg")); -function crc32(buf) { - let crc = 0xffffffff; - for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); - return (crc ^ 0xffffffff) >>> 0; -} - -function u32(n) { - const b = Buffer.alloc(4); - b.writeUInt32BE(n, 0); - return b; -} - -function pngChunk(type, data) { - const typeBytes = Buffer.from(type, "ascii"); - const crc = u32(crc32(Buffer.concat([typeBytes, data]))); - return Buffer.concat([u32(data.length), typeBytes, data, crc]); -} - -/** - * Returns a Buffer containing a valid PNG of size×size filled with (r, g, b). - */ -function solidPNG(size, r, g, b) { - const PNG_SIG = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); - - const ihdr = pngChunk( - "IHDR", - Buffer.concat([ - u32(size), - u32(size), - Buffer.from([8, 2, 0, 0, 0]), // 8-bit RGB, no interlace - ]), - ); - - // One filter byte (0 = None) followed by size×3 RGB bytes per row. - const rowLen = 1 + size * 3; - const raw = Buffer.alloc(size * rowLen); - for (let y = 0; y < size; y++) { - const base = y * rowLen; - raw[base] = 0; - for (let x = 0; x < size; x++) { - raw[base + 1 + x * 3] = r; - raw[base + 2 + x * 3] = g; - raw[base + 3 + x * 3] = b; - } - } - - const idat = pngChunk("IDAT", deflateSync(raw)); - const iend = pngChunk("IEND", Buffer.alloc(0)); - - return Buffer.concat([PNG_SIG, ihdr, idat, iend]); -} - -// Indigo-600 (#4F46E5) -const [R, G, B] = [0x4f, 0x46, 0xe5]; - -const icons = [ +const sizes = [ + { name: "icon-16.png", size: 16 }, + { name: "icon-32.png", size: 32 }, + { name: "icon-180.png", size: 180 }, { name: "icon-192.png", size: 192 }, { name: "icon-384.png", size: 384 }, { name: "icon-512.png", size: 512 }, - { name: "icon-512-maskable.png", size: 512 }, - { name: "icon-180.png", size: 180 }, ]; -for (const { name, size } of icons) { - const out = resolve(publicDir, name); - writeFileSync(out, solidPNG(size, R, G, B)); +for (const { name, size } of sizes) { + await sharp(svg).resize(size, size).png().toFile(join(pub, name)); console.log(` ✓ public/${name} (${size}×${size})`); } + +// Maskable: remove rounded corners so the background fills the full canvas, +// allowing the OS to apply any mask shape without clipping the icon corners. +const maskableSvg = readFileSync(join(pub, "icon.svg"), "utf-8").replace('rx="96"', ""); + +await sharp(Buffer.from(maskableSvg)) + .resize(512, 512) + .png() + .toFile(join(pub, "icon-512-maskable.png")); +console.log(` ✓ public/icon-512-maskable.png (512×512 maskable)`); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1fe1246..ba61772 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -54,6 +54,11 @@ export const metadata: Metadata = { title: "famapp", }, icons: { + icon: [ + { url: "/favicon.svg", type: "image/svg+xml" }, + { url: "/icon-32.png", sizes: "32x32", type: "image/png" }, + { url: "/icon-16.png", sizes: "16x16", type: "image/png" }, + ], apple: "/icon-180.png", }, }; diff --git a/src/components/brand-mark.tsx b/src/components/brand-mark.tsx index a72aebb..7e07c1f 100644 --- a/src/components/brand-mark.tsx +++ b/src/components/brand-mark.tsx @@ -1,12 +1,23 @@ import { cn } from "@/lib/utils"; export function BrandMark({ size = "md", className }: { size?: "sm" | "md"; className?: string }) { + const iconSize = size === "sm" ? 14 : 17; return ( ); } diff --git a/src/lib/session.ts b/src/lib/session.ts index bebf3ab..cf51637 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,4 +1,5 @@ import { eq } from "drizzle-orm"; +import { redirect } from "next/navigation"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; import { householdMembers, households, users } from "@/modules/_core/schema"; @@ -13,7 +14,7 @@ export interface CurrentSession { export async function getCurrentSession(): Promise { const session = await auth(); - if (!session?.user?.id) throw new Error("Not authenticated"); + if (!session?.user?.id) redirect("/login"); const [row] = await db .select({ diff --git a/src/middleware.ts b/src/middleware.ts index 5519687..6fb1281 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,8 +1,8 @@ import { NextResponse, type NextRequest } from "next/server"; import { consume } from "@/lib/rate-limit"; -const PUBLIC_PREFIXES = ["/api/auth/", "/s/"]; -const PUBLIC_PATHS = new Set(["/login"]); +const PUBLIC_PREFIXES = ["/api/auth/", "/s/", "/icon-", "/favicon"]; +const PUBLIC_PATHS = new Set(["/login", "/manifest.webmanifest", "/offline.html"]); const SESSION_COOKIE_NAMES = ["authjs.session-token", "__Secure-authjs.session-token"]; // Token-prefix length used as part of the rate-limit bucket key.