Add Player Menu with Inventory drawer, house selection, and mobile-first header
This commit is contained in:
Regular → Executable
Executable → Regular
BIN
Binary file not shown.
+125
-20
@@ -21,13 +21,6 @@ app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), na
|
||||
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
|
||||
|
||||
# Standard Classic Riddle Solution (Book Accurate):
|
||||
# 1: Poison
|
||||
# 2: Wine
|
||||
# 3: Ahead (Smallest / Dwarf - passes through black fire)
|
||||
# 4: Poison
|
||||
# 5: Poison
|
||||
# 6: Wine
|
||||
# 7: Back (Largest / Giant - passes through purple fire)
|
||||
CLASSIC_SOLUTION = {
|
||||
1: "poison",
|
||||
2: "wine",
|
||||
@@ -38,10 +31,62 @@ CLASSIC_SOLUTION = {
|
||||
7: "back"
|
||||
}
|
||||
|
||||
ALL_ITEMS = {
|
||||
"wand": {
|
||||
"id": "wand",
|
||||
"name": "Zauberstab",
|
||||
"icon": "🪄",
|
||||
"desc": "Stechpalme und Phönixfeder, 11 Zoll. Liegt warm in deiner Hand.",
|
||||
"action": "lumos",
|
||||
"action_label": "Lumos / Licht 💡"
|
||||
},
|
||||
"broom": {
|
||||
"id": "broom",
|
||||
"name": "Nimbus 2000",
|
||||
"icon": "🧹",
|
||||
"desc": "Ein pfeilschneller Rennbesen. Perfekt balanciert für Wendemanöver im Schlüsselraum.",
|
||||
"action": "broom_polish",
|
||||
"action_label": "Besen polieren ✨"
|
||||
},
|
||||
"snape_scroll": {
|
||||
"id": "snape_scroll",
|
||||
"name": "Snapes Logik-Rolle",
|
||||
"icon": "📜",
|
||||
"desc": "Ein vergilbtes Pergament mit Snapes Vierzeilern zur Flammenkammer.",
|
||||
"action": "read_scroll",
|
||||
"action_label": "Rätsel-Verse lesen 📖"
|
||||
},
|
||||
"silver_key": {
|
||||
"id": "silver_key",
|
||||
"name": "Geknickter Silberschlüssel",
|
||||
"icon": "🗝️",
|
||||
"desc": "Der schwere antike Schlüssel aus der Schlüsselkammer. Sein geknickter Flügel verrät ihn.",
|
||||
"action": "examine_key",
|
||||
"action_label": "Schloss prüfen 🔍"
|
||||
},
|
||||
"philosophers_stone": {
|
||||
"id": "philosophers_stone",
|
||||
"name": "Stein der Weisen",
|
||||
"icon": "💎",
|
||||
"desc": "Der legendäre blutrote Alchemistenstein. Er schenkt unsterbliches Leben und pure Weisheit!",
|
||||
"action": "feel_power",
|
||||
"action_label": "Macht spüren ⚡"
|
||||
}
|
||||
}
|
||||
|
||||
HOUSES = {
|
||||
"gryffindor": {"id": "gryffindor", "name": "Gryffindor", "icon": "🦁", "color": "#740001", "accent": "#d3a625"},
|
||||
"slytherin": {"id": "slytherin", "name": "Slytherin", "icon": "🐍", "color": "#1a472a", "accent": "#aaaaaa"},
|
||||
"ravenclaw": {"id": "ravenclaw", "name": "Ravenclaw", "icon": "🦅", "color": "#0e1a40", "accent": "#946b2d"},
|
||||
"hufflepuff": {"id": "hufflepuff", "name": "Hufflepuff", "icon": "🦡", "color": "#ecb939", "accent": "#372e29"}
|
||||
}
|
||||
|
||||
def get_session_state(request: Request) -> 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:
|
||||
|
||||
+529
-125
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = "🟣";
|
||||
|
||||
@@ -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 = "✨ <strong>Schlüssel gefangen!</strong> Fliege jetzt zur schweren Holztür rechts und öffne das Schloss!";
|
||||
statusMsg.innerHTML = "✨ <strong>Schlüssel gefangen & im Beutel!</strong> Fliege zur Holztür rechts und öffne das Schloss!";
|
||||
statusMsg.style.color = "#2ecc71";
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Master key follows player
|
||||
masterKey.x = player.x + 10;
|
||||
|
||||
Executable
+225
@@ -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 = `
|
||||
<div style="font-family: 'Caveat', cursive; font-size: 1.35rem; line-height: 1.7; text-align: left; max-height: 50vh; overflow-y: auto; padding: 0.5rem 1rem;">
|
||||
<em>"Gefahr liegt vor euch, die Rettung zurück,<br>
|
||||
Zwei von uns helfen euch, habt ihr das Glück,<br>
|
||||
Eine von sieben führt euch hindurch,<br>
|
||||
Eine bringt den, der sie trinkt, sicher zurück,<br>
|
||||
Zwei von ihnen enthalten nur Brennnesselwein,<br>
|
||||
Drei sind tödlich, schenkt ihr sie ein.<br>
|
||||
Wählt oder bleibt auf ewig hier stehen,<br>
|
||||
Vier Hinweise helfen euch, alles zu sehen..."</em><br><br>
|
||||
<strong>1.</strong> Links neben jedem Wein steht immer Gift.<br>
|
||||
<strong>2.</strong> Die Enden sind verschieden, keines führt nach vorn.<br>
|
||||
<strong>3.</strong> Weder Zwerg noch Riese birgt Gift.<br>
|
||||
<strong>4.</strong> Zweite von links & zweite von rechts schmecken gleich!
|
||||
</div>
|
||||
`;
|
||||
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(`🎒 <strong>Neuer Gegenstand im Beutel:</strong> ${item.icon} ${item.name}!`);
|
||||
};
|
||||
|
||||
// Auto-select first item if available
|
||||
if (itemSlots.length > 0) {
|
||||
selectItem(itemSlots[0]);
|
||||
}
|
||||
})();
|
||||
+163
-17
@@ -8,28 +8,28 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;700&family=MedievalSharp&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/escape.css?v=1.0">
|
||||
<link rel="stylesheet" href="/static/css/escape.css?v=2.0">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Minimalist & Mobile-Friendly Header -->
|
||||
<header class="site-header">
|
||||
<div class="header-inner">
|
||||
<a href="/" class="brand-link">
|
||||
<span>🏰⚡</span>
|
||||
<span class="brand-sparkle">🏰⚡</span>
|
||||
<span class="brand-title">Hogwarts Escape</span>
|
||||
</a>
|
||||
|
||||
<nav class="nav-links">
|
||||
<a href="/" class="nav-link {% if request.url.path == '/' %}active{% endif %}">🚪 Kammern</a>
|
||||
<a href="/broom" class="nav-link {% if request.url.path == '/broom' %}active{% endif %}">🧹 Besenflug</a>
|
||||
<a href="/bottles" class="nav-link {% if request.url.path == '/bottles' %}active{% endif %}">🧪 Snapes Flaschen</a>
|
||||
<a href="/sliding" class="nav-link {% if request.url.path == '/sliding' %}active{% endif %}">🧩 Schieberätsel</a>
|
||||
<a href="https://quiz.dev.waiswuaschd.de" class="nav-link" target="_blank" rel="noopener">📜 Hogwarts-Quiz ↗</a>
|
||||
</nav>
|
||||
|
||||
<div class="header-right">
|
||||
<button type="button" id="audioToggleBtn" class="btn-control" onclick="toggleSound()">
|
||||
🔊 <span class="audio-lbl">Ton: An</span>
|
||||
<button type="button" id="audioToggleBtn" class="btn-control icon-only" onclick="toggleSound()" title="Ton an/aus">
|
||||
🔊
|
||||
</button>
|
||||
|
||||
<!-- Player Menu & Bag Button -->
|
||||
<button type="button" id="openBagBtn" class="btn-bag" title="Zauberbeutel & Profil öffnen">
|
||||
<span id="bagHouseIcon" class="bag-house-icon">{{ house.icon }}</span>
|
||||
<span class="bag-label">Beutel</span>
|
||||
<span id="bagBadge" class="bag-badge">{{ inventory_items | length }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,18 +49,164 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slide-Over Player Drawer & Inventory -->
|
||||
<div id="drawerBackdrop" class="drawer-backdrop"></div>
|
||||
<div id="playerDrawer" class="player-drawer">
|
||||
<div class="drawer-header">
|
||||
<div class="drawer-title">
|
||||
<span>🎒</span> Zauberer-Cockpit
|
||||
</div>
|
||||
<button type="button" id="closeDrawerBtn" class="drawer-close-btn" aria-label="Schließen">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Tabs inside Drawer -->
|
||||
<div class="drawer-tabs">
|
||||
<button type="button" class="drawer-tab-btn active" data-tab="backpackTab">
|
||||
🎒 Beutel
|
||||
</button>
|
||||
<button type="button" class="drawer-tab-btn" data-tab="profileTab">
|
||||
🧙♂️ Profil
|
||||
</button>
|
||||
<button type="button" class="drawer-tab-btn" data-tab="roomsTab">
|
||||
🗺️ Kammern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="drawer-body">
|
||||
<!-- TAB 1: INVENTORY (BEUTEL) -->
|
||||
<div id="backpackTab" class="drawer-tab-pane active">
|
||||
<div class="inv-grid">
|
||||
{% for item in inventory_items %}
|
||||
<div class="inv-item-slot"
|
||||
data-item-id="{{ item.id }}"
|
||||
data-item-name="{{ item.name }}"
|
||||
data-item-icon="{{ item.icon }}"
|
||||
data-item-desc="{{ item.desc }}"
|
||||
data-action="{{ item.action }}"
|
||||
data-action-label="{{ item.action_label }}">
|
||||
<div class="inv-item-icon">{{ item.icon }}</div>
|
||||
<div class="inv-item-name">{{ item.name }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Item Detail & Interaction Box -->
|
||||
<div id="itemDetailBox" class="inv-detail-card" style="display: none;">
|
||||
<div id="itemDetailIcon" class="inv-detail-icon">🪄</div>
|
||||
<div id="itemDetailName" class="inv-detail-name">Gegenstand</div>
|
||||
<p id="itemDetailDesc" class="inv-detail-desc">Beschreibung des Gegenstands...</p>
|
||||
<button type="button" id="itemDetailActionBtn" class="btn-magical" style="padding: 0.6rem 1.2rem; font-size: 0.95rem; width: 100%;">
|
||||
Benutzen ✨
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 2: PROFILE & HOUSE (HAUS) -->
|
||||
<div id="profileTab" class="drawer-tab-pane">
|
||||
<h4 style="color: var(--gold); margin-bottom: 0.75rem; text-align: center;">Wähle dein Hogwarts-Haus:</h4>
|
||||
<div class="house-picker-grid">
|
||||
{% for hid, h in houses.items() %}
|
||||
<button type="button" class="house-select-btn {% if state.house == hid %}active{% endif %}" data-house="{{ hid }}">
|
||||
<span class="house-icon">{{ h.icon }}</span>
|
||||
<span class="house-name">{{ h.name }}</span>
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="profile-stats-card">
|
||||
<div class="stat-row">
|
||||
<span>⚡ Gesammelte Punkte:</span>
|
||||
<strong style="color: var(--gold);">{{ state.broom_score }} Pkt</strong>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>🧹 Besenflug:</span>
|
||||
<span>{% if state.broom_solved %}✅ Gemeistert{% else %}⏳ Offen{% endif %}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>🧪 Snapes Flammen:</span>
|
||||
<span>{% if state.bottles_solved %}✅ Gelöst{% else %}⏳ Offen{% endif %}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>🧩 Alchemie-Schieberätsel:</span>
|
||||
<span>{% if state.sliding_solved %}✅ Gelöst{% else %}⏳ Frei spielbar{% endif %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 3: CHAMBERS MAP (KAMMERN) -->
|
||||
<div id="roomsTab" class="drawer-tab-pane">
|
||||
<div class="rooms-nav-list">
|
||||
<a href="/" class="room-nav-item {% if request.url.path == '/' %}current{% endif %}">
|
||||
<span class="room-icon">🚪</span>
|
||||
<div class="room-info">
|
||||
<strong>Vorhalle & Übersicht</strong>
|
||||
<small>Die Pforten zu den Verliesen</small>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/broom" class="room-nav-item {% if request.url.path == '/broom' %}current{% endif %}">
|
||||
<span class="room-icon">🧹</span>
|
||||
<div class="room-info">
|
||||
<strong>Kammer I: Der Besenflug</strong>
|
||||
<small>{% if state.broom_solved %}✅ Gemeistert{% else %}🗝️ Geflügelte Schlüssel jagen{% endif %}</small>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/bottles" class="room-nav-item {% if request.url.path == '/bottles' %}current{% endif %}">
|
||||
<span class="room-icon">🧪</span>
|
||||
<div class="room-info">
|
||||
<strong>Kammer II: Snapes Flaschen</strong>
|
||||
<small>{% if state.bottles_solved %}✅ Gemeistert{% else %}🔥 Die Flammenkammer{% endif %}</small>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/sliding" class="room-nav-item {% if request.url.path == '/sliding' %}current{% endif %}">
|
||||
<span class="room-icon">🧩</span>
|
||||
<div class="room-info">
|
||||
<strong>Alchemie-Schieberätsel</strong>
|
||||
<small>{% if state.sliding_solved %}✅ Gelöst{% else %}⚗️ Runen-Schiebepuzzle{% endif %}</small>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/finale" class="room-nav-item {% if request.url.path == '/finale' %}current{% endif %}">
|
||||
<span class="room-icon">🏆</span>
|
||||
<div class="room-info">
|
||||
<strong>Das Finale</strong>
|
||||
<small>Der Stein der Weisen</small>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://quiz.dev.waiswuaschd.de" class="room-nav-item external" target="_blank" rel="noopener">
|
||||
<span class="room-icon">📜</span>
|
||||
<div class="room-info">
|
||||
<strong>Hogwarts-Quiz Portal ↗</strong>
|
||||
<small>Große Wissensprüfung</small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-footer">
|
||||
<button type="button" class="btn-control" onclick="resetEscapeProgress()" style="width: 100%; justify-content: center;">
|
||||
🔄 Fortschritt zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>⚡ <strong>Hogwarts Escape Adventure</strong> · Teil des Magischen Zauberer-Kosmos ⚡</p>
|
||||
<p style="margin-top: 0.5rem;">
|
||||
<a href="/">Startseite</a> ·
|
||||
<a href="/broom">Der Besenflug</a> ·
|
||||
<a href="/bottles">Snapes Flaschenrätsel</a> ·
|
||||
<a href="/sliding">Alchemie-Schieberätsel</a> ·
|
||||
<a href="javascript:void(0)" onclick="openPlayerDrawer('backpackTab')">🎒 Zauberbeutel öffnen</a> ·
|
||||
<a href="https://quiz.dev.waiswuaschd.de" target="_blank" rel="noopener">Hogwarts Quiz Portal</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/audio.js?v=1.0"></script>
|
||||
<script src="/static/js/audio.js?v=2.0"></script>
|
||||
<script src="/static/js/inventory.js?v=2.0"></script>
|
||||
<script>
|
||||
async function resetEscapeProgress() {
|
||||
if (confirm("Möchtest du deinen Escape-Fortschritt und dein Inventar wirklich zurücksetzen?")) {
|
||||
await fetch('/api/reset', { method: 'POST' });
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+25
-19
@@ -9,6 +9,7 @@ def test_endpoints():
|
||||
res = client.get("/")
|
||||
assert res.status_code == 200, f"Index failed: {res.status_code}"
|
||||
assert "Die Kammern des Steins der Weisen" in res.text
|
||||
assert "Zauberer-Cockpit" in res.text
|
||||
|
||||
res = client.get("/broom")
|
||||
assert res.status_code == 200
|
||||
@@ -26,38 +27,43 @@ def test_endpoints():
|
||||
assert res.status_code == 200
|
||||
assert "ESCAPE GEMEISTERT" in res.text
|
||||
|
||||
# Test API Broom complete
|
||||
# Test Inventory API
|
||||
res = client.get("/api/inventory")
|
||||
assert res.status_code == 200
|
||||
inv_data = res.json()
|
||||
assert "inventory" in inv_data
|
||||
assert len(inv_data["inventory"]) >= 3
|
||||
item_ids = [item["id"] for item in inv_data["inventory"]]
|
||||
assert "wand" in item_ids
|
||||
assert "broom" in item_ids
|
||||
assert "snape_scroll" in item_ids
|
||||
|
||||
# Test House API
|
||||
res = client.post("/api/player/house", json={"house": "slytherin"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["house"]["name"] == "Slytherin"
|
||||
|
||||
# Test API Broom complete (adds silver_key)
|
||||
res = client.post("/api/broom/complete", json={"score": 500, "time_taken": 25})
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert data["success"] is True
|
||||
assert "silver_key" in data["state"]["inventory"]
|
||||
|
||||
# Test API Bottles drink: Poison (Bottle 1)
|
||||
res = client.post("/api/bottles/drink", json={"bottle": 1, "gate": "ahead"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["result"] == "poison"
|
||||
|
||||
# Test API Bottles drink: Wine (Bottle 2)
|
||||
res = client.post("/api/bottles/drink", json={"bottle": 2, "gate": "ahead"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["result"] == "wine"
|
||||
|
||||
# Test API Bottles drink: Back (Bottle 7)
|
||||
res = client.post("/api/bottles/drink", json={"bottle": 7, "gate": "back"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["result"] == "success_back"
|
||||
|
||||
# Test API Bottles drink: Ahead / Black fire (Bottle 3 - Dwarf)
|
||||
# Test API Bottles drink: Ahead / Black fire (Bottle 3 - Dwarf, adds philosophers_stone)
|
||||
res = client.post("/api/bottles/drink", json={"bottle": 3, "gate": "ahead"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["result"] == "success_ahead"
|
||||
drink_data = res.json()
|
||||
assert drink_data["result"] == "success_ahead"
|
||||
assert "new_item" in drink_data
|
||||
assert drink_data["new_item"]["id"] == "philosophers_stone"
|
||||
|
||||
# Test API Reset
|
||||
res = client.post("/api/reset")
|
||||
assert res.status_code == 200
|
||||
assert res.json()["success"] is True
|
||||
|
||||
print("ALL TESTS PASSED SUCCESSFULLY! 🧙♂️✨")
|
||||
print("ALL INVENTORY & GAMEPLAY TESTS PASSED! 🎒✨")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_endpoints()
|
||||
|
||||
Reference in New Issue
Block a user