oddadmix commited on
Commit
4d5f58a
ยท
verified ยท
1 Parent(s): fce71d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -201
app.py CHANGED
@@ -136,13 +136,6 @@ def run_model(user_input: str, system_prompt: str) -> str:
136
 
137
 
138
  def play_level(user_input: str, level_idx: int, unlocked_state: list):
139
- """
140
- ุงู„ุฏุงู„ุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ู„ุนุจุฉ:
141
- 1. ุชูุญุต ArabGuard
142
- 2. ู„ูˆ ู…ุญุธูˆุฑ โ†’ ุฑุณุงู„ุฉ ุญุธุฑ
143
- 3. ู„ูˆ ุขู…ู† โ†’ ุชุดุบูŠู„ ุงู„ู†ู…ูˆุฐุฌ
144
- 4. ุชุญู‚ู‚ ู…ู† ูˆุฌูˆุฏ ูƒู„ู…ุฉ ุงู„ู„ูˆุฑ ููŠ ุงู„ุฑุฏ โ†’ ุชูƒุดู ุงู„ู‚ุตุฉ
145
- """
146
  lv = LEVELS[level_idx]
147
  unlocked = list(unlocked_state)
148
 
@@ -200,275 +193,285 @@ def play_level(user_input: str, level_idx: int, unlocked_state: list):
200
 
201
 
202
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
203
- # ู…ุณุงุนุฏุงุช HTML
204
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
205
  def build_progress_html(unlocked: list) -> str:
206
  pips = ""
207
  for i in range(5):
208
  if i in unlocked:
209
- color = "#3A7BD5"
210
- glow = "box-shadow:0 0 6px #3A7BD5;"
211
  else:
212
- color = "rgba(100,160,255,0.12)"
213
- glow = ""
214
- pips += f'<div style="width:52px;height:6px;border-radius:3px;background:{color};{glow}border:0.5px solid rgba(100,160,255,0.25);transition:all .4s;"></div>'
215
-
 
216
  count = len(unlocked)
217
  pct = int(count / 5 * 100)
218
- bar = f'<div style="height:4px;background:#060B14;border-radius:3px;margin-top:10px;overflow:hidden;"><div style="height:100%;width:{pct}%;background:linear-gradient(90deg,#1A4A8A,#5A9BF5);border-radius:3px;transition:width .5s;"></div></div>'
219
-
 
 
 
 
220
  return (
221
- f'<div style="display:flex;gap:6px;justify-content:center;">{pips}</div>'
222
  f'{bar}'
223
- f'<p style="text-align:center;font-size:12px;color:#3A5A8A;margin-top:6px;letter-spacing:1px;">'
224
- f'ุงู„ู…ุณุชูˆูŠุงุช ุงู„ู…ุฎุชุฑู‚ุฉ: <span style="color:#5A9BF5;">{count}</span> / 5'
 
225
  f'</p>'
226
  )
227
 
228
 
229
  def level_info_html(level_idx: int) -> str:
230
  lv = LEVELS[level_idx]
231
- return f"""
232
- <div style="direction:rtl;font-family:'Tajawal',sans-serif;padding:4px 0;">
233
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
234
- <span style="background:rgba(58,123,213,0.18);border:0.5px solid #3A7BD5;color:#7BBFFF;
235
- font-size:11px;letter-spacing:1.5px;padding:3px 10px;border-radius:4px;">
236
- {lv['badge']}
237
- </span>
238
- <span style="font-size:11px;color:#3A5A8A;letter-spacing:1px;">{lv['attack_type']}</span>
239
- </div>
240
- <div style="font-size:18px;font-weight:700;color:#A8C8FF;margin-bottom:6px;">{lv['title']}</div>
241
- <div style="font-size:14px;color:#5A7AAA;line-height:1.7;">{lv['desc']}</div>
242
- </div>"""
 
243
 
244
 
245
  def hint_html(level_idx: int) -> str:
246
  lv = LEVELS[level_idx]
247
- return f"""
248
- <div style="direction:rtl;font-family:'Tajawal',sans-serif;font-size:13px;
249
- color:#4A7AAA;padding:10px 14px;
250
- border-right:2px solid rgba(58,123,213,0.35);
251
- background:rgba(5,15,30,0.6);border-radius:0 6px 6px 0;
252
- line-height:1.7;">
253
- {lv['hint']}
254
- </div>"""
255
 
256
 
257
  def system_prompt_html(level_idx: int) -> str:
258
  lv = LEVELS[level_idx]
259
- # Show only the guard instruction, not the embedded lore trigger
260
  guard_part = lv['system_prompt'].split("ูุงูƒุดู")[0].strip()
261
- return f"""
262
- <div style="direction:rtl;font-family:'Tajawal',sans-serif;font-size:13px;
263
- color:#3A6AAA;padding:10px 14px;
264
- border-right:2px solid #3A7BD5;
265
- background:rgba(5,15,30,0.8);border-radius:0 6px 6px 0;
266
- line-height:1.7;">
267
- <div style="font-size:10px;letter-spacing:1.5px;color:#2A5A9A;margin-bottom:4px;direction:ltr;">
268
- SYSTEM PROMPT (ุงู„ุญุงุฑุณ ูŠุนุฑู ู‡ุฐุง)
269
- </div>
270
- {guard_part}...
271
- </div>"""
272
 
273
 
274
  def status_html(status: str) -> str:
275
- styles = {
276
- "BLOCKED": ("๐Ÿ›ก๏ธ ArabGuard: ู…ุญุธูˆุฑ", "#E87070", "rgba(40,5,5,0.7)", "rgba(220,80,80,0.3)"),
277
- "UNLOCKED": ("โœ“ ุชู… ุงู„ุงุฎุชุฑุงู‚ โ€” ุงู„ู„ูˆุฑ ู…ูƒุดูˆู!", "#70D890", "rgba(5,30,15,0.7)", "rgba(80,200,120,0.3)"),
278
- "SAFE": ("โœ“ ุขู…ู† โ€” ู„ูƒู† ู„ู… ุชููƒุดู ุงู„ุณุฑูŠุฉ", "#7BBFFF", "rgba(5,15,35,0.7)", "rgba(58,123,213,0.2)"),
279
- "ERROR": ("โš ๏ธ ุฎุทุฃ", "#FFAA44", "rgba(30,20,5,0.7)", "rgba(200,130,30,0.3)"),
280
- "WAITING": ("โ‹ฏ ููŠ ุงู„ุงู†ุชุธุงุฑ", "#3A5A8A", "rgba(5,10,25,0.7)", "rgba(30,50,100,0.2)"),
281
  }
282
- label, color, bg, border = styles.get(status, styles["WAITING"])
283
- return f"""
284
- <div style="font-size:13px;color:{color};background:{bg};
285
- border:0.5px solid {border};border-radius:6px;
286
- padding:8px 14px;text-align:center;
287
- font-family:'Tajawal',sans-serif;letter-spacing:0.5px;">
288
- {label}
289
- </div>"""
290
 
291
 
292
  def lore_html(lore_text: str) -> str:
293
  if not lore_text:
294
  return ""
295
- return f"""
296
- <div style="direction:rtl;font-family:'Tajawal',sans-serif;
297
- background:rgba(5,30,15,0.9);border:0.5px solid rgba(80,200,120,0.3);
298
- border-radius:8px;padding:16px 18px;animation:fadeIn 0.6s ease;">
299
- <div style="font-size:10px;letter-spacing:2px;color:#40A860;
300
- margin-bottom:10px;font-family:monospace;">โ—ˆ ู…ุนู„ูˆู…ุฉ ุณุฑูŠุฉ ู…ูƒุดูˆูุฉ โ—ˆ</div>
301
- <div style="font-size:15px;color:#90EAB0;line-height:1.9;">{lore_text}</div>
302
- </div>"""
 
303
 
304
 
305
  def victory_html() -> str:
306
  chapters = "".join([
307
- f'<div style="margin-bottom:18px;">'
308
- f'<div style="font-size:11px;letter-spacing:2px;color:#2A6A40;margin-bottom:6px;">'
 
309
  f'CHAPTER {lv["num"]} โ€” {lv["title"]}</div>'
310
- f'<div style="font-size:14px;color:#80D8A0;line-height:1.9;">{lv["lore"]}</div>'
311
  f'</div>'
312
  for lv in LEVELS
313
  ])
314
- return f"""
315
- <div style="direction:rtl;font-family:'Tajawal',sans-serif;
316
- background:rgba(5,25,15,0.95);border:0.5px solid rgba(80,200,120,0.25);
317
- border-radius:12px;padding:24px 26px;">
318
- <div style="text-align:center;margin-bottom:20px;">
319
- <div style="font-size:24px;font-weight:700;color:#90EAB0;">ู…ุจุฑูˆูƒ โ€” ุงู„ุญู‚ูŠู‚ุฉ ุงู„ูƒุงู…ู„ุฉ</div>
320
- <div style="font-size:12px;letter-spacing:2px;color:#40A860;margin-top:4px;">
321
- ALL 5 LEVELS BREACHED โ€” ARABGUARD DEFEATED
322
- </div>
323
- </div>
324
- {chapters}
325
- </div>"""
326
 
327
 
328
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
329
- # CSS ู…ุฎุตุต ู„ู„ุชุทุจูŠู‚
330
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
331
  CUSTOM_CSS = """
332
- @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700&family=Rajdhani:wght@400;600;700&display=swap');
333
 
334
  body, .gradio-container {
335
- background: #060B14 !important;
336
  font-family: 'Rajdhani', sans-serif !important;
337
  }
338
 
339
- /* Stars background */
340
- .gradio-container::before {
341
- content: '';
342
- position: fixed;
343
- inset: 0;
344
- background-image:
345
- radial-gradient(1px 1px at 15% 25%, rgba(180,200,255,0.4) 0%, transparent 100%),
346
- radial-gradient(1px 1px at 45% 10%, rgba(180,200,255,0.3) 0%, transparent 100%),
347
- radial-gradient(1.2px 1.2px at 70% 60%, rgba(180,200,255,0.35) 0%, transparent 100%),
348
- radial-gradient(0.8px 0.8px at 30% 80%, rgba(180,200,255,0.25) 0%, transparent 100%),
349
- radial-gradient(1px 1px at 85% 35%, rgba(180,200,255,0.3) 0%, transparent 100%),
350
- radial-gradient(0.9px 0.9px at 55% 90%, rgba(180,200,255,0.2) 0%, transparent 100%),
351
- radial-gradient(1.1px 1.1px at 10% 55%, rgba(180,200,255,0.25) 0%, transparent 100%),
352
- radial-gradient(0.8px 0.8px at 92% 75%, rgba(180,200,255,0.2) 0%, transparent 100%);
353
- pointer-events: none;
354
- z-index: 0;
355
- }
356
-
357
- /* Title */
358
  .game-title-block {
359
  text-align: center;
360
  padding: 28px 0 18px;
361
- border-bottom: 0.5px solid rgba(100,160,255,0.1);
362
  margin-bottom: 20px;
363
  }
364
  .game-title-block h1 {
365
  font-family: 'Rajdhani', sans-serif !important;
366
- font-size: 28px !important;
367
  font-weight: 700 !important;
368
- color: #7BBFFF !important;
369
  letter-spacing: 4px !important;
370
  text-transform: uppercase !important;
371
  margin: 0 !important;
372
  }
373
  .game-title-block p {
374
  font-family: 'Tajawal', sans-serif !important;
375
- font-size: 14px !important;
376
- color: #3A5A8A !important;
377
- margin: 6px 0 0 !important;
378
- letter-spacing: 1px !important;
379
  }
380
 
381
- /* Panels */
382
- .gr-panel, .gr-box, .gr-form {
383
- background: rgba(8,18,38,0.85) !important;
384
- border: 0.5px solid rgba(100,160,255,0.15) !important;
385
  border-radius: 10px !important;
386
  }
387
 
388
- /* Inputs */
389
  textarea, input[type="text"] {
390
- background: rgba(4,12,28,0.95) !important;
391
- border: 0.5px solid rgba(100,160,255,0.22) !important;
392
  border-radius: 6px !important;
393
- color: #C8D8F0 !important;
394
  font-family: 'Tajawal', sans-serif !important;
395
- font-size: 14px !important;
396
  direction: rtl !important;
397
  }
398
  textarea:focus, input[type="text"]:focus {
399
- border-color: rgba(100,160,255,0.45) !important;
400
- box-shadow: none !important;
 
401
  }
402
  textarea::placeholder, input::placeholder {
403
- color: #2A4A7A !important;
 
 
 
 
 
404
  }
405
 
406
- /* Buttons */
407
- button.primary {
408
- background: rgba(58,123,213,0.15) !important;
409
- border: 0.5px solid #3A7BD5 !important;
410
- color: #7BBFFF !important;
411
  font-family: 'Rajdhani', sans-serif !important;
412
- font-size: 13px !important;
413
  font-weight: 600 !important;
414
  letter-spacing: 1.5px !important;
415
  text-transform: uppercase !important;
416
  border-radius: 6px !important;
417
  transition: all 0.2s !important;
418
  }
419
- button.primary:hover {
420
- background: rgba(58,123,213,0.3) !important;
 
421
  }
422
 
423
- button.secondary {
424
- background: transparent !important;
425
- border: 0.5px solid rgba(100,160,255,0.2) !important;
426
- color: #3A6AAA !important;
427
  font-family: 'Rajdhani', sans-serif !important;
428
- font-size: 12px !important;
429
  letter-spacing: 1px !important;
430
  text-transform: uppercase !important;
431
  border-radius: 4px !important;
 
432
  }
433
- button.secondary:hover {
434
- border-color: rgba(100,160,255,0.4) !important;
435
- color: #6A9ACA !important;
 
436
  }
437
 
438
- /* Labels */
439
- label span {
440
- color: #4A6A9A !important;
441
  font-family: 'Rajdhani', sans-serif !important;
442
- font-size: 11px !important;
443
  letter-spacing: 1.5px !important;
444
  text-transform: uppercase !important;
445
  }
446
 
447
- /* Tabs */
448
- .tab-nav button {
449
  font-family: 'Tajawal', sans-serif !important;
450
- color: #4A6A9A !important;
 
 
451
  border-bottom: 2px solid transparent !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  }
453
- .tab-nav button.selected {
454
- color: #7BBFFF !important;
455
- border-bottom: 2px solid #3A7BD5 !important;
 
 
456
  }
457
 
458
- /* Slider */
459
- input[type="range"] {
460
- accent-color: #3A7BD5 !important;
461
  }
462
 
463
- /* HTML outputs */
464
- .output-html { background: transparent !important; border: none !important; }
 
 
 
465
 
466
- /* Scrollbars */
467
- ::-webkit-scrollbar { width: 4px; }
468
- ::-webkit-scrollbar-track { background: #060B14; }
469
- ::-webkit-scrollbar-thumb { background: #1A3A6A; border-radius: 2px; }
470
 
471
- /* Animations */
472
  @keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} }
473
  """
474
 
@@ -477,11 +480,9 @@ input[type="range"] {
477
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
478
  with gr.Blocks(css=CUSTOM_CSS, title="ArabGuard Breach โ€” ุงุฎุชุฑุงู‚ ุงู„ุญุงุฑุณ ุงู„ุนุฑุจูŠ") as demo:
479
 
480
- # โ”€โ”€ State โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
481
- unlocked_state = gr.State([]) # list of unlocked level indices
482
- current_level = gr.State(0) # current level index
483
 
484
- # โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
485
  gr.HTML("""
486
  <div class="game-title-block">
487
  <h1>โ—ˆ ArabGuard Breach โ—ˆ</h1>
@@ -489,63 +490,48 @@ with gr.Blocks(css=CUSTOM_CSS, title="ArabGuard Breach โ€” ุงุฎุชุฑุงู‚ ุงู„ุญุง
489
  </div>
490
  """)
491
 
492
- # โ”€โ”€ Progress bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€๏ฟฝ๏ฟฝโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
493
- progress_display = gr.HTML(value=build_progress_html([]), elem_id="progress")
494
 
495
- # โ”€โ”€ Level tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
496
- with gr.Tabs() as tabs:
497
  for i, lv in enumerate(LEVELS):
498
  with gr.TabItem(f"{lv['badge']} โ€” {lv['title']}"):
499
 
500
- # Level info
501
  gr.HTML(value=level_info_html(i))
502
 
503
- # System prompt reveal
504
- with gr.Accordion("๐Ÿ‘ ุนุฑุถ System Prompt ุงู„ุญุงุฑุณ", open=False):
505
  gr.HTML(value=system_prompt_html(i))
506
 
507
- # Hint
508
- with gr.Accordion("๐Ÿ’ก ุชู„ู…ูŠุญ", open=False):
509
  gr.HTML(value=hint_html(i))
510
 
511
- # User input
512
  user_input = gr.Textbox(
513
  label="ุฑุณุงู„ุชูƒ ู„ู„ุญุงุฑุณ",
514
  placeholder="ุงูƒุชุจ ุฑุณุงู„ุชูƒ ุจุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ ู‡ู†ุง...",
515
  lines=3,
516
- elem_id=f"input_lvl_{i}",
517
  )
518
 
519
- # Buttons row
520
  with gr.Row():
521
- send_btn = gr.Button("ุฅุฑุณุงู„ โ†", variant="primary", scale=3)
522
- clear_btn = gr.Button("ู…ุณุญ", variant="secondary", scale=1)
523
 
524
- # Status badge
525
- status_out = gr.HTML(value=status_html("WAITING"), label="ุงู„ุญุงู„ุฉ")
526
 
527
- # Model reply
528
  reply_out = gr.Textbox(
529
  label="ุฑุฏ ุงู„ุญุงุฑุณ",
530
  interactive=False,
531
- lines=4,
532
- elem_id=f"reply_lvl_{i}",
533
  )
534
 
535
- # Lore reveal
536
- lore_out = gr.HTML(value="", label="")
537
 
538
- # ArabGuard trace
539
- with gr.Accordion("๐Ÿ” ุชูุงุตูŠู„ ArabGuard", open=False):
540
  trace_out = gr.JSON(label="Pipeline Trace")
541
 
542
- # โ”€โ”€ Wire up โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
543
  def make_handler(level_idx):
544
  def handler(user_msg, unlocked):
545
  reply, status, lore, new_unlocked, prog = play_level(
546
  user_msg, level_idx, unlocked
547
  )
548
- # Also get full trace for the accordion
549
  trace = {}
550
  if user_msg and user_msg.strip():
551
  res = guard.analyze(user_msg)
@@ -572,21 +558,21 @@ with gr.Blocks(css=CUSTOM_CSS, title="ArabGuard Breach โ€” ุงุฎุชุฑุงู‚ ุงู„ุญุง
572
  outputs=[user_input, status_out, reply_out, lore_out, trace_out],
573
  )
574
 
575
- # โ”€โ”€ Victory panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
576
  gr.Markdown("---")
577
 
578
- with gr.Accordion("๐Ÿ† ุงู„ุฎุงุชู…ุฉ โ€” ุงู‚ุฑุฃ ุจุนุฏ ุงุฎุชุฑุงู‚ ุงู„ู…ุณุชูˆูŠุงุช ุงู„ุฎู…ุณุฉ", open=False):
579
  victory_out = gr.HTML(value="")
580
 
581
  def show_victory(unlocked):
582
  if len(unlocked) >= 5:
583
  return victory_html()
584
  missing = 5 - len(unlocked)
585
- return f"""
586
- <div style='direction:rtl;font-family:Tajawal,sans-serif;color:#3A5A8A;
587
- text-align:center;padding:16px;font-size:14px;'>
588
- ู„ุง ุชุฒุงู„ {missing} ู…ุณุชูˆู‰/ู…ุณุชูˆูŠุงุช ุจุงู†ุชุธุงุฑ ุงู„ุงุฎุชุฑุงู‚...
589
- </div>"""
 
590
 
591
  gr.Button("ูƒุดู ุงู„ุญู‚ูŠู‚ุฉ ุงู„ูƒุงู…ู„ุฉ โœจ", variant="primary").click(
592
  fn=show_victory,
 
136
 
137
 
138
  def play_level(user_input: str, level_idx: int, unlocked_state: list):
 
 
 
 
 
 
 
139
  lv = LEVELS[level_idx]
140
  unlocked = list(unlocked_state)
141
 
 
193
 
194
 
195
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
196
+ # ู…ุณุงุนุฏุงุช HTML โ€” ุฃู„ูˆุงู† ุนุงู„ูŠุฉ ุงู„ุชุจุงูŠู†
197
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
198
  def build_progress_html(unlocked: list) -> str:
199
  pips = ""
200
  for i in range(5):
201
  if i in unlocked:
202
+ color, border = "#4A9EFF", "#4A9EFF"
 
203
  else:
204
+ color, border = "#1A2E50", "#2A4A7A"
205
+ pips += (
206
+ f'<div style="width:52px;height:8px;border-radius:4px;'
207
+ f'background:{color};border:1px solid {border};transition:all .4s;"></div>'
208
+ )
209
  count = len(unlocked)
210
  pct = int(count / 5 * 100)
211
+ bar = (
212
+ f'<div style="height:5px;background:#1A2E50;border-radius:3px;'
213
+ f'margin-top:12px;overflow:hidden;">'
214
+ f'<div style="height:100%;width:{pct}%;background:#4A9EFF;'
215
+ f'border-radius:3px;transition:width .5s;"></div></div>'
216
+ )
217
  return (
218
+ f'<div style="display:flex;gap:8px;justify-content:center;">{pips}</div>'
219
  f'{bar}'
220
+ f'<p style="text-align:center;font-size:14px;color:#8BB8E8;'
221
+ f'margin-top:8px;letter-spacing:1px;font-family:Tajawal,sans-serif;">'
222
+ f'ุงู„ู…ุณุชูˆูŠุงุช ุงู„ู…ุฎุชุฑู‚ุฉ: <span style="color:#4A9EFF;font-weight:700;">{count}</span> / 5'
223
  f'</p>'
224
  )
225
 
226
 
227
  def level_info_html(level_idx: int) -> str:
228
  lv = LEVELS[level_idx]
229
+ return (
230
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;padding:8px 0 14px;">'
231
+ f'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">'
232
+ f'<span style="background:#1A3A6A;border:1px solid #4A9EFF;color:#A8D4FF;'
233
+ f'font-size:13px;padding:4px 14px;border-radius:4px;">{lv["badge"]}</span>'
234
+ f'<span style="font-size:12px;color:#5A9EE8;letter-spacing:2px;'
235
+ f'font-family:monospace;">{lv["attack_type"]}</span>'
236
+ f'</div>'
237
+ f'<div style="font-size:22px;font-weight:700;color:#D8EEFF;margin-bottom:8px;">'
238
+ f'{lv["title"]}</div>'
239
+ f'<div style="font-size:15px;color:#A0C4E8;line-height:1.8;">{lv["desc"]}</div>'
240
+ f'</div>'
241
+ )
242
 
243
 
244
  def hint_html(level_idx: int) -> str:
245
  lv = LEVELS[level_idx]
246
+ return (
247
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;font-size:15px;'
248
+ f'color:#C8E4FF;padding:14px 18px;'
249
+ f'border-right:3px solid #4A9EFF;'
250
+ f'background:#0D1E3A;border-radius:0 8px 8px 0;line-height:1.9;">'
251
+ f'{lv["hint"]}'
252
+ f'</div>'
253
+ )
254
 
255
 
256
  def system_prompt_html(level_idx: int) -> str:
257
  lv = LEVELS[level_idx]
 
258
  guard_part = lv['system_prompt'].split("ูุงูƒุดู")[0].strip()
259
+ return (
260
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;font-size:14px;'
261
+ f'color:#B0D0F4;padding:14px 18px;'
262
+ f'border-right:3px solid #4A9EFF;'
263
+ f'background:#091626;border-radius:0 8px 8px 0;line-height:1.9;">'
264
+ f'<div style="font-size:11px;letter-spacing:2px;color:#5A8ABB;'
265
+ f'margin-bottom:8px;direction:ltr;font-family:monospace;">'
266
+ f'SYSTEM PROMPT โ€” ุงู„ุญุงุฑุณ ูŠุนุฑู ู‡ุฐุง</div>'
267
+ f'{guard_part}...'
268
+ f'</div>'
269
+ )
270
 
271
 
272
  def status_html(status: str) -> str:
273
+ cfg = {
274
+ "BLOCKED": ("๐Ÿ›ก๏ธ ArabGuard: ู…ุญุธูˆุฑ โ€” ุงู„ุฑุณุงู„ุฉ ู„ู… ุชุตู„ ู„ู„ุญุงุฑุณ", "#FF8888", "#2A0C0C", "#DD4444"),
275
+ "UNLOCKED": ("โœ“ ุชู… ุงู„ุงุฎุชุฑุงู‚ ุจู†ุฌุงุญ โ€” ุงู„ู…ุนู„ูˆู…ุฉ ุงู„ุณุฑูŠุฉ ู…ูƒุดูˆูุฉ!", "#50EE88", "#082414", "#28AA55"),
276
+ "SAFE": ("โ†ฉ ุงู„ุญุงุฑุณ ุฑุฏ โ€” ู„ู… ุชููƒุดู ุงู„ุณุฑูŠุฉ ุจุนุฏ", "#A8D4FF", "#091830", "#3A7BD5"),
277
+ "ERROR": ("โš ๏ธ ุฎุทุฃ ููŠ ุงู„ู†ุธุงู…", "#FFB84A", "#221408", "#CC8820"),
278
+ "WAITING": ("โ‹ฏ ููŠ ุงู†ุชุธุงุฑ ุฑุณุงู„ุชูƒ", "#7AAAD8", "#0A1425", "#2A4A7A"),
279
  }
280
+ label, color, bg, border = cfg.get(status, cfg["WAITING"])
281
+ return (
282
+ f'<div style="font-size:15px;color:{color};background:{bg};'
283
+ f'border:1px solid {border};border-radius:6px;'
284
+ f'padding:10px 16px;text-align:center;'
285
+ f'font-family:Tajawal,sans-serif;font-weight:500;">'
286
+ f'{label}</div>'
287
+ )
288
 
289
 
290
  def lore_html(lore_text: str) -> str:
291
  if not lore_text:
292
  return ""
293
+ return (
294
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;'
295
+ f'background:#08200F;border:1px solid #28AA55;'
296
+ f'border-radius:8px;padding:18px 20px;margin-top:6px;">'
297
+ f'<div style="font-size:11px;letter-spacing:2px;color:#40CC70;'
298
+ f'margin-bottom:10px;font-family:monospace;">โ—ˆ ู…ุนู„ูˆู…ุฉ ุณุฑูŠุฉ ู…ูƒุดูˆูุฉ โ—ˆ</div>'
299
+ f'<div style="font-size:16px;color:#90F0B8;line-height:2.0;">{lore_text}</div>'
300
+ f'</div>'
301
+ )
302
 
303
 
304
  def victory_html() -> str:
305
  chapters = "".join([
306
+ f'<div style="margin-bottom:22px;padding-bottom:22px;border-bottom:1px solid #1A4028;">'
307
+ f'<div style="font-size:11px;letter-spacing:2px;color:#40CC70;'
308
+ f'margin-bottom:8px;font-family:monospace;">'
309
  f'CHAPTER {lv["num"]} โ€” {lv["title"]}</div>'
310
+ f'<div style="font-size:15px;color:#A8F0CC;line-height:2.0;">{lv["lore"]}</div>'
311
  f'</div>'
312
  for lv in LEVELS
313
  ])
314
+ return (
315
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;'
316
+ f'background:#061610;border:1px solid #28AA55;'
317
+ f'border-radius:12px;padding:28px 30px;">'
318
+ f'<div style="text-align:center;margin-bottom:26px;">'
319
+ f'<div style="font-size:26px;font-weight:700;color:#70F0A8;">ู…ุจุฑูˆูƒ โ€” ุงู„ุญู‚ูŠู‚ุฉ ุงู„ูƒุงู…ู„ุฉ</div>'
320
+ f'<div style="font-size:12px;letter-spacing:2px;color:#38B860;margin-top:8px;font-family:monospace;">'
321
+ f'ALL 5 LEVELS BREACHED โ€” ARABGUARD DEFEATED</div>'
322
+ f'</div>'
323
+ f'{chapters}'
324
+ f'</div>'
325
+ )
326
 
327
 
328
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
329
+ # CSS โ€” ุฃู„ูˆุงู† ุนุงู„ูŠุฉ ุงู„ุชุจุงูŠู†
330
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
331
  CUSTOM_CSS = """
332
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&family=Rajdhani:wght@400;600;700&display=swap');
333
 
334
  body, .gradio-container {
335
+ background: #0B1628 !important;
336
  font-family: 'Rajdhani', sans-serif !important;
337
  }
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  .game-title-block {
340
  text-align: center;
341
  padding: 28px 0 18px;
342
+ border-bottom: 1px solid #1E3A60;
343
  margin-bottom: 20px;
344
  }
345
  .game-title-block h1 {
346
  font-family: 'Rajdhani', sans-serif !important;
347
+ font-size: 30px !important;
348
  font-weight: 700 !important;
349
+ color: #A8D4FF !important;
350
  letter-spacing: 4px !important;
351
  text-transform: uppercase !important;
352
  margin: 0 !important;
353
  }
354
  .game-title-block p {
355
  font-family: 'Tajawal', sans-serif !important;
356
+ font-size: 15px !important;
357
+ color: #7AAAD8 !important;
358
+ margin: 8px 0 0 !important;
 
359
  }
360
 
361
+ .gr-panel, .gr-box, .gr-form, .gradio-group {
362
+ background: #0F1F3A !important;
363
+ border: 1px solid #1E3A60 !important;
 
364
  border-radius: 10px !important;
365
  }
366
 
 
367
  textarea, input[type="text"] {
368
+ background: #081428 !important;
369
+ border: 1px solid #2A5080 !important;
370
  border-radius: 6px !important;
371
+ color: #E0F0FF !important;
372
  font-family: 'Tajawal', sans-serif !important;
373
+ font-size: 15px !important;
374
  direction: rtl !important;
375
  }
376
  textarea:focus, input[type="text"]:focus {
377
+ border-color: #4A9EFF !important;
378
+ box-shadow: 0 0 0 2px rgba(74,158,255,0.18) !important;
379
+ outline: none !important;
380
  }
381
  textarea::placeholder, input::placeholder {
382
+ color: #3A6090 !important;
383
+ }
384
+ textarea[readonly], textarea[disabled] {
385
+ color: #C8E4FF !important;
386
+ background: #091A30 !important;
387
+ opacity: 1 !important;
388
  }
389
 
390
+ button.primary, .gr-button-primary {
391
+ background: #1A4A9A !important;
392
+ border: 1px solid #4A9EFF !important;
393
+ color: #E8F4FF !important;
 
394
  font-family: 'Rajdhani', sans-serif !important;
395
+ font-size: 14px !important;
396
  font-weight: 600 !important;
397
  letter-spacing: 1.5px !important;
398
  text-transform: uppercase !important;
399
  border-radius: 6px !important;
400
  transition: all 0.2s !important;
401
  }
402
+ button.primary:hover, .gr-button-primary:hover {
403
+ background: #2860C0 !important;
404
+ border-color: #80C0FF !important;
405
  }
406
 
407
+ button.secondary, .gr-button-secondary {
408
+ background: #102040 !important;
409
+ border: 1px solid #2A5080 !important;
410
+ color: #90C0F0 !important;
411
  font-family: 'Rajdhani', sans-serif !important;
412
+ font-size: 13px !important;
413
  letter-spacing: 1px !important;
414
  text-transform: uppercase !important;
415
  border-radius: 4px !important;
416
+ transition: all 0.2s !important;
417
  }
418
+ button.secondary:hover, .gr-button-secondary:hover {
419
+ border-color: #4A9EFF !important;
420
+ color: #C8E4FF !important;
421
+ background: #18305A !important;
422
  }
423
 
424
+ label, label span, .gr-label {
425
+ color: #90B8E8 !important;
 
426
  font-family: 'Rajdhani', sans-serif !important;
427
+ font-size: 12px !important;
428
  letter-spacing: 1.5px !important;
429
  text-transform: uppercase !important;
430
  }
431
 
432
+ .tab-nav button, .tabs button {
 
433
  font-family: 'Tajawal', sans-serif !important;
434
+ color: #7AAAD8 !important;
435
+ font-size: 14px !important;
436
+ background: transparent !important;
437
  border-bottom: 2px solid transparent !important;
438
+ padding: 8px 14px !important;
439
+ transition: all 0.2s !important;
440
+ }
441
+ .tab-nav button.selected, .tabs button.selected {
442
+ color: #A8D4FF !important;
443
+ border-bottom: 2px solid #4A9EFF !important;
444
+ background: rgba(74,158,255,0.08) !important;
445
+ }
446
+
447
+ .accordion > .label-wrap {
448
+ background: #0F1F3A !important;
449
+ border: 1px solid #1E3A60 !important;
450
+ border-radius: 6px !important;
451
+ padding: 8px 14px !important;
452
  }
453
+ .accordion > .label-wrap span,
454
+ .accordion > .label-wrap button {
455
+ color: #90C0F0 !important;
456
+ font-size: 14px !important;
457
+ font-family: 'Tajawal', sans-serif !important;
458
  }
459
 
460
+ .prose p, .gr-markdown p, .gr-markdown li {
461
+ color: #C0D8F4 !important;
462
+ font-size: 14px !important;
463
  }
464
 
465
+ .json-component {
466
+ background: #081428 !important;
467
+ color: #80C0FF !important;
468
+ border: 1px solid #1E3A60 !important;
469
+ }
470
 
471
+ ::-webkit-scrollbar { width: 5px; }
472
+ ::-webkit-scrollbar-track { background: #0B1628; }
473
+ ::-webkit-scrollbar-thumb { background: #2A4A7A; border-radius: 3px; }
 
474
 
 
475
  @keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} }
476
  """
477
 
 
480
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
481
  with gr.Blocks(css=CUSTOM_CSS, title="ArabGuard Breach โ€” ุงุฎุชุฑุงู‚ ุงู„ุญุงุฑุณ ุงู„ุนุฑุจูŠ") as demo:
482
 
483
+ unlocked_state = gr.State([])
484
+ current_level = gr.State(0)
 
485
 
 
486
  gr.HTML("""
487
  <div class="game-title-block">
488
  <h1>โ—ˆ ArabGuard Breach โ—ˆ</h1>
 
490
  </div>
491
  """)
492
 
493
+ progress_display = gr.HTML(value=build_progress_html([]))
 
494
 
495
+ with gr.Tabs():
 
496
  for i, lv in enumerate(LEVELS):
497
  with gr.TabItem(f"{lv['badge']} โ€” {lv['title']}"):
498
 
 
499
  gr.HTML(value=level_info_html(i))
500
 
501
+ with gr.Accordion("๐Ÿ‘ ุนุฑุถ System Prompt ุงู„ุญุงุฑุณ", open=False):
 
502
  gr.HTML(value=system_prompt_html(i))
503
 
504
+ with gr.Accordion("๐Ÿ’ก ุชู„ู…ูŠุญ", open=False):
 
505
  gr.HTML(value=hint_html(i))
506
 
 
507
  user_input = gr.Textbox(
508
  label="ุฑุณุงู„ุชูƒ ู„ู„ุญุงุฑุณ",
509
  placeholder="ุงูƒุชุจ ุฑุณุงู„ุชูƒ ุจุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ ู‡ู†ุง...",
510
  lines=3,
 
511
  )
512
 
 
513
  with gr.Row():
514
+ send_btn = gr.Button("ุฅุฑุณุงู„ โ†", variant="primary", scale=3)
515
+ clear_btn = gr.Button("ู…ุณุญ โœ•", variant="secondary", scale=1)
516
 
517
+ status_out = gr.HTML(value=status_html("WAITING"))
 
518
 
 
519
  reply_out = gr.Textbox(
520
  label="ุฑุฏ ุงู„ุญุงุฑุณ",
521
  interactive=False,
522
+ lines=5,
 
523
  )
524
 
525
+ lore_out = gr.HTML(value="")
 
526
 
527
+ with gr.Accordion("๐Ÿ” ุชูุงุตูŠู„ ArabGuard", open=False):
 
528
  trace_out = gr.JSON(label="Pipeline Trace")
529
 
 
530
  def make_handler(level_idx):
531
  def handler(user_msg, unlocked):
532
  reply, status, lore, new_unlocked, prog = play_level(
533
  user_msg, level_idx, unlocked
534
  )
 
535
  trace = {}
536
  if user_msg and user_msg.strip():
537
  res = guard.analyze(user_msg)
 
558
  outputs=[user_input, status_out, reply_out, lore_out, trace_out],
559
  )
560
 
 
561
  gr.Markdown("---")
562
 
563
+ with gr.Accordion("๐Ÿ† ุงู„ุฎุงุชู…ุฉ โ€” ุงู‚ุฑุฃ ุจุนุฏ ุงุฎุชุฑุงู‚ ุงู„ู…ุณุชูˆูŠุงุช ุงู„ุฎู…ุณุฉ", open=False):
564
  victory_out = gr.HTML(value="")
565
 
566
  def show_victory(unlocked):
567
  if len(unlocked) >= 5:
568
  return victory_html()
569
  missing = 5 - len(unlocked)
570
+ return (
571
+ f'<div style="direction:rtl;font-family:Tajawal,sans-serif;'
572
+ f'color:#7AAAD8;text-align:center;padding:18px;font-size:15px;">'
573
+ f'ู„ุง ุชุฒุงู„ <span style="color:#FF9A50;font-weight:700;">{missing}</span>'
574
+ f' ู…ุณุชูˆู‰/ู…ุณุชูˆูŠุงุช ุจุงู†ุชุธุงุฑ ุงู„ุงุฎุชุฑุงู‚...</div>'
575
+ )
576
 
577
  gr.Button("ูƒุดู ุงู„ุญู‚ูŠู‚ุฉ ุงู„ูƒุงู…ู„ุฉ โœจ", variant="primary").click(
578
  fn=show_victory,