Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

sleep-mode.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

sleep-mode.tsxBlame771 lines · 1 contributor
46d6165Claude1/**
2 * Block L1 — Sleep Mode marketing page.
3 *
4 * Public, no auth. Pitch: "Toggle Sleep Mode. Walk away. Wake up to a
54eab24Claude5 * digest of what Claude shipped overnight." Status card surfaces the
6 * viewer's own toggle (Active / Inactive) when signed in, a config card
7 * mirrors the upcoming threshold form fields, an excluded-repos section
8 * card stubs the per-repo opt-out list, and a sample digest renders from
9 * a synthetic `SleepModeReport`.
10 *
11 * The form/card UIs are read-only previews — the real mutation lives in
12 * `/settings`. We render them here so the marketing page mirrors the
13 * shape an operator will see once they enable the feature. CSS scoped
14 * under `.sleep-` so it can't bleed into other surfaces.
46d6165Claude15 */
16
17import { Hono } from "hono";
18import { raw } from "hono/html";
19import { Layout } from "../views/layout";
20import { softAuth } from "../middleware/auth";
21import type { AuthEnv } from "../middleware/auth";
22import {
23 renderSleepModeDigest,
24 type SleepModeReport,
25} from "../lib/sleep-mode";
26
27const sleepMode = new Hono<AuthEnv>();
28sleepMode.use("*", softAuth);
29
30/** A synthetic, on-brand report used to render the sample digest screenshot. */
31const SAMPLE_REPORT: SleepModeReport = {
32 windowHours: 24,
33 prsAutoMerged: [
34 { number: 412, title: "Bump axios to 1.7.4", repo: "api-gateway" },
35 { number: 88, title: "Fix flaky retry test", repo: "billing" },
36 { number: 134, title: "Cache stage results", repo: "workflow-runner" },
37 ],
38 issuesBuiltByAi: [
39 {
40 number: 207,
41 title: "Add /metrics endpoint with Prometheus format",
42 repo: "api-gateway",
43 prNumber: 413,
44 },
45 {
46 number: 56,
47 title: "Dark-mode toggle in admin nav",
48 repo: "dashboard",
49 prNumber: 89,
50 },
51 ],
52 aiReviewsPosted: 14,
53 securityIssuesAutoFixed: 2,
54 gateFailuresAutoRepaired: 5,
55 hoursSaved: 7.4,
56};
57
58sleepMode.get("/sleep-mode", (c) => {
59 const user = c.get("user");
60 const sample = renderSleepModeDigest(SAMPLE_REPORT, {
61 username: user?.username || "you",
62 });
54eab24Claude63
64 // Viewer is treated as "Inactive" by default — the real status lives on
65 // the per-user row in /settings. The card is a marketing preview only;
66 // signed-in visitors see a personalised "Inactive — turn it on" CTA.
67 const isActive = false;
68
46d6165Claude69 return c.html(
70 <Layout title="Sleep Mode — gluecron" user={user}>
71 <style dangerouslySetInnerHTML={{ __html: pageCss }} />
54eab24Claude72 <div class="sleep-wrap">
73 <section class="sleep-hero">
74 <div class="sleep-hero-orb" aria-hidden="true" />
75 <div class="sleep-hero-inner">
76 <div class="sleep-eyebrow">
77 <span class="sleep-eyebrow-pill" aria-hidden="true">
78 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
79 <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
80 </svg>
81 </span>
82 Sleep Mode · overnight autopilot
83 </div>
84 <h1 class="sleep-title">
85 Toggle Sleep Mode. Walk away.{" "}
86 <span class="sleep-title-grad">Wake up to a digest.</span>
87 </h1>
88 <p class="sleep-sub">
89 Claude keeps your repos shipping while you sleep — auto-merging
90 green PRs, building features from <code>ai:build</code> issues,
91 reviewing code, and quietly fixing the gates that fail. One
92 email lands at the UTC hour you pick.
93 </p>
94 <div class="sleep-hero-cta">
95 <a href="/settings" class="sleep-btn sleep-btn-primary">
96 Enable Sleep Mode in Settings <span aria-hidden="true">→</span>
97 </a>
98 <a
99 href="/settings/sleep-mode/preview"
100 class="sleep-btn"
101 >
102 Preview your digest
103 </a>
104 </div>
46d6165Claude105 </div>
54eab24Claude106 </section>
46d6165Claude107
54eab24Claude108 {/* ─── Status card — Active / Inactive ─── */}
109 <section
110 class={"sleep-status-card " + (isActive ? "is-active" : "is-inactive")}
111 aria-labelledby="sleep-status-title"
112 >
113 <div class="sleep-status-left">
114 <span class="sleep-status-dot" aria-hidden="true" />
115 <div>
116 <p class="sleep-status-title" id="sleep-status-title">
117 Sleep Mode is{" "}
118 <strong>{isActive ? "Active" : "Inactive"}</strong>
119 {user ? <> for {user.username}</> : null}
46d6165Claude120 </p>
54eab24Claude121 <p class="sleep-status-sub">
122 {isActive
123 ? "The autopilot will email your digest at the next configured hour."
124 : user
125 ? "Turn it on in Settings and pick a UTC hour. Defaults are safe."
126 : "Sign in to flip the toggle for your account."}
46d6165Claude127 </p>
128 </div>
54eab24Claude129 </div>
130 <div class="sleep-status-actions">
131 {user ? (
132 <a href="/settings#sleep-mode" class="sleep-btn sleep-btn-primary">
133 {isActive ? "Manage" : "Turn on"}
134 </a>
135 ) : (
136 <a href="/login?next=/settings" class="sleep-btn sleep-btn-primary">
137 Sign in
138 </a>
139 )}
140 </div>
141 </section>
142
143 {/* ─── How it works — three steps ─── */}
144 <section class="sleep-section" aria-labelledby="sleep-steps-h">
145 <header class="sleep-section-head">
146 <div>
147 <p class="sleep-section-eyebrow">How it works</p>
148 <h2 class="sleep-section-title" id="sleep-steps-h">
149 Three steps. Then forget about it.
150 </h2>
151 </div>
152 </header>
153 <div class="sleep-section-body">
154 <div class="sleep-steps">
155 <article class="sleep-step">
156 <div class="sleep-step-num">1</div>
157 <h3 class="sleep-step-title">Flip the toggle</h3>
158 <p class="sleep-step-body">
159 A single checkbox in <a href="/settings">/settings</a>.
160 Pick the UTC hour you want the digest to land — default
161 is 9 AM.
162 </p>
163 </article>
164 <article class="sleep-step">
165 <div class="sleep-step-num">2</div>
166 <h3 class="sleep-step-title">Claude works the night shift</h3>
167 <p class="sleep-step-body">
168 The autopilot sweeps every 5 minutes. Green PRs get
169 auto-merged. <code>ai:build</code> issues become PRs.
170 Gate failures get auto-repaired. Security findings get
171 patched.
172 </p>
173 </article>
174 <article class="sleep-step">
175 <div class="sleep-step-num">3</div>
176 <h3 class="sleep-step-title">Wake up to a digest</h3>
177 <p class="sleep-step-body">
178 One email. Subject line tells you everything: "while you
179 slept, Claude shipped <em>N</em> things". Headlines,
180 links, and an estimate of hours saved.
181 </p>
182 </article>
183 </div>
184 </div>
185 </section>
186
187 {/* ─── Threshold form (preview) ─── */}
188 <section class="sleep-section" aria-labelledby="sleep-thresholds-h">
189 <header class="sleep-section-head">
190 <div>
191 <p class="sleep-section-eyebrow">Thresholds</p>
192 <h2 class="sleep-section-title" id="sleep-thresholds-h">
193 Conservative defaults you can tighten.
194 </h2>
195 <p class="sleep-section-sub">
196 Preview of the form rendered in <code>/settings</code>. Mutations
197 live there — this card is read-only.
46d6165Claude198 </p>
199 </div>
54eab24Claude200 </header>
201 <div class="sleep-section-body">
202 <div class="sleep-form" aria-disabled="true">
203 <div class="sleep-field">
204 <label class="sleep-field-label" for="sleep-hour">
205 Digest delivery hour <span class="sleep-field-hint">UTC, 0–23</span>
206 </label>
207 <input
208 id="sleep-hour"
209 class="sleep-input"
210 type="number"
211 min={0}
212 max={23}
213 value={9}
214 disabled
215 />
216 </div>
217 <div class="sleep-field">
218 <label class="sleep-field-label" for="sleep-max-merges">
219 Max auto-merges per night <span class="sleep-field-hint">soft cap</span>
220 </label>
221 <input
222 id="sleep-max-merges"
223 class="sleep-input"
224 type="number"
225 min={1}
226 max={500}
227 value={25}
228 disabled
229 />
230 </div>
231 <div class="sleep-field">
232 <label class="sleep-field-label" for="sleep-max-builds">
233 Max AI builds per night <span class="sleep-field-hint">issue → PR</span>
234 </label>
235 <input
236 id="sleep-max-builds"
237 class="sleep-input"
238 type="number"
239 min={0}
240 max={100}
241 value={10}
242 disabled
243 />
244 </div>
245 <div class="sleep-field sleep-field-wide">
246 <label class="sleep-field-label" for="sleep-min-checks">
247 Required green checks before auto-merge
248 </label>
249 <input
250 id="sleep-min-checks"
251 class="sleep-input"
252 type="text"
253 value="ci,gate,build"
254 disabled
255 />
256 <p class="sleep-field-help">
257 Comma-separated check names. PRs are only merged once
258 every listed check is green.
259 </p>
260 </div>
261 </div>
46d6165Claude262 </div>
54eab24Claude263 <footer class="sleep-section-foot">
264 <span class="sleep-foot-hint">
265 Edit on <a href="/settings#sleep-mode">/settings</a>.
266 </span>
267 </footer>
46d6165Claude268 </section>
269
54eab24Claude270 {/* ─── Excluded repos ─── */}
271 <section class="sleep-section" aria-labelledby="sleep-excluded-h">
272 <header class="sleep-section-head">
273 <div>
274 <p class="sleep-section-eyebrow">Excluded repos</p>
275 <h2 class="sleep-section-title" id="sleep-excluded-h">
276 Skip the riskiest projects.
277 </h2>
278 <p class="sleep-section-sub">
279 Sleep Mode will never touch a repo on this list. Add the
280 ones with manual-only release workflows.
281 </p>
282 </div>
283 </header>
284 <div class="sleep-section-body">
285 <div class="sleep-empty">
286 <div class="sleep-empty-orb" aria-hidden="true" />
287 <div class="sleep-empty-inner">
288 <p class="sleep-empty-title">No excluded repos yet.</p>
289 <p class="sleep-empty-sub">
290 When Sleep Mode is on, every repo you own is eligible.
291 Add one here to opt it out. Manage the list from{" "}
292 <a href="/settings#sleep-mode-excluded">Settings</a>.
293 </p>
294 </div>
295 </div>
46d6165Claude296 </div>
54eab24Claude297 </section>
298
299 {/* ─── Sample digest ─── */}
300 <section class="sleep-section" aria-labelledby="sleep-sample-h">
301 <header class="sleep-section-head">
302 <div>
303 <p class="sleep-section-eyebrow">Sample digest</p>
304 <h2 class="sleep-section-title" id="sleep-sample-h">
305 Here's what lands in your inbox.
306 </h2>
307 <p class="sleep-section-sub">
308 A real Sleep Mode digest, rendered from a synthetic report
309 so you can see the shape before you turn it on.
310 </p>
311 </div>
312 </header>
313 <div class="sleep-section-body">
314 <div class="sleep-sample">
315 <div class="sleep-sample-frame">
316 <div class="sleep-sample-meta">
317 <span class="sleep-sample-from">no-reply@gluecron.app</span>
318 <span class="sleep-sample-subject">{sample.subject}</span>
319 </div>
320 <div class="sleep-sample-body">{raw(sample.html)}</div>
46d6165Claude321 </div>
322 </div>
323 </div>
324 </section>
325
54eab24Claude326 <section class="sleep-cta">
327 <h2 class="sleep-cta-title">Ready to walk away?</h2>
328 <p class="sleep-cta-sub">
329 Sleep Mode is on-by-default safe — it can't merge anything
330 that wouldn't pass your branch protection rules. Turn it on,
331 sleep well.
46d6165Claude332 </p>
54eab24Claude333 <a href="/settings" class="sleep-btn sleep-btn-primary sleep-btn-lg">
334 Enable Sleep Mode <span aria-hidden="true">→</span>
46d6165Claude335 </a>
336 </section>
337 </div>
338 </Layout>
339 );
340});
341
342const pageCss = `
eed4684Claude343 .sleep-wrap { max-width: 1680px; margin: 0 auto; padding: var(--space-6) var(--space-4); }
54eab24Claude344
345 /* ─── Hero ─── */
346 .sleep-hero {
347 position: relative;
348 margin-bottom: var(--space-5);
349 padding: var(--space-6) var(--space-6);
350 background: var(--bg-elevated);
351 border: 1px solid var(--border);
352 border-radius: 18px;
353 overflow: hidden;
354 }
355 .sleep-hero::before {
356 content: '';
357 position: absolute;
358 top: 0; left: 0; right: 0;
359 height: 2px;
360 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
361 opacity: 0.7;
362 pointer-events: none;
363 }
364 .sleep-hero-orb {
365 position: absolute;
366 inset: -25% -10% auto auto;
367 width: 460px; height: 460px;
368 background: radial-gradient(circle, rgba(140,109,255,0.22), rgba(54,197,214,0.10) 45%, transparent 70%);
369 filter: blur(80px);
370 opacity: 0.7;
371 pointer-events: none;
372 z-index: 0;
373 }
374 .sleep-hero-inner { position: relative; z-index: 1; max-width: 720px; }
375 .sleep-eyebrow {
376 font-size: 12px;
377 color: var(--text-muted);
378 margin-bottom: var(--space-2);
379 letter-spacing: 0.02em;
380 display: inline-flex;
381 align-items: center;
382 gap: 8px;
383 }
384 .sleep-eyebrow-pill {
385 display: inline-flex;
386 align-items: center;
387 justify-content: center;
388 width: 18px; height: 18px;
389 border-radius: 6px;
390 background: rgba(140,109,255,0.14);
391 color: #b69dff;
392 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.35);
393 }
394 .sleep-title {
395 font-size: clamp(32px, 5vw, 52px);
396 font-family: var(--font-display);
397 font-weight: 800;
398 letter-spacing: -0.028em;
399 line-height: 1.05;
400 margin: 0 0 var(--space-3);
401 color: var(--text-strong);
402 }
403 .sleep-title-grad {
404 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
405 -webkit-background-clip: text;
406 background-clip: text;
407 -webkit-text-fill-color: transparent;
408 color: transparent;
409 }
410 .sleep-sub {
411 font-size: 16px;
412 color: var(--text-muted);
413 margin: 0 0 var(--space-4);
414 line-height: 1.55;
415 max-width: 640px;
416 }
417 .sleep-sub code {
418 font-family: var(--font-mono);
419 font-size: 13px;
420 background: var(--bg-tertiary);
421 padding: 1px 5px;
422 border-radius: 4px;
423 }
424 .sleep-hero-cta { display: flex; gap: 10px; flex-wrap: wrap; }
425
426 /* ─── Buttons ─── */
427 .sleep-btn {
428 appearance: none;
429 border: 1px solid var(--border-strong);
430 background: var(--bg-secondary);
431 color: var(--text);
432 padding: 10px 16px;
433 border-radius: 10px;
434 font-family: inherit;
435 font-size: 14px;
436 font-weight: 500;
437 cursor: pointer;
438 transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
439 text-decoration: none;
440 display: inline-flex; align-items: center; gap: 8px;
441 }
442 .sleep-btn:hover {
443 border-color: var(--border-focus);
444 background: rgba(255,255,255,0.03);
445 transform: translateY(-1px);
446 text-decoration: none;
447 }
448 .sleep-btn-primary {
449 border-color: rgba(140,109,255,0.45);
450 background: linear-gradient(135deg, rgba(140,109,255,0.20), rgba(54,197,214,0.14));
451 color: var(--text-strong);
452 }
453 .sleep-btn-primary:hover {
454 border-color: rgba(140,109,255,0.65);
455 background: linear-gradient(135deg, rgba(140,109,255,0.28), rgba(54,197,214,0.20));
456 }
457 .sleep-btn-lg { padding: 12px 20px; font-size: 15px; }
458
459 /* ─── Status card ─── */
460 .sleep-status-card {
461 margin-bottom: var(--space-5);
462 padding: var(--space-4) var(--space-5);
463 background: var(--bg-elevated);
464 border: 1px solid var(--border);
465 border-radius: 14px;
466 display: flex;
467 align-items: center;
468 justify-content: space-between;
469 gap: var(--space-4);
470 flex-wrap: wrap;
471 }
472 .sleep-status-left {
473 display: flex;
474 align-items: center;
475 gap: 14px;
476 flex: 1;
477 min-width: 240px;
478 }
479 .sleep-status-dot {
480 width: 12px; height: 12px;
481 border-radius: 50%;
482 flex-shrink: 0;
483 background: var(--text-faint);
484 }
485 .sleep-status-card.is-active .sleep-status-dot {
486 background: #3fb950;
487 box-shadow: 0 0 0 4px rgba(63,185,80,0.18);
488 }
489 .sleep-status-card.is-inactive .sleep-status-dot {
490 background: #6e7681;
491 box-shadow: 0 0 0 4px rgba(110,118,129,0.14);
492 }
493 .sleep-status-title {
494 margin: 0;
495 font-size: 14.5px;
496 color: var(--text-strong);
497 font-weight: 600;
498 }
499 .sleep-status-title strong {
500 color: var(--text-strong);
501 font-weight: 700;
502 }
503 .sleep-status-card.is-active .sleep-status-title strong { color: #6ee7b7; }
504 .sleep-status-card.is-inactive .sleep-status-title strong { color: var(--text-muted); }
505 .sleep-status-sub {
506 margin: 4px 0 0;
507 font-size: 12.5px;
508 color: var(--text-muted);
509 line-height: 1.5;
510 }
511 .sleep-status-actions { flex-shrink: 0; }
512
513 /* ─── Section cards ─── */
514 .sleep-section {
515 margin-bottom: var(--space-5);
516 background: var(--bg-elevated);
517 border: 1px solid var(--border);
518 border-radius: 14px;
519 overflow: hidden;
520 }
521 .sleep-section-head {
522 padding: var(--space-4) var(--space-5);
523 border-bottom: 1px solid var(--border);
524 }
525 .sleep-section-eyebrow {
526 font-size: 11px;
527 font-weight: 600;
528 letter-spacing: 0.08em;
529 text-transform: uppercase;
530 color: var(--text-faint);
531 margin: 0 0 6px;
532 }
533 .sleep-section-title {
534 margin: 0;
535 font-family: var(--font-display);
536 font-size: 17px;
537 font-weight: 700;
538 letter-spacing: -0.018em;
539 color: var(--text-strong);
540 }
541 .sleep-section-sub {
542 margin: 6px 0 0;
543 font-size: 12.5px;
544 color: var(--text-muted);
545 line-height: 1.5;
546 }
547 .sleep-section-sub code {
548 font-family: var(--font-mono);
549 font-size: 11.5px;
550 background: var(--bg-tertiary);
551 padding: 1px 5px;
552 border-radius: 4px;
553 }
554 .sleep-section-body { padding: var(--space-5); }
555 .sleep-section-foot {
556 padding: var(--space-3) var(--space-5);
557 border-top: 1px solid var(--border);
558 background: rgba(255,255,255,0.012);
559 font-size: 12.5px;
560 color: var(--text-muted);
561 }
562 .sleep-foot-hint a { color: var(--accent); text-decoration: none; }
563 .sleep-foot-hint a:hover { text-decoration: underline; }
564
565 /* ─── Steps ─── */
566 .sleep-steps {
567 display: grid;
568 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
569 gap: var(--space-3);
570 }
571 .sleep-step {
572 position: relative;
573 padding: var(--space-4);
574 background: var(--bg-secondary);
575 border: 1px solid var(--border-subtle);
576 border-radius: 12px;
577 display: flex;
578 flex-direction: column;
579 gap: 10px;
580 transition: border-color 150ms ease, transform 150ms ease;
581 }
582 .sleep-step:hover {
583 border-color: var(--border-strong);
584 transform: translateY(-1px);
585 }
586 .sleep-step-num {
587 display: inline-flex;
588 align-items: center;
589 justify-content: center;
590 width: 30px; height: 30px;
591 border-radius: 50%;
592 background: linear-gradient(135deg, rgba(140,109,255,0.22), rgba(54,197,214,0.16));
593 color: #c5b3ff;
594 border: 1px solid rgba(140,109,255,0.40);
595 font-family: var(--font-display);
596 font-weight: 700;
597 font-size: 14px;
598 }
599 .sleep-step-title {
600 margin: 0;
601 font-family: var(--font-display);
602 font-size: 15px;
603 font-weight: 700;
604 color: var(--text-strong);
605 letter-spacing: -0.012em;
606 }
607 .sleep-step-body {
608 margin: 0;
609 font-size: 13.5px;
610 color: var(--text-muted);
611 line-height: 1.55;
612 }
613 .sleep-step-body code {
614 font-family: var(--font-mono);
615 font-size: 12px;
616 background: var(--bg-tertiary);
617 padding: 1px 5px;
618 border-radius: 4px;
619 }
620 .sleep-step-body a { color: var(--accent); text-decoration: none; }
621 .sleep-step-body a:hover { text-decoration: underline; }
622
623 /* ─── Form (preview) ─── */
624 .sleep-form {
625 display: grid;
626 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
627 gap: var(--space-4);
628 }
629 .sleep-field { display: flex; flex-direction: column; gap: 6px; }
630 .sleep-field-wide { grid-column: 1 / -1; }
631 .sleep-field-label {
632 font-family: var(--font-mono);
633 font-size: 12.5px;
634 font-weight: 600;
635 color: var(--text-strong);
636 letter-spacing: -0.005em;
637 display: flex;
638 align-items: baseline;
639 justify-content: space-between;
640 gap: 8px;
641 }
642 .sleep-field-hint {
643 font-family: var(--font-sans, inherit);
644 font-size: 11px;
645 color: var(--text-faint);
646 text-transform: none;
647 font-weight: 500;
648 letter-spacing: 0.02em;
649 }
650 .sleep-input {
651 width: 100%;
652 padding: 9px 12px;
653 font-size: 13.5px;
654 color: var(--text);
655 background: var(--bg);
656 border: 1px solid var(--border-strong);
657 border-radius: 8px;
658 outline: none;
659 font-family: var(--font-mono);
660 box-sizing: border-box;
661 transition: border-color 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
662 }
663 .sleep-input:disabled {
664 opacity: 0.75;
665 cursor: not-allowed;
666 }
667 .sleep-field-help {
668 margin: 0;
669 font-size: 11.5px;
670 color: var(--text-muted);
671 line-height: 1.45;
672 }
673
674 /* ─── Empty state ─── */
675 .sleep-empty {
676 position: relative;
677 padding: var(--space-6) var(--space-5);
678 border: 1px dashed var(--border-strong);
679 border-radius: 14px;
680 background: rgba(255,255,255,0.02);
681 text-align: center;
682 overflow: hidden;
683 }
684 .sleep-empty-orb {
685 position: absolute;
686 inset: -40% -10% auto auto;
687 width: 320px; height: 320px;
688 background: radial-gradient(circle, rgba(140,109,255,0.20), rgba(54,197,214,0.10) 45%, transparent 70%);
689 filter: blur(70px);
690 opacity: 0.55;
691 pointer-events: none;
692 z-index: 0;
693 }
694 .sleep-empty-inner { position: relative; z-index: 1; }
695 .sleep-empty-title {
696 margin: 0 0 6px;
697 font-family: var(--font-display);
698 font-size: 16px;
699 font-weight: 700;
700 color: var(--text-strong);
701 letter-spacing: -0.012em;
702 }
703 .sleep-empty-sub {
704 margin: 0 auto;
705 max-width: 460px;
706 font-size: 13px;
707 color: var(--text-muted);
708 line-height: 1.5;
709 }
710 .sleep-empty-sub a { color: var(--accent); text-decoration: none; }
711 .sleep-empty-sub a:hover { text-decoration: underline; }
712
713 /* ─── Sample digest ─── */
714 .sleep-sample { display: flex; justify-content: center; }
715 .sleep-sample-frame {
716 background: #fff;
717 color: #111;
718 border-radius: 12px;
719 width: 100%;
720 max-width: 720px;
721 box-shadow: 0 16px 40px rgba(0,0,0,0.25);
722 overflow: hidden;
723 border: 1px solid var(--border);
724 }
725 .sleep-sample-meta {
726 background: #f6f7fb;
727 padding: 12px 20px;
728 display: flex;
729 flex-direction: column;
730 gap: 4px;
731 border-bottom: 1px solid #e6e7ed;
732 font-size: 13px;
733 color: #4a4d59;
734 }
735 .sleep-sample-from { font-size: 12px; color: #8a8e9c; }
736 .sleep-sample-subject { font-weight: 600; color: #0f1019; }
737 .sleep-sample-body { background: #fff; }
738
739 /* ─── CTA card ─── */
740 .sleep-cta {
741 margin-top: var(--space-6);
742 padding: var(--space-6) var(--space-5);
743 background: linear-gradient(135deg, rgba(140,109,255,0.14), rgba(54,197,214,0.08));
744 border: 1px solid rgba(140,109,255,0.30);
745 border-radius: 16px;
746 text-align: center;
747 }
748 .sleep-cta-title {
749 margin: 0 0 8px;
750 font-family: var(--font-display);
751 font-size: 24px;
752 font-weight: 800;
753 color: var(--text-strong);
754 letter-spacing: -0.022em;
755 }
756 .sleep-cta-sub {
757 max-width: 540px;
758 margin: 0 auto var(--space-4);
759 font-size: 14px;
760 color: var(--text-muted);
761 line-height: 1.55;
762 }
763
764 @media (max-width: 640px) {
765 .sleep-status-card { flex-direction: column; align-items: flex-start; }
766 .sleep-status-actions { width: 100%; }
767 .sleep-status-actions .sleep-btn { width: 100%; justify-content: center; }
768 }
46d6165Claude769`;
770
771export default sleepMode;