feat: refactor and optimize frontend with Next.js App Router, TypeScript, and Tailwind CSS
- Complete refactoring of old frontend into Next.js App Router workspace - Redesigned sidebar collapsing animation with absolute toggle positioning - Resolved visual canvas bleed transitions between light/dark themes - Added custom dark theme variant for toggle switch buttons - Implemented full localization across Indonesian, English, Spanish, Japanese, and Chinese - Synchronized HTML document themes to apply dark mode styles to portals/overlays
@@ -0,0 +1,9 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[47257,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ClientPageRoot"]
|
||||
3:I[52683,["/_next/static/chunks/0dbhjjzl8qfwv.js","/_next/static/chunks/01yjdu1pd4sew.js"],"default"]
|
||||
6:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
7:"$Sreact.suspense"
|
||||
0:{"rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/01yjdu1pd4sew.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
4:{}
|
||||
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
|
||||
8:null
|
||||
@@ -0,0 +1,20 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
4:I[47257,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ClientPageRoot"]
|
||||
5:I[52683,["/_next/static/chunks/0dbhjjzl8qfwv.js","/_next/static/chunks/01yjdu1pd4sew.js"],"default"]
|
||||
8:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
9:"$Sreact.suspense"
|
||||
b:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
d:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
f:I[68027,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default",1]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
:HL["/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/_next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@6","$@7"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/01yjdu1pd4sew.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$","$9",null,{"name":"Next.MetadataOutlet","children":"$@a"}]}]]}],{},null,false,null]},null,false,null],["$","$1","h",{"children":[null,["$","$Lb",null,{"children":"$Lc"}],["$","div",null,{"hidden":true,"children":["$","$Ld",null,{"children":["$","$9",null,{"name":"Next.Metadata","children":"$Le"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$f",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
6:{}
|
||||
7:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
|
||||
c:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
10:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
a:null
|
||||
e:[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L10","3",{}]]
|
||||
@@ -0,0 +1,6 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
3:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
4:"$Sreact.suspense"
|
||||
5:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L5","3",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -0,0 +1,5 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -0,0 +1,4 @@
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
:HL["/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/_next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}},"staleTime":300,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,11 @@
|
||||
self.__BUILD_MANIFEST = {
|
||||
"__rewrites": {
|
||||
"afterFiles": [],
|
||||
"beforeFiles": [],
|
||||
"fallback": []
|
||||
},
|
||||
"sortedPages": [
|
||||
"/_app",
|
||||
"/_error"
|
||||
]
|
||||
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
|
||||
@@ -0,0 +1 @@
|
||||
self.__MIDDLEWARE_MATCHERS = [];self.__MIDDLEWARE_MATCHERS_CB && self.__MIDDLEWARE_MATCHERS_CB()
|
||||
@@ -0,0 +1 @@
|
||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
||||
@@ -0,0 +1,16 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
4:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
5:"$Sreact.suspense"
|
||||
8:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
a:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
c:I[68027,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default",1]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
0:{"P":null,"c":["","_not-found"],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,null],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
d:[]
|
||||
7:"$Wd"
|
||||
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
e:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
6:null
|
||||
b:[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$Le","3",{}]]
|
||||
@@ -0,0 +1,16 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
4:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
5:"$Sreact.suspense"
|
||||
8:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
a:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
c:I[68027,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default",1]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
0:{"P":null,"c":["","_not-found"],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L4",null,{"children":["$","$5",null,{"name":"Next.MetadataOutlet","children":"$@6"}]}]]}],{},null,false,null]},null,false,"$@7"]},null,false,null],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$5",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
d:[]
|
||||
7:"$Wd"
|
||||
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
e:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
6:null
|
||||
b:[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$Le","3",{}]]
|
||||
@@ -0,0 +1,6 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
3:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
4:"$Sreact.suspense"
|
||||
5:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
0:{"rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L5","3",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -0,0 +1,5 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -0,0 +1,5 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
4:[]
|
||||
0:{"rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"isPartial":false,"staleTime":300,"varyParams":"$W4","buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -0,0 +1,5 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
3:"$Sreact.suspense"
|
||||
0:{"rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
4:null
|
||||
@@ -0,0 +1,2 @@
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"/_not-found","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
@@ -1,744 +0,0 @@
|
||||
/**
|
||||
* Omni Real-Time Voice Changer - Client App
|
||||
* High-performance browser-based mic streaming and RVC playback.
|
||||
*/
|
||||
|
||||
// UI Elements
|
||||
const wsUrlInput = document.getElementById('ws_url');
|
||||
const connectionStatus = document.getElementById('connection_status');
|
||||
const connectBtn = document.getElementById('connect_btn');
|
||||
const streamBtn = document.getElementById('stream_btn');
|
||||
const playToggleBtn = document.getElementById('play_toggle_btn');
|
||||
|
||||
const modelSelect = document.getElementById('model_select');
|
||||
const deviceSelect = document.getElementById('device_select');
|
||||
const transposeSlider = document.getElementById('transpose_slider');
|
||||
const transposeVal = document.getElementById('transpose_val');
|
||||
const gateSlider = document.getElementById('gate_slider');
|
||||
const gateVal = document.getElementById('gate_val');
|
||||
const inputGainSlider = document.getElementById('input_gain_slider');
|
||||
const inputGainVal = document.getElementById('input_gain_val');
|
||||
const outputGainSlider = document.getElementById('output_gain_slider');
|
||||
const outputGainVal = document.getElementById('output_gain_val');
|
||||
const chunkSelect = document.getElementById('chunk_select');
|
||||
const noiseCancelCheckbox = document.getElementById('noise_cancel_checkbox');
|
||||
const routingModeSelect = document.getElementById('routing_mode_select');
|
||||
const hardwareDevicesPanel = document.getElementById('hardware_devices_panel');
|
||||
const serverInputSelect = document.getElementById('server_input_select');
|
||||
const serverOutputSelect = document.getElementById('server_output_select');
|
||||
const browserNoiseCancelGroup = document.getElementById('browser_noise_cancel_group');
|
||||
|
||||
const presetLatencyBtn = document.getElementById('preset_latency_btn');
|
||||
const presetQualityBtn = document.getElementById('preset_quality_btn');
|
||||
|
||||
const inputCanvas = document.getElementById('input_canvas');
|
||||
const outputCanvas = document.getElementById('output_canvas');
|
||||
|
||||
const hudLatency = document.getElementById('hud_latency');
|
||||
const hudTime = document.getElementById('hud_time');
|
||||
const hudGateStatus = document.getElementById('hud_gate_status');
|
||||
const hudSr = document.getElementById('hud_sr');
|
||||
|
||||
// Audio Visualizer Contexts
|
||||
const inputCtx = inputCanvas.getContext('2d');
|
||||
const outputCtx = outputCanvas.getContext('2d');
|
||||
|
||||
// Web Audio State
|
||||
let audioContext = null;
|
||||
let micStream = null;
|
||||
let micSourceNode = null;
|
||||
let scriptProcessorNode = null;
|
||||
let micAccumulator = new Float32Array(0); // Accumulates audio for large/custom chunk sizes
|
||||
|
||||
// WebSocket State
|
||||
let socket = null;
|
||||
let isStreaming = false;
|
||||
let playOutput = true;
|
||||
let targetSampleRate = 40000; // RVC Model default, updated dynamically
|
||||
|
||||
// Playback Sync State
|
||||
let nextPlaybackTime = 0;
|
||||
const safetyDelay = 0.10; // 100ms buffer to absorb network/websocket jitter (increased for perfect smoothness!)
|
||||
|
||||
// Latency Tracking Queues
|
||||
let sentTimestamps = [];
|
||||
const maxSentLogs = 50;
|
||||
|
||||
// --- SMOOTH VISUALIZER (Rolling Display Buffers + RAF loop) ---
|
||||
// Fixed display buffer size: ~85ms window looks great at all chunk sizes.
|
||||
const VIS_DISPLAY_SIZE = 4096;
|
||||
let inputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE); // rolling input (updated ~85ms)
|
||||
let outputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE); // fallback for hardware mode
|
||||
let rafHandle = null;
|
||||
|
||||
// Time-synced output queue: each entry = { data: Float32Array, startTime: number (audioCtx seconds) }
|
||||
let outputChunkQueue = [];
|
||||
|
||||
function pushToDisplayBuf(displayBuf, newSamples) {
|
||||
if (newSamples.length >= VIS_DISPLAY_SIZE) {
|
||||
displayBuf.set(newSamples.slice(newSamples.length - VIS_DISPLAY_SIZE));
|
||||
} else {
|
||||
displayBuf.copyWithin(0, newSamples.length);
|
||||
displayBuf.set(newSamples, VIS_DISPLAY_SIZE - newSamples.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Build a VIS_DISPLAY_SIZE window of output samples ending at audioContext.currentTime
|
||||
function buildTimeSyncedOutputBuf() {
|
||||
if (!audioContext || outputChunkQueue.length === 0) return outputDisplayBuf;
|
||||
|
||||
const now = audioContext.currentTime;
|
||||
const windowDuration = VIS_DISPLAY_SIZE / targetSampleRate;
|
||||
const windowStart = now - windowDuration;
|
||||
|
||||
// Drop chunks that ended before our window start
|
||||
while (outputChunkQueue.length > 0) {
|
||||
const c = outputChunkQueue[0];
|
||||
if (c.startTime + c.data.length / targetSampleRate < windowStart) {
|
||||
outputChunkQueue.shift();
|
||||
} else break;
|
||||
}
|
||||
|
||||
const out = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
for (const chunk of outputChunkQueue) {
|
||||
const chunkEnd = chunk.startTime + chunk.data.length / targetSampleRate;
|
||||
// Overlap between [windowStart, now] and [chunk.startTime, chunkEnd]
|
||||
const overlapStart = Math.max(windowStart, chunk.startTime);
|
||||
const overlapEnd = Math.min(now, chunkEnd);
|
||||
if (overlapStart >= overlapEnd) continue;
|
||||
|
||||
const srcOffset = Math.floor((overlapStart - chunk.startTime) * targetSampleRate);
|
||||
const destOffset = Math.floor((overlapStart - windowStart) * targetSampleRate);
|
||||
const count = Math.floor((overlapEnd - overlapStart) * targetSampleRate);
|
||||
const safeCount = Math.min(count,
|
||||
chunk.data.length - srcOffset,
|
||||
VIS_DISPLAY_SIZE - destOffset);
|
||||
if (safeCount > 0) out.set(chunk.data.subarray(srcOffset, srcOffset + safeCount), destOffset);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function startVisualizerLoop() {
|
||||
if (rafHandle) return;
|
||||
function frame() {
|
||||
drawWaveform(inputDisplayBuf, inputCanvas, '#a3e635');
|
||||
// Time-synced output: scrub through queued chunks using audioContext clock
|
||||
drawWaveform(buildTimeSyncedOutputBuf(), outputCanvas, '#22d3ee');
|
||||
rafHandle = requestAnimationFrame(frame);
|
||||
}
|
||||
rafHandle = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
function stopVisualizerLoop() {
|
||||
if (rafHandle) {
|
||||
cancelAnimationFrame(rafHandle);
|
||||
rafHandle = null;
|
||||
}
|
||||
outputChunkQueue = [];
|
||||
}
|
||||
|
||||
// Setup Canvas Sizes dynamically
|
||||
function resizeCanvases() {
|
||||
inputCanvas.width = inputCanvas.clientWidth * window.devicePixelRatio;
|
||||
inputCanvas.height = inputCanvas.clientHeight * window.devicePixelRatio;
|
||||
outputCanvas.width = outputCanvas.clientWidth * window.devicePixelRatio;
|
||||
outputCanvas.height = outputCanvas.clientHeight * window.devicePixelRatio;
|
||||
}
|
||||
resizeCanvases();
|
||||
window.addEventListener('resize', resizeCanvases);
|
||||
|
||||
// Connect / Disconnect WebSocket
|
||||
connectBtn.addEventListener('click', () => {
|
||||
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
|
||||
disconnectServer();
|
||||
} else {
|
||||
connectServer();
|
||||
}
|
||||
});
|
||||
|
||||
function connectServer() {
|
||||
const url = wsUrlInput.value.trim();
|
||||
updateConnectionStatus('connecting');
|
||||
|
||||
try {
|
||||
socket = new WebSocket(url);
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log('Connected to RVC Server');
|
||||
updateConnectionStatus('connected');
|
||||
sendConfigToServer(); // Send initial configurations
|
||||
streamBtn.disabled = false;
|
||||
playToggleBtn.disabled = false;
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log('WebSocket Connection Closed');
|
||||
disconnectServer();
|
||||
};
|
||||
|
||||
socket.onerror = (err) => {
|
||||
console.error('WebSocket Error:', err);
|
||||
disconnectServer();
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
if (typeof event.data === 'string') {
|
||||
// Config or control response
|
||||
try {
|
||||
const response = JSON.parse(event.data);
|
||||
if (response.type === 'config_success') {
|
||||
targetSampleRate = response.target_sr;
|
||||
console.log('Server configuration synced successfully:', response);
|
||||
} else if (response.type === 'init_devices') {
|
||||
populateServerDevices(response.devices, response.default_input, response.default_output);
|
||||
} else if (response.type === 'visualizer') {
|
||||
// Feed rolling display buffers — RAF loop handles drawing at 60fps
|
||||
pushToDisplayBuf(inputDisplayBuf, new Float32Array(response.input));
|
||||
pushToDisplayBuf(outputDisplayBuf, new Float32Array(response.output));
|
||||
if (!rafHandle) startVisualizerLoop();
|
||||
} else if (response.type === 'error') {
|
||||
alert('Server Error: ' + response.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing text message:', e);
|
||||
}
|
||||
} else if (event.data instanceof ArrayBuffer) {
|
||||
// Binary processed PCM audio chunk returned from server (Browser Mode only)
|
||||
handleServerAudioChunk(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
console.error('Connection failed:', e);
|
||||
disconnectServer();
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectServer() {
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
}
|
||||
|
||||
if (socket) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (e) {}
|
||||
socket = null;
|
||||
}
|
||||
|
||||
updateConnectionStatus('disconnected');
|
||||
streamBtn.disabled = true;
|
||||
playToggleBtn.disabled = true;
|
||||
}
|
||||
|
||||
function updateConnectionStatus(status) {
|
||||
connectionStatus.className = 'status-badge ' + status;
|
||||
if (status === 'connected') {
|
||||
connectionStatus.textContent = 'Terhubung';
|
||||
connectBtn.textContent = 'Putuskan Server';
|
||||
connectBtn.className = 'btn btn-primary';
|
||||
} else if (status === 'connecting') {
|
||||
connectionStatus.textContent = 'Menghubungkan';
|
||||
connectBtn.textContent = 'Batal';
|
||||
} else {
|
||||
connectionStatus.textContent = 'Terputus';
|
||||
connectBtn.textContent = 'Hubungkan Server';
|
||||
connectBtn.className = 'btn btn-primary';
|
||||
}
|
||||
}
|
||||
|
||||
// Config synchronization
|
||||
function sendConfigToServer() {
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
const activeF0 = document.querySelector('input[name="f0_method"]:checked').value;
|
||||
|
||||
const config = {
|
||||
type: 'config',
|
||||
model_name: modelSelect.value,
|
||||
device: deviceSelect.value,
|
||||
f0_method: activeF0,
|
||||
f0_up_key: parseInt(transposeSlider.value),
|
||||
noise_gate: parseFloat(gateSlider.value),
|
||||
input_gain: parseFloat(inputGainSlider.value),
|
||||
output_gain: parseFloat(outputGainSlider.value),
|
||||
input_sr: audioContext ? audioContext.sampleRate : 44100,
|
||||
routing_mode: routingModeSelect.value,
|
||||
input_device: serverInputSelect.value ? parseInt(serverInputSelect.value) : null,
|
||||
output_device: serverOutputSelect.value ? parseInt(serverOutputSelect.value) : null,
|
||||
chunk_size: parseInt(chunkSelect.value)
|
||||
};
|
||||
|
||||
socket.send(jsonEncode(config));
|
||||
console.log('Sent configuration change:', config);
|
||||
}
|
||||
|
||||
// Populate Server Audio Devices dropdowns
|
||||
function populateServerDevices(devices, defaultInput, defaultOutput) {
|
||||
serverInputSelect.innerHTML = '';
|
||||
serverOutputSelect.innerHTML = '';
|
||||
|
||||
if (devices.length === 0) {
|
||||
const optIn = document.createElement('option');
|
||||
optIn.textContent = 'Tidak ada mic terdeteksi di server';
|
||||
serverInputSelect.appendChild(optIn);
|
||||
|
||||
const optOut = document.createElement('option');
|
||||
optOut.textContent = 'Tidak ada output terdeteksi di server';
|
||||
serverOutputSelect.appendChild(optOut);
|
||||
return;
|
||||
}
|
||||
|
||||
devices.forEach(device => {
|
||||
if (device.max_input_channels > 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = device.id;
|
||||
opt.textContent = `[ID ${device.id}] ${device.name}`;
|
||||
if (device.id === defaultInput) opt.selected = true;
|
||||
serverInputSelect.appendChild(opt);
|
||||
}
|
||||
|
||||
if (device.max_output_channels > 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = device.id;
|
||||
opt.textContent = `[ID ${device.id}] ${device.name}`;
|
||||
if (device.id === defaultOutput) opt.selected = true;
|
||||
serverOutputSelect.appendChild(opt);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Successfully populated server hardware devices in UI.');
|
||||
}
|
||||
|
||||
// UI Event Listeners to trigger instant sync
|
||||
modelSelect.addEventListener('change', sendConfigToServer);
|
||||
deviceSelect.addEventListener('change', sendConfigToServer);
|
||||
document.querySelectorAll('input[name="f0_method"]').forEach(radio => {
|
||||
radio.addEventListener('change', sendConfigToServer);
|
||||
});
|
||||
|
||||
transposeSlider.addEventListener('input', () => {
|
||||
transposeVal.textContent = (transposeSlider.value >= 0 ? '+' : '') + transposeSlider.value + ' semitone';
|
||||
});
|
||||
transposeSlider.addEventListener('change', sendConfigToServer);
|
||||
|
||||
gateSlider.addEventListener('input', () => {
|
||||
gateVal.textContent = gateSlider.value + ' dB';
|
||||
});
|
||||
gateSlider.addEventListener('change', sendConfigToServer);
|
||||
|
||||
inputGainSlider.addEventListener('input', () => {
|
||||
inputGainVal.textContent = parseFloat(inputGainSlider.value).toFixed(1) + 'x';
|
||||
});
|
||||
inputGainSlider.addEventListener('change', sendConfigToServer);
|
||||
|
||||
outputGainSlider.addEventListener('input', () => {
|
||||
outputGainVal.textContent = parseFloat(outputGainSlider.value).toFixed(1) + 'x';
|
||||
});
|
||||
outputGainSlider.addEventListener('change', sendConfigToServer);
|
||||
|
||||
chunkSelect.addEventListener('change', () => {
|
||||
// Reinitialize stream if buffer size is changed during active streaming
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
noiseCancelCheckbox.addEventListener('change', () => {
|
||||
// Reinitialize microphone with new noise cancellation constraints if streaming
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
// Helper to dynamically adjust UI layout based on Routing Mode
|
||||
function applyAudioRoutingUI() {
|
||||
if (routingModeSelect.value === 'hardware') {
|
||||
hardwareDevicesPanel.style.display = 'block';
|
||||
playToggleBtn.style.display = 'none'; // Hide browser-only "Mendengarkan" button
|
||||
browserNoiseCancelGroup.style.display = 'none'; // Hide browser-only Noise Cancel checkbox
|
||||
} else {
|
||||
hardwareDevicesPanel.style.display = 'none';
|
||||
playToggleBtn.style.display = 'inline-block'; // Show browser-only "Mendengarkan" button
|
||||
browserNoiseCancelGroup.style.display = 'block'; // Show browser-only Noise Cancel checkbox
|
||||
}
|
||||
}
|
||||
|
||||
// Routing Mode Event Listeners
|
||||
routingModeSelect.addEventListener('change', () => {
|
||||
applyAudioRoutingUI();
|
||||
sendConfigToServer();
|
||||
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
serverInputSelect.addEventListener('change', sendConfigToServer);
|
||||
serverOutputSelect.addEventListener('change', sendConfigToServer);
|
||||
|
||||
// Quick Presets Event Listeners
|
||||
presetLatencyBtn.addEventListener('click', () => {
|
||||
const radioPM = document.querySelector('input[name="f0_method"][value="pm"]');
|
||||
if (radioPM) radioPM.checked = true;
|
||||
chunkSelect.value = "8192";
|
||||
|
||||
console.log("Preset loaded: Latency (PM + 8192)");
|
||||
sendConfigToServer();
|
||||
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
presetQualityBtn.addEventListener('click', () => {
|
||||
const radioRMVPE = document.querySelector('input[name="f0_method"][value="rmvpe"]');
|
||||
if (radioRMVPE) radioRMVPE.checked = true;
|
||||
chunkSelect.value = "16384";
|
||||
|
||||
console.log("Preset loaded: Quality (RMVPE + 16384)");
|
||||
sendConfigToServer();
|
||||
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions for UI JSON safely
|
||||
function jsonEncode(obj) {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
playToggleBtn.addEventListener('click', () => {
|
||||
playOutput = !playOutput;
|
||||
if (playOutput) {
|
||||
playToggleBtn.textContent = '🔊 Mendengarkan: AKTIF';
|
||||
playToggleBtn.className = 'btn btn-primary';
|
||||
} else {
|
||||
playToggleBtn.textContent = '🔇 Mendengarkan: SENYAP';
|
||||
playToggleBtn.className = 'btn btn-accent';
|
||||
}
|
||||
});
|
||||
|
||||
// Stream Toggle
|
||||
streamBtn.addEventListener('click', () => {
|
||||
if (isStreaming) {
|
||||
stopStreaming();
|
||||
} else {
|
||||
startStreaming();
|
||||
}
|
||||
});
|
||||
|
||||
async function startStreaming() {
|
||||
isStreaming = true;
|
||||
streamBtn.textContent = 'Hentikan Pengubah Suara';
|
||||
streamBtn.className = 'btn btn-primary';
|
||||
|
||||
const isHardwareMode = (routingModeSelect.value === 'hardware');
|
||||
|
||||
if (isHardwareMode) {
|
||||
// --- SERVER HARDWARE ROUTING MODE ---
|
||||
inputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
outputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
startVisualizerLoop();
|
||||
sendConfigToServer(); // Sends config with routing_mode: 'hardware' which triggers stream start on server
|
||||
console.log('Server Hardware Mode initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
// --- CLIENT BROWSER MODE ---
|
||||
// 1. Create AudioContext if not active
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)({
|
||||
latencyHint: 'interactive'
|
||||
});
|
||||
}
|
||||
|
||||
if (audioContext.state === 'suspended') {
|
||||
await audioContext.resume();
|
||||
}
|
||||
|
||||
hudSr.textContent = audioContext.sampleRate + ' Hz';
|
||||
sendConfigToServer(); // sync actual input sample rate
|
||||
|
||||
// 2. Request user microphone with high-fidelity, lowest possible latency constraints
|
||||
try {
|
||||
const useNoiseCancel = noiseCancelCheckbox.checked;
|
||||
micStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: useNoiseCancel,
|
||||
noiseSuppression: useNoiseCancel,
|
||||
autoGainControl: useNoiseCancel
|
||||
}
|
||||
});
|
||||
|
||||
micSourceNode = audioContext.createMediaStreamSource(micStream);
|
||||
|
||||
// 3. Create Audio Processing Loop Node (ScriptProcessorNode)
|
||||
// BaseAudioContext's createScriptProcessor buffer size MUST be a power of two between 256 and 16384.
|
||||
// We use a fixed, highly supported buffer size of 4096 for recording, and accumulate samples in-memory
|
||||
// to support ANY arbitrary or extremely large chunk size (like 12288, 24576, 32768) selected by the user!
|
||||
const recordBufferSize = 4096;
|
||||
scriptProcessorNode = audioContext.createScriptProcessor(recordBufferSize, 1, 1);
|
||||
|
||||
scriptProcessorNode.onaudioprocess = (event) => {
|
||||
if (!isStreaming) return;
|
||||
|
||||
const inputBuffer = event.inputBuffer;
|
||||
const inputData = inputBuffer.getChannelData(0); // 4096 samples
|
||||
|
||||
// Push latest mic samples into the rolling display buffer every callback (~85ms)
|
||||
pushToDisplayBuf(inputDisplayBuf, inputData);
|
||||
|
||||
// Append incoming recorded samples to our accumulator
|
||||
const temp = new Float32Array(micAccumulator.length + inputData.length);
|
||||
temp.set(micAccumulator);
|
||||
temp.set(inputData, micAccumulator.length);
|
||||
micAccumulator = temp;
|
||||
|
||||
const targetChunkSize = parseInt(chunkSelect.value);
|
||||
|
||||
// Process and send chunks of the user's selected target size
|
||||
while (micAccumulator.length >= targetChunkSize) {
|
||||
const chunkToSend = micAccumulator.slice(0, targetChunkSize);
|
||||
micAccumulator = micAccumulator.slice(targetChunkSize); // Keep remainder
|
||||
|
||||
// Voice Activity Detection for gate status badge
|
||||
let maxVal = 0;
|
||||
for (let i = 0; i < chunkToSend.length; i++) maxVal = Math.max(maxVal, Math.abs(chunkToSend[i]));
|
||||
if (maxVal > 0.005) {
|
||||
hudGateStatus.textContent = 'Bicara';
|
||||
hudGateStatus.className = 'hud-value active-badge';
|
||||
} else {
|
||||
hudGateStatus.textContent = 'Berdiam';
|
||||
hudGateStatus.className = 'hud-value text-muted';
|
||||
}
|
||||
|
||||
// Send binary PCM Float32 audio chunk of target size to Python Server
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
const packetTime = performance.now();
|
||||
sentTimestamps.push({ id: packetTime, sent: packetTime });
|
||||
if (sentTimestamps.length > maxSentLogs) {
|
||||
sentTimestamps.shift();
|
||||
}
|
||||
|
||||
socket.send(chunkToSend.buffer); // Send direct array buffer
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
micSourceNode.connect(scriptProcessorNode);
|
||||
scriptProcessorNode.connect(audioContext.destination); // Required to trigger onaudioprocess
|
||||
|
||||
// Reset playback sync clock
|
||||
nextPlaybackTime = 0;
|
||||
micAccumulator = new Float32Array(0); // Reset accumulator
|
||||
inputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
outputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
startVisualizerLoop();
|
||||
|
||||
console.log('Browser Streaming active. Recording buffer size: 4096 | Target chunk size:', chunkSelect.value);
|
||||
} catch (e) {
|
||||
console.error('Failed to access microphone:', e);
|
||||
alert('Gagal mengakses mikrofon Anda: ' + e.message);
|
||||
stopStreaming();
|
||||
}
|
||||
}
|
||||
|
||||
function stopStreaming() {
|
||||
isStreaming = false;
|
||||
streamBtn.textContent = 'Mulai Mengubah Suara';
|
||||
streamBtn.className = 'btn btn-accent';
|
||||
|
||||
playOutput = true;
|
||||
playToggleBtn.textContent = '🔊 Mendengarkan: AKTIF';
|
||||
playToggleBtn.className = 'btn btn-primary';
|
||||
|
||||
const isHardwareMode = (routingModeSelect.value === 'hardware');
|
||||
|
||||
if (isHardwareMode) {
|
||||
// --- SERVER HARDWARE ROUTING MODE ---
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
const config = {
|
||||
type: 'config',
|
||||
routing_mode: 'browser' // Tells server to stop local hardware stream
|
||||
};
|
||||
socket.send(jsonEncode(config));
|
||||
}
|
||||
console.log('Server Hardware Mode stopped.');
|
||||
|
||||
hudGateStatus.textContent = 'Berdiam';
|
||||
hudGateStatus.className = 'hud-value text-muted';
|
||||
hudLatency.textContent = '-- ms';
|
||||
hudTime.textContent = '-- ms';
|
||||
|
||||
stopVisualizerLoop();
|
||||
inputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
outputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
clearCanvas(inputCanvas);
|
||||
clearCanvas(outputCanvas);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- CLIENT BROWSER MODE ---
|
||||
// Stop microphone stream tracks
|
||||
if (micStream) {
|
||||
micStream.getTracks().forEach(track => track.stop());
|
||||
micStream = null;
|
||||
}
|
||||
|
||||
// Disconnect Web Audio nodes
|
||||
if (micSourceNode) {
|
||||
micSourceNode.disconnect();
|
||||
micSourceNode = null;
|
||||
}
|
||||
if (scriptProcessorNode) {
|
||||
scriptProcessorNode.disconnect();
|
||||
scriptProcessorNode = null;
|
||||
}
|
||||
|
||||
micAccumulator = new Float32Array(0); // Reset accumulator
|
||||
|
||||
stopVisualizerLoop();
|
||||
inputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
outputDisplayBuf = new Float32Array(VIS_DISPLAY_SIZE);
|
||||
|
||||
hudGateStatus.textContent = 'Berdiam';
|
||||
hudGateStatus.className = 'hud-value text-muted';
|
||||
hudLatency.textContent = '-- ms';
|
||||
hudTime.textContent = '-- ms';
|
||||
|
||||
clearCanvas(inputCanvas);
|
||||
clearCanvas(outputCanvas);
|
||||
}
|
||||
|
||||
// Seamless Audio Playback Scheduler (Absorbs WebSocket & processing jitter)
|
||||
function handleServerAudioChunk(arrayBuffer) {
|
||||
if (!isStreaming) return;
|
||||
|
||||
// 1. Measure Round-Trip Time Latency (RTT)
|
||||
const now = performance.now();
|
||||
let rtt = 0;
|
||||
if (sentTimestamps.length > 0) {
|
||||
const oldestSent = sentTimestamps.shift();
|
||||
rtt = now - oldestSent.sent;
|
||||
hudLatency.textContent = Math.round(rtt) + ' ms';
|
||||
}
|
||||
|
||||
// Convert arrayBuffer to Float32 samples
|
||||
const payload = new Float32Array(arrayBuffer);
|
||||
const processingTime = payload[0]; // first float32 is the server processing time in ms
|
||||
const pcmData = payload.subarray(1); // the rest is the audio
|
||||
|
||||
// 2. Schedule chunk smoothly inside the AudioContext timeline
|
||||
const audioBuf = audioContext.createBuffer(1, pcmData.length, targetSampleRate);
|
||||
audioBuf.getChannelData(0).set(pcmData);
|
||||
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = audioBuf;
|
||||
|
||||
if (playOutput) {
|
||||
source.connect(audioContext.destination);
|
||||
}
|
||||
|
||||
// Calculate precise playback clock scheduling
|
||||
const currentTime = audioContext.currentTime;
|
||||
const chunkDuration = audioBuf.duration; // actual chunk duration in seconds
|
||||
// Adaptive buffer: enough headroom so next chunk always arrives before this one ends.
|
||||
// 2.5× chunk or 500ms cap — absorbs even 300ms+ processing spikes.
|
||||
const adaptiveBuf = Math.min(chunkDuration * 2.5, 0.50);
|
||||
|
||||
if (nextPlaybackTime < currentTime) {
|
||||
// Clock behind — first chunk or dropout recovery.
|
||||
// Use full adaptiveBuf on BOTH cases so recovery fully rebuilds headroom.
|
||||
// (0.5× recovery was causing cascading dropouts: one late chunk → the next also late)
|
||||
nextPlaybackTime = currentTime + adaptiveBuf;
|
||||
} else if (nextPlaybackTime > currentTime + chunkDuration * 5.0) {
|
||||
// --- ADAPTIVE LATENCY BUSTER ---
|
||||
// Only snap when queue is >5 chunk-durations ahead (genuine backlog, not normal look-ahead).
|
||||
// At 8192 (170ms): threshold = 850ms
|
||||
// At 65536 (1.6s): threshold = 8s
|
||||
const snapTarget = currentTime + adaptiveBuf;
|
||||
console.log(`Latency Buster: ${Math.round((nextPlaybackTime-currentTime)*1000)}ms → ${Math.round(adaptiveBuf*1000)}ms`);
|
||||
nextPlaybackTime = snapTarget;
|
||||
}
|
||||
|
||||
// Record schedule start time BEFORE advancing the clock (for time-synced visualizer)
|
||||
const scheduleStartTime = nextPlaybackTime;
|
||||
|
||||
// Schedule play
|
||||
source.start(nextPlaybackTime);
|
||||
|
||||
hudTime.textContent = Math.max(0, Math.round(processingTime)) + ' ms';
|
||||
|
||||
// Advance playback sync clock
|
||||
nextPlaybackTime += audioBuf.duration;
|
||||
|
||||
// Push to time-synced output queue for visualizer (keyed by when audio actually plays)
|
||||
outputChunkQueue.push({ data: pcmData, startTime: scheduleStartTime });
|
||||
// Keep queue bounded to ~10 seconds of audio max
|
||||
while (outputChunkQueue.length > 0) {
|
||||
const c = outputChunkQueue[0];
|
||||
if (c.startTime + c.data.length / targetSampleRate < audioContext.currentTime - 2.0) {
|
||||
outputChunkQueue.shift();
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- VISUALIZATION / DRAWING ROUTINES ---
|
||||
function drawWaveform(dataArray, canvas, strokeColor) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
// Dark transparent redraw for trace/motion-blur effect
|
||||
ctx.fillStyle = 'rgba(5, 7, 4, 0.4)';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.lineWidth = 2 * window.devicePixelRatio;
|
||||
ctx.strokeStyle = strokeColor;
|
||||
ctx.beginPath();
|
||||
|
||||
const sliceWidth = width / dataArray.length;
|
||||
let x = 0;
|
||||
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
// Center the wave around half-height and scale scale amplitude
|
||||
const v = dataArray[i] * 1.5;
|
||||
const y = (v * (height / 2)) + (height / 2);
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
x += sliceWidth;
|
||||
}
|
||||
|
||||
ctx.lineTo(width, height / 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw a subtle baseline center glowing path
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height / 2);
|
||||
ctx.lineTo(width, height / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function clearCanvas(canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#050704';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
// Apply initial UI layout on startup
|
||||
applyAudioRoutingUI();
|
||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,20 @@
|
||||
1:"$Sreact.fragment"
|
||||
2:I[39756,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
3:I[37457,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default"]
|
||||
4:I[47257,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ClientPageRoot"]
|
||||
5:I[52683,["/_next/static/chunks/0dbhjjzl8qfwv.js","/_next/static/chunks/01yjdu1pd4sew.js"],"default"]
|
||||
8:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"OutletBoundary"]
|
||||
9:"$Sreact.suspense"
|
||||
b:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"ViewportBoundary"]
|
||||
d:I[97367,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"MetadataBoundary"]
|
||||
f:I[68027,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"default",1]
|
||||
:HL["/_next/static/chunks/0~n~ndg8g34g9.css","style"]
|
||||
:HL["/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
:HL["/_next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
||||
0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0dbhjjzl8qfwv.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable h-full antialiased","children":["$","body",null,{"className":"min-h-full flex flex-col","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@6","$@7"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/01yjdu1pd4sew.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$","$9",null,{"name":"Next.MetadataOutlet","children":"$@a"}]}]]}],{},null,false,null]},null,false,null],["$","$1","h",{"children":[null,["$","$Lb",null,{"children":"$Lc"}],["$","div",null,{"hidden":true,"children":["$","$Ld",null,{"children":["$","$9",null,{"name":"Next.Metadata","children":"$Le"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$f",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0~n~ndg8g34g9.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"ncF6Dp_0OCMhTp1KUVnS4"}
|
||||
6:{}
|
||||
7:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
|
||||
c:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||
10:I[27201,["/_next/static/chunks/0dbhjjzl8qfwv.js"],"IconMark"]
|
||||
a:null
|
||||
e:[["$","title","0",{"children":"🎙️ ONNX VC - Real-Time AI Voice Changer"}],["$","meta","1",{"name":"description","content":"ONNX VC - Pengubah suara real-time berbasis AI berlatensi ultra-rendah dengan ONNX Runtime."}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0x3dzn~oxb6tn.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L10","3",{}]]
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,596 +0,0 @@
|
||||
/* ==========================================================================
|
||||
CSS GLOBAL TOKENS & RESET (LIME LIGHT THEME)
|
||||
========================================================================== */
|
||||
:root {
|
||||
--bg-dark: #0a0d08;
|
||||
--bg-card: rgba(15, 20, 13, 0.7);
|
||||
--border-color: rgba(163, 230, 53, 0.18);
|
||||
|
||||
--primary: #a3e635;
|
||||
--primary-glow: rgba(163, 230, 53, 0.4);
|
||||
--accent: #22d3ee;
|
||||
--accent-glow: rgba(34, 211, 238, 0.45);
|
||||
--emerald: #10b981;
|
||||
--rose: #f43f5e;
|
||||
|
||||
--text-main: #f8fafc;
|
||||
--text-muted: #94a3b8;
|
||||
--font-header: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
|
||||
--transition-smooth: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text-main);
|
||||
font-family: var(--font-body);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
DYNAMIC GLOWING BACKGROUND
|
||||
========================================================================== */
|
||||
.glow-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
background:
|
||||
radial-gradient(circle at 10% 20%, rgba(163, 230, 53, 0.08) 0%, transparent 40%),
|
||||
radial-gradient(circle at 90% 80%, rgba(34, 211, 238, 0.09) 0%, transparent 45%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
LAYOUT CONTAINER & CARDS
|
||||
========================================================================== */
|
||||
.dashboard-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.glassmorphism {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.glassmorphism:hover {
|
||||
border-color: rgba(163, 230, 53, 0.3);
|
||||
box-shadow: 0 10px 40px 0 rgba(163, 230, 53, 0.1);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1.75rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: var(--font-header);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.25rem;
|
||||
background: linear-gradient(135deg, #fff 0%, var(--text-muted) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
APP HEADER
|
||||
========================================================================== */
|
||||
.app-header {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logo-area h1 {
|
||||
font-family: var(--font-header);
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 0 0 40px rgba(163, 230, 53, 0.2);
|
||||
}
|
||||
|
||||
.pulse-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--rose);
|
||||
box-shadow: 0 0 10px var(--rose);
|
||||
}
|
||||
|
||||
.pulse-indicator.active {
|
||||
background-color: var(--emerald);
|
||||
box-shadow: 0 0 10px var(--emerald);
|
||||
animation: pulse 1.8s infinite;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 400;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
DASHBOARD GRID LAYOUT
|
||||
========================================================================== */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.col-span-2 {
|
||||
grid-column: span 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
INPUTS & CONTROLS
|
||||
========================================================================== */
|
||||
.control-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.control-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
width: 100%;
|
||||
padding: 0.8rem 1rem;
|
||||
background-color: rgba(14, 20, 13, 0.8);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-main);
|
||||
font-size: 0.9rem;
|
||||
font-family: var(--font-body);
|
||||
outline: none;
|
||||
transition: var(--transition-smooth);
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 1rem center;
|
||||
background-size: 1.2rem;
|
||||
}
|
||||
|
||||
.custom-select:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 8px var(--primary-glow);
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
background-color: rgba(14, 20, 13, 0.8);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-main);
|
||||
padding: 0.8rem 1rem;
|
||||
width: 100%;
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 8px var(--primary-glow);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
SLIDERS STYLING
|
||||
========================================================================== */
|
||||
.slider-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
font-family: var(--font-header);
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
text-shadow: 0 0 8px var(--accent-glow);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.custom-slider {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(163, 230, 53, 0.15);
|
||||
outline: none;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.custom-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 10px var(--primary-glow);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.custom-slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.slider-ticks {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
BUTTONS
|
||||
========================================================================== */
|
||||
.btn {
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-family: var(--font-header);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: var(--transition-smooth);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #65a30d 100%);
|
||||
color: #0c0f0a;
|
||||
box-shadow: 0 4px 14px 0 var(--primary-glow);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px 0 rgba(163, 230, 53, 0.6);
|
||||
}
|
||||
|
||||
.btn-accent {
|
||||
background: linear-gradient(135deg, var(--accent) 0%, #0891b2 100%);
|
||||
color: #0c0f0a;
|
||||
box-shadow: 0 4px 14px 0 var(--accent-glow);
|
||||
}
|
||||
|
||||
.btn-accent:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px 0 rgba(34, 211, 238, 0.65);
|
||||
}
|
||||
|
||||
.btn:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
CONNECTION BAR
|
||||
========================================================================== */
|
||||
.connection-bar {
|
||||
padding: 1rem 1.5rem !important;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-row .input-group {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.connection-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.status-badge::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-badge.connected {
|
||||
background-color: rgba(16, 185, 129, 0.15);
|
||||
color: var(--emerald);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.status-badge.connected::before {
|
||||
background-color: var(--emerald);
|
||||
box-shadow: 0 0 6px var(--emerald);
|
||||
}
|
||||
|
||||
.status-badge.disconnected {
|
||||
background-color: rgba(254, 63, 94, 0.15);
|
||||
color: var(--rose);
|
||||
border: 1px solid rgba(254, 63, 94, 0.3);
|
||||
}
|
||||
|
||||
.status-badge.disconnected::before {
|
||||
background-color: var(--rose);
|
||||
box-shadow: 0 0 6px var(--rose);
|
||||
}
|
||||
|
||||
.status-badge.connecting {
|
||||
background-color: rgba(34, 211, 238, 0.15);
|
||||
color: var(--accent);
|
||||
border: 1px solid rgba(34, 211, 238, 0.3);
|
||||
}
|
||||
|
||||
.status-badge.connecting::before {
|
||||
background-color: var(--accent);
|
||||
box-shadow: 0 0 6px var(--accent);
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.btn-group-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
MODERN RADIO TILES
|
||||
========================================================================== */
|
||||
.radio-group-modern {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.radio-tile {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.radio-tile input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tile-label {
|
||||
display: block;
|
||||
padding: 0.6rem;
|
||||
background-color: rgba(15, 20, 13, 0.5);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.radio-tile input:checked + .tile-label {
|
||||
background-color: rgba(163, 230, 53, 0.12);
|
||||
border-color: var(--primary);
|
||||
color: var(--text-main);
|
||||
box-shadow: 0 0 10px rgba(163, 230, 53, 0.2);
|
||||
}
|
||||
|
||||
.radio-tile:hover .tile-label {
|
||||
border-color: rgba(163, 230, 53, 0.4);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
OSCILLOSCOPE WAVEFORM CANVASES
|
||||
========================================================================== */
|
||||
.visualizer-row {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.visualizer-container {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.vis-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.input-dot {
|
||||
background-color: var(--primary);
|
||||
box-shadow: 0 0 6px var(--primary);
|
||||
}
|
||||
|
||||
.output-dot {
|
||||
background-color: var(--accent);
|
||||
box-shadow: 0 0 6px var(--accent);
|
||||
}
|
||||
|
||||
.waveform-canvas {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
background-color: #050704;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
PERFORMANCE HUD
|
||||
========================================================================== */
|
||||
.performance-hud {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.85rem 1.75rem !important;
|
||||
}
|
||||
|
||||
.hud-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.hud-label {
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hud-value {
|
||||
font-family: var(--font-header);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hud-separator {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.hud-value.text-accent {
|
||||
color: var(--accent);
|
||||
text-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
color: var(--emerald);
|
||||
text-shadow: 0 0 6px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.performance-hud {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.hud-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
KEYFRAME ANIMATIONS
|
||||
========================================================================== */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |