idnameraj commited on
Commit
01cbc60
·
verified ·
1 Parent(s): 48d657f

Upload 66 files

Browse files
Dockerfile CHANGED
@@ -15,7 +15,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
15
  PIP_NO_CACHE_DIR=1 \
16
  HOST=0.0.0.0 \
17
  PORT=7860 \
18
- APP_TITLE=PlainRewrite
19
 
20
  WORKDIR /app
21
 
 
15
  PIP_NO_CACHE_DIR=1 \
16
  HOST=0.0.0.0 \
17
  PORT=7860 \
18
+ APP_TITLE=ZuZu Writer
19
 
20
  WORKDIR /app
21
 
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: PlainRewrite
3
  emoji: ✍️
4
  colorFrom: gray
5
  colorTo: blue
@@ -8,7 +8,7 @@ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
- # PlainRewrite
12
 
13
  Rewrite text in Neutral, Casual, Formal, or Academic tone.
14
 
 
1
  ---
2
+ title: ZuZu Writer
3
  emoji: ✍️
4
  colorFrom: gray
5
  colorTo: blue
 
8
  pinned: false
9
  ---
10
 
11
+ # ZuZu Writer
12
 
13
  Rewrite text in Neutral, Casual, Formal, or Academic tone.
14
 
app/__init__.py CHANGED
@@ -1 +1 @@
1
- # PlainRewrite app package
 
1
+ # ZuZu Writer app package
app/config.py CHANGED
@@ -13,7 +13,7 @@ load_dotenv(ROOT_DIR / ".env")
13
  APP_DIR = Path(__file__).resolve().parent
14
  DATA_DIR = APP_DIR / "data"
15
 
16
- APP_TITLE = os.environ.get("APP_TITLE", "PlainRewrite")
17
  HOST = os.environ.get("HOST", "0.0.0.0")
18
  PORT = int(os.environ.get("PORT", "7860") or "7860")
19
  SPACY_MODEL = os.environ.get("SPACY_MODEL", "en_core_web_sm")
 
13
  APP_DIR = Path(__file__).resolve().parent
14
  DATA_DIR = APP_DIR / "data"
15
 
16
+ APP_TITLE = os.environ.get("APP_TITLE", "ZuZu Writer")
17
  HOST = os.environ.get("HOST", "0.0.0.0")
18
  PORT = int(os.environ.get("PORT", "7860") or "7860")
19
  SPACY_MODEL = os.environ.get("SPACY_MODEL", "en_core_web_sm")
docker-compose.yml CHANGED
@@ -7,7 +7,7 @@ services:
7
  environment:
8
  HOST: "0.0.0.0"
9
  PORT: "7860"
10
- APP_TITLE: "PlainRewrite"
11
  SPACY_MODEL: "en_core_web_sm"
12
  SUPABASE_URL: ${SUPABASE_URL:-}
13
  SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY:-}
 
7
  environment:
8
  HOST: "0.0.0.0"
9
  PORT: "7860"
10
+ APP_TITLE: "ZuZu Writer"
11
  SPACY_MODEL: "en_core_web_sm"
12
  SUPABASE_URL: ${SUPABASE_URL:-}
13
  SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY:-}
frontend/dist/assets/index.css CHANGED
@@ -31,25 +31,51 @@ body {
31
  margin: 0;
32
  color: var(--ink);
33
  font-family: var(--font-body);
34
- background-color: #dfe8e4;
35
  background-image:
36
- radial-gradient(ellipse 90% 60% at 10% -20%, #c5ddd4 0%, transparent 55%),
37
- radial-gradient(ellipse 70% 50% at 100% 0%, #d4dde8 0%, transparent 50%),
38
- linear-gradient(165deg, #eef4f1 0%, #e3ebe7 45%, #d8e2dd 100%);
 
39
  background-attachment: fixed;
40
  }
41
 
 
42
  body::before {
43
  content: "";
44
  position: fixed;
45
  inset: 0;
46
  pointer-events: none;
47
- opacity: 0.35;
48
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.45'/%3E%3C/svg%3E");
49
  mix-blend-mode: soft-light;
50
  z-index: 0;
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  button,
54
  textarea {
55
  font: inherit;
@@ -67,6 +93,36 @@ textarea {
67
  animation: enter 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
68
  }
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @keyframes enter {
71
  from {
72
  opacity: 0;
@@ -104,44 +160,105 @@ textarea {
104
  align-items: flex-end;
105
  justify-content: space-between;
106
  gap: 1.5rem;
107
- margin-bottom: 1.75rem;
108
  }
109
 
110
  .brand {
111
- max-width: 36rem;
 
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  .brand h1 {
115
  margin: 0;
116
  font-family: var(--font-display);
117
- font-size: clamp(2.6rem, 5vw, 3.6rem);
118
  font-weight: 700;
119
  letter-spacing: -0.035em;
120
  line-height: 0.95;
121
  color: var(--ink);
122
  }
123
 
124
- .brand p {
125
- margin: 0.7rem 0 0;
126
  color: var(--ink-soft);
127
- font-size: 1.05rem;
128
  line-height: 1.45;
129
  max-width: 28rem;
130
  }
131
 
132
  .top-meta {
133
- text-align: right;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  color: var(--muted);
135
- font-size: 0.82rem;
136
- letter-spacing: 0.02em;
137
- padding-bottom: 0.35rem;
138
- min-width: 10rem;
139
  }
140
 
141
  .stage {
142
  border: 1px solid var(--panel-edge);
143
  border-radius: 22px;
144
- background: rgba(251, 252, 251, 0.78);
145
  box-shadow: var(--shadow-soft);
146
  backdrop-filter: blur(14px);
147
  overflow: hidden;
@@ -151,11 +268,20 @@ textarea {
151
  .toolbar {
152
  display: flex;
153
  flex-wrap: wrap;
154
- align-items: center;
 
155
  gap: 1rem 1.25rem;
156
- padding: 0.95rem 1.15rem;
157
  border-bottom: 1px solid var(--panel-edge);
158
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(244, 248, 246, 0.65));
 
 
 
 
 
 
 
 
159
  }
160
 
161
  .tool-group {
@@ -164,7 +290,7 @@ textarea {
164
  gap: 0.35rem;
165
  }
166
 
167
- .tool-group > span {
168
  font-size: 0.68rem;
169
  font-weight: 600;
170
  letter-spacing: 0.1em;
@@ -184,7 +310,7 @@ textarea {
184
  border: 0;
185
  background: transparent;
186
  color: var(--ink-soft);
187
- padding: 0.42rem 0.72rem;
188
  border-radius: 8px;
189
  font-size: 0.86rem;
190
  font-weight: 500;
@@ -209,11 +335,11 @@ textarea {
209
  display: inline-flex;
210
  align-items: center;
211
  gap: 0.5rem;
212
- margin-left: auto;
213
  color: var(--ink-soft);
214
  font-size: 0.88rem;
215
  cursor: pointer;
216
  user-select: none;
 
217
  }
218
 
219
  .check input {
@@ -225,24 +351,27 @@ textarea {
225
  .toolbar-actions {
226
  display: flex;
227
  align-items: center;
228
- gap: 0.5rem;
 
229
  }
230
 
231
  .btn {
232
  border: 0;
233
- border-radius: 10px;
234
- padding: 0.7rem 1.15rem;
235
  font-weight: 600;
236
  font-size: 0.92rem;
237
  cursor: pointer;
238
  transition:
239
  transform 0.15s ease,
240
  background 0.2s ease,
241
- opacity 0.2s ease;
 
 
242
  }
243
 
244
  .btn:disabled {
245
- opacity: 0.5;
246
  cursor: not-allowed;
247
  transform: none;
248
  }
@@ -250,6 +379,7 @@ textarea {
250
  .btn-primary {
251
  background: var(--accent);
252
  color: #f5faf8;
 
253
  }
254
 
255
  .btn-primary:not(:disabled):hover {
@@ -261,14 +391,47 @@ textarea {
261
  transform: translateY(0);
262
  }
263
 
 
 
 
 
 
 
264
  .btn-quiet {
265
- background: transparent;
266
  color: var(--ink-soft);
267
  border: 1px solid var(--panel-edge);
268
  }
269
 
270
  .btn-quiet:not(:disabled):hover {
271
- background: rgba(255, 255, 255, 0.7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  }
273
 
274
  .editors {
@@ -281,12 +444,17 @@ textarea {
281
  display: flex;
282
  flex-direction: column;
283
  min-height: 0;
 
284
  }
285
 
286
  .pane + .pane {
287
  border-left: 1px solid var(--panel-edge);
288
  }
289
 
 
 
 
 
290
  .pane-head {
291
  display: flex;
292
  align-items: center;
@@ -306,7 +474,7 @@ textarea {
306
 
307
  .pane-actions {
308
  display: flex;
309
- gap: 0.35rem;
310
  }
311
 
312
  .ghost-btn {
@@ -361,15 +529,16 @@ textarea {
361
  align-items: center;
362
  justify-content: space-between;
363
  gap: 1rem;
364
- padding: 0.75rem 1.15rem;
365
  border-top: 1px solid var(--panel-edge);
366
- background: rgba(244, 248, 246, 0.55);
367
  color: var(--muted);
368
  font-size: 0.84rem;
369
  }
370
 
371
  .statusbar .error {
372
  color: var(--warn);
 
373
  }
374
 
375
  .statusbar .loading {
@@ -388,30 +557,6 @@ textarea {
388
  font-size: 0.84rem;
389
  }
390
 
391
- .account-chip {
392
- display: flex;
393
- flex-direction: column;
394
- align-items: flex-end;
395
- gap: 0.2rem;
396
- text-align: right;
397
- }
398
-
399
- .account-plan {
400
- font-weight: 700;
401
- color: var(--accent);
402
- letter-spacing: 0.04em;
403
- text-transform: uppercase;
404
- font-size: 0.72rem;
405
- }
406
-
407
- .account-email {
408
- max-width: 14rem;
409
- overflow: hidden;
410
- text-overflow: ellipsis;
411
- white-space: nowrap;
412
- font-size: 0.78rem;
413
- }
414
-
415
  .auth-shell {
416
  position: relative;
417
  z-index: 1;
@@ -527,12 +672,13 @@ textarea {
527
  }
528
 
529
  .teaser-banner {
530
- margin: -0.5rem 0 1.1rem;
531
- padding: 0.75rem 1rem;
532
- border-radius: 12px;
533
- background: var(--accent-soft);
 
534
  color: var(--ink-soft);
535
- font-size: 0.92rem;
536
  line-height: 1.4;
537
  }
538
 
@@ -540,23 +686,40 @@ textarea {
540
  color: var(--accent);
541
  }
542
 
543
- .idle-banner {
544
  display: flex;
545
  flex-wrap: wrap;
546
  align-items: center;
547
- gap: 0.35rem 0.5rem;
 
548
  }
549
 
550
- .auth-inline {
551
  display: flex;
552
- align-items: center;
553
- gap: 0.35rem;
554
- margin-top: 0.25rem;
 
555
  }
556
 
557
- .btn-compact {
558
- padding: 0.4rem 0.75rem;
559
- font-size: 0.8rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  }
561
 
562
  .counts .over-limit {
@@ -564,6 +727,14 @@ textarea {
564
  font-weight: 600;
565
  }
566
 
 
 
 
 
 
 
 
 
567
  .modal-backdrop {
568
  position: fixed;
569
  inset: 0;
@@ -675,22 +846,6 @@ textarea {
675
  font-size: 0.78rem;
676
  }
677
 
678
- .teaser-cta {
679
- display: flex;
680
- flex-wrap: wrap;
681
- align-items: center;
682
- justify-content: space-between;
683
- gap: 0.75rem 1rem;
684
- }
685
-
686
- .tool-hint {
687
- font-size: 0.72rem;
688
- color: var(--muted);
689
- letter-spacing: 0.02em;
690
- text-transform: none;
691
- font-weight: 500;
692
- }
693
-
694
  .word-meter {
695
  height: 3px;
696
  background: rgba(18, 28, 26, 0.06);
@@ -878,34 +1033,44 @@ textarea {
878
  @media (max-width: 920px) {
879
  .topbar {
880
  flex-direction: column;
881
- align-items: flex-start;
882
  }
883
 
884
  .top-meta {
885
- text-align: left;
886
  }
887
 
888
- .account-chip {
889
- align-items: flex-start;
890
- text-align: left;
891
  }
892
 
893
- .check {
894
- margin-left: 0;
895
  }
896
 
897
  .toolbar {
898
  align-items: stretch;
 
 
 
 
 
899
  }
900
 
901
  .toolbar-actions {
902
  width: 100%;
 
903
  }
904
 
905
  .toolbar-actions .btn {
906
  flex: 1;
907
  }
908
 
 
 
 
 
909
  .editors {
910
  grid-template-columns: 1fr;
911
  }
@@ -926,6 +1091,10 @@ textarea {
926
 
927
  .teaser-cta {
928
  flex-direction: column;
929
- align-items: flex-start;
 
 
 
 
930
  }
931
  }
 
31
  margin: 0;
32
  color: var(--ink);
33
  font-family: var(--font-body);
34
+ background-color: #d4e0da;
35
  background-image:
36
+ radial-gradient(ellipse 100% 70% at 0% -10%, rgba(120, 170, 150, 0.55) 0%, transparent 58%),
37
+ radial-gradient(ellipse 80% 55% at 100% 5%, rgba(160, 175, 200, 0.4) 0%, transparent 52%),
38
+ radial-gradient(ellipse 70% 45% at 50% 110%, rgba(90, 130, 110, 0.22) 0%, transparent 55%),
39
+ linear-gradient(165deg, #eef5f1 0%, #e0ebe5 42%, #d2dfd8 100%);
40
  background-attachment: fixed;
41
  }
42
 
43
+ /* Paper grain */
44
  body::before {
45
  content: "";
46
  position: fixed;
47
  inset: 0;
48
  pointer-events: none;
49
+ opacity: 0.42;
50
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.45'/%3E%3C/svg%3E");
51
  mix-blend-mode: soft-light;
52
  z-index: 0;
53
  }
54
 
55
+ /* Soft writing-desk wash + faint ruled lines */
56
+ body::after {
57
+ content: "";
58
+ position: fixed;
59
+ inset: 0;
60
+ pointer-events: none;
61
+ z-index: 0;
62
+ background-image:
63
+ radial-gradient(ellipse 55% 40% at 85% 75%, rgba(26, 92, 74, 0.12) 0%, transparent 70%),
64
+ radial-gradient(ellipse 40% 35% at 12% 60%, rgba(70, 100, 140, 0.1) 0%, transparent 65%),
65
+ linear-gradient(
66
+ to bottom,
67
+ transparent 0,
68
+ transparent 11px,
69
+ rgba(18, 28, 26, 0.035) 11px,
70
+ rgba(18, 28, 26, 0.035) 12px
71
+ );
72
+ background-size:
73
+ auto,
74
+ auto,
75
+ 100% 28px;
76
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.25) 40%, rgba(0, 0, 0, 0.45));
77
+ }
78
+
79
  button,
80
  textarea {
81
  font: inherit;
 
93
  animation: enter 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
94
  }
95
 
96
+ /* Large brand watermark in the atmosphere */
97
+ .shell::before {
98
+ content: "ZuZu";
99
+ position: fixed;
100
+ right: -4vw;
101
+ top: 8vh;
102
+ z-index: 0;
103
+ font-family: var(--font-display);
104
+ font-size: clamp(7rem, 22vw, 16rem);
105
+ font-weight: 700;
106
+ letter-spacing: -0.06em;
107
+ line-height: 0.85;
108
+ color: rgba(26, 92, 74, 0.07);
109
+ pointer-events: none;
110
+ user-select: none;
111
+ transform: rotate(-8deg);
112
+ animation: watermark-in 1.2s cubic-bezier(0.22, 1, 0.36, 1) both;
113
+ }
114
+
115
+ @keyframes watermark-in {
116
+ from {
117
+ opacity: 0;
118
+ transform: rotate(-8deg) translateY(24px);
119
+ }
120
+ to {
121
+ opacity: 1;
122
+ transform: rotate(-8deg) translateY(0);
123
+ }
124
+ }
125
+
126
  @keyframes enter {
127
  from {
128
  opacity: 0;
 
160
  align-items: flex-end;
161
  justify-content: space-between;
162
  gap: 1.5rem;
163
+ margin-bottom: 1.35rem;
164
  }
165
 
166
  .brand {
167
+ max-width: 38rem;
168
+ }
169
+
170
+ .brand-mark {
171
+ margin: 0 0 0.35rem;
172
+ font-family: var(--font-body);
173
+ font-size: 0.72rem;
174
+ font-weight: 700;
175
+ letter-spacing: 0.18em;
176
+ text-transform: uppercase;
177
+ color: var(--accent);
178
  }
179
 
180
  .brand h1 {
181
  margin: 0;
182
  font-family: var(--font-display);
183
+ font-size: clamp(2.5rem, 5vw, 3.45rem);
184
  font-weight: 700;
185
  letter-spacing: -0.035em;
186
  line-height: 0.95;
187
  color: var(--ink);
188
  }
189
 
190
+ .brand-tag {
191
+ margin: 0.75rem 0 0;
192
  color: var(--ink-soft);
193
+ font-size: 1.02rem;
194
  line-height: 1.45;
195
  max-width: 28rem;
196
  }
197
 
198
  .top-meta {
199
+ flex-shrink: 0;
200
+ padding-bottom: 0.15rem;
201
+ }
202
+
203
+ .account-panel {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.85rem;
207
+ padding: 0.7rem 0.8rem 0.7rem 1rem;
208
+ border: 1px solid var(--panel-edge);
209
+ border-radius: 16px;
210
+ background: rgba(251, 252, 251, 0.88);
211
+ box-shadow: 0 10px 28px rgba(18, 28, 26, 0.05);
212
+ backdrop-filter: blur(10px);
213
+ }
214
+
215
+ .account-panel-quiet {
216
+ flex-direction: column;
217
+ align-items: flex-end;
218
+ gap: 0.2rem;
219
+ padding: 0.65rem 0.9rem;
220
+ }
221
+
222
+ .account-panel-main {
223
+ display: flex;
224
+ flex-direction: column;
225
+ align-items: flex-start;
226
+ gap: 0.12rem;
227
+ min-width: 0;
228
+ }
229
+
230
+ .account-plan {
231
+ font-weight: 700;
232
+ color: var(--accent);
233
+ letter-spacing: 0.08em;
234
+ text-transform: uppercase;
235
+ font-size: 0.68rem;
236
+ }
237
+
238
+ .account-quota {
239
+ color: var(--ink-soft);
240
+ font-size: 0.84rem;
241
+ font-weight: 500;
242
+ }
243
+
244
+ .account-email {
245
+ max-width: 12rem;
246
+ overflow: hidden;
247
+ text-overflow: ellipsis;
248
+ white-space: nowrap;
249
+ font-size: 0.75rem;
250
+ color: var(--muted);
251
+ }
252
+
253
+ .kbd-hint {
254
+ font-size: 0.75rem;
255
  color: var(--muted);
 
 
 
 
256
  }
257
 
258
  .stage {
259
  border: 1px solid var(--panel-edge);
260
  border-radius: 22px;
261
+ background: rgba(251, 252, 251, 0.82);
262
  box-shadow: var(--shadow-soft);
263
  backdrop-filter: blur(14px);
264
  overflow: hidden;
 
268
  .toolbar {
269
  display: flex;
270
  flex-wrap: wrap;
271
+ align-items: flex-end;
272
+ justify-content: space-between;
273
  gap: 1rem 1.25rem;
274
+ padding: 1rem 1.2rem;
275
  border-bottom: 1px solid var(--panel-edge);
276
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(244, 248, 246, 0.7));
277
+ }
278
+
279
+ .toolbar-controls {
280
+ display: flex;
281
+ flex-wrap: wrap;
282
+ align-items: flex-end;
283
+ gap: 1rem 1.35rem;
284
+ min-width: 0;
285
  }
286
 
287
  .tool-group {
 
290
  gap: 0.35rem;
291
  }
292
 
293
+ .tool-group > span:first-child {
294
  font-size: 0.68rem;
295
  font-weight: 600;
296
  letter-spacing: 0.1em;
 
310
  border: 0;
311
  background: transparent;
312
  color: var(--ink-soft);
313
+ padding: 0.45rem 0.75rem;
314
  border-radius: 8px;
315
  font-size: 0.86rem;
316
  font-weight: 500;
 
335
  display: inline-flex;
336
  align-items: center;
337
  gap: 0.5rem;
 
338
  color: var(--ink-soft);
339
  font-size: 0.88rem;
340
  cursor: pointer;
341
  user-select: none;
342
+ padding-bottom: 0.35rem;
343
  }
344
 
345
  .check input {
 
351
  .toolbar-actions {
352
  display: flex;
353
  align-items: center;
354
+ gap: 0.55rem;
355
+ margin-left: auto;
356
  }
357
 
358
  .btn {
359
  border: 0;
360
+ border-radius: 11px;
361
+ padding: 0.72rem 1.2rem;
362
  font-weight: 600;
363
  font-size: 0.92rem;
364
  cursor: pointer;
365
  transition:
366
  transform 0.15s ease,
367
  background 0.2s ease,
368
+ opacity 0.2s ease,
369
+ border-color 0.2s ease,
370
+ box-shadow 0.2s ease;
371
  }
372
 
373
  .btn:disabled {
374
+ opacity: 0.45;
375
  cursor: not-allowed;
376
  transform: none;
377
  }
 
379
  .btn-primary {
380
  background: var(--accent);
381
  color: #f5faf8;
382
+ box-shadow: 0 8px 18px rgba(26, 92, 74, 0.22);
383
  }
384
 
385
  .btn-primary:not(:disabled):hover {
 
391
  transform: translateY(0);
392
  }
393
 
394
+ .btn-rewrite {
395
+ min-width: 8.5rem;
396
+ padding: 0.82rem 1.45rem;
397
+ font-size: 0.98rem;
398
+ }
399
+
400
  .btn-quiet {
401
+ background: rgba(255, 255, 255, 0.75);
402
  color: var(--ink-soft);
403
  border: 1px solid var(--panel-edge);
404
  }
405
 
406
  .btn-quiet:not(:disabled):hover {
407
+ background: #fff;
408
+ color: var(--ink);
409
+ border-color: rgba(26, 92, 74, 0.28);
410
+ }
411
+
412
+ .btn-compact {
413
+ padding: 0.52rem 1rem;
414
+ font-size: 0.84rem;
415
+ min-height: 2.4rem;
416
+ }
417
+
418
+ .btn-tiny {
419
+ padding: 0.32rem 0.7rem;
420
+ font-size: 0.76rem;
421
+ border-radius: 8px;
422
+ font-weight: 600;
423
+ }
424
+
425
+ .auth-inline {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 0.45rem;
429
+ }
430
+
431
+ .auth-inline .btn-quiet {
432
+ background: rgba(255, 255, 255, 0.95);
433
+ color: var(--ink);
434
+ box-shadow: 0 1px 2px rgba(18, 28, 26, 0.04);
435
  }
436
 
437
  .editors {
 
444
  display: flex;
445
  flex-direction: column;
446
  min-height: 0;
447
+ background: transparent;
448
  }
449
 
450
  .pane + .pane {
451
  border-left: 1px solid var(--panel-edge);
452
  }
453
 
454
+ .pane-out {
455
+ background: rgba(244, 248, 246, 0.35);
456
+ }
457
+
458
  .pane-head {
459
  display: flex;
460
  align-items: center;
 
474
 
475
  .pane-actions {
476
  display: flex;
477
+ gap: 0.4rem;
478
  }
479
 
480
  .ghost-btn {
 
529
  align-items: center;
530
  justify-content: space-between;
531
  gap: 1rem;
532
+ padding: 0.8rem 1.2rem;
533
  border-top: 1px solid var(--panel-edge);
534
+ background: rgba(244, 248, 246, 0.6);
535
  color: var(--muted);
536
  font-size: 0.84rem;
537
  }
538
 
539
  .statusbar .error {
540
  color: var(--warn);
541
+ font-weight: 500;
542
  }
543
 
544
  .statusbar .loading {
 
557
  font-size: 0.84rem;
558
  }
559
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  .auth-shell {
561
  position: relative;
562
  z-index: 1;
 
672
  }
673
 
674
  .teaser-banner {
675
+ margin: 0 0 1.15rem;
676
+ padding: 0.95rem 1.15rem;
677
+ border-radius: 16px;
678
+ border: 1px solid rgba(26, 92, 74, 0.18);
679
+ background: linear-gradient(135deg, rgba(26, 92, 74, 0.1), rgba(251, 252, 251, 0.75));
680
  color: var(--ink-soft);
681
+ font-size: 0.94rem;
682
  line-height: 1.4;
683
  }
684
 
 
686
  color: var(--accent);
687
  }
688
 
689
+ .teaser-cta {
690
  display: flex;
691
  flex-wrap: wrap;
692
  align-items: center;
693
+ justify-content: space-between;
694
+ gap: 0.85rem 1.25rem;
695
  }
696
 
697
+ .teaser-copy {
698
  display: flex;
699
+ flex-direction: column;
700
+ gap: 0.25rem;
701
+ min-width: 0;
702
+ flex: 1;
703
  }
704
 
705
+ .teaser-copy p {
706
+ margin: 0;
707
+ }
708
+
709
+ .teaser-label {
710
+ font-size: 0.68rem;
711
+ font-weight: 700;
712
+ letter-spacing: 0.12em;
713
+ text-transform: uppercase;
714
+ color: var(--accent);
715
+ }
716
+
717
+ .idle-banner {
718
+ display: flex;
719
+ flex-wrap: wrap;
720
+ align-items: center;
721
+ justify-content: space-between;
722
+ gap: 0.65rem 1rem;
723
  }
724
 
725
  .counts .over-limit {
 
727
  font-weight: 600;
728
  }
729
 
730
+ .tool-hint {
731
+ font-size: 0.72rem;
732
+ color: var(--muted);
733
+ letter-spacing: 0.02em;
734
+ text-transform: none;
735
+ font-weight: 500;
736
+ }
737
+
738
  .modal-backdrop {
739
  position: fixed;
740
  inset: 0;
 
846
  font-size: 0.78rem;
847
  }
848
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
  .word-meter {
850
  height: 3px;
851
  background: rgba(18, 28, 26, 0.06);
 
1033
  @media (max-width: 920px) {
1034
  .topbar {
1035
  flex-direction: column;
1036
+ align-items: stretch;
1037
  }
1038
 
1039
  .top-meta {
1040
+ width: 100%;
1041
  }
1042
 
1043
+ .account-panel {
1044
+ width: 100%;
1045
+ justify-content: space-between;
1046
  }
1047
 
1048
+ .account-panel-quiet {
1049
+ align-items: flex-start;
1050
  }
1051
 
1052
  .toolbar {
1053
  align-items: stretch;
1054
+ flex-direction: column;
1055
+ }
1056
+
1057
+ .toolbar-controls {
1058
+ width: 100%;
1059
  }
1060
 
1061
  .toolbar-actions {
1062
  width: 100%;
1063
+ margin-left: 0;
1064
  }
1065
 
1066
  .toolbar-actions .btn {
1067
  flex: 1;
1068
  }
1069
 
1070
+ .btn-rewrite {
1071
+ flex: 1.4;
1072
+ }
1073
+
1074
  .editors {
1075
  grid-template-columns: 1fr;
1076
  }
 
1091
 
1092
  .teaser-cta {
1093
  flex-direction: column;
1094
+ align-items: stretch;
1095
+ }
1096
+
1097
+ .teaser-cta .btn {
1098
+ width: 100%;
1099
  }
1100
  }
frontend/dist/assets/index.js CHANGED
@@ -461,7 +461,7 @@ function App() {
461
  const url = URL.createObjectURL(blob);
462
  const a = document.createElement("a");
463
  a.href = url;
464
- a.download = "plainrewrite.txt";
465
  a.click();
466
  URL.revokeObjectURL(url);
467
  }
@@ -475,7 +475,7 @@ function App() {
475
  return h("div", { className: "app shell" },
476
  h("div", { className: "topbar" },
477
  h("header", { className: "brand" },
478
- h("h1", null, "PlainRewrite"),
479
  h("p", null, "From AI-generated to plagiarism-safe — rewrite in a voice that feels real."),
480
  ),
481
  h("div", { className: "top-meta" },
@@ -493,7 +493,7 @@ function App() {
493
  ? `${account.usage.remaining_rewrites}/${account.plan.daily_rewrites} free rewrite today`
494
  : `Try ${guestMaxWords} words free`),
495
  h("div", { className: "auth-inline" },
496
- h("button", { type: "button", className: "ghost-btn", onClick: () => openAuth("signin", "Sign in") }, "Sign in"),
497
  h("button", { type: "button", className: "btn btn-primary btn-compact", onClick: () => openAuth("signup", "Create free account") }, "Sign up"),
498
  ),
499
  )
 
461
  const url = URL.createObjectURL(blob);
462
  const a = document.createElement("a");
463
  a.href = url;
464
+ a.download = "zuzu-writer.txt";
465
  a.click();
466
  URL.revokeObjectURL(url);
467
  }
 
475
  return h("div", { className: "app shell" },
476
  h("div", { className: "topbar" },
477
  h("header", { className: "brand" },
478
+ h("h1", null, "ZuZu Writer"),
479
  h("p", null, "From AI-generated to plagiarism-safe — rewrite in a voice that feels real."),
480
  ),
481
  h("div", { className: "top-meta" },
 
493
  ? `${account.usage.remaining_rewrites}/${account.plan.daily_rewrites} free rewrite today`
494
  : `Try ${guestMaxWords} words free`),
495
  h("div", { className: "auth-inline" },
496
+ h("button", { type: "button", className: "btn btn-quiet btn-compact", onClick: () => openAuth("signin", "Sign in") }, "Sign in"),
497
  h("button", { type: "button", className: "btn btn-primary btn-compact", onClick: () => openAuth("signup", "Create free account") }, "Sign up"),
498
  ),
499
  )
frontend/dist/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>PlainRewrite</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
  <link
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ZuZu Writer</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
  <link
frontend/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>PlainRewrite</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
  <link
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ZuZu Writer</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
  <link
frontend/package.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "name": "plainrewrite-ui",
3
  "private": true,
4
  "version": "1.0.0",
5
  "type": "module",
 
1
  {
2
+ "name": "zuzu-writer-ui",
3
  "private": true,
4
  "version": "1.0.0",
5
  "type": "module",
frontend/src/App.tsx CHANGED
@@ -258,7 +258,7 @@ function LandingSections({
258
  return (
259
  <div className="landing">
260
  <section className="land-block">
261
- <h2>How to use PlainRewrite</h2>
262
  <p className="land-lead">Three steps from AI draft to natural wording.</p>
263
  <ol className="steps">
264
  <li>
@@ -350,7 +350,7 @@ function LandingSections({
350
 
351
  <footer className="site-footer">
352
  <p>Review every rewrite before you share or publish. Follow your school or workplace AI policy.</p>
353
- <p className="footer-brand">PlainRewrite</p>
354
  </footer>
355
  </div>
356
  );
@@ -512,7 +512,7 @@ export default function App() {
512
  const url = URL.createObjectURL(blob);
513
  const a = document.createElement("a");
514
  a.href = url;
515
- a.download = "plainrewrite.txt";
516
  a.click();
517
  URL.revokeObjectURL(url);
518
  }
@@ -534,33 +534,40 @@ export default function App() {
534
  <div className="app shell">
535
  <div className="topbar">
536
  <header className="brand">
537
- <h1>PlainRewrite</h1>
538
- <p>From AI-generated to plagiarism-safe — rewrite in a voice that feels real.</p>
 
 
 
539
  </header>
540
  <div className="top-meta">
541
  {session && account ? (
542
- <div className="account-chip">
543
- <div className="account-plan">{account.plan.name}</div>
544
- <div>
545
- {account.usage.remaining_rewrites}/{account.plan.daily_rewrites} rewrites today
 
 
 
546
  </div>
547
- <div className="account-email">{account.email}</div>
548
- <button type="button" className="ghost-btn" onClick={() => void signOut()}>
549
  Sign out
550
  </button>
551
  </div>
552
  ) : authEnabled ? (
553
- <div className="account-chip">
554
- <div className="account-plan">Preview</div>
555
- <div>
556
- {account
557
- ? `${account.usage.remaining_rewrites}/${account.plan.daily_rewrites} free rewrite today`
558
- : `Try ${guestMaxWords} words free`}
 
 
559
  </div>
560
  <div className="auth-inline">
561
  <button
562
  type="button"
563
- className="ghost-btn"
564
  onClick={() => openAuth("signin", "Sign in")}
565
  >
566
  Sign in
@@ -575,11 +582,12 @@ export default function App() {
575
  </div>
576
  </div>
577
  ) : (
578
- <>
579
- {tone} · {strength}
580
- <br />
581
- /Ctrl + Enter
582
- </>
 
583
  )}
584
  </div>
585
  </div>
@@ -589,7 +597,7 @@ export default function App() {
589
  <span>Signed out after {sessionIdleMinutes} minutes of inactivity.</span>
590
  <button
591
  type="button"
592
- className="ghost-btn"
593
  onClick={() => {
594
  clearIdleNotice();
595
  openAuth("signin", "Sign in again");
@@ -602,13 +610,16 @@ export default function App() {
602
 
603
  {isGuest && !idleSignedOut ? (
604
  <div className="teaser-banner teaser-cta">
605
- <div>
606
- You’re on <strong>Free Preview</strong> — up to <strong>{maxWords} words</strong>,{" "}
607
- {account?.plan.daily_rewrites ?? 1} rewrite/day. Sign up free for 400 words &amp; 5/day.
 
 
 
608
  </div>
609
  <button
610
  type="button"
611
- className="btn btn-primary btn-compact"
612
  onClick={() => openAuth("signup", "Create free account")}
613
  >
614
  Unlock Free plan
@@ -618,58 +629,52 @@ export default function App() {
618
 
619
  <div className="stage">
620
  <div className="toolbar">
621
- <div className="tool-group">
622
- <span>Tone</span>
623
- <div className="segment" role="group" aria-label="Tone">
624
- {TONES.map((t) => (
625
- <button
626
- key={t}
627
- type="button"
628
- title={TONE_HINT[t]}
629
- className={tone === t ? "active" : ""}
630
- onClick={() => setTone(t)}
631
- >
632
- {t}
633
- </button>
634
- ))}
 
 
 
635
  </div>
636
- <span className="tool-hint">{TONE_HINT[tone]}</span>
637
- </div>
638
 
639
- <div className="tool-group">
640
- <span>Strength</span>
641
- <div className="segment" role="group" aria-label="Strength">
642
- {STRENGTHS.map((s) => (
643
- <button
644
- key={s}
645
- type="button"
646
- className={strength === s ? "active" : ""}
647
- onClick={() => setStrength(s)}
648
- >
649
- {s}
650
- </button>
651
- ))}
 
652
  </div>
653
- </div>
654
 
655
- <label className="check">
656
- <input
657
- type="checkbox"
658
- checked={preserveLength}
659
- onChange={(e) => setPreserveLength(e.target.checked)}
660
- />
661
- Match length
662
- </label>
 
663
 
664
  <div className="toolbar-actions">
665
- <button
666
- type="button"
667
- className="btn btn-quiet"
668
- onClick={loadSample}
669
- disabled={loading}
670
- >
671
- Try sample
672
- </button>
673
  <button
674
  type="button"
675
  className="btn btn-quiet"
@@ -686,7 +691,7 @@ export default function App() {
686
  </button>
687
  <button
688
  type="button"
689
- className="btn btn-primary"
690
  onClick={() => void onRewrite()}
691
  disabled={loading}
692
  >
@@ -699,6 +704,16 @@ export default function App() {
699
  <div className="pane">
700
  <div className="pane-head">
701
  <h2>Original</h2>
 
 
 
 
 
 
 
 
 
 
702
  </div>
703
  <textarea
704
  value={input}
@@ -712,13 +727,13 @@ export default function App() {
712
  />
713
  </div>
714
 
715
- <div className={`pane${freshOut ? " writing" : ""}`}>
716
  <div className="pane-head">
717
  <h2>Rewrite</h2>
718
  <div className="pane-actions">
719
  <button
720
  type="button"
721
- className="ghost-btn"
722
  onClick={() => void onCopy()}
723
  disabled={!output.trim()}
724
  >
@@ -726,7 +741,7 @@ export default function App() {
726
  </button>
727
  <button
728
  type="button"
729
- className="ghost-btn"
730
  onClick={onDownload}
731
  disabled={!output.trim()}
732
  >
 
258
  return (
259
  <div className="landing">
260
  <section className="land-block">
261
+ <h2>How to use ZuZu Writer</h2>
262
  <p className="land-lead">Three steps from AI draft to natural wording.</p>
263
  <ol className="steps">
264
  <li>
 
350
 
351
  <footer className="site-footer">
352
  <p>Review every rewrite before you share or publish. Follow your school or workplace AI policy.</p>
353
+ <p className="footer-brand">ZuZu Writer</p>
354
  </footer>
355
  </div>
356
  );
 
512
  const url = URL.createObjectURL(blob);
513
  const a = document.createElement("a");
514
  a.href = url;
515
+ a.download = "zuzu-writer.txt";
516
  a.click();
517
  URL.revokeObjectURL(url);
518
  }
 
534
  <div className="app shell">
535
  <div className="topbar">
536
  <header className="brand">
537
+ <p className="brand-mark">ZuZu</p>
538
+ <h1>ZuZu Writer</h1>
539
+ <p className="brand-tag">
540
+ From AI-generated to plagiarism-safe — rewrite in a voice that feels real.
541
+ </p>
542
  </header>
543
  <div className="top-meta">
544
  {session && account ? (
545
+ <div className="account-panel">
546
+ <div className="account-panel-main">
547
+ <span className="account-plan">{account.plan.name}</span>
548
+ <span className="account-quota">
549
+ {account.usage.remaining_rewrites}/{account.plan.daily_rewrites} left today
550
+ </span>
551
+ <span className="account-email">{account.email}</span>
552
  </div>
553
+ <button type="button" className="btn btn-quiet btn-compact" onClick={() => void signOut()}>
 
554
  Sign out
555
  </button>
556
  </div>
557
  ) : authEnabled ? (
558
+ <div className="account-panel">
559
+ <div className="account-panel-main">
560
+ <span className="account-plan">Preview</span>
561
+ <span className="account-quota">
562
+ {account
563
+ ? `${account.usage.remaining_rewrites} of ${account.plan.daily_rewrites} free today`
564
+ : `Up to ${guestMaxWords} words`}
565
+ </span>
566
  </div>
567
  <div className="auth-inline">
568
  <button
569
  type="button"
570
+ className="btn btn-quiet btn-compact"
571
  onClick={() => openAuth("signin", "Sign in")}
572
  >
573
  Sign in
 
582
  </div>
583
  </div>
584
  ) : (
585
+ <div className="account-panel account-panel-quiet">
586
+ <span className="account-quota">
587
+ {tone} · {strength}
588
+ </span>
589
+ <span className="kbd-hint">⌘/Ctrl + Enter</span>
590
+ </div>
591
  )}
592
  </div>
593
  </div>
 
597
  <span>Signed out after {sessionIdleMinutes} minutes of inactivity.</span>
598
  <button
599
  type="button"
600
+ className="btn btn-quiet btn-compact"
601
  onClick={() => {
602
  clearIdleNotice();
603
  openAuth("signin", "Sign in again");
 
610
 
611
  {isGuest && !idleSignedOut ? (
612
  <div className="teaser-banner teaser-cta">
613
+ <div className="teaser-copy">
614
+ <span className="teaser-label">Free Preview</span>
615
+ <p>
616
+ Try up to <strong>{maxWords} words</strong>, {account?.plan.daily_rewrites ?? 1}{" "}
617
+ rewrite/day — then unlock Free for longer drafts.
618
+ </p>
619
  </div>
620
  <button
621
  type="button"
622
+ className="btn btn-primary"
623
  onClick={() => openAuth("signup", "Create free account")}
624
  >
625
  Unlock Free plan
 
629
 
630
  <div className="stage">
631
  <div className="toolbar">
632
+ <div className="toolbar-controls">
633
+ <div className="tool-group">
634
+ <span>Tone</span>
635
+ <div className="segment" role="group" aria-label="Tone">
636
+ {TONES.map((t) => (
637
+ <button
638
+ key={t}
639
+ type="button"
640
+ title={TONE_HINT[t]}
641
+ className={tone === t ? "active" : ""}
642
+ onClick={() => setTone(t)}
643
+ >
644
+ {t}
645
+ </button>
646
+ ))}
647
+ </div>
648
+ <span className="tool-hint">{TONE_HINT[tone]}</span>
649
  </div>
 
 
650
 
651
+ <div className="tool-group">
652
+ <span>Strength</span>
653
+ <div className="segment" role="group" aria-label="Strength">
654
+ {STRENGTHS.map((s) => (
655
+ <button
656
+ key={s}
657
+ type="button"
658
+ className={strength === s ? "active" : ""}
659
+ onClick={() => setStrength(s)}
660
+ >
661
+ {s}
662
+ </button>
663
+ ))}
664
+ </div>
665
  </div>
 
666
 
667
+ <label className="check">
668
+ <input
669
+ type="checkbox"
670
+ checked={preserveLength}
671
+ onChange={(e) => setPreserveLength(e.target.checked)}
672
+ />
673
+ Match length
674
+ </label>
675
+ </div>
676
 
677
  <div className="toolbar-actions">
 
 
 
 
 
 
 
 
678
  <button
679
  type="button"
680
  className="btn btn-quiet"
 
691
  </button>
692
  <button
693
  type="button"
694
+ className="btn btn-primary btn-rewrite"
695
  onClick={() => void onRewrite()}
696
  disabled={loading}
697
  >
 
704
  <div className="pane">
705
  <div className="pane-head">
706
  <h2>Original</h2>
707
+ <div className="pane-actions">
708
+ <button
709
+ type="button"
710
+ className="btn btn-quiet btn-tiny"
711
+ onClick={loadSample}
712
+ disabled={loading}
713
+ >
714
+ Try sample
715
+ </button>
716
+ </div>
717
  </div>
718
  <textarea
719
  value={input}
 
727
  />
728
  </div>
729
 
730
+ <div className={`pane pane-out${freshOut ? " writing" : ""}`}>
731
  <div className="pane-head">
732
  <h2>Rewrite</h2>
733
  <div className="pane-actions">
734
  <button
735
  type="button"
736
+ className="btn btn-quiet btn-tiny"
737
  onClick={() => void onCopy()}
738
  disabled={!output.trim()}
739
  >
 
741
  </button>
742
  <button
743
  type="button"
744
+ className="btn btn-quiet btn-tiny"
745
  onClick={onDownload}
746
  disabled={!output.trim()}
747
  >
frontend/src/index.css CHANGED
@@ -31,25 +31,51 @@ body {
31
  margin: 0;
32
  color: var(--ink);
33
  font-family: var(--font-body);
34
- background-color: #dfe8e4;
35
  background-image:
36
- radial-gradient(ellipse 90% 60% at 10% -20%, #c5ddd4 0%, transparent 55%),
37
- radial-gradient(ellipse 70% 50% at 100% 0%, #d4dde8 0%, transparent 50%),
38
- linear-gradient(165deg, #eef4f1 0%, #e3ebe7 45%, #d8e2dd 100%);
 
39
  background-attachment: fixed;
40
  }
41
 
 
42
  body::before {
43
  content: "";
44
  position: fixed;
45
  inset: 0;
46
  pointer-events: none;
47
- opacity: 0.35;
48
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.45'/%3E%3C/svg%3E");
49
  mix-blend-mode: soft-light;
50
  z-index: 0;
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  button,
54
  textarea {
55
  font: inherit;
@@ -67,6 +93,36 @@ textarea {
67
  animation: enter 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
68
  }
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @keyframes enter {
71
  from {
72
  opacity: 0;
@@ -104,44 +160,105 @@ textarea {
104
  align-items: flex-end;
105
  justify-content: space-between;
106
  gap: 1.5rem;
107
- margin-bottom: 1.75rem;
108
  }
109
 
110
  .brand {
111
- max-width: 36rem;
 
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  .brand h1 {
115
  margin: 0;
116
  font-family: var(--font-display);
117
- font-size: clamp(2.6rem, 5vw, 3.6rem);
118
  font-weight: 700;
119
  letter-spacing: -0.035em;
120
  line-height: 0.95;
121
  color: var(--ink);
122
  }
123
 
124
- .brand p {
125
- margin: 0.7rem 0 0;
126
  color: var(--ink-soft);
127
- font-size: 1.05rem;
128
  line-height: 1.45;
129
  max-width: 28rem;
130
  }
131
 
132
  .top-meta {
133
- text-align: right;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  color: var(--muted);
135
- font-size: 0.82rem;
136
- letter-spacing: 0.02em;
137
- padding-bottom: 0.35rem;
138
- min-width: 10rem;
139
  }
140
 
141
  .stage {
142
  border: 1px solid var(--panel-edge);
143
  border-radius: 22px;
144
- background: rgba(251, 252, 251, 0.78);
145
  box-shadow: var(--shadow-soft);
146
  backdrop-filter: blur(14px);
147
  overflow: hidden;
@@ -151,11 +268,20 @@ textarea {
151
  .toolbar {
152
  display: flex;
153
  flex-wrap: wrap;
154
- align-items: center;
 
155
  gap: 1rem 1.25rem;
156
- padding: 0.95rem 1.15rem;
157
  border-bottom: 1px solid var(--panel-edge);
158
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(244, 248, 246, 0.65));
 
 
 
 
 
 
 
 
159
  }
160
 
161
  .tool-group {
@@ -164,7 +290,7 @@ textarea {
164
  gap: 0.35rem;
165
  }
166
 
167
- .tool-group > span {
168
  font-size: 0.68rem;
169
  font-weight: 600;
170
  letter-spacing: 0.1em;
@@ -184,7 +310,7 @@ textarea {
184
  border: 0;
185
  background: transparent;
186
  color: var(--ink-soft);
187
- padding: 0.42rem 0.72rem;
188
  border-radius: 8px;
189
  font-size: 0.86rem;
190
  font-weight: 500;
@@ -209,11 +335,11 @@ textarea {
209
  display: inline-flex;
210
  align-items: center;
211
  gap: 0.5rem;
212
- margin-left: auto;
213
  color: var(--ink-soft);
214
  font-size: 0.88rem;
215
  cursor: pointer;
216
  user-select: none;
 
217
  }
218
 
219
  .check input {
@@ -225,24 +351,27 @@ textarea {
225
  .toolbar-actions {
226
  display: flex;
227
  align-items: center;
228
- gap: 0.5rem;
 
229
  }
230
 
231
  .btn {
232
  border: 0;
233
- border-radius: 10px;
234
- padding: 0.7rem 1.15rem;
235
  font-weight: 600;
236
  font-size: 0.92rem;
237
  cursor: pointer;
238
  transition:
239
  transform 0.15s ease,
240
  background 0.2s ease,
241
- opacity 0.2s ease;
 
 
242
  }
243
 
244
  .btn:disabled {
245
- opacity: 0.5;
246
  cursor: not-allowed;
247
  transform: none;
248
  }
@@ -250,6 +379,7 @@ textarea {
250
  .btn-primary {
251
  background: var(--accent);
252
  color: #f5faf8;
 
253
  }
254
 
255
  .btn-primary:not(:disabled):hover {
@@ -261,14 +391,47 @@ textarea {
261
  transform: translateY(0);
262
  }
263
 
 
 
 
 
 
 
264
  .btn-quiet {
265
- background: transparent;
266
  color: var(--ink-soft);
267
  border: 1px solid var(--panel-edge);
268
  }
269
 
270
  .btn-quiet:not(:disabled):hover {
271
- background: rgba(255, 255, 255, 0.7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  }
273
 
274
  .editors {
@@ -281,12 +444,17 @@ textarea {
281
  display: flex;
282
  flex-direction: column;
283
  min-height: 0;
 
284
  }
285
 
286
  .pane + .pane {
287
  border-left: 1px solid var(--panel-edge);
288
  }
289
 
 
 
 
 
290
  .pane-head {
291
  display: flex;
292
  align-items: center;
@@ -306,7 +474,7 @@ textarea {
306
 
307
  .pane-actions {
308
  display: flex;
309
- gap: 0.35rem;
310
  }
311
 
312
  .ghost-btn {
@@ -361,15 +529,16 @@ textarea {
361
  align-items: center;
362
  justify-content: space-between;
363
  gap: 1rem;
364
- padding: 0.75rem 1.15rem;
365
  border-top: 1px solid var(--panel-edge);
366
- background: rgba(244, 248, 246, 0.55);
367
  color: var(--muted);
368
  font-size: 0.84rem;
369
  }
370
 
371
  .statusbar .error {
372
  color: var(--warn);
 
373
  }
374
 
375
  .statusbar .loading {
@@ -388,30 +557,6 @@ textarea {
388
  font-size: 0.84rem;
389
  }
390
 
391
- .account-chip {
392
- display: flex;
393
- flex-direction: column;
394
- align-items: flex-end;
395
- gap: 0.2rem;
396
- text-align: right;
397
- }
398
-
399
- .account-plan {
400
- font-weight: 700;
401
- color: var(--accent);
402
- letter-spacing: 0.04em;
403
- text-transform: uppercase;
404
- font-size: 0.72rem;
405
- }
406
-
407
- .account-email {
408
- max-width: 14rem;
409
- overflow: hidden;
410
- text-overflow: ellipsis;
411
- white-space: nowrap;
412
- font-size: 0.78rem;
413
- }
414
-
415
  .auth-shell {
416
  position: relative;
417
  z-index: 1;
@@ -527,12 +672,13 @@ textarea {
527
  }
528
 
529
  .teaser-banner {
530
- margin: -0.5rem 0 1.1rem;
531
- padding: 0.75rem 1rem;
532
- border-radius: 12px;
533
- background: var(--accent-soft);
 
534
  color: var(--ink-soft);
535
- font-size: 0.92rem;
536
  line-height: 1.4;
537
  }
538
 
@@ -540,23 +686,40 @@ textarea {
540
  color: var(--accent);
541
  }
542
 
543
- .idle-banner {
544
  display: flex;
545
  flex-wrap: wrap;
546
  align-items: center;
547
- gap: 0.35rem 0.5rem;
 
548
  }
549
 
550
- .auth-inline {
551
  display: flex;
552
- align-items: center;
553
- gap: 0.35rem;
554
- margin-top: 0.25rem;
 
555
  }
556
 
557
- .btn-compact {
558
- padding: 0.4rem 0.75rem;
559
- font-size: 0.8rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  }
561
 
562
  .counts .over-limit {
@@ -564,6 +727,14 @@ textarea {
564
  font-weight: 600;
565
  }
566
 
 
 
 
 
 
 
 
 
567
  .modal-backdrop {
568
  position: fixed;
569
  inset: 0;
@@ -675,22 +846,6 @@ textarea {
675
  font-size: 0.78rem;
676
  }
677
 
678
- .teaser-cta {
679
- display: flex;
680
- flex-wrap: wrap;
681
- align-items: center;
682
- justify-content: space-between;
683
- gap: 0.75rem 1rem;
684
- }
685
-
686
- .tool-hint {
687
- font-size: 0.72rem;
688
- color: var(--muted);
689
- letter-spacing: 0.02em;
690
- text-transform: none;
691
- font-weight: 500;
692
- }
693
-
694
  .word-meter {
695
  height: 3px;
696
  background: rgba(18, 28, 26, 0.06);
@@ -878,34 +1033,44 @@ textarea {
878
  @media (max-width: 920px) {
879
  .topbar {
880
  flex-direction: column;
881
- align-items: flex-start;
882
  }
883
 
884
  .top-meta {
885
- text-align: left;
886
  }
887
 
888
- .account-chip {
889
- align-items: flex-start;
890
- text-align: left;
891
  }
892
 
893
- .check {
894
- margin-left: 0;
895
  }
896
 
897
  .toolbar {
898
  align-items: stretch;
 
 
 
 
 
899
  }
900
 
901
  .toolbar-actions {
902
  width: 100%;
 
903
  }
904
 
905
  .toolbar-actions .btn {
906
  flex: 1;
907
  }
908
 
 
 
 
 
909
  .editors {
910
  grid-template-columns: 1fr;
911
  }
@@ -926,6 +1091,10 @@ textarea {
926
 
927
  .teaser-cta {
928
  flex-direction: column;
929
- align-items: flex-start;
 
 
 
 
930
  }
931
  }
 
31
  margin: 0;
32
  color: var(--ink);
33
  font-family: var(--font-body);
34
+ background-color: #d4e0da;
35
  background-image:
36
+ radial-gradient(ellipse 100% 70% at 0% -10%, rgba(120, 170, 150, 0.55) 0%, transparent 58%),
37
+ radial-gradient(ellipse 80% 55% at 100% 5%, rgba(160, 175, 200, 0.4) 0%, transparent 52%),
38
+ radial-gradient(ellipse 70% 45% at 50% 110%, rgba(90, 130, 110, 0.22) 0%, transparent 55%),
39
+ linear-gradient(165deg, #eef5f1 0%, #e0ebe5 42%, #d2dfd8 100%);
40
  background-attachment: fixed;
41
  }
42
 
43
+ /* Paper grain */
44
  body::before {
45
  content: "";
46
  position: fixed;
47
  inset: 0;
48
  pointer-events: none;
49
+ opacity: 0.42;
50
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.45'/%3E%3C/svg%3E");
51
  mix-blend-mode: soft-light;
52
  z-index: 0;
53
  }
54
 
55
+ /* Soft writing-desk wash + faint ruled lines */
56
+ body::after {
57
+ content: "";
58
+ position: fixed;
59
+ inset: 0;
60
+ pointer-events: none;
61
+ z-index: 0;
62
+ background-image:
63
+ radial-gradient(ellipse 55% 40% at 85% 75%, rgba(26, 92, 74, 0.12) 0%, transparent 70%),
64
+ radial-gradient(ellipse 40% 35% at 12% 60%, rgba(70, 100, 140, 0.1) 0%, transparent 65%),
65
+ linear-gradient(
66
+ to bottom,
67
+ transparent 0,
68
+ transparent 11px,
69
+ rgba(18, 28, 26, 0.035) 11px,
70
+ rgba(18, 28, 26, 0.035) 12px
71
+ );
72
+ background-size:
73
+ auto,
74
+ auto,
75
+ 100% 28px;
76
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.25) 40%, rgba(0, 0, 0, 0.45));
77
+ }
78
+
79
  button,
80
  textarea {
81
  font: inherit;
 
93
  animation: enter 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
94
  }
95
 
96
+ /* Large brand watermark in the atmosphere */
97
+ .shell::before {
98
+ content: "ZuZu";
99
+ position: fixed;
100
+ right: -4vw;
101
+ top: 8vh;
102
+ z-index: 0;
103
+ font-family: var(--font-display);
104
+ font-size: clamp(7rem, 22vw, 16rem);
105
+ font-weight: 700;
106
+ letter-spacing: -0.06em;
107
+ line-height: 0.85;
108
+ color: rgba(26, 92, 74, 0.07);
109
+ pointer-events: none;
110
+ user-select: none;
111
+ transform: rotate(-8deg);
112
+ animation: watermark-in 1.2s cubic-bezier(0.22, 1, 0.36, 1) both;
113
+ }
114
+
115
+ @keyframes watermark-in {
116
+ from {
117
+ opacity: 0;
118
+ transform: rotate(-8deg) translateY(24px);
119
+ }
120
+ to {
121
+ opacity: 1;
122
+ transform: rotate(-8deg) translateY(0);
123
+ }
124
+ }
125
+
126
  @keyframes enter {
127
  from {
128
  opacity: 0;
 
160
  align-items: flex-end;
161
  justify-content: space-between;
162
  gap: 1.5rem;
163
+ margin-bottom: 1.35rem;
164
  }
165
 
166
  .brand {
167
+ max-width: 38rem;
168
+ }
169
+
170
+ .brand-mark {
171
+ margin: 0 0 0.35rem;
172
+ font-family: var(--font-body);
173
+ font-size: 0.72rem;
174
+ font-weight: 700;
175
+ letter-spacing: 0.18em;
176
+ text-transform: uppercase;
177
+ color: var(--accent);
178
  }
179
 
180
  .brand h1 {
181
  margin: 0;
182
  font-family: var(--font-display);
183
+ font-size: clamp(2.5rem, 5vw, 3.45rem);
184
  font-weight: 700;
185
  letter-spacing: -0.035em;
186
  line-height: 0.95;
187
  color: var(--ink);
188
  }
189
 
190
+ .brand-tag {
191
+ margin: 0.75rem 0 0;
192
  color: var(--ink-soft);
193
+ font-size: 1.02rem;
194
  line-height: 1.45;
195
  max-width: 28rem;
196
  }
197
 
198
  .top-meta {
199
+ flex-shrink: 0;
200
+ padding-bottom: 0.15rem;
201
+ }
202
+
203
+ .account-panel {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.85rem;
207
+ padding: 0.7rem 0.8rem 0.7rem 1rem;
208
+ border: 1px solid var(--panel-edge);
209
+ border-radius: 16px;
210
+ background: rgba(251, 252, 251, 0.88);
211
+ box-shadow: 0 10px 28px rgba(18, 28, 26, 0.05);
212
+ backdrop-filter: blur(10px);
213
+ }
214
+
215
+ .account-panel-quiet {
216
+ flex-direction: column;
217
+ align-items: flex-end;
218
+ gap: 0.2rem;
219
+ padding: 0.65rem 0.9rem;
220
+ }
221
+
222
+ .account-panel-main {
223
+ display: flex;
224
+ flex-direction: column;
225
+ align-items: flex-start;
226
+ gap: 0.12rem;
227
+ min-width: 0;
228
+ }
229
+
230
+ .account-plan {
231
+ font-weight: 700;
232
+ color: var(--accent);
233
+ letter-spacing: 0.08em;
234
+ text-transform: uppercase;
235
+ font-size: 0.68rem;
236
+ }
237
+
238
+ .account-quota {
239
+ color: var(--ink-soft);
240
+ font-size: 0.84rem;
241
+ font-weight: 500;
242
+ }
243
+
244
+ .account-email {
245
+ max-width: 12rem;
246
+ overflow: hidden;
247
+ text-overflow: ellipsis;
248
+ white-space: nowrap;
249
+ font-size: 0.75rem;
250
+ color: var(--muted);
251
+ }
252
+
253
+ .kbd-hint {
254
+ font-size: 0.75rem;
255
  color: var(--muted);
 
 
 
 
256
  }
257
 
258
  .stage {
259
  border: 1px solid var(--panel-edge);
260
  border-radius: 22px;
261
+ background: rgba(251, 252, 251, 0.82);
262
  box-shadow: var(--shadow-soft);
263
  backdrop-filter: blur(14px);
264
  overflow: hidden;
 
268
  .toolbar {
269
  display: flex;
270
  flex-wrap: wrap;
271
+ align-items: flex-end;
272
+ justify-content: space-between;
273
  gap: 1rem 1.25rem;
274
+ padding: 1rem 1.2rem;
275
  border-bottom: 1px solid var(--panel-edge);
276
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(244, 248, 246, 0.7));
277
+ }
278
+
279
+ .toolbar-controls {
280
+ display: flex;
281
+ flex-wrap: wrap;
282
+ align-items: flex-end;
283
+ gap: 1rem 1.35rem;
284
+ min-width: 0;
285
  }
286
 
287
  .tool-group {
 
290
  gap: 0.35rem;
291
  }
292
 
293
+ .tool-group > span:first-child {
294
  font-size: 0.68rem;
295
  font-weight: 600;
296
  letter-spacing: 0.1em;
 
310
  border: 0;
311
  background: transparent;
312
  color: var(--ink-soft);
313
+ padding: 0.45rem 0.75rem;
314
  border-radius: 8px;
315
  font-size: 0.86rem;
316
  font-weight: 500;
 
335
  display: inline-flex;
336
  align-items: center;
337
  gap: 0.5rem;
 
338
  color: var(--ink-soft);
339
  font-size: 0.88rem;
340
  cursor: pointer;
341
  user-select: none;
342
+ padding-bottom: 0.35rem;
343
  }
344
 
345
  .check input {
 
351
  .toolbar-actions {
352
  display: flex;
353
  align-items: center;
354
+ gap: 0.55rem;
355
+ margin-left: auto;
356
  }
357
 
358
  .btn {
359
  border: 0;
360
+ border-radius: 11px;
361
+ padding: 0.72rem 1.2rem;
362
  font-weight: 600;
363
  font-size: 0.92rem;
364
  cursor: pointer;
365
  transition:
366
  transform 0.15s ease,
367
  background 0.2s ease,
368
+ opacity 0.2s ease,
369
+ border-color 0.2s ease,
370
+ box-shadow 0.2s ease;
371
  }
372
 
373
  .btn:disabled {
374
+ opacity: 0.45;
375
  cursor: not-allowed;
376
  transform: none;
377
  }
 
379
  .btn-primary {
380
  background: var(--accent);
381
  color: #f5faf8;
382
+ box-shadow: 0 8px 18px rgba(26, 92, 74, 0.22);
383
  }
384
 
385
  .btn-primary:not(:disabled):hover {
 
391
  transform: translateY(0);
392
  }
393
 
394
+ .btn-rewrite {
395
+ min-width: 8.5rem;
396
+ padding: 0.82rem 1.45rem;
397
+ font-size: 0.98rem;
398
+ }
399
+
400
  .btn-quiet {
401
+ background: rgba(255, 255, 255, 0.75);
402
  color: var(--ink-soft);
403
  border: 1px solid var(--panel-edge);
404
  }
405
 
406
  .btn-quiet:not(:disabled):hover {
407
+ background: #fff;
408
+ color: var(--ink);
409
+ border-color: rgba(26, 92, 74, 0.28);
410
+ }
411
+
412
+ .btn-compact {
413
+ padding: 0.52rem 1rem;
414
+ font-size: 0.84rem;
415
+ min-height: 2.4rem;
416
+ }
417
+
418
+ .btn-tiny {
419
+ padding: 0.32rem 0.7rem;
420
+ font-size: 0.76rem;
421
+ border-radius: 8px;
422
+ font-weight: 600;
423
+ }
424
+
425
+ .auth-inline {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 0.45rem;
429
+ }
430
+
431
+ .auth-inline .btn-quiet {
432
+ background: rgba(255, 255, 255, 0.95);
433
+ color: var(--ink);
434
+ box-shadow: 0 1px 2px rgba(18, 28, 26, 0.04);
435
  }
436
 
437
  .editors {
 
444
  display: flex;
445
  flex-direction: column;
446
  min-height: 0;
447
+ background: transparent;
448
  }
449
 
450
  .pane + .pane {
451
  border-left: 1px solid var(--panel-edge);
452
  }
453
 
454
+ .pane-out {
455
+ background: rgba(244, 248, 246, 0.35);
456
+ }
457
+
458
  .pane-head {
459
  display: flex;
460
  align-items: center;
 
474
 
475
  .pane-actions {
476
  display: flex;
477
+ gap: 0.4rem;
478
  }
479
 
480
  .ghost-btn {
 
529
  align-items: center;
530
  justify-content: space-between;
531
  gap: 1rem;
532
+ padding: 0.8rem 1.2rem;
533
  border-top: 1px solid var(--panel-edge);
534
+ background: rgba(244, 248, 246, 0.6);
535
  color: var(--muted);
536
  font-size: 0.84rem;
537
  }
538
 
539
  .statusbar .error {
540
  color: var(--warn);
541
+ font-weight: 500;
542
  }
543
 
544
  .statusbar .loading {
 
557
  font-size: 0.84rem;
558
  }
559
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  .auth-shell {
561
  position: relative;
562
  z-index: 1;
 
672
  }
673
 
674
  .teaser-banner {
675
+ margin: 0 0 1.15rem;
676
+ padding: 0.95rem 1.15rem;
677
+ border-radius: 16px;
678
+ border: 1px solid rgba(26, 92, 74, 0.18);
679
+ background: linear-gradient(135deg, rgba(26, 92, 74, 0.1), rgba(251, 252, 251, 0.75));
680
  color: var(--ink-soft);
681
+ font-size: 0.94rem;
682
  line-height: 1.4;
683
  }
684
 
 
686
  color: var(--accent);
687
  }
688
 
689
+ .teaser-cta {
690
  display: flex;
691
  flex-wrap: wrap;
692
  align-items: center;
693
+ justify-content: space-between;
694
+ gap: 0.85rem 1.25rem;
695
  }
696
 
697
+ .teaser-copy {
698
  display: flex;
699
+ flex-direction: column;
700
+ gap: 0.25rem;
701
+ min-width: 0;
702
+ flex: 1;
703
  }
704
 
705
+ .teaser-copy p {
706
+ margin: 0;
707
+ }
708
+
709
+ .teaser-label {
710
+ font-size: 0.68rem;
711
+ font-weight: 700;
712
+ letter-spacing: 0.12em;
713
+ text-transform: uppercase;
714
+ color: var(--accent);
715
+ }
716
+
717
+ .idle-banner {
718
+ display: flex;
719
+ flex-wrap: wrap;
720
+ align-items: center;
721
+ justify-content: space-between;
722
+ gap: 0.65rem 1rem;
723
  }
724
 
725
  .counts .over-limit {
 
727
  font-weight: 600;
728
  }
729
 
730
+ .tool-hint {
731
+ font-size: 0.72rem;
732
+ color: var(--muted);
733
+ letter-spacing: 0.02em;
734
+ text-transform: none;
735
+ font-weight: 500;
736
+ }
737
+
738
  .modal-backdrop {
739
  position: fixed;
740
  inset: 0;
 
846
  font-size: 0.78rem;
847
  }
848
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
  .word-meter {
850
  height: 3px;
851
  background: rgba(18, 28, 26, 0.06);
 
1033
  @media (max-width: 920px) {
1034
  .topbar {
1035
  flex-direction: column;
1036
+ align-items: stretch;
1037
  }
1038
 
1039
  .top-meta {
1040
+ width: 100%;
1041
  }
1042
 
1043
+ .account-panel {
1044
+ width: 100%;
1045
+ justify-content: space-between;
1046
  }
1047
 
1048
+ .account-panel-quiet {
1049
+ align-items: flex-start;
1050
  }
1051
 
1052
  .toolbar {
1053
  align-items: stretch;
1054
+ flex-direction: column;
1055
+ }
1056
+
1057
+ .toolbar-controls {
1058
+ width: 100%;
1059
  }
1060
 
1061
  .toolbar-actions {
1062
  width: 100%;
1063
+ margin-left: 0;
1064
  }
1065
 
1066
  .toolbar-actions .btn {
1067
  flex: 1;
1068
  }
1069
 
1070
+ .btn-rewrite {
1071
+ flex: 1.4;
1072
+ }
1073
+
1074
  .editors {
1075
  grid-template-columns: 1fr;
1076
  }
 
1091
 
1092
  .teaser-cta {
1093
  flex-direction: column;
1094
+ align-items: stretch;
1095
+ }
1096
+
1097
+ .teaser-cta .btn {
1098
+ width: 100%;
1099
  }
1100
  }