shmulc commited on
Commit
c9c8b50
·
verified ·
1 Parent(s): dd1bfe8

Update app.py, methods.html, webapp/index.html, webapp/mockServiceWorker.js, webapp/assets/index-LC1qePO9.js, webapp/assets/index-w435VGSR.css

Browse files
app.py CHANGED
@@ -31,7 +31,7 @@ import threading
31
  import time
32
 
33
  import numpy as np
34
- from flask import Flask, request, jsonify, send_file, abort
35
 
36
  import morph
37
  import probe
@@ -328,8 +328,22 @@ def _classical_mds(D: np.ndarray, dim: int = 2) -> np.ndarray:
328
  # Pages
329
  # --------------------------------------------------------------------------- #
330
 
 
 
 
331
  @app.get("/")
332
  def index():
 
 
 
 
 
 
 
 
 
 
 
333
  return send_file("copilot.html")
334
 
335
 
 
31
  import time
32
 
33
  import numpy as np
34
+ from flask import Flask, request, jsonify, send_file, send_from_directory, abort
35
 
36
  import morph
37
  import probe
 
328
  # Pages
329
  # --------------------------------------------------------------------------- #
330
 
331
+ WEBAPP = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webapp")
332
+
333
+
334
  @app.get("/")
335
  def index():
336
+ spa = os.path.join(WEBAPP, "index.html")
337
+ return send_file(spa) if os.path.exists(spa) else send_file("copilot.html")
338
+
339
+
340
+ @app.get("/assets/<path:asset>")
341
+ def spa_assets(asset):
342
+ return send_from_directory(os.path.join(WEBAPP, "assets"), asset)
343
+
344
+
345
+ @app.get("/classic")
346
+ def classic():
347
  return send_file("copilot.html")
348
 
349
 
methods.html CHANGED
The diff for this file is too large to render. See raw diff
 
webapp/assets/index-LC1qePO9.js ADDED
The diff for this file is too large to render. See raw diff
 
webapp/assets/index-w435VGSR.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap";.board{min-width:0}.board__toolbar,.board__toolbar-actions,.board__remaining,.board__game-over,.board__legend-popover span{align-items:center;display:flex}.board__toolbar{justify-content:space-between;gap:var(--space-6);margin-bottom:var(--space-4)}.board__toolbar h2{margin:0;font-size:clamp(1rem,1.5vw,1.25rem)}.board__eyebrow{margin:0 0 var(--space-1);color:var(--cn-text-dim);font-size:.68rem}.board__toolbar-actions{gap:var(--space-3)}.board__remaining{gap:var(--space-4);padding-inline:var(--space-3);color:var(--cn-text-dim);font-size:.7rem}.board__remaining span{align-items:center;gap:var(--space-1);display:inline-flex}.board__remaining .role-red{color:var(--cn-red-text)}.board__remaining .role-blue{color:var(--cn-blue-text)}.board__legend{position:relative}.board__legend-popover{z-index:20;top:calc(100% + var(--space-2));gap:var(--space-3);min-width:9rem;padding:var(--space-4);border:1px solid var(--color-divider);border-radius:var(--radius-md);background:var(--cn-sheet);box-shadow:var(--shadow-md);font-size:.78rem;display:grid;position:absolute;inset-inline-end:0}.board__legend-popover span{gap:var(--space-3)}.board__game-over{justify-content:center;gap:var(--space-3);margin-bottom:var(--space-4);min-height:2.5rem;color:var(--cn-text);border:1px solid var(--cn-assassin-dot);border-radius:var(--radius-md);background:#07070a;font-weight:700}.board__grid{border-radius:var(--radius-lg);background:var(--cn-board-well);direction:rtl;border:1px solid #ffffff06;grid-template-columns:repeat(5,minmax(0,1fr));gap:clamp(5px,.65vw,9px);padding:clamp(8px,1vw,14px);display:grid;box-shadow:inset 0 8px 24px #00000047}.board-cell{min-width:0;position:relative}.board-tile{--tile-ring:var(--cn-neutral-ring);--tile-tint:var(--cn-neutral-tint);--tile-gradient:var(--cn-neutral-grad);aspect-ratio:1.5;border-radius:var(--radius-md);background:var(--cn-card-face);width:100%;min-width:0;color:var(--cn-card-ink);box-shadow:inset 0 0 0 2px var(--tile-ring), inset 0 0 22px var(--tile-tint), inset 0 1.5px 0 #ffffff8c, inset 0 -3px 6px #785f3747, 0 3px 7px #00000080;cursor:pointer;border:0;padding:0;transition:transform .14s,box-shadow .14s;position:relative;overflow:hidden}.board-tile.role-red{--tile-ring:var(--cn-red-ring);--tile-tint:var(--cn-red-tint);--tile-gradient:var(--cn-red-grad);--tile-role-icon:var(--cn-red-text);background:var(--cn-red-grad)}.board-tile.role-blue{--tile-ring:var(--cn-blue-ring);--tile-tint:var(--cn-blue-tint);--tile-gradient:var(--cn-blue-grad);--tile-role-icon:var(--cn-blue-text);background:var(--cn-blue-grad)}.board-tile.role-neutral{--tile-ring:var(--cn-neutral-ring);--tile-tint:var(--cn-neutral-tint);--tile-gradient:var(--cn-neutral-grad);--tile-role-icon:var(--cn-neutral-icon)}.board-tile.role-assassin{--tile-ring:var(--cn-assassin-ring);--tile-tint:var(--cn-assassin-tint);--tile-gradient:var(--cn-assassin-grad);--tile-role-icon:var(--cn-assassin-dot);background:var(--cn-assassin-grad)}.board-cell:hover .board-tile:not(.is-chosen){transform:translateY(-1px)}.board-tile.is-selected{box-shadow:inset 0 0 0 2px var(--tile-ring), inset 0 0 22px var(--tile-tint), inset 0 1.5px 0 #ffffff8c, 0 0 0 3px var(--cn-select-ring), 0 3px 7px #00000080}.board-tile.is-hover-linked{outline:2px solid var(--color-accent-300);outline-offset:2px}.board-tile__content{width:100%;color:inherit;font:inherit;cursor:inherit;background:0 0;border:0;flex-direction:column;justify-content:flex-end;padding:clamp(5px,.7vw,8px);display:flex;position:absolute;inset:0}.board-tile__hole{background:var(--cn-card-hole);border-radius:50%;width:8px;height:8px;position:absolute;top:6px;left:50%;transform:translate(-50%);box-shadow:inset 0 1.5px 2.5px #5a46288c}.board-tile__role{color:var(--tile-role-icon,var(--tile-ring));font-size:.72rem;position:absolute;top:6px;left:7px}.board-tile__mirror{text-align:center;color:var(--cn-card-mirror);margin-bottom:2px;font-size:clamp(.52rem,.8vw,.72rem);font-weight:600;line-height:1;transform:rotate(180deg)}.board-tile.role-red .board-tile__mirror,.board-tile.role-blue .board-tile__mirror,.board-tile.role-assassin .board-tile__mirror{color:#ffffff94}.board-tile__label{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--cn-card-label-bd);border-radius:var(--radius-sm);background:var(--cn-card-label);min-width:0;color:var(--cn-card-ink);text-align:center;padding:3px 4px;font-size:clamp(.9rem,1.3vw,1.1rem);font-weight:700;line-height:1.25;overflow:hidden}.board-tile__badge{background:var(--cn-badge-bg);width:1.15rem;height:1.15rem;color:var(--cn-badge-ink);border-radius:50%;place-items:center;font-size:.65rem;font-weight:700;display:grid;position:absolute;top:4px;right:5px}.board-tile__lifecycle{z-index:3;color:#2d2515b3;max-width:calc(100% - 3.5rem);font:inherit;white-space:nowrap;opacity:0;cursor:pointer;background:#fffdf6b3;border:1px solid #2d251538;border-radius:999px;padding:1px 5px;font-size:clamp(.46rem,.6vw,.58rem);line-height:1.4;transition:opacity .12s;position:absolute;top:4px;left:50%;overflow:hidden;transform:translate(-50%)}.board-cell:hover .board-tile__lifecycle,.board-cell:focus-within .board-tile__lifecycle,.board-tile.is-chosen+.board-tile__lifecycle{opacity:1}.board-tile.is-chosen{background:var(--tile-gradient);cursor:default;box-shadow:inset 0 0 0 1px #ffffff14,0 2px 5px #0006}.board-tile.is-chosen .board-tile__content{opacity:.4}.board-tile.is-chosen .board-tile__hole,.board-tile.is-chosen .board-tile__mirror{display:none}.board-tile.is-chosen .board-tile__label{color:var(--cn-text);background:0 0;border:0;font-size:clamp(.85rem,1.15vw,1rem);text-decoration:line-through}.board-tile.is-chosen.role-neutral .board-tile__label{color:var(--cn-card-ink)}.board-tile.is-assassin-chosen{background:#030305}.board-tile.is-assassin-chosen .board-tile__label{color:#fff}.board-tile.is-chosen+.board-tile__lifecycle{color:var(--cn-text);background:#0a0a0f99;border-color:#fff3}.board-tile__chosen-chip{width:1.2rem;height:1.2rem;color:var(--cn-text);background:#0a0a0fa6;border-radius:50%;place-items:center;display:inline-grid;position:absolute;top:6px;left:7px}.board-tile__chosen-chip .cn-role-icon{font-size:.65rem}.board__hint{margin:var(--space-3) var(--space-2) 0;color:var(--cn-text-faint);font-size:.68rem}@media (width<=720px){.board__toolbar{flex-direction:column;align-items:flex-start}.board__toolbar-actions{flex-wrap:wrap;width:100%}.board__remaining{margin-inline-end:auto}.board__grid{grid-template-columns:repeat(5,minmax(7.5rem,1fr));overflow-x:auto}}@media (prefers-reduced-motion:reduce){.board-tile{transition:none}}.feedback-controls{gap:var(--space-3);border-block-start:1px solid var(--color-divider);color:var(--cn-text-muted);padding-block-start:var(--space-3);display:grid}.feedback-controls__gut-check,.feedback-controls__comment-actions,.feedback-controls__status,.feedback-controls__sent{align-items:center;display:flex}.feedback-controls__gut-check{gap:var(--space-2);min-height:2.25rem}.feedback-controls__prompt{margin-inline-end:var(--space-1);font-size:13px;font-weight:600}.feedback-controls__vote{border-radius:var(--radius-md);width:2.25rem;height:2.25rem;color:var(--color-accent-400);cursor:pointer;filter:grayscale();opacity:.78;background:0 0;border:1px solid #0000;padding:0;font-size:1rem;transition:border-color .14s,background .14s,filter .14s,opacity .14s}.feedback-controls__vote:hover:not(:disabled),.feedback-controls__vote[aria-pressed=true]{border-color:color-mix(in srgb, var(--color-accent) 60%, transparent);background:color-mix(in srgb, var(--color-accent) 10%, transparent);filter:none;opacity:1}.feedback-controls__vote:disabled:not([aria-pressed=true]){opacity:.28}.feedback-controls__details,.feedback-controls__comment-row{gap:var(--space-2);animation:.16s ease-out both feedback-reveal;display:grid}.feedback-controls__question,.feedback-controls__comment-row label{color:var(--cn-text-muted);margin:0;font-size:12px}.feedback-controls__reasons{gap:var(--space-2);flex-wrap:wrap;display:flex}.feedback-controls__reason{min-height:2rem;padding:var(--space-1) var(--space-3);border:1px solid color-mix(in srgb, var(--color-accent) 72%, transparent);border-radius:var(--radius-md);color:var(--color-accent-300);cursor:pointer;font:inherit;background:0 0;font-size:12px}.feedback-controls__reason:hover:not(:disabled),.feedback-controls__reason[aria-pressed=true]{background:color-mix(in srgb, var(--color-accent) 14%, transparent);border-color:var(--color-accent)}.feedback-controls__reason:disabled:not([aria-pressed=true]){opacity:.36}.feedback-controls__comment-actions{align-items:stretch;gap:var(--space-2)}.feedback-controls__comment{resize:vertical;min-height:3.5rem}.feedback-controls__comment-submit{flex:none}.feedback-controls__status,.feedback-controls__sent{gap:var(--space-2);min-height:2rem;margin:0;font-size:12px}.feedback-controls__sent{color:var(--cn-ok)}.feedback-controls__spinner{border:2px solid color-mix(in srgb, var(--color-accent) 28%, transparent);border-block-start-color:var(--color-accent);border-radius:50%;width:.9rem;height:.9rem;animation:.7s linear infinite feedback-spin}@keyframes feedback-reveal{0%{opacity:0;transform:translateY(calc(var(--space-1) * -1))}}@keyframes feedback-spin{to{transform:rotate(1turn)}}@media (prefers-reduced-motion:reduce){.feedback-controls__details,.feedback-controls__comment-row,.feedback-controls__spinner{animation:none}}.check-panel{gap:var(--space-6);display:grid}.check-panel__intro{gap:var(--space-2);padding:var(--space-4);border-inline-start:2px solid var(--color-accent);border-radius:var(--radius-sm);background:color-mix(in srgb, var(--color-accent) 7%, transparent);display:grid}.check-panel__intro p{color:var(--cn-text-muted);margin:0;font-size:.78rem;line-height:1.55}.check-panel__intro .check-panel__privacy{color:var(--color-accent-300);font-weight:700}.check-target{border:0;min-width:0;margin:0;padding:0}.check-target legend,.check-form label{color:var(--cn-text-muted);margin-block-end:var(--space-2);font-size:.78rem}.check-target__options{border:1px solid var(--color-divider);border-radius:var(--radius-md);grid-template-columns:repeat(2,minmax(0,1fr));display:grid;overflow:hidden}.check-target__option{justify-content:center;align-items:center;gap:var(--space-2);min-height:2.65rem;color:var(--cn-text-muted);cursor:pointer;display:flex;position:relative}.check-target__option+.check-target__option{border-inline-start:1px solid var(--color-divider)}.check-target__option input{opacity:0;pointer-events:none;position:absolute}.check-target__option:hover{background:color-mix(in srgb, var(--cn-role-dot) 7%, transparent)}.check-target__option.is-active{color:var(--cn-role-text);background:var(--cn-role-tint);box-shadow:inset 0 -2px 0 var(--cn-role-ring)}.check-target__option:has(input:focus-visible){outline:2px solid var(--color-accent);outline-offset:-2px}.check-form{gap:var(--space-3);display:grid}.check-form__input{box-sizing:border-box;width:100%;min-height:2.8rem;font-size:1rem}.check-form__validation{margin:calc(var(--space-1) * -1) 0 0;color:var(--cn-red-text);font-size:.75rem}.check-form__submit{width:100%}.check-result{gap:var(--space-4);display:grid}.check-illegal{gap:var(--space-1);padding:var(--space-4);border:1px solid var(--cn-red-ring);border-radius:var(--radius-md);background:var(--cn-red-tint);color:var(--cn-red-text);font-size:.78rem;line-height:1.5;display:grid}.check-summary{gap:var(--space-3);padding:var(--space-4);border:1px solid var(--color-divider);border-radius:var(--radius-md);background:var(--cn-panel);display:grid}.check-summary p{color:var(--cn-text-muted);margin:0;font-size:.78rem;line-height:1.55}.check-summary__safe{padding:var(--space-2) var(--space-3);border:1px solid color-mix(in srgb, var(--cn-ok) 48%, transparent);background:color-mix(in srgb, var(--cn-ok) 10%, transparent);color:var(--cn-ok);border-radius:999px;justify-self:start;font-size:.8rem;font-weight:700}.check-summary .check-summary__assassin{align-items:center;gap:var(--space-2);display:flex}.check-summary .check-summary__assassin.is-close{padding:var(--space-3);border:1px solid var(--cn-red-ring);border-radius:var(--radius-sm);background:var(--cn-red-tint);color:var(--cn-red-text);font-weight:700}.check-ranked{background:var(--cn-panel)}.check-ranked__scale{color:var(--cn-text-dim);white-space:nowrap;font-size:.7rem}.check-ranked__list{gap:var(--space-1);scrollbar-color:var(--color-neutral-700) transparent;max-height:23rem;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.check-ranked__row{align-items:center;gap:var(--space-3);min-height:2.25rem;padding-inline:var(--space-2);border-radius:var(--radius-sm);grid-template-columns:1.5rem minmax(4.5rem,auto) minmax(4rem,1fr) 2rem;font-size:.76rem;display:grid}.check-ranked__row:hover,.check-ranked__row:focus-visible{background:color-mix(in srgb, var(--cn-role-dot) 8%, transparent);outline:1px solid color-mix(in srgb, var(--cn-role-ring) 55%, transparent)}.check-ranked__rank{color:var(--cn-text-dim);font-variant-numeric:tabular-nums}.check-ranked__word{align-items:center;gap:var(--space-2);min-width:0;color:var(--cn-text);font-weight:600;display:flex}.check-ranked__bar{background:color-mix(in srgb, var(--cn-text) 7%, transparent);border-radius:999px;height:.34rem;overflow:hidden}.check-ranked__bar>span{border-radius:inherit;background:var(--cn-role-dot);height:100%;display:block}.check-ranked__row strong{color:var(--cn-role-text);font-variant-numeric:tabular-nums;text-align:end}.check-ranked__help,.check-ranked__empty{margin:var(--space-3) 0 0;color:var(--cn-text-dim);font-size:.7rem;line-height:1.5}@media (width<=1100px){.check-ranked__row{gap:var(--space-2);grid-template-columns:1.25rem minmax(4rem,auto) minmax(3rem,1fr) 1.8rem}}.clue-panel{gap:var(--space-4);color:var(--cn-text);display:grid}.clue-builder,.clue-result,.clue-placeholder{border:1px solid var(--color-divider);border-radius:var(--radius-md);background:color-mix(in srgb, var(--cn-panel) 82%, transparent)}.clue-builder{gap:var(--space-4);padding:var(--space-4);display:grid}.clue-builder__heading{justify-content:space-between;align-items:end;gap:var(--space-4);display:flex}.clue-builder h2{margin:0;font-size:1rem;font-weight:600}.clue-panel__eyebrow{margin:0 0 var(--space-1);color:var(--color-accent-400);letter-spacing:.04em;font-size:.7rem;font-weight:600}.clue-target,.clue-risk__options{background:color-mix(in srgb, var(--cn-app-bg) 35%, transparent);width:fit-content;max-width:100%;display:inline-flex}.clue-target__option,.clue-risk__option{min-height:2.25rem;padding:var(--space-2) var(--space-3);color:var(--cn-text-muted);cursor:pointer;font:inherit;background:0 0;border:0;font-size:.8rem}.clue-target__option{justify-content:center;align-items:center;gap:var(--space-2);display:inline-flex}.clue-target__option+.clue-target__option,.clue-risk__option+.clue-risk__option{border-inline-start:1px solid var(--color-divider)}.clue-target__option[aria-pressed=true],.clue-risk__option[aria-pressed=true]{color:var(--color-accent-300);box-shadow:inset 0 0 0 1px var(--color-accent)}.clue-target__option:disabled,.clue-risk__option:disabled{cursor:not-allowed;opacity:.5}.clue-selection{gap:var(--space-3);padding-block:var(--space-3);border-block:1px solid var(--color-divider);display:grid}.clue-selection__summary{justify-content:space-between;align-items:center;gap:var(--space-2);color:var(--cn-text-muted);flex-wrap:wrap;font-size:.75rem;display:flex}.clue-selection__color{align-items:center;gap:var(--space-1);color:var(--cn-role-text);display:inline-flex}.clue-chip-list{gap:var(--space-2);flex-wrap:wrap;display:flex}.clue-chip{align-items:center;gap:var(--space-1);padding:var(--space-1) var(--space-3);border:1px solid color-mix(in srgb, var(--cn-role-ring,var(--color-divider)) 60%, transparent);background:var(--cn-role-tint,color-mix(in srgb, var(--cn-text) 5%, transparent));color:var(--cn-role-text,var(--cn-text));border-radius:999px;font-size:.74rem;line-height:1.4;display:inline-flex}.clue-chip:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}.clue-risk{gap:var(--space-2);border:0;min-width:0;margin:0;padding:0;display:grid}.clue-risk legend{color:var(--cn-text-muted);padding:0;font-size:.77rem}.clue-risk__options{justify-self:start}.clue-risk__help{color:var(--cn-text-dim);margin:0;font-size:.69rem;line-height:1.55}.clue-actions{gap:var(--space-3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.clue-actions__button{width:100%;min-height:2.7rem;padding-inline:var(--space-2);white-space:normal}.clue-placeholder{align-items:center;gap:var(--space-4);padding:var(--space-4);color:var(--cn-text-muted);display:flex}.clue-placeholder>span{color:var(--color-accent-400);font-size:1.35rem}.clue-placeholder strong{color:var(--cn-text);font-size:.85rem}.clue-placeholder p{margin:var(--space-1) 0 0;font-size:.72rem}.clue-result{gap:var(--space-4);min-width:0;padding:var(--space-4);display:grid;position:relative;overflow:hidden}.clue-result__header{justify-content:space-between;align-items:start;gap:var(--space-4);display:flex}.clue-result__title-row{align-items:center;gap:var(--space-3);display:flex}.clue-result__title-row h3{color:var(--cn-text);margin:0;font-size:clamp(2rem,3vw,2.8rem);font-weight:500;line-height:1}.clue-result__count{padding:var(--space-1) var(--space-3);border:1px solid var(--color-accent-600);color:var(--color-accent-300);border-radius:999px;font-size:.75rem}.clue-intended{gap:var(--space-2);color:var(--cn-text-muted);font-size:.72rem;display:grid}.clue-reason{color:var(--cn-text-muted);margin:0;font-size:.8rem;line-height:1.65}.clue-warning,.clue-empty,.clue-stale{border:1px solid var(--cn-warn-bd);border-radius:var(--radius-md);background:var(--cn-warn-bg);color:var(--cn-warn)}.clue-warning,.clue-empty{padding:var(--space-3)}.clue-warning strong,.clue-empty strong{font-size:.8rem}.clue-warning p,.clue-empty p{margin:var(--space-2) 0 0;font-size:.74rem}.clue-warning .clue-chip-list{margin-top:var(--space-3)}.clue-assassin{align-items:center;gap:var(--space-2);color:var(--cn-text-muted);margin:0;font-size:.74rem;display:flex}.clue-ranking{gap:var(--space-3);min-width:0;display:grid}.clue-ranking__header{justify-content:space-between;align-items:baseline;gap:var(--space-3);display:flex}.clue-ranking__header h4{margin:0;font-size:.78rem;font-weight:600}.clue-ranking__header span{color:var(--cn-text-dim);font-size:.66rem}.clue-ranking__list{gap:var(--space-2);margin:0;padding:0;list-style:none;display:grid}.clue-ranking__row{align-items:center;gap:var(--space-2);grid-template-columns:minmax(4.5rem,auto) minmax(3rem,1fr) 2rem;font-size:.7rem;display:grid}.clue-ranking__word{align-items:center;gap:var(--space-1);text-overflow:ellipsis;white-space:nowrap;display:inline-flex;overflow:hidden}.clue-ranking__track{background:color-mix(in srgb, var(--cn-text) 8%, transparent);border-radius:999px;height:4px;overflow:hidden}.clue-ranking__fill{border-radius:inherit;background:var(--cn-role-dot);height:100%;display:block}.clue-ranking__score{color:var(--cn-text-dim);font-variant-numeric:tabular-nums;text-align:end}.clue-use{align-items:center;gap:var(--space-3);padding-top:var(--space-3);border-top:1px solid var(--color-divider);flex-wrap:wrap;display:flex}.clue-use__confirmation{color:var(--cn-ok);font-size:.72rem}.clue-carousel{align-items:center;gap:var(--space-2);padding-top:var(--space-3);border-top:1px solid var(--color-divider);color:var(--cn-text-muted);grid-template-columns:1fr auto 1fr;font-size:.7rem;display:grid}.clue-carousel>:last-child{justify-self:end}.clue-stale{z-index:5;justify-content:space-between;align-items:center;gap:var(--space-4);padding:var(--space-4);background:color-mix(in srgb, var(--cn-app-bg) 94%, transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;position:absolute;inset:0}.clue-stale div{gap:var(--space-2);display:grid}.clue-stale strong{font-size:.82rem}.clue-stale span{color:var(--cn-text-muted);font-size:.72rem}@media (width<=1100px){.clue-builder__heading,.clue-stale{flex-direction:column;align-items:stretch}.clue-actions{grid-template-columns:minmax(0,1fr)}}@media (prefers-reduced-motion:reduce){.clue-stale{-webkit-backdrop-filter:none;backdrop-filter:none}}.session-log{border:1px solid var(--color-divider);background:color-mix(in srgb, var(--cn-sheet) 88%, transparent);padding:0;overflow:hidden}.session-log>.cn-panel__header{display:block}.session-log__toggle{align-items:center;gap:var(--space-2);width:100%;min-height:2.75rem;padding:var(--space-3) var(--space-4);color:var(--cn-text);cursor:pointer;font:inherit;text-align:start;background:0 0;border:0;font-size:14px;font-weight:600;display:flex}.session-log__toggle:hover{background:color-mix(in srgb, var(--cn-text) 4%, transparent)}.session-log__total{border:1px solid color-mix(in srgb, var(--color-accent) 52%, transparent);min-width:1.35rem;height:1.35rem;color:var(--color-accent-300);border-radius:50%;place-items:center;font-size:11px;display:inline-grid}.session-log__chevron{color:var(--cn-text-muted);margin-inline-start:auto;transition:transform .16s}.session-log__toggle[aria-expanded=false] .session-log__chevron{transform:rotate(90deg)}.session-log .cn-panel__body{border-block-start:1px solid var(--color-divider)}.session-log__empty{padding:var(--space-4);color:var(--cn-text-muted);margin:0;font-size:12px}.session-log__list{max-height:22rem;overflow-y:auto}.session-log__entry{gap:var(--space-2);padding:var(--space-4);display:grid}.session-log__entry+.session-log__entry{border-block-start:1px solid var(--color-divider)}.session-log__entry-header,.session-log__clue-line,.session-log__meta,.session-log__intended,.session-log__outcome,.session-log__team,.session-log__reveal{align-items:center;display:flex}.session-log__entry-header{justify-content:space-between;gap:var(--space-3)}.session-log__clue-line{gap:var(--space-2)}.session-log__clue-line strong{color:var(--cn-text);font-size:16px}.session-log__count{border:1px solid var(--color-accent);width:1.6rem;height:1.6rem;color:var(--color-accent-300);border-radius:50%;place-items:center;font-size:12px;display:inline-grid}.session-log__entry time,.session-log__meta,.session-log__pending{color:var(--cn-text-muted);font-size:11px}.session-log__meta,.session-log__intended,.session-log__outcome{gap:var(--space-2);flex-wrap:wrap}.session-log__team,.session-log__reveal{gap:var(--space-1)}.session-log__repeat{color:var(--cn-warn)}.session-log__reveal{padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);border:1px solid;font-size:11px}.session-log__reveal--correct{color:var(--cn-ok);background:color-mix(in srgb, var(--cn-ok) 8%, transparent)}.session-log__reveal--miss{color:var(--cn-warn);background:var(--cn-warn-bg)}.session-log__reveal--danger{color:var(--cn-red-text);background:color-mix(in srgb, var(--cn-red-dot) 10%, transparent)}@media (prefers-reduced-motion:reduce){.session-log__chevron{transition:none}}.semantic-panel{border:1px solid var(--color-divider);border-radius:var(--radius-lg);background:var(--cn-panel);min-width:0;overflow:hidden;box-shadow:0 14px 34px #0000003d}.semantic-panel__header{min-height:4.25rem;padding:var(--space-4) var(--space-6);justify-content:space-between;align-items:center;gap:var(--space-6);border-bottom:1px solid var(--color-divider);background:color-mix(in srgb, var(--cn-surface-2) 82%, transparent);display:flex}.semantic-panel__header h2,.semantic-panel__header p{margin:0}.semantic-panel__header h2{font-size:1.05rem}.semantic-panel__eyebrow{color:var(--color-accent-400);letter-spacing:.04em;font-size:.68rem;font-weight:700;margin-block-end:var(--space-1)!important}.semantic-panel__clue{padding:var(--space-2) var(--space-4);border:1px solid var(--color-accent-700);background:color-mix(in srgb, var(--color-accent) 10%, transparent);color:var(--color-accent-200);border-radius:999px;font-size:.76rem}.semantic-panel__clue.is-empty{border-color:var(--color-divider);color:var(--cn-text-dim);background:0 0}.semantic-map__frame{min-height:28rem;padding:var(--space-4);background:var(--cn-board-void);place-items:center;display:grid;position:relative}.semantic-map{direction:rtl;width:min(100%,48rem);max-height:min(72vh,48rem);display:block;overflow:visible}.semantic-map__background{fill:var(--cn-map-bg);stroke:var(--color-divider);stroke-width:1.5px}.semantic-map__grid{fill:url(#semantic-grid);opacity:.56}.semantic-map__grid-line{fill:none;stroke:var(--cn-map-grid);stroke-width:1px}.semantic-map__target-line{stroke:var(--color-accent-500);stroke-width:2px;stroke-linecap:round;opacity:.7}.semantic-map__dot{cursor:pointer;outline:none}.semantic-map__dot-hit-area{fill:#0000;pointer-events:all}.semantic-map__dot-circle{fill:var(--cn-map-bg);stroke:var(--cn-role-dot);stroke-width:2.5px;transition:r .12s,filter .12s}.semantic-map__dot.is-target .semantic-map__dot-circle{fill:var(--cn-role-dot);filter:drop-shadow(0 0 8px var(--cn-role-tint))}.semantic-map__dot:hover .semantic-map__dot-circle,.semantic-map__dot:focus-visible .semantic-map__dot-circle,.semantic-map__dot.is-linked .semantic-map__dot-circle{filter:drop-shadow(0 0 7px var(--cn-role-dot))}.semantic-map__dot-shape{fill:var(--cn-role-dot);pointer-events:none;font-size:8px;font-weight:800}.semantic-map__dot.is-target .semantic-map__dot-shape{fill:var(--cn-map-bg)}.semantic-map__word-label{fill:var(--cn-role-text);font-family:var(--font-hebrew);paint-order:stroke;stroke:var(--cn-map-bg);stroke-width:4px;stroke-linejoin:round;pointer-events:none;font-size:12px;font-weight:700}.semantic-map__linked-ring{fill:none;stroke:var(--color-accent-300);stroke-width:2px;stroke-dasharray:3 3;pointer-events:none}.semantic-map__danger{fill:none;stroke:var(--cn-warn);stroke-width:2px;opacity:.9;transform-box:fill-box;transform-origin:50%;pointer-events:none;animation:1.8s ease-in-out infinite semantic-danger-pulse}.semantic-map__danger.is-assassin{stroke:var(--cn-red-dot);stroke-width:3.5px;stroke-dasharray:4 3}.semantic-map__hint{pointer-events:none}.semantic-map__hint-glow{fill:url(#semantic-hint-glow)}.semantic-map__hint-node{fill:var(--color-accent-300);stroke:var(--cn-map-hint);stroke-width:4px;filter:drop-shadow(0 0 9px var(--cn-map-hint-glow))}.semantic-map__hint-label{fill:var(--color-accent-100);font-family:var(--font-hebrew);paint-order:stroke;stroke:var(--cn-map-bg);stroke-width:5px;stroke-linejoin:round;font-size:21px;font-weight:800}.semantic-map__loading{justify-content:center;align-items:center;gap:var(--space-4);background:color-mix(in srgb, var(--cn-map-bg) 86%, transparent);color:var(--cn-text-muted);font-size:.8rem;display:flex;position:absolute;inset:0}.semantic-map__loading .cn-loading-spinner{width:1rem;height:1rem;position:static;transform:none}.semantic-map__tooltip{min-height:2.6rem;padding:var(--space-3) var(--space-4);align-items:center;gap:var(--space-3);border:1px solid color-mix(in srgb, var(--cn-role-ring) 75%, var(--color-divider));border-radius:var(--radius-md);background:color-mix(in srgb, var(--cn-sheet) 94%, transparent);box-shadow:var(--shadow-md);color:var(--cn-text);pointer-events:none;font-size:.76rem;display:flex;position:absolute;inset-block-end:var(--space-6);inset-inline-start:50%;transform:translate(50%)}.semantic-map__tooltip span{color:var(--cn-text-muted)}.semantic-map__legend{min-height:3rem;padding:var(--space-3) var(--space-6);align-items:center;gap:var(--space-3);border-top:1px solid var(--color-divider);color:var(--cn-text-muted);flex-wrap:wrap;font-size:.72rem;line-height:1.4;display:flex}.semantic-map__legend strong{color:var(--cn-text)}.semantic-map__legend-role{align-items:center;gap:var(--space-1);color:var(--cn-role-text);display:inline-flex}.semantic-map__legend-hintless{color:var(--cn-text-dim);flex-basis:100%}@keyframes semantic-danger-pulse{0%,to{opacity:.55;transform:scale(.94)}50%{opacity:1;transform:scale(1.08)}}@media (prefers-reduced-motion:reduce){.semantic-map__danger{animation:none}.semantic-map__dot-circle{transition:none}}@media (width<=900px){.semantic-map__frame{min-height:22rem;padding:var(--space-3)}.semantic-panel__header,.semantic-map__legend{padding-inline:var(--space-4)}}.photo-setup{gap:var(--space-8);flex-direction:column;width:min(100%,1240px);min-height:min(820px,100svh - 44.8px);display:flex}.photo-setup__header,.photo-setup__section-title,.photo-setup__key-tools,.photo-setup__counts,.photo-setup__key-upload,.photo-setup__manual-note{align-items:center;display:flex}.photo-setup__header{justify-content:space-between;gap:var(--space-8)}.photo-setup__header h1{font-size:clamp(1.65rem,3vw,2.35rem)}.photo-setup__header p,.photo-setup__section-title p,.photo-setup__uploads>p{color:var(--cn-text-muted)}.photo-setup__eyebrow{margin:0 0 var(--space-2);font-size:.75rem}.photo-setup__modes{border:1px solid var(--color-divider);border-radius:var(--radius-md);grid-template-columns:repeat(3,minmax(7.5rem,1fr));display:grid;overflow:hidden}.photo-setup__modes button{min-height:3rem;padding:0 var(--space-6);border:0;border-inline-start:1px solid var(--color-divider);color:var(--cn-text-muted);font:inherit;cursor:pointer;background:0 0}.photo-setup__modes button:last-child{border-inline-start:0}.photo-setup__modes button:hover:not(:disabled){background:color-mix(in srgb, var(--cn-text) 7%, transparent)}.photo-setup__modes button.is-active{color:var(--color-accent-300);background:color-mix(in srgb, var(--color-accent) 9%, transparent);box-shadow:inset 0 0 0 1px var(--color-accent)}.photo-setup__modes button:disabled{opacity:.55;cursor:wait}.photo-setup__body{gap:var(--space-8);grid-template-columns:minmax(0,2.25fr) minmax(18rem,.9fr);align-items:start;display:grid}.photo-setup__editor,.photo-setup__uploads{border:1px solid var(--color-divider);border-radius:var(--radius-lg);background:var(--cn-panel);padding:var(--space-8)}.photo-setup__section-title{justify-content:space-between;gap:var(--space-6);margin-bottom:var(--space-6)}.photo-setup__section-title h2,.photo-setup__uploads h2{font-size:1.15rem}.photo-setup__section-title p,.photo-setup__uploads>p{margin:0;font-size:.78rem}.photo-setup__key-status{color:var(--cn-warn);white-space:nowrap;font-size:.78rem}.photo-setup__key-status.is-valid{color:var(--cn-ok)}.photo-setup__word-grid{padding:var(--space-4);border-radius:var(--radius-lg);background:var(--cn-board-well);direction:rtl;grid-template-columns:repeat(5,minmax(0,1fr));gap:clamp(5px,.65vw,9px);display:grid}.photo-setup__word-cell{--setup-role:var(--cn-neutral-ring);--setup-face:var(--cn-card-face);--setup-icon:var(--cn-neutral-icon);aspect-ratio:1.5;border-radius:var(--radius-md);background:var(--setup-face);min-width:0;box-shadow:inset 0 0 0 2px var(--setup-role), inset 0 1.5px 0 #ffffff73, 0 2px 5px #0006;border:0;flex-direction:column;justify-content:flex-end;padding:clamp(5px,.65vw,8px);display:flex;position:relative;overflow:hidden}.photo-setup__word-cell.role-red{--setup-role:var(--cn-red-ring);--setup-face:var(--cn-red-grad);--setup-icon:var(--cn-red-text)}.photo-setup__word-cell.role-blue{--setup-role:var(--cn-blue-ring);--setup-face:var(--cn-blue-grad);--setup-icon:var(--cn-blue-text)}.photo-setup__word-cell.role-neutral{--setup-role:var(--cn-neutral-ring);--setup-icon:var(--cn-neutral-icon)}.photo-setup__word-cell.role-assassin{--setup-role:var(--cn-assassin-dot);--setup-face:var(--cn-assassin-grad);--setup-icon:var(--cn-assassin-dot)}.photo-setup__word-cell.is-low-confidence{box-shadow:0 0 0 2px var(--cn-warn)}.photo-setup__word-cell:focus-within{box-shadow:0 0 0 2px var(--color-accent)}.photo-setup__word-cell input{z-index:2;border:1px solid var(--cn-card-label-bd);border-radius:var(--radius-sm);background:var(--cn-card-label);width:100%;min-width:0;color:var(--cn-card-ink);font:inherit;text-align:center;direction:rtl;padding:3px 4px;font-size:clamp(.72rem,1vw,.9rem);font-weight:700;line-height:1.25;position:relative}.photo-setup__word-cell input:focus{outline:0}.photo-setup__word-hole{background:var(--cn-card-hole);border-radius:50%;width:7px;height:7px;position:absolute;top:5px;left:50%;transform:translate(-50%);box-shadow:inset 0 1px 2px #5a46288c}.photo-setup__word-mirror{color:var(--cn-card-mirror);text-align:center;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px;font-size:clamp(.5rem,.7vw,.66rem);font-weight:600;line-height:1;overflow:hidden;transform:rotate(180deg)}.photo-setup__word-cell.role-red .photo-setup__word-mirror,.photo-setup__word-cell.role-blue .photo-setup__word-mirror,.photo-setup__word-cell.role-assassin .photo-setup__word-mirror{color:#ffffff94}.photo-setup__word-role{z-index:3;border-radius:var(--radius-sm);width:1.45rem;height:1.45rem;color:var(--setup-icon);cursor:pointer;background:#0a0a0f1f;border:0;place-items:center;padding:0;display:grid;position:absolute;top:5px;left:6px}.photo-setup__word-role:hover{background:#0a0a0f3d}.photo-setup__validation{color:var(--cn-warn);margin:var(--space-3) 0 0;font-size:.82rem}.photo-setup__key-tools{justify-content:space-between;gap:var(--space-4);margin-bottom:var(--space-4)}.photo-setup__counts{gap:var(--space-4);color:var(--cn-text-muted);flex-wrap:wrap;font-size:.75rem}.photo-setup__counts span{align-items:center;gap:var(--space-2);display:inline-flex}.photo-setup__counts .role-red{color:var(--cn-red-text)}.photo-setup__counts .role-blue{color:var(--cn-blue-text)}.photo-setup__counts .role-neutral{color:var(--cn-neutral-text)}.photo-setup__confirm{margin-top:var(--space-6);min-height:2.75rem;margin-inline-start:auto}.photo-setup__uploads{transition:border-color .2s,box-shadow .2s}.photo-setup__uploads.is-emphasized{border-color:var(--color-accent-700);box-shadow:0 0 18px #968ae017}.photo-setup__drop-zone{min-height:12.5rem;margin-top:var(--space-6);border:1px dashed var(--color-neutral-700);border-radius:var(--radius-lg);justify-content:center;align-items:center;gap:var(--space-2);text-align:center;cursor:pointer;flex-direction:column;display:flex;overflow:hidden}.photo-setup__drop-zone:hover{border-color:var(--color-accent)}.photo-setup__drop-zone img{object-fit:cover;opacity:.65;width:100%;height:7.5rem}.photo-setup__drop-zone small,.photo-setup__key-upload small{color:var(--cn-text-dim)}.photo-setup__upload-icon{color:var(--cn-text-dim);font-size:2.3rem;line-height:1}.photo-setup__drop-zone input,.photo-setup__key-upload input{opacity:0;width:1px;height:1px;position:absolute}.photo-setup__ocr-status{align-items:center;gap:var(--space-3);min-height:2rem;color:var(--cn-text-muted);font-size:.72rem;display:flex}.photo-setup__skip{min-height:1.75rem;font-size:.68rem}.photo-setup__ocr-status.is-success,.photo-setup__ocr-status.is-ready{color:var(--cn-ok)}.photo-setup__ocr-status.is-error{color:var(--cn-warn)}.photo-setup__spinner{border:2px solid var(--color-neutral-700);border-top-color:var(--color-accent);border-radius:50%;width:1rem;height:1rem;animation:.8s linear infinite photo-spin}@keyframes photo-spin{to{transform:rotate(360deg)}}.photo-setup__key-upload{justify-content:space-between;gap:var(--space-3);padding:var(--space-4);border:1px solid var(--color-divider);border-radius:var(--radius-md);cursor:pointer;position:relative}.photo-setup__key-upload span{flex-direction:column;display:flex}.photo-setup__key-upload img{border-radius:var(--radius-sm);object-fit:cover;width:3rem;height:3rem}.photo-setup__manual-note{gap:var(--space-3);margin-top:var(--space-6);padding:var(--space-4);color:var(--color-accent-300);border:1px solid var(--color-accent-800);border-radius:var(--radius-md);background:color-mix(in srgb, var(--color-accent) 7%, transparent)}.photo-setup__manual-note p{margin:0;font-size:.78rem}.sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=960px){.photo-setup__body{grid-template-columns:1fr}.photo-setup__header{flex-direction:column;align-items:stretch}}@media (width<=620px){.photo-setup__word-grid{gap:4px;padding:6px}.photo-setup__word-cell input{padding:2px 3px;font-size:.72rem}.photo-setup__modes{grid-template-columns:repeat(3,1fr)}.photo-setup__modes button{min-width:0;padding:0 4px;font-size:.75rem}}@media (prefers-reduced-motion:reduce){.photo-setup__spinner{animation:none}}.setup-shell,.main-screen{background:var(--cn-app-bg);min-height:100svh;color:var(--cn-text)}.setup-shell{padding:var(--space-8);place-items:center;display:grid}.main-screen{flex-direction:column;display:flex}.main-screen__header{min-height:4.5rem;padding:var(--space-4) clamp(var(--space-6), 3vw, 2.5rem);border-bottom:1px solid var(--color-divider);background:var(--color-bg);justify-content:space-between;align-items:center;gap:var(--space-8);display:flex}.main-screen__header h1,.main-screen__header p{margin:0}.main-screen__header h1{font-size:1.1rem;font-weight:700}.main-screen__eyebrow,.main-screen__status{color:var(--cn-text-muted);font-size:.75rem}.main-screen__eyebrow{margin-bottom:var(--space-1)!important}.main-screen__header-end{align-items:center;gap:var(--space-4);display:flex}.main-screen__methods{color:var(--cn-text-muted);white-space:nowrap;border-bottom:1px solid;padding-bottom:1px;font-size:.75rem;text-decoration:none}.main-screen__methods:hover{filter:brightness(1.4);text-decoration:none}.main-screen__workspace{flex:1;grid-template-columns:minmax(0,2.25fr) minmax(19rem,.85fr);min-height:0;display:grid}.main-screen__primary{min-width:0;padding:clamp(var(--space-4), 2vw, var(--space-8));align-content:start;gap:var(--space-6);background:var(--cn-board-void);display:grid}.main-screen__controls{min-width:0;padding:var(--space-6);border-inline-start:1px solid var(--color-divider);background:var(--cn-surface-2);gap:var(--space-6);flex-direction:column;display:flex}.main-screen__tabs{border:1px solid var(--color-divider);border-radius:var(--radius-md);grid-template-columns:repeat(2,minmax(0,1fr));display:grid;overflow:hidden}.main-screen__tabs button{min-height:2.75rem;color:var(--cn-text-muted);font:inherit;cursor:pointer;background:0 0;border:0;font-weight:600}.main-screen__tabs button+button{border-inline-start:1px solid var(--color-divider)}.main-screen__tabs button.is-active{color:var(--color-accent-300);box-shadow:inset 0 -2px 0 var(--color-accent)}.main-screen__tabs button:focus-visible{outline:2px solid var(--color-accent);outline-offset:-2px}.main-screen__active-panel{min-height:12rem}@media (width<=900px){.main-screen__workspace{grid-template-columns:minmax(0,1fr)}.main-screen__controls{border-inline-start:0;border-top:1px solid var(--color-divider)}}:root{--color-bg:#161826;--color-surface:#232532;--color-text:#e9e9ed;--color-accent:#9184d9;--color-accent-2:#a7a1db;--color-divider:#e9e9ed29;--color-neutral-100:#f3f5fe;--color-neutral-200:#e4e7f5;--color-neutral-300:#cfd3e5;--color-neutral-400:#b2b6ca;--color-neutral-500:#9397ab;--color-neutral-600:#75798c;--color-neutral-700:#595d6c;--color-neutral-800:#3f424d;--color-neutral-900:#292b31;--color-accent-100:#f5f4ff;--color-accent-200:#e7e5fe;--color-accent-300:#d2cefd;--color-accent-400:#b5abfc;--color-accent-500:#968ae0;--color-accent-600:#796cbf;--color-accent-700:#5d5294;--color-accent-800:#423a6a;--color-accent-900:#2b2741;--color-accent-2-100:#f5f4ff;--color-accent-2-200:#e7e5fe;--color-accent-2-300:#d2cefd;--color-accent-2-400:#b5afe8;--color-accent-2-500:#9690c9;--color-accent-2-600:#7972a9;--color-accent-2-700:#5c5783;--color-accent-2-800:#423e5d;--color-accent-2-900:#2b293a;--color-section:#262a60;--color-section-glow:#353b80;--color-section-ghost:#4c5397;--font-heading:"Inter", system-ui, sans-serif;--font-heading-weight:500;--font-body:"Inter", system-ui, sans-serif;--space-1:2.8px;--space-2:5.6px;--space-3:8.4px;--space-4:11.2px;--space-6:16.8px;--space-8:22.4px;--radius-sm:4px;--radius-md:8px;--radius-lg:14px;--shadow-sm:0 0 0 1px #3f424d;--shadow-md:0 0 0 1px #595d6c, 0 6px 18px #0000008c;--shadow-lg:0 0 0 1px #9397ab, 0 16px 40px #000000a6}body{background:var(--color-bg);color:var(--color-text);font-family:var(--font-body)}h1,h2,h3,h4{font-family:var(--font-heading);font-weight:var(--font-heading-weight)}.lighten{mix-blend-mode:lighten;background-color:#0000}*,:before,:after{box-sizing:border-box}body{margin:0;font-size:15px;font-weight:400;line-height:1.55}h1,h2,h3,h4,h5,h6{font-family:var(--font-heading);font-weight:var(--font-heading-weight);letter-spacing:-.015em;margin:0 0 var(--space-2);line-height:1.12}h1{font-size:42px}h2{font-size:32px}h3{font-size:25px}h4{font-size:20px}h5{font-size:16px}h6{letter-spacing:.08em;text-transform:uppercase;font-size:13px}p{margin:0 0 var(--space-3)}a{color:var(--color-accent);text-underline-offset:3px}img{max-width:100%;display:block}figure{margin:0}figcaption{margin-top:var(--space-1);color:color-mix(in srgb, var(--color-text) 55%, transparent);font-size:11px}.text-muted{color:color-mix(in srgb, var(--color-text) 55%, transparent)}:focus{outline:none}:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}::selection{background:color-mix(in srgb, var(--color-accent) 30%, transparent)}.hr{height:1px;margin:var(--space-4) 0;background:linear-gradient(to right, transparent, var(--color-divider) 48px, var(--color-divider) calc(100% - 48px), transparent);border:0}.btn{cursor:pointer;font-family:var(--font-heading);font-weight:var(--font-heading-weight);color:var(--color-text);padding:var(--space-2) calc(var(--space-3) * 1.2);border-radius:var(--radius-md);background:0 0;border:1px solid #0000;justify-content:center;align-items:center;gap:6px;font-size:14px;line-height:1.2;text-decoration:none;display:inline-flex}.btn svg{display:block}.btn:disabled{opacity:.45;cursor:not-allowed}.btn-primary{color:var(--color-accent);border-color:var(--color-accent)}.btn-primary:hover{background:color-mix(in srgb, var(--color-accent) 12%, transparent)}.btn-primary:active{background:color-mix(in srgb, var(--color-accent) 22%, transparent)}.btn-secondary{border-color:var(--color-divider)}.btn-secondary:hover{background:color-mix(in srgb, var(--color-text) 7%, transparent)}.btn-secondary:active{background:color-mix(in srgb, var(--color-text) 14%, transparent)}.btn-ghost{color:var(--color-accent);padding-inline:var(--space-1)}.btn-ghost:hover{background:color-mix(in srgb, var(--color-accent) 10%, transparent)}.btn-ghost:active{background:color-mix(in srgb, var(--color-accent) 18%, transparent)}.btn-icon{width:36px;height:36px;padding:0}.btn-block{width:100%;margin-top:var(--space-2)}.field>label{color:color-mix(in srgb, var(--color-text) 70%, transparent);margin-bottom:5px;font-size:12px;display:block}.input{width:100%;min-height:36px;font:inherit;color:var(--color-text);caret-color:var(--color-accent);background:var(--color-surface);border:1px solid var(--color-divider);border-radius:var(--radius-md);padding:6px 10px;font-size:14px}.input:hover{border-color:color-mix(in srgb, var(--color-text) 45%, transparent)}.input:focus-visible{border-color:var(--color-accent);outline-offset:0}textarea.input{resize:vertical;min-height:90px}.radio{cursor:pointer;align-items:center;gap:8px;font-size:14px;display:inline-flex}.radio input,.seg-opt input{opacity:0;pointer-events:none;width:0;height:0;position:absolute}.radio .dot{border:1.5px solid var(--color-divider);border-radius:50%;flex:none;width:16px;height:16px}.radio:hover .dot{border-color:var(--color-accent)}.radio input:checked+.dot{border-color:var(--color-accent);background:var(--color-accent);box-shadow:inset 0 0 0 4px var(--color-bg)}.radio input:focus-visible+.dot{outline:2px solid var(--color-accent);outline-offset:2px}.seg{border:1px solid var(--color-divider);border-radius:var(--radius-md);display:inline-flex;overflow:hidden}.seg-opt{cursor:pointer;align-items:center;gap:6px;padding:7px 12px;font-size:13px;display:inline-flex}.seg-opt+.seg-opt{border-left:1px solid var(--color-divider)}.seg-opt:has(input:checked){color:var(--color-accent);box-shadow:inset 0 0 0 1px var(--color-accent)}.seg-opt:not(:has(input:checked)):hover{background:color-mix(in srgb, var(--color-text) 7%, transparent)}.seg-opt:has(input:focus-visible){outline:2px solid var(--color-accent);outline-offset:-2px}.card{gap:var(--space-2);padding:var(--space-3);border-radius:var(--radius-md);background:var(--color-surface);flex-direction:column;display:flex}.card-kicker{letter-spacing:.1em;text-transform:uppercase;color:var(--color-accent);font-size:10px}.card-title{font-family:var(--font-heading);font-weight:var(--font-heading-weight);font-size:17px;line-height:1.2}.card-body{opacity:.8;flex:1;margin:0;font-size:13px}.card-meta{color:color-mix(in srgb, var(--color-text) 50%, transparent);align-items:center;gap:6px;font-size:11px;display:flex}.elev-sm{box-shadow:var(--shadow-sm)}.elev-md{box-shadow:var(--shadow-md)}.elev-lg{box-shadow:var(--shadow-lg)}.tag{letter-spacing:.02em;border-radius:calc(var(--radius-md) * .75);align-items:center;padding:3px 10px;font-size:11px;display:inline-flex}.tag-accent{background:var(--color-accent-800);color:var(--color-accent-100)}.tag-accent-2{background:var(--color-accent-2-800);color:var(--color-accent-2-100)}.tag-neutral{background:var(--color-neutral-800);color:var(--color-neutral-100)}.tag-outline{border:1px solid var(--color-accent);color:var(--color-accent)}.nav{align-items:center;gap:var(--space-4);padding:var(--space-3) var(--space-4);border-bottom:none;display:flex}.nav-brand{font-family:var(--font-heading);font-weight:var(--font-heading-weight);margin-right:auto;font-size:18px}.nav a{color:inherit;font-size:14px;text-decoration:none}.nav a:hover,.nav a[aria-current=page]{color:var(--color-accent)}.table{border-collapse:collapse;width:100%;font-size:14px}.table th{text-align:left;letter-spacing:.08em;text-transform:uppercase;color:color-mix(in srgb, var(--color-text) 60%, transparent);padding:var(--space-2);border-bottom:1px solid #0000;font-size:11px}.table td{padding:var(--space-2);border-bottom:1px solid #0000}.table thead tr{background:linear-gradient(to right, transparent, var(--color-divider) 48px, var(--color-divider) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px}.table tbody tr{background:linear-gradient(to right, transparent, color-mix(in srgb, var(--color-text) 8%, transparent) 48px, color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px}.table tbody tr:hover{background:linear-gradient(color-mix(in srgb, var(--color-text) 4%, transparent), color-mix(in srgb, var(--color-text) 4%, transparent)) no-repeat 0 0 / 100% 100%, linear-gradient(to right, transparent, color-mix(in srgb, var(--color-text) 8%, transparent) 48px, color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px}.dialog-backdrop{padding:var(--space-4);background:color-mix(in srgb, var(--color-neutral-900) 50%, transparent);place-items:center;display:grid;position:fixed;inset:0}.dialog{gap:var(--space-3);width:min(440px,100%);padding:var(--space-4);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:var(--shadow-lg);flex-direction:column;display:flex}.dialog-title{font-family:var(--font-heading);font-weight:var(--font-heading-weight);font-size:20px}.dialog-body{opacity:.85;font-size:14px}.dialog-actions{justify-content:flex-end;gap:var(--space-2);margin-top:var(--space-2);display:flex}:root{--color-bg:#161826;--color-surface:#232532;--color-text:#e9e9ed;--color-accent:#9184d9;--color-divider:#e9e9ed29;--color-neutral-100:#f3f5fe;--color-neutral-200:#e4e7f5;--color-neutral-300:#cfd3e5;--color-neutral-400:#b2b6ca;--color-neutral-500:#9397ab;--color-neutral-600:#75798c;--color-neutral-700:#595d6c;--color-neutral-800:#3f424d;--color-neutral-900:#292b31;--color-accent-100:#f5f4ff;--color-accent-200:#e7e5fe;--color-accent-300:#d2cefd;--color-accent-400:#b5abfc;--color-accent-500:#968ae0;--color-accent-600:#796cbf;--color-accent-700:#5d5294;--color-accent-800:#423a6a;--color-accent-900:#2b2741;--font-heading:"Noto Sans Hebrew", "Inter", system-ui, sans-serif;--font-body:"Noto Sans Hebrew", "Inter", system-ui, sans-serif;--font-hebrew:"Noto Sans Hebrew", "Inter", system-ui, sans-serif;--space-1:2.8px;--space-2:5.6px;--space-3:8.4px;--space-4:11.2px;--space-6:16.8px;--space-8:22.4px;--radius-sm:4px;--radius-md:8px;--radius-lg:14px;--shadow-sm:0 0 0 1px #3f424d;--shadow-md:0 0 0 1px #595d6c, 0 6px 18px #0000008c;--shadow-lg:0 0 0 1px #9397ab, 0 16px 40px #000000a6;--cn-app-bg:#0e0f1a;--cn-board-well:#0e0f18;--cn-board-void:#0b0c13;--cn-panel:#14151f;--cn-surface-2:#1a1c2a;--cn-sheet:#1e2030;--cn-red-ring:#a85454;--cn-red-dot:#c46a6a;--cn-red-text:#ecc9c9;--cn-red-tint:#c46a6a2e;--cn-red-grad:linear-gradient(165deg,#a75050,#7c3a3a);--cn-red-shape:"◆";--cn-blue-ring:#4d7fb3;--cn-blue-dot:#5b8fc7;--cn-blue-text:#cfe2f5;--cn-blue-tint:#5b8fc72e;--cn-blue-grad:linear-gradient(165deg,#3d78b5,#2c5a86);--cn-blue-shape:"●";--cn-neutral-ring:#a08c62;--cn-neutral-dot:#b7a578;--cn-neutral-text:#ded2b5;--cn-neutral-icon:#8a744b;--cn-neutral-tint:#a08c6226;--cn-neutral-grad:linear-gradient(165deg,#d6c493,#bfa96f);--cn-neutral-shape:"−";--cn-assassin-ring:#3c3f4c;--cn-assassin-dot:#aeb2c4;--cn-assassin-icon:#454033;--cn-assassin-tint:#14141e47;--cn-assassin-grad:linear-gradient(165deg,#26272f,#0e0f15);--cn-assassin-shape:"☠";--cn-card-face:linear-gradient(160deg,#f6efdc 0%,#eee3c6 55%,#e0d2ae 100%);--cn-card-label:linear-gradient(180deg,#fffdf6,#f3edd9);--cn-card-label-bd:#c9ba92;--cn-card-ink:#2d2515;--cn-card-mirror:#6554307a;--cn-card-hole:#d8caa2;--cn-warn:#d9bd7a;--cn-warn-bg:#d9bd7a14;--cn-warn-bd:#d9bd7a66;--cn-ok:#7bbf8a;--cn-ok-strong:#5aa06a;--cn-select-ring:#968ae099;--cn-badge-bg:#968ae0;--cn-badge-ink:#161826;--cn-map-bg:#12131e;--cn-map-hint:#968ae0;--cn-map-hint-glow:#968ae059;--cn-map-grid:#1b2436;--cn-text:#e9e9ed;--cn-text-muted:#9397ab;--cn-text-dim:#75798c;--cn-text-faint:#5c6072}html{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background:var(--cn-app-bg)}body{background:var(--cn-app-bg);min-width:320px;min-height:100svh;color:var(--cn-text);font-family:var(--font-body)}button,input,textarea,select{font:inherit}button{-webkit-tap-highlight-color:transparent}#root{min-height:100svh}.role-red{--cn-role-ring:var(--cn-red-ring);--cn-role-dot:var(--cn-red-dot);--cn-role-text:var(--cn-red-text);--cn-role-tint:var(--cn-red-tint);--cn-role-gradient:var(--cn-red-grad);--cn-role-shape:var(--cn-red-shape)}.role-blue{--cn-role-ring:var(--cn-blue-ring);--cn-role-dot:var(--cn-blue-dot);--cn-role-text:var(--cn-blue-text);--cn-role-tint:var(--cn-blue-tint);--cn-role-gradient:var(--cn-blue-grad);--cn-role-shape:var(--cn-blue-shape)}.role-neutral{--cn-role-ring:var(--cn-neutral-ring);--cn-role-dot:var(--cn-neutral-dot);--cn-role-text:var(--cn-neutral-text);--cn-role-icon:var(--cn-neutral-icon);--cn-role-tint:var(--cn-neutral-tint);--cn-role-gradient:var(--cn-neutral-grad);--cn-role-shape:var(--cn-neutral-shape)}.role-assassin{--cn-role-ring:var(--cn-assassin-ring);--cn-role-dot:var(--cn-assassin-dot);--cn-role-text:var(--cn-text);--cn-role-icon:var(--cn-assassin-icon);--cn-role-tint:var(--cn-assassin-tint);--cn-role-gradient:var(--cn-assassin-grad);--cn-role-shape:var(--cn-assassin-shape)}.cn-button{min-height:36px;position:relative}.cn-button__content{justify-content:center;align-items:center;gap:var(--space-2);display:inline-flex}.cn-button[data-loading=true] .cn-button__content{visibility:hidden}.cn-loading-spinner{width:var(--space-4);height:var(--space-4);border:2px solid color-mix(in srgb, currentColor 28%, transparent);border-top-color:currentColor;border-radius:50%;animation:.7s linear infinite cn-spin;position:absolute;inset:50% auto auto 50%;transform:translate(-50%,-50%)}@keyframes cn-spin{to{transform:translate(-50%,-50%)rotate(1turn)}}.cn-panel{gap:var(--space-3);min-width:0}.cn-panel__header{justify-content:space-between;align-items:center;gap:var(--space-3);display:flex}.cn-panel__title{color:var(--cn-text);margin:0;font-size:16px;font-weight:600;line-height:1.3}.cn-panel__body{min-width:0}.cn-role-icon{width:1.25em;height:1.25em;color:var(--cn-role-icon,var(--cn-role-dot));font-family:var(--font-body);flex:none;place-items:center;font-size:1em;font-style:normal;font-weight:700;line-height:1;display:inline-grid}.cn-role-icon:before{content:var(--cn-role-shape)}.cn-tooltip{display:inline-flex;position:relative}.cn-tooltip__content{z-index:20;width:max-content;max-width:min(260px, calc(100vw - (2 * var(--space-4))));padding:var(--space-2) var(--space-3);border:1px solid var(--color-divider);border-radius:var(--radius-sm);background:var(--cn-sheet);box-shadow:var(--shadow-md);color:var(--cn-text);opacity:0;pointer-events:none;transform:translate(50%, var(--space-1));font-size:12px;line-height:1.45;transition:opacity .12s,transform .12s;position:absolute;inset-block-end:calc(100% + var(--space-2));inset-inline-start:50%}.cn-tooltip:hover .cn-tooltip__content,.cn-tooltip:focus-within .cn-tooltip__content{opacity:1;transform:translate(50%)}.cn-toast-viewport{z-index:100;width:min(380px, calc(100vw - (2 * var(--space-4))));pointer-events:none;position:fixed;inset-block-end:var(--space-4);inset-inline-start:var(--space-4)}.cn-toast{align-items:flex-start;gap:var(--space-3);padding:var(--space-3) var(--space-4);border:1px solid var(--color-divider);border-inline-start:3px solid var(--cn-toast-accent);border-radius:var(--radius-md);background:var(--cn-sheet);box-shadow:var(--shadow-lg);color:var(--cn-text);pointer-events:auto;animation:.16s ease-out both cn-toast-in;display:flex}.cn-toast--info{--cn-toast-accent:var(--color-accent)}.cn-toast--success{--cn-toast-accent:var(--cn-ok)}.cn-toast--error{--cn-toast-accent:var(--cn-red-dot)}.cn-toast__message{flex:1;margin:0;font-size:13px;line-height:1.5}.cn-toast__close{width:28px;height:28px;margin:calc(var(--space-1) * -1);border-radius:var(--radius-sm);color:var(--cn-text-muted);cursor:pointer;background:0 0;border:0;flex:none}.cn-toast__close:hover{background:color-mix(in srgb, var(--cn-text) 7%, transparent);color:var(--cn-text)}@keyframes cn-toast-in{0%{opacity:0;transform:translateY(var(--space-2))}}@media (prefers-reduced-motion:reduce){.cn-loading-spinner{animation:none}.cn-tooltip__content{transition:none}.cn-toast{animation:none}}
webapp/index.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="he" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#0e0f1a" />
7
+ <title>קודנהיימס קופיילוט</title>
8
+ <style>
9
+ html {
10
+ font-family: "Noto Sans Hebrew", "Inter", system-ui, sans-serif;
11
+ }
12
+ </style>
13
+ <script type="module" crossorigin src="/assets/index-LC1qePO9.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-w435VGSR.css">
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ </body>
19
+ </html>
webapp/mockServiceWorker.js ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable */
2
+ /* tslint:disable */
3
+
4
+ /**
5
+ * Mock Service Worker.
6
+ * @see https://github.com/mswjs/msw
7
+ * - Please do NOT modify this file.
8
+ */
9
+
10
+ const PACKAGE_VERSION = '2.15.0'
11
+ const INTEGRITY_CHECKSUM = '03cb67ac84128e63d7cd722a6e5b7f1e'
12
+ const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
+ const activeClientIds = new Set()
14
+
15
+ addEventListener('install', function () {
16
+ self.skipWaiting()
17
+ })
18
+
19
+ addEventListener('activate', function (event) {
20
+ event.waitUntil(self.clients.claim())
21
+ })
22
+
23
+ addEventListener('message', async function (event) {
24
+ const clientId = Reflect.get(event.source || {}, 'id')
25
+
26
+ if (!clientId || !self.clients) {
27
+ return
28
+ }
29
+
30
+ const client = await self.clients.get(clientId)
31
+
32
+ if (!client) {
33
+ return
34
+ }
35
+
36
+ const allClients = await self.clients.matchAll({
37
+ type: 'window',
38
+ })
39
+
40
+ switch (event.data) {
41
+ case 'KEEPALIVE_REQUEST': {
42
+ sendToClient(client, {
43
+ type: 'KEEPALIVE_RESPONSE',
44
+ })
45
+ break
46
+ }
47
+
48
+ case 'INTEGRITY_CHECK_REQUEST': {
49
+ sendToClient(client, {
50
+ type: 'INTEGRITY_CHECK_RESPONSE',
51
+ payload: {
52
+ packageVersion: PACKAGE_VERSION,
53
+ checksum: INTEGRITY_CHECKSUM,
54
+ },
55
+ })
56
+ break
57
+ }
58
+
59
+ case 'MOCK_ACTIVATE': {
60
+ activeClientIds.add(clientId)
61
+
62
+ sendToClient(client, {
63
+ type: 'MOCKING_ENABLED',
64
+ payload: {
65
+ client: {
66
+ id: client.id,
67
+ frameType: client.frameType,
68
+ },
69
+ },
70
+ })
71
+ break
72
+ }
73
+
74
+ case 'CLIENT_CLOSED': {
75
+ activeClientIds.delete(clientId)
76
+
77
+ const remainingClients = allClients.filter((client) => {
78
+ return client.id !== clientId
79
+ })
80
+
81
+ // Unregister itself when there are no more clients
82
+ if (remainingClients.length === 0) {
83
+ self.registration.unregister()
84
+ }
85
+
86
+ break
87
+ }
88
+ }
89
+ })
90
+
91
+ addEventListener('fetch', function (event) {
92
+ const requestInterceptedAt = Date.now()
93
+
94
+ // Bypass navigation requests.
95
+ if (event.request.mode === 'navigate') {
96
+ return
97
+ }
98
+
99
+ // Opening the DevTools triggers the "only-if-cached" request
100
+ // that cannot be handled by the worker. Bypass such requests.
101
+ if (
102
+ event.request.cache === 'only-if-cached' &&
103
+ event.request.mode !== 'same-origin'
104
+ ) {
105
+ return
106
+ }
107
+
108
+ // Bypass all requests when there are no active clients.
109
+ // Prevents the self-unregistered worked from handling requests
110
+ // after it's been terminated (still remains active until the next reload).
111
+ if (activeClientIds.size === 0) {
112
+ return
113
+ }
114
+
115
+ const requestId = crypto.randomUUID()
116
+ event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
117
+ })
118
+
119
+ /**
120
+ * @param {FetchEvent} event
121
+ * @param {string} requestId
122
+ * @param {number} requestInterceptedAt
123
+ */
124
+ async function handleRequest(event, requestId, requestInterceptedAt) {
125
+ const client = await resolveMainClient(event)
126
+ const requestCloneForEvents = event.request.clone()
127
+ const response = await getResponse(
128
+ event,
129
+ client,
130
+ requestId,
131
+ requestInterceptedAt,
132
+ )
133
+
134
+ // Send back the response clone for the "response:*" life-cycle events.
135
+ // Ensure MSW is active and ready to handle the message, otherwise
136
+ // this message will pend indefinitely.
137
+ if (client && activeClientIds.has(client.id)) {
138
+ const serializedRequest = await serializeRequest(requestCloneForEvents)
139
+
140
+ // Omit the body of server-sent event stream responses.
141
+ // Cloning such responses would prevent client-side stream cancelations
142
+ // from reaching the original stream (a teed stream only cancels its
143
+ // source once both of its branches cancel) and would buffer the
144
+ // entire stream into the unconsumed clone indefinitely.
145
+ const isEventStreamResponse = response.headers
146
+ .get('content-type')
147
+ ?.toLowerCase()
148
+ .startsWith('text/event-stream')
149
+
150
+ // Clone the response so both the client and the library could consume it.
151
+ const responseClone = isEventStreamResponse ? null : response.clone()
152
+
153
+ sendToClient(
154
+ client,
155
+ {
156
+ type: 'RESPONSE',
157
+ payload: {
158
+ isMockedResponse: IS_MOCKED_RESPONSE in response,
159
+ request: {
160
+ id: requestId,
161
+ ...serializedRequest,
162
+ },
163
+ response: {
164
+ type: response.type,
165
+ status: response.status,
166
+ statusText: response.statusText,
167
+ headers: Object.fromEntries(response.headers.entries()),
168
+ body: responseClone ? responseClone.body : null,
169
+ },
170
+ },
171
+ },
172
+ responseClone && responseClone.body
173
+ ? [serializedRequest.body, responseClone.body]
174
+ : [],
175
+ )
176
+ }
177
+
178
+ return response
179
+ }
180
+
181
+ /**
182
+ * Resolve the main client for the given event.
183
+ * Client that issues a request doesn't necessarily equal the client
184
+ * that registered the worker. It's with the latter the worker should
185
+ * communicate with during the response resolving phase.
186
+ * @param {FetchEvent} event
187
+ * @returns {Promise<Client | undefined>}
188
+ */
189
+ async function resolveMainClient(event) {
190
+ const client = await self.clients.get(event.clientId)
191
+
192
+ if (activeClientIds.has(event.clientId)) {
193
+ return client
194
+ }
195
+
196
+ if (client?.frameType === 'top-level') {
197
+ return client
198
+ }
199
+
200
+ const allClients = await self.clients.matchAll({
201
+ type: 'window',
202
+ })
203
+
204
+ return allClients
205
+ .filter((client) => {
206
+ // Get only those clients that are currently visible.
207
+ return client.visibilityState === 'visible'
208
+ })
209
+ .find((client) => {
210
+ // Find the client ID that's recorded in the
211
+ // set of clients that have registered the worker.
212
+ return activeClientIds.has(client.id)
213
+ })
214
+ }
215
+
216
+ /**
217
+ * @param {FetchEvent} event
218
+ * @param {Client | undefined} client
219
+ * @param {string} requestId
220
+ * @param {number} requestInterceptedAt
221
+ * @returns {Promise<Response>}
222
+ */
223
+ async function getResponse(event, client, requestId, requestInterceptedAt) {
224
+ // Clone the request because it might've been already used
225
+ // (i.e. its body has been read and sent to the client).
226
+ const requestClone = event.request.clone()
227
+
228
+ function passthrough() {
229
+ // Cast the request headers to a new Headers instance
230
+ // so the headers can be manipulated with.
231
+ const headers = new Headers(requestClone.headers)
232
+
233
+ // Remove the "accept" header value that marked this request as passthrough.
234
+ // This prevents request alteration and also keeps it compliant with the
235
+ // user-defined CORS policies.
236
+ const acceptHeader = headers.get('accept')
237
+ if (acceptHeader) {
238
+ const values = acceptHeader.split(',').map((value) => value.trim())
239
+ const filteredValues = values.filter(
240
+ (value) => value !== 'msw/passthrough',
241
+ )
242
+
243
+ if (filteredValues.length > 0) {
244
+ headers.set('accept', filteredValues.join(', '))
245
+ } else {
246
+ headers.delete('accept')
247
+ }
248
+ }
249
+
250
+ return fetch(requestClone, { headers })
251
+ }
252
+
253
+ // Bypass mocking when the client is not active.
254
+ if (!client) {
255
+ return passthrough()
256
+ }
257
+
258
+ // Bypass initial page load requests (i.e. static assets).
259
+ // The absence of the immediate/parent client in the map of the active clients
260
+ // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
261
+ // and is not ready to handle requests.
262
+ if (!activeClientIds.has(client.id)) {
263
+ return passthrough()
264
+ }
265
+
266
+ // Notify the client that a request has been intercepted.
267
+ const serializedRequest = await serializeRequest(event.request)
268
+ const clientMessage = await sendToClient(
269
+ client,
270
+ {
271
+ type: 'REQUEST',
272
+ payload: {
273
+ id: requestId,
274
+ interceptedAt: requestInterceptedAt,
275
+ ...serializedRequest,
276
+ },
277
+ },
278
+ [serializedRequest.body],
279
+ )
280
+
281
+ switch (clientMessage.type) {
282
+ case 'MOCK_RESPONSE': {
283
+ return respondWithMock(clientMessage.data)
284
+ }
285
+
286
+ case 'PASSTHROUGH': {
287
+ return passthrough()
288
+ }
289
+ }
290
+
291
+ return passthrough()
292
+ }
293
+
294
+ /**
295
+ * @param {Client} client
296
+ * @param {any} message
297
+ * @param {Array<Transferable>} transferrables
298
+ * @returns {Promise<any>}
299
+ */
300
+ function sendToClient(client, message, transferrables = []) {
301
+ return new Promise((resolve, reject) => {
302
+ const channel = new MessageChannel()
303
+
304
+ channel.port1.onmessage = (event) => {
305
+ if (event.data && event.data.error) {
306
+ return reject(event.data.error)
307
+ }
308
+
309
+ resolve(event.data)
310
+ }
311
+
312
+ client.postMessage(message, [
313
+ channel.port2,
314
+ ...transferrables.filter(Boolean),
315
+ ])
316
+ })
317
+ }
318
+
319
+ /**
320
+ * @param {Response} response
321
+ * @returns {Response}
322
+ */
323
+ function respondWithMock(response) {
324
+ // Setting response status code to 0 is a no-op.
325
+ // However, when responding with a "Response.error()", the produced Response
326
+ // instance will have status code set to 0. Since it's not possible to create
327
+ // a Response instance with status code 0, handle that use-case separately.
328
+ if (response.status === 0) {
329
+ return Response.error()
330
+ }
331
+
332
+ const mockedResponse = new Response(response.body, response)
333
+
334
+ Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
335
+ value: true,
336
+ enumerable: true,
337
+ })
338
+
339
+ return mockedResponse
340
+ }
341
+
342
+ /**
343
+ * @param {Request} request
344
+ */
345
+ async function serializeRequest(request) {
346
+ return {
347
+ url: request.url,
348
+ mode: request.mode,
349
+ method: request.method,
350
+ headers: Object.fromEntries(request.headers.entries()),
351
+ cache: request.cache,
352
+ credentials: request.credentials,
353
+ destination: request.destination,
354
+ integrity: request.integrity,
355
+ redirect: request.redirect,
356
+ referrer: request.referrer,
357
+ referrerPolicy: request.referrerPolicy,
358
+ body: await request.arrayBuffer(),
359
+ keepalive: request.keepalive,
360
+ }
361
+ }