From 9f5e5085ec626d0deeda0b43ec5d7d73a7c42ce9 Mon Sep 17 00:00:00 2001 From: Maxi Date: Tue, 22 Sep 2026 19:02:07 +0200 Subject: [PATCH] Add Player Menu with Inventory drawer, house selection, and mobile-first header --- .gitignore | 0 app/__pycache__/main.cpython-313.pyc | Bin 11087 -> 15810 bytes app/main.py | 145 +++++- app/static/css/escape.css | 654 ++++++++++++++++++++++----- app/static/js/bottles.js | 4 + app/static/js/broom.js | 6 +- app/static/js/inventory.js | 225 +++++++++ app/templates/base.html | 180 +++++++- test_app.py | 44 +- 9 files changed, 1076 insertions(+), 182 deletions(-) mode change 100644 => 100755 .gitignore mode change 100755 => 100644 app/__pycache__/main.cpython-313.pyc create mode 100755 app/static/js/inventory.js diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc old mode 100755 new mode 100644 index 71f543c961f774f86d3ece3b01c2c659b90e3131..91b5932174d64f43854cc50c24c698390ecbddb5 GIT binary patch literal 15810 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 EA4j7J=>Px# delta 3761 zcmbtWO>7&-72eq;m;d^u^)GQrkrLOUDB0GZ?b?G8x)^lQ9@-v!2tb3r#D});4Jq0b zV#IBi+_!ILzW4Ucn|;hctS8ksFvRe@Tj*Z>RR#u7~re6gg6Zev= zwJv#wOj=u;aNb0HOYVhf_GxRQw|&W`bsQG8mL+*e)H-o=xNJ-P6;xLh%2S03R8UG4 z%3Fo%si1sSsO~CMZw2MALItW&!Ch2$&oC(kTaLfC3a0`tg0t)n?!l>5xV{Q*{~lal z6)sf4h4$e3t8o1lTzC&|pb8gWA?o1vIrDD_d&kk~z}1xCF8)hkA3MAx%06=(w`vmn zL``HJ&Q~lZ%^bEx1a{SR*eB2CW!;F!*7Q?U1hc%D(6?-PyP+FKGMkAe6RfdjMN}>9 zgPJkt7NI@>8JI6tta;IX1QZo}(rl(yVVT;&K%+fizht>md%5;UQ2-=*6Fqm@A?_XaeTRfxl|N|9)@S$z7|{2qdW*Y107><7n3aN zJ|o|2^W3cqvrpX3-QJ0A@>m`?N3+>eaP!Rry33DfW!t?}qpc5!wKrs#C>!RZp z^d%TQiLh^@_zs8->%h5eE|-GUF2%&go8&!faQ->gz*+Y4oIyEx%E~r$4g#5nn!;xEm&I2Q!9uqu~Z-9 zXo+>c`$fBAYsn~7M6^^(i!hX9AGL?rps&S_T*3?VA8f%lWk&+hh(^xJ>~lv4`-AVO zP0~zAB@0d@$Rp-nFgRNaf?t}fSu|@{+@-H_S=(kyR8y*JG@$|ZT6foG-eRoB<3K!l zOBy7Unrfv7ar_X9Ar!c+&=C})D0m&?1Sysx=cRI~*5bsdZd9iBT>xA}5N7Xz@RsmM z7Mk0x`+vOugZ&?dcA9z_@gL}Kw0q^Zrr(`=d+zGYT~p^HV6T=NC+`fubhl-8$1!)u zH20MugM%MVgeL}B)ZcB#7EjU$oAVrHxBb^li^_r>6Vhwr32^~}UywX|MARH%=}Ahg z!fcw6p7s+LmxW`JM_4AD`<%0o)V~}>2pW(y+-(d?_Pb=LyM_ZE)Cy;XQ-9D z(>Q4n-XoJ{(=`)l*C*7;R&vWsK)+>iPBuxm8b$2cM^JB)CR^F7L7xK`m#6n*iOqZZ z2w~g70ZG&(_RpXy!B)oVRj&kJ8XHzsvsJTcb{1C$+RaDcTaH~9govb-^mRed91&S_ zvR|m}#Kr!krfoI2K#Q*}xN*5QYq6!(Wh`2K#(K6H8vN26T0_JfF=-a=6UrxInIjRa z)~LC|<|j+3?E}lED$8cAW$|>`4rUUuXsuz>6ASS1RD07eM9f+ne+h6m1YXiRA*gn6 z-BIP*p>=+3P+fb1@_?mMW%;b2Ug5KaAgZF`L3w#lUI@eo?z;E5^M2DD*(ZJ8i6{AN z*8JaO@pP6}h*g*-vp;9qQ0L~atIDKz=Zv#C6se8Siu|0fsis3Qihcnc%08Bp*EG^spQ5+ zN*_3>FYB?~Q`mzcWrPD~b=#1D%90f$N z#1_Ig>o~P!&?bvwiMa>9ATMsa20s%UoHUMooTYTsrr4JU+k5!_^DN3%ZKXYFnT;Lt zIQRs(<8gNGkgt!Qx9X9^%V&ouUqE^RyuS~(@mr`vr13%n`{kkEv!4zfbl>X;Jaow3 znxY`PYYT$xsx8!QKOB0GkTknI;u)TH0}D+PV4+jTfrUW3e#!@(e>_e=zfGLe_0nyZ z1U+zlkr>)Z?BJuWYYiyaBec5E4k#PJcF;pBOE_4@k0pT_$ltsdDB|f z$Wh$SC~`7S34ha;K#yVv56JqhbT+Y`(vQ dict: if "escape" not in request.session: request.session["escape"] = { "session_id": str(uuid.uuid4()), + "house": "gryffindor", + "inventory": ["wand", "broom", "snape_scroll"], "broom_solved": False, "broom_score": 0, "broom_time": 0, @@ -51,8 +96,33 @@ def get_session_state(request: Request) -> dict: "completed_at": None, "lang": "de" } + else: + # Ensure new fields exist for existing sessions + state = request.session["escape"] + if "inventory" not in state: + state["inventory"] = ["wand", "broom", "snape_scroll"] + if state.get("broom_solved"): + state["inventory"].append("silver_key") + if state.get("bottles_solved"): + state["inventory"].append("philosophers_stone") + if "house" not in state: + state["house"] = "gryffindor" + request.session["escape"] = state return request.session["escape"] +def get_template_context(request: Request, state: dict, lang: str) -> dict: + inv_ids = state.get("inventory", ["wand", "broom", "snape_scroll"]) + inv_items = [ALL_ITEMS[i] for i in inv_ids if i in ALL_ITEMS] + house_info = HOUSES.get(state.get("house", "gryffindor"), HOUSES["gryffindor"]) + return { + "state": state, + "lang": lang, + "inventory_items": inv_items, + "all_items": ALL_ITEMS, + "house": house_info, + "houses": HOUSES + } + @app.get("/", response_class=HTMLResponse) async def page_index(request: Request): state = get_session_state(request) @@ -62,7 +132,7 @@ async def page_index(request: Request): return templates.TemplateResponse( request=request, name="index.html", - context={"state": state, "lang": lang} + context=get_template_context(request, state, lang) ) @app.get("/broom", response_class=HTMLResponse) @@ -74,7 +144,7 @@ async def page_broom(request: Request): return templates.TemplateResponse( request=request, name="broom.html", - context={"state": state, "lang": lang} + context=get_template_context(request, state, lang) ) @app.get("/bottles", response_class=HTMLResponse) @@ -86,7 +156,7 @@ async def page_bottles(request: Request): return templates.TemplateResponse( request=request, name="bottles.html", - context={"state": state, "lang": lang} + context=get_template_context(request, state, lang) ) @app.get("/sliding", response_class=HTMLResponse) @@ -98,7 +168,7 @@ async def page_sliding(request: Request): return templates.TemplateResponse( request=request, name="sliding.html", - context={"state": state, "lang": lang} + context=get_template_context(request, state, lang) ) @app.get("/finale", response_class=HTMLResponse) @@ -110,7 +180,7 @@ async def page_finale(request: Request): return templates.TemplateResponse( request=request, name="finale.html", - context={"state": state, "lang": lang} + context=get_template_context(request, state, lang) ) @app.get("/api/state") @@ -118,10 +188,41 @@ async def api_get_state(request: Request): state = get_session_state(request) return JSONResponse(state) +@app.get("/api/inventory") +async def api_get_inventory(request: Request): + state = get_session_state(request) + inv_ids = state.get("inventory", ["wand", "broom", "snape_scroll"]) + inv_items = [ALL_ITEMS[i] for i in inv_ids if i in ALL_ITEMS] + house_info = HOUSES.get(state.get("house", "gryffindor"), HOUSES["gryffindor"]) + return JSONResponse({ + "inventory": inv_items, + "house": house_info, + "score": state.get("broom_score", 0), + "broom_solved": state.get("broom_solved", False), + "bottles_solved": state.get("bottles_solved", False), + "sliding_solved": state.get("sliding_solved", False) + }) + +@app.post("/api/player/house") +async def api_set_house(request: Request): + try: + data = await request.json() + except Exception: + data = {} + house_id = data.get("house", "gryffindor").lower() + if house_id not in HOUSES: + raise HTTPException(status_code=400, detail="Ungültiges Hogwarts-Haus.") + state = get_session_state(request) + state["house"] = house_id + request.session["escape"] = state + return JSONResponse({"success": True, "house": HOUSES[house_id]}) + @app.post("/api/reset") async def api_reset_state(request: Request): request.session["escape"] = { "session_id": str(uuid.uuid4()), + "house": "gryffindor", + "inventory": ["wand", "broom", "snape_scroll"], "broom_solved": False, "broom_score": 0, "broom_time": 0, @@ -143,28 +244,27 @@ async def api_broom_complete(request: Request): state["broom_solved"] = True state["broom_score"] = int(data.get("score", 100)) state["broom_time"] = int(data.get("time_taken", 30)) + # Add Silver Key to Inventory! + if "inventory" not in state: + state["inventory"] = ["wand", "broom", "snape_scroll"] + if "silver_key" not in state["inventory"]: + state["inventory"].append("silver_key") request.session["escape"] = state return JSONResponse({ "success": True, "message": "Der Schlüssel passt ins Schloss! Die Tür zur Flammenkammer steht offen!", + "new_item": ALL_ITEMS["silver_key"], "state": state }) @app.post("/api/bottles/drink") async def api_bottles_drink(request: Request): - """ - Check the bottle the player decided to drink to pass through the flames. - Bottle 3 is Ahead (Black fire -> Success). - Bottle 7 is Back (Purple fire -> Escaped back). - Bottles 1, 4, 5 are Poison (Fizzle/Defeat). - Bottles 2, 6 are Nettle Wine (Harmless wine). - """ try: data = await request.json() except Exception: data = {} bottle_index = int(data.get("bottle", 0)) - target_gate = data.get("gate", "ahead") # 'ahead' (black fire) or 'back' (purple fire) + target_gate = data.get("gate", "ahead") lang = data.get("lang", "de") if bottle_index not in CLASSIC_SOLUTION: @@ -178,6 +278,10 @@ async def api_bottles_drink(request: Request): state["bottles_solved"] = True if state.get("broom_solved"): state["completed_at"] = datetime.datetime.utcnow().isoformat() + if "inventory" not in state: + state["inventory"] = ["wand", "broom", "snape_scroll"] + if "philosophers_stone" not in state["inventory"]: + state["inventory"].append("philosophers_stone") request.session["escape"] = state msg_de = "Ein eisiger Schauer durchströmt dich! Das schwarze Feuer weicht vor dir zurück. Du trittst siegreich in die Schatzkammer des Steins der Weisen!" msg_en = "An icy chill flows through you! The black flames retreat. You step victoriously into the chamber of the Philosopher's Stone!" @@ -186,6 +290,7 @@ async def api_bottles_drink(request: Request): "result": "success_ahead", "content": content, "message": msg_de if lang == "de" else msg_en, + "new_item": ALL_ITEMS["philosophers_stone"], "next_url": "/finale" }) else: diff --git a/app/static/css/escape.css b/app/static/css/escape.css index 716485b..7f87aa9 100755 --- a/app/static/css/escape.css +++ b/app/static/css/escape.css @@ -1,6 +1,6 @@ :root { --bg-dark: #0d0c13; - --bg-card: rgba(26, 24, 38, 0.92); + --bg-card: rgba(26, 24, 38, 0.94); --gold: #e5b948; --gold-glow: rgba(229, 185, 72, 0.4); --gold-hover: #f7d274; @@ -15,6 +15,7 @@ --wine-red: #9e2a2b; --text-light: #ece8e1; --text-dim: #a59fa8; + --house-accent: #d3a625; --font-gothic: 'MedievalSharp', cursive, serif; --font-script: 'Caveat', cursive, sans-serif; } @@ -39,7 +40,7 @@ body { flex-direction: column; } -/* Site Header */ +/* Site Header (Sleek & Mobile First) */ .site-header { background: rgba(13, 12, 19, 0.95); border-bottom: 2px solid var(--gold); @@ -53,55 +54,89 @@ body { .header-inner { max-width: 1200px; margin: 0 auto; - padding: 0.75rem 1.25rem; + padding: 0.6rem 1rem; display: flex; align-items: center; justify-content: space-between; - flex-wrap: wrap; - gap: 0.75rem; + gap: 0.5rem; } .brand-link { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.4rem; text-decoration: none; color: var(--gold); - font-size: 1.4rem; + font-size: 1.3rem; text-shadow: 0 0 10px var(--gold-glow); - transition: transform 0.2s; + white-space: nowrap; } -.brand-link:hover { - transform: scale(1.03); +.brand-sparkle { + font-size: 1.3rem; +} + +.brand-title { + font-weight: bold; } .header-right { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.6rem; } -.nav-links { +/* Player Bag Button */ +.btn-bag { + background: linear-gradient(135deg, #2b1f14, #4a3520); + color: var(--gold); + border: 2px solid var(--gold); + padding: 0.4rem 0.85rem; + border-radius: 20px; + cursor: pointer; + font-family: inherit; + font-size: 1rem; + font-weight: bold; display: flex; align-items: center; - gap: 1rem; + gap: 0.4rem; + transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); + user-select: none; } -.nav-link { - color: var(--text-light); - text-decoration: none; - font-size: 1rem; - padding: 0.35rem 0.75rem; - border-radius: 6px; - border: 1px solid transparent; - transition: all 0.2s; +.btn-bag:hover { + background: linear-gradient(135deg, #4a3520, #6e4f30); + transform: translateY(-2px) scale(1.04); + box-shadow: 0 4px 15px var(--gold-glow); + color: #fff; } -.nav-link:hover, .nav-link.active { - color: var(--gold); - border-color: var(--gold); - background: rgba(229, 185, 72, 0.1); +.bag-house-icon { + font-size: 1.15rem; +} + +.bag-label { + display: inline-block; +} + +.bag-badge { + background: #e63946; + color: #fff; + font-size: 0.75rem; + padding: 0.15rem 0.45rem; + border-radius: 10px; + font-weight: bold; + border: 1px solid #ffd700; +} + +.bag-badge.pulse { + animation: badgePulse 0.6s infinite alternate; +} + +@keyframes badgePulse { + 0% { transform: scale(1); filter: brightness(1); } + 100% { transform: scale(1.3); filter: brightness(1.4) drop-shadow(0 0 8px #ff0055); } } /* Control Buttons */ @@ -109,15 +144,22 @@ body { background: rgba(255, 255, 255, 0.08); color: var(--gold); border: 1px solid var(--gold); - padding: 0.4rem 0.8rem; - border-radius: 6px; + padding: 0.4rem 0.75rem; + border-radius: 8px; cursor: pointer; font-family: inherit; - font-size: 0.9rem; + font-size: 0.95rem; display: flex; align-items: center; gap: 0.4rem; transition: all 0.2s; + user-select: none; +} + +.btn-control.icon-only { + padding: 0.4rem 0.55rem; + font-size: 1.1rem; + border-radius: 50%; } .btn-control:hover { @@ -126,16 +168,371 @@ body { box-shadow: 0 0 12px var(--gold-glow); } -.lang-btn.active { - background: var(--gold); - color: var(--bg-dark); +/* Slide-Over Drawer & Backdrop */ +.drawer-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(4px); + z-index: 2000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.drawer-backdrop.open { + opacity: 1; + pointer-events: auto; +} + +.player-drawer { + position: fixed; + top: 0; + right: 0; + width: 400px; + max-width: 92vw; + height: 100vh; + background: var(--bg-dark); + background-image: + radial-gradient(circle at 10% 10%, rgba(229, 185, 72, 0.08) 0%, transparent 40%), + url('/static/img/parchment_texture.jpg'); + background-blend-mode: overlay; + border-left: 3px solid var(--gold); + box-shadow: -5px 0 35px rgba(0, 0, 0, 0.85); + z-index: 2001; + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1); +} + +.player-drawer.open { + transform: translateX(0); +} + +.drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.1rem 1.25rem; + border-bottom: 2px solid rgba(229, 185, 72, 0.3); + background: rgba(17, 15, 25, 0.95); +} + +.drawer-title { + font-size: 1.4rem; + color: var(--gold); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.drawer-close-btn { + background: none; + border: none; + color: var(--text-dim); + font-size: 1.5rem; + cursor: pointer; + transition: color 0.2s; + line-height: 1; +} + +.drawer-close-btn:hover { + color: var(--gold); +} + +.drawer-tabs { + display: flex; + border-bottom: 1px solid rgba(229, 185, 72, 0.25); + background: rgba(22, 19, 32, 0.9); +} + +.drawer-tab-btn { + flex: 1; + padding: 0.75rem 0.5rem; + background: none; + border: none; + color: var(--text-dim); + font-family: inherit; + font-size: 0.95rem; + cursor: pointer; + border-bottom: 3px solid transparent; + transition: all 0.2s; +} + +.drawer-tab-btn:hover { + color: var(--text-light); + background: rgba(229, 185, 72, 0.05); +} + +.drawer-tab-btn.active { + color: var(--gold); + border-bottom-color: var(--gold); font-weight: bold; + background: rgba(229, 185, 72, 0.1); +} + +.drawer-body { + flex: 1; + overflow-y: auto; + padding: 1.25rem; +} + +.drawer-tab-pane { + display: none; +} + +.drawer-tab-pane.active { + display: block; + animation: fadeIn 0.25s ease; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +.drawer-footer { + padding: 1rem 1.25rem; + border-top: 1px solid rgba(229, 185, 72, 0.25); + background: rgba(17, 15, 25, 0.95); +} + +/* Inventory Grid (Tab 1) */ +.inv-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + margin-bottom: 1.25rem; +} + +.inv-item-slot { + background: rgba(26, 23, 38, 0.85); + border: 2px solid rgba(229, 185, 72, 0.35); + border-radius: 10px; + padding: 0.85rem 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + transition: all 0.2s; + user-select: none; +} + +.inv-item-slot:hover { + border-color: var(--gold); + transform: translateY(-3px); + box-shadow: 0 4px 15px rgba(229, 185, 72, 0.25); +} + +.inv-item-slot.selected { + border-color: var(--gold); + background: rgba(229, 185, 72, 0.15); + box-shadow: 0 0 15px var(--gold-glow); +} + +.inv-item-icon { + font-size: 2.2rem; + margin-bottom: 0.35rem; +} + +.inv-item-name { + font-size: 0.85rem; + text-align: center; + color: var(--text-light); + font-weight: bold; +} + +/* Item Detail Card */ +.inv-detail-card { + background: rgba(18, 15, 26, 0.95); + border: 2px solid var(--gold); + border-radius: 12px; + padding: 1.1rem; + text-align: center; + box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.6); +} + +.inv-detail-icon { + font-size: 3rem; + margin-bottom: 0.4rem; +} + +.inv-detail-name { + font-size: 1.25rem; + color: var(--gold); + margin-bottom: 0.4rem; + font-weight: bold; +} + +.inv-detail-desc { + color: var(--text-dim); + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +/* House Picker (Tab 2) */ +.house-picker-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.6rem; + margin-bottom: 1.25rem; +} + +.house-select-btn { + background: rgba(22, 19, 32, 0.85); + border: 2px solid rgba(229, 185, 72, 0.3); + border-radius: 10px; + padding: 0.75rem 0.5rem; + color: var(--text-light); + font-family: inherit; + font-size: 0.95rem; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; + transition: all 0.2s; +} + +.house-select-btn:hover { + border-color: var(--gold); + transform: scale(1.03); +} + +.house-select-btn.active { + border-color: var(--gold); + background: rgba(229, 185, 72, 0.2); + box-shadow: 0 0 12px var(--gold-glow); + font-weight: bold; +} + +.house-icon { + font-size: 1.8rem; +} + +.profile-stats-card { + background: rgba(20, 18, 29, 0.9); + border: 1px solid rgba(229, 185, 72, 0.3); + border-radius: 10px; + padding: 0.9rem 1rem; +} + +.stat-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.45rem 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + font-size: 0.9rem; +} + +.stat-row:last-child { + border-bottom: none; +} + +/* Chambers Navigation (Tab 3) */ +.rooms-nav-list { + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.room-nav-item { + display: flex; + align-items: center; + gap: 0.85rem; + padding: 0.75rem 1rem; + background: rgba(25, 22, 36, 0.85); + border: 1px solid rgba(229, 185, 72, 0.3); + border-radius: 10px; + text-decoration: none; + color: var(--text-light); + transition: all 0.2s; +} + +.room-nav-item:hover, .room-nav-item.current { + border-color: var(--gold); + background: rgba(229, 185, 72, 0.15); + transform: translateX(4px); +} + +.room-nav-item.external { + border-color: #ffd700; + background: rgba(212, 175, 55, 0.1); +} + +.room-icon { + font-size: 1.8rem; +} + +.room-info { + display: flex; + flex-direction: column; +} + +.room-info strong { + font-size: 1rem; + color: var(--gold); +} + +.room-info small { + font-size: 0.8rem; + color: var(--text-dim); +} + +/* Floating Toast */ +.escape-toast { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%) translateY(30px); + background: rgba(18, 14, 26, 0.96); + border: 2px solid var(--gold); + border-radius: 12px; + padding: 0.8rem 1.4rem; + color: var(--text-light); + font-size: 1rem; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.8), 0 0 20px var(--gold-glow); + z-index: 9999; + opacity: 0; + pointer-events: none; + transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + text-align: center; + max-width: 90vw; +} + +.escape-toast.show { + opacity: 1; + transform: translateX(-50%) translateY(0); +} + +/* Lumos Effect Overlay */ +.lumos-light { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 500; + background: radial-gradient(circle at 50% 50%, rgba(255, 248, 220, 0.18) 0%, transparent 60%); + opacity: 0; + transition: opacity 0.5s ease; +} + +.lumos-light.active { + opacity: 1; } /* Container */ .container { max-width: 1100px; - margin: 1.5rem auto 3rem auto; + margin: 1.2rem auto 2.5rem auto; padding: 0 1rem; flex: 1; width: 100%; @@ -147,26 +544,26 @@ body { color: var(--parchment-ink); border: 4px double var(--parchment-border); border-radius: 12px; - padding: 2rem; + padding: 1.5rem 1.25rem; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.7), inset 0 0 40px rgba(139, 109, 59, 0.25); position: relative; - margin-bottom: 2rem; + margin-bottom: 1.5rem; } .parchment-title { - font-size: 2rem; + font-size: 1.8rem; text-align: center; color: #5c1818; - margin-bottom: 1rem; + margin-bottom: 0.75rem; text-shadow: 1px 1px 0px rgba(255,255,255,0.7); } .riddle-poem { font-family: var(--font-script); - font-size: 1.5rem; - line-height: 1.8; + font-size: 1.35rem; + line-height: 1.7; text-align: center; - margin: 1.5rem 0; + margin: 1rem 0; white-space: pre-line; font-weight: bold; } @@ -178,22 +575,22 @@ body { border-radius: 12px; padding: 1.5rem; box-shadow: 0 8px 24px rgba(0,0,0,0.5); - margin-bottom: 2rem; + margin-bottom: 1.5rem; } /* Chamber Overview Grid */ .chamber-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; - margin-top: 1.5rem; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.25rem; + margin-top: 1.25rem; } .chamber-card { background: rgba(20, 18, 30, 0.9); border: 2px solid rgba(229, 185, 72, 0.3); border-radius: 12px; - padding: 1.5rem; + padding: 1.25rem; display: flex; flex-direction: column; justify-content: space-between; @@ -203,20 +600,20 @@ body { } .chamber-card:hover { - transform: translateY(-5px); + transform: translateY(-4px); border-color: var(--gold); box-shadow: 0 10px 30px var(--gold-glow); } .chamber-badge { position: absolute; - top: 1rem; - right: 1rem; + top: 0.8rem; + right: 0.8rem; background: rgba(229, 185, 72, 0.2); border: 1px solid var(--gold); color: var(--gold); - font-size: 0.8rem; - padding: 0.2rem 0.6rem; + font-size: 0.75rem; + padding: 0.2rem 0.5rem; border-radius: 20px; } @@ -227,23 +624,23 @@ body { } .chamber-icon { - font-size: 3.5rem; - margin-bottom: 1rem; + font-size: 3rem; + margin-bottom: 0.75rem; text-align: center; } .chamber-title { - font-size: 1.5rem; + font-size: 1.35rem; color: var(--gold); - margin-bottom: 0.75rem; + margin-bottom: 0.6rem; text-align: center; } .chamber-desc { color: var(--text-dim); - font-size: 0.95rem; - line-height: 1.6; - margin-bottom: 1.25rem; + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 1rem; text-align: center; } @@ -254,8 +651,8 @@ body { color: #1a1505; text-decoration: none; font-weight: bold; - font-size: 1.1rem; - padding: 0.8rem 1.5rem; + font-size: 1.05rem; + padding: 0.75rem 1.25rem; border-radius: 8px; border: none; cursor: pointer; @@ -271,13 +668,13 @@ body { color: #000; } -/* Flaming Gates (Bottles Riddle) */ +/* Flaming Gates */ .flame-chamber-arena { position: relative; background: radial-gradient(circle at center, #191629 0%, #0c0a14 100%); border: 2px solid #4a3b63; border-radius: 16px; - padding: 2rem 1.5rem; + padding: 1.5rem 1rem; box-shadow: inset 0 0 80px rgba(0,0,0,0.9), 0 10px 40px rgba(0,0,0,0.8); overflow: hidden; } @@ -285,13 +682,14 @@ body { .flame-wall-container { display: flex; justify-content: space-between; - margin-bottom: 2rem; + margin-bottom: 1.5rem; position: relative; + gap: 0.75rem; } .flame-gate { - width: 48%; - padding: 1.5rem 1rem; + flex: 1; + padding: 1.2rem 0.75rem; border-radius: 12px; text-align: center; position: relative; @@ -301,17 +699,17 @@ body { .flame-gate.purple { background: rgba(75, 15, 110, 0.35); border: 2px solid var(--purple-fire); - box-shadow: 0 0 35px var(--purple-fire-glow); + box-shadow: 0 0 25px var(--purple-fire-glow); } .flame-gate.black { background: rgba(18, 12, 28, 0.8); border: 2px solid #5a287a; - box-shadow: 0 0 35px var(--black-fire-glow); + box-shadow: 0 0 25px var(--black-fire-glow); } .flame-icon-animated { - font-size: 3rem; + font-size: 2.5rem; animation: flameFlicker 1.5s infinite alternate; } @@ -323,8 +721,8 @@ body { /* 7 Bottles Table */ .bottles-table-wrapper { - margin: 2rem 0; - padding: 1.5rem 1rem; + margin: 1.5rem 0; + padding: 1.25rem 0.75rem; background: linear-gradient(180deg, #2a201b 0%, #17110e 100%); border: 3px solid #6b5338; border-radius: 14px; @@ -335,63 +733,63 @@ body { display: flex; justify-content: space-around; align-items: flex-end; - gap: 0.75rem; + gap: 0.5rem; flex-wrap: nowrap; overflow-x: auto; - padding: 1rem 0; + padding: 0.5rem 0; + -webkit-overflow-scrolling: touch; } .bottle-slot { display: flex; flex-direction: column; align-items: center; - min-width: 95px; + min-width: 85px; cursor: pointer; transition: transform 0.2s; user-select: none; } .bottle-slot:hover { - transform: translateY(-8px); + transform: translateY(-6px); } .bottle-slot.selected { - transform: translateY(-12px); + transform: translateY(-10px); filter: drop-shadow(0 0 15px var(--gold)); } .bottle-pedestal { - width: 80px; + width: 75px; height: 18px; background: linear-gradient(180deg, #574635 0%, #2f251c 100%); border: 2px solid #856a52; border-radius: 6px; - margin-top: 0.5rem; + margin-top: 0.4rem; display: flex; align-items: center; justify-content: center; - font-size: 0.8rem; + font-size: 0.75rem; color: var(--gold); font-weight: bold; } -/* SVG Bottles */ .bottle-svg { - filter: drop-shadow(0 8px 12px rgba(0,0,0,0.6)); + filter: drop-shadow(0 6px 10px rgba(0,0,0,0.6)); transition: filter 0.3s; } -/* Bottle tags */ .bottle-tag-select { - margin-top: 0.4rem; + margin-top: 0.35rem; background: rgba(13, 12, 19, 0.9); border: 1px solid var(--gold); color: var(--text-light); font-family: inherit; font-size: 0.75rem; - padding: 0.2rem; + padding: 0.15rem; border-radius: 4px; cursor: pointer; + width: 80px; } /* Canvas Broom Game */ @@ -410,7 +808,7 @@ body { #broomCanvas { display: block; width: 100%; - height: 520px; + height: 480px; cursor: crosshair; } @@ -418,18 +816,20 @@ body { display: flex; justify-content: space-between; align-items: center; - padding: 0.75rem 1.25rem; + padding: 0.6rem 1rem; background: rgba(13, 12, 19, 0.9); border-bottom: 2px solid var(--gold); - font-size: 1.1rem; + font-size: 1rem; color: var(--gold); + flex-wrap: wrap; + gap: 0.5rem; } .mobile-controls { display: none; margin-top: 1rem; justify-content: center; - gap: 1rem; + gap: 0.8rem; } @media (max-width: 768px) { @@ -438,13 +838,18 @@ body { } .flame-wall-container { flex-direction: column; - gap: 1rem; - } - .flame-gate { - width: 100%; } #broomCanvas { - height: 380px; + height: 360px; + } + .brand-title { + font-size: 1.15rem; + } + .bag-label { + display: none; /* Icon and badge only on small screens for ultra compact header */ + } + .btn-bag { + padding: 0.35rem 0.6rem; } } @@ -475,9 +880,9 @@ body { background: var(--bg-card); border: 2px solid var(--gold); border-radius: 14px; - max-width: 550px; + max-width: 520px; width: 100%; - padding: 2rem; + padding: 1.75rem; text-align: center; box-shadow: 0 0 50px var(--gold-glow); transform: scale(0.9); @@ -489,57 +894,38 @@ body { } .modal-icon { - font-size: 4rem; - margin-bottom: 1rem; + font-size: 3.5rem; + margin-bottom: 0.8rem; } .modal-title { - font-size: 1.8rem; + font-size: 1.6rem; color: var(--gold); - margin-bottom: 1rem; + margin-bottom: 0.8rem; } .modal-text { color: var(--text-light); - font-size: 1.1rem; + font-size: 1.05rem; line-height: 1.6; - margin-bottom: 1.5rem; -} - -/* Footer */ -.footer { - background: rgba(10, 9, 15, 0.95); - border-top: 1px solid rgba(229, 185, 72, 0.3); - text-align: center; - padding: 1.5rem 1rem; - color: var(--text-dim); - font-size: 0.9rem; -} - -.footer a { - color: var(--gold); - text-decoration: none; -} - -.footer a:hover { - text-decoration: underline; + margin-bottom: 1.25rem; } /* Sliding Puzzle (Schieberätsel) */ .sliding-board-container { - max-width: 440px; - margin: 1.5rem auto; + max-width: 380px; + margin: 1.25rem auto; background: #171421; border: 3px solid var(--gold); border-radius: 14px; - padding: 1.25rem; + padding: 1rem; box-shadow: 0 0 30px rgba(0,0,0,0.8), inset 0 0 30px rgba(0,0,0,0.6); } .sliding-grid { display: grid; grid-template-columns: repeat(3, 1fr); - grid-gap: 10px; + grid-gap: 8px; width: 100%; aspect-ratio: 1 / 1; } @@ -556,7 +942,7 @@ body { user-select: none; transition: transform 0.15s, background 0.2s; box-shadow: 0 4px 8px rgba(0,0,0,0.5); - padding: 0.5rem; + padding: 0.4rem; } .sliding-tile:hover { @@ -572,19 +958,37 @@ body { } .tile-icon { - font-size: 2.2rem; + font-size: 2rem; } .tile-num { - font-size: 0.85rem; + font-size: 0.8rem; color: var(--gold); font-weight: bold; } .tile-name { - font-size: 0.75rem; + font-size: 0.7rem; color: var(--text-dim); text-align: center; margin-top: 2px; } +/* Footer */ +.footer { + background: rgba(10, 9, 15, 0.95); + border-top: 1px solid rgba(229, 185, 72, 0.3); + text-align: center; + padding: 1.25rem 1rem; + color: var(--text-dim); + font-size: 0.85rem; +} + +.footer a { + color: var(--gold); + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} diff --git a/app/static/js/bottles.js b/app/static/js/bottles.js index 959eed6..ebb7a07 100755 --- a/app/static/js/bottles.js +++ b/app/static/js/bottles.js @@ -124,6 +124,9 @@ if (data.result === 'success_ahead') { if (window.soundFX) window.soundFX.playMagicSuccess(); + if (window.notifyNewItem && data.new_item) { + window.notifyNewItem(data.new_item); + } modalIcon.innerText = "❄️⚡"; modalTitle.innerText = "Hervorragend kombiniert!"; modalTitle.style.color = "#ffd700"; @@ -133,6 +136,7 @@ modalBtn.innerText = "Zum Finale & Escape-Triumph 🏆"; modalBtn.onclick = () => { window.location.href = data.next_url; }; modalBackdrop.classList.add('show'); + } else if (data.result === 'success_back') { if (window.soundFX) window.soundFX.playMagicSuccess(); modalIcon.innerText = "🟣"; diff --git a/app/static/js/broom.js b/app/static/js/broom.js index 168d359..9be927b 100755 --- a/app/static/js/broom.js +++ b/app/static/js/broom.js @@ -207,11 +207,15 @@ score += 500; if (scoreVal) scoreVal.innerText = score; if (window.soundFX) window.soundFX.playKeyChime(); + if (window.notifyNewItem) { + window.notifyNewItem({ icon: "🗝️", name: "Geknickter Silberschlüssel" }); + } if (statusMsg) { - statusMsg.innerHTML = "✨ Schlüssel gefangen! Fliege jetzt zur schweren Holztür rechts und öffne das Schloss!"; + statusMsg.innerHTML = "✨ Schlüssel gefangen & im Beutel! Fliege zur Holztür rechts und öffne das Schloss!"; statusMsg.style.color = "#2ecc71"; } } + } else { // Master key follows player masterKey.x = player.x + 10; diff --git a/app/static/js/inventory.js b/app/static/js/inventory.js new file mode 100755 index 0000000..11ab456 --- /dev/null +++ b/app/static/js/inventory.js @@ -0,0 +1,225 @@ +// Player Menu & Inventory (Zauberer-Beutel & Profil) +(function() { + const drawer = document.getElementById('playerDrawer'); + const drawerBackdrop = document.getElementById('drawerBackdrop'); + const bagBtn = document.getElementById('openBagBtn'); + const closeBtn = document.getElementById('closeDrawerBtn'); + const bagBadge = document.getElementById('bagBadge'); + const bagHouseIcon = document.getElementById('bagHouseIcon'); + + // Tabs + const tabBtns = document.querySelectorAll('.drawer-tab-btn'); + const tabPanes = document.querySelectorAll('.drawer-tab-pane'); + + // Item Detail Card + const itemDetailBox = document.getElementById('itemDetailBox'); + const itemDetailIcon = document.getElementById('itemDetailIcon'); + const itemDetailName = document.getElementById('itemDetailName'); + const itemDetailDesc = document.getElementById('itemDetailDesc'); + const itemDetailActionBtn = document.getElementById('itemDetailActionBtn'); + + // House selection buttons + const houseBtns = document.querySelectorAll('.house-select-btn'); + + let currentItem = null; + let lumosActive = false; + + window.togglePlayerDrawer = function() { + if (!drawer) return; + const isOpen = drawer.classList.contains('open'); + if (isOpen) { + closePlayerDrawer(); + } else { + openPlayerDrawer(); + } + }; + + window.openPlayerDrawer = function(tabName) { + if (!drawer) return; + drawer.classList.add('open'); + if (drawerBackdrop) drawerBackdrop.classList.add('open'); + document.body.style.overflow = 'hidden'; + if (window.soundFX) window.soundFX.playStoneSlide(); + if (tabName) switchTab(tabName); + }; + + window.closePlayerDrawer = function() { + if (!drawer) return; + drawer.classList.remove('open'); + if (drawerBackdrop) drawerBackdrop.classList.remove('open'); + document.body.style.overflow = ''; + if (window.soundFX) window.soundFX.playStoneSlide(); + }; + + if (bagBtn) bagBtn.addEventListener('click', window.togglePlayerDrawer); + if (closeBtn) closeBtn.addEventListener('click', window.closePlayerDrawer); + if (drawerBackdrop) drawerBackdrop.addEventListener('click', window.closePlayerDrawer); + + window.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && drawer && drawer.classList.contains('open')) { + window.closePlayerDrawer(); + } + }); + + // Tab Switching + tabBtns.forEach(btn => { + btn.addEventListener('click', () => { + const target = btn.dataset.tab; + switchTab(target); + }); + }); + + function switchTab(tabId) { + tabBtns.forEach(b => b.classList.toggle('active', b.dataset.tab === tabId)); + tabPanes.forEach(p => p.classList.toggle('active', p.id === tabId)); + if (window.soundFX) window.soundFX.playBottleClink(); + } + + // Item Selection in Bag + const itemSlots = document.querySelectorAll('.inv-item-slot'); + itemSlots.forEach(slot => { + slot.addEventListener('click', () => { + selectItem(slot); + }); + }); + + function selectItem(slot) { + itemSlots.forEach(s => s.classList.remove('selected')); + slot.classList.add('selected'); + + const itemId = slot.dataset.itemId; + const itemName = slot.dataset.itemName; + const itemIcon = slot.dataset.itemIcon; + const itemDesc = slot.dataset.itemDesc; + const actionLabel = slot.dataset.actionLabel; + const action = slot.dataset.action; + + currentItem = { id: itemId, name: itemName, icon: itemIcon, action: action }; + + if (itemDetailBox) { + itemDetailBox.style.display = 'block'; + itemDetailIcon.innerText = itemIcon; + itemDetailName.innerText = itemName; + itemDetailDesc.innerText = itemDesc; + itemDetailActionBtn.innerText = actionLabel || "Benutzen ✨"; + itemDetailActionBtn.onclick = () => performItemAction(action); + } + + if (window.soundFX) window.soundFX.playBottleClink(); + } + + function performItemAction(action) { + if (action === 'lumos') { + lumosActive = !lumosActive; + let glow = document.getElementById('lumosLightOverlay'); + if (!glow) { + glow = document.createElement('div'); + glow.id = 'lumosLightOverlay'; + glow.className = 'lumos-light'; + document.body.appendChild(glow); + } + glow.classList.toggle('active', lumosActive); + itemDetailActionBtn.innerText = lumosActive ? "Nox / Erlöschen 🌑" : "Lumos wirken 💡"; + if (window.soundFX) window.soundFX.playKeyChime(); + showFloatingToast(lumosActive ? "💡 Lumos! Dein Zauberstab erleuchtet die Kammer." : "🌑 Nox! Das Zauberlicht erlischt."); + } else if (action === 'read_scroll') { + // Open scroll modal + window.closePlayerDrawer(); + const modalBackdrop = document.getElementById('modalBackdrop'); + const modalTitle = document.getElementById('modalTitle'); + const modalText = document.getElementById('modalText'); + const modalBtn = document.getElementById('modalBtn'); + const modalIcon = document.getElementById('modalIcon'); + if (modalBackdrop) { + modalIcon.innerText = "📜"; + modalTitle.innerText = "Snapes Logik-Rätsel"; + modalText.innerHTML = ` +
+ "Gefahr liegt vor euch, die Rettung zurück,
+ Zwei von uns helfen euch, habt ihr das Glück,
+ Eine von sieben führt euch hindurch,
+ Eine bringt den, der sie trinkt, sicher zurück,
+ Zwei von ihnen enthalten nur Brennnesselwein,
+ Drei sind tödlich, schenkt ihr sie ein.
+ Wählt oder bleibt auf ewig hier stehen,
+ Vier Hinweise helfen euch, alles zu sehen..."


+ 1. Links neben jedem Wein steht immer Gift.
+ 2. Die Enden sind verschieden, keines führt nach vorn.
+ 3. Weder Zwerg noch Riese birgt Gift.
+ 4. Zweite von links & zweite von rechts schmecken gleich! +
+ `; + modalBtn.innerText = "Verstanden ➔"; + modalBtn.onclick = () => modalBackdrop.classList.remove('show'); + modalBackdrop.classList.add('show'); + } + } else if (action === 'broom_polish') { + if (window.soundFX) window.soundFX.playWhoosh(); + showFloatingToast("🧹 Der Nimbus 2000 glänzt wie neu. Die Zweige sind perfekt gestutzt!"); + } else if (action === 'examine_key') { + if (window.soundFX) window.soundFX.playKeyChime(); + showFloatingToast("🗝️ Der alte Silberschlüssel ist schwer und trägt Spuren vergangener Kämpfe."); + } else if (action === 'feel_power') { + if (window.soundFX) window.soundFX.playMagicSuccess(); + showFloatingToast("💎 Eine unendliche Wärme durchströmt deine Finger. Der Stein schützt die Welt!"); + } + } + + // House selection + houseBtns.forEach(btn => { + btn.addEventListener('click', async () => { + const house = btn.dataset.house; + try { + const res = await fetch('/api/player/house', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ house: house }) + }); + const data = await res.json(); + if (data.success) { + houseBtns.forEach(b => b.classList.toggle('active', b.dataset.house === house)); + if (bagHouseIcon) bagHouseIcon.innerText = data.house.icon; + document.documentElement.style.setProperty('--house-accent', data.house.accent); + if (window.soundFX) window.soundFX.playMagicSuccess(); + showFloatingToast(`Du gehörst nun stolz zum Haus ${data.house.name} ${data.house.icon}!`); + } + } catch (e) { + console.error(e); + } + }); + }); + + // Toast Notification + window.showFloatingToast = function(msg) { + let toast = document.getElementById('floatingEscapeToast'); + if (!toast) { + toast = document.createElement('div'); + toast.id = 'floatingEscapeToast'; + toast.className = 'escape-toast'; + document.body.appendChild(toast); + } + toast.innerHTML = msg; + toast.classList.add('show'); + clearTimeout(toast._timeout); + toast._timeout = setTimeout(() => { + toast.classList.remove('show'); + }, 3500); + }; + + window.notifyNewItem = function(item) { + if (!item) return; + if (bagBadge) { + let count = parseInt(bagBadge.innerText, 10) || 0; + bagBadge.innerText = count + 1; + bagBadge.classList.add('pulse'); + setTimeout(() => bagBadge.classList.remove('pulse'), 2000); + } + if (window.soundFX) window.soundFX.playMagicSuccess(); + window.showFloatingToast(`🎒 Neuer Gegenstand im Beutel: ${item.icon} ${item.name}!`); + }; + + // Auto-select first item if available + if (itemSlots.length > 0) { + selectItem(itemSlots[0]); + } +})(); diff --git a/app/templates/base.html b/app/templates/base.html index 8bbc2c2..e036f43 100755 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -8,28 +8,28 @@ - + {% block head %}{% endblock %} +