From 1ea0757b4791dcb79566b42e950b60b4dd7e449d Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 01:44:44 +0500 Subject: [PATCH 01/52] Update design_guidelines.md --- design_guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/design_guidelines.md b/design_guidelines.md index c02af4d6..50d1b292 100644 --- a/design_guidelines.md +++ b/design_guidelines.md @@ -13,6 +13,7 @@ --- + ## Typography System **Font Stack**: From c970996a10a1715e754291fab688702fd4fba00c Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 01:47:45 +0500 Subject: [PATCH 02/52] Updated .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8ac9df1a..f6e1e6ba 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ vite.config.ts.* test-results playwright-report attached_assets +.env From a079630db5599bcedc27b9bd402ff973af1b2993 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 01:52:16 +0500 Subject: [PATCH 03/52] Updated vite.config.ts --- vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 303602be..adb423e1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -44,6 +44,9 @@ export default defineConfig({ "@assets": path.resolve(import.meta.dirname, "attached_assets"), }, }, + optimizeDeps: { + include: ["modern-screenshot"], + }, root: path.resolve(import.meta.dirname, "client"), build: { outDir: path.resolve(import.meta.dirname, "dist/public"), From 4b46e65559e7d0dbb7c02afe952696832566293b Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:41:41 +0500 Subject: [PATCH 04/52] Updated vite.config.ts --- vite.config.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index adb423e1..68f3cbc1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,14 +8,6 @@ export default defineConfig({ plugins: [ react(), runtimeErrorOverlay(), - ...(process.env.NODE_ENV !== "production" && - process.env.REPL_ID !== undefined - ? [ - await import("@replit/vite-plugin-cartographer").then(m => - m.cartographer() - ), - ] - : []), viteStaticCopy({ targets: [ { From 50fc037605685e36c51fed937cbf39412f207926 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:48:24 +0500 Subject: [PATCH 05/52] Added themeicon.switch --- client/assets/header-icons/themeicon.switch | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/themeicon.switch diff --git a/client/assets/header-icons/themeicon.switch b/client/assets/header-icons/themeicon.switch new file mode 100644 index 00000000..e69de29b From 225da624a21f3ea9457c88525d17bbd3b0a54ffd Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:48:36 +0500 Subject: [PATCH 06/52] Added themeicon.svg --- client/assets/header-icons/{themeicon.switch => themeicon.svg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/assets/header-icons/{themeicon.switch => themeicon.svg} (100%) diff --git a/client/assets/header-icons/themeicon.switch b/client/assets/header-icons/themeicon.svg similarity index 100% rename from client/assets/header-icons/themeicon.switch rename to client/assets/header-icons/themeicon.svg From 0b0c2391482dd4c8a7835c02441651a8318a2fa2 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:48:58 +0500 Subject: [PATCH 07/52] Added header-minimize.svg --- client/assets/header-icons/header-minimize.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/header-minimize.svg diff --git a/client/assets/header-icons/header-minimize.svg b/client/assets/header-icons/header-minimize.svg new file mode 100644 index 00000000..e69de29b From ed0b99fbeafe72bcc8af84d74a944c6443705649 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:49:18 +0500 Subject: [PATCH 08/52] Added demo-button-play.svg --- client/assets/header-icons/demo-button-play.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/demo-button-play.svg diff --git a/client/assets/header-icons/demo-button-play.svg b/client/assets/header-icons/demo-button-play.svg new file mode 100644 index 00000000..e69de29b From abece4ee6168cf2952951cb9fa76d57862a71295 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:49:32 +0500 Subject: [PATCH 09/52] Added search-icon.svg --- client/assets/header-icons/search-icon.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/search-icon.svg diff --git a/client/assets/header-icons/search-icon.svg b/client/assets/header-icons/search-icon.svg new file mode 100644 index 00000000..e69de29b From c7dbaf4cb0b2b884d4af4dc726e8c826fc606d96 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:49:52 +0500 Subject: [PATCH 10/52] Added side-panel-switch-icon.svg --- client/assets/header-icons/side-panel-switch-icon.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/side-panel-switch-icon.svg diff --git a/client/assets/header-icons/side-panel-switch-icon.svg b/client/assets/header-icons/side-panel-switch-icon.svg new file mode 100644 index 00000000..e69de29b From 78bf2f69e1b2d406629a32ac570a7cd626544103 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:50:47 +0500 Subject: [PATCH 11/52] Added icon.png --- client/assets/header-icons/icon.png | Bin 0 -> 1698 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/assets/header-icons/icon.png diff --git a/client/assets/header-icons/icon.png b/client/assets/header-icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0d6cfde4bd8912fd5c52eb12c8488479d804ee GIT binary patch literal 1698 zcmV;T23`4yP)W8k8Y=BTNYYYYenQfT$Dg!P{7zA30@)55Cal}LE#SxMj;|mf{8&1 zAS$92LVyGip(x;{)|LXgw9t0D+ij=Qz3*dYc6_g~4aF@-Vl***nJ@2s-}~PCe)GNe z&0xrX+{OULGVqUOU}7z`%vxbwWu0P`-_O0iZvb13wc21NR~RYnTRIE5*;t}^-D=k@ z6v?I6_(H)MX%D$79F{r&fdaYfbI$-KO7jep>WsP3Bb6k~W{z&hU+8qHV&$SyP{?Msle{8t`ql{cTnucbQxhj7r z^I1HSTHEId9(`n9)#IZ%j|xy%SBLvOp}nbiX6k5*N3#Q148_obTt&1~6HV`?k{QG1 z%}CK$MU8M{x38%VobzHh9ui!H#Yuzw;*fr)UML7yGM1@gvz)EH#rGWmC=~NA3IN}} zuPgN0?F+`&P+OF=Od8T}?~4L-g)Z9? zk@VzSosCxYp<4U&?PrfQGUFy$*1$P%+2Xhe%LpjKYmKS~IVo)|sW2W;l0-Y03qv2C zFAz*$;z>9zCPe{~(d-0{;}viLcr)HyuG{IH9a~vnU(Z9^jh3EosS4v-p@86|Ms^j$ zuuvkFt|_aud`No&FDgjlid-e@1ik_gBp%MpfPFD33Xn^)+97A1Np+I(h1b#P8WNP_ zdaYi$+UayC{hq)sYlZ%4o<|vQ1U|dNx?Us|^{MsBO%jQy-C$LpkSU~oxKBRM+F)Ny z{svH9uGV5=@t_?SVXV$f{^)c%MUik~n%1J~GneUOS*b7)Zsu)g&c!3iCHhkJDuYFT zs=%|9xlH?qR3`1Dw4x6bYQlx%7zaxN0aZ{vb=)ijLBRJ48E%7thO|fL*(YN%;su`L zYM~9Jmi2cAHZ(T5d|g-R&wIOqTYOyuTdui0hviCX%cNMxJ2;Sh%3P`1 zmrrwpN5JMexk!v%2N!BTA(wm!&W)5+Xqa2LL5ZS=N8VIT|(6H3V;xr+Eg zu958nzWWtQ=@FhSNH~^L#{!8(trt7$6m<@Y5Urc9Y4h75xM<7-Rj_j-bJNQtnc z@nlEbHeR<`YbHOzajamXv@d}-*{C;b_9|7xg@+uv_p9vI1QMG7*z;nk=%z}m_^G_wxE%VmHF=IFLb=P)9K8Ks?g=_#NgeEh5Zt5b(19WfbYo<54-8+zdFJ{8B;CL{#R(VusQE!K9 zvdJuOe~?-k2_~NFa?@W`I;?Y`9efoz2_e4H*6hCqbtnNkd@7sa%y2viptY*^WCd)E zeY%~5MkpAL$x+bM8oVHp@;(uc%6OKqlBtNFsSE|mQIO&i^n|%o^AU!j2uOKKzyv%M z&n=Rvq+UIx^TK^_5snH4o`7R0R;yKBGEToyK}ya-eGs7SSFfwI(((fvO|Gw=R;`B< zxPZ_oxUab+zl3oIQ1EA4YN0fb@zLbDn_l;dT!tkzCN+(!6bS&Ei$}Aogk<_?iA}rE zY%whL^#{Taf?E%M&s8do(g*i+%yj4wN&ZdJpmg21>^})@g8*>Q+~96BmFtcq0)aQ? zE}B$gQe&6Y2IW!T&4E2$U))1?hkhBLV@EumsE76j4(LqEb6T7Ey!*U6X|?HhGXBVV ze_z5A8A$D>{mk*`jj+ev?7JjaN!|7;{hpb1GiB*W=oL$a;Y540w*kh4G7JEfrZeu= zUQcM#h11vR^XI#>k%7cA@KZrSHR$s>t|Xu3sC=F?6bb?wW)e>{A8$^>bpUdMAP6X! z*JV<<%3Ovk%Vk*;^h-q2wP%n0&baDbneH}bTT4S*Gh8zyBLIes|7Jf1FqVP;e+KSW sQGWse0RR7bzsdXn000I_L_t&o0DPpwUsCx=9smFU07*qoM6N<$f{;Kej{pDw literal 0 HcmV?d00001 From a67d54b518add77ec55219c1faee6575468df81a Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:55:25 +0500 Subject: [PATCH 12/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 273 +++++++++++++----------- 1 file changed, 146 insertions(+), 127 deletions(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 8ae7fdb6..c0b7c98b 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -1,4 +1,4 @@ -import { Search, Moon, Sun, Menu, X } from "lucide-react"; +import { Search, Moon, Sun, Menu, X, Play, ChevronLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -13,6 +13,7 @@ import { useSearch } from "@/hooks/use-search"; import { SearchResults } from "@/components/ui/search-results"; import { getToolsCount } from "@/data/tools"; import { useState, useRef, useEffect } from "react"; +import { useDemo } from "@/hooks/use-demo-hook"; interface HeaderProps { onMenuClick: () => void; @@ -32,6 +33,7 @@ export function Header({ onMenuClick }: HeaderProps) { const [showResults, setShowResults] = useState(false); const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false); const searchRef = useRef(null); + const { startDemo, totalTools } = useDemo(); const toggleTheme = () => { if (theme === "dark") { @@ -136,71 +138,68 @@ export function Header({ onMenuClick }: HeaderProps) { return ( -
-
-
- {/* Logo and Title - Always leftmost */} -
-
- {/* Blue FD Logo - toggles menu */} -
{ - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - onMenuClick(); - } - }} - aria-label="Toggle navigation menu" - data-testid="logo-menu-toggle" - > - - FD - -
+
+
+ {/* Left Section - Logo and Title */} +
+
- {/* Text Logo - links to homepage */} -
- -

- FreeDevTool.App -

- -

- - Secure - {" "} - Developer Tools -

+
+ {/* Logo with cyan background */} + +
+ FreeDevTool
+ + + {/* Title */} +
+ +

+ FreeDevTool +

+ +

+ Secure Developer Tools +

- {/* Search and Actions */} -
- {/* Desktop Search */} -
- + {/* Collapse/Expand Icon */} + + + + + +

Toggle Menu (Ctrl+M)

+
+
+
+ + {/* Right Section - Search and Controls */} +
+ {/* Search Bar */} +
+
+ handleSearchChange(e.target.value)} onKeyDown={handleSearchKeyDown} - className="pl-10 pr-8 w-64 bg-slate-50 dark:bg-slate-700 border-slate-300 dark:border-slate-600" + className="h-12 pl-10 pr-8 bg-[#ffffff12] border-0 rounded-xl text-[#AEB9E1] placeholder:text-[#AEB9E199] text-sm shadow-[0px_2px_4px_rgba(1,5,17,0.2)] focus-visible:ring-1 focus-visible:ring-[#00C2FF]" data-testid="search-input" onFocus={() => setShowResults(searchQuery.trim().length > 0)} /> @@ -214,7 +213,7 @@ export function Header({ onMenuClick }: HeaderProps) { className="absolute right-2 top-1/2 transform -translate-y-1/2 h-6 w-6 p-0 hover:bg-transparent" data-testid="desktop-clear-search" > - + @@ -222,70 +221,90 @@ export function Header({ onMenuClick }: HeaderProps) { ) : null} - {showResults ? ( - - ) : null}
+ {showResults ? ( + + ) : null} +
- {/* Mobile Search Toggle */} + {/* Mobile Search Toggle */} +

Open Search (Ctrl+S)

+
+ + {/* Right Side Controls */} +
+ {/* Demo Tour Button */} + + + + + +

Start Interactive Demo Tour

+
+
{/* Theme Toggle */} - + -

Switch to {theme === "dark" ? "Light" : "Dark"} Mode

+

Switch to {theme === "dark" ? "Light" : "Dark"} Mode (Ctrl+D)

- {/* Hamburger Menu - Always visible */} + {/* Menu Button */} - + +

Toggle Menu (Ctrl+M)

@@ -293,55 +312,55 @@ export function Header({ onMenuClick }: HeaderProps) {
+
- {/* Mobile Search Bar */} - {isMobileSearchOpen ? ( -
-
- - handleSearchChange(e.target.value)} - onKeyDown={handleSearchKeyDown} - className="pl-10 pr-8 w-full bg-slate-50 dark:bg-slate-700 border-slate-300 dark:border-slate-600" - data-testid="mobile-search-input" - autoFocus - /> - {searchQuery ? ( - - - - - -

Clear Search

-
-
- ) : null} -
- {showResults ? ( - + {/* Mobile Search Bar */} + {isMobileSearchOpen ? ( +
+
+ + handleSearchChange(e.target.value)} + onKeyDown={handleSearchKeyDown} + className="h-12 pl-10 pr-8 w-full bg-[#ffffff12] border-0 rounded-xl text-[#AEB9E1] placeholder:text-[#AEB9E199] text-sm" + data-testid="mobile-search-input" + autoFocus + /> + {searchQuery ? ( + + + + + +

Clear Search

+
+
) : null}
- ) : null} -
+ {showResults ? ( + + ) : null} +
+ ) : null}
); From 15a01396903a9c4791d8dec88f1368b7af22316d Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:57:32 +0500 Subject: [PATCH 13/52] Updated header-minimize.svg --- client/assets/header-icons/header-minimize.svg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/assets/header-icons/header-minimize.svg b/client/assets/header-icons/header-minimize.svg index e69de29b..14985561 100644 --- a/client/assets/header-icons/header-minimize.svg +++ b/client/assets/header-icons/header-minimize.svg @@ -0,0 +1,5 @@ + + + + + From 0d136b7e518f7947f00de92be65cee07ea71fcb8 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:57:46 +0500 Subject: [PATCH 14/52] Updated themeicon.svg --- client/assets/header-icons/themeicon.svg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/assets/header-icons/themeicon.svg b/client/assets/header-icons/themeicon.svg index e69de29b..5a12dcb4 100644 --- a/client/assets/header-icons/themeicon.svg +++ b/client/assets/header-icons/themeicon.svg @@ -0,0 +1,3 @@ + + + From fd43a9fdf0f8984a733e41c56cf6d54138551bce Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:58:00 +0500 Subject: [PATCH 15/52] Updated demo-button-play.svg --- client/assets/header-icons/demo-button-play.svg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/assets/header-icons/demo-button-play.svg b/client/assets/header-icons/demo-button-play.svg index e69de29b..b232e0f4 100644 --- a/client/assets/header-icons/demo-button-play.svg +++ b/client/assets/header-icons/demo-button-play.svg @@ -0,0 +1,4 @@ + + + + From cd22edce7731d5fcf84bd4f5758de46e0985f6a7 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:58:12 +0500 Subject: [PATCH 16/52] Updated search-icon.svg --- client/assets/header-icons/search-icon.svg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/assets/header-icons/search-icon.svg b/client/assets/header-icons/search-icon.svg index e69de29b..3ba5d18a 100644 --- a/client/assets/header-icons/search-icon.svg +++ b/client/assets/header-icons/search-icon.svg @@ -0,0 +1,3 @@ + + + From 236b1d0d9118b55487aa9c0cb6db02455c372ba0 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 07:58:25 +0500 Subject: [PATCH 17/52] Updated side-panel-switch-icon.svg --- client/assets/header-icons/side-panel-switch-icon.svg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/assets/header-icons/side-panel-switch-icon.svg b/client/assets/header-icons/side-panel-switch-icon.svg index e69de29b..8153fbba 100644 --- a/client/assets/header-icons/side-panel-switch-icon.svg +++ b/client/assets/header-icons/side-panel-switch-icon.svg @@ -0,0 +1,5 @@ + + + + + From ae37c4b1f827420e71b060022510d5a29f4addf1 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:02:16 +0500 Subject: [PATCH 18/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 336 ++++++++++++++++++++---- 1 file changed, 279 insertions(+), 57 deletions(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index c0b7c98b..3affd141 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -1,4 +1,4 @@ -import { Search, Moon, Sun, Menu, X, Play, ChevronLeft } from "lucide-react"; +import { X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -141,29 +141,75 @@ export function Header({ onMenuClick }: HeaderProps) {
{/* Left Section - Logo and Title */} -
-
+
+
-
+
{/* Logo with cyan background */} -
+
FreeDevTool
{/* Title */} -
+
-

+

FreeDevTool

-

+

Secure Developer Tools

@@ -174,11 +220,21 @@ export function Header({ onMenuClick }: HeaderProps) { @@ -188,21 +244,64 @@ export function Header({ onMenuClick }: HeaderProps) {
{/* Right Section - Search and Controls */} -
+
{/* Search Bar */} -
-
- - handleSearchChange(e.target.value)} - onKeyDown={handleSearchKeyDown} - className="h-12 pl-10 pr-8 bg-[#ffffff12] border-0 rounded-xl text-[#AEB9E1] placeholder:text-[#AEB9E199] text-sm shadow-[0px_2px_4px_rgba(1,5,17,0.2)] focus-visible:ring-1 focus-visible:ring-[#00C2FF]" - data-testid="search-input" - onFocus={() => setShowResults(searchQuery.trim().length > 0)} - /> +
+
+
+
+ Search +
+ handleSearchChange(e.target.value)} + onKeyDown={handleSearchKeyDown} + className="border-0 bg-transparent p-0 h-auto focus-visible:ring-0 focus-visible:ring-offset-0" + style={{ + opacity: 0.6, + color: '#AEB9E1', + fontSize: '14px', + fontFamily: 'Inter', + fontWeight: '400', + wordWrap: 'break-word' + }} + data-testid="search-input" + onFocus={() => setShowResults(searchQuery.trim().length > 0)} + /> +
{searchQuery ? ( @@ -235,16 +334,29 @@ export function Header({ onMenuClick }: HeaderProps) {
- +
+ Search +
+

Open Search (Ctrl+S)

@@ -253,19 +365,49 @@ export function Header({ onMenuClick }: HeaderProps) {
{/* Right Side Controls */} -
+
{/* Demo Tour Button */} @@ -278,15 +420,29 @@ export function Header({ onMenuClick }: HeaderProps) { @@ -299,11 +455,44 @@ export function Header({ onMenuClick }: HeaderProps) { @@ -320,18 +509,51 @@ export function Header({ onMenuClick }: HeaderProps) { className="md:hidden border-t border-[#111F49] p-4 relative z-50" ref={searchRef} > -
- - handleSearchChange(e.target.value)} - onKeyDown={handleSearchKeyDown} - className="h-12 pl-10 pr-8 w-full bg-[#ffffff12] border-0 rounded-xl text-[#AEB9E1] placeholder:text-[#AEB9E199] text-sm" - data-testid="mobile-search-input" - autoFocus - /> +
+
+
+ Search +
+ handleSearchChange(e.target.value)} + onKeyDown={handleSearchKeyDown} + className="border-0 bg-transparent p-0 h-auto focus-visible:ring-0 focus-visible:ring-offset-0 flex-1" + style={{ + opacity: 0.6, + color: '#AEB9E1', + fontSize: '14px', + fontFamily: 'Inter', + fontWeight: '400', + wordWrap: 'break-word' + }} + data-testid="mobile-search-input" + autoFocus + /> +
{searchQuery ? ( @@ -339,7 +561,7 @@ export function Header({ onMenuClick }: HeaderProps) { variant="ghost" size="sm" onClick={clearSearch} - className="absolute right-2 top-1/2 transform -translate-y-1/2 h-6 w-6 p-0 hover:bg-transparent" + className="h-6 w-6 p-0 hover:bg-transparent flex-shrink-0" data-testid="mobile-clear-search" > From 79b2ca555b9c821f8ec6591cbc245049d633085c Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:04:29 +0500 Subject: [PATCH 19/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 3affd141..af3faa6d 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -174,7 +174,8 @@ export function Header({ onMenuClick }: HeaderProps) { padding: '5px', background: '#00C2FF', borderRadius: '8px', - gap: '10px' + gap: '10px', + radius: 12px, }} > Date: Wed, 7 Jan 2026 08:12:25 +0500 Subject: [PATCH 20/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index af3faa6d..3affd141 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -174,8 +174,7 @@ export function Header({ onMenuClick }: HeaderProps) { padding: '5px', background: '#00C2FF', borderRadius: '8px', - gap: '10px', - radius: 12px, + gap: '10px' }} > Date: Wed, 7 Jan 2026 08:17:56 +0500 Subject: [PATCH 21/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 3affd141..7b6907d8 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -197,7 +197,7 @@ export function Header({ onMenuClick }: HeaderProps) { fontWeight: '700', wordWrap: 'break-word' }} - > + /> FreeDevTool From 71bd2cd1c76d58a85e59cb8af48c41f1851fc157 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:18:23 +0500 Subject: [PATCH 22/52] Updated icon.png --- client/assets/header-icons/icon.png | Bin 1698 -> 148 bytes client/src/components/layout/Header.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/client/assets/header-icons/icon.png b/client/assets/header-icons/icon.png index 6e0d6cfde4bd8912fd5c52eb12c8488479d804ee..71e03a618feb48ca3062f57408b8c9bc10c35c39 100644 GIT binary patch literal 148 zcmWN^OAdlC5CG5W8k8Y=BTNYYYYenQfT$Dg!P{7zA30@)55Cal}LE#SxMj;|mf{8&1 zAS$92LVyGip(x;{)|LXgw9t0D+ij=Qz3*dYc6_g~4aF@-Vl***nJ@2s-}~PCe)GNe z&0xrX+{OULGVqUOU}7z`%vxbwWu0P`-_O0iZvb13wc21NR~RYnTRIE5*;t}^-D=k@ z6v?I6_(H)MX%D$79F{r&fdaYfbI$-KO7jep>WsP3Bb6k~W{z&hU+8qHV&$SyP{?Msle{8t`ql{cTnucbQxhj7r z^I1HSTHEId9(`n9)#IZ%j|xy%SBLvOp}nbiX6k5*N3#Q148_obTt&1~6HV`?k{QG1 z%}CK$MU8M{x38%VobzHh9ui!H#Yuzw;*fr)UML7yGM1@gvz)EH#rGWmC=~NA3IN}} zuPgN0?F+`&P+OF=Od8T}?~4L-g)Z9? zk@VzSosCxYp<4U&?PrfQGUFy$*1$P%+2Xhe%LpjKYmKS~IVo)|sW2W;l0-Y03qv2C zFAz*$;z>9zCPe{~(d-0{;}viLcr)HyuG{IH9a~vnU(Z9^jh3EosS4v-p@86|Ms^j$ zuuvkFt|_aud`No&FDgjlid-e@1ik_gBp%MpfPFD33Xn^)+97A1Np+I(h1b#P8WNP_ zdaYi$+UayC{hq)sYlZ%4o<|vQ1U|dNx?Us|^{MsBO%jQy-C$LpkSU~oxKBRM+F)Ny z{svH9uGV5=@t_?SVXV$f{^)c%MUik~n%1J~GneUOS*b7)Zsu)g&c!3iCHhkJDuYFT zs=%|9xlH?qR3`1Dw4x6bYQlx%7zaxN0aZ{vb=)ijLBRJ48E%7thO|fL*(YN%;su`L zYM~9Jmi2cAHZ(T5d|g-R&wIOqTYOyuTdui0hviCX%cNMxJ2;Sh%3P`1 zmrrwpN5JMexk!v%2N!BTA(wm!&W)5+Xqa2LL5ZS=N8VIT|(6H3V;xr+Eg zu958nzWWtQ=@FhSNH~^L#{!8(trt7$6m<@Y5Urc9Y4h75xM<7-Rj_j-bJNQtnc z@nlEbHeR<`YbHOzajamXv@d}-*{C;b_9|7xg@+uv_p9vI1QMG7*z;nk=%z}m_^G_wxE%VmHF=IFLb=P)9K8Ks?g=_#NgeEh5Zt5b(19WfbYo<54-8+zdFJ{8B;CL{#R(VusQE!K9 zvdJuOe~?-k2_~NFa?@W`I;?Y`9efoz2_e4H*6hCqbtnNkd@7sa%y2viptY*^WCd)E zeY%~5MkpAL$x+bM8oVHp@;(uc%6OKqlBtNFsSE|mQIO&i^n|%o^AU!j2uOKKzyv%M z&n=Rvq+UIx^TK^_5snH4o`7R0R;yKBGEToyK}ya-eGs7SSFfwI(((fvO|Gw=R;`B< zxPZ_oxUab+zl3oIQ1EA4YN0fb@zLbDn_l;dT!tkzCN+(!6bS&Ei$}Aogk<_?iA}rE zY%whL^#{Taf?E%M&s8do(g*i+%yj4wN&ZdJpmg21>^})@g8*>Q+~96BmFtcq0)aQ? zE}B$gQe&6Y2IW!T&4E2$U))1?hkhBLV@EumsE76j4(LqEb6T7Ey!*U6X|?HhGXBVV ze_z5A8A$D>{mk*`jj+ev?7JjaN!|7;{hpb1GiB*W=oL$a;Y540w*kh4G7JEfrZeu= zUQcM#h11vR^XI#>k%7cA@KZrSHR$s>t|Xu3sC=F?6bb?wW)e>{A8$^>bpUdMAP6X! z*JV<<%3Ovk%Vk*;^h-q2wP%n0&baDbneH}bTT4S*Gh8zyBLIes|7Jf1FqVP;e+KSW sQGWse0RR7bzsdXn000I_L_t&o0DPpwUsCx=9smFU07*qoM6N<$f{;Kej{pDw diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 7b6907d8..3affd141 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -197,7 +197,7 @@ export function Header({ onMenuClick }: HeaderProps) { fontWeight: '700', wordWrap: 'break-word' }} - /> + > FreeDevTool From 68bb4c3763f1fda2e19e2ba2193f2f7a3ad1d5a3 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:26:45 +0500 Subject: [PATCH 23/52] Updated Layout.tsx --- client/src/components/layout/Header.tsx | 37 +++++++++---------------- client/src/components/layout/Layout.tsx | 1 + 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 3affd141..a02bbf64 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -17,9 +17,10 @@ import { useDemo } from "@/hooks/use-demo-hook"; interface HeaderProps { onMenuClick: () => void; + onHeaderMinimize: () => void; } -export function Header({ onMenuClick }: HeaderProps) { +export function Header({ onMenuClick, onHeaderMinimize }: HeaderProps) { const { theme, setTheme } = useTheme(); const { searchQuery, @@ -450,11 +451,11 @@ export function Header({ onMenuClick }: HeaderProps) { - {/* Menu Button */} + {/* Header Minimize Button */} -

Toggle Menu (Ctrl+M)

+

Minimize Header

diff --git a/client/src/components/layout/Layout.tsx b/client/src/components/layout/Layout.tsx index dde2ee42..a109adfd 100644 --- a/client/src/components/layout/Layout.tsx +++ b/client/src/components/layout/Layout.tsx @@ -220,6 +220,7 @@ export function Layout({ children }: LayoutProps) { setMobileMenuOpen(!mobileMenuOpen); } }} + onHeaderMinimize={() => setHeaderCollapsed(!headerCollapsed)} />
From 465b0b5ea489a07c27e18cdd396a4a0396cf56e3 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:29:53 +0500 Subject: [PATCH 24/52] Updated Header.tsx --- client/src/components/layout/Header.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index a02bbf64..9f006473 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -216,25 +216,30 @@ export function Header({ onMenuClick, onHeaderMinimize }: HeaderProps) {
- {/* Collapse/Expand Icon */} + {/* Menu Toggle Icon */} From 7dc501c55b442755cc1f010bf3f485ef31198a3e Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:32:27 +0500 Subject: [PATCH 25/52] Updated vite.config.ts --- vite.config.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 68f3cbc1..01a03de4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,9 +36,6 @@ export default defineConfig({ "@assets": path.resolve(import.meta.dirname, "attached_assets"), }, }, - optimizeDeps: { - include: ["modern-screenshot"], - }, root: path.resolve(import.meta.dirname, "client"), build: { outDir: path.resolve(import.meta.dirname, "dist/public"), From 14c4a2ce7a0c95e1b7c21ba6fd8e75020d825151 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:37:05 +0500 Subject: [PATCH 26/52] Added home-v2.tsx --- client/src/pages/V2/home-v2.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/src/pages/V2/home-v2.tsx diff --git a/client/src/pages/V2/home-v2.tsx b/client/src/pages/V2/home-v2.tsx new file mode 100644 index 00000000..e69de29b From 859ca287e41c8aa10d1432ead355e64161ef6608 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 08:37:06 +0500 Subject: [PATCH 27/52] Updated vite.config.ts --- vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 01a03de4..96dc168f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -29,6 +29,9 @@ export default defineConfig({ ], }), ], + optimizeDeps: { + exclude: ["modern-screenshot"], + }, resolve: { alias: { "@": path.resolve(import.meta.dirname, "client", "src"), From b7e862a04b09760e98f7ad6d8bb9130872520525 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 09:02:00 +0500 Subject: [PATCH 28/52] Updated App.tsx --- client/src/App.tsx | 6 + client/src/pages/V2/components/Content-v2.tsx | 107 ++++++++++ client/src/pages/V2/components/Header-v2.tsx | 59 ++++++ .../src/pages/V2/components/SideMenu-v2.tsx | 198 ++++++++++++++++++ client/src/pages/V2/home-v2.tsx | 15 ++ 5 files changed, 385 insertions(+) create mode 100644 client/src/pages/V2/components/Content-v2.tsx create mode 100644 client/src/pages/V2/components/Header-v2.tsx create mode 100644 client/src/pages/V2/components/SideMenu-v2.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 60f4b42c..4c3c5246 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -12,6 +12,9 @@ import { ScrollToTop } from "@/components/ScrollToTop"; // Eager load home page for fast initial load import Home from "@/pages/home"; +// V2 Pages +const HomeV2 = lazy(() => import("@/pages/V2/home-v2")); + // Lazy load all tool pages const DateConverter = lazy(() => import("@/pages/tools/date-converter")); const JsonYamlConverter = lazy( @@ -92,6 +95,9 @@ function Router() { {/* Home */} + {/* V2 Home */} + + {/* Conversions */} +
+
Welcome Back!
+
+
+
+
+
+
+
Your Data Never Leaves Your Device
+
No back-end design. All processing happens entirely in your browser. Your data is never sent to any server.
+
+
+
+
Open Source
+
+
+
Free
+
+
+
Offline
+
+
+
+
+
+
+
+
Conversions
+
+
6 Tools
+
+
+
+ {tools.map((tool, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
{tool.title}
+
+
{tool.shortcut}
+
+
+
{tool.description}
+
+
+
+
+
Open Tool
+
+
+
+
+
+
+
+
+
+ ))} +
+
+
+ ); +} diff --git a/client/src/pages/V2/components/Header-v2.tsx b/client/src/pages/V2/components/Header-v2.tsx new file mode 100644 index 00000000..7d3e5805 --- /dev/null +++ b/client/src/pages/V2/components/Header-v2.tsx @@ -0,0 +1,59 @@ +export default function HeaderV2() { + return ( +
+
+
+
+
+ Logo +
+
+
FreeDevTool
+
Secure Developer Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Search 47 tools... (Ctrl+S)
+
+
+
+
+
+
+
+
+
+
Demo Tour (47 tools)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/client/src/pages/V2/components/SideMenu-v2.tsx b/client/src/pages/V2/components/SideMenu-v2.tsx new file mode 100644 index 00000000..409524c4 --- /dev/null +++ b/client/src/pages/V2/components/SideMenu-v2.tsx @@ -0,0 +1,198 @@ +export default function SideMenuV2() { + return ( +
+
+
+
+
+
+
+
+
+
Home
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Conversions
+
+
+
+
+
+
+
+
+
+
+
Date Converter
+
+
+
+
+
JSON - YAML
+
+
+
+
+
Timezone Converter
+
+
+
+
+
Unit Converter
+
+
+
+
+
URL to JSON
+
+
+
+
+
CSV to JSON
+
+
+
+
+
Number Base Converter
+
+
+
+
+
+
+
+
+
+
+
Formatters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Encoders
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Text Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Time Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Financial Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Color Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
System
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Settings
+
+
+
+
+
+
+
+
Support
+
+
+
+
+ ); +} diff --git a/client/src/pages/V2/home-v2.tsx b/client/src/pages/V2/home-v2.tsx index e69de29b..85dc6999 100644 --- a/client/src/pages/V2/home-v2.tsx +++ b/client/src/pages/V2/home-v2.tsx @@ -0,0 +1,15 @@ +import HeaderV2 from "./components/Header-v2"; +import SideMenuV2 from "./components/SideMenu-v2"; +import ContentV2 from "./components/Content-v2"; + +export default function HomeV2() { + return ( +
+ +
+ + +
+
+ ); +} From 0c088c5161f68df85762acb4b98946520115a4a9 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Wed, 7 Jan 2026 09:34:06 +0500 Subject: [PATCH 29/52] Updated home-v2.tsx --- client/src/pages/V2/components/Content-v2.tsx | 84 +++---- client/src/pages/V2/components/Header-v2.tsx | 68 +++-- .../src/pages/V2/components/SideMenu-v2.tsx | 232 +++++++++--------- client/src/pages/V2/home-v2.tsx | 12 +- 4 files changed, 199 insertions(+), 197 deletions(-) diff --git a/client/src/pages/V2/components/Content-v2.tsx b/client/src/pages/V2/components/Content-v2.tsx index 9f07d320..da6da517 100644 --- a/client/src/pages/V2/components/Content-v2.tsx +++ b/client/src/pages/V2/components/Content-v2.tsx @@ -33,67 +33,67 @@ export default function ContentV2() { ]; return ( -
-
-
Welcome Back!
+
+
+
Welcome Back!
-
-
-
-
-
-
Your Data Never Leaves Your Device
-
No back-end design. All processing happens entirely in your browser. Your data is never sent to any server.
+
+
+
+
+
+
Your Data Never Leaves Your Device
+
No back-end design. All processing happens entirely in your browser. Your data is never sent to any server.
-
-
-
Open Source
+
+
+
Open Source
-
-
Free
+
+
Free
-
-
Offline
+
+
Offline
-
-
-
Conversions
-
-
6 Tools
+
+
+
Conversions
+
+
6 Tools
-
+
{tools.map((tool, index) => ( -
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
{tool.title}
+
+
+
+
{tool.title}
-
{tool.shortcut}
+
{tool.shortcut}
-
-
{tool.description}
+
+
{tool.description}
-
-
Open Tool
-
-
-
-
+
+
Open Tool
+
+
+
+
diff --git a/client/src/pages/V2/components/Header-v2.tsx b/client/src/pages/V2/components/Header-v2.tsx index 7d3e5805..df9b4fd1 100644 --- a/client/src/pages/V2/components/Header-v2.tsx +++ b/client/src/pages/V2/components/Header-v2.tsx @@ -1,54 +1,52 @@ export default function HeaderV2() { return ( -
-
-
-
-
- Logo +
+
+
+
+
+ Logo
-
-
FreeDevTool
-
Secure Developer Tools
+
+
FreeDevTool
+
Secure Developer Tools
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
Search 47 tools... (Ctrl+S)
+
Search 47 tools... (Ctrl+S)
-
-
-
-
-
+
+
+
+
+
-
Demo Tour (47 tools)
+
Demo Tour (47 tools)
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
diff --git a/client/src/pages/V2/components/SideMenu-v2.tsx b/client/src/pages/V2/components/SideMenu-v2.tsx index 409524c4..543e5f9e 100644 --- a/client/src/pages/V2/components/SideMenu-v2.tsx +++ b/client/src/pages/V2/components/SideMenu-v2.tsx @@ -1,195 +1,195 @@ export default function SideMenuV2() { return ( -
-
-
-
-
-
-
+
+
+
+
+
+
+
-
Home
+
Home
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Conversions
+
Conversions
-
-
-
+
+
+
-
-
-
-
Date Converter
+
+
+
+
Date Converter
-
-
-
JSON - YAML
+
+
+
JSON - YAML
-
-
-
Timezone Converter
+
+
+
Timezone Converter
-
-
-
Unit Converter
+
+
+
Unit Converter
-
-
-
URL to JSON
+
+
+
URL to JSON
-
-
-
CSV to JSON
+
+
+
CSV to JSON
-
-
-
Number Base Converter
+
+
+
Number Base Converter
-
-
-
-
-
+
+
+
+
+
-
Formatters
+
Formatters
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Encoders
+
Encoders
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Text Tools
+
Text Tools
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Time Tools
+
Time Tools
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Financial Tools
+
Financial Tools
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
Color Tools
+
Color Tools
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
System
+
System
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
Settings
+
+
Settings
-
-
-
+
+
+
-
-
Support
+
+
Support
diff --git a/client/src/pages/V2/home-v2.tsx b/client/src/pages/V2/home-v2.tsx index 85dc6999..7a405281 100644 --- a/client/src/pages/V2/home-v2.tsx +++ b/client/src/pages/V2/home-v2.tsx @@ -4,11 +4,15 @@ import ContentV2 from "./components/Content-v2"; export default function HomeV2() { return ( -
+
-
- - +
+
+ +
+
+ +
); From 49d759a45b6c5ad54562b434a4603370340f44b1 Mon Sep 17 00:00:00 2001 From: Aamirrafiqgfx Date: Sun, 11 Jan 2026 15:35:24 +0500 Subject: [PATCH 30/52] Initial commit with updated project code --- .dockerignore | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 202 + .github/ISSUE_TEMPLATE/config.yml | 11 + .github/ISSUE_TEMPLATE/documentation.yml | 139 + .github/ISSUE_TEMPLATE/feature_request.yml | 168 + .github/ISSUE_TEMPLATE/performance_issue.yml | 260 + .github/ISSUE_TEMPLATE/tool_request.yml | 213 + .github/PULL_REQUEST_TEMPLATE.md | 43 + .github/copilot-instructions.md | 78 + .github/workflows/ci.yml | 103 + .github/workflows/release.yml | 160 + .gitignore | 11 + .prettierignore | 6 + .prettierrc | 15 + .replit | 43 + CONTRIBUTING.md | 8 + DEPLOYMENT.md | 395 + LICENSE | 202 + Makefile | 273 + README.md | 163 + SECURITY.md | 41 + STYLE.md | 55 + client/assets/android-chrome-192x192.png | Bin 0 -> 11936 bytes client/assets/android-chrome-512x512.png | Bin 0 -> 36088 bytes client/assets/apple-touch-icon.png | Bin 0 -> 18511 bytes ...6f2cda9a25c54a6f83a4fd1205dc5119a27992.png | Bin 0 -> 1698 bytes client/assets/favicon-16x16.png | Bin 0 -> 647 bytes client/assets/favicon-32x32.png | Bin 0 -> 4065 bytes client/assets/favicon.ico | Bin 0 -> 15406 bytes client/assets/original-lgo.svg | 35 + client/assets/robots-prod.txt | 6 + client/assets/robots.txt | 4 + client/index.html | 143 + client/src/App.tsx | 211 + client/src/components/ScrollToTop.tsx | 25 + .../components/figma/ImageWithFallback.tsx | 27 + client/src/components/layout/AppLayout.tsx | 21 + client/src/components/layout/Header.tsx | 348 + client/src/components/layout/HomeHeader.tsx | 326 + client/src/components/layout/Layout.tsx | 521 + client/src/components/layout/Sidebar.tsx | 621 + client/src/components/tool-explanations.tsx | 289 + client/src/components/ui/alert-dialog.tsx | 133 + client/src/components/ui/alert.tsx | 59 + client/src/components/ui/badge-variants.ts | 23 + client/src/components/ui/badge.tsx | 15 + client/src/components/ui/button-variants.ts | 32 + client/src/components/ui/button.tsx | 26 + client/src/components/ui/card.tsx | 79 + client/src/components/ui/checkbox.tsx | 28 + client/src/components/ui/command.tsx | 108 + client/src/components/ui/copy-button.tsx | 46 + client/src/components/ui/input.tsx | 20 + client/src/components/ui/label.tsx | 24 + client/src/components/ui/popover.tsx | 31 + client/src/components/ui/scroll-area.tsx | 46 + client/src/components/ui/search-results.tsx | 101 + client/src/components/ui/security-banner.tsx | 88 + client/src/components/ui/select.tsx | 147 + client/src/components/ui/separator.tsx | 29 + client/src/components/ui/sheet.tsx | 125 + client/src/components/ui/shortcut-badge.tsx | 18 + client/src/components/ui/slider.tsx | 26 + client/src/components/ui/switch.tsx | 27 + client/src/components/ui/textarea.tsx | 785 ++ .../src/components/ui/timezone-selector.tsx | 161 + client/src/components/ui/toast.tsx | 124 + client/src/components/ui/toaster.tsx | 31 + client/src/components/ui/tool-button.tsx | 421 + client/src/components/ui/tooltip.tsx | 30 + client/src/contexts/demo-context.ts | 23 + client/src/contexts/theme-context.ts | 23 + client/src/data/defaults.ts | 635 + client/src/data/domain-tlds.ts | 10140 +++++++++++++++ client/src/data/tools.ts | 5357 ++++++++ client/src/hooks/use-demo-hook.ts | 10 + client/src/hooks/use-persistent-state.ts | 74 + client/src/hooks/use-search.ts | 112 + client/src/hooks/use-theme-hook.ts | 11 + client/src/hooks/use-theme.tsx | 51 + client/src/hooks/use-toast.ts | 188 + client/src/hooks/use-tool-default.ts | 32 + client/src/imports/svg-3vrq16klz5.ts | 28 + client/src/index.css | 812 ++ client/src/lib/color-tools.ts | 370 + client/src/lib/encoders.ts | 182 + client/src/lib/formatters.ts | 1035 ++ client/src/lib/queryClient.ts | 41 + client/src/lib/svg-paths.ts | 28 + client/src/lib/text-tools.ts | 58 + client/src/lib/time-tools.ts | 365 + client/src/lib/url-parser.ts | 75 + client/src/lib/url-sharing.ts | 229 + client/src/lib/utils.ts | 6 + client/src/main.tsx | 59 + client/src/pages/home.tsx | 1988 +++ client/src/pages/not-found.tsx | 23 + client/src/pages/tools/barcode-generator.tsx | 376 + client/src/pages/tools/base64-encoder.tsx | 207 + client/src/pages/tools/bcrypt-hash.tsx | 393 + client/src/pages/tools/browser-info.tsx | 745 ++ .../pages/tools/color-palette-generator.tsx | 497 + client/src/pages/tools/compound-interest.tsx | 670 + client/src/pages/tools/countdown.tsx | 566 + client/src/pages/tools/css-formatter.tsx | 288 + client/src/pages/tools/csv-to-json.tsx | 449 + client/src/pages/tools/date-converter.tsx | 1014 ++ client/src/pages/tools/datetime-diff.tsx | 756 ++ client/src/pages/tools/debt-repayment.tsx | 474 + client/src/pages/tools/graphql-formatter.tsx | 171 + client/src/pages/tools/html-formatter.tsx | 242 + client/src/pages/tools/json-formatter.tsx | 211 + .../src/pages/tools/json-yaml-converter.tsx | 192 + client/src/pages/tools/jsonc-formatter.tsx | 183 + client/src/pages/tools/jwt-decoder.tsx | 274 + client/src/pages/tools/keyboard-test.tsx | 384 + client/src/pages/tools/lorem-generator.tsx | 411 + client/src/pages/tools/markdown-formatter.tsx | 169 + client/src/pages/tools/md5-hash.tsx | 321 + client/src/pages/tools/metronome.tsx | 650 + client/src/pages/tools/microphone-test.tsx | 583 + .../src/pages/tools/number-base-converter.tsx | 610 + client/src/pages/tools/password-generator.tsx | 600 + client/src/pages/tools/qr-generator.tsx | 525 + client/src/pages/tools/regex-tester.tsx | 382 + client/src/pages/tools/search-replace.tsx | 306 + client/src/pages/tools/stopwatch.tsx | 378 + client/src/pages/tools/text-counter.tsx | 263 + client/src/pages/tools/text-diff.tsx | 326 + client/src/pages/tools/text-sort.tsx | 328 + client/src/pages/tools/text-split.tsx | 276 + client/src/pages/tools/time-formatter.tsx | 437 + client/src/pages/tools/timer.tsx | 975 ++ client/src/pages/tools/timezone-converter.tsx | 540 + client/src/pages/tools/tls-decoder.tsx | 379 + .../src/pages/tools/typescript-formatter.tsx | 189 + client/src/pages/tools/unicode-characters.tsx | 911 ++ client/src/pages/tools/unit-converter.tsx | 829 ++ client/src/pages/tools/url-encoder.tsx | 218 + client/src/pages/tools/url-to-json.tsx | 295 + client/src/pages/tools/uuid-generator.tsx | 315 + client/src/pages/tools/webcam-test.tsx | 463 + client/src/pages/tools/world-clock.tsx | 473 + client/src/pages/tools/yaml-formatter.tsx | 171 + client/src/providers/demo-provider.tsx | 215 + client/src/providers/theme-provider.tsx | 4 + client/src/types/tools.ts | 15 + components.json | 20 + current_page.html | 2201 ++++ design_guidelines.md | 202 + dev-server/index.ts | 98 + dev-server/routes.ts | 7 + dev-server/vite.ts | 84 + eslint.config.js | 124 + generated-icon.png | Bin 0 -> 2979 bytes infra/cloudformation/production.yaml | 282 + infra/cloudformation/stage.yaml | 255 + infra/images/Dockerfile | 31 + infra/images/Dockerfile.e2e | 30 + infra/images/runtime-files/nginx.conf | 70 + knip.json | 17 + logo.png | Bin 0 -> 118007 bytes package-lock.json | 10715 ++++++++++++++++ package.json | 96 + playwright.config.ts | 69 + pnpm-lock.yaml | 7613 +++++++++++ postcss.config.js | 6 + replit.md | 178 + scripts/generate-static-routes.ts | 268 + scripts/html-utils.ts | 8 + scripts/prepare-release.sh | 187 + scripts/render-explanations.ts | 169 + scripts/render-tool-directory.ts | 61 + scripts/warm-cache.ts | 457 + shared/page-title.ts | 12 + tailwind.config.ts | 109 + tests/e2e/demo.spec.ts | 158 + tests/e2e/menu-toggle.spec.ts | 148 + tests/e2e/page-title.spec.ts | 99 + tests/e2e/search.spec.ts | 214 + tests/e2e/site-metadata.spec.ts | 75 + tests/e2e/theme-toggle.spec.ts | 332 + tests/e2e/tools/barcode-generator.spec.ts | 96 + tests/e2e/tools/base64.spec.ts | 16 + tests/e2e/tools/bcrypt-hash.spec.ts | 21 + tests/e2e/tools/browser-info.spec.ts | 16 + tests/e2e/tools/check-default-editor-value.ts | 40 + .../e2e/tools/color-palette-generator.spec.ts | 21 + tests/e2e/tools/compound-interest.spec.ts | 95 + tests/e2e/tools/countdown.spec.ts | 93 + tests/e2e/tools/css-formatter.spec.ts | 27 + tests/e2e/tools/csv-to-json.spec.ts | 27 + tests/e2e/tools/date-converter.spec.ts | 78 + tests/e2e/tools/datetime-diff.spec.ts | 231 + tests/e2e/tools/debt-repayment.spec.ts | 95 + tests/e2e/tools/graphql-formatter.spec.ts | 27 + tests/e2e/tools/html-formatter.spec.ts | 27 + tests/e2e/tools/json-formatter.spec.ts | 114 + tests/e2e/tools/json-yaml-converter.spec.ts | 120 + tests/e2e/tools/jsonc-formatter.spec.ts | 27 + tests/e2e/tools/jwt-decoder.spec.ts | 43 + tests/e2e/tools/keyboard-test.spec.ts | 38 + tests/e2e/tools/lorem-generator.spec.ts | 21 + tests/e2e/tools/markdown-formatter.spec.ts | 27 + tests/e2e/tools/md5-hash.spec.ts | 21 + tests/e2e/tools/metronome.spec.ts | 16 + tests/e2e/tools/microphone-test.spec.ts | 37 + tests/e2e/tools/number-base-converter.spec.ts | 21 + tests/e2e/tools/password-generator.spec.ts | 21 + tests/e2e/tools/qr-generator.spec.ts | 97 + tests/e2e/tools/regex-tester.spec.ts | 26 + tests/e2e/tools/search-replace.spec.ts | 95 + tests/e2e/tools/stopwatch.spec.ts | 16 + tests/e2e/tools/text-counter.spec.ts | 21 + tests/e2e/tools/text-diff.spec.ts | 26 + tests/e2e/tools/text-sort.spec.ts | 27 + tests/e2e/tools/text-split.spec.ts | 21 + tests/e2e/tools/time-formatter.spec.ts | 21 + tests/e2e/tools/timer.spec.ts | 74 + tests/e2e/tools/timezone-converter.spec.ts | 29 + tests/e2e/tools/tls-decoder.spec.ts | 114 + tests/e2e/tools/typescript-formatter.spec.ts | 27 + tests/e2e/tools/unicode-characters.spec.ts | 16 + tests/e2e/tools/unit-converter.spec.ts | 95 + tests/e2e/tools/url-encoder.spec.ts | 27 + tests/e2e/tools/url-to-json.spec.ts | 296 + tests/e2e/tools/utils.ts | 44 + tests/e2e/tools/uuid-generator.spec.ts | 21 + tests/e2e/tools/webcam-test.spec.ts | 59 + tests/e2e/tools/world-clock.spec.ts | 16 + tests/e2e/tools/yaml-formatter.spec.ts | 27 + tests/lib/color-tools.test.ts | 260 + tests/lib/date-converter.test.ts | 916 ++ tests/lib/domain-tlds.test.ts | 58 + tests/lib/encoders.test.ts | 222 + tests/lib/formatters-async.test.ts | 157 + tests/lib/formatters.test.ts | 287 + tests/lib/text-tools.test.ts | 200 + tests/lib/time-tools.test.ts | 272 + tests/lib/tools-shortcuts.test.ts | 74 + tests/lib/url-parser.test.ts | 559 + tests/setup.ts | 8 + tsconfig.json | 23 + vite.config.ts | 72 + vitest.config.ts | 17 + 245 files changed, 80685 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/performance_issue.yml create mode 100644 .github/ISSUE_TEMPLATE/tool_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .replit create mode 100644 CONTRIBUTING.md create mode 100644 DEPLOYMENT.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 STYLE.md create mode 100644 client/assets/android-chrome-192x192.png create mode 100644 client/assets/android-chrome-512x512.png create mode 100644 client/assets/apple-touch-icon.png create mode 100644 client/assets/cc6f2cda9a25c54a6f83a4fd1205dc5119a27992.png create mode 100644 client/assets/favicon-16x16.png create mode 100644 client/assets/favicon-32x32.png create mode 100644 client/assets/favicon.ico create mode 100644 client/assets/original-lgo.svg create mode 100644 client/assets/robots-prod.txt create mode 100644 client/assets/robots.txt create mode 100644 client/index.html create mode 100644 client/src/App.tsx create mode 100644 client/src/components/ScrollToTop.tsx create mode 100644 client/src/components/figma/ImageWithFallback.tsx create mode 100644 client/src/components/layout/AppLayout.tsx create mode 100644 client/src/components/layout/Header.tsx create mode 100644 client/src/components/layout/HomeHeader.tsx create mode 100644 client/src/components/layout/Layout.tsx create mode 100644 client/src/components/layout/Sidebar.tsx create mode 100644 client/src/components/tool-explanations.tsx create mode 100644 client/src/components/ui/alert-dialog.tsx create mode 100644 client/src/components/ui/alert.tsx create mode 100644 client/src/components/ui/badge-variants.ts create mode 100644 client/src/components/ui/badge.tsx create mode 100644 client/src/components/ui/button-variants.ts create mode 100644 client/src/components/ui/button.tsx create mode 100644 client/src/components/ui/card.tsx create mode 100644 client/src/components/ui/checkbox.tsx create mode 100644 client/src/components/ui/command.tsx create mode 100644 client/src/components/ui/copy-button.tsx create mode 100644 client/src/components/ui/input.tsx create mode 100644 client/src/components/ui/label.tsx create mode 100644 client/src/components/ui/popover.tsx create mode 100644 client/src/components/ui/scroll-area.tsx create mode 100644 client/src/components/ui/search-results.tsx create mode 100644 client/src/components/ui/security-banner.tsx create mode 100644 client/src/components/ui/select.tsx create mode 100644 client/src/components/ui/separator.tsx create mode 100644 client/src/components/ui/sheet.tsx create mode 100644 client/src/components/ui/shortcut-badge.tsx create mode 100644 client/src/components/ui/slider.tsx create mode 100644 client/src/components/ui/switch.tsx create mode 100644 client/src/components/ui/textarea.tsx create mode 100644 client/src/components/ui/timezone-selector.tsx create mode 100644 client/src/components/ui/toast.tsx create mode 100644 client/src/components/ui/toaster.tsx create mode 100644 client/src/components/ui/tool-button.tsx create mode 100644 client/src/components/ui/tooltip.tsx create mode 100644 client/src/contexts/demo-context.ts create mode 100644 client/src/contexts/theme-context.ts create mode 100644 client/src/data/defaults.ts create mode 100644 client/src/data/domain-tlds.ts create mode 100644 client/src/data/tools.ts create mode 100644 client/src/hooks/use-demo-hook.ts create mode 100644 client/src/hooks/use-persistent-state.ts create mode 100644 client/src/hooks/use-search.ts create mode 100644 client/src/hooks/use-theme-hook.ts create mode 100644 client/src/hooks/use-theme.tsx create mode 100644 client/src/hooks/use-toast.ts create mode 100644 client/src/hooks/use-tool-default.ts create mode 100644 client/src/imports/svg-3vrq16klz5.ts create mode 100644 client/src/index.css create mode 100644 client/src/lib/color-tools.ts create mode 100644 client/src/lib/encoders.ts create mode 100644 client/src/lib/formatters.ts create mode 100644 client/src/lib/queryClient.ts create mode 100644 client/src/lib/svg-paths.ts create mode 100644 client/src/lib/text-tools.ts create mode 100644 client/src/lib/time-tools.ts create mode 100644 client/src/lib/url-parser.ts create mode 100644 client/src/lib/url-sharing.ts create mode 100644 client/src/lib/utils.ts create mode 100644 client/src/main.tsx create mode 100644 client/src/pages/home.tsx create mode 100644 client/src/pages/not-found.tsx create mode 100644 client/src/pages/tools/barcode-generator.tsx create mode 100644 client/src/pages/tools/base64-encoder.tsx create mode 100644 client/src/pages/tools/bcrypt-hash.tsx create mode 100644 client/src/pages/tools/browser-info.tsx create mode 100644 client/src/pages/tools/color-palette-generator.tsx create mode 100644 client/src/pages/tools/compound-interest.tsx create mode 100644 client/src/pages/tools/countdown.tsx create mode 100644 client/src/pages/tools/css-formatter.tsx create mode 100644 client/src/pages/tools/csv-to-json.tsx create mode 100644 client/src/pages/tools/date-converter.tsx create mode 100644 client/src/pages/tools/datetime-diff.tsx create mode 100644 client/src/pages/tools/debt-repayment.tsx create mode 100644 client/src/pages/tools/graphql-formatter.tsx create mode 100644 client/src/pages/tools/html-formatter.tsx create mode 100644 client/src/pages/tools/json-formatter.tsx create mode 100644 client/src/pages/tools/json-yaml-converter.tsx create mode 100644 client/src/pages/tools/jsonc-formatter.tsx create mode 100644 client/src/pages/tools/jwt-decoder.tsx create mode 100644 client/src/pages/tools/keyboard-test.tsx create mode 100644 client/src/pages/tools/lorem-generator.tsx create mode 100644 client/src/pages/tools/markdown-formatter.tsx create mode 100644 client/src/pages/tools/md5-hash.tsx create mode 100644 client/src/pages/tools/metronome.tsx create mode 100644 client/src/pages/tools/microphone-test.tsx create mode 100644 client/src/pages/tools/number-base-converter.tsx create mode 100644 client/src/pages/tools/password-generator.tsx create mode 100644 client/src/pages/tools/qr-generator.tsx create mode 100644 client/src/pages/tools/regex-tester.tsx create mode 100644 client/src/pages/tools/search-replace.tsx create mode 100644 client/src/pages/tools/stopwatch.tsx create mode 100644 client/src/pages/tools/text-counter.tsx create mode 100644 client/src/pages/tools/text-diff.tsx create mode 100644 client/src/pages/tools/text-sort.tsx create mode 100644 client/src/pages/tools/text-split.tsx create mode 100644 client/src/pages/tools/time-formatter.tsx create mode 100644 client/src/pages/tools/timer.tsx create mode 100644 client/src/pages/tools/timezone-converter.tsx create mode 100644 client/src/pages/tools/tls-decoder.tsx create mode 100644 client/src/pages/tools/typescript-formatter.tsx create mode 100644 client/src/pages/tools/unicode-characters.tsx create mode 100644 client/src/pages/tools/unit-converter.tsx create mode 100644 client/src/pages/tools/url-encoder.tsx create mode 100644 client/src/pages/tools/url-to-json.tsx create mode 100644 client/src/pages/tools/uuid-generator.tsx create mode 100644 client/src/pages/tools/webcam-test.tsx create mode 100644 client/src/pages/tools/world-clock.tsx create mode 100644 client/src/pages/tools/yaml-formatter.tsx create mode 100644 client/src/providers/demo-provider.tsx create mode 100644 client/src/providers/theme-provider.tsx create mode 100644 client/src/types/tools.ts create mode 100644 components.json create mode 100644 current_page.html create mode 100644 design_guidelines.md create mode 100644 dev-server/index.ts create mode 100644 dev-server/routes.ts create mode 100644 dev-server/vite.ts create mode 100644 eslint.config.js create mode 100644 generated-icon.png create mode 100644 infra/cloudformation/production.yaml create mode 100644 infra/cloudformation/stage.yaml create mode 100644 infra/images/Dockerfile create mode 100644 infra/images/Dockerfile.e2e create mode 100644 infra/images/runtime-files/nginx.conf create mode 100644 knip.json create mode 100644 logo.png create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.js create mode 100644 replit.md create mode 100644 scripts/generate-static-routes.ts create mode 100644 scripts/html-utils.ts create mode 100644 scripts/prepare-release.sh create mode 100644 scripts/render-explanations.ts create mode 100644 scripts/render-tool-directory.ts create mode 100644 scripts/warm-cache.ts create mode 100644 shared/page-title.ts create mode 100644 tailwind.config.ts create mode 100644 tests/e2e/demo.spec.ts create mode 100644 tests/e2e/menu-toggle.spec.ts create mode 100644 tests/e2e/page-title.spec.ts create mode 100644 tests/e2e/search.spec.ts create mode 100644 tests/e2e/site-metadata.spec.ts create mode 100644 tests/e2e/theme-toggle.spec.ts create mode 100644 tests/e2e/tools/barcode-generator.spec.ts create mode 100644 tests/e2e/tools/base64.spec.ts create mode 100644 tests/e2e/tools/bcrypt-hash.spec.ts create mode 100644 tests/e2e/tools/browser-info.spec.ts create mode 100644 tests/e2e/tools/check-default-editor-value.ts create mode 100644 tests/e2e/tools/color-palette-generator.spec.ts create mode 100644 tests/e2e/tools/compound-interest.spec.ts create mode 100644 tests/e2e/tools/countdown.spec.ts create mode 100644 tests/e2e/tools/css-formatter.spec.ts create mode 100644 tests/e2e/tools/csv-to-json.spec.ts create mode 100644 tests/e2e/tools/date-converter.spec.ts create mode 100644 tests/e2e/tools/datetime-diff.spec.ts create mode 100644 tests/e2e/tools/debt-repayment.spec.ts create mode 100644 tests/e2e/tools/graphql-formatter.spec.ts create mode 100644 tests/e2e/tools/html-formatter.spec.ts create mode 100644 tests/e2e/tools/json-formatter.spec.ts create mode 100644 tests/e2e/tools/json-yaml-converter.spec.ts create mode 100644 tests/e2e/tools/jsonc-formatter.spec.ts create mode 100644 tests/e2e/tools/jwt-decoder.spec.ts create mode 100644 tests/e2e/tools/keyboard-test.spec.ts create mode 100644 tests/e2e/tools/lorem-generator.spec.ts create mode 100644 tests/e2e/tools/markdown-formatter.spec.ts create mode 100644 tests/e2e/tools/md5-hash.spec.ts create mode 100644 tests/e2e/tools/metronome.spec.ts create mode 100644 tests/e2e/tools/microphone-test.spec.ts create mode 100644 tests/e2e/tools/number-base-converter.spec.ts create mode 100644 tests/e2e/tools/password-generator.spec.ts create mode 100644 tests/e2e/tools/qr-generator.spec.ts create mode 100644 tests/e2e/tools/regex-tester.spec.ts create mode 100644 tests/e2e/tools/search-replace.spec.ts create mode 100644 tests/e2e/tools/stopwatch.spec.ts create mode 100644 tests/e2e/tools/text-counter.spec.ts create mode 100644 tests/e2e/tools/text-diff.spec.ts create mode 100644 tests/e2e/tools/text-sort.spec.ts create mode 100644 tests/e2e/tools/text-split.spec.ts create mode 100644 tests/e2e/tools/time-formatter.spec.ts create mode 100644 tests/e2e/tools/timer.spec.ts create mode 100644 tests/e2e/tools/timezone-converter.spec.ts create mode 100644 tests/e2e/tools/tls-decoder.spec.ts create mode 100644 tests/e2e/tools/typescript-formatter.spec.ts create mode 100644 tests/e2e/tools/unicode-characters.spec.ts create mode 100644 tests/e2e/tools/unit-converter.spec.ts create mode 100644 tests/e2e/tools/url-encoder.spec.ts create mode 100644 tests/e2e/tools/url-to-json.spec.ts create mode 100644 tests/e2e/tools/utils.ts create mode 100644 tests/e2e/tools/uuid-generator.spec.ts create mode 100644 tests/e2e/tools/webcam-test.spec.ts create mode 100644 tests/e2e/tools/world-clock.spec.ts create mode 100644 tests/e2e/tools/yaml-formatter.spec.ts create mode 100644 tests/lib/color-tools.test.ts create mode 100644 tests/lib/date-converter.test.ts create mode 100644 tests/lib/domain-tlds.test.ts create mode 100644 tests/lib/encoders.test.ts create mode 100644 tests/lib/formatters-async.test.ts create mode 100644 tests/lib/formatters.test.ts create mode 100644 tests/lib/text-tools.test.ts create mode 100644 tests/lib/time-tools.test.ts create mode 100644 tests/lib/tools-shortcuts.test.ts create mode 100644 tests/lib/url-parser.test.ts create mode 100644 tests/setup.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts create mode 100644 vitest.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..21135f60 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,202 @@ +name: 🐛 Bug Report +description: Report a bug or unexpected behavior in the application +title: "[Bug]: " +labels: ["bug", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug! Please fill out the sections below to help us understand and fix the issue. + + - type: dropdown + id: tool-affected + attributes: + label: Which tool is affected? + description: Select the tool where you encountered the bug + options: + - "Homepage/Navigation" + - "ASCII Art Generator" + - "Barcode Generator" + - "Base64 Encoder/Decoder" + - "Color Converter" + - "Color Palette Generator" + - "Countdown Timer" + - "CPU Benchmark" + - "CSV to JSON" + - "Date Converter" + - "Diff Checker" + - "GPU Test" + - "Hash Generator (MD5)" + - "HTML Formatter" + - "HTML to Markdown" + - "HTTP Status Codes" + - "Image Optimizer" + - "JSON Formatter" + - "JSON to CSV" + - "JWT Decoder" + - "Lorem Ipsum Generator" + - "Markdown Formatter" + - "Memory Test" + - "Metronome" + - "Monitor Test" + - "Number Base Converter" + - "Password Generator" + - "QR Code Generator" + - "Regex Tester" + - "Screen Resolution" + - "Search & Replace" + - "Stopwatch" + - "Text Counter" + - "Text Sort" + - "Text Split" + - "Time Formatter" + - "Timezone Converter" + - "TLS Certificate Decoder" + - "Unicode Characters" + - "Unit Converter" + - "URL Encoder/Decoder" + - "URL to JSON" + - "UUID Generator" + - "Webcam Test" + - "World Clock" + - "YAML Formatter" + - "Multiple tools" + - "Other/Unknown" + validations: + required: true + + - type: dropdown + id: category + attributes: + label: Bug Category + description: What type of issue are you experiencing? + options: + - "Functionality - Tool not working as expected" + - "Performance - Tool is slow or unresponsive" + - "UI/UX - Visual or interaction problems" + - "Data Processing - Incorrect results or parsing errors" + - "Browser Compatibility - Works in some browsers but not others" + - "Mobile/Responsive - Issues on mobile devices" + - "Accessibility - Screen reader or keyboard navigation issues" + - "Error Handling - Crashes or unhelpful error messages" + - "URL Sharing - Problems with shareable links" + - "Demo Mode - Issues with the demo functionality" + - "Other" + validations: + required: true + + - type: textarea + id: bug-description + attributes: + label: Bug Description + description: Clearly describe what the bug is and what you expected to happen + placeholder: "When I [describe action], [describe what happens] instead of [describe expected behavior]" + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Provide detailed steps to reproduce the issue + placeholder: | + 1. Go to [tool name] + 2. Enter the following input: [example] + 3. Click on [button/action] + 4. See error/unexpected behavior + validations: + required: true + + - type: textarea + id: input-data + attributes: + label: Input Data (if applicable) + description: Provide the exact input data that causes the issue + placeholder: "Paste the input data that triggers the bug here" + render: text + + - type: textarea + id: expected-output + attributes: + label: Expected Output + description: What output or behavior did you expect? + placeholder: "Describe what you expected to happen" + + - type: textarea + id: actual-output + attributes: + label: Actual Output + description: What actually happened? Include error messages if any + placeholder: "Describe what actually happened, including any error messages" + + - type: dropdown + id: browser + attributes: + label: Browser + description: Which browser are you using? + options: + - "Chrome" + - "Firefox" + - "Safari" + - "Edge" + - "Opera" + - "Other (specify in additional context)" + validations: + required: true + + - type: input + id: browser-version + attributes: + label: Browser Version + description: What version of the browser are you using? + placeholder: "e.g., Chrome 120.0.0.0" + + - type: dropdown + id: device-type + attributes: + label: Device Type + description: What type of device are you using? + options: + - "Desktop" + - "Laptop" + - "Tablet" + - "Mobile Phone" + - "Other" + + - type: input + id: screen-resolution + attributes: + label: Screen Resolution (if relevant) + description: Your screen resolution, if the issue seems display-related + placeholder: "e.g., 1920x1080" + + - type: dropdown + id: frequency + attributes: + label: How often does this occur? + description: Is this a consistent issue? + options: + - "Always - Every time I try" + - "Often - Most of the time" + - "Sometimes - Intermittently" + - "Rarely - Only happened once or twice" + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Add any other context about the problem here + placeholder: "Any other information that might help us understand the issue" + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3b320c9c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 General Discussion + url: https://github.com/spring1843/FreeDevTool.App/discussions + about: Ask questions, share ideas, or discuss the project with the community + - name: 🔒 Security Issue + url: https://github.com/spring1843/FreeDevTool.App/security/advisories/new + about: Report security vulnerabilities privately + - name: 📚 Documentation + url: https://github.com/spring1843/FreeDevTool.App#readme + about: Check the documentation and FAQ before opening an issue diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 00000000..5cf1ced3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,139 @@ +name: 📚 Documentation Issue +description: Report issues with documentation or request documentation improvements +title: "[Docs]: " +labels: ["documentation", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Help us improve our documentation! Report issues or suggest improvements. + + - type: dropdown + id: doc-type + attributes: + label: Documentation Type + description: What type of documentation issue is this? + options: + - "Missing Documentation - Feature lacks documentation" + - "Incorrect Information - Documentation contains errors" + - "Outdated Information - Documentation needs updates" + - "Unclear Instructions - Documentation is confusing" + - "Broken Links - Links don't work or go to wrong pages" + - "Typos/Grammar - Text needs corrections" + - "Missing Examples - Need more examples or use cases" + - "Accessibility - Documentation accessibility issues" + - "Translation - Multi-language documentation needs" + - "API Documentation - Technical API documentation issues" + - "Other" + validations: + required: true + + - type: dropdown + id: doc-location + attributes: + label: Documentation Location + description: Where is the documentation issue located? + options: + - "README.md" + - "Tool-specific help text" + - "In-app tooltips or hints" + - "GitHub Wiki" + - "API Documentation" + - "Contributing Guidelines" + - "Installation Instructions" + - "User Guide" + - "Developer Guide" + - "FAQ" + - "Code Comments" + - "Error Messages" + - "Other/Multiple locations" + validations: + required: true + + - type: textarea + id: current-documentation + attributes: + label: Current Documentation + description: Copy/paste the current documentation that has issues (if applicable) + placeholder: "Paste the current text that needs improvement..." + render: markdown + + - type: textarea + id: issue-description + attributes: + label: Issue Description + description: Describe what's wrong with the current documentation + placeholder: "The documentation says... but it should say... This is confusing because..." + validations: + required: true + + - type: textarea + id: suggested-improvement + attributes: + label: Suggested Improvement + description: How should the documentation be improved? + placeholder: "The documentation should explain... It would be clearer if... Add an example showing..." + validations: + required: true + + - type: dropdown + id: user-type + attributes: + label: Target Audience + description: Who would benefit from this documentation improvement? + multiple: true + options: + - "New Users - First time using the application" + - "Experienced Users - Regular users needing advanced info" + - "Developers - Contributors or developers extending the app" + - "API Users - Using the application programmatically" + - "Mobile Users - Users on mobile devices" + - "Accessibility Users - Users with accessibility needs" + - "Non-English Speakers - Users needing translation" + - "All Users" + + - type: textarea + id: examples-needed + attributes: + label: Examples Needed + description: What examples would make this documentation clearer? + placeholder: | + - Example 1: Show how to [specific use case] + - Example 2: Demonstrate [common workflow] + - Example 3: Code snippet for [integration] + + - type: input + id: priority + attributes: + label: Priority Level + description: How important is this documentation improvement? + placeholder: "e.g., High - blocks new users, Medium - would be helpful, Low - nice to have" + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other context about the documentation issue + placeholder: "This confusion led to... Other users have mentioned... Related to issue #..." + + - type: checkboxes + id: contribution + attributes: + label: Contribution Willingness + description: Are you willing to help improve this documentation? + options: + - label: I'm willing to submit a pull request with the documentation improvements + - label: I can provide additional examples or use cases + - label: I can help review documentation improvements + - label: I'm only reporting the issue + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..18a6469c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,168 @@ +name: ✨ Feature Request +description: Suggest a new feature or enhancement for the application +title: "[Feature]: " +labels: ["enhancement", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature! Please provide detailed information about your idea. + + - type: dropdown + id: feature-type + attributes: + label: Feature Type + description: What type of feature are you requesting? + options: + - "New Tool - Add a completely new developer tool" + - "Tool Enhancement - Improve an existing tool" + - "UI/UX Improvement - Better user interface or experience" + - "Performance - Speed or efficiency improvements" + - "Accessibility - Better accessibility features" + - "Mobile/Responsive - Better mobile experience" + - "Integration - Connect with external services" + - "Export/Import - New file format support" + - "Demo/Tutorial - Better onboarding or help" + - "Developer Experience - Build, testing, or development improvements" + - "Other" + validations: + required: true + + - type: input + id: feature-title + attributes: + label: Feature Title + description: A concise title for your feature request + placeholder: "e.g., Add CSS Minifier Tool, Dark mode toggle, Export to PDF" + validations: + required: true + + - type: textarea + id: problem-statement + attributes: + label: Problem Statement + description: What problem does this feature solve? What pain point are you experiencing? + placeholder: "I'm frustrated when... It would be helpful if... Currently there's no way to..." + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like to see implemented + placeholder: "I would like to see... The tool should be able to... It should work by..." + validations: + required: true + + - type: textarea + id: user-stories + attributes: + label: User Stories + description: Describe how users would interact with this feature + placeholder: | + As a [type of user], I want [functionality] so that [benefit]. + + Example: + - As a web developer, I want to minify CSS code so that I can reduce file sizes + - As a designer, I want to convert colors between formats so that I can match brand guidelines + + - type: dropdown + id: target-users + attributes: + label: Target Users + description: Who would benefit from this feature? + multiple: true + options: + - "Web Developers" + - "Software Engineers" + - "Designers" + - "Data Analysts" + - "DevOps Engineers" + - "Students" + - "Content Creators" + - "QA Testers" + - "System Administrators" + - "General Users" + - "Other (specify in details)" + + - type: dropdown + id: priority + attributes: + label: Priority Level + description: How important is this feature to you? + options: + - "Low - Nice to have" + - "Medium - Would be helpful" + - "High - Important for my workflow" + - "Critical - Blocking my work" + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: What alternatives have you considered or are currently using? + placeholder: "I currently use [tool/method]... I've considered [alternative]... This doesn't work because..." + + - type: textarea + id: mockups-examples + attributes: + label: Mockups or Examples + description: Provide mockups, wireframes, or examples of similar tools/features + placeholder: "Attach images, links to similar tools, or describe the UI you envision" + + - type: textarea + id: technical-considerations + attributes: + label: Technical Considerations + description: Any technical aspects, requirements, or constraints to consider? + placeholder: "Should support... Must be compatible with... Performance requirements..." + + - type: dropdown + id: complexity + attributes: + label: Estimated Complexity + description: How complex do you think this feature would be to implement? + options: + - "Simple - Small UI change or minor feature" + - "Medium - New tool or significant enhancement" + - "Complex - Major feature requiring architecture changes" + - "Not sure" + + - type: textarea + id: success-criteria + attributes: + label: Success Criteria + description: How would you measure if this feature is successful? + placeholder: "This feature would be successful if... Users should be able to... The expected outcome is..." + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other context, screenshots, or information about the feature request + placeholder: "Any other information that would help us understand your request" + + - type: checkboxes + id: existing-check + attributes: + label: Due Diligence + description: Please confirm you've done the following + options: + - label: I have searched existing issues to ensure this feature hasn't been requested + required: true + - label: I have checked the current application to confirm this feature doesn't already exist + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/performance_issue.yml b/.github/ISSUE_TEMPLATE/performance_issue.yml new file mode 100644 index 00000000..115d2e95 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/performance_issue.yml @@ -0,0 +1,260 @@ +name: ⚡ Performance Issue +description: Report performance problems, slow loading, or resource usage issues +title: "[Performance]: " +labels: ["performance", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Help us improve performance! Please provide detailed information about the performance issue you're experiencing. + + - type: dropdown + id: performance-type + attributes: + label: Performance Issue Type + description: What type of performance issue are you experiencing? + options: + - "Slow Loading - Page or tool takes too long to load" + - "Processing Speed - Tool processing is slow" + - "UI Responsiveness - Interface is laggy or unresponsive" + - "Memory Usage - High memory consumption" + - "Battery Drain - Excessive battery usage on mobile" + - "Network Usage - Excessive data usage" + - "Bundle Size - Large JavaScript download" + - "Rendering Performance - Visual glitches or slow rendering" + - "Mobile Performance - Poor performance on mobile devices" + - "Other" + validations: + required: true + + - type: dropdown + id: affected-tool + attributes: + label: Which tool is affected? + description: Select the tool experiencing performance issues + options: + - "Homepage/Navigation" + - "ASCII Art Generator" + - "Barcode Generator" + - "Base64 Encoder/Decoder" + - "Color Converter" + - "Color Palette Generator" + - "Countdown Timer" + - "CPU Benchmark" + - "CSV to JSON" + - "Date Converter" + - "Diff Checker" + - "GPU Test" + - "Hash Generator (MD5)" + - "HTML Formatter" + - "HTML to Markdown" + - "HTTP Status Codes" + - "Image Optimizer" + - "JSON Formatter" + - "JSON to CSV" + - "JWT Decoder" + - "Lorem Ipsum Generator" + - "Markdown Formatter" + - "Memory Test" + - "Metronome" + - "Monitor Test" + - "Number Base Converter" + - "Password Generator" + - "QR Code Generator" + - "Regex Tester" + - "Screen Resolution" + - "Search & Replace" + - "Stopwatch" + - "Text Counter" + - "Text Sort" + - "Text Split" + - "Time Formatter" + - "Timezone Converter" + - "TLS Certificate Decoder" + - "Unicode Characters" + - "Unit Converter" + - "URL Encoder/Decoder" + - "URL to JSON" + - "UUID Generator" + - "Webcam Test" + - "World Clock" + - "YAML Formatter" + - "Multiple tools" + - "Application-wide" + validations: + required: true + + - type: textarea + id: performance-description + attributes: + label: Performance Issue Description + description: Describe the performance problem in detail + placeholder: "When I [action], the tool/page [describe slowness/issue]. It takes approximately [time] to [complete action]." + validations: + required: true + + - type: input + id: expected-time + attributes: + label: Expected Performance + description: How fast should this operation be? + placeholder: "e.g., Should complete in under 1 second, Should load immediately" + + - type: input + id: actual-time + attributes: + label: Actual Performance + description: How long does it actually take? + placeholder: "e.g., Takes 5-10 seconds, Takes over 30 seconds" + + - type: textarea + id: data-size + attributes: + label: Data Size/Complexity + description: Describe the size or complexity of data being processed + placeholder: | + - File size: 1MB CSV file + - Text length: 50,000 characters + - Number of items: 1000+ lines + - Image resolution: 4K (3840x2160) + + - type: dropdown + id: device-specs + attributes: + label: Device Performance Level + description: What type of device are you using? + options: + - "High-end Desktop (powerful CPU, 16GB+ RAM)" + - "Mid-range Desktop (moderate CPU, 8-16GB RAM)" + - "Low-end Desktop (older CPU, 4-8GB RAM)" + - "High-end Laptop (powerful CPU, 16GB+ RAM)" + - "Mid-range Laptop (moderate CPU, 8-16GB RAM)" + - "Low-end Laptop (older CPU, 4-8GB RAM)" + - "High-end Mobile (flagship phone/tablet)" + - "Mid-range Mobile (moderate phone/tablet)" + - "Low-end Mobile (budget phone/tablet)" + - "Other/Unknown" + validations: + required: true + + - type: input + id: device-details + attributes: + label: Device Details + description: Specific device information if known + placeholder: "e.g., MacBook Pro M2, iPhone 14, Windows PC with Intel i5" + + - type: dropdown + id: browser-performance + attributes: + label: Browser + description: Which browser are you using? + options: + - "Chrome" + - "Firefox" + - "Safari" + - "Edge" + - "Opera" + - "Mobile Safari (iOS)" + - "Chrome Mobile (Android)" + - "Other (specify in additional context)" + validations: + required: true + + - type: input + id: browser-version + attributes: + label: Browser Version + description: Browser version number + placeholder: "e.g., Chrome 120.0.0.0" + + - type: dropdown + id: frequency + attributes: + label: How often does this occur? + description: Is this a consistent performance issue? + options: + - "Always - Every time I use the tool" + - "Often - Most of the time" + - "Sometimes - With larger data or specific conditions" + - "Rarely - Only with very large data sets" + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Detailed steps to reproduce the performance issue + placeholder: | + 1. Go to [tool name] + 2. Input [specific data/file] + 3. Click [button/action] + 4. Notice the delay/performance issue + validations: + required: true + + - type: textarea + id: workarounds + attributes: + label: Workarounds Found + description: Any workarounds or ways to mitigate the performance issue? + placeholder: "Using smaller data sets works fine... Refreshing the page helps... Switching browsers improves performance..." + + - type: textarea + id: error-messages + attributes: + label: Error Messages + description: Any error messages or console warnings related to performance? + placeholder: "Paste any error messages, console warnings, or browser developer tools information" + render: text + + - type: dropdown + id: impact-level + attributes: + label: Impact Level + description: How does this performance issue affect your work? + options: + - "Low - Minor inconvenience, still usable" + - "Medium - Noticeable delay, but manageable" + - "High - Significantly impacts productivity" + - "Critical - Makes the tool unusable" + validations: + required: true + + - type: textarea + id: performance-comparison + attributes: + label: Performance Comparison + description: How does this compare to similar tools or previous versions? + placeholder: "Other tools like [tool name] handle this instantly... This used to be faster... Compared to [similar tool], this is much slower..." + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other relevant information about the performance issue + placeholder: "Network conditions, other running applications, system load, etc." + + - type: checkboxes + id: debugging-steps + attributes: + label: Debugging Steps Taken + description: Have you tried any of these debugging steps? + options: + - label: Tested with different browsers + - label: Tested with smaller data sets + - label: Checked browser console for errors + - label: Tested on different devices/networks + - label: Cleared browser cache and cookies + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/tool_request.yml b/.github/ISSUE_TEMPLATE/tool_request.yml new file mode 100644 index 00000000..30d330c8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tool_request.yml @@ -0,0 +1,213 @@ +name: 🛠️ New Tool Request +description: Request a new developer tool to be added to the application +title: "[New Tool]: " +labels: ["new-tool", "enhancement", "needs-triage"] +assignees: [] + +body: + - type: markdown + attributes: + value: | + Request a new developer tool! Please provide detailed information about the tool you'd like to see added. + + - type: input + id: tool-name + attributes: + label: Tool Name + description: What should this tool be called? + placeholder: "e.g., CSS Minifier, SQL Formatter, Image Converter" + validations: + required: true + + - type: dropdown + id: tool-category + attributes: + label: Tool Category + description: Which category does this tool belong to? + options: + - "Converters - Transform data between formats" + - "Formatters - Format and beautify code/data" + - "Encoders/Decoders - Encode or decode data" + - "Generators - Generate code, data, or assets" + - "Validators - Validate syntax or data integrity" + - "Analyzers - Analyze and provide insights about data" + - "Utilities - General purpose developer utilities" + - "Testing - Tools for testing and debugging" + - "Security - Security-related tools" + - "Performance - Performance analysis tools" + - "Text Processing - Advanced text manipulation" + - "Image/Media - Image or media processing" + - "API Tools - API development and testing" + - "Database - Database-related utilities" + - "DevOps - Development operations tools" + - "Other" + validations: + required: true + + - type: textarea + id: tool-description + attributes: + label: Tool Description + description: Describe what this tool does and why it's useful + placeholder: "This tool would... It helps developers... The main purpose is to..." + validations: + required: true + + - type: textarea + id: input-output + attributes: + label: Input and Output + description: Describe what users would input and what the tool would output + placeholder: | + Input: What format/type of data users would provide (e.g., CSS code, JSON data, image file) + Output: What the tool would produce (e.g., minified CSS, formatted JSON, optimized image) + render: markdown + validations: + required: true + + - type: textarea + id: example-usage + attributes: + label: Example Usage + description: Provide a concrete example of how this tool would be used + placeholder: | + Input Example: + ```css + body { + color: red; + background-color: blue; + } + ``` + + Expected Output: + ```css + body{color:red;background-color:blue} + ``` + + - type: dropdown + id: use-cases + attributes: + label: Primary Use Cases + description: Who would use this tool and for what purpose? + multiple: true + options: + - "Web Development" + - "Backend Development" + - "Mobile Development" + - "Data Analysis" + - "DevOps/Infrastructure" + - "Content Creation" + - "Testing/QA" + - "Security Analysis" + - "Performance Optimization" + - "Documentation" + - "Design/UI Work" + - "Database Management" + - "API Development" + - "Education/Learning" + - "Other" + + - type: textarea + id: similar-tools + attributes: + label: Similar Existing Tools + description: List similar tools that exist elsewhere (web tools, CLI tools, etc.) + placeholder: | + - Tool Name 1 - URL (what it does well, what it lacks) + - Tool Name 2 - URL (what it does well, what it lacks) + - CLI tool xyz (what it does well, what it lacks) + + - type: textarea + id: required-features + attributes: + label: Required Features + description: List the essential features this tool must have + placeholder: | + - Feature 1 (e.g., Support for CSS3 syntax) + - Feature 2 (e.g., Error highlighting) + - Feature 3 (e.g., Copy to clipboard) + render: markdown + + - type: textarea + id: nice-to-have-features + attributes: + label: Nice-to-Have Features + description: List additional features that would be great but not essential + placeholder: | + - Optional feature 1 (e.g., Multiple output formats) + - Optional feature 2 (e.g., Batch processing) + - Optional feature 3 (e.g., Integration with external APIs) + render: markdown + + - type: dropdown + id: complexity + attributes: + label: Implementation Complexity + description: How complex do you think this tool would be to build? + options: + - "Simple - Basic text transformation or formatting" + - "Medium - Requires parsing or moderate logic" + - "Complex - Advanced algorithms or external dependencies" + - "Very Complex - Requires specialized libraries or services" + - "Not sure" + + - type: textarea + id: technical-requirements + attributes: + label: Technical Requirements + description: Any specific technical considerations or requirements? + placeholder: | + - Must work offline (no external API calls) + - Needs to support large files (>1MB) + - Requires specific parsing library + - Must be accessible/screen-reader friendly + - Performance requirements + + - type: input + id: priority-justification + attributes: + label: Priority Justification + description: Why should this tool be prioritized? How many developers would benefit? + placeholder: "This tool would help because... Many developers struggle with... It would save time by..." + + - type: textarea + id: user-workflow + attributes: + label: User Workflow + description: Describe the step-by-step workflow for using this tool + placeholder: | + 1. User opens the [Tool Name] tool + 2. User pastes/uploads their [input type] + 3. User configures options (if any): [list options] + 4. User clicks "Process" or similar action + 5. Tool displays [output type] with copy/download options + 6. User can share the result via URL + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information about this tool request + placeholder: "Any other details, use cases, or considerations" + + - type: checkboxes + id: research + attributes: + label: Research Confirmation + description: Please confirm you've done the following + options: + - label: I have searched existing tools to confirm this doesn't already exist in the application + required: true + - label: I have researched similar tools to understand the feature requirements + required: true + - label: I believe this tool would benefit multiple developers, not just my specific use case + required: true + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..76a1749c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +# Pull Request + +## 📋 Description + + + +## 🔧 Type of Change + + + +- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] 📚 Documentation update +- [ ] 🧹 Code refactoring (no functional changes) +- [ ] ⚡ Performance improvement +- [ ] 🧪 Test addition or modification +- [ ] 🔧 Build/CI configuration change + +## 📱 Screenshots/Videos + + + +## 📝 Checklist + + + +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code where necessary +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## 🔗 Related Issues + + + +## 📋 Additional Notes + + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..b01f803e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,78 @@ +# FreeDevTool.App – Copilot Coding Agent Onboarding + +## Purpose + +- Help agents contribute safely and efficiently with minimal friction and green CI. +- Keep changes clean, small, and aligned with project standards. + +## What this app is + +- FreeDevTool.App is a stand-alone web app offering developer/productivity utilities. +- No network calls after load; treat it as an offline SPA. Do not add telemetry or remote calls. +- Built with Vite. There is no backend aside from rendering the initially rendering the page by Vite. +- See `README.md` for details. + +## How to build, test, and verify locally + +- Fresh setup: `make deps` +- Build for production: `make build` +- Full CI checks (lint, typecheck, tests, build, etc.): `make ci` +- Always ensure `make ci` passes before proposing changes. + +## Project layout hints + +- `Makefile` contains all commands for install, build, lint, tests, and CI checks. Do not add raw shell in workflows; add/update Makefile targets instead. +- The app is packaged via `make build`. Production readiness checks run via `make ci`. +- Review docs first: `README.md`, `STYLE.md`, `Makefile`, and other docs. + +## Coding guidelines + +- Follow `STYLE.md` strictly (naming, formatting, folder structure, imports, testing approach). +- Prefer small, focused PRs. Keep diffs minimal and avoid churn. +- Reuse existing utilities/components/hooks. Do not duplicate logic; refactor shared code where helpful. +- Maintain strong typing and avoid `any`. Add/adjust types as needed. +- Keep components simple, composable, and accessible. Avoid hidden side-effects. +- Respect the app’s offline constraint. Do not introduce network I/O or background syncing. +- Keep browser compatibility aligned with existing tooling and configs. + +## Dependencies + +- Minimize new dependencies. Justify additions clearly and prefer lightweight, well-maintained packages. +- Use `make deps` to manage installs. If adding a dep, ensure lockfiles are updated and CI remains green. +- Avoid adding tooling that duplicates what the `Makefile`/CI already covers. + +## Testing and quality + +- Run `make ci` locally before proposing changes. +- Add/maintain tests when changing logic. Keep coverage consistent with repo standards. +- Fix lint/type errors rather than suppressing. If suppression is necessary, keep it scoped and documented. + +## Performance and UX + +- Avoid unnecessary re-renders and heavy runtime work. +- Defer non-critical work and keep bundles lean. Prefer code-splitting where it makes sense. +- Maintain consistent styling and theming. Follow existing design system and components. + +## Security and privacy + +- No outbound requests. Do not introduce analytics, CDNs for runtime data, or feature flags that call home. +- Validate and sanitize user inputs where relevant within the app’s utilities. + +## PR readiness checklist (pre-submit) + +- Code adheres to `STYLE.md` and the tenets section in `README.md`. +- `make ci` passes locally. +- No hard-coded paths, secrets, or environment assumptions. +- Only `Makefile` commands are used in docs/scripts; no raw bash in the .github/workflows directory. +- Changes are minimal, reversible, and documented in code comments where non-obvious. + +## First steps for a new task + +- Inventory the codebase and read: `README.md`, `STYLE.md`, and any related docs in the feature area. +- Identify existing components/utilities to reuse. +- Plan the minimal change set needed to meet goals while keeping CI green. +- Implement, test locally via `make ci`, and prepare a concise PR. + +## Contact points + +- Refer to repo docs and issues for conventions and decisions. If ambiguity exists, align with `STYLE.md` and existing patterns. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2e2e1a2b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,103 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main] + workflow_dispatch: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} + +jobs: + ci: + container: ghcr.io/spring1843/freedevtool.app/e2e:v0.0.4 + name: Continuous Integration + permissions: + contents: read + packages: write + id-token: write + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: git config --global --add safe.directory $(pwd) + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24.6.0" + cache: "npm" + + - run: make install + - run: make type-check + - run: make test + - run: make format-check + - run: make lint + - run: make knip + - run: make build + - run: make format + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: dist/ + retention-days: 7 + + - run: make e2e-install + - run: make e2e-test + + # Running the above should not cause a git diff. + # Investigate why this file change occurred and eliminate the cause + - name: Check for changes + run: git diff --exit-code + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: | + test-results/ + playwright-report/ + retention-days: 7 + + - name: Log in to the Container registry + uses: docker/login-action@v3.6.0 + with: + registry: https://ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Images when a PR is opened + if: github.event_name == 'pull_request' + run: make build-all-images + + - name: Build and Push Images when merged to main + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: make build-and-push-image + + - name: Configure AWS Credentials + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: aws-actions/configure-aws-credentials@v5.1.0 + with: + role-to-assume: "arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_ROLE_STAGE }}" + aws-region: ${{ vars.AWS_REGION }} + + - name: Deploy to Stage + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: make deploy-to-stage + + - name: Preload Stage Cache Few Times + run: | + make warm-cache-stage + make warm-cache-stage + make warm-cache-stage + + - name: Run E2E Tests Against Stage For Cache Warming + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + continue-on-error: true + run: TARGET=https://stage.freedevtool.app make e2e-test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..8ebbd1c8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Build and Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + version: + description: "Version tag (e.g., v1.0.0)" + required: true + type: string + +jobs: + build-and-release: + container: ghcr.io/spring1843/freedevtool.app/e2e:v0.0.4 + permissions: + contents: write + pages: write + packages: write + id-token: write + + name: Build and Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history for changelog generation + - run: git config --global --add safe.directory $(pwd) + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + + - name: Install dependencies + run: make deps + + - name: Build application + run: make build-prod + + - name: Create dist archive + run: | + # Create a clean build directory for release + mkdir -p release-build + + # Copy deployable public files + if [ -d "dist" ]; then + cp -r dist/public/* release-build/ + fi + + # Create archive + cd dist + tar -czf ../freedevtool-app.tar.gz . + cd .. + + # Create zip for Windows users + cd release-build + zip -r ../freedevtool-app.zip . + cd .. + + - name: Get version from tag or input + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + else + echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + fi + + - name: Log in to the Container registry + uses: docker/login-action@v3.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and Push Images + run: | + make build-and-push-image IMAGE_TAG="ghcr.io/spring1843/freedevtool.app/app:${{ steps.version.outputs.version }}" + make build-and-push-e2e-image E2E_IMAGE_TAG="ghcr.io/spring1843/freedevtool.app/e2e:${{ steps.version.outputs.version }}" + + - name: Generate changelog + id: changelog + run: | + CURRENT_TAG="${{ steps.version.outputs.version }}" + + echo "Generating changelog for ${CURRENT_TAG}" + + # Create changelog content + CHANGELOG_FILE="CHANGELOG.md" + + # Use a heredoc to write the changelog content + cat <> ${CHANGELOG_FILE} + + Released on $(date '+%Y-%m-%d') + + ## 🐳 Run Compiled Image Locally + + Using Docker + \`\`\`bash + docker run -p 9090:9090 ghcr.io/spring1843/freedevtool.app/app:${CURRENT_TAG} + \`\`\` + + ## 📦 Run Dev Environment Locally + + 1. Download source code file \`${CURRENT_TAG}.tar.gz\` or \`${CURRENT_TAG}.zip\` from release assets. + 2. Extract the archive + 3. Run \`make deps\` to install all dependencies + 4. Run \`make dev\` to start dev server + 5. When the webserver is ready, click on the localhost URL to browse the project + + ## 🔗 Links + + - [Live FreeDevTool.App](https://freedevtool.app) + - [GitHub Repository](https://github.com/spring1843/freedevtool.app) + - [Documentation](https://github.com/spring1843/freedevtool.app#readme) + - [Report Issues](https://github.com/spring1843/freedevtool.app/issues) + EOF + + # Set output for release notes + echo "changelog_file=${CHANGELOG_FILE}" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + append_body: true + tag_name: ${{ steps.version.outputs.version }} + name: "FreeDev Tool App ${{ steps.version.outputs.version }}" + body_path: ${{ steps.changelog.outputs.changelog_file }} + files: | + freedevtool-app.tar.gz + freedevtool-app.zip + ${{ steps.changelog.outputs.changelog_file }} + draft: false + prerelease: ${{ github.event_name == 'workflow_dispatch' && inputs.bypass_ci }} + generate_release_notes: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5.1.0 + with: + role-to-assume: "arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.AWS_ROLE_PRODUCTION }}" + aws-region: ${{ vars.AWS_REGION }} + + - name: Deploy to Production + run: make deploy-to-production + + - name: Preload Production Cache Few Times + run: | + make warm-cache-production + make warm-cache-production + make warm-cache-production + + - name: Run E2E Tests Against Production For Cache Warming + continue-on-error: true + run: TARGET=https://freedevtool.app make e2e-test diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f6e1e6ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules +dist +.DS_Store +server/public +vite.config.ts.* +*.tar.gz +.vscode +test-results +playwright-report +attached_assets +.env diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..d6db8688 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules/ +.replit +package-lock.json +.cache/ +.local/ +.upm/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..7e9419b1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": false, + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto" +} \ No newline at end of file diff --git a/.replit b/.replit new file mode 100644 index 00000000..cc5d0e59 --- /dev/null +++ b/.replit @@ -0,0 +1,43 @@ +modules = ["web", "docker", "nodejs-20"] +run = "npm run dev" +hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"] + +[nix] +channel = "stable-24_05" + +[deployment] +deploymentTarget = "cloudrun" +build = ["sh", "-c", "make build"] +run = ["sh", "-c", "npm start"] + +[env] +PORT = "9095" + +[workflows] +runButton = "Project" + +[[workflows.workflow]] +name = "Project" +mode = "parallel" +author = "agent" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Start application" + +[[workflows.workflow]] +name = "Start application" +author = "agent" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "PORT=9095 npm run dev" +waitForPort = 9095 + +[[ports]] +localPort = 3000 +externalPort = 3000 + +[[ports]] +localPort = 9095 +externalPort = 80 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e5142f71 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# Contributing + +Thank you for your interest in contributing. Please consider opening an issue first to discuss the changes you are +considering before working on them. + +Before pushing to our repo please ensure `make ci` does not return any errors. Refer to [README.md](README.md) for dev setup steps. + +For UI contributions please refer to the [STYLE.md](STYLE.md) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 00000000..e22c0865 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,395 @@ +# Static Deployment Guide + +This application is designed to be deployed as a fully static website to any static hosting platform. No backend server is required in production. + +## Building for Production + +### Static Site Build + +Run the static site generation build: + +```bash +make build +``` + +This command: + +1. Builds the Vite frontend application +2. Generates SEO-optimized HTML files for all 47 routes +3. Creates the proper directory structure in `dist/public/` +4. Embeds unique meta tags (title, description, keywords, Open Graph) for each tool + +### Build Output + +The build creates the following structure in `dist/public/`: + +``` +dist/public/ +├── index.html # Homepage with SEO metadata +├── assets/ # JavaScript, CSS, and image assets +│ ├── index-[hash].js # Main application bundle +│ ├── index-[hash].css # Styles +│ └── ... # Other chunked assets +├── tools/ # Tool pages with SEO metadata +│ ├── json-formatter/ +│ │ └── index.html # JSON Formatter tool page +│ ├── date-converter/ +│ │ └── index.html # Date Converter tool page +│ └── ... # 44 more tool directories +├── favicon.ico +├── robots.txt +└── sitemap.xml +``` + +Each `index.html` file contains: + +- Unique, descriptive page title +- SEO-optimized meta description +- Relevant keywords +- Open Graph tags for social sharing +- Content Security Policy headers +- References to the main application bundles + +### Build Size + +The complete static build is approximately **2.6 MB** including: + +- All 46 tools +- React and UI libraries +- Code formatting engines (Prettier) +- All assets and icons + +## Deployment Options + +### AWS S3 + CloudFront + +1. **Create S3 Bucket:** + + ```bash + aws s3 mb s3://your-bucket-name + ``` + +2. **Configure for Static Website Hosting:** + + ```bash + aws s3 website s3://your-bucket-name --index-document index.html + ``` + +3. **Upload Files:** + + ```bash + aws s3 sync dist/public/ s3://your-bucket-name --delete + ``` + +4. **Set up CloudFront** (recommended for HTTPS and CDN): + - Create CloudFront distribution + - Point origin to S3 bucket + - Configure custom domain (optional) + - Add SSL certificate via ACM + +5. **Configure Cache Headers:** + Add cache control headers for better performance: + ```bash + aws s3 cp dist/public/assets/ s3://your-bucket-name/assets/ \ + --recursive \ + --cache-control "public, max-age=31536000, immutable" + ``` + +### Netlify + +1. **Install Netlify CLI:** + + ```bash + npm install -g netlify-cli + ``` + +2. **Deploy:** + ```bash + netlify deploy --prod --dir=dist/public + ``` + +Or use Netlify's web interface: + +- Drag and drop the `dist/public` folder +- Or connect your Git repository and set build command to `make build` + +### Vercel + +1. **Install Vercel CLI:** + + ```bash + npm install -g vercel + ``` + +2. **Deploy:** + ```bash + vercel --prod + ``` + +Configuration in `vercel.json`: + +```json +{ + "buildCommand": "make build", + "outputDirectory": "dist/public", + "cleanUrls": true +} +``` + +### GitHub Pages + +1. **Build Locally:** + + ```bash + make build + ``` + +2. **Deploy to gh-pages Branch:** + ```bash + npm install -g gh-pages + gh-pages -d dist/public + ``` + +Or use GitHub Actions (recommended) - see `.github/workflows/deploy.yml` + +### Azure Static Web Apps + +1. **Create Static Web App** in Azure Portal + +2. **Deploy via CLI:** + ```bash + az staticwebapp create \ + --name your-app-name \ + --resource-group your-resource-group \ + --source dist/public + ``` + +### Cloudflare Pages + +1. **Connect Git Repository** in Cloudflare dashboard + +2. **Configure Build Settings:** + - Build command: `make build` + - Build output directory: `dist/public` + +Or use Wrangler CLI: + +```bash +npx wrangler pages deploy dist/public +``` + +## SEO Optimization + +### Pre-generated SEO Content + +Each route includes unique SEO metadata: + +- **Homepage**: General developer tools landing page +- **Tool Pages**: Specific metadata for each tool + - Example: `/tools/json-formatter/` has "JSON Formatter - Format and Validate JSON" + - Includes relevant keywords and descriptions + +### Sitemap + +The build includes `sitemap.xml` with all routes for search engine discovery. + +### Robots.txt + +Configured to allow all search engine crawlers. + +### Social Sharing + +Open Graph tags ensure proper previews when shared on: + +- Twitter/X +- LinkedIn +- Facebook +- Slack +- Discord + +## Performance Optimization + +### Asset Caching + +Configure your CDN/hosting to cache assets aggressively: + +- **HTML files**: `Cache-Control: public, max-age=3600, must-revalidate` +- **JS/CSS files** (hashed): `Cache-Control: public, max-age=31536000, immutable` +- **Images** (hashed): `Cache-Control: public, max-age=31536000, immutable` + +### Compression + +Enable gzip or brotli compression: + +- Main JS bundle: ~1.3MB → ~352KB gzipped +- CSS bundle: ~83KB → ~14KB gzipped + +Most static hosting platforms enable this automatically. + +### HTTP/2 + +Use hosting platforms that support HTTP/2 for: + +- Parallel asset loading +- Header compression +- Server push (optional) + +## Custom Domain + +### DNS Configuration + +Point your domain to your hosting platform: + +**AWS CloudFront:** + +- Create CNAME record: `www.yourdomain.com` → `d111111abcdef8.cloudfront.net` +- Create A record (Alias): `yourdomain.com` → CloudFront distribution + +**Netlify:** + +- Add custom domain in Netlify dashboard +- Update DNS to Netlify's nameservers or add CNAME + +**Vercel:** + +- Add domain in Vercel dashboard +- Update DNS with provided records + +### SSL/TLS + +Most platforms provide automatic HTTPS: + +- **AWS**: Use AWS Certificate Manager (ACM) +- **Netlify/Vercel/Cloudflare**: Automatic Let's Encrypt certificates +- **GitHub Pages**: Automatic for custom domains + +## Monitoring and Analytics + +Since this is a privacy-focused application, consider: + +- **Server-side analytics** (respects privacy) +- **Error tracking** (Sentry, LogRocket) +- **Performance monitoring** (CloudWatch, Vercel Analytics) + +Avoid client-side tracking that compromises the offline-first, privacy-focused nature. + +## Environment Variables + +This application doesn't require environment variables in production as all processing is client-side. + +If you need to customize: + +- Update meta tags in `scripts/generate-static-routes.ts` +- Rebuild with `make build` + +## Rollback Strategy + +Keep previous builds for easy rollback: + +```bash +# Tag builds +mv dist/public dist/public-$(date +%Y%m%d-%H%M%S) + +# Rollback +aws s3 sync dist/public-20250104-153000/ s3://your-bucket-name --delete +``` + +## Testing Deployment + +### Local Testing + +Test the production build locally: + +```bash +make build +npx serve dist/public +``` + +Visit `http://localhost:3000` to verify all routes work correctly. + +### Verify SEO + +Check that each tool page has unique metadata: + +```bash +curl https://yourdomain.com/tools/json-formatter/ | grep "" +curl https://yourdomain.com/tools/date-converter/ | grep "<meta name=\"description\"" +``` + +## Troubleshooting + +### Routes Return 404 + +Configure your hosting platform for SPA routing: + +**AWS S3:** + +- Set error document to `index.html` + +**Netlify:** +Add `_redirects` file: + +``` +/* /index.html 200 +``` + +**Vercel:** +Add to `vercel.json`: + +```json +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} +``` + +### Assets Not Loading + +Ensure all paths are absolute (start with `/`): + +- ✅ `/assets/index-abc123.js` +- ❌ `assets/index-abc123.js` + +The build script already handles this correctly. + +### CSP Errors + +The Content Security Policy is strict for security. If you need to modify: + +- Edit `client/index.html` +- Rebuild with `make build` + +## Cost Estimation + +**AWS S3 + CloudFront:** + +- Storage: $0.023/GB (~$0.06/month for 2.6MB) +- Data transfer: $0.085/GB (first 10TB) +- Requests: $0.0004/1000 GET requests +- **Estimated**: $1-5/month for moderate traffic + +**Netlify/Vercel:** + +- Free tier: 100GB bandwidth/month +- **Estimated**: Free for most use cases + +**GitHub Pages:** + +- **Free** for public repositories + +## Continuous Deployment + +Set up automatic deployments on git push: + +1. Add build script to your CI/CD pipeline +2. Deploy on successful build +3. Run smoke tests post-deployment + +Example GitHub Actions workflow included in `.github/workflows/deploy.yml` + +## Support + +For deployment issues: + +- Check hosting platform documentation +- Review build logs: `make build` +- Verify route structure: `ls -R dist/public/tools/` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..670d95c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,273 @@ +# Development Makefile for DevTools Suite +# Comprehensive development workflow management + +.PHONY: help setup start stop restart dev build lint lint-fix format type-check test clean deps install status health deploy prepare-deploy ci all apply-cloudformation-stage warm-cache-stage warm-cache-production + +# Default target +.DEFAULT_GOAL := help + +# Colors for terminal output +RED=\033[0;31m +GREEN=\033[0;32m +YELLOW=\033[1;33m +BLUE=\033[0;34m +NC=\033[0m # No Color + +GIT_TAG:=$(shell git describe --tags --abbrev=7 --always HEAD) +GIT_SHA:=$(shell git rev-parse HEAD) +GIT_SHA_SHORT:=$(shell git rev-parse --short HEAD) + +AWS_PAGER="" + +# Image tags +IMAGE_REPO:=ghcr.io/spring1843/freedevtool.app +IMAGE=${IMAGE_REPO}/app +IMAGE_TAG:=${IMAGE}:${GIT_SHA_SHORT} +E2E_IMAGE:=${IMAGE_REPO}/e2e +E2E_IMAGE_TAG:=${E2E_IMAGE}:${GIT_SHA_SHORT} +E2E_IMAGE_USE:=${E2E_IMAGE}:e9c0ff7 +PWD:=$(shell pwd) +STAGE_CLOUDFRONT_ID:=E17MASS8004CRI +PROD_CLOUDFRONT_ID:=E1CLKXJU2N6KW7 + +## Setup Commands + +setup: ## Complete project setup - install dependencies, browsers, and prepare for development + make deps + make e2e-install + +## Combined Commands + +all: clean setup lint type-check test build ## Run full development setup with all dependencies including tests + +pre-commit: format type-check lint-fix knip ## Pre-commit hook (fix, format, check) + +ci-containerized: ## Run CI checks inside a container that has dependencies installed + docker run --rm -v "${PWD}:/app" ${E2E_IMAGE_USE} make setup && make ci + +ci-without-e2e: pre-commit build test ## CI commands without end-to-end tests, for environments that can't run e2e tests + +ci: ci-without-e2e e2e-test ## Commands run in the CI. Good to run before pushing changes + +install: deps ## Install dependencies (alias for deps) + +deps: ## Install all dependencies + npm install + +## Knip - unused dependencies/exports/files analysis + +knip: ## Analyze unused deps/exports/files with Knip + npm run knip + +knip-fix: ## Attempt automatic Knip fixes (exports/deps) + npm run knip:fix + +deps-update: ## Update all dependencies + npm update + +deps-audit: ## Audit dependencies for security issues + npm audit + +deps-audit-fix: ## Fix dependency security issues + npm audit fix + +## Core Development Commands + +help: ## Display this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(GREEN)%-20s$(NC) %s\n", $$1, $$2}' + +start: clean build ## Build and serve static files locally (for preview) + npx serve dist/public + +stop: ## Stop the development server (if running in background) + @pkill -f "tsx dev-server/index.ts" || true + @pkill -f "vite" || true + +restart: stop start ## Restart the development server + +dev: ## Start development server with verbose logging + PORT=9095 NODE_ENV=development DEBUG=* npm run dev + +npm-build: ## Run npm build to build assets + npm run build + +build: clean npm-build ## Build the project (version.json generated in routes script) + +move-robots-prod: ## Use a different robots.txt for production to allow indexing + mv dist/public/robots-prod.txt dist/public/robots.txt + +build-prod: build move-robots-prod ## Build the project for production + +build-image: ## Build the Docker image for the app + docker build --platform linux/amd64 -t ${IMAGE_TAG} -f infra/images/Dockerfile . + +run-image: build-image ## Run the Docker image locally on port 9090 + docker run --rm -p 9090:9090 ${IMAGE_TAG} + +build-and-push-image: build-image # Build and push the Docker image for the app + docker push ${IMAGE_TAG} + +build-e2e-image: ## Build the Docker image for end-to-end testing + docker build --platform linux/amd64 -t ${E2E_IMAGE_TAG} -f infra/images/Dockerfile.e2e . + +build-and-push-e2e-image: build-e2e-image ## Build the Docker image for end-to-end testing + docker push ${E2E_IMAGE_TAG} + +build-all-images: build-image build-e2e-image ## Build all Docker images + +push-all-images: build-and-push-image build-and-push-e2e-image ## Build and push all Docker images + +## Code Quality Commands + +lint: ## Run ESLint to check for code issues + npx eslint . --ext ts,tsx --report-unused-disable-directives + +lint-fix: ## Run ESLint with automatic fixing + npx eslint . --ext ts,tsx --fix + +format: ## Format code with Prettier + npx prettier --list-different --write "**/*.{ts,tsx,js,jsx,json,css,md,html,yaml,yml}" + +format-check: ## Check if code is properly formatted + npx prettier --check "**/*.{ts,tsx,js,jsx,json,css,md,html,yaml,yml}" + +type-check: ## Run TypeScript type checking + npx tsc --noEmit + +## Testing Commands + +test: ## Run unit tests + npx vitest run + +test-watch: ## Run tests in watch mode + npx vitest + +test-ui: ## Run tests with UI interface + npx vitest --ui + +test-coverage: ## Run tests with coverage report + npx vitest run --coverage + +e2e-test: ## Run end-to-end tests with Playwright (use TARGET=https://host to test remote) + @echo "Running e2e tests against: ${TARGET:-http://localhost:3000}" + @npx playwright test + +e2e-test-ui: ## Run end-to-end tests with UI + npx playwright test --ui + +e2e-install: ## Install Playwright browsers + npx playwright install + npx playwright install-deps + +## Maintenance Commands + +clean: ## Clean build artifacts and temporary files + rm -rf dist/ + rm -rf node_modules/.cache/ + rm -rf .next/ + rm -rf playwright-report/ + rm -rf test-results/ + +clean-all: clean ## Clean everything including node_modules + rm -rf node_modules/ + npm install + +cache-clear: ## Clear npm and build caches + npm cache clean --force + rm -rf node_modules/.cache/ + +## Status and Health Commands + +status: ## Show project status and health check + @echo "$(GREEN)Node version:$(NC) $$(node --version)" + @echo "$(GREEN)NPM version:$(NC) $$(npm --version)" + @npm list --depth=0 2>/dev/null | head -20 || true + @git status --porcelain 2>/dev/null || echo "Not a git repository" + +health: status ## Comprehensive health check + @echo -n "$(GREEN)TypeScript:$(NC) " + @npx tsc --noEmit >/dev/null 2>&1 && echo "✓ OK" || echo "✗ Errors found" + @echo -n "$(GREEN)ESLint:$(NC) " + @npx eslint . --ext ts,tsx --max-warnings 0 >/dev/null 2>&1 && echo "✓ OK" || echo "✗ Issues found" + @echo -n "$(GREEN)Prettier:$(NC) " + @npx prettier --check "**/*.{ts,tsx,js,jsx,json,css,md}" >/dev/null 2>&1 && echo "✓ OK" || echo "✗ Formatting needed" + +## Deployment Commands + +copy-static-assets-to-stage: + aws s3 sync ./dist/public s3://freedevtool-staging + +invalidate-stage-cdn: + INVALIDATION_ID=$$(aws cloudfront create-invalidation --distribution-id ${STAGE_CLOUDFRONT_ID} --paths "/*" --query 'Invalidation.Id' --output text); \ + aws cloudfront wait invalidation-completed --distribution-id ${STAGE_CLOUDFRONT_ID} --id $$INVALIDATION_ID + +warm-cache-stage: ## Warm CloudFront cache for staging (shortcut for warm-cache DOMAIN=stage.freedevtool.app) + npx tsx scripts/warm-cache.ts https://stage.freedevtool.app + +deploy-to-stage: build copy-static-assets-to-stage invalidate-stage-cdn warm-cache-stage + +copy-static-assets-to-production: + aws s3 sync ./dist/public s3://freedevtool-production + +invalidate-production-cdn: + INVALIDATION_ID=$$(aws cloudfront create-invalidation --distribution-id ${PROD_CLOUDFRONT_ID} --paths "/*" --query 'Invalidation.Id' --output text); \ + aws cloudfront wait invalidation-completed --distribution-id ${PROD_CLOUDFRONT_ID} --id $$INVALIDATION_ID + +warm-cache-production: + npx tsx scripts/warm-cache.ts https://freedevtool.app + +deploy-to-production: build-prod copy-static-assets-to-production invalidate-production-cdn warm-cache-production + +## Infra Commands + +apply-cloudformation-stage: + aws cloudformation deploy \ + --template-file infra/cloudformation/stage.yaml \ + --stack-name freedevtool-staging \ + --region us-east-1 \ + --capabilities CAPABILITY_NAMED_IAM \ + --no-fail-on-empty-changeset + +apply-cloudformation-production: + aws cloudformation deploy \ + --template-file infra/cloudformation/production.yaml \ + --stack-name freedevtool-production \ + --region us-east-1 \ + --capabilities CAPABILITY_NAMED_IAM \ + --no-fail-on-empty-changeset + +## Documentation + +docs: ## Open project documentation + @echo "See replit.md for project documentation" + +## Environment Commands + +env-check: ## Check environment variables and configuration + @echo "$(GREEN)NODE_ENV:$(NC) $${NODE_ENV:-development}" + @echo "$(GREEN)Working Directory:$(NC) $$(pwd)" + @echo "$(GREEN)Package Manager:$(NC) npm" + +## Advanced Development Commands + +dev-https: ## Start development server with HTTPS (if configured) + HTTPS=true npm run dev + +dev-debug: ## Start development server with debugging enabled + NODE_ENV=development DEBUG=express:* npm run dev + +benchmark: ## Run performance benchmarks (placeholder) + @echo "$(YELLOW)Benchmarking not yet implemented$(NC)" + +profile: ## Profile application performance (placeholder) + @echo "$(YELLOW)Profiling not yet implemented$(NC)" + +## Information Commands + +version: ## Show version information + @echo "$(GREEN)Project:$(NC) DevTools Suite v1.0.0" + @echo "$(GREEN)Node:$(NC) $$(node --version)" + @echo "$(GREEN)NPM:$(NC) $$(npm --version)" + @echo "$(GREEN)TypeScript:$(NC) $$(npx tsc --version | cut -d' ' -f2)" + +info: version status ## Show comprehensive project information diff --git a/README.md b/README.md new file mode 100644 index 00000000..0a4b7dc4 --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# FreeDevTool.app + +[FreeDevTool.App](https://FreeDevTool.App) + +**A comprehensive collection of 49+ open source developer tools with offline functionality** + +## Overview + +FreeDevTool.App is a comprehensive web-based collection of open source developer and productivity utilities. It provides a wide range of tools across sections like Conversions, Formatters, Encoders, Text, Time, Financial, Color, and Hardware tools. Built as a full-stack TypeScript application with a privacy-first design, it offers a modern, responsive UI with dark/light theme support, optimized for desktop, mobile, and TV displays. Key advantages include: Open Source & Community-Driven, Browser-Based Computation for Enhanced Security, Zero Data Transmission, and Offline Functionality. All current tools are free to use since computation happens entirely in your browser, providing both security and performance benefits. + +## Tenets + +### Privacy First + +- **Zero Data Transmission**: All operations happen locally in your browser - no data ever leaves your device +- **Air-Gapped Compatible**: Designed to work in completely isolated environments +- **Enterprise Ready**: Designed to be safe for handling sensitive company data and proprietary information + +Read more on our [Security](./SECURITY.md). + +### Developer Focused + +- **By Developers, For Developers**: Built to solve real problems we face daily +- **Keyboard-First UX**: Every tool accessible via keyboard shortcuts for maximum productivity +- **Offline Always**: Works without internet - no external dependencies or CDN calls + +### Open & Accessible + +- **100% Open Source**: Community-driven development with transparent codebase +- **Completely Free**: All current tools are free to use since computation happens in your browser +- **Universal Access**: Works on desktop, mobile, and TV displays with responsive design + +## User Preferences + +Preferred communication style: Simple, everyday language. +Preferred UI behavior: All menu sections collapsed by default, with minimize buttons (< and ^) for maximum screen real estate optimization suitable for TV displays. Sidebar should be collapsed by default. +Preferred terminology: "Camera" instead of "Webcam" for video capture devices. + +## System Architecture + +### Frontend Architecture + +The client-side is a React with TypeScript application, built on a component-based architecture. It utilizes shadcn/ui components (based on Radix UI) and Tailwind CSS for styling, supporting light and dark modes via CSS variables. + +- **Routing**: Uses wouter for lightweight client-side routing. +- **State Management**: Leverages React's built-in state management with hooks. +- **UI/UX Decisions**: Features collapsible header/sidebar menus with state persistence, responsive sidebar modes, a unified text editor with line numbers, and a searchable tool directory with keyboard shortcut navigation. All 49 tools now use simple React state (no session persistence) for optimal reliability and performance, and UI uniformity is achieved through shared components like `ToolButton` and `ToolTextArea`. Regex Tester uses Ctrl+E shortcut (Ctrl+R reserved for browser refresh). Session management has been completely removed from all tools for enhanced stability. + +### Backend Architecture + +There is no back-end, this is entirely a stand-alone, and self sufficient front-end application that can be served using a webserver like Nginx. + +### Data Storage Solutions + +The application currently uses an in-memory storage implementation but is architected for future PostgreSQL integration via Drizzle ORM for type-safe database interactions. + +### Authentication and Authorization + +Not implemented + +### Technical Implementations & Feature Specifications + +- **Tool Suite**: Comprehensive conversion tools (URL/CSV to JSON, Number Base, Date, Timezone, Unit), advanced text tools (password generator, UUID v1/v4), and hardware tests (Camera, Microphone, Keyboard) with Web Audio API integration for alerts. Added Metronome with multi-tone functionality. All 48 tools converted to simple React state management for enhanced reliability. +- **Demo System**: Automated tour functionality with customizable speed controls (Slow 8s, Normal 5s, Fast 3s, Very Fast 1.5s), persistent state management, skip/stop controls, and speed preference persistence via localStorage. +- **Offline Functionality**: All external dependencies for tools like QR and Barcode generators have been replaced with local JavaScript libraries to ensure 100% offline functionality. +- **URL Sharing**: Implemented across all major tools, including timing tools, for state persistence and sharing. +- **Monetization**: Includes a 3-ad placement system for revenue generation. +- **Security**: Implemented Content Security Policy and removed external dependencies enforcing a privacy-first, zero-network-dependency architecture suitable for air-gapped environments. Strict validation and sanitization are applied to all URL parameters. + +## Quick Start + +### Run Locally With Docker + +[Install docker](https://docs.docker.com/engine/install/), pick a [release](https://github.com/spring1843/FreeDevTool.App/releases) tag e.g. v0.0.2, replace it with the variable in the following command and run it. + +```bash +docker run -it -p 9090:9090 ghcr.io/spring1843/freedevtool.app/app:{RELEASE_TAG} +``` + +Then browse [https://localhost:9090](https://localhost:9090). + +### Development Setup + +```bash +make setup # Install dependencies and setup development environment +make start # Start the development server +``` + +### Available Commands + +```bash +make help # Show all available commands +make deps # Install dependencies +make build # Build for production +make test # Run tests +make lint # Check code quality +make format # Format code +``` + +### Testing + +```bash +make test # Run unit tests +make e2e-test # Run end-to-end tests +make test-coverage # Run tests with coverage +``` + +### Release Process + +- **Automatic**: Push a git tag (e.g., `v1.0.0`) to trigger a release +- **Manual**: Use GitHub Actions workflow dispatch with version input +- **CI Requirement**: All releases require CI to pass first +- **Emergency Bypass**: Manual releases can bypass CI if needed (creates prerelease with warning) + +## External Dependencies + +### Core Framework Dependencies + +- **React 18**: Frontend framework. +- **Express.js**: Backend web framework. +- **TypeScript**: Type safety across the stack. +- **Vite**: Build tool and development server. + +### UI and Styling + +- **Tailwind CSS**: Utility-first CSS framework. +- **Radix UI**: Headless UI components. +- **shadcn/ui**: Component library based on Radix UI. +- **Lucide React**: Icon library. + +### Development and Build Tools + +- **ESBuild**: Fast JavaScript bundler. +- **Prettier**: Code formatter. +- **wouter**: Lightweight React router alternative. + +### Utility Libraries + +- **js-yaml**: YAML parsing and stringification. +- **date-fns**: Date manipulation and formatting. +- **clsx/tailwind-merge**: Conditional CSS class composition. +- **nanoid**: Unique ID generation. +- **connect-pg-simple**: PostgreSQL session storage. + +## License + +Licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for full details. + +``` +Copyright 2025 FreeDevTool.App + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..e84b8f83 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ +# Security and Privacy Policy for FreeDevTool.App + +## Our Core Philosophy: User Data Stays on Your Device + +FreeDevTool.App is designed as a secure, offline tool for everyday development tasks. We address the common pitfalls of online tools—such as privacy risks and untrustworthy data handling—by ensuring all processing occurs entirely within your browser. No user data ever leaves your device, and the application operates without any server-side dependencies or external communications after initial loading. + +## Security Tenets + +- **Offline Processing**: All computations, data manipulations, and tool executions happen locally in your browser using JavaScript. The app is a standalone front-end with no back-end infrastructure. +- **Data Flow**: We serve static assets (e.g., HTML, CSS, JS) from our servers during the initial page load, but no data is ever sent back. No API calls, dynamic requests, or external scripts are loaded at runtime. +- **No Tracking or Analytics**: We do not use cookies, local storage for tracking purposes, or any form of user monitoring. If local storage is used (e.g., for user preferences like theme settings), it is optional, transparent, and fully under your control. +- **Open Source Transparency**: The entire codebase is open source, hosted on a public [GitHub repo](https://github.com/spring1843/freedevtool.app/), allowing public inspection, contributions, and independent audits. We encourage community reviews to verify our claims. +- **No Monetization Intrusions**: FreeDevTool.App is and will remain completely free. We will never display advertisements. In the future, we may accept non-intrusive sponsorships (e.g., a simple "Sponsored by" mention), but these will not involve tracking or data collection. + +## Strict CSP (Content Security Policy) + +We [apply](https://github.com/spring1843/FreeDevTool.App/blob/68053bd519fada6f3d7fca5b5c4533ac0a65bc1a/client/index.html#L11) a strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) in all our pages that ensures at browser level that: + +- No external scripts can be loaded +- No outbound calls can be made + +## Guarantees for the Future + +We commit to maintaining these standards indefinitely. Specifically: + +- No external scripts or third-party libraries will be loaded dynamically. +- No data will ever be transmitted to our servers or any external entities. +- No cookies or persistent tracking mechanisms will be implemented. +- No user tracking, profiling, or analytics will be added. +- The app will remain a pure, offline front-end with no back-end evolution. +- No API integrations or outbound network calls will be introduced. +- The tool will always be free of charge. +- The tool can always be downloaded and served locally or behind firewalls. +- It will stay open source for community oversight and improvements. +- Any potential sponsorships will be vetted to ensure they align with our privacy ethos. + +If circumstances require changes to this policy (e.g., due to legal requirements), we will notify users prominently on the app's homepage and repository at least 30 days in advance, providing a rationale and migration options. + +## Contact Us + +If you have questions, suggestions, or concerns about our security practices, reach out via [GitHub Issues](https://github.com/spring1843/FreeDevTool.App/issues). We're committed to continuous improvement while upholding our privacy-first mission. diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 00000000..6ba2da14 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,55 @@ +# Style Guide + +## User Interface and Experience + +We strive to: + +- Be minimal, do more with less +- Pick good defaults +- Allow meaningful customization +- Do similar things similarly +- Save advanced users' time +- Respond quickly to user actions + +### Tools + +- Tools must have a default value for input where it makes sense. +- Each tool must have a primary action button that processes the user's input. +- Allow uploading data as input, and downloading or copying the output. +- Have interesting and meaningful presets. +- Allow clearing the input and resetting it to the default value. +- An "Auto-update" checkbox, enabled by default, should be placed near the main action button. When checked, any change to the input automatically triggers the main action. +- Tools should automatically process their default input upon launch. The exception is for tools that produce sound or request browser permissions, which should require user interaction to start. +- For tools with a text input area, the keyboard focus should be set to that area on launch. +- Tools should be keyboard-friendly. Use standard keys like `Enter`, `Space`, and `Escape` for common actions like start, pause, and stop. +- Provide immediate feedback for user actions with a toast notification. This confirms that the action was registered. Toasts can be disabled globally. +- Do not request elevated permissions (e.g., camera access) on launch. Prompt for permissions only when the user explicitly tries to use the feature. +- Tool settings are reflected in the URL, allowing users to share links with others and see the same configuration. +- Tools should have a Reset button that resets input values to their defaults. This button should be disabled if values are already at their default. +- Tools should have a Clear button that clears all inputs so the user can start fresh. If the user has modified data, they should be warned with an "Are you sure?" prompt to prevent accidental data loss. +- In all tools, there are action buttons that perform operations and data buttons that manipulate input (such as Reset, Clear, and Now in time-related tools). Action buttons should appear on the left, and data buttons on the right. Action buttons should have more vibrant colors, while data buttons should have more muted colors to visually distinguish them. + +### Buttons + +- Buttons should have a tooltip explaining what the button does. +- Buttons that perform the same action should look identical across all tools. For example, a button that copies to clipboard should look exactly the same in every tool. + +### Menus + +- Menus must be collapsible to save space. +- Menus must be keyboard-friendly, allowing efficient navigation by advanced users. + +## Code Level + +### Linters + +- Run all linters with `make lint`. +- We use ESLint for TypeScript. + +### Formatters + +- Format all applicable files with `make format`. + +## GitHub Workflows + +- Only `Makefile` commands should be used in .github/workflows. Avoid shell or bash commands as much as possible. diff --git a/client/assets/android-chrome-192x192.png b/client/assets/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..4921299135b09351482db7eb2b905418c31c2308 GIT binary patch literal 11936 zcmc(lWmg<cw}z3yg1ZI}5Zqk`cL|W-7Tn$40tDAUa1S2bU4jno?(XiVpC9qA^P#8b zL$B^tyQ+5G`?`08ijp+y2ci#9P*A9{G7_r5z3aaV5gzzeU9mv`Zb%L?TFy{V%vAqf z&>n>%ZonT^SqV{fkBn1a1Q(Kj$+pV(I~i<9*n!<9C^VG&5*R4o(I^XVm#*#`=Z$Q& zj;lHw=I^|AFWS4`8a^%R>!g@OBrr0l;3P^!U?I`41!F3ZBVt4h5-6;w!J;50K+(XU zIy_}vqfJcDex3d5TE@_R?~tp4*b_jZ2c4e@jY2R1l7d39M2&(Xm?9#8W-Z`ig)x+| zHUxFW&2=FLP*enBLWyg`QbGNwga4mDcwu1<C4S<Nv@f{kbT^mCNcu_;E%}(#UBtOb ze(=(rY5s2lviu!RDnJrc8UA<$UJt*zDoI~Q4$WZ#Q3`{N6Ie>4r&eLujx!ArpLPb< z%+Qw}brrgd_9pSkKKx4kmhn=si%um@YPZ8f5*4het6d8o_M@Uod>(#tSK`i3AL)mV zf{Mg>bd~z|Xw0L3dQPai?jI{d6ZN6+<kSZG)ss86+WWngwpuO=ngAM)jsDwWk~zA8 zcVXFSjCad3D~vb{(lNGH%7uoF@N1QK!^hh(e`b7cRv1PnMo-Rc_oL~Q&XtxqJq{h- zz@$~`d9xP*=`IiB`!Z>m>tDU0z$BnU6E)yWD4%rR$UWa?71qM{iN>k)SSIY7yu1vq zIoprwz~i&B!W2ODqnZs*1cZ%kP4gM&>&KY)jSv&xRtxqT<7D%IQQ(6@fH~eYqzfni zn=M1*@yBHfBTecgZ)!;IRj7BB6K@w%ysR8e^BX}UIm_!*>|aA=L&u^lR6wt&*ym^1 zX6|hS>AS3FL^)$aP&jtKJX6r*CR^q@KI^?JDp8y98_9UM#?kFVh<;Tz?hV3ax1o*c z&)!XP5IO=9wU0=I!a>f2Y=s2-reLP5<v{lYVN9Io+jgVUP2cavrCX(el3<Os&ebIy z0(lQXOe8a_5dRb_kjT<IWN1W%2!;xbvolZVsUJJOHZP>kFSzlU;E0SXI@&7~+TTHG zx*r=OT!O-FLTiV^1#g!;Qu#eZf|5kw;w?=J=}Y6}C%&5dmoWDh`1<N1Nz=I2QMe=8 z-)>p2#7Smdh^(k5rhMNK5x%;v9M}>Zd}RJedLtvwc6a0q_ZOmU9B0qoT=z=JcP~T| zvh3}${&;uTlftQe731e&lnsP9ipvixlvQ`Zu)+W0sTx^@9~uKH5kvh=hE%AL*E&?4 z=>a)e^b90f8wYo@#2&rD(%^K;u2G-m_5QptT$dfo13A%MoVJ+n^+7%&1nbzCYF9vL z-l3(tngV(^RqPioKcEeSYWghkwToZ6Y+}`ZKWa5KLt2lat&CEDI9tpkNCkLexku;9 zlwiL`D11W8TMbux_<Hc|sQ=xRX>2agozWnzm?j=cyc#>;nQ`eh2-7g8|65T7E`#Ut zr3ZBr2GOZ5VuTzCWEA4j&@`Dhvxxit6O)`XzhKN##XY*)?^t_Wq|xA53-dx`g*exk zO5f_Za9P=<epM?yen=>QcEi^%I~(JakD&W8m$O$~Ibdr**U#Mg+`ZDE>DBZ+H5OEe zDM+Ocxkdgs$umF$&ciTXbN0}@P{Hof&#Ha9I&2_Azd;jm_>R>Zym4`;@!v~i1Gc2( z2gXt=vm5l=+PUWR7F`g6x<u)NeTd{Z1YJN5^8_inf|F0#zN_Mk7W<}bfnv0-7(*eJ zvEmCOt`h9gsf+7EsgmSd9PRp@7#Zc_7)_aAi5RkF_f1fpX5by8;8)cZi-E(FpXZq2 z9_wP75n48s^JX^b+F8IxFPyAcqMSv1>%wGJil+;qAP6M$h>_`emPlK<|3EzYt!^1i zd8I-U`e$`xC*!^Rc5zO=aXv5eS3e>N8h#B<X=5B+0qm=g_{dXBSjyc!O0y7^{<m^R z6~Y~T`9QN(AZOi)MIu>nuxdG~@9iKKp7dJW=R|5@2%g}}Ec}x(5%XuiR5{#POW18@ zu3MH64Dy0ch_Au@h95249Owqy1Nw~vpT4ABJ!~e?YAbjHqSLmjgdV20(BP$+fbH3u zWcgZIJu+!Wh0X*GWH=(3q_BOI@m@L@Byay!vwPTLb^PCq9Rurzl~iBC*~l(>KthwL z1^(dHc0qLzolbnZauMe9M5U%ePHO+oH<1XpR!=%al?6lg$<!V0ItCnUI%W!t%e4e4 zd5Wb|a~dt2xm;_WmXNWqDqCXG3z-QB@{fm&>n9}hSpDa*1nFzb37Ebhkq^}_*3eFG z2e<n`RBkNxKdpkPH+@~W>tU6_OP8Pmo(=S<Kz;0Ps=w}|%6HL^i@b=#7cq}%eg*-X z*H7_>TZg9ve{}`s-}DfJvV!mk@LrVuHiNG-ITSVgqTGb1FcC@75(fJyutFz9P2(yz z4IhX?<=TeF4E1lKJgO2b`*avdE8K1RXix0U@wXk~rq0pLP;bzP|4Fs&<pxfcSAQ{R z+Ly*4AgLOBH+e1*pMdEJq5&UXtppWij*&gJz18vm>m&e^Z@p5}Z#F1Uy_`1Z&-=N& zzg|h(cx2CK^Re?D9UT*up0_P({M(w=Deu?%dj9&DgRe~o_QqmQoUwJAZl>Imt)q2J z1%FAtLOzNf$(t>%5DYu6<YFw*waw38Z7BM1M3^dj<JRAqtJj<gW<!dUZ&htv9FuGv z6XYxde~QLi-kr8x2Mdr9d>8jW|6982vW%W184f2M@Pi4iDW&#Qq<i2s5?LdCxf5~E zW`eRehzhLXy?zvPygF;K51WcrTuav_M)HFI;{xI0m(rR~k&%3NoqR4bF7i((c`^oS z1HR?A)zH7JpOTz}8<H*O$vqx68*&rGR_sohPHkFiiL@2|8_w60UB1|Sp<LJ?T5{w5 z=oFtg+7j&x=A}n1g&s_HrFmPY#0fk3NINXu&PBx*x5`(tTsWv`t*5fr^9&o9_p+!2 z|Nah`>TRX{oRZp8h?GS&lr6ke-?t*YFt>BK5C)TslJyfag|T#17B9?GA10d34{61v zbNqPy8it}i&?z=U|LXM)-##oolE+=>=j+_|#o(t0)QnN9^IzV}s&7Gg@#*LuoRYA@ z?s{3U0q=(SY{j~-jKZOE+N|(+RBSe!-(B(~y!5D}K>6+BE^oCGZwx<YTk1R|KqTBs zzkvP?4LI=3IaYsJhjtoXAO6~75b5$?_%3C>oLMuq`wgLPB9I%_dQM=`h5MG~cROyP ze5-!_9CJj<{EK$46G0XkEs+Gfkwp2<$Mo2`KPXop4Ik)dFU@pJ-b3A<wJ!_;-qtp! zayu|TYaebSh0pJN^R*jq$ZyOG6>7RZS-v8f%-*Lm(2tEB-J2G{8|FpW<7diEs*RPP zaVy|oyCW7V1A_7WPVEQ03N84)qGQ*h$ljqe>+Pm}noWmeq+u`%)L_4*Xx{UxsvBFU zf25mcJobXmABpAebs5l|ONtB$#NEv8_;Tp*E`wVa5J(SQI=4_F^zqfyqOfWv^4;TV zT3#e}KTWTSq{G&wq(jh%mcxwIv(8VDG{RcM=#Rg;XKkT%eIcDSZkpUlXaXookhdzp zNH<PmzySKE{shU!+-(+IJ4X~fv=sb5M)B8tBF-sb9AUuP{)O#%w*lceH`yP46Us6d z>sE>DA@6Tb@;deThn=;_mRPH;F|aj_?PjJ}6z^3#T7!S~{&Ap}lt7e!-)h)*|ISdd zpPm>&I|rN$1?&C;8Rl)Hjm4dncg7mN_<OInzc6ot1&LMugJpF_E6dykmAJ7w-mf_< zdw#<_w<GX~bCywdTMooRZ6{nmB%Zen6+x6g{biXw`Xc*MVu#e0kzn9a#>7ViZD4i# zF?}Epg_hOE8>%a=`(Pp!Tl){#8J>9rtBre|L6{T1IX`DD2}^Bl_L#&8KFmvKu8yNN zwDsZpH!*EH5apV2)!^cXDc7&Z44;4n1%q@RyQ?J`b1)t+c>U>wO6@%UbDu!6wGDhs zoFn%YF&aYX49$=m2;E!l%idDkXm)=4QtigKK&v@{&%wU=p4VwKp-M*$8aw5}f45!a zNcd0uZU<5G)o$tH6$oNJy`0VoC;>WCl9CWTg=8CMBKF}VKhj_INTRcdZAf~l(uJv7 zV`rh3sfG;rdOr0BqopcBgC60^boLo+8O`N$q<~<DZm<QYo63EkfLAw8hLR){(*s2g z+Dial&T5YDi-AKL+8<M^9CuA&{)4o}U1j>rnbY!_ikE{5?Cc`;wP1nqlW~TjHxjH~ z(ERabb*tU&E-}fleUHNnRi_FoNUzi3SzcPCZNg?)Lj)-_ry-J7C;QB*b-OObgxCg1 zD1V|8-pkm-{i#$h+#+{YR)P6^eP-cOga|!Q&bgVaNZJrkAO=Z?4FvI$xOfkTBcVOO zWO~5Bj26;H=e~VuAIFaqMaH!7^;)U_QMjQdK-NnNpYGM}(!a7)!P7kZ4(h)U-iFX| z`kO;EM6ynbh#aDrzFZn`HGkFlnB3ys);CV=n!bXl-1owWg^(5Cq08k}zeM*qUg9V~ z?^^bVHP@Xq1C92fx%75qlJt!655+`gB}K0fUB`t)d97D*%~^hDCX^EA9!fDFB)$-T z5`kOYw}A@v+w?eQ;S(D`^7e$3A~DP(8JSG<wz}4PucnD>yt@Ev(laK&QM7#LooSi! z**_E&@6lzv?Ojz3T(HrO*^CAXq820_H+8ZoF8YD|083S;&NqfEs$y_I5hREgNCf?& z-|1F49cKj+V3PcOB;E?i3fqHbDqhY~C8p-cqWx)TGb1n1aK42G|7%INsV`}K%rHx) zAL3|mAJ?$$HU44z&p4Qxf}_&GF?4i3g1U{Cy`1QbY^ZQTHe2yyN~l;|>X><mE2U~+ zyl5<;b^tgUiYl0NSGPZU!zgFwDv~6<?J{^iw$LZipSv!@^0{Gv+Lva@s9`hk%Ve(J z7@6lkA6=c2xypL8H)7uRM4P9y#hyk*yV)yWd(!i)&!hkBvzy419PcD55zeA^rnQo4 zO^7UO%AdS{*eEccstHnbjJ2k*a<cwbYv^gYK>3)%cIKqMv%v>5u*1ulEM~0AulRVR zXc5Z<-0U}z!dX&w74zk9nJyHDYIIrg{8J<-Xm-lv%Xdyow2zyl)68icza9QkEWZxp zg&EMgNY_ojw4!Ft3U@8HON{KpWQ7#u=?|)JfhSd+6|R%Q{Ls2~rVjiJz^TK$j;v@0 z=N+aj(|DP}3s>wR>`G)dkIng%=0c&|&zSIk+dS_sX0s#Mo1cRM$*Xo1mhnF(^S7^6 zdi<s`$p9JDYC;kDD`olIX4y)A|E&h()BEP_M5Diy#SD6OT;a)W?=dT6O&sO--moOO zQLo%dFj1a9Q2{Kh<_51Jf$+!l^9$^>oaL%B$BFiP$!7N8R-Uxn;(DZldpqGVYQrYz zms=RB8nvuOb1f5*ok*=P8Jw*_mvN-N^58-SCqASdTVLuJo5+*(ZhIT(e7-9k{WLf0 zMY%F)sSQK=qoD&;U$oPwCIc1OI**e647sp5_+MA&6U>8)1umY<R3nVf=z<h&Z=|=r z7$CIg1@_&tPW6VV-<T1_CR&xFs(%*CCnB=UFAw$+C49i-M*dr+Q;}Fh8oen#ZLEpc zNHU3HRJSvBF4e$ZLroN?hqFgVq>ZZ)QIr%qJpD<QuZNEu1XXF(PMo@?B$*rJ=mv-{ z$<37MFC}H$W~{OO0&ei>+FoH>$1!r5pRhD;`4PS|L2B@3(!Ud3k5dEM2q8WM-CwJ2 zZZtb^otpmbd9$XUYVGH21EX}Ioh|*5T;-St(kZ_t2Iy~!NwN|Nt&;YF&(q^?S2U*@ zPkIAWDvOFVC-XvxDzHV$YKL&)<P}2HJH3b4k^6zjB*q8ekq}U_@y_7IXSC*<c-lEK z9^%fVYw~$IDLUr&Cz-~Z?#>gE63}bO*93|NVFZSdL8Eq<wQ~dy73#rumY{C!g9-3r z-S`$;uw)inC+#kP;qJk>N|qw#%tRQ-(6EHcM>R0k-r$o^Tyus>wZIY)5|pY~6bN%? z`)M1eyaDQHOwpCF$qJ?CWM#BiYvqABWq{hV(knBV$9QGdD+Z9msS^F~#KDT~@G~Eg zB>I#ov40%e9JxmPd^gj#yj%MD)iV(^ug&2v$ab=bR5?O6G-B%XXI@n3YgIjit?APE zCmoG8l)Z@IS#I8>GZ5=;8G{io*MH%n<$PnY<COpEb$K36{i0dNDPz*}N0rvAqiK>- z$yE!7iL3Tnu9gh<bCbZ}%%U%unngk2%GSw0^Pgotg}CO-6Fz}Mxq3gVrG-!a43?yo z<2N4Z7`_=fm35$2%BSqods6QCMf~n8HnN89!K7)V9^RQ`p0#&Uo3Oh<M}<ZNKIzv! ziDCa#N#yIggH0A2U<9^{9}w2q-71~4R<N7Ju3_Bd=8trOr65nVgpI_xyCS()7@xCo zxZ<b{vG3FSA<<{gGX5w=owbrzu3a7E-eghIjHer16|z?O^2M*+v9?yblRNENhm)qT z!m1)O)GgoN0<jB7l;zsn;no@Ybu+r$ddkQhAV$(tEQ3)iU<$JxLZi0T;hV8Nz|D<V zL$>`RPOi>+QjhiU4U>iRTutf*<3ox59Gwu>Z8_y#t8`UzBeJZ$5iX!YV-9fNiqH(} zepWo;UdKig-3X|a>*U7DZe8kfR6in7n|7EdD!)x%Rgd`D$uRkp$3ED|@NM@}{7?zX zIk!j<*BL5W_UYJTs$%s3r^dsmzUSha99@iXKbR!_1xX`X^t^mZvE}@;UVJ2Q9z*_W z>@a>kknpEWK+2EbmvB1!cbmQ<tK6nWAy4X>DDh#fLuL((&J<UJPPAJ3o~%(i+hTQ7 zC!+~a3Vjv(VRrmd9&<A!gHH*FS%c0lc!l{*lkCkq{FzD2<GW^NNa&JoaoaRdnKNHI z*p&(7++j3-s)|GDlyFy$vA9PY%_eP*dg`RPnU;^^@n}00TD|FPO2La#{4EV~+l4OC zOfHT{`b6GXA!u%VtBrs)Qro~@p;x{W>g@uG4Y@%}wr#jjjaE^h#Kv0d(hUcJ`;}P( z1C*EK$>YTczB`mSHm?-a(uIYn-nkJaI;rLDy<gLwXC3{#T?9%EP-zqciqb64k7wy? zI?gFn3+MnD$`#d}@GNw=;I4$*X|gsy#yxlbhV!248eGp?8{zFTUJ{(qQM@4c49tz= zpkPNsSPr%ghfS6l8&Nq8&E3ojchcf&x^uGUi<b;iBXn5QIb5#Pa*yv1Nk%>GBFcSF z<yLw&LLX^LFeR0K#EDV4(y$>MlzUJsv+5B2hkwW!0TEZ7V)BdesMq1Ken1-#gu7F? z&G!dyTM-YUASI>sr2hPm3)e@Oqjv9tL6E!{$da$LC@V&vI4cUyqd6!mJY<#!5J64& z%5@!S8l_A@dl=5#ASd;sxolyF-SIUxGNbQPb$m{-U@*KP2mukC_JqQE4^+j15?Hy| zq8k(>QP(}J5OgZmIJOJos4#>}4-YzkczT_}|5`Xl-hh3BqQF3;>^4M%|KO3UVj=ZC z*!XGMGH=tteJm*C8d>-rTi$Pjs2o1fn&3xpR|p~^dX(HIIUUqy4wP6ILd){gOXIRv zunXSoyw8KxmU!rMPW`Z~*ynTw^LV5{Fd`WU3x#qcQ@k4kWlaF*!=_j_#sj4kQ!wG0 z5a<H}N@&mg<~^Ce`31h{202f#C`xFbKgsn2JMfq=!3Rw7K#b1>D9{E_4=SvP2*KbX z8UiH|9F+BZsUJ%?{3TQy$fHY^mI^iIXD(>p?(b2`0tc2uw|Ul_<_YFWmVCL*usQ#D z|3tv9u2s}{TNdCxJN#mtB<TJK9M)9#kwlE2!tS}xFTG*=o{_-0nC+}E=o{o2=pQuT zaX^cpkAohcL`5eUfe>*KAjSvhl9Q^xNNl5bPCp28F^BZTt~v{mPJ^Ha-ap!tVDAvn zit_sN>Ou`{zT~P5;(rkJ6NDx$0(SF);L|^VxYkp7kN4Y7lzNm%&aF;%HguXdnVC%F zBf<xjs~}Rw@YG%Y5MCPuZ-&rF%~7bRke7XtTDphkxy*9+4@c4Ix$lu65OE>}UkpMW zB=uzW>IH?2K!pYOz=&e+vcGZqG`5i&uwvY78ZJni#_^|;EVTk8H@UrvtaZOkrD4iS z@YZsK7(i%z8^0Ak-R&n^HIgnk@_&-9Jr^zgr><?i_Y1MF_0B3l3{JB+W~EBkR_&j$ zym>WpsMOIo)9_Q6=17%WJ~AMt{UZm+-I+pO#GM)0qYb@RX?U*Roc`z^$C;Zr31_w( z9+pqpE4g|rTAJ*^+%^_7+kY_4cs(NucCjL;7rhdwL-y@}yUA(pUo?pP=7*|t?Wl!! zSZlG5|J!^BlKg7ba`i`h-!Nil<uu;GLvX7R$JEzW6sv1P{q1<>m+E0ZOG;JEs<V2d zTX3YL`4>B;5V34~inxpXPa}+p|I&)Ff$U@bJ0-gKC0_8<#dNEZO{HJ%Z8`ogYY=i8 z^31w6E9j5@ewTS#VP~`nh5{aN$|)c5H1}LbmTZ+y>d+>YiyzWi<?lQ$4r67cNC?DY z$bEPD{O(ue^x1}0Ey-(A-dz?d>;`Rjx`Z68S_b}VeKI)feCpq{w9)~m3;X&tQ}J>J zkotc7dJmHnECN}x5n-sSr>|UvBNd_DU<@0O5`@nYk%WMlaQfyRg*cb9|KRU0q3L$z z#%!Vr{rv@iqrU)1rx6}j^x&oXSVWbOdbDh;F-wkqQzC7<l1pWGDN2K~oEqaYKSpkK zj;^;r4wU`dcp~y^m!q)?{5|qRU4AAHqU2D`U<cla1r<<byx$9&rwBKi$O}8_hk;#* z^~wR993w;1bvPX4wf)m#oG@d!)P)v`W}+@{EZLW9?yk6&!H@g)zxuWQN%~s$njKWS zyaVBV9NL|?Z(REfKM!oa5$%lnv#uD9FFf>rj1Vw4vzSeVp)WkB2RBs4Cp32$5V0D5 z;Q6m?)*m|<L1I+GH$Gn1{y-314ECkc>MA8#%3=H5XsK|;#~!^9*mdFHfdCbT3^p_{ zf$ue^pz}e;cc!GED^G>mx{mVbhZX9*100BN!}{NS&@JXZZk6!5-z+8W!6^D)$6Jel zdUKEQ`RAahI=PNBV^95VWqeW^w~gfc>2>f7Mt^f!^lt!koxv%iTG*f+(hY~3Qd%e( zs&u33*FI#*FkPhU?#?~LFs-@*r0Cp}GyYp2<aFiERE<*KHk(k_xmqq~ipN{2dO4Q2 z{t(HNq_)_0N?N<^W<Z}ZV5d~}Is$5b6}QaRF)b`&zwnfix&2cD0c)T3ltqLNv3v=3 zdz{O5m?K4|UVKgAUx`Ay#B_LWr@huoXO7gcpDEf{?4b#Aj`IYh{n&AT=nE%9#At87 zRW^Jou(M)T5fT1c@wI%zSF>gmt9D9JA+W1NkUx>#^8<zlVTrbN_R4Cs^svU>;SlFk zrpX6|_{3_|)8SZ|F|V+wVRH9Tjcr=B2zS7z+S8?C!pT4Q_|sqhE|XDG>pfM0lq9)@ zOX5mzm();VRIBh;Q3w>EO63RDzpp2ak7l(fo)rkUhNAA#<Hh>1#rKFIhByA{_K9sF zUCOH+%1&c}u~0~`bXS~As7d|?t>PjZHCiC*-0_rNG8hIZ9HKt~Qd?%o1hm5%yOO5` z=Iw{2bN1l`ioY#h{LA!@Q`LzTq&^%e>Rvo1l)`n6jbhm)mcpH1^_6c=&YT8(%sFz6 z3HjB&Cw7wDLYbo>Fdt2p6x}+Yl5HUjlv!fi#-Vj>?6z{ZbS6Zinre33xwUlji#|`X zh6D=K`yEy6;hpA8Ll9Y0;j7~)c{-OV6Z5gXX%_X$>O*WwFrbg$2e9LY#BG6{eh+AW z=cNm|U;97vKI@v8INj6esoY9M4wJvd0IK(E`A&O2yNsW`1AS+zfw?*@mcin%;AcM3 zNXR8(V88=F-ESRtZ9~5njwFhtQS5h&>`eXE67*`KkedyfM`51DKf&eWSi=AL6)0A` zW4c#ZHc(>gsaqGcN~rKyYDY`3)MWDuqXx(urKjK#SVq2So9q5byZe_R_~=d*wgj%q z2lgWWue2B$(e`+Vp<31Gw^R}eV~xDaR_kc(926z<FHh`%?u|@tGk}4+YRxwJo~_FA zrU<Nkh3$Xgz=)9pU^|*=pa}NqT?pB3e}|&dI5kK<VN<5DmK93_B&!zyl5A;SYDW^v z5IHEAtq}&rcE{?pg01*AK>(>xB?xPJGTPNCSj(OVlFdk67F5w~^bYIhq&3dsNf*Y* z8z<<PGbm(*r^R^+nHTzfPLn!<Io~Or<56lir=RfgEq%nM`Q_$X1d|>J)#FJ8R+5@Q zayXU3Hz?mYHFx;K!?Ck6|A=)bJ^2#<`F0DRsDZj6t$;n;n4+uv!~JL4ZhF9bD%1<# zf!2I={z^<_Mh#w-!k4s7dU0f*mS{7u9?RfN<@5*TjLHRs_iuSncS1DD*q{><Y2W)@ zj6hg(xh02y9aH&pmV5d;75LSRN2>_)WEe8`qP0u(Tp)|HK_{@}h+HrF^6y6lTvIc7 zKm{YMz3?&?hDN>qSmyV!7{F<3wnmjN`hj$g^R1#DS&2u_^LltuCpOTUJ(<@EqG8|S z@G@{c(Ic)a&|XihOIVy<`LZUkhiqL8SW1?w1?<f?-Wh$-C|(bMaU!JC$(IeqL<x{u zA9eziP3Eko%bm6P7^No`VfT|KWjG)dwL;#7&9Bvvxi#s1a-A;~qN8wVr*<8A?sUfh zhgk3mH}8`daLw7>%_~f)kx6BRW+t_NU9Bivb=~mK={8{+(!XAm#%~ZshXzofuVs_J zI#x3&ArV6+^NPxQ_}SaKUE>{|TjM>fj#wlNkTcuL_Ln+k3loc6?W9dr0(c&@CY|=? z-b+?DTBJ1uKFKU@{(86=x>K`F5mWU2k<m(~8;cfQ{hv);IQV*@l>=$$NVtH*yi8vy zy+v3Tsl|>S4RW-i_x`zrs-*hwHfvVRzt!>bZeMTqhhEq{r?QtqBaT(a?Ccy*#cd$q zSAb>V9w2f=fS*gLJn9uGLy0006AHVkmBYI>JJ;xz<Ic;F_VC;iMlS^~RL&M_Ahh}# zANWQ5h8Nz|cQrUC3?vMr`Xgf3Rvn7!qP>D1hjK+63ILX8w-3CzhKTdiY#Sluh`4|? z<#B@~m@j+_UboP!=7*{JCoS}L+0TQ^KgmrK=9b&q-g$5rz1DIOVnGD;-oSO7v9h`x zGkSE)Kom~h27r58UVzwN9-e7bxxRE*?k@)S29tJ2#YgG*rB<_?9+M8k`3qFKSIr6} zk!1*Yo!-ZLH?W`118h{$@5x*VCdkVkJ*DV3ltr#fGuQD-_3?6x{cWh?b)03NU$8vf zjMF(5IY3@EFJJKu&>n&ZllOE)X0$m%0rBtW+QA{Psl{n3B-ur_1C#jq{{8G84v~j> zC5v;Ko!H0NZ|~r57=oyZDu_G|@l`>8f%3L);d*HjFCxL+!ee5ZS4XfG7qal@7a^pU z2i)Y5ck7OY>(7MJ`|ITmtTaC+z|~ElT=nGZ2bUxkm9CNY&I08ivJA9g9K*Kv8twZ4 zwIIj8B29mj^X8$%k<GQ6L4<w%q3YpIl1mwn!<{kzLBNR6d(xy}&nC>OLC(l~AUCKH zA4gInmrZv9{Yw(>UPO(j0D&@+6bS~CsQNcj9iHp6S{fMi44QrOdG-cGVrF>e9<xCB zGwE^=vd(IusfR#MpqOm9J3w$J7U?nx%@A_B<$*80LIzDWtwKfs=xHJBbNcCTIXu%8 zMV$Spf4U4!pS$pja9l}&+ttIFi)w&T1_DJ_EmnkrBJ)T1^baX<iSY4ash9G}ba{Me z%Ac;Rqsx?lReW|Zp$XpvAI1~t-w?aTe;IH(?M*aY0?X7n!US0oqYiaP&$ZrPp64O! zxRaYD@_v|ZSpa}R1JEwuox-yXr`)&Fc)cs$#pwlnas{mHEwT`qimC491j=C_LI`xM zHhE>bW=|!f!5xB)S0Whyji4!CXTX0q*{>l)wcRTkm~<^_7jFhEtXSPl<`>vPSCjE( z+Z4*Nln9PFmE&Cq?tYBkyY()Sw;WeMk+=sBcWCzTM;2~4Q=;x2308e5Q{?Id9zG*E z_^$GN^FD#HIqbl21anO+_+W8?0c^JInp9trCb0ooykB_w?7B7+i#y8yQBCX4d9tkK z;d(-QleEH-dm91%{QeWm)<~dKu^nNUt)!in*$BWVkpGq`;`SO{gF8pab&pREP=(0v zaOc4iSznJN$NjF(N(zY7K_D!ni<!U_tsx;n)+Ou;glx<D4~F*)VXs!f4+yML@*b&q zY$I>GufNbgiR`BlorhpybA}B9Y51}qn<pFEne0E#5e76!6u}B8Dciv-g%QfPTFx&v zlHe|VHPP0@H_QNKuX@bu8DkU*KVzo_2BJb18Cdiq=avn44`u~$V;w`HoG%3tINNKO zYV5^6!^B6S>j-6`Zp4wYHVI7o8s1~aX8Y>pS!!H+YnQ$3N9=q7NZZ{+Wv%_dE4dZJ z3r5{dwOGa+{rl{$8c!$o8Hoc%fjeDxv(MAs`uU<8Cw;uRt-|v<-)&&6YGcp<inX9< zCeJyJp782jW!VyW=pK{@<r;pllgmnf`+dDJ55TBD!5}?vYs%26A?r^q;vT!VE#OQq zh}e{K6a20>#Tp9vv+Ye3>4ebT%K^a-3dCR>!IRQUQ%k3QPqH0)aTUZ?E>wDvZvJ+| zMF$;nR(mC6@JH*MI$OZ&Rr!}vgCC!&JACnxz?gKUAMKvZcRqEX6-A#k({;X@L%NGs zuYN99_I9)IH>y$y!}qKxC!(9$CDh6gpRtsRzgS~%zpE}JMz?{+DTjB_S`@;4SZPT} zX%o8MEI~lOz`D&Ngngs~pDAl8nwi^{dpe$%Zs*U05SDcZ3iIxItw)`#(Ai<gK!M8+ z9<8~(T1l&Qz3mTiA1c5gwp1jep$B>^I@FmwSQ6j^L++u`(=P|Gox84*J<Vu-ahD7r zsuLLtF%iaw<F>8V;?e-UNfk0Gf&7%o)+ZqO+yTqaGcTEIxi3Ep!L>u$37HqI2bsgg z`I$R2i#XYlc*>&fNa$^95WnxvZr4B2`?Sn=2=z+G<<(z0YaVZ$)%lwfA$fH`-RaB6 z{iyaSx;afYdDluHjj(@_=i*Mac_54Z(;FZ#6|xS_4)xeWc{fTY|E)UqOG1%DtlEE2 znQ%&D!5>5V<;Oxxr~?4cK(}rnNh9GS&-A`rK(_(Bx1001VQ^K>nmVDp4`3#aS$qV~ z^0!jPxAD`v;3VraPAuX&rlBE?QC9vw=m-Yvpu;6pNK$HLiKZ=&1L$VR`OVNnzim`C z%fiP|L}kSy9;x6&_73`^f_pTDPYMlgf=&VScC!|8Qh#CnjIERNer@hJHBh4$z)VI( zr^ubK7^LB=DW}_xy@=L51jx$OtqXu$(7BrlNJKpxjpklTZ?;5=DBFR?8t4hb^pgO- z8HP$LU)7)7NJ8q(11S_qj=zi4F9PbR1xhXZGz<QApNL&t6jbM@N#x?b;u7pj)<PfH zTvbU;*TKNdFduUdaN7eu3Q^wEX1^#!yWXWZ=X!h7B#itljDsBa(X#-(%W<M2OBH8p zpd9V3#MX}d!oO9nA0aMH#d1C9FbwusT>kqz5@8|tL>a>c26bK)!HB|X)i~u6{r<%s zC49Symlg>I<|aY#1Z7pm?c$HGn%m5JStG@4Lu^{(YDax6{AD+#kACkx@U>z^c%%I4 z=|>+stSW>)pI<afY8{_Dt9P(`b^cl46T$@a**We_%>J_1KSRkj?8C;W`fP9=x=m&P zHMfrSW2nOK>14A^<mV^GXwkDx`Gw_x%8S9cead7}!XDT}U&CkLALaq#%?={`&>J^) zMGvBcfwO&+@oX?bT?ySClRqmP1k#_qe~_u<q@jT*{mTD#CpDgrZL^(=)7@@~=!vXD z-(chg$<ew;;;h)BpxH{ZV(^adad^>`*DLObgutG&`myP+Z{8!&t_5xHms0MR4fHDh z_$N7x^gw>?{TjZGj}wdHnX=ll^!m67t*r1Y3g8icJ$_G)+}*EGI-ZbU<0LkZ=-E6N zYpcnoNb77Z>aiVweK_Nr?CF>=|Mye2N6X>xH4lq>C<BTI3D166r<>1vzX1)A`7BXu zlco7}NjwF*NY;J;90(LBO%t{H!`ivUp)2la<~YKEBNVl&C`=5a`}mR#7F!^?8m1|t z-K|5YD1ROkhbuKQ-Hpqi1O<vVsT%rkk^}_`1*iZ$&@qu1oEYsdMr+-ZOM14Z*gw%^ z+%A)f1OxRN{JFDtjZI|!JaUzTDME~dUfT+?`n>eT@Q}(bb>--shpZV-Ul@LH6syCK zUd4a29Ura8u9zP;8`C_Q^xIKiqT6zJL*rycOv{;2y5<M2_3&Nn*0@j!3bfIkC)(eG zH6dT36`5SvTD~HAI)2tN>t=AxYt;{P;N6fuF@L-rXu`eAmmO9FDd}NN@0q<x=Q+;W zyI})<5px{_8(}q$MMwO>GwgGA0NA1vHYq23XQll%cHq1z&#yK;J}tA_wp2WRWoySK z3_=VYK%iTCuB_J|XU*}}YE#IS%e8S-??%XKALeFCLrEXJ6IvMCZ~xt-l$YK`$5_i$ z+oA`*I!2?)xvyPIt9dB}s15x{BErCZ-Wka)|Mp5R$C021WOH85)Uiy=%-i%k7suqN zD}%feWBT(A#9CFiG3#Zoo}b3fLZTE_S%78!PH=2BEryX5erpLh4Ak-N$w0v@u?0OR zI|}zZiLqi_0GfuuPVW4Z>V<Im>VXL@mc}J}q*y6tTcq2r6gKN22Vml59@W%o?AYYg z(j}T#<K=a+l1!x*bdFSUtZftVkU|ri1)OlwG>>Q%R%IOzh!lgMU{W}WAxJ<U%{VP~ zc0K2_BVe97l@j)bk^@Z-46}&(ELN;K`=yK{-!wV6Xogsdf0r5{2K8=s70npwq;Vgv zwYW$!bJ4QYVlFsj#txuUADT{B{KYf%eO)o<=}8pTDRFb2uB^?b#{RFamb2dnfAzn= zxXOLQ{AaixK6oDOYsoCx6;CH2k|^H}*eN1g5vlgvV}@tcvvgLh4BqzW!c+5Hn(Ol% z1pq0Ez>anl4y2k6C#{V0RG`~_38l*EeC{UBRy-48q#5OdW;N6tl+h`kC18d6|LJl5 hAGcRS%bCBRU(<BcTwF`i0xjoIvXV*?m12fL{|BDq0+|2+ literal 0 HcmV?d00001 diff --git a/client/assets/android-chrome-512x512.png b/client/assets/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..5c3cff16cf58547397db2ac2262b93910b629b54 GIT binary patch literal 36088 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 QM1MEsWt620AHRP8KZweU#Q*>R literal 0 HcmV?d00001 diff --git a/client/assets/apple-touch-icon.png b/client/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..499047e48cec6cd53e0636e84328ac4aabae2dcc GIT binary patch literal 18511 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 GW&a0hKAimk literal 0 HcmV?d00001 diff --git a/client/assets/cc6f2cda9a25c54a6f83a4fd1205dc5119a27992.png b/client/assets/cc6f2cda9a25c54a6f83a4fd1205dc5119a27992.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0d6cfde4bd8912fd5c52eb12c8488479d804ee GIT binary patch literal 1698 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 literal 0 HcmV?d00001 diff --git a/client/assets/favicon-16x16.png b/client/assets/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f1a46e8836c9c0a7933f789614633ad3eebe2dfe GIT binary patch literal 647 zcmV;20(kw2P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00009a7bBm001r{ z001r{0eGc9b^rhZIY~r8R5*>rlTAw%Q5eUc=bSTVUha!`uIbHup~xhJLJ^5TH-Win z(Wc0+QD3GuVVf|J3S86|2!f0-3@pOXw7kCDd!4y6bIzPIb7G31)-HTj&*t~z|NP-W zV+@>R$cbnGz#Dg_{wxHc_462ZHxI$&1}ukpGFEllwF0NaCzR0f#VdoWM|&Np_gBGo zITY#He>Q@Eh$BfX?g#B?F3*gQ6py`<y(5JlB^JHS7}+>;mdL*hLj)*VAcTO7)wTA1 zu$)Rg^pAHSP1I#X$bW1eyzu)fuL0o!ASAy9N@_qrNVW2}zBC^fFn6pL7=x+Q{$GnR zK$O%MEY31S%!)JfEC&_^0w^WWIqc4LJMlOos!Lag7F4PSzJA<UBoqZ>tjRcSMxmH) zAA~pewp)(vTB+l4pgp}MP-zB7&ERg*m9t}$!}nyYJ}}PSQ%ZIbvDP<Odt!Th*>-s? zY{%1X$$sXR?S1YrV3biHh$C9%qs*eBh?&H4E{<f^De*<WQCW*ZaZ9Vbh`&`74Fu=& z%;W&@iHSI!@G1^3_c-6fPSlD5K>=aI<oWsCZ^2stNJ~{`$#&R|h;?F&*-ip6>Utd0 zeLX{xP>w61S8KhMN9C&fsxj;@X_Z~?AF4m)HhW`n`mWYnel8O=Q>l3?w#!#aRp+5f z_4@H)_Z%G=YY3B@Dekal*p3%enz1a)APVJVxyN0v_}*H#6VC(U0e3795NGR+>dS($ hIQxvJ{tKrs_yvfN5eiHz4rKrU002ovPDHLkV1no5Bzph= literal 0 HcmV?d00001 diff --git a/client/assets/favicon-32x32.png b/client/assets/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..6c7eed57409bd1f40d803026f7320b1109a4a7ea GIT binary patch literal 4065 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 Tgsj0k00000NkvXXu0mjfd`#0? literal 0 HcmV?d00001 diff --git a/client/assets/favicon.ico b/client/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1e9dcb1cafeb88a03f2b373e16d2419f5abc9b4e GIT binary patch literal 15406 zcmeHO37A|}m9Bkz_4cja{_0hAZQWJf>FRXTbb<|p5HJK260!gSVT+>9L`C7784QcV zDuNhbRA8dA7}+5Pgs>&D8KR((iK7TZKx2pm0<uKH{HMA*Gy%uX7{;0T`hDN+_v*d7 zoO|xM=bZbW)1go}6b%VN2zNiUC>9F!hC(6VANpMpL!qniuB&VKdnpupNDYN5sDmbG zG4vj{|G(FgC6Q*c8L!o92~iaJky0!clk}c`OOnLV-S_>};NV~^gf_$wdJZPGPjqX= z8YA(aPcrHAQe5UN+|Or3_Oy&7EKGBmucvwDoK!Y*1|#$51f7+V-DvBhwrZQ-rC02_ z-QS<i@YyRA&*;tw%x!v`H9gH{zMsgXmMOkDFUh3t)ACv;Bd}MiwOUs8{fbpX+eSgt zYlZ#Pk~2A;Ogxbl`9-{;EXpe4Nzp`XV>A|hfmMXhiMI9yP8L2MPbHq@RB4)93nqDu za-QZ;>Ft%ZqIFa>9{WR97B1y<<*R&7xj@k5nSv>QjMb#`IbFUq&1bJmrc=-8W#>@8 ztKy?+nA+2&6x?Pc64{#OvKQ+Gr$Y_wwyey3k`cKTvSa*%nm12Qi|mgQne;lPtgu6^ z-u<iP^Ljjycsd*ozkzz^u&S^y5{teZOT@P((#fZjOlEx~8ht&*GFQ=>yxVf<iFlcn zxHHiHEzt2twB3v^@1X2x-NT_B(Rg&L8kh^I?og}u{>9n~$!zLh!jbT6JKLcT^tl~; zd?}eptpIQ4FBx9@_qHDTW!rW-BXS>&XOdSWQpr2w>D0BHB3_=vT5`56h2~&0j^QF2 zN!F<3AX`#V;?Qg-zsJzU|A{K3XMB`FlyK__598N32gW(@zm)^w;z0)`VH-Kp!!5m= z%c-eU%0qF}>9j+Ip6Q*g>)Nosjk>NY4!?KPl4Pf)ck3~#nj`y5b#c$q7+}Dz`Yv1> z9LxrLw@=KK^M@BEb~sv_8$jPKjV0r&6UoF6U@LA-WKfdH6?nIXk=QG>qEpBfi>i=Q zzSOFhO0I-HzB!Ri--7zL<K2U>ZOhGCaYAwO1l4TMACm8>^_B+)$WD#W5T@V7-g>`X z3l`Ybpe<-Nc|GsUfK6S?YT_xdk$tQ#%wQDp2*FVI;S^ypZ1UrhtIiQDwF|ajHEiiu zI76M5RrnJ*U7QbF-NR|}k+Ab?6)*SsN^eDWI*J9o&6#i4iyxsehz5VqPnodUu&wjl zx*HI$YJ;_`UUU}5;_=7PZ@*dhD~!ZlpUkG$P<zEudQzFx8di}$%c<hQu<wuZws9D2 z@o}K}$H{Ewhep-wR&BK{#b#FUIq7`MvSeCUBcJ!wf-~1@1pAY3f-yzew?3hI<^-aJ zXd>E_ym3M#7F)~7+(frtXu{@yH^VV2^^&t#@s#dVHuX?OU~hxpawY2hLNK+D=@oAQ z_<b{M`=h+2@5k$W9)7{C9C)Mper}lOaauQ{<Q?yK`GVbVr*jp5P|90}8`ZpLHk%ou zJuCB<WByNJ-UkxRvZwDSS-II-Ip1Ja5k67;XYd<dLD?FPL|%aXUZ4kdU2%;Q6~|bl z7VIJ;GL3jTc^~M!LvpmKG+)1~OV9&rf3v+XuRKt0$w9HNkMky0_NI4cXK@02s&nBN zJWu_nIp#L<?ZD@=v94#+9do<^-#5h)vFE_=4S4>2nq^k8ig+Gx$X`Y|o0G+}GaS2& zzM*V^|FqmJ`ITTo+Z3~-FxT!ZS>VTr&(M;=ptqyPY!BuuzVTJG`2+a44SvzjQ0_<j z2jO?#pG8Rv?1O1Gdy530M{<k<;h$cW;<FFn{c5U{=Gptv<^jmwZ>SIWE6<9ic7Zps zW0qYBW_iu3@d5rynE2zB%X!tyeF}5`eKZk&k$3@~HnAo%b0zO&qwFuT8m0Y3({xMC zl41s?t2)MHT0^s%KTd0N7U@Orc+ty!6f$!Q__Ku(viGXK+7<Ly;1Aa%%)8}d<Nm&@ zTrRVcqc4Q7_xngR@>*8nzbE@<B|o9+iH<%`wvAa{wQ6V`d0jBHSy@?J3cvSiqRsCr zeN3|T6LMw$i`YM3hAvnijm5TQcy@)NC}wR=Er9_?_Wk?DrWeEmHE+$v_%Gn++i3q= zM&Zv_@@AQbo>M@dQ+7=3z0W0>^y3+UTbIf*Kf`*gQS;8p@IxobPHt&d<sXKx`YPtI zja8+qa?qt8IN&ZXX^%>t)eoQfDfp*v577|)T}I|FBb`{U*G0)zPQcpydo&q;h5Tsv zug_<M?Ao-DeE_m|4ff1qu~c#s+W!gb_#*VrDM2$JeKVFTY!*xhe7}+WZ8RWX8+?eq zAi25IRnI;+kxo4VU-~Vg4|{Jr_UX&`{wnIfj`6k;&*?pl4?Xz{rDzWh^L{MjhsaK- zdHXo@`5YRw{CxDa9eq8EGuUe4=`dYH8~O3Xr=jz}(C-~s<sE4Ml4vTILC^T1Idew# z8mj)qD*}1;aaOq+YxojskI?WZ%<IjO-?+ENf8QNrgT}W~+4Ke_pS!^BE>CMroywrz ze<^$TzVjkEfFA3C&c6vf{R2MioFAS)c-*1)JBHU`OEez;9cWnPv=<MA{UzPLTNj;^ zVaK{NB6|hSSkGeauMP8h*YS7YJ)O@;-e1R=_DSgcFJ%?s48hcAxR6;4Jm$HNCbG33 zEXihY#!YeA>m!NyhHx~p3AAj%crW4pB7Sd-XHpO2JbXLkej{}Ly;&n$rTt3w<-a@j zC_2LMcNp20E!GexLoSZxRq<5J?{rR)7Q?<C4jpx<?CM{`KD<G&w2zaX7}b7HJ%-6Y z&>K1<1_5Cff&_n!`0Lc$_EC%0Y{kSWd}!=Ht7G>RF{Anklik)E4dw&v%BX7NkK-H| z=fG$V5KaP!HgpWiI8Fj4j9=p%80Ww^2gW%t&Vg|bd{`U^*XGPg5bl)+jzZ@L`b}po z7C0fRDCKx2-W`v}dor0!H_8NtVY;Z$GrgzpvMg6~#hf~f14ja!NpMce<9pX|+X;*y zAhyX+8EM-@+h$f2I~30|Xk2Pv>??9WX;s2v=^ZBi=gx~^s^77HpH6sLuI$bs{IoPU zsL(uEU6~FX<WgXBD}h7Y4ZQd^l$(JE--vP(o^QqbAI6f2yMPORQ!wONgpK$(A8SRg zpUX+7<J;}PRPQ0Y8Rzfs;|#qV&o`j`E$H`7e7ifvG1sy=`RMY%1haThA4j;M*(lBk z20D6bgA;SZJbB+7Xm0eq&{H0;8pYXGC77;Pf_icCWYw&fD!`LY2Y&r9;-WtQj(a&U zyfYAAJ}bqh&rPz-d5KKs%w#6B6ld%eDJH!(D{^P&YHp3<#jMI7ml3mf5nh;JGRx5J z%fJM`(kd+C)S1ke5Wl_v{V&J;r-ZRu?S*{{dp9(@D`+E}z-|u?a~h?|guxR}MhPHf zrk2R}b?AhZyY=E6tCpW_cNdBcocrpNCW(Y=10!Ar{yhPV_Ef#-@5Ne5i8tjbz|I$B zRcWpgI6Ydy8~}d**?1!U6U^&7YT2GfxO*a%I3u1)K8bpl$euAvbj?W=X9uQ!lAz0n z%D!A7EPNPO{}Hgxm0DmevWi6;vYT-_fGY>~EWPB<wL6M~gs~3KAsku~iV*H;c9jQ= zvUdb<%xP}DEg;%4#|Yu`R!7@(2Jr&O$75+(nCVO?n`+UVz-q#USnqov3pcC2b#%VF z+~9yqCxI;kA70Lx%6ug-dVon^2rTT!h|!;Db`+<Ij`m4luXo1NiAPgB^DV*#EBp7# ztR(fLuNx8HSP9;I(a0Mf(Ll5i)(w1os#bE3GV5(q2=^rHiuPu%WcR8C>u4pgrYre; zc^KPho%4x)7xWX(9F9gGN;1q;Z*tYhRqR3V?|Sh0DZ~OE1&(>9*Hzh@71@Clle!D@ zx*J%{nY<w#4_xnh!u%=D;dEA}0@L^c=zI|C`8?+E5N~LQH6SarpA$^#yM(Lrx_p{p zwCE>Mi4bO_xo!Zga*BaD*tL8w(cZ!>fV<1C`DqpFN_hbxyhocb&5tnMT+y8k%=RYG z5BX0{E9}$Z(B5V>{sQDs+=!fmYvjN<&FsWGm7Rn-cVP}sLgwzox?Km%bX_DC`6(l_ zM;pLHw4!|^Wc)Jh6`I2etm907Vl5YEm`=oOt^qE*QqZI&vT0U!(gJ!2>j5q8M%h_p zmE8S__Bljjhga1~_Ds<?=gYp`N3k+$7ffmL6tfUb^;Ga{HT1)eGeYK2ujv^in}Vb6 zBbn-4(bVS|<)FvFnu4zVAU~^O@z`eIlrN(E4p`PJ;QxBs$H)z6*Yb9QuyE1RPovxq z?1?I2JJ7*RtjBWXkvzi6!j%*Qp#8s7PW+s2=G_AjPdVOgFANa>X>Hs|b<yoCw(E$4 znKl1Vt6uCUeh}?~seT50x&yNR9I(D$Cwb<4$iaLrryeBO+Dya&j+8C!D9qsyKBvxx zZdn4_k-I^dZ{$tT|3~0(Zvm^^h_(6#t4qgGjtRwMAV;4;Jn4ARQl^VJ^<eCo#S|mL znrwv3t%b~9j2Pa&WCPlI+5)p4Owp_UTnqUx`6}f9-8e~v;)KP%S`B-1hSOe}=X8}O z>TUJ{=!fqhFXnl|i9yR&lJ&I6eGhx=GUUE|o$_C@s&EO)*BHdIAZM#l{{`rC(2#fn zdiEjU%TM9=^U%9bK(=oIhQE~4rAtsQMvUQN>I2yNzXI>S2Wz|u^L_*T+5%qu2yuu{ z<QoW{^t2t|Gz#;qj$k_Jk73?p3PTskQ7hKj+gb9uYx|K6FslAy#1WTa>=)4ITj0&h z(9cgp-~W{6f$|7qP7lGZtpUEc26Fpol4E{?_`_Pzb2VZuCrh?=SO$4AsCN&(tpkry zvJX=miVNX+4gF4JQ$LTTlN&L{E11Kp&`-a~YT~teoBQef#P&lF13kj-KqL@yL3??G z?p^NT@{9qtvR9XnSk~TV&08Q@+C|Xk&tm<z!%qGdF{wMjuZuzN>DYG{K_0Gv41ZHJ zly3+*^=n*CITP#iDalr+1%1`Fd|#hV`kvR6MPxe=_q>qm^VAL{tBSx@xieD&^KGo% z&!KBy0?xY`aku-?=Sg<Ga1d<M0WEqu3eFgC$QG7r*KEnE7WYD2_aA|m{|bBXZP=h^ zA*+`e75_l==Xkx9ep)ArZ$Td|^t!4C7Y91q?OIJD{FP$RnxhrCTy7#_K$G2irRjE6 z53sw6M<9-HG~!Xmn$^Nw(q)3BET$M8V$+)ux7mo?rfV_Jrrlq%h!3;}!0WNbD#Elz zR(q)vdid06H2N#FeFyt@GxWmkh<(f>yI}WsS~~O?#l|EjcdS-$2TIMR(wN#{^s<+y zSRHWee?rXiQsjr7k9P|ZC!b+=6lN-}cB0~2i;ZeP<?lIDz8X62myr8y*t^d`Cx06` zbnWCD^v~%}?`E7OM1GRj-BA?`<+HI^?3YA8aQ#0*E;k@<crnF&m8xe@%viGYy}+v; ziaiqVRnMD1`wn~cO32#^(DEQ7@b@7v>pIyrPIKUoNv?JPbkGS};9La#`EAsF6mm~7 z<2OP7uPKKMvBB~#@;&ySWEXlHdC+wN=wFY9t(YCfV6YD!N9^zd(*Js)pwU_l+rn(l z&9zeu5xV3q=$`*T{Pq{H#p|)}H$eZahYxYB=36I`&p`2P=!fOd$B&?HOUIF(L*1W1 zZqL$OEf_n&yVOL^8~L6TONIS>40Mmgx8BC{Ht59lh&^6T`xJICOSUlR3`)GEoP>Sy zAYzc4VyVPdit}PWZURluK>x36#j0igCbYW*ytxK3^vCEMc)u+a4pY47P57y+$bX}F z>Mm66&d+f3kY)z9cM|O3?a*sm;8D;Tv|?14!)x%}$)=u5eoqJdS}CU<4n27z<r2bX zZO8o$vSIMuUV&e*3H$OlSi5H_Zcb|fIyX>^6|}zsxoO2c(eJC!r{5(%uMWGqI|T1q zm+}rx#Fqu+XTq<dxbL4v=*N8rVy(!J%&bAa=n^fk55u^Z#FDUepk=ro;+<i{9HXy8 zhixOfM12gG?VuI&i?-fTeYAT6>-r4z=~;GntvGnhd;EM<+5L;ok>ppy?%WP~DQ-ME zUP<xe&9GPN5KFujIh5;&2CUQQI`3c(6i+6)cdj4$p>-sA8Hw>C-!rxuvA)|xPhZgD zJ@(-4Q~cf**q0fnQSE@uIty#R0X%zsc<h!OVf^jj;Wo(nZ!wphG2zj5{?aqe0c~GJ zF6vXVms@JK`}6WW<uZ+pw(mPD;@$3Ct@DH8=rh2Z?_gg(gLN3$%dI(}Gm?`LK90N_ z(;c*v9KMbH^-_jqo`62ON-w*A=QZ2+t@Zayp?BW-Yw4%CM(s}9qBA-6-OZHejB!cE z-zz7>dmHb^N;~8#yq#nYd*ij1?Zx?*@}h#i&PjbZztiA*;v)Sn;5^rgSF@RA&>{Z@ zSsKZs7!$pqV|2Z@p`W%v*CJOsbuY>)DmYJG$r<ud)xE18Z0P87`d>W_((%Yi*U5jU z96b1SkHNQqZyF{2x1Ds&E;PPNFXU^8&(W=DvjzNq9lDDAv<Ki@oFW?9B2E)ODY+({ zPdwrm`q~q+GkV@(>enop3a{|putz7Rc=j8}MSC~`9~<-`KP3$P6Mhr&wX@*&8~FBe zG?{oBxdy9&QLLh~FXUwtY{-hNoZZhVTDEN47Uiyu<jaj7c#l6L@0^Ek$!qcyng?*5 ze@3puRq)|&f-i6<^7dCz0eve8qk?RF3;AE?!k@ZGFqP%-m7f59^#J_vGkA?JfWH69 zyZ~Yct!)%ZJqa-odCJQZU2OpV#!Q?8j)m{u`o9J|A0s&Wp^BgTh*@$c=_UIF(NdSf zk64EDz-h=K`UvIj4fjX={w=NutvUJVBwLiz=s1pv{|&I{*{Z<TE|n}77?WA`=Mh#U z7{Wf}pAqfk>;5f>-kmX9Ie_^85xXALDPNKDOvZ4Q+MOXj?7A&Hsm1Fde(!1B@?q0I W-v2lU#yK#~fpHFub6}5g;6DKvEYf@c literal 0 HcmV?d00001 diff --git a/client/assets/original-lgo.svg b/client/assets/original-lgo.svg new file mode 100644 index 00000000..d1dd5ac8 --- /dev/null +++ b/client/assets/original-lgo.svg @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no" ?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1280" height="1024" viewBox="0 0 1280 1024" xml:space="preserve"> +<desc>Created with Fabric.js 5.3.0</desc> +<defs> +</defs> +<g transform="matrix(1 0 0 1 640 512)" id="background-logo" > +<rect style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-opacity: 0; fill-rule: nonzero; opacity: 1;" paint-order="stroke" x="-640" y="-512" rx="0" ry="0" width="1280" height="1024" /> +</g> +<g transform="matrix(4.409608640845863 0 0 4.409608640845863 639.575765692782 483.59323765322904)" id="logo-logo" > +<g style="" paint-order="stroke" > + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 -0.33628603433743365 0)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-1023.4867588947095, -970.8556833164547)" d="M 899 368 C 898.481 372.043 896.575 373.637 893.562 376.25 L 890.801 378.652 C 887.494 381.424 884.133 384.116 880.7460000000001 386.789 C 875.6790000000001 390.86899999999997 870.777 395.142 865.8570000000001 399.397 C 864.0073774683491 400.9916527081135 862.1540390966135 402.5819903648671 860.297 404.16799999999995 C 847.7330000000002 414.93600000000004 835.7240000000002 426.198 824.0000000000001 437.875 L 821.6080000000001 440.252 C 817.4090000000001 444.463 813.3760000000001 448.759 809.4970000000001 453.267 C 807.676 455.375 805.7760000000001 457.402 803.8750000000001 459.438 C 786.138 479.185 771.342 501.567 761 526 L 762.949 525.025 L 765.688 523.656 L 768.717 522.141 L 775.357 518.824 C 782.2389999999999 515.3879999999999 782.2389999999999 515.3879999999999 789.102 511.91399999999993 C 858.8629999999999 476.49199999999996 932.054 455.55299999999994 1009.75 446.8119999999999 L 1012.03 446.5549999999999 C 1030.949 444.4369999999999 1049.653 443.7589999999999 1068.6879999999999 443.7499999999999 L 1072.485 443.7479999999999 C 1117.193 443.7859999999999 1161.245 447.62399999999985 1205 456.9999999999999 L 1206.962 457.41699999999986 C 1260.908 468.8799999999999 1312.162 487.57299999999987 1362 510.9999999999999 L 1365.007 512.4139999999999 C 1379.144 519.1219999999998 1392.582 526.9669999999999 1406 534.9999999999999 L 1408.558 536.5209999999998 C 1418.6093701689476 542.5003279914471 1428.4285367980367 548.861637531355 1437.9939999999997 555.5909999999999 C 1440.0386212864407 557.0245325637745 1442.0889710368579 558.4498773097968 1444.1450000000002 559.8670000000001 C 1458.3107278086452 569.6904053810316 1471.9589545550316 580.2398271111927 1485.0349999999999 591.4729999999997 C 1487.942 593.9509999999998 1490.8990000000001 596.3549999999998 1493.875 598.7499999999999 C 1497.549 601.7379999999999 1500.956 604.8219999999999 1504.234 608.2349999999999 C 1505.9209999999998 609.9209999999999 1507.664 611.4559999999999 1509.484 612.9959999999999 C 1514.562 617.4099999999999 1519.301 622.1509999999998 1524.0459999999998 626.9149999999998 Q 1526.3179999999998 629.1929999999999 1528.6 631.4629999999999 C 1533.3829999999998 636.2429999999998 1538.0559999999998 641.0729999999999 1542.455 646.2139999999998 C 1544.461 648.5319999999998 1546.5649999999998 650.7459999999999 1548.677 652.9679999999998 C 1555.625 660.2899999999998 1561.932 667.9409999999998 1568 675.9999999999999 L 1569.58 678.0799999999999 C 1591.56 707.0719999999999 1611.047 738.0899999999999 1626.946 770.8349999999999 C 1627.6779153777534 772.3378303969731 1628.4142518997246 773.8385035621671 1629.1550000000002 775.3369999999998 C 1658.262 834.231 1674.142 898.878 1681 964 L 1681.412 967.88 C 1683.799 990.968 1684.4170000000001 1013.833 1684.2640000000001 1037.034 C 1684.2473332860056 1040.5279755769673 1684.238666596621 1044.0219847356602 1684.2379999999998 1047.5160000000012 Q 1684.228 1053.903 1684.203 1060.289 L 1684.197 1062.642 C 1684.167 1072.191 1683.875 1081.684 1683.2489999999998 1091.214 C 1682.8689999999997 1096.9959999999999 1682.5889999999997 1102.77 1682.4379999999999 1108.562 L 1682.2479999999998 1111.885 C 1682.0949999999998 1117.984 1682.0949999999998 1117.984 1684.6689999999999 1123.343 C 1686.7019999999998 1125.3400000000001 1688.8249999999998 1127.161 1690.9999999999998 1129 C 1703.2699999999998 1144.806 1704.3739999999998 1161.069 1704.3709999999999 1180.433 C 1704.3749999999998 1182.866 1704.4109999999998 1185.298 1704.4489999999998 1187.73 C 1704.5859999999998 1206.736 1700.754 1224.771 1687.2299999999998 1239 C 1680.7109999999998 1244.846 1673.4609999999998 1248.832 1664.9999999999998 1251 L 1661.3479999999997 1252.005 C 1657.2699999999998 1253.0210000000002 1653.2619999999997 1253.3780000000002 1649.0699999999997 1253.527 L 1644.6639999999998 1253.74 Q 1641.2369999999999 1253.894 1637.8109999999997 1254.029 C 1635.5839999999996 1254.12 1633.3589999999997 1254.229 1631.1329999999996 1254.34 L 1629.0529999999997 1254.406 C 1626.9189999999996 1254.485 1626.9189999999996 1254.485 1623.9999999999995 1255 C 1618.7629999999995 1262.855 1620.8489999999995 1275.455 1620.8419999999996 1284.663 L 1620.8269999999995 1289.315 Q 1620.8099999999995 1294.295 1620.8009999999995 1299.277 Q 1620.7829999999994 1307.188 1620.7479999999994 1315.101 C 1620.6859999999995 1330.095 1620.6319999999994 1345.0890000000002 1620.5989999999995 1360.083 Q 1620.5709999999995 1372.526 1620.5109999999995 1384.969 C 1620.4899999999996 1389.342 1620.4739999999995 1393.7150000000001 1620.4719999999995 1398.088 C 1620.4369999999994 1448.706 1611.7129999999995 1492.19 1575.9999999999995 1530 L 1573.7069999999997 1532.488 C 1550.0509999999997 1557.3020000000001 1514.1329999999996 1570.94 1480.2069999999997 1572.095 C 1477.1279999999997 1572.134 1474.0509999999997 1572.143 1470.9719999999998 1572.14 L 1467.4159999999997 1572.1490000000001 Q 1462.5709999999997 1572.159 1457.7259999999997 1572.16 Q 1452.4839999999997 1572.164 1447.2419999999997 1572.1760000000002 Q 1435.7999999999997 1572.1960000000001 1424.3569999999997 1572.2 L 1410.0459999999998 1572.212 Q 1390.2009999999998 1572.231 1370.3559999999998 1572.238 L 1367.7949999999998 1572.238 L 1365.2269999999999 1572.239 L 1360.078 1572.24 L 1357.498 1572.241 Q 1336.815 1572.248 1316.133 1572.286 Q 1294.857 1572.3210000000001 1273.58 1572.323 Q 1261.6519999999998 1572.323 1249.722 1572.3480000000002 Q 1239.564 1572.371 1229.404 1572.3600000000001 C 1225.955 1572.3560000000002 1222.506 1572.3570000000002 1219.057 1572.371 C 1179.618 1572.506 1147.205 1560.851 1118 1534 L 1115.961 1532.176 C 1106.286 1523.0739999999998 1098.457 1511.5059999999999 1092 1500 L 1090.997 1498.214 C 1075.256 1469.73 1071.353 1437.973 1069 1406 L 1068.805 1403.359 C 1065.601 1359.79 1065.601 1359.79 1065.554 1342.6589999999999 C 1065.4450000000002 1336.647 1064.7430000000002 1331.8519999999999 1061 1326.9999999999998 C 1054.065 1322.4319999999998 1047.891 1322.4139999999998 1039.742 1322.4689999999998 L 1035.918 1322.4569999999999 Q 1031.9319999999998 1322.4519999999998 1027.946 1322.4789999999998 C 1024.552 1322.5009999999997 1021.1589999999999 1322.4879999999998 1017.7649999999999 1322.4639999999997 C 1014.5069999999998 1322.4459999999997 1011.2499999999999 1322.4589999999996 1007.9919999999998 1322.4689999999998 L 1004.3179999999999 1322.4439999999997 C 997.9229999999999 1322.5149999999996 993.8399999999999 1322.6499999999996 988.9999999999999 1326.9999999999998 L 987.1719999999999 1328.6409999999998 L 985.7499999999999 1330.2499999999998 L 984.2969999999999 1331.8279999999997 C 980.1519999999999 1338.7689999999998 980.271 1347.9559999999997 979.5619999999999 1355.7889999999998 L 979.1819999999999 1359.8339999999998 Q 978.6819999999999 1365.1519999999998 978.1979999999999 1370.4709999999998 C 977.872020634352 1374.0016141815793 977.5430205139286 1377.5319488071232 977.2110000000007 1381.0619999999997 C 976.2639999999999 1391.1939999999997 975.3169999999999 1401.3259999999996 974.4329999999999 1411.4629999999997 C 973.5899999999999 1421.1059999999998 972.6649999999998 1430.6869999999997 971.1249999999999 1440.2499999999998 L 970.6009999999999 1443.5469999999998 C 964.2949999999998 1482.2879999999998 948.8309999999999 1519.8779999999997 916.7699999999999 1544.5779999999997 C 913.5769999999999 1546.8469999999998 910.3379999999999 1548.9519999999998 906.9999999999999 1550.9999999999998 L 905.1659999999999 1552.1349999999998 C 877.204 1569.2539999999997 846.7959999999999 1573.9629999999997 814.452 1573.7129999999997 Q 810.396 1573.7149999999997 806.3389999999999 1573.7249999999997 C 799.8439999999999 1573.7359999999996 793.3489999999999 1573.7159999999997 786.8539999999999 1573.6849999999997 C 779.2583726837896 1573.6546677696194 771.6626869955477 1573.6413344028967 764.0669999999983 1573.6450000000007 C 750.5039999999999 1573.6419999999998 736.9409999999999 1573.6109999999996 723.3779999999999 1573.5619999999997 Q 703.718 1573.4919999999997 684.0569999999999 1573.4799999999996 L 681.6049999999999 1573.4789999999996 L 676.7119999999999 1573.4759999999997 L 674.2729999999999 1573.4749999999997 L 671.8389999999999 1573.4739999999997 C 659.262 1573.4659999999997 646.685 1573.4439999999997 634.108 1573.4189999999996 Q 627.3159999999999 1573.4049999999997 620.5219999999999 1573.3979999999997 Q 609.7549999999999 1573.3859999999997 598.9889999999999 1573.3439999999996 Q 594.0809999999999 1573.3239999999996 589.175 1573.3189999999995 C 553.6819999999999 1573.2839999999994 553.6819999999999 1573.2839999999994 538.875 1568.8119999999994 L 536.649 1568.1629999999996 C 513.645 1561.1779999999997 493.383 1548.4609999999996 476 1531.9999999999995 L 473.508 1529.6679999999994 C 450.29699999999997 1507.2889999999995 436.54699999999997 1477.3409999999994 429.688 1446.1879999999994 L 429.204 1444.0189999999993 C 426.67400000000004 1431.6359999999993 426.848 1418.8819999999994 426.826 1406.2959999999994 L 426.80600000000004 1401.4229999999993 Q 426.785 1396.2229999999993 426.77000000000004 1391.0239999999994 Q 426.74600000000004 1382.8039999999994 426.70500000000004 1374.5859999999993 Q 426.59900000000005 1351.2319999999993 426.523 1327.8769999999993 Q 426.48100000000005 1314.9439999999993 426.41700000000003 1302.0089999999993 Q 426.377 1293.8339999999994 426.36100000000005 1285.6579999999994 Q 426.345 1280.5929999999994 426.314 1275.5259999999994 Q 426.30400000000003 1273.1719999999993 426.302 1270.8169999999993 C 426.2995018995649 1268.6813047946193 426.28950183787106 1266.545624952199 426.27200000000005 1264.4099999999987 L 426.25900000000007 1260.7839999999994 C 426.28600000000006 1257.8599999999994 426.28600000000006 1257.8599999999994 424.00000000000006 1255.9999999999993 C 421.39900000000006 1255.6929999999993 421.39900000000006 1255.6929999999993 418.34800000000007 1255.7269999999994 L 414.83200000000005 1255.6729999999993 L 411.06200000000007 1255.6249999999993 C 388.12000000000006 1255.0579999999993 371.78100000000006 1251.7809999999993 355.2150000000001 1235.2149999999992 C 345.0890000000001 1224.2599999999993 342.87200000000007 1210.7499999999993 342.7620000000001 1196.4219999999993 L 342.73700000000014 1193.3699999999994 Q 342.71700000000016 1190.1819999999993 342.70500000000015 1186.9959999999994 C 342.69333621999556 1184.8392564576557 342.6726693732639 1182.6825710003363 342.64300000000014 1180.5259999999992 C 342.6035040870435 1177.3871425421296 342.58050376944675 1174.2480991970542 342.5740000000003 1171.1089999999995 L 342.52100000000013 1168.2329999999995 C 342.56400000000014 1154.8339999999996 349.2720000000001 1141.3879999999995 358.28500000000014 1131.5859999999996 C 370.5070000000001 1120.2829999999994 382.5430000000001 1118.3859999999995 398.57000000000016 1118.1789999999996 Q 401.41400000000016 1118.1199999999997 404.2570000000002 1118.0589999999997 C 409.4490000000002 1117.9499999999998 414.64200000000017 1117.8659999999998 419.8340000000002 1117.7869999999998 C 424.28700000000015 1117.716 428.73900000000015 1117.6309999999999 433.19100000000014 1117.5469999999998 C 446.57100000000014 1117.293 459.95200000000017 1117.0769999999998 473.33300000000014 1116.87 Q 484.83500000000015 1116.6899999999998 496.33500000000015 1116.495 C 573.3830000000002 1115.215 650.4210000000002 1114.619 727.4800000000001 1114.625 L 738.1420000000002 1114.622 C 785.3520000000002 1114.602 832.5550000000002 1114.7530000000002 879.7550000000001 1115.778 Q 883.8030000000001 1115.863 887.8530000000001 1115.94 Q 894.1270000000001 1116.064 900.4010000000001 1116.226 Q 902.657 1116.2820000000002 904.9140000000001 1116.324 C 929.0790000000001 1116.79 949.6060000000001 1122.7060000000001 968.0000000000001 1139 C 969.7800000000001 1141.018 971.4420000000001 1142.991 973.0620000000001 1145.125 L 974.3710000000001 1146.847 C 980.8650000000001 1155.639 983.3040000000001 1164.2169999999999 986.0000000000001 1175 L 1056 1175 L 1057 1168 C 1062.024 1150.588 1072.68 1135.902 1088.227 1126.531 C 1102.064 1118.8909999999998 1118.1080000000002 1117.0249999999999 1133.7150000000001 1116.719 L 1136.152 1116.67 C 1209.644 1115.2250000000001 1283.126 1114.835 1356.632 1114.871 Q 1371.269 1114.8770000000002 1385.906 1114.8680000000002 Q 1400.331 1114.861 1414.7559999999999 1114.8650000000002 Q 1423.2399999999998 1114.8680000000002 1431.724 1114.8640000000003 C 1452.9279999999999 1114.8530000000003 1474.106 1114.9230000000002 1495.3 1115.6420000000003 C 1508.912 1116.0990000000004 1522.514 1116.1270000000002 1536.133 1116.0980000000002 L 1543.924 1116.093 Q 1552.964 1116.088 1562.004 1116.0720000000001 Q 1572.336 1116.057 1582.668 1116.0500000000002 Q 1603.8339999999998 1116.035 1625 1116.0000000000002 Q 1622.133 1115.5100000000002 1619.266 1115.0250000000003 L 1616.04 1114.4760000000003 C 1612.301 1113.8910000000003 1608.667 1113.5970000000004 1604.887 1113.4020000000003 L 1602.7269999999999 1113.2900000000002 C 1595.2279999999998 1112.9200000000003 1587.723 1112.7610000000002 1580.216 1112.612 C 1566.2109999999998 1112.324 1552.216 1111.8780000000002 1538.225 1111.1870000000001 C 1536.5381660019339 1111.1041751864263 1534.8511623887068 1111.0248416831762 1533.1639999999998 1110.9489999999998 Q 1529.65 1110.785 1526.137 1110.598 L 1524.024 1110.5059999999999 C 1519.2289999999998 1110.2289999999998 1519.2289999999998 1110.2289999999998 1517 1107.9999999999998 C 1516.2641672026366 1105.5529012071734 1515.6012080101023 1103.084478700564 1515.012 1100.5979999999997 L 1514.451 1098.3209999999997 C 1513.856 1095.9029999999998 1513.272 1093.4829999999997 1512.688 1091.0619999999997 Q 1511.5400000000002 1086.3609999999996 1510.3870000000002 1081.6599999999996 L 1509.794 1079.2379999999996 C 1507.864 1071.3779999999997 1505.797 1063.5569999999996 1503.7220000000002 1055.7339999999997 C 1501.3930000000003 1046.9169999999997 1499.1020000000003 1038.0899999999997 1496.8060000000003 1029.2639999999997 C 1496.183071053852 1026.8703005333193 1495.55873761304 1024.4769667879757 1494.9329999999998 1022.0839999999985 Q 1493.5520000000001 1016.7979999999998 1492.1800000000003 1011.5119999999997 L 1491.3000000000002 1008.1359999999997 C 1489.535 1001.2969999999997 1487.976 994.4829999999997 1486.8050000000003 987.5149999999998 C 1485.2560000000003 982.6759999999997 1481.4710000000002 980.4889999999998 1477.2460000000003 978.0389999999998 L 1474.4350000000004 976.3819999999997 L 1471.4380000000003 974.6249999999998 C 1460.4330000000002 968.0509999999998 1450.1360000000004 960.8339999999997 1440.0000000000002 952.9999999999998 L 1438.0040000000001 951.4639999999998 C 1433.9736314829283 948.3463364547858 1429.9721276736516 945.1915353842268 1426 942.0000000000002 L 1423.117 939.707 C 1417.201 934.946 1411.5729999999999 929.947 1406.02 924.773 C 1403.866 922.7710000000001 1401.658 920.851 1399.422 918.941 C 1394.077 914.283 1389.101 909.25 1384.1000000000001 904.23 C 1382.439748355909 902.5665833913621 1380.776746080373 900.9059144523521 1379.1109999999994 899.2479999999994 C 1373.5300000000002 893.6700000000001 1368.1190000000001 888.009 1363 882 C 1361.587246553395 880.4547419761622 1360.1705753282572 878.9130703488065 1358.75 877.3750000000002 C 1354.66 872.848 1350.89 868.128 1347.155 863.31 C 1344.204816242755 859.5166870406074 1341.1731103767622 855.7874854447504 1338.062 852.1249999999999 C 1333.117 846.239 1328.5269999999998 840.12 1323.945 833.949 C 1321.3 830.3879999999999 1318.597 826.877 1315.875 823.375 C 1311.667 817.939 1307.651 812.383 1303.684 806.77 C 1301.8066971376147 804.1233340091112 1299.9029350019368 801.4955355647847 1297.973 798.8870000000001 C 1289.982 788.0329999999999 1282.693 776.8309999999999 1275.526 765.42 C 1273.5057701878745 762.2190344582289 1271.4553453592514 759.037225810983 1269.375 755.875 L 1267.648 753.242 C 1265.944 750.924 1264.086 748.977 1262 747 L 1261.515 748.613 C 1259.5800000000002 754.753 1257.037 760.3910000000001 1254.125 766.125 L 1252.68 768.991 C 1247.1390000000001 779.86 1241.1080000000002 790.442 1235 801 L 1233.677 803.307 C 1223.299 821.395 1212.038 838.973 1200 856 L 1198.474 858.17 C 1191.163 868.5419999999999 1183.495 878.5219999999999 1175.4009999999998 888.294 C 1173.502 890.606 1171.658 892.959 1169.812 895.312 C 1166.3319999999999 899.6850000000001 1162.6029999999998 903.791 1158.7849999999999 907.871 C 1156.444 910.425 1154.3229999999999 913.122 1152.176 915.84 C 1146.335 923.0160000000001 1139.811 929.489 1133.253 936.0070000000001 C 1131.1304976015326 938.122835043998 1129.0134904707077 940.244175522752 1126.9019999999998 942.3709999999996 C 1119.5679999999998 949.7130000000001 1112.1559999999997 956.575 1103.9999999999998 963.0000000000001 C 1102.8229533688304 964.0052161626198 1101.654906797671 965.0209233265248 1100.4959999999996 966.0470000000003 C 1091.464 974.0440000000001 1081.9129999999998 981.1330000000002 1071.9999999999998 988.0000000000001 L 1069.4379999999999 989.7760000000001 C 1037.876 1011.5210000000001 1002.9979999999998 1028.672 965.9999999999999 1039 L 963.0299999999999 1039.832 C 935.8149999999998 1047.401 935.8149999999998 1047.401 925.9999999999999 1049 L 923.5549999999998 1049.418 C 914.2859999999998 1050.274 907.1119999999999 1042.985 900.3749999999999 1037.5 L 897.6019999999999 1035.176 C 895.3519999999999 1033.2939999999999 893.0739999999998 1031.4759999999999 890.7499999999999 1029.6879999999999 C 886.5398456258097 1026.4251317340543 882.3524112323407 1023.1330522825424 878.1879999999996 1019.8120000000001 L 876.2279999999998 1018.262 C 872.9639999999998 1015.66 869.8779999999998 1013.0369999999999 866.9999999999999 1010 L 868.9139999999999 1009.652 C 884.9429999999999 1006.697 900.4669999999999 1002.9300000000001 915.9999999999999 998 L 917.9239999999999 997.393 C 946.763 988.292 974.169 977.657 999 960 L 1001.941 957.926 C 1037.098 932.395 1059.964 896.08 1070 854 L 1068 854 L 1067.633 856.16 C 1060.028 890.2869999999999 1029.406 920.536 1002 940 L 999.991 941.44 C 961.65 968.5840000000001 912.744 980.9920000000001 867.857 992.4540000000001 C 864.771 993.2450000000001 861.7909999999999 994.0780000000001 858.805 995.195 C 855.486 996.147 854.2109999999999 996.2850000000001 851 995 C 848.357 992.999 846.001 990.745 843.625 988.438 L 841.703 986.631 C 838.51 983.5899999999999 835.491 980.473 832.617 977.129 C 828.4939999999999 972.333 824.005 967.934 819.5 963.5 C 814.368 958.449 809.382 953.376 804.693 947.911 C 801.22 943.9899999999999 797.399 940.4 793.635 936.762 L 791.816 934.977 L 790.174 933.385 C 789 932 789 932 789 930 L 790.886 929.696 C 829.435 923.458 867.31 915.857 898 890 L 899.546 888.712 C 917.452 873.622 936.528 848.737 943 826 L 943 822 L 940.91 825.148 C 935.4259999999999 833.322 929.775 840.833 923 848 C 921.9332622347729 849.2672564319198 920.8705901066373 850.5379296290504 919.8119999999997 851.8120000000004 C 894.57 880.686 860.457 896.165 823.793 904.373 Q 821.177 904.9590000000001 818.569 905.58 C 813.332 906.813 808.1669999999999 907.5120000000001 802.812 908 C 795.783 908.678 788.85 909.564 781.883 910.73 C 778.844 911.015 776.8810000000001 911.013 774 910 C 771.676 907.953 771.676 907.953 769.312 905.25 L 768.0070000000001 903.762 C 763.5820000000001 898.651 759.4820000000001 893.285 755.4230000000001 887.882 C 753.8213887620489 885.7698051036372 752.209034533544 883.6657778366695 750.586 881.5699999999997 L 748.8160000000001 879.2739999999999 C 747.6713521571096 877.7896225032539 746.5253516811403 876.3062885538474 745.378 874.8240000000008 C 738 865.217 738 865.217 738 862 L 736 862 L 735.66 864.471 C 734.889 868.593 733.7149999999999 872.474 732.4449999999999 876.465 L 731.6659999999999 878.942 Q 730.4019999999999 882.942 729.1249999999999 886.938 L 728.2429999999999 889.707 C 718.5989999999999 919.955 708.438 949.958 693.8749999999999 978.25 L 692.1729999999999 981.567 C 678.2069999999999 1008.147 659.3809999999999 1029.007 633.1879999999999 1043.75 L 630.8139999999999 1045.092 C 628.9119999999998 1046.13 626.9589999999998 1047.074 624.9999999999999 1048 L 622.9999999999999 1047 L 625.1329999999999 1044.457 C 639.534 1027.03 651.126 1008.319 661 988 L 661.983 986 C 670.266 968.896 674.9989999999999 949.701 678 931 L 678.533 927.881 C 680.327 916.2529999999999 680.437 904.682 680.438 892.938 L 680.444 889.7429999999999 C 680.4359999999999 868.0509999999999 677.992 847.9069999999999 672 826.9999999999999 L 670 826.9999999999999 L 670.032 830.9019999999999 C 670.46 888.777 669.413 958.06 632 1006 L 630.661 1007.729 C 621.112 1019.913 609.92 1028.64 596.9649999999999 1037.0040000000001 C 594.8231098009754 1038.4402685621374 592.7305724735354 1039.9488032022412 590.6909999999998 1041.527 L 589.0419999999999 1042.806 C 587.5209533240433 1044.0001090047035 586.0056028022386 1045.201455964797 584.4959999999998 1046.4100000000003 L 582 1048 L 579 1047 L 581.215 1045.398 C 584.26 1042.7759999999998 586.392 1039.963 588.562 1036.625 L 589.841 1034.67 C 607.535 1007.0910000000001 619.154 976.258 625 944.0000000000001 L 625.536 941.2370000000001 C 627.1289999999999 932.2160000000001 627.3389999999999 923.3670000000001 627.3159999999999 914.2360000000001 C 627.3129999999999 911.7020000000001 627.3359999999999 909.1690000000001 627.3609999999999 906.6350000000001 C 627.3999999999999 896.0910000000001 626.3609999999999 886.2760000000001 623.9999999999999 876.0000000000001 C 623.2289999999999 878.8100000000001 622.7999999999998 881.4840000000002 622.5699999999999 884.3870000000002 L 622.3539999999999 886.9550000000002 L 622.1249999999999 889.6880000000001 C 621.3419999999999 898.4200000000001 620.1839999999999 906.9740000000002 618.4779999999998 915.575 C 618.1084084059858 917.4570917187253 617.7487367460895 919.3411178741784 617.3989999999999 921.2270000000003 C 615.5039999999999 931.1460000000001 613.0399999999998 940.8420000000001 610.3119999999999 950.5620000000001 L 609.6439999999999 952.9520000000001 C 600.1139999999999 986.8470000000001 585.1019999999999 1017.3860000000001 567.4999999999999 1047.8120000000001 C 564.6647880929867 1052.7021183005975 562.1592165070242 1057.7760678002771 560 1063 L 561.715 1061.469 L 563.938 1059.5 L 566.152 1057.531 C 568 1056 568 1056 569 1056 C 569.03 1060.312 569.047 1064.625 569.062 1068.938 L 569.088 1072.4940000000001 C 569.121 1085.0100000000002 568.5559999999999 1097.4990000000003 568 1110.0000000000002 Q 554.157 1110.4750000000001 540.312 1110.9380000000003 L 538.223 1111.0070000000003 C 518.635 1111.6610000000003 499.049 1112.2270000000003 479.45399999999995 1112.6180000000004 C 470.25299999999993 1112.8020000000004 461.05799999999994 1112.9880000000003 451.864 1113.4120000000005 L 448.5 1113.5620000000006 L 446.091 1113.6770000000006 C 437.986 1114.0280000000007 429.89300000000003 1114.0500000000006 421.781 1114.0270000000005 L 417.747 1114.0210000000004 Q 412.873 1114.0140000000004 408 1114.0000000000005 Q 407.857 1099.1950000000004 407.793 1084.3890000000004 C 407.772 1079.8010000000004 407.745 1075.2130000000004 407.698 1070.6250000000005 C 407.36499999999995 1036.8630000000005 409.73499999999996 1003.7510000000004 415.375 970.4380000000004 L 416.287 964.8940000000005 C 417.166 959.5910000000005 418.075 954.2950000000004 419 949.0000000000005 L 419.396 946.7270000000004 C 433.935 863.6040000000004 464.75300000000004 783.7540000000004 511.312 713.3120000000005 L 513.344 710.2310000000004 C 524.591 693.3910000000004 524.591 693.3910000000004 531.975 691.4920000000004 C 534.386 691.0320000000004 536.799 690.6450000000004 539.227 690.2810000000004 L 541.961 689.8450000000004 C 544.847 689.3870000000004 547.736 688.9430000000003 550.625 688.5000000000003 Q 553.457 688.0550000000003 556.289 687.6050000000004 Q 561.899 686.7200000000004 567.512 685.8510000000003 C 573.577 684.9120000000004 579.64 683.9540000000003 585.702 682.9930000000004 C 605.397 679.8760000000004 605.397 679.8760000000004 613 680.0000000000003 L 613 678.0000000000003 Q 600.884 677.9640000000004 588.767 677.9480000000003 Q 583.1370000000001 677.9410000000004 577.5060000000001 677.9250000000003 C 544.07 677.6710000000003 544.07 677.6710000000003 510.81200000000007 680.6880000000003 L 507.0880000000001 681.2090000000003 C 503.05500000000006 681.7860000000003 499.0270000000001 682.3880000000003 495.00000000000006 683.0000000000003 L 492.78700000000003 683.3340000000003 C 467.16600000000005 687.2100000000003 442.79100000000005 692.2970000000003 418.33500000000004 701.1050000000002 L 415.172 702.2420000000002 L 412.362 703.2690000000002 C 410 704 410 704 407 704 C 408.478 698.851 410.766 694.618 413.5 690.062 L 414.846 687.8000000000001 C 418.891 681.0680000000001 423.18 674.523 427.612 668.041 C 429.1303978105093 665.800661767595 430.6374172136716 663.5526328237933 432.133 661.297 C 437.855 652.745 443.73300000000006 644.578 450.52000000000004 636.84 C 452.74514189396973 634.2811988322417 454.90581994704723 631.6670451383948 457 629 C 463.642 620.643 470.361 612.372 478.05 604.95 C 479.868 603.1320000000001 481.546 601.263 483.21500000000003 599.3090000000001 C 487.09700000000004 594.8430000000001 491.158 590.5860000000001 495.312 586.3750000000001 L 497.42 584.1800000000001 C 503.77700000000004 577.7460000000001 510.874 572.893 519.375 569.6880000000001 L 521.773 568.7620000000001 C 556.365 556.928 612.75 575.153 646 586 L 649.48 587.133 C 656.3353811685579 589.3757787517351 663.1755371620947 591.6648309548353 670.0000000000003 594.0000000000002 L 669.329 591.807 C 660.752 563.653 660.752 563.653 658.406 551.15 Q 657.9989999999999 548.995 657.5509999999999 546.85 C 654.8549999999999 533.72 654.7099999999999 520.6 654.6879999999999 507.25 L 654.6769999999999 504.575 C 654.6709999999999 502.013 654.6819999999999 499.452 654.699 496.89099999999996 L 654.697 494.58199999999994 C 654.792 487.13999999999993 656.019 480.04999999999995 659.562 473.43799999999993 L 660.879 470.8709999999999 C 667.025 462.55099999999993 675.539 457.57899999999995 684.438 452.62499999999994 L 687.91 450.65999999999997 C 691.266 448.76099999999997 694.6329999999999 446.881 698 444.99999999999994 Q 700.57 443.54699999999997 703.137 442.0899999999999 C 711.0769702232732 437.5933337688857 719.1467533233921 433.3298444951546 727.3359999999999 429.3049999999999 C 730.9398805570089 427.52803516922387 734.5286754448266 425.720637260832 738.1019999999999 423.883 C 781.7449999999999 401.6199999999999 828.5149999999999 381.37199999999984 876.6549999999999 371.4879999999999 C 878.8629999999998 371.0289999999999 881.0599999999998 370.5389999999999 883.2579999999998 370.0349999999999 C 888.6429999999998 368.8149999999999 893.4569999999998 367.8429999999999 898.9999999999998 367.9999999999999 Z M 526.9100000000001 1226.438 C 515.7740000000001 1241.2530000000002 512.5290000000001 1258.433 512.6120000000001 1276.558 L 512.5960000000001 1281.102 C 512.5820000000001 1285.1760000000002 512.5870000000001 1289.251 512.5970000000001 1293.325 C 512.6050000000001 1297.616 512.5980000000001 1301.906 512.5930000000001 1306.1960000000001 Q 512.5860000000001 1317.0020000000002 512.609 1327.8070000000002 C 512.625 1336.0990000000002 512.62 1344.3900000000003 512.6030000000001 1352.6820000000002 Q 512.5830000000001 1363.4150000000002 512.5960000000001 1374.1480000000001 Q 512.6040000000002 1380.535 512.5910000000001 1386.921 Q 512.5790000000002 1392.931 512.6060000000001 1398.943 C 512.6101618452383 1400.4006665141383 512.6088285024306 1401.858343538775 512.6020000000002 1403.316 C 512.4970000000001 1428.444 520.2030000000001 1449.931 538.0000000000001 1468 C 550.7080000000001 1480.731 568.7440000000001 1490.088 586.9900000000001 1490.298 L 589.6040000000002 1490.299 L 592.4420000000001 1490.3229999999999 C 595.5660000000001 1490.3469999999998 598.6890000000001 1490.357 601.8120000000001 1490.3659999999998 L 608.5500000000002 1490.4109999999998 C 614.6440000000002 1490.4519999999998 620.7370000000002 1490.4789999999998 626.8310000000001 1490.504 Q 634.4790000000002 1490.5369999999998 642.1270000000002 1490.579 C 654.8570000000002 1490.647 667.5880000000002 1490.701 680.3180000000002 1490.751 C 691.3340000000002 1490.7939999999999 702.3490000000002 1490.854 713.3640000000003 1490.922 Q 730.3770000000003 1491.026 747.3910000000003 1491.095 Q 756.9240000000003 1491.131 766.4550000000003 1491.193 Q 775.4370000000002 1491.251 784.4190000000003 1491.27 Q 787.6940000000003 1491.28 790.9690000000003 1491.307 C 820.9590000000003 1491.548 845.7680000000003 1486.51 868.1880000000003 1465.125 C 882.1040000000004 1450.678 892.0890000000003 1429.454 892.1400000000003 1409.285 L 892.1490000000003 1407.315 Q 892.1610000000003 1404.069 892.1610000000003 1400.8220000000001 L 892.1760000000003 1396.1490000000001 Q 892.1950000000003 1389.813 892.2000000000003 1383.478 L 892.2120000000002 1375.549 Q 892.2320000000002 1363.125 892.2390000000003 1350.702 Q 892.2460000000002 1336.396 892.2900000000003 1322.092 Q 892.3220000000003 1311.01 892.3230000000003 1299.93 Q 892.3230000000003 1293.324 892.3480000000003 1286.717 Q 892.3710000000003 1280.4940000000001 892.3580000000003 1274.2720000000002 Q 892.3560000000003 1272.0010000000002 892.3710000000003 1269.7300000000002 C 892.4900000000004 1250.5080000000003 887.2980000000003 1235.0660000000003 874.0470000000004 1220.9140000000002 C 856.4380000000003 1203.8920000000003 833.6420000000004 1202.255 810.5890000000004 1202.3850000000002 Q 806.6250000000003 1202.38 802.6600000000004 1202.3680000000002 C 796.2630000000004 1202.3540000000003 789.8650000000005 1202.3660000000002 783.4680000000004 1202.3870000000002 C 776.0030000000004 1202.411 768.5380000000005 1202.409 761.0720000000005 1202.4070000000002 C 747.7470000000004 1202.4030000000002 734.4210000000005 1202.4240000000002 721.0960000000005 1202.458 Q 701.7390000000005 1202.508 682.3830000000005 1202.499 Q 661.2500000000005 1202.487 640.1170000000005 1202.5140000000001 L 637.8560000000006 1202.518 L 633.3630000000005 1202.5240000000001 Q 625.5730000000005 1202.5330000000001 617.7830000000005 1202.5300000000002 Q 608.2830000000005 1202.5270000000003 598.7840000000004 1202.5620000000001 Q 593.9450000000004 1202.5810000000001 589.1060000000004 1202.575 C 585.5990000000005 1202.5710000000001 582.0930000000004 1202.586 578.5860000000005 1202.606 L 575.5410000000005 1202.589 C 555.6690000000006 1202.778 539.6170000000005 1211.617 526.9100000000005 1226.4379999999999 Z M 1172 1223 C 1160.393 1237.93 1155.685 1253.338 1155.741 1272.098 L 1155.731 1276.674 C 1155.722 1280.8 1155.725 1284.927 1155.731 1289.0529999999999 C 1155.736 1293.388 1155.732 1297.723 1155.729 1302.058 Q 1155.724 1312.985 1155.739 1323.912 Q 1155.7540000000001 1336.499 1155.736 1349.085 Q 1155.7220000000002 1359.928 1155.73 1370.77 Q 1155.737 1377.229 1155.727 1383.689 Q 1155.72 1389.7730000000001 1155.737 1395.8580000000002 Q 1155.741 1398.074 1155.7350000000001 1400.2910000000002 C 1155.701 1412.4260000000002 1155.8000000000002 1425.0090000000002 1161.188 1436.1250000000002 L 1162.1260000000002 1438.1440000000002 C 1163.0596616387181 1440.107850897532 1164.0177358778385 1442.0600021655516 1165 1444 L 1166.629 1447.258 C 1170.9759999999999 1455.282 1176.599 1461.5900000000001 1183 1468 L 1184.992 1470.027 C 1199.411 1483.673 1220.424 1490.354 1239.961 1490.2540000000001 L 1242.842 1490.2630000000001 C 1246.0010000000002 1490.2710000000002 1249.159 1490.265 1252.3180000000002 1490.2590000000002 L 1259.1450000000002 1490.2690000000002 C 1265.3090000000002 1490.2790000000002 1271.4720000000002 1490.2750000000003 1277.6350000000002 1490.2690000000002 C 1284.0980000000002 1490.2640000000001 1290.5610000000001 1490.2680000000003 1297.0240000000001 1490.2710000000002 Q 1314.3480000000002 1490.2760000000003 1331.6730000000002 1490.2590000000002 Q 1347.3470000000002 1490.2460000000003 1363.0200000000002 1490.2610000000002 Q 1381.2600000000002 1490.2770000000003 1399.4990000000003 1490.2700000000002 Q 1409.1390000000004 1490.2630000000001 1418.7810000000002 1490.2730000000001 Q 1427.851 1490.2800000000002 1436.9210000000003 1490.2630000000001 Q 1440.2390000000003 1490.2590000000002 1443.5560000000003 1490.265 C 1468.6940000000002 1490.3110000000001 1491.3230000000003 1487.3110000000001 1510.1450000000002 1468.91 C 1524.2720000000002 1454.66 1533.0620000000001 1437.0910000000001 1533.1540000000002 1416.767 L 1533.1670000000001 1414.665 Q 1533.1850000000002 1411.206 1533.1930000000002 1407.7459999999999 L 1533.2180000000003 1402.762 C 1533.2440000000004 1397.32 1533.2590000000002 1391.879 1533.2730000000004 1386.4379999999999 L 1533.2900000000004 1380.7969999999998 Q 1533.3230000000003 1369.0569999999998 1533.3410000000003 1357.3149999999998 Q 1533.3640000000003 1340.5449999999998 1533.4480000000003 1323.7759999999998 C 1533.4855010283475 1315.894385935565 1533.5065010743717 1308.0127019956044 1533.5110000000013 1300.1310000000012 C 1533.5150000000003 1295.434 1533.5270000000005 1290.7369999999999 1533.5590000000004 1286.04 Q 1533.6040000000005 1279.402 1533.5840000000005 1272.763 Q 1533.5840000000005 1270.3439999999998 1533.6090000000006 1267.925 C 1533.8010000000006 1249.522 1528.4080000000006 1234.029 1515.6020000000005 1220.387 C 1502.4670000000006 1208.337 1484.5000000000005 1202.674 1466.9510000000005 1202.739 L 1463.6420000000005 1202.728 C 1459.9930000000006 1202.717 1456.3450000000005 1202.721 1452.6970000000006 1202.7250000000001 Q 1448.7660000000005 1202.719 1444.8350000000005 1202.71 C 1438.4616741982288 1202.6979998567926 1432.0883358437889 1202.6939998536416 1425.7149999999942 1202.6980000000003 Q 1417.9390000000005 1202.7 1410.1620000000005 1202.693 L 1407.9230000000005 1202.692 L 1403.4190000000006 1202.688 C 1389.3470000000007 1202.6770000000001 1375.2760000000005 1202.679 1361.2040000000006 1202.6850000000002 C 1348.3510000000006 1202.6910000000003 1335.4990000000007 1202.679 1322.6460000000006 1202.66 Q 1302.8220000000006 1202.632 1282.9990000000007 1202.6360000000002 Q 1271.8810000000008 1202.6410000000003 1260.7620000000006 1202.6220000000003 C 1253.7880052428618 1202.6091664656947 1246.8139921760826 1202.609833133611 1239.8399999999972 1202.6240000000007 Q 1236.0090000000007 1202.6280000000002 1232.1790000000005 1202.6150000000002 C 1208.8760000000004 1202.5450000000003 1188.5030000000006 1204.3610000000003 1172.0000000000005 1223.0000000000002 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 41.878195699847595 53.535786090608724)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-725.5409401417688, -1354.0946541365124)" d="M 784.159 1283.499 C 794.869 1292.233 802.999 1301.538 808.999 1313.999 L 810.481 1316.864 C 815.901 1327.849 817.548 1339.16 817.438 1351.313 L 817.415 1354.883 C 816.958 1379.13 806.664 1400.558 789.495 1417.578 C 770.869 1435.2939999999999 745.598 1444.783 719.977 1444.305 C 694.904 1442.384 672.37 1429.9080000000001 656 1411 C 641.536 1393.588 632.364 1371.09 633.783 1348.135 C 636.235 1324.094 646.773 1302.972 665 1287 C 675.919 1278.369 689.35 1270.287 703 1267 L 706.465 1266.165 C 733.95 1260.051 761.792 1266.241 784.159 1283.499 Z M 747.999 1294.999 L 745.864 1297.089 C 739.652 1303.614 736.8770000000001 1308.8229999999999 736.624 1317.9379999999999 C 736.914 1328.0829999999999 739.426 1333.358 746.496 1340.723 C 753.783 1347.3509999999999 760.586 1348.8329999999999 770.371 1348.489 C 775.679 1347.775 779.607 1345.998 784 1342.999 C 784.583 1342.605 785.166 1342.209 785.766 1341.801 C 791.0519999999999 1337.783 794.9689999999999 1332.301 797 1326 C 797.783 1317.872 798.175 1309.755 793 1303 C 790.646 1300.229 790.646 1300.229 788 1298 L 785.75 1295.938 C 775.538 1288.7430000000002 757.785 1285.124 748 1295 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 -44.311119754529514 53.318442099348744)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-1306.9146910866534, -1352.5606608061016)" d="M 1360 1285 C 1363.498 1287.849 1366.785 1290.837 1370 1294 L 1372.34 1296.301 C 1382.8519999999999 1307.28 1388.129 1319.5459999999998 1392 1334 L 1392.54 1335.861 C 1396.657 1355.0010000000002 1392.797 1377.2540000000001 1382.9379999999999 1394.0200000000002 C 1368.697 1415.9260000000002 1350.4129999999998 1430.5550000000003 1324.6209999999999 1436.457 L 1322 1437 L 1319.684 1437.559 C 1297.191 1441.258 1274.2559999999999 1434.067 1256 1421 C 1251.715 1417.621 1247.829 1413.881 1244 1410 L 1241.984 1407.98 C 1227.433 1392.413 1219.456 1370.627 1219.664 1349.461 C 1220.92 1326.483 1231.754 1305.238 1248.184 1289.41 C 1260.189 1279.1360000000002 1275.519 1271.19 1291 1268 L 1293.656 1267.449 C 1317.818 1264.203 1340.513 1270.229 1360 1285 Z M 1294 1288 C 1288.006 1296.787 1286.147 1303.326 1287 1314 C 1288.934 1322.344 1293.572 1328.869 1300.535 1333.793 C 1308.5040000000001 1338.734 1315.488 1340.4989999999998 1324.816 1338.3519999999999 C 1333.756 1335.4419999999998 1340.2930000000001 1330.368 1344.9650000000001 1322.1639999999998 C 1348.3010000000002 1315.1889999999999 1350.123 1308.5989999999997 1348.0000000000002 1300.9999999999998 C 1344.2760000000003 1291.5599999999997 1339.2020000000002 1284.5929999999998 1330.0000000000002 1279.9999999999998 C 1316.8120000000001 1274.8159999999998 1303.7950000000003 1277.9819999999997 1294.0000000000002 1287.9999999999998 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 116.27746954734931 49.03306561427681)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-221.9917306946614, -1334.761241229538)" d="M 338 1100 C 341 1102.5 341 1102.5 343 1105 L 344 1106 C 344.775 1113.23 344.315 1119.058 340 1125 C 335.144 1130.911 329.911 1136.329 324.062 1141.258 C 321.66 1143.287 319.331 1145.3890000000001 317 1147.5 C 314.1241858203869 1150.108792050252 311.207151874555 1152.6717814188871 308.24999999999994 1155.188 C 303.694 1159.142 299.493 1163.4540000000002 295.274 1167.7620000000002 C 292.3 1170.7820000000002 289.275 1173.6710000000003 286.062 1176.438 C 281.83 1180.0910000000001 277.914 1184.01 274 1188 C 269.42 1192.669 264.769 1197.141 259.812 1201.41 C 257.42900000000003 1203.501 255.211 1205.729 253 1208 C 249.52 1211.574 245.885 1214.87 242.119 1218.135 C 238.579 1221.251 235.17 1224.503 231.75 1227.75 C 226.897 1232.343 222.026 1236.903 217.062 1241.375 C 211 1246.892 205.241 1252.702 199.49200000000002 1258.544 C 195.37400000000002 1262.711 191.16600000000003 1266.679 186.72400000000002 1270.499 C 184.71900000000002 1272.2450000000001 182.846 1274.088 181.00000000000003 1276 C 178.48000000000002 1278.559 175.91900000000004 1280.982 173.18800000000002 1283.312 C 169.79800000000003 1286.223 166.63500000000002 1289.322 163.50000000000003 1292.5 C 159.81009736064908 1296.2502018637567 155.96018756327436 1299.8395362601652 151.96100000000004 1303.258 C 149.71400000000006 1305.2540000000001 147.61100000000005 1307.3600000000001 145.50000000000003 1309.5 C 142.85300000000004 1312.183 140.18900000000002 1314.753 137.31200000000004 1317.188 C 130.84200000000004 1322.7220000000002 124.97800000000004 1328.9460000000001 119.00000000000004 1335 C 120.89300000000004 1339.179 123.75100000000005 1341.796 127.12500000000004 1344.812 L 128.91800000000003 1346.442 C 132.48120597263122 1349.6595125263498 136.08178374302315 1352.835394798225 139.7190000000003 1355.9690000000003 C 142.99 1358.882 146.17900000000003 1361.88 149.37500000000003 1364.875 C 156.41700000000003 1371.473 163.52100000000002 1377.989 170.81200000000004 1384.312 C 175.51500000000004 1388.4089999999999 180.07400000000004 1392.6599999999999 184.64800000000005 1396.898 C 187.71200000000005 1399.7369999999999 190.79200000000006 1402.558 193.87500000000006 1405.375 C 198.40000000000006 1409.515 202.89400000000006 1413.688 207.37500000000006 1417.875 L 209.27700000000004 1419.652 L 213.09300000000005 1423.218 C 223.95900000000006 1433.421 223.95900000000006 1433.421 235.08600000000004 1443.336 C 238.24400000000003 1446.082 241.34300000000005 1448.895 244.44900000000004 1451.699 Q 246.88500000000005 1453.896 249.33200000000005 1456.082 C 255.17100000000005 1461.3010000000002 260.898 1466.632 266.6120000000001 1471.987 C 270.2643449835203 1475.4127448045897 273.98816444120496 1478.7614817222172 277.7810000000001 1482.0310000000002 C 281.08000000000004 1484.958 284.28400000000005 1487.9840000000002 287.50000000000006 1491.0000000000002 C 291.93100000000004 1495.1550000000002 296.37600000000003 1499.2680000000003 300.9800000000001 1503.2300000000002 C 303.69500000000005 1505.6090000000002 306.34600000000006 1508.0550000000003 309.00000000000006 1510.5000000000002 C 312.94930580639357 1514.1451188382246 316.9524119742914 1517.7315125885782 321.00800000000015 1521.2580000000005 C 323.35490988923186 1523.3203999658595 325.6856478895891 1525.4011303145764 327.99999999999966 1527.4999999999995 C 330.97639935747947 1530.19309966877 333.9765162663445 1532.8598702544277 336.9999999999999 1535.4999999999998 L 338.922 1537.184 Q 341.24300000000005 1539.211 343.57800000000003 1541.223 C 354.36800000000005 1550.672 354.36800000000005 1550.672 355.367 1555.934 C 355.608 1559.687 355.064 1561.864 353.43800000000005 1565.312 C 349.63400000000007 1569.5059999999999 345.52400000000006 1571.106 339.93800000000005 1571.4379999999999 C 331.96400000000006 1570.552 327.88100000000003 1565.001 322.492 1559.4999999999998 C 319.872 1556.8719999999998 317.146 1554.4539999999997 314.32800000000003 1552.0429999999997 C 311.15700000000004 1549.2609999999997 308.083 1546.3789999999997 305.00000000000006 1543.4999999999998 C 300.69800000000004 1539.4869999999999 296.37600000000003 1535.5169999999998 291.9220000000001 1531.6719999999998 C 289.58900000000006 1529.6419999999998 287.2950000000001 1527.572 285.00000000000006 1525.4999999999998 C 282.0678257413008 1522.8456662577378 279.10934633336313 1520.2205366422438 276.1250000000001 1517.6249999999998 C 270.65400000000005 1512.8559999999998 265.33500000000004 1507.9199999999998 260.00000000000006 1502.9999999999998 L 258.40000000000003 1501.5249999999999 C 253.4501673197048 1496.9637045466645 248.50682897970964 1492.3953665864726 243.5699999999992 1487.8199999999981 C 238.48700000000002 1483.1109999999999 233.39000000000001 1478.432 228.14800000000002 1473.898 C 224.68600000000004 1470.839 221.30900000000003 1467.686 217.92000000000002 1464.5449999999998 C 213.822901546648 1460.7574540198364 209.6944259118616 1457.0039913111002 205.53499999999968 1453.2849999999994 C 202.63474119933198 1450.6632528508458 199.74803606820672 1448.026551050938 196.87499999999983 1445.3749999999995 C 193.68100000000004 1442.4369999999997 190.47600000000003 1439.5199999999998 187.18800000000002 1436.6879999999999 C 182.54700000000003 1432.6699999999998 178.096 1428.447 173.63000000000002 1424.2369999999999 C 170.47800000000004 1421.2759999999998 167.29700000000003 1418.3659999999998 164.03100000000003 1415.531 C 159.22700000000003 1411.356 154.62000000000003 1406.982 150.00000000000003 1402.605 C 146.04126124390373 1398.8742604948923 142.00607150529945 1395.225486100173 137.89699999999996 1391.6609999999998 C 135.08900000000003 1389.203 132.35600000000002 1386.6680000000001 129.62500000000003 1384.125 C 123.3060246336545 1378.2674802524728 116.92297381596447 1372.479465245811 110.47700000000023 1366.7620000000002 C 90.19000000000003 1348.691 90.19000000000003 1348.691 88.56200000000004 1335.4379999999999 C 88.81100000000004 1324.2259999999999 98.65900000000003 1315.4779999999998 106.00000000000004 1307.9999999999998 L 108.20700000000004 1305.7109999999998 C 111.27583258913332 1302.564988388325 114.48055046657717 1299.5544553942411 117.81200000000004 1296.6879999999999 C 121.20200000000004 1293.7769999999998 124.36500000000004 1290.6779999999999 127.50000000000004 1287.4999999999998 C 131.2108726314793 1283.7267866774434 135.08280609691283 1280.1154288291148 139.10500000000002 1276.6759999999997 C 141.86900000000006 1274.2309999999998 144.42000000000004 1271.5889999999997 147.00800000000004 1268.9609999999998 C 148.90300000000005 1267.0949999999998 150.85400000000004 1265.3529999999998 152.87500000000003 1263.6249999999998 C 156.06700000000004 1260.8839999999998 159.04300000000003 1257.9929999999997 162.00000000000003 1254.9999999999998 C 164.81500000000003 1252.1509999999998 167.62700000000004 1249.3949999999998 170.68800000000002 1246.8119999999997 C 174.848 1243.2619999999997 178.65400000000002 1239.3829999999996 182.50000000000003 1235.4999999999998 C 186.80200000000002 1231.1559999999997 191.10300000000004 1226.9399999999998 195.78100000000003 1222.9999999999998 C 199.15000000000003 1219.9629999999997 202.30900000000003 1216.7219999999998 205.50000000000003 1213.4999999999998 C 209.85900000000004 1209.1039999999998 214.20600000000002 1204.7839999999999 218.93400000000003 1200.7809999999997 C 223.50500000000002 1196.8409999999997 227.87900000000002 1192.6719999999998 232.30300000000003 1188.5669999999998 C 233.92925031223118 1187.0670632615113 235.5612623781234 1185.5733855516944 237.19899999999987 1184.0859999999998 C 243.84918047622807 1178.0207537161032 250.3270859435265 1171.7692714863367 256.625 1165.3389999999995 C 260.266 1161.6439999999998 264.031 1158.1769999999997 267.958 1154.7879999999998 C 271.47900000000004 1151.7049999999997 274.85600000000005 1148.4719999999998 278.25 1145.2499999999998 C 283.017 1140.7369999999999 287.801 1136.2579999999998 292.688 1131.8749999999998 C 296.699 1128.2369999999999 300.546 1124.4659999999997 304.348 1120.6089999999997 L 305.807 1119.1329999999996 C 308.244 1116.6659999999995 310.671 1114.1919999999996 313.08500000000004 1111.7029999999995 C 328.062 1096.4789999999996 328.062 1096.4789999999996 338.00000000000006 1099.9999999999995 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 -116.33503565476053 51.63473827510188)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-1825.236028462144, -1335.0520514387852)" d="M 1724 1099 C 1730.754 1102.104 1735.325 1107.013 1740.438 1112.312 C 1745.5030000000002 1117.5159999999998 1750.5600000000002 1122.609 1756.077 1127.3349999999998 C 1759.372 1130.1879999999999 1762.521 1133.1949999999997 1765.688 1136.1879999999999 L 1769.9060000000002 1140.156 L 1772.0000000000002 1142.125 C 1774.8820000000003 1144.827 1777.7820000000002 1147.51 1780.6880000000003 1150.188 L 1782.2740000000003 1151.651 Q 1786.2640000000004 1155.3300000000002 1790.2730000000004 1158.988 C 1796.268599280347 1164.4627489504546 1802.1257786423414 1170.0871882922581 1807.8390000000004 1175.856 C 1810.9992394944868 1179.0351608645324 1814.2900716985123 1182.0817828928334 1817.7030000000004 1184.988 C 1820.8880000000004 1187.778 1823.9720000000004 1190.67 1827.0620000000004 1193.5620000000001 L 1829.0030000000004 1195.3790000000001 Q 1833.0130000000004 1199.1340000000002 1837.0190000000005 1202.8940000000002 C 1846.2890000000004 1211.6050000000002 1846.2890000000004 1211.6050000000002 1855.8010000000004 1220.0510000000002 C 1860.3300000000004 1224.065 1864.7120000000004 1228.2420000000002 1869.1290000000004 1232.3790000000001 C 1873.1430797221676 1236.137223013511 1877.174455854633 1239.8769293827959 1881.2229999999995 1243.5979999999995 L 1884.4030000000005 1246.5280000000002 C 1886.5775409154116 1248.5333583921617 1888.7508746424044 1250.540025422301 1890.923000000001 1252.5480000000025 Q 1894.9600000000005 1256.2750000000003 1899.0000000000005 1260.0000000000002 L 1900.6070000000004 1261.4820000000002 C 1907.6390000000004 1267.976 1907.6390000000004 1267.976 1914.8120000000004 1274.3120000000001 C 1919.4970000000003 1278.3680000000002 1923.9830000000004 1282.6370000000002 1928.5000000000005 1286.8770000000002 C 1931.7220000000004 1289.8960000000002 1934.9560000000004 1292.8790000000001 1938.3120000000004 1295.7500000000002 C 1957.1090000000004 1312.0430000000001 1957.1090000000004 1312.0430000000001 1958.2380000000003 1322.1290000000001 C 1958.4930000000004 1330.4740000000002 1956.4980000000003 1337.363 1951.0620000000004 1343.8120000000001 C 1949.7260763975016 1345.2254196944002 1948.3719635928076 1346.621535998206 1947 1348 L 1943.938 1351.141 C 1939.5040000000001 1355.6470000000002 1935.083 1360.102 1930.277 1364.2150000000001 C 1924.91 1368.861 1919.98 1373.949 1915 1379.0000000000002 C 1909.43 1384.6490000000001 1903.865 1390.2300000000002 1897.832 1395.3910000000003 C 1894.737 1398.1090000000004 1891.88 1401.0580000000002 1889 1404.0000000000002 C 1884.815 1408.2340000000002 1880.603 1412.3180000000002 1876.055 1416.1640000000002 C 1872.702 1419.1600000000003 1869.587 1422.3930000000003 1866.428 1425.5920000000003 C 1863.0657411235268 1428.972358312889 1859.5619400164844 1432.2089203524447 1855.9260000000002 1435.2930000000003 C 1853.689 1437.2760000000003 1851.5990000000002 1439.3720000000003 1849.5000000000002 1441.5000000000005 C 1846.8530000000003 1444.1830000000004 1844.1890000000003 1446.7530000000004 1841.3120000000001 1449.1880000000006 C 1837.334 1452.5920000000006 1833.68 1456.2810000000006 1830.0000000000002 1460.0000000000005 C 1825.7910000000002 1464.2530000000004 1821.5600000000002 1468.3640000000005 1816.9880000000003 1472.2300000000005 C 1813.4630000000002 1475.3680000000004 1810.2020000000002 1478.7810000000004 1806.9060000000002 1482.1560000000004 C 1804.0970000000002 1484.9700000000005 1801.1060000000002 1487.5500000000004 1798.102 1490.1520000000005 C 1794.566 1493.2610000000004 1791.164 1496.5090000000005 1787.75 1499.7500000000005 C 1782.919 1504.3230000000005 1778.069 1508.8630000000005 1773.125 1513.3120000000004 C 1766.816 1519.0530000000003 1760.833 1525.1120000000003 1754.848 1531.1890000000003 C 1750.608 1535.4750000000004 1746.2839999999999 1539.5520000000004 1741.711 1543.4800000000002 C 1739.5720000000001 1545.3800000000003 1737.72 1547.4410000000003 1735.875 1549.6250000000002 C 1732.05 1553.9560000000001 1728.019 1557.9830000000002 1723.812 1561.9380000000003 L 1722.048 1563.6220000000003 C 1716.023 1569.2150000000004 1710.939 1571.6330000000003 1702.633 1571.4300000000003 C 1698.477 1570.7510000000002 1696.794 1569.0880000000002 1694 1566.0000000000002 C 1691.747 1561.4940000000001 1691.877 1556.8220000000001 1693 1552.0000000000002 C 1697.406 1544.7930000000001 1704.615 1539.3120000000001 1710.916 1533.8410000000001 C 1714.528 1530.65 1718.008 1527.3210000000001 1721.5 1524.0000000000002 C 1726.315 1519.4220000000003 1731.166 1514.9210000000003 1736.207 1510.5920000000003 C 1738.047 1508.9580000000003 1739.775 1507.2540000000004 1741.5 1505.5000000000002 C 1743.814184956085 1503.1467541949278 1746.2252696945902 1500.8908035927732 1748.727 1498.7380000000003 C 1752.861 1495.1430000000003 1756.883 1491.4400000000003 1760.875 1487.6880000000003 L 1762.982 1485.7110000000002 C 1767.7 1481.2690000000002 1772.296 1476.7330000000002 1776.848 1472.1210000000003 C 1779.4199999999998 1469.5870000000002 1782.127 1467.2550000000003 1784.8709999999999 1464.9100000000003 C 1788.435 1461.7130000000004 1791.7499999999998 1458.2680000000003 1795.0939999999998 1454.8440000000003 C 1797.9209999999998 1452.0130000000004 1800.9289999999999 1449.4130000000002 1803.9519999999998 1446.7940000000003 C 1807.1859999999997 1443.9610000000002 1810.3109999999997 1441.0140000000004 1813.4379999999999 1438.0620000000004 L 1815.4899999999998 1436.1310000000003 L 1819.7889999999998 1432.0820000000003 C 1822.0347639301335 1429.9682429387758 1824.2834327217702 1427.8575742775472 1826.534999999999 1425.7500000000005 L 1830.9999999999998 1421.5620000000001 L 1833.0639999999999 1419.631 C 1836.437056172387 1416.4723624027445 1839.749656695156 1413.249778198529 1843 1409.9650000000001 C 1845.5024653310043 1407.432570937528 1848.1086967452356 1405.0048256736568 1850.8119999999997 1402.688 C 1856.1579999999997 1398.084 1861.0509999999997 1393.0200000000002 1865.9999999999998 1388 C 1871.6099999999997 1382.31 1877.2159999999997 1376.683 1883.2929999999997 1371.484 C 1885.6479999999997 1369.436 1887.8229999999996 1367.235 1889.9999999999998 1365 C 1892.8759999999997 1362.089 1895.7589999999998 1359.276 1898.8749999999998 1356.625 C 1902.9959999999999 1353.105 1906.7589999999998 1349.269 1910.5639999999999 1345.416 C 1913.9969999999998 1341.9569999999999 1917.5459999999998 1338.701 1921.2379999999998 1335.52 C 1924.0279999999998 1333.113 1926.58 1330.792 1928.9999999999998 1328 C 1928.8229999999999 1324.086 1926.4879999999998 1322.128 1923.8089999999997 1319.48 L 1921.1929999999998 1316.881 L 1918.4379999999996 1314.188 L 1915.7639999999997 1311.529 C 1911.2699999999998 1307.088 1906.7039999999997 1302.789 1901.9139999999998 1298.669 C 1897.8289999999997 1295.1070000000002 1893.9349999999997 1291.333 1889.9999999999998 1287.605 C 1886.0412612439034 1283.8742604948923 1882.0060715052991 1280.225486100173 1877.8969999999995 1276.6609999999998 C 1875.0889999999997 1274.203 1872.3559999999998 1271.6680000000001 1869.6249999999998 1269.125 C 1864.9575956244812 1264.7997729852336 1860.2544341906778 1260.5132925204755 1855.5159999999996 1256.266 C 1847.897 1249.404 1840.377 1242.432 1832.8469999999998 1235.472 C 1829.3259999999998 1232.219 1825.7909999999997 1228.982 1822.2499999999998 1225.75 C 1817.2469999999998 1221.172 1812.3059999999998 1216.531 1807.3749999999998 1211.875 C 1802.1949999999997 1206.985 1796.9809999999998 1202.147 1791.6599999999999 1197.41 C 1787.3429999999998 1193.499 1783.1139999999998 1189.4930000000002 1778.8829999999998 1185.49 C 1774.9460335532553 1181.7822005939622 1770.933525151442 1178.155439780092 1766.8479999999995 1174.612 C 1764.3639999999998 1172.4450000000002 1761.9309999999998 1170.226 1759.4999999999998 1168 C 1754.0365846634618 1163.0113914746564 1748.4864038842418 1158.1186444109953 1742.8519999999999 1153.324 C 1710.896 1126.161 1710.896 1126.161 1709.6639999999998 1116.316 C 1709.5789999999997 1110.3210000000001 1709.8839999999998 1106.703 1713.9999999999998 1102 L 1715.3119999999997 1100.125 C 1718.2329999999997 1098.178 1720.5919999999996 1098.602 1723.9999999999998 1099 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 -37.86757883171936 10.938128884755312)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-1288.379073143281, -1048.055962280309)" d="M 1334 1024 L 1337.117 1025.52 C 1355.262 1034.81 1371.722 1047.445 1381 1066 C 1381.518 1070.799 1381.712 1074.734 1379.312 1079 C 1375.741 1082.089 1373.5179999999998 1083.079 1368.7579999999998 1082.832 C 1364.4159999999997 1081.871 1361.6179999999997 1079.99 1358.1879999999999 1077.3120000000001 Q 1356.3429999999998 1075.938 1354.492 1074.5700000000002 L 1352.687 1073.228 C 1348.962 1070.516 1345.0449999999998 1068.1960000000001 1341 1066 L 1338.75 1064.773 C 1311.861 1051.415 1278.597 1048.433 1249.908 1057.7649999999999 C 1235.655 1062.774 1222.4509999999998 1070.589 1210.559 1079.8359999999998 C 1207.3319999999999 1082.2459999999999 1204.998 1082.5489999999998 1201 1081.9999999999998 C 1197.823 1079.3749999999998 1196.395 1077.7269999999999 1195.48 1073.7269999999999 C 1194.76 1063.108 1202.0330000000001 1055.455 1208.555 1047.828 C 1221.0510000000002 1033.732 1236.5320000000002 1025.304 1254 1019 L 1256.641 1018.035 C 1281.423 1009.788 1310.767 1012.4499999999999 1334 1024 Z" stroke-linecap="round" /> +</g> + <g transform="matrix(-0.14168509533335535 0 0 0.14168509533335533 38.2749770027191 10.963563253927713)" > +<path style="stroke: rgb(68,86,52); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-750.9721168555292, -1048.2354756519342)" d="M 822 1042 C 840.891 1058.642 840.891 1058.642 843.188 1070.75 C 843.3629999999999 1074.054 842.847 1075.828 840.812 1078.438 C 836.611 1082.266 836.611 1082.266 834 1083 C 830.093 1082.528 827.638 1081.346 824.445 1079.086 L 821.855 1077.289 L 819.125 1075.375 C 810.678 1069.6 802.486 1064.842 793 1061 L 790.375 1059.84 C 762.253 1048.283 730.5409999999999 1051.5939999999998 702.812 1062.25 C 694.582 1065.836 687.224 1071.061 679.879 1076.172 L 677 1078.125 L 674.5 1079.883 C 671.189 1081.362 669.439 1081.049 666 1080 C 662.891 1077.839 660.822 1075.312 659 1072 C 657.821 1065.409 660.593 1059.901 664.141 1054.504 C 667.462 1050.031 671.0949999999999 1045.9689999999998 675 1042 L 677.309 1039.652 C 683.8149999999999 1033.424 691.049 1029.0800000000002 699 1025 L 702.293 1023.309 C 741.906 1004.057 788.971 1014.523 822 1042 Z" stroke-linecap="round" /> +</g> +</g> +</g> +</svg> \ No newline at end of file diff --git a/client/assets/robots-prod.txt b/client/assets/robots-prod.txt new file mode 100644 index 00000000..3a529ad8 --- /dev/null +++ b/client/assets/robots-prod.txt @@ -0,0 +1,6 @@ +# prod + +User-agent: * +Allow: / +Disallow: +Sitemap: https://freedevtool.app/sitemap.xml diff --git a/client/assets/robots.txt b/client/assets/robots.txt new file mode 100644 index 00000000..f313849e --- /dev/null +++ b/client/assets/robots.txt @@ -0,0 +1,4 @@ +# non-prod + +User-agent: * +Disallow: / diff --git a/client/index.html b/client/index.html new file mode 100644 index 00000000..92bf5916 --- /dev/null +++ b/client/index.html @@ -0,0 +1,143 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1.0, maximum-scale=1" + /> + <meta name="color-scheme" content="light dark" /> + <!-- + Security & Privacy: CSP meta tag blocks external scripts, enforces + a sandbox, and prevents outbound calls. All processing is offline + and local—your data never leaves your browser. + + Read more about FreeDevTool.App's security policies: + https://github.com/spring1843/FreeDevTool.App/blob/main/SECURITY.md +--> + <meta + http-equiv="Content-Security-Policy" + content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self' ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self';" + /> + <script> + (function () { + var theme = localStorage.getItem("theme"); + if (!theme) { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + } + document.documentElement.classList.add(theme); + })(); + </script> + <title> + FreeDevTool.App - Free, Secure, Open Source, and Offline Developer Tools + + + + + + + + + + + + + + + + + + + +
+
+
+
+ Loading FreeDevTool.App... +
+
+ Secure + Developer Tools +
+
+
+
+ + + + diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 00000000..a558a1d7 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,211 @@ +import { lazy, Suspense } from "react"; +import { Switch, Route } from "wouter"; +import { queryClient } from "./lib/queryClient"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { ThemeProvider } from "@/providers/theme-provider"; +import { DemoProvider } from "@/providers/demo-provider"; +import { AppLayout } from "@/components/layout/AppLayout"; +import { ScrollToTop } from "@/components/ScrollToTop"; + +// Eager load home page for fast initial load +import Home from "@/pages/home"; + +// Lazy load all tool pages +const DateConverter = lazy(() => import("@/pages/tools/date-converter")); +const JsonYamlConverter = lazy( + () => import("@/pages/tools/json-yaml-converter") +); +const TimezoneConverter = lazy( + () => import("@/pages/tools/timezone-converter") +); +const UnitConverter = lazy(() => import("@/pages/tools/unit-converter")); +const JsonFormatter = lazy(() => import("@/pages/tools/json-formatter")); +const JSONCFormatter = lazy(() => import("@/pages/tools/jsonc-formatter")); +const HtmlFormatter = lazy(() => import("@/pages/tools/html-formatter")); +const YamlFormatter = lazy(() => import("@/pages/tools/yaml-formatter")); +const MarkdownFormatter = lazy( + () => import("@/pages/tools/markdown-formatter") +); +const CssFormatter = lazy(() => import("@/pages/tools/css-formatter")); +const TypeScriptFormatter = lazy( + () => import("@/pages/tools/typescript-formatter") +); +const GraphQLFormatter = lazy(() => import("@/pages/tools/graphql-formatter")); +const TimeFormatter = lazy(() => import("@/pages/tools/time-formatter")); +const Base64Encoder = lazy(() => import("@/pages/tools/base64-encoder")); +const UrlEncoder = lazy(() => import("@/pages/tools/url-encoder")); +const JwtDecoder = lazy(() => import("@/pages/tools/jwt-decoder")); +const TlsDecoder = lazy(() => import("@/pages/tools/tls-decoder")); +const TextDiff = lazy(() => import("@/pages/tools/text-diff")); +const RegexTester = lazy(() => import("@/pages/tools/regex-tester")); +const TextSort = lazy(() => import("@/pages/tools/text-sort")); +const TextCounter = lazy(() => import("@/pages/tools/text-counter")); +const TextSplit = lazy(() => import("@/pages/tools/text-split")); +const SearchReplace = lazy(() => import("@/pages/tools/search-replace")); +const WorldClock = lazy(() => import("@/pages/tools/world-clock")); +const Timer = lazy(() => import("@/pages/tools/timer")); +const Stopwatch = lazy(() => import("@/pages/tools/stopwatch")); +const Countdown = lazy(() => import("@/pages/tools/countdown")); +const CompoundInterest = lazy(() => import("@/pages/tools/compound-interest")); +const DebtRepayment = lazy(() => import("@/pages/tools/debt-repayment")); +const ColorPaletteGenerator = lazy( + () => import("@/pages/tools/color-palette-generator") +); +const CameraTest = lazy(() => import("@/pages/tools/webcam-test")); +const MicrophoneTest = lazy(() => import("@/pages/tools/microphone-test")); +const KeyboardTest = lazy(() => import("@/pages/tools/keyboard-test")); +const QRGenerator = lazy(() => import("@/pages/tools/qr-generator")); +const BarcodeGenerator = lazy(() => import("@/pages/tools/barcode-generator")); +const LoremGenerator = lazy(() => import("@/pages/tools/lorem-generator")); +const UnicodeCharacters = lazy( + () => import("@/pages/tools/unicode-characters") +); +const MD5Hash = lazy(() => import("@/pages/tools/md5-hash")); +const BCryptHash = lazy(() => import("@/pages/tools/bcrypt-hash")); +const PasswordGenerator = lazy( + () => import("@/pages/tools/password-generator") +); +const UUIDGenerator = lazy(() => import("@/pages/tools/uuid-generator")); +const DateTimeDiff = lazy(() => import("@/pages/tools/datetime-diff")); +const Metronome = lazy(() => import("@/pages/tools/metronome")); +const BrowserInfo = lazy(() => import("@/pages/tools/browser-info")); +const URLToJSON = lazy(() => import("@/pages/tools/url-to-json")); +const CSVToJSON = lazy(() => import("@/pages/tools/csv-to-json")); +const NumberBaseConverter = lazy( + () => import("@/pages/tools/number-base-converter") +); +const NotFound = lazy(() => import("@/pages/not-found")); + +function Router() { + return ( + + Loading... +
+ } + > + + {/* Home */} + + + {/* Conversions */} + + + + + + + + + {/* Formatters */} + + + + + + + + + + + + + + {/* Encoders */} + + + + + + + + {/* Text Tools */} + + + + + + + + + + + + + + {/* Time Tools */} + + + + + + + + {/* Financial Tools */} + + + + {/* Color Tools */} + + + {/* Hardware Tools */} + + + + + {/* Browser Tools */} + + + {/* Fallback to 404 */} + + + + ); +} + +function App() { + return ( + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/client/src/components/ScrollToTop.tsx b/client/src/components/ScrollToTop.tsx new file mode 100644 index 00000000..0499e0c2 --- /dev/null +++ b/client/src/components/ScrollToTop.tsx @@ -0,0 +1,25 @@ +import { useEffect, useRef } from "react"; +import { useLocation } from "wouter"; + +// Scroll to top on pathname changes. Ignores hash-only changes. +export function ScrollToTop() { + const [location] = useLocation(); + const prevLocationRef = useRef(location); + + useEffect(() => { + const prevLocation = prevLocationRef.current; + const currentPathname = + typeof location === "string" ? location.split("?")[0] : ""; + const prevPathname = + typeof prevLocation === "string" ? prevLocation.split("?")[0] : ""; + + // Only scroll if the pathname changed (not just query params or hash) + if (currentPathname !== prevPathname) { + window.scrollTo({ top: 0, left: 0 }); + } + + prevLocationRef.current = location; + }, [location]); + + return null; +} diff --git a/client/src/components/figma/ImageWithFallback.tsx b/client/src/components/figma/ImageWithFallback.tsx new file mode 100644 index 00000000..0e26139b --- /dev/null +++ b/client/src/components/figma/ImageWithFallback.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react' + +const ERROR_IMG_SRC = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg==' + +export function ImageWithFallback(props: React.ImgHTMLAttributes) { + const [didError, setDidError] = useState(false) + + const handleError = () => { + setDidError(true) + } + + const { src, alt, style, className, ...rest } = props + + return didError ? ( +
+
+ Error loading image +
+
+ ) : ( + {alt} + ) +} diff --git a/client/src/components/layout/AppLayout.tsx b/client/src/components/layout/AppLayout.tsx new file mode 100644 index 00000000..e4dee90e --- /dev/null +++ b/client/src/components/layout/AppLayout.tsx @@ -0,0 +1,21 @@ +// This component wraps all pages with the home page's exact header and sidebar +import { useLocation } from "wouter"; +import Home from "@/pages/home"; + +interface AppLayoutProps { + children: React.ReactNode; +} + +export function AppLayout({ children }: AppLayoutProps) { + const [location] = useLocation(); + const isHomePage = location === "/"; + + // If it's the home page, just render it directly (it has its own layout) + if (isHomePage) { + return <>{children}; + } + + // For all other pages, wrap them with the home page structure + // but replace the content area with the actual tool page + return {children}; +} diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx new file mode 100644 index 00000000..8ae7fdb6 --- /dev/null +++ b/client/src/components/layout/Header.tsx @@ -0,0 +1,348 @@ +import { Search, Moon, Sun, Menu, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useTheme } from "@/providers/theme-provider"; +import { Link } from "wouter"; +import { useSearch } from "@/hooks/use-search"; +import { SearchResults } from "@/components/ui/search-results"; +import { getToolsCount } from "@/data/tools"; +import { useState, useRef, useEffect } from "react"; + +interface HeaderProps { + onMenuClick: () => void; +} + +export function Header({ onMenuClick }: HeaderProps) { + const { theme, setTheme } = useTheme(); + const { + searchQuery, + setSearchQuery, + searchResults, + selectedIndex, + navigateResults, + selectResult, + resetSelection, + } = useSearch(); + const [showResults, setShowResults] = useState(false); + const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false); + const searchRef = useRef(null); + + const toggleTheme = () => { + if (theme === "dark") { + setTheme("light"); + } else { + setTheme("dark"); + } + }; + + const handleSearchChange = (value: string) => { + setSearchQuery(value); + setShowResults(value.trim().length > 0); + resetSelection(); // Reset selection when search changes + }; + + const handleResultClick = () => { + setShowResults(false); + setSearchQuery(""); + setIsMobileSearchOpen(false); + }; + + const clearSearch = () => { + setSearchQuery(""); + setShowResults(false); + resetSelection(); + }; + + const focusSearch = () => { + const searchInput = document.querySelector( + '[data-testid="search-input"]' + ) as HTMLInputElement; + if (searchInput) { + searchInput.focus(); + searchInput.select(); + } + }; + + // Close search results when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + searchRef.current && + !searchRef.current.contains(event.target as Node) + ) { + setShowResults(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + // Handle keyboard navigation in search + const handleSearchKeyDown = ( + event: React.KeyboardEvent + ) => { + if (!showResults || searchResults.length === 0) return; + + switch (event.key) { + case "ArrowDown": + event.preventDefault(); + navigateResults("down"); + break; + case "ArrowUp": + event.preventDefault(); + navigateResults("up"); + break; + case "Enter": { + event.preventDefault(); + const selected = selectResult(); + if (selected) { + // Navigate to the selected result + window.location.href = selected.path; + handleResultClick(); + } + break; + } + case "Escape": + event.preventDefault(); + setShowResults(false); + resetSelection(); + (event.target as HTMLInputElement).blur(); + break; + default: { + // Handle default case + } + } + }; + + // Add Ctrl+S keyboard shortcut for search + useEffect(() => { + const handleKeydown = (event: KeyboardEvent) => { + if (event.ctrlKey && event.key === "s") { + event.preventDefault(); + focusSearch(); + } + }; + + document.addEventListener("keydown", handleKeydown); + return () => document.removeEventListener("keydown", handleKeydown); + }, []); + + return ( + +
+
+
+ {/* Logo and Title - Always leftmost */} +
+
+ {/* Blue FD Logo - toggles menu */} +
{ + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onMenuClick(); + } + }} + aria-label="Toggle navigation menu" + data-testid="logo-menu-toggle" + > + + FD + +
+ + {/* Text Logo - links to homepage */} +
+ +

+ FreeDevTool.App +

+ +

+ + Secure + {" "} + Developer Tools +

+
+
+
+ + {/* Search and Actions */} +
+ {/* Desktop Search */} +
+ + handleSearchChange(e.target.value)} + onKeyDown={handleSearchKeyDown} + className="pl-10 pr-8 w-64 bg-slate-50 dark:bg-slate-700 border-slate-300 dark:border-slate-600" + data-testid="search-input" + onFocus={() => setShowResults(searchQuery.trim().length > 0)} + /> + {searchQuery ? ( + + + + + +

Clear Search

+
+
+ ) : null} + {showResults ? ( + + ) : null} +
+ + {/* Mobile Search Toggle */} + + + + + +

Open Search (Ctrl+S)

+
+
+ + {/* Theme Toggle */} + + + + + +

Switch to {theme === "dark" ? "Light" : "Dark"} Mode

+
+
+ + {/* Hamburger Menu - Always visible */} + + + + + +

Toggle Menu (Ctrl+M)

+
+
+
+
+ + {/* Mobile Search Bar */} + {isMobileSearchOpen ? ( +
+
+ + handleSearchChange(e.target.value)} + onKeyDown={handleSearchKeyDown} + className="pl-10 pr-8 w-full bg-slate-50 dark:bg-slate-700 border-slate-300 dark:border-slate-600" + data-testid="mobile-search-input" + autoFocus + /> + {searchQuery ? ( + + + + + +

Clear Search

+
+
+ ) : null} +
+ {showResults ? ( + + ) : null} +
+ ) : null} +
+
+
+ ); +} diff --git a/client/src/components/layout/HomeHeader.tsx b/client/src/components/layout/HomeHeader.tsx new file mode 100644 index 00000000..80aebc5e --- /dev/null +++ b/client/src/components/layout/HomeHeader.tsx @@ -0,0 +1,326 @@ +import clsx from "clsx"; +import svgPaths from "../../imports/svg-3vrq16klz5"; +import { Moon, Sun, Search } from "lucide-react"; +import { useState, useRef, useEffect } from "react"; +import { useDemo } from "@/hooks/use-demo-hook"; + +interface HomeHeaderProps { + sidebarCollapsed: boolean; + setSidebarCollapsed: (value: boolean) => void; + headerMinimized: boolean; + setHeaderMinimized: (value: boolean) => void; + searchQuery: string; + setSearchQuery: (value: string) => void; + showSearchDropdown: boolean; + setShowSearchDropdown: (value: boolean) => void; + selectedSearchIndex: number; + setSelectedSearchIndex: (value: number) => void; + filteredTools: any[]; + handleToolClick: (toolName: string) => void; + handleSearchChange: (e: React.ChangeEvent) => void; + handleSearchKeyDown: (e: React.KeyboardEvent) => void; + handleSearchFocus: () => void; + handleSearchBlur: () => void; + searchRef: React.RefObject; + theme: 'dark' | 'light'; + toggleTheme: () => void; + currentColors: any; +} + +export function HomeHeader({ + sidebarCollapsed, + setSidebarCollapsed, + headerMinimized, + setHeaderMinimized, + searchQuery, + setSearchQuery, + showSearchDropdown, + setShowSearchDropdown, + selectedSearchIndex, + setSelectedSearchIndex, + filteredTools, + handleToolClick, + handleSearchChange, + handleSearchKeyDown, + handleSearchFocus, + handleSearchBlur, + searchRef, + theme, + toggleTheme, + currentColors +}: HomeHeaderProps) { + const { startDemo } = useDemo(); + + return ( + <> +
+
+ +
+ +
+ + + ); +} diff --git a/client/src/components/layout/Layout.tsx b/client/src/components/layout/Layout.tsx new file mode 100644 index 00000000..dde2ee42 --- /dev/null +++ b/client/src/components/layout/Layout.tsx @@ -0,0 +1,521 @@ +import { useState, useEffect } from "react"; +import { useLocation } from "wouter"; +import { Header } from "./Header"; +import { Sidebar } from "./Sidebar"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, +} from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + ChevronUp, + Square, + SkipForward, + SkipBack, + Timer, + Play, + Pause, +} from "lucide-react"; +import { toolsData, getToolByPath } from "@/data/tools"; +import { useDemo } from "@/hooks/use-demo-hook"; +import { useTheme } from "@/providers/theme-provider"; +import { HOMEPAGE_TITLE, getToolPageTitle } from "@shared/page-title"; + +interface LayoutProps { + children: React.ReactNode; +} + +export function Layout({ children }: LayoutProps) { + const [location, setLocation] = useLocation(); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [headerCollapsed, setHeaderCollapsed] = useState(false); + const [desktopSidebarVisible, setDesktopSidebarVisible] = useState(true); + + // Determine if sidebar should be shown by default (only on homepage and desktop) + const isHomepage = location === "/"; + const isToolPage = location.startsWith("/tools/"); + const shouldShowSidebarByDefault = isHomepage && !isToolPage; + const { + isDemoRunning, + isDemoPaused, + currentDemoTool, + demoProgress, + demoSpeed, + stopDemo, + skipToNext, + skipToPrevious, + setDemoSpeed, + pauseDemo, + resumeDemo, + } = useDemo(); + + const { theme, setTheme } = useTheme(); + + // Mobile fix: Blur CodeMirror when touching anywhere outside the editor + // This prevents the focus lock issue on mobile devices + useEffect(() => { + const blurCodeMirror = () => { + // Blur any focused CodeMirror editors + const focusedEditors = document.querySelectorAll(".cm-editor.cm-focused"); + focusedEditors.forEach(editor => { + const contentArea = editor.querySelector(".cm-content") as HTMLElement; + if (contentArea) { + contentArea.blur(); + } + }); + // Also blur any active element that might be inside CodeMirror + if (document.activeElement instanceof HTMLElement) { + const isInEditor = document.activeElement.closest(".cm-editor"); + if (isInEditor) { + document.activeElement.blur(); + } + } + }; + + const handlePointerDown = (event: PointerEvent) => { + const target = event.target as HTMLElement; + + // If touch/click is NOT inside a CodeMirror editor, blur any focused editors + const isInsideEditor = target.closest(".cm-editor"); + if (!isInsideEditor) { + blurCodeMirror(); + } + }; + + // Use pointerdown for better cross-device support (touch and mouse) + document.addEventListener("pointerdown", handlePointerDown, { + passive: true, + }); + return () => document.removeEventListener("pointerdown", handlePointerDown); + }, []); + + // Update document title based on current location + useEffect(() => { + if (location === "/") { + document.title = HOMEPAGE_TITLE; + } else if (location.startsWith("/tools/")) { + const tool = getToolByPath(location); + if (tool) { + document.title = getToolPageTitle(tool); + } + } + }, [location]); + + // Global keyboard shortcut handler + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Only handle if no input field is focused + if (event.target && (event.target as HTMLElement).tagName === "INPUT") + return; + + // Handle Escape key to close menu + if (event.key === "Escape" && mobileMenuOpen) { + event.preventDefault(); + setMobileMenuOpen(false); + return; + } + + if (event.ctrlKey) { + // Handle theme toggle with Ctrl+D (case-sensitive check) + if (event.key === "d" || event.key === "D") { + event.preventDefault(); + event.stopPropagation(); + setTheme(theme === "dark" ? "light" : "dark"); + return; + } + + // Handle menu toggle with Ctrl+M (case-sensitive check) + if (event.key === "m" || event.key === "M") { + event.preventDefault(); + event.stopPropagation(); + + // Toggle appropriate menu based on current context + if (shouldShowSidebarByDefault) { + // On homepage, toggle desktop sidebar on large screens, mobile menu on small screens + const isLargeScreen = window.innerWidth >= 1024; // lg breakpoint + if (isLargeScreen) { + setDesktopSidebarVisible(!desktopSidebarVisible); + } else { + toggleStateOfMobileMenu(mobileMenuOpen, setMobileMenuOpen); + } + return; + } + + // On tool pages, always toggle mobile menu + toggleStateOfMobileMenu(mobileMenuOpen, setMobileMenuOpen); + return; + } + + // Find matching tool by shortcut + Object.values(toolsData).forEach(section => { + section.tools.forEach(tool => { + const shortcutParts = tool.shortcut.split("+"); + let matches = true; + + if (shortcutParts.includes("Ctrl") && !event.ctrlKey) + matches = false; + if (shortcutParts.includes("Shift") && !event.shiftKey) + matches = false; + + const key = shortcutParts[shortcutParts.length - 1].toLowerCase(); + if (event.key.toLowerCase() !== key) matches = false; + + if (matches) { + event.preventDefault(); + setLocation(tool.path); // tool.path already includes /tools prefix + } + }); + }); + } + }; + + document.addEventListener("keydown", handleKeyDown, true); + return () => document.removeEventListener("keydown", handleKeyDown, true); + }, [ + setLocation, + mobileMenuOpen, + shouldShowSidebarByDefault, + desktopSidebarVisible, + theme, + setTheme, + ]); + + return ( + +
+ {/* Collapsible Header */} +
+
{ + // Toggle appropriate menu based on current context + if (shouldShowSidebarByDefault) { + // On homepage, toggle desktop sidebar on large screens, mobile menu on small screens + const isLargeScreen = window.innerWidth >= 1024; // lg breakpoint + if (isLargeScreen) { + setDesktopSidebarVisible(!desktopSidebarVisible); + } else { + setMobileMenuOpen(!mobileMenuOpen); + } + } else { + // On tool pages, always toggle mobile menu + setMobileMenuOpen(!mobileMenuOpen); + } + }} + /> +
+ + {/* Demo Status Bar */} + {isDemoRunning ? ( +
+
+
+ + Demo Mode Active + + + {currentDemoTool} + +
+
+
+ + {Math.round(demoProgress)}% + + + {/* Speed Control */} +
+ + +
+
+
+ + + + + +

Previous Tool in Demo

+
+
+ + + + + +

+ {isDemoPaused ? "Resume Demo Tour" : "Pause Demo Tour"} +

+
+
+ + + + + +

Next Tool in Demo

+
+
+ + + + + +

Stop Demo Tour

+
+
+
+
+
+ ) : null} + + {/* Header Collapse Toggle */} +
+ + + + + +

{headerCollapsed ? "Expand Header" : "Minimize Header"}

+
+
+
+ +
+ {/* Sidebar - Show by default on homepage desktop, hamburger menu on mobile and tool pages */} + {shouldShowSidebarByDefault ? ( + <> + {/* Default sidebar on homepage - hidden on mobile (lg:block) */} + {desktopSidebarVisible ? ( + + ) : null} + + {/* Hamburger menu for mobile on homepage */} + + + + Navigation Menu + + Navigation menu with all available developer tools + organized by category + + +
+ setMobileMenuOpen(false)} + /> +
+
+
+ + {/* Main content with sidebar visible on desktop, full width on mobile */} +
+ {children} +
+ + ) : ( + <> + {/* Hamburger menu for tool pages */} + + + + Navigation Menu + + Navigation menu with all available developer tools + organized by category + + +
+ setMobileMenuOpen(false)} + /> +
+
+
+ {/* Main content full width */} +
+ {children} +
+ + )} +
+ + {/* Footer */} +
+
+
+ Ready + + All operations client-side +
+
+ + Ctrl+M + + Menu + + + Ctrl+S + + Search + + + Ctrl+D + + Theme +
+
+
+
+ + ); +} + +function toggleStateOfMobileMenu( + mobileMenuOpen: boolean, + setMobileMenuOpen: React.Dispatch> +) { + const wasOpen = mobileMenuOpen; + setMobileMenuOpen(!mobileMenuOpen); + + // If opening the menu, focus the sidebar after a brief delay + if (!wasOpen) { + setTimeout(() => { + const sidebar = document.querySelector( + '[role="navigation"]' + ) as HTMLElement; + if (sidebar) { + sidebar.focus(); + } + }, 100); + } +} diff --git a/client/src/components/layout/Sidebar.tsx b/client/src/components/layout/Sidebar.tsx new file mode 100644 index 00000000..11adec71 --- /dev/null +++ b/client/src/components/layout/Sidebar.tsx @@ -0,0 +1,621 @@ +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Link, useLocation } from "wouter"; +import { useState, useEffect, useRef, useCallback } from "react"; +import { toolsData } from "@/data/tools"; +import { + Home, + Calendar, + ArrowRightLeft, + Code, + FileText, + Paintbrush, + FileCode, + Hash, + Link as LinkIcon, + Key, + Shield, + GitCompare, + Search, + ArrowUpDown, + FileBarChart, + Clock, + Timer, + Globe, + Calculator, + CreditCard, + Square, + BarChart3, + Type, + Palette, + Video, + Volume2, + Command, + ChevronDown, + ChevronRight, + FileSpreadsheet, + ChevronsDown, + ChevronsUp, +} from "lucide-react"; + +interface SidebarProps { + className?: string; + collapsed?: boolean; + onToolClick?: () => void; // Callback for when a tool is clicked (to close mobile menu) +} + +// Icon mapping function based on category and tool name +function getToolIcon(category: string, toolName: string) { + const iconMap: Record> = { + Conversions: { + "Date Converter": , + "JSON ↔ YAML": , + "Timezone Converter": , + "Unit Converter": , + "URL to JSON": , + "CSV to JSON": , + "Number Base Converter": , + }, + Formatters: { + "JSON Formatter": , + "HTML Formatter": , + "YAML Formatter": , + "Markdown Formatter": , + "CSS Formatter": , + "LESS Formatter": , + "Time Formatter": , + }, + Encoders: { + "Base64 Encoder": , + "URL Encoder": , + "JWT Decoder": , + "TLS Decoder": , + "MD5 Hash": , + "BCrypt Hash": , + }, + "Text Tools": { + "Text Diff Viewer": , + "Regex Tester": , + "Text Sort": , + "Word Counter": , + "QR Code Generator": , + "Barcode Generator": , + "Lorem Ipsum Generator": , + "Unicode Character Map": , + "Password Generator": , + "UUID Generator": , + "Search & Replace": , + "Text Split": , + }, + "Time Tools": { + "World Clock": , + Timer: , + Stopwatch: , + Countdown: , + "Date/Time Diff": , + Metronome: , + }, + "Financial Tools": { + "Compound Interest": , + "Debt Repayment": , + }, + "Color Tools": { + "Color Palette Generator": , + }, + Hardware: { + "Camera Test":