@@ -32,14 +32,20 @@ class App:
3232 Wraps FastAPI to provide a full-stack Python web framework experience.
3333 """
3434
35- def __init__ (self , title : str = "Violetear App" , favicon : str | None = None ):
35+ def __init__ (
36+ self ,
37+ title : str = "Violetear App" ,
38+ favicon : str | None = None ,
39+ fade_in : float = 0.1 ,
40+ ):
3641 self .title = title
3742 self .api = FastAPI (title = title )
3843
3944 if favicon is None :
4045 favicon = str (Path (__file__ ).parent / "icon.png" )
4146
4247 self .favicon = favicon
48+ self .fade_in = fade_in
4349
4450 # Standard path for browser favicon requests
4551 @self .api .get ("/favicon.ico" , include_in_schema = False )
@@ -290,18 +296,51 @@ def _generate_bundle(self) -> str:
290296
291297 def _inject_client_side (self , doc : Document ):
292298 """Injects Pyodide and the Bundle bootstrapper."""
293- # 1. Load Pyodide
299+
300+ if self .fade_in > 0 :
301+ print ("fadeout" )
302+ # 1. The "Cloak" Script
303+ # We inject this FIRST so it runs immediately before the body renders.
304+ # It creates a style that hides the body and disables clicking.
305+ cloak_script = dedent (
306+ """
307+ var cloak = document.createElement("style");
308+ cloak.id = "violetear-cloak";
309+ // opacity: 0 -> hides content visually
310+ // pointer-events: none -> prevents clicking on invisible buttons
311+ cloak.innerHTML = "body { opacity: 0; pointer-events: none; }";
312+ document.head.appendChild(cloak);
313+ """
314+ )
315+ doc .script (content = cloak_script )
316+
317+ # 2. Load Pyodide (from CDN)
294318 doc .script (src = "https://cdn.jsdelivr.net/pyodide/v0.29.0/full/pyodide.js" )
295319
296- # 2. Bootstrap Script
320+ # 3. Bootstrap Script
321+ # We update this to remove the cloak once hydration is complete.
297322 bootstrap = dedent (
298- """
299- async function main() {
323+ f"""
324+ async function main() {{
325+ // optional: You could inject a 'Loading...' spinner here if you wanted
326+
300327 let pyodide = await loadPyodide();
301328 let response = await fetch("/_violetear/bundle.py");
302329 let code = await response.text();
303330 await pyodide.runPythonAsync(code);
304- }
331+
332+ // --- Hydration Complete ---
333+
334+ // Find the cloak style
335+ let cloak = document.getElementById("violetear-cloak");
336+ if (cloak) {{
337+ // Update styles to fade in
338+ cloak.innerHTML = "body {{ opacity: 1; pointer-events: auto; transition: opacity { self .fade_in } s ease-in-out; }}";
339+
340+ // Clean up the style tag after the transition finishes
341+ setTimeout(() => cloak.remove(), { int (self .fade_in * 1000 )} );
342+ }}
343+ }}
305344 main();
306345 """
307346 )
0 commit comments