SaketR1 commited on
Commit
1d55cfa
·
verified ·
1 Parent(s): f570eef

Upload 5 files

Browse files
Files changed (3) hide show
  1. README.md +5 -1
  2. app.py +1 -1
  3. labs_theme.py +295 -83
README.md CHANGED
@@ -26,6 +26,10 @@ Remove the Tulu 3 examples whose training gradients sit closest to harmful SafeR
26
  - **Live view:** score histogram with the cutoff, share removed per source, and agreement with the released SafeTulu
27
  - **Export:** a shareable link to the exact selection, kept/removed ID files, a standalone Python script that reproduces the selection, or a push of the filtered dataset to your own Hub account
28
 
 
 
 
 
29
  ## How it works
30
 
31
  `tulu_with_metadata` in [SaketR1/SafeTulu](https://huggingface.co/datasets/SaketR1/SafeTulu) is about 7.5 GB, almost all gradient embeddings. At startup the app reads only the `document_id` and `score` columns using parquet column projection over HTTP range requests. That's a few tens of MB, cached in `cache/`. It also reads `document_id` from `removed_tulu` to compare against the released split.
@@ -40,7 +44,7 @@ Remove the Tulu 3 examples whose training gradients sit closest to harmful SafeR
40
  |---|---|
41
  | `app.py` | Gradio UI |
42
  | `safetulu_data.py` | Loading, selection logic, export. Has no UI code, so you can import it in a notebook |
43
- | `labs_theme.py` | The Datology Labs look: palette, fonts, hero, film grain, chart colors |
44
  | `requirements.txt` | Dependencies |
45
 
46
  ## Configuration (environment variables)
 
26
  - **Live view:** score histogram with the cutoff, share removed per source, and agreement with the released SafeTulu
27
  - **Export:** a shareable link to the exact selection, kept/removed ID files, a standalone Python script that reproduces the selection, or a push of the filtered dataset to your own Hub account
28
 
29
+ ## Light and dark themes
30
+
31
+ The Space opens in a light theme. The light-bulb button in the top right of the header switches to the dark theme and back, and the browser remembers the choice. Adding `?__theme=dark` or `?__theme=light` to the URL forces a theme.
32
+
33
  ## How it works
34
 
35
  `tulu_with_metadata` in [SaketR1/SafeTulu](https://huggingface.co/datasets/SaketR1/SafeTulu) is about 7.5 GB, almost all gradient embeddings. At startup the app reads only the `document_id` and `score` columns using parquet column projection over HTTP range requests. That's a few tens of MB, cached in `cache/`. It also reads `document_id` from `removed_tulu` to compare against the released split.
 
44
  |---|---|
45
  | `app.py` | Gradio UI |
46
  | `safetulu_data.py` | Loading, selection logic, export. Has no UI code, so you can import it in a notebook |
47
+ | `labs_theme.py` | The Datology Labs look: light and dark palettes, fonts, hero, film grain, chart colors, and the light-bulb theme switch |
48
  | `requirements.txt` | Dependencies |
49
 
50
  ## Configuration (environment variables)
app.py CHANGED
@@ -266,4 +266,4 @@ with gr.Blocks(title="Build Your Own SafeTulu") as demo:
266
  demo.load(on_load, None, controls + [exempt_acc] + outputs)
267
 
268
  if __name__ == "__main__":
269
- demo.launch(css=lt.CSS, theme=lt.build_theme(), js=lt.FORCE_DARK_JS)
 
266
  demo.load(on_load, None, controls + [exempt_acc] + outputs)
267
 
268
  if __name__ == "__main__":
269
+ demo.launch(css=lt.CSS, theme=lt.build_theme(), js=lt.THEME_JS)
labs_theme.py CHANGED
@@ -1,19 +1,23 @@
1
- """Datology Labs look for the Space: deep navy, cobalt to cyan gradients, an orange accent, film grain.
2
 
3
- Colors were sampled from the Datology Labs brand film. Everything visual lives here so app.py
4
- stays the same as the plain version apart from a few imports.
 
 
 
 
5
  """
6
 
7
  from __future__ import annotations
8
 
 
 
9
  import gradio as gr
10
 
11
  # ---------------------------------------------------------------------------
12
  # Palette (sampled from the brand film)
13
  # ---------------------------------------------------------------------------
14
- NIGHT = "#040b18" # deepest background
15
- NAVY = "#0a1630" # panels / chart surface
16
- NAVY_2 = "#0c2447" # raised surfaces, inputs
17
  DEEP_COBALT = "#052d78"
18
  COBALT = "#1574eb"
19
  ELECTRIC = "#2f8ff0" # primary blue; "kept" in charts
@@ -22,15 +26,33 @@ ICE = "#a4d9e8"
22
  ORANGE = "#e8692a" # the single warm accent; "removed" in charts
23
  ORANGE_HI = "#f2894c"
24
  EMBER = "#e5b06d"
 
 
 
 
 
25
  TEXT = "#f2f6fc"
26
  TEXT_MUTED = "#9fb3cf"
27
  LINE = "rgba(140, 180, 235, 0.16)"
28
  LINE_STRONG = "rgba(140, 180, 235, 0.32)"
29
 
 
 
 
 
 
 
 
 
 
 
 
30
  FONT = "Hanken Grotesk"
31
  FONT_MONO = "JetBrains Mono"
32
 
33
- # Chart roles (validated: CVD ΔE 27, contrast ≥ 3:1 on NAVY)
 
 
34
  KEPT = ELECTRIC
35
  REMOVED = ORANGE
36
  INK = TEXT_MUTED
@@ -38,92 +60,144 @@ GRID = "rgba(159, 179, 207, 0.12)"
38
  CUTOFF_LINE = "rgba(242, 246, 252, 0.7)"
39
 
40
  # ---------------------------------------------------------------------------
41
- # Gradio theme. Light and dark variants get the same values, so the Space
42
- # looks the same whatever the viewer's system setting is.
43
  # ---------------------------------------------------------------------------
44
- _VARS = dict(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  body_background_fill=NIGHT,
46
  body_text_color=TEXT,
47
  body_text_color_subdued=TEXT_MUTED,
48
- body_text_weight="400",
49
  background_fill_primary=NAVY,
50
  background_fill_secondary=NAVY_2,
51
  border_color_primary=LINE,
52
- border_color_accent=ELECTRIC,
53
  border_color_accent_subdued=LINE_STRONG,
54
- color_accent=ELECTRIC,
55
  color_accent_soft="rgba(47, 143, 240, 0.18)",
56
  link_text_color=CYAN,
57
  link_text_color_hover=ICE,
58
  link_text_color_visited=CYAN,
59
  link_text_color_active=ICE,
60
  code_background_fill=NIGHT,
61
- shadow_drop="none",
62
  shadow_drop_lg="0 20px 60px rgba(0, 0, 0, 0.35)",
63
- shadow_spread="0px",
64
  block_background_fill="rgba(10, 22, 48, 0.72)",
65
  block_border_color=LINE,
66
- block_border_width="1px",
67
- block_radius="16px",
68
- container_radius="16px",
69
- block_label_background_fill="transparent",
70
- block_label_border_width="0px",
71
  block_label_text_color=TEXT_MUTED,
72
- block_label_text_weight="500",
73
- block_title_background_fill="transparent",
74
- block_title_border_width="0px",
75
  block_title_text_color=TEXT,
76
- block_title_text_weight="500",
77
- block_title_padding="0",
78
- panel_background_fill="transparent",
79
  input_background_fill=NAVY_2,
80
  input_background_fill_focus=NAVY_2,
81
  input_background_fill_hover=NAVY_2,
82
  input_border_color=LINE,
83
  input_border_color_hover=LINE_STRONG,
84
- input_border_color_focus=ELECTRIC,
85
- input_border_width="1px",
86
  checkbox_background_color=NIGHT,
87
  checkbox_background_color_hover=NIGHT,
88
  checkbox_background_color_focus=NIGHT,
89
- checkbox_background_color_selected=ELECTRIC,
90
  checkbox_border_color=LINE_STRONG,
91
- checkbox_border_color_selected=ELECTRIC,
92
- checkbox_border_color_focus=ELECTRIC,
93
  checkbox_label_background_fill="rgba(12, 36, 71, 0.6)",
94
  checkbox_label_background_fill_hover="rgba(21, 116, 235, 0.14)",
95
  checkbox_label_background_fill_selected="rgba(21, 116, 235, 0.22)",
96
  checkbox_label_border_color=LINE,
97
  checkbox_label_border_color_hover=LINE_STRONG,
98
  checkbox_label_border_color_selected="rgba(47, 143, 240, 0.65)",
99
- checkbox_label_border_width="1px",
100
- checkbox_label_shadow="none",
101
  checkbox_label_text_color=TEXT,
102
  checkbox_label_text_color_selected=TEXT,
103
- checkbox_label_text_weight="400",
104
- slider_color=ORANGE,
105
- loader_color=ORANGE,
106
- stat_background_fill=f"linear-gradient(90deg, {COBALT}, {CYAN})",
107
  table_border_color=LINE,
108
  table_even_background_fill=NAVY,
109
  table_odd_background_fill=NAVY_2,
110
  table_row_focus="rgba(21, 116, 235, 0.18)",
111
  table_text_color=TEXT,
112
- button_primary_background_fill=ORANGE,
113
- button_primary_background_fill_hover=ORANGE_HI,
114
- button_primary_border_color=ORANGE,
115
- button_primary_border_color_hover=ORANGE_HI,
116
- button_primary_text_color="#ffffff",
117
- button_primary_text_color_hover="#ffffff",
118
  button_primary_shadow="0 0 24px rgba(232, 105, 42, 0.35)",
119
  button_primary_shadow_hover="0 0 32px rgba(232, 105, 42, 0.5)",
120
  button_secondary_background_fill="rgba(12, 36, 71, 0.6)",
121
  button_secondary_background_fill_hover="rgba(21, 116, 235, 0.18)",
122
  button_secondary_border_color=LINE_STRONG,
123
- button_secondary_border_color_hover=ELECTRIC,
124
  button_secondary_text_color=TEXT,
125
  button_secondary_text_color_hover=TEXT,
126
- button_secondary_shadow="none",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  )
128
 
129
 
@@ -136,35 +210,83 @@ def build_theme() -> gr.themes.Base:
136
  font=[gr.themes.GoogleFont(FONT, weights=(300, 400, 500, 600)), "ui-sans-serif", "system-ui", "sans-serif"],
137
  font_mono=[gr.themes.GoogleFont(FONT_MONO), "ui-monospace", "monospace"],
138
  )
139
- import inspect
140
-
141
  valid = set(inspect.signature(theme.set).parameters)
142
- both = {}
143
- for k, v in _VARS.items():
 
 
 
 
 
144
  if k in valid:
145
- both[k] = v
 
146
  if f"{k}_dark" in valid:
147
- both[f"{k}_dark"] = v
148
- return theme.set(**both)
 
 
149
 
150
 
151
- # Always render the dark variant, so components with their own light/dark styles
152
- # (the code editor, plots, file lists) match the navy page.
153
- FORCE_DARK_JS = """
 
154
  () => {
155
- document.body.classList.add('dark');
156
- document.documentElement.style.colorScheme = 'dark';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  }
158
  """
159
 
160
  # ---------------------------------------------------------------------------
161
  # Brand pieces
162
  # ---------------------------------------------------------------------------
163
- _GRAIN = (
164
- "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'>"
165
- "<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/>"
166
- "<feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0'/></filter>"
167
- "<rect width='100%' height='100%' filter='url(%23n)'/></svg>\")"
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  )
169
 
170
 
@@ -172,6 +294,8 @@ def hero_html(repo_id: str, n_examples: int, official_pct: float) -> str:
172
  return f"""
173
  <section class="hero">
174
  <div class="hero-glow"></div>
 
 
175
  <div class="hero-inner">
176
  <h1><b>Build your own</b> SafeTulu</h1>
177
  <p class="hero-sub">remove the Tulu 3 data that sits closest to harm, at any threshold you choose</p>
@@ -181,32 +305,91 @@ def hero_html(repo_id: str, n_examples: int, official_pct: float) -> str:
181
  <p>Every Tulu 3 example is scored by how close its training gradient is to harmful SafeRLHF data.
182
  Pick a threshold and the sources to filter, see what gets removed, then download the IDs,
183
  copy a script that reproduces your selection, or push the result to your own Hub account.</p>
 
 
 
184
  </div>
185
  """
186
 
187
 
188
  CSS = f"""
189
- /* page: night navy with soft cobalt and teal glows, plus film grain */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  body, gradio-app {{
191
- background: radial-gradient(1100px 620px at 8% -8%, rgba(21,116,235,0.30), transparent 60%),
192
- radial-gradient(900px 700px at 105% 20%, rgba(6,78,111,0.45), transparent 60%),
193
- radial-gradient(700px 500px at 60% 115%, rgba(5,45,120,0.55), transparent 65%),
194
- {NIGHT} !important;
 
195
  }}
196
  body::after {{
197
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9999;
198
- background-image: {_GRAIN}; opacity: 0.07; mix-blend-mode: soft-light;
199
  }}
200
  .gradio-container {{ width: 100% !important; max-width: 1240px !important; margin: 0 auto !important; background: transparent !important; }}
201
  footer {{ opacity: 0.55; }}
202
 
203
- /* hero: the closing card of the brand film */
204
  .hero {{
205
  position: relative; overflow: hidden; border-radius: 24px; min-height: 280px;
206
  display: flex; align-items: center; justify-content: center; text-align: center;
207
  background:
208
  radial-gradient(520px 340px at 16% 18%, rgba(164,217,232,0.95), transparent 70%),
209
  linear-gradient(118deg, {ICE} 0%, {CYAN} 26%, #1a9be8 48%, {COBALT} 76%, #0b45cf 100%);
 
210
  isolation: isolate;
211
  }}
212
  .hero-glow {{
@@ -222,45 +405,74 @@ footer {{ opacity: 0.55; }}
222
  @media (max-width: 640px) {{
223
  .hero-glow {{ width: 260px; height: 260px; right: -18%; top: -14%; }}
224
  .hero {{ border-radius: 20px; }}
 
 
225
  }}
226
  .hero-inner {{ position: relative; padding: 44px 24px; color: #fff; }}
227
  .hero h1 {{
228
  margin: 0; color: #fff !important; font-weight: 300; letter-spacing: -0.035em; line-height: 1.02;
229
  font-size: clamp(38px, 6.4vw, 76px); text-shadow: 0 2px 24px rgba(5,45,120,0.25);
230
  }}
231
- .hero h1 b {{ font-weight: 500; }}
232
  .hero-sub {{
233
  margin: 14px 0 0; font-size: clamp(17px, 2vw, 22px); font-weight: 300; color: #fff;
234
  text-shadow: 0 1px 14px rgba(5,45,120,0.35);
235
  }}
236
- .hero-meta {{ max-width: 860px; margin: 22px auto 6px; text-align: center; color: {TEXT_MUTED}; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  .hero-meta p {{ margin: 0 0 8px; font-size: 15px; line-height: 1.6; }}
238
  .hero-meta .meta-line {{ font-size: 13px; letter-spacing: 0.01em; }}
239
  .hero-meta .meta-line span {{ margin: 0 6px; opacity: 0.6; }}
240
- .hero-meta em {{ font-style: normal; color: {EMBER}; }}
241
- .hero-meta a {{ color: {CYAN}; text-decoration: none; border-bottom: 1px solid rgba(57,176,223,0.4); }}
242
 
243
  /* stat tiles */
244
  .tiles {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }}
245
  .tile {{
246
- position: relative; overflow: hidden; border: 1px solid {LINE}; border-radius: 16px; padding: 16px 18px;
247
- background: linear-gradient(160deg, rgba(21,116,235,0.16), rgba(10,22,48,0.6) 55%);
248
  }}
249
  .tile::before {{
250
  content: ""; position: absolute; left: 18px; right: 18px; top: 0; height: 1px;
251
- background: linear-gradient(90deg, transparent, rgba(57,176,223,0.7), transparent);
252
  }}
253
- .tile-label {{ font-size: 11px; font-weight: 500; color: {CYAN}; text-transform: uppercase; letter-spacing: 0.12em; }}
254
- .tile-value {{ font-size: 30px; font-weight: 400; letter-spacing: -0.02em; color: {TEXT};
255
  font-variant-numeric: tabular-nums; margin-top: 6px; }}
256
- .tile-sub {{ font-size: 12px; color: {TEXT_MUTED}; margin-top: 4px; line-height: 1.4; }}
257
- .tile.accent .tile-value {{ color: {ORANGE_HI}; }}
258
 
259
  /* section headings above the charts */
260
  .section-title p {{ margin: 6px 0 -4px !important; font-size: 13px !important; font-weight: 500 !important;
261
- color: {TEXT_MUTED} !important; text-transform: uppercase; letter-spacing: 0.12em; }}
262
 
263
  /* tabs */
264
- button[role="tab"] {{ color: {TEXT_MUTED} !important; font-weight: 500; }}
265
- button[role="tab"][aria-selected="true"] {{ color: {TEXT} !important; border-color: {ORANGE} !important; }}
 
 
 
 
 
 
 
 
 
 
266
  """
 
1
+ """Datology Labs look for the Space, with a light theme (default) and a dark theme.
2
 
3
+ Colors were sampled from the Datology Labs brand film. The light theme follows the film's opening
4
+ scenes (off-white paper, soft blue glows, navy type); the dark theme follows its night scenes
5
+ (deep navy, cobalt and teal glows). Both share the cobalt-to-cyan hero, the orange accent and
6
+ the film grain. A light-bulb button in the hero switches between them and remembers the choice.
7
+
8
+ Everything visual lives here, so app.py stays the same as the plain version apart from a few imports.
9
  """
10
 
11
  from __future__ import annotations
12
 
13
+ import inspect
14
+
15
  import gradio as gr
16
 
17
  # ---------------------------------------------------------------------------
18
  # Palette (sampled from the brand film)
19
  # ---------------------------------------------------------------------------
20
+ # Shared brand colors
 
 
21
  DEEP_COBALT = "#052d78"
22
  COBALT = "#1574eb"
23
  ELECTRIC = "#2f8ff0" # primary blue; "kept" in charts
 
26
  ORANGE = "#e8692a" # the single warm accent; "removed" in charts
27
  ORANGE_HI = "#f2894c"
28
  EMBER = "#e5b06d"
29
+
30
+ # Dark theme (the night scenes)
31
+ NIGHT = "#040b18" # deepest background
32
+ NAVY = "#0a1630" # panels / chart surface
33
+ NAVY_2 = "#0c2447" # raised surfaces, inputs
34
  TEXT = "#f2f6fc"
35
  TEXT_MUTED = "#9fb3cf"
36
  LINE = "rgba(140, 180, 235, 0.16)"
37
  LINE_STRONG = "rgba(140, 180, 235, 0.32)"
38
 
39
+ # Light theme (the opening scenes)
40
+ PAPER = "#f6f9fd" # page
41
+ WHITE = "#ffffff" # panels / chart surface
42
+ PAPER_2 = "#eef3fa" # raised surfaces, inputs
43
+ INK_NAVY = "#0b1b3a" # body text (navy captions in the film)
44
+ INK_MUTED = "#4b5e7c"
45
+ COBALT_TEXT = "#1257c4" # blue that reads as text on white
46
+ ORANGE_TEXT = "#c9531a" # orange that reads as text on white
47
+ LINE_L = "rgba(21, 60, 120, 0.12)"
48
+ LINE_STRONG_L = "rgba(21, 60, 120, 0.24)"
49
+
50
  FONT = "Hanken Grotesk"
51
  FONT_MONO = "JetBrains Mono"
52
 
53
+ # Chart roles. The same two colors pass the palette checks on navy and on white
54
+ # (CVD ΔE 27, contrast ≥ 3:1), so bars keep their colors in both themes.
55
+ # Axis text, gridlines and the cutoff line switch with the theme via CSS (see PLOT CSS below).
56
  KEPT = ELECTRIC
57
  REMOVED = ORANGE
58
  INK = TEXT_MUTED
 
60
  CUTOFF_LINE = "rgba(242, 246, 252, 0.7)"
61
 
62
  # ---------------------------------------------------------------------------
63
+ # Gradio theme: light values for the default, dark values under .dark
 
64
  # ---------------------------------------------------------------------------
65
+ _SHARED = dict(
66
+ body_text_weight="400",
67
+ border_color_accent=ELECTRIC,
68
+ color_accent=ELECTRIC,
69
+ shadow_drop="none",
70
+ shadow_spread="0px",
71
+ block_border_width="1px",
72
+ block_radius="16px",
73
+ container_radius="16px",
74
+ block_label_background_fill="transparent",
75
+ block_label_border_width="0px",
76
+ block_label_text_weight="500",
77
+ block_title_background_fill="transparent",
78
+ block_title_border_width="0px",
79
+ block_title_text_weight="500",
80
+ block_title_padding="0",
81
+ panel_background_fill="transparent",
82
+ input_border_color_focus=ELECTRIC,
83
+ input_border_width="1px",
84
+ checkbox_background_color_selected=ELECTRIC,
85
+ checkbox_border_color_selected=ELECTRIC,
86
+ checkbox_border_color_focus=ELECTRIC,
87
+ checkbox_label_border_width="1px",
88
+ checkbox_label_shadow="none",
89
+ checkbox_label_text_weight="400",
90
+ slider_color=ORANGE,
91
+ loader_color=ORANGE,
92
+ stat_background_fill=f"linear-gradient(90deg, {COBALT}, {CYAN})",
93
+ button_primary_background_fill=ORANGE,
94
+ button_primary_background_fill_hover=ORANGE_HI,
95
+ button_primary_border_color=ORANGE,
96
+ button_primary_border_color_hover=ORANGE_HI,
97
+ button_primary_text_color="#ffffff",
98
+ button_primary_text_color_hover="#ffffff",
99
+ button_secondary_border_color_hover=ELECTRIC,
100
+ button_secondary_shadow="none",
101
+ )
102
+
103
+ _DARK = dict(
104
  body_background_fill=NIGHT,
105
  body_text_color=TEXT,
106
  body_text_color_subdued=TEXT_MUTED,
 
107
  background_fill_primary=NAVY,
108
  background_fill_secondary=NAVY_2,
109
  border_color_primary=LINE,
 
110
  border_color_accent_subdued=LINE_STRONG,
 
111
  color_accent_soft="rgba(47, 143, 240, 0.18)",
112
  link_text_color=CYAN,
113
  link_text_color_hover=ICE,
114
  link_text_color_visited=CYAN,
115
  link_text_color_active=ICE,
116
  code_background_fill=NIGHT,
 
117
  shadow_drop_lg="0 20px 60px rgba(0, 0, 0, 0.35)",
 
118
  block_background_fill="rgba(10, 22, 48, 0.72)",
119
  block_border_color=LINE,
 
 
 
 
 
120
  block_label_text_color=TEXT_MUTED,
 
 
 
121
  block_title_text_color=TEXT,
 
 
 
122
  input_background_fill=NAVY_2,
123
  input_background_fill_focus=NAVY_2,
124
  input_background_fill_hover=NAVY_2,
125
  input_border_color=LINE,
126
  input_border_color_hover=LINE_STRONG,
 
 
127
  checkbox_background_color=NIGHT,
128
  checkbox_background_color_hover=NIGHT,
129
  checkbox_background_color_focus=NIGHT,
 
130
  checkbox_border_color=LINE_STRONG,
 
 
131
  checkbox_label_background_fill="rgba(12, 36, 71, 0.6)",
132
  checkbox_label_background_fill_hover="rgba(21, 116, 235, 0.14)",
133
  checkbox_label_background_fill_selected="rgba(21, 116, 235, 0.22)",
134
  checkbox_label_border_color=LINE,
135
  checkbox_label_border_color_hover=LINE_STRONG,
136
  checkbox_label_border_color_selected="rgba(47, 143, 240, 0.65)",
 
 
137
  checkbox_label_text_color=TEXT,
138
  checkbox_label_text_color_selected=TEXT,
 
 
 
 
139
  table_border_color=LINE,
140
  table_even_background_fill=NAVY,
141
  table_odd_background_fill=NAVY_2,
142
  table_row_focus="rgba(21, 116, 235, 0.18)",
143
  table_text_color=TEXT,
 
 
 
 
 
 
144
  button_primary_shadow="0 0 24px rgba(232, 105, 42, 0.35)",
145
  button_primary_shadow_hover="0 0 32px rgba(232, 105, 42, 0.5)",
146
  button_secondary_background_fill="rgba(12, 36, 71, 0.6)",
147
  button_secondary_background_fill_hover="rgba(21, 116, 235, 0.18)",
148
  button_secondary_border_color=LINE_STRONG,
 
149
  button_secondary_text_color=TEXT,
150
  button_secondary_text_color_hover=TEXT,
151
+ )
152
+
153
+ _LIGHT = dict(
154
+ body_background_fill=PAPER,
155
+ body_text_color=INK_NAVY,
156
+ body_text_color_subdued=INK_MUTED,
157
+ background_fill_primary=WHITE,
158
+ background_fill_secondary=PAPER_2,
159
+ border_color_primary=LINE_L,
160
+ border_color_accent_subdued=LINE_STRONG_L,
161
+ color_accent_soft="rgba(21, 116, 235, 0.10)",
162
+ link_text_color=COBALT_TEXT,
163
+ link_text_color_hover=COBALT,
164
+ link_text_color_visited=COBALT_TEXT,
165
+ link_text_color_active=COBALT,
166
+ code_background_fill=PAPER,
167
+ shadow_drop_lg="0 18px 50px rgba(21, 60, 120, 0.10)",
168
+ block_background_fill="rgba(255, 255, 255, 0.82)",
169
+ block_border_color=LINE_L,
170
+ block_label_text_color=INK_MUTED,
171
+ block_title_text_color=INK_NAVY,
172
+ input_background_fill=WHITE,
173
+ input_background_fill_focus=WHITE,
174
+ input_background_fill_hover=WHITE,
175
+ input_border_color=LINE_STRONG_L,
176
+ input_border_color_hover="rgba(21, 60, 120, 0.36)",
177
+ checkbox_background_color=WHITE,
178
+ checkbox_background_color_hover=WHITE,
179
+ checkbox_background_color_focus=WHITE,
180
+ checkbox_border_color="rgba(21, 60, 120, 0.36)",
181
+ checkbox_label_background_fill=PAPER_2,
182
+ checkbox_label_background_fill_hover="rgba(21, 116, 235, 0.08)",
183
+ checkbox_label_background_fill_selected="rgba(21, 116, 235, 0.10)",
184
+ checkbox_label_border_color=LINE_L,
185
+ checkbox_label_border_color_hover=LINE_STRONG_L,
186
+ checkbox_label_border_color_selected="rgba(21, 116, 235, 0.45)",
187
+ checkbox_label_text_color=INK_NAVY,
188
+ checkbox_label_text_color_selected=INK_NAVY,
189
+ table_border_color=LINE_L,
190
+ table_even_background_fill=WHITE,
191
+ table_odd_background_fill=PAPER,
192
+ table_row_focus="rgba(21, 116, 235, 0.08)",
193
+ table_text_color=INK_NAVY,
194
+ button_primary_shadow="0 6px 18px rgba(232, 105, 42, 0.25)",
195
+ button_primary_shadow_hover="0 8px 24px rgba(232, 105, 42, 0.35)",
196
+ button_secondary_background_fill=WHITE,
197
+ button_secondary_background_fill_hover="rgba(21, 116, 235, 0.06)",
198
+ button_secondary_border_color=LINE_STRONG_L,
199
+ button_secondary_text_color=INK_NAVY,
200
+ button_secondary_text_color_hover=INK_NAVY,
201
  )
202
 
203
 
 
210
  font=[gr.themes.GoogleFont(FONT, weights=(300, 400, 500, 600)), "ui-sans-serif", "system-ui", "sans-serif"],
211
  font_mono=[gr.themes.GoogleFont(FONT_MONO), "ui-monospace", "monospace"],
212
  )
 
 
213
  valid = set(inspect.signature(theme.set).parameters)
214
+ values = {}
215
+ for k, v in _SHARED.items():
216
+ if k in valid:
217
+ values[k] = v
218
+ if f"{k}_dark" in valid:
219
+ values[f"{k}_dark"] = v
220
+ for k, v in _LIGHT.items():
221
  if k in valid:
222
+ values[k] = v
223
+ for k, v in _DARK.items():
224
  if f"{k}_dark" in valid:
225
+ values[f"{k}_dark"] = v
226
+ elif k in valid and k not in _LIGHT:
227
+ values[k] = v
228
+ return theme.set(**values)
229
 
230
 
231
+ # Opens in light unless the viewer chose dark before (remembered in this browser) or the URL has
232
+ # ?__theme=dark. Gradio would otherwise follow the system setting, so the class is set explicitly.
233
+ # The light-bulb button toggles the theme; a click handler on the document survives re-renders.
234
+ THEME_JS = """
235
  () => {
236
+ const KEY = 'safetulu-theme';
237
+ const sync = (dark) => {
238
+ const btn = document.getElementById('theme-toggle');
239
+ if (!btn) return;
240
+ const label = dark ? 'Switch to light theme' : 'Switch to dark theme';
241
+ btn.setAttribute('aria-label', label);
242
+ btn.setAttribute('title', label);
243
+ btn.setAttribute('aria-pressed', String(dark));
244
+ };
245
+ const apply = (mode) => {
246
+ const dark = mode === 'dark';
247
+ document.body.classList.toggle('dark', dark);
248
+ document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
249
+ sync(dark);
250
+ };
251
+ let saved = null;
252
+ try { saved = localStorage.getItem(KEY); } catch (e) {}
253
+ const param = new URLSearchParams(window.location.search).get('__theme');
254
+ apply(param === 'dark' || param === 'light' ? param : (saved === 'dark' ? 'dark' : 'light'));
255
+ document.addEventListener('click', (event) => {
256
+ if (!event.target.closest('#theme-toggle')) return;
257
+ const next = document.body.classList.contains('dark') ? 'light' : 'dark';
258
+ apply(next);
259
+ try { localStorage.setItem(KEY, next); } catch (e) {}
260
+ });
261
+ // The hero (and its button) can render after this runs; label it once it exists.
262
+ const obs = new MutationObserver(() => {
263
+ if (document.getElementById('theme-toggle')) { sync(document.body.classList.contains('dark')); obs.disconnect(); }
264
+ });
265
+ obs.observe(document.body, { childList: true, subtree: true });
266
  }
267
  """
268
 
269
  # ---------------------------------------------------------------------------
270
  # Brand pieces
271
  # ---------------------------------------------------------------------------
272
+ def _grain(rgb: str) -> str:
273
+ return (
274
+ "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'>"
275
+ "<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/>"
276
+ f"<feColorMatrix values='0 0 0 0 {rgb} 0 0 0 0 {rgb} 0 0 0 0 {rgb} 0 0 0 0.6 0'/></filter>"
277
+ "<rect width='100%' height='100%' filter='url(%23n)'/></svg>\")"
278
+ )
279
+
280
+
281
+ _GRAIN = _grain("1") # light speckle, for dark surfaces and the hero
282
+ _GRAIN_DARK = _grain("0.1") # dark speckle, for the light page
283
+
284
+ # Light-bulb icon (Lucide "lightbulb", ISC license). The glass fills in when the light theme is on.
285
+ _BULB = (
286
+ '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" '
287
+ 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
288
+ '<path class="bulb-glass" d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 '
289
+ '1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>'
290
  )
291
 
292
 
 
294
  return f"""
295
  <section class="hero">
296
  <div class="hero-glow"></div>
297
+ <button id="theme-toggle" class="theme-toggle" type="button" aria-pressed="false"
298
+ aria-label="Switch to dark theme" title="Switch to dark theme">{_BULB}</button>
299
  <div class="hero-inner">
300
  <h1><b>Build your own</b> SafeTulu</h1>
301
  <p class="hero-sub">remove the Tulu 3 data that sits closest to harm, at any threshold you choose</p>
 
305
  <p>Every Tulu 3 example is scored by how close its training gradient is to harmful SafeRLHF data.
306
  Pick a threshold and the sources to filter, see what gets removed, then download the IDs,
307
  copy a script that reproduces your selection, or push the result to your own Hub account.</p>
308
+ <p class="meta-line">
309
+ <a href="https://huggingface.co/datasets/DatologyAI/SafeTulu" target="_blank">DatologyAI/SafeTulu</a>
310
+ </p>
311
  </div>
312
  """
313
 
314
 
315
  CSS = f"""
316
+ /* ---- theme tokens: light by default, dark under .dark ---- */
317
+ :root {{
318
+ --lt-page: {PAPER};
319
+ --lt-glow-1: rgba(21,116,235,0.10);
320
+ --lt-glow-2: rgba(57,176,223,0.14);
321
+ --lt-glow-3: rgba(232,105,42,0.06);
322
+ --lt-grain: {_GRAIN_DARK};
323
+ --lt-grain-opacity: 0.05;
324
+ --lt-grain-blend: multiply;
325
+ --lt-ink: {INK_NAVY};
326
+ --lt-ink-muted: {INK_MUTED};
327
+ --lt-line: {LINE_L};
328
+ --lt-label: {COBALT_TEXT};
329
+ --lt-edge: rgba(21,116,235,0.45);
330
+ --lt-tile-bg: linear-gradient(160deg, rgba(21,116,235,0.08), rgba(255,255,255,0.92) 55%);
331
+ --lt-tile-shadow: 0 10px 30px rgba(21,60,120,0.06);
332
+ --lt-accent-num: {ORANGE_TEXT};
333
+ --lt-meta-em: {ORANGE_TEXT};
334
+ --lt-link: {COBALT_TEXT};
335
+ --lt-link-line: rgba(18,87,196,0.35);
336
+ --lt-tab-active: {INK_NAVY};
337
+ --lt-plot-ink: {INK_MUTED};
338
+ --lt-plot-strong: {INK_NAVY};
339
+ --lt-plot-grid: rgba(21,60,120,0.10);
340
+ --lt-plot-cutoff: rgba(11,27,58,0.6);
341
+ --lt-hero-shadow: 0 24px 60px rgba(21,116,235,0.22);
342
+ }}
343
+ .dark {{
344
+ --lt-page: {NIGHT};
345
+ --lt-glow-1: rgba(21,116,235,0.30);
346
+ --lt-glow-2: rgba(6,78,111,0.45);
347
+ --lt-glow-3: rgba(5,45,120,0.55);
348
+ --lt-grain: {_GRAIN};
349
+ --lt-grain-opacity: 0.07;
350
+ --lt-grain-blend: soft-light;
351
+ --lt-ink: {TEXT};
352
+ --lt-ink-muted: {TEXT_MUTED};
353
+ --lt-line: {LINE};
354
+ --lt-label: {CYAN};
355
+ --lt-edge: rgba(57,176,223,0.7);
356
+ --lt-tile-bg: linear-gradient(160deg, rgba(21,116,235,0.16), rgba(10,22,48,0.6) 55%);
357
+ --lt-tile-shadow: none;
358
+ --lt-accent-num: {ORANGE_HI};
359
+ --lt-meta-em: {EMBER};
360
+ --lt-link: {CYAN};
361
+ --lt-link-line: rgba(57,176,223,0.4);
362
+ --lt-tab-active: {TEXT};
363
+ --lt-plot-ink: {TEXT_MUTED};
364
+ --lt-plot-strong: {TEXT};
365
+ --lt-plot-grid: rgba(159,179,207,0.12);
366
+ --lt-plot-cutoff: rgba(242,246,252,0.7);
367
+ --lt-hero-shadow: none;
368
+ }}
369
+
370
+ /* page: soft glows and film grain over the theme's base color */
371
  body, gradio-app {{
372
+ background: radial-gradient(1100px 620px at 8% -8%, var(--lt-glow-1), transparent 60%),
373
+ radial-gradient(900px 700px at 105% 20%, var(--lt-glow-2), transparent 60%),
374
+ radial-gradient(700px 500px at 60% 115%, var(--lt-glow-3), transparent 65%),
375
+ var(--lt-page) !important;
376
+ transition: background-color 0.25s ease;
377
  }}
378
  body::after {{
379
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9999;
380
+ background-image: var(--lt-grain); opacity: var(--lt-grain-opacity); mix-blend-mode: var(--lt-grain-blend);
381
  }}
382
  .gradio-container {{ width: 100% !important; max-width: 1240px !important; margin: 0 auto !important; background: transparent !important; }}
383
  footer {{ opacity: 0.55; }}
384
 
385
+ /* hero: the closing card of the brand film (same in both themes) */
386
  .hero {{
387
  position: relative; overflow: hidden; border-radius: 24px; min-height: 280px;
388
  display: flex; align-items: center; justify-content: center; text-align: center;
389
  background:
390
  radial-gradient(520px 340px at 16% 18%, rgba(164,217,232,0.95), transparent 70%),
391
  linear-gradient(118deg, {ICE} 0%, {CYAN} 26%, #1a9be8 48%, {COBALT} 76%, #0b45cf 100%);
392
+ box-shadow: var(--lt-hero-shadow);
393
  isolation: isolate;
394
  }}
395
  .hero-glow {{
 
405
  @media (max-width: 640px) {{
406
  .hero-glow {{ width: 260px; height: 260px; right: -18%; top: -14%; }}
407
  .hero {{ border-radius: 20px; }}
408
+ .hero-inner {{ padding-top: 68px !important; }}
409
+ .theme-toggle {{ top: 12px; right: 12px; }}
410
  }}
411
  .hero-inner {{ position: relative; padding: 44px 24px; color: #fff; }}
412
  .hero h1 {{
413
  margin: 0; color: #fff !important; font-weight: 300; letter-spacing: -0.035em; line-height: 1.02;
414
  font-size: clamp(38px, 6.4vw, 76px); text-shadow: 0 2px 24px rgba(5,45,120,0.25);
415
  }}
416
+ .hero h1 b {{ font-weight: 500; color: #fff !important; }}
417
  .hero-sub {{
418
  margin: 14px 0 0; font-size: clamp(17px, 2vw, 22px); font-weight: 300; color: #fff;
419
  text-shadow: 0 1px 14px rgba(5,45,120,0.35);
420
  }}
421
+
422
+ /* light-bulb theme switch, top right of the hero */
423
+ .theme-toggle {{
424
+ position: absolute; top: 16px; right: 16px; z-index: 2;
425
+ width: 44px; height: 44px; border-radius: 999px; cursor: pointer;
426
+ display: grid; place-items: center; color: #fff;
427
+ background: rgba(255,255,255,0.18); border: 1px solid rgba(255,255,255,0.45);
428
+ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
429
+ transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
430
+ }}
431
+ .theme-toggle:hover {{ background: rgba(255,255,255,0.3); transform: translateY(-1px); }}
432
+ .theme-toggle:focus-visible {{ outline: 2px solid #fff; outline-offset: 3px; }}
433
+ .theme-toggle .bulb-glass {{ fill: rgba(255, 214, 140, 0.95); transition: fill 0.2s ease; }}
434
+ .theme-toggle {{ box-shadow: 0 0 22px rgba(255, 190, 110, 0.55); }}
435
+ .dark .theme-toggle {{ background: rgba(4,11,24,0.35); border-color: rgba(255,255,255,0.3); box-shadow: none; }}
436
+ .dark .theme-toggle .bulb-glass {{ fill: none; }}
437
+
438
+ .hero-meta {{ max-width: 860px; margin: 22px auto 6px; text-align: center; color: var(--lt-ink-muted); }}
439
  .hero-meta p {{ margin: 0 0 8px; font-size: 15px; line-height: 1.6; }}
440
  .hero-meta .meta-line {{ font-size: 13px; letter-spacing: 0.01em; }}
441
  .hero-meta .meta-line span {{ margin: 0 6px; opacity: 0.6; }}
442
+ .hero-meta em {{ font-style: normal; color: var(--lt-meta-em); }}
443
+ .hero-meta a {{ color: var(--lt-link); text-decoration: none; border-bottom: 1px solid var(--lt-link-line); }}
444
 
445
  /* stat tiles */
446
  .tiles {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }}
447
  .tile {{
448
+ position: relative; overflow: hidden; border: 1px solid var(--lt-line); border-radius: 16px; padding: 16px 18px;
449
+ background: var(--lt-tile-bg); box-shadow: var(--lt-tile-shadow);
450
  }}
451
  .tile::before {{
452
  content: ""; position: absolute; left: 18px; right: 18px; top: 0; height: 1px;
453
+ background: linear-gradient(90deg, transparent, var(--lt-edge), transparent);
454
  }}
455
+ .tile-label {{ font-size: 11px; font-weight: 500; color: var(--lt-label); text-transform: uppercase; letter-spacing: 0.12em; }}
456
+ .tile-value {{ font-size: 30px; font-weight: 400; letter-spacing: -0.02em; color: var(--lt-ink);
457
  font-variant-numeric: tabular-nums; margin-top: 6px; }}
458
+ .tile-sub {{ font-size: 12px; color: var(--lt-ink-muted); margin-top: 4px; line-height: 1.4; }}
459
+ .tile.accent .tile-value {{ color: var(--lt-accent-num); }}
460
 
461
  /* section headings above the charts */
462
  .section-title p {{ margin: 6px 0 -4px !important; font-size: 13px !important; font-weight: 500 !important;
463
+ color: var(--lt-ink-muted) !important; text-transform: uppercase; letter-spacing: 0.12em; }}
464
 
465
  /* tabs */
466
+ button[role="tab"] {{ color: var(--lt-ink-muted) !important; font-weight: 500; }}
467
+ button[role="tab"][aria-selected="true"] {{ color: var(--lt-tab-active) !important; border-color: {ORANGE} !important; }}
468
+
469
+ /* PLOT CSS: axis text, gridlines and the cutoff line follow the theme without re-rendering the charts.
470
+ Hover tooltips stay navy in both themes. */
471
+ .js-plotly-plot .cartesianlayer text,
472
+ .js-plotly-plot .infolayer text {{ fill: var(--lt-plot-ink) !important; }}
473
+ .js-plotly-plot .infolayer .annotation text {{ fill: var(--lt-plot-strong) !important; }}
474
+ .js-plotly-plot .gridlayer path,
475
+ .js-plotly-plot path.xlines-above,
476
+ .js-plotly-plot path.ylines-above {{ stroke: var(--lt-plot-grid) !important; }}
477
+ .js-plotly-plot .shapelayer path {{ stroke: var(--lt-plot-cutoff) !important; }}
478
  """