diff --git a/app.py b/app.py
index b5118af..f478686 100644
--- a/app.py
+++ b/app.py
@@ -25,6 +25,9 @@ BMAC = "https://buymeacoffee.com/r26xrthzttg"
SITE = "https://dark0rbits.thetempleofdoom.com"
def _migrate(con):
+ cols = [r[1] for r in con.execute("PRAGMA table_info(settings)")]
+ if not cols:
+ con.execute("CREATE TABLE IF NOT EXISTS settings(user_id INTEGER, key TEXT, val TEXT, PRIMARY KEY(user_id,key))")
cols = [r[1] for r in con.execute("PRAGMA table_info(sms_rentals)")]
if "user_id" not in cols:
con.execute("ALTER TABLE sms_rentals ADD COLUMN user_id INTEGER DEFAULT 0")
@@ -53,6 +56,47 @@ def db():
_migrate(con)
return con
+
+# ---------- USER SETTINGS (visible tunables, agent-settable) ----------
+DEFAULT_SETTINGS = {
+ "bg": "1", "warp": "1", "parallax": "1", "density": "1.0", "speed": "1.0", "twinkle": "1.0",
+ "hue": "0", "grid": "1", "scan": "1", "toast": "1", "type": "1",
+}
+BOOL_SETTINGS = {"bg", "warp", "parallax", "grid", "scan", "toast", "type"}
+RANGE_SETTINGS = {"density": (0, 2.5), "speed": (0, 3), "twinkle": (0, 3), "hue": (-180, 180)}
+
+
+def get_settings(uid):
+ out = dict(DEFAULT_SETTINGS)
+ if not uid:
+ return out
+ con = db()
+ try:
+ for r in con.execute("SELECT key,val FROM settings WHERE user_id=?", (uid,)):
+ if r["key"] in out:
+ out[r["key"]] = r["val"]
+ except Exception:
+ pass
+ return out
+
+
+def set_setting(uid, key, val):
+ if key not in DEFAULT_SETTINGS:
+ return False
+ if key in BOOL_SETTINGS:
+ val = "1" if str(val) in ("1", "true", "on", "yes") else "0"
+ elif key in RANGE_SETTINGS:
+ try:
+ lo, hi = RANGE_SETTINGS[key]
+ val = str(max(lo, min(hi, float(val))))
+ except Exception:
+ return False
+ con = db()
+ con.execute("INSERT INTO settings(user_id,key,val) VALUES(?,?,?) ON CONFLICT(user_id,key) DO UPDATE SET val=excluded.val", (uid, key, str(val)))
+ con.commit()
+ return True
+
+
# ---------- BILLING CORE (per-call metering for outside users) ----------
def get_balance(uid):
con = db()
@@ -194,6 +238,13 @@ header{position:sticky;top:0;z-index:40;background:rgba(7,10,19,.86);backdrop-fi
.dnav a{color:var(--dim);text-decoration:none;font-size:.72rem;padding:.3rem .55rem;border:1px solid var(--line);border-radius:999px;white-space:nowrap;transition:.15s}
.dnav a.on,.dnav a:hover{color:var(--acc);border-color:var(--acc)}
.tchip{color:var(--fg);margin-right:.5rem;white-space:nowrap}
+.srow{display:flex;align-items:center;gap:.5rem;margin:.45rem 0;font-size:.85rem;color:var(--fg)}
+.srow input[type=range]{flex:1;accent-color:var(--acc)}
+.srow input[type=checkbox]{accent-color:var(--acc);width:16px;height:16px}
+#pinp{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:.6rem;color:var(--fg)}
+.pitem{display:block;padding:.5rem .6rem;border:1px solid transparent;border-radius:8px;color:var(--fg);text-decoration:none;cursor:pointer}
+.pitem:hover,.pitem.sel{border-color:var(--acc);color:var(--acc)}
+.pitem small{display:block;color:var(--dim)}
:focus-visible{outline:2px solid var(--acc2);outline-offset:2px;border-radius:4px}
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--acc);color:#0d0722;padding:.5rem 1rem;border-radius:0 0 8px 0;font-weight:800}
.skip:focus{left:0}
@@ -292,6 +343,30 @@ li{text-align:left;margin:.2rem 0}