implicant 0.7.0__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {implicant-0.7.0 → implicant-0.7.2}/PKG-INFO +1 -1
  2. {implicant-0.7.0 → implicant-0.7.2}/pyproject.toml +1 -1
  3. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/copy.py +100 -41
  4. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/app.css +58 -25
  5. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/app.js +45 -11
  6. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/index.html +13 -3
  7. {implicant-0.7.0 → implicant-0.7.2}/.dockerignore +0 -0
  8. {implicant-0.7.0 → implicant-0.7.2}/.github/workflows/publish-pypi.yml +0 -0
  9. {implicant-0.7.0 → implicant-0.7.2}/.github/workflows/release.yml +0 -0
  10. {implicant-0.7.0 → implicant-0.7.2}/.gitignore +0 -0
  11. {implicant-0.7.0 → implicant-0.7.2}/LICENSE +0 -0
  12. {implicant-0.7.0 → implicant-0.7.2}/README.md +0 -0
  13. {implicant-0.7.0 → implicant-0.7.2}/docker/Dockerfile +0 -0
  14. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/__init__.py +0 -0
  15. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/binarize.py +0 -0
  16. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/cli/__init__.py +0 -0
  17. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/cli/main.py +0 -0
  18. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/client.py +0 -0
  19. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/config.py +0 -0
  20. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/README.txt +0 -0
  21. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L6-3631b8781e3a/public_key.bin +0 -0
  22. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L6-3631b8781e3a/secret_key.bin +0 -0
  23. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L7-a394f2ee7032/public_key.bin +0 -0
  24. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L7-a394f2ee7032/secret_key.bin +0 -0
  25. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L8-5f657f27f77c/public_key.bin +0 -0
  26. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/demo_keys/bgv-n16384-L8-5f657f27f77c/secret_key.bin +0 -0
  27. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/envelope.py +0 -0
  28. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/keys.py +0 -0
  29. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/manifest.py +0 -0
  30. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/models.py +0 -0
  31. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/postprocess.py +0 -0
  32. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/samples/bankfraud.json +0 -0
  33. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/samples/cancer.json +0 -0
  34. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/samples/credit.json +0 -0
  35. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/samples/diabetes.json +0 -0
  36. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/samples/heartdisease.json +0 -0
  37. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/transport/__init__.py +0 -0
  38. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/transport/base.py +0 -0
  39. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/transport/errors.py +0 -0
  40. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/transport/http.py +0 -0
  41. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/transport/mock.py +0 -0
  42. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/README.md +0 -0
  43. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/__init__.py +0 -0
  44. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/ceremony_meta.json +0 -0
  45. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/events.py +0 -0
  46. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/server.py +0 -0
  47. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/service.py +0 -0
  48. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/frieza_fire.png +0 -0
  49. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/frieza_idle.png +0 -0
  50. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/frieza_point.png +0 -0
  51. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/goku_charge.png +0 -0
  52. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/goku_fire.png +0 -0
  53. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fanart/goku_stance.png +0 -0
  54. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/FONTS.md +0 -0
  55. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2 +0 -0
  56. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/ibm-plex-mono/IBMPlexMono-Medium.woff2 +0 -0
  57. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2 +0 -0
  58. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/ibm-plex-mono/OFL.txt +0 -0
  59. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/inter/Inter-Bold.woff2 +0 -0
  60. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/fonts/inter/OFL.txt +0 -0
  61. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/logo.png +0 -0
  62. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/models/bankfraud.png +0 -0
  63. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/models/cancer.png +0 -0
  64. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/models/credit.png +0 -0
  65. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/models/diabetes.png +0 -0
  66. {implicant-0.7.0 → implicant-0.7.2}/src/implicant/ui/static/models/heartdisease.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: implicant
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Python client SDK for the Implicant FHE inference platform
5
5
  Author: Implicant
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "implicant"
7
- version = "0.7.0"
7
+ version = "0.7.2"
8
8
  description = "Python client SDK for the Implicant FHE inference platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,8 +1,12 @@
1
1
  """The ONLY place user-facing UI copy lives (spec §6.6).
2
2
 
3
- Policy (enforced by tests/test_ui_copy.py): in demo mode no string may CLAIM
4
- privacy. The act-2 DISCLOSURE — which negates privacy — is the single place
5
- privacy is mentioned at all. Crypto vocabulary is welcome here: this
3
+ Policy (enforced by tests/test_ui_copy.py): the specific overclaim PATTERNS
4
+ in that test's BANNED_CLAIMS stay banned everywhere. The Technology-page
5
+ intro (owner's copy, verbatim, 2026-09-04) makes the PRODUCT's
6
+ confidentiality claims — that supersedes the older blanket "no privacy
7
+ claims" reading of §6.6 — while the DISCLOSURE still negates privacy for
8
+ this demo specifically (shared demonstration key) and is now visible from
9
+ before the ceremony even runs. Crypto vocabulary is welcome here: this
6
10
  audience came to see the cryptography.
7
11
  """
8
12
  from __future__ import annotations
@@ -30,6 +34,11 @@ __all__ = [
30
34
  "ACT_TITLES",
31
35
  "INTRO_TITLE",
32
36
  "INTRO_PARAS",
37
+ "INTRO_FLOW",
38
+ "INTRO_OUTRO",
39
+ "INTRO_CONTACT_LEAD",
40
+ "INTRO_PANELS",
41
+ "MODEL_NAMES",
33
42
  "FHE_FIG_CLIENT",
34
43
  "FHE_FIG_SERVER",
35
44
  "FHE_FIG_CAPTION",
@@ -80,7 +89,6 @@ __all__ = [
80
89
  "BOOT_INCOMPLETE",
81
90
  "SERVER_GONE",
82
91
  "SESSION_STALE",
83
- "RESULT_SCORES",
84
92
  "RESULT_LABEL_FALLBACK",
85
93
  "REQUEST_FAILED",
86
94
  "CIPHER_BYTES",
@@ -160,36 +168,72 @@ STAGE_LABELS: dict[str, str] = {
160
168
  "reading": "Decrypting the result…",
161
169
  }
162
170
 
163
- # Act 2: the single sanctioned mention of privacy (spec §6, verbatim).
171
+ # The demo-honesty note (spec §6). Since 2026-09-04 (owner's direction) it
172
+ # shows BEFORE the ceremony has run, so the wording is tense-neutral: it may
173
+ # not presuppose that keys exist yet.
164
174
  DISCLOSURE = (
165
- "These keys were really generated on this machine, just now — but this is "
166
- "a demonstration: predictions will run on a shared demonstration key that "
167
- "ships with the software, so they are not private. The keys you just made "
168
- "are set aside."
175
+ "This is a demonstration: predictions run on a shared demonstration key "
176
+ "that ships with the software, so they are not private. Any keys you "
177
+ "generate here are really made on this machine, just now — and then "
178
+ "set aside."
169
179
  )
170
180
 
171
- # The home-screen intro, above the Choose-a-model title. Original wording
172
- # (inspiration acknowledged, not copied). §6.6 rule: it tells the story of
173
- # the MECHANISM — encrypt, compute on ciphertext, decrypt — and attributes
174
- # the promise to the technology, never claiming privacy for this demo.
175
- INTRO_TITLE = "Computing on encrypted data"
181
+ # The Technology-page intro. Owner's copy VERBATIM (2026-09-04 rewrite,
182
+ # second pass): three paragraphs, then the pipeline line, then the
183
+ # "At Implicant" paragraph (INTRO_OUTRO) — all in the hero's left column.
184
+ # Below the hero, the four INTRO_PANELS, then a centered line where
185
+ # INTRO_CONTACT_LEAD is plain text and ONLY the Contact us button beside
186
+ # it is clickable — the page reuses the header CTA's label
187
+ # (NAV_TABS["contact"]) and target (NAV_LINKS["contact"]) so the two
188
+ # buttons can never drift apart. The demo-honesty caveat lives in
189
+ # DISCLOSURE, permanently visible on the Models page.
190
+ INTRO_TITLE = "Run AI on Sensitive Data. Keep It Encrypted."
176
191
  INTRO_PARAS = (
177
- "Encryption is the internet's everyday lock. Bank sessions, messages "
178
- "and backups travel as ciphertext, bytes that carry no meaning "
179
- "without the matching key. But a lock has one built-in limitation: to "
180
- "do anything useful with what it protects, someone has to open it "
181
- "first.",
182
- "Laboratories met a similar problem long ago and answered it with the "
183
- "glovebox: hands work on a sample through sealed gloves, and the "
184
- "chamber never opens. Fully homomorphic encryption (FHE) is the "
185
- "mathematical counterpart. Additions and multiplications reach "
186
- "through the seal, operating on the ciphertext itself instead of on "
187
- "the data inside.",
188
- "That one property changes what a service can be. The models in this "
189
- "demonstration run exactly that way: the platform receives "
190
- "ciphertext, computes on ciphertext, returns ciphertext, and the "
191
- "result becomes readable back on this machine. Pick a model below "
192
- "and run the whole path yourself.",
192
+ "Encryption is the internet's everyday lock. Bank sessions, messages, "
193
+ "files, and backups are encrypted into ciphertext — data that carries "
194
+ "no meaning without the matching key.",
195
+ "But traditional encryption has a fundamental limitation: to do "
196
+ "anything useful with encrypted data, it normally has to be decrypted "
197
+ "first. This means that even if your data is protected in transit and "
198
+ "at rest, it becomes exposed while it is being processed.",
199
+ "Fully Homomorphic Encryption (FHE) changes this. It allows "
200
+ "computations — including AI inference — to be performed directly on "
201
+ "encrypted data, without ever converting it back to plaintext.",
202
+ )
203
+ INTRO_FLOW = "Encrypt locally → Compute while encrypted → Decrypt the result locally"
204
+ INTRO_OUTRO = (
205
+ "At Implicant, we use FHE to build AI models that can operate on your "
206
+ "data while it remains encrypted throughout the entire computation. "
207
+ "Your data is encrypted before it is sent to the server, and neither "
208
+ "Implicant nor the server ever receives, sees, or decrypts it."
209
+ )
210
+ INTRO_CONTACT_LEAD = (
211
+ "If you want to train, deploy, or use AI models while keeping your "
212
+ "data private,"
213
+ )
214
+
215
+ # The four capability panels under the hero (owner's request 2026-09-04,
216
+ # zama.org card-row reference): title + one short sentence each. The
217
+ # accuracy figure is MEASURED, not marketing: artifacts/*/benchmark.md
218
+ # (same held-out folds) has the rule models above XGBoost's AUC on
219
+ # bankfraud (0.8829 vs 0.8490, 71 vs 10,983 rules -> ~150x), diabetes,
220
+ # credit and heartdisease, and statistically level on cancer (served
221
+ # bundle 0.9884 vs 0.9924 on n=114 — hence "matches or beats", owner-
222
+ # approved wording 2026-09-04). Cross-checked against the served bundles
223
+ # in splits/ACCURACY.md.
224
+ INTRO_PANELS: tuple[tuple[str, str], ...] = (
225
+ ("Sparse truth-table rules",
226
+ "AI distilled into a few dozen sparse truth-table rules — compact, "
227
+ "auditable logic instead of millions of opaque weights."),
228
+ ("Accuracy that holds up",
229
+ "Matches or beats XGBoost across all five demo benchmarks — e.g. "
230
+ "0.88 vs 0.85 AUC on fraud screening, with about 150× fewer rules."),
231
+ ("FHE on BGV",
232
+ "Inference runs under fully homomorphic encryption (BGV): the server "
233
+ "computes on ciphertext and returns ciphertext, end to end."),
234
+ ("Quantum safe",
235
+ "The encryption is lattice-based (Ring-LWE) — the same mathematics "
236
+ "behind post-quantum standards, with no known quantum attack."),
193
237
  )
194
238
 
195
239
  # The decorative FHE explainer figure beside the intro (act 1). Educational
@@ -239,12 +283,22 @@ FHE_FIG_RECORD_POOLS: dict[str, tuple[str, ...]] = {
239
283
  "blood": ("A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"),
240
284
  }
241
285
 
286
+ # act1/act2 are numbered (owner, 2026-09-04): the ceremony no longer GATES
287
+ # the model list — the numbers name the suggested order instead of a lock.
242
288
  ACT_TITLES: dict[str, str] = {
243
289
  "access": "Connect to the platform",
244
- "act1": "Choose a model",
245
- "act2": "Generate your keys",
246
- "act3": "Data to encrypt",
247
- "act4": "The blind result",
290
+ "act1": "2. Choose a model",
291
+ "act2": "1. Generate your keys",
292
+ "act3": "Input data",
293
+ "act4": "Result",
294
+ }
295
+
296
+ # Display-name overrides for model slugs (owner, 2026-09-04): the page's
297
+ # default is capitalize(slug); a slug listed here renders as its curated
298
+ # name everywhere a name shows (cards, act-3 title, history). The wire
299
+ # keeps raw slugs.
300
+ MODEL_NAMES: dict[str, str] = {
301
+ "heartdisease": "Heart disease",
248
302
  }
249
303
 
250
304
  # Act 3 titles itself with the model's name + icon (2026-09-01 — the old
@@ -542,8 +596,12 @@ BITGRID_TRUNCATED = "Showing the first {shown} of {total} encoded slots."
542
596
 
543
597
  # Act 4 (spec §6): the byte-count phrasing this replaced (D2) lost a race
544
598
  # against the POST (measured 7/10) and rendered empty most runs — this needs
545
- # no response-timing data, so it is always present.
546
- RESULT_DECRYPTED = "Decrypted on this machine, from the ciphertext the server sent back."
599
+ # no response-timing data, so it is always present. Wording is the owner's
600
+ # verbatim hammer line (2026-09-04).
601
+ RESULT_DECRYPTED = (
602
+ "The input was encrypted on your device, processed by Implicant without "
603
+ "decryption, and the result was decrypted locally on your device."
604
+ )
547
605
 
548
606
  ACCESS_PROMPT = (
549
607
  "Paste the access key you were given. It stays in this process's memory "
@@ -641,9 +699,9 @@ SESSION_STALE = (
641
699
  # pending -> done, retrospectively, once the encrypted response is back
642
700
  # (ADD-EXEC-5/6).
643
701
  EXEC_ZONE_LABELS: dict[str, str] = {
644
- "local": "Participant's computer",
702
+ "local": "Your device",
645
703
  "api": "API round trip",
646
- "platform": "Implicant platform",
704
+ "platform": "Implicant platform computes on encrypted data",
647
705
  }
648
706
 
649
707
  EXEC_STEP_LABELS: dict[str, str] = {
@@ -727,8 +785,8 @@ RECAP_TITLE = "ENCRYPTED PREDICTION COMPLETE"
727
785
  # Act-4 replay of the act-3 encryption panel (user request 2026-08-26): the
728
786
  # round trip can be too fast to inspect mid-run, so the recap offers the
729
787
  # SAME observed bytes again behind a toggle. Labels flip with the state.
730
- RECAP_CIPHER_SHOW = "View this run's encrypted request"
731
- RECAP_CIPHER_HIDE = "Hide the encrypted request"
788
+ RECAP_CIPHER_SHOW = "View encrypted payload"
789
+ RECAP_CIPHER_HIDE = "Hide encrypted payload"
732
790
  # Participant wording, not API wording (ADD-RECAP-3).
733
791
  RECAP_SIZE_LABELS: dict[str, str] = {
734
792
  "payload_bytes": "Encrypted input sent",
@@ -783,7 +841,8 @@ def format_expansion_ratio(payload_bytes: int, encoded_bytes: int) -> str:
783
841
  # The key id is deliberately NOT shown (product owner, 2026-08-26 — same
784
842
  # rule as act 2: nothing hinting at the model's parameters). ADD-RECAP-1
785
843
  # only says key_id MAY remain as secondary detail, so dropping it complies.
786
- RESULT_SCORES = "signed class scores: [{scores}]"
844
+ # The signed-class-scores line is gone too (owner, 2026-09-04): the decided
845
+ # label + the hammer line carry act 4; raw logits read as noise there.
787
846
  RESULT_LABEL_FALLBACK = "class {index}"
788
847
  REQUEST_FAILED = "The request failed ({status}). Please try again."
789
848
  CIPHER_BYTES = "{bytes} bytes"
@@ -213,12 +213,15 @@ input.invalid { border-color: var(--err); }
213
213
  /* Two animations: the cascade's rise AND the frame pulse — this selector
214
214
  outranks bare .fhe-fig, so the pulse MUST ride here or it never runs
215
215
  (root cause of the invisible bounce, live review). fig-pulse is listed
216
- last so it owns `transform`; rise keeps opacity+blur. */
217
- #act1 .fhe-fig {
216
+ last so it owns `transform`; rise keeps opacity+blur. Scoped #tech, not
217
+ #act1: the figure moved to the Technology page with the 2026-09-01 tab
218
+ split and the stale #act1 scope had silently dropped the entrance rise
219
+ (the base .fhe-fig rule kept the pulse and hover alive). */
220
+ #tech .fhe-fig {
218
221
  animation: rise .35s var(--ease-settle) 80ms both,
219
222
  fig-pulse 2.6s ease-in-out infinite;
220
223
  }
221
- #act1 .fhe-fig:hover, #act1 .fhe-fig:focus-visible {
224
+ #tech .fhe-fig:hover, #tech .fhe-fig:focus-visible {
222
225
  animation: rise .35s var(--ease-settle) 80ms both;
223
226
  transform: scale(1.05);
224
227
  }
@@ -228,15 +231,57 @@ input.invalid { border-color: var(--err); }
228
231
  animation-delay: calc(160ms + var(--i, 0) * 40ms);
229
232
  }
230
233
 
231
- /* Home-screen intro: the encryption story (left half) beside the animated
232
- FHE figure (right half); stacks on narrow windows. */
234
+ /* Tech-page hero (owner, 2026-09-04, zama.org reference): a centered
235
+ one-line title over the whole hero, then the left-aligned story beside
236
+ the animated FHE figure, the two halves vertically centered against
237
+ each other. Generous air; stacks on narrow windows. */
233
238
  .intro-grid {
234
- display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 4vw, 4.5rem);
239
+ display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2.5rem, 5vw, 6rem);
235
240
  align-items: center; margin-bottom: 2.2rem;
236
241
  }
237
242
  @media (max-width: 46rem) { .intro-grid { grid-template-columns: 1fr; } }
238
- #intro-paras p { color: var(--ink); margin: 0 0 .8rem; }
243
+ /* Centered, sized against the viewport so the sentence holds ONE line on
244
+ desktop widths (it may wrap on phones — nowrap would overflow there);
245
+ the bottom margin is the owner's asked-for title-to-body gap. */
246
+ #intro-title {
247
+ text-align: center; font-size: clamp(1.6rem, 3.2vw, 2.6rem);
248
+ line-height: 1.15;
249
+ margin: clamp(1rem, 3vh, 2.2rem) 0 clamp(2.2rem, 6vh, 4rem);
250
+ }
251
+ #intro-paras p { color: var(--ink); line-height: 1.65; margin: 0 0 1.1rem; }
239
252
  #intro-paras p:last-child { margin-bottom: 0; }
253
+ /* Owner's copy 2026-09-04: the pipeline line reads as its own strip; the
254
+ closing lead is plain text + the SAME .nav-cta pill as the header's
255
+ Contact button — only the button is clickable. */
256
+ #intro-paras .intro-flow {
257
+ font-weight: 600; margin: 1.6rem 0;
258
+ }
259
+ /* The four capability panels under the hero (owner, 2026-09-04, zama.org
260
+ card-row reference): one row of four on desktop, 2x2 on middling
261
+ widths, stacked on phones. */
262
+ #intro-panels {
263
+ display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem;
264
+ margin: clamp(1.6rem, 4vh, 3rem) 0 0;
265
+ animation: rise .35s var(--ease-settle) both; animation-delay: 120ms;
266
+ }
267
+ @media (max-width: 64rem) { #intro-panels { grid-template-columns: 1fr 1fr; } }
268
+ @media (max-width: 34rem) { #intro-panels { grid-template-columns: 1fr; } }
269
+ #intro-panels .panel {
270
+ background: var(--panel); border-radius: .6rem; box-shadow: var(--ring);
271
+ padding: 1.15rem 1.25rem;
272
+ }
273
+ #intro-panels .panel h3 { margin: 0 0 .45rem; font-size: 1rem; }
274
+ #intro-panels .panel p {
275
+ margin: 0; color: var(--dim); font-size: .88rem; line-height: 1.55;
276
+ }
277
+ /* The centered contact row below the panels: plain lead text + the SAME
278
+ .nav-cta pill as the header's Contact button. */
279
+ #intro-contact {
280
+ display: flex; flex-wrap: wrap; justify-content: center;
281
+ align-items: center; gap: .8rem;
282
+ margin: clamp(1.8rem, 4vh, 3rem) 0 1rem;
283
+ animation: rise .35s var(--ease-settle) both; animation-delay: 200ms;
284
+ }
240
285
 
241
286
  /* ---- the FHE explainer figure ----
242
287
  Decorative and educational ONLY: aria-hidden SVG, never bound to live
@@ -250,12 +295,12 @@ input.invalid { border-color: var(--err); }
250
295
  slightly on the floating 1011's rhythm (2.6s ease-in-out); hovering
251
296
  (or focusing) settles it smoothly into the swollen form. */
252
297
  .fhe-fig {
253
- /* The PLAYGROUND grew, not the items (live review 2026-08-28,
254
- superseding the *1.25): the viewBox widened 360 -> 400 units and the
255
- container follows at the ORIGINAL item scale (31rem/360 = 34.5rem/400),
256
- so the wide fire poses fit whole while everything renders at its
257
- pre-enlargement size. */
258
- margin: 0; justify-self: center; cursor: default; max-width: 34.5rem; width: 100%;
298
+ /* History: the 2026-08-28 pass widened the PLAYGROUND only (viewBox
299
+ 360 -> 400, container 31rem -> 34.5rem at the original item scale).
300
+ 2026-09-04 (owner, hero pass): the whole figure now scales UP with
301
+ its column — the cap rises to 42rem, so items grow proportionally
302
+ with the SVG until the grid column runs out of width. */
303
+ margin: 0; justify-self: center; cursor: default; max-width: 42rem; width: 100%;
259
304
  background: transparent; border: 0;
260
305
  padding: .8rem .8rem .5rem;
261
306
  transition: transform .35s var(--ease-settle);
@@ -671,14 +716,6 @@ input.invalid { border-color: var(--err); }
671
716
  /* scrollIntoView target for "Choose a different model": clear the sticky
672
717
  header instead of sliding under it. */
673
718
  #choose-model { scroll-margin-top: 5.5rem; }
674
- /* Until the ceremony has run, the model list sits BLURRED and inert —
675
- visible enough to promise what comes next, unreachable until the
676
- participant holds keys (owner's refactor 2026-08-31). */
677
- #model-cards.locked {
678
- filter: blur(2.5px); opacity: .55;
679
- pointer-events: none; user-select: none;
680
- }
681
- #model-cards { transition: filter .4s var(--ease-settle), opacity .4s var(--ease-settle); }
682
719
  /* ---- the keygen ceremony (on the home screen) ----
683
720
  Armed button: one restrained light sweep while it is pressable — a "live
684
721
  and ready" cue that stops the moment the ceremony starts (disabled). */
@@ -931,10 +968,6 @@ input.invalid { border-color: var(--err); }
931
968
  @keyframes resolve {
932
969
  from { opacity: 0; transform: translateY(14px) scale(.97); filter: blur(10px); }
933
970
  }
934
- #result-scores {
935
- color: var(--dim); font-family: var(--font-data); font-size: .88rem;
936
- animation: rise .4s var(--ease-settle) both; animation-delay: .22s;
937
- }
938
971
  #result-cipher { animation: rise .4s var(--ease-settle) both; animation-delay: .55s; }
939
972
  #stage-line { color: var(--dim); min-height: 1.5rem; }
940
973
  /* Replayed by the stage arm on each REAL token change (the .swap class is
@@ -88,7 +88,7 @@ const backend = {
88
88
  const $ = (id) => document.getElementById(id);
89
89
  const state = { copy: null, models: [], current: null, prepared: null,
90
90
  act: null, returnTo: null, encodedBytes: null,
91
- lastPlain: null, lastCipher: null, keysDone: false };
91
+ lastPlain: null, lastCipher: null };
92
92
 
93
93
  /* Directional glide (owner's direction 2026-08-28): forward navigation
94
94
  * (1->2->3->4) enters from the right, backward from the left. The reflow
@@ -149,6 +149,40 @@ function applyCopy(C) {
149
149
  p.textContent = text;
150
150
  introHolder.append(p);
151
151
  }
152
+ // Owner's copy 2026-09-04: the pipeline line, then the "At Implicant"
153
+ // paragraph close the left column.
154
+ const flow = document.createElement("p");
155
+ flow.className = "intro-flow";
156
+ flow.textContent = C.INTRO_FLOW;
157
+ const outro = document.createElement("p");
158
+ outro.textContent = C.INTRO_OUTRO;
159
+ introHolder.append(flow, outro);
160
+ // The four capability panels under the hero (owner, 2026-09-04,
161
+ // zama.org card-row reference): title + one short sentence each.
162
+ const panelHolder = $("intro-panels");
163
+ panelHolder.replaceChildren();
164
+ for (const [title, body] of C.INTRO_PANELS) {
165
+ const panel = document.createElement("div");
166
+ panel.className = "panel";
167
+ const h = document.createElement("h3");
168
+ h.textContent = title;
169
+ const p = document.createElement("p");
170
+ p.textContent = body;
171
+ panel.append(h, p);
172
+ panelHolder.append(panel);
173
+ }
174
+ // Below the panels, a centered lead sentence where ONLY the Contact us
175
+ // button is clickable — the exact same .nav-cta pill as the header's
176
+ // CTA (label AND target reused, so they never drift).
177
+ const contact = $("intro-contact");
178
+ contact.replaceChildren();
179
+ const lead = document.createElement("span");
180
+ lead.textContent = C.INTRO_CONTACT_LEAD;
181
+ const cta = document.createElement("a");
182
+ cta.className = "nav-cta";
183
+ cta.textContent = C.NAV_TABS.contact;
184
+ cta.href = C.NAV_LINKS.contact;
185
+ contact.append(lead, cta);
152
186
  for (const el of document.querySelectorAll("[data-copy-title]"))
153
187
  el.textContent = C.ACT_TITLES[el.dataset.copyTitle];
154
188
  $("access-prompt").textContent = C.ACCESS_PROMPT;
@@ -340,9 +374,12 @@ function applyBoundary(ev) {
340
374
  const DISABLED_MODELS = new Set(["credit"]);
341
375
 
342
376
  /* Display capitalization for model names (owner's direction 2026-09-01) —
343
- * every place a slug shows to the reader; the wire keeps raw slugs. */
377
+ * every place a slug shows to the reader; the wire keeps raw slugs. A slug
378
+ * curated in copy.MODEL_NAMES renders as that name instead (2026-09-04:
379
+ * heartdisease -> "Heart disease"). */
344
380
  function modelName(slug) {
345
- return slug.charAt(0).toUpperCase() + slug.slice(1);
381
+ const curated = (state.copy.MODEL_NAMES || {})[slug];
382
+ return curated || slug.charAt(0).toUpperCase() + slug.slice(1);
346
383
  }
347
384
 
348
385
  function renderModels() {
@@ -499,18 +536,17 @@ async function runKeygen() {
499
536
 
500
537
  /* Adopt a completed ceremony — shared by the live keygen, the boot path
501
538
  * (a reload re-reads the process's last artifacts from /api/models) and
502
- * the SSE keygen_done arm (mirror tabs): artifacts + disclosure shown,
503
- * model list unlocked, the big CTA yielding to Regenerate. */
539
+ * the SSE keygen_done arm (mirror tabs): artifacts shown, the big CTA
540
+ * yielding to Regenerate. The model list is never gated on this (owner,
541
+ * 2026-09-04) and the disclosure is permanently visible. */
504
542
  function adoptKeys(art) {
505
543
  revealArtifacts(art);
506
- state.keysDone = true;
507
544
  // Regenerate replaces the zone's TITLE, not just the big CTA (owner,
508
545
  // 2026-09-01): the ceremony heading is done teaching once keys exist,
509
546
  // and the shorter zone lets the model list rise up the page.
510
547
  $("keyzone-title").hidden = true;
511
548
  $("keygen-btn").hidden = true;
512
549
  $("regen-keys").hidden = false;
513
- $("model-cards").classList.remove("locked");
514
550
  }
515
551
 
516
552
  function revealArtifacts(art) {
@@ -556,7 +592,6 @@ function revealArtifacts(art) {
556
592
  dl.append(dt, dd);
557
593
  }
558
594
  dl.hidden = false;
559
- $("disclosure").hidden = false;
560
595
  cue(CUE_KEYGEN); // the ceremony really completed (keygen HTTP success)
561
596
  }
562
597
 
@@ -784,7 +819,6 @@ function resetAct3Visuals() {
784
819
  $("recap-beforeafter").hidden = true;
785
820
  state.lastCipher = null;
786
821
  $("result-label").textContent = "";
787
- $("result-scores").textContent = "";
788
822
  $("result-cipher").textContent = "";
789
823
  }
790
824
 
@@ -971,8 +1005,8 @@ function formatRatio(ratio) {
971
1005
  function showResult(result) {
972
1006
  $("result-label").textContent =
973
1007
  result.label ?? state.copy.RESULT_LABEL_FALLBACK.replace("{index}", result.label_index);
974
- $("result-scores").textContent = state.copy.RESULT_SCORES
975
- .replace("{scores}", result.scores.join(", "));
1008
+ // The signed-class-scores line is gone (owner, 2026-09-04): the decided
1009
+ // label + RESULT_DECRYPTED carry act 4.
976
1010
  // Spec §6 act 4 as extended by ADD-RECAP-4: name what the decision was
977
1011
  // decrypted from; its exact byte size renders in the recap below, sourced
978
1012
  // from this same authoritative HTTP response — never from an SSE frame
@@ -57,6 +57,11 @@
57
57
  reference): the intro paragraphs + the figure, reached from the
58
58
  header's first tab. -->
59
59
  <section id="tech" class="act" hidden>
60
+ <!-- Hero layout (owner, 2026-09-04, zama.org reference): the title is
61
+ a centered single line over the whole hero; below it, copy and
62
+ animation sit as two balanced, vertically centered halves; then
63
+ the four capability panels, then the centered contact row (all
64
+ filled from /api/copy in applyCopy). -->
60
65
  <h2 id="intro-title"></h2>
61
66
  <div class="intro-grid">
62
67
  <div id="intro-paras"></div>
@@ -205,6 +210,8 @@
205
210
  <figcaption id="fhe-caption"></figcaption>
206
211
  </figure>
207
212
  </div>
213
+ <div id="intro-panels"></div>
214
+ <p id="intro-contact"></p>
208
215
  </section>
209
216
 
210
217
  <!-- The Models page: the key ceremony, then the model list. -->
@@ -251,11 +258,15 @@
251
258
  </div>
252
259
  </div>
253
260
  </div>
254
- <p id="disclosure" class="disclosure" hidden></p>
261
+ <!-- Permanently visible (owner, 2026-09-04): the demo-honesty note
262
+ shows BEFORE any ceremony has run, not only with the artifacts. -->
263
+ <p id="disclosure" class="disclosure"></p>
255
264
  <p id="keys-error" class="error" role="alert"></p>
256
265
  </div>
257
266
  <h2 data-copy-title="act1" id="choose-model"></h2>
258
- <div id="model-cards" class="locked"></div>
267
+ <!-- No lock (owner, 2026-09-04): the ceremony no longer gates model
268
+ testing — the 1./2. numbering names the suggested order instead. -->
269
+ <div id="model-cards"></div>
259
270
  <p id="act1-error" class="error" role="alert"></p>
260
271
  </section>
261
272
 
@@ -306,7 +317,6 @@
306
317
  <h2 data-copy-title="act4"></h2>
307
318
  <h3 id="recap-title"></h3>
308
319
  <div id="result-label"></div>
309
- <div id="result-scores"></div>
310
320
  <div id="exec-strip-done" class="strip"></div>
311
321
  <button id="recap-cipher-toggle" type="button" hidden aria-expanded="false" aria-controls="recap-beforeafter"></button>
312
322
  <div id="recap-beforeafter" hidden>
File without changes
File without changes
File without changes
File without changes
File without changes